首页
社区
课程
招聘
[原创]看雪.TSRC 2017CTF秋季赛第三题WP
2017-10-29 02:01 2584

[原创]看雪.TSRC 2017CTF秋季赛第三题WP

2017-10-29 02:01
2584

看雪.TSRC 2017CTF秋季赛第三题WP

初识

首先将程序拉入IDA,这已经成为我做CTF题目的习惯了。
查看入口,还算正常。应该也是vc写的,通常8.0以上版本的入口为call,jmp的形式,但此题不是,是两个call。随后在第二个call中看到去main函数的跳转函数,跳到434CA0

.text:0043D38E                 mov     ecx, [ebp+var_78]
.text:0043D391                 push    ecx             ; int
.text:0043D392                 mov     edx, [ebp+var_68]
.text:0043D395                 push    edx             ; int
.text:0043D396                 push    0               ; int
.text:0043D398                 push    400000h         ; hInstance
.text:0043D39D                 call    sub_42E040

main函数直接看伪代码:

int __stdcall sub_434CA0(HINSTANCE hInstance, int a2, int a3, int a4)
{
  int v4; // eax@1
  int v6; // [sp+0h] [bp-CCh]@1
  char v7; // [sp+Ch] [bp-C0h]@1

  memset(&v7, 0xCCu, 0xC0u);
  dword_49C784 = (int)hInstance;
  v4 = DialogBoxParamA(hInstance, (LPCSTR)0x65, 0, DialogFunc, 0);
  sub_42DE51(&v6 == &v6, v4);
  return sub_42DE51(1, 0);
}

DialogFunc跳转去的伪代码如下(patch过的):

int __stdcall sub_434EF0(HWND hDlg, int a2, int a3, int a4)
{
  int v4; // eax@8
  int v5; // ST0C_4@11
  CHAR *v6; // esi@11
  int v7; // eax@11
  int v8; // eax@13
  int v9; // eax@14
  int v11; // [sp+0h] [bp-1A4Ch]@8
  int v12; // [sp+Ch] [bp-1A40h]@1
  int i; // [sp+1C4h] [bp-1888h]@8
  char v14[1032]; // [sp+1D0h] [bp-187Ch]@10
  char v15[40]; // [sp+5D8h] [bp-1474h]@8
  int v16; // [sp+600h] [bp-144Ch]@8
  char v17; // [sp+60Ch] [bp-1440h]@8
  char v18; // [sp+60Dh] [bp-143Fh]@8
  char v19; // [sp+A14h] [bp-1038h]@8
  char v20; // [sp+A15h] [bp-1037h]@8
  char v21; // [sp+E1Ch] [bp-C30h]@8
  char v22; // [sp+E1Dh] [bp-C2Fh]@8
  CHAR String; // [sp+1224h] [bp-828h]@8
  char v24; // [sp+1225h] [bp-827h]@8
  int v25; // [sp+162Ch] [bp-420h]@8
  char v26; // [sp+1638h] [bp-414h]@1
  char v27; // [sp+1639h] [bp-413h]@1
  int v28; // [sp+1A40h] [bp-Ch]@1
  unsigned int v29; // [sp+1A48h] [bp-4h]@1
  int savedregs; // [sp+1A4Ch] [bp+0h]@1

  memset(&v12, 0xCCu, 0x1A40u);
  v29 = (unsigned int)&savedregs ^ dword_49B344;
  v28 = 0;
  v26 = 0;
  memset_42D5E6((int)&v27, 0, 1023);
  v12 = a2;
  if ( a2 == 16 )
    ExitProcess(0);
  if ( v12 == 0x110 )
  {
    sub_42D4F1();
    v28 = 0;
    sub_42E428();
    v28 = 0;
    v28 = sub_42D825();
    sub_42D14F(hDlg, 1);
  }
  else if ( v12 == 0x111 )
  {
    v12 = (unsigned __int16)a3;
    if ( (unsigned __int16)a3 == 1002 )
    {
      String = 0;
      memset_42D5E6((int)&v24, 0, 1023);
      v21 = 0;
      memset_42D5E6((int)&v22, 0, 1023);
      v4 = GetDlgItemTextA(hDlg, 1001, &String, 1025);
      v25 = sub_42DE51(&v11 == &v11, v4);
      v19 = 0;
      memset_42D5E6((int)&v20, 0, 1023);
      j_debase_42D267((int)&String, 1024, (int)&v21);
      v17 = 0;
      memset_42D5E6((int)&v18, 0, 1023);
      j_debase_42D267((int)&v21, 1024, (int)&v19);
      j_trans_42D96A((int)&v19, (int)&v17, 1024);
      v16 = 3;
      j_sm3_hash_437E70((int)&v19, 3, (int)v15);
      for ( i = 0; i < 32; ++i )
        sprintf_42DF05((int)&v14[2 * i], "%02x", v15[i]);
      v5 = strlen_42D794((int)v14);
      v6 = &String + strlen_42D794((int)&String);
      v7 = strlen_42D794((int)v14);
      if ( !j_compare_42DB27((int)v14, (int)&v6[-v7], v5) )
      {
        sub_42D0B4();
        if ( (unsigned __int8)j_maze_435400((int)&dword_49B000, (int)&v17) == 1 )
        {
          v8 = MessageBoxA(0, "ok", "CrackMe", 0);
          sub_42DE51(&v11 == &v11, v8);
        }
      }
    }
  }
  sub_42D65E(&savedregs, &dword_435250);
  v9 = sub_42D1E5((unsigned int)&savedregs ^ v29);
  return sub_42DE51(1, v9);
}

明显此处应该是主要流程所在了。

算法分析

通过仔细分析,此处有4个算法,一是定制的base64解码算法,一是SM3 hash算法,一是替换算法,一是走迷宫算法。

base64 解码算法

此解法是与通常解法不同之处在于,取编码字符在base64常串中的位置偏移使用编码字符直接索引数组,数组如下:

56 57 58 59 5A 61 62 63  64 65 66 67 68 69 6A 6B
6C 6D 6E 6F 70 71 72 73  74 75 76 77 78 79 7A 30
31 32 33 34 35 36 37 38  39 2B 2F 3E FF FF FF 3F
34 35 36 37 38 39 3A 3B  3C 3D FF FF FF FF FF FF
FF 00 01 02 03 04 05 06  07 08 09 0A 0B 0C 0D 0E
0F 10 11 12 13 14 15 16  17 18 19 FF FF FF FF FF
FF 1A 1B 1C 1D 1E 1F 20  21 22 23 24 25 26 27 28
29 2A 2B 2C 2D 2E 2F 30  31 32 33 00 00 00 00 00

这个数组使部分非base64字符也能解码。虽然对于索引进行了>=0x2b的限制,但由于文中进行了两次base64解码,使第一次解码后末位字符为\x00--\x2a,就会产生基本一致的多解,如;

TDkA2465b4d68ca27b9210e4aa31e0aa8da7e618e79f2d56c4326fede74b16e2b667
TDkB2465b4d68ca27b9210e4aa31e0aa8da7e618e79f2d56c4326fede74b16e2b667
TDkC2465b4d68ca27b9210e4aa31e0aa8da7e618e79f2d56c4326fede74b16e2b667
...

当然这种解码算法比较强悍,鲁棒性好。

SM3 hash算法

算法过程请百度,这里只说解题怎么应对。
题目中将两次base64解码后输入的前3字节进行hash计算,然后比较hash结果与输入的后64字节。除此之外还有一个校验,为了简便计算,我们可以把64字节的比较输入始终附加在原有输入后面。

替换算法

此算法是由两次base64解码后的字符,通过改写一个8byte的字串s,然后与规则对照进行替换,替换规则如下:

2D 2D 2D 2D 2D 0
2E 2D 2D 2D 2D 1
2E 2E 2D 2D 2D 2
2E 2E 2E 2D 2D 3
2E 2E 2E 2E 2D 4
2E 2E 2E 2E 2E 5
2D 2E 2E 2E 2E 6
2D 2D 2E 2E 2E 7
2D 2D 2D 2E 2E 8
2D 2D 2D 2D 2E 9

2E 2D 2A 2A a
2D 2E 2E 2E b
2D 2E 2D 2E c
2D 2E 2E 2A d
2E 2A 2A 2A e
2E 2E 2D 2E f
2D 2D 2E 2A g
2E 2E 2E 2E h
2E 2E 2A 2A i
2E 2D 2D 2D j
2D 2E 2D 2A k
2E 2D 2E 2E l
2D 2D 2A 2A m
2D 2E 2A 2A n
2D 2D 2D 2A o
2E 2D 2D 2E p
2D 2D 2E 2D q
2E 2D 2E 2A r
2E 2E 2E 2A s
2D 2A 2A 2A t
2E 2E 2D 2A u
2E 2E 2E 2D v
2E 2D 2D 2A w
2D 2E 2E 2D x
2D 2E 2D 2D y
2D 2D 2E 2E z

当前输入为\x2f时,则目标值为\x20;若当前为\x20时,则用规则进行替换;若为其它字符,则改写s。一次替换后,s还原默认状态。

迷宫算法

这部分应该是一个10X10的深宫走法算法,深宫如下:

0 1 1 1 1 1 1 1 1 0
0 0 1 1 1 1 1 0 0 0
1 0 0 0 0 0 1 0 1 1
1 1 1 1 1 0 1 0 0 1
1 0 0 0 1 0 1 0 0 1
1 0 1 0 0 0 1 0 1 1
1 0 1 1 1 1 1 0 0 1
1 0 0 0 0 1 1 1 0 0
1 1 1 1 0 0 0 0 1 0 
1 1 1 1 1 1 1 0 0 0

其中的0为可行路径。题目中,替换后的输入字符为q z分别表示向上一行和向下一行;p l分别表示向左一格和向右一格。
[3,8]为死路。若输入为\x20,则结束,返回True;若走错,则返回False,验证失败。
这里并没有对路径完成量进行约束,也产生了多解可能。这里,我们为简化,可以直接让替换后的第一个字符为\x20

反调试

这题反调试都是常规做法,但是比较多。主要方式有:API调用查询调试状态,如CheckRemoteDebuggerPresentZwQueryInformationProcessIsDebuggerPresent等;枚举窗体名;枚举进程;通过打开设备检查调试类驱动;抛出异常等方式。
这些IDA简单patch下就好

反算

上面说了,为了简化,最后替换完成后这有一个\x20就行,那替换前那就是\x2f。还原到第一次debase后,这也会产生多解,如L1-L9;再进行base64还原,得到TDETDk等。因为后面还要附加上hash串,所以要在hash串前结束base64的解码动作,方法有三个:第一是让等解码字符小于\0x2b或大于\x7f,这一般在第二次解码时利用;第二是让等解码字符索引base64解码表时落入\xff;两次解码时都能利用;第三是等解码字符为=,两次解码时都能利用。但是因为比赛规则是不能有符号,所以有些情况用不了。
注册码为base64最终的编码串附加上hash,这两者之间也可以附加任何字串,也是产生多解的另一个小地方,这个最终还是因为解base时遇到不恰当的字符就中止解码。

 

最后附上一组我用的:

TDkT2465b4d68ca27b9210e4aa31e0aa8da7e618e79f2d56c4326fede74b16e2b667

[CTF入门培训]顶尖高校博士及硕士团队亲授《30小时教你玩转CTF》,视频+靶场+题目!助力进入CTF世界

收藏
点赞1
打赏
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回