除了去花以及最后的flag比较算法挺麻烦的,这道题其实还好

官方wp:https://mp.weixin.qq.com/s/rMh-hABCdGpYpGqtFFiKOA

ida先打开程序发现加了壳,遂脱壳

脱壳后,发现程序加了花,各种无意义跳转

许多类似这样的跳转

1
2
3
4
5
6
7
8
9
10
11
12
13
14
text:12341487                 call    loc_12341490
.text:12341487 ; ---------------------------------------------------------------------------
.text:1234148C db 77h ; w
.text:1234148D ; ---------------------------------------------------------------------------
.text:1234148D jmp short loc_12341496
.text:1234148D ; ---------------------------------------------------------------------------
.text:1234148F db 88h
.text:12341490 ; ---------------------------------------------------------------------------
.text:12341490
.text:12341490 loc_12341490: ; CODE XREF: sub_123413B0+D7↑p
.text:12341490 db 36h
.text:12341490 add dword ptr [esp+0], 1
.text:12341495 retn
.text:12341496 ; ---------------------------------------------------------------------------

对于没有经验的人来说,最方便的方法是手动patch,奈何这里的花太多,patch了许久

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
int sub_123413B0()
{
unsigned int v0; // ST40_4
unsigned int v1; // ST44_4
char *Buf1; // [esp+6Ch] [ebp-Ch]

nullsub_11();
sub_12341340(0);
v0 = strlen("npointer{");
v1 = strlen("}");
Buf1 = (char *)sub_123416AD(v0 + v1 + 33);
nullsub_12();
sub_12341020("Input the correct keys: ");
nullsub_13();
sub_12341050("%s", Buf1, v0 + v1 + 33);
if ( !memcmp(Buf1, "npointer{", strlen("npointer{"))
&& Buf1[strlen(Buf1) - 1] == asc_12343738[0]
&& (Buf1[strlen(Buf1) - 1] = 0, sub_12341090(&unk_12343798, 0x30D3u, (int)&Buf1[strlen("npointer{")])) )
{
sub_12341340(1);
sub_12341020("Congrats!\n");
}
else
{
sub_12341020("Sorry, the gate remains closed.\n");
}
return system("pause");
}

明白我们的输入是npointer{32字节}

unk_12343798是代码。

1
2
3
4
5
6
text:123411D3 sub_123411D3    proc near               ; CODE XREF: sub_123411C7+6↑p
.text:123411D3 add esp, 4
.text:123411D6 add edi, [ebp-14h]
.text:123411D9 xor ecx, ecx
.text:123411DB call dword ptr [ebp-14h]
.text:123411DE jmp short loc_123411E3

此处会调用它

进入代码后,发现是这样一个逻辑:

  • 按顺序取花括号内字符,与硬编码到指令的字符进行比较
  • 第一个比较很直接,如果第一个字符是’f’,则继续,若非,则失败
  • 如果继续,在执行到retf之后,cs部分会变成0x33,然后进入到push到栈中的下一个指令的地址,但是一般的调试器在执行这一步的时候会因为检测到异常而停止程序……
  • 程序在cs是0x23的时候表示32位模式,cs是0x33的时候表示是64位模式
  • 所以这段代码就是64位和32位循环交替来对每一位进行比较,比如第一位32位模式下比较,第二位64位模式下比较,以此循环

由于32位和64位代码不一样,所以可以把代码dump出来,copy一份,一份ida32打开它,一份ida64打开它,这样分析会比较方便

但是由于分支众多,所以如何才能成功呢?

经过观察,我发现,每次分叉的时候,都只有一条路径是之前没执行过的,其他路径要么执行过,要么跳入失败的函数,所以我们只需要提前分析每条路径的下一次跳转到了什么地方,然后找到一条不在原地转圈的通路就可以了

在偏向后期的时候,遇到一个三叉路口,但只能否定其中一个,因此我两个都测试了一下,发现只有中间的那个je可以直通终点

通到终点之后,继续运行程序就会告诉你成功了

flag就是所有的输入