160个CrackMe之001
环境和工具
Win10+OD+PEID
程序分析
- 想要破解一个程序,首先我们要了解它,知道它的执行流程,这要我们才好反向跟踪
- 怎么搜集信息呢?程序本身和外部工具
- PEID查壳后发现没有壳,是一个Delphi的程序
- 打开程序Acid burn.exe运行,我么发现这个程序破解分两部分:
- 一个是Serial/Name,需要输入正确的用户名和密码才能通过
- 一个Serial只需要输入一个序列号就可以了
第一部分
我们开始第一个,先随意输入一个用户名密码:
Name:15pb
Password:123456
点击Check It Baby! 会弹出一个对话框提示信息: Sorry, The Serial is incorrect !
这说明作者检测我们输入错误会弹出提示信息,我们只需要定位到弹出对话框或者字符串位置,往上找到判断代码Nop掉就爆破成功
分析过程
- 程序拖到OD
- 搜索字符串 Sorry, The Serial is incorrect !
- 发现有两处,在两个地方分别下断点
OD运行程序,输入用户名密码:
Name:15pb
Password:123456
点击Check It Baby!
程序会断在这个地方
提示信息的字符串我们找到了,但是对话框还没弹出来,可能有人会担心会不会找错了,毕竟搜索出来了两个字符串.记住位置,F9
继续运行
会弹出一个对话框提示信息: Sorry, The Serial is incorrect !
这时不要急着点确定,按F12
暂停,打开调用堆栈
发现了弹出对话框MessageBox,惊喜有木有,两个主模块调用分别过去看一看,发现一个是函数MessageBox调用的位置,一个就是我们字符串下面的CALL,这就说明我们找对位置了.
OK,分析一下得到的信息: - 一个CALL对我们输入的用户名密码做了验证,根据返回值跳转(JNZ)
- 返回值不等于0说明用户名密码错误,跳转调用MessageBox弹出错误提示信息
我猜测流程可能是这样的,对不对?需要验证一下:
- 在JNZ上的CALL下断点,重新运行
- 到达断点后,我们直接把跳转Nop掉,运行一下看看,发现成功了,这说明我们的猜想没错,到这其实就爆破成功了。
想写注册机的话我们还是要详细分析一下,重新运行程序
程序断下来后,我们先观察参数(Delphi是寄存器传参)
- 参数一:EAX=Local4=“CW-4018-CRACKED”
- 参数二:EDX=Local3=“123456”(是不是很熟悉,这不就是我们输入的密码吗?)
我猜这个CW-4018-CRACKED应该就是真正的密码了,是不是呢?试一下,成功了!!!换个名字试一下,又不行了!!!
总结一下:动态密码,并且这个密码和用户名相关(应该是根据算法计算出来的)
到现在我们还没看见用户名,往上找,发现一个有意思的地方,这个字符串是不是有点眼熟,然而它并没有执行,那JGE和JMP两个跳转就很关键了,jmp直接就跳过了我们弹出对话框的地方,它肯定不是,在JGE下断点,先看看
分析参数发现:跳转后第一个CALL的参数EDX就是用户名,F8
走一遍:
注意观察指针参数的传入传出变化以及返回值
0042FA79 |> \8D55 F0 LEA EDX,[LOCAL.4] ; |跳转位置,EDX=username
0042FA7C |. 8B83 DC010000 MOV EAX,DWORD PTR DS:[EBX+0x1DC] ; |
0042FA82 |. E8 D1AFFEFF CALL <Acid_bur.strlen> ; \用户名长度
0042FA87 |. 8B45 F0 MOV EAX,[LOCAL.4] ; eax=name
0042FA8A |. 0FB600 MOVZX EAX,BYTE PTR DS:[EAX] ; AL=name[0]
0042FA8D |. F72D 50174300 IMUL DWORD PTR DS:[0x431750] ; eax=name[0]*0x29
0042FA93 |. A3 50174300 MOV DWORD PTR DS:[0x431750],EAX ; temp=name[0]*0x29
0042FA98 |. A1 50174300 MOV EAX,DWORD PTR DS:[0x431750]
0042FA9D |. 0105 50174300 ADD DWORD PTR DS:[0x431750],EAX ; temp=(name[0]*0x29)*2
0042FAA3 |. 8D45 FC LEA EAX,[LOCAL.1]
0042FAA6 |. BA ACFB4200 MOV EDX,Acid_bur.0042FBAC ; edx=CW
0042FAAB |. E8 583CFDFF CALL Acid_bur.00403708 ; local1=CW
0042FAB0 |. 8D45 F8 LEA EAX,[LOCAL.2]
0042FAB3 |. BA B8FB4200 MOV EDX,Acid_bur.0042FBB8 ; edx=CRACKED
0042FAB8 |. E8 4B3CFDFF CALL Acid_bur.00403708 ; local2=CRACKED
0042FABD |. FF75 FC PUSH [LOCAL.1] ; CW
0042FAC0 |. 68 C8FB4200 PUSH Acid_bur.0042FBC8 ; 看了下可能是个字符串
0042FAC5 |. 8D55 E8 LEA EDX,[LOCAL.6] ; edx=local6=0
0042FAC8 |. A1 50174300 MOV EAX,DWORD PTR DS:[0x431750] ; 咦?这不是我们的首字母加密值吗?
0042FACD |. E8 466CFDFF CALL Acid_bur.00406718 ; Local6=4018
0042FAD2 |. FF75 E8 PUSH [LOCAL.6] ; 4018
0042FAD5 |. 68 C8FB4200 PUSH Acid_bur.0042FBC8 ; 算法相关字符串
0042FADA |. FF75 F8 PUSH [LOCAL.2] ; local2=CRACKED
0042FADD |. 8D45 F4 LEA EAX,[LOCAL.3] ; local3=0 是不是该注意了
0042FAE0 |. BA 05000000 MOV EDX,0x5 ; ??? 没看懂继续走
0042FAE5 |. E8 C23EFDFF CALL Acid_bur.004039AC ; local3=CW-4018-CRACKED 密码出现了
0042FAEA |. 8D55 F0 LEA EDX,[LOCAL.4] ; |
0042FAED |. 8B83 E0010000 MOV EAX,DWORD PTR DS:[EBX+0x1E0] ; |
0042FAF3 |. E8 60AFFEFF CALL <Acid_bur.strlen> ; \strlen
0042FAF8 |. 8B55 F0 MOV EDX,[LOCAL.4] ; EDX=密码
0042FAFB |. 8B45 F4 MOV EAX,[LOCAL.3] ; CW-4018-CRACKED
0042FAFE |. E8 F93EFDFF CALL Acid_bur.004039FC ; 比较密码和计算出来的值
0042FB03 |. 75 1A JNZ SHORT Acid_bur.0042FB1F
可以发现从用户名到密码出现中间有很多CALL,能F8
坚决不F7
,不可能每个CALL都进去看的,如果你见CALL就进,大概率分析一会你都不知道你在哪了,想一想是不是呢?
分析方法总结:
- 观察参数,注意参数传入传出的变化,一旦有变化,地址?ASCII码?Unicode?每种格式都看一看,实在看不出来(可能确实没有意义,可能没有联想到一些东西,不要紧打个?看看别的例如返回值或者继续往下走)不可能全部看懂的(我说的是我,大佬除外)
- 观察返回值,方法同上
多走几次就会发现,CW和CRACKED是固定不变的,我唯一变得就是中间的数字(如4018)关键函数很明显了对不对,搞明白它是怎么计算的就可以完美破解了
0042FABD |. FF75 FC PUSH [LOCAL.1] ; CW
0042FAC0 |. 68 C8FB4200 PUSH Acid_bur.0042FBC8 ; 看了下可能是个字符串
0042FAC5 |. 8D55 E8 LEA EDX,[LOCAL.6] ; edx=local6=0
0042FAC8 |. A1 50174300 MOV EAX,DWORD PTR DS:[0x431750] ; 咦?这不是我们的首字母加密值吗?
0042FACD |. E8 466CFDFF CALL Acid_bur.00406718 ; Local6=4018
仔细分析,首字母加密值0xFB2换为10进制就是4018,一开始犯错误见CALL就进,分析了老半天,搞了半天才发现其实这个CALL也不用分析的,我们一开始就算出来了
00406718 /$ 83C4 F8 ADD ESP,-0x8
0040671B |. 6A 00 PUSH 0x0
0040671D |. 894424 04 MOV DWORD PTR SS:[ESP+0x4],EAX ; 首字母加密值
00406721 |. C64424 08 00 MOV BYTE PTR SS:[ESP+0x8],0x0
00406726 |. 8D4C24 04 LEA ECX,DWORD PTR SS:[ESP+0x4] ; ecx=首字母加密值
0040672A |. 8BC2 MOV EAX,EDX ; local6=0
0040672C |. BA 44674000 MOV EDX,Acid_bur.00406744 ; %d
00406731 |. E8 8A080000 CALL Acid_bur.00406FC0 ;一开始我跟进去了,哈哈,不用进去
00406736 |. 59 POP ECX
00406737 |. 5A POP EDX
00406738 \. C3 RETN
注册机算法
总结:根据用户名首字母算出一个数,在获得两个固定字符串CW和CRACKED,拼接起来,密码格式:CW-用户名首字母加密值(十进制)-CRACKED
我们用C/C++代码还原一下
int mian()
{
printf("输入用户名(不能小于4位):\r\n");
// 取第一个字符值
int cName = getchar();
cName *= 0x29*2;
printf("密码: CW-%4d-CRACKED\r\n",cName);
system("pause");
return 0;
}
到此第一部分就算破解完成了。
第二部分
我们开始第二个,先随意输入一个序列号:123456,弹出对话框:Try Again!!
老方法搜索字符串下断点,OD提示我们字符串在数据区,下内存访问断点,重新运行OD,停下后查看调用堆栈
看了下这里调用MessageBox弹出提示信息,说明已经判断完了,这应该不是我们要找的地方,在两个地方下断点后重新运行,继续看调用堆栈,找上一层
我们发现调用只有一处地方,应该就是我们要找的地方了,看一看
JNZ很关键,在它上面的CALL下断点(这个CALL应该就是验证函数)
参数很明显可以看到真正的序列号,试了试,确实是真的
往上翻了翻,F8走一遍,序列号是固定的就是:Hello Dude!