44018723 发表于 2014-6-10 19:24

[反汇编练习]160个CrackMe之001

本帖最后由 44018723 于 2014-6-10 19:31 编辑

[反汇编练习] 160个CrackMe之001。本系列文章的目的是从一个没有任何经验的新手的角度(其实就是我自己),一步步尝试将160个CrackMe全部破解,如果可以,通过任何方式写出一个类似于注册机的东西。其中,文章中按照如下逻辑编排(解决如下问题):1、使用什么环境和工具2、程序分析3、思路分析和破解流程4、注册机的探索1、工具和环境:WinXP SP3 + 52Pojie六周年纪念版OD + PEID + 汇编金手指。160个CrackMe的打包文件。下载地址: http://pan.baidu.com/s/1xUWOY密码: jbnq注:1、Win7系统对于模块和程序开启了随机初始地址的功能,会给分析带来很大的负担,所以不建议使用Win7进行分析。2、以上工具都是在52PoJie论坛下的原版程序,NOD32不报毒,个人承诺绝对不会进行任何和木马病毒相关内容。http://images.cnitblog.com/blog/573547/201406/091056509677900.png
2、程序分析:    想要破解一个程序,必须先了解这个程序。所以,在破解过程中,对最初程序的分析很重要,他可以帮助我们理解作者的目的和意图,特别是对于注册码的处理细节,从而方便我们反向跟踪和推导。    打开CHM文件,将第一个文件 Acid burn.exe 保存下来,新建一个01的文件夹,将exe放到这里,同时将以后的分析文件也存放这里。打开Acid burn.exe,随意输入和点击,熟悉程序流程。http://images.cnitblog.com/blog/573547/201406/091057110148443.png    我们发现,这个软件分为了两个部分,一个是Serial/Name,需要输入用户名和注册码才能通过,另外一个Serial只需要输入一个注册码一类的东西。我们随意选一个开始,比如,先进行第一个。http://images.cnitblog.com/blog/573547/201406/091057589835720.png我们随意输入一个用户名和序列号(伪码):伪码:
Name:112233
Serial:44556677点击Check it Baby! 它会弹出一个对话框提示: Sorry, The Serial is incorrect !再换几个随意试试,发现就这一种情况。OK,出现了对话框这就很好办,说明作者在校验注册码之后发现如果错误了就直接弹窗,我们只要找到弹出对话框的地方,向上跟踪,就可以找出判断是否正确的地方了,jmp或者Nop就算爆破了。3、具体步骤如下:我们随意输入一个用户名和序列号(伪码):Name:112233
Serial:44556677点击Check it Baby! 它会弹出一个对话框提示: Sorry, The Serial is incorrect !此时不要点击确定按钮,返回OD暂停(F12),点击堆栈-K小图标(Ctrl+K) ,如下图:http://images.cnitblog.com/blog/573547/201406/101703148427449.png这里有两个MessageBox的地址,第一个地址为77D5082F这个地址明显太大,不在模块的领空,不是的。第二个地址为0042A1AE,和00400100地址非常接近,十有八九就是它了。右键 show call, 在Call上面设置断点。查看附近代码:0042A170/$55            push ebp
0042A171|.8BEC          mov ebp,esp
0042A173|.83C4 F4       add esp,-0xC
0042A176|.53            push ebx
0042A177|.56            push esi
0042A178|.57            push edi
0042A179|.8BF9          mov edi,ecx
0042A17B|.8BF2          mov esi,edx
0042A17D|.8BD8          mov ebx,eax
0042A17F|.E8 7CB4FDFF   call <jmp.&user32.GetActiveWindow>       ; [GetActiveWindow
0042A184|.8945 F8       mov ,eax
0042A187|.33C0          xor eax,eax
0042A189|.E8 12A0FFFF   call 004241A0
0042A18E|.8945 F4       mov ,eax
0042A191|.33C0          xor eax,eax
0042A193|.55            push ebp
0042A194|.68 D0A14200   push 0042A1D0
0042A199|.64:FF30       push dword ptr fs:
0042A19C|.64:8920       mov dword ptr fs:,esp
0042A19F|.8B45 08       mov eax,
0042A1A2|.50            push eax                                 ; /Style
0042A1A3|.57            push edi                                 ; |Title
0042A1A4|.56            push esi                                 ; |Text
0042A1A5|.8B43 24       mov eax,dword ptr ds:          ; |
0042A1A8|.50            push eax                                 ; |hOwner
0042A1A9|.E8 FAB5FDFF   call <jmp.&user32.MessageBoxA>         ; \MessageBoxA
发现,没有跳转语句,逻辑很简单,在之上几行处就有retn,在头部push ebp下断,重新点击Check it baby 按钮,在右下角堆栈处找到最近一条Return语句:0012F974   0042FB37RETURN to Acid_bur.0042FB37 from Acid_bur.0042A170右键 Follow in Disassm..(反汇编跟随),这里直接连接了一个跳转,代码如下:0042FAD5|.68 C8FB4200   push 0042FBC8                            ;UNICODE "-"
0042FADA|.FF75 F8       push
0042FADD|.8D45 F4       lea eax,
0042FAE0|.BA 05000000   mov edx,0x5
0042FAE5|.E8 C23EFDFF   call 004039AC
0042FAEA|.8D55 F0       lea edx,
0042FAED|.8B83 E0010000 mov eax,dword ptr ds:
0042FAF3|.E8 60AFFEFF   call 0041AA58
0042FAF8|.8B55 F0       mov edx,
0042FAFB|.8B45 F4       mov eax,
0042FAFE|.E8 F93EFDFF   call 004039FC
0042FB03      75 1A         jnz short 0042FB1F                     ;// 这个JNZ条件判断很关键?
0042FB05|.6A 00         push 0x0
0042FB07|.B9 CCFB4200   mov ecx,0042FBCC
0042FB0C|.BA D8FB4200   mov edx,0042FBD8
0042FB11|.A1 480A4300   mov eax,dword ptr ds:
0042FB16|.8B00          mov eax,dword ptr ds:
0042FB18|.E8 53A6FFFF   call 0042A170
0042FB1D|.EB 18         jmp short 0042FB37                     ;// 这个跳转是不是很可疑?
0042FB1F|>6A 00         push 0x0
0042FB21|.B9 74FB4200   mov ecx,0042FB74                         ;ASCII 54,"ry Again!"
0042FB26|.BA 80FB4200   mov edx,0042FB80                         ;ASCII 53,"orry , The serial is incorect !"
0042FB2B|.A1 480A4300   mov eax,dword ptr ds:
0042FB30|.8B00          mov eax,dword ptr ds:
0042FB32|.E8 39A6FFFF   call 0042A170                            ;这个CALL是导致跳转的语句
0042FB37|>33C0          xor eax,eax                              ;返回到了这里
看到和提示框一样的文本,是不是感到很亲切?OK,我们大概浏览下代码,最近部分有两个可疑跳转JNZ 和JMP, JNZ会通过它上面的call 004039FC 判断我们的伪码是否正确,判断的结果存在EAX中,如果EAX不等于就跳转到错误提示信息框那里。我们的目的是无论伪码是否正确都通过验证,所以最简单的办法就是将jnz这句使用NOP填充,我们尝试一下:选择JNZ这句,右键Binary->Fill with NOPS.回到原始程序,再次点击Check it baby!哈哈,提示Good Job!通过了!再次比对MessageBox和堆栈(Ctrl+K)窗口最后一个调用,是不是发现什么特殊的地方?对啦!那个CALL就是调用MessageBox的地方,所以,下次我们就不用在MessageBox处下断跟踪了,直接最后一个地址,show call。4、注册机部分以上部分是爆破分析,我们看看能否分析出注册机算法。
在原JNZ上面一行CALL下断:点击Ckeck it Baby!按钮,程序断下:0042FAF8|.8B55 F0       mov edx,                        ;// EDX=44556677
0042FAFB|.8B45 F4       mov eax,                        ;// EAX=CW-4018-CRACKED
0042FAFE|.E8 F93EFDFF   call 004039FC
我们发现,EDX存储的是我们的假序列号,EAX看上去像是一个正确的序列号,也有可能是对应的用户名也说不定,我们可疑尝试一下。
现将我们之前修改的代码恢复:选中那两行NOP,右键Undo... 。取消断点。先尝试是否是Name,Name输入EAX的值,下一行继续44556677,结果继续弹错。然后尝试作为序列号,Name为112233,Serial为CW-4018-CRACKED,再尝试,OK!完全正确!小结一下:在这个CALL之前程序已经将用户名对应的序列号算出来了,然后和我们输入的Serial通过这个CALL对比,最终给出提示信息。我们要的算法不再这个CALL中。下一步的思路就是继续在这个CALL之上的CALL下断,分析出产生出这个序列号的CALL。由于CALL的返回值一般都存在EAX中,所以我们可以查看附近的CALL之后EAX的值,从而判断出正确的注册码生成函数。附近的两个CALL:0042FADD|.8D45 F4       lea eax,
0042FAE0|.BA 05000000   mov edx,0x5
0042FAE5|.E8 C23EFDFF   call 004039AC                            ;// 最近的CALL2
0042FAEA|.8D55 F0       lea edx,
0042FAED|.8B83 E0010000 mov eax,dword ptr ds:
0042FAF3|.E8 60AFFEFF   call 0041AA58                            ;// 最近的CALL 1
0042FAF8|.8B55 F0       mov edx,                        ;// EDX=44556677
0042FAFB|.8B45 F4       mov eax,                        ;// EAX=CW-4018-CRACKED
0042FAFE|.E8 F93EFDFF   call 004039FC
分别在CALL之后的那一句下断,点击按钮,F8步过,发现在 附近的CALL 1 处,EAX出现正确的注册码,说明他可能是关键的注册码CALL,下面对它的进行分析:
重新在call 0041AA58 下断,0042FAEA|.8D55 F0       lea edx,                        ;edx=0012F998
0042FAED|.8B83 E0010000 mov eax,dword ptr ds:         ;eax=00A85E4C
0042FAF3|.E8 60AFFEFF   call 0041AA58                            ;// 最近的CALL 1,注册码CALL
其中edx和eax都没有特殊信息出现,我们就可以更加断定注册码都是在call中生成的。这时我们就有两种方案了,一种分析CALL的参数,直接远程调用CALL生成正确的注册码,另一种是分析CALL的内容,根据他的算法自己写一个这个生成流程。这两个无明显的好坏之分,只看用在什么地方了。按照我的理解,第一种,内存调用适合算法复杂或者更新不频繁的程序,这么做可以节省时间和复杂度。第二种,适合算法不是特别复杂,或者软件更新频繁的软件,算法的复杂度就不说了,软件无论更新多么频繁,一般注册码算法是不会变的,这样生成的注册机就可以适用所有的版本,大家都省事。好了,啰嗦了这么多,开始正式分析:
(由于已经在这里CALL被调用了很多次了,各个寄存器参数很乱,建议从新加载一次程序,方便分析)
进入CALL 0041AA58,代码如下:
(由于这个CALL被很多地方一直调用,所以我们必须每次从上层CALL单步F7跟踪,直到找到正确的CALL)0041AA58/$53            push ebx
0041AA59|.56            push esi
0041AA5A|.57            push edi
0041AA5B|.8BFA          mov edi,edx
0041AA5D|.8BF0          mov esi,eax
0041AA5F|.8BC6          mov eax,esi
0041AA61|.E8 A2FFFFFF   call 0041AA08                            ;// 这里直接跳出去了,所以需要继续跟踪
0041AA66|.8BD8          mov ebx,eax
0041AA68|.8BC7          mov eax,edi
0041AA6A|.8BCB          mov ecx,ebx
0041AA6C|.33D2          xor edx,edx
0041AA6E|.E8 E18CFEFF   call 00403754
0041AA73|.85DB          test ebx,ebx
0041AA75|.74 0C         je short 0041AA83
0041AA77|.8D4B 01       lea ecx,dword ptr ds:
0041AA7A|.8B17          mov edx,dword ptr ds:
0041AA7C|.8BC6          mov eax,esi
0041AA7E|.E8 95FFFFFF   call 0041AA18
0041AA83|>5F            pop edi
0041AA84|.5E            pop esi
0041AA85|.5B            pop ebx
0041AA86\.C3            retn

继续跟踪到这里:

0041AA08/$6A 00         push 0x0                                 ; /Arg1 = 00000000
0041AA0A|.33C9          xor ecx,ecx                              ; |
0041AA0C|.BA 0E000000   mov edx,0xE                              ; |
0041AA11|.E8 F6070000   call 0041B20C                            ; \Acid_bur.0041B20C
0041AA16\.C3            retn

继续:
0041B20C/$55            push ebp
0041B20D|.8BEC          mov ebp,esp
0041B20F|.83C4 F0       add esp,-0x10
0041B212|.53            push ebx
0041B213|.8955 F0       mov ,edx
0041B216|.894D F4       mov ,ecx
0041B219|.8B55 08       mov edx,
0041B21C|.8955 F8       mov ,edx
0041B21F|.33D2          xor edx,edx
0041B221|.8955 FC       mov ,edx
0041B224|.85C0          test eax,eax
0041B226|.74 0B         je short 0041B233
0041B228|.8D55 F0       lea edx,
0041B22B|.8BD8          mov ebx,eax
0041B22D|.8B43 2C       mov eax,dword ptr ds:
0041B230|.FF53 28       call dword ptr ds:
0041B233|>8B45 FC       mov eax,
0041B236|.5B            pop ebx
0041B237|.8BE5          mov esp,ebp
0041B239|.5D            pop ebp
0041B23A\.C2 0400       retn 0x4

继续F7步入:

0041CB64/$53            push ebx
0041CB65|.56            push esi
0041CB66|.57            push edi
0041CB67|.83C4 F0       add esp,-0x10
0041CB6A|.8BF2          mov esi,edx
0041CB6C|.8BD8          mov ebx,eax
0041CB6E|.8B06          mov eax,dword ptr ds:
0041CB70|.3D 84000000   cmp eax,0x84                           ;Switch (cases 7..20A)
0041CB75|.7F 18         jg short 0041CB8F
0041CB77|.74 69         je short 0041CBE2
...
跟了一大堆,我们发现没有地方可产生注册码,并且所有的CALL都是不断地在被调用,所以,初步判定注册码不是在这里生成的。但是这么想就与之前的猜测完全推翻了,说明我们的想法有问题,即思路有问题。既然他的码不是及时算出来的,那肯定就是事先算好的,我们再次回到产生注册码的CALL那里,向上查找,F8单步进行查看。0042FA4D|.A1 6C174300   mov eax,dword ptr ds:          ;// name/Tag
0042FA52|.E8 D96EFDFF   call 00406930                            ;// 关键CALL
0042FA57|.83F8 04       cmp eax,0x4                              ;// 判断tag/serial 是否合格
0042FA5A|.7D 1D         jge short 0042FA79
0042FA5C|.6A 00         push 0x0
0042FA5E|.B9 74FB4200   mov ecx,0042FB74                         ;ASCII 54,"ry Again!"
0042FA63|.BA 80FB4200   mov edx,0042FB80                         ;ASCII 53,"orry , The serial is incorect !"
0042FA68|.A1 480A4300   mov eax,dword ptr ds:
0042FA6D|.8B00          mov eax,dword ptr ds:
0042FA6F|.E8 FCA6FFFF   call 0042A170
0042FA74|.E9 BE000000   jmp 0042FB37
0042FA79|>8D55 F0       lea edx,
0042FA7C|.8B83 DC010000 mov eax,dword ptr ds:
0042FA82|.E8 D1AFFEFF   call 0041AA58
0042FA87|.8B45 F0       mov eax,                        ;EAX=112233的地址
0042FA8A|.0FB600      movzx eax,byte ptr ds:            ;第一个字节1=0x31
0042FA8D|.F72D 50174300 imul dword ptr ds:             ;*0x29
0042FA93|.A3 50174300   mov dword ptr ds:,eax          ;eax=0x7d9=0x29*0x31
0042FA98|.A1 50174300   mov eax,dword ptr ds:
0042FA9D|.0105 50174300 add dword ptr ds:,eax          ;加一倍
0042FAA3|.8D45 FC       lea eax,
0042FAA6|.BA ACFB4200   mov edx,0042FBAC
0042FAAB|.E8 583CFDFF   call 00403708
0042FAB0|.8D45 F8       lea eax,
0042FAB3|.BA B8FB4200   mov edx,0042FBB8
0042FAB8|.E8 4B3CFDFF   call 00403708
0042FABD|.FF75 FC       push
0042FAC0|.68 C8FB4200   push 0042FBC8                            ;UNICODE "-"
0042FAC5|.8D55 E8       lea edx,                        ;12F990
0042FAC8|.A1 50174300   mov eax,dword ptr ds:          ;4018
0042FACD|.E8 466CFDFF   call 00406718
0042FAD2|.FF75 E8       push                            ;// 注册码中间的值
0042FAD5|.68 C8FB4200   push 0042FBC8                            ;UNICODE "-"
0042FADA|.FF75 F8       push
0042FADD|.8D45 F4       lea eax,
0042FAE0|.BA 05000000   mov edx,0x5
0042FAE5|.E8 C23EFDFF   call 004039AC
0042FAEA|.8D55 F0       lea edx,                        ;edx=0012F998
0042FAED|.8B83 E0010000 mov eax,dword ptr ds:         ;eax=00A85E4C
0042FAF3|.E8 60AFFEFF   call 0041AA58                            ;// 注册码CALL
0042FAF8|.8B55 F0       mov edx,                        ;// EDX=44556677
0042FAFB|.8B45 F4       mov eax,                        ;// EAX=CW-4018-CRACKED
0042FAFE|.E8 F93EFDFF   call 004039FC
0042FB03      75 1A         jnz short 0042FB1F                     ;// 这个JNZ条件判断很关键?
0042FB05|.6A 00         push 0x0
0042FB07|.B9 CCFB4200   mov ecx,0042FBCC
0042FB0C|.BA D8FB4200   mov edx,0042FBD8
0042FB11|.A1 480A4300   mov eax,dword ptr ds:
0042FB16|.8B00          mov eax,dword ptr ds:
0042FB18|.E8 53A6FFFF   call 0042A170
0042FB1D|.EB 18         jmp short 0042FB37                     ;// 这个跳转是不是很可疑?
0042FB1F|>6A 00         push 0x0
0042FB21|.B9 74FB4200   mov ecx,0042FB74                         ;ASCII 54,"ry Again!"
0042FB26|.BA 80FB4200   mov edx,0042FB80                         ;ASCII 53,"orry , The serial is incorect !"
总结:取第一个字母的ASNI的数字,如112233中第一个字符1对应数字0x31,然后用它乘以0x29,结果再自增一倍(即x2),将得到的数字转为10进制的字符串,在前加上”CW-”,后加上”-CRACKED”,就组成了用户名对应的注册码。C/CPP程序:// CrackMe160.cpp : 定义控制台应用程序的入口点。
// NameSerial部分

#include "stdafx.h"
#include "iostream"

int _tmain(int argc, _TCHAR* argv[])
{
    printf("Input Name:\r\n");
    // 取第一个字符值
    int cName = getchar();
    if ( cName > 0x21) // 只处理可见字符
    {
      cName *= 0x29; // 乘法
      cName *= 2; // 自增一倍
      printf("Serial: CW-%4d-CRACKED\r\n",cName);
    }else{
      printf("input error!\r\n");
    }
    system("pause");
    return 0;
}
第二个单独Serial流程同第一个,http://images.cnitblog.com/blog/573547/201406/091602496081332.png但是这个很简单,直接在调用CALL那里向上F8走一遍就基本明白了。核心代码:CALL之前的那个JNZ是爆破的关键,直接NOP就OK了。JNZ之前的那个CALL是进行Serial判断的关键,通过单步跟踪发现他就是一个固定值。代码如下:0042F470/.55            push ebp
0042F471|.8BEC          mov ebp,esp
0042F473|.33C9          xor ecx,ecx
0042F475|.51            push ecx
0042F476|.51            push ecx
0042F477|.51            push ecx
0042F478|.51            push ecx
0042F479|.53            push ebx
0042F47A|.8BD8          mov ebx,eax
0042F47C|.33C0          xor eax,eax
0042F47E|.55            push ebp
0042F47F|.68 2CF54200   push 0042F52C
0042F484|.64:FF30       push dword ptr fs:
0042F487|.64:8920       mov dword ptr fs:,esp
0042F48A|.8D45 FC       lea eax,
0042F48D|.BA 40F54200   mov edx,0042F540                         ;ASCII 48,"ello"
0042F492|.E8 7142FDFF   call 00403708
0042F497|.8D45 F8       lea eax,
0042F49A|.BA 50F54200   mov edx,0042F550                         ;ASCII 44,"ude!"
0042F49F|.E8 6442FDFF   call 00403708
0042F4A4|.FF75 FC       push
0042F4A7|.68 60F54200   push 0042F560                            ;UNICODE " "
0042F4AC|.FF75 F8       push
0042F4AF|.8D45 F4       lea eax,
0042F4B2|.BA 03000000   mov edx,0x3
0042F4B7|.E8 F044FDFF   call 004039AC
0042F4BC|.8D55 F0       lea edx,
0042F4BF|.8B83 E0010000 mov eax,dword ptr ds:
0042F4C5|.E8 8EB5FEFF   call 0041AA58
0042F4CA|.8B45 F0       mov eax,                        ;// eax=112233
0042F4CD|.8B55 F4       mov edx,                        ;// edx=Hello Dude!
0042F4D0|.E8 2745FDFF   call 004039FC
0042F4D5      75 1A         jnz short 0042F4F1                     ;// 爆破的关键
0042F4D7|.6A 00         push 0x0
0042F4D9|.B9 64F54200   mov ecx,0042F564
0042F4DE|.BA 70F54200   mov edx,0042F570
0042F4E3|.A1 480A4300   mov eax,dword ptr ds:
0042F4E8|.8B00          mov eax,dword ptr ds:
0042F4EA|.E8 81ACFFFF   call 0042A170
0042F4EF|.EB 18         jmp short 0042F509
0042F4F1|>6A 00         push 0x0
0042F4F3|.B9 84F54200   mov ecx,0042F584
0042F4F8|.BA 8CF54200   mov edx,0042F58C
0042F4FD|.A1 480A4300   mov eax,dword ptr ds:
0042F502|.8B00          mov eax,dword ptr ds:
0042F504|.E8 67ACFFFF   call 0042A170
0042F509|>33C0          xor eax,eax
这个没有动态生成的注册码,是一个固定的:Hello Dude!
PS: 哎,写个帖子真难,幸亏还是我用WLW写好的。

syh123q 发表于 2014-6-10 19:30

代码贴成那样了

44018723 发表于 2014-6-10 19:31

syh123q 发表于 2014-6-10 19:30
代码贴成那样了

恩,发现了,刚改好。

44018723 发表于 2014-6-10 19:42

Hmily 发表于 2014-6-10 19:36
不错,看出来是用心写的,把图片上传本地吧,防止以后丢失。

多谢袋袋夸奖,今天的图片已经不行了,以后会传的。

Hmily 发表于 2014-6-10 19:36

不错,看出来是用心写的,把图片上传本地吧,防止以后丢失。

刘宏伟大人丶 发表于 2014-6-10 19:47

44018723 发表于 2014-6-10 19:55

Hmily 发表于 2014-6-10 19:36
不错,看出来是用心写的,把图片上传本地吧,防止以后丢失。

文章使用Windows Live Writer 写的,当初截图就没保存本地图片,直接传到博客里的。

manbajie 发表于 2014-6-10 21:37

正在学习中谢谢楼主

BlackTrace 发表于 2014-6-11 08:12

楼主求 交流啊

凤凰城 发表于 2014-6-11 08:25

太棒了,感谢分享啦!学习一下
页: [1] 2 3 4 5 6
查看完整版本: [反汇编练习]160个CrackMe之001