首页
社区
课程
招聘
[原创]看雪CTF2017秋季赛第三题Writeup
发表于: 2017-10-29 14:49 4124

[原创]看雪CTF2017秋季赛第三题Writeup

2017-10-29 14:49
4124

简要记录了破解过程的步骤以及思考过程。

打开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直播授课

收藏
免费 1
支持
分享
最新回复 (2)
雪    币: 2070
活跃值: (3872)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
你好,我的ida识别函数都是sub_xxxxx形式,但是我看了很多的writeup,包括你的里面有诸如j__memset,甚至有的人还有j_sm3_func,我想问下,这个是你们的ida自己识别出来的,还是人工识别,然后改的名字呢?
2017-10-31 14:21
0
雪    币: 148
活跃值: (148)
能力值: ( LV6,RANK:140 )
在线值:
发帖
回帖
粉丝
3
hkdong 你好,我的ida识别函数都是sub_xxxxx形式,但是我看了很多的writeup,包括你的里面有诸如j__memset,甚至有的人还有j_sm3_func,我想问下,这个是你们的ida自己识别出来的 ...
memset应该是不同版本IDA影响,有些版本的能够识别;sm3是人工识别了//
2017-11-1 20:21
0
游客
登录 | 注册 方可回帖
返回
//