【文章标题】:【原创】 【看雪读书月】【学习心得】一个陷阱crackme分析
【文章作者】: eanson
【软件名称】: crackme.exe
【软件大小】: 16KB
【下载地址】: 附件上传
【加壳方式】: 无壳
【保护方式】: 无壳
【编写语言】: MASM32 / TASM32
【使用工具】: OllyDbg 1.10, PEiD 0.94
【操作平台】: win2k3
【软件介绍】: 不知哪位大侠写的crackme,我拿来学习。
【作者声明】: 只是感兴趣,没有其他目的。失误之处敬请诸位大侠赐教!
先上PEID查壳,显示程序无壳,编写程序为MASM32 / TASM32,难怪程序这么短小精悍。不过陷阱在后面,呵呵。
运行程序,随便输入序列号,提示it's not so easy boy. keep tryin...
OK,我知道没那么容易呵呵,根据字符串参考搜索到下面这段:
004010AB . E8 6C000000 CALL crackme.0040111C ; F7跟进
004010B0 . 0BC0 OR EAX,EAX
004010B2 75 14 JNZ SHORT crackme.004010C8 ; 判断EAX是否为0,不为0则跳
004010B4 . 68 33324000 PUSH crackme.00403233 ; right serial
004010B9 . 68 E8314000 PUSH crackme.004031E8 ; you've done it. write a tutorial and send it to fornix@fusionrulez.cjb.net
004010BE . FF75 08 PUSH DWORD PTR SS:[EBP+8] ;
004010C1 . E8 A8000000 CALL crackme.0040116E ;
004010C6 . EB 12 JMP SHORT crackme.004010DA
004010C8 > 68 DB314000 PUSH crackme.004031DB ; wrong serial
004010CD . 68 B7314000 PUSH crackme.004031B7 ; it's not so easy boy. keep tryin...
F7跟进来到下面:
0040111C $ 55 PUSH EBP
0040111D . 8BEC MOV EBP,ESP
0040111F . E8 1D010000 CALL crackme.00401241
00401124 . 33C0 XOR EAX,EAX
00401126 . 0BC0 OR EAX,EAX
00401128 . 74 09 JE SHORT crackme.00401133
0040112A . 7A 9B JPE SHORT crackme.004010C7
0040112C . 03B3 F3D3CBCB ADD ESI,DWORD PTR DS:[EBX+CBCBD3F3]
00401132 63 DB 63 ; CHAR 'c'
00401133 . 6A 14 PUSH 14 ; /Count = 14 (20.)
00401135 . 68 84324000 PUSH crackme.00403284 ; |Buffer = crackme.00403284
0040113A . 68 EA030000 PUSH 3EA ; |ControlID = 3EA (1002.)
0040113F . FF75 08 PUSH DWORD PTR SS:[EBP+8] ; |hWnd = 06B80674 ('Fornix's Crackme 2',class='#32770')
00401142 . E8 77010000 CALL <JMP.&user32.GetDlgItemTextA> ; \GetDlgItemTextA
00401147 . 68 84324000 PUSH crackme.00403284
0040114C . E8 D0000000 CALL crackme.00401221 ; F7跟进
在上面代码看到GetDlgItemTextA函数,知道这段代码大体上是得到输入的Serial,所以直接F8,在最后一行F7跟进:
F7跟进来到下面:
00401221 /$ 55 PUSH EBP
00401222 |. 8BEC MOV EBP,ESP
00401224 |. 8B75 08 MOV ESI,DWORD PTR SS:[EBP+8] ; 输入的Serial出现,偏移地址为00403284
00401227 |> 803E 00 /CMP BYTE PTR DS:[ESI],0 ; 判断Serial是否转换完
0040122A |. 74 0C |JE SHORT crackme.00401238 ; 完了则跳
0040122C |. 8A06 |MOV AL,BYTE PTR DS:[ESI]
0040122E |. 34 15 |XOR AL,15 ; serial每一个字符与15异或
00401230 |. C0C8 05 |ROR AL,5 ; 再循环右移5位
00401233 |. 8806 |MOV BYTE PTR DS:[ESI],AL ; 存回原来位置
00401235 |. 46 |INC ESI ; 偏移地址减1,移到下一个字符
00401236 |.^ EB EF \JMP SHORT crackme.00401227
然后一路F8,来到下面:
00401151 . BF 20114000 MOV EDI,crackme.00401120 ; 把地址00401120赋给EDI
00401156 . 83C7 0B ADD EDI,0B ; EDI变为0040112B
00401159 . BE 84324000 MOV ESI,crackme.00403284 ; 把地址00403284赋给ESI
0040115E . B9 09000000 MOV ECX,9 ; 计数器置9
00401163 > 49 DEC ECX
00401164 . AC LODS BYTE PTR DS:[ESI] ; 循环比较地址00403284中的数据
00401165 . AE SCAS BYTE PTR ES:[EDI] ; 是否与地址0040112B中的数据相同
00401166 .^ 74 FB JE SHORT crackme.00401163 ; 相同则跳回去接着比较下一位
00401168 . 8BC1 MOV EAX,ECX ; 不相同则把计数器值赋给EAX
0040116A . C9 LEAVE
0040116B . C2 0400 RETN 4 ; 返回
返回以后来到:
004010B0 . 0BC0 OR EAX,EAX ; 判断EAX是否为0,也就判断两个地址中的数据是否一样
004010B2 75 14 JNZ SHORT crackme.004010C8 ; 判断EAX是否为0,不为0则跳
004010B4 . 68 33324000 PUSH crackme.00403233 ; right serial
004010B9 . 68 E8314000 PUSH crackme.004031E8 ; you've done it. write a tutorial and send it to fornix@fusionrulez.cjb.net
004010BE . FF75 08 PUSH DWORD PTR SS:[EBP+8]
004010C1 . E8 A8000000 CALL crackme.0040116E ;
004010C6 . EB 12 JMP SHORT crackme.004010DA
004010C8 > 68 DB314000 PUSH crackme.004031DB ; wrong serial
004010CD . 68 B7314000 PUSH crackme.004031B7 ; it's not so easy boy. keep tryin...
看看起来程序到这基本上清楚了,无非就是把serial每一个字符XOR 15,然后再ROR 5,结果等于0040112B中的数据就行了。马上写算法,得到serial是"fuckolly"(怎么看起来不妙..),运行原exe程序,输入"fuckolly",错误!!怎么会这样...继续跟下去吧:
再看看上面的代码,在正确提示与错误提示之间还有一个CALL crackme.0040116E,跟进去:
0040116E $ 55 PUSH EBP
0040116F . 8BEC MOV EBP,ESP
00401171 . 68 68324000 PUSH crackme.00403268 ; /lParam = 403268
00401176 . 6A 14 PUSH 14 ; |wParam = 14
00401178 . 6A 0D PUSH 0D ; |Message = WM_GETTEXT
0040117A . 68 EA030000 PUSH 3EA ; |ControlID = 3EA (1002.)
0040117F . FF75 08 PUSH DWORD PTR SS:[EBP+8] ; |hWnd = 06B90674 ('Fornix's Crackme 2',class='#32770')
00401182 . E8 49010000 CALL <JMP.&user32.SendDlgItemMessageA> ; \SendDlgItemMessageA
00401187 . EB 08 JMP SHORT crackme.00401191
00401189 . 91 XCHG EAX,ECX
0040118A . 71 39 JNO SHORT crackme.004011C5
0040118C . A9 41112103 TEST EAX,3211141
00401191 > 68 68324000 PUSH crackme.00403268
00401196 . E8 66000000 CALL crackme.00401201 ; F7跟进
在上面代码看到SendDlgItemMessageA函数,知道这段代码大体上是得到显示消息对话框,所以直接F8,在最后一行F7跟进:
00401204 |. 8B75 08 MOV ESI,DWORD PTR SS:[EBP+8] ; 输入的Serial出现
00401207 |> 803E 00 /CMP BYTE PTR DS:[ESI],0 ; 判断Serial是否转换完
0040120A |. 74 0C |JE SHORT crackme.00401218 ; 完了则跳
0040120C |. 8A06 |MOV AL,BYTE PTR DS:[ESI]
0040120E |. 34 41 |XOR AL,41 ; serial每一个字符与41异或
00401210 |. C0C0 03 |ROL AL,3 ; 再循环左移3位
00401213 |. 8806 |MOV BYTE PTR DS:[ESI],AL ; 存回原来位置
00401215 |. 46 |INC ESI ; 偏移地址减1,移到下一个字符
00401216 |.^ EB EF \JMP SHORT crackme.00401207
这一段代码似曾相识吧,没错,就是转换输入的Serial。一路F8,直到返回,来到下面:
0040119B . B8 05000000 MOV EAX,5
004011A0 . F625 40324000 MUL BYTE PTR DS:[403240]
004011A6 . BE 68324000 MOV ESI,crackme.00403268 ; 把地址00403268赋给ESI
004011AB . BF 75114000 MOV EDI,crackme.00401175 ; 把地址00401175赋给EDI
004011B0 . 03F8 ADD EDI,EAX
004011B2 . B9 09000000 MOV ECX,9 ; 计数器置9
004011B7 > 49 DEC ECX
004011B8 . AC LODS BYTE PTR DS:[ESI] ; 循环比较地址00403284中的数据
004011B9 . AE SCAS BYTE PTR ES:[EDI] ; 是否与地址00401175中的数据相同
004011BA .^ 74 FB JE SHORT crackme.004011B7 ; 相同则跳回去接着比较下一位
004011BC . 0BC9 OR ECX,ECX ; 判断ECX是否为0
004011BE 74 21 JE SHORT crackme.004011E1 ; 不为0则跳,
分析到这,后面的代码基本上没有Call,应该这下程序应该清楚了吧。根据程序的逆过程,计算了一下注册码,竟然不是可输入的ASCII字符!用OllyDbg再跟一次,勉强用KeyGen生成的乱码复制到exe对话框中,点check,到了最后一步循环比较时,会发生ECX已经跳到0时,由于越界的字符比较依然相同,ECX会继续DEC,导致紧接着发生跳转,依然提示Serial错误!!这是怎么回事呢?难道程序有问题?这几乎是不可能的。。但是这程序用汇编写的,总共就那么长,代码差不多都看到了,会是哪里有问题呢?
其实在跟踪的时候,有一段程序一直很可疑,一开始我没太想清楚这段程序的作用(菜鸟就是菜鸟),只知道可能是用来效验程序是否修改。后来我就重点盯着这段程序看了,注释是后来才加的:
00401241 /$ 8D3D 00104000 LEA EDI,DWORD PTR DS:[<模块入口点>] ; 程序入口点
00401247 |. B9 97020000 MOV ECX,297 ; 297正好是用户代码长度
0040124C |. E8 45000000 CALL crackme.00401296 ; 把0CC放到AL里
00401251 F2:AE REPNE SCAS BYTE PTR ES:[EDI] ; 循环搜索用户代码中是否有0CC
00401253 |. 85C9 TEST ECX,ECX ; 判断ECX是否为0,也就是是否搜索完毕
00401255 |. 75 3E JNZ SHORT crackme.00401295 ; 不为0则跳
00401257 |. 8D05 9A124000 LEA EAX,DWORD PTR DS:[40129A]
0040125D |. A3 7D324000 MOV DWORD PTR DS:[40327D],EAX
00401262 |> A1 7D324000 /MOV EAX,DWORD PTR DS:[40327D]
00401267 |. 8B15 7D324000 |MOV EDX,DWORD PTR DS:[40327D]
0040126D |. 833A 00 |CMP DWORD PTR DS:[EDX],0
00401270 |. 74 14 |JE SHORT crackme.00401286
00401272 |. 8BC8 |MOV ECX,EAX
00401274 |. E8 1D000000 |CALL crackme.00401296 ; 又是0CC
00401279 |. 3801 |CMP BYTE PTR DS:[ECX],AL
0040127B |. 74 18 |JE SHORT crackme.00401295
0040127D |. 8305 7D324000 06 |ADD DWORD PTR DS:[40327D],6
00401284 |.^ EB DC \JMP SHORT crackme.00401262 ; 循环检查是否有0CC
00401286 |> 8A1D 40324000 MOV BL,BYTE PTR DS:[403240] ; [403240]中的值放入BL
0040128C |. FEC3 INC BL ; BL加1
0040128E |. 881D 40324000 MOV BYTE PTR DS:[403240],BL ; 再存入[403240],其实就是403240中的值加1
00401294 |. C3 RETN
00401295 \> C3 RETN
00401296 /$ B0 CC MOV AL,0CC
00401298 \. C3 RETN
我在这段程序上调了很多次。开始我一直没搞懂这段代码到底想干嘛,后来我发现F7调试这句代码REPNE SCAS BYTE PTR ES:[EDI] 时,EDI跳到1EB时,DS:[EDI]=[004010AC]=06C,AL=0CC,然后再按F7就跳倒下一行了!!明明不等怎么会跳呢??再一看我在004010AC下了中断!猛地想起OllyDbg调试时之所以可以下断点,实际上是用INT3中断替换下断点的字符!再一查,果然就是0CC(菜鸟就是菜鸟,这时才发现,唉...)
其实在前面跟踪的时候,还有一处疑点:
0040119B . B8 05000000 MOV EAX,5
004011A0 . F625 40324000 MUL BYTE PTR DS:[403240]
在调试时[403240]中的值始终为0,这样就没什么意义了。在看看反调试那段代码,如果将00401255处的代码替换为空指令,将00401270处的JE修改为JMP,便会一路执行下来,并使([403240])加1。好的,这样应该差不多了。载入exe用ollydbg一跟,可以看到([403240])最终的值是4,也就是比较的地址将是:00401175+4*5=00401189。根据前面的分析,只要把00401189处的值,以字节为单位,先ROR 3,再XOR 41就可以得到Serial了。Serial固定,就不写注册机了。终于搞定,有点累,但收获很大。
总结:0CC,可不能大意了。
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)