ollydbg -- WinISO v5.3中文版註冊演算法

看雪資料發表於2015-11-15

【軟體名稱】:WinISO v5.3中文版

【主要功能】:一款功能超級強大的光碟工具,它可以轉換CD-ROM映像檔案格式,並且可以直接編輯光碟映像檔案,
可以處理幾乎所有的CD-ROM 映像檔案,包括 ISO 和 BIN 。透過 WinISO,你可以在映像檔案內部新增/刪除/重新命名/提取檔案。
你可以將其他格式的映像檔案轉換為標準的ISO格式,同時你也可以從你的 CD-ROM 中建立 ISO 映像檔案。

【軟體限制】:未註冊版不支援超過100M的檔案且每次啟動彈出註冊提示框。
 
【作者宣告】:破解該軟體純粹出於興趣,無其他目的,失誤之處敬請指教。

【破解工具】:ollydbg1.08, FI2.50,UPX1.2x

――――――――――――――――――――――――――――――――――――

【破解過程】:

FI檢視是UPX1.20加的殼,用UPX脫之,存為WinISO1.exe。再查是BC編的。

使用者名稱:worldhello 
試煉碼:12345678

程式要求重啟驗證,稍加觀察可發現程式把註冊資訊寫到登錄檔了。
用ollydbg載入,找和登錄檔有關的api,最後發現RegQueryValueExA能迅速找到註冊碼驗證之處。
一路往下,來到0041BCD7,這裡的call計算試煉碼的長度並與48比較,超過則截掉48位以後的,不足就補足48位。
再往下:

0041BCDF  LEA ECX,DWORD PTR SS:[EBP-D0]
0041BCE5  PUSH ECX                                                              ; /Arg1
0041BCE6  CALL WinISO1.004BBE6C                                      ; WinISO1.004BBE6C
0041BCEB  POP ECX
0041BCEC  XOR ESI,ESI
0041BCEE  MOV EBX,WinISO1.004D0DBC
0041BCF3  /MOV EAX,DWORD PTR DS:[EBX]
0041BCF5  |LEA EDX,DWORD PTR SS:[EBP-D0]
0041BCFB  |/MOV CL,BYTE PTR DS:[EAX]
0041BCFD  ||CMP CL,BYTE PTR DS:[EDX]
0041BCFF  ||JNZ SHORT WinISO1.0041BD38
0041BD01  ||TEST CL,CL
0041BD03  ||JE SHORT WinISO1.0041BD17
0041BD05  ||MOV CL,BYTE PTR DS:[EAX+1]
0041BD08  ||CMP CL,BYTE PTR DS:[EDX+1]
0041BD0B  ||JNZ SHORT WinISO1.0041BD38
0041BD0D  ||ADD EAX,2
0041BD10  ||ADD EDX,2
0041BD13  ||TEST CL,CL
0041BD15  |JNZ SHORT WinISO1.0041BCFB
0041BD17  |JNZ SHORT WinISO1.0041BD38
0041BD19  |DEC DWORD PTR SS:[EBP-30]                               ;到這裡就說明輸入的試煉碼和某組黑名單相同
0041BD1C  |LEA EAX,DWORD PTR SS:[EBP-4]
0041BD1F  |MOV EDX,2
0041BD24  |CALL WinISO1.004C37FC
0041BD29  |MOV ECX,DWORD PTR SS:[EBP-4C]
0041BD2C  |MOV DWORD PTR FS:[0],ECX
0041BD33  |JMP WinISO1.0041BE5D                                                                  ;跳出整個驗證註冊碼的call,玩完啦
0041BD38  |INC ESI
0041BD39  |ADD EBX,4
0041BD3C  |CMP ESI,12                                                   ;總共18組黑名單
0041BD3F  JL SHORT WinISO1.0041BCF3
0041BD41  MOV WORD PTR SS:[EBP-3C],14

上面一段程式碼是把試煉碼和一組黑名單上的註冊碼比較,如果碰巧相同,就不進行以後的驗證了。

...........
0041BD55  /MOV AL,BYTE PTR DS:[EBX]
0041BD57  |PUSH EAX
0041BD58  |CALL WinISO1.0041BA48    
0041BD5D  |SHL EAX,4
0041BD60  |POP ECX
0041BD61  |MOV BYTE PTR DS:[ESI],AL    
0041BD63  |MOV DL,BYTE PTR DS:[EBX+1]
0041BD66  |PUSH EDX                                  ; /Arg1
0041BD67  |CALL WinISO1.0041BA48                     ; WinISO1.0041BA48
0041BD6C  |POP ECX
0041BD6D  |ADD BYTE PTR DS:[ESI],AL    
0041BD6F  |INC EDI
0041BD70  |INC ESI
0041BD71  |ADD EBX,2
0041BD74  |CMP EDI,18        
0041BD77  JL SHORT WinISO1.0041BD55

上面把48位的試煉碼換成24個位元組,不允許註冊碼中含0-9及A-F(a-f)中的字元。


0041BD79  MOV CX,WORD PTR SS:[EBP-E8]
0041BD80  CMP DWORD PTR DS:[4E1CA4],0      
0041BD87  PUSH ECX
0041BD88  LEA EAX,DWORD PTR SS:[EBP-F4]
0041BD8E  PUSH EAX
0041BD8F  JE SHORT WinISO1.0041BD99
0041BD91  MOV EDX,DWORD PTR DS:[4E1CA4]      ;edx為使用者名稱
0041BD97  JMP SHORT WinISO1.0041BD9E
0041BD99  MOV EDX,WinISO1.004D11D9
0041BD9E  PUSH EDX                                   ; |Arg1
0041BD9F  CALL WinISO1.0041CAB8                      ; WinISO1.0041CAB8,第一次加密,追進去看
0041BDA4  ADD ESP,0C
0041BDA7  PUSH 0C                                    ; /Arg3 = 0000000C
0041BDA9  LEA EAX,DWORD PTR SS:[EBP-E8]              ; |
0041BDAF  PUSH EAX                                   ; |Arg2
0041BDB0  LEA ECX,DWORD PTR SS:[EBP-F4]              ; |
0041BDB6  PUSH ECX                                   ; |Arg1
0041BDB7  CALL WinISO1.004B56B4                      ; WinISO1.004B56B4,第一次驗證,比較12個位元組
0041BDBC  ADD ESP,0C
0041BDBF  TEST EAX,EAX          ;測試eax
0041BDC1  JE SHORT WinISO1.0041BDDF      ;eax為0則透過第一次驗證
0041BDC3  DEC DWORD PTR SS:[EBP-30]
0041BDC6  LEA EAX,DWORD PTR SS:[EBP-4]
0041BDC9  MOV EDX,2
0041BDCE  CALL WinISO1.004C37FC
0041BDD3  MOV ECX,DWORD PTR SS:[EBP-4C]
0041BDD6  MOV DWORD PTR FS:[0],ECX
0041BDDD  JMP SHORT WinISO1.0041BE5D      ;到這裡就完嘍
0041BDDF  PUSH 18                                    ; /Arg3 = 00000018
0041BDE1  LEA EAX,DWORD PTR SS:[EBP-E8]              ; |
0041BDE7  PUSH EAX                                   ; |Arg2
0041BDE8  MOV EDX,DWORD PTR SS:[EBP-50]              ; |
0041BDEB  ADD EDX,3                                  ; |
0041BDEE  PUSH EDX                                   ; |Arg1
0041BDEF  CALL WinISO1.004B5404                      ; WinISO1.004B5404
0041BDF4  ADD ESP,0C
0041BDF7  LEA ECX,DWORD PTR SS:[EBP-E8]
0041BDFD  PUSH ECX                                   ; /Arg1
0041BDFE  CALL WinISO1.0041CA1C                      ; WinISO1.0041CA1C,第二次加密,追
0041BE03  POP ECX
0041BE04  PUSH 8                                    ; /Arg3 = 00000008
0041BE06  LEA EAX,DWORD PTR SS:[EBP-DC]             ; |
0041BE0C  PUSH EAX                                  ; |Arg2
0041BE0D  LEA EDX,DWORD PTR SS:[EBP-E8]             ; |
0041BE13  PUSH EDX                                  ; |Arg1
0041BE14  CALL WinISO1.004B56B4                     ; WinISO1.004B56B4,第二次驗證,比較8個位元組
0041BE19  ADD ESP,0C
0041BE1C  TEST EAX,EAX          ;測試eax
0041BE1E  JE SHORT WinISO1.0041BE3C      ;eax為0則第二次驗證透過
0041BE20  DEC DWORD PTR SS:[EBP-30]
0041BE23  LEA EAX,DWORD PTR SS:[EBP-4]
0041BE26  MOV EDX,2
0041BE2B  CALL WinISO1.004C37FC
0041BE30  MOV ECX,DWORD PTR SS:[EBP-4C]
0041BE33  MOV DWORD PTR FS:[0],ECX
0041BE3A  JMP SHORT WinISO1.0041BE5D
0041BE3C  MOV EAX,DWORD PTR SS:[EBP-50]
0041BE3F  MOV BYTE PTR DS:[EAX+27],1      ;看來是註冊標誌了
0041BE43  DEC DWORD PTR SS:[EBP-30]
0041BE46  LEA EAX,DWORD PTR SS:[EBP-4]
0041BE49  MOV EDX,2
0041BE4E  CALL WinISO1.004C37FC
0041BE53  MOV ECX,DWORD PTR SS:[EBP-4C]
0041BE56  MOV DWORD PTR FS:[0],ECX
0041BE5D  POP EDI
0041BE5E  POP ESI
0041BE5F  POP EBX
0041BE60  MOV ESP,EBP
0041BE62  POP EBP
0041BE63  RETN

共有兩次加密和兩次驗證,不管驗證透過與否,下面幾句都會執行(包括註冊碼在黑名單上的情況):
DEC DWORD PTR SS:[EBP-30]
LEA EAX,DWORD PTR SS:[EBP-4]
MOV EDX,2
CALL WinISO1.004C37FC
MOV ECX,DWORD PTR SS:[EBP-4C]
MOV DWORD PTR FS:[0],ECX
唯一不同的是,若驗證透過,會多執行MOV EAX,DWORD PTR SS:[EBP-50]和MOV BYTE PTR DS:[EAX+27],1兩步,而這就應該是註冊標誌。
如果只要爆破的話,把驗證不透過的幾處跳轉轉向0041BE3C,處理MOV EAX,DWORD PTR SS:[EBP-50]和MOV BYTE PTR DS:[EAX+27],1。

當然我們的目的不僅是爆破了事。看看第一次加密的call:

0041CAB8  PUSH EBP
0041CAB9  MOV EBP,ESP
0041CABB  ADD ESP,-28
0041CABE  PUSH EBX
0041CABF  PUSH ESI
0041CAC0  PUSH EDI
0041CAC1  PUSH 0C                                    ; /Arg3 = 0000000C
0041CAC3  PUSH 0                                     ; |Arg2 = 00000000
0041CAC5  MOV EAX,DWORD PTR SS:[EBP+C]               ; |
0041CAC8  PUSH EAX                                   ; |Arg1
0041CAC9  CALL WinISO1.004B5474                      ; WinISO1.004B5474
0041CACE  ADD ESP,0C
0041CAD1  MOV EDX,DWORD PTR SS:[EBP+8]
0041CAD4  PUSH EDX
0041CAD5  CALL WinISO1.004B55A8        ;使用者名稱長度
0041CADA  POP ECX
0041CADB  MOV DWORD PTR SS:[EBP-4],EAX
0041CADE  CMP DWORD PTR SS:[EBP-4],0
0041CAE2  JE WinISO1.0041CB68
0041CAE8  LEA EDI,DWORD PTR SS:[EBP-1C]
0041CAEB  MOV ESI,WinISO1.004D1DAC                   ;  ASCII "WinISO_Computing_Inc"
0041CAF0  MOV ECX,5
0041CAF5  LEA EAX,DWORD PTR SS:[EBP-1C]
0041CAF8  REP MOVS DWORD PTR ES:[EDI],DWORD PTR DS>
0041CAFA  MOVS BYTE PTR ES:[EDI],BYTE PTR DS:[ESI]
0041CAFB  PUSH EAX
0041CAFC  CALL WinISO1.004B55A8        ;WinISO_Computing_Inc長度
0041CB01  POP ECX
0041CB02  MOV DWORD PTR SS:[EBP-20],EAX
0041CB05  XOR EDX,EDX
0041CB07  XOR ECX,ECX
0041CB09  XOR EAX,EAX
0041CB0B  MOV DWORD PTR SS:[EBP-24],EAX
0041CB0E  XOR EAX,EAX
0041CB10  MOV DWORD PTR SS:[EBP-28],EAX
0041CB13  /MOV EAX,DWORD PTR SS:[EBP+8]      ;使用者名稱=>eax
0041CB16  |MOV AL,BYTE PTR DS:[EAX+EDX]
0041CB19  |INC EDX
0041CB1A  |CMP EDX,DWORD PTR SS:[EBP-4]      ;是否超過使用者名稱長度
0041CB1D  |JL SHORT WinISO1.0041CB21      
0041CB1F  |XOR EDX,EDX          ;edx清零,重新計算
0041CB21  |XOR AL,BYTE PTR SS:[EBP+10]      ;使用者名稱某位xor試煉碼第1位元組
0041CB24  |ADD AL,23          ;加23H
0041CB26  |XOR AL,BYTE PTR SS:[EBP+ECX-1C]    ;xor WinISO_Computing_Inc的某位
0041CB2A  |INC ECX
0041CB2B  |CMP ECX,DWORD PTR SS:[EBP-20]    ;是否超過WinISO_Computing_Inc長度
0041CB2E  |JL SHORT WinISO1.0041CB32      ;
0041CB30  |XOR ECX,ECX          ;ecx清零,重新計算
0041CB32  |MOVZX EBX,WORD PTR SS:[EBP+10]    ;試煉碼前2位元組放到ebx
0041CB36  |SAR EBX,8          ;右移8,得到試煉碼第2位元組
0041CB39  |MOV ESI,DWORD PTR SS:[EBP-24]    ;esi做計數器
0041CB3C  |ADD AL,BL          ;al加bl
0041CB3E  |MOV EBX,DWORD PTR SS:[EBP+C]      ;[EBP+C]初始為0,由此開始的12位元組將與輸入的前12位元組xor
0041CB41  |XOR BYTE PTR DS:[EBX+ESI],AL      ;
0041CB44  |INC DWORD PTR SS:[EBP-24]      ;
0041CB47  |CMP DWORD PTR SS:[EBP-24],0C      ;計數器是否超過12
0041CB4B  |JL SHORT WinISO1.0041CB52      
0041CB4D  |XOR EAX,EAX
0041CB4F  |MOV DWORD PTR SS:[EBP-24],EAX    ;超過計數器置0,重新開始
0041CB52  |INC DWORD PTR SS:[EBP-28]      ;
0041CB55  |CMP DWORD PTR SS:[EBP-28],0FF    ;是否超過255次
0041CB5C  JL SHORT WinISO1.0041CB13      ;沒有就繼續
0041CB5E  MOV EDX,DWORD PTR SS:[EBP+C]
0041CB61  MOV CX,WORD PTR SS:[EBP+10]
0041CB65  MOV WORD PTR DS:[EDX],CX      ;把試煉碼前2位元組重新置回
0041CB68  POP EDI
0041CB69  POP ESI
0041CB6A  POP EBX
0041CB6B  MOV ESP,EBP
0041CB6D  POP EBP
0041CB6E  RETN


第二次的加密call:

0041CA1C  PUSH EBP
0041CA1D  MOV EBP,ESP
0041CA1F  ADD ESP,-18
0041CA22  LEA EAX,DWORD PTR SS:[EBP-18]
0041CA25  PUSH EBX
0041CA26  PUSH ESI
0041CA27  PUSH EDI
0041CA28  MOV EDI,WinISO1.004D19AC
0041CA2D  MOV EBX,DWORD PTR SS:[EBP+8]
0041CA30  MOV ESI,WinISO1.004D18AC
0041CA35  MOV DWORD PTR SS:[EBP-4],WinISO1.004D1AAC
0041CA3C  MOV DWORD PTR SS:[EBP-8],WinISO1.004D1BAC
0041CA43  PUSH EAX                                           ; /Arg1
0041CA44  CALL WinISO1.0041C464                              ; WinISO1.0041C464 
0041CA49  POP ECX
0041CA4A  LEA EDX,DWORD PTR SS:[EBP-18]           ; edx指向eax
0041CA4D  PUSH EDX                                           ; /Arg2  
0041CA4E  PUSH EBX                                           ; |Arg1  試煉碼1-4位元組
0041CA4F  CALL WinISO1.0041C524                              ; WinISO1.0041C524  
0041CA54  ADD ESP,8
0041CA57  LEA ECX,DWORD PTR SS:[EBP-18]
0041CA5A  PUSH ECX                                           ; /Arg2  
0041CA5B  LEA EAX,DWORD PTR DS:[EBX+4]                       ; |
0041CA5E  PUSH EAX                                           ; |Arg1  試煉碼5-8位元組
0041CA5F  CALL WinISO1.0041C524                              ; WinISO1.0041C524
0041CA64  ADD ESP,8
0041CA67  LEA EDX,DWORD PTR SS:[EBP-18]
0041CA6A  PUSH EDX                                           ; /Arg2  
0041CA6B  LEA ECX,DWORD PTR DS:[EBX+8]                       ; |
0041CA6E  PUSH ECX                                           ; |Arg1  試煉碼9-12位元組
0041CA6F  CALL WinISO1.0041C524                              ; WinISO1.0041C524
0041CA74  ADD ESP,8
0041CA77  LEA EAX,DWORD PTR DS:[EBX+C]
0041CA7A  PUSH EDI                                           ; /Arg3  引數2,3是內建的加密表
0041CA7B  PUSH ESI                                           ; |Arg2  
0041CA7C  PUSH EAX                                           ; |Arg1  引數1指向試煉碼第13位元組
0041CA7D  CALL WinISO1.0041C958                              ; WinISO1.0041C958
0041CA82  ADD ESP,0C
0041CA85  MOV EDX,DWORD PTR SS:[EBP-8]
0041CA88  PUSH EDX                                           ; /Arg3  另2組內建加密表
0041CA89  MOV ECX,DWORD PTR SS:[EBP-4]                       ; |
0041CA8C  PUSH ECX                                           ; |Arg2
0041CA8D  LEA EAX,DWORD PTR DS:[EBX+10]                      ; |
0041CA90  PUSH EAX                                           ; |Arg1  引數1指向試煉碼第17位元組
0041CA91  CALL WinISO1.0041C958                              ; WinISO1.0041C958
0041CA96  ADD ESP,0C
0041CA99  XOR ECX,ECX            ;ecx清0,計數器
0041CA9B  LEA EDX,DWORD PTR DS:[EBX+C]        ;edx指向試煉碼第13位元組
0041CA9E  MOV EAX,WinISO1.004D18A0
0041CAA3  MOV BL,BYTE PTR DS:[EAX]        ;eax所指位元組傳給bl
0041CAA5  ADD BYTE PTR DS:[EDX],BL        ;edx所指地址加bl
0041CAA7  INC ECX            ;計數器加1
0041CAA8  INC EDX            
0041CAA9  INC EAX            
0041CAAA  CMP ECX,0C            ;是否超過12
0041CAAD  JL SHORT WinISO1.0041CAA3        ;小於12繼續
0041CAAF  POP EDI
0041CAB0  POP ESI
0041CAB1  POP EBX
0041CAB2  MOV ESP,EBP
0041CAB4  POP EBP
0041CAB5  RETN

第二次加密有點BT,如果進0041CA44的call看一下,會發現MD5演算法的4個常數,很自然的會想到CALL WinISO1.0041C524用了MD5加密。
進去看一下,怎麼看都覺得是MD5,再研究一下發現,總共3次call 0041C524,第一次CALL之後把加密結果覆蓋了4個常數所在記憶體,
因此第二次加密時4個常數(還能稱常數嗎?)已改變,然後加密結果再度覆蓋同一記憶體地址再進行第三次加密。
說了這麼多,真正有用的是下面一句:以上這個類似MD5的演算法根本就是煙霧彈!在之後的加密及驗證中根本沒有用到加密後的資料。
(氣憤啊!浪費了我多少時間!)

CALL WinISO1.0041C958才是有用的call,去看一下:

0041C958  PUSH EBP
0041C959  MOV EBP,ESP
0041C95B  ADD ESP,-0C
0041C95E  XOR EDX,EDX
0041C960  XOR ECX,ECX
0041C962  PUSH EBX
0041C963  PUSH ESI
0041C964  PUSH EDI
0041C965  MOV EAX,DWORD PTR SS:[EBP+8]        ;eax指向引數1
0041C968  ADD EAX,4            ;地址往前移4位元組
0041C96B  XOR EBX,EBX
0041C96D  MOV DWORD PTR SS:[EBP-4],EAX        ;寫到臨時變數1中
0041C970  MOV DWORD PTR SS:[EBP-8],EDX        ;0給臨時變數2
0041C973  MOV DWORD PTR SS:[EBP-C],ECX        ;0給臨時變數3
0041C976  /MOV EAX,DWORD PTR SS:[EBP+8]        ;引數1傳給eax
0041C979  |MOV EDX,DWORD PTR SS:[EBP-4]        ;臨時變數1的值傳給edx
0041C97C  |MOV ESI,DWORD PTR DS:[EAX]        ;eax地址的值賦給esi
0041C97E  |MOV EAX,DWORD PTR SS:[EBP+C]        ;引數2的地址傳給eax
0041C981  |MOV EDI,DWORD PTR DS:[EDX]        ;edx地址的值賦給edi
0041C983  |AND ESI,DWORD PTR DS:[EAX]        ;esi 與 引數2
0041C985  |MOV EAX,DWORD PTR SS:[EBP+10]      ;引數3的地址傳給eax
0041C988  |AND EDI,DWORD PTR DS:[EAX]        ;edi 與 引數3
0041C98A  |PUSH ESI                                  ; /Arg1
0041C98B  |CALL WinISO1.0041C93C                       ; WinISO1.0041C93C
0041C990  |POP ECX
0041C991  |PUSH EAX            ;把第1次返回值入棧
0041C992  |PUSH EDI  
0041C993  |CALL WinISO1.0041C93C        
0041C998  |POP ECX
0041C999  |POP EDX            ;第1次返回值出棧,即傳值給edx
0041C99A  |XOR AL,DL            ;第2次返回值xor第1次返回值
0041C99C  |MOV EDX,DWORD PTR SS:[EBP-8]        ;臨時變數2的值給edx
0041C99F  |ADD DWORD PTR SS:[EBP-8],EDX        ;臨時變數2的值加edx
0041C9A2  |AND EAX,0FF            ;eax 與 11111111b
0041C9A7  |AND EAX,1            ;eax 與 00000001b
0041C9AA  |INC EBX            ;ebx自加1,計數器
0041C9AB  |XOR DWORD PTR SS:[EBP-8],EAX        臨時變數2 xor eax
0041C9AE  |ADD DWORD PTR SS:[EBP+C],4        ;引數2的地址+4
0041C9B2  |ADD DWORD PTR SS:[EBP+10],4        ;引數3的地址+4
0041C9B6  |CMP BL,20            ;是否經過32次運算
0041C9B9  JB SHORT WinISO1.0041C976        ;小於32次則繼續
0041C9BB  XOR EBX,EBX            ;清0
0041C9BD  MOV EAX,DWORD PTR SS:[EBP+8]
0041C9C0  MOV EDX,DWORD PTR SS:[EBP-4]
0041C9C3  MOV ESI,DWORD PTR DS:[EAX]
0041C9C5  MOV EAX,DWORD PTR SS:[EBP+C]
0041C9C8  MOV EDI,DWORD PTR DS:[EDX]
0041C9CA  AND ESI,DWORD PTR DS:[EAX]
0041C9CC  MOV EAX,DWORD PTR SS:[EBP+10]
0041C9CF  AND EDI,DWORD PTR DS:[EAX]
0041C9D1  PUSH ESI                                     ; /Arg1
0041C9D2  CALL WinISO1.0041C93C                        ; WinISO1.0041C93C
0041C9D7  POP ECX
0041C9D8  PUSH EAX
0041C9D9  PUSH EDI
0041C9DA  CALL WinISO1.0041C93C
0041C9DF  POP ECX
0041C9E0  POP EDX
0041C9E1  XOR AL,DL
0041C9E3  MOV EDX,DWORD PTR SS:[EBP-C]        ;這裡臨時變數3傳給edx
0041C9E6  ADD DWORD PTR SS:[EBP-C],EDX        ;臨時變數3+edx
0041C9E9  AND EAX,0FF
0041C9EE  AND EAX,1
0041C9F1  INC EBX
0041C9F2  XOR DWORD PTR SS:[EBP-C],EAX
0041C9F5  ADD DWORD PTR SS:[EBP+C],4
0041C9F9  ADD DWORD PTR SS:[EBP+10],4
0041C9FD  CMP BL,20
0041CA00  JB SHORT WinISO1.0041C9BD        ;完全與上面演算法一樣,但引數2和3的地址相應+0x80
0041CA02  MOV ECX,DWORD PTR SS:[EBP+8]        ;引數1給ecx
0041CA05  MOV EAX,DWORD PTR SS:[EBP-8]        ;臨時變數2給eax
0041CA08  MOV DWORD PTR DS:[ECX],EAX        ;eax傳到ecx所指地址
0041CA0A  MOV EDX,DWORD PTR SS:[EBP-4]        ;臨時變數1給edx
0041CA0D  MOV ECX,DWORD PTR SS:[EBP-C]        ;臨時變數3給ecx
0041CA10  MOV DWORD PTR DS:[EDX],ECX        ;ecx傳到edx所指地址
0041CA12  POP EDI
0041CA13  POP ESI
0041CA14  POP EBX
0041CA15  MOV ESP,EBP
0041CA17  POP EBP
0041CA18  RETN

CALL WinISO1.0041C958的引數,第一次引數1指向試煉碼第13位元組,引數2為地址4d18ac,引數3為地址4d19ac,
第二次引數1指向試煉碼第17位元組,引數2為地址4d1aac,引數3為地址4d1bac。
這4處(4d18ac,4d19ac,4d1aac,4d1bac)地址存放了軟體作者內建的4張表。
這個call,第一次將試煉碼13-20位元組加密得到新的8個位元組覆蓋掉13-20位元組所在記憶體。第二次則加密新的17-24位元組,
或者說是第一次加密得到的後4位元組和原21-24位元組,經加密後覆蓋掉17-24位元組。
兩次call 0041C958之後,將這個新得到的12位元組加上由004D18A0開始的內建的12位元組,得到第二次加密結果。

再看一下CALL WinISO1.0041C93C

0041C93C  PUSH EBP
0041C93D  MOV EBP,ESP
0041C93F  MOV EAX,DWORD PTR SS:[EBP+8]      ;引數1給eax
0041C942  XOR EDX,EDX          ;edx清0
0041C944  TEST EAX,EAX          ;eax是否空
0041C946  JE SHORT WinISO1.0041C952      ;是空就結束
0041C948  /INC EDX          ;edx加1
0041C949  |MOV ECX,EAX          ;eax傳給ecx
0041C94B  |DEC ECX          ;ecx減1
0041C94C  |AND EAX,ECX          ;eax 與 ecx
0041C94E  |TEST EAX,EAX          ;測試eax是否為0
0041C950  JNZ SHORT WinISO1.0041C948      ;不為0繼續
0041C952  MOV EAX,EDX          ;edx傳給eax,即函式返回迴圈次數
0041C954  POP EBP
0041C955  RETN

上面這個call實際上計算引數的二進位制值有多少位1,並把該值傳給eax。

下面是比較的call:

004B56B4  PUSH EBP
004B56B5  MOV EBP,ESP
004B56B7  PUSH ESI
004B56B8  PUSH EDI
004B56B9  MOV EDI,DWORD PTR SS:[EBP+10]
004B56BC  MOV ECX,DWORD PTR SS:[EBP+8]
004B56BF  MOV ESI,DWORD PTR SS:[EBP+C]
004B56C2  /CMP EDI,4
004B56C5  |JL SHORT WinISO1.004B56FB
004B56C7  |MOV AL,BYTE PTR DS:[ECX]
004B56C9  |MOV DL,BYTE PTR DS:[ESI]
004B56CB  |CMP DL,AL
004B56CD  |JNZ SHORT WinISO1.004B56FB
004B56CF  |MOV AL,BYTE PTR DS:[ECX+1]
004B56D2  |MOV DL,BYTE PTR DS:[ESI+1]
004B56D5  |CMP DL,AL
004B56D7  |JNZ SHORT WinISO1.004B56FB
004B56D9  |MOV AL,BYTE PTR DS:[ECX+2]
004B56DC  |MOV DL,BYTE PTR DS:[ESI+2]
004B56DF  |CMP DL,AL
004B56E1  |JNZ SHORT WinISO1.004B56FB
004B56E3  |MOV AL,BYTE PTR DS:[ECX+3]
004B56E6  |MOV DL,BYTE PTR DS:[ESI+3]
004B56E9  |CMP DL,AL
004B56EB  |JNZ SHORT WinISO1.004B56FB
004B56ED  |SUB EDI,4
004B56F0  |ADD ECX,4
004B56F3  |ADD ESI,4
004B56F6  |CMP EDI,4
004B56F9  JGE SHORT WinISO1.004B56C2
004B56FB  TEST EDI,EDI
004B56FD  JNZ SHORT WinISO1.004B5703
004B56FF  XOR EAX,EAX
004B5701  JMP SHORT WinISO1.004B571C
004B5703  /MOV AL,BYTE PTR DS:[ECX]
004B5705  |MOV DL,BYTE PTR DS:[ESI]
004B5707  |CMP DL,AL
004B5709  |JNZ SHORT WinISO1.004B5710
004B570B  |INC ECX
004B570C  |INC ESI
004B570D  |DEC EDI
004B570E  JNZ SHORT WinISO1.004B5703
004B5710  XOR ECX,ECX
004B5712  MOV CL,AL
004B5714  XOR EAX,EAX
004B5716  MOV AL,DL
004B5718  SUB ECX,EAX
004B571A  MOV EAX,ECX
004B571C  POP EDI
004B571D  POP ESI
004B571E  POP EBP
004B571F  RETN

第一次驗證是將試煉碼前2位元組經第一次加密得到的12位元組與試煉碼的前12位元組比較,
第二次驗證是將試煉碼後12位元組經第二次加密所得12位元組的前8位元組與試煉碼前8位元組比較(汗,口才不好,說得這麼亂)。
總之這個比較的call演算法很清晰,無需多解釋了。


【思路總結】:

軟體共有兩次加密,我們可以直接利用其中一次而無需求反函式。仔細思考一下,如果先確定後12位元組,則由加密演算法2,
前12位元組也就固定了,要求前2位元組經加密演算法1而得到完全相同的12個位元組可以說是不可能的,因此必須先得到前12位元組。
若要保持註冊碼的隨機性,我們可以隨機產生註冊碼前2位元組,然後由加密演算法1確定3-12位元組。

加密演算法2的最後一步是0041CA99 - 0041CAAD的簡單加法,反函式自然就是減法。
call 0041C958的加密,有兩個自變數,第一次call為13-16位元組和17-20位元組,第二次則為17-20位元組和21-24位元組,
我們仍然可以隨機產生13-16位元組,這樣就相當於只有一個自變數,由13-16位元組可以得到固定的17-20位元組,
再由這17-20位元組可得到固定的21-24位元組。
經過這樣的簡化,call 0041C958似乎還是找不到反函式,因為其中有and計算,最笨的辦法是窮舉,那需要0xFFFFFFFF次的迴圈!
本人嘗試了一下,得到一個註冊碼需半個多小時(還不是最壞情況),這樣的序號產生器是不合格的。
當然最後還是想到了等價的反函式,先看一下call 0041C958的c程式碼(看c比看彙編簡單吧?):

void encrypt2(unsigned long* p, unsigned long* t1, unsigned long* t2) {
  unsigned long l1,l2,tmp1,tmp2;
  int i;
  i = tmp1 = tmp2 = 0;
  
  for (; i < 32; i++) {
    l1 = *p;
    l2 = *(p+1);
    l1 &= *(t1 + i);
    l2 &= *(t2 + i);
    l1 = getQuantityOfOne(l1);
    l2 = getQuantityOfOne(l2);
    l2 ^= l1;
    tmp1 *= 2;
    l2 &= 255;
    l2 &= 1;
    tmp1 ^= l2;
  }
  for (; i < 64; i++) {
    l1 = *p;
    l2 = *(p+1);
    l1 &= *(t1 + i);
    l2 &= *(t2 + i);
    l1 = getQuantityOfOne(l1);
    l2 = getQuantityOfOne(l2);
    l2 ^= l1;
    tmp2 *= 2;
    l2 &= 255;
    l2 &= 1;
    tmp2 ^= l2;
  }
  *p = tmp1;
  *(p+1) = tmp2;
}
引數p第一次指向註冊碼第13-16位元組,第二次指向17-20位元組,t1,t2分別指向加密表。
getQuantityOfOne()函式就是CALL WinISO1.0041C93C,返回引數二進位制中1的數量,這裡程式碼略過了。

其實只要看第一個for迴圈就行了,找到第一個for的反函式,就可以得到自變數l2(另一個自變數l1當然已經簡化掉了)。
仔細考慮可以發現,每次l2的值只需要最後1 bit,並且這1 bit儲存在tmp1的某位當中供以後的比較。
其實要得到l2相當於解方程組了:f(i)(x31,x30,...,x0) = a(i)(31)x31^a(i)(30)x30^...^a(i)(0)x0 = b(i) (i = 0,1,...,31),
其中常數a(31)(31)...a(31)(0),a(30)(31)...a(30)(0),......,a(0)(31)...a(0)(0)以及b(31)...b(0)取值非0即1,
自變數x31...x0取值同樣非0即1,而係數與自變數的乘法就能簡化成&運算。
寫成矩陣形式AX = B,矩陣A可由第2和第4內建表得到,B可由註冊碼前12位元組減去004D18A0開始的12位元組然後與l1運算後得到。
因為內建表是固定的,於是係數矩陣A也固定,因此X相當於B的函式,可以獨立寫一個程式計算出這種關係(X=CB),
這樣在序號產生器程式碼中就可以由常數陣列C直接運算X了,不需每次都算一下C。

給出一組可用註冊碼:
使用者名稱:worldhello
註冊碼:000012C6D4C4D7CCE0FFEAE7000000009CA2C909CC9E7CC5

【後記】:由於這是本人第一次寫這樣的破解文章,因此錯誤在所難免,希望各位大蝦不吝賜教。

相關文章