160 个 CrackMe 之 091 - Cruehead ,避过陷阱及防护,暴力算出密码

160 个 CrackMe 之 091 - Cruehead 在本坛中已有人研究过,不过其陷入 CrackMe 设置的陷阱,没能发现其真正的密码。
这个 CrackMe 也包含了多种反调试,包括 API 代{过}{滤}理,防 bpx 中断,数据加密,防静态分析,代码动态破坏/恢复,设置陷阱(真假两个密码验证,文件内容验证也没有用)。


同时, CrackMe 还要求在其目录中有一个"crueme.dat"的文件,并且其长度、内容无限制,但在假的密码验证内有进行计算等操作。
启动后,如果没有这个“crueme.dat"文件,会报个提示,并会退出,如下图:


我们用文本编辑工具生成一个这样的文件并保存到CrackMe的目录内即可。


再次启动,就可以正常启动了,界面如下:




我们重新用 OD 载入,来到  OEP,如下图:


可以看到,其第一条指令就是修改内存的值,其实这个值 0x00401493,一看就是一个地址,其实,这个地址就是一个 API 代{过}{滤}理函数的入口地址,这个 代{过}{滤}理函数封装了42个API函数,通过 al = ”索引号“ 的方式来调用这些API函数,因此,静态分析就其本没有可能。


下面就是 CrackMe 开头的代码:
[Asm] 纯文本查看 复制代码

00401000 c> $  C705 64234000 93144000   mov     dword ptr [402364], 00401493    ;  修正 api 代{过}{滤}理 call的函数入口
0040100A    .  68 D9204000              push    004020D9                        ; /pLocaltime = crueme.004020D9
0040100F    .  E8 F20C0000              call    <jmp.&KERNEL32.GetLocalTime>    ; \GetLocalTime
00401014    .  8D1C9D 00000000          lea     ebx, dword ptr [ebx*4]
0040101B    .  D12D 80234000            shr     dword ptr [402380], 1           ;  [addr]== 0x00401224
00401021    .  66:A1 E7204000           mov     ax, word ptr [4020E7]           ;  AX == 0x01AC
00401027    .  A2 63234000              mov     byte ptr [402363], al           ;  [addr]==0xAC,解码密钥
0040102C    .  E8 4A040000              call    0040147B                        ;  解码
00401031    .  83C0 05                  add     eax, 5                          ;  eax = 0x0201
00401034    .  8D0485 00000000          lea     eax, dword ptr [eax*4]
0040103B    .  83C0 05                  add     eax, 5                          ;  eax = 0x0809
0040103E    .  E8 27040000              call    0040146A                        ;  解码字符串:"Win95 File Monitor"
00401043    .  6A 00                    push    0
00401045    .  68 80000000              push    80
0040104A    .  6A 03                    push    3
0040104C    .  6A 00                    push    0
0040104E    .  6A 00                    push    0
00401050    .  68 000000C0              push    C0000000
00401055    .  68 76214000              push    00402176                        ;  文件名:CRUEME.DAT
0040105A    .  B0 1F                    mov     al, 1F                          ;  函数 CreateFileA() 的索引
0040105C    .  FF15 64234000            call    dword ptr [402364]              ;  第1条指令修改了这里的地址,改为 0x00401493,这个调用是一个API入口代{过}{滤}理,AL为索引参数
00401062    .  83F8 FF                  cmp     eax, -1                         ;  eax != -1,表示读取文件成功
00401065    .  75 24                    jnz     short 0040108B
00401067    .  6A 30                    push    30
00401069    .  68 A0214000              push    004021A0                        ;  ASCII "This is the end - My only friend the end!"
0040106E    .  68 CA214000              push    004021CA                        ;  ASCII "AAARGH! Where is my CRUEME.DAT file???",CR,"    I cant go on without my beloved file!"
00401073    .  6A 00                    push    0
00401075    .  B0 1A                    mov     al, 1A                          ;  MessageBoxA()
00401077    .  FF15 64234000            call    dword ptr [402364]              ;  crueme.00401493
0040107D    .  FF35 54204000            push    dword ptr [402054]
00401083    .  B0 0A                    mov     al, 0A                          ;  ExitProcess()
00401085    .  FF15 64234000            call    dword ptr [402364]              ;  crueme.004014930040108B    >  A3 81214000              mov     dword ptr [402181], eax         ;  保存文件句柄

可以看到            call    dword ptr [402364]      就是这个 API 代{过}{滤}理调用,al 为参数,如 al = 0x1F,表示调用 CreateFileA() ,al=0x1A,表示调用 MessageBoxA(),等等,并且该函数内部通过对al 参数进行一定计算,计算的数据有一个是通过查表取得,结合在一起才能确定 API 的入口跳转位置。这个数据表是加密的,通过上面代码中的 call    0040147B 进行解码,解码后才可以用。同时上面代码中的 call 0040146A 解码了一个字符串: "Win95 File Monitor",后面会用 FindWindow()查找这个标题的窗口,防止文件访问监控。
读取文件内容后,会与一组常量进行运算,不过后面真正计算密码时也没有用到这些数据。


F8 往下走,来到这里,如下图:


中间插入了生成对话框应用的代码,就是由这里的代码显示对话框。而附近有很多代码,其实都是没用的废代码,包括生成标准的 Windows 窗口代码,消息循环代码,WndProc代码,都是没用的。
具体代码如下:
[Asm] 纯文本查看 复制代码

00401138    .  6A 00                    push    0
0040113A    .  68 C1114000              push    004011C1                        ;  DlgProc
0040113F    .  6A 00                    push    0
00401141    .  68 E9204000              push    004020E9                        ;  字符串 (ASCII "DLG_START")
00401146    .  FF35 D6234000            push    dword ptr [4023D6]              ;  hInstance
0040114C    .  B0 12                    mov     al, 12                          ;  DialogBoxParamA()
0040114E    .  FF15 64234000            call    dword ptr [402364]              ;  crueme.00401493
00401154    .  EB 64                    jmp     short 004011BA


如下图,上面代码前面的 LoadIconA(), LoadCursorA() 等,就没什么用。




通过前面的代码,我们知道 DlgProc 的地址是 0x004011C1,其主体部分如下图所示:


只处理了 WM_COMMAND 和 WM_CLOSE 消息。具体代码如下:
[Asm] 纯文本查看 复制代码

004011C1   /.  C8 000000                enter   0, 0                            ;  DlgProc
004011C5   |.  53                       push    ebx
004011C6   |.  56                       push    esi
004011C7   |.  57                       push    edi
004011C8   |.  817D 0C 11010000         cmp     dword ptr [ebp+C], 111          ;  WM_COMMAND
004011CF   |.  0F84 2F010000            je      00401304
004011D5   |.  837D 0C 10               cmp     dword ptr [ebp+C], 10           ;  WM_CLOSE
004011D9   |.  0F84 48010000            je      00401327
004011DF   |.  B8 00000000              mov     eax, 0
004011E4   |>  5F                       pop     edi
004011E5   |.  5E                       pop     esi
004011E6   |.  5B                       pop     ebx
004011E7   |.  C9                       leave004011E8   |.  C2 1000                  retn    10

通过跟随            je      00401304 可以找到处理 WM_COMMAND 消息的 Handler。如下图所示:

然后跟随 je 00401213 可以找到按钮”Check It!“的Click事件的 Handler。如下图所示:

具体的代码如下所示:
[Asm] 纯文本查看 复制代码

00401213   |>  BA 1D214000              mov     edx, 0040211D                   ;  保存密码的缓冲区地址
00401218   |.  8D1C85 00000000          lea     ebx, dword ptr [eax*4]          ;  (无用代码)Win9x下eax=ecx=0, WinNT下eax=ecx=DlgProc
0040121F   |.  83C3 05                  add     ebx, 5                          ;  (无用代码)改成 mov ebx, 5
00401222   |.  03DA                     add     ebx, edx                        ;  (无用代码)
00401224   |.  52                       push    edx                             ;  lParam, 缓冲区地址
00401225   |.  6A 0E                    push    0E                              ;  wParam, 可取得的最大字符数,14
00401227   |.  6A 0D                    push    0D                              ;  uMsg = WM_GETTEXT
00401229   |.  68 EA030000              push    3EA                             ;  ControlID. 密码文本框
0040122E   |.  FF75 08                  push    dword ptr [ebp+8]               ;  hInstance, 堆栈 ss:[0019F8D8]=006F0C10
00401231   |.  B0 25                    mov     al, 25                          ;  SendDlgItemMessageA(WM_GETTEXT)
00401233   |.  FF15 64234000            call    dword ptr [402364]              ;  取得注册密码"1234567890", eax为长度
00401239   |.  E8 35090000              call    00401B73                        ;  密码验证,正确返回0,错误返回1
0040123E   |.  50                       push    eax                             ;  保存验证结果
0040123F   |.  BF 01000000              mov     edi, 1
00401244   |.  E8 FD080000              call    00401B46                        ;  edi = 1,将0x00401224处的代码替成错误代码
00401249   |.  B9 08000000              mov     ecx, 8
0040124E   |.  BF 85214000              mov     edi, 00402185                   ;  文件内容 (ASCII "787878787878")
00401253   |.  BE 34214000              mov     esi, 00402134                   ;  esi ==> 常量 FF 77 88 66 99 55 AA 44 BB 33 CC 22 DD 11 EE C0
00401258   |>  8A07                     /mov     al, byte ptr [edi]
0040125A   |.  8A1E                     |mov     bl, byte ptr [esi]
0040125C   |.  32C3                     |xor     al, bl
0040125E   |.  D0C0                     |rol     al, 1
00401260   |.  8806                     |mov     byte ptr [esi], al
00401262   |.  47                       |inc     edi
00401263   |.  46                       |inc     esi
00401264   |.^ E2 F2                    \loopd   short 00401258
00401266   |.  58                       pop     eax                             ;  密码验证结果
00401267   |.  85C0                     test    eax, eax
00401269   |.  0F84 84000000            je      004012F3                        ;  密码正确则跳转

这一段代码就是真正的密码验证过程,其中算法在 call    00401B73 中,如下图所示:

继续:

其算法代码如下:
[Asm] 纯文本查看 复制代码

00401B73   /$  33FF                     xor     edi, edi
00401B75   |.  33F6                     xor     esi, esi
00401B77   |.  85C0                     test    eax, eax                        ;  长度
00401B79   |.  0F84 0F010000            je      00401C8E
00401B7F   |.  A3 72214000              mov     dword ptr [402172], eax         ;  注册密码的长度 len
00401B84   |.  BB 1D214000              mov     ebx, 0040211D                   ;  ebx ===> (ASCII "1234567890")
00401B89   |>  B9 FF000000              /mov     ecx, 0FF                       ;  循环次数, count=255
00401B8E   |>  8B03                     |/mov     eax, dword ptr [ebx]          ;  第1~4字符
00401B90   |.  05 87D61200              ||add     eax, 12D687
00401B95   |.  0343 08                  ||add     eax, dword ptr [ebx+8]        ;  第9~12字符
00401B98   |.  2D 672B0000              ||sub     eax, 2B67
00401B9D   |.  33D2                     ||xor     edx, edx                      ;  清空积高32位
00401B9F   |.  F725 72214000            ||mul     dword ptr [402172]            ;  乘以长度值,取积低32位
00401BA5   |.  8B53 04                  ||mov     edx, dword ptr [ebx+4]        ;  第5~8字符
00401BA8   |.  81C2 EAF48F04            ||add     edx, 48FF4EA
00401BAE   |.  33C2                     ||xor     eax, edx
00401BB0   |.  8B53 08                  ||mov     edx, dword ptr [ebx+8]        ;  第9~12字符
00401BB3   |.  81EA 015CBC00            ||sub     edx, 0BC5C01
00401BB9   |.  2B13                     ||sub     edx, dword ptr [ebx]          ;  第1~4字符
00401BBB   |.  81C2 672B0000            ||add     edx, 2B67
00401BC1   |.  33C2                     ||xor     eax, edx
00401BC3   |.  0D E2599302              ||or      eax, 29359E2
00401BC8   |.  03F8                     ||add     edi, eax
00401BCA   |.  233D 62214000            ||and     edi, dword ptr [402162]       ;  [addr]== 0x15263748,静态常量
00401BD0   |.  8B03                     ||mov     eax, dword ptr [ebx]          ;  第1~4字符
00401BD2   |.  2D 87D61200              ||sub     eax, 12D687
00401BD7   |.  2B43 08                  ||sub     eax, dword ptr [ebx+8]        ;  第9~12字符
00401BDA   |.  05 CE560000              ||add     eax, 56CE
00401BDF   |.  33D2                     ||xor     edx, edx                      ;  清除被除数高32位
00401BE1   |.  F735 72214000            ||div     dword ptr [402172]            ;  除以长度值
00401BE7   |.  8B53 04                  ||mov     edx, dword ptr [ebx+4]        ;  第5~8字符
00401BEA   |.  81EA EAF48F04            ||sub     edx, 48FF4EA
00401BF0   |.  33C2                     ||xor     eax, edx
00401BF2   |.  8B53 08                  ||mov     edx, dword ptr [ebx+8]        ;  第9~12字符
00401BF5   |.  81C2 015CBC00            ||add     edx, 0BC5C01
00401BFB   |.  0313                     ||add     edx, dword ptr [ebx]          ;  第1~4字符
00401BFD   |.  81EA CE560000            ||sub     edx, 56CE
00401C03   |.  33C2                     ||xor     eax, edx
00401C05   |.  25 E2599302              ||and     eax, 29359E2
00401C0A   |.  03F0                     ||add     esi, eax
00401C0C   |.  0B35 66214000            ||or      esi, dword ptr [402166]       ;  [addr] = 0x596A7B8C,静态常量
00401C12   |.  E2 02                    ||loopd   short 00401C16
00401C14   |.  EB 05                    ||jmp     short 00401C1B
00401C16   |>^ E9 73FFFFFF              |\jmp     00401B8E
00401C1B   |>  81C7 11090000            |add     edi, 911
00401C21   |.  81EE 11090000            |sub     esi, 911
00401C27   |.  FE0D 33214000            |dec     byte ptr [402133]              ;  0xFF,静态初始化的变量
00401C2D   |.  803D 33214000 00         |cmp     byte ptr [402133], 0
00401C34   |.^ 0F85 4FFFFFFF            \jnz     00401B89
00401C3A   |.  C605 33214000 FF         mov     byte ptr [402133], 0FF          ;  恢复
00401C41   |.  BB 1D214000              mov     ebx, 0040211D                   ;  ebx ===> (ASCII "1234567890")
00401C46   |.  B9 0E000000              mov     ecx, 0E                         ;  长度,14
00401C4B   |>  C603 00                  /mov     byte ptr [ebx], 0              ;  循环清除密码数据
00401C4E   |.  43                       |inc     ebx
00401C4F   |.^ E2 FA                    \loopd   short 00401C4B
00401C51   |.  81C7 AFE3FDEF            add     edi, EFFDE3AF
00401C57   |.  75 35                    jnz     short 00401C8E                  ;  等于0
00401C59   |.  81C6 238D94A4            add     esi, A4948D23
00401C5F   |.  75 2D                    jnz     short 00401C8E                  ;  等于0
00401C61   |.  BE 2C224000              mov     esi, 0040222C                   ;  ASCII "for input"
00401C66   |.  83C6 0A                  add     esi, 0A                         ;  esi ===> "Correct password - Good work!"
00401C69   |.  BF 6F224000              mov     edi, 0040226F                   ;  ASCII "Correct password - Good work!"
00401C6E   |.  B9 1D000000              mov     ecx, 1D
00401C73   |.  F3:A4                    rep     movs byte ptr es:[edi], byte pt>;  复制 "Correct password - Good work!"
00401C75   |.  68 6F224000              push    0040226F                        ;  addr ===> "Correct password - Good work!"
00401C7A   |.  68 F2030000              push    3F2
00401C7F   |.  FF75 08                  push    dword ptr [ebp+8]               ;  hInstance
00401C82   |.  B0 15                    mov     al, 15                          ;  SetDlgItemTextA()
00401C84   |.  FF15 64234000            call    dword ptr [402364]              ;  crueme.00401493
00401C8A   |.  33C0                     xor     eax, eax
00401C8C   |.  EB 05                    jmp     short 00401C93
00401C8E   |>  B8 01000000              mov     eax, 1
00401C93   \>  C3                       retn

可见,在上面函数包括了验证算法,并且验证通过,则会修改界面静态文本标签(控件ID为 0x03F2)的内容为"Correct password - Good work!",不会象另一个假的验证过程,会在一个无效的控件ID(0x03F3)上显示,正确标签控件上还是会显示错误提示。
这个算法不复杂,但对数据计算是破坏性的,并后循256x256次,无法逆推,只好暴破。暴破条件就是最后 edi 和 esi 都等于0时,输入的密码有效。如下图所示位置,就是条件:

先回到界面,输入假码:

进行验证处理,可了解其算法,并写出暴力计算密码代码。通过35分钟计算,得到一组4字符的密码:
”*A*
其中引号是半角的。
输入这个密码,如下图:

再次按“Check it!”,可得到正确的提示“Correct password - Good work!”。

下面看看其陷阱,call 00401B73 中如果验证错误,则没有显示提示直接返回,来到下面代码处,如下图:

先对  API 函数 GetDlgItemTextA 进行检查,看看有没有 BPX GetDlgItemTextA 进行中断跟踪。有则提示并退出。没有则来到下面的代码:

可以看到 call 004013B1 又对密码进行了一番操作,但是返回后,并没有正确的进行提示。如下代码:
[Asm] 纯文本查看 复制代码

004012BE   |>  FFD7                     call    edi                             ;  call GetDlgItemTextA(),读取密码
004012C0   |.  E8 EC000000              call    004013B1                        ;  对密码进行操作
004012C5   |.  85C0                     test    eax, eax
004012C7   |.  74 15                    je      short 004012DE
004012C9   |.  68 36224000              push    00402236                        ;  addr ===> (ASCII "Correct password - Good work!")
004012CE   |.  68 F3030000              push    3F3                             ;  ControlID
004012D3   |.  FF75 08                  push    dword ptr [ebp+8]
004012D6   |.  B0 15                    mov     al, 15                          ;  SetDlgItemTextA()
004012D8   |.  FF15 64234000            call    dword ptr [402364]              ;  crueme.00401493
004012DE   |>  68 54224000              push    00402254                        ;  addr ===>(ASCII "False password - Try again")
004012E3   |.  68 F2030000              push    3F2                             ;  提示框 ContrilID
004012E8   |.  FF75 08                  push    dword ptr [ebp+8]               ;  hInstance
004012EB   |.  B0 15                    mov     al, 15                          ;  SetDlgItemTextA()
004012ED   |.  FF15 64234000            call    dword ptr [402364]              ;  crueme.00401493
004012F3   |>  33FF                     xor     edi, edi                        ;  edi = 0 恢复代码
004012F5   |.  E8 4C080000              call    00401B46                        ;  恢复代码
004012FA   |.  B8 01000000              mov     eax, 1004012FF   |.^ E9 E0FEFFFF              jmp     004011E4

不管call    004013B1 返回什么值,最终我们能看到的提示都是“False password - Try again”。所以这里就是个陷阱,并不是真正的密码验证的地方。
下面交出暴破代码,使用 Dev-C++调试通过(没有逆推,如果谁有能力,看看是否可以逆推或优化,我这个在i5 4570上计算了2047秒):
[C++] 纯文本查看 复制代码

#include <iostream>
#include <string.h>

int checkPassword(char * password);

int getPassword(int len);
int genPassword(int index, int len, char * testPassword); 

int main(int argc, char** argv) {
	
	//char password[] = "1234567890";
	//char password[] = "\"*A*";
	//checkPassword(password);
	
	/// 暴破密码 
	int len = 4;
	int b = getPassword(len);
	
	return 0;
}

int checkPassword(char * password) {
	union {
		char pwd[16];
		unsigned long key[4];
	} passwd;
	
	/// 初始化缓冲区 
	passwd.key[0] = 0;
	passwd.key[1] = 0;
	passwd.key[2] = 0;
	passwd.key[3] = 0;
	
	long n = strlen(password);
	if(n<=0) {
		return -1;
	}
	
	/// 长度处理,最长12个字符 
	if (n>12) {
		password[12] = '\0'; /// 截断 
	}
	
	//// 存入计算缓冲区
	strcpy(passwd.pwd, password);
	
	long a, d;
	
	long check1 = 0;  /// edi
	long check2 = 0;  /// esi
	
	for(long i=255; i>0; i--) {
		for(long j=255; j>0; j--) {
			a = passwd.key[0];
			a += 0x0012D687;
			a += passwd.key[2];
			a -= 0x00002B67;
			a *= n;
			
			d = passwd.key[1];
			d += 0x048FF4EA;
			a ^= d;  // xor
			
			d = passwd.key[2];
			d -= 0x00BC5C01;
			d -= passwd.key[0];
			d += 0x00002B67;
			a ^= d;
			a |= 0x029359E2;
			
			check1 += a;
			check1 &= 0x15263748;
			
			a = passwd.key[0];
			a -= 0x0012D687;
			a -= passwd.key[2];
			a += 0x000056CE;
			//a &= 0xFFFFFFFF;
			a /= n;
			
			d = passwd.key[1];
			d -= 0x048FF4EA;
			a ^= d;
			
			d = passwd.key[2];
			d += 0x00BC5C01;
			d += passwd.key[0];
			d -= 0x000056CE;
			a ^= d;
			a &= 0x029359E2;
			
			check2 += a;
			check2 |= 0x596A7B8C;
		}
		check1 += 0x00000911;
		check2 -= 0x00000911;
	}
	//printf("check: 0x%08X - 0x%08X\n", check1, check2);

	check1 += 0xEFFDE3AF;
	check2 += 0xA4948D23;

	//// check1 = 0x05201300,check2 = 0x000EFFEE
	//printf("check: 0x%08X - 0x%08X\n", check1, check2);
	
	if(check1 | check2) {
		return 1; //// ERROR
	}
	
	return 0; ///OK
}

int getPassword(int len) {
	char password[16];
	for(int i=0; i<16;i++) {
		password[i] = '\0';
	}
	
	int b = genPassword(0, len, password);
	if(b) {
		printf("Password not found.\n");
	} else {
		printf("Found Password: %s\n", password);
	}

	
	return b;
} 

int genPassword(int index, int len, char * testPassword) {
	if(index == len) {
		return checkPassword(testPassword);
	}
	
	for(int i=0x20; i<0x7F; i++) { // printable characters,有 
	//for(int i=0x30; i<=0x39; i++) { // '0'~'9',无 
	//for(int i=0x41; i<=0x5A; i++) { // 'A'~'Z',无 
	//for(int i=0x61; i<=0x7A; i++) {  // 'a'~'z',无 
		testPassword[index] = (char)i;
		int b = genPassword(index+1, len, testPassword);
		if(b==0) {
			return 0;
		}
	}
	
	return 1;
}

计算结果:
[HTML] 纯文本查看 复制代码

Found Password: "*A*

--------------------------------
Process exited after 2047 seconds with return value 0

分析完毕!!!


THE END
喜欢就支持以下吧
点赞0
分享
评论 抢沙发
管埋员的头像-小北的自留地

昵称

取消
昵称