[反汇编练习] 160个CrackMe之034(Cruehead.3.exe)算法分析及注册机编写
本帖最后由 pk8900 于 2017-12-9 09:49 编辑昨天晚上,继续研究 了【适合破解新手的160个crackme练手】,本文内容为第34个CrackMe: Cruehead.3.exe,有些收获,分析并编写注册机后,在论坛里搜索了一下关于这个CrackMe的帖子进行参照,找到了两篇,有一篇未分析出算法,有一篇已基本将算法分析出来,但注册机编写的不是很完美,参考地址:https://www.52pojie.cn/thread-371674-1-1.html,所以写了这篇帖子,分享一下我的分析过程及注册机编写方法。
【crackme简介】
下载地址:http://pan.baidu.com/share/link?shareid=541269&uk=4146939145
MASM32 / TASM32编写,无壳,是一个Keyfile验证方式,界面打开窗口空白,file菜单只有一个exit,help菜单也只有一个About说明。
分析工具:X64dbg,OD,(因X64DBG不能自动注释API函数调用参数,所以和OD结合使用)
【crackme截图】
【算法分析过程】
因为在下载crackme的时候,看见后面标注的验证方式是Keyfile,所以分析起来就省了不少事,直接搜索字符串,可以搜索到“CRACKME3.KEY”,明显的Key文件,同时还有"Good work cracker!"及"Cracked by: Now try the next crackme!"字符串,估计是成功后标志。直接双击“CRACKME3.KEY”来到代码处:
0040100C|.C705 F9204000>mov dword ptr ds:,0x0
00401016|.6A 00 push 0x0 ; /hTemplateFile = NULL
00401018|.68 80000000 push 0x80 ; |Attributes = NORMAL
0040101D|.6A 03 push 0x3 ; |Mode = OPEN_EXISTING
0040101F|.6A 00 push 0x0 ; |pSecurity = NULL
00401021|.6A 03 push 0x3 ; |ShareMode = FILE_SHARE_READ|FILE_SHARE_WRITE
00401023|.68 000000C0 push 0xC0000000 ; |Access = GENERIC_READ|GENERIC_WRITE
00401028|.68 D7204000 push Cruehead.004020D7 ; |FileName = "CRACKME3.KEY"
0040102D|.E8 76040000 call <jmp.&KERNEL32.CreateFileA> ; \CreateFileA
00401032|.83F8 FF cmp eax,-0x1
00401035|.75 0C jnz short Cruehead.00401043 ;文件不存在跳走
00401037|>68 0E214000 push Cruehead.0040210E ;ASCII "CrackMe v3.0 "
0040103C|.E8 B4020000 call Cruehead.004012F5
00401041|.EB 6B jmp short Cruehead.004010AE
00401043|>A3 F5204000 mov dword ptr ds:,eax ;kernel32.BaseThreadInitThunk
00401048|.B8 12000000 mov eax,0x12
0040104D|.BB 08204000 mov ebx,Cruehead.00402008
00401052|.6A 00 push 0x0 ; /pOverlapped = NULL
00401054|.68 A0214000 push Cruehead.004021A0 ; |pBytesRead = Cruehead.004021A0
00401059|.50 push eax ; |BytesToRead = 76753358 (1987392344.)
0040105A|.53 push ebx ; |Buffer = 7EFDE000
0040105B|.FF35 F5204000 push dword ptr ds: ; |hFile = NULL
00401061|.E8 30040000 call <jmp.&KERNEL32.ReadFile> ; \ReadFile
00401066|.833D A0214000>cmp dword ptr ds:,0x12 ;读取字节数不为0x12(18)字节,跳走
0040106D|.^ 75 C8 jnz short Cruehead.00401037
0040106F|.68 08204000 push Cruehead.00402008
00401074|.E8 98020000 call Cruehead.00401311
00401079|.8135 F9204000>xor dword ptr ds:,0x12345678
分析此块代码:
1、CreateFileA参数Mode = OPEN_EXISTING,也就是打开已存在的文件,若文件不存在,函数返回-1,是用来验证Key文件是否存在的。
2、ReadFile函数读取文件内容,参数pBytesRead = Cruehead.004021A0是读取文件返回的字节数,接下来一句代码cmp dword ptr ds:[0x4021A0],0x12,如果读取的字节数少于0x12(18)个,则跳至失败处。
于是我们在CrackMe目录下新建一个文件CRACKME3.KEY,并写入不少于18个字节的数据,内容随意。
再次载入程序,在ReadFile后代码进行跟踪。
00401066|.833D A0214000>cmp dword ptr ds:,0x12 ;读取字节数不为0x12(18)字节,跳走
0040106D|.^ 75 C8 jnz short Cruehead.00401037
0040106F|.68 08204000 push Cruehead.00402008 ;123123123123111111
00401074|.E8 98020000 call Cruehead.00401311 ;子程序1
00401079|.8135 F9204000>xor dword ptr ds:,0x12345678 ; XOR 0x12345678
00401083|.83C4 04 add esp,0x4
00401086|.68 08204000 push Cruehead.00402008 ;123123123123111111
0040108B|.E8 AC020000 call Cruehead.0040133C ;子程序2
00401090|.83C4 04 add esp,0x4
00401093|.3B05 F9204000 cmp eax,dword ptr ds: ;比较
00401099|.0F94C0 sete al 比较标志存入AL
0040109C|.50 push eax ;kernel32.BaseThreadInitThunk
0040109D|.84C0 test al,al 测试AL值
0040109F|.^ 74 96 je short Cruehead.00401037 ;跳转
004010A1|.68 0E214000 push Cruehead.0040210E ;CrackMe v3.0- Uncracked
004010A6|.E8 9B020000 call Cruehead.00401346
上面这段代码可以看出,程序先后两次对文件中读取的字符串地址压栈,调用了两个子程序处理,然后是一个cmp比较和一个JE跳转。关键算法就是这里了,先分析子程序1(call Cruehead.00401311)
00401311/$33C9 xor ecx,ecx
00401313|.33C0 xor eax,eax
00401315|.8B7424 04 mov esi,dword ptr ss: ;读入key字符串指针
00401319|.B3 41 mov bl,0x41 ;用于异或解密的字符
0040131B|>8A06 /mov al,byte ptr ds:
0040131D|.32C3 |xor al,bl
0040131F|.8806 |mov byte ptr ds:,al
00401321|.46 |inc esi ;key字符串指针+1
00401322|.FEC3 |inc bl ;异或解密字符+1
00401324|.0105 F9204000 |add dword ptr ds:,eax ;累加解密后字符ASCII值至0x4020F9
0040132A|.3C 00 |cmp al,0x0
0040132C|.74 07 |je short Cruehead.00401335 ;解密后为0(空字符)则结束
0040132E|.FEC1 |inc cl
00401330|.80FB 4F |cmp bl,0x4F ;解密字符累加至4F结束
00401333|.^ 75 E6 \jnz short Cruehead.0040131B
00401335|>890D 49214000 mov dword ptr ds:,ecx
0040133B\.C3 retn
此段代码把从KEY文件中读取的字符串进行异或解密,解密字符从0x41开始,一直到0X4E共14位,中间如果解密出来的字符若是\0(空字符)则结束,(注意:这里在判断字符为\0时,字符与解密字符的异或值已经被累加,存入[0x4020F9]中,因此写注册机的时候要注意判断时机),取出的共18个字节,后4个是不进行解密的。
子程序调用结束返回后,将地址的累加值与0x12345678 异或。
接下来进入子程序2:
0040133C/$8B7424 04 mov esi,dword ptr ss: ;Cruehead.00402008
00401340|.83C6 0E add esi,0xE ;从KEY字符串偏移14
00401343|.8B06 mov eax,dword ptr ds: ;取key字符串后4位
00401345\.C3 retn
子程序2的代码非常简单,就是从读取的Key字符串中取出后4个字节。子程序2返回后,将后4个字节值与子程序1累加并异或的值比较,来确定是否真的注册成功,注册成功后,程序将解密的14字节用户名写入字符串"Cracked by: Now try the next crackme!"中间的14个空格位置,调用信息框显示成功信息。
【注册机编写】
经分析得知,程序读取Key文件中前18个字节得到一个字符串(准确点说应该是字节集),解密前14个字节并累加其ASCII值,遇0结束,也就是正好是我们的用户名部分。所以我们可以自己指定用户名,通过异或加密0x41开始的字符后,写入KEY文件1-14字节,长则截去,短则添0,然后累加ASCII值,后与0x12345678 异或,将这个值写到KEY文件第15-18字节,至此注册机即可实现写KEY文件,完成注册。
注册机C++代码:
#include<iostream>
#include <windows.h>
using namespace std;
int main()
{
char * path = "E:\\crackme\\CRACKME3.KEY";
FILE * keyfile;
char * youkey = new char; //输入用户名
unsigned long retvalue=0; //后4字节检验值
memset(youkey, 0, 260);
cout << "Enter your name:";
gets(youkey);
for (int x = 0; x < 0xE; x++)
{
retvalue += (youkey);
youkey = youkey ^ (0x41 + x);
if ((youkey ^ (0x41 + x ))== 0) //这里要先累加,然后进行判断
break;
}
retvalue ^= 0x12345678;
errno_t err = fopen_s(&keyfile, path, "w+b");
if (err != 0)
{
cout << "file open or create failed!" << endl;
system("pause");
return -1;
}
rewind(keyfile);
fwrite(youkey, sizeof(byte), 0xE, keyfile);
fseek(keyfile, 0xE, 0);
fwrite((char *)&retvalue, sizeof(byte), 4, keyfile);
fclose(keyfile);
cout <<"write file:"<< youkey<< hex << retvalue << endl;
delete[] youkey;
system("pause");
return 1;
}
附个成功注册的截图图:
欢迎大家回贴交流,共同探讨。 支持楼主 利害{:1_893:},加油 学习了,谢谢分享 学习了,谢谢分享 厉害厉害,感谢楼主分享 学习的乐趣,就是不停的进步{:1_893:} 学习了,谢谢分享 膩害,學習了。。 学到了谢谢 加油
页:
[1]
2