ISCC2026 WriteUp
题目类型:Reverse
题目名称:手忙脚乱(新附件 attachment-62.exe)
解题思路
### 1. 初步信息收集
拿到附件 `attachment-62.exe`,首先确认文件类型:
这是一个 64 位 Windows 控制台程序。通过 strings 提取关键字符串:
input 24 char plaintext:
len error
PASS
FAIL
transpose: length mismatch
以及关键函数符号:
decrypt_blob
columnarTransposeEncrypt
encryptPart2
makeTable
hdb3
这表明程序内部包含列换位加密、替换表加密和 HDB3 编码校验等操作。从符号名可以推断出多个加密常量以局部静态变量的形式存放。
### 2. 定位加密常量
通过 objdump 反汇编,定位到 main 函数中 6 处 `decrypt_blob` 调用。这些调用各自引用一个局部静态常量,并传入不同的解密种子:
| 常量名 | 大小 | 种子 | .rdata 偏移 |
|——–|——|——|————-|
| CT_K6 | 6 字节 | 0x5555 | 0x617b |
| CT_K3 | 3 字节 | 0x6666 | 0x6178 |
| CT_MASK1 | 24 字节 | 0x3333 | 0x6120 |
| CT_TARGET | 24 字节 | 0x1111 | 0x6160 |
| CT_MASK2 | 24 字节 | 0x4444 | 0x6100 |
| CT_TARGET_T | 24 字节 | 0x2222 | 0x6140 |
对应的汇编代码确认各常量大小(以 CT_MASK1 为例):
mov \$0x3333,%r9d ; seed
mov \$0x18,%r8d ; size = 24
lea 0x28a8(%rip),%rdx ; → CT_MASK1
call decrypt_blob
从 PE 文件中按偏移提取原始密文字节。
### 3. 还原 decrypt_blob 算法
`decrypt_blob` 内部基于一个自定义的流密码:状态变量通过乘法、移位和异或不断更新,每轮产生一个密钥字节与密文异或,再经过循环右移和最终异或得到明文。
还原后的 Python 实现核心逻辑为:
def decrypt_blob(ct: bytes, seed: int) -> bytes:
state = seed \^ 0xA3B1C2D3
acc = 0
ctr = 0
out = bytearray()
for b in ct:
x = (state + acc) & 0xFFFFFFFF
acc = (acc - 0x61C88647) & 0xFFFFFFFF
state = x
state \^= (state << 13) & 0xFFFFFFFF
state \^= state >> 17
state \^= (state << 5) & 0xFFFFFFFF
mix = (ctr + ((state * 8) & 0xFFFFFFFF) - state) & 0xFFFFFFFF
ctr = (ctr + 11) & 0xFFFFFFFF
v = (mix & 0xFF) \^ b
v = ror8(v, state >> 27)
v \^= state & 0xFF
out.append(v & 0xFF)
return bytes(out)
解密 6 个常量得到:
K6 = `B0`o!*`
K3 = `Re$`
MASK1、TARGET、MASK2、TARGET_T 各为 24 字节的中间值
### 4. 分析主校验流程
`main` 函数是一个两轮交互循环:
第一轮:读入 24 字节输入 → 用 `columnarTransposeEncrypt(K6, K3)` 的结果作为列换位 key 对输入做列换位 → 将结果送入 `encryptPart2`(基于 K6 的 bit 流驱动双表替换)→ 将输出与 MASK1 异或 → 与 TARGET 比较。通过后设置状态标志位,回到输入循环。
第二轮:相同的前置变换 → 输出与 MASK2 异或后再与 TARGET_T 异或 → 结果转为 bit 流做 HDB3 编码统计 → 与全零串的 HDB3 统计比较。若令 `Buf2 = 0`(即第二轮加密结果 = MASK2 xor TARGET_T),则 HDB3 统计自然相同,无需触及 HDB3 编码约束。
第一轮目标:
wanted1 = MASK1 xor TARGET
第二轮目标:
wanted2 = MASK2 xor TARGET_T
### 5. 逆向构造输入
`encryptPart2` 可逆。其内部流程为:
1. `makeTable(key, 0x1234)` 生成替换表 table0
2. `makeTable(key, 0x8888)` 生成替换表 table1
3. 遍历 key 的 bit 流,bit=0 用 table0 替换当前字符,bit=1 用 table1 替换;每满 24 字节做一次列换位
逆向时从后往前遍历 bit 流:在列换位边界处先逆列换位,再用逆向替换表还原字符。逆替换表直接对 table0/table1 建立 `{密文字节: 原始偏移+0x20}` 的映射。
再对 `encryptPart2` 的输出做逆列换位(使用 trans_key = columnarEncrypt(K6, K3) = `` `*Bo0! ``),即得到原始 24 字节输入。
整体逆向链:
wanted → inverse encryptPart2 → inverse columnar transpose → plaintext
### 6. 求解结果
运行脚本得到两行 24 字节明文:
第一轮(状态推进):@"9w3Cv).e,'7>khG-[Vg-0L
第二轮(最终校验):R_0ElnXnl5d(u9q@(48MN\`d\`
向程序依次输入这两行,第二轮输出 `PASS`。
按 ISCC 平台提交格式包裹 flag:
ISCC{R_0ElnXnl5d(u9q@(48MN\`d\`}
Exp
\#!/usr/bin/env python3
from __future__ import annotations
def ror8(x: int, c: int) -> int:
c &= 7
x &= 0xFF
if c == 0:
return x
return ((x >> c) | ((x << (8 - c)) & 0xFF)) & 0xFF
def decrypt_blob(ct: bytes, seed: int) -> bytes:
state = seed \^ 0xA3B1C2D3
acc = 0
ctr = 0
out = bytearray()
for b in ct:
x = (state + acc) & 0xFFFFFFFF
acc = (acc - 0x61C88647) & 0xFFFFFFFF
state = x
state \^= (state << 13) & 0xFFFFFFFF
state \^= state >> 17
state \^= (state << 5) & 0xFFFFFFFF
mix = (ctr + ((state * 8) & 0xFFFFFFFF) - state) & 0xFFFFFFFF
ctr = (ctr + 11) & 0xFFFFFFFF
v = (mix & 0xFF) \^ b
v = ror8(v, state >> 27)
v \^= state & 0xFF
out.append(v & 0xFF)
return bytes(out)
def make_table(key: bytes, seed: int) -> bytes:
table = list(range(0x20, 0x7F))
state = seed & 0xFFFFFFFF
for b in key:
state = (state * 0x83 + b) & 0xFFFFFFFF
for i in range(94, 0, -1):
state = (state * 0x41C64E6D + 0x3039) & 0xFFFFFFFF
j = state % (i + 1)
table[i], table[j] = table[j], table[i]
return bytes(table)
def column_order(key: bytes) -> list[int]:
return sorted(range(len(key)), key=lambda i: (key[i], i))
def columnar_encrypt(data: bytes, key: bytes) -> bytes:
cols = len(key)
assert len(data) % cols == 0
rows = len(data) // cols
return bytes(data[r * cols + c] for c in column_order(key) for r in range(rows))
def columnar_decrypt(ct: bytes, key: bytes) -> bytes:
cols = len(key)
assert len(ct) % cols == 0
rows = len(ct) // cols
grid = [[0] * cols for _ in range(rows)]
p = 0
for c in column_order(key):
for r in range(rows):
grid[r][c] = ct[p]
p += 1
return bytes(grid[r][c] for r in range(rows) for c in range(cols))
def to_bits(data: bytes) -> list[int]:
return [(b >> shift) & 1 for b in data for shift in range(7, -1, -1)]
def encrypt_part2(data: bytes, key: bytes) -> bytes:
table0 = make_table(key, 0x1234)
table1 = make_table(key, 0x8888)
s = bytearray(data)
pos = 0
for bit in to_bits(key):
s[pos] = (table1 if bit else table0)[s[pos] - 0x20]
pos += 1
if pos == len(s):
s = bytearray(columnar_encrypt(bytes(s), key))
pos = 0
return bytes(s)
def decrypt_part2(ct: bytes, key: bytes) -> bytes:
table0 = make_table(key, 0x1234)
table1 = make_table(key, 0x8888)
inv0 = {c: i + 0x20 for i, c in enumerate(table0)}
inv1 = {c: i + 0x20 for i, c in enumerate(table1)}
s = bytes(ct)
bits = to_bits(key)
length = len(s)
for i in range(len(bits) - 1, -1, -1):
if (i + 1) % length == 0:
s = columnar_decrypt(s, key)
pos = i % length
arr = bytearray(s)
arr[pos] = (inv1 if bits[i] else inv0)[arr[pos]]
s = bytes(arr)
return s
def bxor(a: bytes, b: bytes) -> bytes:
return bytes(x \^ y for x, y in zip(a, b))
# 从 attachment-62.exe 提取的密文常量
CT_K6 = bytes.fromhex("1855a378c86c")
CT_K3 = bytes.fromhex("67cedd")
CT_MASK1 = bytes.fromhex("4b5d1c5e410f506bed0c079a18b04c7c09c8d74957090771")
CT_TARGET = bytes.fromhex("8cad03aa6a32b926158f7c95eacadd159400412da55643ac")
CT_MASK2 = bytes.fromhex("cce7e28169d8ffcd64d27a929fbdb6105cb0137a67e78359")
CT_TARGET_T = bytes.fromhex("cce1a2150c88288ca52d9302724bd6b687512c9b7c4f4faa")
def main() -> None:
k6 = decrypt_blob(CT_K6, 0x5555)
k3 = decrypt_blob(CT_K3, 0x6666)
trans_key = columnar_encrypt(k6, k3)
mask1 = decrypt_blob(CT_MASK1, 0x3333)
target1 = decrypt_blob(CT_TARGET, 0x1111)
wanted1 = bxor(mask1, target1)
mask2 = decrypt_blob(CT_MASK2, 0x4444)
target2 = decrypt_blob(CT_TARGET_T, 0x2222)
wanted2 = bxor(mask2, target2)
round1 = columnar_decrypt(decrypt_part2(wanted1, k6), trans_key)
round2 = columnar_decrypt(decrypt_part2(wanted2, k6), trans_key)
assert encrypt_part2(columnar_encrypt(round1, trans_key), k6) == wanted1
assert encrypt_part2(columnar_encrypt(round2, trans_key), k6) == wanted2
print(f"K6 = {k6.decode()}")
print(f"K3 = {k3.decode()}")
print(f"key = {trans_key.decode()}")
print(f"stage1 plaintext = {round1.decode()}")
print(f"final plaintext = {round2.decode()}")
print(f"submit = ISCC{{{round2.decode()}}}")
if __name__ == "__main__":
main()
评论