010Editor 逆向分析
用於測試的用戶名爲
foobar
,序列號爲1112-2233-3444-5556-6677
1. 暴力破解分析
思路:
- 找到註冊窗口
- 測試註冊窗口反應
- 根據反應做出下一步分析(猜測api,敏感字符串)
- 動態分析關鍵跳轉、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;
}
3. 網絡驗證分析
註冊成功一段時間後,再次註冊會發生網絡驗證失敗的情況。OD跟蹤,check2中以及主流程中各有一處條件跳轉,打補丁改爲jmp即可通過網絡驗證。
4. 小結
010Editor其實有3種算法,由serial[3]是0x9C,0xAC,0xFC判斷,這裏只分析了0x9C一種情況。
網絡驗證部分也可以跳進關鍵函數分析數據包,自己搭建服務器通過驗證,當然比較複雜。