hgame2026

Hgame2026

https://hgame.vidar.club/training/9

PVZ

DIE

1

发现是一个封装在C++壳下的jar包

7zip打开gpvz.exe,解压

2

jadx分析一下gpvz文件夹,发现一个很扎眼的what_is_this.png

3

发现是“神秘仪式”的具体内容

4

运行游戏,用一点作弊技巧在结束前摆出神秘阵型,获得flag

5

6

看不懂的华容道

IDA分析

无符号表,一坨sub_xxxxx函数,没有头绪

先看看字符串

7

点进HuarongDao2026_Salt看看

跟踪到sub_1400266B0,发现是一个VM的depatcher

VM控制华容道运作

game.bin就是操作码表

8

opcode:

  • 0x15:读用户输入(交互输入“编号+方向”)

  • 0x16:调用 sub_140011212 -> sub_14001EC40,重建棋盘状态编码

  • 0x18:对状态做哈希,写入寄存器

  • 0x17:打印两个 64-bit 组成的节点值

  • 0xE0/E1/E2/E3:跳转与条件判断

  • 0xFF:结束

棋盘编码

9

分析game.bin恢复初始棋盘为

10

加密算法为md5,不过不重要,可以通过编写bfs脚本直接跑出符合题目要求的路径,直接运行exe输入,获得最终棋盘hash

特别要注意题目追加的条件:操作路径顺序按照棋子编号从小到大 操作顺序wasd

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
from collections import deque

START = (1, 0, 3, 11, 10, 8, 12, 16, 13, 19)
DIRS = [('w', -4), ('a', -1), ('s', 4), ('d', 1)]

def piece_cells(anchor, pid):
if pid == 0:
return (anchor, anchor + 1, anchor + 4, anchor + 5)
if 1 <= pid <= 4:
return (anchor, anchor + 4)
if pid == 5:
return (anchor, anchor + 1)
return (anchor,)

def valid_shape(anchor, pid):
r, c = divmod(anchor, 4)
if pid == 0:
return r <= 3 and c <= 2
if 1 <= pid <= 4:
return r <= 3
if pid == 5:
return c <= 2
return 0 <= anchor < 20

def build_occ(state):
occ = [-1] * 20
pcs = []
for pid, a in enumerate(state):
if not valid_shape(a, pid):
return None, None
cells = piece_cells(a, pid)
for x in cells:
if not (0 <= x < 20) or occ[x] != -1:
return None, None
occ[x] = pid
pcs.append(cells)
return occ, pcs

def can_move(cells, occ, ch, d):
for x in cells:
if ch == 'a' and x % 4 == 0:
return False
if ch == 'd' and x % 4 == 3:
return False
nx = x + d
if not (0 <= nx < 20):
return False
if occ[nx] != -1 and nx not in cells:
return False
return True

def bfs():
q = deque([START])
prev = {START: (None, None)}
while q:
s = q.popleft()
if s[0] == 13:
path = []
cur = s
while prev[cur][0] is not None:
p, act = prev[cur]
path.append(act)
cur = p
path.reverse()
return path
occ, pcs = build_occ(s)
if occ is None:
continue
for pid in range(10):
cells = pcs[pid]
for ch, d in DIRS:
if can_move(cells, occ, ch, d):
ns = list(s)
ns[pid] += d
ns = tuple(ns)
if ns not in prev:
prev[ns] = (s, (pid, ch))
q.append(ns)
return None

if __name__ == "__main__":
path = bfs()
print(len(path))
print(" ".join(f"{pid}{ch}" for pid, ch in path))

11

hgame{c4a8ae149d34f8552875b87bb317ffa}

Signal Storm

DIE

12

IDA分析

13

在main中注册了三个信号处理函数,通过BUG(),raise(5)异常调用,控制执行流

分析信号处理函数,发现这其实是一个魔改 RC4 算法

  • sub_1780 (KSA):利用原始字符串对 S 盒 (byte_4100) 进行初始化打乱。

  • sub_1640 (PRGA变形):这是 RC4 的 S 盒交换逻辑,但它引入了 aC0lmBe4ore7heS 的当前字符参与运算。

  • sub_1740 (密钥旋转):每生成一个字节的密钥,就将密钥字符串循环左移一位

  • sub_16E0 (异或):执行标准的流密码异或操作:s[i] ^= keystream[i]

密文:

14

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
import struct

def solve():

key_str = b"C0lm_be4ore_7he_st0rm"

targets = [
0x8260C1C9C8D936E3, #s[0:8]
0x1C4BB2D52511D975, #s[8:16]
0xF11CAF1C716DE64D, #s[16:24]
0x1A5AF67F261CA506 #s[24:32]
]

S = list(range(256))
j = 0
for i in range(256):
j = (j + S[i] + key_str[i % 21]) % 256
S[i], S[j] = S[j], S[i]
i_ptr = 0
j_ptr = 0
current_rot_str = list(key_str)
keystream = []

for _ in range(32):
i_ptr = (i_ptr + 1) % 256
v2 = S[i_ptr]
j_ptr = (j_ptr + v2 + current_rot_str[i_ptr % 21]) % 256
S[i_ptr], S[j_ptr] = S[j_ptr], S[i_ptr]
res_idx = (S[i_ptr] + S[j_ptr]) % 256
keystream.append(S[res_idx])
first = current_rot_str.pop(0)
current_rot_str.append(first)
ks_qwords = [
struct.unpack("<Q", bytes(keystream[i:i+8]))[0]
for i in range(0, 32, 8)
]
flag_parts = []
for i in range(4):
flag_parts.append(struct.pack("<Q", targets[i] ^ ks_qwords[i]))

flag = b"".join(flag_parts)
print(flag.decode(errors='ignore'))

if __name__ == "__main__":
solve()

15

Noncesense

IDA分析驱动

DriverEntry -> sub_14000154C

16

MajorFunction[0/2] = sub_140001000

MajorFunction[14] = sub_1400010C0

驱动核心在sub_1400010C0

17

0x222000:发 nonce

FsContext 在 open 时通过sub_140001668生成 16 字节随机值

0x222004:验签 + 加密

构造 AUTHv1 || nonce || 0 || len || payload

sub_140001A7C 计算 HMAC-SHA256,比较前 16 字节签名
常量 key:VIDAR_HGAME_A0th_HMaC_K1_bu1ld2026

sub_1400016B0:AES 单块加密(可见 Sbox、ShiftRows、MixColumns)

sub_140001908:AES-128 key schedule(Rcon 常量)

明文先 PKCS#7 padding,再每 16 字节块加密(ECB)

会话 AES key 派生

sub_1400019B8

v10 = HMAC_SHA256(key=00*32, msg=nonce16)

v39byte_140003250xor/ror 变换得到 32 字节

v11 = HMAC_SHA256(key=v10, msg=v39 || 0x01)

AES key = v11[:16]

总结:

  • KDF(两层 HMAC)

  • AES-ECB

具体数据可以从.rdata,.text字段dump

IDA分析客户端

18

main流程:

  1. 读用户输入

  2. CreateFile("\\\\.\\GATE_Driver")

  3. DeviceIoControl(..., 0x222000, ...) 取 nonce

  4. 对输入每字节跑一段小型“指令机”变换(unk_1400043F3

  5. 构造 AUTHv1 包 + HMAC(sub_140001A80

  6. 0x222004

  7. 成功后把返回密文写入 Drv_blob.bin

完整解密路线

Drv_blob.bin 出发:

  1. 取前 16 字节为 nonce

  2. 剩余为 ciphertext

  3. 用驱动 KDF 复现 AES key

  4. AES-ECB 解密 + 去 PKCS#7 padding,得到client变换后的明文

  5. 按索引逐字节做指令机逆变换,恢复原始输入(flag)

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
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
from Crypto.Cipher import AES
import hmac
import hashlib
import sys

BYTE_3250_HEX = "BFE743BAE2BFAB5291E64BA4200E2ED39179FBA4C10A2000F9EF029FEE029645"
byte_3250 = bytes.fromhex(BYTE_3250_HEX)

PROG_RAW = bytes([
0x05,0x02,0x0D,
0x04,0x02,0xC3,
0x02,0x00,0x02,
0x01,0x03,0x01,
0x05,0x03,0x03,
0x04,0x03,0x01,
0x06,0x03,0x07,
0x07,0x00,0x03,
0x03,0x00,0x5A,
0x00,0x00,0x00,
0x00,0x00,0x00,
0x00,0x00,0x00
])

def ror8(x: int, r: int) -> int:
r &= 7
return ((x >> r) | ((x << (8 - r)) & 0xFF)) & 0xFF

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

def pkcs7_unpad(x: bytes) -> bytes:
if not x:
return x
pad = x[-1]
if pad == 0 or pad > 16 or pad > len(x):
return x
if x[-pad:] != bytes([pad]) * pad:
return x
return x[:-pad]

def build_v39() -> bytes:
out = bytearray(32)
for i in range(32):
rot = (1 - 3 * i) & 7
out[i] = ror8(byte_3250[i] ^ 0x5C, rot) ^ 0xA7
return bytes(out)

def derive_aes_key_from_nonce(nonce16: bytes) -> bytes:
if len(nonce16) != 16:
raise ValueError("nonce must be 16 bytes")

# v10 = HMAC_SHA256(key=00*32, msg=nonce)
v10 = hmac.new(b"\x00" * 32, nonce16, hashlib.sha256).digest()

# v11 = HMAC_SHA256(key=v10, msg=v39||0x01)
v39 = build_v39()
v11 = hmac.new(v10, v39 + b"\x01", hashlib.sha256).digest()

return v11[:16]

def client_forward_byte(ch: int, idx: int) -> int:
st = [ch & 0xFF, idx & 0xFF, 0, 0]
op = 0
dst = 2
src = 1

p = 0
for _ in range(256):
if not (0 <= op <= 6):
break
if not (0 <= dst <= 3):
break

if op == 0:
if not (0 <= src <= 3): break
st[dst] = st[src]
elif op == 1:
if not (0 <= src <= 3): break
st[dst] ^= st[src]
elif op == 2:
st[dst] ^= (src & 0xFF)
elif op == 3:
st[dst] = (st[dst] + (src & 0xFF)) & 0xFF
elif op == 4:
st[dst] = (st[dst] * (src & 0xFF)) & 0xFF
elif op == 5:
st[dst] &= (src & 0xFF)
elif op == 6:
if not (0 <= src <= 3): break
st[dst] = rol8(st[dst], st[src] & 7)

if p + 2 >= len(PROG_RAW):
break
b0 = PROG_RAW[p]
b1 = PROG_RAW[p + 1]
b2 = PROG_RAW[p + 2]
p += 3

dst = b1
src = b2
op = ((b0 - 1) & 0xFF)

return st[0] & 0xFF


def build_inverse_table_for_index(idx: int):
inv = [-1] * 256
for b in range(256):
o = client_forward_byte(b, idx)
if inv[o] == -1:
inv[o] = b
return inv

def inverse_client_transform(data: bytes) -> bytes:
out = bytearray(len(data))
for i, c in enumerate(data):
inv = build_inverse_table_for_index(i & 0xFF)
v = inv[c]
if v == -1:
v = c
out[i] = v
return bytes(out)

def solve_blob(blob: bytes):
if len(blob) < 32:
raise ValueError("blob too short")

nonce = blob[:16]
ct = blob[16:]

if len(ct) % 16 != 0:
raise ValueError(f"ciphertext len {len(ct)} is not multiple of 16")

key = derive_aes_key_from_nonce(nonce)

pt_trans = AES.new(key, AES.MODE_ECB).decrypt(ct)
pt_trans = pkcs7_unpad(pt_trans)

pt_raw = inverse_client_transform(pt_trans)
return nonce, key, pt_trans, pt_raw


def print_best_text(bs: bytes):
print("[+] recovered (bytes):", bs)
for enc in ("utf-8", "gbk", "latin1"):
try:
s = bs.decode(enc)
print(f"[+] recovered ({enc}): {s}")
return
except Exception:
pass
print("[+] recovered (text): <decode failed>")


def main():
in_file = "Drv_blob.bin"
if len(sys.argv) >= 2:
in_file = sys.argv[1]

with open(in_file, "rb") as f:
blob = f.read()

nonce, key, pt_trans, pt_raw = solve_blob(blob)

print("[+] file :", in_file)
print("[+] nonce :", nonce.hex())
print("[+] aes_key :", key.hex())
print("[+] trans_len :", len(pt_trans))
print("[+] raw_len :", len(pt_raw))
print("[+] transformed(hex):", pt_trans.hex())
print_best_text(pt_raw)

low = pt_raw.lower()
if b"flag{" in low or b"hgame{" in low or b"vidar{" in low:
print("[+] possible flag detected.")


if __name__ == "__main__":
main()

19


Androuge

apktool + jadx

20

找按键交互

游戏本体在waw和game

21

分别是程序和数据表

waw是ARM aarch64架构,no stripped

逆一下

22

根据函数名,是一个lua语言编写的程序

找一下game数据表是在哪里载入的

lua_load → luaD_protectedparser → f_parser → luaU_undump

23

可以看到,是在对dump数据进行校验,每字节数据都xor0x9C

猜测game数据表不是明文,而是经过异或加密的密文,

24

将前四字节除了第一字节还原后,刚好是.Waw

25

dump一下还原后的数据表,发现有不少游戏数据以及flag的信息

26

27

28

通过这些信息,大概能推断出获取flag的机制:

在游戏中到达target_floor,程序就会计算出flag并显示出来

我的思路是直接修改明文数据表,然后再加密回去,用patch后的数据表快速通关获得flag

在game_dec中检索对应游戏数值的字符串,修改数值

29

这里很容易就能发现数值段有一个03 XX 00 00 00 00 00 00 00 04的固定结构

我第一次尝试的时候直接把target_floor改成了1,结果flag是乱码,说明这个数据影响了flag生成,改为patch一些只优化游戏体验的数值

30

然后加密回去,在终端用qemu-aarch64跑./waw game_patch

花一点时间通关

31

32

Ouroboros

运行 jar

33

静态分析

TradeService

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.seal.ouroborosapp.domain;

import com.seal.ouroborosapi.RiskEngine;
import org.springframework.stereotype.Service;

@Service
/* loaded from: ouroboros-app-0.0.1-SNAPSHOT.jar:BOOT-INF/classes/com/seal/ouroborosapp/domain/TradeService.class */
public class TradeService {
private RiskEngine riskEngine = new RiskPolicy();

public String executeTrade(String token, double amount) {
return this.riskEngine.check(token, amount);
}
}

RiskPolicy

1
2
3
4
5
6
7
8
9
10
11
package com.seal.ouroborosapp.domain;

import com.seal.ouroborosapi.RiskEngine;

/* loaded from: ouroboros-app-0.0.1-SNAPSHOT.jar:BOOT-INF/classes/com/seal/ouroborosapp/domain/RiskPolicy.class */
public class RiskPolicy implements RiskEngine {
@Override // com.seal.ouroborosapi.RiskEngine
public String check(String token, double amount) {
return null;
}
}

return null,显然不是最终逻辑

ShadowLoader

1
2
3
4
5
6
7
public com.seal.ouroborosapi.RiskEngine initContext() throws java.lang.IllegalAccessException, java.lang.NoSuchMethodException, java.lang.ClassNotFoundException, java.lang.SecurityException, java.io.IOException, java.lang.IllegalArgumentException, java.lang.reflect.InvocationTargetException {
/*
Method dump skipped, instructions count: 830
To view this dump change 'Code comments level' option to 'DEBUG'
*/
throw new UnsupportedOperationException("Method not decompiled: com.seal.ouroborosapp.infra.ShadowLoader.initContext():com.seal.ouroborosapi.RiskEngine");
}

这里jadx挂了,直接解包用cfr反编译

1
2
3
4
5
6
7
8
9
10
11
12
int k = IntegrityVerifier.getDeriveKey();
byte[] d = read("/application-data.db"); //R_NAME

long s = k & 0xffffffffL;
for i in [0..len-1]:
s = (s * 1103515245 + 12345) & 0xffffffff
x = (s >>> 16) & 0xff
d[i] ^= x

payload = d[128:]
JarInputStream(payload) -> load classes
target class endsWith("RealRiskEngine")

而核心代码如上

隐藏了两个关键类:RealRiskEngine,OuroborosVM

所以“衔尾蛇”的含义就是

壳中壳 + 动态还原 + 自定义 VM

RealRiskEngine

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
public class RealRiskEngine
implements RiskEngine {
private static final byte[] LEGACY_STORE = new byte[]{-9, -17, -49, 7, 49, -50, -2, 54, 21, -65, -60, 107, 30, -42, 124, -83, 95, -100, -119, 108, 76, 62, -60, 14, -100, -19, -34, 63, -8, 6, -119, 1, -103, 17, 42, 82, -103, 23, 90, 73, -65, -85, 107, -22, 116, 115, 11, -41};
private static final String LEGACY_KEY = "Ouroboros_Legacy";
private static final String LEGACY_IV = "1234567812345678";

public String check(String token, double amount) {
if (this.checkLegacy(token)) {
return "Legacy Access: Validated (But version expired).";
}
boolean isCorrect = OuroborosVM.execute(token, amount);
if (isCorrect) {
return "ACCESS GRANTED: Core Logic Validated.";
}
return null;
}

private boolean checkLegacy(String inputToken) {
try {
if (inputToken == null || inputToken.length() < 10) {
return false;
}
IvParameterSpec iv = new IvParameterSpec(LEGACY_IV.getBytes(StandardCharsets.UTF_8));
SecretKeySpec skeySpec = new SecretKeySpec(LEGACY_KEY.getBytes(StandardCharsets.UTF_8), "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
cipher.init(2, (Key)skeySpec, iv);
byte[] original = cipher.doFinal(LEGACY_STORE);
String decoyFlag = new String(original, StandardCharsets.UTF_8);
return inputToken.equals(decoyFlag);
}
catch (Exception ex) {
return false;
}
}
}

尝试AES-CBC解密,解密成功给出fake_flag

flag{N0p3_Th1s_1s_A_D3c0y_G0_B4ck}

失败则走OuroborosVM

OuroborosVM

自定义VM,代码附带超长opcode和handlers,就不贴了

token(flag)存进memory

derive_keydecFIRMWARE获得VM指令流校验token

已知opcode

  • 0x10:push immediate (16-bit)

  • 0x20:push token.length

  • 0x30:load memory[addr]

  • 0x35:xor

  • 0x4A:条件跳

  • 0xFF:结束并判断栈顶是否为 1

构造z3约束求解器

34

Marionette

main

35

1
2
3
4
5
6
v6 += 2LL;
v25.m128i_i8[v5++] = v10 | (16 * v9);
...
while ( v5 <= 0xF );
if ( v5 != 16 )
return 1;

输入32个十六进制字符,每两个转成一字节存储,总共16B

36

最后memcmp密文

37

分析可知是 fork + ptrace 的木偶

38

sub_401D4E被int3打断,不可读

39

xmm1的值为硬编码xmmword_405020,用作aeskeygenassist

40

trace_parent

可以dump出off_405070,qword_406338,qword_4075F8,喂给ai纯静态还原状态机,但可操性更强的是动调

因为子进程会被父进程ptrace控制,所以需要detach child,单独调父进程

在gdb里直接实现记录idx,打印对应的汇编语块,最后输出idx状态序列

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
set pagination off
set confirm off
set disassemble-next-line off

set follow-fork-mode parent
set detach-on-fork on

handle SIGALRM nostop noprint pass
handle SIGPIPE nostop noprint pass

set logging file gdb_trace.log
set logging overwrite on
set logging enabled on

define pidxblk
set $idx = (unsigned int)$edx
set $tbl = (unsigned long long)0x405070
set $tpl = *(unsigned long long*)($tbl + $idx*8)
printf "[IDX] %u tpl=%#lx\n", $idx, $tpl
x/6i $tpl
end

catch fork
commands
silent
continue
end

b *0x4015A6
commands
silent
pidxblk
continue
end

run

set logging enabled off
shell awk '/^\[IDX\] /{print $2}' gdb_trace.log > idx_seq.txt

run

41

分析汇编,发现这600个block含有AES makekey/AES rounds/XOR/垃圾块,核心运算不是很多,按照状态序列求逆即可

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
import re

target = bytes.fromhex("8cadb48febfd6fae8660ad44c3c75a31")
key0 = bytes.fromhex("5a097c137b8d4f2132be3b19af449c01")

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

def xtime(x):
x <<= 1
if x & 0x100: x ^= 0x11B
return x & 0xff

def mul(a,b):
r = 0
while b:
if b & 1: r ^= a
a = xtime(a)
b >>= 1
return r

def rotw(w): return w[1:]+w[:1]
def subw(w): return bytes(S[x] for x in w)
def xorb(a,b): return bytes(x^y for x,y in zip(a,b))

def expand_key(k, rounds):
ws = [k[i:i+4] for i in range(0,16,4)]
rcon = 1
while len(ws) < 4*(rounds+1):
t = ws[-1]
if len(ws) % 4 == 0:
t = xorb(subw(rotw(t)), bytes([rcon,0,0,0]))
rcon = xtime(rcon)
ws.append(xorb(ws[-4], t))
return [b''.join(ws[i*4:(i+1)*4]) for i in range(rounds+1)]

def b2s(b): return [[b[c*4+r] for c in range(4)] for r in range(4)]
def s2b(s): return bytes(s[r][c] for c in range(4) for r in range(4))

def addk(s, rk):
k = b2s(rk)
for r in range(4):
for c in range(4):
s[r][c] ^= k[r][c]

def inv_sub(s):
for r in range(4):
for c in range(4):
s[r][c] = IS[s[r][c]]

def inv_shift(s):
for r in range(1,4):
s[r] = s[r][-r:] + s[r][:-r]

def inv_mix(s):
for c in range(4):
a = [s[r][c] for r in range(4)]
s[0][c] = mul(a[0],14)^mul(a[1],11)^mul(a[2],13)^mul(a[3],9)
s[1][c] = mul(a[0],9)^mul(a[1],14)^mul(a[2],11)^mul(a[3],13)
s[2][c] = mul(a[0],13)^mul(a[1],9)^mul(a[2],14)^mul(a[3],11)
s[3][c] = mul(a[0],11)^mul(a[1],13)^mul(a[2],9)^mul(a[3],14)

def dec_custom(ct, key, rounds):
rks = expand_key(key, rounds)
s = b2s(ct)
addk(s, rks[rounds]); inv_shift(s); inv_sub(s)
for r in range(rounds-1,0,-1):
addk(s, rks[r]); inv_mix(s); inv_shift(s); inv_sub(s)
addk(s, rks[0])
return s2b(s)

idx = [int(x) for x in re.findall(r'\d+', open('idx_seq.txt').read())]
txt = open('gdb_trace.log').read()

sec = {int(i):b for i,b in re.findall(r'(?ms)^\[IDX\]\s+(\d+)\s*\n(.*?)(?=^\[IDX\]\s+\d+\s*\n|\Z)', txt)}
body = ''.join(sec.get(i,'') for i in idx) if sec else txt

rounds = body.count('aesenc ') + body.count('aesenclast ') or 12
mid = bytearray(dec_custom(target, key0, rounds))

if 'xor' in body and any(x in body for x in ('%al','%cl','%dl')):
for i in range(1,16):
mid[i] ^= mid[i-1]

print(mid.hex())

42