清明节的时候参加了一下西湖论剑杯,我主要做了 re。其实这次的 re 并不难,但是比赛的时候心态炸了,没能静下心来做题。不过我也好久没做题了,趁着这次比赛整理一下思路。

easyCpp

这是一道比较基本的 Cpp 逆向。

拖到 IDA 中看它的大概逻辑,处理后的 vector 和一个生成好的斐波那契数列进行比较。我们专注于它怎么处理输入就好啦。

稍微整理了一下逻辑,大概是这样的:

  • 初始化输入的数组(input)和斐波那契数列

  • 新建一个数列,我们可以称它为 global 数组。其中数据的初始化是这样的:

    • 首先 push_back input[0]

    • global[1] 开始按照 transform 初始化数据,transform 有一个内联的 lambda,分析之后可以写成这个样子:

      1
      
      transform(input.begin()+1,input.end,back_insert(global),[](int x){return x+input[0];});
      

      也就是说输入的第二个数开始和输入的第一个数求和然后塞到 global 中。

  • 接下来它用 accumulate 函数做了一个对换的操作,其结果是倒转了 global 数组。

    PS:这里我刚开始没搞懂是什么意思,这里分析一下还是蛮有意思的。STL 里的骚操作不要太多,我感觉我根本不会 C++。。

  • 然后与之前初始化的斐波那契数列进行比较。

  • 成功了,输出答案,这一段我就没分析了。

综上,输入为

1
987 -377 -610 -754 -843 -898 -932 -953 -966 -974 -979 -982 -984 -985 -986 -986

的时候答案正确,结果是:flag{987-377-843-953-979-985}

Testre

这是一道水题,我上数据段直接看到了 base64 和 base58 的 table。然后它还有一个 fake_secret_makes_you_annoyed,和输入进行了轮异或,果然没什么用。它的比较字符串提出来再 base58 decode 就可以得到 base58_is_boring

当然了。。能够一眼看出来是十分重要的。可能需要总结一下常见加密/编码函数在逆向时出现的关键函数/关键字符串了。

Junk_Instruction

一道 MFC 的逆向,输入 flag 然后检测,会弹出窗口。

通过 Xspy 可以找到这里:

1
OnCommand: notifycode=0000 id=03e9,func= 0x00392420(Junk_Instruction.exe+ 0x002420 )

也就是弹窗函数的地址。我们在 IDA 中进入函数查看一下,地址就是 0x2420。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
if ( sub_402600(v2 + 16) )
  {
    sub_401BF0(&v6, v3);
    v7 = 0;
    v9 = 0;
    sub_403DC0(&v6, 129, 0);
    v6 = &CCorrect::`vftable';
    v16 = 0;
    sub_40484E(&v6);
    v6 = &CCorrect::`vftable';
    v16 = 2;
    sub_407B3B(&v10);
    LOBYTE(v16) = 1;
    v8 = &CBrush::`vftable';
    sub_401580(&v8);
  }

然后能看到判断函数的地址是 0x2600。

接着进去看到一堆奇怪的东西。里面规定了输入的长度是 38。

但是代码加花了,静态编译基本上看不出什么东西来。此时可以把花指令 patch 掉。

仔细阅读汇编代码可以发现一些有趣的汇编,摘录如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
.text:00402B6E                 call    $+5
.text:00402B73                 pop     eax
.text:00402B74                 mov     [ebp+var_28], eax
.text:00402B77                 call    loc_402B7F
.text:00402B77 ; ---------------------------------------------------------------------------
.text:00402B7C                 db 0EAh, 0EBh, 9
.text:00402B7F ; ---------------------------------------------------------------------------
.text:00402B7F
.text:00402B7F loc_402B7F:                             ; CODE XREF: sub_402AF0+87↑j
.text:00402B7F                 pop     ebx
.text:00402B80                 inc     ebx
.text:00402B81                 push    ebx
.text:00402B82                 mov     eax, 11111111h
.text:00402B87                 retn
.text:00402B88 ; ---------------------------------------------------------------------------
.text:00402B88                 call    loc_402B94
.text:00402B8D                 mov     ebx, 33333333h
.text:00402B92                 jmp     short loc_402BA1
.text:00402B94 ; ---------------------------------------------------------------------------
.text:00402B94
.text:00402B94 loc_402B94:                             ; CODE XREF: sub_402AF0+98↑p
.text:00402B94                 mov     ebx, 11111111h
.text:00402B99                 pop     ebx
.text:00402B9A                 mov     ebx, offset loc_402BA1
.text:00402B9F                 push    ebx
.text:00402BA0                 retn
.text:00402BA1 ; ---------------------------------------------------------------------------
.text:00402BA1
.text:00402BA1 loc_402BA1:                             ; CODE XREF: sub_402AF0+A2↑j
.text:00402BA1                                         ; DATA XREF: sub_402AF0+AA↑o
.text:00402BA1                 mov     ebx, 22222222h

基本上每一个带有 JUMPOUT() 的函数都会带上这么些个指令,我推测这一段指令就是万恶之源。我们可以用脚本 patch 掉它们。

提取这一段的特征如下:

1
2
3
4
E8 00 00 00 00 58 89 85 C4 FD FF FF E8 03 00 00
00 EA EB 09 5B 43 53 B8 11 11 11 11 C3 E8 07 00
00 00 BB 33 33 33 33 EB 0D BB 11 11 11 11 5B BB
75 29 40 00 53 C3 BB 22 22 22 22

其开头以 E8 00 00 00 00 开始,结尾以 22 22 22 22 结束,长度为 59。

接下来就可以 patch 掉了。

然后整个世界都变了,相比于之前来说很轻松的就能看出来它的逻辑了。通过一次 Map 的初始化和一次运算最后就能得到 flag 了。

总结

这次的比赛让我发现了自身的许多问题,有时间把 pwn 写一下吧。。