160个CrackMe练手之024
本帖最后由 Double_Z 于 2015-5-28 21:28 编辑最近一直在练习160CrackMe,也积累了一些东西,一直没时间发,今天可能刚好有些时间就找一些可以发的东西发一下。
这里有 44018723 发的相同的一篇帖子:可以参考一下 重复的内容我就不发了:http://www.52pojie.cn/thread-268511-1-1.html
首先找到关键代码区,查找字符串,或者右键->查找->所有程序间的调用 找到编辑框相关断点下断 运行即可。
00401255 > \3B05 58314000 cmp eax, dword ptr
0040125B .74 0C je short 00401269
0040125D .3B05 54314000 cmp eax, dword ptr
00401263 .0F85 AE000000 jnz 00401317
00401269 >C705 D9124000>mov dword ptr , 0x584554
00401273 .6A 00 push 0x0 ; /IsSigned = FALSE
00401275 .8D45 FC lea eax, dword ptr ; |
00401278 .50 push eax ; |pSuccess
00401279 .6A 64 push 0x64 ; |ControlID = 64 (100.)
0040127B .FF35 50314000 push dword ptr ; |hWnd = NULL
00401281 .E8 BC010000 call <jmp.&USER32.GetDlgItemInt> ; \GetDlgItemInt
00401286 .837D FC 00 cmp dword ptr , 0x0
0040128A 74 5F je short 004012EB
0040128C .50 push eax ;在此处下F2断点
0040128D .6A 14 push 0x14 ; /Count = 14 (20.)
0040128F .68 6C314000 push 0040316C ; |用户名
00401294 .FF35 54314000 push dword ptr ; |hWnd = NULL
0040129A .E8 AF010000 call <jmp.&USER32.GetWindowTextA> ; \GetWindowTextA
0040129F .85C0 test eax, eax ;eax = length用户名长度
004012A1 .74 48 je short 004012EB
004012A3 .A1 0B304000 mov eax, dword ptr ;1480938563 0x58455443
004012A8 .BB 6C314000 mov ebx, 0040316C ;用户名
004012AD >0303 add eax, dword ptr ;+*((int*)Name)
004012AF .43 inc ebx
004012B0 .81FB 7C314000 cmp ebx, 0040317C ;循环 16次
004012B6 .^ 75 F5 jnz short 004012AD
004012B8 .5B pop ebx
004012B9 .03C3 add eax, ebx ;eax = 用户名累加 ebx = serial 注册码
004012BB .3105 D9124000 xor dword ptr , eax ;00584554 ^ eax 对下面的代码进行改写
004012C1 .C1E8 10 shr eax, 0x10 ;右移 16位
004012C4 .66:2905 D9124>sub word ptr , ax ;0x4012d9 - ax
004012CB .BE EC114000 mov esi, 004011EC
004012D0 .B9 3E000000 mov ecx, 0x3E
004012D5 .33DB xor ebx, ebx
004012D7 .EB 04 jmp short 004012DD
004012D9 54 push esp ;改写位置
004012DA 45 inc ebp
004012DB 58 pop eax ;改写位置
004012DC 00AD 33D84975 add byte ptr , ch ;改写位置
004012E2 ?FA cli
004012E3 .81FB FBCFFCAF cmp ebx, 0xAFFCCFFB
004012E9 .^ 74 EE je short 004012D9
004012EB >68 59304000 push 00403059 ; /Your serial is not valid.
004012F0 .FF35 5C314000 push dword ptr ; |hWnd = NULL
004012F6 .E8 7D010000 call <jmp.&USER32.SetWindowTextA> ; \SetWindowTextA
004012FB .33C0 xor eax, eax
004012FD .C9 leave ;(Initial CPU selection)
004012FE .C2 1000 retn 0x10
00401301 .68 73 30 40 0>ascii "hs0@",0 ;YES! You found your serial!!
00401306 .FF35 5C314000 push dword ptr ; |hWnd = NULL
0040130C .E8 67010000 call <jmp.&USER32.SetWindowTextA> ; \SetWindowTextA
00401311 .33C0 xor eax, eax
00401313 .C9 leave
00401314 .C2 1000 retn 0x10
这段代码就是将要分析的代码 ,很短,算法也很简单。
首先是 这段代码:
0040128C .50 push eax ;在此处下F2断点
0040128D .6A 14 push 0x14 ; /Count = 14 (20.)
0040128F .68 6C314000 push 0040316C ; |用户名
00401294 .FF35 54314000 push dword ptr ; |hWnd = NULL
0040129A .E8 AF010000 call <jmp.&USER32.GetWindowTextA> ; \GetWindowTextA
0040129F .85C0 test eax, eax ;eax = length用户名长度
004012A1 .74 48 je short 004012EB
004012A3 .A1 0B304000 mov eax, dword ptr ;1480938563 0x58455443
004012A8 .BB 6C314000 mov ebx, 0040316C ;用户名
004012AD >0303 add eax, dword ptr ;+*((int*)Name)
004012AF .43 inc ebx
004012B0 .81FB 7C314000 cmp ebx, 0040317C ;循环 16次
004012B6 .^ 75 F5 jnz short 004012AD
004012B8 .5B pop ebx
004012B9 .03C3 add eax, ebx ;eax = 用户名累加 ebx = serial 注册码
分析一下,大体意思就是这样滴:
#include "stdio.h"
#include "string.h"
#include "stdlib.h"
main()
{
char strUsername="doubleZ"; // 用户名
int serial= 12345678; // 注册码
int *pSerial=&serial;
char *pName = strUsername;
int EAX = 0x58455443; //dword ptr
int i=0;
int nLen=strlen(strUsername);
for( i=nLen;i<20;i++) //字符串后续地址清零
{
*(pName+i)=0;
}
for( i=0;i<16;i++)
{
EAX += *((int*)pName); // add eax, dword ptr
pName++;
}
EAX +=serial; //add eax, ebx
printf("EAX:%X\n",EAX);
system("pause");
}
不好意思,我用 DEV C++ 写的,代码比较粗糙。可以用OD调试下程序,看看结果是否正确:运行程序,然后输入用户名,注册码(这个要和C代码中的一致)
然后,找到刚才的代码区,下断点,点击注册框即可断下。
0040128A /74 5F je short 004012EB
0040128C . |50 push eax ;在此处下F2断点
F8 单步运行,也可直接F4 到此处:
004012B8 .5B pop ebx
004012B9 .03C3 add eax, ebx ;eax = 用户名累加 ebx = serial 注册码
查看EAX的值是否与自己写的代码得出的结果是否相同。
然后分析一下下面的代码
004012BB .3105 D9124000 xor dword ptr , eax ;00584554 ^ eax 对下面的代码进行改写
004012C1 .C1E8 10 shr eax, 0x10 ;右移 16位
004012C4 .66:2905 D9124>sub word ptr , ax ;0x4012d9 - ax
004012CB .BE EC114000 mov esi, 004011EC
004012D0 .B9 3E000000 mov ecx, 0x3E
004012D5 .33DB xor ebx, ebx
004012D7 .EB 04 jmp short 004012DD
第一句 004012BB . xor dword ptr , eax ; 其中eax 为前面计算的那个数,0x4012d9 dword 是什么呢,多调试几次你就突然发现下面的代码会莫名的变红。对
其实就是下面的代码区(jmp 后的四个字节)。那为什么到对这几个字节更改呢?看下面
第二 三句 004012C1 .C1E8 10 shr eax, 0x10 ;右移 16位
004012C4 .66:2905 D9124>sub word ptr , ax ;0x4012d9 - ax
同样是对0x4012d9 位置的地址进行更改,且是根据用户名和注册码计算出的码进行更改,那猜一下,如果是你,你会改成什么?现在还不知道,待会再猜。
在后面的几句是为JMP后面的循环清零;
看看JMP后面是什么?
004012DD AD lods dword ptr ;JMP跳到此处
004012DE 33D8 xor ebx, eax
004012E0 .49 dec ecx
004012E1 .^ 75 FA jnz short 004012DD
004012E3 .81FB FBCFFCAF cmp ebx, 0xAFFCCFFB
004012E9 .^ 74 EE je short 004012D9
这段程序的意思就是从地址004011EC 到 004011EC+0x3E(注意:这个区间包括0x4012d9这个位置,这点很重要) 取值,然后依次异或,最后的计算结果和0xAFFCCFFB比较,相等则跳转。如果不跳转,下面就是显示注册失败的提示,那么这里就是所谓的关键跳了。那么如果相等的话,那会跳转到哪里呢?按照正常的思路,应该是是提示注册成功的那个位置。
也就是这里:
00401301 .68 73 30 40 0>ascii "hs0@",0 ;YES! You found your serial!!
00401306 .FF35 5C314000 push dword ptr ; |hWnd = NULL
0040130C .E8 67010000 call <jmp.&USER32.SetWindowTextA> ; \SetWindowTextA
00401311 .33C0 xor eax, eax
00401313 .C9 leave
00401314 .C2 1000 retn 0x10
但是,他没有跳到这里(爆破的话可以改跳到这里),而是跳到 0x4012d9这个位置,这个位置的代码是根据咱输入的用户名和注册码计算出来的,所以这里的代码也和咱的
注册码和用户名一样是错的。如何强制改下跳转,你会发现程序会死掉滴。
那么,假若,我们输入的是正确的用户名和注册码,那么0x4012d9 位置的代码是什么? 废话,当然是跳到提示注册成功的哪里喽!
我们将 0x4012d9 出的代码改一下,改成这样。若是这样的话,那么若通过注册码检验,则跳到这里,然后在跳到提示成功!
004012D9 /EB 26 jmp short 00401301 ;改写位置
004012DB |90 nop ;改写位置
004012DC |90 nop ;改写位置
这里有两个关键字节,即EB 26 (jmp short 00401301)。那如何验证我们的猜想呢?往下看。
我们先总结一下作者的思路:首先对用户名和注册码进行一系列的计算,得出Number1,
然后通Number1对0x4012d9 进行异或,加减一系列的操作,总之就是要改变dword
这个数。改变这个数的目的,是为了改变0x4012d9处的运行代码(我们猜测含有EB 26 这两个字节)。
最后,对地址004011EC 到 004011EC+0x3E的Dword读取的数进行异或运算,判断是否符合要求。
我们发现地址004011EC 到 004011EC+0x3E 只有两个数是会改变的,即0x4012d8 和 0x4012dc 中的 前者的后三个字节,后者的第一个字节,
这正是被改写的0x4012d9 的 四个字节。 所以最后的验证是验证0x4012d9 是否被改写正确。
如果我们得出0x4012d9的值,那么我们就所有的问题就解决了。那如何得出0x4012d9 处的代码后者说是值呢?
我们在来看一下最后的验证的代码:
004012DD AD lods dword ptr ;JMP跳到此处
004012DE 33D8 xor ebx, eax
004012E0 .49 dec ecx
004012E1 .^ 75 FA jnz short 004012DD
004012E3 .81FB FBCFFCAF cmp ebx, 0xAFFCCFFB
004012E9 .^ 74 EE je short 004012D9
lods dword ptr 可以这样理解:mov eax,dword ptr; add esi,4;
即不断取值,然后异或,最后和0xAFFCCFFB 比较相同即可;
即 可以转换为
A xor B xor C ocr D ...... xor E ?= 0xAFFCCFFB ,其中有有两个 F 和 G (F G的一部分)是未知的。那如何求解F,G;
这里有关异或的资料可以参考:http://www.cnblogs.com/suoloveyou/archive/2012/04/25/2470292.html
看完资料我们会发现 A xor B= C ; 则B = A xor C ;
好了,不废话了,说一下我的思路吧:
我们先计算 0x004012d8 处的值 因为这个值有三个字节是未知的,
把 0x004012d8 之前的 A xor D xor C xor D .... 计算出的值看成一个数 M 后面的看成 N
则 M xor dword xor N = 0xAFFCCFFB ;
dword = 0xAFFCCFFB xor M xor N ;
那我们如何计算 M 和 N 呢?
嗯,又是一个问题,怎么办呢?我们用现成的工具 ——OD;
再次运行,断下,单步到此处(或F4);
004012DD AD lods dword ptr ;JMP跳到此处
004012DE 33D8 xor ebx, eax
004012E0 .49 dec ecx
004012E1 .^ 75 FA jnz short 004012DD
然后调试->设置条件->选择条件为真 填写 esi == 004012d8(注意 ==) 然后跟踪步入,你会发现esi为004012d8,
然后F8一步,此时把ebx的值记下 M =0xA213FC72.
然后F8 单步走到达 xor ebx,ebx 处 此时 eax 为 0x004012d8的值,此时把eax和ebx中的值改为零,当然你也可以只改eax中的值。这里将0x4012dc 中最后一个字节改为00 即D833AD00,
然后在F8,注意jne是否向上跳,不跳的时候把 ebx 记下 N = 0x59C9D849;
则 dword 0x4012d8 =0xAFFCCFFB xor M xor N ;
计算看下面:
#include "stdio.h"
#include "stdlib.h"
main()
{
int b = 0xAFFCCFFB ^ 0x59C9D849 ^ 0xA213FC72;//b=0x5426EBC0
printf("%X",b);
system("pause");
}
0x5426EBC0,嗯,果然,有 eb 26 这两个字节,但是因为 0x4012d8 第一个字节是知道的为04
故0x4012d8 = 0x5426EB04;
然后用同样的方法计算出0x4012dc的值,跟踪 到 esi == 0x4012d8,然后F8 等到 0x004012d8 的数值载入到eax 时将eax 改为0x5426EB04这个值
然后 f8,我们用另一个方法,再次f8 到载入 0x4012dc 的值到eax 后,将eax 改为 0,然后f8直到循环结束,记下ebx 的值 S =0x77CF623F
0x4012dc=0xAFFCCFFB xor S=0xD833ADC4; 明显和原来的数只差一个字节;
0x4012d9 = C45426EB;;//得到这个数的方法很多,同学们可以总结下。
然后我们向上分析:
004012BB .3105 D9124000 xor dword ptr , eax ;00584554 ^ eax 对下面的代码进行改写
004012C1 .C1E8 10 shr eax, 0x10 ;右移 16位
004012C4 .66:2905 D9124>sub word ptr , ax ;0x4012d9 - ax
很简单: 0x00584554 xor C - C >> 0x10=0xC41926eb ; 问题又来了,我们如何计算C?
我们转换一下:
0x00584554 xor C = C41926eb + C>>0x10;
方法很笨,但很有效:
#include "stdio.h"
#include "string.h"
#include "stdlib.h"
main()
{
unsigned int i=0x0;
unsigned int j=0x0;
unsigned int k=0x0;
for( i=0x1;i<=0xFFFFFFFE;i++)
{
j = 0x00584554^i;
k = 0xC45426EB + (i>>0x10);
if(j==k) printf("%X\t%X\t%X\n",i,j,k);
}
//0xC40CAFA3 Number1
//0xC45426EB
system("pause");
}
最终得到 C = 0xC40CAFA3;就是前面我们所说的用用户名和注册码得出的Number1;
最后用户名到注册码的算法前面分析过了,很简单,我把代码放下:
#include "stdio.h"
#include "string.h"
#include "stdlib.h"
main()
{
char strUsername="doubleZ";
intserial= 0;
int *pSerial=&serial;
char *pName = strUsername;
int i=0;
int nLen=strlen(strUsername);
for( i=nLen;i<20;i++)
{
*(pName+i)=0;
}
serial =0x58455443;
for( i=0;i<16;i++)
{
serial += *((int*)pName);
pName++;
}
serial = 0xC40CAFA3-serial;
printf("Name:%s\n",strUsername);
printf("Serial:%u\n",serial);
//Name:doubleZ
//Serial:3703760779
system("pause");
}
最后得出一组用户名和注册码Name:doubleZ ,Serial:3703760779。但是这个注册码只能在调试时通过,正常情况下不能显示成功,这个好像程序设计时没处理好,
嗯,就这样吧,反正我们的最终的目的达到了——了解CrackMe作者的思路。
这是我第一次发帖,水平有限,莫怪。好吧,我把文件放下赚点外快。
Double_Z 发表于 2015-6-19 09:59
嗯,我知道了,这点我没想到。明天有空我把帖子在改一下,省的误导大家。我现在在上班,没有工具。用硬件 ...
不用调试,直接把11EC开始地址开始的 0x3E*4个字节的数据dump出来,分成四部分进行异或就可以了:
xxxxxxxx ^ xxxxxx04 ^ D833ADxx ^ 81FA7549 = AFFCCFFB
最前面的xxxxxxxx可以根据dump出来的数据异或出来, 中间的两个用你的方法就可以计算出来 Pnmker 发表于 2015-6-19 00:40
程序没有问题,非调试状态下以下两组用户名验证通过
pnmker 3155544019
doubleZ 1891791755
嗯,我知道了,这点我没想到。明天有空我把帖子在改一下,省的误导大家。我现在在上班,没有工具。用硬件断点调试可以吗?
好资源,已收藏了 Double_Z分析的很详细了,加精鼓励,期待更多分享,@44018723 带大家一起走入crackme系列,应该搞成一个教程合集,收录到板块推荐贴里。 好东西,谢谢了哦。 这个很详细 我有空也去看看吧 支持学习 收下 有时间多学学 我很纳闷,原创的精品评分这么少,什么音乐电影啥的评分反而那么多,,,郁闷 收藏了,感谢分享!{:301_1003:} 好文章 学习了 谢谢楼主 思路很好 继续关注你