本帖最后由 pk8900 于 2017-12-15 13:22 编辑
昨天晚上,继续研究 了【适合破解新手的160个crackme练手】,本文内容为第47个CrackMe: DueList.2.exe,论坛里搜索了一下关于这个CrackMe的帖子,没找到,所以写了这篇帖子,分享一下我的分析过程及注册机编写方法。
【crackme简介】
下载地址:http://pan.baidu.com/share/link?shareid=541269&uk=4146939145
MASM32 / TASM32编写,无壳,是一个Keyfile验证方式,打开后只有一个提示框,大意是说:你的试用时间已到期,请把KEY文件放到目录下进行注册。
分析工具:X64dbg,OD
这个CRACKME和034有些像,可以参照帖子:https://www.52pojie.cn/thread-673404-1-1.html
【crackme截图】
【算法分析过程】
关键代码位置如下:
[Asm] 纯文本查看 复制代码 00401057 . A3 AF214000 mov dword ptr ds:[0x4021AF],eax ; |kernel32.BaseThreadInitThunk
0040105C . 6A 00 push 0x0 ; |/hTemplateFile = NULL
0040105E . 68 6F214000 push DueList_.0040216F ; ||Attributes = READONLY|HIDDEN|SYSTEM|ARCHIVE|TEMPORARY|402048
00401063 . 6A 03 push 0x3 ; ||Mode = OPEN_EXISTING
00401065 . 6A 00 push 0x0 ; ||pSecurity = NULL
00401067 . 6A 03 push 0x3 ; ||ShareMode = FILE_SHARE_READ|FILE_SHARE_WRITE
00401069 . 68 000000C0 push 0xC0000000 ; ||Access = GENERIC_READ|GENERIC_WRITE
0040106E . 68 79204000 push DueList_.00402079 ; ||FileName = "due-cm2.dat"
00401073 . E8 0B020000 call <jmp.&KERNEL32.CreateFileA> ; |\CreateFileA
00401078 . 83F8 FF cmp eax,-0x1 ; |
0040107B . 75 1D jnz short DueList_.0040109A ; |
0040107D . 6A 00 push 0x0 ; |/Style = MB_OK|MB_APPLMODAL
0040107F . 68 01204000 push DueList_.00402001 ; ||Title = "Duelist's Crackme #2"
00401084 . 68 17204000 push DueList_.00402017 ; ||Text = "Your time-trial has ended... Please register and copy the keyfile sent to you to this directory!"
00401089 . 6A 00 push 0x0 ; ||hOwner = NULL
0040108B . E8 D7020000 call <jmp.&USER32.MessageBoxA> ; |\MessageBoxA
00401090 . E8 24020000 call <jmp.&KERNEL32.ExitProcess> ; \ExitProcess
00401095 . E9 28010000 jmp DueList_.004011C2
0040109A > 6A 00 push 0x0 ; /pOverlapped = NULL
0040109C . 68 73214000 push DueList_.00402173 ; |pBytesRead = DueList_.00402173
004010A1 . 6A 46 push 0x46 ; |BytesToRead = 46 (70.)
004010A3 . 68 1A214000 push DueList_.0040211A ; |Buffer = DueList_.0040211A
004010A8 . 50 push eax ; |hFile = 75753358
004010A9 . E8 2F020000 call <jmp.&KERNEL32.ReadFile> ; \ReadFile
004010AE . 85C0 test eax,eax ; kernel32.BaseThreadInitThunk
004010B0 . 75 02 jnz short DueList_.004010B4
004010B2 . EB 43 jmp short DueList_.004010F7
004010B4 > 33DB xor ebx,ebx
004010B6 . 33F6 xor esi,esi
004010B8 . 833D 73214000>cmp dword ptr ds:[0x402173],0x12 ; 读取长度小于 0x12 失败
004010BF . 7C 36 jl short DueList_.004010F7
004010C1 > 8A83 1A214000 mov al,byte ptr ds:[ebx+0x40211A] ;第一段代码
004010C7 . 3C 00 cmp al,0x0 ; key[N]为零结循环
004010C9 . 74 08 je short DueList_.004010D3
004010CB . 3C 01 cmp al,0x1
004010CD . 75 01 jnz short DueList_.004010D0 ; key[N]等于0x01则累加ESI计数
004010CF . 46 inc esi
004010D0 > 43 inc ebx
004010D1 .^ EB EE jmp short DueList_.004010C1
004010D3 > 83FE 02 cmp esi,0x2 ; 第一条件:至少有两个0x01
004010D6 . 7C 1F jl short DueList_.004010F7
以上代码部分分析结果如下:
1、CreateFileA参数Mode = OPEN_EXISTING,打开已存在的文件,若文件不存在,函数返回-1,是用来验证Key文件是否存在的。
2、ReadFile函数读取文件内容,参数pBytesRead = Cruehead.004021A0是读取文件返回的字节数,接下来一句代码cmp dword ptr ds:[0x402173],0x12,如果读取的字节数少于0x12(18)个,则跳至失败处,BytesToRead = 46 (70.),预读取字节为70个。
3、FileName = "due-cm2.dat",所以我们在CrackMe目录下新建一个文件due-cm2.dat,并写入18个字节以上的数据,内容随意。
再次载入程序,在ReadFile后代码下断点,进行分析。读入的字节我们分别用Key[0]~Key[n]来表示,第一段循环代码,从Key[0]开始,一直遇到\0空字符结束,统计出Key[0]~Key[n]中0x01字节的个数,最少应为2个,少于2个则跳至KEY文件无效提示。
[Asm] 纯文本查看 复制代码 004010DA . 33DB xor ebx,ebx ; 第二段代码循环
004010DC > 8A83 1A214000 mov al,byte ptr ds:[ebx+0x40211A]
004010E2 . 3C 00 cmp al,0x0
004010E4 . 74 09 je short DueList_.004010EF
004010E6 . 3C 01 cmp al,0x1
004010E8 . 74 05 je short DueList_.004010EF
004010EA . 03F0 add esi,eax ; kernel32.BaseThreadInitThunk
004010EC . 43 inc ebx
004010ED .^ EB ED jmp short DueList_.004010DC
004010EF > 81FE D5010000 cmp esi,0x1D5 ; 第一个01前,累加值为0x1D5
004010F5 . 74 1D je short DueList_.00401114
上面的第二段代码循环是:
从Key[0]开始,一直到第一个0x01字节,累加各字节值为0x1D5,不含0x01字节,则成功。
[Asm] 纯文本查看 复制代码 00401114 > \33F6 xor esi,esi ; 第三段代码
00401116 > 43 inc ebx
00401117 . 8A83 1A214000 mov al,byte ptr ds:[ebx+0x40211A]
0040111D . 3C 00 cmp al,0x0
0040111F . 74 18 je short DueList_.00401139
00401121 . 3C 01 cmp al,0x1
00401123 . 74 14 je short DueList_.00401139
00401125 . 83FE 0F cmp esi,0xF ; >=0xF个字符结束
00401128 . 73 0F jnb short DueList_.00401139
0040112A . 3286 1A214000 xor al,byte ptr ds:[esi+0x40211A] ; 与第一字答异或,依次存入402160地址
00401130 . 8986 60214000 mov dword ptr ds:[esi+0x402160],eax ; kernel32.BaseThreadInitThunk
00401136 . 46 inc esi
00401137 .^ EB DD jmp short DueList_.00401116
00401139 > 43 inc ebx ; 遇到01或到第0xf个字符结束
0040113A . 33F6 xor esi,esi
上面的第三段代码循环是:
从第一个0x01字节后的字节开始,分别与Key[0]~Key[N]异或,异或结果存入402160地址,一直到第二个0x01字节或到Key[15]结束,同样不含最后的那个字节,402160地址异或后的字节将成为最终注册成功显示的用户名。
[Asm] 纯文本查看 复制代码 0040113A . 33F6 xor esi,esi ; 第四部分循环代码
0040113C > 8A83 1A214000 mov al,byte ptr ds:[ebx+0x40211A]
00401142 . 3C 00 cmp al,0x0
00401144 . 74 09 je short DueList_.0040114F
00401146 . 3C 01 cmp al,0x1
00401148 .^ 74 F2 je short DueList_.0040113C
0040114A . 03F0 add esi,eax ; kernel32.BaseThreadInitThunk
0040114C . 43 inc ebx
0040114D .^ EB ED jmp short DueList_.0040113C
0040114F > 81FE B2010000 cmp esi,0x1B2 ; 到00字符前累加值为0x1B2,不累加0x01
00401155 .^ 75 A0 jnz short DueList_.004010F7
上面的第四段代码循环是:累加第三部分之后的字节值,一直到0x00字节,其中的0x01字节不累加在内,若累加值为0x1B2则成功验证完成,在文本框内显示402160地址的用户名,上面有已注册的英文提示。
【注册机编写】
第一个0x01前的累加值为:1D5,所以前三个字节可以为0xEA,0xEB,0x01,这样从第四个开始可以留给用户使用,一直到第16个,所以能注册的用户长度最长为13个字符。
用户名部分将想要注册显示的用户名依次序从Key[0]开始异或,存入文件中。后面的部分就比较简单了,长度不限,只要到空字符之前的累加值为0x1B2,并写入第二个0x01就行。
C++代码如下:
[C++] 纯文本查看 复制代码 #define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include <windows.h>
using namespace std;
int main()
{
char * path = "E:\\crackme\\due-cm2.dat";
FILE * keyfile;
char * youkey = new char[40]; //输入用户名
memset(youkey, 0, 40);
unsigned char * writekey = new unsigned char[40];
memset(writekey, 0, 40); //存入文件的内容
cout << "Enter your name:";
gets(youkey);
if (strlen(youkey) >= 8)
cout << "用户名最多支持13位,其余部分将被截断!" << endl;
int now = 0;
writekey[0] = 0xEA;
writekey[1] = 0xEB;
writekey[2] = 0x01;
for (unsigned int x = 0; x < 13 && x<strlen(youkey); x++)
{
writekey[x+3] = youkey[x]^writekey[x];
}
if (strlen(youkey) >= 13)
now = 16;
else
now = strlen(youkey) + 3;
writekey[now] = 0x01;
writekey[now+1] = 0xD9;
writekey[now+2] = 0xD9;
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(writekey, sizeof(byte), 40, keyfile);
fclose(keyfile);
cout << "write file over!" << endl;
delete[] youkey;
delete[] writekey;
system("pause");
return 1;
}
附注册成功后截图:
说明:后来想了一下,上面的注册机中异或后的用户名字符部分有可能含有0x01字符,这部分未加入判断,失败也是有概率的。
分析的对不对,欢迎大家回贴交流。 |