-
-
[原创]wp:第八题:亥子合辰·塔影迷楼
-
发表于: 2026-8-21 12:51 14
-
1. 程序结构
程序是 64 位 PE,主要逻辑不在明文代码里,而是分两段自解密:
第一层解密
入口 start:
- 取模块基址;
- 对
image + 0x1800开始的0x510字节异或0x5A; VirtualProtect改成可写执行;FlushInstructionCache刷新指令缓存。
对应代码区域:
0x140001800 .. 0x140001D0F
之后程序:
- 输出
Input:; - 读取最多 63 字节;
- 去掉结尾
\r\n; - 要求输入长度必须为 16;
- 调用第一层里的校验包装函数
0x1400018D0。
2. 第二层解密
第一层函数 0x1400018D0 会先对第一层明文代码做两个 FNV-1a 风格的 64 位哈希:
h1 = 0xCBF29CE484222325;
h2 = 0x9E3779B97F4A7C15;
for (b in layer1) {
h1 = (h1 ^ b) * 0x100000001B3;
h2 = (h2 ^ b) * 0x100000001B3;
}
得到:
h1 = 0x73FB4F498AAB364F
h2 = 0xB157C7E044B966DF
然后用这两个哈希生成逐字节密钥流,解密:
image + 0x1D10
size = 0x4962
即:
0x140001D10 .. 0x140006671
单字节解密逻辑可写成:
ctr = i * 0x9E3779B97F4A7C15
x = ctr ^ h1 ^ h2
x ^= x >> 33
x *= 0xFF51AFD7ED558CCD
key = x >> 56
plain[i] = enc[i] ^ key
第二层入口是:
0x140001D10
它根据输入首字节查表,但表里的 8 个函数指针全部相同,最终都进入:
0x140004380
3. 输入约束
校验函数首先计算 16 个输入字节的和:
sum(input[i]) == 0x500
也就是:
sum = 1280
之后输入被编码成一个有限域元素:
d[i] = input[i] - 33
s = 0
for i in range(16):
s = (s * 94 + d[i]) % p
其中:
p = 2^127 - 39
由于 byte - 33,可打印字符 [33,126] 正好被映射成 base-94 数字。
4. 有限域运算
大量看起来很复杂的 128 位运算,本质上都是模:
p = 2^127 - 39
汇编中反复出现的:
39
78
0x7FFFFFFFFFFFFFFF
0xFFFFFFFFFFFFFFD9
含义是:
2^128 mod p = 78
p = 2^127 - 39
所以这些代码是在做:
- 128 位加法取模;
- 128 位乘法取模;
- 条件约减。
5. PRNG 与轮密钥
程序利用两个哈希值和固定常量生成 86 个 64 位伪随机数 P[0..85]。
其中:
P[0..9]:组成 5 个有限域多项式系数;P[10..11]:组成乘法常数;P[12..43]:构造 256 字节 S-box;P[44..63]:第一组 20 轮密钥;P[64..83]:第二组 20 轮密钥;P[84..85]:和.data常量异或,得到最终期望密文和哈希。
轮函数是一个两轮结构的 Feistel-like 变换,核心形式为:
t = ((H << 64 | k) % p) * M % p
y = lo64(t) ^ k ^ ror64(hi64(t), 47)
H2 = L ^ S(y) ^ ror64(y, 51)
k2 = k ^ 0x9E3779B97F4A7C15
u = ((H2 << 64 | k2) % p) * M % p
z = lo64(u) ^ k2 ^ ror64(hi64(u), 47)
L2 = H ^ S(z) ^ ror64(z, 51)
其中 S(y) 是对 y 的 8 个字节分别查 S-box。
由于 Feistel 结构可逆,因此可以从最终期望值反向推出多项式输出目标。
6. 反推目标值
反向执行两组 20 轮变换后,两者得到相同的中间状态:
H = 0x4BE831B0AD3A2D36
L = 0x1489375BBA3FB8DE
合并成有限域元素:
target = 0x4BE831B0AD3A2D361489375BBA3FB8DE
7. 解五次多项式
由 PRNG 得到的五个系数为:
c0 = 0x21B3BDE1ACF9ADFA471FB28461A53BB5
c1 = 0x191A3870AD3D941105F15A9C38AAE874
c2 = 0x40C190E211322CAA2F01797348F02C97
c3 = 0x1DF732140B8A3EF758FD09BDE0058A37
c4 = 0x3F0CB8512C6F88E47E5F164FB478AAB5
需要解:
((((s + c0) * s + c1) * s + c2) * s + c3) * s + c4
= target (mod p)
也就是模 p 下的五次方程。
对多项式在 GF(p) 上因式分解,可得到一个线性根:
s = 0x174A5E1D4CF4F146E72F70092AC
8. 还原输入
因为 s 是 16 位 base-94 编码,并且每个字节满足:
input[i] = digit[i] + 33
转换代码:
s = 0x174A5E1D4CF4F146E72F70092AC
digits = []
for _ in range(16):
digits.append(s % 94)
s //= 94
flag = bytes(x + 33 for x in digits[::-1])
print(flag)
得到:
kanxue@2o26o8!@#
校验和也满足:
sum(b"kanxue@2o26o8!@#") == 1280
最终答案:
kanxue@2o26o8!@#
赞赏
他的文章
赞赏
雪币:
留言: