前言

LLM 还是有极限的,还是人脑子好使(虽然高考完三个月脑子就没动过 导致好久才转过来弯 悲

1. 分析

先用错误的 Flag 试一遍,看下程序输出
图片描述

拉进 IDA,发现这些字符串并不存在,那就直接拉进 x64dbg,在输入 flag 前按下暂停,从堆栈找 mainFunc (0x140057A20)
图片描述

通过对 mainFunc (0x140057A20) 伪代码 switch-case 结构的分析,得到下面的操作流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
[EnterPoint]
|
(0) 预处理+打印提示
|
(1) ?
| a1+4708 > 41
(2/3) 判断器 ───────────────> (4) 根据 a1 + 4576 的大小来决定输出 Wrong! / Correct!
|
| a1+4708 < 41
└────────┐
|
(5) 将用户输入映射为只有 0x2/0x3 的数组
|
v
+-------------------------+
| 数组处理 |
| (6) → (8) → (7) |
+-------------------------+
|
v
(9) check
|
└─────────────── 回到 (3)

其中,case 5 会在一开始将a1 + 4580全部初始化为 0x2

图片描述

然后将用户输入按照每 16 字符一次的顺序,将字符转化为 int (所以 a1 + 4580a1 + 4718 这两个的大小都是 16 * 8 = 128 byte)

图片描述

然后根据int对应的二进制将其映射,映射规则为 0 -> 0x2, 1 -> 0x3

图片描述

这就是一个把整数 a2 按位拆成长度为 a3 的二值序列的函数,且0 位写成字节 2,1 位写成字节 3。它从 LSB→MSB 逐位取模/整除 2,并把结果右对齐写进输出缓冲(索引是 width-1-pos),超出 a3 还剩下非零高位会被当成错误处理(设置特殊的 vtable/类型标记)。伪代码还原如下(变量名照反编译语义化):

1
2
3
4
5
6
7
8
9
10
11
// out = a1[0] 指向的对象内部数据区;width = a3
memset(out, 0, width);
int v = a2;
for (int pos = 0; pos <= width-1; ++pos) {
int bit = v % 2;
out[width-1 - pos] = (bit ? 3 : 2); // 3 表示1,2 表示0
v = v / 2;
}
if (v != 0) {
// 说明 a2 的有效位数 > width,走错误路径/设置特殊类型
}

最后把映射后的值填充到 a1 + 4580a1 + 4718 中, 我们称这个原始映射为 $p_1$

图片描述

图片描述

图片描述

在经过case 6/8/7后, $p_1$ 会根据一定计算生成最终用于比较的 $p_2$, 而 $p_2$ 的值是不等于 $p_1$ 的

图片描述

在case 9中,会将 $p_2$ 与预定好的 byte_1400C89D0 比较,如果比较结果 a1 + 4576 大于等于42,那么在case 4就会输出Correct!,反之输出 Wrong! (从 a1 + 4576 也可以看出 Flag 长度应该为 42)

图片描述

同时 case 3 -> case 5 -> case 6/8/7 -> case 9 -> case 3 的这个循环一共会经历三次,这个三次循环是由 a1 + 4708 决定的,而 a1 + 4708 由 case 9 赋值,可以得到 a1 + 4708 的变换为 0 -> 16 -> 32 -> 48 (跳入 case 4)

图片描述

图片描述

2. 调试+撞墙

在经历了12小时逆不出来 $p_1$ 到 $p_2$ 的算法之后,我人已经麻了(╬▔皿▔)╯

后来想了一下,有人能够1h就做出来,绝壁有简单的解法

在推 mainFunc 逻辑的时候,我猜想 $p_1$ 到 $p_2$ 的算法会不会是可逆的(不要管旁边那个 Yes, 那是做出来之后写的【】)
(另外是byte不是bit,半夜脑子不清楚了)

图片描述

于是我将 byte_1400C89D0 导出,用 Python 分割为 128 byte 的三个片段,在 x64dbg 动调的时候把 a1 + 4580 里面的映射替换掉

图片描述

在 x64dbg 里面选中内存,Ctrl+E 进入编辑页面,将片段直接粘贴保存

图片描述
图片描述

最后在 case 9 dump这段 $p_1$ 对应的 $p_2$
图片描述

(上为 $p_1$,下为 $p_2$)
图片描述

最后将 $p_2$ 转化为字符串,得到 Flag
图片描述
图片描述

于是我们也可以得到一个结论,即 $p_1$ 到 $p_2$ 是可逆的,可以从已知的 $p_2$ 倒退回用户输入的 $p_1$

最后可得到 Flag 为 KCTF{84e3229c-310b-4a9b-9977-b20db689d701}

3. 总结

中间有几道题没做是因为偷懒了,也没进前 10(我的T恤啊啊啊啊啊啊啊
(首)

部分图片本地没保存,从看雪那里扒回来的,有水印见谅ㄟ( ▔, ▔ )ㄏ