鹏程杯

开始先小小的吐槽一下这个比赛时间跟四六级撞了一天,难受啊啊啊啊啊.还好是有出解
下面是分享re部分的一些wp

babyconnet

Server.exe 有主逻辑

使用XOR 解密的
flag_dll_check(buf); 验证flag

WASD实现移动
SMC:解密

inside.dll的check,2是终点,1是正常
mov eax, 0; ret 是可通过
mov eax, [eax] (eax=0 时崩溃 ) 不可通过
mov dword_10014AC8, 1 终点

这里patch了53
下面patch了85

这里是用来nop的
这里是大概的迷宫
T是指的传送门,*是可以移动的墙
求解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
 from collections import deque
def solve_maze():
# 初始通路 + 特殊位置(传送门)
initial_paths = {1, 11, 13, 15, 16, 17, 18, 21, 23, 25, 28, 31, 33, 35, 38,
41, 42, 43, 45, 46, 48, 56, 58, 61, 62, 63, 64, 66, 68,
71, 74, 76, 78, 81, 82, 84, 86, 88}
start, end = 1, 88
queue = deque([((start, frozenset()), "D")])
visited = {(start, frozenset())}

while queue:
(pos, opened), path = queue.popleft()
if pos == end:
return path

current_paths = initial_paths | opened

for direction, delta in [('W', -10), ('S', 10), ('A', -1), ('D', 1)]:
new_pos = pos + delta
# 边界检查...

if new_pos in current_paths:
new_opened = set(opened)
if new_pos == 13: new_opened.add(53) # 打开位置53
if new_pos == 82: new_opened.add(85) # 打开位置85

new_state = (new_pos, frozenset(new_opened))
if new_state not in visited:
visited.add(new_state)
queue.append((new_state, path + direction))

return None
# 结果
path = "DSSSSDDWWWSSSSSAASSDAWWDDDSSDDWWWWAWWWDDDSSSSSSS"

flag.dll里面是主要的检查那么看这里

按照后面四个字节来求解,密文在&unk_10015000里面是
1
0x50,0x73,0x65,0xCC,0x0,0xC,0x11,0x2E,0x2,0x26,0x2,0x3,0xD,0x7A,0x7A,0x1B,0x36,0x61,0x4C,0x6,0x18,0x4C,0xF,0x46,0x58,0x30,0x30,0x53,0x62,0x58,0x5A,0x68,0xE,0x34,0x55,0x5,0x5B,0x6C,0x4A,0x44,0x5E,0x36,0x42,0x7D

然后就是exp了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
 maze_path = "DSSSSDDWWWSSSSSAASSDAWWDDDSSDDWWWWAWWWDDDSSSSSSS"
# flag.dll 的目标密文
target = bytes([
0x50, 0x73, 0x65, 0xCC, 0x00, 0x0C, 0x11, 0x2E,
0x02, 0x26, 0x02, 0x03, 0x0D, 0x7A, 0x7A, 0x1B,
0x36, 0x61, 0x4C, 0x06, 0x18, 0x4C, 0x0F, 0x46,
0x58, 0x30, 0x30, 0x53, 0x62, 0x58, 0x5A, 0x68,
0x0E, 0x34, 0x55, 0x05, 0x5B, 0x6C, 0x4A, 0x44,
0x5E, 0x36, 0x42, 0x7D,
])
print(f"目标密文 ({len(target)} 字节): {target.hex()}")
# 分析 nullsub_1 的 XOR 逻辑
# 看起来是: data[i] ^= data[i+1] 的循环
# 尝试逆向 XOR 操作
def reverse_xor(data):
"""逆向 XOR 操作: 从后往前"""
result = bytearray(data)
for i in range(len(result) - 2, -1, -1):
result[i] ^= result[i + 1]
return bytes(result)
# 尝试多次逆向(因为代码中有3个类似的循环)
decrypted = target
for round_num in range(3):
decrypted = reverse_xor(decrypted)
try:
text = decrypted.decode('ascii')
if text.startswith('flag') or text.startswith('PCC') or 'ctf' in text.lower():
print(f"\n第 {round_num + 1} 轮解密后: {text}")
except:
pass
print(f"第 {round_num + 1} 轮: {decrypted.hex()}")
# 检查是否有可打印字符
printable = ''.join(chr(b) if 32 <= b < 127 else '.' for b in decrypted)
print(f" 可打印: {printable}")

More more flower

里面的字节码是

1
2
3
4
5
6
7
8
9
10
11
12
13
14
bytecode = bytes([
0x01, 0x08, 0x06, 0x01, 0x01, 0x02, 0x02, 0x04, 0x02, 0x09, 0x01, 0x01,
0x02, 0x04, 0x05, 0x01, 0x08, 0x08, 0x01, 0x03, 0x04, 0x02, 0x09, 0x01,
0x01, 0x02, 0x04, 0x05, 0x01, 0x08, 0x08, 0x01, 0x03, 0x04, 0x02, 0x09,
0x01, 0x01, 0x02, 0x04, 0x05, 0x01, 0x08, 0x08, 0x01, 0x03, 0x03, 0x04,
0x06, 0x00, 0x03, 0x03, 0x06, 0x1E, 0x01, 0x08, 0x56, 0x01, 0x08, 0x11,
0x01, 0x08, 0x25, 0x01, 0x08, 0x23, 0x02, 0x08, 0x04, 0x04, 0x06, 0x01,
0x07, 0x03, 0x05, 0x01, 0x05, 0x05, 0x05, 0x09, 0x05, 0x04, 0x06, 0x01,
0x04, 0x09, 0x05, 0x01, 0x02, 0x07, 0x04, 0x01, 0x05, 0x07, 0x03, 0x01,
0x0A, 0x03, 0x36, 0x01, 0x07, 0x07, 0x06, 0x01, 0x03, 0x03, 0x08, 0x04,
0x02, 0x09, 0x0A, 0x03, 0x03, 0x03, 0x03, 0x06, 0x18, 0x03, 0x02, 0x06,
0x00, 0x02, 0x02, 0x03, 0x04, 0x07, 0x07, 0x01, 0x00, 0x0A, 0x01, 0x8F,
0x07, 0x03, 0x01, 0x04, 0x02, 0x09, 0x0A, 0x03, 0x79, 0x0B, 0x01, 0x0B
])

密文是

1
2
3
4
5
target = bytes([
0x21, 0x7A, 0x01, 0x1C, 0x33, 0xD3, 0x3E, 0xF7,
0x03, 0x78, 0x25, 0x5E, 0x2F, 0xB8, 0x8B, 0x3B,
0x93, 0x84, 0xAE, 0x5B, 0xDE, 0xA5, 0xD6, 0xE9
])

根据VM得到指令码是

1
2
3
4
5
6
7
8
9
10
11
1       PUSH
2 POP
3 MOV
4 ADD
5 SHL
6 SHR
7 SUB
8 OR
10 XOR
9 JNZ
11 RET

反VM得到

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
  0: PUSH imm 0x6        ; 外层循环计数器 = 6

; === 读取 4 字节组成 32-bit 值 ===
3: PUSH input[R1] ; 读取输入字节
5: POP R0
7: ADD R1, 1
10: PUSH input[R1]
12: POP R2
14: SHL R0, 8 ; R0 = R0 << 8
17: OR R0, R2 ; R0 |= R2
... (重复读取 4 字节)

; === 初始化加密参数 ===
46: MOV R3, 0x0 ; delta = 0
50: MOV R2, 0x1E ; 内层循环 30 次
54: PUSH imm 0x56 ; 压入 KEY 的 4 个字节
57: PUSH imm 0x11
60: PUSH imm 0x25
63: PUSH imm 0x23
66: POP dword (var_8) ; var_8 = 0x23251156 (KEY)
; === 加密内层循环 ===
68: ADD R3, var_8 ; delta += KEY
71: PUSH R0_dword ; 保存 R0
73: MOV R6, R0 ; R6 = R0
76: SHL R6, 5 ; R6 = R0 << 5
79: XOR R6, R3 ; R6 ^= delta
82: SHR R0, 4 ; temp = R0 >> 4
85: XOR R6, R0 ; R6 ^= (R0 >> 4)
88: POP R0_dword ; 恢复 R0
90: ADD R0, R6 ; R0 += R6
93: SUB R2, 1 ; 计数器--
96: JNZ R2, 54 ; 循环 30 次
99: PUSH R0_dword ; 保存加密结果
...
; === 验证阶段 ===
121: POP R0 ; 逐字节弹出
123: MOV R3, target[R1] ; 与目标比较
126: SUB R0, R3
129: JNZ R0, 143 ; 不等则失败
...
141: RET 1 ; 成功返回 1
143: RET 0 ; 失败返回 0

根据这个可以得到算法

1
2
3
4
5
6
7
8
KEY = 0x23251156
def encrypt_block(v):
delta = 0
for _ in range(30):
delta = (delta + KEY) & 0xFFFFFFFF
r6 = ((v << 5) & 0xFFFFFFFF) ^ delta ^ (v >> 4)
v = (v + r6) & 0xFFFFFFFF
return v

那么exp是

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
import hashlib
target = bytes([
0x21, 0x7A, 0x01, 0x1C, 0x33, 0xD3, 0x3E, 0xF7,
0x03, 0x78, 0x25, 0x5E, 0x2F, 0xB8, 0x8B, 0x3B,
0x93, 0x84, 0xAE, 0x5B, 0xDE, 0xA5, 0xD6, 0xE9
])
# 正确的加密函数
KEY = 0x23251156 # var_8 的值
def encrypt_block_correct(v):
"""正确的加密:delta 累加 KEY"""
delta = 0
for _ in range(30):
delta = (delta + KEY) & 0xFFFFFFFF
# R6 = (R0 << 5) ^ delta ^ (R0 >> 4)
r6 = ((v << 5) & 0xFFFFFFFF) ^ delta ^ (v >> 4)
v = (v + r6) & 0xFFFFFFFF
return v
def decrypt_single_round_all(v_new, delta):
"""解密单轮"""
def calc_encrypted(v_old):
r6 = ((v_old << 5) & 0xFFFFFFFF) ^ delta ^ (v_old >> 4)
return (v_old + r6) & 0xFFFFFFFF
stage1 = []
for low16 in range(1 << 16):
r6 = ((low16 << 5) & 0xFFFFFFFF) ^ delta ^ (low16 >> 4)
result = (low16 + r6) & 0xFFFFFFFF
if (result & 0xFF) == (v_new & 0xFF):
stage1.append(low16)
stage2 = []
for low16 in stage1:
for mid8 in range(256):
low24 = low16 | (mid8 << 16)
r6 = ((low24 << 5) & 0xFFFFFFFF) ^ delta ^ (low24 >> 4)
result = (low24 + r6) & 0xFFFFFFFF
if (result & 0xFFFF) == (v_new & 0xFFFF):
stage2.append(low24)
solutions = []
for low24 in stage2:
for high8 in range(256):
v_old = low24 | (high8 << 24)
if calc_encrypted(v_old) == v_new:
solutions.append(v_old)
return solutions
def decrypt_block_correct(enc):
"""正确的解密:30轮,delta从30*KEY递减"""
current = [enc]
delta = (30 * KEY) & 0xFFFFFFFF
for r in range(30):
next_solutions = []
for v in current:
sols = decrypt_single_round_all(v, delta)
next_solutions.extend(sols)
current = next_solutions
delta = (delta - KEY) & 0xFFFFFFFF
if not current:
return []
return current
# 验证加密
print("=== Verify encryption ===")
test_val = 0x41424344
enc = encrypt_block_correct(test_val)
print(f"encrypt(0x41424344) = {enc:#010x}")
# 验证解密
print("\n=== Verify decryption ===")
solutions = decrypt_block_correct(enc)
print(f"Solutions: {len(solutions)}")
if test_val in solutions:
print(f"Original value {test_val:#010x} found!")
else:
print("Original not found")
for s in solutions[:5]:
print(f" {s:#010x}")
# 解密所有块
print("\n=== Decrypting flag ===")
all_solutions = []
for i in range(6):
start = i * 4

print(f"Block {i}: {block:#010x}", end=" -> ")
solutions = decrypt_block_correct(block)
print(f"{len(solutions)} solutions")
# 过滤可打印
printable = []
for sol in solutions:
b = bytes([(sol >> 24) & 0xFF, (sol >> 16) & 0xFF, (sol >> 8) & 0xFF, sol & 0xFF])
if all(0x20 <= c < 0x7F for c in b):
printable.append((sol, b))
print(f" Printable: {len(printable)}")
for sol, b in printable[:5]:
print(f" {sol:#010x} = {b}")
all_solutions.append(
printable if printable else [(s, bytes([(s >> 24) & 0xFF, (s >> 16) & 0xFF, (s >> 8) &
in solutions])
# 组合求解
print("\n=== Finding flag ===")
from itertools import product
expected_sha = "3dbe89f66cb189f9cac1fb5ec23fac941df69119792aad4b6d61d63b98ddb527"
total = 1
for sols in all_solutions:
total *= len(sols)
print(f"Total combinations: {total}")
if total > 0 and total < 10000000:
for combo in product(*all_solutions):
flag = b''
for sol, _ in combo:
flag += bytes([(sol >> 24) & 0xFF, (sol >> 16) & 0xFF, (sol >> 8) & 0xFF, sol & 0xF
sha = hashlib.sha256(flag).hexdigest()
if sha == expected_sha:
print(f"\n*** FOUND FLAG ***")
print(f"Flag: {flag}")
try:
print(f"String: {flag.decode()}")
except:
pass
print(f"SHA256: {sha}")
break
else:
print("Not found")

Get_My_Emoji_wp

在emoji_encoder里面的

这里是标准的RC4加密
exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
import shutil
import subprocess
from pathlib import Path

import numpy as np
from PIL import Image


def encode_constant(binary: Path, workdir: Path, value: int, h: int, w: int) -> np.ndarray:
# 构造全常数 RGBA 图 -> 调用 encoder -> 读回 enc_emoji.png 的 RGBA 数组
plain = np.full((h, w, 4), value, dtype=np.uint8)
Image.fromarray(plain, "RGBA").save(workdir / "my_emoji.png")

subprocess.run(
[str(binary)],
cwd=str(workdir),
check=True,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
)

enc = Image.open(workdir / "enc_emoji.png").convert("RGBA")
return np.array(enc, dtype=np.uint8)


def main():
workdir = Path(__file__).resolve().parent
binary = workdir / "emoji_encoder"
target = workdir / "enc_emoji.png" # 题目给的噪声图
backup = workdir / "enc_emoji_orig.png"
out = workdir / "decoded_emoji.png"

if not binary.exists():
raise FileNotFoundError("emoji_encoder not found")
if not target.exists():
raise FileNotFoundError("enc_emoji.png not found")

# 备份原始密文图,避免被 encoder 覆盖
if not backup.exists():
shutil.copyfile(target, backup)

# 读取题目密文(从 backup 读,避免后续被覆盖)
cipher_img = Image.open(backup).convert("RGBA")
cipher = np.array(cipher_img, dtype=np.uint8)
h, w = cipher.shape[:2]

# Oracle:全 0 & 全 255
e0 = encode_constant(binary, workdir, 0, h, w)
eF = encode_constant(binary, workdir, 255, h, w)

# 恢复 A 和 X
add = ((e0.astype(np.uint16) + eF.astype(np.uint16) + 1) % 256)
add = (add // 2).astype(np.uint8)

xor = ((e0.astype(np.int16) - add.astype(np.int16)) % 256).astype(np.uint8)

# 解密:P = (C - A) XOR X
plain = ((cipher.astype(np.int16) - add.astype(np.int16)) % 256).astype(np.uint8)
plain = np.bitwise_xor(plain, xor).astype(np.uint8)

Image.fromarray(plain, "RGBA").save(out)
print(f"[+] decoded saved to: {out}")


if __name__ == "__main__":
main()

meddddgo

DIE查

GO语言的,无壳.根据Input your flag: 定位到 sub_140001A20()里面
可以看到是处理了\r和\n.
检查位置sub_1400B3740()
汇编里面test rsi, 0xf,要求了是16倍数
输出正确

1
return sub_1400AA4E0(41, &v6, v3, "Congratulate! The mad go flag is flag{%s}", (const char *)1);

里面有个魔改的SM4
在sub_1400B32A0()

这里是s-box

1
0xD6,0x90,0xE9,0xFE,0xCC,0xE1,0x3D,0xB7,0x16,0xB6,0x14,0xC2,0x28,0xFB,0x2C,0x5,0x2B,0x67,0x9A,0x76,0x2A,0xBE,0x4,0xC3,0xAA,0x44,0x13,0x26,0x49,0x86,0x6,0x99,0x9C,0x42,0x50,0xF4,0x91,0xEF,0x98,0x7A,0x33,0x54,0xB,0x43,0xED,0xCF,0xAC,0x62,0xE4,0xB3,0x1C,0xA9,0xC9,0x8,0xE8,0x95,0x80,0xDF,0x94,0xFA,0x75,0x8F,0x3F,0xA6,0x47,0x7,0xA7,0xFC,0xF3,0x73,0x17,0xBA,0x83,0x59,0x3C,0x19,0xE6,0x85,0x4F,0xA8,0x68,0x6B,0x81,0xB2,0x71,0x64,0xDA,0x8B,0xF8,0xEB,0xF,0x4B,0x70,0x56,0x9D,0x35,0x1E,0x24,0xE,0x5E,0x63,0x58,0xD1,0xA2,0x25,0x22,0x7C,0x3B,0x1,0x21,0x78,0x87,0xD4,0x0,0x46,0x57,0x9F,0xD3,0x27,0x52,0x4C,0x36,0x2,0xE7,0xA0,0xC4,0xC8,0x9E,0xEA,0xBF,0x8A,0xD2,0x40,0xC7,0x38,0xB5,0xA3,0xF7,0xF2,0xCE,0xF9,0x61,0x15,0xA1,0xE0,0xAE,0x5D,0xA4,0x9B,0x34,0x1A,0x55,0xAD,0x93,0x32,0x30,0xF5,0x8C,0xB1,0xE3,0x1D,0xF6,0xE2,0x2E,0x82,0x66,0xCA,0x60,0xC0,0x29,0x23,0xAB,0xD,0x53,0x4E,0x6F,0xD5,0xDB,0x37,0x45,0xDE,0xFD,0x8E,0x2F,0x3,0xFF,0x6A,0x72,0x6D,0x6C,0x5B,0x51,0x8D,0x1B,0xAF,0x92,0xBB,0xDD,0xBC,0x7F,0x11,0xD9,0x5C,0x41,0x1F,0x10,0x5A,0xD8,0xA,0xC1,0x31,0x88,0xA5,0xCD,0x7B,0xBD,0x2D,0x74,0xD0,0x12,0xB8,0xE5,0xB4,0xB0,0x89,0x69,0x97,0x4A,0xC,0x96,0x77,0x7E,0x65,0xB9,0xF1,0x9,0xC5,0x6E,0xC6,0x84,0x18,0xF0,0x7D,0xEC,0x3A,0xDC,0x4D,0x20,0x79,0xEE,0x5F,0x3E,0xD7,0xCB,0x39,0x48

SM4是K[i] = MK[i] ^ FK[i]这里是K[i] = MK[i] ^ FK[i] ^ MAGIC[i]
可以从里面找到seed是10 23 45 67 89 ab cd ef 01 35 79 bd f0 22 44 66
exp是

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import struct

# ------------------------
# SM4 S-Box / FK / CK
# ------------------------
SBOX = [
0xd6,0x90,0xe9,0xfe,0xcc,0xe1,0x3d,0xb7,0x16,0xb6,0x14,0xc2,0x28,0xfb,0x2c,0x05,
0x2b,0x67,0x9a,0x76,0x2a,0xbe,0x04,0xc3,0xaa,0x44,0x13,0x26,0x49,0x86,0x06,0x99,
0x9c,0x42,0x50,0xf4,0x91,0xef,0x98,0x7a,0x33,0x54,0x0b,0x43,0xed,0xcf,0xac,0x62,
0xe4,0xb3,0x1c,0xa9,0xc9,0x08,0xe8,0x95,0x80,0xdf,0x94,0xfa,0x75,0x8f,0x3f,0xa6,
0x47,0x07,0xa7,0xfc,0xf3,0x73,0x17,0xba,0x83,0x59,0x3c,0x19,0xe6,0x85,0x4f,0xa8,
0x68,0x6b,0x81,0xb2,0x71,0x64,0xda,0x8b,0xf8,0xeb,0x0f,0x4b,0x70,0x56,0x9d,0x35,
0x1e,0x24,0x0e,0x5e,0x63,0x58,0xd1,0xa2,0x25,0x22,0x7c,0x3b,0x01,0x21,0x78,0x87,
0xd4,0x00,0x46,0x57,0x9f,0xd3,0x27,0x52,0x4c,0x36,0x02,0xe7,0xa0,0xc4,0xc8,0x9e,
0xea,0xbf,0x8a,0xd2,0x40,0xc7,0x38,0xb5,0xa3,0xf7,0xf2,0xce,0xf9,0x61,0x15,0xa1,
0xe0,0xae,0x5d,0xa4,0x9b,0x34,0x1a,0x55,0xad,0x93,0x32,0x30,0xf5,0x8c,0xb1,0xe3,
0x1d,0xf6,0xe2,0x2e,0x82,0x66,0xca,0x60,0xc0,0x29,0x23,0xab,0x0d,0x53,0x4e,0x6f,
0xd5,0xdb,0x37,0x45,0xde,0xfd,0x8e,0x2f,0x03,0xff,0x6a,0x72,0x6d,0x6c,0x5b,0x51,
0x8d,0x1b,0xaf,0x92,0xbb,0xdd,0xbc,0x7f,0x11,0xd9,0x5c,0x41,0x1f,0x10,0x5a,0xd8,
0x0a,0xc1,0x31,0x88,0xa5,0xcd,0x7b,0xbd,0x2d,0x74,0xd0,0x12,0xb8,0xe5,0xb4,0xb0,
0x89,0x69,0x97,0x4a,0x0c,0x96,0x77,0x7e,0x65,0xb9,0xf1,0x09,0xc5,0x6e,0xc6,0x84,
0x18,0xf0,0x7d,0xec,0x3a,0xdc,0x4d,0x20,0x79,0xee,0x5f,0x3e,0xd7,0xcb,0x39,0x48,
]

FK = [0xa3b1bac6, 0x56aa3350, 0x677d9197, 0xb27022dc]
CK = [
0x00070e15,0x1c232a31,0x383f464d,0x545b6269,0x70777e85,0x8c939aa1,0xa8afb6bd,0xc4cbd2d9,
0xe0e7eef5,0xfc030a11,0x181f262d,0x343b4249,0x50575e65,0x6c737a81,0x888f969d,0xa4abb2b9,
0xc0c7ced5,0xdce3eaf1,0xf8ff060d,0x141b2229,0x30373e45,0x4c535a61,0x686f767d,0x848b9299,
0xa0a7aeb5,0xbcc3cad1,0xd8dfe6ed,0xf4fb0209,0x10171e25,0x2c333a41,0x484f565d,0x646b7279
]

# 程序里额外 xor 的那组常量(魔改点)
MAGIC = [0xA5A5A5A5, 0x3C3C3C3C, 0x5A5A5A5A, 0xC3C3C3C3]


def rotl32(x, n):
x &= 0xFFFFFFFF
return ((x << n) & 0xFFFFFFFF) | (x >> (32 - n))


def tau(a):
b = 0
for i in range(4):
byte = (a >> (24 - 8*i)) & 0xFF
b = (b << 8) | SBOX[byte]
return b


def L(b):
return b ^ rotl32(b, 2) ^ rotl32(b, 10) ^ rotl32(b, 18) ^ rotl32(b, 24)


def Lp(b):
return b ^ rotl32(b, 13) ^ rotl32(b, 23)


def T(x):
return L(tau(x))


def Tp(x):
return Lp(tau(x))


def rk_mad(key_bytes: bytes):
'''
魔改 SM4 key schedule:
K[i] = MK[i] ^ FK[i] ^ MAGIC[i]
其余流程与标准 SM4 相同
'''
MK = [struct.unpack(">I", key_bytes[i*4:(i+1)*4])[0] for i in range(4)]
K = [MK[i] ^ FK[i] ^ MAGIC[i] for i in range(4)]
rk = []
for i in range(32):
t = K[i+1] ^ K[i+2] ^ K[i+3] ^ CK[i]
K.append(K[i] ^ Tp(t))
rk.append(K[i+4])
return rk


def sm4_encrypt_block(block16: bytes, rk):
X = [struct.unpack(">I", block16[i*4:(i+1)*4])[0] for i in range(4)]
for i in range(32):
t = X[i+1] ^ X[i+2] ^ X[i+3] ^ rk[i]
X.append(X[i] ^ T(t))
Y = [X[35], X[34], X[33], X[32]]
return b"".join(struct.pack(">I", y) for y in Y)


def sm4_decrypt_ecb(cipher: bytes, key_bytes: bytes) -> bytes:
rk = rk_mad(key_bytes)
rk_rev = list(reversed(rk))
out = b""
for i in range(0, len(cipher), 16):
out += sm4_encrypt_block(cipher[i:i+16], rk_rev)
return out


def derive_key_from_seed(seed16: bytes) -> bytes:
'''
按二进制里的那段 16 字节 key 派生逻辑还原:
- 第一轮生成 tmp[16]
- 然后三轮原地混淆(round=0,1,2)
'''
assert len(seed16) == 16
arr = list(seed16)

# 第一轮
tmp = []
for i in range(16):
idx = (i*5 + 3) & 0xF
b = arr[idx]
b = ((b << 1) & 0xFF) | (b >> 7) # rol1
v = (11*i) ^ b ^ 0xA5
tmp.append(v & 0xFF)

# 三轮原地混淆
for r in range(3):
for j in range(16):
old = tmp[j]
b5 = tmp[(j + 5) & 0xF]
b1 = tmp[(j + 1) & 0xF]
add = (b5 + b1 + 17*r) & 0xFF
tmp[j] = (old ^ add) & 0xFF

return bytes(tmp)
def main():
# 从二进制里提取到的常量
seed = bytes.fromhex("1023456789abcdef013579bdf0224466")
cipher = bytes.fromhex(
"fc66b270e8874c9d734ecd5b766aa589"
"6dda6cc5349d5f3b44b54aaf5ef9ce49"
)
key = derive_key_from_seed(seed)
plain = sm4_decrypt_ecb(cipher, key)
inner = plain.decode()
print("[+] seed :", seed.hex())
print("[+] key :", key.hex())
print("[+] inner:", inner)
print("[+] flag :", f"flag{{{inner}}}")
if __name__ == "__main__":
main()

鹏程杯
https://boke-git-main-huang-chaos-projects.vercel.app/2025/12/15/鹏程杯/
作者
Ined
发布于
2025年12月15日
许可协议