0×01 前言
當拿到加密文件后,瞬間傻眼。不是聯網程序,就是一個孤零零的exe,壓根沒有“忘記密碼”這個選項,雙擊運行后,彈出那冷冰冰的對話框“please enter password”,于是習慣性地進行了“人工智能弱密碼破解”(手動窮舉輸入密碼),一番折騰后,果斷放棄了嘗試。后背一陣冷汗后,還是硬著頭皮上了,誰讓咱爽快的答應別人了哩。同時為了挑戰一下自己,于是決定將這個程序進行逆向解析,徹底 ”爆”出里面的秘密,誰讓咱是屌絲學僧哩,還要指望著修煉技術找工作呢。。。

0×02 猜想
程序在用戶輸入密碼后,會立刻判斷出密碼的對錯,所以文件中存在"對比密鑰"用于判斷密碼的正確性。
對比密鑰的幾種形式:
1.密碼的明文;
2.密碼的散列值;
3.使用密碼和某一特征值生成對比密鑰;
4.使用密碼和用戶待加密的原數據生成對比密鑰;
5.………………
0×03 信息收集第一步:樣本設置
第二步:文件靜態分析
選取樣本1使用UE的檢索功能搜索密碼“123456”,未找到結果,可證明密碼不是以明文形式存儲。
選取樣本1為標準樣本,使用UE的二進制對比功能,對比樣本2、3、4與樣本1的差異。



將內容為空的加密程序用UE打開,最后一行行號為c9f0h。對比上述樣本,可判斷加密程序采用文件末尾追加數據的方式存儲密文數據,進一步分析后得到數據存儲格式。

第三步:IDA靜態分析
使用IDA加載樣本1,彈出提示框。

點擊Ok,程序成功載入,但是函數窗口中只有一個函數,可見程序加了某種殼對IDA逆向分析產生了干擾。

第四步:脫殼
為減少調試中的干擾,進一步理清程序流程,需要進行脫殼處理。使用殼檢測神器PEiD判斷殼類型,結果如下:

PEiD成功檢測出殼名稱為PECompact 2.x -> Jeremy Collake ,如果是未知殼, 則需要進行手動脫殼。這里根據殼信息下載對應的脫殼程序對之前設置的四個樣本程序進行脫殼處理。
將脫殼后程序再進行IDA靜態分析,函數窗口可獲取到所有的函數信息,主程序流程圖如下所示
這密密麻麻的分支,讓我再次一身冷汗
第五步:動態調試
使用OD進行動態調試分析,主要分析程序的密碼比對流程。
1、將樣本1載入OD中,F9直接運行。此時,奇怪的事發生了,程序在彈出密碼輸入框的同時,OD左下角提示進程已經結束,這意味著程序已經運行結束,怎么密碼框還在呢?!!!
由此判斷程序在運行時,創建了其他工作進程后結束了自身進程。打開任務管理器,可以看到如下疑似進程在運行。Kill掉這個進程后,密碼框消失,可證明該線程為密碼框工作線程。

2、使用文件夾的搜索功能,對全盤進行了搜索,尋找該進程對應的程序存放目錄。
打開其對應的文件夾,可以看到有很多類似程序,這些都是測試時記事本生成的中間程序。
3、運行這些程序,均為空白記事本,沒有任何內容。經過UE比對確認,這些程序均為筆記本的原始程序,不包含任何數據。
推測:記事本在運行時,先將原始程序釋放在temp目錄下,然后創建新進程加參數運行釋放的程序。
證明:使用OD查看程序調用的函數列表,找到創建進程的相關函數。
這里確定kernel32庫函數CreateProcessA,右鍵選擇“查看引用”。

407BAE處調用了該函數創建新進程,在407BAE下斷點,運行程序
程序確實是通過加參數的形式運行的,打開cmd,輸入程序路徑并且加參數運行

程序彈出錯誤窗口,并不能正常運行彈出密碼輸入框。
0×04深度分析
獲取上述基本信息后,確定了加密程序的數據存儲格式和運行加載方式。下面采用OD附加進程的方式直接對運行后新創建的進程進行調試,來梳理密碼判斷流程。
1、附加程序
雙擊運行程序,打開OD->文件->附加,雙擊新進程名稱,將OD附加上去,對其進行調試。
由于此時新進程處于密碼框輸入狀態,所以OD會停留在系統函數領空,此時密碼框為不可用狀態。為了跟蹤密碼輸入后的流程,需從密碼輸入后跟蹤調試,使用Alt+F9程序會自動運行并停留在用戶代碼段。此時密碼輸入框處于激活狀態,輸入正確的密碼,點擊確定,程序停留在用戶代碼段。

2、IDA輔助查看程序流程
在獲取到密碼輸入后的關鍵地址后,使用IDA加載程序,使用F5反編譯功能,查看程序的偽代碼。
可以看到While循環中第33行為密碼輸入框,37行調用函數404648進行了密碼正確性判斷,39行為“Invalid passphrase”密碼錯誤信息。將OD定位到404648函數的調用處,可以看到函數的返回值eax決定了后續分支走向,這個值便是密碼正確性判斷后產生的結果。
找到密碼判斷的關鍵后,進入404648函數,查看返回值的生成過程,確定關鍵代碼。
repe cmps byte ptr [esi],byte ptr [edi]
ESI為12FE78,EDI為3E3D99
程序對兩處0×20字節的數據進行比對,而這兩個數據正是樣本1中key中的前0×20字節的數據。可確定程序在獲得輸入密碼后,經過一系列加密變換后生成0×20字節的key與文件中的密鑰進行對比,來判斷輸入的密碼是否正確。

3、加密流程
確定密鑰判斷關鍵位置后,繼續向上追溯,尋找對比密鑰生成過程。經過一番跟蹤后,確定函數407481為對比密鑰生成函數。
size_t __usercall sub_407481@(int a1@, void *a2, size_t a3) { int v3; // edi@1 size_t result; // eax@1 int v5; // ebx@1 size_t v6; // ebx@7 v3 = *(_DWORD *)a1 & 0x3F; result = a3 + *(_DWORD *)a1; v5 = 64 - v3; *(_DWORD *)a1 = result; if ( result < a3 ) ++*(_DWORD *)(a1 + 4); if ( v3 && a3 >= v5 ) { memcpy_0((void *)(v3 + a1 + 40), a2, 64 - v3); result = sub_404B4C(a1 + 40, a1); a3 -= v5; a2 = (char *)a2 + v5; v3 = 0; } if ( a3 >= 0x40 ) { v6 = a3 >> 6; do { result = sub_404B4C((int)a2, a1); a3 -= 64; a2 = (char *)a2 + 64; --v6; } while ( v6 ); } if ( a3 ) result = (size_t)memcpy_0((void *)(v3 + a1 + 40), a2, a3); return result; }
用OD在407481函數處下斷點
什么!!!!函數在執行時,參數1是明文內容,參數2是明文長度。可見在此之前,程序利用輸入的密碼對密文進行了解密,然后又將解密出的明文送入函數407481生成比對密鑰。
明文是如何解出來的,稍后再分析。先繼續分析407481函數如何利用明文生成對比密鑰。經調試后,確定函數404B4C為關鍵的加密函數。
由于該函數非常復雜,所以并不打算對該加密算法進行深入分析,直接將該函數的匯編代碼摳出來作為c程序的內嵌代碼使用。
404B4C函數的輸入分別為eax(待加密的內容,長度為0×40字節),ecx(生成的密鑰存放位置),ecx所指向的密鑰存放位置為0×28字節,前8個字節存放著原始明文的總長度,后面0×20字節存儲著生成密鑰,且這0×20字節密鑰設有初始值。
407481函數
輸入:參數Arg1:原始明文地址
參數Arg2:原始明文長度
輸出: 藍色框中為原始明文長度
紅色框中為密鑰變換后的結果
綠色框為明文長度除以0×40后剩余的明文內容
407481過程表示
藍色框中寫入參數Arg2的值
count = Arg2 / 0x40; //明文長度除以0x40
data=Arg1;
While(count--)
{
Call 404B4C(data); //每次講明文的0x40字節進行加密計算
data=data+0x40;
}
Call 40B240(Arg2 %0x40 ,data); //將明文的剩余部分寫入綠色框中
上述過程結束后,程序再次調用了407481函數,參數為原始加密文件中key2密鑰,長度為0×10字節。
407481函數運行后將0×10字節的密鑰追加在了剩余明文尾部。
隨后的call 00407508函數會計算出0×20字節的對比密鑰。
經過分析,程序主要利用如下區域的數據進行對比密鑰的生成。
藍色框:原始明文長度+末尾附加的數據長度
紅色框:密鑰
綠色框:剩余數據
總結對比密鑰生成過程:
1.將紅色區域初始化,將初始密鑰寫入
2.每次讀取0×40字節的原始數據,使用紅色區域的密鑰進行加密變換,生成的密鑰輸出到紅色區域;
3.將剩余的原始數據進行填充處理,使其達到0×40長度,然后再進行一次密鑰變換,此時生成的密鑰便是比對密鑰,用于和正確的密鑰進行比對。
分析到這里我們發現,對比密鑰的生成條件都是可以從文件數據中獲取,但是有一個條件現在還不知道,那就是明文數據!!!我們似乎陷入了一個死循環中。。
0×05密文解密
1、猜想:程序在獲取到輸入密碼后,利用輸入密碼對密文進行解密,用解密后的密文生成對比密鑰。
證明:繼續回溯跟蹤,確定404A6F地址處調用的call 0040854C函數是解密函數,參數1為密文內容,參數2為密文長度,參數3為文件中的key2密鑰。
40854C函數在解密過程中還調用了一些未知區域的數據進行解密變換
2、猜想:程序在初始化運行時,生成了這些未知區域的數據。
證明:重新運行程序,斷在程序入口點處,查看數據區域41E340處,可見該區域均為0×00。
在此數據區域設置內存寫入斷點,F9運行。
確定位置后用IDA反編譯,可以清晰觀察到程序通過調用408FFB函數向41B300,41BB00,41E340,41EB40,41C700,41CB00六個區域寫入數據,每個區域長度為0×100。
int sub_408FFB() { int v0; // eax@1 int v1; // ecx@1 ………………………… v0 = 1; v1 = 0; do { v2 = 283 * (((unsigned int)v0 >> 7) & 1); *(int *)((char *)&dword_41CF00 + v1) = v0; v1 += 4; v0 = v2 ^ 2 * v0; }while ( (unsigned int)v1 < 0x28 ); v28 = 0; do { v3 = v28; LOBYTE(v2) = v28; v4 = sub_408F52(v1, v2); v5 = 2 * (v4 ^ 2 * (v4 ^ 2 * (v4 ^ 2 * v4))) ^ v4; v6 = (unsigned __int8)(v5 ^ BYTE1(v5) ^ 0x63); v7 = 2 * v6 ^ 283 * (v6 >> 7) | ((v6 | (v6 << 8)) << 8) | 452984832 * (v6 >> 7) ^ ((v6 ^ 2 * v6) << 24); …………………… v20 = 72448 * v17 ^ 72448 * v18 ^ 72448 * v19 ^ ((v14 ^ 8 * v14) << 8) | 18546688 * v17 ^ 18546688 * v15 ^ 18546688 * (((unsigned int)v14 >> 6) & 1) ^ 18546688 * v18 ^ 18546688 * v19 ^ ((v14 ^ 4 * v16) << 16) | 452984832 * ((unsigned int)v14 >> 7) ^ 452984832 * v17 ^ 452984832 * v18 ^ 452984832 * v19 ^ ((v14 ^ 2 * (v14 ^ 4 * v14)) << 24) | 283 * ((unsigned int)v14 >> 7) ^ 283 * v17 ^ 283 * v15 ^ 283 * (((unsigned int)v14 >> 6) & 1) ^ 283 * v18 ^ 283 * v19 ^ 2 * (v14 ^ 2 * v16); …………………… dword_41E340[v8] = v21; dword_41EB40[v8] = v24; dword_41C700[v8] = v1; dword_41CB00[v8] = v26; } while ( v28 < 0x100 ); dword_41FF58 = 1; return 0; }
0×06.解密流程
0×07.后記
根據上述分析后,可確定加密程序并非將密碼存儲在文件中,所以不能根據加密數據逆向推導出原密碼,而只能根據上述分析的密碼驗證流程采用字典攻擊進行暴力破解。而這也正是很多加密程序需要使用字典進行暴力破解的原因。程序分析到這里,就該寫程序結合字典來“爆”出里面的秘密了。。。。。。
|