首页
社区
课程
招聘
[原创]wp:第八题:亥子合辰·塔影迷楼
发表于: 2026-8-21 12:51 14

[原创]wp:第八题:亥子合辰·塔影迷楼

2026-8-21 12:51
14

1. 程序结构

程序是 64 位 PE,主要逻辑不在明文代码里,而是分两段自解密:

第一层解密

入口 start

  • 取模块基址;
  • image + 0x1800 开始的 0x510 字节异或 0x5A
  • VirtualProtect 改成可写执行;
  • FlushInstructionCache 刷新指令缓存。

对应代码区域:

0x140001800 .. 0x140001D0F

之后程序:

  1. 输出 Input:
  2. 读取最多 63 字节;
  3. 去掉结尾 \r\n
  4. 要求输入长度必须为 16;
  5. 调用第一层里的校验包装函数 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!@#

传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!!

收藏
免费 0
打赏
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回