-
-
[原创] 看雪·2026 KCTF 第八题:亥子合辰·塔影迷楼
-
发表于: 3天前 16
-
赛马抽奖,开了两个 ChagptGPT 5.5 High 网页、一个本地 Codex GPT 5.5 High、一个本地 Codex GPT 5.6 Terra High 同时跑,最终一个网页20min率先解出获胜。5.6 Sol Cyber拒答,5.6 Terra虽然也触发了外审,但至少还能回来继续。
共消耗了25%的Plus周额度,绝对缩水了,Tibo的x.com下面一堆人在骂
(人类)
分隔线
(AI)
kctf2026 CrackMe08 静态分析报告
1. 分析目标
本次分析目标是对样本 kctf2026_CrackMe08.exe 进行静态逆向分析,恢复程序的输入校验逻辑,并找出能够通过校验的正确输入。
最终得到的正确输入为:
kanxue@2o26o8!@#
注意:其中 2o26o8 中的两个 o 是小写英文字母 o,不是数字 0。
2. 样本概况
| 项目 | 内容 |
|---|---|
| 文件名 | kctf2026_CrackMe08.exe |
| 文件类型 | Windows PE 可执行文件 |
| 分析方式 | 静态分析为主 |
| 是否使用模拟器 | 否 |
| 是否直接动态执行主程序 | 否 |
| 目标 | 还原输入校验逻辑并求出正确输入 |
3. 总体分析结论
程序的校验逻辑并不是直接暴露在主函数中,而是经过了多层隐藏与加密处理。
整体流程可以概括为:
用户输入
↓
长度检查
↓
字节和检查
↓
base-94 编码映射
↓
模 p = 2^127 - 39 的域运算
↓
多轮二次型变换
↓
两段 20 轮 Feistel / 查表变换
↓
与目标状态比较
↓
输出成功或失败
最终逆向还原后,可以确定唯一可打印输入为:
kanxue@2o26o8!@#
4. 静态分析过程
4.1 初步定位
对 PE 文件进行静态检查后,发现主程序并未直接包含完整的明文校验逻辑。程序中存在一段被简单加密的数据,运行时会先进行解密,再跳转或调用解密后的代码。
第一层解密逻辑的特征较明显,主要使用固定异或常量:
xor key = 0x5A
因此可以通过静态方式直接对对应字节区域进行还原,得到第一层解密后的代码。
4.2 第一层解密
第一层解密后的代码并不是最终校验逻辑,而是继续负责释放或解密第二层核心校验函数。
该阶段说明程序采用了分层隐藏方式:
原始程序
↓
第一层 XOR 解密
↓
第二层校验函数解密
↓
真实校验逻辑
这种结构的作用是干扰直接字符串搜索和简单反汇编,使真正的校验逻辑不会直接出现在初始代码段中。
4.3 第二层校验函数恢复
第二层校验代码同样以字节流形式存在,需要通过还原其解密逻辑得到真实指令。
恢复后可以看到,核心校验函数并不是简单的逐字符比较,而是将输入整体映射为一个大整数,再进行复杂数学变换。
5. 输入基础约束
恢复校验逻辑后,首先可以得到两个非常明确的输入约束。
5.1 长度约束
输入长度必须为:
16 字节
即:
strlen(input) == 16
5.2 字节和约束
程序还检查输入所有字节的 ASCII 值之和,要求其等于:
0x500
换算为十进制:
1280
对最终答案进行验证:
kanxue@2o26o8!@#
逐字节 ASCII 求和:
| 字符 | ASCII |
|---|---|
| k | 107 |
| a | 97 |
| n | 110 |
| x | 120 |
| u | 117 |
| e | 101 |
| @ | 64 |
| 2 | 50 |
| o | 111 |
| 2 | 50 |
| 6 | 54 |
| o | 111 |
| 8 | 56 |
| ! | 33 |
| @ | 64 |
| # | 35 |
求和结果:
107 + 97 + 110 + 120 + 117 + 101 + 64 + 50
+ 111 + 50 + 54 + 111 + 56 + 33 + 64 + 35
= 1280
= 0x500
满足字节和条件。
6. 核心算法结构
6.1 base-94 输入映射
通过静态还原可以发现,输入字符并不是被逐个独立校验,而是先被转换为一个大整数。
该转换使用类似 base-94 的编码方式,通常对应可打印 ASCII 字符范围。
逻辑可抽象为:
x = 0
for each char c in input:
x = x * 94 + mapped_value(c)
其中 mapped_value(c) 通常与可打印字符区间相关。
这一步的作用是将 16 字节输入压缩成一个整体数值,使后续校验不再表现为简单的字符级判断。
6.2 模域运算
输入映射得到的大整数随后进入有限域运算,使用的模数为:
p = 2^127 - 39
即所有核心运算都在如下模域中进行:
mod p
该模数接近 128 位规模,能够有效避免简单枚举或直接逆推。
6.3 多轮二次型变换
在有限域中,程序对输入映射值进行多轮二次型运算。
从恢复出的逻辑看,该部分可以抽象为:
x = F1(x)
x = F2(x)
x = F3(x)
x = F4(x)
x = F5(x)
每一轮 Fi 都包含平方、乘法、加法以及模约减操作。
这类结构的目的通常是将输入空间打散,使输入与最终比较值之间不存在明显线性关系。
6.4 Feistel / 查表变换
在有限域运算之后,程序又进入两段 20 轮结构化变换。
其特征类似 Feistel 网络,并伴随查表操作:
for round in 0..19:
L, R = R, L ^ f(R, round_key)
for round in 0..19:
L, R = R, L ^ g(R, round_key)
Feistel 结构的特点是:
- 即使轮函数不可逆,整体结构仍然可逆;
- 每轮都会交换左右状态;
- 可通过反向轮序还原前一状态。
因此,在静态恢复出轮函数和常量表后,可以对目标状态进行逆变换,逐步还原输入对应的中间值。
7. 求解思路
由于程序的校验结构整体可逆,因此不需要暴力枚举完整 16 字节输入。
求解过程如下:
目标比较状态
↓
逆 Feistel 第二段
↓
逆 Feistel 第一段
↓
逆有限域二次型变换
↓
恢复 base-94 数值
↓
转回 16 字节可打印字符串
在恢复出的候选结果中,满足以下条件的唯一输入为:
kanxue@2o26o8!@#
该输入同时满足:
长度 == 16
字节和 == 0x500
字符均为可打印字符
通过还原出的完整校验逻辑
8. 结果验证
8.1 长度验证
kanxue@2o26o8!@#
字符数:
16
满足长度要求。
8.2 字节和验证
ASCII 求和:
0x500
满足字节和要求。
8.3 算法验证
将该输入代入静态还原出的校验算法:
输入 → base-94 映射 → 模域运算 → 多轮 Feistel / 查表变换
最终得到的状态与程序内置目标状态一致,因此判断该输入可以通过校验。
9. 正确输入
最终正确输入为:
kanxue@2o26o8!@#
特别注意:
kanxue@2o26o8!@#
^ ^
o o
这两个字符是小写字母 o。
10. 总结
该 CrackMe 的主要保护点包括:
多层代码加密
主程序中没有直接暴露真实校验逻辑,需要先还原两层解密代码。整体输入映射
输入不是逐字符比较,而是被映射为一个大整数。有限域复杂运算
程序使用模2^127 - 39的大数域运算,增加了静态分析难度。Feistel 结构混淆
多轮 Feistel / 查表变换进一步隐藏输入与目标状态之间的关系。可逆结构可被静态求解
虽然算法复杂,但其核心结构可逆,因此通过静态还原和逆向推导可以直接得到正确输入。
最终得到的 flag / 正确输入为:
kanxue@2o26o8!@#
冰与火的战歌:Windows内核攻防实战高级班!从零到实战,融合AI与Windows内核攻防全技术栈,打造具备自动化能力的内核开发高手。