2011年8月6日星期六

Reglo v3.5a 註冊算法詳析

【軟件名稱】:
    Reglo v3.5a

【下載地址】:
    Http://www.basta.com/

【軟件簡介】:
    Reglo 是一款電腦屏幕測量工具,其實就是一把尺子,有多種單位可以選擇,用於測量電腦屏幕中圖片或其它.

【軟件限​​制】:
    如果沒有註冊,則只有 30 天的試用期.

【破解聲明】:
    我是搞軟件的,知道一款軟件的開發,需要付出很多很多,所以如果大家有點錢的話,還是多支持支持軟件的作者,這樣人家才有乾勁呀.其實,這款軟件對我沒什麼用,只是好奇它的註冊算法而已,如果大家有興趣,不妨在看完了這篇註冊算法分析後,用Reglo 的姊妹軟件試試,看它們的註冊算法是什麼.它的姊妹軟件包括:
    AppToService v4.0 一個Windows 控制台,這個使用控制台的軟體可以讓你把常規程序或服務器,運行做Windows 服務.
    Buzof v4.0 一款讓你可以消除煩人的系統消息的工具,比如,刪除文件時,系統總會提示是否真的要刪除等信息.
    Deletor v4.2 一款可以按照你的要求,進行刪除的工具.例如,刪除滿足一定大小/時間/屬性等條件的文件夾等等.
    Filo v4.2 修改文件或文件夾屬性的工具.
    Splitty v4.0 文件分割合併工具.
    ZMover v7.2 桌面佈局管理軟件,可以讓你指定程序窗口位置/大小/層次等.

【破解工具】:
    PEID v0.94,OllyDBG v1.10

【破文作者】:
    WSLVIC 電郵:Crk4u@163.com

【破解時間】:
    二〇一一年七月二日

【破解過程】:
———————————————————————————————————————————
    不必多說,首先是查查殼,用PEID 一看,原來是Microsoft Visual C++ 7.0,也好,省的脫殼了.然後當然是用OD 載入,載入後停在這裡:
    00416197 > $ 6A 60 PUSH 60
    00416199 . 68 F0284300 PUSH Reglo2.004328F0
    0041619E . E8 451A0000 CALL Reglo2.00417BE8
    004161A3 . BF 94000000 MOV EDI,94
    004161A8 . 8BC7 MOV EAX,EDI
    004161AA . E8 C10E0000 CALL Reglo2.00417070
    004161AF . 8965 E8 MOV DWORD PTR SS:[EBP-18],ESP
    004161B2 . 8BF4 MOV ESI,ESP
    004161B4 . 893E MOV DWORD PTR DS:[ESI],EDI
    004161B6 . 56 PUSH ESI ; /pVersionInformation
    004161B7 . FF15 A801​​4300 CALL DWORD PTR DS:[<&KERNEL32.GetVersion>; \GetVersionExA
    ......
    典型的VC 入口呀,不急下斷點,先看看註冊窗口是什麼樣的,按F9 運行,在標尺上點擊右鍵選擇"&About Reglo...",在彈出窗口中選擇"&Purchasing Information.. .",終於出現了,原來註冊碼分兩部分,每部分都8 位,總長度是16,也就是十六進制的0x10,好了先看看能不能輸入中文(呵呵,習慣了),輸入"瑤淼"---原來可以輸入中文,當然也可以輸入英文字母和數字,胡亂輸一通,比如"11111111-22222222" 點擊"Ok",彈出對話框"The password you have entered is incorrect." ---現在當然不能註冊,不過記下了這串E 文,點擊"確定" 回到OD 中,用OD 自帶的字符串搜索功能搜索"所有參考文本字符串",很快就搜索完畢了,可惜上面那串E 文,連影子都沒有,再用OD 插件"超級字串參考" 和"中文搜索引擎",也沒找到,暈,只好下對話框斷點了,試試MessageBoxA 吧,在命令條中輸入"bpx messageboxa",然後點擊F9 運行,輸入註冊碼"11111111-22222222",點擊"確定",呵呵,運氣不錯,被OD 斷了下來,斷點停在:
    0042B8C7 |. FF15 9C034300 CALL DWORD PTR DS:[<&USER32.MessageBoxA>>; \MessageBoxA (1)
    不管它,向上看,直到該段代碼的開始處,
    0042B7FB /> /55 PUSH EBP (2)
    點擊該行,這時,OD 顯示"跳轉來自0042B924",一看,竟然是這段代碼的最後一行
    0042B924 \.^\E9 D2FEFFFF JMP Reglo2.0042B7FB (3)
    於是想到應該是從其它某處調用到(1) 之後的某個地方,再執行到(3),然後再跳轉到(2),仔細觀察代碼,發現(1) 之後的0042B904 處
    0042B904 |. C2 0C00 RETN 0C
    非常可疑,其下一行為
    0042B907 |$ 55 PUSH EBP
    在該行點擊,OD 顯示"本地調用來自00401698,00406C15,00425097,0042B96A,0042C086,0042C163",到底從這裡的哪個調用來的呢,不忙,先在該處按F2,下一個斷點,按F9 運行程序,胡亂輸入"1111111122222222" 點擊"確定",發現OD 已經斷在了該處,在OD 工具欄中點擊圖標"K" --- 調用堆棧,發現OD 顯示"函數過程Reglo.0042B907,調用來自Reglo.0042B96A",於是在OD 信息窗口的"本地調用來自00401698,00406C15,00425097,0042B96A...",上點右鍵選擇"轉到Call 來自0042B96A",於是OD 立刻來到了0042B96A所在的代碼段,在該段的段頭:
    0042B929 /$ B8 6DED4200 MOV EAX,Reglo.0042ED6D
    上點擊,發現OD 顯示"本地調用來自004018F9, 00401F57, 0040777D, 00407CB3, 0040887B, 0040A7CC, 0040B31A, 0040B464, 00412AC9, 00412AE0, 004250B4, 004286E2, 0042875A, 0042BF9A",暈,又是一堆,咋辦呢,還是老辦法,F2 斷點→F9 運行→輸入註冊碼→點擊"確定",OD 斷在了0042B929,在點擊圖標"K",OD 顯示"函數過程Reglo.0042B929,調用來自Reglo.0040B464" ,再次轉到0040B464 所在的代碼段,該段的段頭為:
    0040B350 /$ 64:A1 0000000>MOV EAX,DWORD PTR FS:[0]
    下斷點,運行到該行,F8 單步到
    0040B3AC |. E8 519A0100 CALL Reglo.00424E02
    處時,註冊窗口彈出,呵呵,該行是調用註冊窗口的.繼續F8,直到
    0040B3BA |. 8B8424 D80000>MOV EAX,DWORD PTR SS:[ESP+D8]
    這時在OD 信息窗口中看到了已經輸入的註冊碼.
    哈哈,萬里長征終於走到了第一站!
    這點很重要,這是進行下一步調試的關鍵,於是整理一下,刪除除了0040B3BA 處以外的所有其它斷點.註冊碼分析就要開始了.在0040B3BA 下方看到一個Call
    0040B3DA |. E8 A17B0000 CALL Reglo.00412F80
    F7 跟進去一看,果真是進行註冊碼測試,好,先下個斷點.然後按Ctrl+F2 重新載入,輸入註冊碼時輸入"1111111122222222",OD 斷在了0040B3DA,F7 跟進,便來到了下方,
    ┌──────────────────────────────────────────────┐
    │00412F80 PUSH EBX │
    │00412F81 PUSH EBP │
    │00412F82 MOV EBX,EAX ; EBX=輸入的註冊碼│
    │00412F84 PUSH ESI │
    │00412F85 XOR EBP,EBP │
    │00412F87 TEST EBX,EBX │
    │00412F89 PUSH EDI │
    │00412F8A JE Reglo.004130C7 ; 一跳就死│
    │00412F90 LEA EDX,DWORD PTR DS:[EAX+1] ; EDX=第二個字符的地址│
    │00412F93 /MOV CL,BYTE PTR DS:[EAX] ; CL=第一個字符│
    │00412F95 |INC EAX ; 下一個│
    │00412F96 |TEST CL,CL ; 測試CL,是否為全零│
    │00412F98 \JNZ SHORT Reglo.00412F93 ; 循環測試CL 是否為00 │
    │00412F9A SUB EAX,EDX ; EAX=尾字符的地址,EDX=第二個字符的地址│
    │00412F9C CMP EAX,10 ; 比較字符串長度是否為16 │
    │00412F9F JNZ Reglo.004130C7 ; 不等就跳,一跳就完│
    │00412FA5 MOV ESI,EAX ; ESI 指向尾字符│
    └──────────────────────────────────────────────┘
    這段代碼沒什麼難度,就是測試下輸入的註冊碼的長度,及每個字符是否為00,之後則來到,
    ┌──────────────────────────────────────────────┐
    │00412FA7 /MOVSX EAX,BYTE PTR DS:[ESI+EBX-1] │
    │00412FAC |DEC ESI │
    │00412FAD |PUSH EAX │
    ┌──────────────────────────────────────────────┐
    │00412FAE |CALL Reglo.00415ACF ; 判斷是否是數字(4)│
    └──────────────────────────────────────────────┘
    │00412FB3 |ADD ESP,4 │
    │00412FB6 |TEST EAX,EAX │
    │00412FB8 |JE Reglo.004130C7 ; 這裡一跳就完蛋│
    │00412FBE |TEST ESI,ESI │
    │00412FC0 \JNZ SHORT Reglo.00412FA7 │
    └──────────────────────────────────────────────┘
    這是一段循環,中間有個Call,其過程是逐字讀入註冊碼的每一位,然後用Call 進行測試,看來是關鍵測試了,F7 步入,OD 轉到這裡,
    ┌──────────────────────────────────────────────┐
    │00415ACF PUSH EBP │
    │00415AD0 MOV EBP,ESP │
    │00415AD2 PUSH ECX │
    │00415AD3 PUSH EBX │
    │00415AD4 MOV EBX,DWORD PTR SS:[EBP+8] ; 地址移動8位│
    │00415AD7 CMP EBX,0FF ; 判定是否是ASCII 字符│
    │00415ADD /JBE SHORT Reglo.00415B49 ; 小於等於255 時,這裡一定跳,否則必死│
    └──────────────────────────────────────────────┘
    這段的關鍵是上面的最後兩句,0FF 是255,EBX 中是當前位置註冊碼的ASCII 值(別忘了這段是在循環中),所以這兩句是測試,如果當前位置註冊碼的ASCII 值<= 255 時,發生跳轉,由255 可知,要求輸入字符是"基本拉丁字符集" 和"增補拉丁字符集1" 中的字符,由於當前輸入的測試註冊碼是"1111111122222222",故滿足要求,不過你可以試試輸入註冊碼"!111111122222222",這時Reglo 會彈出對話框"Please enter no more than 8 characters." 註冊就失敗了.然後跳轉到00415B49 處,注意,中間有段代碼00415ADF-00415B47 是處理當字符的ASCII>255 時,進行的操作,我沒跟過去,如果誰有興趣不妨看看Reglo 在這段干了什麼.跳轉到了這裡,
    ┌──────────────────────────────────────────────┐
    │00415B49 \PUSH EBX │
    │00415B4A CALL Reglo.00419636 ; 判斷是否是數字:否,則跳出. │
    │00415B4F POP ECX │
    │00415B50 POP EBX │
    │00415B51 LEAVE │
    │00415B52 RETN │
    └──────────────────────────────────────────────┘
    又是一個 Call! 煩哪,還得跟進去,來到,
    ┌──────────────────────────────────────────────┐
    │00419636 /$ CALL Reglo.00419174 ; 別管,給EAX 一個地址│
    │0041963B |. MOV EAX,DWORD PTR DS:[EAX+64] │
    │0041963E |. CMP EAX,DWORD PTR DS:[43FF84] ; Reglo.0043FF30 │
    │00419644 |. JE SHORT Reglo.0041964B ; 必跳│
    │00419646 |. CALL Reglo.0041911B │
    │0041964B |> CMP DWORD PTR DS:[EAX+28],1 │
    │0041964F |. JLE SHORT Reglo.00419661 ; 必跳│
    │00419651 |. PUSH 4 │
    │00419653 |. PUSH DWORD PTR SS:[ESP+8] │
    │00419657 |. PUSH EAX │
    │00419658 |. CALL Reglo.004198A3 │
    │0041965D |. ADD ESP,0C │
    │00419660 |. RETN │
    │00419661 |> MOV EAX,DWORD PTR DS:[EAX+48] ; UNICODE " ((((( │
    │00419664 |. MOV ECX,DWORD PTR SS:[ESP+4] │
    │00419668 |. MOVZX EAX,BYTE PTR DS:[EAX+ECX*2] ; 若ECX 為0-9 的數字,則EAX=84 │
    │0041966C |. AND EAX,4 ; EAX=84 And 4=4 │
    │0041966F \. RETN │
    └──────────────────────────────────────────────┘
    怎麼又是一堆Call!而且第一句就是一個Call,F7 進入,發現一堆系統調用,包括GetLastError/TlsGetValue/TlsSetValue/GetCurrentThreadID/SetLastError 等等,它們是乾什麼的呢?為什麼這麼做?到現在也沒搞清楚,不過至少知道這個Call 改變了EAX,也就是給EAX 了一個新地址,在這個Call 後的兩句,可以看到它是在進行地址比較,不管它了,來到00419644 處,這裡一定要跳,之後來到0041964B,與1 進行比較,之後到了0041964F 處跳轉至00419661,關鍵來了,這句給了EAX 一個UniCode 字符串,這是乾啥用的?先不急,看看它的下一句,在OD 的信息窗口看到了SS:[ESP+4] 中放置的其實就是當前位置註冊碼的ASCII 值,並賦給了ECX,ok,再下一句看到了DS:[EAX+ECX *2],這又是乾什麼呢,分析看看:當前EAX 是一個指向UniCode 的地址,ECX 是ASCII 值,所以EAX+ECX*2 必是一個地址,運行到該句OD 顯示"DS:[00432AC6 ]=84",在其上點擊右鍵,選擇"數據窗口中跟隨地址",發現數據窗口中是這樣一串數據"84 00 84 00 84 00 84 00..."---共10 組"84 00",於是終於明白了這段到底是在幹什麼,也就是說從該段的第一個Call 給了EAX 一個地址,通過這個地址加上一定的偏移量,指向一些特定地址,例如00419661 處, EAX=DS:[EAX+48],如果當前位置的字符是0-9 (ASCII 的30-39),那麼從DS:[EAX+48] 開始,偏移30*2 至39*2,都將得到84 這個值,也就是上面的10 組"84 00",那麼為什麼是84 呢,從0041966C 處,可看出84 與4 進行"與" 操作,EAX 將等於4,不等於0,換句話說EAX 就是一個flag,當EAX 非零時,說明匹配成功,當前字符是0-9 的數字,否則EAX=0 說明匹配失敗.其實在UltraEdit 中也可找到這兩個字符串,其中,
    UNICODE " ((((( H" 在實偏移31C62H 處
    "84 00 84 00 84 00 84 00 84 00 84 00 84 00..." 在實偏移31CC2H 處
    誰有興趣的話,不妨算算它們之間的偏移是否是0x30*2 - 0x39*2
    至此,萬里長征第二步算完成了,可是只是知道註冊碼由數字組成,卻不知道形成規則是什麼,怎麼辦,只好一路返回,回到00412FB3 處,此處仍在循環中,在00412FC2 處按F4,運行到該行,
    ┌──────────────────────────────────────────────┐
    │00412FC2 MOVSX EDI,BYTE PTR DS:[EBX+F] ; EDI=末字符│
    │00412FC6 SUB EDI,30 │
    │00412FC9 MOV EAX,EDI ; EAX=末字符的數值│
    │00412FCB IMUL EAX,EDI ; EAX=末位數的平方│
    │00412FCE CDQ ; CDQ 雙字擴展成四字│
    │00412FCF MOV ECX,0A │
    │00412FD4 IDIV ECX ; 末位數的平方(EAX)除10,商放EAX,余放EDX │
    │00412FD6 MOV AL,BYTE PTR DS:[EDI+EBX] ; AL=第EDI+1 個字符│
    │00412FD9 ADD DL,30 ; 加30 │
    │00412FDC CMP AL,DL ; │
    │00412FDE JNZ Reglo.004130C7 ; 一跳就完│
    └──────────────────────────────────────────────┘
    這段代碼的關鍵是:
    AL 是註冊碼的第EDI+1(EDI=末字符的數值) 個字符,而DL 是註冊碼的最後一個字符的平方,除以10,所得的餘數,由CMP Al,DL 及其下一行的JNZ Reglo.004130C7 可知,註冊碼的最後一位的數值i,決定著註冊碼第i+1 位的值必須為最後一位的數值的平方除10 所得的餘數,即:
    最後一位數i 第i+1 位數模型
    i=0 第1(i+1) 位為0(0^2 Mod 10) 0xxxxxxx xxxxxxx0
    i=1 第2(i+1) 位為1(1^2 Mod 10) x1xxxxxx xxxxxxx1
    i=2 第3(i+1) 位為4(2^2 Mod 10) xx4xxxxx xxxxxxx2
    i=3 第4(i+1) 位為9(3^2 Mod 10) xxx9xxxx xxxxxxx3
    i=4 第5(i+1) 位為6(4^2 Mod 10) xxxx6xxx xxxxxxx4
    i=5 第6(i+1) 位為5(5^2 Mod 10) xxxxx5xx xxxxxxx5
    i=6 第7(i+1) 位為6(6^2 Mod 10) xxxxxx6x xxxxxxx6
    i=7 第8(i+1) 位為9(7^2 Mod 10) xxxxxxx9 xxxxxxx7
    i=8 第9(i+1) 位為4(8^2 Mod 10) xxxxxxxx 4xxxxxx8
    i=9 第10(i+1) 位為1(9^2 Mod 10) xxxxxxxx x1xxxxx9
    由於輸入的測試註冊碼"1111111122222222" 不滿足這個要求,在00412FDE 處,就跳走了.不妨把測試註冊碼改為"1141111122222222",Ctrl+F2 重新運行,終於第一個坎00412FDE 過了,然後代碼來到,
    ┌──────────────────────────────────────────────┐
    │00412FE4 |.LEA EAX,DWORD PTR DS:[EDI+1] ; EAX=末字符數值+1 │
    │00412FE7 |.CMP EAX,0F │
    │00412FEA |.JL SHORT Reglo.00412FEF ; 必跳│
    │00412FEC |.SUB EAX,0F │
    │00412FEF |>MOVSX ECX,BYTE PTR DS:[EAX+EBX] ; ECX=第末字符數值+2 個數字│
    │00412FF3 |.LEA ESI,DWORD PTR DS:[ECX-30] ; ESI=第末字符數值+2 個數字的數值│
    │00412FF6 |.CMP ESI,1 ; 第末字符數值+2 位數字必須大於等於1│
    │00412FF9 |.JGE SHORT Reglo.00413000 ; 必跳│
    └──────────────────────────────────────────────┘
    測試註冊碼"1141111122222222" 的末位為"2",故第4(2+2) 個數字必須大於等於1,測試註冊碼已滿足要求,呵呵,繼續啊,第二個坎00412FF9 過了.繼續來到這裡,
    ┌──────────────────────────────────────────────┐
    │00413000 |>XOR ECX,ECX │
    │00413002 |.CMP ESI,1 │
    │00413005 |.SETG CL ; 根據標誌寄存器,大於1 時,置CL=1 │
    │00413008 |>MOV EDX,DWORD PTR SS:[ESP+14] │
    │0041300C |.INC EAX ; EAX=末字符數值+2 │
    │0041300D |.CMP EAX,0F │
    │00413010 |.MOV DWORD PTR DS:[EDX],ECX ; DS:[0012F3C0]=00000000 │
    │00413012 |.JL SHORT Reglo.00413017 │
    │00413014 |.SUB EAX,0F │
    │00413017 |>MOV ECX,EAX ; ECX=末字符數值+2 │
    │00413019 |.ADD EAX,3 ; EAX=末字符數值+5 │
    │0041301C |.CMP EAX,0F │
    │0041301F |.JL SHORT Reglo.00413024 │
    │00413021 |.SUB EAX,0F │
    │00413024 |>MOVSX ESI,BYTE PTR DS:[ECX+EBX] ; ESI=第末字符數值+3 個數的ASCII 值│
    │00413028 |.MOV ECX,EAX ; ECX=末字符數值+5 │
    │0041302A |.ADD EAX,3 ; EAX=末字符數值+8 │
    │0041302D |.SUB ESI,30 ; ESI=第末字符數值+3 個數的數值│
    │00413030 |.CMP EAX,0F │
    │00413033 |.JL SHORT Reglo.00413038 │
    │00413035 |.SUB EAX,0F │
    │00413038 |>MOVSX ECX,BYTE PTR DS:[ECX+EBX] ; ECX=第末字符數值+6 個數的ASCII 值│
    │0041303C |.SUB ECX,30 ; ECX=第末字符數值+6 個數的數值│
    │0041303F |.MOV EDX,EAX │
    │;EDX=末字符數值+8(末字符數值+8<15 時)或末字符數值+8-15(末字符數值+8>=15 時) │
    │00413041 |.IMUL ECX,ECX,64 ; ECX=(第末字符數值+6 個數的數值)*64 │
    │00413044 |.ADD EAX,3 ; EAX=末字符數值+11 │
    │00413047 |.CMP EAX,0F │
    │0041304A |.JL SHORT Reglo.0041304F │
    │0041304C |.SUB EAX,0F │
    │0041304F |>MOVSX EDX,BYTE PTR DS:[EDX+EBX] │
    │;EDX=第{末字符數值+9(末字符數值+8<15)或末字符數值+8-14(末字符數值+8>=15)}個數字的ASCII值│
    │00413053 |.MOVSX EAX,BYTE PTR DS:[EAX+EBX] │
    │;EAX=第{末字符數值+12(末字符數值+11<15)或末字符數值+11-14(末字符數值+11>=15)}個數字的ASCII值│
    │00413057 |.SUB EDX,30 │
    │;@EDX=第{末字符數值+9(末字符數值+8<15)或末字符數值+8-14(末字符數值+8>=15)}個數字的數值│
    │0041305A |.ADD ECX,EAX ; ECX=ECX+EAX │
    │0041305C |.LEA EDX,DWORD PTR DS:[EDX+EDX*4] ; EDX=@EDX*5 │
    │0041305F |.LEA ECX,DWORD PTR DS:[ECX+EDX*2-30]; ECX=ECX+@EDX*A-30 │
    │00413063 |.CMP ECX,7 ; ECX=ECX+@EDX*A-30=7(@EDX 是0-9 的數字) │
    │00413066 |.JNZ SHORT Reglo.004130C7 ; 這裡一跳就完蛋了│
    └──────────────────────────────────────────────┘
    這段代碼比較複雜,註釋寫的老長,目的是方便修改測試註冊碼,否則,註冊無法繼續下去.其關鍵是00413063 處,要求ECX+@EDX*A-30=7,這裡:
    ECX=(第末字符數值+6 個數的數值)*64+第{末字符數值+12(末字符數值+11<15)或末字符數值+11-14(末字符數值+11>=15 )}個數字的ASCII 值.
    @EDX 是 0-9 中的某個數字.
    對當前的測試註冊碼"1141111122222222" 而言,第8(2+6) 個數是"1",1*64=64;第14(2+12) 個數是"2",其ASCII=32 ,所以ECX=1*64+32=96.
    同時@EDX=第11(2+9) 個數是"2",故ECX+@EDX*A-30=96+2*A-30=7A 不等於7,故無法繼續,如何修改測試註冊碼呢,觀察ECX,@EDX 可知,ECX>=30,@EDX>=0,故若ECX+@EDX*A-30=7,則ECX+@EDX*A=37,就必有@EDX*A<=7 ,所以@EDX=0,ECX=37,換句話說,
    1.@EDX=0,意味著:
      第{末字符數值+9(末字符數值+8<15)或末字符數值+8-14(末字符數值+8>=15)}個數字的數值必為0.
    2.ECX=37,意味著:
      第 末字符數值+6 個數的數值必為 0,
      第{末字符數值+12(末字符數值+11<15)或末字符數值+11-14(末字符數值+11>=15)}個數字的數值必為7.
    由此可以看出,末位數對整個註冊碼具有標誌性作用,我們不妨試著修改一下,對於"1141111122222222" 而言,末位數為"2",因為2+8<15,故第11 (2+9) 位數為0.又從"第末字符數值+6 個數的數值必為0" 可知,第8 位數為0,因為2+11<15,故第14(2+12 ) 位數必為7.測試註冊碼可改為"1141111022022722"
    整理一下當末位數為0-9 時的註冊模型,如下:
    最後一位數 i 模型
    i=0 0xxxx0xx 0xx7xxx0
    i=1 x1xxxx0x x0xx7xx1
    i=2 xx4xxxx0 xx0xx7x2
    i=3 xxx9xxxx 0xx0xx73
    i=4 7xxx6xxx x0xx0xx4
    i=5 x7xxx5xx xx0xx0x5
    i=6 xx7xxx6x xxx0xx06
    i=7 0xx7xxx9 xxxx0xx7
    i=8 x0xx7xxx 4xxxx0x8
    i=9 xx0xx7xx x1xxxx09
    繼續吧,還有不少工作要做,越過第三個坎00413066,我們來到這裡,
    ┌──────────────────────────────────────────────┐
    │00413068 |.XOR EAX,EAX │
    │0041306A |.MOV ECX,10 │
    │0041306F |.NOP │
    │00413070 |>/MOVSX EDX,BYTE PTR DS:[ECX+EBX-1];EDX=第ECX 個數字│
    │00413075 |.|DEC ECX │
    │00413076 |.|IMUL EDX,ECX │
    │00413079 |.|ADD EAX,EDX ;@EAX=Sum{(數字的ACSII 值)*(其所在位置-1)} │
    │0041307B |.|TEST ECX,ECX │
    │0041307D |.\JNZ SHORT Reglo.00413070 │
    │0041307F |.LEA EDX,DWORD PTR DS:[EDI+E] ;EDI=末位數的數值│
    │00413082 |.CMP EDX,0F ;EDX=14(末位數為0 時) │
    │00413085 |.JL SHORT Reglo.0041308A ;必跳│
    │00413087 |.SUB EDX,0F │
    │0041308A |>MOVSX ECX,BYTE PTR DS:[EDX+EBX] ;EDX=末位數的數值+14-15(末位數不為0 時) │
    │;@ECX=第15 個數的ASCII(當末位數為0 時)或第末位數數值位數的ASCII 值(當末位數不為0 時) │
    │0041308E |.IMUL ECX,EDX ;ECX=@ECX*EDX │
    │00413091 |.SUB EAX,ECX ;EAX=@EAX-ECX │
    │00413093 |.DEC EDX │
    │00413094 |.JNS SHORT Reglo.00413099 ;結果為正就跳(SF=0) │
    │00413096 |.ADD EDX,0F │
    │00413099 |>MOV CL,BYTE PTR DS:[EDX+EBX] │
    │;@CL=第14 個數的ASCII(當末位數為0 時)或第末位數數值-1 位數的ASCII 值(當末位數不為0 時)│
    │0041309C |.MOVSX EDI,CL ; │
    │0041309F |.IMUL EDI,EDX ; │
    │004130A2 |.SUB EAX,EDI ;EAX=@EAX-@ECX*EDX-@CL*EDI │
    │004130A4 |.CDQ │
    │004130A5 |.MOV EDI,0A ;EDI=除數10 │
    │004130AA |.IDIV EDI │
    │004130AC |.ADD DL,30 ;DL=餘數的ASCII 值│
    │004130AF |.CMP DL,CL ;CL=第14 個數或第末位數數值-1 位數的ASCII │
    │004130B1 |.JNZ SHORT Reglo.004130C7 ;一跳就死│
    └──────────────────────────────────────────────┘
    一進來,就看見一個循環,不過這個循環倒也簡單,就是從尾到頭,將每個數字的ASCII 值與其(所在位置-1)相乘,並累加起來送給EAX,也就是說:@EAX =n1*0+n2*1+n3*2+n4*3+n5*4+...+n13*12+n14*13+n15*14+n16*15,循環完畢後,來到了0041307F,覺得有點奇怪,因為在這段代碼附近,沒看到EDI 是何時賦值的,仔細找了好一段代碼才發現,上次EDI 是在00412FC6 處賦值的,其值是末位數的數值,對當前測試註冊碼"1141111022022722" 而言,它就是"2".這段代碼的關鍵是004130AF,它事實上是要求:

    當末位數為 0 時,
    設置被除數 EAX=@EAX-n15*14-n14*13
    設置第 14 位數的數值為 EAX/10 的餘數

    當末位數為 1 時,
    設置被除數 EAX=@EAX-n1*0-n15*14=@EAX-n15*14
    設置第 15 位數的數值為 EAX/10 的餘數

    當末位數為 2 時,
    設置被除數 EAX=@EAX-n2*1-n1*0=@EAX-n2*1
    設置第 1 位數的數值為 EAX/10 的餘數

    當末位數不為 0,1,2 時,
    設置被除數 EAX=@EAX-ni*(i-1)-n(i-1)*(i-2)
    設置第i-1 位數的數值為EAX/10 的餘數,(其中,i=末位數數值)

    先在的問題是如何修改測試註冊碼,算出EAX,再求餘,顯然不是一個好辦法,解決方法是在4130AF 處下一個斷點,看OD 信息窗口中的DL,是什麼數字,再退出Reglo ,修改指定位置的數字為DL 中的值.例如,當前測試註冊碼,末位數是"2",故需要修改第1 位數為DL 中的值7,修改後,測試註冊碼變為" 7141111022022722",試試,呵呵,又過一關吶!如此以來,第四個坎也過了.之後,來到這裡,
    ┌──────────────────────────────────────────────┐
    │004130B3 PUSH ESI │
    │004130B4 PUSH 10 │
    ┌──────────────────────────────────────────────┐
    │004130B6 CALL Reglo.00412ED0 (5)│
    └──────────────────────────────────────────────┘
    │004130BB ADD ESP,8 │
    │004130BE TEST EAX,EAX │
    │004130C0 MOV EAX,1 │
    │004130C5 JNZ SHORT Reglo.004130C9 │
    │004130C7 MOV EAX,EBP │
    │004130C9 POP EDI │
    │004130CA POP ESI │
    │004130CB POP EBP │
    │004130CC POP EBX │
    │004130CD RETN │
    └──────────────────────────────────────────────┘
    這裡有個Call,應該是調用註冊成功的對話框吧,哈哈,迫不及待了,按F9 直接運行!什麼也沒彈出來,但仔細一看原來關於對話框中的兩行字符"THIS SOFTWARE IS NOT REGISTERED " 以及"Expiration date:08/01/11" 不見了,呵呵,真的註冊成功了!測試註冊碼"7141111022022722" 真的是註冊碼!
    高興歸高興,可是別得意忘形,凡事都要留個心眼.其實在寫這篇破文之前,我用的測試註冊碼不是"1111111122222222" 而是"0100000000070200",這個註冊碼和"7141111022022722" 一樣可以通過以上所有的坎,可是當你用它來註冊的時候,會讓Reglo 爆掉!不信你試試!為什麼呢?---其實原因很簡單,還有暗樁!在那裡?當然不能放過上面的那個Call (5) 了,F7 步入,到了這裡,
    ┌──────────────────────────────────────────────┐
    │00412ED0 /$SUB ESP,8 ; 指向註冊碼│
    │00412ED3 |.TEST EBX,EBX │
    │00412ED5 |.JNZ SHORT Reglo.00412EDD ; 這裡必跳│
    │00412ED7 |.XOR EAX,EAX │
    │00412ED9 |.ADD ESP,8 │
    │00412EDC |.RETN │
    │00412EDD |>CMP DWORD PTR SS:[ESP+C],-1 ; -1 是在堆棧中設定的某種標誌│
    │00412EE2 |.JNZ SHORT Reglo.00412EFD ; 必跳│
    │00412EE4 |.MOV EAX,EBX │
    │00412EE6 |.LEA EDX,DWORD PTR DS:[EAX+1] │
    │00412EE9 |.LEA ESP,DWORD PTR SS:[ESP] │
    │00412EF0 |>/MOV CL,BYTE PTR DS:[EAX] │
    │00412EF2 |.|INC EAX │
    │00412EF3 |.|TEST CL,CL │
    │00412EF5 |.\JNZ SHORT Reglo.00412EF0 │
    │00412EF7 |.SUB EAX,EDX │
    │00412EF9 |.MOV DWORD PTR SS:[ESP+C],EAX │
    │00412EFD |>MOV EAX,DWORD PTR SS:[ESP+C] │
    │00412F01 |.PUSH EBP │
    │00412F02 |.PUSH ESI │
    │00412F03 |.PUSH EDI │
    │00412F04 |.XOR EBP,EBP │
    │00412F06 |.XOR ESI,ESI │
    │00412F08 |.XOR EDI,EDI │
    │00412F0A |.TEST EAX,EAX │
    │00412F0C |.MOV DWORD PTR SS:[ESP+C],2 ; 12F38C 設置標誌2 │
    │00412F14 |.MOV DWORD PTR SS:[ESP+10],1 ; 12F390 設置標誌1 │
    │00412F1C |.JLE SHORT Reglo.00412F76 ; 一跳就死│
    │00412F1E |.MOV EDI,EDI ; EDI=0 │
    │00412F20 |>/MOVSX EAX,BYTE PTR DS:[EDI+EBX] ; 讀入註冊碼第EDI+1 位│
    │00412F24 |.|PUSH EAX │
    │00412F25 |.|CALL Reglo.00415ACF ; 判斷是否為0-9 的數字│
    │00412F2A |.|ADD ESP,4 │
    │00412F2D |.|TEST EAX,EAX ; 是,則EAX=4,否,則EAX=0 │
    │00412F2F |.|JE SHORT Reglo.00412F52 ; 若不是數字,就跳過本次循環│
    │00412F31 |.|MOVSX ECX,BYTE PTR DS:[EDI+EBX] ; 讀入註冊碼第EDI+1 位│
    │00412F35 |.|MOV EAX,DWORD PTR SS:[ESP+ESI*4+C] ; EAX=2(ESI=0 時)或EAX=1(ESI=1 時) │
    │00412F39 |.|SUB ECX,30 ; ECX=註冊碼第EDI+1 位的數值│
    │00412F3C |.|IMUL EAX,ECX │
    │; 奇數位用2 乘以當前位置數字的數值,偶數位用1 乘以當前位置數字的數值│
    │00412F3F |.|CMP EAX,0A │
    │00412F42 |.|JL SHORT Reglo.00412F47 │
    │00412F44 |.|SUB EAX,9 ; 如果EAX>=10,則EAX=EAX-9 │
    │00412F47 |>|XOR ​​EDX,EDX ; EBP=Sum(EAX) │
    │00412F49 |.|ADD EBP,EAX │
    │00412F4B |.|TEST ESI,ESI │
    │00412F4D |.|SETE DL ; 若ZF=1,則DL 置1 │
    │00412F50 |.|MOV ESI,EDX ; ESI=0(奇數位) 或1(偶數位) │
    │00412F52 |>|MOV EAX,DWORD PTR SS:[ESP+18] ; EAX=10h=16d │
    │00412F56 |.|INC EDI │
    │00412F57 |.|CMP EDI,EAX │
    │00412F59 |.\JL SHORT Reglo.00412F20 │
    │00412F5B |.TEST EBP,EBP │
    │00412F5D |.JE SHORT Reglo.00412F76 ; 一跳就死│
    │00412F5F |.MOV EAX,EBP ; 設置被除數│
    │00412F61 |.CDQ │
    │00412F62 |.IDIV DWORD PTR SS:[ESP+1C] │
    │;第末字符數值+3 個數的數值作為除數放在堆棧SS:[12F39C] 中│
    │00412F66 |.TEST EDX,EDX ; 測試餘數是否為0 │
    │00412F68 |.JNZ SHORT Reglo.00412F76 ; 一跳就死│
    │00412F6A |.POP EDI │
    │00412F6B |.POP ESI │
    │00412F6C |.MOV EAX,1 │
    │00412F71 |.POP EBP │
    │00412F72 |.ADD ESP,8 │
    │00412F75 |.RETN │
    │00412F76 |>POP EDI │
    │00412F77 |.POP ESI │
    │00412F78 |.XOR EAX,EAX │
    │00412F7A |.POP EBP │
    │00412F7B |.ADD ESP,8 │
    │00412F7E \.RETN │
    └──────────────────────────────────────────────┘
    這段代碼比較簡單,其主要過程是,在堆棧中設置兩個標誌2 和1,然後逐字讀入註冊碼,奇數位數字*2,偶數位數字*1,當奇數位數字*2 的值大於等於10 時,對其進行減9 操作,將各位結果累加到EBP 中,作為被除數.同時取出SS:[12F39C] 中的數字作為除數,只要整除成功,就可通過測試.但值得注意的是00412F62 處,關於除數的選擇,由於該處除數位於堆棧SS:[12F39C] 中,改變該堆棧值的操作是在004130B3 處的PUSH ESI 操作,而上一次改變ESI 是在0041302D 處,由此可知所謂的除數其實是:第末字符數值+3 個數的數值.所以測試註冊碼"7141111022022722",恰巧其第5(2+3) 個數是"1",所以可以整除,而最終註冊成功.但對於測試註冊碼"0100000000070200" 來說,其第3(0+3) 個數是"0",所以Reglo 發生了除0 操作,而崩潰了.

    至此,第五個坎也過了,軟件的註冊碼算法已基本清晰.可以寫總結了,但作為一個軟件從業人員,希望提醒大家,Reglo 竟然可以通過非法註冊碼導致軟件崩潰,說明其作者在驗證註冊碼的算法中存在Bug,也就是除零操作,這是一個軟件開發人員應盡量避免的低級錯誤,希望大家在自己的軟​​件中不要犯同樣的毛病.


【破解總結】:
———————————————————————————————————————————
    從Reglo 的註冊算法中,可以明確的感受到,"末位數"的軸心作用,幾乎每一步都涉及到末位數的操作,要么用作指定某位為操作位,要么用作固定某位的數值,要么用作判定某條件是否達成,這可能是Reglo 註冊算法的最大特色吧.
    對於Reglo v3.5a 的註冊碼的形制從現在的觀點來看,其模型可描述為:

     k 註冊碼模型
    k=0 0!#xx0xx 0xx7x?x0
    k=1 x1!#xx0x x0xx7x?1
    k=2 ?x4!#xx0 xx0xx7x2
    k=3 x?x9!#xx 0xx0xx73
    k=4 7x?x6!#x x0xx0xx4
    k=5 x7x?x5!# xx0xx0x5
    k=6 xx7x?x6! #xx0xx06
    k=7 0xx7x?x9 !#xx0xx7
    k=8 x0xx7x?x 4!#xx0x8
    k=9 xx0xx7x? x1!#xx09

    說明:
    k 表示末位數數值,
    x 表示 0-9 的數字,任選
    ! 位的數值必須大於等於 1
    ? 位的數值必須等於EAX 除10 所得的餘數,其中
      @EAX=n1*0+n2*1+n3*2+n4*3+n5*4+...+n13*12+n14*13+n15*14+n16*15 (ni 表示註冊碼第i 位的數值)
      當末位數為 0 時,EAX=@EAX-n15*14-n14*13
      當末位數為 1 時,EAX=@EAX-n15*14
      當末位數為 2 時,EAX=@EAX-n2*1
      當末位數為3-9 時,EAX=@EAX-ni*(i-1)-n(i-1)*(i-2)
    # 位的數值不能為0,且能被SUM 整除,其中:
      SUM=2*(n1+n3+n5+n7+n9+n11+n13+n15)-9*y+(n2+n4+n6+n8+n10+n12+n14+n16)
      (y 表示:大於等於 5 的奇數位的個數)

    那麼如何快速的得到一個註冊碼呢?
    根據上面的模型,首先選擇一個末位數,不妨選"9" 吧,其註冊碼模型為"xx0xx7x? x1!#xx09",對於x 部分可讓它們為"0",這樣註冊碼變為" 0000070? 01!#0009",對於# 位的數字來說,當# = "1" 時,一定可以被整除,故令#="1",對於! 位的數字來說,! 位恰好是第11 位,該位數字乘以10(11-1) 一定被10 整除,故對? 位沒有影響,所以可以令! 位的數字為1-9 中的任意一個,這樣註冊碼變為"0000070? 01510009",就剩最後一位了,0 這麼多,好辦,7*(6-1)+1*(10-1)+5*(11-1)+1*(12-1)+9 *(16-1)=35+9+50+11+135=240,240 Mod 10 = 0,故? 位為"0",這樣註冊碼就為"00000700 01510009".其實上面的# 位可以不參加運算,原因已經說過,這裡寫出來只是為了大家看著方便.

    當然使用註冊機KeyGen 是最方便的,不過我不想寫keygen,有興趣的朋友可以試試---我本來的目的也不是破解.又抽空看了看Reglo 的姊妹軟件Deletor,呵呵,註冊算法很相近呀,不過把註冊失敗的對話框彈出方式變為了MessageBoxW,註冊碼也變為了UniCode,新手們可以用它的姊妹軟件進行練手,是成長的不錯選擇.

    哎,終於完了!神馬都是浮雲!

2011年8月5日星期五

PESpin 1.33全保護脫殼筆記

【文章作者】: Nerin
【加殼方式】: PESpin
【使用工具】: OllyIce PEID LordPE ImportRec
【操作平台】: Windows XP SP2
【作者聲明】: 只是感興趣,沒有其他目的。失誤之處敬請諸位大俠賜教!
-------------------------------------------------- ------------------------------
【詳細過程】
  PEID查殼,顯示為“PESpin 0.3x - 1.xx -> cyberbob”。雙擊文件運行,打開任務管理器發現有兩個進程,我們脫殼的第一步就是要去掉雙進程保護。用OllyIce載入文件,開始我們的脫殼之旅。
  單步F7幾次之後,發現了我們很熟悉的幾條指令:
  
代碼:
  0041C0D7 60 PUSHAD
  0041C0D8 E8 00000000 CALL UnPackMe.0041C0DD
  0041C0DD 8B1C24 MOV EBX,DWORD PTR SS:[ESP]
  0041C0E0 83C3 12 ADD EBX,12
  
  這裡我們使用ESP定律下一個硬件斷點,然後Ctrl+G輸入CreateMutexA,解除雙進程守護。我們在函數末尾的Retn 0C處下斷點,因為殼會判斷這個函數的頭部是否有斷點。運行之後我們Alt+F9返回,停在這裡:
  
  
代碼:
  0041FE1E 8985 6E6E4000 MOV DWORD PTR SS:[EBP+406E6E],EAX
  0041FE24 8D85 E2281F03 LEA EAX,DWORD PTR SS:[EBP+31F28E2]
  0041FE2A 2D FCCEDE02 SUB EAX,2DECEFC
  0041FE2F FF10 CALL DWORD PTR DS:[EAX]
  0041FE31 BB CA7DB9FE MOV EBX,FEB97DCA
  0041FE36 81EB 137DB9FE SUB EBX,FEB97D13
  0041FE3C 3BC3 CMP EAX,EBX
  0041FE3E 9C PUSHFD ★走到這裡將ZF標誌位改成1
  0041FE3F C12C24 06 SHR DWORD PTR SS:[ESP],6
  0041FE43 F71424 NOT DWORD PTR SS:[ESP]
  0041FE46 832424 01 AND DWORD PTR SS:[ESP],1
  0041FE4A 58 POP EAX
  0041FE4B 2BD2 SUB EDX,EDX
  0041FE4D BB BAE74D02 MOV EBX,24DE7BA
  0041FE52 81EB 86E74D02 SUB EBX,24DE786
  0041FE58 F7E3 MUL EBX
  0041FE5A 81CB FE12F40E OR EBX,0EF412FE
  0041FE60 8​​D8428 4E0E91ED LEA EAX,DWORD PTR DS:[EAX+EBP+ED910E4E]
  0041FE67 2D 179B50ED SUB EAX,ED509B17
  0041FE6C FFE0 JMP EAX ★父子進程的分水嶺
  
  經過0041FE6C處的跳轉指令之後,我們來到這裡:
  
代碼:
  0041FE85 F1 INT1
  0041FE86 E8 1C030000 CALL UnPackMe.004201A7
  0041FE8B 85C0 TEST EAX,EAX
  0041FE8D 75 23 JNZ SHORT UnPackMe.0041FEB2
  0041FE8F 8BC3 MOV EAX,EBX
  0041FE91 35 08001F0E XOR EAX,0E1F0008
  0041FE96 C3 RETN
  
  看堆棧顯示:
  
引用:
  0012FFA0 0041C51B RETURN to UnPackMe.0041C51B
  0012FFA4 7C930738 ntdll.7C930738
  
  我們直接到0041C51B新建EIP,同時注意修改ESP寄存器的值。
  
代碼:
  0041C51B B8 4AAC1C95 MOV EAX,951CAC4A ★此處新建EIP
  0041C520 2BC9 SUB ECX,ECX
  0041C522 83C9 15 OR ECX,15
  0041C525 0FA3C8 BT EAX,ECX
  0041C528 0F83 81000000 JNB UnPackMe.0041C5AF
  
  在這裡搜索二進製字符串“F1 87 DF”,找到這裡:
  
代碼:
  004207AD F1 INT1
  004207AE 87DF XCHG EDI,EBX
  004207B0 57 PUSH EDI
  004207B1 C3 RETN
  
  在004207AD這裡下一個斷點,shift+F9停下來,同樣看堆棧新建EIP,調整堆棧。
  
代碼:
  0041D6D4 /EB 04 JMP SHORT UnPackMe.0041D6DA ★新建EIP
  0041D6D6 |7A EB JPE SHORT UnPackMe.0041D6C3
  0041D6D8 |04 9A ADD AL,9A
  0041D6DA ^\EB FB JMP SHORT UnPackMe.0041D6D7
  0041D6DC FFF6 PUSH ESI
  
  到這裡我們就已經處理好了雙進程保護,下面開始處理API Redirection。 Ctrl+G輸入GetVersion,在函數頭部下一個硬件
  訪問斷點,然後F9找到對API進行重定向的地方。
  
代碼:
  0041CE25 F3:A4 REP MOVS BYTE PTR ES:[EDI],BYTE PTR DS:[ESI]
  0041CE27 8BC6 MOV EAX,ESI
  0041CE29 8BF7 MOV ESI,EDI
  0041CE2B 5F POP EDI
  
  走到這裡要注意了,快到關鍵的地方了,刪除硬件斷點,然後單步跟踪。
  
代碼:
  0041CD64 83FA 12 CMP EDX,12
  0041CD67 73 7B JNB SHORT UnPackMe.0041CDE4 ★直接跳過這裡的循環
  0041CD69 8B18 MOV EBX,DWORD PTR DS:[EAX]
  0041CD6B EB 07 JMP SHORT UnPackMe.0041CD74
  
  繼續F7往下走:
  
代碼:
  0041D08D /0F84 92000000 JE UnPackMe.0041D125 ★這裡的循環也直接跳
  0041D093 |47 INC EDI
  0041D094 |EB 01 JMP SHORT UnPackMe.0041D097
  
  來到這裡:
  
代碼:
  0041D1A0 8907 MOV DWORD PTR DS:[EDI],EAX ★這裡就是關鍵了,在這裡下一個硬件執行斷點
  0041D1A2 EB 02 JMP SHORT UnPackMe.0041D1A6
  0041D1A4 02F5 ADD DH,CH
  0041D1A6 F9 STC
  0041D1A7 72 08 JB SHORT UnPackMe.0041D1B1
  0041D1A9 73 0E JNB SHORT UnPackMe.0041D1B9
  0041D1AB - E9 83042417 JMP 1765D633
  0041D1B0 C3 RETN
  
  走到這裡我們看一下堆棧:
  
引用:
  $-34 > 7C8114BA kernel32.7C8114BA ★函數的地址
  $-30 > 0000000F ★被偷的opcode數量
  $-2C > 00000000
  $-28 > 00000007
  $-24 > 0041CDDC UnPackMe.0041CDDC
  $-20 > 0041E4AD UnPackMe.0041E4AD
  $-1C > 00407A81 UnPackMe.00407A81
  $-18 > 004076D8 UnPackMe.004076D8
  $-14 > 0012FF94
  $-10 > 0012FF94
  $-C > 00007060
  $-8 > 00D50180
  $-4 > 0041D07F UnPackMe.0041D07F
  $ ==> > 0041D8A8 UnPackMe.0041D8A8
  
  我們找一塊空的地方,打上我們的補丁代碼,這樣就繞過了殼的API重定向:
  
代碼:
  mov eax,dword ptr [esp-34]
  sub eax,dword ptr [esp-30]
  mov dword ptr [edi],eax
  jmp 0041D1A6
  
  API處理完之後,我們刪除那個硬件執行斷點,F9快速到達偽OEP處,單步跟踪來補上被偷的OEP代碼。
  
代碼:
  0041DCFA F7D2 NOT EDX ★來到這裡
  0041DCFC 39C2 CMP EDX,EAX
  0041DCFE F7C0 74E7F921 TEST EAX,21F9E774
  0041DD04 0FACC2 48 SHRD EDX,EAX,48 ; Shift constant out of range 1..31
  0041DD08 0FBDC8 BSR ECX,EAX
  0041DD0B C7C2 2431C7CD MOV EDX,CDC73124
  0041DD11 85C0 TEST EAX,EAX
  0041DD13 0FBAEA 31 BTS EDX,31
  0041DD17 F7D2 NOT EDX
  0041DD19 F7C1 25C4A65C TEST ECX,5CA6C425
  0041DD1F 3BD0 CMP EDX,EAX
  0041DD21 0FABC2 BTS EDX,EAX
  0041DD24 EB 01 JMP SHORT UnPackMe.0041DD27
  
  0041DD3C 55 PUSH EBP ★第一句被偷的代碼
  0041DD3D EB 01 JMP SHORT UnPackMe.0041DD40
  
  下面就單步跟踪,就不一一列舉過程了,大家可以自己動下手,這裡我就直接貼出來被偷的OEP代碼了:
  
引用:
  0041DD3C 55 PUSH EBP ; 1
  
  0041DD40 8BEC MOV EBP,ESP ; 2
  
  0041DD45 6A FF PUSH -1 ; 3
  
  push 00407190
  push 00403368
  
  0041DD62 64:A1 00000000 MOV EAX,DWORD PTR FS:[0]
  
  0041DD6B 50 PUSH EAX
  
  0041DD6F 64:8925 00000000 MOV DWORD PTR FS:[0],ESP
  
  0041DD79 83EC 58 SUB ESP,58
  
  0041DD7F 53 PUSH EBX
  
  0041DD83 56 PUSH ESI
  
  0041DD87 57 PUSH EDI ; ntdll.7C930738
  
  0041DD8B 8965 E8 MOV DWORD PTR SS:[EBP-18],ESP
  
  0041DD91 FF15 D20E4200 CALL DWORD PTR DS:[420ED2] ;GetVersion
  
  0041DD9A 33D2 XOR EDX,EDX
  
  0041DD9F 8AD4 MOV DL,AH
  
  0041DDA4 8915 34AD4000 MOV DWORD PTR DS:[40AD34],EDX
  
  0041DDAD 8BC8 MOV ECX,EAX
  
  0041DDB2 81E1 FF000000 AND ECX,0FF
  
  0041DDBB 890D 30AD4000 MOV DWORD PTR DS:[40AD30],ECX
  
  0041DDC4 C1E1 08 SHL ECX,8
  
  二進制opcode如下:
  
  55 8B EC 6A FF 68 90 71 40 0​​0 68 68 33 40 0​​0 64 A1 00 00 00 00 50 64 89 25 00 00 00 00 83 EC 58
  53 56 57 89 65 E8 FF 15 D2 0E 42 00 33 D2 8A D4 89 15 34 AD 40 0​​0 8B C8 81 E1 FF 00 00 00 89 0D
  30 AD 40 0​​0 C1 E1 08
  
  補上之後直接在00402598處新建EIP即可。下面我們開始處理Code Redirection,往下隨便找一句看看:
  
代碼:
  004026A0 - E9 9FDBFFFF JMP UnPackMe.00400244
  004026A5 90 NOP
  004026A6 90 NOP
  004026A7 8DC0 LEA EAX,EAX ; Illegal use of register
  
  00400244 833D 20AD4000 01 CMP DWORD PTR DS:[40AD20],1
  0040024B - E9 57240000 JMP UnPackMe.004026A7
  
  我們記下00400244處的代碼,然後再補回去就好了,但是這裡的工作量比較大,所以我就用網上的一個腳本來修復了。修
  复完畢之後,我們用LordPE抓取鏡像(★記得糾正鏡像大小),打開ImportRec,填入OEP:2598,在空白處右鍵,選擇高
  級命令->獲取API調用,然後剪切掉無效的指針就可以得到一份完整的API了,直接修復dump。下面就要修復nanomites了,
  這個我們可以直接用工具進行修復。修復完畢之後雙擊脫殼文件,沒有反應,⊙﹏⊙b 我們重新載入脫殼文件,單步跟踪
  發現問題出現在這裡:
  
代碼:
  0040264F |. E8 2D030000 CALL tuowen_.00402981
  
  00402981 $- E9 AAD9FFFF JMP tuowen_.00400330
  00402986 90 NOP
  00402987 90 NOP
  
  00400330 0000 ADD BYTE PTR DS:[EAX],AL ★這裡看來是腳本沒有修復好,我們手工修復一下好了
  
這裡是我修復的方案,大家可以參考一下:
  
代碼:
  00400330 833D C8B34000 0>CMP DWORD PTR DS:[40B3C8],0
  00400337 - E9 18230000 JMP dumped_1.00402654
  
  這裡修復完畢之後保存,雙擊已經可以看到界面了,但是點擊OK按鈕之後會崩潰,下面我們就來修復SDK保護吧。 O(∩_∩)O~
  用OD載入,F9跑起來,然後點擊OK按鈕之後,發現造成崩潰的地方在這裡。
  
代碼:
  00401160 $​​ 55 PUSH EBP ★我們要修復這裡的代碼
  00401161 . 8BEC MOV EBP,ESP
  00401163 . 81EC 60020000 SUB ESP,260
  00401169 . 53 PUSH EBX
  0040116A . 56 PUSH ESI
  0040116B . 57 PUSH EDI
  0040116C . FF15 8F124200 CALL DWORD PTR DS:[42128F]
  00401172 . B2 1E MOV DL,1E
  00401174 . 57 PUSH EDI
  00401175 . 6F OUTS DX,DWORD PTR ES:[EDI] ; I/O command
  00401176 . DBBF 81D54987 FSTP TBYTE PTR DS:[EDI+8749D581]
  0040117C C5 DB C5
  0040117D . C8 08118B ENTER 1108,8B
  00401181 . C3 RETN
  
  我們帶殼調試源文件,然後看看對應的代碼應該是什麼樣子,然後再補上去就可以恢復那個按鈕的功能了。由於這裡只有
  一處,所以比較簡單:
  
代碼:
  00401160 55 PUSH EBP
  00401161 8BEC MOV EBP,ESP
  00401163 81EC 60020000 SUB ESP,260
  00401169 53 PUSH EBX
  0040116A 56 PUSH ESI
  0040116B 57 PUSH EDI
  0040116C FF15 8F124200 CALL DWORD PTR DS:[42128F]
  
  此時DS:[0042128F]=00D30000,看來源文件是跑到殼臨時申請的空間裡面去直接代碼解碼了,我們跟進去之後,把代碼記
  下來,然後找個空白的地方把代碼補上即可,下面是我自己補的代碼,貼出來僅供大家參考下O(∩_∩)O~:
  
代碼:
  00401160 $​​ 55 PUSH EBP
  00401161 . 8BEC MOV EBP,ESP
  00401163 . 81EC 60020000 SUB ESP,260
  00401169 . 53 PUSH EBX
  0040116A . 56 PUSH ESI
  0040116B . 57 PUSH EDI
  0040116C . 90 NOP
  0040116D . E8 CC650000 CALL dumped_2.0040773E ★到空白的地方去解碼
  
  
  0040773E 9C PUSHFD
  0040773F 60 PUSHAD
  00407740 8B4424 24 MOV EAX,DWORD PTR SS:[ESP+24]
  00407744 8B08 MOV ECX,DWORD PTR DS:[EAX]
  00407746 8D78 04 LEA EDI,DWORD PTR DS:[EAX+4]
  00407749 897C24 24 MOV DWORD PTR SS:[ESP+24],EDI
  0040774D 81E9 BC1D576F SUB ECX,6F571DBC
  00407753 FC CLD
  00407754 8A07 MOV AL,BYTE PTR DS:[EDI]
  00407756 2C B2 SUB AL,0B2
  00407758 F9 STC
  00407759 32C1 XOR AL,CL
  0040775B F8 CLC
  0040775C 34 98 XOR AL,98
  0040775E C0C0 CE ROL AL,0CE ; Shift constant out of range 1..31
  00407761 34 41 XOR AL,41
  00407763 C0C8 06 ROR AL,6
  00407766 2AC1 SUB AL,CL
  00407768 34 EF XOR AL,0EF
  0040776A C0C8 9E ROR AL,9E ; Shift constant out of range 1..31
  0040776D 32C1 XOR AL,CL
  0040776F C0C8 8B ROR AL,8B ; Shift constant out of range 1..31
  00407772 02C1 ADD AL,CL
  00407774 04 F6 ADD AL,0F6
  00407776 04 6B ADD AL,6B
  00407778 AA STOS BYTE PTR ES:[EDI]
  00407779 49 DEC ECX
  0040777A ^ 75 D8 JNZ SHORT dumped_2.00407754
  0040777C 61 POPAD
  0040777D 9D POPFD
  0040777E C3 RETN
  
  補上這裡的代碼之後保存,再次打開發現OK按鈕的功能也正常了,PEID再掃描下,顯示“Microsoft Visual C++ 6.0”看
  來我們已經成功脫殼了( ⊙o⊙ )
  
-------------------------------------------------- ------------------------------
【經驗總結】
  脫這個殼的方法總得來說還是比較傳統的,如果能跟踪一下殼的流程,相信能學到不少東西。這篇文章是匆忙寫成的,有錯誤的地方希望大家能指正。另外我這裡修復SDK的方法還不能夠跨平台,如果有哪位大俠有更好的方法的話,希望能夠給出給我們學習學習。

2011年8月4日星期四

sysmanager.exe逆向分析

作 者: 古越魂
時 間: 2011-07-15,20:23:50

這幾天不知道為啥子電腦速度特別慢,而且有些網頁打開後就直接將瀏覽器關閉了,搞得我很鬱悶.用殺毒軟件查殺,結果啥都沒查出來...於是很糾結,還是手動殺毒比較靠譜...因為這些網頁打開的都有些破解,逆向等字眼,所以就懷疑它是根據關鍵字來進行工作的,於是新建了一個“逆向破解.txt”,用記事本打開,果然:一打開它就關閉了,換editplus打開,結果還是一樣,而換了文件名打開就正常了.
      因為對於每個進程都會產生這種情況,所以初步懷疑是dll注入,可對照兩個進程的dll,除了系統的dll,貌似也沒啥子問題,而注入系統dll的可能性不大,所以還是另尋他法吧。
     將電腦在安全模式下打開,觀察進程列表,然後電腦正常模式下打開,觀察進程列表,然後再進行一個一個排除。
      經過反复的測試,確定是sysmanager.exe文件的問題,關閉進程,結果能正常運行。結果百度了下該進程,果然是個病毒木馬程序,然後清理註冊表,刪除程序,ok,殺毒完畢! ! !
     本來到這裡都應該結束了,但是一時也沒啥事幹,而且這東西搞得我鬱悶無比! ! !所以還是決定分析下。
    PEiD查殼:Microsoft Visual C++ 6.0
    這是一個好消息,接著就IDA + OD進行分析吧.

代碼:
int __stdcall WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
.text:004014F0 _WinMain@16 proc near ; CODE XREF: start+12Fp
.text:004014F0
.text:004014F0 ServiceStartTable= SERVICE_TABLE_ENTRYA ptr -10h
.text:004014F0 var_8 = dword ptr -8
.text:004014F0 var_4 = dword ptr -4
.text:004014F0 hInstance = dword ptr 4
.text:004014F0 hPrevInstance = dword ptr 8
.text:004014F0 Str1 = dword ptr 0Ch
.text:004014F0 nShowCmd = dword ptr 10h
.text:004014F0
.text:004014F0 sub esp, 10h
.text:004014F3 call sub_401000
.text:004014F8 call ds:GetCurrentThreadId
.text:004014FE mov dword_403068, eax
.text:00401503 xor eax, eax
.text:00401505 mov [esp+10h+var_8], eax
.text:00401509 mov [esp+10h+var_4], eax
.text:0040150D mov eax, [esp+10h+Str1]
//判斷是否為刪除指令,如果是,那麼刪除服務,卸載程序
.text:00401511 push offset Str2 ; "/uninstall"
.text:00401516 push eax ; Str1
.text:00401517 mov [esp+18h+ServiceStartTable.lpServiceName], offset ServiceName ; "SystemManager"
.text:0040151F mov [esp+18h+ServiceStartTable.lpServiceProc], offset loc_401450
.text:00401527 call ds:_stricmp
.text:0040152D add esp, 8
.text:00401530 test eax, eax
.text:00401532 jnz short loc_401541
.text:00401534 call sub_401220
.text:00401539 x​​or eax, eax
.text:0040153B add esp, 10h
.text:0040153E retn 10h
.text:00401541 ; --------------------------------------------- ------------------------------
.text:00401541
.text:00401541 loc_401541: ; CODE XREF: WinMain(x,x,x,x)+42j
.text:00401541 call sub_401040
.text:00401546 lea ecx, [esp+10h+ServiceStartTable]
//函數StartServiceCtrlDispatcherA的利用
//對於每一個新建的進程,創建一個線程,進行檢測操作
.text:0040154A push ecx ; lpServiceStartTable
.text:0040154B call ds:StartServiceCtrlDispatcherA
.text:00401551 xor eax, eax
.text:00401553 add esp, 10h
.text:00401556 retn 10h
.text:00401556 _WinMain@16 endp



typedef struct _SERVICE_TABLE_ENTRY {
  LPTSTR lpServiceName;
  LPSERVICE_MAIN_FUNCTION lpServiceProc;
} SERVICE_TABLE_ENTRY,
lpServiceProc = 401450


//將sysmanager.exe複製到系統目錄,並創建啟動服務
.text:00401040 sub esp, 220h
.text:00401046 lea eax, [esp+220h+NewFileName]
.text:0040104A push ebx
.text:0040104B push esi
.text:0040104C push edi
.text:0040104D push 100h ; uSize
.text:00401052 push eax ; lpBuffer
.text:00401053 call ds:GetSystemDirectoryA
.text:00401059 mov edi, offset aSysmanager_exe ; "\\sysmanager.exe"
.text:0040105E or ecx, 0FFFFFFFFh
.text:00401061 xor eax, eax
.text:00401063 lea edx, [esp+22Ch+NewFileName]
.text:00401067 repne scasb
.text:00401069 not ecx
.text:0040106B sub edi, ecx
.text:0040106D push 104h ; nSize
.text:00401072 mov esi, edi
.text:00401074 mov ebx, ecx
.text:00401076 mov edi, edx
.text:00401078 or ecx, 0FFFFFFFFh
.text:0040107B repne scasb
.text:0040107D mov ecx, ebx
.text:0040107F dec edi
.text:00401080 shr ecx, 2
.text:00401083 rep movsd
.text:00401085 mov ecx, ebx
.text:00401087 lea eax, [esp+230h+ExistingFileName]
.text:0040108E and ecx, 3
.text:00401091 push eax ; lpFilename
.text:00401092 rep movsb
.text:00401094 push 0 ; hModule
//獲取自身程序名
.text:00401096 call ds:GetModuleFileNameA
.text:0040109C lea ecx, [esp+22Ch+NewFileName]
.text:004010A0 push 0 ; bFailIfExists
.text:004010A2 lea edx, [esp+230h+ExistingFileName]
.text:004010A9 push ecx ; lpNewFileName
.text:004010AA push edx ; lpExistingFileName
//複製文件
.text:004010AB call ds:CopyFileA
.text:004010B1 test eax, eax
.text:004010B3 jnz short loc_4010BF
.text:004010B5 pop edi
.text:004010B6 pop esi
.text:004010B7 pop ebx
.text:004010B8 add esp, 220h
.text:004010BE retn
.text:004010BF ; --------------------------------------------- ------------------------------
.text:004010BF
.text:004010BF loc_4010BF: ; CODE XREF: sub_401040+73j
.text:004010BF push 0F003Fh ; dwDesiredAccess
.text:004010C4 push 0 ; lpDatabaseName
.text:004010C6 push 0 ; lpMachineName
//打開服務管理
.text:004010C8 call ds:OpenSCManagerA
.text:004010CE test eax, eax
.text:004010D0 mov hSCObject, eax
.text:004010D5 jnz short loc_4010E1
.text:004010D7 pop edi
.text:004010D8 pop esi
.text:004010D9 pop ebx
.text:004010DA add esp, 220h
.text:004010E0 retn
.text:004010E1 ; --------------------------------------------- ------------------------------
.text:004010E1
.text:004010E1 loc_4010E1: ; CODE XREF: sub_401040+95j
.text:004010E1 push 0 ; lpPassword
.text:004010E3 push 0 ; lpServiceStartName
.text:004010E5 push offset Dependencies ; lpDependencies
.text:004010EA push 0 ; lpdwTagId
.text:004010EC lea ecx, [esp+23Ch+NewFileName]
.text:004010F0 push 0 ; lpLoadOrderGroup
.text:004010F2 push ecx ; lpBinaryPathName
.text:004010F3 mov ebx, 2
.text:004010F8 push 1 ; dwErrorControl
.text:004010FA push ebx ; dwStartType
.text:004010FB push 110h ; dwServiceType
.text:00401100 push 0F01FFh ; dwDesiredAccess
.text:00401105 push offset ServiceName ; "SystemManager"
.text:0040110A push offset ServiceName ; "SystemManager"
.text:0040110F push eax ; hSCManage
//創建服務
.text:00401110 call ds:CreateServiceA
.text:00401116 mo​​v esi, ds:GetLastError
.text:0040111C mov dword_403060, eax
.text:00401121 test eax, eax
.text:00401123 jnz short loc_40117C
.text:00401125 call esi ; GetLastError
.text:00401127 cmp eax, 431h
.text:0040112C jz short loc_401147
.text:0040112E mov edx, hSCObject
.text:00401134 push edx ; hSCObject
.text:00401135 call ds:CloseServiceHandle
.text:0040113B pop edi
.text:0040113C pop esi
.text:0040113D xor eax, eax
.text:0040113F pop ebx
.text:00401140 add esp, 220h
.text:00401146 retn
.text:00401147 ; --------------------------------------------- ------------------------------
.text:00401147
.text:00401147 loc_401147: ; CODE XREF: sub_401040+ECj
.text:00401147 mov eax, hSCObject
.text:0040114C push 10h ; dwDesiredAccess
.text:0040114E push offset ServiceName ; "SystemManager"
.text:00401153 push eax ; hSCManager
//打開服務
.text:00401154 call ds:OpenServiceA
.text:0040115A test eax, eax
.text:0040115C mov dword_403060, eax
.text:00401161 jnz short loc_40117C
.text:00401163 mov ecx, hSCObject
.text:00401169 push ecx ; hSCObject
.text:0040116A call ds:CloseServiceHandle
.text:00401170 pop edi
.text:00401171 pop esi
.text:00401172 xor eax, eax
.text:00401174 pop ebx
.text:00401175 add esp, 220h
.text:0040117B retn


//刪除服務
sub_401220 proc near ; CODE XREF: WinMain(x,x,x,x)+44p
.text:00401220 push esi
.text:00401221 push 0F003Fh ; dwDesiredAccess
.text:00401226 push 0 ; lpDatabaseName
.text:00401228 push 0 ; lpMachineName
.text:0040122A call ds:OpenSCManagerA
.text:00401230 test eax, eax
.text:00401232 mov hSCObject, eax
.text:00401237 jz loc_4012C3
.text:0040123D push 0F01FFh ; dwDesiredAccess
.text:00401242 push offset ServiceName ; "SystemManager"
.text:00401247 push eax ; hSCManager
.text:00401248 call ds:OpenServiceA
.text:0040124E test eax, eax
.text:00401250 mov dword_403060, eax
.text:00401255 jz short loc_4012C3
.text:00401257 push offset ServiceStatus ; lpServiceStatus
.text:0040125C push eax ; hService
.text:0040125D call ds:QueryServiceStatus
.text:00401263 cmp ServiceStatus.dwCurrentState, 4
.text:0040126A jnz short loc_40127F
.text:0040126C mov eax, dword_403060
.text:00401271 push offset ServiceStatus ; lpServiceStatus
.text:00401276 push 1 ; dwControl
.text:00401278 push eax ; hService
.text:00401279 call ds:ControlService
.text:0040127F
.text:0040127F loc_40127F: ; CODE XREF: sub_401220+4Aj
.text:0040127F mov ecx, dword_403060
.text:00401285 push ecx ; hService
.text:00401286 call ds:DeleteService
.text:0040128C test eax, eax
.text:0040128E jz short loc_4012AB
.text:00401290 mov edx, hSCObject
.text:00401296 mov esi, ds:CloseServiceHandle
.text:0040129C push edx ; hSCObject
.text:0040129D call esi ; CloseServiceHandle
.text:0040129F mov eax, dword_403060
.text:004012A4 push eax ; hSCObject
.text:004012A5 call esi ; CloseServiceHandle
.text:004012A7 xor eax, eax
.text:004012A9 pop esi
.text:004012AA retn



xt:00401450 loc_401450: ; DATA XREF: WinMain(x,x,x,x)+2Fo
.text:00401450 push offset sub_4012D0
.text:00401455 push offset ServiceName ; "SystemManager"
.text:0040145A mov ServiceStatus.dwCurrentState, 2
.text:00401464 mov ServiceStatus.dwControlsAccepted, 3
.text:0040146E call ds:RegisterServiceCtrlHandlerA
.text:00401474 test eax, eax
.text:00401476 mov hServiceStatus, eax
.text:0040147B jz short locret_4014E4
.text:0040147D push esi
.text:0040147E mov esi, ds:SetServiceStatus
.text:00401484 push offset ServiceStatus
.text:00401489 push eax
.text:0040148A call esi ; SetServiceStatus
.text:0040148C mov eax, hServiceStatus
.text:00401491 push offset ServiceStatus
.text:00401496 push eax
.text:00401497
.text:00401497 __cfltcvt_init:
.text:00401497 mov ServiceStatus.dwWin32ExitCode, 0
.text:004014A1 mov ServiceStatus.dwCheckPoint, 0
.text:004014AB mov ServiceStatus.dwWaitHint, 0
.text:004014B5 mov ServiceStatus.dwCurrentState, 4
.text:004014BF call esi ; SetServiceStatus
.text:004014C1 push 0
.text:004014C3 push 0
.text:004014C5 push 0
.text:004014C7 push offset sub_401380
.text:004014CC push 0
.text:004014CE push 0
.text:004014D0 call ds:CreateThread
.text:004014D6 test eax, eax
.text:004014D8 pop esi
.text:004014D9 jz short locret_4014E4
.text:004014DB push 0FFFFFFFFh
.text:004014DD push eax
.text:004014DE call ds:WaitForSingleObject
.text:004014E4
.text:004014E4 locret_4014E4: ; CODE XREF: .text:0040147Bj
.text:004014E4 ; .text:004014D9j
.text:004014E4 retn


對於每個新建的進程,都創建一個線程,線程函數地址:sub_401380

sub_401380 proc near ; DATA XREF: .text:004014C7o
.text:00401380
.text:00401380 String = byte ptr -104h
.text:00401380
.text:00401380 sub esp, 104h
.text:00401386 push ebx
.text:00401387 mov ebx, ds:_mbsstr
.text:0040138D push esi
.text:0040138E push edi
.text:0040138F
.text:0040138F loc_40138F: ; CODE XREF: sub_401380+47j
.text:0040138F ; sub_401380+BEj
.text:0040138F push 3E8h ; dwMilliseconds
.text:00401394 call ds:Sleep
.text:0040139A xor eax, eax
.text:0040139C mov ecx, 19h
.text:004013A1 mov edi, offset dword_403094
.text:004013A6 push eax ; lParam
.text:004013A7 rep stosd
.text:004013A9 push offset EnumFunc ; lpEnumFunc
.text:004013AE mov dword_403224, 0
.text:004013B8 call ds:EnumWindows //枚舉窗口
.text:004013BE mov eax, dword_403224
.text:004013C3 xor edi, edi
.text:004013C5 test eax, eax
.text:004013C7 jle short loc_40138F
.text:004013C9 mov esi, offset dword_403094
.text:004013CE
.text:004013CE loc_4013CE: ; CODE XREF: sub_401380+BCj
.text:004013CE mov eax, [esi]
.text:004013D0 test eax, eax
.text:004013D2 jz short loc_401431
.text:004013D4 lea ecx, [esp+110h+String]
.text:004013D8 push 80h ; nMaxCount
.text:004013DD push ecx ; lpString
.text:004013DE push eax ; hWnd
.text:004013DF call ds:GetWindowTextA //獲取窗口文本
.text:004013E5 test eax, eax
.text:004013E7 jz short loc_401431
.text:004013E9 lea edx, [esp+110h+String]
.text:004013ED push offset unk_403040
.text:004013F2 push edx
.text:004013F3 call ebx ; _mbsstr
.text:004013F5 add esp, 8
.text:004013F8 test eax, eax
.text:004013FA jnz short loc_401422
.text:004013FC lea eax, [esp+110h+String]
.text:00401400 push offset aI ; "專用"
.text:00401405 push eax
.text:00401406 call ebx ; _mbsstr //檢測關鍵字:專用
.text:00401408 add esp, 8
.text:0040140B test eax, eax
.text:0040140D jnz short loc_401422
.text:0040140F lea ecx, [esp+110h+String]
.text:00401413 push offset aT ; "破解"
.text:00401418 push ecx
.text:00401419 call ebx ; _mbsstr //檢測關鍵字:破解
.text:0040141B add esp, 8
.text:0040141E test eax, eax
.text:00401420 jz short loc_401431
.text:00401422
.text:00401422 loc_401422: ; CODE XREF: sub_401380+7Aj
.text:00401422 ; sub_401380+8Dj
.text:00401422 mov edx, [esi]
.text:00401424 push 0 ; lParam
.text:00401426 push 0 ; wParam
.text:00401428 push 10h ; Msg
.text:0040142A push edx ; hWnd
.text:0040142B call ds:SendMessageA //發送消息,關閉進程
.text:00401431
.text:00401431 loc_401431: ; CODE XREF: sub_401380+52j
.text:00401431 ; sub_401380+67j ...
.text:00401431 mov eax, dword_403224
.text:00401436 inc edi
.text:00401437 add esi, 4
.text:0040143A cmp edi, eax
.text:0040143C jl short loc_4013CE
.text:0040143E jmp loc_40138F
.text:0040143E sub_401380 endp
一些感觸:
到這裡就分析完畢了,終於知道啥原因了,同時也學了個函數StartServiceCtrlDispatcherA,這是一個好函數,用來做文件監控真是再好不過了,而且可以對一些關鍵字進行過濾,假如:有關破解逆向的網頁全部都被屏蔽掉了.好東西,學習了

2011年8月3日星期三

IDA + Bochs 調試器插件進行PE+ 格式DLL脫殼

By :obaby 

在IDA Pro6.1中我們擴展了Bochs調試器插件,現在已經可以進行64位代碼段的調試。在IDA Pro 6.2版本中將有可能實現PE+ 可執行程序的動態調試。由於程序將會在Bochs系統中執行,因而在調試的過程中我們並不需要實際的64位操作系統,因而在實際的調試過程中可以從任何的32位或者64位的Linux,Mac OS 或者Windows操作系統中使用IDA Pro進行64位可執行文件的調試。
為了確認這一項新的功能,我們將進行PE+格式的一個木馬程序進行脫殼並且進行一個大體的分析,這個文件是由MATCODE Software公司的mpress進行壓縮的。我們將會對講解DLL文件脫殼,修復輸入表並且最終修復數據庫來進行分析。

Unpacking the DLL
我們的目標文件是一個木馬的DLL文件,該文件被殺軟識別為“Win32/Giku”。我們從使用idaq64載入DLL文件開始進行分析,載入之後按Ctrl+S鍵打開區段窗口:

打開區段窗口之後注意觀察區段的名稱和mpress壓縮殼設置的區段的屬性。
為了進行DLL文件調試需要確保在啟動之前已經設置調試器的選項設置(“Bochs debugger plugin”)為PE 和64bit emulation 模式。

在啟動調試器之後,注意觀察下面的代碼段,在這段代碼中調用了unpack()函數:

如果我們繼續單步執行到更遠的地方我們將會到達修復輸入表的代碼處,為了實現輸入表的修復程序將會循環調用LoadLibrary()/GetModuleHandle()函數並且在這個循環中會包含另外的一個子循環調用GetProcAddress()。 Mpress外殼通過這兩層循環來實現IAT修復:

在stosq執行之後我們將可以從rdi寄存器中得到IAT結構的起始地址,同樣在兩層循環全部結束之後我們可以從rdi寄存器中得到IAT結構的結束地址。
在IAT修復之後不遠的地方我們可以找到一個跳轉到原始入口點的jmp代碼:

程序的入口點代碼如下所示:

這裡就是脫殼之後的程序的真實的DllEntryPoint()函數了。現在有了程序的OEP和IAT結構的起始/結束地址,我們就可以清空數據庫並且重現脫殼之後的程序了。
Reconstructing and cleaning the database
到這裡有許多的辦法在程序脫殼之後進行清理數據庫清理.通常會包含如下幾步:
1. 定位IAT並且創建一個額外的區段來重現程序的輸入表;
2. 刪除外殼代碼的入口點,並且添加脫殼之後程序的原始入口點OEP;
3. 重新分析代碼;
4. 重新加載FLIRT特徵庫
5. 刪除無用的外殼區段(可選)
其中第一步到第三步可以通過IDA的uunp插件來自動完成,執行菜單中的“Edit/Plugins/Universal unpacker manual reconstruct”即運行該插件:

在插件中填入通過上面的操作得到的數據即可:

點擊確定之後一個新的區段將會被創建,並且代碼段將會被重新分析,在分析完成之後脫殼之後的程序的一個內存快照將會被呈現出來。
現在我們就可以重新引用FLIRT特徵庫了:

在選擇“vc64rtf”簽名(shi​​ft +F5)之後我們可以看到IDA已經成功的識別出了庫函數並且對這些庫函數使用淺藍色進行了標記,這樣可以使得後續的分析工作變得更加簡單。
Analyzing the unpacked code
在代碼解壓之後我們可以通過String Window窗口進行一個快速分析,String Window窗口呈現了一些加密的字符串:

通過交叉引用,我們可以定位到解密函數。在給函數適當的參數之後我們可以直接通過Appcall來解密這些字符串:

我們得到了一個指向加密的文本文件的URL。在深入挖掘之後我們定位到了解密文件的函數:

下面是Appcall版本的decrypt_file()函數:

我們使用這個函數就可以解密spm.txt文件了,解密之後的內容如下所示:

X32.jpg是一個upx壓縮的DLL文件,x64.jpg是一個mpress壓縮的PE+格式的Dll文件。

2011年8月2日星期二

C++中使用NtQueryDirectoryFile枚舉文件和文件夾

作者:腾袭
為了方便使用,我將其封裝成NtQuery類,這樣會更方便使用的!
支持UNICODE和非UNICODE的~

首先是頭文件NtQuery.h:

//NtQuery.h By 騰襲 2010-7-25
 
#ifndef _NT_QUERY_H_INCLUDE_
#define _NT_QUERY_H_INCLUDE_ 1
 
#pragma once
 
typedef struct _LSA_STRING {
USHORT Length;
USHORT MaximumLength;
PCHAR Buffer;
} LSA_STRING, *PLSA_STRING, ANSI_STRING, *PANSI_STRING;
 
typedef struct _LSA_UNICODE_STRING {
USHORT Length;
USHORT MaximumLength;
PWSTR Buffer;
} LSA_UNICODE_STRING, *PLSA_UNICODE_STRING, UNICODE_STRING, *PUNICODE_STRING;
 
typedef struct _IO_STATUS_BLOCK {
NTSTATUS Status;
ULONG Information;
}IO_STATUS_BLOCK, *PIO_STATUS_BLOCK;
 
typedef struct _OBJECT_ATTRIBUTES
{
ULONG Length;
HANDLE RootDirectory;
PUNICODE_STRING ObjectName;
ULONG Attributes;
PVOID SecurityDescriptor;
PVOID SecurityQualityOfService;
} OBJECT_ATTRIBUTES, *POBJECT_ATTRIBUTES;
 
typedef struct _FILE_DIRECTORY_INFORMATION {
ULONG NextEntryOffset;
ULONG Unknown;
LARGE_INTEGER CreationTime;
LARGE_INTEGER LastAccessTime;
LARGE_INTEGER LastWriteTime;
LARGE_INTEGER ChangeTime;
LARGE_INTEGER EndOfFile;
LARGE_INTEGER AllocationSize;
ULONG FileAttributes;
ULONG FileNameLength;
WCHAR FileName[1];
} FILE_DIRECTORY_INFORMATION, *PFILE_DIRECTORY_INFORMATION;
 
//FileFullDirectoryInformation:
typedef struct _FILE_FULL_DIRECTORY_INFORMATION {
ULONG NextEntryOffset;
ULONG Unknown;
LARGE_INTEGER CreationTime;
LARGE_INTEGER LastAccessTime;
LARGE_INTEGER LastWriteTime;
LARGE_INTEGER ChangeTime;
LARGE_INTEGER EndOfFile;
LARGE_INTEGER AllocationSize;
ULONG FileAttributes;
ULONG FileNameLength;
ULONG EaInformationLength;
WCHAR FileName[1];
} FILE_FULL_DIRECTORY_INFORMATION, *PFILE_FULL_DIRECTORY_INFORMATION;
 
//FileBothDirectoryInformation:
typedef struct _FILE_BOTH_DIRECTORY_INFORMATION {
ULONG NextEntryOffset;
ULONG Unknown;
LARGE_INTEGER CreationTime;
LARGE_INTEGER LastAccessTime;
LARGE_INTEGER LastWriteTime;
LARGE_INTEGER ChangeTime;
LARGE_INTEGER EndOfFile;
LARGE_INTEGER AllocationSize;
ULONG FileAttributes;
ULONG FileNameLength;
ULONG EaInformationLength;
UCHAR AlternateNameLength;
WCHAR AlternateName[12];
WCHAR FileName[1];
} FILE_BOTH_DIRECTORY_INFORMATION, *PFILE_BOTH_DIRECTORY_INFORMATION;
 
//FileNamesInformation:
typedef struct _FILE_NAMES_INFORMATION {
ULONG NextEntryOffset;
ULONG Unknown;
ULONG FileNameLength;
WCHAR FileName[1];
} FILE_NAMES_INFORMATION, *PFILE_NAMES_INFORMATION;
 
typedef enum _FILE_INFORMATION_CLASS {
FileDirectoryInformation = 1,
FileFullDirectoryInformation, // 2
FileBothDirectoryInformation, // 3
FileBasicInformation, // 4 wdm
FileStandardInformation, // 5 wdm
FileInternalInformation, // 6
FileEaInformation, // 7
FileAccessInformation, // 8
FileNameInformation, // 9
FileRenameInformation, // 10
FileLinkInformation, // 11
FileNamesInformation, // 12
FileDispositionInformation, // 13
FilePositionInformation, // 14 wdm
FileFullEaInformation, // 15
FileModeInformation, // 16
FileAlignmentInformation, // 17
FileAllInformation, // 18
FileAllocationInformation, // 19
FileEndOfFileInformation, // 20 wdm
FileAlternateNameInformation, // 21
FileStreamInformation, // 22
FilePipeInformation, // 23
FilePipeLocalInformation, // 24
FilePipeRemoteInformation, // 25
FileMailslotQueryInformation, // 26
FileMailslotSetInformation, // 27
FileCompressionInformation, // 28
FileObjectIdInformation, // 29
FileCompletionInformation, // 30
FileMoveClusterInformation, // 31
FileQuotaInformation, // 32
FileReparsePointInformation, // 33
FileNetworkOpenInformation, // 34
FileAttributeTagInformation, // 35
FileTrackingInformation, // 36
FileIdBothDirectoryInformation, // 37
FileIdFullDirectoryInformation, // 38
FileValidDataLengthInformation, // 39
FileShortNameInformation, // 40
FileMaximumInformation
} FILE_INFORMATION_CLASS, *PFILE_INFORMATION_CLASS;
 
#ifndef SYNCHRONIZE
#define SYNCHRONIZE 0x100000
#endif
 
#ifndef FILE_ANY_ACCESS
#define FILE_ANY_ACCESS 0
#endif
 
#ifndef FILE_LIST_DIRECTORY
#define FILE_LIST_DIRECTORY 1
#endif
 
#ifndef FILE_DIRECTORY_FILE
#define FILE_DIRECTORY_FILE 1
#endif
 
#ifndef FILE_SYNCHRONOUS_IO_NONALERT
#define FILE_SYNCHRONOUS_IO_NONALERT 0x20
#endif
 
#ifndef FILE_OPEN_FOR_BACKUP_INTENT
#define FILE_OPEN_FOR_BACKUP_INTENT 0x4000
#endif
 
#ifndef OBJ_CASE_INSENSITIVE
#define OBJ_CASE_INSENSITIVE 0x40
#endif
 
typedef NTSTATUS (NTAPI *FN_NtQueryDirectoryFile)( IN HANDLE FileHandle,
IN HANDLE Event OPTIONAL,
IN PVOID ApcRoutine OPTIONAL, /*PIO_APC_ROUTINE*/
IN PVOID ApcContext OPTIONAL,
OUT PIO_STATUS_BLOCK IoStatusBlock,
OUT PVOID FileInformation,
IN ULONG FileInformationLength,
IN FILE_INFORMATION_CLASS FileInformationClass,
IN BOOLEAN ReturnSingleEntry,
IN PUNICODE_STRING FileName OPTIONAL,
IN BOOLEAN RestartScan );
 
typedef NTSTATUS (NTAPI *FN_NtOpenFile)(
__out PHANDLE FileHandle,
__in ACCESS_MASK DesiredAccess,
__in POBJECT_ATTRIBUTES ObjectAttributes,
__out PIO_STATUS_BLOCK IoStatusBlock,
__in ULONG ShareAccess,
__in ULONG OpenOptions
);
 
typedef NTSTATUS (NTAPI *FN_NtClose)(
__in HANDLE Handle
);
 
typedef void (NTAPI *FN_RtlInitUnicodeString)(
__inout PUNICODE_STRING DestinationString,
__in_opt PCWSTR SourceString
);
 
typedef void (NTAPI *FN_RtlInitAnsiString)(
__inout PANSI_STRING DestinationString,
__inout PCSTR SourceString
);
 
typedef NTSTATUS (NTAPI *FN_RtlAnsiStringToUnicodeString)(
PUNICODE_STRING DestinationString,
PANSI_STRING SourceString,
BOOLEAN AllocateDestinationString
);
 
typedef NTSTATUS (NTAPI *FN_RtlUnicodeStringToAnsiString)(
PANSI_STRING DestinationString,
PUNICODE_STRING SourceString,
BOOLEAN AllocateDestinationString
);
 
typedef void (NTAPI *FN_RtlFreeUnicodeString)(
PUNICODE_STRING UnicodeString
);
 
typedef void (NTAPI *FN_RtlFreeAnsiString)(
PANSI_STRING AnsiString
);
 
class CNtQuery
{
public:
CNtQuery(void);
virtual ~CNtQuery(void);
static bool CNtQuery::GetAddr();
static bool m_bInit;
 
HANDLE FindFirstFile(LPCTSTR strDirectory,PBYTE bytBuffer,ULONG size);
bool FindNextFile(HANDLE hFind,PBYTE bytBuffer,ULONG size);
void Enum(LPCTSTR lpszPath);
 
NTSTATUS CNtQuery::ANSI2UNICODE(LPCSTR lpBuf, PUNICODE_STRING pUnicodeString, BOOLEAN AllocateDestinationString = TRUE);
NTSTATUS CNtQuery::UNICODE2ANSI(LPCWSTR lpBuf, PANSI_STRING pAnsiString, BOOLEAN AllocateDestinationString = TRUE);
private:
static HMODULE m_hNtdll;
protected:
static FN_NtQueryDirectoryFile fn_NtQueryDirectoryFile;
static FN_NtOpenFile fn_NtOpenFile;
static FN_NtClose fn_NtClose;
static FN_RtlInitUnicodeString fn_RtlInitUnicodeString;
static FN_RtlInitAnsiString fn_RtlInitAnsiString;
static FN_RtlAnsiStringToUnicodeString fn_RtlAnsiStringToUnicodeString;
static FN_RtlUnicodeStringToAnsiString fn_RtlUnicodeStringToAnsiString;
static FN_RtlFreeUnicodeString fn_RtlFreeUnicodeString;
static FN_RtlFreeAnsiString fn_RtlFreeAnsiString;
};
 
#endif

然後是NtQuery.cpp:


//NtQuery.cpp By 騰襲 2010-7-25
 
#include "StdAfx.h"
#include "NtQuery.h"
 
HMODULE CNtQuery::m_hNtdll = NULL;
FN_NtQueryDirectoryFile CNtQuery::fn_NtQueryDirectoryFile;
FN_NtOpenFile CNtQuery::fn_NtOpenFile;
FN_NtClose CNtQuery::fn_NtClose;
FN_RtlInitUnicodeString CNtQuery::fn_RtlInitUnicodeString;
FN_RtlInitAnsiString CNtQuery::fn_RtlInitAnsiString;
FN_RtlAnsiStringToUnicodeString CNtQuery::fn_RtlAnsiStringToUnicodeString;
FN_RtlUnicodeStringToAnsiString CNtQuery::fn_RtlUnicodeStringToAnsiString;
FN_RtlFreeUnicodeString CNtQuery::fn_RtlFreeUnicodeString;
FN_RtlFreeAnsiString CNtQuery::fn_RtlFreeAnsiString;
 
bool CNtQuery::m_bInit = false;
 
bool CNtQuery::GetAddr()
{
m_hNtdll = GetModuleHandle(_T("ntdll.dll"));
if (m_hNtdll == NULL)
return false;
 
fn_NtQueryDirectoryFile = (FN_NtQueryDirectoryFile)GetProcAddress(m_hNtdll,"NtQueryDirectoryFile");
fn_NtOpenFile = (FN_NtOpenFile)GetProcAddress(m_hNtdll,"NtOpenFile");
fn_NtClose = (FN_NtClose)GetProcAddress(m_hNtdll,"NtClose");
fn_RtlInitUnicodeString = (FN_RtlInitUnicodeString)GetProcAddress(m_hNtdll,"RtlInitUnicodeString");
fn_RtlInitAnsiString = (FN_RtlInitAnsiString)GetProcAddress(m_hNtdll,"RtlInitAnsiString");
fn_RtlAnsiStringToUnicodeString = (FN_RtlAnsiStringToUnicodeString)GetProcAddress(m_hNtdll,"RtlAnsiStringToUnicodeString");
fn_RtlUnicodeStringToAnsiString = (FN_RtlUnicodeStringToAnsiString)GetProcAddress(m_hNtdll,"RtlUnicodeStringToAnsiString");
fn_RtlFreeUnicodeString = (FN_RtlFreeUnicodeString)GetProcAddress(m_hNtdll,"RtlFreeUnicodeString");
fn_RtlFreeAnsiString = (FN_RtlFreeAnsiString)GetProcAddress(m_hNtdll,"RtlFreeAnsiString");
 
if (!(fn_NtQueryDirectoryFile && fn_NtOpenFile && fn_NtClose && fn_RtlInitUnicodeString))
{
return false;
}
return true;
}
 
CNtQuery::CNtQuery(void)
{
if (!m_bInit)
m_bInit = GetAddr();
}
 
CNtQuery::~CNtQuery(void)
{
}
 
HANDLE CNtQuery::FindFirstFile(LPCTSTR strDirectory,PBYTE bytBuffer,ULONG size)
{
if (!m_bInit)
return INVALID_HANDLE_VALUE;
 
TCHAR strFolder[MAX_PATH];
OBJECT_ATTRIBUTES obAttr;
IO_STATUS_BLOCK objIoStatus;
NTSTATUS ntStatus;
HANDLE hFind = NULL;
UNICODE_STRING strUnicode;
 
wsprintf(strFolder,_T("\\??\\%s"),strDirectory);
 
//'初始化Unicode字符串
#ifdef UNICODE
fn_RtlInitUnicodeString(&strUnicode, strFolder);
#else
ANSI2UNICODE(strFolder,&strUnicode);
#endif
 
memset(&objIoStatus,0,sizeof(IO_STATUS_BLOCK));
memset(&obAttr,0,sizeof(OBJECT_ATTRIBUTES));
 
obAttr.Length = sizeof(OBJECT_ATTRIBUTES); //'初始化OBJECT_ATTRIBUTES結構
obAttr.Attributes = OBJ_CASE_INSENSITIVE;
obAttr.ObjectName = &strUnicode; //'需要打開的文件夾路徑
obAttr.RootDirectory = NULL;
obAttr.SecurityDescriptor = NULL;
obAttr.SecurityQualityOfService = NULL;
//'獲取文件夾句柄
ntStatus = fn_NtOpenFile(&hFind,
FILE_LIST_DIRECTORY | SYNCHRONIZE | FILE_ANY_ACCESS,
&obAttr,
&objIoStatus,
3,
FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT | FILE_OPEN_FOR_BACKUP_INTENT);
#ifndef UNICODE
//釋放內存
fn_RtlFreeUnicodeString(&strUnicode);
#endif
if (ntStatus == 0 && hFind != INVALID_HANDLE_VALUE)
{
 
//'獲取文件夾文件/目錄信息,其實這個函數是Kernel32裡的FindFirstFile的封裝
ntStatus = fn_NtQueryDirectoryFile(hFind,
NULL,
NULL,
NULL,
&objIoStatus,
bytBuffer,
size,
FileBothDirectoryInformation,
TRUE,
NULL,
NULL);
if (ntStatus == 0) //'Nt系列函數一般返回大於零表示成功,而NtQueryDirectoryFile返回零表示成功
{
//_tprintf(_T("Succeed!\n"));
return hFind;
}else{
fn_NtClose(hFind);
}
}
return INVALID_HANDLE_VALUE;
}
 
bool CNtQuery::FindNextFile(HANDLE hFind,PBYTE bytBuffer,ULONG size)
{
if (!m_bInit)
return false;
NTSTATUS ntStatus;
IO_STATUS_BLOCK objIoStatus;
//'這是Kernel32 FindNextFile的封裝,只是是一次把所有文件/目錄都取出來了
ntStatus = fn_NtQueryDirectoryFile(hFind,
NULL,
NULL,
NULL,
&objIoStatus,
bytBuffer,
size,
FileBothDirectoryInformation,
FALSE,
NULL,
NULL);
//_tprintf(_T("ntStatus\n"));
return (ntStatus == 0);
}
 
void CNtQuery::Enum(LPCTSTR lpszPath)
{
if (!m_bInit)
return;
#ifndef UNICODE
ANSI_STRING asfn,asan;
#endif
 
PFILE_BOTH_DIRECTORY_INFORMATION pDir;
HANDLE hFind;
LPBYTE bytBuffer;
wchar_t fileName[_MAX_FNAME];
wchar_t AlternateName[13];
TCHAR strPath[MAX_PATH];
TCHAR strFileName[MAX_PATH];
 
ULONG dwDirOffset;
ULONG iCount = 0;
ULONG usize = sizeof(FILE_BOTH_DIRECTORY_INFORMATION) + MAX_PATH * 2 - 3;
bytBuffer = new BYTE[usize];
//memset(bytBuffer,0,usize);
 
lstrcpy(strPath,lpszPath);
 
hFind = FindFirstFile(strPath,bytBuffer,usize); //'獲取第一個文件/目錄對象
if (hFind == INVALID_HANDLE_VALUE)
return ;
pDir = (PFILE_BOTH_DIRECTORY_INFORMATION)bytBuffer;
//獲取FILE_BOTH_DIRECTORY_INFORMATION結構,目的是獲取FileNameLength和NextEntryOffset數據
 
memcpy(fileName, pDir->FileName, pDir->FileNameLength);
memcpy(AlternateName,pDir->AlternateName,pDir->AlternateNameLength); //8.3短文件名
fileName[pDir->FileNameLength / sizeof(wchar_t)] = 0;
AlternateName[pDir->AlternateNameLength / sizeof(wchar_t)] = 0; //這裡的長度都是字節數
 
#ifndef UNICODE
UNICODE2ANSI(fileName, &asfn);
UNICODE2ANSI(AlternateName, &asan);
wsprintf(strFileName,_T("%s%s"),strPath, asfn.Buffer);
_tprintf(_T("%02d %8s\t%s\n"),pDir->AlternateNameLength, asan.Buffer, strFileName);
fn_RtlFreeAnsiString(&asfn);
fn_RtlFreeAnsiString(&asan);
#else
wsprintf(strFileName,_T("%s%s"),strPath,fileName);
_tprintf(_T("%02d %8s\t%s\n"),pDir->AlternateNameLength, AlternateName, strFileName);
#endif
 
delete[] bytBuffer;
 
usize = (sizeof(FILE_BOTH_DIRECTORY_INFORMATION) + (260 * 2 - 3)) * 0x2000;
bytBuffer = new BYTE[usize];
 
iCount = 1;
while (FindNextFile(hFind,bytBuffer,usize)) //雖然分配了許多空間了,但是仍可能不能把全部文件列舉出來,因此,這裡需要進一步測試
{
dwDirOffset = 0;
//遍歷緩存
while(true)
{
//移動指針
pDir = (PFILE_BOTH_DIRECTORY_INFORMATION)(bytBuffer + dwDirOffset);
//得到FILE_BOTH_DIRECTORY_INFORMATION結構
 
memcpy(fileName, pDir->FileName, pDir->FileNameLength); //文件名
memcpy(AlternateName,pDir->AlternateName,pDir->AlternateNameLength); //8.3短文件名
AlternateName[pDir->AlternateNameLength / sizeof(wchar_t)] = 0; //這裡的長度都是字節數
fileName[pDir->FileNameLength / sizeof(wchar_t)] = 0;
 
#ifndef UNICODE
UNICODE2ANSI(fileName, &asfn);
UNICODE2ANSI(AlternateName, &asan);
wsprintf(strFileName,_T("%s%s"),strPath, asfn.Buffer);
_tprintf(_T("%02d %8s\t%s\n"),pDir->AlternateNameLength, asan.Buffer, strFileName);
fn_RtlFreeAnsiString(&asfn);
fn_RtlFreeAnsiString(&asan);
#else
wsprintf(strFileName,_T("%s%s"),strPath,fileName);
_tprintf(_T("%02d %8s\t%s\n"),pDir->AlternateNameLength, AlternateName, strFileName);
#endif
//這裡如果是目錄可以遞歸遍歷目錄下所有文件/目錄,可以自己封裝一個遞歸函數吧
 
iCount++;
if (pDir->NextEntryOffset == 0)
break;
dwDirOffset += pDir->NextEntryOffset; //這裡指向下一個FILE_BOTH_DIRECTORY_INFORMATION結構在內存中的位置
}
}
 
delete[] bytBuffer;
fn_NtClose(hFind);
_tprintf(_T("\nfilecount: %d\n"),iCount);
}
 
//RtlAnsiStringToUnicodeString()
//RtlUnicodeStringToAnsiString()
//當第3個參數為TRUE時,系統將新分配內存,所以要記得調用RtlFreeUnicodeString()和RtlFreeAnsiString()釋放掉。
 
NTSTATUS CNtQuery::ANSI2UNICODE(LPCSTR lpBuf, PUNICODE_STRING pUnicodeString, BOOLEAN AllocateDestinationString)
{
ANSI_STRING as;
fn_RtlInitAnsiString( &as, lpBuf );
return fn_RtlAnsiStringToUnicodeString( pUnicodeString, &as, AllocateDestinationString );
}
 
NTSTATUS CNtQuery::UNICODE2ANSI(LPCWSTR lpBuf, PANSI_STRING pAnsiString, BOOLEAN AllocateDestinationString)
{
UNICODE_STRING ns;
fn_RtlInitUnicodeString( &ns, lpBuf );
return fn_RtlUnicodeStringToAnsiString( pAnsiString, &ns, AllocateDestinationString );
}

2011年8月1日星期一

ZeroAccess:內核模式下的一個高級rootkit 分析

文/Marco Giuliani 

      ZeroAccess rootkit 的安裝是由dropper病毒感染而成,dropper病毒通常是用戶訪問到crack 或warez 網站時被惡意下載至客戶端或者是黑客使用漏洞利用工具包exploit packs 惡意加載dropper 到客戶端生成的。 Dropper 運用了大量反調試技術以降低代碼被分析的可能。在首次解壓縮後,代碼會嘗試獲取如下特權:SeDebugPrivilege (允許用戶綁定一個調試器到任何進程.可訪問敏感和關鍵的操作系統組件),SeTakeOwnershipPrivilege(取得文件或其他對象的所有權), SeRestorePrivilege(恢復數據權限), SeSystemtimePrivilege(更改系統時間權限), SeSecurityPrivilege(管理審核和安全日誌). 然後才會感染有效載荷。

      在詳細分析感染過程之前,有必要簡要分析下ZeroAccess 是如何感染系統的。Dropper病毒會在驅動目錄下如“C:\WINDOWS\system32\drivers”下隨機選擇一個驅動,並覆寫此驅動源代碼(覆寫之前備份源碼),編譯並加載此惡意驅動後,它會創建一個新的磁盤設備對象,用來作為rootkit 自身創建的捲隱藏驅動入口點,並保存它的文件和數據。

      儘管很類似TDL3 rootkit 的感染方式,但它很有效。 ZeroAccess 與TDL3 的主要區別:

   其一,ZeroAccess在文件系統內創建一個隱藏的加密新卷,而TDL3 則是利用硬件驅動的末尾幾個扇區創建一個嶄新的加密文件系統;其二,兩者的rootkit 都是感染一個隨機驅動,但ZeroAccess 是通過完整覆寫指定驅動的源代碼,而TDL3 則是劫持驅動的入口點,覆寫不超過1KB字節大小的驅動資源段;其三,磁盤I/O 驅動過濾引擎完全不一樣,TDL3 在性能上略勝一籌。

      下面我們將深入地分析ZeroAccess rootkit 中的驅動感染函數如何工作,以及rootkit如何選擇合適驅動進行感染:
    (1) rootkit 計算一個用來校驗驅動鏡像文件大小的特殊值,分析實例的值為0x7410(29712字節大小),即rootkit 內核驅動大小。

    (2) rootkit 調用SystemModuleInformation類中的成員函數ZwQuerySystemInformation 枚舉系統內所有驅動。

    (3) 目標驅動必須在​​定位在classpnp.sys驅動和win32k.sys 驅動之間。

    (4) 在classmp.sys和win32k.sys 之間且鏡像文件小於0x7410 的驅動將被放棄。

    (5) 大於0x7410值的所有驅動需校驗以下值:驅動文件名必須以“.sys”擴展名結尾;驅動寄存器鍵碼的起始值必須大於0 (驅動不應該在系統啟動時被啟動起來);驅動的PE 導出表必須為0 (驅動不應導出任何內容)。

    (6) 若第5步列出的要求都符合,則此驅動可標識為“良好的潛在目標”,即將SYSTEM_MODULE->ld 結構體設置為1.

    (7)循環遍歷所要滿足大於0x7410值的驅動,直到驅動全部分析完畢。

      第一輪循環遍歷完成後,找出所有具備條件的目標驅動,然後再進入第二輪的循環搜索,直至找到一個最適合感染的驅動,具體策略:其一,rootkit 調用GetTickCount( )和

   RtlRandom() 方法計算出一個隨機值;其二,使用RandomValue%NumberOfProtentialTargetsFount 等式初始化輪詢的計數器counter;其三rootkit 循環遍歷所有系統驅動,每找到一個具備條件的驅動(設置SYSTEM_MODULE->ld =1),counter 自減。

      當counter == 0 時,rootkit 確定出一個可感染的目標驅動,並創建一個新的section段,命名為\.<待感染的驅動名>,如“\.NdProxy”),以暫存一個未感染的(clean driver) 驅動源碼。隨後,Rootkit會在“HKLM\SYSTEM\CurrentControlSet\Services”下創建一個新服務的註冊表鍵值,鍵碼的ImagePath值設置成“\*.”,這是避免文件加載時被安全軟件攔截的一個巧點子。通過設置值“\*.”,安全軟件將會被愚弄過去,因為它並沒有指向任何一個真實文件,但實際上,rootkit的dropper已經通過調用API 函數ZwCreateSysbolicLinkObject 創建了一個新符號鏈接使其“\ *”指向一個真實文件。

      Dropper 通過整體覆寫內核模式下的驅動源碼方式感染目標驅動,並調用ZwLoadDriver函數加載驅動。在覆蓋驅動實體前,dropper 會首先暫停所有與sfc_os.dll 模塊相關的線程,最後確定係統文件校驗SFC線程是否暫停運行。在整個感染完成後,重新啟動已暫停線程。在執行受感染的有效載荷之前,dropper會檢驗自身是否運行在WoW64 的模擬環境下。

   若是,進程立刻終止。 Rootkit 當前並不會去感染X64 位的Windows 系統,而且dropper會校驗感染函數是否調用ZwOpenFile 函數嘗試打開了rootkit 設備,並運行在系統內部。若係統已經感染,rootkit 設備會返回一個NTSTATUS 錯誤即STATUS_VALIDATE_CONTINUE.
      Rootkit 驅動加載後,用戶模式下可以訪問到rootkit設備“\\?\ACPI#PNP0303#2&da1a3ff&0”,同時dropper也能使用NTFS文件系統格式化一個新卷,為了實現這些動作,它會加載fmifs .dll 模塊——針對可安裝的文件系統模塊格式化管理器,並導入API函數FormatEx( ).

     Format_Virtual_Drivproc near ; CODE XREF :Infection_Payload + 390
            pushesi
            push offset LibFileName ; "fmifs"
            callds:LoadLibraryW
            movesi, eax
            testesi, esi
            jz short loc_402032
            pushoffset_ProcName ; "FormatEx"
            pushesi ; hModule
            callds:GetProcAddress
            testeax, eax
            jz short loc_40202B
            push offset sub_401FEB
            push 0
            push 1
            push offset unk_40A3A0
            push offset aNtfs; "NTFS"
            push 0Bh

            push offset a?AcpiPnp03032D
            calleax
     loc_40202B: ;CODE XREF : Format_Virtual_Drive+20

     pushesi ; hLibModule
     call ds: FreeLibrary


      新的隱藏卷現在可以用來存放被覆蓋之前的驅動源碼實現。 Dropper 並不會使用真實的文件名,它會隨機產生一個文件名,具體操作如下所示:

    (1)rootkit 調用ZwQueryKey 函數,帶上參數KeyBasicInformation,查詢註冊表鍵值“HKLM\SYSTEM\CurrentControlSet\Control\agp ” ; 之後rootkit 會詢問參數值“_KEY_BASIC_INFORMATION->LastWriteTime”,並產生兩個特殊種子值,第一個值即通過LastWriteTime 參數的LowPart 和HighPart 做異或操作得到;第二個種子的值即在前一個種子值的基礎上再加上“LowPart+1”。完成步驟1後,rootkit 會使用一個啟動字符串變量用來產生一個隨機的字符串組成新的文件名。字符數組值為“eaoimnqazwsxedcrfvtgbyhnujmikolp”。

    (2)文件名由8 個字符構成,故函數按照以下方式循環獲取單個字符,種子Seed 和0x1F做與操作(0x1F 即字符數組長度),得到的值作為啟動數組StartingString 的索引,即新文件名的某個字符值;同時每次循環時,使用_allshr()函數使Seed 值右移5位。 (在內核模式的驅動程序中針對一個64 位的LARGE_INTEGER 使用移位操作符<<和>>,編譯器和鏈接器會自動導入ntoskrnl.exe 的_allshr() 和_allshl()函數)。

    (3 ) 直到完整構造出一個8 字節的字符串, 並將文件存儲在“\??\ACPI#PNP0303#2&da1a3ff&0\L\Snifer67”,其中Snifer67被替換成新產生的文件名,文件名生成函數如下:

    char *StartString = "";
    charFileName[9];
    DWORD index=7;
    RegOpenKeyA(HKEY_LOCAL_MACHINE, "SYSTEM\\CurrentControlSet\\Control\\agp",
    &regKey);
    NtQueryKey(regKey, KeyBasicInformation, &KeyInfo,
    sizeof(KEY_BASIC_INFORMATION),&result);

    seed2 = (KeyInfo.LastWriteTime.HighPart ^ KeyInfo.LastWriteTime.LowPart);
    seed = (Seed2 + KeyInfo.LastWriteTime.LowPart + 1);
    while(index >= 0){
        FileName[index] = StartString[(seed & 0x1F)];
        _allshr(&seed, &seed2, 5);
         if(index == 0) break;
           index--;
    }

      文件名產生後,rootkit 設備內創建此文件,同時clean驅動的整體拷貝存放在此處。
                                  

                            內核模式Rootkit 感染

                      
      下面我們將更深入地分析ZeroAccess rootkit 感染後的內核驅動是如何工作的。由上小節可知,rootkit創建了一個命名為“ACPI#PNP0303#2&da1a3ff&0”的新設備對象,用作訪問rootkit 隱藏設備的入口Gate. 然後,它通過劫持與底層設備端口通信的disk.sys攔截Windows 的磁盤I/O. 此時,若用戶嘗試讀寫被攔截的驅動時,rootkit 會顯示原有驅動清潔拷貝來偽造文件內容。

       Rootkit 啟動後,它會校驗註冊表啟動項鍵值是否已執行來判斷其是否首次運行在系統上。如它來自.<drivername> (eg. .NdProxy)服務的註冊表鍵值,則斷定其是首次運行,此時rootkit 會刪除此鍵值。隨後,rootkit 讀取受感染的驅動路徑,並調用RtlHashUnicodeString 函數計算出驅動路徑和文件名的hash 值。 Rootkit 利用計算出的hash 來校驗是否有人嘗試訪問過受感染驅動。感染後的驅動備份拷貝存儲在內存中,由特殊的MDL 指向。

      此時,Rootkit 具備創建屬於自己的代碼的機會,調用IoCreateDriver ()函數,並設置自己的驅動對象,將對象隱藏在DriverSection 中,同時將它所有的派遣函數地址指向一個特殊的rootkit 派遣函數。為了隱藏新產生的驅動對象,rootkit 竊取原磁盤驅動“\driver\disk”的對象,並一個一字段的拷貝clean驅動disk.sys 的驅動對象成員到一個偽造的驅動對像上,兩者之間的每個成員地址如下:

   偽造的磁盤驅動結構體:
lkd>dt _DRIVER_OBJECT 0x8201f590
     nt!_DRIVER_OBJECT
        +0x000 Type : 4
        +0x002 Size : 168
        +0x004 DeviceObject : 0x81fd5040 _DEVICE_OBJECT
        +0x008 Flags: 0x12
        +0x00c DriverStart: 0xf86cb000
        +0x010 DriverSize : 0x8e00
        +0x014 DriverSection : 0x821edbc0
        +0x018 DriverExtension : 0x8201f638 _DRIVER_EXTENSION
        +0x01c DriverName : _UNICODE_STRING "\Driver\Disk"
        +0x024 HardwareDatabase : 0x8066e9d8 _UNICODE_STRING
     "\REGISTRY\MACHINE\HARDWARE\DESCRIPTION\SYSTEM"
        +0x028 FastIoDispatch : (null)
        +0x02c DriverInit : 0xf86d28ab long +fffffffff86d28ab
        +0x030 DriverStartIo : (null)
        +0x034 DriverUnload : (null)
        +0x038 MajorFunction : [28] 0xf4b79134 long +ffffffffff4b79134

   原磁盤驅動對象結構體:

     lkd>dt _DRIVER_OBJECT 821eb320
     nt!_DRIVER_OBJECT
        +0x000 Type : 4
        +0x002 Size : 168
        +0x004 DeviceObject : 0x821a89f0 _DEVICE_OBJECT
        +0x008 Flags : 0x12
        +0x00c DriverStart : 0xf86cb000
        +0x010 DriverSize : 0x8e00
 
        +0x014 DriverSection : 0x821edbc0
        +0x018 DriverExtension : 0x821eb3c8 _DRIVER_EXTENSION
        +0x01c DriverName : _UNICODE_STRING "\Driver\Disk"
        +0x024 HardwareDatabase : 0x8066e9d8 _UNICODE_STRING

     "\REGISTRY\MACHINE\HARDWARE\DESCRIPTION\SYSTEM"
        +0x028 FastIoDispatch : (null)
        +0x02c DriverInit : 0xf86d28ab long +fffffffff86d28ab
        +0x030 DriverStartIo : (null)

        +0x034 DriverUnload : 0xf86e253a
        +0x038 MajorFunction : [28] 0xf86e1c30 long +ffffffffff4b79134

       從上面的各結構體成員來看,除派遣函數和驅動對象(偽造的驅動對象指針指向自己構造的對象)外,其它成員值完全等同。 Rootkit 的驅動對象創建了兩個不同的設備對象,一個用來攔截發送給磁盤disk.sys 的I/O請求,另一個則指向隱藏設備對象,即訪問隱藏設備對象的入口地址。為了攔截磁盤驅動disk.sys 的I/O請求,rootkit 通過交換它的設備擴展對象結構體,實現設備對象“\driver\disk\DR0 ”設備對象的攔截。此時DR0_Device_Object->DevExtension->LowerDeviceObject 指針值被修改為rootkit 設備對象指針。此時rootkit 可以設置回調函數指針,待disk.sys 完成上層的I/O請求返回IRP時,IRP會轉到rootkit 的設備對象層,此時它可以分析IRP 內容並有效過濾部分內容,然後再將IRP 向上傳遞到端口設備驅動如atapi.sys等。

        Rootkit 分析IRP是否發送到偽造的設備對像上,若是,則它會調用自己的派遣函數處理IRP請求,在偽造的隱藏卷內,它可以處理所有IOCTL請求,如IOCTL_DISK_CHECK_VERIFY、IOCTL_DISK_IS_WRITEABLE等。隱藏卷是加密處理過的,rootkit 的讀寫請求能夠對磁盤上的數據進行加解碼操作。偽造的捲存儲在一個文件內,它位於“系統目錄\system32\config\<random file name>”,隨機名與dropper產生的文件名一致,並用來存儲感染驅動的拷貝。這個文件在磁盤上總是加密的,rootkit 使用的加密算法為RC4,密鑰長度為128 位,RC4 加解密操作是以扇區為單位進行的,加密密鑰內容為:
   “0xFF,0x7C,0xF1,0x64,0x12,0xE2,0x2D,​​0x4D,0xB1,0xCF,0x0F,0x5D,0x6F,0xE5,0xA0,0x49”,rootkit 的加解密處理如下:

      相反,若IRP沒有轉向rootkit 設備上來,派遣函數會分析請求包,在磁盤上的已感染驅動文件內搜索I/O 請求。 Rootkit 過濾IPR_MJ_INTERNAL_DEVICE_CONTROL 函數,並蒐索SCSI 請求塊結構。若SRB->Function == SRB_FUNCTION_EXECUTE_SCSI,過濾函數會繼續搜索。 Rootkit 檢驗文件對象結構體是否為未決IRP 請求填充的,若是,則調用RtlHashUnicodeString 函數計算文件路徑的hash值,同時計算rootkit 驅動啟動時受感染驅動路徑的hash 值,將兩者進行比較,若相等,則此IRP請求系rootkit 偽造。

      若SCSI_REQUEST_BLOCK 請求包操作是SCSIOP_READ,讀請求會傳遞給底層端口設備,返回的結果被rootkit 的CompletionRoutine 函數虛構;若操作為“SCSIOP_WRITE”,則底層返回的buffer 會被受感染的驅動rootkit 覆寫,ZeroAccess感染後的驅動代碼執行流如下圖所示:


       Rootkit 工作在TDI 網絡層,可繞過那些未在網絡層級監控網絡連接的防火牆及各類安全軟件,rootkit 發送一個加密請求給列表內的所有server 時,請求包會發送到遠端的TCP端口13620上。 Rootkit 通過下載並存儲一些相關文件到rootkit 的隱藏卷中,允許攻擊者放棄系統內的深層次感染,從而實現對安全軟件的透明。這些dropped文件以內核模式驅動的形式存在,因為rootkit 主驅動能夠在內核中通過調用IoCreateDriver()函數定位到它們。

                                  小結

       ZeroAccess 絕對是當前最先進的內核模式rootkit 之一,儘管它沒有TDL rootkit 家族系列那麼強大,但它具備很多獨有的特性,使其具備很大的威脅。 ZeroAccess 提供的一些強大的功能如修改並感染系統核心驅動如disk.sys,atapi.sys,並竊取PIC 驅動對象抵禦文件系統取證技術;用戶模式下的進程創建攔截與DLL 劫持;、DLL 隱藏與反病毒軟件繞過技術等。