All Aboard! SE 完全破解實戰

看雪資料發表於2001-07-18

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+EAU-27]             <-- EBP-28指向剛才處理後的結果
0167:012A12ED XOR [EBP+EAU-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+ECU-28]            <-- EBP+ECU-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)

相關文章