压在透明的玻璃上c-国产精品国产一级A片精品免费-国产精品视频网-成人黄网站18秘 免费看|www.tcsft.com

如何破解勒索軟件

在2013年初,某公司向筆者求助,因為他們有大量的重要文件被勒索軟件加密,導致無法訪問。這次攻擊者使用的,是一款披著“反兒童色情郵件”外衣的勒索軟件,它會遍歷所有磁盤,并加密其中的重要文件。由于該軟件發動攻擊時,會安裝備份驅動器,因此總的來說,公司的數據會面臨一定的損失。不過幸運的是,筆者能夠破解其口令,從而將被加密的數據恢復如初。下面,筆者詳細介紹破解的流程。

筆者之所以拖到現在才將其公之于眾,主要擔心會促使勒索軟件的開發者采用強度更高的口令保護方法。有大量證據表明,惡意軟件開發者經常會瀏覽安全公司的有關文章。例如,在筆者破解這個軟件幾個月后 ,就有另外一家公司公開宣稱能夠恢復被該勒索軟件所加密的文件。雖然這家公司沒有公布細節,但是看上去惡意軟件作者仍然認真采取了相應的對策。該勒索軟件的新版本不久就面世了,與此同時,原來的口令猜解技術也毫無懸念地失效了。不僅如此,其作者還在軟件注釋中明確介紹了弱口令生成缺陷的有關情況。雖然早在2013年的美國黑帽大會上,筆者就介紹過如何攻擊偽隨機數生成器,但是在如何破解勒索軟件方面,這還是頭一回公之于眾。

在公布勒索軟件本身漏洞方面,筆者希望研究人員酌情處理;最穩妥的方案是將其提交給受害者信賴的機構或公司,這樣,只要漏洞仍然存在于勒索軟件中,就能為受害者爭取更長的救助時間。
改進型的ACCDFISA
最初的勒索軟件樣本,都是來自同一個系列:ACCDFISA。由于該系列開發者的人為錯誤或者疏漏,該軟件系列早就被研究人員干倒了。不過,這次筆者所面臨的勒索軟件,比以前的有了很大改進。它自稱使用了AES-256加密算法,為每位受害者都單獨使用了一個長256個字符的隨機密鑰,并且將其發送給了攻擊者。此外,它還聲稱為了防止未加密的原始文件和口令被人恢復,已經將其徹底刪除了。實際上,這些聲明只是勒索軟件妄圖讓受害者放棄反抗,乖乖就范的攻心術罷了。但是,勒索軟件作者會不會真有如此神通呢?

為此,筆者來研究被加密一個的文件,這個文件已經被更名為:“(!! to decrypt email id … to …@… !!).exe”。它實際上是一個存放經過加密的RAR文件的WinRAR自解包程序。很明顯,如果想要尋找WinRAR加密實現的漏洞的話,那是指望不了,所以,筆者另辟蹊徑,直接從破解口令下手。為此,筆者需要了解創建該口令的那些代碼。

在被感染的驅動器上,筆者不僅發現了這個勒索軟件,同時還發現其他一些看起來也與此有關的文件。其中一個文件,就是Microsoft Sysinternals中的sdelete工具,該工具的作用是永久性的刪除文件,所以筆者不僅暢想:要是該軟件存在Bug的話,筆者就能速戰速決了。此外,筆者還找到了一個名為“NoSafeMode”的庫,以及一個建立自解包程序的RAR工具。這些文件的存在不禁使人浮想聯翩:攻擊者已經制造出了一個竊取代碼的弗蘭肯斯坦怪物,然后與勒索軟件的主要可執行代碼簡單縫合在一起,就像PureBasic 所寫的那樣。這里的RAR工具就是筆者破解該勒索軟件的突破口。由于這個工具需要提供密鑰口令來作為命令行參數,因此筆者推斷,如果從勒索軟件中啟動這個壓縮工具的代碼處開始回溯,就能夠找到構造該口令的代碼了。
尋找口令生成器
首先,在筆者一次性的機器上運行該勒索軟件,然后,連接調試工具來攔截CreateProcess調用,這個調用的作用是啟動加密文件的那個RAR工具。不一會兒,調試工具就停下來,這時就可以查看完整的命令行了,其中口令就位于“-hp”選項中。
當在調試工具中運行的勒索軟件試圖啟動該RAR工具(這里它被偽裝成“svchost.exe”)時,就會被筆者截獲。這樣,筆者可以看到執行勒索軟件的命令的具體內容了,其中就包括用來加密文件的口令。

經過一番折騰后,筆者得到了一個口令,雖然它未必能夠用來解密受害者文件,但是,至少為逆向工程提供了有用的線索:筆者可以尋找口令或者口令中的片段,搜索可能用于生成口令的“字母表”(如果它是隨機生成的話),以及從命令行中尋找建立口令的“-hp”字符串。

攔截到的口令,看起來像是由57個字母、數字和標點符號混合而成的。對于這個口令,如果是由人鍵入的話,看起來好像太隨意了一點,并且其前綴為字符串“aes”。后者也許只是一個巧合,就像汽車牌照上也會出現有意義的單詞或者號碼,同時,這個前綴也可能是勒索軟件中的硬編碼字符串造成的。事實上,當筆者用反匯編程序打開該勒索軟件時,筆者不僅找到了字符串“aes”,而且發現,更為完整的前綴實際上是“aesT322”:
在這個勒索軟件的反匯編代碼中,出現了硬編碼的字符串“aesT322”,這與筆者之前攔截的口令的前七個字符非常吻合。同時,筆者還為在之前的逆向工程期間得到的一些函數(“_main”, “_strcat_edx”)和全局變量(“PasswordPrefix”)進行了命名。

這表明,該口令事實上是由“aesT322”后跟50個左右的隨機字符組合而成的。

在上圖中,部分高亮顯示的指令,就是用來加載指向“aesT322”字符串的引用的。筆者由此推斷,后面的指令,將加載一個指向存儲字符串的全局變量的引用。筆者已經給該變量取名為“PasswordPrefix”,下面筆者來定位該變量在內存中的具體地址。
該勒索軟件的數據段包含了一些與生成隨機口令有關的全局變量。就像以前一樣,這里也對某些變量重新進行命名并加以注釋。

知道了這些變量的地址后,筆者就可以利用調試工具來查看在實際測試期間,這些變量到底存放了哪些值,具體如下所示:
盡管筆者可以輕松地通過反匯編程序瀏覽勒索軟件的程序代碼,但是這樣做的缺點是,這只能針對磁盤上的靜態程序,或者說是“無生命的”程序。要想操作運行在內存中的、活蹦亂跳的勒索軟件進程的話,筆者還需借助于調試工具。下面,筆者就通過它來觀察三個字符串變量的取值情況。

正如筆者所預料的那樣,名為“passwordprefix”的變量指向前綴字符串“aest322”的一個副本,名為“passwordrandom”的變量指向由50個隨機字符構成的字符串,而名為“passwordfull”的變量則指向由前面兩部分連接而成的一個字符串。

然后,利用前面的發現和方法,繼續跟蹤字符串“-hp”。通過反匯編程序,筆者迅速鎖定了幾個實例,下面便是其中之一:
筆者在程序代碼中發現了字符串“-hp”的一個實例,顯然,它肯定位于構建含有口令的命令行并(通過 CreateProcessA 或 ShellExecuteA)執行該命令的代碼中。 到此為止,筆者雖然對該勒索軟件有了更多的了解,但是仍然不知道是否足以解救受害者。不過有一件事情是肯定的,那就是該口令不能適用于所有受害者。幸運的是,通過反匯編程序筆者可以輕松找出程序中所有訪問某個變量的代碼,這樣筆者就能追蹤到構造“PasswordFull”的代碼了:
上面的代碼,通過連接“aesT322”和隨機字符串而構造了“PasswordFull”所指向的字符串。

然后,筆者利用交叉引用找到了“PasswordRandom”。
這個循環(圖中用粗虛線箭頭加以指示)的作用是從由78個字符組成的“字母表”中挑選50個字符組成一個串,通過前面攔截的口令可以看出,它好像是隨機挑選出來的。 筆者還發現了一個循環,它的循環計數是從1到50,這跟口令中隨機部分的那50個字符的長度非常吻合。同時,筆者還在這個循環內部找到了一個字符串,看起來非常像是用來隨機選擇50個字符的“字母表”。在這個字母表中,包含了26個小寫字母,26個大寫字母,10個數字,以及16個標點符號。

接下來,筆者需要找出選擇隨機字符的那個函數,當然,上面循環內部的指令肯定會調用這個函數的。盡管人們普遍認為計算機能夠產生真正的隨機數,但是現實是,它只能帶來偽隨機數。長期以來,人們不斷探索針對偽隨機數生成器的攻擊技術,以便能夠戰勝加密術,這為筆者的工作做好了很好的鋪墊。上面,筆者標出了一個名為“_get_random_Alphabet_char”的函數,其反匯編代碼如下所示:
觀察上面的反匯編代碼不難發現,函數“_get_random_Alphabet_char”好像生成了一個隨機數,并將其保存到了局部變量“var_8”中。并且,這段循環代碼會順序遍歷字母表的各個字符,直到抵達某個字符為止。然后,那個字符被轉換成一個單字符字符串,其地址被存放到被筆者命名為“RandomAlphabetChar”的全局變量內。

當確定出上面高亮顯示的函數就是PureBasic的隨機數生成函數“Rnd”之后,這些匯編代碼就立馬變得非常容易閱讀了。這個函數的代碼如下所示:
隨機數函數“Rnd”的作用是,確保初始化偽隨機數生成器,以生成一個介于0到指定最大值“arg_0”之間的一個數值。當然,這個范圍也包括了它的兩個邊界值。 函數“Rnd”封裝了許多其他函數的調用,尤其是筆者前面命名的那些函數。這里的“_internal_Randomize_default”函數,除了調用一些Windows函數外,還調用了該勒索軟件自身的一個函數,該函數被命名為“_internal_RandomInit”。下面,筆者將這些函數并列顯示,具體如下圖所示。
在上圖中,左窗口顯示的是“_internal_Randomize_default”函數的反匯編結果,而右窗口中是筆者更為感興趣的“_internal_RandomInit”函數的反匯編結果。 現在,筆者終于取得了一個重大突破。在左邊窗口可以看到,PRNG是通過由執行這個程序代碼的線程的標示符和以毫秒計算的系統運行時間共同得到的一個32位數字來初始化或者說指定種子值的,而這兩個數值相對來說都比較容易預測。在右邊的窗口中,筆者高亮顯示了一個常量“magic”,這是一個專用的數字,通常用于網絡搜索。在這里,這個數字好像采用了16進制的形式,具體為53A9B4FBh,當然,還有其他可能的表示形式,如0x53A9B4FB,或者十進制表示形式1403630843。它后面的指令,可以轉換為“1 – EAX * 0x53A9B4FB”的形式,這意味著筆者所看到的這個常量實際上可能是負數-1403630843,如果看作是無符號數字的話,還可以表示為AC564B05h,0xAC564B05或者2891336453。利用上面的這些內容作為搜索詞進行網絡搜索,筆者竟然在PureBasic論壇中找到了與這些隨機數生成程序有關的源代碼,以及相應的反匯編代碼。

下面所謂“Rnd”函數的反匯編代碼中明顯的循環操作進一步印證了筆者的發現,它跟筆者從網絡上找到的源代碼的反匯編代碼是一致的。
上面高亮顯示的“ror”助記符表示的是循環左移指令,分別表示循環左移13比特和5比特。并且,筆者從網上找到的源代碼也在類似的上下文中執行循環左移操作,甚至移動的位數也是相同的。

好了,見證奇跡的時刻就要到了。由于它使用了32位的種子值,也就是說,可能的口令至多能有2的32次方種,而不是從78個字符組成的字母表中真正隨機挑選出50個字符的可能組合數。之所以出現這種情況,是因為計算機無法真正實現隨機性。對于任何給定的種子值,PRNG每次用它初始化的時候,都會以完全相同的次序生成完全相同的數值。由于這個隨機數種子值是一個32位的數字,也就是說其取值范圍是從0到2的32次方左右,因此可能的初始狀態也會受到相同的限制。

如果筆者能知道被侵害系統在受到攻擊之前已經運行的時間的話,那么就能極大地縮小種子值中運行時間分量的取值范圍,從而顯著減少需要嘗試的口令的數量。當然,列出所有2的32次方個口令也很麻煩。但是就本例而言,種子值的來源,即線程標識符和系統運行時間卻給筆者提供了莫大的幫助。前者是4的倍數,并且通常小于10000,而后者的變化范圍則要更大一些:它以49.7天為周期,取值從0變為2的32次方,之后,又從0開始循環往復,步進通常為15或者16。如果筆者能獲悉被侵害系統在受到攻擊之前已經運行的時間的話,那么就能極大地縮小種子值的運行時間分量的取值范圍,從而顯著減少需要嘗試的口令的數量。
猜解口令

實際上,猜口令并不是很難,問題在于這需要耗費大量的時間。當然,單純利用給定種子值生成一個口令的話,速度是極快的,但是對于筆者匆匆拼湊起來的這臺一次性機器來說,筆者只進行了一種猜解試驗,即嘗試解密RAR看看能否找出有用的東西來,這種嘗試所需的運算量非常之大,尤其是需要嘗試幾百萬次的時候。正如結果所展示的那樣,雖然沒能找到受害者電腦運行時間方面的線索,但是,卻出乎意料地發現了可能比這個更有用的東西。 首先,在檢查被感染的磁盤的時候,筆者注意到,\ProgramData文件夾下面隱藏了許多子目錄,這些目錄名是由隨機字母組合而成的,并且這些子目錄下面還隱藏了許多奇怪的文件。此外,筆者還發現了一個名為\ProgramData\svcfnmainstvestvs\stppthmainfv.dll的文本文件,其中共21行,每行含有8個隨機字母。隨著進一步的檢查,筆者發現每一行內容實際上就是勒索軟件\ProgramData目錄下面的由隨機字母組成的子目錄名或文件名,只不過順序給倒過來了。
件\ProgramData\svcfnmainstvestvs\stppthmainfv.dll中的內容,是筆者在運行測試過程中得到的。雖然這個文件的擴展名是.dll,但是這純粹是忽悠人的,它實際上就是一個文本文件。對于每一行字母,只要把它們倒過來,例如chlqfohk,都對應于一個子目錄或者文件的隨機名稱,這些都是勒索軟件在\ProgramData目錄下面創建的。

當然,這些數據的價值不在于筆者需要這些名稱,而在于筆者知道了它是PRNG在感染后輸出的。在實際發生感染的時候,例如本次面臨的這個案例,筆者當然無法像測試運行這樣獲得相應口令,但是仍然有機會找到stppthmainfv.dll或者根據被感染驅動器的\ProgramData目錄中的內容來重構它。利用這些數據,筆者就能夠暴力破解所有可能的種子值,最多需要2的32次方次,就能找出PRNG生成這些隨機名稱時所對應的種子值了。對于目前的計算機來說,這種暴力搜索可能需要幾個小時才能完成,但是相對于利用RAR工具逐個嘗試推測的口令來說,速度要快上好幾個數量級了。

這里有幾點需要注意。首先,就像在隨機函數中識別出的線程本地存儲(TLS)調用所暗示的那樣,每個線程都有自己的PRNG狀態,并且在第一次被“Rnd”調用時,都會單獨進行初始化。碰巧的是,那八個隨機字母構成的名稱是由勒索軟件進程的主線程生成的,而密碼是由分線程決定的。下面是生成21個名稱的一段循環代碼;標為綠色的代碼交叉引用(“CODE XREF”)注釋表明,該代碼駐留在程序的“start”函數中,并且該函數是該進程第一個執行的線程。
在勒索軟件的“start”函數中的這段循環代碼,將創建21個隨機的文件名和子目錄名。
這是上面提到過的那個從A到Z的字母表中隨機選擇8個字母函數中的循環代碼段。正如所料,該函數會針對每個字符都調用一次“Rnd”。

通過跟蹤生成口令的代碼,筆者找到了一個標記為“sub_406582”的函數,它會被筆者命名為“_ServiceMain”的函數調用,當勒索軟件作為一個Windows服務運行的時候,這個函數將在一個單獨的線程中運行。也就是說,筆者關心的這兩個代碼段在執行時會使用不同的PRNG狀態,每個狀態都是由不同的隨機數的種子值來產生的。通過暴力破解種子值,筆者可以得到隨機的文件名或子目錄名,但是卻無法直接得到口令的種子值,盡管如此,筆者已經離成功越來越近了,這主要得益于口令種子值構成部分的簡單性和可預測性。換句話說,種子值的系統運行時間部分和線程標識符部分還是有很大的不同的,因為線程可以在不同的時刻啟動,并且如果多個線程同時運行的話,必須使用不同的ID,但是它們不會相差太大。因此,要是手頭上有了第一個種子值,筆者就能適當地將第二個種子值的取值范圍縮小到幾十萬個之內。

第二個注意事項是,筆者已經知道了字母的次序,但是這一點很容易就會被忽視。另外,從技術上講PRNG會發出一個數字序列。就本例而言,就像前面的快照所顯示的那樣,數字0到25表示的就是字母A到Z,所以很明顯,這個字母表就是用來將字母映射到一個數字的。然而,在其他情況下,這些字母可能會出現遺漏、重復、重新排序,或者穿插了stppthmainfv.dll中所見之外的字符。無論出現上述哪一種情況,都意味著筆者需要花費許多小時來破除這些阻止筆者暴力破解的障礙。

第三個需要注意的事項是,筆者無法確定PRNG的種子值初始化之后,該勒索軟件是否立即生成了這些名稱或口令,也就是說,這中間否有什么岔子。如果其他代碼先于這兩個線程中的某一個,或先于這兩個線程之前就已經調用過了Rnd的話,則意味著PRNG的狀態已經不是筆者所關心的隨機數生成代碼給它提供種子值時的原始狀態了。筆者需要弄清,“Rnd”被筆者感興趣的那個線程調用之前,已經被其他線程調用了多少次,同時,還要去掉在生成筆者自己推測性的名稱和口令之前的那些隨機數。

因此,筆者需要找出針對“Rnd”的各個調用。如果將反匯編代碼稍微上滾一點,就會看到“start”函數中針對“Rnd”的唯一的一個調用,具體如下所示:
這是該勒索軟件對“Rnd”的第一次調用,并且是生成文件和子目錄名稱的循環代碼之前的唯一的一次調用。

另一方面,執行口令生成代碼的“_ServiceMain”線程對“Rnd”總共調用了3次,并且都是在它使用PRNG構造口令之前調用的,具體見下圖。
這三個針對“Rnd”的調用,都先于“_ServiceMain”線程中生成口令的那些代碼。

因為存在這三個調用,因此,當筆者準備開始生成候選密碼的時候,每次初始化好隨機數種子值后,最好放棄前三個隨機數。

現在,筆者終于要開始暴力破解了。對于尋找名稱的種子值的代碼,如下所示。需要注意的是,為了簡潔起見,筆者這里省略了實現PRNG的PureBasic代碼。
for (unsigned int seed = 0; ; seed++)
{
// seed the PRNG with a possible value
RandomInit(seed);

// discard one random number
Rnd();

size_t i;
for (i = 0; i < 21 * 8; i++)
{
// generate the next random letter
// Rnd(25): from 0 to 25 inclusive
char ch = “abcdefghijklmnopqrstuvwxyz”[Rnd(25)];

// does this letter match the next in the sequence?
if (ch != “chlqfohkayfwicdd…dszeljdp”[i])
break;
}

// did we complete the entire sequence?
if (i == 21 * 8)
{
// yes, display the result and finish
printf(“Names seed = %u\n”, seed);
break;
}

// no, try the next seed value
}
在一個單核機器上面,筆者使用了大約4秒鐘的時間,測試了31956209種可能的值,并發現最后一個種子值即31956208生成的字母序列與“stppthmainfv.dll”找到的完全一致。這個數字說明,筆者之前的觀點是正確的。

盡管筆者這個將這些信息轉化為結果的系統算不上優雅,但作為原型卻是行之有效的。即使筆者靠硬猜的話,需要嘗試的用來生成口令的種子值不會超過32768或者180000(以毫秒為單位的3分鐘時間),就能找出前面恢復出來的名稱對應的種子值。因此,筆者可以根據這個范圍內的種子值來生成一個大約包含200000個口令的清單,代碼如下所示:
// this is the names seed value we brute-forced earlier
unsigned int namesseed = 31956208;

// loop through a range of seed values around the determined names seed value
for (unsigned int seed = namesseed – 32768; seed <= namesseed + 180000; seed++)
{
// seed the PRNG with a candidate password seed value
RandomInit(seed);

// discard three random numbers
Rnd();
Rnd();
Rnd();

char pwrandom[51];
pwrandom[50] = ‘\0′;

// generate the fifty-char random portion of the password corresponding to
// the candidate seed value, using the alphabet extracted from the ransomware
// Rnd(77): from 0 to 77 inclusive (this alphabet contains 78 characters)
for (size_t i = 0; i < 50; i++)
pwrandom[i] = “abc…xyzABC…XYZ0123456789!@#$%^&*&*()-+_=”[Rnd(77)];;

// output the full password; this output can be captured to compile a list
printf(“aesT322%s\n”, pwrandom);
}
運行上面的代碼,將運行結果重定向到一個文本文件中。這個存放待測試的口令的文本文件的大小只有12MB左右,但是,如果不縮小口令種子值取值范圍的話,則需要測試的口令有2的32次方個,那么存放它們的文本文件的大小就會陡升至236GB左右。這樣一來,筆者就可以編寫一個批處理文件,讓它運行7-Zip,遍歷文件中的口令來解壓經過加密的RAR自解包程序。對于7-Zip來說,有時候即使已經正確解密了,它也會謊報軍情,為此,筆者需要使用find命令在其輸出中搜索只有文件被正確解密才可能出現的東西。筆者使用的批處理文件如下所示:
@for /f %%p in (pwlist.txt) do @(
7z.exe l “testtargetfile_0123456789abcdef.docx(!! to decrypt email id … to … !!).exe” “-p%%p” 2>&1 |find “..”
if NOT ERRORLEVEL 1 (echo “%%p”)
)
筆者讓這個批處理文件通宵運行,在第二天早晨終于找到了期盼已久的東西:正確的口令。至此,筆者大功告成了! 筆者沒有辜負受害者對筆者的信任,最終將其數據恢復如初。

上一篇:Hacking Team攻擊代碼分析:Win32k KALSR

下一篇:通過灰盒Fuzzing技術來發現Mac OS X安全漏洞