• 标 题:All Aboard! SE 完全破解实战
  • 作 者:ddcrack
  • 时 间:2001/7/18
  • 链 接:http://bbs.pediy.com

All Aboard! SE 完全破解实战


  只需要一个网卡,不需要设置,就可以和别人共享上网,这就是All Aboard的特别之处。它支持各种上网方式:Analog MODEM、 xDSL 、Cable MODEM、 ISDN 、T1, T3/E1, E3以及Satellite ,真的是一个非常棒的上网共享工具。未注册版有30天的试用期,而且只用户数也少。这个软件加密部分的汇编代码挺复杂的,我们也不能从对程序的动态跟踪过程中找到或者直接推算出正确的注册码,而且即便是你看明白了汇编代码,也要花一些时间才能想到实现注册机的算法,否则直接通过对程序的跟踪而用穷举法写出注册机,真的是“等到花儿也榭了”也难以算出正确注册码,即使你用1G的P4来跑注册机程序,不信你可以试试,哈哈。。。^_^

程序名 :All Aboard! SE
版本   :V2.5
大小   :1,182KB
运行平台:Windows 95/98/Me/NT/2000
保护方式:注册码
破解方式:注册码破解
破解难度:较难
程序下载:allaboard25.exe
附件  :注册机及其C语言源程序

破解步骤:

1. 用softice载入windows(通过CTRL+D来检查softice是否已经准备好,按F5退出softice);

2. 在“开始”菜单中点击“All_Aboard Standard Edition”下的“Server Settings”,然后选择“License”进行注册;

3. 在“License”中输入:12345678(随意);

4. 用CTRL+D呼出softice,下万能断点:bpx hmemcpy,按F5返回到All Aboard;

5. 在All Aboard中点击“应用”,很快程序就被softice拦截下来;

6. 用 bd * 暂停断点 bpx hmemcpy ;

7. 按F12键8次,返回到All Aboard的领空,程序停留在下面的地方(注意此时Softice中显示的是“CONTYPE!.text+000241A2”字样,程序的注册码程序放在contype.dll和key_prot.dll两个动态连接库中,因为“Server Settings”调用的是settings.exe,所以我们是不可能在Softice看到所谓的“ALL_ABOARD”领空字样的,之所以会用“All Aboard领空”的表述,主要是简化而已,因为你在破解过程中会碰到“SETTINGS!.text”、“CONTYPE!.text”和“KEY_PROT!.text”的字样,这里统统用All Aboard来代表,不做区分了):
。。。
0167:100251A2 CALL [USER32!GetWindowTextA]
0167:100251A8 MOV ECX, [EBP+10]            <-- 程序停在这里
0167:100251AB PUSH FF
0167:100251AD CALL 1002322F
0167:100251B2 JMP 100251D4
。。。

8. 连续按F10(多少次我可不知道啊^_^),注意是否有可疑的地方,中间你会进入WINDOWS系统区域“COMCTL32!.text”中,按F12继续走,最后你会发现在还没有重新返回到All Aboard的领空之前All Aboard就已经弹出窗口“Invalid License Key”告诉你注册码错误。是不是有点奇怪:为什么都没有看到任何跟输入注册码“12345678”相关的程序段就被告之注册码错误?那么程序究竟在何处判断注册码正确与否的呢?看来刚才我们下的断点“bpx hmemcpy”不能正确拦截到关键的地方,没关系,山不转水转,我们换另外一个断点试试;

9. 首先在Softice中用 BC * 清除原来设置的断点,然后重新来到All Aboard中注册的地方,输入注册码“12345678”并按“应用”,接着All Aboard弹出“Invalid License Key”的错误窗口,按CTRL+D呼出Softice,下断点“bpx lockmytask”(这个断点的作用是拦截按键的动作),然后按F5返回,点击“确定”按钮,程序马上被Softice拦截下来;

10. 用 bd * 暂停断点 bpx lockmytask ,然后按F12键20次,返回到All Aboard的领空,程序停留在下面的地方:
。。。
0167:100034A6 CALL [USER32!MessageBoxA]
0167:100034AC PUSH ESI                 <-- 程序停在这里
0167:100034AD CALL [KERNEL32!FreeLibrary]
0167:100034B3 LEA ECX, [ESP+0C]
0167:100034B7 MOV [ESP+00000118], BL
0167:100034BE CALL 1002304E
0167:100034C3 LEA ECX, [ESP+08]
0167:100034C7 MOV DWORD PTR [ESP+00000118], FFFFFFFF
0167:100034D2 CALL 1002304E
0167:100034D7 POP ESI
0167:100034D8 MOV EAX, 00000001
0167:100034DD POP EBX
0167:100034DE MOV ECX, [ESP+00000108]
0167:100034E5 MOV FS:[00000000], ECX
0167:100034EC ADD ESP, 00000114
0167:100034F2 RET
。。。

11. 上面0167:100034A6的CALL [USER32!MessageBoxA]自然就是刚才错误框的弹出地方了,按一下F12(或按多次F10)走出这段子程序,来到它的下一条指令:
。。。
0167:1000D7BA MOV ECX, [ESI+00000090]
0167:1000D7C0 MOV EDX, [ESI+1C]
0167:1000D7C3 MOV EDI, EAX
0167:1000D7C5 PUSH EDI
0167:1000D7C6 PUSH ECX
0167:1000D7C7 PUSH EDX
0167:1000D7C8 CALL 10003360
0167:1000D7CD ADD ESP, 0C               <-- 程序来到这里
0167:1000D7D0 TEST EAX, EAX
0167:1000D7D2 JZ 1000D831
。。。

12. 从步骤10的程序中返回后来到0167:1000D7CD ADD ESP, 0C,往前看我们会发现其上一句0167:1000D7C8是个子程序CALL 10003360,而错误框正是从它里面跑出来的,所以我们要进去看一看:将鼠标移到0167:1000D7C5 PUSH EDI处点击一下,然后按F9在此设置断点;

13. 按F5返回All Aboard,重新输入注册码“12345678”,然后按“应用”按钮,程序被Softice拦截并停在0167:1000D7C5 PUSH EDI处,按F10走到0167:1000D7C8 CALL 10003360停下来,分别用 D EDI 、D ECX 和 D EDX 命令,你会发现EDI指向我们输入的注册码“12345678”,哈哈。。。现在终于找到了和输入注册码有关系的地方,自然不能放过它,按F8进入这个CALL 10003360去看看:
0167:10003360 PUSH FF
0167:10003362 PUSH 100285B6
0167:10003367 MOV EAX, FS:[00000000]
0167:1000336D PUSH EAX
0167:1000336E MOV FS:[00000000], ESP
0167:10003375 SUB ESP, 00000108
0167:1000337B MOV EAX, [10035974]
0167:10003380 PUSH EBX
0167:10003381 PUSH ESI
0167:10003382 MOV [ESP+08], EAX
0167:10003386 XOR EBX, EBX
0167:10003388 MOV [ESP+0C], EAX
0167:1000338C MOV [ESP+00000118], EBX
0167:10003393 MOV ECX, [10036E0C]
0167:10003399 MOV BYTE PTR [ESP+00000118], 01
0167:100033A1 CMP ECX, EBX
0167:100033A3 JZ 100033B1
0167:100033A5 CMP [10036E08], EBX
0167:100033AB JNZ 1000345B
。。。
0167:1000345B MOV ESI, [ESP+08]
0167:1000345F CALL ECX
0167:10003461 MOV ECX, [ESP+00000128]
0167:10003468 PUSH EAX
0167:10003469 PUSH ECX                 <-- ECX指向我们输入的注册码“12345678”
0167:1000346A CALL [10036E08]
0167:10003470 TEST EAX, EAX
0167:10003472 JNZ 100034F3
0167:10003474 PUSH 00000011
0167:10003476 LEA ECX, [ESP+0C]
0167:1000347A CALL 100232C5
。。。

14. 按F10走到0167:1000346A CALL [10036E08]停下,分别用 D EAX 和 D ECX 命令,你会看到ECX指向我们输入的注册码“12345678”,而EAX指向的内存区域则是数据“17 00 03 ...”,没有什么特别的字符串,那么CALL [10036E08]究竟有何作用呢?继续按F10走到0167:10003472 JNZ 100034F3,你会发现此时由于EAX=0程序将继续往下走而不是跳到100034F3去:
。。。
0167:10003474 PUSH 00000011
0167:10003476 LEA ECX, [ESP+0C]
0167:1000347A CALL 100232C5
0167:1000347F TEST EAX, EAX
0167:10003481 JZ 100034B3
0167:10003483 PUSH 00000022
0167:10003485 LEA ECX, [ESP+10]
0167:10003489 CALL 100232C5
0167:1000348E TEST EAX, EAX
0167:10003490 JZ 100034B3
0167:10003492 MOV EDX, [ESP+0C]
0167:10003496 MOV EAX, [ESP+08]
0167:1000349A MOV ECX, [ESP+00000120]
0167:100034A1 PUSH 00000010
0167:100034A3 PUSH EDX
0167:100034A4 PUSH EAX
0167:100034A5 PUSH ECX
0167:100034A6 CALL [USER32!MessageBoxA]         <-- 这里又来到步骤10的地方
0167:100034AC PUSH ESI
0167:100034AD CALL [KERNEL32!FreeLibrary]
0167:100034B3 LEA ECX, [ESP+0C]
0167:100034B7 MOV [ESP+00000118], BL
0167:100034BE CALL 1002304E
0167:100034C3 LEA ECX, [ESP+08]
0167:100034C7 MOV DWORD PTR [ESP+00000118], FFFFFFFF
0167:100034D2 CALL 1002304E
0167:100034D7 POP ESI
0167:100034D8 MOV EAX, 00000001
0167:100034DD POP EBX
0167:100034DE MOV ECX, [ESP+00000108]
0167:100034E5 MOV FS:[00000000], ECX
0167:100034EC ADD ESP, 00000114
0167:100034F2 RET
。。。

15. 接着刚才继续按F10往下走(除了0167:10003472 JNZ 100034F3能跳离这里,程序将必然会继续往下走),当走过0167:100034A6 CALL [USER32!MessageBoxA]这句时你会看到可恶的错误窗又跑出来了(其实这里就是步骤10的地方),现在说明0167:1000346A处的CALL [10036E08]一定有问题,接下来知道该怎么做了吧^_^;

16. 按一下错误框的“确定”按钮,程序将返回Softice,先用 BD * 暂停以前设置的所有断点,然后将鼠标移到0167:1000346A CALL [10036E08]并按F9在这里设置断点,接着按F5返回All Aboard;

17. 重新输入注册码“12345678”进行注册,按“应用”,程序被Softice拦截住停在0167:1000346A CALL [10036E08],按F8进去看个究竟:
。。。
0167:012A13EE PUSH EBP
0167:012A13EF MOV EBP, ESP
0167:012A13F1 SUB ESP, 00000010
0167:012A13F4 PUSH ESI
0167:012A13F5 MOV ESI, [EBP+0C]
0167:012A13F8 MOV AL, [ESI]               <-- ESI指向内存数据“17 00 03 ...”
0167:012A13FA MOV [EBP+0C], AL
0167:012A13FD MOV AL, [ESI+01]
0167:012A1400 MOV [EBP+0D], AL
0167:012A1403 MOV AL, [ESI+02]
0167:012A1406 MOV [EBP+0E], AL
0167:012A1409 LEA EAX, [EBP-08]
0167:012A140C PUSH EAX
0167:012A140D LEA EAX, [EBP-0C]
0167:012A1410 PUSH EAX
0167:012A1411 LEA EAX, [EBP-10]
0167:012A1414 PUSH EAX
0167:012A1415 LEA EAX, [EBP+08]
0167:012A1418 PUSH EAX
0167:012A1419 LEA EAX, [EBP+0C]
0167:012A141C PUSH EAX
0167:012A141D PUSH DWORD PTR [EBP+08]           <-- [EBP+08]指向我们输入的注册码“12345678”
0167:012A1420 CALL 012A122D
0167:012A1425 TEST EAX, EAX
0167:012A1427 JZ 012A1466
。。。

18. 进入上面的程序后,如果你看一下Softice中程序领空的位置,你会发现此时我们已经进入Key_prot.dll中了。按F10一路走,一路用 D 寄存器名 命令,你会发现0167:012A13F8 MOV AL, [ESI]时ESI指向的内存区域是刚才步骤0167:1000346A CALL [10036E08]的入口参数“17 00 03”,下面的几条指令将这几个数放进堆栈中,中间有很多的PUSH EAX,我们用 D EAX 并没有看到可疑字符串,而且内存数据也没什么特别的,当走到0167:012A141D PUSH DWORD PTR [EBP+08]时[EBP+08]=00D74390,用 D 00D74390 你会看到内存里是我们输入的注册码“12345678”,那么0167:012A1420的CALL 012A122D有什么作用呢?没办法,按F8进去看看吧:
。。。
0167:012A122D PUSH EBP
0167:012A122E MOV  EBP, ESP
0167:012A1230 SUB ESP, 00000028
0167:012A1233 PUSH EBX
0167:012A1234 PUSH ESI                  <-- ESI指向内存数据“17 00 03 ...”
0167:012A1235 PUSH EDI                  <-- EDI指向我们输入的注册码“12345678”
0167:012A1236 CALL 012A100C
0167:012A123B MOV  ESI, [EBP+08]
0167:012A123E PUSH  ESI
0167:012A123F CALL 012A13AB
0167:012A1244 CMP BYTE PTR [ESI], 00
0167:012A1247 POP  ECX
0167:012A1248 JZ  012A137D
。。。

19. 按F10走到0167:012A1236 CALL 012A100C,用 D EBX 、D ESI 和 D EDI 命令,你会发现ESI指向内存数据“17 00 03 ...”,而EDI指向我们输入的注册码“12345678”,为了明白CALL 012A100C的作用,按F8进去看看:
。。。
0167:012A100C AND BYTE PTR [10009D60], 00
0167:012A1013 MOV BYTE PTR [10009D40], 51
0167:012A101A MOV BYTE PTR [10009D41], 39
0167:012A1021 MOV BYTE PTR [10009D42], 52
0167:012A1028 MOV BYTE PTR [10009D43], 32
0167:012A102F MOV BYTE PTR [10009D44], 57
0167:012A1036 MOV BYTE PTR [10009D45], 5A
0167:012A103D MOV BYTE PTR [10009D46], 41
0167:012A1044 MOV BYTE PTR [10009D47], 53
0167:012A104B MOV BYTE PTR [10009D48], 58
0167:012A1052 MOV BYTE PTR [10009D49], 38
0167:012A1059 MOV BYTE PTR [10009D4A], 4B
0167:012A1060 MOV BYTE PTR [10009D4B], 42
0167:012A1067 MOV BYTE PTR [10009D4C], 4D
0167:012A106E MOV BYTE PTR [10009D4D], 47
0167:012A1075 MOV BYTE PTR [10009D4E], 54
0167:012A107C MOV BYTE PTR [10009D4F], 35
0167:012A1083 MOV BYTE PTR [10009D50], 33
0167:012A108A MOV BYTE PTR [10009D51], 44
0167:012A1091 MOV BYTE PTR [10009D52], 45
0167:012A1098 MOV BYTE PTR [10009D53], 43
0167:012A109F MOV BYTE PTR [10009D54], 36
0167:012A10A6 MOV BYTE PTR [10009D55], 59
0167:012A10AD MOV BYTE PTR [10009D56], 34
0167:012A10B4 MOV BYTE PTR [10009D57], 4E
0167:012A10BB MOV BYTE PTR [10009D58], 48
0167:012A10C2 MOV BYTE PTR [10009D59], 50
0167:012A10C9 MOV BYTE PTR [10009D5A], 37
0167:012A10D0 MOV BYTE PTR [10009D5B], 56
0167:012A10D7 MOV BYTE PTR [10009D5C], 25
0167:012A10DE MOV BYTE PTR [10009D5D], 4A
0167:012A10E5 MOV BYTE PTR [10009D5E], 46
0167:012A10EC MOV BYTE PTR [10009D5F], 55
0167:012A10F3 RET
。。。

20. 哇噻!程序将总共32个字符分别依次放在内存地址10009D40开始的地方,按F10走到最后一句0167:012A10F3 RET,用 D 10009D40 我们可以看到这串字符是: “Q9R2WZASX8KBMGT53DEC6Y4NHP7V%JFU”,再按一次F10,从这个子程序中返回:
。。。
0167:012A1236 CALL 012A100C
0167:012A123B MOV ESI, [EBP+08]             <-- 返回后程序来到这里
0167:012A123E PUSH ESI                  <-- ESI指向我们输入的注册码“12345678”
0167:012A123F CALL 012A13AB
0167:012A1244 CMP BYTE PTR [ESI], 00
0167:012A1247 POP ECX
0167:012A1248 JZ 012A137D
。。。

21. 按F10走到0167:012A123F CALL 012A13AB,用 D ESI 可以看到ESI指向我们输入的注册码“12345678”,按F8跟踪进入CALL 012A13AB里去看看:
。。。
0167:012A13AB PUSH EBP
0167:012A13AC MOV EBP, ESP
0167:012A13AE SUB ESP, 00000040
0167:012A13B1 PUSH ESI
0167:012A13B2 MOV ESI, [EBP+08]
0167:012A13B5 LEA EAX, [EBP-40]
0167:012A13B8 PUSH ESI                  <-- ESI指向我们输入的注册码“12345678”
0167:012A13B9 PUSH EAX
0167:012A13BA CALL 012A1940
0167:012A13BF CMP BYTE PTR [EBP-40], 00
0167:012A13C3 POP ECX
0167:012A13C4 POP ECX
0167:012A13C5 JZ 012A13EB
。。。

22. 按F10走到0167:012A13BA CALL 012A1940,用 D ESI 可以看到ESI指向我们输入的注册码“12345678”,再次按F8跟踪进入CALL 012A1940里去看看:
。。。
0167:012A1940 PUSH EDI
0167:012A1941 MOV EDI, [ESP+08]
0167:012A1945 JMP 012A19B1
。。。
0167:012A19B1 MOV ECX, [ESP+0C]
0167:012A19B5 TEST ECX, 00000003             <-- ECX指向我们输入的注册码“12345678”
0167:012A19BB JZ 012A19D6
。。。
0167:012A19D1 MOV [EDI], EDX               <-- 将处理后得到的注册码字符放在[EDI]中
0167:012A19D3 ADD EDI, 00000004              <-- 指针EDI加4,用于下一次存放注册码字符
下面的指令比上面两句先执行。。。
0167:012A19D6 MOV EDX, 7EFEFEFF              <-- EDX赋值为7EFEFEFF
0167:012A19DB MOV EAX, [ECX]               <-- 取出4位注册码放在EAX中
0167:012A19DD ADD EDX, EAX                <-- 注册码的ASCII值加上7EFEFEFF
0167:012A19DF XOR EAX, FFFFFFFF              <-- 注册码的ASCII值与FFFFFFFF异或
0167:012A19E2 XOR EAX, EDX                <-- 将上面两步注册码的计算结果进行异或
0167:012A19E4 MOV EDX, [ECX]               <-- 将刚才取出的4位注册码备份放在EDX中
0167:012A19E6 ADD ECX, 00000004              <-- 指向字符串的指针加4,即指向下4个字符
0167:012A19E9 TEST EAX, 81010100             <-- 将刚才注册码的计算结果和81010100异或
0167:012A19EE JZ 012A19D1                 <-- 异或结果为零则跳到100019D1去
0167:012A19F0 TEST DL, DL                 <-- 否则测试取出的4位注册码字符第一位是否为零,即字符串已经结束
0167:012A19F2 JZ 012A1A28
0167:012A19F4 TEST DH, DH                 <-- 如果第一位有效则继续测试第二位
0167:012A19F6 JZ 012A1A1F
0167:012A19F8 TEST EDX, 00FF0000             <-- 如果第二位有效则继续测试第三位
0167:012A19FE JZ 012A1A12
0167:012A1A00 TEST EDX, FF000000             <-- 如果第三位有效则继续测试第四位
0167:012A1A06 JZ 012A1A0A
0167:012A1A08 JMP 012A19D1
0167:012A1A0A MOV [EDI], EDX               <-- 如果取出的字符第四位为零则直接将EDX的值存在[EDI]中并返回(因为最后的DL=00刚好可以作为字符串结束符)
0167:012A1A0C MOV EAX, [ESP+08]
0167:012A1A10 POP EDI
0167:012A1A11 RET
0167:012A1A12 MOV [EDI], DX                <-- 如果取出的字符第三位为零则将DX的值(即前两位字符)存在[EDI]中,然后在其后补“00”并返回
0167:012A1A15 MOV EAX, [ESP+08]
0167:012A1A19 MOV BYTE PTR [EDI+02], 00
0167:012A1A1D POP EDI
0167:012A1A1E RET
0167:012A1A1F MOV [EDI], DX                <-- 如果取出的字符第二位为零则将DX的值存在[EDI]中并返回
0167:012A1A22 MOV EAX, [ESP+08]
0167:012A1A26 POP EDI
0167:012A1A27 RET
0167:012A1A28 MOV [EDI], DL                <-- 如果取出的字符第二位为零则将DL=00放在[EDI]中作为字符串结束符并返回
0167:012A1A2A MOV EAX, [ESP+08]
0167:012A1A2E POP EDI
0167:012A1A2F RET
。。。

23. 上面的程序段到底有什么作用呢?看起来好象挺复杂,不能明显的明白是什么意思。如果你在上面的程序段的适当地方用F9设置一个断点,然后返回All Aboard试者多次输入不同的字符(平常用的或者是很古怪的字符,例如中文字符等),你会发现无论你输入什么样的字符,都没有发现某个字符被滤掉的情况,最后你会明白其实这段程序的作用就是将我们输入的注册码“12345678”从内存地址[ECX]转移到[EDI]去,至于为什么这么简单的任务要搞得如此复杂我也不明白,大概是故意给CRACKER看的吧,哈哈。。。;

24. 按F10走出上面的程序段,我们会从步骤20的0167:012A13BA CALL 012A1940中返回来到其下一句:
。。。
0167:012A13BA CALL 012A1940
0167:012A13BF CMP BYTE PTR [EBP-40], 00          <-- 我们来到这里
0167:012A13C3 POP ECX
0167:012A13C4 POP ECX
0167:012A13C5 JZ 012A13EB
。。。

25. 此时用 D EBP-40 你会看到内存中是刚才处理后的输入注册码“12345678”(也就是输入注册码的备份),CMP BYTE PTR [EBP-40], 00的作用是判断输入的字符串是否为空,显然当按F10走到0167:012A13C5 JZ 012A13EB时程序将继续运行其下一条指令,而不是跳到012A13EB去:
。。。
0167:012A13C5 JZ 012A13EB
0167:012A13C7 LEA ECX, [EBP-40]              <-- ECX指向我们输入的注册码“12345678”
0167:012A13CA MOV AL, [ECX]                <-- 取出一个字符
0167:012A13CC CMP AL, 61                  <-- 取出的字符和61,即和“a”比较
0167:012A13CE JL 012A13D8
0167:012A13D0 CMP AL, 7A                  <-- 如果字符大于等于“a”则和7A,即和“z”比较
0167:012A13D2 JG 012A13D8
0167:012A13D4 SUB AL, 20                  <-- 如果是小写字母则变成大写字母
0167:012A13D6 MOV [ECX], AL                <-- 将处理后的注册码字符存在[ECX]中
0167:012A13D8 MOV AL, [ECX]
0167:012A13DA CMP AL, 20                  <-- 如果不是小写字母则判断是否是空格(即20)
0167:012A13DC JZ 012A13E5                  <-- 是空格则取下一个字符,空格字符不保存
0167:012A13DE CMP AL, 2D                  <-- 如果不是空格则判断是否是减号“-”(即2D)
0167:012A13E0 JZ 012A13E5                  <-- 是减号“-”则取下一个字符,减号“-”不保存
0167:012A13E2 MOV [ESI], AL                 <-- 如果既不是小写字母,也不是空格或减号,则直接保存字符
0167:012A13E4 INC ESI                    <-- 处理后的注册码字符保存在这里
0167:012A13E5 INC ECX                    <-- 原始注册码放在这里
0167:012A13E6 CMP BYTE PTR [ECX], 00             <-- 是否已经处理完注册码字符
0167:012A13E9 JNZ 012A13CA                   <-- 没有处理完则继续
0167:012A13EB POP ESI
0167:012A13EC LEAVE
0167:012A13ED RET
。。。

26. 上面已经讲过步骤22的作用其实是将输入的注册码备份在另外一个内存地址,到了上面的程序段时ECX和ESI其实都同样指向我们输入的注册码“12345678”,而上面程序段的作用是:将小写字母转变成大写字母,滤掉其中的空格“ ”及减号“-”。到底过滤掉空格和减号之后的注册码变成什么样子了呢?你还是可以设置断点,然后输入含有空格和减号的注册码来看,例如:输入注册码为“12 567-66”,则处理后变成“125676666”;输入“12--345”,结果为“1234545”;输入“-123456”,结果为“1234566”;输入“123456--”,结果为“123456--”,哈哈。。。是不是奇怪,减号或空格出现在最后就滤不掉了,不管它了,程序就是这样走的嘛^_^

27. 按F10走出上面的程序段,我们会从步骤19的0167:012A123F CALL 012A13AB中返回来到其下一句:
。。。
0167:012A123F CALL 012A13AB
0167:012A1244 CMP BYTE PTR [ESI], 00           <-- 我们来到这里
0167:012A1247 POP ECX
0167:012A1248 JZ 012A137D
。。。

28. 现在用 D ESI 你将看到[ESI]中是处理后的输入注册码,因为我们输入的注册码是“12345678”,没有小写,也没有空格或减号,所以还是老样子, CMP BYTE PTR [ESI], 00是判断处理后的输入注册码是否为空,显然这里程序将会走到0167:012A1248 JZ 012A137D的下一句,不会跳到012A137D去:
。。。
0167:012A1248 JZ 012A137D
0167:012A124E PUSH 10009060                <-- 内存地址10009060中是字符串“DEMO”
0167:012A1253 PUSH ESI                  <-- ESI指向处理后的注册码“12345678”
0167:012A1254 CALL 012A18B0
0167:012A1259 POP ECX
0167:012A125A TEST EAX, EAX
0167:012A125C POP ECX
0167:012A125D JZ 012A137D
。。。

29. 当按F10走到0167:012A1254 CALL 012A18B0时用 D 10009060 和 D ESI 命令,你会分别看到字符串“DEMO”和处理后的注册码“12345678”,不用说,CALL 012A18B0的作用肯定是判断我们输入的注册码是否是默认的“DEMO”,你将看到程序将在0167:012A125D JZ 012A137D继续往下走而不发生跳转:
。。。
0167:012A125D JZ 012A137D
0167:012A1263 XOR EDI, EDI
0167:012A1265 PUSH 00000020
0167:012A1267 LEA EAX, [EBP-28]
0167:012A126A PUSH EDI
0167:012A126B PUSH EAX
0167:012A126C CALL 012A16A0
0167:012A1271 MOV [EBP-04], EDI
0167:012A1274 MOV [EBP-08], EDI
0167:012A1277 ADD ESP, 0000000C
0167:012A127A MOV EDI, 10009D40
0167:012A127F JMP 012A1284
。。。

30. 按F10走到0167:012A126C CALL 012A16A0停下,用 D EDI 和 D EAX 命令,你会发现没有任何可疑的数据,但我们还是按F8进入CALL 012A16A0里去大概瞧一瞧吧:
。。。
0167:012A16A0 MOV EDX, [ESP+0C]
0167:012A16A4 MOV ECX, [ESP+04]
0167:012A16A8 TEST EDX, EDX
0167:012A16AA JZ 012A16F3
0167:012A16AC XOR EAX, EAX
0167:012A16AE MOV AL, [ESP+08]
0167:012A16B2 PUSH EDI
0167:012A16B3 MOV EDI, ECX
0167:012A16B5 CMP EDX, 00000004
0167:012A16B8 JB 012A16E7
0167:012A16BA NEG ECX
0167:012A16BC AND ECX, 00000003
0167:012A16BF JZ 012A16C9
0167:012A16C1 SUB EDX, ECX
0167:012A16C3 MOV [EDI], AL
0167:012A16C5 INC  EDI
0167:012A16C6 DEC ECX
0167:012A16C7 JNZ 012A16C3
0167:012A16C9 MOV ECX, EAX
0167:012A16CB SHL EAX, 08
0167:012A16CE ADD EAX, ECX
0167:012A16D0 MOV ECX, EAX
0167:012A16D2 SHL EAX, 10
0167:012A16D5 ADD EAX, ECX
0167:012A16D7 MOV ECX, EDX
0167:012A16D9 AND EDX, 00000003
0167:012A16DC SHR  ECX, 02
0167:012A16DF JZ 012A16E7
0167:012A16E1 REPZ STOSD                  <-- EDI指向内存地址0063ED44
0167:012A16E3 TEST EDX, EDX
0167:012A16E5 JZ 012A16ED
0167:012A16E7 MOV [EDI], AL
0167:012A16E9 INC  EDI
0167:012A16EA DEC EDX
0167:012A16EB JNZ 012A16E7
0167:012A16ED MOV EAX, [ESP+08]
0167:012A16F1 POP EDI
0167:012A16F2 RET
。。。

31. 上面子程序的关键指令是0167:012A16E1处的REPZ STOSD,走到这里时我们可以看到此刻AL=00,而EDI则指向内存地址0063ED44,所以这个子程序的作用就是将内存地址0063ED44开始的地方清零,暂且不去管它,按F10走出这个子程序,来到0167:012A126C CALL 012A16A0的下一句:
。。。
0167:012A126C CALL 012A16A0
0167:012A1271 MOV [EBP-04], EDI
0167:012A1274 MOV [EBP-08], EDI
0167:012A1277 ADD ESP, 0000000C
0167:012A127A MOV EDI, 10009D40
0167:012A127F JMP 012A1284
。。。
0167:012A1284 MOV EAX, [EBP-08]
0167:012A1287 MOVZX EAX, BYTE PTR [EAX+ESI]       <-- [EAX+ESI]中是处理后的输入注册码“12345678”
0167:012A128B PUSH EAX                  <-- 将取出的注册码字符压栈
0167:012A128C PUSH EDI                  <-- EDI指向字符串“Q9R2WZASX8KBMGT53DEC6Y4NHP7V%JFU”
0167:012A128D CALL 012A17F0
0167:012A1292 POP ECX
0167:012A1293 TEST EAX, EAX
0167:012A1295 POP ECX
0167:012A1296 JZ 012A1379
。。。

32. 按F10走到0167:012A1287 MOVZX EAX, BYTE PTR [EAX+ESI],下命令:D EAX+ESI,你会看到内存中是处理后的输入注册码“12345678”,而这条指令的作用就是取出一个字符放在EAX中,下一条语句PUSH EAX将这个字符压栈,走到0167:012A128C PUSH EDI时用 D EDI 你会发现内存中藏着步骤19的那串奇怪的字符“Q9R2WZASX8KBMGT53DEC6Y4NHP7V%JFU”,下面的CALL 012A17F0一定有问题,让我们按F8杀进去:
。。。
0167:012A17F0 XOR EAX, EAX
0167:012A17F2 MOV AL, [ESP+08]              <-- 取出堆栈中的注册码字符放入AL,初值为31,即“1”
0167:012A17F6 PUSH EBX                  下面以注册码第一个字符“1”来注解程序
0167:012A17F7 MOV EBX, EAX                <-- 将注册码字符备份在EBX中,EBX=00000031
0167:012A17F9 SHL EAX, 08                 <-- EAX左移8为等于00003100
0167:012A17FC MOV EDX, [ESP+08]
0167:012A1800 TEST  EDX, 00000003             <-- EDX指向字符串“Q9R2WZASX8KBMGT53DEC6Y4NHP7V%JFU”
0167:012A1806 JZ 012A181B                 <-- 这条指令跟我们的输入没有任何关系,不用理它
。。。
0167:012A181B OR EBX, EAX                 <-- EBX与EAX或,结果EBX=00003131
0167:012A181D PUSH EDI
0167:012A181E MOV EAX, EBX                <-- EAX=EBX=00003131
0167:012A1820 SHL EBX, 10                 <-- EBX左移16位,得到EBX=31310000
0167:012A1823 PUSH ESI
0167:012A1824 OR EBX, EAX                 <-- EBX与EAX相或,得到EBX=31313131
0167:012A1826 MOV ECX, [EDX]                <-- 取出字符串“Q9R2WZASX8KBMGT53DEC6Y4NHP7V%JFU”的其中4位
0167:012A1828 MOV EDI, 7EFEFEFF              <-- EDI赋值为7EFEFEFF
0167:012A182D MOV EAX, ECX                 <-- ECX初始值为32523951,即字符串的前4位“Q9R2”
0167:012A182F MOV ESI, EDI                 <-- ESI=EDI=7EFEFEFF
0167:012A1831 XOR ECX, EBX                 <-- ECX异或EBX,即32523951^31313131
0167:012A1833 ADD ESI, EAX                 <-- ESI=7EFEFEFF+32523951
0167:012A1835 ADD EDI, ECX                 <-- EDI=7EFEFEFF+32523951^31313131
0167:012A1837 XOR ECX, -01                 <-- ECX=32523951^31313131^FFFFFFFF
0167:012A183A XOR EAX, -01                 <-- EAX=32523951^FFFFFFFF
0167:012A183D XOR ECX, EDI                 <-- ECX=ECX^EDI
0167:012A183F XOR EAX, ESI                 <-- EAX=EAX^ESI
0167:012A1841 ADD EDX, 00000004              <-- EDX指向字符串“Q9R2WZASX8KBMGT53DEC6Y4NHP7V%JFU”的下面4位
0167:012A1844 AND ECX, 81010100              <-- 将ECX和81010100作与测试
0167:012A184A JNZ 012A1868                 <-- 不为零则跳到012A1868去
0167:012A184C AND EAX, 81010100              <-- 为零则将EAX和81010100作与测试
0167:012A1851 JZ  012A1826                <-- 为零则取出字符串“Q9R2WZAS......”的下4位继续判断
0167:012A1853 AND EAX, 01010100              <-- 否则将将EAX和01010100作与测试
0167:012A1858 JNZ 012A1862                 <-- 不为零则跳到012A1862去
0167:012A185A AND ESI, 80000000              <-- 为零则将ESI和80000000作与测试
0167:012A1860 JNZ 012A1826                 <-- 不为零则取出字符串“Q9R2WZAS......”的下4位继续判断
0167:012A1862 POP ESI
0167:012A1863 POP EDI
0167:012A1864 POP EBX
0167:012A1865 XOR EAX, EAX                 <-- EAX清零
0167:012A1867 RET

0167:012A1868 MOV EAX, [EDX-04]              <-- 将刚才取出的4位字符重新放入EAX,此时EAX=32523951,BL=31
0167:012A186B CMP AL, BL                 <-- 判断AL=‘Q’是否等于BL=‘1’
0167:012A186D JZ 012A18A5                 <-- 相等则跳到012A18A5
0167:012A186F TEST AL, AL                 <-- 不等则判断AL是否等于00,既是否取完了字符串“Q9R2WZAS......”
0167:012A1871 JZ 012A1862                 <-- 等于零则跳到012A1862
0167:012A1873 CMP AH, BL                 <-- 判断AH=‘9’是否等于BL=‘1’
0167:012A1875 JZ 012A189E                 <-- 相等则跳到012A189E
0167:012A1877 TEST  AH, AH                <-- 不等则判断AH是否等于00
0167:012A1879 JZ 012A1862                 <-- 等于零则跳到012A1862
0167:012A187B SHR EAX, 10                 <-- EAX右移16位,得到EAX=00003252
0167:012A187E CMP AL, BL                 <-- 判断AL=‘R’是否等于BL=‘1’
0167:012A1880 JZ 012A1897                 <-- 相等则跳到012A1897
0167:012A1882 TEST AL, AL                 <-- 不等则判断AL是否等于00
0167:012A1884 JZ 012A1862                 <-- 等于零则跳到012A1862
0167:012A1886 CMP AH, BL                 <-- 判断AH=‘2’是否等于BL=‘1’
0167:012A1888 JZ 012A1890                 <-- 相等则跳到012A1897
0167:012A188A TEST AH, AH                 <-- 不等则判断AH是否等于00
0167:012A188C JZ 012A1862                 <-- 等于零则跳到012A1862
0167:012A188E JMP 012A1826                <-- 如果条件都不满足则跳到012A1826取下4位字符

0167:012A1890 POP ESI
0167:012A1891 POP EDI
0167:012A1892 LEA EAX, [EDX-01]              <-- 将与BL=‘1’相等的字符的地址放在EAX中
0167:012A1895 POP EBX
0167:012A1896 RET

0167:012A1897 LEA EAX, [EDX-02]              <-- 将与BL=‘1’相等的字符的地址放在EAX中
0167:012A189A POP ESI
0167:012A189B POP EDI
0167:012A189C POP EBX
0167:012A189D RET

0167:012A189E LEA EAX, [EDX-03]              <-- 将与BL=‘1’相等的字符的地址放在EAX中
0167:012A18A1 POP ESI
0167:012A18A2 POP EDI
0167:012A18A3 POP EBX
0167:012A18A4 RET

0167:012A18A5 LEA EAX, [EDX-04]              <-- 将与BL=‘1’相等的字符的地址放在EAX中
0167:012A18A8 POP ESI
0167:012A18A9 POP DI
0167:012A18AA POP EBX
0167:012A18AB RET
。。。

33. 当我们第一次进入上面的子程序时,由于程序将输入注册码的第一个字符“1”压栈,所以0167:012A17F2 MOV AL, [ESP+08]这条指令将“1”放在AL中。按F10走到0167:012A184A JNZ 012A1868你会发现此时零标志位为1,程序继续执行下一条指令AND EAX, 81010100,到了0167:012A1851 JZ  10001826时程序将跳回012A1826并取出 “Q9R2WZASX8KBMGT53DEC6Y4NHP7V%JFU”的下4位字符“WZAS”进行判断,如此循环,最后你会走到0167:012A1853 AND EAX, 01010100这一句,然后到了0167:012A1858 JNZ 012A1862时程序将会跳到012A1862去,等一等,看看012A1862开始的指令,先是:POP ESI、POP EDI、POP EBX,然后是:XOR EAX, EAX和RET,在我们CRACK软件的过程中,XOR EAX, EAX可以称得上是死亡指令了,因为当你你在关键的子程序中发现XOR EAX, EAX后子程序就返回这样的情况时,通常表示你已经完蛋了,EAX的返回值为零一般表示判断失败,不过也没什么奇怪的,本来我们输入注册码就是随意的,所以中途被判死刑是很正常的事嘛^_^

34. 也许你会问:为什么我能如此肯定程序跳到012A1862去就完蛋了呢?首先是破解的经验感觉,其次是对程序的分析:在0167:012A184A JNZ 012A1868时我们可以看到其实这段程序原本可以绕过012A1862而跑到012A1868去的,而我们在这段子程序执行了才一半就已经返回了,下面012A1868开始还有好长一段程序没有运行,总不可能这段程序是垃圾吧?所以我肯定我们应该要跳到012A1868去,但是什么样的注册码字符才能通过刚才那段程序的验证而跳到012A1868去呢?目前我们还不清楚,既然软的不行,咱们就来硬的:用 BD * 暂停前面的断点,将鼠标移到0167:012A17F0 XOR EAX, EAX并按F9在此设置断点,然后按F5返回All Aboard,重新输入注册码“12345678”,按“应用”,被Softice拦截住后按F10走到0167:012A184A JNZ 012A1868停下,此时零标志位为1,下命令:RFL Z 改变程序的运行轨迹,按一下F10就来到了012A1868处,其下的指令请看注解;

35. 从上面的跟踪我们可以分析出:如果注册码字符通过了上半段程序的验证后,程序将会把与这个注册码字符相同的字符在字符串“Q9R2WZASX8KBMGT53DEC6Y4NHP7V%JFU”中的地址值放在EAX中并返回,那么你是否已经明白了些什么呢?哈哈。。。既然程序将字符在“Q9R2WZASX8KBMGT53DEC6Y4NHP7V%JFU”中的地址取出放在EAX中,那么我们输入的注册码字符必须在这串字符中间,否则如何找到它对应的地址呢?^_^也就是我们输入的注册码字符必须从“Q9R2WZASX8KBMGT53DEC6Y4NHP7V%JFU”选出才能通过上面的子程序验证;

36. 因为字符“1”并没有在“Q9R2WZASX8KBMGT53DEC6Y4NHP7V%JFU”中,所以当按F10走到0167:012A186D JZ 012A18A5要用命令 RFL Z 改变程序运行的方向,否则又会在0167:012A188E JMP 012A1826时跳回012A1826处验证字符的程序,之后我们按F10一直走出上面的子程序:
。。。
0167:012A1281 MOV ESI, [EBP+08]              <-- ESI指向处理后的输入注册码“12345678”
0167:012A1284 MOV EAX, [EBP-08]              <-- EAX是注册码字符的地址偏移值
0167:012A1287 MOVZX EAX, BYTE PTR [EAX+ESI]       <-- EAX+ESI指向处理后的输入注册码“12345678”的某一位
0167:012A128B PUSH EAX                   <-- 将取出的注册码字符压栈
0167:012A128C PUSH EDI                   <-- EDI指向字符串“Q9R2WZASX8KBMGT53DEC6Y4NHP7V%JFU”
0167:012A128D CALL 012A17F0                <-- 验证并取得注册码字符在字符串“Q9R2WZAS...”中的地址值
0167:012A1292 POP ECX                   <-- 我们来到这里
0167:012A1293 TEST EAX, EAX                <-- 测试EAX返回值,为零就完蛋了!
0167:012A1295 POP ECX
0167:012A1296 JZ 012A1379
0167:012A129C SUB EAX, EDI                 <-- 求得注册码字符在字符串“Q9R2WZAS...”中的偏移值
0167:012A129E PUSH 00000008                <-- 压栈立即数00000008
0167:012A12A0 MOV EBX, EAX                 <-- 字符偏移值备份在EBX中
0167:012A12A2 MOV EAX, [EBP-04]              <-- [EBP-04]的初始值等于0
0167:012A12A5 CDQ                      <-- 将EAX的值扩展到EDX中
0167:012A12A6 POP ECX                   <-- 立即数00000008出栈赋予ECX
0167:012A12A7 IDIV ECX                   <-- EAX除以ECX=00000008
0167:012A12A9 PUSH 00000003                <-- 压栈立即数00000003
0167:012A12AB MOV ESI, EAX                 <-- 除法的商EAX赋予ESI
0167:012A12AD MOV EAX, [EBP-04]              <-- 将内存地址EBP-04中的内容赋给EAX
0167:012A12B0 CDQ                      <-- 将EAX的值扩展到EDX中
0167:012A12B1 IDIV ECX                   <-- EAX除以ECX=00000008
0167:012A12B3 POP ECX                   <-- 立即数00000003出栈赋予ECX
0167:012A12B4 CMP EDX, ECX                 <-- 除法余数EDX和ECX=00000003比较
0167:012A12B6 JG 012A12C4                 <-- 大于3则跳到012A12C4去
0167:012A12B8 SUB ECX, EDX                 <-- 小于3则用3减去余数EDX,结果放在ECX中
0167:012A12BA LEA EAX, [EBP+ESI-28]            <-- 内存地址值EBP+ESI-28赋予EAX
0167:012A12BE SHL BL, CL                  <-- 字符偏移值左移CL位
0167:012A12C0 OR  [EAX], BL                <-- 左移结果存入内存地址EBP+ESI-28中
0167:012A12C2 JMP 012A12DA
0167:012A12C4 LEA ECX, [EDX-03]              <-- 余数大于3则用余数减去03,结果放在ECX中
0167:012A12C7 MOV EAX, EBX                 <-- 字符偏移值备份在EAX中
0167:012A12C9 SAR EAX, CL                 <-- 字符偏移值右移CL位
0167:012A12CB PUSH 0000000B                <-- 压栈立即数0000000B
0167:012A12CD POP ECX                   <-- 立即数0000000B出栈赋予ECX
0167:012A12CE SUB ECX, EDX                 <-- ECX=0000000B减去余数EDX
0167:012A12D0 OR [EBP+ESI-28], AL             <-- 内存地址EBP+ESI-28的值与AL相或
0167:012A12D4 SHL BL, CL                  <-- 字符偏移值左移0000000B-EDX位
0167:012A12D6 MOV [EBP+ESI-27], BL             <-- 结果放在内存地址EBP+ESI-27中
0167:012A12DA INC [EBP-08]                 <-- 注册码字符偏移值加1
0167:012A12DD ADD DWORD PTR [EBP-04], 00000005      <-- 内存地址EBP-04中的值递增加5
0167:012A12E1 CMP DWORD PTR [EBP-08], 00000010      <-- 循环16次
0167:012A12E5 JL 012A1281
。。。

37. 上面程序的具体算法注解已经比较清楚,我们很明显得知的信息是注册码应该有16位,因为0167:012A12E1 CMP DWORD PTR [EBP-08], 00000010这一句表明了这一点,不过因为程序中并没有检测我们输入的注册码是否真的有16位,所以我们不用担心,还可以继续往下走,另外,程序将处理结果放在内存地址EBP+ESI-28和EBP+ESI-27中,而EBP+ESI-28的初始值等于0063ED44,也就是步骤30被清零的那个内存地址处,EBP始终是保持不变的,每次循环ESI都会由EAX对8的除法的商得到,而EAX初始值为00,每次循环递增加5,至此,我们已经将上面的算法过程从具体的程序中脱离了出来(因为其它的指令不依赖具体的内存地址);

38. 按F10继续走到上面程序段的下一条指令(即经过了16次循环以后):
。。。
0167:012A12E5 JL 012A1281
0167:012A12E7 XOR EAX, EAX                 <-- 我们走到这里,EAX=0
0167:012A12E9 MOV CL, [EBP+EAX-27]             <-- EBP-28指向刚才处理后的结果
0167:012A12ED XOR [EBP+EAX-28], CL             <-- 前一字节被后一字节异或
0167:012A12F1 INC EAX                   <-- 指向下一个字节
0167:012A12F2 CMP EAX, 00000009              <-- 循环9次
0167:012A12F5 JL 012A12E9
。。。

39. 从上面的程序可以看出16位的注册码经过处理后得到10字节的数据,例如 D0,D1,D2,D3,D4,D5,D6,D7,D8,D9,然后依次将后一个字节的数据异或到前一个字节,如:T0 = D0 XOR D1,T8 = D8 XOR D9,也许你还不太明白为什么会是10字节数据,不是只循环了9次吗?是的,虽然程序只循环了9次,但是程序每次循环都会操作一前一后两个字节的内容,所以9次循环后其实处理了10个字节的数据;

40. 按F10继续走过这段程序,来到其下面一句(为了便于注解,假设16位的注册码经过上面所有的程序处理过后得到10字节的数据为:T0,T1,T2,T3,T4,T5,T6,T7,T8,T9):
。。。
0167:012A12F7 MOV EAX, [EBP+0C]              <-- 地址值0063EDAC赋给EAX
0167:012A12FA MOV CL, [EBP-25]               <-- EBP-25指向T3
0167:012A12FD MOV EDX, [EBP+18]              <-- 地址值0063ED94赋给EDX
0167:012A1300 MOV [EAX], CL                <-- T3放在内存地址0063EDAC中
0167:012A1302 MOV CL, [EBP-23]               <-- EBP-23指向T5
0167:012A1305 MOV [EAX+01], CL               <-- T5放在内存地址0063EDAD中
0167:012A1308 MOV CL, [EBP-28]               <-- EBP-28指向T0
0167:012A130B MOV [EAX+02], CL               <-- T0放在内存地址0063EDAE中
0167:012A130E MOVZX EAX, BYTE PTR [EBP-27]         <-- 取出T1放在EAX中
0167:012A1312 MOV ECX, EAX                 <-- T1备份在ECX中
0167:012A1314 AND EAX, 00000007              <-- T1和07相与
0167:012A1317 SHR ECX, 03                 <-- T1右移3位
0167:012A131A MOV [EDX], ECX                <-- 右移结果放在内存地址0063ED94中
0167:012A131C MOVZX ECX, BYTE PTR [EBP-26]         <-- 取出T2放在ECX中
0167:012A1320 MOVZX EDX, BYTE PTR [EBP-24]         <-- 取出T4放在EDX中
0167:012A1324 SHL ECX, 08                 <-- T2左移8位
0167:012A1327 OR ECX, EDX                 <-- ECX=0000XX**,T2=XX,T4=**
0167:012A1329 MOV EDX, [EBP+10]              <-- 地址值0063EDA8赋给EDX
0167:012A132C MOV [EDX], ECX                <-- ECX=0000XX**存入内存地址0063EDA8中
0167:012A132E MOV ECX, [EBP+14]              <-- 地址值0063ED90赋给ECX
0167:012A1331 MOVZX EAX, WORD PTR [2*EAX+10009050]     <-- 用 (T1 AND 07)* 2 查表,结果放在EAX中
0167:012A1339 MOV [ECX], EAX                <-- 查表结果存入内存地址0063ED90中
0167:012A133B MOVZX EAX, BYTE PTR [EBP-22]         <-- 取出T6放在EAX中
0167:012A133F MOVZX ECX, BYTE PTR [EBP-21]         <-- 取出T7放在ECX中
0167:012A1343 SHL EAX, 08                 <-- T6左移8位
0167:012A1346 OR EAX, ECX                 <-- EAX=0000XX**,T6=XX,T7=**
0167:012A1348 MOVZX ECX, BYTE PTR [EBP-20]         <-- 取出T8放在ECX中
0167:012A134C SHL EAX, 08                 <-- EAX=00XX**00
0167:012A134F OR EAX, ECX                 <-- EAX=00XX**##,T6=XX,T7=**,T8=##
0167:012A1351 MOV ECX, [EBP+1C]              <-- 地址值0063ED98赋给ECX
0167:012A1354 MOV [ECX], EAX                <-- EAX=00XX**##存入内存地址0063ED98中
。。。

41. 上面程序的作用目前还不得而知,需要说明的地方是0167:012A1331 MOVZX EAX, WORD PTR [2*EAX+10009050]这条指令,因为 EAX = T1 AND 07,所以EAX小于等于7,按F10走到这条指令时用 D 10009050 可以发现EAX分别等于0,1,2,3,4,5,6,7时这条指令从内存中取出的对应值分别是0002,0003,0006,000A,0019,0032,0001,03E8,继续按F10走完上面的程序,来到其下一句:
。。。
0167:012A1356 XOR ECX, ECX                 <-- ECX清零
0167:012A1358 XOR EAX, EAX                 <-- EAX清零
0167:012A135A MOVZX EDX, [EBP+ECX-28]            <-- EBP+ECX-28指向Ti,i=0,1,2....9
0167:012A135F ADD EAX, EDX                 <-- 将Ti累加
0167:012A1361 INC ECX                    <-- 指向下一个Ti
0167:012A1362 CMP ECX, 00000009               <-- 循环9次
0167:012A1365 JL 012A135A
0167:012A1367 MOVZX ECX, BYTE PTR [EBP-1F]         <-- EBP-1F指向T9
0167:012A136B AND EAX, 000000FF               <-- 上面的累加和跟000000FF相与
0167:012A1370 XOR EAX, ECX                 <-- EAX等于EAX异或T9
0167:012A1372 NEG EAX                    <-- 求EAX的负数
0167:012A1374 SBB EAX, EAX                 <-- EAX=EAX-CF标志位
0167:012A1376 INC EAX                    <-- EAX加1
0167:012A1377 JMP 012A13A4                 <-- 程序返回
。。。
0167:012A13A4 POP EDI
0167:012A13A5 POP ESI
0167:012A13A6 POP EBX
0167:012A13A7 LEAVE
0167:012A13A8 RET 0018
。。。

42. 注意:上面的累加和EAX=T0+T1+T2+T3+T4+T5+T6+T7+T8,继续按F10走出这个子程序,我们将来到步骤17中0167:012A1420 CALL 012A122D的下一句:
。。。
0167:012A1420 CALL 1000122D
0167:012A1425 TEST EAX, EAX                <-- 我们来到这里
0167:012A1427 JZ 012A1466                 <-- 跳到012A1466去就完蛋了
0167:012A1429 MOV AL, [ESI]                <-- ESI指向T3
0167:012A142B CMP AL, [EBP+0C]               <-- 判断T3是否等于[EBP+0C]=17
0167:012A142E JNZ 012A1466
0167:012A1430 MOV AL, [ESI+01]               <-- ESI+1指向T5
0167:012A1433 CMP AL, [EBP+0D]               <-- 判断T5是否等于[EBP+0D]=00
0167:012A1436 JNZ 012A1466
0167:012A1438 MOV AL, [ESI+02]               <-- ESI+2指向T0
0167:012A143B CMP AL, [EBP+0E]               <-- 判断T0是否等于[EBP+0E]=03
0167:012A143E JNZ 012A1466
0167:012A1440 CMP DWORD PTR [EBP+08], 00000000      <-- 判断[EBP+08]=0000XX**是否等于0,其中T2=XX,T4=**
0167:012A1444 JNZ 012A144B
0167:012A1446 PUSH 00000001                <-- 立即数00000001压栈
0167:012A1448 POP EAX                   <-- 立即数00000001出栈赋给EAX
0167:012A1449 JMP 012A1468
0167:012A144B LEA EAX, [EBP-04]
0167:012A144E PUSH EAX
0167:012A144F PUSH 10009068
0167:012A1454 CALL 012A14E7
0167:012A1459 MOV ECX, [EBP-04]
0167:012A145C XOR EAX, EAX
0167:012A145E CMP ECX, [EBP+08]
0167:012A1461 SETLE AL
0167:012A1464 JMP 012A1468
0167:012A1466 XOR EAX, EAX
0167:012A1468 POP ESI
0167:012A1469 LEAVE
0167:012A146A RET 0008
。。。

43. 我们从0167:012A1420 CALL 1000122D中返回时EAX等于0,所以程序将跳到012A1466去,而012A1466处的指令是XOR EAX, EAX,肯定完蛋,所以步骤41时EAX应该要不等于0才对,考察012A1372开始的指令:NEG EAX、SBB EAX, EAX和INC EAX,若要EAX最后不等于0,则在012A1372时EAX必须等于0,这样EAX的返回值才会等于1而非0;

44. 按F10走到0167:012A1427 JZ 012A1466时用命令 RFL Z 改变程序原来的执行方向,使其继续往下走,通过对上面程序的分析,我们知道只有当T3=17、T5=00、T0=03且T2=T4=0时程序才会走到0167:012A1446 PUSH 00000001去,下一句0167:012A1448 POP EAX使得EAX=00000001,从而表示注册码正确;

注册码算法整理。。。

45. 程序终于走完了,是不是头都大了^_^,现在让我们清理一下大脑,将注册码的算法整理一下:

  ①. 首先,注册码总共有16位,且每个字符都必须从字符串“Q9R2WZASX8KBMGT53DEC6Y4NHP7V%JFU”中取得;

  ②. 经过步骤32得到注册码字符在字符串“Q9R2WZASX8KBMGT53DEC6Y4NHP7V%JFU”中的对应地址内存EAX,在步骤36的0167:012A129C SUB EAX, EDI指令后将其转化成在字符串“Q9R2WZASX8KBMGT53DEC6Y4NHP7V%JFU”中偏移值,例如我们输入的注册码中有字符“2”,则这个注册码字符在字符串“Q9R2WZASX8KBMGT53DEC6Y4NHP7V%JFU”中对应的偏移值为3;

  ③. 每个注册码字符都经过步骤36进行计算,其方法如下:
    a. 假设注册码字符在字符串“Q9R2WZASX8KBMGT53DEC6Y4NHP7V%JFU”中对应的偏移值放在EBX中;
    b. EAX=XXXX,第一个注册码字符对应的EAX=0,以后对应每个注册码EAX递增加5(如第二个注册码字符对应EAX=5,第三个对应EAX=10,以此类推);
    c. ESI = EAX / 8,EDX = EAX % 8;
    d. EDX和3比较:
      如果EDX小于等于3,则 BL 左移 3-EDX 位,BL 存入 [ESI] 中;
      如果EDX大于3,则 EAX = EBX ,EAX 右移 EDX-3 位,用 AL 或 [ESI] 的内容
                     BL 左移 0B-EDX 位,BL 存入 [ESI+1]中;

  ④. 16位注册码经过步骤③得到10字节的数据:
    D0,D1,D2,D3,D4,D5,D6,D7,D8,D9

  ⑤. Di(i=0,1,2...9)经过步骤39进行异或运算得到10字节数据Ti(i=0,1,2...9):
    T0 = D0 XOR D1,T1 = D1 XOR D2,T2 = D2 XOR D3,T3 = D3 XOR D4,T4 = D4 XOR D5
    T5 = D5 XOR D6,T6 = D6 XOR D7,T7 = D7 XOR D8,T8 = D8 XOR D9,T9 = D9

  ⑥. Ti(i=0,1,2...9)经过步骤41进行首次验证:
    EAX = T0 + T1 + T2 + T3 + T4 + T5 + T6 + T7 + T8,ECX = T9
    EAX = EAX AND 000000FF,EAX = EAX XOR ECX
    验证通过条件:EAX 必须等于 0

  ⑦. 首次验证通过后Ti(i=0,1,2...9)经过步骤42进行再次验证:
    验证通过条件:
    T3 必须等于 17
    T5 必须等于 00
    T0 必须等于 03
    T2 和 T4 必须等于 00

  ⑧. 通过了上面所有的验证后,表示注册码正确。

注册机算法研究。。。

46. 写到这里你是不是已经有点跃跃欲试想立马去写注册机呢?哈哈。。。告诉你,不行^_^!为什么?不是已经完全知道程序的注册码算法了吗?难道不可以用穷举法将注册码抓出来吗?是的,理论上我们可以利用穷举法将所有可能的注册码找出来,可是你有没有想过,16位的注册码,每位注册码字符有32中变化(即“Q9R2WZASX8KBMGT53DEC6Y4NHP7V%JFU”之一),这样你的程序中将会有32的16次方共1208925819614629174706176次FOR语句循环,按验证一个注册码电脑需要百分之一秒的时间计算(实际上没有这么快),你的电脑要跑12089258196146291747061.76秒,也就是383347862637820年,是不是等到地老天荒也等不到啊,哈哈。。。^_^

47. 既然穷举法不可行,我们只能通过进一步分析注册码算法来找到注册机的突破口了:

  ⑴. 回头看一看步骤45的步骤⑦和步骤⑥两个验证条件,原程序中是先验证步骤⑥,条件满足后才验证步骤⑦,那么我们可不可以反其道而行之,先找到仅仅符合步骤⑦条件的注册码形式,然后再反推是否有符合步骤⑥的注册码,如果反推成功,岂不是就得到注册码了吗^_^;

  ⑵. 那么T3、T5、T0、T2和T4是否能由注册码的某些单独位导出呢?从步骤⑤我们可知Ti是由Di得到的,而 Ti = Di XOR Di+1,也就是Ti只由Di和Di+1决定;从步骤③我们知道Di是注册码经过计算放在[ESI]和[ESI+1]中得到的,而 ESI = EAX / 8,EAX又由具体的注册码决定,那么Di跟ESI(也就是EAX)的关系是怎样的呢?

  ⑶. 下面我们来演示一下注册码字符经过计算之后怎样得到Di的,注册码有16位,这里用0,1,2,3,......D,E,F代表:

注册码:     0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F

对应的EAX取值:  0  5  10  15  20  25  30  35  40  45  50  55 60  65  70  75

对应的ESI=EAX/8: 0  0  1  1  2  3  3  4  5  5  6  6  7  8  8  9

对应的EDX=EAX%8: 0  5  2  7  4  1  6  3  0  5  2  7  4  1  6  3

是否影响Di+1?: N  Y  N  Y  Y  N  Y  N  N  Y  N  Y  Y  N  Y  N

  注:上面一行Di+1的i等于ESI=EAX/8,而是否影响Di+1是根据EDX=EAX%8是否大于3来决定的(Y表示影响,N表示不影响),这一点在步骤③中可知;

  ⑷. 从步骤⑶我们可以得到如下结论:
    D0 由注册码字符“0”和“1”得到(即注册码第0位和第1位,以下类似);
    D1 由注册码字符“1”,“2”和“3”得到;
    D2 由注册码字符“3”和“4”得到;
    D3 由注册码字符“4”,“5”和“6”得到;
    D4 由注册码字符“6”和“7”得到;
    D5 由注册码字符“8”和“9”得到;
    D6 由注册码字符“9”,“A”和“B”得到;
    D7 由注册码字符“B”和“C”得到;
    D8 由注册码字符“C”,“D”和“E”得到;
    D9 由注册码字符“E”和“F”得到;

  ⑸. 从步骤⑷我们可以得到如下结论:
    T0 由注册码字符“0”,“1”,“2”和“3”得到;
    T1 由注册码字符“1”,“2”,“3”和“4”得到;
    T2 由注册码字符“3”,“4”,“5”和“6”得到;
    T3 由注册码字符“4”,“5”,“6”和“7”得到;
    T4 由注册码字符“6”,“7”,“8”和“9”得到;
    T5 由注册码字符“8”,“9”,“A”和“B”得到;
    T6 由注册码字符“9”,“A”,“B”和“C”得到;
    T7 由注册码字符“B”,“C”,“D”和“E”得到;
    T8 由注册码字符“C”,“D”,“E”和“F”得到;
    T9 由注册码字符“E”和“F”得到;

    注:看步骤⑤中Di是怎样影响Ti的

48. 通过上面对注册码算法的进一步研究我们可以开始写注册机了:

  Ⅰ. 利用步骤45的计算找到使TO等于03的注册码字符“0”,“1”,“2”,“3”;

  Ⅱ. 利用Ⅰ得到的“3”找到使T2等于00的注册码字符“4”,“5”,“6”;

  Ⅲ. 利用Ⅱ得到的“4”,“5”,“6”找到使T3等于17的注册码字符“7”;

  Ⅳ. 利用Ⅲ得到的“6”,“7”找到使T4等于00的注册码字符“8”,“9”;

  Ⅴ. 利用Ⅳ得到的“8”,“9”找到使T5等于00的注册码字符“A”,“B”;

  Ⅵ. 利用上面得到的注册码字符“0”,“1”,“2”,“3”,“4”,“5”,“6”,“7”,“8”,“9”, “A”,“B”找到满足步骤⑥的注册码字符“C”,“D”,“E”,“F”;

  Ⅶ. 现在每个注册码字符都找到了,将它们按顺序拼起来得到“0123456789ABCDEF”,这就是正确的注册码了^_^。

更进一步的研究。。。

49. 至此,我们可以写出注册机了,那么是否就此大功告成呢?非也!用注册机产生一个注册码先,例如是“QQ93QQQNR%B3QP3Q”,用这个注册码在All Aboard中注册,结果自然是成功了,你看到了什么呢?All Aboard显示“All Aboard Max Users 10”的信息,哈。。。为什么是10个用户?这样不是还意味着还有其它的用户数注册码吗?那么程序是如何决定用户数的呢?

50. 回头看看步骤40,其中0167:012A1331处有条查表语句MOVZX EAX, WORD PTR [2*EAX+10009050],而我们当时已经知道对应EAX=0,1,2,3,4,5,6,7得到的结果分别为0002,0003,0006,000A,0019,0032,0001,03E8,其中的000A对应的10进制数就是10,有没有觉得这个000A就是用户数呢^_^?而 EAX = T1 AND 07,我们可以利用输入注册码“QQ93QQQNR%B3QP3Q”再次跟踪程序,来到0167:012A1331 MOVZX EAX, WORD PTR [2*EAX+10009050]时你会发现EAX=03,所以这条指令执行以后EAX恰好等于000A,也就是10,哈哈。。。这个地方肯定就是计算用户数的关键之处,因此最终T1决定了用户数,我们可以看到0x0019=25,0x0032=50,0x03E8=1000,哇噻,最多可以支持1000个用户,很恐怖啊,哈哈。。。有趣的是,如果你去All Aboard的官方网站跑一趟,你会发现他们只提供All Aboard! SE最大10个用户的支持,而且要159.95美圆的注册费,哎,大家来我这里注册算了,少收一点钱嘛,哈哈^_^!;

51. 为了完善注册机,我们应该将上面的注册机算法重新调整一下:

  Ⅰ. 利用步骤45的计算找到使TO等于03的注册码字符“0”,“1”,“2”,“3”;

  Ⅱ. 利用Ⅰ得到的“1”,“2”,“3”找到符合用户数要求(你想要的^_^)的注册码字符“4”;

  Ⅲ. 利用Ⅱ得到的“3”,“4”找到使T2等于00的注册码字符“5”,“6”;

  Ⅳ. 利用Ⅲ得到的“4”,“5”,“6”找到使T3等于17的注册码字符“7”;

  Ⅴ. 利用Ⅳ得到的“6”,“7”找到使T4等于00的注册码字符“8”,“9”;

  Ⅵ. 利用Ⅴ得到的“8”,“9”找到使T5等于00的注册码字符“A”,“B”;

  Ⅶ. 利用上面得到的注册码字符“0”,“1”,“2”,“3”,“4”,“5”,“6”,“7”,“8”,“9”, “A”,“B”找到满足步骤⑥的注册码字符“C”,“D”,“E”,“F”;

  Ⅷ. 现在你可以随心所欲的得到你想要的注册码了,哈哈。。。^_^!

52. 完了吗?没有!为什么?还有一点遗留问题:也许有朋友会问在步骤42中的0167:012A1440 CMP DWORD PTR [EBP+08], 00000000如果[EBP+08]不等于0,程序会在0167:012A1444 JNZ 012A144B时跳到012A144B去,其下面有条指令0167:012A1461 SETLE AL同样也可能使得AL等于1,这样返回之后EAX不也是等于1吗?是的,但只是有可能使EAX返回值为1,其返回值并不能肯定是什么?如果你跟踪到0167:012A1454 CALL 012A14E7里面去,你会发现程序会去取系统时间,然后经过复杂的运算得到某个值,最后通过指令0167:012A145E CMP ECX, [EBP+08]来确定AL的取值。如果你在这段程序设置断点,然后输入注册码时用默认的“DEMO”,你会发现All Aboard会调用这里的程序段,你可以试着随便输入一个注册码,然后用暴力法走过这里的程序,并且让AL返回值为1,你会发现All Aboard显示的“All Aboard Expires:”中的天数非常奇怪,依据你输入的注册码的情况,会有“-2142552 days”或者“353466 days”的奇怪结果,所以由此看来这段程序其实是计算你的注册码还有多少使用天数;

53. OK,到这里是真的没有什么疑问了,如果你还有,自己去研究吧,这篇破解实战写得真是好辛苦啊,希望下次不要再有这样的差事了^_^!!!


作者:ddcrack (2001/7/18)