• 标 题:ollydbg -- WinISO v5.3中文版注册算法
  • 作 者:worldhello
  • 时 间:2003年10月03日 03:27
  • 链 接:http://bbs.pediy.com

【软件名称】: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

【后记】:由于这是本人第一次写这样的破解文章,因此错误在所难免,希望各位大虾不吝赐教。