海天一色001 发表于 2019-5-30 10:11

160个Crackme之023学习笔记

打开023,输入Name/Serial=“52pojie/1234567890”,程序界面如下:

Status栏中仍然是错误提示,点击About按钮,弹出提示框:

初步猜测这个程序可能是当Name文本框中每输入一个字符就进行运算后得到注册码,然后再与输入的Serial进行比较,结果输入到Status框中去。这个程序的关闭按钮和Alt+F4组合键都不管用,只能用右键点击弹出的菜单中选择“Alt+F4”这一项才能关闭,不知道怎么回事。
先查壳,无壳,显示是ASM程序:

第一步、爆破
将Chafe.1.exe加载入OD中,习惯性地用智能搜索去查字符串,很轻松地得到了各类文本内容:

双击004012CE这一行正确提示处来到CPU窗口:

马上看到正确提示来源于上面一句je命令,跳过了错误提示,所以直接将je命令修改为jmp命令,

存为可执行文件Chafe.1_jmp.exe,试运行一下,输入name后,在Serial框中刚输入“1”这个字符,下方的Status框中就出现了正确提示,说明爆破成功。

第二步、追码
撤消刚才爆破时的修改,然后上下观察代码,感觉ASM的代码确实是很短小,一会儿就翻完了。

因为作者在About中说了不让爆破,要找到注册算法,所以仔细查看004012B6上面,004012B3处是比较命令,只有=0x10时,才能成功,所以继续向上分析,找到给eax赋值的语句:
从00401239至00401322处应该是判断注册码的代码段,很自然地在段首00401239处下断,然后F9运行程序,不行,程序仍然是暂停状态,再多按几次,仍然是在这里暂停!
不管它,先F8向下,可以走:
从0040123F处看到注释的内容是Switch (cases 1..113),然后看指令,先是与0x10比较,再与0xF比较,再与0x1比较,均不能相等,再与0x113比较,必须相等,而下面又是要求=0x10这样才能正确,所以只有00401294处,这里的call可能就是赋值的关键call了。
所以在寄存器窗口将Z标志位的0改为1,使00401292处的jnz short Chafe_1.004012E4指令不成立,可以到下一步去:
到00401294处的call Chafe_1.00401453指令,F7跟入,F8单步,运行一遍后出来了,还是错误,找不到算法!不死心地F9了几次,还是不行。

静下心来,回头去看,断点位置在自己认为的判断注册码算法的段首处,可能不是很合适,因为这个断点作用不大,在没有输入任何内容,没来得及给ds:/ ds: /ds:之类的地址进行赋值之前程序就被它中断了,造成=ds:=0,向下单步运行肯定不正确。所以要找到一个合适的断点来看看:
从开始向下看,到00401076处,自动注释了“GetWindowTextA”,应该是得到输入的文本吧,在这里下断试一试:
Ctrl+F2重载程序,F9运行,不知道怎么回事023程序窗口直接最大化显示,但还好能恢复到原窗口大小。
输入name=“52pojie”,刚在Serial框中输入1个字符“1”时,程序中断到00401076处,此时GetWindowTextA的参数是Count=0x14(20),Buffer= Chafe_1.0040318C,hWnd = 00450750 (class='Edit',parent=01200766),=0x4;
这个0040318C地址里是什么东西?在这里点右键,选择“数据窗口中跟随—立即数”,内存地址栏中马上显示出一串0来。
F8单步,马上变成了0x7!此时地址栏里,0040318C地址变成了“52pojie”,刚输入的name,而=7,可能是=len(name)了。
00401063   .8B25 A0314000 mov esp,dword ptr ds:
00401069   .6A 14         push 0x14                              ; /Count = 14 (20.)
0040106B   .68 8C314000   push Chafe_1.0040318C                  ; |Buffer = Chafe_1.0040318C=str(name)
00401070   .FF35 74314000 push dword ptr ds:             ; |hWnd = 00470750 (class='Edit',parent=01CD076E)
00401076   .E8 7D040000   call <jmp.&USER32.GetWindowTextA>      ; \GetWindowTextA
0040107B   .B9 14000000   mov ecx,0x14                           ;=0x14(20)
00401080   .2BC8          sub ecx,eax                              ;=0x14-=0xD(13)
00401082   .8DB8 8C314000 lea edi,dword ptr ds:
00401088   >C607 00       mov byte ptr ds:,0x0
0040108B   .47            inc edi                                  ;=+1
0040108C   .49            dec ecx                                  ;=-1
0040108D   .^ 75 F9         jnz short Chafe_1.00401088               ;循环0x14-len(name)次,将后面全变成0
0040108F   .85C0          test eax,eax
00401091   .74 10         je short Chafe_1.004010A3                ;name不能为0
00401093   .8005 66314000>add byte ptr ds:,0x4         ;给ds:赋值
0040109A   .C605 68314000>mov byte ptr ds:,0x0         ;ds:=0x0
004010A1   .EB 06         jmp short Chafe_1.004010A9
004010A3   >8825 66314000 mov byte ptr ds:,ah
004010A9   >C9            leave
004010AA   .C3            retn                                     ;返回到 00401299 (Chafe_1.00401299)
多试了几个name,确定从00401069 到0040108D间代码的作用是取得name字符串并将其格式化,如果len(name)<20,则将name字符串后面用“0”填充成有20位的字符串;如果len(name)>=20则len(name)=19,然后后面用“0”填充,总之,最后一位字符是“0”。
再向下到004010AA处,retn至401299地址,此时ds:=0x8,运行这一句后=0x8。继续向下又失败了。
(后来的分析中慢慢发现这一段的用处,先格式化得到的字符串,再使ds:= ds:+0x4。)
这样下断可能也不合适,没有找到程序的执行顺序。
再到004012B3处看,由于在最后判断前要让= 0x10这样才能正确,而之前可以看到的值必须是0x113,那么只有00401294处call里会给赋值0x10这样才能正确。由于刚才在00401294处的call Chafe_1.00401453指令,F7跟入,F8单步运行一遍后没有找到算法,但在这个call出来后看到 = ds:,那么找到ds:的赋值语句去试着看看:

在00401299处右键选择“查找参考—地址常量”命令,立即看到有8处与ds:有关的指令,

第一个是地址00401093处,一般来说会是首先运行这一句,所以先在这一行下断,并清除原来的断点,重新加载程序,F9运行,输入name=“52pojie”,刚在Serial框中输入1个字符“1”时,程序中断,猜测是每个输入的字符都要经过一定的运算,结果存入ds:中吧。(错误的猜测,在后来的分析过程中发现是Serial框输入时开始先对name框中的内容进行格式化,然后再判断name框的字符串是否全为0,不为0则ds:累加0x4。)继续F8向下,一直到004012B3处,此时=0x8,还是失败;
干脆将所有为ds:赋值的6个地址都下断试一试:
重载程序,F9运行,输入name=“52pojie”,没变化,刚在Serial框中输入1个字符“1”时,程序中断于00401093处,F9再向下中断于00401093,F9再向下中断于00401398,F9再向下中断于004012B3, F9再向下中断于004014B3仍是失败,说明应该在输入Serial前就已经算好了值,只在这里找ds:是远远不够的:



Ctrl+F2重新开始,在内存0x403166处选择“断点—内存写入”,清除其他所有断点,F9运行,粘贴入name=“52pojie”,粘贴入serial=“1234567890”,
此时程序中断于00401493处,
F9运行,又中断于00401093处,
F9运行,又中断于00401398处,
F9运行,又中断于004014B3处,
继续运行,又是00401493—00401093—00401398--004014B3的循环。这和给6个赋值语句都下断的结果差不多,只少了004012B3一处。
结合以前的经验,程序运行顺序应该是:
从00401000开始,到00401018处转到00401023处,再一直执行到004011F7处显示程序界面;
输入name/serial后,在00401239段内利用时间控件进行循环:
到00401294处运行call 00401453,再运行0040146F--0040149B处的代码段,返回到00401299处;
运行计算判断后再call 00401453,再运行00401063—004010AA处的段,返回到00401299处;
继续call 00401453,再运行00401361—004013A0处的段,返回到00401299处;
再call 00401453,再运行0040149C-- 004014C1处的段,返回到00401299处;
代码段的作用继续具体分析:
(1)0040146F--0040149B处,作用是得到输入的注册码,为纯数字时ds:= ds:+0x4;
0040146F   .8B25 A0314000 mov esp,dword ptr ds:
00401475   .6A 00         push 0x0                                 ; /IsSigned = FALSE
00401477   .8D45 FC       lea eax,dword ptr ss:         ; |
0040147A   .50            push eax                                 ; |pSuccess = NULL
0040147B   .6A 64         push 0x64                              ; |ControlID = 64 (100.)
0040147D   .FF35 70314000 push dword ptr ds:             ; |hWnd = 008707B6 ('TEXme v1.0',class='TEXcls')
00401483   .E8 64000000   call <jmp.&USER32.GetDlgItemInt>         ; \=hex(Serial)
00401488   .A3 88314000   mov dword ptr ds:,eax          ;ds:==0-0x9112478
0040148D   .837D FC 00    cmp dword ptr ss:,0x0
00401491   .74 07         je short Chafe_1.0040149A
00401493   .8005 66314000>add byte ptr ds:,0x4         ;此句未运行前ds:=0x8才能正确
0040149A   >C9            leave
0040149B   .C3            retn                                     ;retn 00401299
(2)00401063—004010AA处作用是格式化name字符串并给ds:赋值为ds:+0x4(前面已经分析过了);
(3)00401361—004013A0处作用是循环16次对name字符串进行各种运算,使之最后的值为0-0x9112478=0xF6EEDB88,存入ds:中,并给ds:赋值为ds:+0x4;
00401361   .8D3D 8C314000 lea edi,dword ptr ds:          ;=ds:(name),导入输入的name字符串
00401367   .0FBE05 683140>movsx eax,byte ptr ds:         ;=ds:
0040136E   .03F8          add edi,eax                              ;=+
00401370   .FE05 68314000 inc byte ptr ds:               ;ds:=ds:+1
00401376   .A1 88314000   mov eax,dword ptr ds:          ;=ds:
0040137B   .8B25 A0314000 mov esp,dword ptr ds:          ;=ds:
00401381   .40            inc eax                                  ;=+1
00401382      FF05 88314000 inc dword ptr ds:            ;ds:=ds:+1
00401388   .3307          xor eax,dword ptr ds:               ;= xor ds:
0040138A   .A3 88314000   mov dword ptr ds:,eax          ;循环16次后ds:==0-0x9112478=0xF6EEDB88才能正确
0040138F   .803D 68314000>cmp byte ptr ds:,0x10          ;ds:必等于0x10才行,上面代码需循环16次
00401396   .75 07         jnz short Chafe_1.0040139F
00401398   .8005 66314000>add byte ptr ds:,0x4         ;给ds:赋值处
0040139F   >C9            leave
004013A0   .C3            retn
(4)0040149C-- 004014C1处判断计算后的注册码,如正确则给ds:赋值为ds:+0x4;
0040149C   .A1 88314000   mov eax,dword ptr ds:          ;ds:=0-0x9112478
004014A1   .05 78241109   add eax,0x9112478                        ;=+0x9112478
004014A6   .85C0          test eax,eax                           ;此时必等于0
004014A8   .75 09         jnz short Chafe_1.004014B3
004014AA   .8005 66314000>add byte ptr ds:,0x4         ;此句未运行前ds:必为0xC才能正确
004014B1   .EB 07         jmp short Chafe_1.004014BA
004014B3   >C605 66314000>mov byte ptr ds:,0x0         ;给ds:赋值为0,与上方互斥关系,肯定失败,所以必跳过
004014BA   >8B25 A0314000 mov esp,dword ptr ds:
004014C0   .C9            leave
004014C1   .C3            retn
从最后正确的结果开始逆向分析,先确定正确的情况该怎么执行代码,一步步向上倒推,在注释栏加上自己的注释;从这四段代码的作用可以看出,程序中只要输入了name与纯数字的serial就可以让给ds:的值达到0xC(12),只要再将注册码填对就可以再给ds:赋值为ds:+0x4,使ds:=0x10(16),让程序界面的satus栏显示正确提示。所以注册码算法在第三段代码的位置:
Ctrl+F2重新开始,在内存00401361处下断,清除其他所有断点,F9运行,粘贴入name=“52pojie”,粘贴入serial=“1234567890”,此时程序中断于00401361处,下方可以看到给edi导入输入的name(“52pojie”):

为了找到如何对name字符串进行运算得到注册码的,F8单步进行下去,进行分析的结果,是ds:作为循环变量的寄存器,进行循环加1运算(00401370处),然后判断ds:是否为0x10,是则给ds:赋值为ds:+0x4;ds:首先存入的是输入的serial字符串“1234567890”的16进制数值0x499602D2,

然后加1,变成0x499602D3;到00401388处,= xor ds:,

此时在信息窗口右键点击第一行的ds:,选择“数据窗口中跟随地址”,可以看到内存地址中ASCII码为“52pojie”,而ds:的值是逆向取name字符串前4个字符的16进制ASCII数值:

F8向下,到0040138A处,将结果0x26E630E6存入ds:中;
继续向下,返回到00401299处,再到004012AB处,命令是retn 0x10,信息窗口中是“返回到 77D18734 (user32.77D18734)”,耐心地F8走,终于返回到程序领空00401207处,继续F8走:
结果在00401230这里陷入了死循环,不断地跳回00401207处。在上面四段代码中没有循环语句,但是能做到循环,说明应该是利用时间控件做到的,我这样单步,时间控件估计不起作用,所以F9一下试试,程序马上又中断到00401361处了;
第二次循环后,ds:=0x2,ds:= 0x4C8940D5;
第三次循环后,ds:=0x3,ds:= 0x25E32FA6;
。。。。。。
第十五次循环后,ds:=0xF,ds:= 0x40EF49B3;
第十六次循环后,ds:=0x10,ds:= 0x40EF49B4;
而0x40EF49B4与0xF6EEDB88相差太大,肯定不是正确的结果,“1234567890”不是“52pojie”对应的注册码;继续向下,ds:=0xC,不是0x10,所以会失败。
那么程序应该从第十六次循环倒推回去,将0xF6EEDB88减去1,再与name字符串从后向前每四位的hex值组成的数值xor,最后的结果就是注册码了。
还是用VB编写注册机吧,下面是最简单的原理代码,到处是错误,主要是数值溢出错误,还有Name字符串长度过长过短等问题没在下面显示。
Serial=0xF6EEDB88
Namestr=text1.text
For i=1 to 16
Namehex=hex(mid(Namestr,i+3,1) & hex(mid(Namestr,i+2,1) & hex(mid(Namestr,i+1,1) & hex(mid(Namestr,i,1)
Serial=Serial xor Namehex
Serial=Serial-1
Next
Text2.text=Serial
注册机已写好,程序源码均在内。一开始就是一个数值溢出问题,上网找了一大堆资料,借用了别人的大数运算代码,总是有问题所以又试着用VC编程,本想VB、VC都是微软同一公司的,界面什么的都相似,只是数值范围不一样,应该好学一些,结果是悲剧,死活不会用,找不到可视化编程的窗体等,还需要以后再重新学习;再就是用易语言了,也是费了九牛二虎之力,终于编写出了易语言的注册机,不知怎么回事,杀毒软件老杀。这两个注册机的源码和程序均放入压缩包里了,如不放心,易语言生成的注册机请勿使用。
附件 ,含CM原程序、爆破后的程序、注册机、OD的调试文件等。
百度链接是:http://pan.baidu.com/s/1skMkJY9密码: 86pm,160个CM、我已练习过的前23个crackme程序(不含012)都在里面。

q130545 发表于 2019-5-30 10:26

牛人!看的我一愣一愣的

SnowRen 发表于 2019-5-30 10:27

易语言写的程序老报毒算是基本操作,就是这个原因我都没在用易写程序

Ars 发表于 2019-5-30 16:52

标记一下,先看看好不好,谢谢楼主咯!

吾爱、小江 发表于 2019-5-30 20:32

写的太棒了,叔,特别详细,{:1_921:}{:1_921:}
页: [1]
查看完整版本: 160个Crackme之023学习笔记