pk8900 发表于 2017-12-9 09:35

[反汇编练习] 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;
}
附个成功注册的截图图:

  欢迎大家回贴交流,共同探讨。

dice1412 发表于 2017-12-9 10:39

支持楼主

hmf505 发表于 2017-12-9 20:16

利害{:1_893:},加油

s3nake 发表于 2017-12-9 23:29

学习了,谢谢分享

Halry 发表于 2017-12-10 11:57

学习了,谢谢分享

linclon 发表于 2017-12-10 15:14

厉害厉害,感谢楼主分享

qvbzyx 发表于 2017-12-10 15:44

学习的乐趣,就是不停的进步{:1_893:}

newchange452pj 发表于 2017-12-10 17:16

学习了,谢谢分享

电脑疯了 发表于 2017-12-12 14:19

膩害,學習了。。

LUGY! 发表于 2018-3-14 21:41

学到了谢谢 加油
页: [1] 2
查看完整版本: [反汇编练习] 160个CrackMe之034(Cruehead.3.exe)算法分析及注册机编写