|
本帖最后由 pk8900 于 2018-6-25 23:17 编辑
前言
很长时间没有发帖子了,这段时间一直在饿补,随着逆向学习深入,发现自己要学的东西太多了,许多东西过去都是只听说过,跟本没学习过,于是看网上的各种教程,并寻找用来练手的东西,发现看雪CTF里有不少CRACKME,于是拿来分析研究。
这个CRACKME的作者叫[凉飕飕],CTF要求是追码,整个追码过程断续的进行2天时间,然后写的这个帖子,尽量还原原始分析步骤,如果有遗漏和不对的地方,欢迎大家请跟贴讨论。
此贴并非技术贴,大牛可以一带而过,和我一样的新手也许可以从中学习到一些方法和经验。
下载地址
CRACKME原帖及下载地址:https://ctf.pediy.com/game-fight-2.htm
crack简介
PEid显示,Microsoft Visual C++ 8 *,图标是个MFC图标,估计是VC++的程序错不了。界面如下:
输入任意注册码,点击OK,有提示信息框:something you lost!,并且每尝试一次,左边还有一个文本框递增次数。
使用工具
IDA7.0,X64DBG,辅助工具:PEid,慧星小助手(查看控件ID,本例中:密码框为1002,次数框为1000)
摸清流程
既然程序有提示字符串,那就从字符串下手。X64DBG载入,运行后程序自动退出,有反调试。
打开IDA进行静态分析,查找字符串,找到结果如下:
.text:00402423 sub_402120 push offset Text ; "something you lost!"
转到sub_402120调用处,此处IDA代码如下:
[C++] 纯文本查看 复制代码 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | BeginPaint(hWnd, &Paint); EndPaint(hWnd, &Paint);
return 0;
}
return DefWindowProcW(hWnd, Msg, wParam, lParam);
}
if ( (unsigned __int16 )wParam > 0x40Au )
{
switch ( (unsigned __int16 )wParam )
{
case 0x40Bu:
*(_OWORD *)v20 = xmmword_41DB98;
v23 = 0;
v21 = xmmword_41DBA8;
v22 = xmmword_41DBB8;
memset (&v24, 0, 0x96u);
MessageBoxW(hWnd, v20, L "Successed" , 0);
return 0;
case 0x40Cu:
sub_402970(lParam, &v19);
v9 = operator new [](2 * (v19 + 1));
sub_402870(v9, lParam);
sub_4029B0(v9);
sub_402A00(v9);
memset (v20, 0, 0xC8u);
sub_402870(v20, v9);
j_j_j___free_base(v9);
v10 = 0;
if ...
if ( sub_402810(lParam, v20) )
SendMessageW(hWnd, 0x111u, 0x40Au, 0);
return 0;
case 0x40Fu:
MessageBoxW(0, L "something you lost!" , L "Failed" , 0);
return 0;
}
return DefWindowProcW(hWnd, 0x111u, wParam, lParam);
}
if ( (unsigned __int16 )wParam == 0x40A )
{
*(_OWORD *)Text = xmmword_41DB60;
v26 = xmmword_41DB70;
v27 = xmmword_41DB80;
v28 = qword_41DB90;
memset (&v29, 0, 0x2Cu);
MessageBoxW(hWnd, Text, L "Failed" , 0);
|
在失败对话框上方,有明显"Successed"成功字样,分析得出成功的条件就是:wParam==0x40B,再往上可以看到:
[C++] 纯文本查看 复制代码 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 | switch ( Msg )
{
case 0x113u:
v13 = operator new (0x14u);
v40 = 0;
*(_OWORD *)v39 = 0i64;
v14 = GetDlgItem(hWnd, 1000);
GetDlgItemTextW(v14, 1000, v39, 10);
v19 = -858993460;
sub_4027C0(v39, ( const char *)L "%d" , &v19);
*v13 = v19;
v13[1] = hWnd;
v13[3] = GetDlgItem(hWnd, 1002);
v13[2] = GetDlgItem(hWnd, 1000);
v13[4] = v13;
“.............................”
while ( *((_WORD *)&v32 + v15) );
if ( OpenEventW(0x1F0003u, 0, L "Cracker" ) )
CreateThread(0, 0, StartAddress, v13, 0, 0);
return 0;
“.............................”
return DefWindowProcW(hWnd, Msg, wParam, lParam);
|
case 0x113u,百度查询,0x113为时间事件,也就是程序里有一个时钟事件,用做检测,事件中包含"Cracker",“Failed”等可疑字串,未发现成功字串,只有一个可疑的CreateThread(0, 0, StartAddress, v13, 0, 0);函数,创建新线程。
(此期间尝试跟踪此时间事件,结果一点进展都没有,关于程序反调试,我采用的附加的方法轻松绕过,反调试部分放在最后分析。)
绕了半天的弯路后,又回到这里,决定从成功注册的条件:wParam==0x40B入手,IDA查的常量:0x40B(IDA操作为查找立即数),果然找到了关键位置,sub_401870,右键对函数重命名为:check_right_401870(命名方便在函数列表中查找,保留地址是方便X64DBG中进行调试定位),在这个子程序中调用了 return PostMessageW((HWND)struct_count_list->win_handle, 0x111u, 0x40Bu, 0);,这样wParam就被赋值0x40B,就可以出现成功的提示了。(此期间尝试在X64DBG中对基址+1870地址下断,根本断不下来,说明验证过程中没有调用这里)
继续IDA中静态分析,查找check_right_401870函数调用关系,(IDA操作:鼠标定位于check_right_401870函数中,右键“邻近浏览器”),在邻近浏览器图表视图中通过双击父结点,及隐藏非重要节点,得出如下流程图:
成功只有一个路线,IDA的这个功能直的很有用,如果想爆破实现的话,那就可以按图索骥了。
3.逐段分析
这个过程是漫长的,也可以说是经验积累的过程,大牛通常能一眼看穿的代码,而新手就要推敲半天,我想这就是大牛和新手之间最大的差别吧~~~。
3.1msgcheck_2120
此函数代码很长,下面仅贴上关键部分(IDA F5 伪代码):
[C++] 纯文本查看 复制代码 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 | if ( (unsigned __int16 )wParam == 0x3EB )
{
sub_402790(String, 10, ( const char *)L "%d" , ++dword_420318);
v4 = GetDlgItem(hWnd, 1000);
SetWindowTextW(v4, String);
v5 = (struct_count_array *)operator new (0x14u);
v40 = 0;
v6 = v5;
*(_OWORD *)v39 = '\0' ;
v7 = GetDlgItem(hWnd, 1000);
GetWindowTextW(v7, v39, 10);
vsscanf_27C0(( int )v39, ( const char *)L "%d" , &v19);
*(_DWORD *)&v6->Now_count = v19;
v6->win_handle = ( int )hWnd;
v6->passwd_handle = ( int )GetDlgItem(hWnd, 1002);
v6->count_handle = ( int )GetDlgItem(hWnd, 1000);
v6->field_10 = ( int )v6;
CreateThread(0, 0, (LPTHREAD_START_ROUTINE)check_01_4020E0, v6, 0, 0);
return 0;
}
|
msgcheck_402120是MSG处理函数,这里处理所有返回的事件,上面代码为OK按钮事件,用CreateThread函数创建一个新线程,处理按钮事件,参数V6,即上面的V5,通过X64DBG中查看,估计是一个结构体,于是在IDA中自定义了一个结构体:如下
[Asm] 纯文本查看 复制代码 1 2 3 4 5 6 7 8 9 | struct_count_array struc
00000000 Now_count dw ?
00000002 db ?
00000003 db ?
00000004 win_handle dd ?
00000008 count_handle dd ?
0000000C passwd_handle dd ?
00000010 field_10 dd ?
00000014 struct_count_array ends
|
在IDA中将变量V5指定为结构体指针,这样代码会更加清晰,结构体中:Now_count=当前尝试次数 win_handle=主窗体句柄 count_handle=显示次数文本框句柄 passwd_handle=密码框句柄 field_10=当前结构体指针,通过这结构体可以轻松访问密码框和次数框的文本内容。
结构体大小是20,从v5 = (struct_count_array *)operator new(0x14u);这句中就可以确定。
3.2 check_01_20E0
代码比较少,调用了check_02_401CB0,参数共2个,第一个是我们自定义的那个结构体数据,第二个参数是0
[C++] 纯文本查看 复制代码 1 2 3 4 5 6 | int __stdcall check_01_4020E0(struct_count_array *lpMem)
{
check_02_401CB0(( int )lpMem, 0);
sub_402CE4(lpMem);
return 0;
}
|
3.3 check_02_401CB0
伪代码如下:
[C++] 纯文本查看 复制代码 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 | void __fastcall check_02_401CB0( int a1, int a2)
{
struct_count_array *canshu_1;
int v3;
unsigned int len;
WCHAR *v5;
_WORD *next_passwd;
clock_t v7;
WCHAR String;
canshu_1 = (struct_count_array *)a1;
if ( a1 )
{
if ( a2 )
{
v7 = clock ();
memset (&String, 0, 200u);
GetWindowTextW(( HWND )canshu_1->passwd_handle, &String, 200);
len = 0;
v5 = &String;
if ( String )
{
do
{
++v5;
++len;
}
while ( *v5 );
}
next_passwd = operator new [](2 * (len + 1));
if ( clock () - v7 > 2 )
exit (0);
sub_402870(( int )next_passwd, &String);
if ( len >= 7 )
{
if ( len <= 7 )
{
check_03_401A60(( int )canshu_1, next_passwd);
return ;
}
SendMessageW(( HWND )canshu_1->win_handle, 0x40Du, 0, 0);
}
else
{
SendMessageW(( HWND )canshu_1->win_handle, 0x40Eu, 0, 0);
}
j_j_j___free_base(next_passwd);
return ;
}
if ( checkhave_b_401C00(( HWND *)a1)
&& ( memset (&String, 0, 0xC8u),
GetWindowTextW(( HWND )canshu_1->passwd_handle, &String, 100),
checkhave_402A50(v3, ( __int16 *)&String, 'p' )) )
{
check_02_401CB0(( int )canshu_1, 1);
}
else
{
SendMessageW(( HWND )canshu_1->win_handle, 0x111u, 0x40Fu, 0);
}
}
}
|
这个函数很有意思,参数a2=0时,检测密码中是否含有字母‘p’和‘b’,如果有则调用自身,传a2参数为1,当a2=1时,检查密码长度是否为7位,通过这两个条件即可进入下一验证环节。
3.4check_03_1A60
这个函数是一个加密函数,check_03_401A60(struct_count_array *a1, _WORD *a2),a2取我们输入的密码,在这个过程中被加密,加密过程也比较简单,只是做了一下异或加密。前半部分分析了半天貌似没有实际做用,估计是作者加的干扰代码。
函数最后调用:return check_right_401870(next_canshu_a1, next_passwd);,将加密后的密码传入下一验证步骤。
3.5check_right_1870
伪代码如下:
[C++] 纯文本查看 复制代码 001 002 003 004 005 006 007 008 009 010 011 012 013 014 015 016 017 018 019 020 021 022 023 024 025 026 027 028 029 030 031 032 033 034 035 036 037 038 039 040 041 042 043 044 045 046 047 048 049 050 051 052 053 054 055 056 057 058 059 060 061 062 063 064 065 066 067 068 069 070 071 072 073 074 075 076 077 078 079 080 081 082 083 084 085 086 087 088 089 090 091 092 093 094 095 096 097 098 099 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 | BOOL __fastcall check_right_401870(struct_count_array *a1, _WORD *a2)
{
struct_count_array *struct_count_list;
_WORD *password;
char *char_N;
signed int v5;
signed int v6;
__int16 *v7;
unsigned int count;
__int16 *i;
unsigned int v10;
unsigned int v11;
unsigned int len_pw;
_WORD *v13;
unsigned int v14;
unsigned int len_pw1;
_WORD *j;
unsigned int v17;
unsigned int v18;
int n;
__int16 enc_s;
__int64 *v21;
__int16 *_pswd;
__int16 v23;
__int16 *v24;
int v25;
int v26;
__int64 *v27;
_WORD *v28;
unsigned int v29;
struct_count_array *tmp_arg_1;
__int16 enc_list[28];
char v33;
__int64 v34;
__int16 v35;
struct_count_list = a1;
password = a2;
tmp_arg_1 = a1;
memset (enc_list, 0, 54u);
char_N = &v33;
v5 = '0' ;
do
{
*(_WORD *)char_N = v5;
char_N += 2;
++v5;
}
while ( v5 <= '9' );
v6 = 'a' ;
v7 = enc_list;
do
{
*v7 = v6;
++v7;
++v6;
}
while ( v6 <= 'z' );
count = 0;
for ( i = enc_list; *i; ++count )
++i;
v10 = 0;
if ( count )
{
do
{
v11 = (unsigned __int16 )enc_list[v10];
if ( v11 >= 'a' && v11 <= 'z' )
enc_list[v10] = v11 - 32;
++v10;
}
while ( v10 < count );
}
len_pw = 0;
v13 = password;
if ( password )
{
if ( *password )
{
do
{
++v13;
++len_pw;
}
while ( *v13 );
}
v14 = 0;
if ( len_pw )
{
do
{
if ( v14 >= 2 )
{
if ( v14 >= 4 )
password[v14] ^= 'B' ;
else
password[v14] ^= 'P' ;
}
else
{
password[v14] ^= 0xFu;
}
++v14;
}
while ( v14 < len_pw );
}
len_pw1 = 0;
for ( j = password; *j; ++len_pw1 )
++j;
v17 = 0;
if ( len_pw1 )
{
do
{
v18 = (unsigned __int16 )password[v17];
if ( v18 >= 'a' && v18 <= 'z' )
password[v17] = v18 - 32;
++v17;
}
while ( v17 < len_pw1 );
}
}
n = 0;
v34 = '\0' ;
v35 = 0;
if ( *password )
{
enc_s = enc_list[0];
v21 = &v34;
_pswd = password;
do
{
if ( enc_s )
{
v23 = *_pswd;
v24 = enc_list;
v25 = 0;
while ( v23 != *v24 )
{
v24 = &enc_list[++v25];
if ( !enc_list[v25] )
goto LABEL_37;
}
*(_WORD *)v21 = enc_list[v25];
v21 = ( __int64 *)(( char *)v21 + 2);
LABEL_37:
enc_s = enc_list[0];
}
_pswd = &password[++n];
}
while ( password[n] );
struct_count_list = tmp_arg_1;
}
v26 = 0;
v27 = &v34;
if ( (_WORD)v34 )
{
do
{
v27 = ( __int64 *)(( char *)v27 + 2);
++v26;
}
while ( *(_WORD *)v27 );
if ( v26 == 2 )
{
LODWORD(v34) = '5\01' ;
HIDWORD(v34) = &unk_420050;
v28 = password + 2;
v35 = 0;
v29 = 0;
while ( *((_WORD *)&v34 + v29) == *v28 )
{
++v29;
++v28;
if ( v29 >= 4 )
{
if ( !sub_401740(struct_count_list, password) )
return PostMessageW(( HWND )struct_count_list->win_handle, 0x111u, 0x40Au, 0);
return PostMessageW(( HWND )struct_count_list->win_handle, 0x111u, 0x40Bu, 0);
}
}
}
}
return PostMessageW(( HWND )struct_count_list->win_handle, 0x111u, 0x40Au, 0);
}
|
这一函数很长,实现的功能也很复杂,函数首先构建一个密码表“123456789AB......Z”,实际用到的只有前9个数字字符,然后还原上一步聚中加密的密码,并转为大写,然后对比了我们输入密码的3至6位是否为“15PB”,这样我们就可以确定我们的密码第3-6位正确的应该是:“15pb”
函数另外调用了sub_401740(struct_count_list, password)来进行验证,IDA伪代码如下:
[C++] 纯文本查看 复制代码 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 | signed int __fastcall sub_401740(_DWORD *a1, _WORD *a2)
{
_WORD *the_password;
struct_count_array *v3;
__int128 *v4;
signed int char_1;
int len;
_WORD *v7;
char *new_string;
unsigned __int16 *last_passwd;
int v10;
__int128 *v11;
__int16 v12;
__int16 v13;
__int128 *v14;
signed int v15;
unsigned __int16 *v17;
struct_count_array *v18;
char *v19;
__int128 charlist_1to9;
__int64 v21;
int v22;
__int16 v23;
the_password = a2;
v22 = 0;
v3 = (struct_count_array *)a1;
v23 = 0;
v18 = (struct_count_array *)a1;
charlist_1to9 = '\0' ;
v4 = &charlist_1to9;
char_1 = '1' ;
v21 = '\0' ;
do
{
*(_WORD *)v4 = char_1;
v4 = (__int128 *)(( char *)v4 + 2);
++char_1;
}
while ( char_1 <= '9' );
len = 0;
v7 = the_password;
if ( the_password && *the_password )
{
do
{
++v7;
++len;
}
while ( *v7 );
}
new_string = cat_4028D0(( char *)&charlist_1to9, &the_password[*(_DWORD *)&v3->Now_count]);
last_passwd = &the_password[len - 1];
v19 = new_string;
v10 = 0;
v17 = last_passwd;
if ( (_WORD)charlist_1to9 )
{
v11 = &charlist_1to9;
v12 = *last_passwd & 1;
while ( 1 )
{
v13 = v12 + (*(_WORD *)v11 >> 2);
if ( v13 == '2' )
break ;
if ( v13 != 'd' )
{
v11 = (__int128 *)(( char *)&charlist_1to9 + 2 * ++v10);
if ( *(_WORD *)v11 )
continue ;
}
new_string = v19;
goto LABEL_12;
}
}
else
{
LABEL_12:
v14 = &charlist_1to9;
v15 = '1' ;
while ( *(_WORD *)v14 == *(_WORD *)(( char *)v14 + ( char *)the_password - ( char *)&charlist_1to9) )
{
v15 += 6;
v14 = (__int128 *)(( char *)v14 + 2);
if ( v15 > '9' )
{
if ( (unsigned __int16 )*the_password + *((unsigned __int16 *)new_string + 9) == 'c'
二位是2
&& *v17 == *(_DWORD *)&v18->Now_count + *((unsigned __int16 *)new_string + 6) )
{
return 1;
}
return 0;
}
}
}
return 0;
}
|
在sub_401740中,验证第一位是否为“1”,函数将密码连接成“123456789”+从调用次数开始截取到最后的密码部分,比如第一次点OK时密码是“ABCDEFG”,连接后即为“123456789BCDEFG”,验证第一位+第二位是否等于‘c’,所以第二位为‘2’,通过取“123456789BCDEFG”中第(7+验证次数)位与我们输入密码的最后一位比较,来验证最后一位,所以第一次验证时最后一位应该是“8”,但第二次验证则无法验证成功第二位的2,因为2被截去了,所以只能在第一次按OK时输入“1215pb8”才能通过验证。
至此整个分析过程完成,并成功找到密码。
截个图:
4.反调试分析
关于反调试:程序启动有反调试:2处:
1、比较父进程名称,是否为exeplorer.exe
0109152C | 74 04 | je crack_.1091532 |
0109152E | 84 D2 | test dl, dl |
01091530 | 74 1A | je crack_.109154C | 跳去结束程序
01091532 | 57 | push edi |
01091533 | FF 15 60 80 0A 01 | call dword ptr ds:[<&CloseHandle>] |
01091539 | 8B 4D FC | mov ecx, dword ptr ss:[ebp-0x4] |
0109153C | 33 C0 | xor eax, eax |
0109153E | 5F | pop edi |
0109153F | 5E | pop esi |
01091540 | 33 CD | xor ecx, ebp |
01091542 | 5B | pop ebx |
01091543 | E8 7D 17 00 00 | call crack_.1092CC5 |
01091548 | 8B E5 | mov esp, ebp |
0109154A | 5D | pop ebp |
0109154B | C3 | ret |
0109154C | 6A 00 | push 0x0 |
0109154E | E8 5E 36 00 00 | call <crack_.sub_1094BB1> |
2、通过以下代码,比较运行中延时时间,大于2毫秒,则认为被调试,程序退出,这块没明白具体原理,下断结果3毫秒,比判断多了1毫秒,不知道是怎么产生的,猜测是程序被调试响应速度慢。
[Asm] 纯文本查看 复制代码 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 | 01091575 | 57 | push edi | 获取时间
01091576 | E8 63 33 00 00 | call <crack_.sub_10948DE> |
0109157B | 8B 35 84 81 0A 01 | mov esi , dword ptr ds :[<&LoadStringW>] |
01091581 | 8B F8 | mov edi , eax |
01091583 | 6A 64 | push 0x64 |
01091585 | 68 20 04 0B 01 | push crack_.10B0420 | 10B0420:L "Crack_Me_4"
0109158A | 6A 67 | push 0x67 |
0109158C | 53 | push ebx |
0109158D | FF D6 | call esi |
0109158F | 6A 64 | push 0x64 |
01091591 | 68 58 03 0B 01 | push crack_.10B0358 | 10B0358:L "CRACK_ME_4"
01091596 | 6A 6D | push 0x6D |
01091598 | 53 | push ebx |
01091599 | FF D6 | call esi |
0109159B | 8B CB | mov ecx , ebx |
0109159D | E8 FE 00 00 00 | call <crack_.sub_10916A0> |
010915A2 | E8 37 33 00 00 | call <crack_.sub_10948DE> |
010915A7 | 2B C7 | sub eax , edi |
010915A9 | 6A 00 | push 0x0 |
010915AB | 83 F8 02 | cmp eax , 0x2 | 比较时间
010915AE | 0F 8F DA 00 00 00 | jg crack_.109168E |
|
中间也有几处反调试,皆为第2种方法,通过时间差来判断。
附上Crackme程序:
Crack_Me.exe.rar
(124.37 KB, 下载次数: 33)
IDA数据文件:
Crack_Me.idb.rar
(456.83 KB, 下载次数: 21)
|
免费评分
-
查看全部评分
|