软件:Avi恢复向导
壳:ASPack 2.12 -> Alexey Solodovnikov脱壳:Microsoft Visual C++ 6.0
追注册码:
OD载入脱壳后程序,Ctrl+N查看函数,看到程序有lstrcmp与lstrcmpi两个函数,在lstrcmp函数上回车查看函数信息,并在全部使用lstrcmp函数上下断(右键—在每条命令上设置断点),lstrcmpi同样。F9运行程序,输入假的注册信息:
用户名:Alex
注册码:0123456789
点击注册,程序停在字符串比较函数00491F0E处:
00491ED1 /$ 55 PUSH EBP
00491ED2 |. 8BEC MOV EBP,ESP
00491ED4 |. 81EC 00010000 SUB ESP,100
00491EDA |. 56 PUSH ESI
00491EDB |. FF75 0C PUSH DWORD PTR SS:[EBP+C] ; /String
00491EDE |. FF15 60A14E00 CALL DWORD PTR DS:[<&kernel32.lstrlen>] ; /lstrlenA
00491EE4 |. 8BF0 MOV ESI,EAX
00491EE6 |. B8 00010000 MOV EAX,100
00491EEB |. 3BF0 CMP ESI,EAX
00491EED |. 77 29 JA SHORT unpack.00491F18
00491EEF |. 50 PUSH EAX ; /Count => 100 (256.)
00491EF0 |. 8D85 00FFFFFF LEA EAX,DWORD PTR SS:[EBP-100] ; |
00491EF6 |. 50 PUSH EAX ; |Buffer
00491EF7 |. FF75 08 PUSH DWORD PTR SS:[EBP+8] ; |hWnd
00491EFA |. FF15 2CA54E00 CALL DWORD PTR DS:[<&user32.GetWindowTex>; /GetWindowTextA
00491F00 |. 3BC6 CMP EAX,ESI
00491F02 |. 75 14 JNZ SHORT unpack.00491F18
00491F04 |. FF75 0C PUSH DWORD PTR SS:[EBP+C] ; /String2
00491F07 |. 8D85 00FFFFFF LEA EAX,DWORD PTR SS:[EBP-100] ; |
00491F0D |. 50 PUSH EAX ; |String1
00491F0E |. FF15 4CA14E00 CALL DWORD PTR DS:[<&kernel32.lstrcmp>] ; /lstrcmpA
00491F14 |. 85C0 TEST EAX,EAX
00491F16 |. 74 0C JE SHORT unpack.00491F24
00491F18 |> FF75 0C PUSH DWORD PTR SS:[EBP+C] ; /Text
00491F1B |. FF75 08 PUSH DWORD PTR SS:[EBP+8] ; |hWnd
00491F1E |. FF15 7CA44E00 CALL DWORD PTR DS:[<&user32.SetWindowTex>; /SetWindowTextA
00491F24 |> 5E POP ESI
00491F25 |. C9 LEAVE
00491F26 /. C2 0800 RETN 8
我们向上可以看到程序调用了GetWidnowTextA来取我们的假信息。堆栈窗口可以看到取了机器码:
0012DC50 0012DC5C |String1 = "7a22983c58866dcd"
0012DC54 017A5528 /String2 = "7a22983c58866dcd"
在00491ED1处下断,这样方便我们以后重载程序直接飞到这来。
向下F8走,就会来到0041C19D,可以知道我们跟出的这个CALL就是用来取用户输入信息的,并且下面还有两个相同CALL用来取信息,第一CALL取机器码,二CALL用户名,三CALL注册码。
0041C191 . 50 PUSH EAX ; /Arg3
0041C192 . 68 04040000 PUSH 404 ; |Arg2 = 00000404
0041C197 . 57 PUSH EDI ; |Arg1
0041C198 . E8 E7860700 CALL unpack.00494884 ; /unpack.00494884
0041C19D . 8D8E 0C030000 LEA ECX,DWORD PTR DS:[ESI+30C]
0041C1A3 . 51 PUSH ECX ; /Arg3
0041C1A4 . 68 06040000 PUSH 406 ; |Arg2 = 00000406
0041C1A9 . 57 PUSH EDI ; |Arg1
0041C1AA . E8 D5860700 CALL unpack.00494884 ; /unpack.00494884
0041C1AF . 81C6 10030000 ADD ESI,310
0041C1B5 . 56 PUSH ESI ; /Arg3
0041C1B6 . 68 05040000 PUSH 405 ; |Arg2 = 00000405
0041C1BB . 57 PUSH EDI ; |Arg1
0041C1BC . E8 C3860700 CALL unpack.00494884 ; /unpack.00494884
接着向下F8走,跟出0041C57A处可以看到有跳转,是比较注册码长度是否为32个字符长度,不够32个字符则跳走。因为们输入0123456789不够32个字符,F8跟到0041C586跳转处,直接双击寄存器中的ZF位,让其由0变1使其成功跳转。
0041C578 . 8BCE MOV ECX,ESI
0041C57A . E8 CBB30600 CALL 11.0048794A ; 这里面进行了取注册码
0041C57F . 8B45 00 MOV EAX,DWORD PTR SS:[EBP]
0041C582 . 8378 F8 20 CMP DWORD PTR DS:[EAX-8],20 ; 注册码必须为32字符
0041C586 0F84 95000000 JE 11.0041C621
跳转来到0041C621,往下看可以看到很我跳转,并且下面出现了打开注册表等函数,所以我们可以怀疑此处就有可能会是真正注册码出现的地方。事实证明注册码就出现在0041C638处的CALL中,当然在0041C638中还有很多CALL要分析。先跟入这CALL。
0041C621 > /8B15 84D44D00 MOV EDX,DWORD PTR DS:[4DD484] ; 11.004DD498
0041C627 . 895424 0C MOV DWORD PTR SS:[ESP+C],EDX
0041C62B . 8BCE MOV ECX,ESI
0041C62D . C78424 BC0200>MOV DWORD PTR SS:[ESP+2BC],5
0041C638 . E8 FC4CFEFF CALL 11.00401339 ; 在这里面出现真正的注册码
0041C63D . 85C0 TEST EAX,EAX
0041C63F 0F85 EF000000 JNZ 11.0041C734
看到的打开注册表等函数:此处我们可以看到程序把我们的注册码放到注册表的哪个位置了。
注册信息在注册表中位置:HKEY_LOCAL_MACHINE/SOFTWARE/HYDATA/Avi Recovery/20006
0041C73A . 8D4C24 1C LEA ECX,DWORD PTR SS:[ESP+1C]
0041C73E . 68 90B44D00 PUSH 11.004DB490 ; ASCII "Software/Hydata/"
0041C743 . 51 PUSH ECX
0041C744 . E8 C8CE0600 CALL 11.00489611
0041C749 . 8B00 MOV EAX,DWORD PTR DS:[EAX]
0041C74B . 8D5424 14 LEA EDX,DWORD PTR SS:[ESP+14]
0041C74F . 52 PUSH EDX ; /pHandle
0041C750 . 50 PUSH EAX ; |Subkey
0041C751 . 68 02000080 PUSH 80000002 ; |hKey = HKEY_LOCAL_MACHINE
0041C756 . FF15 309D4E00 CALL DWORD PTR DS:[<&ADVAPI32.RegOpenKey>; /RegOpenKeyA
0041C75C . 85C0 TEST EAX,EAX
跟入0041C638的CALL后可以在下面见到大量的CALL,先进入第一个0041CB80 CALL看看情况,情况不妙,又见好多CALL,这里暂且不分析了,第一个CALL实际是会根据两个常量字符串与机器码得到三个长度为32的字符串。
0041CB78 . 50 PUSH EAX
0041CB79 . 889C24 A00000>MOV BYTE PTR SS:[ESP+A0],BL
0041CB80 . E8 D34BFEFF CALL 11.00401758 ;进入出现3个32位字符串
0041CB85 . 50 PUSH EAX ; "c8a6ddea90ef46a3f1fab79ffd6f9eef"地址压入
跟入0041CB79处的CALL后可见到下面常量串。
常量一:
gU3eITbF9PiJUIBPTryY2kB6XthrCXwEVJVKoxXlPHv6RzbKC55JHpfoFWz2wxyVJQ5VeQg3ebnj6suD7CcRrmU0Qolf95NeybAMN6EmvBVP9Eat8FxeHgOL7jyEYnYgge5HJor4vNZ0N78qlgZjWayqKqwcUhZfpvRNIIqnHORCs7vQwS1hUFfpsscX1gaujNLhpp4GzIKAtyCjexQ4N1EFZYxWelvj
常量二:
6VwzF4bO1Y6vkZhmmyHMHp1TQxqBvlMBtliKBvzTwVm4Hv9l6Oa1ulGzHmzuiOoSurjABk524a0HKAzGEDC9pkTaeS4sx1E4ehH1IUNUFCJ
根据以上常量得到三个字符串:
"c8a6ddea90ef46a3f1fab79ffd6f9eef"
"d8a6d8e2f0f346a3f0fab49df47a9ae3" 下面的很多CALL是对上面这两个字符串做文章的。
"de167882f4f31e3c108fb47d847a8a83"
接着回到上面的0041CB85处,一直向下跟,CALL就不进去分析了。当来到0041CDB2处的CALL的时候堆栈里已经出现很多长度为32的字符串,这实际上是我们跳过的那些CALL的杰作。0041CDB2处的CALL有所不同,因为它的下面有一句跳转语句,所以此CALL我们跟入。
0041CDB0 . 8BCE MOV ECX,ESI
0041CDB2 . E8 1E45FEFF CALL 11.004012D5 ; 这里面出现真正的注册码
0041CDB7 . 85C0 TEST EAX,EAX
0041CDB9 . 0F85 3C0A0000 JNZ 11.0041D7FB
堆栈窗口中的数据:
0012DD48 017A5888 ASCII "m8j3k3j1h8o823i2j5ofg95ej17b9ff7"
0012DD4C 017A56F8 ASCII "e3h4l6e2k3n372j9l4fac89hg35j9je5"
0012DD50 017A56A8 ASCII "d1f7l8i9l2n332b1o3mic92fn57j9dg3"
0012DD54 017A54C8 ASCII "j4j5h9j1o9l827i2j1kab33mm43c8jf1"
0012DD58 017A5748 ASCII "l5b7m2g7h4i341e9n7njd79lk55g9ai1"
0012DD5C 017A5608 ASCII "d8a6d8e2f0f346a3f0fab49df47a9ae3"
0012DD60 017A5798 ASCII "g7j8e3m7o7j967b7m3fjd26eg62d4jg9"
0012DD64 017A5888 ASCII "m8j3k3j1h8o823i2j5ofg95ej17b9ff7"
0012DD68 017A5838 ASCII "f2j9i6m3l1k557g1m0mci29kk21i4ik6"
0012DD6C 017A57E8 ASCII "h9f2m4l6g2k375a4k5ohb49jl21e1bn2"
跟入0041CDB2处的CALL后我们接着向下走来到CALL,在来到这个0041EB62处的CALL之前我们能看到前面的一些CALL又算出了一个新串m2ji8ch9lbm3aj8j237j3f59144j1ok,实际我们真正的注册码是这个字符串算出来的,而前的那些个串的作用为了算此串,算法繁杂 - -# 。
0041EB55 . C68424 240200>MOV BYTE PTR SS:[ESP+224],2
0041EB5D . 51 PUSH ECX ; m2ji8ch9lbm3aj8j237j3f59144j1ok
0041EB5E . 8D4C24 38 LEA ECX,DWORD PTR SS:[ESP+38]
0041EB62 . E8 872CFEFF CALL 11.004017EE ; 这里面出现真正注册码
0041EB67 . 8D4C24 34 LEA ECX,DWORD PTR SS:[ESP+34]
接着跟入0041EB62处的CALL后可以看到非常短的代码,第一个CALL填充了MD5的四个常量(- - #竟然用MD5算法,而且竟然还是修改过的,郁闷)
0042F860 > /56 PUSH ESI
0042F861 . 8BF1 MOV ESI,ECX
0042F863 . E8 6F18FDFF CALL 11.004010D7 ; 里面填充了MD5的四个常量
0042F868 . 84C0 TEST AL,AL
0042F86A . 75 09 JNZ SHORT 11.0042F875
0042F86C . 6A 01 PUSH 1
0042F86E . 8BCE MOV ECX,ESI
0042F870 . E8 171DFDFF CALL 11.0040158C
0042F875 > 8B4424 08 MOV EAX,DWORD PTR SS:[ESP+8]
0042F879 . 8BCE MOV ECX,ESI
0042F87B . 50 PUSH EAX ;压入m2ji8ch9lbm3aj8j237j3f59144j1ok
0042F87C . E8 9817FDFF CALL 11.00401019 ;里面出现注册码
0042F881 . 5E POP ESI
0042F882 . C2 0400 RETN 4
填充完MD5常量后就压入我们前的字符串m2ji8ch9lbm3aj8j237j3f59144j1ok,可见真正算注册码的地方要出现了,直接进0042F87C的CALL,再走不远就看到:
00430A14 . C740 3C 00000>MOV DWORD PTR DS:[EAX+3C],0
00430A1B . E8 000EFDFF CALL 11.00401820 ;里面是修改过的MD5算法
00430A20 . 8BCB MOV ECX,EBX
00430A22 . E8 EF08FDFF CALL 11.00401316 ; 进了这里后出现真正的注册码
上面00430A1B处的CALL里面是真正的MD5变异算法,会得到消息摘要,用于我们下面这个CALL计算注册码。所以我现在跟入0042F87C处的CALL即可,进入此CALL后代码也非常短。
我们的注册码前身也是由它算出来的,之所以说注册码前身是因为00430A1B处CALL算出来的还不是真正的注册码,但已经很接近了。下面0042F87C处的CALL又把32位的注册码前身位置相互换了一下便得到真正注册码了。
00430B10 > /83EC 10 SUB ESP,10
00430B13 . 53 PUSH EBX
00430B14 . 55 PUSH EBP
00430B15 . 8D81 C2010000 LEA EAX,DWORD PTR DS:[ECX+1C2]
00430B1B . 56 PUSH ESI
00430B1C . 57 PUSH EDI
00430B1D . 894424 10 MOV DWORD PTR SS:[ESP+10],EAX
00430B21 . 8D59 6C LEA EBX,DWORD PTR DS:[ECX+6C]
00430B24 . BD 04000000 MOV EBP,4
00430B29 > 8B0B MOV ECX,DWORD PTR DS:[EBX]
00430B2B . 8BC1 MOV EAX,ECX
00430B2D . 8BD1 MOV EDX,ECX
00430B2F . 25 0000FF00 AND EAX,0FF0000
00430B34 . C1EA 10 SHR EDX,10
00430B37 . 0BC2 OR EAX,EDX
00430B39 . 8BD1 MOV EDX,ECX
00430B3B . C1E2 10 SHL EDX,10
00430B3E . 81E1 00FF0000 AND ECX,0FF00
00430B44 . 0BD1 OR EDX,ECX
00430B46 . C1E8 08 SHR EAX,8
00430B49 . C1E2 08 SHL EDX,8
00430B4C . 0BC2 OR EAX,EDX
00430B4E . 8903 MOV DWORD PTR DS:[EBX],EAX
00430B50 . 50 PUSH EAX
00430B51 . 8D4424 18 LEA EAX,DWORD PTR SS:[ESP+18]
00430B55 . 68 4CC24D00 PUSH 11.004DC24C ; ASCII "%08x"
00430B5A . 50 PUSH EAX
00430B5B . E8 D5570200 CALL 11.00456335 这里算出一段8字符字串
00430B60 . 8D7C24 20 LEA EDI,DWORD PTR SS:[ESP+20]
00430B64 . 83C9 FF OR ECX,FFFFFFFF
00430B67 . 33C0 XOR EAX,EAX
00430B69 . 83C4 0C ADD ESP,0C
00430B6C . F2:AE REPNE SCAS BYTE PTR ES:[EDI]
00430B6E . F7D1 NOT ECX
00430B70 . 2BF9 SUB EDI,ECX
00430B72 . 83C3 04 ADD EBX,4
00430B75 . 8BF7 MOV ESI,EDI
00430B77 . 8B7C24 10 MOV EDI,DWORD PTR SS:[ESP+10]
00430B7B . 8BD1 MOV EDX,ECX
00430B7D . 83C9 FF OR ECX,FFFFFFFF
00430B80 . F2:AE REPNE SCAS BYTE PTR ES:[EDI]
00430B82 . 8BCA MOV ECX,EDX
00430B84 . 4F DEC EDI
00430B85 . C1E9 02 SHR ECX,2
00430B88 . F3:A5 REP MOVS DWORD PTR ES:[EDI],DWORD PTR DS>
00430B8A . 8BCA MOV ECX,EDX
00430B8C . 83E1 03 AND ECX,3
00430B8F . 4D DEC EBP
00430B90 . F3:A4 REP MOVS BYTE PTR ES:[EDI],BYTE PTR DS:[>
00430B92 .^ 75 95 JNZ SHORT 11.00430B29
00430B94 . 5F POP EDI ; 到这里的时候显示出注册码
00430B95 . 5E POP ESI
00430B96 . 5D POP EBP
00430B97 . 5B POP EBX
00430B98 . 83C4 10 ADD ESP,10
00430B9B . C3 RETN
上面这段代码是一个循环,共循环4次,每次得到一个8个字符串的数据,所以循环4次也就得到了长度为32的注册码。8个字符由00430B5B处的CALL来取得,而00430B5B处的CALL里面还有CALL,这里不分析下去了。当我们来到00430B94处的时候,即4次循环完成之后,在堆栈就可以看到我们的注册码了。
堆栈窗口中的数据:
0012DAD0 0012DB40
0012DAD4 0012DD02 ASCII "9de760e139360774875a24feb713591c" ;注册码
0012DAD8 33313762
经过我们的分析可以知道,我们输入的用户名什么作用都没起,不用用户名一样可以注册,信息保存在注册表中的HKEY_LOCAL_MACHINE/SOFTWARE/HYDATA/Avi Recovery/20006。