2026蓝桥杯 Reverse WriteUp

2026 年 4 月 25 日 星期六
22
1

2026蓝桥杯 Reverse WriteUp

题目信息

附件包含两个文件:

$ file veil_gate panel.dat
veil_gate: ELF 64-bit LSB pie executable, x86-64, dynamically linked, stripped
panel.dat: data

运行程序会读取当前目录下的 panel.dat,然后从标准输入读取 flag:

$ ./veil_gate
xxx
no

正确输入时输出:

ok

最终 flag:

flag{9d7a228ca5825bc15cd60bee0bb6d585}

1. 初步分析

strings 可以看到几个关键信息:

TPK2
panel.dat
load failed
read failed
no
ok

说明程序核心逻辑是:

  1. 打开 panel.dat
  2. 检查文件头 TPK2
  3. panel.dat 中读取若干参数和查表数据
  4. 读取用户输入
  5. 对输入做变换并与 panel.dat 内的目标值比较

panel.dat 大小为 420 字节,开头如下:

54 50 4b 32 7c fa 7c 1b ...

前 4 字节是魔数:

TPK2

2. panel.dat 格式解析

逆向 veil_gate 后可以看出,panel.dat 结构大致如下:

OffsetSize含义
0x004magic,固定为 TPK2
0x044加密后的 header
0x0824key material
0x20128table A
0xa0128table B
0x12064table C
0x16064target
0x1a04seed

header 会与常量 0x3d6ca55a 异或:

header = enc_header ^ 0x3d6ca55a

解析得到:

header = 26 5f 10 26

即:

header[0] = 0x26
header[1] = 0x5f
key_len   = 0x10 = 16
flag_len  = 0x26 = 38
seed      = 0x7a6b5c4d

所以 flag 长度应为 38


3. 关键逻辑分析

程序会先根据 panel.dat 生成一个 16 字节 key:

cur = header[0]

for i in range(24):
    key[i] = key_material[i] ^ cur ^ ((i & 3) * 7 + 0x33)
    cur = (cur + 0x0b) & 0xff

其中真正使用的是前 16 字节:

3c a9 0c 1e 1a 93 68 f8 74 18 bd 95 4c 6c bc d7

随后程序构造一个 256 字节的 S-Box:

for i in range(128):
    sbox[2 * i]     = table_a[i] ^ key[i % key_len]
    sbox[2 * i + 1] = table_b[i] ^ key[(i + 5) % key_len]

还会从 table C 派生一个 64 字节数组:

idx_table[i] = table_c[i] ^ (i + 0x5d) ^ key[(3 * i + 1) % key_len]

前 38 个值刚好是 0 ~ 37 的一个置换,用来决定每一轮处理 flag 的哪个字符:

[2, 27, 34, 17, 8, 26, 21, 7, 35, 13, 16, 12, 24, 14, 32, 3,
 33, 15, 20, 22, 5, 0, 1, 4, 18, 31, 19, 28, 36, 30, 29, 10,
 9, 37, 25, 11, 23, 6]

程序里有两条校验路径。第一条会尝试用 seed == 0x7a6b5c4d 的路径比较,但不满足后会进入第二条 fallback 路径。真正的 flag 是通过第二条路径还原出来的。


4. 逆向求解脚本

下面脚本直接解析 panel.dat 并反推 flag:

from pathlib import Path
import struct

data = Path("panel.dat").read_bytes()

assert data[:4] == b"TPK2"

def rol8(x, r):
    r &= 7
    return ((x << r) | (x >> (8 - r))) & 0xff

def rol16(x, r):
    r &= 15
    return ((x << r) | (x >> (16 - r))) & 0xffff

# parse header
enc_header = struct.unpack("<I", data[4:8])[0]
header = (enc_header ^ 0x3d6ca55a).to_bytes(4, "little")

h0 = header[0]
h1 = header[1]
key_len = header[2]
flag_len = header[3]

key_material = data[8:32]
table_a = data[32:160]
table_b = data[160:288]
table_c = data[288:352]
target = data[352:416]
seed = struct.unpack("<I", data[416:420])[0]

# derive key
cur = h0
key = []

for i, b in enumerate(key_material):
    key.append(b ^ cur ^ ((i & 3) * 7 + 0x33))
    cur = (cur + 0x0b) & 0xff

key = key[:key_len]

# derive sbox
sbox = []

for i in range(128):
    sbox.append(table_a[i] ^ key[i % key_len])
    sbox.append(table_b[i] ^ key[(i + 5) % key_len])

# derive index table
idx_table = []

for i in range(64):
    v = table_c[i] ^ ((i + 0x5d) & 0xff) ^ key[(3 * i + 1) % key_len]
    idx_table.append(v)

# fallback checker inversion
edi = rol16(int.from_bytes(header[:2], "little"), 8)
r8 = 3
r9 = 1

flag = [None] * flag_len

for i in range(flag_len):
    rem = (edi + i + idx_table[i]) % key_len

    possible_tmp = []

    for tmp in range(256):
        a = key[rem] ^ tmp
        cl = (rol8(edi & 0xff, i) + i) & 0xff
        cl = (cl + sbox[a]) & 0xff

        if cl == target[i]:
            possible_tmp.append(tmp)

    pos = idx_table[i] % flag_len
    possible_char = []

    for tmp in possible_tmp:
        for ch in range(32, 127):
            c = rol8(ch, 4) if (i & 1) == 0 else ch
            calc = ((c ^ key[(pos + i) % key_len]) + rol8(h0, i)) & 0xff

            if calc == tmp:
                possible_char.append(ch)

    assert len(set(possible_char)) == 1
    flag[pos] = possible_char[0]

    # update state with known target byte
    out = target[i]

    edi = (edi * 0x83) & 0xffffffff
    edi ^= idx_table[r8 % flag_len]
    r8 += 7

    edi = (key[r9 % key_len] + ((out ^ edi) & 0xffffffff)) & 0xffffffff
    r9 += 5

flag = bytes(flag).decode()
print(flag)

运行结果:

$ python3 solve.py
flag{9d7a228ca5825bc15cd60bee0bb6d585}

使用社交账号登录

  • Loading...
  • Loading...
  • Loading...
  • Loading...
  • Loading...