010Editor逆向分析

010Editor 逆向分析


用於測試的用戶名爲foobar,序列號爲1112-2233-3444-5556-6677

1. 暴力破解分析

思路:

  1. 找到註冊窗口
  2. 測試註冊窗口反應
  3. 根據反應做出下一步分析(猜測api,敏感字符串)
  4. 動態分析關鍵跳轉、call

1.1 API下斷

註冊失敗會彈框,OD中搜索當前模塊中的名稱(字符串),查看導入的API,會發現這是個QT程序(不熟悉,GG)。

也可以用IDA。因爲0xeditor.exe本身有45MB,所以分析完後的數據庫會很大。

因爲底層肯定還是調用了API,所以在OD的E窗口中找到user32.dll(和窗口有關),QT底層實際上調用了CreaetWindow。斷下後棧回溯分析,用戶領空調用來自010editor.01CE69BB

在這裏插入圖片描述

但是點進去後沒有發現什麼信息,所以繼續看上一層調用,010editor.011B8912

在這裏插入圖片描述

發現了關鍵字符串,所以棧回溯找到這個函數的開頭。試着找一下關鍵跳轉。

在這裏插入圖片描述

有一個QT的判斷字符串爲空的函數,猜測是用戶名或序列號。經過分析,isEmpty的上一個函數,<&Qt5Widgets.?text@QLineEdit@@QB>獲取了用戶名,並且是unicode編碼。

在這裏插入圖片描述

01435675    8B4E 74                  MOV ECX,DWORD PTR DS:[ESI+0x74]
01435678    8D45 D8                  LEA EAX,DWORD PTR SS:[EBP-0x28]             ; 序列號
0143567B    50                       PUSH EAX
0143567C    FFD7                     CALL EDI                                    ; 獲取序列號

繼續跟進,發現再次調用這個函數,獲取序列號。

在這裏插入圖片描述
繼續往下跟進,就會找到關鍵跳轉。

有兩個驗證函數,我加了標籤,check1, check2

014357F3    MOV ECX,DWORD PTR DS:[0x3E10F20]
014357F9    PUSH 0x4389
014357FE    PUSH 0x9
01435800    CALL <010Edito.check1>                            ;第一次驗證
01435805    MOV ECX,DWORD PTR DS:[0x34D0F20]
0143580B    MOV EBX,EAX                                       ; ebx = eax
0143580D    PUSH 0x4389
01435812    PUSH 0x9
01435814    CALL <010Edito.check2>                            ;第二次驗證
01435819    MOV ECX,DWORD PTR DS:[0x34D0F20]
0143581F    MOV EDI,EAX                                       ; edi = eax( 0xD8 is right)
01435821    CMP EBX,0xE7
01435827    JE 010Edito.01435920                              ; if(ebx == 0xE7) jmp
0143582D    CMP DWORD PTR DS:[ECX+0x2C],0x0
01435831    JE 010Edito.01435920
;...
01435920    CMP EDI,0xDB                                      ; if(edi!=0xdb) fail
01435926    JNZ 010Edito.01435A58                             ; 關鍵跳轉!!!!
;...
01435A84    PUSH 010Edito.0297D5FC   ; ASCII "Password accepted. Your trial period has been extended."
01435A89    CALL DWORD PTR DS:[<&Qt5Core.??0QString@@QAE@PBD@Z>]   ; Qt5Core.??0QString@@QAE@PBD@Z

如果序列號錯誤在0x01435926處會跳轉,彈出無效序列號對話框。如果把它改爲nop,就可以驗證通過。

在這裏插入圖片描述

但這樣更改後dump,每次運行還是需要驗證註冊,並沒有完美破解。

如果讓驗證始終正確,也就是edi == 0xDB,或者說check2返回0xDB,就可以完美破解了。

分析一下check2:

01CFE4E0    PUSH EBP
01CFE4E1    MOV EBP,ESP
01CFE4E3    PUSH ESI
01CFE4E4    MOV ESI,ECX
;...
01A1E4FE    CALL <010Edito.check1>
01A1E503    CMP EAX,0x2D
01A1E506    JE 010Edito.01A1E5AF
;...
01A1E5AF    POP EDI
01A1E5B0    MOV EAX,0xDB	;返回0xDB
01A1E5B5    POP ESI
01A1E5B6    POP EBP
01A1E5B7    RETN 0x8

check2再次調用check1,如果返回0x2D,check2就會返回0xDB.

直接修改check2開頭:

在這裏插入圖片描述

也可以修改check1返回0x2D,但我們看check1開頭:

在這裏插入圖片描述

地址處有下劃線,如果修改的話需要關閉隨機基址。而且,check1返回值會影響外部主驗證流程,所以最終還是修改了check2返回值。

在這裏插入圖片描述

1.2 字符串

彈出無效窗口後,點擊窗口,ctrl+c複製到記事本,OD搜索所有參考文本字符串。找到關鍵字符串,後面步驟同上。

2. check1算法分析

014357F3    8B0D 200FE103   MOV ECX,DWORD PTR DS:[0x3E10F20]
014357F9    68 89430000     PUSH 0x4389
014357FE    6A 09           PUSH 0x9
01435800    CALL <010Edito.check1>           ;第一次驗證,check1(this, 0x4389, 0x9)

check1是關鍵函數,我們應該分析一下,如何讓它返回0x2D。

因爲有ecx傳參,所以應該也是個c++對象的函數,另外還有兩個常量參數。這個函數很長,跟蹤時應該關注數據(用戶名和序列號)的訪問和修改。

先分析一下this對象。

this:

002CAA68  699EB740  OFFSET Qt5Core.?shared_null@QArrayData@@2QBU1@B
002CAA6C  07732198
002CAA70  0774ACA0
002CAA74  0771B8B8

[this]暫時不知道用途。

[this+4]`中保存了4個數字和用戶名:

07732198  02 00 00 00 06 00 00 00 0D 00 00 00 10 00 00 00  .....
077321A8  66 00 6F 00 6F 00 62 00 61 00 72 00 00 00 00     foobar..

[this+8]中保存了4個數字和輸入的序列號:

0774ACA0  02 00 00 00 18 00 00 00 38 00 00 00 10 00 00 00  ..8..
0774ACB0  31 00 31 00 31 00 32 00 2D 00 32 00 32 00 33 00  1112-223
0774ACC0  33 00 2D 00 33 00 34 00 34 00 34 00 2D 00 35 00  3-3444-5
0774ACD0  35 00 35 00 36 00 2D 00 36 00 36 00 37 00 37 00  556-6677

而且用戶名和序列號都是unicode編碼。

然後進入check1,分析驗證流程。

0235DBE8    8BF9            MOV EDI,ECX                              ; edi = this
0235DBEA    8D5F 04         LEA EBX,DWORD PTR DS:[EDI+0x4]           ; ebx = this+4 = stcName
0235DBED    C745 F0 0000000>MOV DWORD PTR SS:[EBP-0x10],0x0
0235DBF4    8BCB            MOV ECX,EBX                              ; ecx = this+4 = stcName
0235DBF6    C747 30 0000000>MOV DWORD PTR DS:[EDI+0x30],0x0
0235DBFD    C747 18 0000000>MOV DWORD PTR DS:[EDI+0x18],0x0
0235DC04    C747 34 0000000>MOV DWORD PTR DS:[EDI+0x34],0x0
0235DC0B    FF15 B424E103   CALL DWORD PTR DS:[<&Qt5Core.?isEmpty@QS>; Qt5Core.?isEmpty@QString@@QBE_NXZ
0235DC11    84C0            TEST AL,AL
0235DC13    0F85 75020000   JNZ 010Edito.0235DE8E
0235DC19    8D4F 08         LEA ECX,DWORD PTR DS:[EDI+0x8]           ; ecx = 序列號
0235DC1C    FF15 B424E103   CALL DWORD PTR DS:[<&Qt5Core.?isEmpty@QS>; Qt5Core.?isEmpty@QString@@QBE_NXZ
0235DC22    84C0            TEST AL,AL
0235DC24    0F85 64020000   JNZ 010Edito.0235DE8E

以上一段校驗字符串是否爲空的代碼。繼續往下分析。


0235DC2A    8D45 DC         LEA EAX,DWORD PTR SS:[EBP-0x24]
0235DC2D    8BCF            MOV ECX,EDI                              ; ecx = this
0235DC2F    50              PUSH EAX                                 ; 傳出參數
0235DC30    E8 3ABF04FF     CALL 010Edito.013A9B6F

某函數傳入了一個局部變量地址ebp-0x24,調用前後觀察一下這個變量:

0018A230  05658AD8
0018A234  0018A258
0018A238  697CC033  返回到 Qt5Core.697CC033 來自 msvcr120.free

調用後:

0018A230  11 12 22 33 34 44 55 56
0018A238  66 77 7C 69 D8 8A 65 05

也就是說,這個函數把輸入的序列號字符串轉換成了十六進制數。

這時我意識到,序列號應該用0011-2233-4455-6677-8899,這樣後面的下標定位可能會方便些。


022FDC40    PUSH DWORD PTR DS:[ESI]                  ; ascii "999"
022FDC42    MOV ECX,EBX                              ; ecx = this+4 = name
022FDC44    CALL DWORD PTR DS:[<&Qt5Core.??8QString@>; Qt5Core.??8QString@@QBE_NPBD@Z
022FDC4A    TEST AL,AL
022FDC4C    JNZ 010Edito.022FDE75
022FDC52    ADD ESI,0x4
022FDC55    CMP ESI,010Edito.03DA4618
022FDC5B  ^ JL SHORT 010Edito.022FDC40               ; while(esi < xxx )

接下來的這個函數,通過分析只發現了“999”字符串,暫時跳過。下面會遇到關鍵的3個函數,我給它們加了標籤,calc1, calc2, calc3.


第1個call

022FDC5D    MOV BL,BYTE PTR SS:[EBP-0x21]            ; bl = serial[3]
022FDC60    MOV BH,BYTE PTR SS:[EBP-0x1F]            ; bh = serial[5]
022FDC63    CMP BL,0x9C                              ; if(serial[3]!=[0x9C,0xFC,0xAC]) return 0xE7
022FDC66    JNZ SHORT 010Edito.022FDCD8
022FDC68    MOV AL,BYTE PTR SS:[EBP-0x24]
022FDC6B    XOR AL,BYTE PTR SS:[EBP-0x1E]            ; al = serial[0] ^ serial[6]
022FDC6E    MOV BYTE PTR SS:[EBP-0x18],AL            ; [ebp-0x18] = serial[0] ^ serial[6]
022FDC71    MOV AL,BYTE PTR SS:[EBP-0x23]
022FDC74    XOR AL,BYTE PTR SS:[EBP-0x1D]            ; al = serial[1] ^ serial[7]
022FDC77    PUSH DWORD PTR SS:[EBP-0x18]             ; ARG: serial[0] ^ serial[6]
022FDC7A    MOVZX ECX,AL
022FDC7D    MOV EAX,0x100
022FDC82    IMUL CX,AX                               ; ecx = (serial[1] ^ serial[7]) * 0x100
022FDC86    MOV AL,BYTE PTR SS:[EBP-0x22]
022FDC89    XOR AL,BH                                ; al = serial[2] ^ serial[5]
022FDC8B    MOVZX EAX,AL
022FDC8E    ADD CX,AX                                ; cl = serial[2] ^ serial[5]
022FDC91    MOVZX ESI,CX                             ; esi = ecx & 0xFFFF
022CDC94    CALL <010Edito.calc1>

上面流程做了以下內容:

  • if(serial[3]!=[0x9C,0xFC,0xAC]) return 0xE7
  • ecx = (serial[1] ^ serial[7]) * 0x100 + serial[2] ^ serial[5], ecx &= 0xFFFF
  • 010Edito.01347644( serial[0]^serial[6] )

這個call經過jmp之後,執行下面的邏輯。

022FD0B0    PUSH EBP
022FD0B1    MOV EBP,ESP
022FD0B3    MOV EAX,DWORD PTR SS:[EBP+0x8]           ; serial[0] ^ serial[6]
022FD0B6    XOR AL,0x18
022FD0B8    ADD AL,0x3D
022FD0BA    XOR AL,0xA7                              ; al = ((al ^ 0x18) + 0x3D) ^ 0xA7
022FD0BC    POP EBP
022FD0BD    RETN

是一段簡單的計算返回新的數字。

第2個call

022FDC99    0FB6C0          MOVZX EAX,AL                             ; eax = eax & 0xFF
022FDC9C    56              PUSH ESI                                 ; ARG:esi = ecx & 0xFFFF
022FDC9D    8947 1C         MOV DWORD PTR DS:[EDI+0x1C],EAX          ; [this+0x1c] = eax
022FDCA0    E8 23A704FF     CALL <010Edito.calc2>
022FDCA5    8B4F 1C         MOV ECX,DWORD PTR DS:[EDI+0x1C]          ; ecx = [this+0x1c]
022FDCA8    83C4 08         ADD ESP,0x8
022FDCAB    0FB7C0          MOVZX EAX,AX                             ; eax = eax & 0xFFFF
022FDCAE    8947 20         MOV DWORD PTR DS:[EDI+0x20],EAX          ; [this+0x20] = eax

第一個call之後,結果會保存在對象的偏移0x1c處。calc2的結果也會保存在對象偏移0x20處。

緊接着會對之前得到的esi,也就是ecx進行計算。這個call經過jmp進入下面的流程。

022FD020    PUSH EBP
022FD021    MOV EBP,ESP
022FD023    MOV EAX,DWORD PTR SS:[EBP+0x8]
022FD026    MOV ECX,0xB                              ; ecx = 0xb
022FD02B    XOR EAX,0x7892
022FD030    ADD EAX,0x4D30
022FD035    XOR EAX,0x3421
022FD03A    MOVZX EAX,AX                             ; ax = ((ARG ^ 0x7892) + 0x4D30) ^ 0x3421
022FD03D    CDQ
022FD03E    IDIV ECX                                 ; edx = eax % 0xB, eax = eax / 0xB
022FD040    TEST EDX,EDX
022FD042    JE SHORT 010Edito.022FD046               ; if(eax % 0xB == 0) return eax / 0xB
022FD044    XOR EAX,EAX                              ; else return 0
022FD046    POP EBP
022FD047    RETN

這個函數內部計算出一個ax,然後除以11,如果整除就返回商,不整除就返回0.

022FDCA5    MOV ECX,DWORD PTR DS:[EDI+0x1C]          ; ecx = [this+0x1c]
022FDCA8    ADD ESP,0x8
022FDCAB    MOVZX EAX,AX                             ; eax = eax & 0xFFFF
022FDCAE    MOV DWORD PTR DS:[EDI+0x20],EAX          ; [this+0x20] = eax
022FDCB1    TEST ECX,ECX
022FDCB3    JE 010Edito.022FDE75
022FDCB9    TEST EAX,EAX
022FDCBB    JE 010Edito.022FDE75
022FDCC1    CMP EAX,0x3E8
022FDCC6    JA 010Edito.022FDE75                   ; if(ecx==0 || eax==0 || eax>0x3E8) return 0xE7
022FDCCC    CMP ECX,0x2
022FDCCF    SBB ESI,ESI
022FDCD1    AND ESI,ECX
022FDCD3    JMP 010Edito.022FDD8B                   ;跳轉

這裏要注意,第2個call要返回商纔行,而且這個商不能大於0x3E8。到這裏,第二個call的流程已經可以實現了。

while (1)
{
    BYTE k1 = 0, k7 = 0, k2 = 0, k5 = 0;
    k1 = rand() % 0xFF;
    k7 = rand() % 0xFF;
    k2 = rand() % 0xFF;
    k5 = rand() % 0xFF;
    dwArg1 = ((k1 ^ k7 & 0xFF) * 0x100 + k2 ^ k5 & 0xFF) & 0xFFFF;
    dwRet1 = (((dwArg1 ^ 0x7892) + 0x4D30) ^ 0x3421) & 0xFFFF;
    if (dwRet1 % 0xB == 0
        && dwRet1 / 0xB <= 0x3E8)
    {
        dwRet1 /= 0xB;
        serial[1] = k1;
        serial[7] = k7;
        serial[2] = k2;
        serial[5] = k5;
        break;
    }
}
/*
eg. 110ff89c345f55976677
check1 返回0x27664033
*/

第3個call

0225DD8B    LEA EAX,DWORD PTR SS:[EBP-0x14]
0225DD8E    PUSH EAX                         ; 傳出參數,調用後得到utf8 stcName(\0\0結尾)
0225DD8F    LEA ECX,DWORD PTR DS:[EDI+0x4]   ; &this->stcName
022CDD92    CALL DWORD PTR DS:[<&Qt5Core.?toUtf8]

跳轉後,程序將用戶名轉爲了utf8編碼,在這裏其實就是以兩個0結尾的ascii字符串,地址保存在棧裏。

在這裏插入圖片描述

這個結構體和對象中的結構體一致,其中有個0x10代表字符串相對結構體的偏移。

0225DD98    PUSH DWORD PTR DS:[EDI+0x20]
0225DD9B    XOR EAX,EAX
0225DD9D    MOV DWORD PTR SS:[EBP-0x4],0x0
0225DDA4    CMP BL,0xFC
0225DDA7    LEA ECX,DWORD PTR SS:[EBP-0x14]  ; ecx = &stcName utf8
0225DDAA    PUSH ESI
0225DDAB    SETNE AL                         ; if(bl = serial[3] != 0xFC) al = 1
0225DDAE    PUSH EAX
0225DDAF    CALL DWORD PTR DS:[<&Qt5Core.?data@QByteArray@@QAEPADXZ>>; Qt5Core.?data@QByteArray@@QAEPADXZ
0225DDB5    PUSH EAX                        ; szName utf8
0225DDB6    CALL <010Edito.calc3>          ; calc(szName, 1, 0, [this+0x20])!!!!!!!!!!!!!!!

經過分析,calc3有4個參數:用戶名, 1, 0, calc2結果。 之後要想讓check1返回0x2D,就需要[this+0x1c],也就是calc1計算出來的結果大於等於arg0 = 9。 它應該是結合用戶名和calc2計算出一個驗證碼。

分析一下calc3.

0225D120    PUSH EBP
0225D121    MOV EBP,ESP
0225D123    SUB ESP,0x10
0225D126    MOV EDX,DWORD PTR SS:[EBP+0x8]          ; edx = utf8 szName
0225D129    XOR ECX,ECX
0225D12B    PUSH ESI
0225D12C    MOV ESI,EDX                             ; esi = urf8 szName
0225D12E    MOV DWORD PTR SS:[EBP-0x4],ECX          ; unset
0225D131    PUSH EDI                                ; this
0225D132    LEA EDI,DWORD PTR DS:[ESI+0x1]          ; edi = esi+1
0225D135    MOV AL,BYTE PTR DS:[ESI]                ; do{ al = szName[esi]
0225D137    INC ESI                                 ; ++esi
0225D138    TEST AL,AL                              ; }
0225D13A  ^ JNZ SHORT 010Edito.0225D135             ; while(al != 0) ;
0225D13C    SUB ESI,EDI                             ; esi = len(name)
0225D13E    XOR EDI,EDI                             ; edi = 0
0225D140    TEST ESI,ESI
0225D142    JLE 010Edito.0225D238                   ; if(len(name) <= 0) return 0
0225D148    PUSH EBX
0225D149    MOV EBX,DWORD PTR SS:[EBP+0x14]
0225D14C    MOV DWORD PTR SS:[EBP-0x10],ECX         ; unset
0225D14F    MOV DWORD PTR SS:[EBP-0xC],ECX          ; unset
0225D152    MOV ECX,DWORD PTR SS:[EBP+0x10]         ; ecx = 0
0225D155    SHL EBX,0x4
0225D158    SUB EBX,DWORD PTR SS:[EBP+0x14]         ;ebx = (calc2<<0x4) - calc2
0225D15B    SHL ECX,0x4
0225D15E    ADD ECX,DWORD PTR SS:[EBP+0x10]
0225D161    MOV DWORD PTR SS:[EBP-0x8],ECX          ; unset
0225D164    MOVZX EAX,BYTE PTR DS:[EDI+EDX]         ; do{ eax = name[edi]
0225D168    PUSH EAX
0225D169    CALL DWORD PTR DS:[<&MSVCR120.toupper>] ; msvcr120.toupper
0225D16F    MOV EDX,EAX                             ; edx = eax = upper(name[0])
0225D171    ADD ESP,0x4
0225D174    MOV ECX,DWORD PTR DS:[EDX*4+0x3D04148]
0225D17B    ADD ECX,DWORD PTR SS:[EBP-0x4]          ; ecx = g_array[4 * upper(name[0]) ] + [ebp-4]
0225D17E    CMP DWORD PTR SS:[EBP+0xC],0x0
0225D182    JE SHORT 010Edito.0225D1CE              ; if(arg1 == 0) jmp
0225D184    LEA EAX,DWORD PTR DS:[EDX+0xD]          ; if(arg1 != 0){
0225D187    AND EAX,0xFF                            	; eax = ( upper(name[0]) + 0xD ) & 0xFF
0225D18C    XOR ECX,DWORD PTR DS:[EAX*4+0x3D04148]  	; ecx ^= g_array[eax * 4]
0225D193    LEA EAX,DWORD PTR DS:[EDX+0x2F]
0225D196    AND EAX,0xFF                            	; eax = ( upper(name[0]) + 0x2F ) & 0xFF
0225D19B    IMUL ECX,DWORD PTR DS:[EAX*4+0x3D04148] 	; ecx *= g_array[eax * 4]
0225D1A3    MOV EAX,DWORD PTR SS:[EBP-0x8]
0225D1A6    MOVZX EAX,AL                            	; eax = [ebp-8] & 0xFF
0225D1A9    ADD ECX,DWORD PTR DS:[EAX*4+0x3D04148]  	; ecx += g_array[eax * 4]
0225D1B0    MOVZX EAX,BL                            	; eax = ebx & 0xFF
0225D1B3    ADD ECX,DWORD PTR DS:[EAX*4+0x3D04148]  	; ecx += g_array[eax * 4]
0225D1BA    MOV EAX,DWORD PTR SS:[EBP-0xC]
0225D1BD    MOVZX EAX,AL                            	; eax = [ebp-0xc] & 0xFF
0225D1C0    ADD ECX,DWORD PTR DS:[EAX*4+0x3D04148]  	; ecx += g_array[eax * 4]
0225D1C7    MOV EAX,ECX                             	; eax = ecx
0225D1C9    MOV DWORD PTR SS:[EBP-0x4],EAX          	; [ebp-4] = eax
0225D1CC    JMP SHORT 010Edito.0225D216             ; }
0225D1CE    LEA EAX,DWORD PTR DS:[EDX+0x3F]         ; else{
0225D1D1    AND EAX,0xFF                            	; eax = (upper(name[edi) + 0x3F) & 0xFF
0225D1D6    XOR ECX,DWORD PTR DS:[EAX*4+0x3D04148]  	; ecx ^= g_array[eax * 4]
0225D1DD    LEA EAX,DWORD PTR DS:[EDX+0x17]
0225D1E0    AND EAX,0xFF                            	; eax = (upper(name[edi) + 0x17) & 0xFF
0225D1E5    IMUL ECX,DWORD PTR DS:[EAX*4+0x3D04148] 	; ecx *= g_array[eax * 4]
0225D1ED    MOV EAX,DWORD PTR SS:[EBP-0x8]
0225D1F0    MOVZX EAX,AL                            	; eax = [EBP-0x8] & 0xFF
0225D1F3    ADD ECX,DWORD PTR DS:[EAX*4+0x3D04148]  	; ecx += g_array[eax * 4]
0225D1FA    MOVZX EAX,BL                            	; eax = bl & 0xFF
0225D1FD    ADD ECX,DWORD PTR DS:[EAX*4+0x3D04148]  	; ecx += g_array[eax * 4]
0225D204    MOV EAX,DWORD PTR SS:[EBP-0x10]
0225D207    MOVZX EAX,AL                            	; eax = [EBP-0x10] & 0xFF
0225D20A    ADD ECX,DWORD PTR DS:[EAX*4+0x3D04148]  	; ecx += g_array[eax * 4]
0225D211    MOV EAX,ECX                             	; eax = ecx
0225D213    MOV DWORD PTR SS:[EBP-0x4],ECX          	; [ebp-4] = ecx  }
0225D216    ADD DWORD PTR SS:[EBP-0xC],0x13         ; [ebp-c] += 0x13
0225D21A    INC EDI                                 ; ++edi
0225D21B    ADD DWORD PTR SS:[EBP-0x8],0x9          ; [ebp-8] += 9
0225D21F    ADD EBX,0xD                             ; ebx += 0xD
0225D222    ADD DWORD PTR SS:[EBP-0x10],0x7         ; [ebp-0x10] += 7
0225D226    MOV EDX,DWORD PTR SS:[EBP+0x8]          ; edx = [ebp+8]
0225D229    CMP EDI,ESI                             ; }
0225D22B  ^ JL 010Edito.0225D164                    ; while(edi < len(name))
0225D231    POP EBX
0225D232    POP EDI
0225D233    POP ESI
0225D234    MOV ESP,EBP
0225D236    POP EBP
0225D237    RETN
0225D238    POP EDI
0225D239    MOV EAX,ECX
0225D23B    POP ESI
0225D23C    MOV ESP,EBP
0225D23E    POP EBP
0225D23F    RETN

這個函數在計算時用到了一個全局數組,實現註冊機就需要在OD中數據窗口把數組以DWORD的形式拷貝下來。

代碼驗證:

DWORD calc3(char* pName, DWORD dwArg1 /*=1*/, DWORD dwArg2 /*=0*/, DWORD dwCalc2)
{
	int nLenName = strlen(pName);
	DWORD ebx = (dwCalc2 << 0x4) - dwCalc2;
	DWORD eax = 0, ecx = 0, edx;
	DWORD loc_4 = 0, loc_8 = 0, loc_c = 0, loc_10 = 0;

	for (int i = 0; i < nLenName; ++i)
	{
		edx = upper(pName[i]);
		ecx = g_array[edx] + loc_4;
		if (dwArg1 != 0)
		{
			eax = (edx + 0xD) & 0xFF;
			ecx ^= g_array[eax];

			eax = (edx + 0x2F) & 0xFF;
			ecx *= g_array[eax];

			eax = loc_8 & 0xFF;
			ecx += g_array[eax];

			eax = ebx & 0xFF;
			ecx += g_array[eax];

			eax = loc_c & 0xFF;
			ecx += g_array[eax];

			eax = ecx;
			loc_4 = eax;
		}
		else
		{
			eax = (edx + 0x3F) & 0xFF;
			ecx ^= g_array[eax];

			eax = (edx + 0x17) & 0xFF;
			ecx *= g_array[eax];

			eax = loc_8 & 0xFF;
			ecx += g_array[eax];

			eax = ebx & 0xFF;
			ecx += g_array[eax];

			eax = loc_10 & 0xFF;
			ecx += g_array[eax];

			eax = ecx;
			loc_4 = eax;
		}

		//printf("	eax: 0x%X\n", eax);

		loc_c += 0x13;
		loc_8 += 0x9;
		ebx += 0xD;
		loc_10 += 0x7;

	}
	return eax;
}

在這裏插入圖片描述

至此,3個計算函數都已經實現了。

修改第二個call

之後,還有對於這個驗證碼的驗證過程。

0225DDBB    MOV EDX,EAX                      ; edx = calc3
0225DDBD    ADD ESP,0x10
0225DDC0    CMP BYTE PTR SS:[EBP-0x20],DL
0225DDC3    JNZ 010Edito.0225DE4A            ; if( serial[4] != dl)
0225DDC9    MOV ECX,EDX
0225DDCB    SHR ECX,0x8
0225DDCE    CMP BH,CL
0225DDD0    JNZ SHORT 010Edito.0225DE4A      ; || bh != cl (serial[5] != (edx>>8) & 0xFF)
0225DDD2    MOV ECX,EDX
0225DDD4    SHR ECX,0x10
0225DDD7    CMP BYTE PTR SS:[EBP-0x1E],CL    ; || serial[6] != (edx>>0x10) & 0xFF
0225DDDA    JNZ SHORT 010Edito.0225DE4A
0225DDDC    SHR EAX,0x18
0225DDDF    CMP BYTE PTR SS:[EBP-0x1D],AL
0225DDE2    JNZ SHORT 010Edito.0225DE4A      ; || serial[7] != (eax>>0x18) & 0xFF
0225DDE4    CMP BL,0x9C
0225DDE7    JNZ SHORT 010Edito.0225DDF8      ; || (serial[3] != [0x9C, 0xFC, 0xAC])  return 0xE7
0225DDE9    MOV EAX,DWORD PTR SS:[EBP+0x8]
0225DDEC    CMP EAX,DWORD PTR DS:[EDI+0x1C]
0225DDEF    JBE SHORT 010Edito.0225DE43      ; else if(arg0 <= [this+0x1C]) return 0x2D!!!!!!!!!!!!!!!
0225DDF1    MOV ESI,0x4E
0225DDF6    JMP SHORT 010Edito.0225DE4F      ; return 0x4E

需要滿足以下幾點:

  • serial[4] == calc3 & 0xFF
  • serial[5] == (calc3>>8) & 0xFF
  • serial[6] == (calc3>>0x10) & 0xFF
  • serial[7] == (calc3>>0x18) & 0xFF
  • serial[3] == 0x9C || 0xFC || 0xAC
  • calc1 >= 9

所以上面的代碼,尤其是calc2生成隨機序列號字節的部分,還需要滿足這幾個條件,而且因爲calc1, calc3都涉及serial[6]calc2,calc3都涉及serial[5], serial[7],所以calc3中需要重新生成這幾個序列號字節。

DWORD Obj::calc2(DWORD dwArg)
{
	DWORD eax = 0;
	BYTE k1 = 0, k7 = 0, k2 = 0, k5 = 0;

	srand(time(NULL));
	while (1)
	{
		k1 = rand() % 0xFF;
		k7 = rand() % 0xFF;
		k2 = rand() % 0xFF;
		k5 = rand() % 0xFF;
		dwArg = ((k1 ^ k7 & 0xFF) * 0x100 + k2 ^ k5 & 0xFF) & 0xFFFF;
		eax = (((dwArg ^ 0x7892) + 0x4D30) ^ 0x3421) & 0xFFFF;
		if (eax % 0xB == 0
			&& eax / 0xB <= 0x3E8)
		{

			eax /= 0xB;
			dwCalc2 = eax;

			dwCalc3 = calc3(name, 1, 0, dwCalc2);


			/***********************************
			*	serial[5],serial[7]與calc3衝突
			*************************************/
			if (
				((dwCalc3 >> 0x18) & 0xFF) != k7
				|| ((dwCalc3 >> 0x8) & 0xFF) != k5)
			{
				continue;
			}


			serial[1] = k1;
			serial[7] = k7;
			serial[2] = k2;
			serial[5] = k5;

			serial[4] = dwCalc3 & 0xFF;
			serial[5] = (dwCalc3 >> 0x8) & 0xFF;
			serial[6] = (dwCalc3 >> 0x10) & 0xFF;

			/********************************
			*	serial[6],calc3與calc1衝突
			*********************************/
			while (1)
			{
				dwCalc1 = calc1(serial[0] ^ serial[6]);
				if (dwCalc1 >= 9)
				{
					break;
				}
				serial[0] = rand() % 0xFF;
			}

			break;
		}
	}

	return eax;
}

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-32uMnm7q-1573782008059)(010Editor逆向.assets/1573734143844.png)]

在這裏插入圖片描述

3. 網絡驗證分析

註冊成功一段時間後,再次註冊會發生網絡驗證失敗的情況。OD跟蹤,check2中以及主流程中各有一處條件跳轉,打補丁改爲jmp即可通過網絡驗證。

4. 小結

010Editor其實有3種算法,由serial[3]是0x9C,0xAC,0xFC判斷,這裏只分析了0x9C一種情況。

網絡驗證部分也可以跳進關鍵函數分析數據包,自己搭建服務器通過驗證,當然比較複雜。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章