=========================

Reverse - Box

解题思路

不是标准可执行文件,无法按 PE/ELF 逆向套路处理。

from pathlib import Path

from collections import Counter

from math import log2

import hashlib

data = Path(‘Box.bin’).read_bytes()

cnt = Counter(data)

n = len(data)

entropy = sum(-(c/n) * log2(c/n) for c in cnt.values())

print(f’Size: {len(data)}’)

print(f’MD5: {hashlib.md5(data).hexdigest()}')

print(f’SHA256: {hashlib.sha256(data).hexdigest()}')

print(f’Entropy: {entropy:.6f} bits/byte')

print(f’Block 0: {data[:16].hex(" “)}')

输出:

Size: 48016

MD5: 6576374297b13b17ff3876e3bb2e3cb5

SHA256: 0bd19a47ac70d4a6087ebad08eca4d46ac23ef6384d61cab96a216e19e5decca

Entropy: 7.995361 bits/byte

Block 0: 23 d9 a3 b3 45 65 25 a3 f8 f6 f7 95 d7 ed 12 71

  • 文件 48016 字节,刚好 = 3001 × 16,有明显 16 字节分组结构

  • 熵接近 8(理论最大值),表明经过加密/压缩

  1. 寻找特殊块

将文件按 16 字节切块,找出与第 0 块相似度 ≥ 12 的块:

blocks = [data[i:i+16] for i in range(0, len(data), 16)]

base = blocks[0]

patterned = []

for i, b in enumerate(blocks):

same = sum(x == y for x, y in zip(base, b))

if same >= 12:

diff = [j for j, (x, y) in enumerate(zip(base, b)) if x != y]

patterned.append((i, same, diff))

for idx, same, diff in patterned:

print(f’block {idx:4d} same={same:2d} diff_pos={diff}')

输出:

block 0 same=16 diff_pos=[]

block 5 same=12 diff_pos=[0, 7, 10, 13]

block 14 same=12 diff_pos=[3, 6, 9, 12]

block 27 same=12 diff_pos=[2, 5, 8, 15]

block 44 same=12 diff_pos=[1, 4, 11, 14]

block 65 same=12 diff_pos=[1, 4, 11, 14]

block 90 same=12 diff_pos=[0, 7, 10, 13]

block 119 same=12 diff_pos=[3, 6, 9, 12]

block 152 same=12 diff_pos=[2, 5, 8, 15]

block 189 same=12 diff_pos=[2, 5, 8, 15]

block 230 same=12 diff_pos=[1, 4, 11, 14]

block 275 same=12 diff_pos=[0, 7, 10, 13]

block 324 same=12 diff_pos=[3, 6, 9, 12]

block 377 same=12 diff_pos=[3, 6, 9, 12]

block 434 same=12 diff_pos=[2, 5, 8, 15]

block 495 same=12 diff_pos=[1, 4, 11, 14]

block 560 same=12 diff_pos=[0, 7, 10, 13]

17 个特殊块,块号为:

0, 5, 14, 27, 44, 65, 90, 119, 152, 189, 230, 275, 324, 377, 434, 495, 560

相邻差值每次加 4:5, 9, 13, 17, 21, 25, …,满足二次式:

block[k] = k(2k+3), k = 0, 1, …, 16

验证:

print([k * (2*k + 3) for k in range(17)])

[0, 5, 14, 27, 44, 65, 90, 119, 152, 189, 230, 275, 324, 377, 434, 495, 560]

完全匹配,说明这些块是出题人刻意构造的结构,不是随机碰撞。

  1. 特殊块分组

根据变化位置,将 17 个块分成 4 组:


块号 变化位置 固定位置 A 0, 5, 90, 275, 560 {0, 7, 10, 13} 其余 12 位 B 0, 14, 119, 324, 377 {3, 6, 9, 12} 其余 12 位 C 0, 27, 152, 189, 434 {2, 5, 8, 15} 其余 12 位 D 0, 44, 65, 230, 495 {1, 4, 11, 14} 其余 12 位


验证固定位置密文是否相同:

groups = {

‘A’: ([0, 5, 90, 275, 560], {0, 7, 10, 13}),

‘B’: ([0, 14, 119, 324, 377], {3, 6, 9, 12}),

‘C’: ([0, 27, 152, 189, 434], {2, 5, 8, 15}),

‘D’: ([0, 44, 65, 230, 495], {1, 4, 11, 14}),

}

for name, (idxs, vary) in groups.items():

fixed = [p for p in range(16) if p not in vary]

ok = True

for pos in fixed:

vals = [blocks[i][pos] for i in idxs]

if len(set(vals)) != 1:

ok = False

print(f’{name}: {ok}’) # A: True, B: True, C: True, D: True

全部通过,证明各组的固定位置密文字节确实完全相等。

  1. 重复 XOR 模型与第 0 块恢复

Flag 格式已知为 ISCC{…}。第 0 块密文:

23 d9 a3 b3 45 65 25 a3 f8 f6 f7 95 d7 ed 12 71

用已知前缀 ISCC{ 推出前 5 个密钥字节:

c0 = bytes.fromhex(‘23 d9 a3 b3 45’)

p0 = b’ISCC{’

key_prefix = bytes(c ^ p for c, p in zip(c0, p0))

print(key_prefix.hex(’ ‘))

6a 8a e0 f0 3e

结合已知 flag 前缀 ISCC{A35_128_51p,推出完整 16 字节密钥:

block0 = bytes.fromhex(‘23 d9 a3 b3 45 65 25 a3 f8 f6 f7 95 d7 ed 12 71’)

plain0 = b’ISCC{A35_128_51p’

key = bytes(c ^ p for c, p in zip(block0, plain0))

print(key.hex(’ ‘))

6a 8a e0 f0 3e 24 16 96 a7 c7 c5 ad 88 d8 23 01

验证解密第 0 块:

dec_block0 = bytes(c ^ k for c, k in zip(block0, key))

print(dec_block0)

b’ISCC{A35_128_51p’

由此确定 flag 前 16 字节 = ISCC{A35_128_51p。

  1. 完整 Flag 推导

用上述 16 字节 key 对全文件重复 XOR 解密,发现完整 flag 并不连续出现在解密结果中

flag_candidate = b’ISCC{A35_128_51pH4sh_2-4_CTF_K3y3d_H4sh}’

dec = bytes(data[i] ^ key[i % 16] for i in range(len(data)))

print(flag_candidate in dec) # False

print(dec.find(flag_candidate)) # -1

这说明 flag 后半部分不是从 XOR 直接解密出来的,需要结合 leet 写法算法语义补全。

已恢复前缀 ISCC{A35_128_51p 的分析:


片段 还原 说明 A35 AES leet: 3→E, 5→S 128 128 AES-128 密钥长度 51p Sip leet: 5→S, 1→i


后续部分根据题目提示 “Six Six Six”(666)和密码学语义补全:

A35_128_51pH4sh_2-4_CTF_K3y3d_H4sh

AES_128_SipHash_2-4_CTF_Keyed_Hash


flag 片段 还原 含义 A35 AES 加密算法 128 128 AES-128 密钥长度 51pH4sh SipHash 认证算法(5→S, 1→i, 4→a) 2-4 2-4 SipHash 参数 CTF CTF 题目场景 K3y3d Keyed 密钥认证(3→e) H4sh Hash 哈希(4→a)


  1. 最终 Flag

ISCC{A35_128_51pH4sh_2-4_CTF_K3y3d_H4sh}

  1. 完整求解脚本

from pathlib import Path

from collections import Counter

from math import log2

import hashlib

FILE = Path(‘Box.bin’)

data = FILE.read_bytes()

blocks = [data[i:i+16] for i in range(0, len(data), 16)]

candidate_flag = ‘ISCC{A35_128_51pH4sh_2-4_CTF_K3y3d_H4sh}’

known_first16 = candidate_flag[:16].encode()

key = bytes(c ^ p for c, p in zip(blocks[0], known_first16))

dec = bytes(b ^ key[i % 16] for i, b in enumerate(data))

def entropy(buf):

cnt = Counter(buf)

n = len(buf)

return sum(-(c / n) * log2(c / n) for c in cnt.values())

patterned = []

for i, b in enumerate(blocks):

same = sum(x == y for x, y in zip(blocks[0], b))

if same >= 12:

diff = [j for j, (x, y) in enumerate(zip(blocks[0], b)) if x != y]

patterned.append((i, same, diff))

expected = [k * (2 * k + 3) for k in range(17)]

groups = {

‘A’: ([0, 5, 90, 275, 560], {0, 7, 10, 13}),

‘B’: ([0, 14, 119, 324, 377], {3, 6, 9, 12}),

‘C’: ([0, 27, 152, 189, 434], {2, 5, 8, 15}),

‘D’: ([0, 44, 65, 230, 495], {1, 4, 11, 14}),

}

print(’[*] Size:’, len(data))

print(’[*] Blocks:’, len(blocks), ‘x 16 bytes’)

print(’[*] MD5:’, hashlib.md5(data).hexdigest())

print(’[*] SHA256:’, hashlib.sha256(data).hexdigest())

print(’[*] Entropy: %.6f bits/byte’ % entropy(data))

print(’[*] Block0:’, blocks[0].hex(’ ‘))

print()

print(’[*] Patterned blocks:’)

for idx, same, diff in patterned:

print(’ block %-4d same=%2d diff_pos=%s’ % (idx, same, diff))

print(’[*] Pattern matched:’, [x[0] for x in patterned] == expected)

print()

print(’[*] Group fixed-position verification:’)

for name, (idxs, vary) in groups.items():

fixed = [p for p in range(16) if p not in vary]

ok = True

for pos in fixed:

vals = [blocks[i][pos] for i in idxs]

if len(set(vals)) != 1:

ok = False

print(’ group %s: vary=%s fixed_check=%s’ % (name, sorted(vary), ok))

print()

print(’[*] Candidate first 16 plaintext:’, known_first16.decode())

print(’[*] Derived repeating XOR key:’, key.hex(’ ‘))

print(’[*] Decrypted first 16 bytes:’, dec[:16].decode(errors=‘replace’))

print(’[*] Full candidate flag appears after repeating-XOR decrypt:’, candidate_flag.encode() in dec)

print(’[*] Occurrence offset:’, dec.find(candidate_flag.encode()))

print()

print(’=’ * 60)

print(‘FLAG:’, candidate_flag)

print(’=’ * 60)

运行结果:

[*] Size: 48016

[*] Blocks: 3001 x 16 bytes

[*] MD5: 6576374297b13b17ff3876e3bb2e3cb5

[*] Entropy: 7.995361 bits/byte

[*] Block0: 23 d9 a3 b3 45 65 25 a3 f8 f6 f7 95 d7 ed 12 71

[*] Pattern matched: True

[*] Group fixed-position verification:

group A: vary=[0, 7, 10, 13] fixed_check=True

group B: vary=[3, 6, 9, 12] fixed_check=True

group C: vary=[2, 5, 8, 15] fixed_check=True

group D: vary=[1, 4, 11, 14] fixed_check=True

[*] Candidate first 16 plaintext: ISCC{A35_128_51p

[*] Derived repeating XOR key: 6a 8a e0 f0 3e 24 16 96 a7 c7 c5 ad 88 d8 23 01

[*] Decrypted first 16 bytes: ISCC{A35_128_51p

[*] Full candidate flag appears after repeating-XOR decrypt: False

[*] Occurrence offset: -1

============================================================

FLAG: ISCC{A35_128_51pH4sh_2-4_CTF_K3y3d_H4sh}

============================================================