简要记录了破解过程的步骤以及思考过程。
打开CrackMe,出现对话框主界面。随便输入sn,点击”验证序列号“无反应。
拖入IDA中,初步判断无壳。
F5反编译失败,提示”positive sp value has been found“,alt+k修正后成功反编译。
CrackMe主界面由DialogBoxParamA产生:
说来尴尬,没搞懂这个CALL是干嘛的。。不过不影响做题。
sub_42d96a:
sn的后64字节和格式化后的sm3_hash相等。
经调试发现,本题的sm3_hash遇到0x00就截止了。
push 0 ; dwInitParam
push offset DialogFunc ; lpDialogFunc
push 0 ; hWndParent
push 65h ; lpTemplateName
mov eax, [ebp+hInstance]
push eax ; hInstance
call ds:DialogBoxParamA
双击DialogFunc进入窗口过程,寻找按钮点击处理函数(msg=WM_COMMAND,LOWORD(wParam)=0x3EA):
else if ( msg == 0x111 )
{
msg = (unsigned __int16)wParam;
if ( (unsigned __int16)wParam == 0x3EA )
{
sn = 0;
j__memset(&v18, 0, 0x3FFu);
v15 = 0;
j__memset(&v16, 0, 0x3FFu);
v19 = GetDlgItemTextA(hDlg, 1001, &sn, 1025);
v13 = 0;
j__memset(&v14, 0, 0x3FFu);
sub_42D267(&sn, 1024, &v15);
v12[0] = 0;
j__memset(&v12[1], 0, 0x3FFu);
sub_42D267(&v15, 1024, &v13);
sub_42D96A(&v13, (int)v12, 1024);
v11 = 3;
sub_42DA78(&v13, 3u, (int)v10);
for ( i = 0; i < 32; ++i )
{
v22 = v10[i];
j__sprintf(&v9[2 * i], "%02x", v22);
}
v22 = j__strlen(v9);
v4 = &sn + j__strlen(&sn);
v5 = j__strlen(v9);
if ( !j__memcmp(v9, &v4[-v5], v22) )
{
sub_42D0B4(v23, v24, v25);
if ( (unsigned __int8)sub_42D9AB(&unk_49B000, v12) == 1 )
MessageBoxA(0, "ok", "CrackMe", 0);
}
}
}
大致流程:
1、将相关buffer清零。
2、调用GetDlgItemTextA获取sn
3、调用sub_42d267,输入sn,输出v15
4、再次调用sub_42d267,输入v15,输出v13。
5、调用sub_42d96a,输入v13,输出v12。
6、调用sub_42DA78,输入v13,输出v10。
7,、以连续32个”%02x“为格式,把v10输出到v9。
接下来是两个关键判断,都成功就输出ok。
1、sn的后64个字节和v9的比较,相同则成功。
2、sub_42d9ab(49b000,v12)等于1则成功。
算法1:
首先进入sub_42d267函数,看看sn是如何生成v13的(代码较长,只贴关键部分):
for ( i = 0; i < a2; ++i )
{
if ( *(_BYTE *)(i + a1) == '=' )
return 0;
if ( *(char *)(i + a1) < '+' )
return 1;
if ( *(char *)(i + a1) > 'z' )
return 1;
v4 = byte_48B0FD[*(char *)(i + a1)];
if ( v4 == -1 )
return 1;
switch ( i % 4 )
{
.........
.........
两个关键点:
1.遇到‘=’就返回。
2.switch(i%4)说明输入以4个为1组。
马上想到了base64解密。
上OD动态调试,CM自动退出,怀疑是反调试作祟。在OD上手动将反调试call一个个nop掉(大概有20多处,但作者写的比较工整,很容易识别)。
patch后重新调试,输入sn:MTIz。经过sub_42d267后,v15为”123“。
至此确定sub_42d267为base64_decode。
算法2:
说来尴尬,没搞懂这个CALL是干嘛的。。不过不影响做题。
sub_42d96a:
if ( a1[v9] == ' ' || a1[v9] == '/' )
{
if ( a1[v9] != ' ' || a1[v9 - 1] == '/' )
{
if ( a1[v9] == '/' )
*(_BYTE *)(v7++ + a2) = ' ';
}
输出一个‘/’对应一个空格‘ ’输出,动态调试也验证了下。
算法3:
接下来看sub_42da78:
void *__cdecl sub_437E70(void *a1, size_t a2, int a3)
{
int v4; // [esp+D0h] [ebp-F0h]
sub_42D294((int)&v4);
sub_42DF2D((int)&v4, a1, a2);
sub_42D15E(&v4, a3);
return j__memset(&v4, 0, 0xE8u);
}
第一个call初始化局部变量v4:
int __cdecl sub_436700(_DWORD *a1)
{
int v1; // ecx
int result; // eax
*a1 = 0;
a1[1] = 0;
a1[2] = 0x7380166F;
a1[3] = 0x4914B2B9;
a1[4] = 0x172442D7;
a1[5] = 0xDA8A0600;
a1[6] = 0xA96F30BC;
a1[7] = 0x163138AA;
a1[8] = 0xE38DEE4D;
a1[9] = 0xB0FB0E4E;
if ( sub_42DA7D(v1) == 1 )
sub_42E086(0);
sub_42D389();
if ( sub_42D807() == 1 )
sub_42E086(0);
result = sub_42D39D();
if ( result == 1 )
sub_42E086(0);
return result;
}
看见前面3个call和这一系列的常数,联想到了md5算法中的md5_init,md5_update,md5_final。
上网搜索0x7380166f,发现这是sm3哈希算法。
再加上之后的32个"%02x"格式化打印,基本确定sub_42da78为sm3哈希算法。
判断1:
sn的后64字节和格式化后的sm3_hash相等。
经调试发现,本题的sm3_hash遇到0x00就截止了。
判断2:
char __cdecl sub_435400(int a1, _BYTE *a2)
{
......
while ( *a2 != ' ' )
{
......
......
++a2;
}
return 1;
}
最简单的绕过情况:首字符为' '。
参考算法2,首字符为‘/。’
令x[0]='/',x[1]=0x00。则sm3(x)恒为”56364f77ef88bb65dd684f12d3c8bd93ff72d525e2322c1be48cf496f4388177“。
整理一下条件:
x=base64_decode(base64_decode(sn))
sm3_hash(x)=sn[-40:]
x[0]='/'
x[1]=0x0
设x为字节序列['/',0,'/','/',0,'/','/',0,'/']
则sn=b64encode(b64encode(x))+sm3(x)
sn:THdBdkx3QXZMd0F256364f77ef88bb65dd684f12d3c8bd93ff72d525e2322c1be48cf496f4388177
总结:
1.感觉这不是作者设计的解法(明显无穷多解)。
2.多动态调试,有时不必完全了解算法。
else if ( msg == 0x111 )
{
msg = (unsigned __int16)wParam;
if ( (unsigned __int16)wParam == 0x3EA )
{
sn = 0;
j__memset(&v18, 0, 0x3FFu);
v15 = 0;
j__memset(&v16, 0, 0x3FFu);
v19 = GetDlgItemTextA(hDlg, 1001, &sn, 1025);
v13 = 0;
j__memset(&v14, 0, 0x3FFu);
sub_42D267(&sn, 1024, &v15);
v12[0] = 0;
j__memset(&v12[1], 0, 0x3FFu);
sub_42D267(&v15, 1024, &v13);
sub_42D96A(&v13, (int)v12, 1024);
v11 = 3;
sub_42DA78(&v13, 3u, (int)v10);
for ( i = 0; i < 32; ++i )
{
v22 = v10[i];
j__sprintf(&v9[2 * i], "%02x", v22);
}
v22 = j__strlen(v9);
v4 = &sn + j__strlen(&sn);
v5 = j__strlen(v9);
if ( !j__memcmp(v9, &v4[-v5], v22) )
{
sub_42D0B4(v23, v24, v25);
if ( (unsigned __int8)sub_42D9AB(&unk_49B000, v12) == 1 )
MessageBoxA(0, "ok", "CrackMe", 0);
}
}
}
大致流程:
1、将相关buffer清零。
2、调用GetDlgItemTextA获取sn
3、调用sub_42d267,输入sn,输出v15
4、再次调用sub_42d267,输入v15,输出v13。
5、调用sub_42d96a,输入v13,输出v12。
6、调用sub_42DA78,输入v13,输出v10。
7,、以连续32个”%02x“为格式,把v10输出到v9。
接下来是两个关键判断,都成功就输出ok。
1、sn的后64个字节和v9的比较,相同则成功。
2、sub_42d9ab(49b000,v12)等于1则成功。
算法1:
首先进入sub_42d267函数,看看sn是如何生成v13的(代码较长,只贴关键部分):
for ( i = 0; i < a2; ++i )
{
if ( *(_BYTE *)(i + a1) == '=' )
return 0;
if ( *(char *)(i + a1) < '+' )
return 1;
if ( *(char *)(i + a1) > 'z' )
return 1;
v4 = byte_48B0FD[*(char *)(i + a1)];
if ( v4 == -1 )
return 1;
switch ( i % 4 )
{
.........
.........
两个关键点:
1.遇到‘=’就返回。
2.switch(i%4)说明输入以4个为1组。
for ( i = 0; i < a2; ++i )
{
if ( *(_BYTE *)(i + a1) == '=' )
return 0;
if ( *(char *)(i + a1) < '+' )
return 1;
if ( *(char *)(i + a1) > 'z' )
return 1;
v4 = byte_48B0FD[*(char *)(i + a1)];
if ( v4 == -1 )
return 1;
switch ( i % 4 )
{
.........
.........
两个关键点:
1.遇到‘=’就返回。
马上想到了base64解密。
上OD动态调试,CM自动退出,怀疑是反调试作祟。在OD上手动将反调试call一个个nop掉(大概有20多处,但作者写的比较工整,很容易识别)。
patch后重新调试,输入sn:MTIz。经过sub_42d267后,v15为”123“。
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课