1.Illusion
[hook]
表面上看是 RC4
其实被hook到了AES上
from struct import pack
SBOX = [
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,
]
INV_SBOX = [0] * 256
for i, v in enumerate(SBOX):
INV_SBOX[v] = i
RCON = [0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80,0x1B,0x36]
def bytes2matrix(b):
return [list(b[i:i+4]) for i in range(0, len(b), 4)]
def matrix2bytes(m):
return bytes(sum(m, []))
def add_round_key(s, k):
for i in range(4):
for j in range(4):
s[i][j] ^= k[i][j]
def inv_sub_bytes(s):
for i in range(4):
for j in range(4):
s[i][j] = INV_SBOX[s[i][j]]
def inv_shift_rows(s):
s[0][1], s[1][1], s[2][1], s[3][1] = s[3][1], s[0][1], s[1][1], s[2][1]
s[0][2], s[1][2], s[2][2], s[3][2] = s[2][2], s[3][2], s[0][2], s[1][2]
s[0][3], s[1][3], s[2][3], s[3][3] = s[1][3], s[2][3], s[3][3], s[0][3]
def xtime(a):
return (((a << 1) ^ 0x1B) & 0xFF) if (a & 0x80) else ((a << 1) & 0xFF)
def mul(a, b):
res = 0
while b:
if b & 1:
res ^= a
a = xtime(a)
b >>= 1
return res
def inv_mix_columns(s):
for i in range(4):
a = s[i]
s[i] = [
mul(a[0],14) ^ mul(a[1],11) ^ mul(a[2],13) ^ mul(a[3],9),
mul(a[0],9) ^ mul(a[1],14) ^ mul(a[2],11) ^ mul(a[3],13),
mul(a[0],13) ^ mul(a[1],9) ^ mul(a[2],14) ^ mul(a[3],11),
mul(a[0],11) ^ mul(a[1],13) ^ mul(a[2],9) ^ mul(a[3],14),
]
def expand_key(master_key):
key_columns = bytes2matrix(master_key)
i = 1
while len(key_columns) < 44:
word = key_columns[-1][:]
if len(key_columns) % 4 == 0:
word = word[1:] + word[:1]
word = [SBOX[x] for x in word]
word[0] ^= RCON[i - 1]
i += 1
word = [a ^ b for a, b in zip(word, key_columns[-4])]
key_columns.append(word)
return [key_columns[4*i:4*(i+1)] for i in range(11)]
def decrypt_block(block, key):
round_keys = expand_key(key)
state = bytes2matrix(block)
add_round_key(state, round_keys[10])
for rnd in range(9, 0, -1):
inv_shift_rows(state)
inv_sub_bytes(state)
add_round_key(state, round_keys[rnd])
inv_mix_columns(state)
inv_shift_rows(state)
inv_sub_bytes(state)
add_round_key(state, round_keys[0])
return matrix2bytes(state)
def ecb_decrypt(data, key):
out = b""
for i in range(0, len(data), 16):
out += decrypt_block(data[i:i+16], key)
return out
key = pack("<IIII", 873608210, 873608210, 873608210, 559105345)
ct = bytes.fromhex("f27b7e75b45c08fa193c8a4a04f81f671b059ce72740786d28f6a8b806c6c551")
pt = ecb_decrypt(ct, key)
pad = pt[-1]
pt = pt[:-pad]
flag = b"xmctf{" + pt + b"}"
print(flag.decode())
这题表面主逻辑是 RC4,但真正的 flag 在 MessageBoxA 钩子里的 AES 分支里
分析main函数
前缀固定为 xmctf{
总长度固定为 25
} 在最后一位
中间 18 字节会被取出来参与校验
sub_140001440 很像标准 RC4:
初始化 256 字节 S 盒
用密钥 nev_gona_give_up 做 KSA
再做 PRGA 异或输入
最后与一段 18 字节常量比较
逆出来,可以得到nev_gona_letydown\x07 但是是fakeflag
hook函数
MessageBoxA 本来是 系统弹窗函数
但是程序一开始还调用了 sub_140001000:
int sub_140001000()
{
int (__stdcall *MessageBoxA)(HWND, LPCSTR, LPCSTR, UINT); // rax
_DWORD *lpAddress; // rcx
__int128 v3; // [rsp+20h] [rbp-18h]
DWORD flOldProtect; // [rsp+40h] [rbp+8h] BYREF
MessageBoxA = (int (__stdcall *)(HWND, LPCSTR, LPCSTR, UINT))NtCurrentPeb();
if ( (*((_BYTE *)MessageBoxA + 188) & 0x70) == 0 )
{
MessageBoxA = (int (__stdcall *)(HWND, LPCSTR, LPCSTR, UINT))GetModuleHandleA("user32.dll");
if ( MessageBoxA )
{
MessageBoxA = (int (__stdcall *)(HWND, LPCSTR, LPCSTR, UINT))GetProcAddress((HMODULE)MessageBoxA, "MessageBoxA");
::lpAddress = MessageBoxA;
if ( MessageBoxA )
{
VirtualProtect(MessageBoxA, 0xEu, 0x40u, &flOldProtect);
lpAddress = ::lpAddress;
LOWORD(v3) = -18360;
WORD5(v3) = -7937;
qword_140028AE8 = *(_QWORD *)::lpAddress;
dword_140028AF0 = *((_DWORD *)::lpAddress + 2);
word_140028AF4 = *((_WORD *)::lpAddress + 6);
lpAddress_0 = (__int64)::lpAddress;
*(_QWORD *)((char *)&v3 + 2) = sub_1400010F0;
*(_QWORD *)::lpAddress = v3;
lpAddress[2] = DWORD2(v3);
*((_WORD *)lpAddress + 6) = 0;
LODWORD(MessageBoxA) = VirtualProtect(::lpAddress, 0xEu, flOldProtect, &flOldProtect);
}
}
}
return (int)MessageBoxA;
}
动态获取 user32!MessageBoxA
直接改写 MessageBoxA 的前几个字节,把它 hook 到 sub_1400010F0
也就是说,后面如果程序走到 MessageBoxA,实际执行的是作者自己写的逻辑。

那关键自然在 sub_1400010F0
__int64 __fastcall sub_1400010F0(__int64 a1, char *a2, const char *p_real_world, unsigned int a4)
{
_BYTE *v8; // r14
_BYTE *v9; // rdi
__int64 n2; // rsi
char v11; // r8
bool v12; // cl
bool v13; // dl
bool v14; // al
bool v15; // cl
bool v16; // al
bool v17; // dl
bool v18; // al
bool v19; // cl
bool v20; // al
char v21; // dl
bool v22; // al
char v23; // cl
char v24; // al
char v25; // dl
char v26; // al
char v27; // cl
char v28; // al
char v29; // dl
char v30; // al
char v31; // cl
char v32; // al
char v33; // dl
char v34; // al
char v35; // cl
char v36; // al
char v37; // dl
char v38; // al
char v39; // cl
char v40; // al
char v41; // dl
char v42; // al
char v43; // cl
char v44; // al
char v45; // dl
char v46; // al
char v47; // cl
char v48; // al
char v49; // dl
char v50; // cl
char v51; // al
char v52; // dl
char v53; // al
char v54; // cl
char v55; // al
char v56; // dl
char v57; // al
char v58; // cl
char v59; // al
char v60; // dl
char v61; // al
char v62; // cl
char v63; // al
char v64; // dl
char v65; // al
char v66; // cl
char v67; // al
char v68; // dl
char *v69; // rcx
char v70; // al
_DWORD *lpAddress; // r10
DWORD flOldProtect; // [rsp+20h] [rbp-E0h] BYREF
__int128 Src; // [rsp+28h] [rbp-D8h] BYREF
__int16 v75; // [rsp+38h] [rbp-C8h]
_BYTE v76[192]; // [rsp+40h] [rbp-C0h] BYREF
_DWORD v77[4]; // [rsp+100h] [rbp+0h] BYREF
_OWORD v78[4]; // [rsp+110h] [rbp+10h] BYREF
v77[0] = 873608210;
v77[1] = 873608210;
v77[2] = 873608210;
Src = *(_OWORD *)p_Destination;
v77[3] = 559105345;
v75 = word_140028B10;
v8 = (_BYTE *)sub_140001690(&Src);
sub_1400019F0(v76, v77);
v9 = v8;
n2 = 2;
do
{
sub_1400019E0(v76, v9);
v9 += 16;
--n2;
}
while ( n2 );
v11 = 0;
v12 = 0;
v13 = 0;
if ( v8[1] == 123 )
v12 = *v8 == 0xF2;
v14 = v12;
v15 = 0;
if ( v8[2] == 126 )
v13 = v14;
v16 = v13;
v17 = 0;
if ( v8[3] == 117 )
v15 = v16;
v18 = v15;
v19 = 0;
if ( v8[4] == 0xB4 )
v17 = v18;
v20 = v17;
v21 = 0;
if ( v8[5] == 92 )
v19 = v20;
v22 = v19;
v23 = 0;
if ( v8[6] == 8 )
v21 = v22;
v24 = v21;
v25 = 0;
if ( v8[7] == 0xFA )
v23 = v24;
v26 = v23;
v27 = 0;
if ( v8[8] == 25 )
v25 = v26;
v28 = v25;
v29 = 0;
if ( v8[9] == 60 )
v27 = v28;
v30 = v27;
v31 = 0;
if ( v8[10] == 0x8A )
v29 = v30;
v32 = v29;
v33 = 0;
if ( v8[11] == 74 )
v31 = v32;
v34 = v31;
v35 = 0;
if ( v8[12] == 4 )
v33 = v34;
v36 = v33;
v37 = 0;
if ( v8[13] == 0xF8 )
v35 = v36;
v38 = v35;
v39 = 0;
if ( v8[14] == 31 )
v37 = v38;
v40 = v37;
v41 = 0;
if ( v8[15] == 103 )
v39 = v40;
v42 = v39;
v43 = 0;
if ( v8[16] == 27 )
v41 = v42;
v44 = v41;
v45 = 0;
if ( v8[17] == 5 )
v43 = v44;
v46 = v43;
v47 = 0;
if ( v8[18] == 0x9C )
v45 = v46;
v48 = v45;
v49 = 0;
if ( v8[19] == 0xE7 )
v47 = v48;
if ( v8[20] == 39 )
v49 = v47;
v50 = 0;
v51 = v49;
v52 = 0;
if ( v8[21] == 64 )
v50 = v51;
v53 = v50;
v54 = 0;
if ( v8[22] == 120 )
v52 = v53;
v55 = v52;
v56 = 0;
if ( v8[23] == 109 )
v54 = v55;
v57 = v54;
v58 = 0;
if ( v8[24] == 40 )
v56 = v57;
v59 = v56;
v60 = 0;
if ( v8[25] == 0xF6 )
v58 = v59;
v61 = v58;
v62 = 0;
if ( v8[26] == 0xA8 )
v60 = v61;
v63 = v60;
v64 = 0;
if ( v8[27] == 0xB8 )
v62 = v63;
v65 = v62;
v66 = 0;
if ( v8[28] == 6 )
v64 = v65;
v67 = v64;
v68 = 0;
if ( v8[29] == 0xC6 )
v66 = v67;
if ( v8[30] == 0xC5 )
v68 = v66;
if ( v8[31] == 81 )
v11 = v68;
memset(v78, 0, sizeof(v78));
if ( v11 )
{
*(_QWORD *)&v78[0] = 0xE6D5E9B9C3BBD1D0uLL;
p_real_world = "real world";
WORD4(v78[0]) = -19522;
BYTE10(v78[0]) = 0;
}
else
{
v69 = (char *)((char *)v78 - a2);
do
{
v70 = *a2;
v69[(_QWORD)a2] = *a2;
++a2;
}
while ( v70 );
}
VirtualProtect(::lpAddress, 0xEu, 0x40u, &flOldProtect);
lpAddress = ::lpAddress;
*(_QWORD *)::lpAddress = qword_140028AE8;
lpAddress[2] = dword_140028AF0;
*((_WORD *)lpAddress + 6) = word_140028AF4;
VirtualProtect(::lpAddress, 0xEu, flOldProtect, &flOldProtect);
return lpAddress_0(a1, v78, p_real_world, a4);
}
有第二层校验:
1. 先把一个 16 字节 key 做 AES 密钥扩展
2. 再把输入按 16 字节分组做加密
3. 最后把结果与硬编码的 32 字节密文比较
这里的 key 在代码里是 4 个 DWORD:
873608210, 873608210, 873608210, 559105345
按小端拼出来是
1234123412341234AES!

目标密文是: v8
f27b7e75b45c08fa193c8a4a04f81f671b059ce72740786d28f6a8b806c6c551
把这段密文用 AES-128-ECB 解密,去掉 PKCS#7 padding,得到明文:
R3a1_w0rld_M47ters
flag是
xmctf{R3a1_w0rld_M47ters}
exp
import struct
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad
def solve():
dwords = [873608210, 873608210, 873608210, 559105345]
key = b"".join([struct.pack('<I', d) for d in dwords])
print(f" Derived Key (Bytes): {key}")
print(f" Derived Key (Repr): {key.decode(errors='ignore')}")
ct_hex = "f27b7e75b45c08fa193c8a4a04f81f671b059ce72740786d28f6a8b806c6c551"
ciphertext = bytes.fromhex(ct_hex)
cipher = AES.new(key, AES.MODE_ECB)
decrypted = cipher.decrypt(ciphertext)
try:
flag_content = unpad(decrypted, AES.block_size).decode('utf-8')
except (ValueError, KeyError):
flag_content = decrypted.decode('utf-8', errors='ignore').strip()
print("-" * 30)
print(f"[+] Decrypted Flag: xmctf{{{flag_content}}}")
print("-" * 30)
if __name__ == "__main__":
solve()
flag
xmctf{R3a1_w0rld_M47ters}
2.ez_uds
uds(统一诊断服务)
核心服务 0x27 SecurityAccess。
连接远程后,服务端会给出提示:
27 01 -> Request Seed
27 02 <4byteskey> -> Send Key
也就是说流程很标准:
1. 发送 2701 请求 seed
2. 服务端返回 67 01 XX XX XX XX
3. 根据 seed 算出 key
4. 发送 2702 <key>
5. 认证成功后拿 flag
算法
def generate_seed():
return random.randint(0, 0xFFFFFFFF)
def calculate_key(seed):
key = seed ^ 0xA5A5A5A5
key = ((key << 3) | (key >> 29)) & 0xFFFFFFFF #循环左移 变成了 [4-32位][1-3位]
key = (key + 0x12345678) & 0xFFFFFFFF
return key
67 01 后面的 4 字节 seed 按大端解析
计算出的 key 也按大端拆成 4 个字节发送
如果按小端处理,会收到否定响应 7F 27 35
例如服务端返回:
67 01 1A 2E 0E 7F
那么:
seed = 0x1A2E0E7F
key = calculate_key(seed) = 0x0E91B54D
发送:
2702 0E 91 B5 4D
即可通过认证。
exp
import socket
import re
HOST = "nc1.ctfplus.cn"
PORT = 43593
def calculate_key(seed):
key = seed ^ 0xA5A5A5A5
key = ((key << 3) | (key >> 29)) & 0xFFFFFFFF
key = (key + 0x12345678) & 0xFFFFFFFF
return key
def recv_some(sock):
sock.settimeout(1)
data = b""
try:
while True:
part = sock.recv(4096)
if not part:
break
data += part
if len(part) < 4096:
break
except Exception:
pass
return data.decode(errors="ignore")
with socket.create_connection((HOST, PORT), timeout=8) as s:
print(recv_some(s))
s.sendall(b"2701\n")
resp = recv_some(s)
print(resp)
m = re.search(r"67 01 ((?:[0-9A-F]{2} ){3}[0-9A-F]{2})", resp)
seed_bytes = bytes(int(x, 16) for x in m.group(1).split())
seed = int.from_bytes(seed_bytes, "big")
key = calculate_key(seed)
key_bytes = key.to_bytes(4, "big")
payload = "2702 " + " ".join(f"{b:02X}" for b in key_bytes) + "\n"
print("send:", payload.strip())
s.sendall(payload.encode())
print(recv_some(s))
3.ezFinger
[IOT]
描述是
sub_8003498 和 sub_8000EC0 处对应的函数名是什么?flag格式xmctf{名称1_名称2}
对应函数名:
sub_8003498 -> HAL_RCC_GetSysClockFreq
HAL: Hardware Abstraction Layer(硬件抽象层)。
RCC: Reset and Clock Control(复位与时钟控制)。
GetSysClockFreq: 获取系统时钟频率。
nsigned int sub_8003498()
{
int v1; // r3
__int64 v2; // r0
unsigned __int64 v3; // r0
if ( (MEMORY[0x40023808] & 0xC) == 4 )
return 8000000;
if ( (MEMORY[0x40023808] & 0xC) != 8 )
return 16000000;
v1 = (MEMORY[0x40023804] >> 6) & 0x1FF;
HIDWORD(v2) = (__int64)((unsigned int)(32 * v1) - (unsigned __int64)(unsigned int)v1) >> 26; #高 32 位
LODWORD(v2) = 1984 * v1; #低 32 位
if ( (MEMORY[0x40023804] & 0x400000) != 0 )
v3 = (8 * (v2 - ((unsigned int)(32 * v1) - (unsigned __int64)(unsigned int)v1)) + (unsigned int)v1) << 9;
else
v3 = (8 * (v2 - ((unsigned int)(32 * v1) - (unsigned __int64)(unsigned int)v1)) + (unsigned int)v1) << 10;
return sub_80002D0(v3, HIDWORD(v3), MEMORY[0x40023804] & 0x3F, 0) / (2 * ((HIWORD(MEMORY[0x40023804]) & 3u) + 1));
}
这是一个 系统频率计算器
if ( (MEMORY[0x40023808] & 0xC) == 4 ) ##直接内存访问##
这里在看 SWS 位。如果是 4,说明系统直接在用 HSE(外部高速时钟)。
它直接返回 8000000 (8MHz),因为程序员知道外接的是 8M 晶振。
if ( (MEMORY[0x40023808] & 0xC) != 8 )
如果不是 8(PLL),那就默认是 HSI(内部高速时钟)。
返回 16000000 (16MHz),这是芯片内置振荡器的标准频率。
sub_8003498 直接读 RCC->CFGR / RCC->PLLCFGR,按 SWS/PLLM/PLLN/PLLP 计算系统时钟,且被上层时钟配置函数用于更新
SystemCoreClock,这就是 HAL_RCC_GetSysClockFreq 的典型实现。
CFGR 是 Clock Configuration Register(时钟配置寄存器)
SWS (System clock switch status) —— 系统时钟切换状态 状态指示灯 00: 正在用 HSI(内部 16MHz =)。
01: 正在用 HSE(外部晶振)。
10: 正在用 PLL
PLLCFGR 是 Main PLL configuration register(主 PLL 配置寄存器)。
由于晶振(HSE/HSI)提供的频率通常只有 8MHz 或 16MHz,想让 CPU 跑到 168MHz 甚至更高,就需要这个加压泵(PLL)。
PLLSRC 选择 HSI(16M) 或 HSE(8M) 初始动力
PLLM除法 ($\div M$)
PLLN乘法 ($\times N$)
PLLP除法 ($\div P$)调整到额定速度
SWS 确认 PLL 已选定 供 CPU 使用
sub_8000EC0 -> digitalWrite
unsigned int __fastcall sub_8000EC0(unsigned int n0x5F, int a2)
{
int v2; // r4
int v4; // r0
if ( n0x5F > 0x5F )
v2 = -1;
else
v2 = aInMKi[n0x5F]; #引脚映射表 作用:硬件芯片(如 STM32)有 PA1, PB5 这种物理叫法,但用户喜欢用 1, 2, 3 这种数字。这个 aInMKi 数组就是一个“户口本”,记录了逻辑号 13 对应的是哪个物理端口和哪个引脚。编码方式:这里的 v2 通常是一个字节,高 4 位存端口(Port),低 4 位存引脚(Pin)。
if ( v2 != -1 )
{
n0x5F = sub_8000F10(v2, &unk_200001B8);
if ( n0x5F )
{
v4 = sub_8000F64((unsigned __int8)v2 >> 4); #提取端口索引
return sub_800128E(v4, (unsigned __int16)(1 << (v2 & 0xF)), a2); #低 4 位 BSRR 寄存器写入
}
}
return n0x5F;
}
sub_8000EC0 的参数是逻辑 pin 号(芯片封装(Package)上的物理位置。)和高低电平,通过 pin 映射表找到端口/引脚,再往 GPIO BSRR 写置位/复位,语义就是
digitalWrite(pin, value)。
如果要设置高电平:往 BSRR 的低 16 位写掩码。
如果要设置低电平:往 BSRR 的高 16 位写掩码。
告诉单片机的某个引脚(Pin),现在是输出高电压还是低电压。
所以 flag 是:
xmctf{HAL_RCC_GetSysClockFreq_digitalWrite}
4.Hulua Lua 5.3
[ Lua 字节码] 由 Lua 虚拟机(Lua VM) 解释执行的指令集
分析main函数
int __fastcall main(int argc, const char **argv, const char **envp)
{
FILE *______stdin_(Standard_Input)_______stdout_(; // rax
char Buffer[68]; // [rsp+30h] [rbp-60h] BYREF
unsigned int v6; // [rsp+74h] [rbp-1Ch]
__int64 v7; // [rsp+78h] [rbp-18h]
size_t v8; // [rsp+80h] [rbp-10h]
int v9; // [rsp+8Ch] [rbp-4h]
sub_140026690(*(__int64 *)&argc, (__int64)argv, (__int64)envp);
v9 = 0;
printf("Please enter the flag: ");
______stdin_(Standard_Input)_______stdout_( = (FILE *)psub_1400317D0();// 标准输入流
// stdin (Standard Input):标准输入
//
// stdout (Standard Output):标准输出
//
// stderr (Standard Error):标准错误输出
if ( !fgets(Buffer, 64, ______stdin_(Standard_Input)_______stdout_() )
return 1;
v8 = strlen(Buffer);
if ( v8 && Buffer[v8 - 1] == '\n' ) // 去除换行符
Buffer[v8 - 1] = 0;
v7 = luaL_newstate(); // 创建虚拟机
// v7是lua_State指针
luaL_openlibs(v7); // 记载lua标准库
lua_pushstring(v7, Buffer); // 将输入压入栈
lua_setglobal(v7, "user_input"); // 将压入栈的字符串 取出来, 在lua环境中 定义为user_input
v6 = luaL_loadbuffer(v7, (__int64)&dword_140033000, (unsigned int)dword_1400333DC, (unsigned int)"check", 0);// 加载字节码
// dword_140033000 字节码所在的内存地址
if ( (unsigned int)sub_14000152F(v7, v6) )
{
v6 = sub_140003F20(v7, 0, 1, 0, 0, 0);
if ( (unsigned int)sub_14000152F(v7, v6) )
{
if ( (unsigned int)sub_140001FC0(v7, 0xFFFFFFFFLL) == 1 )
{
v9 = sub_140002960(v7, 0xFFFFFFFFLL);
if ( v9 )
printf("\n[+] Congratulations! The flag is correct.\n");
else
printf("\n[-] Wrong flag. Try again.\n");
}
else
{
printf("\n[!] Error: Script returned non-boolean value.\n");
}
}
}
sub_14000EA30(v7);
system("pause");
return 0;
}
它会先读入用户输入,然后创建 Lua 虚拟机,把一段内存里的内容作为脚本 check 加载执
行,并把输入放到 Lua 全局变量 user_input 里。
sub_1400014A4
unsigned __int64 sub_1400014A4()
{
unsigned __int64 result; // rax
unsigned __int64 v1; // [rsp+18h] [rbp-18h]
unsigned __int64 i; // [rsp+28h] [rbp-8h]
v1 = (unsigned int)dword_1400333DC;
for ( i = 0; ; ++i )
{
result = i;
if ( i >= v1 )
break;
*((_BYTE *)&dword_140033000 + i) ^= aHulua[i % 5];
}
return result;
}
for ( i = 0; i < dword_1400333DC; ++i )
byte_140033000[i] ^= "hulua"[i % 5];
密文起点是 byte_140033000
长度是 dword_1400333DC = 0x3dc
密钥是 "hulua"
对应文件偏移就是:
起始偏移 0x31a00
长度 0x3dc
把这段数据用 hulua 循环异或
def decrypt():
encrypted_data = [
0x73, 0x39, 0x19, 0x14, 0x32, 0x68, 0x6C, 0xFF, 0x78, 0x6B, 0x72, 0x7F, 0x68, 0x7D, 0x65, 0x60, 0x7D, 0x14,
0x23, 0x61,
0x68, 0x75, 0x6C, 0x75, 0x61, 0x68, 0x75, 0x6C, 0x75, 0x61, 0x40, 0x02, 0x2C, 0x74, 0x61, 0x68, 0x75, 0x6C,
0x75, 0x61,
0x68, 0x75, 0x6C, 0x75, 0x60, 0x61, 0x57, 0x6C, 0x75, 0x61, 0x6B, 0x75, 0xEC, 0x75, 0x20, 0x68, 0x75, 0x6C,
0xF4, 0x21,
0x68, 0x75, 0x80, 0x75, 0x61, 0x68, 0x59, 0x2D, 0x75, 0x61, 0x2E, 0xF4, 0x2C, 0x75, 0x6E, 0xA8, 0xB5, 0x6E,
0x7B, 0x21,
0x68, 0xF5, 0x2F, 0x74, 0x61, 0x68, 0x13, 0x6D, 0x75, 0x60, 0x2E, 0xF4, 0x2C, 0x75, 0x00, 0x69, 0xF5, 0x6E,
0x3A, 0x61,
0xA9, 0x77, 0x62, 0x35, 0x61, 0xE8, 0x36, 0x6D, 0x75, 0x61, 0x0E, 0x74, 0x6C, 0x74, 0x21, 0x69, 0x75, 0x6E,
0xF5, 0x60,
0xE8, 0x75, 0x08, 0xF4, 0x61, 0x69, 0xF5, 0x6D, 0xF5, 0x60, 0xA8, 0x74, 0xEC, 0x77, 0x67, 0xEA, 0x35, 0x6C,
0xD1, 0xE0,
0xE8, 0x74, 0xAC, 0x74, 0x61, 0x6A, 0x75, 0x6E, 0x75, 0x60, 0x8C, 0xF4, 0x6C, 0x74, 0x6E, 0xA8, 0x74, 0x6F,
0x7B, 0xE1,
0x68, 0xF5, 0x6F, 0x77, 0xE1, 0x68, 0x53, 0x6E, 0x75, 0x60, 0x66, 0x35, 0x6C, 0xF5, 0x62, 0x6A, 0x75, 0x6C,
0x53, 0x63,
0x68, 0x74, 0x4A, 0x75, 0xE1, 0x68, 0x70, 0x6C, 0x75, 0x61, 0x6C, 0x6E, 0x5B, 0x4D, 0x41, 0x5E, 0x31, 0x4C,
0x43, 0x52,
0x48, 0x42, 0x58, 0x55, 0x57, 0x5E, 0x55, 0x5F, 0x47, 0x41, 0x5B, 0x45, 0x4C, 0x46, 0x53, 0x48, 0x46, 0x5A,
0x61, 0x01,
0x50, 0x37, 0x4C, 0x4D, 0x23, 0x48, 0x42, 0x5B, 0x55, 0x23, 0x2D, 0x55, 0x5A, 0x4D, 0x41, 0x5E, 0x44, 0x4C,
0x4D, 0x57,
0x48, 0x43, 0x54, 0x55, 0x24, 0x5D, 0x55, 0x5A, 0x46, 0x41, 0x2D, 0x30, 0x4C, 0x4D, 0x55, 0x48, 0x46, 0x59,
0x55, 0x57,
0x2E, 0x55, 0x59, 0x4D, 0x41, 0x2B, 0x4D, 0x4C, 0x40, 0x50, 0x48, 0x45, 0x2A, 0x55, 0x57, 0x2D, 0x55, 0x55,
0x41, 0x41,
0x5F, 0x45, 0x4C, 0x30, 0x56, 0x48, 0x47, 0x5A, 0x55, 0x58, 0x58, 0x55, 0x2E, 0x43, 0x41, 0x5F, 0x40, 0x4C,
0x30, 0x22,
0x48, 0x47, 0x54, 0x55, 0x20, 0x2E, 0x55, 0x5D, 0x41, 0x41, 0x2D, 0x47, 0x4C, 0x30, 0x52, 0x6C, 0x7E, 0x19,
0x06, 0x04,
0x1A, 0x2A, 0x05, 0x1B, 0x11, 0x1D, 0x01, 0x6C, 0x66, 0x41, 0x68, 0x75, 0x6C, 0x75, 0x61, 0x68, 0x75, 0x6D,
0x75, 0x61,
0x68, 0x74, 0x6C, 0x77, 0x61, 0x68, 0x75, 0x6C, 0x65, 0x61, 0x68, 0x75, 0x5F, 0x75, 0x61, 0x68, 0x77, 0x6C,
0x60, 0x31,
0x68, 0x75, 0x6C, 0xFE, 0x61, 0x68, 0x75, 0xA7, 0x75, 0x61, 0x68, 0x73, 0x6D, 0x35, 0x61, 0x6F, 0x34, 0x2C,
0x77, 0x21,
0x69, 0x75, 0x6C, 0xF4, 0xE0, 0x68, 0x75, 0xAD, 0xB4, 0x61, 0x68, 0x51, 0x6D, 0x75, 0x63, 0x83, 0x35, 0x6C,
0x75, 0x40,
0x69, 0xF5, 0x6D, 0x34, 0x60, 0x69, 0x75, 0xED, 0x34, 0x60, 0x68, 0xB4, 0xED, 0x75, 0x61, 0x00, 0x74, 0x6C,
0xF5, 0xEB,
0x68, 0x77, 0x68, 0x12, 0x20, 0x97, 0x0A, 0x2D, 0x74, 0x60, 0x68, 0xF4, 0x6D, 0x74, 0x61, 0xA9, 0x34, 0x6D,
0x75, 0x60,
0xEA, 0x75, 0x6C, 0xDD, 0xE0, 0x6A, 0xF5, 0xF9, 0x77, 0xE0, 0x6C, 0xE7, 0xEE, 0x35, 0x64, 0xEF, 0xF7, 0xEE,
0x74, 0xA6,
0x2A, 0x77, 0x6D, 0xA7, 0xA3, 0xEA, 0x77, 0xBE, 0xF7, 0xE3, 0x6D, 0x20, 0xED, 0xB4, 0x64, 0xAF, 0x37, 0x6D,
0x74, 0x66,
0x2B, 0x77, 0x6D, 0xFF, 0x61, 0xEB, 0x77, 0xE6, 0xB5, 0xE3, 0x6C, 0xD2, 0xAD, 0x89, 0x1E, 0xE9, 0x74, 0x6D,
0x75, 0xA0,
0x69, 0x74, 0x6C, 0x7E, 0x63, 0x68, 0x75, 0x27, 0x77, 0x61, 0x68, 0xF3, 0x6E, 0x35, 0x61, 0xEF, 0x37, 0x2C,
0x70, 0xA1,
0x6A, 0xF5, 0x6C, 0x74, 0xE2, 0x68, 0x75, 0x2D, 0xB6, 0x61, 0x68, 0xD1, 0x6E, 0x75, 0x63, 0x03, 0x37, 0x6C,
0x75, 0xE0,
0xEA, 0x75, 0x6C, 0x94, 0x63, 0xE8, 0x71, 0x6D, 0xF6, 0x61, 0x68, 0xDD, 0x6E, 0x73, 0xE1, 0xFA, 0xF6, 0x2C,
0x76, 0xF4,
0xE9, 0x34, 0x6B, 0xF2, 0xE2, 0x69, 0x74, 0xFE, 0xF6, 0xE2, 0x6B, 0xA0, 0xED, 0x34, 0x66, 0xEF, 0xB6, 0x6D,
0x74, 0xA6,
0xEB, 0x74, 0x6D, 0xFF, 0xA1, 0xEB, 0x76, 0xE6, 0xF5, 0x62, 0x6B, 0xF2, 0xEF, 0x74, 0x60, 0xAF, 0xB6, 0x6D,
0x74, 0xF3,
0xAB, 0x76, 0x6B, 0xE0, 0xE2, 0x29, 0x72, 0xAB, 0xF6, 0x62, 0x69, 0x72, 0x28, 0xF6, 0x65, 0x73, 0xB1, 0x6F,
0x7D, 0x7A,
0xAC, 0x34, 0x64, 0x33, 0x65, 0x2A, 0x75, 0x2B, 0x31, 0xA3, 0x60, 0xF5, 0x68, 0x75, 0x65, 0xAE, 0x71, 0x2C,
0x75, 0xA6,
0xEC, 0xB7, 0x65, 0x75, 0x64, 0x68, 0x7D, 0x88, 0x71, 0x61, 0x69, 0x11, 0x28, 0x75, 0x61, 0xCF, 0x37, 0x95,
0x0A, 0xE7,
0x6A, 0x37, 0x6C, 0xF2, 0xA3, 0x2A, 0x70, 0xAC, 0x77, 0x61, 0x6C, 0xD0, 0x6E, 0x75, 0x60, 0xCE, 0x77, 0x6C,
0x75, 0x47,
0x68, 0xF5, 0x6C, 0x79, 0x61, 0x68, 0x75, 0x68, 0x72, 0x12, 0x1C, 0x07, 0x05, 0x1B, 0x06, 0x6C, 0x70, 0x0E,
0x0C, 0x15,
0x0D, 0x66, 0x6D, 0x75, 0x61, 0x68, 0x75, 0x6C, 0x75, 0x61, 0x7B, 0x8A, 0x93, 0x8A, 0x9E, 0x97, 0x8A, 0x93,
0x8A, 0x72,
0x68, 0x75, 0x6C, 0x75, 0x61, 0x68, 0x75, 0x6C, 0x66, 0x9E, 0x68, 0x75, 0x6C, 0x75, 0x61, 0x68, 0x75, 0x7F,
0x75, 0x60,
0x68, 0x75, 0x6C, 0x75, 0x61, 0x68, 0x66, 0x0A, 0x75, 0x61, 0x68, 0x75, 0x6C, 0x75, 0x61, 0x6C, 0x73, 0x18,
0x14, 0x03,
0x04, 0x10, 0x68, 0x72, 0x08, 0x06, 0x06, 0x09, 0x07, 0x15, 0x6C, 0x70, 0x0F, 0x1D, 0x00, 0x1A, 0x71, 0x6B,
0x16, 0x0E,
0x06, 0x16, 0x0D, 0x01, 0x60, 0x68, 0x75, 0x6C, 0x75, 0x61, 0x68, 0x75, 0x6C, 0x75, 0x61, 0x68, 0x75, 0x6C,
0x75, 0x61,
0x68, 0x75, 0x6C, 0x75, 0x61, 0x68, 0x75, 0x53, 0x75, 0x61, 0x68, 0x30, 0x6C, 0x75, 0x61, 0x69, 0x75, 0x67,
0x61, 0x61,
0x68, 0x75, 0x2D, 0x75, 0x61, 0x68, 0xF3, 0x2C, 0x35, 0x61, 0xEF, 0xF5, 0x2C, 0x74, 0xA1, 0x68, 0x75, 0x6C,
0x74, 0xA0,
0x68, 0x75, 0xC8, 0x75, 0xE0, 0x69, 0x7B, 0x6C, 0x77, 0xE1, 0xE8, 0x74, 0xEC, 0x75, 0xA7, 0x29, 0x35, 0x6C,
0xB2, 0x60,
0xA9, 0x76, 0x6A, 0x37, 0x20, 0x68, 0x35, 0x6E, 0xF5, 0x63, 0xE9, 0xF7, 0x6D, 0x75, 0x45, 0x6A, 0xF5, 0x6D,
0x91, 0xE0,
0x68, 0x75, 0x21, 0xB5, 0x60, 0x6B, 0xDC, 0x2C, 0x75, 0x61, 0x42, 0x74, 0x91, 0x0A, 0x07, 0x68, 0x75, 0x6D,
0x53, 0x61,
0xE8, 0x75, 0x6B, 0x75, 0x61, 0x68, 0x71, 0x6D, 0x71, 0x66, 0x1B, 0x01, 0x1E, 0x1C, 0x0F, 0x0F, 0x71, 0x6B,
0x12, 0x0C,
0x09, 0x01, 0x0F, 0x1D, 0x65, 0x6C, 0x50, 0x14, 0x5E, 0x65, 0x6D, 0x16, 0x04, 0x14, 0x13, 0x6C, 0x7C, 0x18,
0x1A, 0x0F,
0x1D, 0x18, 0x0E, 0x10, 0x13, 0x7B, 0x65, 0x6C, 0x75, 0x61, 0x68, 0x75, 0x6C, 0x75, 0x60, 0x68, 0x75, 0x6C,
0x75, 0x61,
0x68, 0x75, 0x6C, 0x75, 0x61, 0x68, 0x75, 0x6C, 0x75, 0x61, 0x68, 0x75, 0x6C, 0x75, 0x61, 0x68, 0x75, 0x6C,
0x75, 0x61,
0x68, 0x75, 0x6C, 0x75, 0x61, 0x68, 0x75, 0x6C, 0xDC, 0x03, 0x00, 0x00
]
key = b"hulua"
decrypted = bytearray()
for i in range(len(encrypted_data)):
decrypted.append(encrypted_data[i] ^ key[i % len(key)])
try:
print("Decrypted String/Data:")
print(decrypted.decode('ascii'))
except UnicodeDecodeError:
print("Could not decode as ASCII. Raw Hex Result:")
print(decrypted.hex())
if __name__ == "__main__":
decrypt()
1b4c7561530019930d0a1a0a0408040808785600000000000000000000002877400100000000000000000000010922000000030080004100000081400000ec0000002c410000468140000fc0c0020e400080430100006601000146814000610180024f00c1020e400080430100006601000140010002800180006481000180018001c001800206824000a4818001c001000200020001e48100010fc001030e80008003028000260200010e40008003020000260200012600800005000000041b373820364420363320373420363620333220333020333220333614603842203842203737204245203638203631203836203638204535203633204545203834203335203646203538204338203531203046203645203934203730204537203236203930204236203735204543203238204146203134204532204533040b757365725f696e7075740013200000000000000001000000010002000000001000000033000000020015500000008b000000cb00000006014000074140024001000081810000c1c1000024010002eb400000210180014101010081410100c1810000680100808a0002046741ff7f4101010081010100c141010001820000a8810280950281049282400587828201c7420201d2c28202d28282055581c105c7420101074302018a0083028ac08204a7c1fc7f81010100c10101000b0200004b0200008602400087424005c00280000183000041c30000a40200026b42000081820000e102800401830000a802068092834003958141078783010192838303d581410787c30101c78301018ac083038a80030387830101c7c3010192c3030795834107c7830301074483041bc403081bc44108460442004744c20880040004c6044000c784c20900050008e404000164440000a742f97f8602420087c24205c0020004a5020001a6020000260080000c0000000407737472696e6704056279746513010000000000000013ffffffffffffffff13000000000000000013ff0000000000000013000100000000000013660000000000000004067461626c650407696e736572740405636861720407636f6e63617401000000000000000000000000000000000000000000003f0000004500000001000b14000000410000008640400087804001c000000001c10000a40081010e00028080018000c6414000c701c10306424100400280028182010024028001e48100004dc00103a94000002a01fd7f66000001260080000700000004010407737472696e670407676d61746368040425782b0405636861720409746f6e756d62657213100000000000000001000000000000000000000000000000000000000000000000000000000000000000a9626875
分析字节码
luac -l -l check.luac
字节码还原成可读逻辑 [Lua 工具箱](https://www.luatool.cn/)
保存为 .luac 文件 反汇编 unluac
.version 5.3
.format 0
.int_size 4
.size_t_size 8
.instruction_size 4
.integer_format 8
.float_format 8
.endianness LITTLE
.function main
.source null
.linedefined 0
.lastlinedefined 0
.numparams 0
.is_vararg 1
.maxstacksize 9
.upvalue null 0 true
.constant k0 "78 6D 63 74 66 32 30 32 36"
.constant k1 L"8B 8B 77 BE 68 61 86 68 E5 63 EE 84 35 6F 58 C8 51 0F 6E 94 70 E7 26 90 B6 75 EC 28 AF 14 E2 E3"
.constant k2 "user_input"
.constant k3 nil
.constant k4 32
loadbool r0 1 0
loadk r1 k0 ; k0 = "78 6D 63 74 66 32 30" (truncated)
loadk r2 k1 ; k1 = L"8B 8B 77 BE 68 61 86" (truncated)
closure r3 f0
closure r4 f1
gettabup r5 u0 k2 ; k2 = "user_input"
mul r0 r5 k3 ; k3 = nil
sub r0 k0 r1 ; k0 = "78 6D 63 74 66 32 30" (truncated)
loadbool r5 0 0
return r5 2
gettabup r5 u0 k2 ; k2 = "user_input"
le 5 r5 r0
mul r1 r5 k4 ; k4 = 32
sub r0 k0 r1 ; k0 = "78 6D 63 74 66 32 30" (truncated)
loadbool r5 0 0
return r5 2
move r5 r4
move r6 r1
call r5 2 2
move r6 r3
move r7 r5
gettabup r8 u0 k2 ; k2 = "user_input"
call r6 3 2
move r7 r4
move r8 r2
call r7 2 2
mul r0 r6 r7
sub r0 k0 r2 ; k0 = "78 6D 63 74 66 32 30" (truncated)
loadbool r8 1 0
return r8 2
sub r0 k0 r1 ; k0 = "78 6D 63 74 66 32 30" (truncated)
loadbool r8 0 0
return r8 2
return r0 1
.function main/f0
.source null
.linedefined 16
.lastlinedefined 51
.numparams 2
.is_vararg 0
.maxstacksize 21
.upvalue null 0 false
.constant k0 "string"
.constant k1 "byte"
.constant k2 1
.constant k3 -1
.constant k4 0
.constant k5 255
.constant k6 1.265E-321
.constant k7 102
.constant k8 "table"
.constant k9 "insert"
.constant k10 "char"
.constant k11 "concat"
newtable r2 0 0
newtable r3 0 0
gettabup r4 u0 k0 ; k0 = "string"
gettable r4 r4 k1 ; k1 = "byte"
move r5 r0
loadk r6 k2 ; k2 = 1
loadk r7 k3 ; k3 = -1
call r4 4 0
setlist r3 0 1
le 4 r3 r0
loadk r5 k4 ; k4 = 0
loadk r6 k5 ; k5 = 255
loadk r7 k2 ; k2 = 1
forprep r5 l16
.label l15
settable r2 r8 r8
.label l16
forloop r5 l15
loadk r5 k4 ; k4 = 0
loadk r6 k4 ; k4 = 0
loadk r7 k5 ; k5 = 255
loadk r8 k2 ; k2 = 1
forprep r6 l33
.label l22
bor r10 r9 r4
div r10 r10 k2 ; k2 = 1
gettable r10 r3 r10
gettable r11 r2 r9
div r11 r5 r11
div r11 r11 r10
bor r5 r11 k6 ; k6 = 1.265E-321
gettable r11 r2 r5
gettable r12 r2 r9
settable r2 r5 r12
settable r2 r9 r11
.label l33
forloop r6 l22
loadk r6 k4 ; k4 = 0
loadk r7 k4 ; k4 = 0
newtable r8 0 0
newtable r9 0 0
gettabup r10 u0 k0 ; k0 = "string"
gettable r10 r10 k1 ; k1 = "byte"
move r11 r1
loadk r12 k2 ; k2 = 1
loadk r13 k3 ; k3 = -1
call r10 4 0
setlist r9 0 1
loadk r10 k2 ; k2 = 1
le 11 r9 r0
loadk r12 k2 ; k2 = 1
forprep r10 l74
.label l49
div r14 r6 k2 ; k2 = 1
bor r6 r14 k6 ; k6 = 1.265E-321
gettable r14 r2 r6
div r14 r7 r14
bor r7 r14 k6 ; k6 = 1.265E-321
gettable r14 r2 r7
gettable r15 r2 r6
settable r2 r7 r15
settable r2 r6 r14
gettable r14 r2 r6
gettable r15 r2 r7
div r14 r14 r15
bor r14 r14 k6 ; k6 = 1.265E-321
gettable r15 r2 r14
gettable r16 r9 r13
not r16 r16
not r16 r16
gettabup r17 u0 k8 ; k8 = "table"
gettable r17 r17 k9 ; k9 = "insert"
move r18 r8
gettabup r19 u0 k0 ; k0 = "string"
gettable r19 r19 k10 ; k10 = "char"
move r20 r16
call r19 2 0
call r17 0 1
.label l74
forloop r10 l49
gettabup r10 u0 k8 ; k8 = "table"
gettable r10 r10 k11 ; k11 = "concat"
move r11 r8
tailcall r10 2
return r10 0
return r0 1
.function main/f1
.source null
.linedefined 63
.lastlinedefined 69
.numparams 1
.is_vararg 0
.maxstacksize 11
.upvalue null 0 false
.constant k0 ""
.constant k1 "string"
.constant k2 "gmatch"
.constant k3 "%x+"
.constant k4 "char"
.constant k5 "tonumber"
.constant k6 16
loadk r1 k0 ; k0 = ""
gettabup r2 u0 k1 ; k1 = "string"
gettable r2 r2 k2 ; k2 = "gmatch"
move r3 r0
loadk r4 k3 ; k3 = "%x+"
call r2 3 4
sub r0 k0 r8 ; k0 = ""
.label l8
move r6 r1
gettabup r7 u0 k1 ; k1 = "string"
gettable r7 r7 k4 ; k4 = "char"
gettabup r8 u0 k5 ; k5 = "tonumber"
move r9 r5
loadk r10 k6 ; k6 = 16
call r8 3 0
call r7 0 2
add r1 r6 r7
tforcall r2 1
tforloop r4 l8
return r1 2
return r0 1
"78 6D 63 74 66 32 30 32 36"·
第一串转成 ASCII 是:
xmctf2026
这就是 key。
第二串是目标密文。
"8B 8B 77 BE 68 61 86 68 E5 63 EE 84 35 6F 58 C8 51 0F 6E 94 70 E7 26 90 B6 75 EC 28
AF 14 E2 E3"
Lua 里还有两个子函数:
一个负责把十六进制字符串转成字节串
一个负责做 RC4 风格的流加密/解密
也就是说校验逻辑可以概括成:
user_input -> 某种变换 -> 和目标密文比较
继续结合字节码里的常量可发现还有一个常量 102,即 0x66。
实际验证后可知,正确流程是:
目标密文
用 key = "xmctf2026" 做 RC4 逆变换
结果每个字节再异或 0x66
得到 flag
常量里出现 255
两个 for 0..255
表初始化成 S[i] = i
string.byte(key,1,-1)
string.byte(data,1,-1)
反复交换同一个表的两个元素
最后 string.char + table.concat
local function rc4(key, data)
-- 初始化 S
-- KSA
-- PRGA
-- 逐字节输出
return out
end
local function hex_to_bytes(s)
-- string.gmatch + tonumber(...,16) + string.char
end
local key = hex_to_bytes("78 6D 63 74 66 32 30 32 36") -- xmctf2026
local target = hex_to_bytes("8B 8B 77 BE ...")
-- user_input 经^0x66
然后送进 rc4
再和 target 比较
exp
def rc4(key, data):
S = list(range(256))
j = 0
for i in range(256):
j = (j + S[i] + key[i % len(key)]) % 256
S[i], S[j] = S[j], S[i]
i = j = 0
out = bytearray()
for b in data:
i = (i + 1) % 256
j = (j + S[i]) % 256
S[i], S[j] = S[j], S[i]
k = S[(S[i] + S[j]) % 256]
out.append(b ^ k)
return bytes(out)
key = bytes.fromhex("78 6D 63 74 66 32 30 32 36")
ct = bytes.fromhex(
"8B 8B 77 BE 68 61 86 68 E5 63 EE 84 35 6F 58 C8 "
"51 0F 6E 94 70 E7 26 90 B6 75 EC 28 AF 14 E2 E3"
)
mid = rc4(key, ct)
flag = bytes(b ^ 0x66 for b in mid)
print(flag.decode())
xmctf{lu4t1c_r3v3rs3_ch4ll3ng3!}
VM 我对各种VM的理解 有一套虚拟指令执行机 虚拟指令集 和字节码 我们的输入转换成字节码 然后再去对应虚拟指令集 执行
OLLVM 类似以VM 有个大的分发器 state的值,跳转到对应的 case
5.hajimi
一共给了两个附件 ,main.py是模型调用器, challenge.pkl.zst 是他真实用来判断输入是否正确的模型本体
[模型逆向]
main.py源码分析
import pickle
import types
import haiku as hk
import jax.nn
import zstandard as zstd
from tracr.compiler.assemble import AssembledTransformerModel, _make_embedding_modules
from tracr.transformer.model import CompiledTransformerModel, Transformer, TransformerConfig
VALID_DIGITS = set("1234")
def load_model(path: str):
with open(path, "rb") as fp, zstd.ZstdDecompressor().stream_reader(fp) as cfp:
o = types.SimpleNamespace(**pickle.load(cfp))
o.config["activation_function"] = getattr(jax.nn, o.config["activation_function"])
def get_compiled_model():
transformer = Transformer(TransformerConfig(**o.config))
embed_modules = _make_embedding_modules(*o.embed_spaces)
return CompiledTransformerModel(
transformer,
embed_modules.token_embed,
embed_modules.pos_embed,
embed_modules.unembed,
use_unembed_argmax=True,
)
@hk.without_apply_rng
@hk.transform
def forward(emb):
cmodel = get_compiled_model()
return cmodel(emb, use_dropout=False)
return AssembledTransformerModel(
forward=forward.apply,
get_compiled_model=None, # type: ignore[arg-type]
params=o.params,
model_config=o.config,
residual_labels=o.residual_labels,
input_encoder=o.input_encoder,
output_encoder=o.output_encoder,
)
def decode_output(output):
out = output.decoded
if "EOS" in out:
out = out[: out.index("EOS")]
return "".join(out[1:])
if __name__ == "__main__":
prompt = input("You: ").strip()
if len(prompt) != 16:
print("Wrong grid.")
raise SystemExit(1)
if any(c not in VALID_DIGITS for c in prompt):
print("Wrong grid.")
raise SystemExit(1)
tokens = ["BOS"] + list(prompt)
print("Psychic:", decode_output(load_model("challenge.pkl.zst").apply(tokens))) # type: ignore[arg-type]
核心逻辑
if len(prompt) != 16:
print("Wrong grid.")
if any(c not in VALID_DIGITS for c in prompt):
print("Wrong grid.")
检查长度:输入16 位
每一位只能是 1/2/3/4
tokens = ["BOS"] + list(prompt)
把 ["BOS"] + list(prompt) 喂给 challenge.pkl.zst 里的模型,然后把输出 decode 成字符串
print("Psychic:", decode_output(load_model("challenge.pkl.zst").apply(tokens)))
load_model(...) 会把压缩文件里的模型读出来,apply(tokens) 跑一次前向推理,decode_output(...) 再把模型输出解码成可读字符串
load_model()分析
恢复一个完整的 Transformer 模型
打开 challenge.pkl.zst 用 zstd 解压
再用 pickle 反序列化出对象
从里面取出:
config params embed_spaces input_encoder output_encoder residual_labels
然后重新拼成一个 AssembledTransformerModel 返回。
decode_output()
def decode_output(output):
out = output.decoded
if "EOS" in out:
out = out[: out.index("EOS")]
return "".join(out[1:])
如果模型输出里有 EOS,就在 EOS 处截断
丢掉第一个 token(通常是 BOS 对应位置),把剩下的字符拼起来。
challenge.pkl.zst
.zst:说明外层是 Zstandard 压缩
.pkl:说明解压后是 pickle 序列化对象 (Pickling(封存):把内存中复杂的 Python 对象转换成一串 字节流(Byte Stream),这样就能存进磁盘文件。)
这个模型是用 pickle + zstd 存的,直接 pickle.load 会因为本地没有 tracr 模块而失败 (pickle.load 是 Python 里的“对象还原器”或称为反序列化函数)
先用几个 stub class 把 tracr 里需要的类名补出来,只为了成功反序列化,把参数字典拿到手。
(劫持模块注册表
在 Python 中,所有加载过的模块都存在 sys.modules 这个字典里。当你手动创建一个假的类并把它挂载到 sys.modules['tracr.compiler'] 下)
import argparse
import pickle
import subprocess
import sys
from pathlib import Path
REQUIRED_PYTHON = (3, 11)
PYTHON311 = Path(r"C:\Program Files\Python311\python.exe")
def ensure_compatible_python():
if sys.version_info[:2] == REQUIRED_PYTHON:
return
if PYTHON311.exists():
result = subprocess.run(
[str(PYTHON311), __file__, *sys.argv[1:]],
check=False,
)
raise SystemExit(result.returncode)
raise SystemExit(
"This script needs Python 3.11 because C:\\Users\\Lenovo\\Documents\\Playground\\pydeps "
"contains cp311 wheels. Current interpreter: "
f"{sys.executable} ({sys.version.split()[0]})."
)
ensure_compatible_python()
PYDEPS = Path(r"C:\Users\Lenovo\Documents\Playground\pydeps")
if str(PYDEPS) not in sys.path:
sys.path.insert(0, str(PYDEPS))
import numpy as np
import zstandard as zstd
class DummyArray:
def __init__(self, *args, **kwargs):
self.args = args
self.state = None
def __setstate__(self, state):
self.state = state
class DummyObject:
def __setstate__(self, state):
if isinstance(state, dict):
self.__dict__.update(state)
else:
self.state = state
_stub_cache = {}
def make_stub(module: str, name: str):
key = (module, name)
if key not in _stub_cache:
_stub_cache[key] = type(name, (DummyObject,), {"__module__": module})
return _stub_cache[key]
class PickleExtractor(pickle.Unpickler):
def find_class(self, module, name):
if module == "builtins":
return super().find_class(module, name)
if (module, name) == ("numpy._core.multiarray", "_reconstruct"):
return lambda *args, **kwargs: DummyArray(*args, **kwargs)
if (module, name) == ("numpy", "ndarray"):
return DummyArray
if (module, name) == ("numpy", "dtype"):
return np.dtype
if (module, name) == ("jax._src.array", "_reconstruct_array"):
return lambda *args, **kwargs: DummyArray(*args, **kwargs)
if module.startswith("tracr"):
return make_stub(module, name)
return super().find_class(module, name)
def load_payload(path: Path):
with path.open("rb") as fp, zstd.ZstdDecompressor().stream_reader(fp) as cfp:
return PickleExtractor(cfp).load()
def main():
parser = argparse.ArgumentParser(
description="Extract config and parameter keys from the hajimi challenge pickle."
)
parser.add_argument(
"path",
nargs="?",
default=r"F:\hajimi\challenge.pkl.zst",
help="Path to challenge.pkl.zst",
)
args = parser.parse_args()
payload = load_payload(Path(args.path))
config = payload["config"]
params = payload["params"]
print("top-level keys:")
for key in payload.keys():
print(f" {key}")
print("\nconfig:")
for key in sorted(config.keys()):
print(f" {key} = {config[key]}")
print("\nparam keys:")
for key in sorted(params.keys()):
print(f" {key}")
print("\nrequired tracr stubs:")
for module, name in sorted(_stub_cache):
print(f" {module}.{name}")
if __name__ == "__main__":
main()
输出是
top-level keys:
config
params
input_encoder
output_encoder
residual_labels
embed_spaces
config:
activation_function = relu
causal = False
dropout_rate = 0.0
key_size = 257
layer_norm = False
mlp_hidden_size = 1290
num_heads = 5
num_layers = 13
param keys:
pos_embed
token_embed
transformer/layer_0/attn/key
transformer/layer_0/attn/linear
transformer/layer_0/attn/query
transformer/layer_0/attn/value
transformer/layer_0/mlp/linear_1
transformer/layer_0/mlp/linear_2
transformer/layer_1/attn/key
transformer/layer_1/attn/linear
transformer/layer_1/attn/query
transformer/layer_1/attn/value
transformer/layer_1/mlp/linear_1
transformer/layer_1/mlp/linear_2
transformer/layer_10/attn/key
transformer/layer_10/attn/linear
transformer/layer_10/attn/query
transformer/layer_10/attn/value
transformer/layer_10/mlp/linear_1
transformer/layer_10/mlp/linear_2
transformer/layer_11/attn/key
transformer/layer_11/attn/linear
transformer/layer_11/attn/query
transformer/layer_11/attn/value
transformer/layer_11/mlp/linear_1
transformer/layer_11/mlp/linear_2
transformer/layer_12/attn/key
transformer/layer_12/attn/linear
transformer/layer_12/attn/query
transformer/layer_12/attn/value
transformer/layer_12/mlp/linear_1
transformer/layer_12/mlp/linear_2
transformer/layer_2/attn/key
transformer/layer_2/attn/linear
transformer/layer_2/attn/query
transformer/layer_2/attn/value
transformer/layer_2/mlp/linear_1
transformer/layer_2/mlp/linear_2
transformer/layer_3/attn/key
transformer/layer_3/attn/linear
transformer/layer_3/attn/query
transformer/layer_3/attn/value
transformer/layer_3/mlp/linear_1
transformer/layer_3/mlp/linear_2
transformer/layer_4/attn/key
transformer/layer_4/attn/linear
transformer/layer_4/attn/query
transformer/layer_4/attn/value
transformer/layer_4/mlp/linear_1
transformer/layer_4/mlp/linear_2
transformer/layer_5/attn/key
transformer/layer_5/attn/linear
transformer/layer_5/attn/query
transformer/layer_5/attn/value
transformer/layer_5/mlp/linear_1
transformer/layer_5/mlp/linear_2
transformer/layer_6/attn/key
transformer/layer_6/attn/linear
transformer/layer_6/attn/query
transformer/layer_6/attn/value
transformer/layer_6/mlp/linear_1
transformer/layer_6/mlp/linear_2
transformer/layer_7/attn/key
transformer/layer_7/attn/linear
transformer/layer_7/attn/query
transformer/layer_7/attn/value
transformer/layer_7/mlp/linear_1
transformer/layer_7/mlp/linear_2
transformer/layer_8/attn/key
transformer/layer_8/attn/linear
transformer/layer_8/attn/query
transformer/layer_8/attn/value
transformer/layer_8/mlp/linear_1
transformer/layer_8/mlp/linear_2
transformer/layer_9/attn/key
transformer/layer_9/attn/linear
transformer/layer_9/attn/query
transformer/layer_9/attn/value
transformer/layer_9/mlp/linear_1
transformer/layer_9/mlp/linear_2
required tracr stubs:
tracr.craft.bases.BasisDirection
tracr.craft.bases.VectorSpaceWithBasis
tracr.transformer.encoder.CategoricalEncoder
num_layers = 13
num_heads = 5
key_size = 257
mlp_hidden_size = 1290
layer_norm = False
causal = False
而且参数名是标准 Transformer 结构:
attn/query
attn/key
attn/value
attn/linear
mlp/linear_1
mlp/linear_2
核心前向过程就是:
x = token_embed[token_id] + pos_embed[pos_id]
for layer in range(13):
q = x @ Wq + bq
k = x @ Wk + bk
v = x @ Wv + bv
q, k, v = reshape_to_heads(...)
attn = softmax(q @ k.T / sqrt(257))
y = attn @ v
y = y @ Wo + bo
x = x + y
y = relu(x @ W1 + b1)
y = y @ W2 + b2
x = x + y
最后不需要真正实现 Tracr 的完整 unembed,只要读出 residual 里对应 sequence_map_1:* 那些维度,做 argmax 就行,因为输出字符就是从这些 basis direction 里读出来的。
(在标准的深度学习模型中,最后一层通常有一个巨大的矩阵(Unembed/Head),把隐藏向量转换成成千上万个词的概率。
但在 Tracr 模型中:
输出已在向量中:计算结果(比如你要找的 Flag 字符)其实已经存在于残差流 x 的某些特定索引(Index)里了。
sequence_map_1:* 维度:这是 Tracr 编译器给变量起的“物理地址”。比如第 100 到 126 维可能就对应着变量 sequence_map_1。
直接读取:你不需要做最后的矩阵乘法,只需要把最后一次迭代后的向量 x 拿出来,盯着第 100 到 126 维看。哪一维的值最大(argmax),那个维度对应的字符就是输出。)
Tracr 是 Google DeepMind 开发的一个工具,它能将类似程序的逻辑(用 RASP 语言编写)直接“编译”成 Transformer 的权重


把模型跑起来之后(手写 forward 跑),最关键的是看 residual_labels。 (residual_labels 是最关键的“地址簿”或“内存映射表”。 Transformer 的残差流(Residual Stream)是一个高维向量(比如 1285 维),而 residual_labels 告诉了你:向量中的每一个维度具体代表哪一个变量。 )
exp
import argparse
import math
import pickle
import subprocess
import sys
from pathlib import Path
REQUIRED_PYTHON = (3, 11)
PYTHON311 = Path(r"C:\Program Files\Python311\python.exe")
PYDEPS = Path(r"C:\Users\Lenovo\Documents\Playground\pydeps")
def ensure_compatible_python():
if sys.version_info[:2] == REQUIRED_PYTHON:
return
if PYTHON311.exists():
result = subprocess.run([str(PYTHON311), __file__, *sys.argv[1:]], check=False)
raise SystemExit(result.returncode)
raise SystemExit(
"This script needs Python 3.11 because pydeps contains cp311 wheels. "
f"Current interpreter: {sys.executable} ({sys.version.split()[0]})."
)
ensure_compatible_python()
if str(PYDEPS) not in sys.path:
sys.path.insert(0, str(PYDEPS))
import numpy as np
import zstandard as zstd
class DummyObject:
def __setstate__(self, state):
if isinstance(state, dict):
self.__dict__.update(state)
else:
self.state = state
_stub_cache = {}
def make_stub(module: str, name: str):
key = (module, name)
if key not in _stub_cache:
_stub_cache[key] = type(name, (DummyObject,), {"__module__": module})
return _stub_cache[key]
def reconstruct_jax_array(np_reconstruct, np_args, np_state, jax_state):
arr = np_reconstruct(*np_args)
arr.__setstate__(np_state)
return arr
class PayloadLoader(pickle.Unpickler):
def find_class(self, module, name):
if module == "builtins":
return super().find_class(module, name)
if (module, name) == ("numpy._core.multiarray", "_reconstruct"):
return np._core.multiarray._reconstruct
if (module, name) == ("numpy", "ndarray"):
return np.ndarray
if (module, name) == ("numpy", "dtype"):
return np.dtype
if (module, name) == ("jax._src.array", "_reconstruct_array"):
return reconstruct_jax_array
if module.startswith("tracr"):
return make_stub(module, name)
return super().find_class(module, name)
def load_payload(path: Path):
with path.open("rb") as fp, zstd.ZstdDecompressor().stream_reader(fp) as cfp:
return PayloadLoader(cfp).load()
def softmax(x, axis=-1):
x = x - np.max(x, axis=axis, keepdims=True)
e = np.exp(x)
return e / np.sum(e, axis=axis, keepdims=True)
def run_model(payload, prompt: str):
cfg = payload["config"]
params = payload["params"]
input_map = payload["input_encoder"].encoding_map
tokens = ["BOS"] + list(prompt)
token_ids = np.array([input_map[t] for t in tokens], dtype=np.int64)
pos_ids = np.arange(len(tokens), dtype=np.int64)
x = (
params["token_embed"]["embeddings"][token_ids]
+ params["pos_embed"]["embeddings"][pos_ids]
)
num_heads = cfg["num_heads"]
key_size = cfg["key_size"]
for layer in range(cfg["num_layers"]):
base = f"transformer/layer_{layer}"
q = x @ params[f"{base}/attn/query"]["w"] + params[f"{base}/attn/query"]["b"]
k = x @ params[f"{base}/attn/key"]["w"] + params[f"{base}/attn/key"]["b"]
v = x @ params[f"{base}/attn/value"]["w"] + params[f"{base}/attn/value"]["b"]
seq_len = x.shape[0]
q = q.reshape(seq_len, num_heads, key_size).transpose(1, 0, 2)
k = k.reshape(seq_len, num_heads, key_size).transpose(1, 0, 2)
v = v.reshape(seq_len, num_heads, key_size).transpose(1, 0, 2)
scores = q @ k.transpose(0, 2, 1) / math.sqrt(key_size)
attn = softmax(scores, axis=-1)
y = attn @ v
y = y.transpose(1, 0, 2).reshape(seq_len, num_heads * key_size)
y = y @ params[f"{base}/attn/linear"]["w"] + params[f"{base}/attn/linear"]["b"]
x = x + y
y = x @ params[f"{base}/mlp/linear_1"]["w"] + params[f"{base}/mlp/linear_1"]["b"]
y = np.maximum(y, 0.0)
y = y @ params[f"{base}/mlp/linear_2"]["w"] + params[f"{base}/mlp/linear_2"]["b"]
x = x + y
return x
def get_sequence_map_block(payload, prefix="sequence_map_1:"):
labels = payload["residual_labels"]
idxs = [i for i, label in enumerate(labels) if label.startswith(prefix)]
alphabet = [labels[i].split(":", 1)[1] for i in idxs]
return idxs, alphabet
def decode_from_residual(payload, residual, prefix="sequence_map_1:"):
idxs, alphabet = get_sequence_map_block(payload, prefix=prefix)
scores = residual[:, idxs]
best = scores.argmax(axis=-1)
out = []
for idx in best[1:]:
ch = alphabet[idx]
if ch == "EOS":
break
out.append(ch)
return "".join(out), idxs, alphabet
def dump_config(payload):
print("config:")
for key in sorted(payload["config"].keys()):
print(f" {key} = {payload['config'][key]}")
def dump_sequence_map(payload, prefix="sequence_map_1:"):
idxs, alphabet = get_sequence_map_block(payload, prefix=prefix)
print(f"{prefix} block:")
for i, ch in zip(idxs, alphabet):
print(f" {i}: {ch!r}")
def main():
parser = argparse.ArgumentParser(description="Pure NumPy runner for hajimi challenge.pkl.zst")
parser.add_argument("prompt", help="16-char grid string, e.g. 1234123412341234")
parser.add_argument(
"--path",
default=r"F:\hajimi\challenge.pkl.zst",
help="Path to challenge.pkl.zst",
)
parser.add_argument(
"--dump-config",
action="store_true",
help="Print model config before running",
)
parser.add_argument(
"--dump-sequence-map",
action="store_true",
help="Print residual_labels positions for sequence_map_1:*",
)
args = parser.parse_args()
payload = load_payload(Path(args.path))
if args.dump_config:
dump_config(payload)
print()
if args.dump_sequence_map:
dump_sequence_map(payload)
print()
residual = run_model(payload, args.prompt)
decoded, idxs, alphabet = decode_from_residual(payload, residual)
print("decoded:", decoded)
print("sequence_map_indices:", idxs)
print("sequence_map_alphabet:", alphabet)
if __name__ == "__main__":
main()
python C:\Users\Lenovo\Desktop\run_hajimi_numpy.py 1234341221434321 --dump-values map_53: --dump-values map_52: --dump-values map_50: --dump-values map_41:
map_53: values:
map_53:0 @ 415
values = [0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0, 1.0, 1.0, 1.0, 1.0, 0.0]
active_with_bos = [2, 3, 4, 5, 6, 8, 9, 11, 12, 13, 14, 15]
active_prompt_positions = [2, 3, 4, 5, 6, 8, 9, 11, 12, 13, 14, 15]
map_53:1 @ 416
values = [0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0]
active_with_bos = [1, 7, 10, 16]
active_prompt_positions = [1, 7, 10, 16]
map_52: values:
map_52:0 @ 413
values = [0.0, 1.0, 0.0, 1.0, 1.0, 1.0, 1.0, 1.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 1.0, 0.0, 1.0]
active_with_bos = [1, 3, 4, 5, 6, 7, 10, 11, 12, 13, 14, 16]
active_prompt_positions = [1, 3, 4, 5, 6, 7, 10, 11, 12, 13, 14, 16]
map_52:1 @ 414
values = [0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0]
active_with_bos = [2, 8, 9, 15]
active_prompt_positions = [2, 8, 9, 15]
map_50: values:
map_50:0 @ 411
values = [0.0, 1.0, 1.0, 0.0, 1.0, 0.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 0.0, 1.0, 0.0, 1.0, 1.0]
active_with_bos = [1, 2, 4, 6, 7, 8, 9, 10, 11, 13, 15, 16]
active_prompt_positions = [1, 2, 4, 6, 7, 8, 9, 10, 11, 13, 15, 16]
map_50:1 @ 412
values = [0.0, 0.0, 0.0, 1.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 1.0, 0.0, 0.0]
active_with_bos = [3, 5, 12, 14]
active_prompt_positions = [3, 5, 12, 14]
map_41: values:
map_41:0 @ 408
values = [0.0, 1.0, 1.0, 1.0, 0.0, 1.0, 0.0, 1.0, 1.0, 1.0, 1.0, 0.0, 1.0, 0.0, 1.0, 1.0, 1.0]
active_with_bos = [1, 2, 3, 5, 7, 8, 9, 10, 12, 14, 15, 16]
active_prompt_positions = [1, 2, 3, 5, 7, 8, 9, 10, 12, 14, 15, 16]
map_41:1 @ 409
values = [0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 1.0, 0.0, 0.0, 0.0]
active_with_bos = [4, 6, 11, 13]
active_prompt_positions = [4, 6, 11, 13]
decoded: Grid accepted.
sequence_map_indices: [1068, 1069, 1070, 1071, 1072, 1073, 1074, 1075, 1076, 1077, 1078, 1079, 1080, 1081, 1082, 1083]
sequence_map_alphabet: [' ', '.', 'EOS', 'G', 'W', 'a', 'c', 'd', 'e', 'g', 'i', 'n', 'o', 'p', 'r', 't']
这里面能看到很多 Tracr 编译后的符号变量名,比如:
sequence_map_*
map_*
selector_width_*
count_True_*
继续观察最终 residual,会发现有四组非常关键的位置掩码:

map_53 = 1 出现在位置:1, 7, 10, 16
map_52 = 1 出现在位置:2, 8, 9, 15
map_50 = 1 出现在位置:3, 5, 12, 14
map_41 = 1 出现在位置:4, 6, 11, 13
这四组恰好把 16 个位置分成了四类。进一步对照模型行为,可以恢复出每一类对应的数字分别是:
1 → 1, 7, 10, 16
2 → 2, 8, 9, 15
3 → 3, 5, 12, 14
4 → 4, 6, 11, 13

分组排序
于是答案串就是:
1234341221434321
按 4×4 排开就是:
1 2 3 4
3 4 1 2
2 1 4 3
4 3 2 1
flag
1234341221434321
做 SHA-256,得到:
b0a0d1edc0fb5b75770a5dcbe7b0d4fb08e42fd281a94ee67b405e36056f1df1
最终 flag:
xmctf{b0a0d1edc0fb5b75770a5dcbe7b0d4fb08e42fd281a94ee67b405e36056f1df1}
6.FunPyVM
main.pyc 只是启动器
真正的 VM 解释器在 kernelVM.pyc
真正的字节码程序在 opcode.bin
pyc3.13 反编译网站
(https://pychaos.io/
main.pyc 源码
import sys
import os
import bitstring
from kernelVM import CustomVM
if __name__ == '__main__':
if getattr(sys,'frozen',False):
current_dir = os.path.dirname(sys.executable)
else:
current_dir = os.path.dirname(os.path.abspath(__file__))
filename = os.path.join(current_dir,'opcode.bin')
try:
stream = bitstring.ConstBitStream(filename=filename)
bytecode = stream.tobytes()
except Exception as e:
print('Error: Could not read \'opcode.bin\' in the current directory.')
print('Please ensure \'opcode.bin\' is placed next to the executable.')
sys.exit(1)
vm = CustomVM()
print('--- VM Start ---')
vm.run(bytecode)
print('\n--- VM End ---')
分析:
找到当前目录下的 opcode.bin
读取 opcode.bin
实例化 kernelVM.CustomVM
执行 vm.run(bytecode)
kernelVM.pyc 源码
import sys
from random import randint
class CustomVM:
def __init__(self):
self.R0 = 0
self.R1 = 0
self.PC = 0
self.heap = {}
self.heapNum = 61444
self.first_create_done = False
def run(self,bytecode):
self.PC = 0
length = len(bytecode)
while self.PC < length:
opcode = bytecode[self.PC]
instruction_length = 1
if opcode in (16,17,18,32,48,49,50,51,52,53,54,55,64,65,66,67,68,69,70,80,82):
instruction_length = 2
opnum = bytecode[self.PC+1] if instruction_length == 2 else 0
match opcode:
case 16:
if self.first_create_done:
self.R0 = 0
self.first_create_done = True
self.heap[0] = [0]*opnum
else:
self.R0 = self.heapNum
self.heapNum += randint(1,10)
self.heap[self.R0] = [0]*opnum
self.PC += 2
case 17:
if self.R0 in self.heap and opnum < len(self.heap[self.R0]):
self.R1 = self.heap[self.R0][opnum]
else:
if 0 in self.heap and opnum < len(self.heap[0]):
self.R1 = self.heap[0][opnum]
else:
self.R1 = 0
self.PC += 2
if __CHAOS_PY_NULL_PTR_VALUE_ERR__ == 18:
if self.R0 in self.heap:
self.heap[self.R0][opnum] = self.R1
if 0 in self.heap and opnum < len(self.heap[0]):
self.heap[0][opnum if opnum < len(self.heap[self.R0]) else __CHAOS_PY_NULL_PTR_VALUE_ERR__] = self.R1
else:
raise IndexError(f'''VM Memory Access Violation: store {opnum} out of bounds for both chunk 0x{self.R0:X} and fallback chunk 0''')
raise ValueError(f'''VM Segmentation Fault: store {opnum} to unallocated memory pointer 0x{self.R0:X}''')
self.PC += 2
continue
if __name__ == '__main__':
if len(sys.argv) < 2:
print('Usage: python kernelVM.py <program.bin>')
sys.exit(1)
filename = sys.argv[1]
with open(filename,'rb') as f:
bytecode = f.read()
vm = CustomVM()
print('--- VM Start ---')
vm.run(bytecode)
print('\n--- VM End ---')
VM 源码
从 kernelVM.pyc 可以还原出这一套自定义虚拟机。核心寄存器和内存为:
R0:当前堆块指针
R1:通用寄存器
PC:程序计数器
heap:虚拟机堆
本题主要用到这些指令:
0x10 imm:ALLOC imm
0x11 imm:LOAD imm
0x12 imm:STORE imm
0x13:FREE
0x20 imm:MOVI imm
0x30 imm:ADDI imm
0x31 imm:XORI imm
0x35 imm:对内存单元做异或,即 heap[R0][imm] ^= R1
0x40 imm:CMPEQ imm
0x42 imm:JZ imm
0x43 imm:JNZ imm
0x51:交换 R0 和 R1
0x52 0x01:读取输入
0x52 0x02:输出字符串
opcode.bin字节码文件
python C:\Users\Lenovo\Documents\Playground\disasm_funpyvm.py "F:\attachment(6)\opcode.bin"
反汇编exp
import argparse
from pathlib import Path
LEN2_OPS = {
0x10, 0x11, 0x12, 0x20,
0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46,
0x50, 0x52,
}
OPINFO = {
0x10: ("ALLOC", True),
0x11: ("LOAD", True),
0x12: ("STORE", True),
0x13: ("FREE", False),
0x20: ("MOVI", True),
0x30: ("ADDI", True),
0x31: ("XORI", True),
0x32: ("OP32", True),
0x33: ("OP33", True),
0x34: ("OP34", True),
0x35: ("XOR_MEM", True),
0x36: ("OP36", True),
0x37: ("OP37", True),
0x40: ("CMPEQ", True),
0x41: ("OP41", True),
0x42: ("JZ", True),
0x43: ("JNZ", True),
0x44: ("OP44", True),
0x45: ("OP45", True),
0x46: ("OP46", True),
0x50: ("OP50", True),
0x51: ("SWAP", False),
0x52: ("SYSCALL", True),
}
SYSCALL_NAMES = {
0x01: "READ_INPUT",
0x02: "PRINT_STRING",
}
def decode_instruction(bytecode: bytes, pc: int):
opcode = bytecode[pc]
instr_len = 2 if opcode in LEN2_OPS else 1
arg = bytecode[pc + 1] if instr_len == 2 and pc + 1 < len(bytecode) else None
name, has_arg = OPINFO.get(opcode, (f"DB_0x{opcode:02X}", instr_len == 2))
return {
"pc": pc,
"opcode": opcode,
"len": instr_len,
"arg": arg,
"name": name,
"has_arg": has_arg,
}
def collect_instructions(bytecode: bytes):
pc = 0
insns = []
while pc < len(bytecode):
insn = decode_instruction(bytecode, pc)
insns.append(insn)
pc += insn["len"]
return insns
def annotate_labels(insns):
labels = {}
for insn in insns:
if insn["name"] in {"JZ", "JNZ"} and insn["arg"] is not None:
target = insn["pc"] + insn["arg"]
insn["target"] = target
labels.setdefault(target, f"loc_{target:04X}")
return labels
def format_hex(insn):
if insn["len"] == 2:
return f"{insn['opcode']:02X} {insn['arg']:02X}"
return f"{insn['opcode']:02X}"
def format_detail(insn, labels):
name = insn["name"]
arg = insn["arg"]
if name == "SYSCALL" and arg is not None:
return f"SYSCALL 0x{arg:02X} ; {SYSCALL_NAMES.get(arg, 'UNKNOWN_SYSCALL')}"
if name in {"JZ", "JNZ"} and arg is not None:
target = insn.get("target", insn["pc"] + arg)
label = labels.get(target, f"loc_{target:04X}")
return f"{name} 0x{arg:02X} ; -> {label} (0x{target:04X})"
if insn["has_arg"] and arg is not None:
return f"{name} 0x{arg:02X}"
return name
def disassemble(bytecode: bytes):
insns = collect_instructions(bytecode)
labels = annotate_labels(insns)
print(f"; size = {len(bytecode)} bytes")
print(f"; instructions = {len(insns)}")
print()
for insn in insns:
if insn["pc"] in labels:
print(f"{labels[insn['pc']]}:")
print(
f" 0x{insn['pc']:04X}: "
f"{format_hex(insn):<5} "
f"{format_detail(insn, labels)}"
)
def main():
parser = argparse.ArgumentParser(description="Disassemble FunPyVM bytecode")
parser.add_argument("path", help="Path to opcode.bin or hidden VM payload")
args = parser.parse_args()
data = Path(args.path).read_bytes()
disassemble(data)
if __name__ == "__main__":
main()
F:\attachment (6)>python 1.py opcode.bin
; size = 855 bytes
; instructions = 428
0x0000: 10 64 ALLOC 0x64
0x0002: 10 32 ALLOC 0x32
0x0004: 52 01 SYSCALL 0x01 ; READ_INPUT
0x0006: 11 00 LOAD 0x00
0x0008: 40 00 CMPEQ 0x00
0x000A: 43 06 JNZ 0x06 ; -> loc_0010 (0x0010)
0x000C: 11 00 LOAD 0x00
0x000E: 31 55 XORI 0x55
loc_0010:
0x0010: 12 00 STORE 0x00
0x0012: 11 01 LOAD 0x01
0x0014: 40 00 CMPEQ 0x00
0x0016: 43 06 JNZ 0x06 ; -> loc_001C (0x001C)
0x0018: 11 01 LOAD 0x01
0x001A: 31 55 XORI 0x55
loc_001C:
0x001C: 12 01 STORE 0x01
0x001E: 11 02 LOAD 0x02
0x0020: 40 00 CMPEQ 0x00
0x0022: 43 06 JNZ 0x06 ; -> loc_0028 (0x0028)
0x0024: 11 02 LOAD 0x02
0x0026: 31 55 XORI 0x55
loc_0028:
0x0028: 12 02 STORE 0x02
0x002A: 11 03 LOAD 0x03
0x002C: 40 00 CMPEQ 0x00
0x002E: 43 06 JNZ 0x06 ; -> loc_0034 (0x0034)
0x0030: 11 03 LOAD 0x03
0x0032: 31 55 XORI 0x55
loc_0034:
0x0034: 12 03 STORE 0x03
0x0036: 11 04 LOAD 0x04
0x0038: 40 00 CMPEQ 0x00
0x003A: 43 06 JNZ 0x06 ; -> loc_0040 (0x0040)
0x003C: 11 04 LOAD 0x04
0x003E: 31 55 XORI 0x55
loc_0040:
0x0040: 12 04 STORE 0x04
0x0042: 11 05 LOAD 0x05
0x0044: 40 00 CMPEQ 0x00
0x0046: 43 06 JNZ 0x06 ; -> loc_004C (0x004C)
0x0048: 11 05 LOAD 0x05
0x004A: 31 55 XORI 0x55
loc_004C:
0x004C: 12 05 STORE 0x05
0x004E: 11 06 LOAD 0x06
0x0050: 40 00 CMPEQ 0x00
0x0052: 43 06 JNZ 0x06 ; -> loc_0058 (0x0058)
0x0054: 11 06 LOAD 0x06
0x0056: 31 55 XORI 0x55
loc_0058:
0x0058: 12 06 STORE 0x06
0x005A: 11 07 LOAD 0x07
0x005C: 40 00 CMPEQ 0x00
0x005E: 43 06 JNZ 0x06 ; -> loc_0064 (0x0064)
0x0060: 11 07 LOAD 0x07
0x0062: 31 55 XORI 0x55
loc_0064:
0x0064: 12 07 STORE 0x07
0x0066: 11 08 LOAD 0x08
0x0068: 40 00 CMPEQ 0x00
0x006A: 43 06 JNZ 0x06 ; -> loc_0070 (0x0070)
0x006C: 11 08 LOAD 0x08
0x006E: 31 55 XORI 0x55
loc_0070:
0x0070: 12 08 STORE 0x08
0x0072: 11 09 LOAD 0x09
0x0074: 40 00 CMPEQ 0x00
0x0076: 43 06 JNZ 0x06 ; -> loc_007C (0x007C)
0x0078: 11 09 LOAD 0x09
0x007A: 31 55 XORI 0x55
loc_007C:
0x007C: 12 09 STORE 0x09
0x007E: 11 0A LOAD 0x0A
0x0080: 40 00 CMPEQ 0x00
0x0082: 43 06 JNZ 0x06 ; -> loc_0088 (0x0088)
0x0084: 11 0A LOAD 0x0A
0x0086: 31 55 XORI 0x55
loc_0088:
0x0088: 12 0A STORE 0x0A
0x008A: 11 0B LOAD 0x0B
0x008C: 40 00 CMPEQ 0x00
0x008E: 43 06 JNZ 0x06 ; -> loc_0094 (0x0094)
0x0090: 11 0B LOAD 0x0B
0x0092: 31 55 XORI 0x55
loc_0094:
0x0094: 12 0B STORE 0x0B
0x0096: 11 0C LOAD 0x0C
0x0098: 40 00 CMPEQ 0x00
0x009A: 43 06 JNZ 0x06 ; -> loc_00A0 (0x00A0)
0x009C: 11 0C LOAD 0x0C
0x009E: 31 55 XORI 0x55
loc_00A0:
0x00A0: 12 0C STORE 0x0C
0x00A2: 11 0D LOAD 0x0D
0x00A4: 40 00 CMPEQ 0x00
0x00A6: 43 06 JNZ 0x06 ; -> loc_00AC (0x00AC)
0x00A8: 11 0D LOAD 0x0D
0x00AA: 31 55 XORI 0x55
loc_00AC:
0x00AC: 12 0D STORE 0x0D
0x00AE: 11 0E LOAD 0x0E
0x00B0: 40 00 CMPEQ 0x00
0x00B2: 43 06 JNZ 0x06 ; -> loc_00B8 (0x00B8)
0x00B4: 11 0E LOAD 0x0E
0x00B6: 31 55 XORI 0x55
loc_00B8:
0x00B8: 12 0E STORE 0x0E
0x00BA: 11 0F LOAD 0x0F
0x00BC: 40 00 CMPEQ 0x00
0x00BE: 43 06 JNZ 0x06 ; -> loc_00C4 (0x00C4)
0x00C0: 11 0F LOAD 0x0F
0x00C2: 31 55 XORI 0x55
loc_00C4:
0x00C4: 12 0F STORE 0x0F
0x00C6: 11 10 LOAD 0x10
0x00C8: 40 00 CMPEQ 0x00
0x00CA: 43 06 JNZ 0x06 ; -> loc_00D0 (0x00D0)
0x00CC: 11 10 LOAD 0x10
0x00CE: 31 55 XORI 0x55
loc_00D0:
0x00D0: 12 10 STORE 0x10
0x00D2: 11 11 LOAD 0x11
0x00D4: 40 00 CMPEQ 0x00
0x00D6: 43 06 JNZ 0x06 ; -> loc_00DC (0x00DC)
0x00D8: 11 11 LOAD 0x11
0x00DA: 31 55 XORI 0x55
loc_00DC:
0x00DC: 12 11 STORE 0x11
0x00DE: 11 12 LOAD 0x12
0x00E0: 40 00 CMPEQ 0x00
0x00E2: 43 06 JNZ 0x06 ; -> loc_00E8 (0x00E8)
0x00E4: 11 12 LOAD 0x12
0x00E6: 31 55 XORI 0x55
loc_00E8:
0x00E8: 12 12 STORE 0x12
0x00EA: 11 13 LOAD 0x13
0x00EC: 40 00 CMPEQ 0x00
0x00EE: 43 06 JNZ 0x06 ; -> loc_00F4 (0x00F4)
0x00F0: 11 13 LOAD 0x13
0x00F2: 31 55 XORI 0x55
loc_00F4:
0x00F4: 12 13 STORE 0x13
0x00F6: 11 14 LOAD 0x14
0x00F8: 40 00 CMPEQ 0x00
0x00FA: 43 06 JNZ 0x06 ; -> loc_0100 (0x0100)
0x00FC: 11 14 LOAD 0x14
0x00FE: 31 55 XORI 0x55
loc_0100:
0x0100: 12 14 STORE 0x14
0x0102: 11 15 LOAD 0x15
0x0104: 40 00 CMPEQ 0x00
0x0106: 43 06 JNZ 0x06 ; -> loc_010C (0x010C)
0x0108: 11 15 LOAD 0x15
0x010A: 31 55 XORI 0x55
loc_010C:
0x010C: 12 15 STORE 0x15
0x010E: 11 16 LOAD 0x16
0x0110: 40 00 CMPEQ 0x00
0x0112: 43 06 JNZ 0x06 ; -> loc_0118 (0x0118)
0x0114: 11 16 LOAD 0x16
0x0116: 31 55 XORI 0x55
loc_0118:
0x0118: 12 16 STORE 0x16
0x011A: 11 17 LOAD 0x17
0x011C: 40 00 CMPEQ 0x00
0x011E: 43 06 JNZ 0x06 ; -> loc_0124 (0x0124)
0x0120: 11 17 LOAD 0x17
0x0122: 31 55 XORI 0x55
loc_0124:
0x0124: 12 17 STORE 0x17
0x0126: 11 18 LOAD 0x18
0x0128: 40 00 CMPEQ 0x00
0x012A: 43 06 JNZ 0x06 ; -> loc_0130 (0x0130)
0x012C: 11 18 LOAD 0x18
0x012E: 31 55 XORI 0x55
loc_0130:
0x0130: 12 18 STORE 0x18
0x0132: 11 19 LOAD 0x19
0x0134: 40 00 CMPEQ 0x00
0x0136: 43 06 JNZ 0x06 ; -> loc_013C (0x013C)
0x0138: 11 19 LOAD 0x19
0x013A: 31 55 XORI 0x55
loc_013C:
0x013C: 12 19 STORE 0x19
0x013E: 11 1A LOAD 0x1A
0x0140: 40 00 CMPEQ 0x00
0x0142: 43 06 JNZ 0x06 ; -> loc_0148 (0x0148)
0x0144: 11 1A LOAD 0x1A
0x0146: 31 55 XORI 0x55
loc_0148:
0x0148: 12 1A STORE 0x1A
0x014A: 11 00 LOAD 0x00
0x014C: 40 00 CMPEQ 0x00
0x014E: 43 06 JNZ 0x06 ; -> loc_0154 (0x0154)
0x0150: 11 00 LOAD 0x00
0x0152: 30 07 ADDI 0x07
loc_0154:
0x0154: 12 00 STORE 0x00
0x0156: 11 01 LOAD 0x01
0x0158: 40 00 CMPEQ 0x00
0x015A: 43 06 JNZ 0x06 ; -> loc_0160 (0x0160)
0x015C: 11 01 LOAD 0x01
0x015E: 30 0A ADDI 0x0A
loc_0160:
0x0160: 12 01 STORE 0x01
0x0162: 11 02 LOAD 0x02
0x0164: 40 00 CMPEQ 0x00
0x0166: 43 06 JNZ 0x06 ; -> loc_016C (0x016C)
0x0168: 11 02 LOAD 0x02
0x016A: 30 0D ADDI 0x0D
loc_016C:
0x016C: 12 02 STORE 0x02
0x016E: 11 03 LOAD 0x03
0x0170: 40 00 CMPEQ 0x00
0x0172: 43 06 JNZ 0x06 ; -> loc_0178 (0x0178)
0x0174: 11 03 LOAD 0x03
0x0176: 30 10 ADDI 0x10
loc_0178:
0x0178: 12 03 STORE 0x03
0x017A: 11 04 LOAD 0x04
0x017C: 40 00 CMPEQ 0x00
0x017E: 43 06 JNZ 0x06 ; -> loc_0184 (0x0184)
0x0180: 11 04 LOAD 0x04
0x0182: 30 13 ADDI 0x13
loc_0184:
0x0184: 12 04 STORE 0x04
0x0186: 11 05 LOAD 0x05
0x0188: 40 00 CMPEQ 0x00
0x018A: 43 06 JNZ 0x06 ; -> loc_0190 (0x0190)
0x018C: 11 05 LOAD 0x05
0x018E: 30 16 ADDI 0x16
loc_0190:
0x0190: 12 05 STORE 0x05
0x0192: 11 06 LOAD 0x06
0x0194: 40 00 CMPEQ 0x00
0x0196: 43 06 JNZ 0x06 ; -> loc_019C (0x019C)
0x0198: 11 06 LOAD 0x06
0x019A: 30 19 ADDI 0x19
loc_019C:
0x019C: 12 06 STORE 0x06
0x019E: 11 07 LOAD 0x07
0x01A0: 40 00 CMPEQ 0x00
0x01A2: 43 06 JNZ 0x06 ; -> loc_01A8 (0x01A8)
0x01A4: 11 07 LOAD 0x07
0x01A6: 30 1C ADDI 0x1C
loc_01A8:
0x01A8: 12 07 STORE 0x07
0x01AA: 11 08 LOAD 0x08
0x01AC: 40 00 CMPEQ 0x00
0x01AE: 43 06 JNZ 0x06 ; -> loc_01B4 (0x01B4)
0x01B0: 11 08 LOAD 0x08
0x01B2: 30 1F ADDI 0x1F
loc_01B4:
0x01B4: 12 08 STORE 0x08
0x01B6: 11 09 LOAD 0x09
0x01B8: 40 00 CMPEQ 0x00
0x01BA: 43 06 JNZ 0x06 ; -> loc_01C0 (0x01C0)
0x01BC: 11 09 LOAD 0x09
0x01BE: 30 22 ADDI 0x22
loc_01C0:
0x01C0: 12 09 STORE 0x09
0x01C2: 11 0A LOAD 0x0A
0x01C4: 40 00 CMPEQ 0x00
0x01C6: 43 06 JNZ 0x06 ; -> loc_01CC (0x01CC)
0x01C8: 11 0A LOAD 0x0A
0x01CA: 30 25 ADDI 0x25
loc_01CC:
0x01CC: 12 0A STORE 0x0A
0x01CE: 11 0B LOAD 0x0B
0x01D0: 40 00 CMPEQ 0x00
0x01D2: 43 06 JNZ 0x06 ; -> loc_01D8 (0x01D8)
0x01D4: 11 0B LOAD 0x0B
0x01D6: 30 28 ADDI 0x28
loc_01D8:
0x01D8: 12 0B STORE 0x0B
0x01DA: 11 0C LOAD 0x0C
0x01DC: 40 00 CMPEQ 0x00
0x01DE: 43 06 JNZ 0x06 ; -> loc_01E4 (0x01E4)
0x01E0: 11 0C LOAD 0x0C
0x01E2: 30 2B ADDI 0x2B
loc_01E4:
0x01E4: 12 0C STORE 0x0C
0x01E6: 11 0D LOAD 0x0D
0x01E8: 40 00 CMPEQ 0x00
0x01EA: 43 06 JNZ 0x06 ; -> loc_01F0 (0x01F0)
0x01EC: 11 0D LOAD 0x0D
0x01EE: 30 2E ADDI 0x2E
loc_01F0:
0x01F0: 12 0D STORE 0x0D
0x01F2: 11 0E LOAD 0x0E
0x01F4: 40 00 CMPEQ 0x00
0x01F6: 43 06 JNZ 0x06 ; -> loc_01FC (0x01FC)
0x01F8: 11 0E LOAD 0x0E
0x01FA: 30 31 ADDI 0x31
loc_01FC:
0x01FC: 12 0E STORE 0x0E
0x01FE: 11 0F LOAD 0x0F
0x0200: 40 00 CMPEQ 0x00
0x0202: 43 06 JNZ 0x06 ; -> loc_0208 (0x0208)
0x0204: 11 0F LOAD 0x0F
0x0206: 30 34 ADDI 0x34
loc_0208:
0x0208: 12 0F STORE 0x0F
0x020A: 11 10 LOAD 0x10
0x020C: 40 00 CMPEQ 0x00
0x020E: 43 06 JNZ 0x06 ; -> loc_0214 (0x0214)
0x0210: 11 10 LOAD 0x10
0x0212: 30 37 ADDI 0x37
loc_0214:
0x0214: 12 10 STORE 0x10
0x0216: 11 11 LOAD 0x11
0x0218: 40 00 CMPEQ 0x00
0x021A: 43 06 JNZ 0x06 ; -> loc_0220 (0x0220)
0x021C: 11 11 LOAD 0x11
0x021E: 30 3A ADDI 0x3A
loc_0220:
0x0220: 12 11 STORE 0x11
0x0222: 11 12 LOAD 0x12
0x0224: 40 00 CMPEQ 0x00
0x0226: 43 06 JNZ 0x06 ; -> loc_022C (0x022C)
0x0228: 11 12 LOAD 0x12
0x022A: 30 3D ADDI 0x3D
loc_022C:
0x022C: 12 12 STORE 0x12
0x022E: 11 13 LOAD 0x13
0x0230: 40 00 CMPEQ 0x00
0x0232: 43 06 JNZ 0x06 ; -> loc_0238 (0x0238)
0x0234: 11 13 LOAD 0x13
0x0236: 30 40 ADDI 0x40
loc_0238:
0x0238: 12 13 STORE 0x13
0x023A: 11 14 LOAD 0x14
0x023C: 40 00 CMPEQ 0x00
0x023E: 43 06 JNZ 0x06 ; -> loc_0244 (0x0244)
0x0240: 11 14 LOAD 0x14
0x0242: 30 43 ADDI 0x43
loc_0244:
0x0244: 12 14 STORE 0x14
0x0246: 11 15 LOAD 0x15
0x0248: 40 00 CMPEQ 0x00
0x024A: 43 06 JNZ 0x06 ; -> loc_0250 (0x0250)
0x024C: 11 15 LOAD 0x15
0x024E: 30 46 ADDI 0x46
loc_0250:
0x0250: 12 15 STORE 0x15
0x0252: 11 16 LOAD 0x16
0x0254: 40 00 CMPEQ 0x00
0x0256: 43 06 JNZ 0x06 ; -> loc_025C (0x025C)
0x0258: 11 16 LOAD 0x16
0x025A: 30 49 ADDI 0x49
loc_025C:
0x025C: 12 16 STORE 0x16
0x025E: 11 17 LOAD 0x17
0x0260: 40 00 CMPEQ 0x00
0x0262: 43 06 JNZ 0x06 ; -> loc_0268 (0x0268)
0x0264: 11 17 LOAD 0x17
0x0266: 30 4C ADDI 0x4C
loc_0268:
0x0268: 12 17 STORE 0x17
0x026A: 11 18 LOAD 0x18
0x026C: 40 00 CMPEQ 0x00
0x026E: 43 06 JNZ 0x06 ; -> loc_0274 (0x0274)
0x0270: 11 18 LOAD 0x18
0x0272: 30 4F ADDI 0x4F
loc_0274:
0x0274: 12 18 STORE 0x18
0x0276: 11 19 LOAD 0x19
0x0278: 40 00 CMPEQ 0x00
0x027A: 43 06 JNZ 0x06 ; -> loc_0280 (0x0280)
0x027C: 11 19 LOAD 0x19
0x027E: 30 52 ADDI 0x52
loc_0280:
0x0280: 12 19 STORE 0x19
0x0282: 11 1A LOAD 0x1A
0x0284: 40 00 CMPEQ 0x00
0x0286: 43 06 JNZ 0x06 ; -> loc_028C (0x028C)
0x0288: 11 1A LOAD 0x1A
0x028A: 30 55 ADDI 0x55
loc_028C:
0x028C: 12 1A STORE 0x1A
0x028E: 11 00 LOAD 0x00
0x0290: 40 29 CMPEQ 0x29
0x0292: 42 B2 JZ 0xB2 ; -> loc_0344 (0x0344)
0x0294: 11 01 LOAD 0x01
0x0296: 40 47 CMPEQ 0x47
0x0298: 42 AC JZ 0xAC ; -> loc_0344 (0x0344)
0x029A: 11 02 LOAD 0x02
0x029C: 40 39 CMPEQ 0x39
0x029E: 42 A6 JZ 0xA6 ; -> loc_0344 (0x0344)
0x02A0: 11 03 LOAD 0x03
0x02A2: 40 1A CMPEQ 0x1A
0x02A4: 42 A0 JZ 0xA0 ; -> loc_0344 (0x0344)
0x02A6: 11 04 LOAD 0x04
0x02A8: 40 3F CMPEQ 0x3F
0x02AA: 42 9A JZ 0x9A ; -> loc_0344 (0x0344)
0x02AC: 11 05 LOAD 0x05
0x02AE: 40 50 CMPEQ 0x50
0x02B0: 42 94 JZ 0x94 ; -> loc_0344 (0x0344)
0x02B2: 11 06 LOAD 0x06
0x02B4: 40 39 CMPEQ 0x39
0x02B6: 42 8E JZ 0x8E ; -> loc_0344 (0x0344)
0x02B8: 11 07 LOAD 0x07
0x02BA: 40 26 CMPEQ 0x26
0x02BC: 42 88 JZ 0x88 ; -> loc_0344 (0x0344)
0x02BE: 11 08 LOAD 0x08
0x02C0: 40 40 CMPEQ 0x40
0x02C2: 42 82 JZ 0x82 ; -> loc_0344 (0x0344)
0x02C4: 11 09 LOAD 0x09
0x02C6: 40 5F CMPEQ 0x5F
0x02C8: 42 7C JZ 0x7C ; -> loc_0344 (0x0344)
0x02CA: 11 0A LOAD 0x0A
0x02CC: 40 61 CMPEQ 0x61
0x02CE: 42 76 JZ 0x76 ; -> loc_0344 (0x0344)
0x02D0: 11 0B LOAD 0x0B
0x02D2: 40 63 CMPEQ 0x63
0x02D4: 42 70 JZ 0x70 ; -> loc_0344 (0x0344)
0x02D6: 11 0C LOAD 0x0C
0x02D8: 40 69 CMPEQ 0x69
0x02DA: 42 6A JZ 0x6A ; -> loc_0344 (0x0344)
0x02DC: 11 0D LOAD 0x0D
0x02DE: 40 38 CMPEQ 0x38
0x02E0: 42 64 JZ 0x64 ; -> loc_0344 (0x0344)
0x02E2: 11 0E LOAD 0x0E
0x02E4: 40 52 CMPEQ 0x52
0x02E6: 42 5E JZ 0x5E ; -> loc_0344 (0x0344)
0x02E8: 11 0F LOAD 0x0F
0x02EA: 40 71 CMPEQ 0x71
0x02EC: 42 58 JZ 0x58 ; -> loc_0344 (0x0344)
0x02EE: 11 10 LOAD 0x10
0x02F0: 40 73 CMPEQ 0x73
0x02F2: 42 52 JZ 0x52 ; -> loc_0344 (0x0344)
0x02F4: 11 11 LOAD 0x11
0x02F6: 40 60 CMPEQ 0x60
0x02F8: 42 4C JZ 0x4C ; -> loc_0344 (0x0344)
0x02FA: 11 12 LOAD 0x12
0x02FC: 40 47 CMPEQ 0x47
0x02FE: 42 46 JZ 0x46 ; -> loc_0344 (0x0344)
0x0300: 11 13 LOAD 0x13
0x0302: 40 7C CMPEQ 0x7C
0x0304: 42 40 JZ 0x40 ; -> loc_0344 (0x0344)
0x0306: 11 14 LOAD 0x14
0x0308: 40 69 CMPEQ 0x69
0x030A: 42 3A JZ 0x3A ; -> loc_0344 (0x0344)
0x030C: 11 15 LOAD 0x15
0x030E: 40 50 CMPEQ 0x50
0x0310: 42 34 JZ 0x34 ; -> loc_0344 (0x0344)
0x0312: 11 16 LOAD 0x16
0x0314: 40 6A CMPEQ 0x6A
0x0316: 42 2E JZ 0x2E ; -> loc_0344 (0x0344)
0x0318: 11 17 LOAD 0x17
0x031A: 40 73 CMPEQ 0x73
0x031C: 42 28 JZ 0x28 ; -> loc_0344 (0x0344)
0x031E: 11 18 LOAD 0x18
0x0320: 40 6F CMPEQ 0x6F
0x0322: 42 22 JZ 0x22 ; -> loc_0344 (0x0344)
0x0324: 11 19 LOAD 0x19
0x0326: 40 82 CMPEQ 0x82
0x0328: 42 1C JZ 0x1C ; -> loc_0344 (0x0344)
0x032A: 11 1A LOAD 0x1A
0x032C: 40 BF CMPEQ 0xBF
0x032E: 42 16 JZ 0x16 ; -> loc_0344 (0x0344)
0x0330: 10 04 ALLOC 0x04
0x0332: 20 79 MOVI 0x79
0x0334: 12 00 STORE 0x00
0x0336: 20 65 MOVI 0x65
0x0338: 12 01 STORE 0x01
0x033A: 20 73 MOVI 0x73
0x033C: 12 02 STORE 0x02
0x033E: 20 00 MOVI 0x00
0x0340: 12 03 STORE 0x03
0x0342: 52 02 SYSCALL 0x02 ; PRINT_STRING
loc_0344:
0x0344: 41 10 OP41 0x10
0x0346: 10 03 ALLOC 0x03
0x0348: 20 4E MOVI 0x4E
0x034A: 12 00 STORE 0x00
0x034C: 20 6F MOVI 0x6F
0x034E: 12 01 STORE 0x01
0x0350: 20 00 MOVI 0x00
0x0352: 12 02 STORE 0x02
0x0354: 52 02 SYSCALL 0x02 ; PRINT_STRING
0x0356: 13 FREE
opcode.bin 是 main.pyc 明面上加载的 VM 程序。把它按上面的指令集反汇编后,可以发现逻辑:
分配输入缓冲区
读取用户输入
对前 27 个字节做变换
与常量表比较
输出 yes 或 No
对前 27 个字节,程序分两轮处理:
第一轮:
if input[i] != 0:
input[i] ^= 0x55
第二轮:
if input[i] != 0:
input[i] += 7 + 3*i
最后比较的常量表是:
[41, 71, 57, 26, 63, 80, 57, 38, 64, 95, 97, 99, 105, 56, 82, 113, 115, 96, 71, 124, 105, 80, 106, 115, 111, 130, 191]
逆变换为:
orig = ((target - (7 + 3*i)) & 0xff) ^ 0x55
fake flag的exp
TARGET = [
41, 71, 57, 26, 63, 80, 57, 38, 64,
95, 97, 99, 105, 56, 82, 113, 115, 96,
71, 124, 105, 80, 106, 115, 111, 130, 191,
]
def recover_stage1(target):
out = []
for i, value in enumerate(target):
original = ((value - (7 + 3 * i)) & 0xFF) ^ 0x55
out.append(original)
return bytes(out)
def main():
recovered = recover_stage1(TARGET)
print("stage1 bytes:", list(recovered))
print("stage1 text :", recovered.decode("latin1"))
if __name__ == "__main__":
main()
why_you_think_this_is_true?
opcode.bin 只校验前 27 个字节,后面内容根本不检查。
重点分析:ntbase.pyd 里的第二段字节码
正常 .pyd 应该是一个 PE 文件,开头应为 MZ。
但这个文件开头是:
10 64 10 32 20 00 51 12 63
这明显不是 PE 而是 和 opcode.bin 一样的 VM 指令流。
把 ntbase.pyd 用反汇编后
C:\Users\Lenovo\Desktop>python .\disasm_funpyvm.py "F:\python-3.13.0-embed-amd64\main.exe_extracted\ntbase.pyd"
; size = 2390 bytes
; instructions = 1370
0x0000: 10 64 ALLOC 0x64
0x0002: 10 32 ALLOC 0x32
0x0004: 20 00 MOVI 0x00
0x0006: 51 SWAP
0x0007: 12 63 STORE 0x63
0x0009: 11 01 LOAD 0x01
0x000B: 50 63 OP50 0x63
0x000D: 51 SWAP
0x000E: 11 63 LOAD 0x63
0x0010: 51 SWAP
0x0011: 12 63 STORE 0x63
0x0013: 20 00 MOVI 0x00
0x0015: 51 SWAP
0x0016: 12 02 STORE 0x02
0x0018: 11 63 LOAD 0x63
0x001A: 51 SWAP
0x001B: 20 42 MOVI 0x42
0x001D: 30 13 ADDI 0x13
0x001F: 12 63 STORE 0x63
0x0021: 20 00 MOVI 0x00
0x0023: 51 SWAP
0x0024: 12 63 STORE 0x63
0x0026: 11 02 LOAD 0x02
0x0028: 50 63 OP50 0x63
0x002A: 51 SWAP
0x002B: 11 63 LOAD 0x63
0x002D: 51 SWAP
0x002E: 12 63 STORE 0x63
0x0030: 20 00 MOVI 0x00
0x0032: 51 SWAP
0x0033: 11 01 LOAD 0x01
0x0035: 51 SWAP
0x0036: 11 63 LOAD 0x63
0x0038: 51 SWAP
0x0039: 52 01 SYSCALL 0x01 ; READ_INPUT
0x003B: 11 00 LOAD 0x00
0x003D: 40 00 CMPEQ 0x00
0x003F: 43 06 JNZ 0x06 ; -> loc_0045 (0x0045)
0x0041: 11 00 LOAD 0x00
0x0043: 30 0A ADDI 0x0A
loc_0045:
0x0045: 12 00 STORE 0x00
0x0047: 11 01 LOAD 0x01
0x0049: 40 00 CMPEQ 0x00
0x004B: 43 3F JNZ 0x3F ; -> loc_008A (0x008A)
0x004D: 11 01 LOAD 0x01
0x004F: 30 0A ADDI 0x0A
0x0051: 12 01 STORE 0x01
0x0053: 11 00 LOAD 0x00
0x0055: 20 00 MOVI 0x00
0x0057: 51 SWAP
0x0058: 51 SWAP
0x0059: 12 63 STORE 0x63
0x005B: 20 00 MOVI 0x00
0x005D: 51 SWAP
0x005E: 12 02 STORE 0x02
0x0060: 11 63 LOAD 0x63
0x0062: 51 SWAP
0x0063: 20 00 MOVI 0x00
0x0065: 51 SWAP
0x0066: 12 63 STORE 0x63
0x0068: 11 01 LOAD 0x01
0x006A: 50 63 OP50 0x63
0x006C: 51 SWAP
0x006D: 11 63 LOAD 0x63
0x006F: 51 SWAP
0x0070: 12 63 STORE 0x63
0x0072: 20 00 MOVI 0x00
0x0074: 51 SWAP
0x0075: 11 01 LOAD 0x01
0x0077: 51 SWAP
0x0078: 11 63 LOAD 0x63
0x007A: 51 SWAP
0x007B: 20 00 MOVI 0x00
0x007D: 51 SWAP
0x007E: 12 63 STORE 0x63
0x0080: 11 02 LOAD 0x02
0x0082: 50 63 OP50 0x63
0x0084: 51 SWAP
0x0085: 11 63 LOAD 0x63
0x0087: 51 SWAP
0x0088: 11 00 LOAD 0x00
loc_008A:
0x008A: 35 01 XOR_MEM 0x01
0x008C: 11 02 LOAD 0x02
0x008E: 40 00 CMPEQ 0x00
0x0090: 43 3F JNZ 0x3F ; -> loc_00CF (0x00CF)
0x0092: 11 02 LOAD 0x02
0x0094: 30 0A ADDI 0x0A
0x0096: 12 02 STORE 0x02
0x0098: 11 01 LOAD 0x01
0x009A: 20 00 MOVI 0x00
0x009C: 51 SWAP
0x009D: 51 SWAP
0x009E: 12 63 STORE 0x63
0x00A0: 20 00 MOVI 0x00
0x00A2: 51 SWAP
0x00A3: 12 02 STORE 0x02
0x00A5: 11 63 LOAD 0x63
0x00A7: 51 SWAP
0x00A8: 20 00 MOVI 0x00
0x00AA: 51 SWAP
0x00AB: 12 63 STORE 0x63
0x00AD: 11 01 LOAD 0x01
0x00AF: 50 63 OP50 0x63
0x00B1: 51 SWAP
0x00B2: 11 63 LOAD 0x63
0x00B4: 51 SWAP
0x00B5: 12 63 STORE 0x63
0x00B7: 20 00 MOVI 0x00
0x00B9: 51 SWAP
0x00BA: 11 01 LOAD 0x01
0x00BC: 51 SWAP
0x00BD: 11 63 LOAD 0x63
0x00BF: 51 SWAP
0x00C0: 20 00 MOVI 0x00
0x00C2: 51 SWAP
0x00C3: 12 63 STORE 0x63
0x00C5: 11 02 LOAD 0x02
0x00C7: 50 63 OP50 0x63
0x00C9: 51 SWAP
0x00CA: 11 63 LOAD 0x63
0x00CC: 51 SWAP
0x00CD: 11 01 LOAD 0x01
loc_00CF:
0x00CF: 35 02 XOR_MEM 0x02
0x00D1: 11 03 LOAD 0x03
0x00D3: 40 00 CMPEQ 0x00
0x00D5: 43 3F JNZ 0x3F ; -> loc_0114 (0x0114)
0x00D7: 11 03 LOAD 0x03
0x00D9: 30 0A ADDI 0x0A
0x00DB: 12 03 STORE 0x03
0x00DD: 11 02 LOAD 0x02
0x00DF: 20 00 MOVI 0x00
0x00E1: 51 SWAP
0x00E2: 51 SWAP
0x00E3: 12 63 STORE 0x63
0x00E5: 20 00 MOVI 0x00
0x00E7: 51 SWAP
0x00E8: 12 02 STORE 0x02
0x00EA: 11 63 LOAD 0x63
0x00EC: 51 SWAP
0x00ED: 20 00 MOVI 0x00
0x00EF: 51 SWAP
0x00F0: 12 63 STORE 0x63
0x00F2: 11 01 LOAD 0x01
0x00F4: 50 63 OP50 0x63
0x00F6: 51 SWAP
0x00F7: 11 63 LOAD 0x63
0x00F9: 51 SWAP
0x00FA: 12 63 STORE 0x63
0x00FC: 20 00 MOVI 0x00
0x00FE: 51 SWAP
0x00FF: 11 01 LOAD 0x01
0x0101: 51 SWAP
0x0102: 11 63 LOAD 0x63
0x0104: 51 SWAP
0x0105: 20 00 MOVI 0x00
0x0107: 51 SWAP
0x0108: 12 63 STORE 0x63
0x010A: 11 02 LOAD 0x02
0x010C: 50 63 OP50 0x63
0x010E: 51 SWAP
0x010F: 11 63 LOAD 0x63
0x0111: 51 SWAP
0x0112: 11 02 LOAD 0x02
loc_0114:
0x0114: 35 03 XOR_MEM 0x03
0x0116: 11 04 LOAD 0x04
0x0118: 40 00 CMPEQ 0x00
0x011A: 43 3F JNZ 0x3F ; -> loc_0159 (0x0159)
0x011C: 11 04 LOAD 0x04
0x011E: 30 0A ADDI 0x0A
0x0120: 12 04 STORE 0x04
0x0122: 11 03 LOAD 0x03
0x0124: 20 00 MOVI 0x00
0x0126: 51 SWAP
0x0127: 51 SWAP
0x0128: 12 63 STORE 0x63
0x012A: 20 00 MOVI 0x00
0x012C: 51 SWAP
0x012D: 12 02 STORE 0x02
0x012F: 11 63 LOAD 0x63
0x0131: 51 SWAP
0x0132: 20 00 MOVI 0x00
0x0134: 51 SWAP
0x0135: 12 63 STORE 0x63
0x0137: 11 01 LOAD 0x01
0x0139: 50 63 OP50 0x63
0x013B: 51 SWAP
0x013C: 11 63 LOAD 0x63
0x013E: 51 SWAP
0x013F: 12 63 STORE 0x63
0x0141: 20 00 MOVI 0x00
0x0143: 51 SWAP
0x0144: 11 01 LOAD 0x01
0x0146: 51 SWAP
0x0147: 11 63 LOAD 0x63
0x0149: 51 SWAP
0x014A: 20 00 MOVI 0x00
0x014C: 51 SWAP
0x014D: 12 63 STORE 0x63
0x014F: 11 02 LOAD 0x02
0x0151: 50 63 OP50 0x63
0x0153: 51 SWAP
0x0154: 11 63 LOAD 0x63
0x0156: 51 SWAP
0x0157: 11 03 LOAD 0x03
loc_0159:
0x0159: 35 04 XOR_MEM 0x04
0x015B: 11 05 LOAD 0x05
0x015D: 40 00 CMPEQ 0x00
0x015F: 43 3F JNZ 0x3F ; -> loc_019E (0x019E)
0x0161: 11 05 LOAD 0x05
0x0163: 30 0A ADDI 0x0A
0x0165: 12 05 STORE 0x05
0x0167: 11 04 LOAD 0x04
0x0169: 20 00 MOVI 0x00
0x016B: 51 SWAP
0x016C: 51 SWAP
0x016D: 12 63 STORE 0x63
0x016F: 20 00 MOVI 0x00
0x0171: 51 SWAP
0x0172: 12 02 STORE 0x02
0x0174: 11 63 LOAD 0x63
0x0176: 51 SWAP
0x0177: 20 00 MOVI 0x00
0x0179: 51 SWAP
0x017A: 12 63 STORE 0x63
0x017C: 11 01 LOAD 0x01
0x017E: 50 63 OP50 0x63
0x0180: 51 SWAP
0x0181: 11 63 LOAD 0x63
0x0183: 51 SWAP
0x0184: 12 63 STORE 0x63
0x0186: 20 00 MOVI 0x00
0x0188: 51 SWAP
0x0189: 11 01 LOAD 0x01
0x018B: 51 SWAP
0x018C: 11 63 LOAD 0x63
0x018E: 51 SWAP
0x018F: 20 00 MOVI 0x00
0x0191: 51 SWAP
0x0192: 12 63 STORE 0x63
0x0194: 11 02 LOAD 0x02
0x0196: 50 63 OP50 0x63
0x0198: 51 SWAP
0x0199: 11 63 LOAD 0x63
0x019B: 51 SWAP
0x019C: 11 04 LOAD 0x04
loc_019E:
0x019E: 35 05 XOR_MEM 0x05
0x01A0: 11 06 LOAD 0x06
0x01A2: 40 00 CMPEQ 0x00
0x01A4: 43 3F JNZ 0x3F ; -> loc_01E3 (0x01E3)
0x01A6: 11 06 LOAD 0x06
0x01A8: 30 0A ADDI 0x0A
0x01AA: 12 06 STORE 0x06
0x01AC: 11 05 LOAD 0x05
0x01AE: 20 00 MOVI 0x00
0x01B0: 51 SWAP
0x01B1: 51 SWAP
0x01B2: 12 63 STORE 0x63
0x01B4: 20 00 MOVI 0x00
0x01B6: 51 SWAP
0x01B7: 12 02 STORE 0x02
0x01B9: 11 63 LOAD 0x63
0x01BB: 51 SWAP
0x01BC: 20 00 MOVI 0x00
0x01BE: 51 SWAP
0x01BF: 12 63 STORE 0x63
0x01C1: 11 01 LOAD 0x01
0x01C3: 50 63 OP50 0x63
0x01C5: 51 SWAP
0x01C6: 11 63 LOAD 0x63
0x01C8: 51 SWAP
0x01C9: 12 63 STORE 0x63
0x01CB: 20 00 MOVI 0x00
0x01CD: 51 SWAP
0x01CE: 11 01 LOAD 0x01
0x01D0: 51 SWAP
0x01D1: 11 63 LOAD 0x63
0x01D3: 51 SWAP
0x01D4: 20 00 MOVI 0x00
0x01D6: 51 SWAP
0x01D7: 12 63 STORE 0x63
0x01D9: 11 02 LOAD 0x02
0x01DB: 50 63 OP50 0x63
0x01DD: 51 SWAP
0x01DE: 11 63 LOAD 0x63
0x01E0: 51 SWAP
0x01E1: 11 05 LOAD 0x05
loc_01E3:
0x01E3: 35 06 XOR_MEM 0x06
0x01E5: 11 07 LOAD 0x07
0x01E7: 40 00 CMPEQ 0x00
0x01E9: 43 3F JNZ 0x3F ; -> loc_0228 (0x0228)
0x01EB: 11 07 LOAD 0x07
0x01ED: 30 0A ADDI 0x0A
0x01EF: 12 07 STORE 0x07
0x01F1: 11 06 LOAD 0x06
0x01F3: 20 00 MOVI 0x00
0x01F5: 51 SWAP
0x01F6: 51 SWAP
0x01F7: 12 63 STORE 0x63
0x01F9: 20 00 MOVI 0x00
0x01FB: 51 SWAP
0x01FC: 12 02 STORE 0x02
0x01FE: 11 63 LOAD 0x63
0x0200: 51 SWAP
0x0201: 20 00 MOVI 0x00
0x0203: 51 SWAP
0x0204: 12 63 STORE 0x63
0x0206: 11 01 LOAD 0x01
0x0208: 50 63 OP50 0x63
0x020A: 51 SWAP
0x020B: 11 63 LOAD 0x63
0x020D: 51 SWAP
0x020E: 12 63 STORE 0x63
0x0210: 20 00 MOVI 0x00
0x0212: 51 SWAP
0x0213: 11 01 LOAD 0x01
0x0215: 51 SWAP
0x0216: 11 63 LOAD 0x63
0x0218: 51 SWAP
0x0219: 20 00 MOVI 0x00
0x021B: 51 SWAP
0x021C: 12 63 STORE 0x63
0x021E: 11 02 LOAD 0x02
0x0220: 50 63 OP50 0x63
0x0222: 51 SWAP
0x0223: 11 63 LOAD 0x63
0x0225: 51 SWAP
0x0226: 11 06 LOAD 0x06
loc_0228:
0x0228: 35 07 XOR_MEM 0x07
0x022A: 11 08 LOAD 0x08
0x022C: 40 00 CMPEQ 0x00
0x022E: 43 3F JNZ 0x3F ; -> loc_026D (0x026D)
0x0230: 11 08 LOAD 0x08
0x0232: 30 0A ADDI 0x0A
0x0234: 12 08 STORE 0x08
0x0236: 11 07 LOAD 0x07
0x0238: 20 00 MOVI 0x00
0x023A: 51 SWAP
0x023B: 51 SWAP
0x023C: 12 63 STORE 0x63
0x023E: 20 00 MOVI 0x00
0x0240: 51 SWAP
0x0241: 12 02 STORE 0x02
0x0243: 11 63 LOAD 0x63
0x0245: 51 SWAP
0x0246: 20 00 MOVI 0x00
0x0248: 51 SWAP
0x0249: 12 63 STORE 0x63
0x024B: 11 01 LOAD 0x01
0x024D: 50 63 OP50 0x63
0x024F: 51 SWAP
0x0250: 11 63 LOAD 0x63
0x0252: 51 SWAP
0x0253: 12 63 STORE 0x63
0x0255: 20 00 MOVI 0x00
0x0257: 51 SWAP
0x0258: 11 01 LOAD 0x01
0x025A: 51 SWAP
0x025B: 11 63 LOAD 0x63
0x025D: 51 SWAP
0x025E: 20 00 MOVI 0x00
0x0260: 51 SWAP
0x0261: 12 63 STORE 0x63
0x0263: 11 02 LOAD 0x02
0x0265: 50 63 OP50 0x63
0x0267: 51 SWAP
0x0268: 11 63 LOAD 0x63
0x026A: 51 SWAP
0x026B: 11 07 LOAD 0x07
loc_026D:
0x026D: 35 08 XOR_MEM 0x08
0x026F: 11 09 LOAD 0x09
0x0271: 40 00 CMPEQ 0x00
0x0273: 43 3F JNZ 0x3F ; -> loc_02B2 (0x02B2)
0x0275: 11 09 LOAD 0x09
0x0277: 30 0A ADDI 0x0A
0x0279: 12 09 STORE 0x09
0x027B: 11 08 LOAD 0x08
0x027D: 20 00 MOVI 0x00
0x027F: 51 SWAP
0x0280: 51 SWAP
0x0281: 12 63 STORE 0x63
0x0283: 20 00 MOVI 0x00
0x0285: 51 SWAP
0x0286: 12 02 STORE 0x02
0x0288: 11 63 LOAD 0x63
0x028A: 51 SWAP
0x028B: 20 00 MOVI 0x00
0x028D: 51 SWAP
0x028E: 12 63 STORE 0x63
0x0290: 11 01 LOAD 0x01
0x0292: 50 63 OP50 0x63
0x0294: 51 SWAP
0x0295: 11 63 LOAD 0x63
0x0297: 51 SWAP
0x0298: 12 63 STORE 0x63
0x029A: 20 00 MOVI 0x00
0x029C: 51 SWAP
0x029D: 11 01 LOAD 0x01
0x029F: 51 SWAP
0x02A0: 11 63 LOAD 0x63
0x02A2: 51 SWAP
0x02A3: 20 00 MOVI 0x00
0x02A5: 51 SWAP
0x02A6: 12 63 STORE 0x63
0x02A8: 11 02 LOAD 0x02
0x02AA: 50 63 OP50 0x63
0x02AC: 51 SWAP
0x02AD: 11 63 LOAD 0x63
0x02AF: 51 SWAP
0x02B0: 11 08 LOAD 0x08
loc_02B2:
0x02B2: 35 09 XOR_MEM 0x09
0x02B4: 11 0A LOAD 0x0A
0x02B6: 40 00 CMPEQ 0x00
0x02B8: 43 3F JNZ 0x3F ; -> loc_02F7 (0x02F7)
0x02BA: 11 0A LOAD 0x0A
0x02BC: 30 0A ADDI 0x0A
0x02BE: 12 0A STORE 0x0A
0x02C0: 11 09 LOAD 0x09
0x02C2: 20 00 MOVI 0x00
0x02C4: 51 SWAP
0x02C5: 51 SWAP
0x02C6: 12 63 STORE 0x63
0x02C8: 20 00 MOVI 0x00
0x02CA: 51 SWAP
0x02CB: 12 02 STORE 0x02
0x02CD: 11 63 LOAD 0x63
0x02CF: 51 SWAP
0x02D0: 20 00 MOVI 0x00
0x02D2: 51 SWAP
0x02D3: 12 63 STORE 0x63
0x02D5: 11 01 LOAD 0x01
0x02D7: 50 63 OP50 0x63
0x02D9: 51 SWAP
0x02DA: 11 63 LOAD 0x63
0x02DC: 51 SWAP
0x02DD: 12 63 STORE 0x63
0x02DF: 20 00 MOVI 0x00
0x02E1: 51 SWAP
0x02E2: 11 01 LOAD 0x01
0x02E4: 51 SWAP
0x02E5: 11 63 LOAD 0x63
0x02E7: 51 SWAP
0x02E8: 20 00 MOVI 0x00
0x02EA: 51 SWAP
0x02EB: 12 63 STORE 0x63
0x02ED: 11 02 LOAD 0x02
0x02EF: 50 63 OP50 0x63
0x02F1: 51 SWAP
0x02F2: 11 63 LOAD 0x63
0x02F4: 51 SWAP
0x02F5: 11 09 LOAD 0x09
loc_02F7:
0x02F7: 35 0A XOR_MEM 0x0A
0x02F9: 11 0B LOAD 0x0B
0x02FB: 40 00 CMPEQ 0x00
0x02FD: 43 3F JNZ 0x3F ; -> loc_033C (0x033C)
0x02FF: 11 0B LOAD 0x0B
0x0301: 30 0A ADDI 0x0A
0x0303: 12 0B STORE 0x0B
0x0305: 11 0A LOAD 0x0A
0x0307: 20 00 MOVI 0x00
0x0309: 51 SWAP
0x030A: 51 SWAP
0x030B: 12 63 STORE 0x63
0x030D: 20 00 MOVI 0x00
0x030F: 51 SWAP
0x0310: 12 02 STORE 0x02
0x0312: 11 63 LOAD 0x63
0x0314: 51 SWAP
0x0315: 20 00 MOVI 0x00
0x0317: 51 SWAP
0x0318: 12 63 STORE 0x63
0x031A: 11 01 LOAD 0x01
0x031C: 50 63 OP50 0x63
0x031E: 51 SWAP
0x031F: 11 63 LOAD 0x63
0x0321: 51 SWAP
0x0322: 12 63 STORE 0x63
0x0324: 20 00 MOVI 0x00
0x0326: 51 SWAP
0x0327: 11 01 LOAD 0x01
0x0329: 51 SWAP
0x032A: 11 63 LOAD 0x63
0x032C: 51 SWAP
0x032D: 20 00 MOVI 0x00
0x032F: 51 SWAP
0x0330: 12 63 STORE 0x63
0x0332: 11 02 LOAD 0x02
0x0334: 50 63 OP50 0x63
0x0336: 51 SWAP
0x0337: 11 63 LOAD 0x63
0x0339: 51 SWAP
0x033A: 11 0A LOAD 0x0A
loc_033C:
0x033C: 35 0B XOR_MEM 0x0B
0x033E: 11 0C LOAD 0x0C
0x0340: 40 00 CMPEQ 0x00
0x0342: 43 3F JNZ 0x3F ; -> loc_0381 (0x0381)
0x0344: 11 0C LOAD 0x0C
0x0346: 30 0A ADDI 0x0A
0x0348: 12 0C STORE 0x0C
0x034A: 11 0B LOAD 0x0B
0x034C: 20 00 MOVI 0x00
0x034E: 51 SWAP
0x034F: 51 SWAP
0x0350: 12 63 STORE 0x63
0x0352: 20 00 MOVI 0x00
0x0354: 51 SWAP
0x0355: 12 02 STORE 0x02
0x0357: 11 63 LOAD 0x63
0x0359: 51 SWAP
0x035A: 20 00 MOVI 0x00
0x035C: 51 SWAP
0x035D: 12 63 STORE 0x63
0x035F: 11 01 LOAD 0x01
0x0361: 50 63 OP50 0x63
0x0363: 51 SWAP
0x0364: 11 63 LOAD 0x63
0x0366: 51 SWAP
0x0367: 12 63 STORE 0x63
0x0369: 20 00 MOVI 0x00
0x036B: 51 SWAP
0x036C: 11 01 LOAD 0x01
0x036E: 51 SWAP
0x036F: 11 63 LOAD 0x63
0x0371: 51 SWAP
0x0372: 20 00 MOVI 0x00
0x0374: 51 SWAP
0x0375: 12 63 STORE 0x63
0x0377: 11 02 LOAD 0x02
0x0379: 50 63 OP50 0x63
0x037B: 51 SWAP
0x037C: 11 63 LOAD 0x63
0x037E: 51 SWAP
0x037F: 11 0B LOAD 0x0B
loc_0381:
0x0381: 35 0C XOR_MEM 0x0C
0x0383: 11 0D LOAD 0x0D
0x0385: 40 00 CMPEQ 0x00
0x0387: 43 3F JNZ 0x3F ; -> loc_03C6 (0x03C6)
0x0389: 11 0D LOAD 0x0D
0x038B: 30 0A ADDI 0x0A
0x038D: 12 0D STORE 0x0D
0x038F: 11 0C LOAD 0x0C
0x0391: 20 00 MOVI 0x00
0x0393: 51 SWAP
0x0394: 51 SWAP
0x0395: 12 63 STORE 0x63
0x0397: 20 00 MOVI 0x00
0x0399: 51 SWAP
0x039A: 12 02 STORE 0x02
0x039C: 11 63 LOAD 0x63
0x039E: 51 SWAP
0x039F: 20 00 MOVI 0x00
0x03A1: 51 SWAP
0x03A2: 12 63 STORE 0x63
0x03A4: 11 01 LOAD 0x01
0x03A6: 50 63 OP50 0x63
0x03A8: 51 SWAP
0x03A9: 11 63 LOAD 0x63
0x03AB: 51 SWAP
0x03AC: 12 63 STORE 0x63
0x03AE: 20 00 MOVI 0x00
0x03B0: 51 SWAP
0x03B1: 11 01 LOAD 0x01
0x03B3: 51 SWAP
0x03B4: 11 63 LOAD 0x63
0x03B6: 51 SWAP
0x03B7: 20 00 MOVI 0x00
0x03B9: 51 SWAP
0x03BA: 12 63 STORE 0x63
0x03BC: 11 02 LOAD 0x02
0x03BE: 50 63 OP50 0x63
0x03C0: 51 SWAP
0x03C1: 11 63 LOAD 0x63
0x03C3: 51 SWAP
0x03C4: 11 0C LOAD 0x0C
loc_03C6:
0x03C6: 35 0D XOR_MEM 0x0D
0x03C8: 11 0E LOAD 0x0E
0x03CA: 40 00 CMPEQ 0x00
0x03CC: 43 3F JNZ 0x3F ; -> loc_040B (0x040B)
0x03CE: 11 0E LOAD 0x0E
0x03D0: 30 0A ADDI 0x0A
0x03D2: 12 0E STORE 0x0E
0x03D4: 11 0D LOAD 0x0D
0x03D6: 20 00 MOVI 0x00
0x03D8: 51 SWAP
0x03D9: 51 SWAP
0x03DA: 12 63 STORE 0x63
0x03DC: 20 00 MOVI 0x00
0x03DE: 51 SWAP
0x03DF: 12 02 STORE 0x02
0x03E1: 11 63 LOAD 0x63
0x03E3: 51 SWAP
0x03E4: 20 00 MOVI 0x00
0x03E6: 51 SWAP
0x03E7: 12 63 STORE 0x63
0x03E9: 11 01 LOAD 0x01
0x03EB: 50 63 OP50 0x63
0x03ED: 51 SWAP
0x03EE: 11 63 LOAD 0x63
0x03F0: 51 SWAP
0x03F1: 12 63 STORE 0x63
0x03F3: 20 00 MOVI 0x00
0x03F5: 51 SWAP
0x03F6: 11 01 LOAD 0x01
0x03F8: 51 SWAP
0x03F9: 11 63 LOAD 0x63
0x03FB: 51 SWAP
0x03FC: 20 00 MOVI 0x00
0x03FE: 51 SWAP
0x03FF: 12 63 STORE 0x63
0x0401: 11 02 LOAD 0x02
0x0403: 50 63 OP50 0x63
0x0405: 51 SWAP
0x0406: 11 63 LOAD 0x63
0x0408: 51 SWAP
0x0409: 11 0D LOAD 0x0D
loc_040B:
0x040B: 35 0E XOR_MEM 0x0E
0x040D: 11 0F LOAD 0x0F
0x040F: 40 00 CMPEQ 0x00
0x0411: 43 3F JNZ 0x3F ; -> loc_0450 (0x0450)
0x0413: 11 0F LOAD 0x0F
0x0415: 30 0A ADDI 0x0A
0x0417: 12 0F STORE 0x0F
0x0419: 11 0E LOAD 0x0E
0x041B: 20 00 MOVI 0x00
0x041D: 51 SWAP
0x041E: 51 SWAP
0x041F: 12 63 STORE 0x63
0x0421: 20 00 MOVI 0x00
0x0423: 51 SWAP
0x0424: 12 02 STORE 0x02
0x0426: 11 63 LOAD 0x63
0x0428: 51 SWAP
0x0429: 20 00 MOVI 0x00
0x042B: 51 SWAP
0x042C: 12 63 STORE 0x63
0x042E: 11 01 LOAD 0x01
0x0430: 50 63 OP50 0x63
0x0432: 51 SWAP
0x0433: 11 63 LOAD 0x63
0x0435: 51 SWAP
0x0436: 12 63 STORE 0x63
0x0438: 20 00 MOVI 0x00
0x043A: 51 SWAP
0x043B: 11 01 LOAD 0x01
0x043D: 51 SWAP
0x043E: 11 63 LOAD 0x63
0x0440: 51 SWAP
0x0441: 20 00 MOVI 0x00
0x0443: 51 SWAP
0x0444: 12 63 STORE 0x63
0x0446: 11 02 LOAD 0x02
0x0448: 50 63 OP50 0x63
0x044A: 51 SWAP
0x044B: 11 63 LOAD 0x63
0x044D: 51 SWAP
0x044E: 11 0E LOAD 0x0E
loc_0450:
0x0450: 35 0F XOR_MEM 0x0F
0x0452: 11 10 LOAD 0x10
0x0454: 40 00 CMPEQ 0x00
0x0456: 43 3F JNZ 0x3F ; -> loc_0495 (0x0495)
0x0458: 11 10 LOAD 0x10
0x045A: 30 0A ADDI 0x0A
0x045C: 12 10 STORE 0x10
0x045E: 11 0F LOAD 0x0F
0x0460: 20 00 MOVI 0x00
0x0462: 51 SWAP
0x0463: 51 SWAP
0x0464: 12 63 STORE 0x63
0x0466: 20 00 MOVI 0x00
0x0468: 51 SWAP
0x0469: 12 02 STORE 0x02
0x046B: 11 63 LOAD 0x63
0x046D: 51 SWAP
0x046E: 20 00 MOVI 0x00
0x0470: 51 SWAP
0x0471: 12 63 STORE 0x63
0x0473: 11 01 LOAD 0x01
0x0475: 50 63 OP50 0x63
0x0477: 51 SWAP
0x0478: 11 63 LOAD 0x63
0x047A: 51 SWAP
0x047B: 12 63 STORE 0x63
0x047D: 20 00 MOVI 0x00
0x047F: 51 SWAP
0x0480: 11 01 LOAD 0x01
0x0482: 51 SWAP
0x0483: 11 63 LOAD 0x63
0x0485: 51 SWAP
0x0486: 20 00 MOVI 0x00
0x0488: 51 SWAP
0x0489: 12 63 STORE 0x63
0x048B: 11 02 LOAD 0x02
0x048D: 50 63 OP50 0x63
0x048F: 51 SWAP
0x0490: 11 63 LOAD 0x63
0x0492: 51 SWAP
0x0493: 11 0F LOAD 0x0F
loc_0495:
0x0495: 35 10 XOR_MEM 0x10
0x0497: 11 11 LOAD 0x11
0x0499: 40 00 CMPEQ 0x00
0x049B: 43 3F JNZ 0x3F ; -> loc_04DA (0x04DA)
0x049D: 11 11 LOAD 0x11
0x049F: 30 0A ADDI 0x0A
0x04A1: 12 11 STORE 0x11
0x04A3: 11 10 LOAD 0x10
0x04A5: 20 00 MOVI 0x00
0x04A7: 51 SWAP
0x04A8: 51 SWAP
0x04A9: 12 63 STORE 0x63
0x04AB: 20 00 MOVI 0x00
0x04AD: 51 SWAP
0x04AE: 12 02 STORE 0x02
0x04B0: 11 63 LOAD 0x63
0x04B2: 51 SWAP
0x04B3: 20 00 MOVI 0x00
0x04B5: 51 SWAP
0x04B6: 12 63 STORE 0x63
0x04B8: 11 01 LOAD 0x01
0x04BA: 50 63 OP50 0x63
0x04BC: 51 SWAP
0x04BD: 11 63 LOAD 0x63
0x04BF: 51 SWAP
0x04C0: 12 63 STORE 0x63
0x04C2: 20 00 MOVI 0x00
0x04C4: 51 SWAP
0x04C5: 11 01 LOAD 0x01
0x04C7: 51 SWAP
0x04C8: 11 63 LOAD 0x63
0x04CA: 51 SWAP
0x04CB: 20 00 MOVI 0x00
0x04CD: 51 SWAP
0x04CE: 12 63 STORE 0x63
0x04D0: 11 02 LOAD 0x02
0x04D2: 50 63 OP50 0x63
0x04D4: 51 SWAP
0x04D5: 11 63 LOAD 0x63
0x04D7: 51 SWAP
0x04D8: 11 10 LOAD 0x10
loc_04DA:
0x04DA: 35 11 XOR_MEM 0x11
0x04DC: 11 12 LOAD 0x12
0x04DE: 40 00 CMPEQ 0x00
0x04E0: 43 3F JNZ 0x3F ; -> loc_051F (0x051F)
0x04E2: 11 12 LOAD 0x12
0x04E4: 30 0A ADDI 0x0A
0x04E6: 12 12 STORE 0x12
0x04E8: 11 11 LOAD 0x11
0x04EA: 20 00 MOVI 0x00
0x04EC: 51 SWAP
0x04ED: 51 SWAP
0x04EE: 12 63 STORE 0x63
0x04F0: 20 00 MOVI 0x00
0x04F2: 51 SWAP
0x04F3: 12 02 STORE 0x02
0x04F5: 11 63 LOAD 0x63
0x04F7: 51 SWAP
0x04F8: 20 00 MOVI 0x00
0x04FA: 51 SWAP
0x04FB: 12 63 STORE 0x63
0x04FD: 11 01 LOAD 0x01
0x04FF: 50 63 OP50 0x63
0x0501: 51 SWAP
0x0502: 11 63 LOAD 0x63
0x0504: 51 SWAP
0x0505: 12 63 STORE 0x63
0x0507: 20 00 MOVI 0x00
0x0509: 51 SWAP
0x050A: 11 01 LOAD 0x01
0x050C: 51 SWAP
0x050D: 11 63 LOAD 0x63
0x050F: 51 SWAP
0x0510: 20 00 MOVI 0x00
0x0512: 51 SWAP
0x0513: 12 63 STORE 0x63
0x0515: 11 02 LOAD 0x02
0x0517: 50 63 OP50 0x63
0x0519: 51 SWAP
0x051A: 11 63 LOAD 0x63
0x051C: 51 SWAP
0x051D: 11 11 LOAD 0x11
loc_051F:
0x051F: 35 12 XOR_MEM 0x12
0x0521: 11 13 LOAD 0x13
0x0523: 40 00 CMPEQ 0x00
0x0525: 43 3F JNZ 0x3F ; -> loc_0564 (0x0564)
0x0527: 11 13 LOAD 0x13
0x0529: 30 0A ADDI 0x0A
0x052B: 12 13 STORE 0x13
0x052D: 11 12 LOAD 0x12
0x052F: 20 00 MOVI 0x00
0x0531: 51 SWAP
0x0532: 51 SWAP
0x0533: 12 63 STORE 0x63
0x0535: 20 00 MOVI 0x00
0x0537: 51 SWAP
0x0538: 12 02 STORE 0x02
0x053A: 11 63 LOAD 0x63
0x053C: 51 SWAP
0x053D: 20 00 MOVI 0x00
0x053F: 51 SWAP
0x0540: 12 63 STORE 0x63
0x0542: 11 01 LOAD 0x01
0x0544: 50 63 OP50 0x63
0x0546: 51 SWAP
0x0547: 11 63 LOAD 0x63
0x0549: 51 SWAP
0x054A: 12 63 STORE 0x63
0x054C: 20 00 MOVI 0x00
0x054E: 51 SWAP
0x054F: 11 01 LOAD 0x01
0x0551: 51 SWAP
0x0552: 11 63 LOAD 0x63
0x0554: 51 SWAP
0x0555: 20 00 MOVI 0x00
0x0557: 51 SWAP
0x0558: 12 63 STORE 0x63
0x055A: 11 02 LOAD 0x02
0x055C: 50 63 OP50 0x63
0x055E: 51 SWAP
0x055F: 11 63 LOAD 0x63
0x0561: 51 SWAP
0x0562: 11 12 LOAD 0x12
loc_0564:
0x0564: 35 13 XOR_MEM 0x13
0x0566: 11 14 LOAD 0x14
0x0568: 40 00 CMPEQ 0x00
0x056A: 43 3F JNZ 0x3F ; -> loc_05A9 (0x05A9)
0x056C: 11 14 LOAD 0x14
0x056E: 30 0A ADDI 0x0A
0x0570: 12 14 STORE 0x14
0x0572: 11 13 LOAD 0x13
0x0574: 20 00 MOVI 0x00
0x0576: 51 SWAP
0x0577: 51 SWAP
0x0578: 12 63 STORE 0x63
0x057A: 20 00 MOVI 0x00
0x057C: 51 SWAP
0x057D: 12 02 STORE 0x02
0x057F: 11 63 LOAD 0x63
0x0581: 51 SWAP
0x0582: 20 00 MOVI 0x00
0x0584: 51 SWAP
0x0585: 12 63 STORE 0x63
0x0587: 11 01 LOAD 0x01
0x0589: 50 63 OP50 0x63
0x058B: 51 SWAP
0x058C: 11 63 LOAD 0x63
0x058E: 51 SWAP
0x058F: 12 63 STORE 0x63
0x0591: 20 00 MOVI 0x00
0x0593: 51 SWAP
0x0594: 11 01 LOAD 0x01
0x0596: 51 SWAP
0x0597: 11 63 LOAD 0x63
0x0599: 51 SWAP
0x059A: 20 00 MOVI 0x00
0x059C: 51 SWAP
0x059D: 12 63 STORE 0x63
0x059F: 11 02 LOAD 0x02
0x05A1: 50 63 OP50 0x63
0x05A3: 51 SWAP
0x05A4: 11 63 LOAD 0x63
0x05A6: 51 SWAP
0x05A7: 11 13 LOAD 0x13
loc_05A9:
0x05A9: 35 14 XOR_MEM 0x14
0x05AB: 11 15 LOAD 0x15
0x05AD: 40 00 CMPEQ 0x00
0x05AF: 43 3F JNZ 0x3F ; -> loc_05EE (0x05EE)
0x05B1: 11 15 LOAD 0x15
0x05B3: 30 0A ADDI 0x0A
0x05B5: 12 15 STORE 0x15
0x05B7: 11 14 LOAD 0x14
0x05B9: 20 00 MOVI 0x00
0x05BB: 51 SWAP
0x05BC: 51 SWAP
0x05BD: 12 63 STORE 0x63
0x05BF: 20 00 MOVI 0x00
0x05C1: 51 SWAP
0x05C2: 12 02 STORE 0x02
0x05C4: 11 63 LOAD 0x63
0x05C6: 51 SWAP
0x05C7: 20 00 MOVI 0x00
0x05C9: 51 SWAP
0x05CA: 12 63 STORE 0x63
0x05CC: 11 01 LOAD 0x01
0x05CE: 50 63 OP50 0x63
0x05D0: 51 SWAP
0x05D1: 11 63 LOAD 0x63
0x05D3: 51 SWAP
0x05D4: 12 63 STORE 0x63
0x05D6: 20 00 MOVI 0x00
0x05D8: 51 SWAP
0x05D9: 11 01 LOAD 0x01
0x05DB: 51 SWAP
0x05DC: 11 63 LOAD 0x63
0x05DE: 51 SWAP
0x05DF: 20 00 MOVI 0x00
0x05E1: 51 SWAP
0x05E2: 12 63 STORE 0x63
0x05E4: 11 02 LOAD 0x02
0x05E6: 50 63 OP50 0x63
0x05E8: 51 SWAP
0x05E9: 11 63 LOAD 0x63
0x05EB: 51 SWAP
0x05EC: 11 14 LOAD 0x14
loc_05EE:
0x05EE: 35 15 XOR_MEM 0x15
0x05F0: 11 16 LOAD 0x16
0x05F2: 40 00 CMPEQ 0x00
0x05F4: 43 3F JNZ 0x3F ; -> loc_0633 (0x0633)
0x05F6: 11 16 LOAD 0x16
0x05F8: 30 0A ADDI 0x0A
0x05FA: 12 16 STORE 0x16
0x05FC: 11 15 LOAD 0x15
0x05FE: 20 00 MOVI 0x00
0x0600: 51 SWAP
0x0601: 51 SWAP
0x0602: 12 63 STORE 0x63
0x0604: 20 00 MOVI 0x00
0x0606: 51 SWAP
0x0607: 12 02 STORE 0x02
0x0609: 11 63 LOAD 0x63
0x060B: 51 SWAP
0x060C: 20 00 MOVI 0x00
0x060E: 51 SWAP
0x060F: 12 63 STORE 0x63
0x0611: 11 01 LOAD 0x01
0x0613: 50 63 OP50 0x63
0x0615: 51 SWAP
0x0616: 11 63 LOAD 0x63
0x0618: 51 SWAP
0x0619: 12 63 STORE 0x63
0x061B: 20 00 MOVI 0x00
0x061D: 51 SWAP
0x061E: 11 01 LOAD 0x01
0x0620: 51 SWAP
0x0621: 11 63 LOAD 0x63
0x0623: 51 SWAP
0x0624: 20 00 MOVI 0x00
0x0626: 51 SWAP
0x0627: 12 63 STORE 0x63
0x0629: 11 02 LOAD 0x02
0x062B: 50 63 OP50 0x63
0x062D: 51 SWAP
0x062E: 11 63 LOAD 0x63
0x0630: 51 SWAP
0x0631: 11 15 LOAD 0x15
loc_0633:
0x0633: 35 16 XOR_MEM 0x16
0x0635: 11 17 LOAD 0x17
0x0637: 40 00 CMPEQ 0x00
0x0639: 43 3F JNZ 0x3F ; -> loc_0678 (0x0678)
0x063B: 11 17 LOAD 0x17
0x063D: 30 0A ADDI 0x0A
0x063F: 12 17 STORE 0x17
0x0641: 11 16 LOAD 0x16
0x0643: 20 00 MOVI 0x00
0x0645: 51 SWAP
0x0646: 51 SWAP
0x0647: 12 63 STORE 0x63
0x0649: 20 00 MOVI 0x00
0x064B: 51 SWAP
0x064C: 12 02 STORE 0x02
0x064E: 11 63 LOAD 0x63
0x0650: 51 SWAP
0x0651: 20 00 MOVI 0x00
0x0653: 51 SWAP
0x0654: 12 63 STORE 0x63
0x0656: 11 01 LOAD 0x01
0x0658: 50 63 OP50 0x63
0x065A: 51 SWAP
0x065B: 11 63 LOAD 0x63
0x065D: 51 SWAP
0x065E: 12 63 STORE 0x63
0x0660: 20 00 MOVI 0x00
0x0662: 51 SWAP
0x0663: 11 01 LOAD 0x01
0x0665: 51 SWAP
0x0666: 11 63 LOAD 0x63
0x0668: 51 SWAP
0x0669: 20 00 MOVI 0x00
0x066B: 51 SWAP
0x066C: 12 63 STORE 0x63
0x066E: 11 02 LOAD 0x02
0x0670: 50 63 OP50 0x63
0x0672: 51 SWAP
0x0673: 11 63 LOAD 0x63
0x0675: 51 SWAP
0x0676: 11 16 LOAD 0x16
loc_0678:
0x0678: 35 17 XOR_MEM 0x17
0x067A: 11 18 LOAD 0x18
0x067C: 40 00 CMPEQ 0x00
0x067E: 43 3F JNZ 0x3F ; -> loc_06BD (0x06BD)
0x0680: 11 18 LOAD 0x18
0x0682: 30 0A ADDI 0x0A
0x0684: 12 18 STORE 0x18
0x0686: 11 17 LOAD 0x17
0x0688: 20 00 MOVI 0x00
0x068A: 51 SWAP
0x068B: 51 SWAP
0x068C: 12 63 STORE 0x63
0x068E: 20 00 MOVI 0x00
0x0690: 51 SWAP
0x0691: 12 02 STORE 0x02
0x0693: 11 63 LOAD 0x63
0x0695: 51 SWAP
0x0696: 20 00 MOVI 0x00
0x0698: 51 SWAP
0x0699: 12 63 STORE 0x63
0x069B: 11 01 LOAD 0x01
0x069D: 50 63 OP50 0x63
0x069F: 51 SWAP
0x06A0: 11 63 LOAD 0x63
0x06A2: 51 SWAP
0x06A3: 12 63 STORE 0x63
0x06A5: 20 00 MOVI 0x00
0x06A7: 51 SWAP
0x06A8: 11 01 LOAD 0x01
0x06AA: 51 SWAP
0x06AB: 11 63 LOAD 0x63
0x06AD: 51 SWAP
0x06AE: 20 00 MOVI 0x00
0x06B0: 51 SWAP
0x06B1: 12 63 STORE 0x63
0x06B3: 11 02 LOAD 0x02
0x06B5: 50 63 OP50 0x63
0x06B7: 51 SWAP
0x06B8: 11 63 LOAD 0x63
0x06BA: 51 SWAP
0x06BB: 11 17 LOAD 0x17
loc_06BD:
0x06BD: 35 18 XOR_MEM 0x18
0x06BF: 11 19 LOAD 0x19
0x06C1: 40 00 CMPEQ 0x00
0x06C3: 43 3F JNZ 0x3F ; -> loc_0702 (0x0702)
0x06C5: 11 19 LOAD 0x19
0x06C7: 30 0A ADDI 0x0A
0x06C9: 12 19 STORE 0x19
0x06CB: 11 18 LOAD 0x18
0x06CD: 20 00 MOVI 0x00
0x06CF: 51 SWAP
0x06D0: 51 SWAP
0x06D1: 12 63 STORE 0x63
0x06D3: 20 00 MOVI 0x00
0x06D5: 51 SWAP
0x06D6: 12 02 STORE 0x02
0x06D8: 11 63 LOAD 0x63
0x06DA: 51 SWAP
0x06DB: 20 00 MOVI 0x00
0x06DD: 51 SWAP
0x06DE: 12 63 STORE 0x63
0x06E0: 11 01 LOAD 0x01
0x06E2: 50 63 OP50 0x63
0x06E4: 51 SWAP
0x06E5: 11 63 LOAD 0x63
0x06E7: 51 SWAP
0x06E8: 12 63 STORE 0x63
0x06EA: 20 00 MOVI 0x00
0x06EC: 51 SWAP
0x06ED: 11 01 LOAD 0x01
0x06EF: 51 SWAP
0x06F0: 11 63 LOAD 0x63
0x06F2: 51 SWAP
0x06F3: 20 00 MOVI 0x00
0x06F5: 51 SWAP
0x06F6: 12 63 STORE 0x63
0x06F8: 11 02 LOAD 0x02
0x06FA: 50 63 OP50 0x63
0x06FC: 51 SWAP
0x06FD: 11 63 LOAD 0x63
0x06FF: 51 SWAP
0x0700: 11 18 LOAD 0x18
loc_0702:
0x0702: 35 19 XOR_MEM 0x19
0x0704: 11 1A LOAD 0x1A
0x0706: 40 00 CMPEQ 0x00
0x0708: 43 3F JNZ 0x3F ; -> loc_0747 (0x0747)
0x070A: 11 1A LOAD 0x1A
0x070C: 30 0A ADDI 0x0A
0x070E: 12 1A STORE 0x1A
0x0710: 11 19 LOAD 0x19
0x0712: 20 00 MOVI 0x00
0x0714: 51 SWAP
0x0715: 51 SWAP
0x0716: 12 63 STORE 0x63
0x0718: 20 00 MOVI 0x00
0x071A: 51 SWAP
0x071B: 12 02 STORE 0x02
0x071D: 11 63 LOAD 0x63
0x071F: 51 SWAP
0x0720: 20 00 MOVI 0x00
0x0722: 51 SWAP
0x0723: 12 63 STORE 0x63
0x0725: 11 01 LOAD 0x01
0x0727: 50 63 OP50 0x63
0x0729: 51 SWAP
0x072A: 11 63 LOAD 0x63
0x072C: 51 SWAP
0x072D: 12 63 STORE 0x63
0x072F: 20 00 MOVI 0x00
0x0731: 51 SWAP
0x0732: 11 01 LOAD 0x01
0x0734: 51 SWAP
0x0735: 11 63 LOAD 0x63
0x0737: 51 SWAP
0x0738: 20 00 MOVI 0x00
0x073A: 51 SWAP
0x073B: 12 63 STORE 0x63
0x073D: 11 02 LOAD 0x02
0x073F: 50 63 OP50 0x63
0x0741: 51 SWAP
0x0742: 11 63 LOAD 0x63
0x0744: 51 SWAP
0x0745: 11 19 LOAD 0x19
loc_0747:
0x0747: 35 1A XOR_MEM 0x1A
0x0749: 11 00 LOAD 0x00
0x074B: 40 00 CMPEQ 0x00
0x074D: 43 06 JNZ 0x06 ; -> loc_0753 (0x0753)
0x074F: 11 00 LOAD 0x00
0x0751: 30 10 ADDI 0x10
loc_0753:
0x0753: 12 00 STORE 0x00
0x0755: 11 02 LOAD 0x02
0x0757: 40 00 CMPEQ 0x00
0x0759: 43 06 JNZ 0x06 ; -> loc_075F (0x075F)
0x075B: 11 02 LOAD 0x02
0x075D: 30 10 ADDI 0x10
loc_075F:
0x075F: 12 02 STORE 0x02
0x0761: 11 04 LOAD 0x04
0x0763: 40 00 CMPEQ 0x00
0x0765: 43 06 JNZ 0x06 ; -> loc_076B (0x076B)
0x0767: 11 04 LOAD 0x04
0x0769: 30 10 ADDI 0x10
loc_076B:
0x076B: 12 04 STORE 0x04
0x076D: 11 06 LOAD 0x06
0x076F: 40 00 CMPEQ 0x00
0x0771: 43 06 JNZ 0x06 ; -> loc_0777 (0x0777)
0x0773: 11 06 LOAD 0x06
0x0775: 30 10 ADDI 0x10
loc_0777:
0x0777: 12 06 STORE 0x06
0x0779: 11 08 LOAD 0x08
0x077B: 40 00 CMPEQ 0x00
0x077D: 43 06 JNZ 0x06 ; -> loc_0783 (0x0783)
0x077F: 11 08 LOAD 0x08
0x0781: 30 10 ADDI 0x10
loc_0783:
0x0783: 12 08 STORE 0x08
0x0785: 11 0A LOAD 0x0A
0x0787: 40 00 CMPEQ 0x00
0x0789: 43 06 JNZ 0x06 ; -> loc_078F (0x078F)
0x078B: 11 0A LOAD 0x0A
0x078D: 30 10 ADDI 0x10
loc_078F:
0x078F: 12 0A STORE 0x0A
0x0791: 11 0C LOAD 0x0C
0x0793: 40 00 CMPEQ 0x00
0x0795: 43 06 JNZ 0x06 ; -> loc_079B (0x079B)
0x0797: 11 0C LOAD 0x0C
0x0799: 30 10 ADDI 0x10
loc_079B:
0x079B: 12 0C STORE 0x0C
0x079D: 11 0E LOAD 0x0E
0x079F: 40 00 CMPEQ 0x00
0x07A1: 43 06 JNZ 0x06 ; -> loc_07A7 (0x07A7)
0x07A3: 11 0E LOAD 0x0E
0x07A5: 30 10 ADDI 0x10
loc_07A7:
0x07A7: 12 0E STORE 0x0E
0x07A9: 11 10 LOAD 0x10
0x07AB: 40 00 CMPEQ 0x00
0x07AD: 43 06 JNZ 0x06 ; -> loc_07B3 (0x07B3)
0x07AF: 11 10 LOAD 0x10
0x07B1: 30 10 ADDI 0x10
loc_07B3:
0x07B3: 12 10 STORE 0x10
0x07B5: 11 12 LOAD 0x12
0x07B7: 40 00 CMPEQ 0x00
0x07B9: 43 06 JNZ 0x06 ; -> loc_07BF (0x07BF)
0x07BB: 11 12 LOAD 0x12
0x07BD: 30 10 ADDI 0x10
loc_07BF:
0x07BF: 12 12 STORE 0x12
0x07C1: 11 14 LOAD 0x14
0x07C3: 40 00 CMPEQ 0x00
0x07C5: 43 06 JNZ 0x06 ; -> loc_07CB (0x07CB)
0x07C7: 11 14 LOAD 0x14
0x07C9: 30 10 ADDI 0x10
loc_07CB:
0x07CB: 12 14 STORE 0x14
0x07CD: 11 16 LOAD 0x16
0x07CF: 40 00 CMPEQ 0x00
0x07D1: 43 06 JNZ 0x06 ; -> loc_07D7 (0x07D7)
0x07D3: 11 16 LOAD 0x16
0x07D5: 30 10 ADDI 0x10
loc_07D7:
0x07D7: 12 16 STORE 0x16
0x07D9: 11 18 LOAD 0x18
0x07DB: 40 00 CMPEQ 0x00
0x07DD: 43 06 JNZ 0x06 ; -> loc_07E3 (0x07E3)
0x07DF: 11 18 LOAD 0x18
0x07E1: 30 10 ADDI 0x10
loc_07E3:
0x07E3: 12 18 STORE 0x18
0x07E5: 11 1A LOAD 0x1A
0x07E7: 40 00 CMPEQ 0x00
0x07E9: 43 06 JNZ 0x06 ; -> loc_07EF (0x07EF)
0x07EB: 11 1A LOAD 0x1A
0x07ED: 30 10 ADDI 0x10
loc_07EF:
0x07EF: 12 1A STORE 0x1A
0x07F1: 11 01 LOAD 0x01
0x07F3: 40 00 CMPEQ 0x00
0x07F5: 43 06 JNZ 0x06 ; -> loc_07FB (0x07FB)
0x07F7: 11 01 LOAD 0x01
0x07F9: 30 31 ADDI 0x31
loc_07FB:
0x07FB: 12 01 STORE 0x01
0x07FD: 11 03 LOAD 0x03
0x07FF: 40 00 CMPEQ 0x00
0x0801: 43 06 JNZ 0x06 ; -> loc_0807 (0x0807)
0x0803: 11 03 LOAD 0x03
0x0805: 30 31 ADDI 0x31
loc_0807:
0x0807: 12 03 STORE 0x03
0x0809: 11 05 LOAD 0x05
0x080B: 40 00 CMPEQ 0x00
0x080D: 43 06 JNZ 0x06 ; -> loc_0813 (0x0813)
0x080F: 11 05 LOAD 0x05
0x0811: 30 31 ADDI 0x31
loc_0813:
0x0813: 12 05 STORE 0x05
0x0815: 11 07 LOAD 0x07
0x0817: 40 00 CMPEQ 0x00
0x0819: 43 06 JNZ 0x06 ; -> loc_081F (0x081F)
0x081B: 11 07 LOAD 0x07
0x081D: 30 31 ADDI 0x31
loc_081F:
0x081F: 12 07 STORE 0x07
0x0821: 11 09 LOAD 0x09
0x0823: 40 00 CMPEQ 0x00
0x0825: 43 06 JNZ 0x06 ; -> loc_082B (0x082B)
0x0827: 11 09 LOAD 0x09
0x0829: 30 31 ADDI 0x31
loc_082B:
0x082B: 12 09 STORE 0x09
0x082D: 11 0B LOAD 0x0B
0x082F: 40 00 CMPEQ 0x00
0x0831: 43 06 JNZ 0x06 ; -> loc_0837 (0x0837)
0x0833: 11 0B LOAD 0x0B
0x0835: 30 31 ADDI 0x31
loc_0837:
0x0837: 12 0B STORE 0x0B
0x0839: 11 0D LOAD 0x0D
0x083B: 40 00 CMPEQ 0x00
0x083D: 43 06 JNZ 0x06 ; -> loc_0843 (0x0843)
0x083F: 11 0D LOAD 0x0D
0x0841: 30 31 ADDI 0x31
loc_0843:
0x0843: 12 0D STORE 0x0D
0x0845: 11 0F LOAD 0x0F
0x0847: 40 00 CMPEQ 0x00
0x0849: 43 06 JNZ 0x06 ; -> loc_084F (0x084F)
0x084B: 11 0F LOAD 0x0F
0x084D: 30 31 ADDI 0x31
loc_084F:
0x084F: 12 0F STORE 0x0F
0x0851: 11 11 LOAD 0x11
0x0853: 40 00 CMPEQ 0x00
0x0855: 43 06 JNZ 0x06 ; -> loc_085B (0x085B)
0x0857: 11 11 LOAD 0x11
0x0859: 30 31 ADDI 0x31
loc_085B:
0x085B: 12 11 STORE 0x11
0x085D: 11 13 LOAD 0x13
0x085F: 40 00 CMPEQ 0x00
0x0861: 43 06 JNZ 0x06 ; -> loc_0867 (0x0867)
0x0863: 11 13 LOAD 0x13
0x0865: 30 31 ADDI 0x31
loc_0867:
0x0867: 12 13 STORE 0x13
0x0869: 11 15 LOAD 0x15
0x086B: 40 00 CMPEQ 0x00
0x086D: 43 06 JNZ 0x06 ; -> loc_0873 (0x0873)
0x086F: 11 15 LOAD 0x15
0x0871: 30 31 ADDI 0x31
loc_0873:
0x0873: 12 15 STORE 0x15
0x0875: 11 17 LOAD 0x17
0x0877: 40 00 CMPEQ 0x00
0x0879: 43 06 JNZ 0x06 ; -> loc_087F (0x087F)
0x087B: 11 17 LOAD 0x17
0x087D: 30 31 ADDI 0x31
loc_087F:
0x087F: 12 17 STORE 0x17
0x0881: 11 19 LOAD 0x19
0x0883: 40 00 CMPEQ 0x00
0x0885: 43 06 JNZ 0x06 ; -> loc_088B (0x088B)
0x0887: 11 19 LOAD 0x19
0x0889: 30 31 ADDI 0x31
loc_088B:
0x088B: 12 19 STORE 0x19
0x088D: 11 00 LOAD 0x00
0x088F: 40 60 CMPEQ 0x60
0x0891: 42 B2 JZ 0xB2 ; -> loc_0943 (0x0943)
0x0893: 11 01 LOAD 0x01
0x0895: 40 9B CMPEQ 0x9B
0x0897: 42 AC JZ 0xAC ; -> loc_0943 (0x0943)
0x0899: 11 02 LOAD 0x02
0x089B: 40 22 CMPEQ 0x22
0x089D: 42 A6 JZ 0xA6 ; -> loc_0943 (0x0943)
0x089F: 11 03 LOAD 0x03
0x08A1: 40 AC CMPEQ 0xAC
0x08A3: 42 A0 JZ 0xA0 ; -> loc_0943 (0x0943)
0x08A5: 11 04 LOAD 0x04
0x08A7: 40 40 CMPEQ 0x40
0x08A9: 42 9A JZ 0x9A ; -> loc_0943 (0x0943)
0x08AB: 11 05 LOAD 0x05
0x08AD: 40 79 CMPEQ 0x79
0x08AF: 42 94 JZ 0x94 ; -> loc_0943 (0x0943)
0x08B1: 11 06 LOAD 0x06
0x08B3: 40 36 CMPEQ 0x36
0x08B5: 42 8E JZ 0x8E ; -> loc_0943 (0x0943)
0x08B7: 11 07 LOAD 0x07
0x08B9: 40 80 CMPEQ 0x80
0x08BB: 42 88 JZ 0x88 ; -> loc_0943 (0x0943)
0x08BD: 11 08 LOAD 0x08
0x08BF: 40 82 CMPEQ 0x82
0x08C1: 42 82 JZ 0x82 ; -> loc_0943 (0x0943)
0x08C3: 11 09 LOAD 0x09
0x08C5: 40 4A CMPEQ 0x4A
0x08C7: 42 7C JZ 0x7C ; -> loc_0943 (0x0943)
0x08C9: 11 0A LOAD 0x0A
0x08CB: 40 74 CMPEQ 0x74
0x08CD: 42 76 JZ 0x76 ; -> loc_0943 (0x0943)
0x08CF: 11 0B LOAD 0x0B
0x08D1: 40 18 CMPEQ 0x18
0x08D3: 42 70 JZ 0x70 ; -> loc_0943 (0x0943)
0x08D5: 11 0C LOAD 0x0C
0x08D7: 40 97 CMPEQ 0x97
0x08D9: 42 6A JZ 0x6A ; -> loc_0943 (0x0943)
0x08DB: 11 0D LOAD 0x0D
0x08DD: 40 25 CMPEQ 0x25
0x08DF: 42 64 JZ 0x64 ; -> loc_0943 (0x0943)
0x08E1: 11 0E LOAD 0x0E
0x08E3: 40 B3 CMPEQ 0xB3
0x08E5: 42 5E JZ 0x5E ; -> loc_0943 (0x0943)
0x08E7: 11 0F LOAD 0x0F
0x08E9: 40 23 CMPEQ 0x23
0x08EB: 42 58 JZ 0x58 ; -> loc_0943 (0x0943)
0x08ED: 11 10 LOAD 0x10
0x08EF: 40 A9 CMPEQ 0xA9
0x08F1: 42 52 JZ 0x52 ; -> loc_0943 (0x0943)
0x08F3: 11 11 LOAD 0x11
0x08F5: 40 D3 CMPEQ 0xD3
0x08F7: 42 4C JZ 0x4C ; -> loc_0943 (0x0943)
0x08F9: 11 12 LOAD 0x12
0x08FB: 40 32 CMPEQ 0x32
0x08FD: 42 46 JZ 0x46 ; -> loc_0943 (0x0943)
0x08FF: 11 13 LOAD 0x13
0x0901: 40 4A CMPEQ 0x4A
0x0903: 42 40 JZ 0x40 ; -> loc_0943 (0x0943)
0x0905: 11 14 LOAD 0x14
0x0907: 40 86 CMPEQ 0x86
0x0909: 42 3A JZ 0x3A ; -> loc_0943 (0x0943)
0x090B: 11 15 LOAD 0x15
0x090D: 40 57 CMPEQ 0x57
0x090F: 42 34 JZ 0x34 ; -> loc_0943 (0x0943)
0x0911: 11 16 LOAD 0x16
0x0913: 40 75 CMPEQ 0x75
0x0915: 42 2E JZ 0x2E ; -> loc_0943 (0x0943)
0x0917: 11 17 LOAD 0x17
0x0919: 40 4A CMPEQ 0x4A
0x091B: 42 28 JZ 0x28 ; -> loc_0943 (0x0943)
0x091D: 11 18 LOAD 0x18
0x091F: 40 8A CMPEQ 0x8A
0x0921: 42 22 JZ 0x22 ; -> loc_0943 (0x0943)
0x0923: 11 19 LOAD 0x19
0x0925: 40 61 CMPEQ 0x61
0x0927: 42 1C JZ 0x1C ; -> loc_0943 (0x0943)
0x0929: 11 1A LOAD 0x1A
0x092B: 40 5F CMPEQ 0x5F
0x092D: 42 16 JZ 0x16 ; -> loc_0943 (0x0943)
0x092F: 10 04 ALLOC 0x04
0x0931: 20 79 MOVI 0x79
0x0933: 12 00 STORE 0x00
0x0935: 20 65 MOVI 0x65
0x0937: 12 01 STORE 0x01
0x0939: 20 73 MOVI 0x73
0x093B: 12 02 STORE 0x02
0x093D: 20 00 MOVI 0x00
0x093F: 12 03 STORE 0x03
0x0941: 52 02 SYSCALL 0x02 ; PRINT_STRING
loc_0943:
0x0943: 41 10 OP41 0x10
0x0945: 10 03 ALLOC 0x03
0x0947: 20 4E MOVI 0x4E
0x0949: 12 00 STORE 0x00
0x094B: 20 6F MOVI 0x6F
0x094D: 12 01 STORE 0x01
0x094F: 20 00 MOVI 0x00
0x0951: 12 02 STORE 0x02
0x0953: 52 02 SYSCALL 0x02 ; PRINT_STRING
0x0955: 13 FREE
可以看到它同样:
- 申请内存
- 读入输入
- 对前 27 个字符做多轮变换
- 与一组常量表比较
- 输出
yes 或 No
成功分支附近的比较表为:
60 9B 22 AC 40 79 36 80 82 4A 74 18 97 25 B3 23 A9 D3 32 4A 86 57 75 4A 8A 61 5F
[96, 155, 34, 172, 64, 121, 54, 128, 130, 74, 116, 24, 151, 37, 179, 35, 169, 211, 50, 74, 134, 87, 117, 74, 138, 97, 95]
每字节加 10
if s[i] != 0:
s[i] += 10
前缀异或
从下标 1 到 26:
s[i] ^= s[i - 1]
和“前一个已经处理后的字节”异或。
奇偶位分别加不同常数
偶数位:
if s[i] != 0:
s[i] += 0x10
奇数位:
if s[i] != 0:
s[i] += 0x31
最后拿结果和目标表逐字节比较。
exp
target = [
0x60, 0x9B, 0x22, 0xAC, 0x40, 0x79, 0x36, 0x80, 0x82,
0x4A, 0x74, 0x18, 0x97, 0x25, 0xB3, 0x23, 0xA9, 0xD3,
0x32, 0x4A, 0x86, 0x57, 0x75, 0x4A, 0x8A, 0x61, 0x5F
]
z = []
for i, c in enumerate(target):
if i % 2 == 0:
z.append((c - 0x10) & 0xFF)
else:
z.append((c - 0x31) & 0xFF)
y = [z[0]]
for i in range(1, len(z)):
y.append(z[i] ^ z[i - 1])
x = []
for v in y:
x.append((v - 10) & 0xFF)
plain = ''.join(chr(c) for c in x)
flag = f"xmctf{{{plain}}}"
print(plain)
print(flag)
输出:
F0n_And_3asyViMGa1v1eF9rY@u
xmctf{F0n_And_3asyViMGa1v1eF9rY@u}
flag
xmctf{F0n_And_3asyViMGa1v1eF9rY@u}
python .\disasm_funpyvm.py "F:\python-3.13.0-embed-amd64\main.exe_extracted\ntbase.pyd"
7.移动的秘密
[爆破]
分析main函数
输入长度限制为 29
scanf("%29s", input);
for ( i = 0; i != v4; ++i )
*((_BYTE *)v15 + i) = (unsigned __int8)s[i] >> 1;
v6 = 0;
*(__m128i *)v14 = _mm_load_si128((const __m128i *)&xmmword_3080);
*(__m128i *)&v14[13] = _mm_load_si128((const __m128i *)&xmmword_3090);
while ( v14[v6] == *((_BYTE *)v15 + v6) )
{
if ( v6 == (_DWORD)v4 - 1 )
goto LABEL_10;
++v6;
}
v3 = 0;
}
移位
for (i = 0; i < len; i++) {
tmp[i] = input[i] >> 1;
}
随后程序把 v15 和目标字节数组(v14)比较。
从 .rodata 中可以取出目标数组,实际参与比较的 29 个字节为: v14:
3c 36 31 3a 33 3d 3b 32 36 31 18 36 32 2f 19 2f 38 37 36 30 39 18 39 2f 18 18 19 19 3e
input[i] >> 1 == target[i]
每一位输入字符都只有 两个可能值:
2 * target[i]
2 * target[i] + 1
再结合 flag 常用字符集 [a-z0-9_{}]
| 位置 |
目标字节 |
候选字符 |
| 0 |
0x3c |
x / y |
| 1 |
0x36 |
l / m |
| 2 |
0x31 |
b / c |
| 3 |
0x3a |
t / u |
| 4 |
0x33 |
f / g |
| 5 |
0x3d |
z / { |
| 6 |
0x3b |
v / w |
| 7 |
0x32 |
d / e |
| 8 |
0x36 |
l / m |
| 9 |
0x31 |
b / c |
| 10 |
0x18 |
0 / 1 |
| 11 |
0x36 |
l / m |
| 12 |
0x32 |
d / e |
| 13 |
0x2f |
_ |
| 14 |
0x19 |
2 / 3 |
| 15 |
0x2f |
_ |
| 16 |
0x38 |
p / q |
| 17 |
0x37 |
n / o |
| 18 |
0x36 |
l / m |
| 19 |
0x30 |
a |
| 20 |
0x39 |
r / s |
| 21 |
0x18 |
0 / 1 |
| 22 |
0x39 |
r / s |
| 23 |
0x2f |
_ |
| 24 |
0x18 |
0 / 1 |
| 25 |
0x18 |
0 / 1 |
| 26 |
0x19 |
2 / 3 |
| 27 |
0x19 |
2 / 3 |
| 28 |
0x3e |
} |
前 6 位一眼就能恢复成:
xmctf{
这已经非常接近真实 flag 了。
MD5
主函数后半段会对 原始输入 做 MD5。
3a22c098710019b31c328a861429d3ad
因此最终需要满足两个条件:
input[i] >> 1 == target[i]
md5(input) == 3a22c098710019b31c328a861429d3ad
exp
笛卡尔积爆破
import hashlib
import itertools
md5_target = bytes.fromhex("3a22c098710019b31c328a861429d3ad")
cand = [
['x', 'y'],
['l', 'm'],
['b', 'c'],
['t', 'u'],
['f', 'g'],
['z', '{'],
['v', 'w'],
['d', 'e'],
['l', 'm'],
['b', 'c'],
['0', '1'],
['l', 'm'],
['d', 'e'],
['_'],
['2', '3'],
['_'],
['p', 'q'],
['n', 'o'],
['l', 'm'],
['a'],
['r', 's'],
['0', '1'],
['r', 's'],
['_'],
['0', '1'],
['0', '1'],
['2', '3'],
['2', '3'],
['}'],
]
prefix = "xmctf{" # 由前几位直接恢复
rest = cand[6:]
for tup in itertools.product(*rest):
s = prefix + ''.join(tup)
if hashlib.md5(s.encode()).digest() == md5_target:
print(s)
break
flag
xmctf{welc0me_2_polar1s_1022}
8.BankGuardian
[混淆]
分析main函数
有些奇怪 ,没有验证flag的逻辑
int __fastcall main(int argc, const char **argv, const char **envp)
{
_OWORD *v3; // rax
unsigned __int64 n0x2B; // rdx
__int128 *v5; // rax
const char *v6; // rdx
const char *v7; // rdx
const char *v8; // rdx
HMODULE ModuleHandleA; // rbx
__int64 _______kernel32.dll___; // rdi
__int64 v11; // r15
__int64 v12; // rsi
void (__fastcall *v13)(__int64, const char *, __int64, int *, _QWORD); // r13
__int64 v14; // r14
void (__fastcall *v15)(__int64); // r12
void *v16; // rax
_BYTE *v17; // rbx
char *v18; // rdi
_BYTE *v19; // rsi
int v20; // r14d
__int64 v21; // rax
const char *v22; // rdx
const char *v23; // rax
__int64 (__fastcall *v24)(wchar_t *, __int64, _QWORD, _QWORD, int, int, _QWORD); // rbx
__int64 v25; // rax
__int64 v26; // r15
__m128i v27; // xmm6
__m128i *p_si128_1; // rdx
void *v29; // rcx
int v30; // ebx
const char *v31; // rax
__int64 v32; // rax
__int64 v33; // rbx
void (__fastcall *v34)(wchar_t *); // rbx
const char *v35; // rdx
void *v36; // rcx
void *dotne__1; // rcx
void *v38; // rcx
__m128i *p_si128_2; // rdx
void *v40; // rcx
char *v41; // rax
__m128i *p_si128; // rdx
void *v43; // rcx
void *v44; // rcx
void *v45; // rcx
void *v46; // rcx
void *v47; // rcx
void *v48; // rcx
__m128i v50; // [rsp+50h] [rbp-B0h] BYREF
_QWORD __d__.$_.e_[2]; // [rsp+60h] [rbp-A0h] BYREF
__m128i si128; // [rsp+70h] [rbp-90h] BYREF
__m128i v53; // [rsp+80h] [rbp-80h]
_QWORD d[2]; // [rsp+90h] [rbp-70h] BYREF
__m128i v55; // [rsp+A0h] [rbp-60h] BYREF
int n1249817; // [rsp+B0h] [rbp-50h]
int n1325943113; // [rsp+B4h] [rbp-4Ch]
int n1212170827; // [rsp+B8h] [rbp-48h]
__int16 n330; // [rsp+BCh] [rbp-44h]
char v60; // [rsp+BEh] [rbp-42h]
__int128 dotne; // [rsp+C0h] [rbp-40h] BYREF
__int64 n6; // [rsp+D0h] [rbp-30h]
unsigned __int64 n15; // [rsp+D8h] [rbp-28h]
__int128 v64; // [rsp+F0h] [rbp-10h] BYREF
__int64 n43; // [rsp+100h] [rbp+0h]
unsigned __int64 n47; // [rsp+108h] [rbp+8h]
unsigned int (__fastcall *v67)(_QWORD, wchar_t *, _QWORD, _QWORD, _DWORD, int, _QWORD, _QWORD, int *, __int128 *); // [rsp+110h] [rbp+10h]
void (__fastcall *v68)(_QWORD, __int64); // [rsp+118h] [rbp+18h]
void (__fastcall *v69)(wchar_t *); // [rsp+120h] [rbp+20h]
__int128 v70; // [rsp+128h] [rbp+28h] BYREF
__int64 v71; // [rsp+138h] [rbp+38h]
_QWORD v72[2]; // [rsp+140h] [rbp+40h] BYREF
__m128i v73; // [rsp+150h] [rbp+50h]
_QWORD v74[2]; // [rsp+160h] [rbp+60h] BYREF
__m128i v75; // [rsp+170h] [rbp+70h]
_QWORD v76[2]; // [rsp+180h] [rbp+80h] BYREF
__m128i v77; // [rsp+190h] [rbp+90h]
_QWORD v78[2]; // [rsp+1A0h] [rbp+A0h] BYREF
__m128i v79; // [rsp+1B0h] [rbp+B0h]
_QWORD v80[2]; // [rsp+1C0h] [rbp+C0h] BYREF
__m128i v81; // [rsp+1D0h] [rbp+D0h]
_QWORD v82[3]; // [rsp+1E0h] [rbp+E0h] BYREF
unsigned __int64 n0xF; // [rsp+1F8h] [rbp+F8h]
int n104; // [rsp+200h] [rbp+100h] BYREF
__int128 v85; // [rsp+208h] [rbp+108h]
__int128 v86; // [rsp+218h] [rbp+118h]
__int128 v87; // [rsp+228h] [rbp+128h]
__int128 v88; // [rsp+238h] [rbp+138h]
__int128 v89; // [rsp+248h] [rbp+148h]
__int128 v90; // [rsp+258h] [rbp+158h]
wchar_t Buffer[136]; // [rsp+270h] [rbp+170h] BYREF
char v92[272]; // [rsp+380h] [rbp+280h] BYREF
wchar_t Buffer_[136]; // [rsp+490h] [rbp+390h] BYREF
wchar_t Buffer__1[264]; // [rsp+5A0h] [rbp+4A0h] BYREF
int v95; // [rsp+810h] [rbp+710h] BYREF
__int64 v96; // [rsp+818h] [rbp+718h] BYREF
void (__fastcall *v97)(__int64, char *); // [rsp+820h] [rbp+720h]
__int64 (__fastcall *v98)(wchar_t *, __int64, _QWORD, _QWORD, int, int, _QWORD); // [rsp+828h] [rbp+728h]
si128 = _mm_load_si128((const __m128i *)&xmmword_140032B00);
v53 = _mm_load_si128((const __m128i *)&xmmword_140032B20);
LOWORD(d[0]) = 197;
sub_140003670(&si128, v82, envp);
si128 = _mm_load_si128((const __m128i *)&xmmword_140032AD0);
v53 = _mm_load_si128((const __m128i *)&xmmword_140032A40);
d[0] = 0x7F7E613D39222ELL;
sub_1400034B0(&si128, v80);
si128 = _mm_load_si128((const __m128i *)&xmmword_140032A70);
v53 = _mm_load_si128((const __m128i *)&xmmword_140032AE0);
LOWORD(d[0]) = 100;
sub_140003320(&si128, v78);
v50 = _mm_load_si128((const __m128i *)&xmmword_140032AC0);
__d__.$_.e_[0] = 0x214F1E444852484CLL;
strcpy((char *)&__d__.$_.e_[1], "5!+jkh");
sub_140003130(&v50, v76);
si128 = _mm_load_si128((const __m128i *)&xmmword_140032AB0);
v53 = _mm_load_si128((const __m128i *)&xmmword_140032A60);
strcpy((char *)d, "+*/8?+;#<(|");
v64 = 0;
n43 = 0;
n47 = 0;
v3 = operator new(0x30u);
*(_QWORD *)&v64 = v3;
n43 = 43;
n47 = 47;
*v3 = 0;
v3[1] = 0;
*((_QWORD *)v3 + 4) = 0;
*((_DWORD *)v3 + 10) = 0;
for ( n0x2B = 0; n0x2B < 0x2B; ++n0x2B )
{
v5 = &v64;
if ( n47 > 0xF )
v5 = (__int128 *)v64;
*((_BYTE *)v5 + n0x2B) = si128.m128i_i8[n0x2B] ^ (n0x2B + 40);
}
v6 = (const char *)v82;
if ( n0xF > 0xF )
v6 = (const char *)v82[0];
sub_140001010("%s\n\n", v6);
v7 = (const char *)v80;
if ( v81.m128i_i64[1] > 0xFuLL )
v7 = (const char *)v80[0];
sub_140001010("%s\n", v7);
v8 = (const char *)v78;
if ( v79.m128i_i64[1] > 0xFuLL )
v8 = (const char *)v78[0];
sub_140001010("%s\n", v8);
ModuleHandleA = GetModuleHandleA("kernel32.dll");
_______kernel32.dll___ = sub_140001440((__int64)ModuleHandleA, -818824417);// 加载了大量的 kernel32.dll 函数
v11 = sub_140001440((__int64)ModuleHandleA, 1606414587);
v96 = sub_140001440((__int64)ModuleHandleA, 942411671);
v12 = sub_140001440((__int64)ModuleHandleA, -342440454);
v98 = (__int64 (__fastcall *)(wchar_t *, __int64, _QWORD, _QWORD, int, int, _QWORD))v12;
v13 = (void (__fastcall *)(__int64, const char *, __int64, int *, _QWORD))sub_140001440(
(__int64)ModuleHandleA,
1715268784);
v14 = sub_140001440((__int64)ModuleHandleA, -1363857895);
v67 = (unsigned int (__fastcall *)(_QWORD, wchar_t *, _QWORD, _QWORD, _DWORD, int, _QWORD, _QWORD, int *, __int128 *))v14;
v68 = (void (__fastcall *)(_QWORD, __int64))sub_140001440((__int64)ModuleHandleA, -322068038);
v15 = (void (__fastcall *)(__int64))sub_140001440((__int64)ModuleHandleA, 946915847);
v97 = (void (__fastcall *)(__int64, char *))sub_140001440((__int64)ModuleHandleA, -1627817495);
v69 = (void (__fastcall *)(wchar_t *))sub_140001440((__int64)ModuleHandleA, 483952409);
if ( !_______kernel32.dll___ || !v11 || !v96 || !v12 || !v13 || !v14 )
{
v50 = _mm_load_si128((const __m128i *)&xmmword_140032A50);
strcpy((char *)__d__.$_.e_, "--d#'.$,.e");
sub_140002DB0(&v50, &si128);
p_si128 = &si128;
if ( v53.m128i_i64[1] > 0xFuLL )
p_si128 = (__m128i *)si128.m128i_i64[0];
sub_140001010("%s\n", p_si128->m128i_i8);
if ( v53.m128i_i64[1] > 0xFuLL )
{
v43 = (void *)si128.m128i_i64[0];
if ( (unsigned __int64)(v53.m128i_i64[1] + 1) >= 0x1000 )
{
v43 = *(void **)(si128.m128i_i64[0] - 8);
if ( (unsigned __int64)(si128.m128i_i64[0] - (_QWORD)v43 - 8) > 0x1F )
invalid_parameter_noinfo_noreturn();
}
j_j_free(v43);
}
v30 = 1;
goto LABEL_72;
}
v50 = 0;
__d__.$_.e_[0] = 0;
v16 = operator new(0xE427u);
if ( !v16 )
LABEL_100:
invalid_parameter_noinfo_noreturn();
v17 = (_BYTE *)(((unsigned __int64)v16 + 39) & 0xFFFFFFFFFFFFFFE0uLL);
*((_QWORD *)v17 - 1) = v16;
v18 = v17;
v50.m128i_i64[0] = (__int64)v17;
v19 = v17 + 58368;
__d__.$_.e_[0] = v17 + 58368;
memcpy(v17, &unk_140024628, 0xE400u);
v20 = (_DWORD)v17 + 58368;
v50.m128i_i64[1] = (__int64)(v17 + 58368);
v21 = sub_140001260(&dotne, 2356100023LL);
si128 = *(__m128i *)v21;
v53 = *(__m128i *)(v21 + 16);
d[0] = *(_QWORD *)(v21 + 32);
LODWORD(d[1]) = *(_DWORD *)(v21 + 40);
sub_140003DE0((__int64)&si128, (__int64)d, (__int64)v17, 58368u);// 解密
if ( *v17 == 'M' && v17[1] == 'Z' ) // 看是不是MZ头
{
v22 = (const char *)v76;
if ( v77.m128i_i64[1] > 0xFuLL )
v22 = (const char *)v76[0];
sub_140001010("%s\n", v22);
memset(v92, 0, 0x104u);
memset(Buffer, 0, 0x104u);
v97(260, v92);
v55 = _mm_load_si128((const __m128i *)&xmmword_140032A90);
n1249817 = 1249817;
sub_1400028C0(&v55, v74);
dotne = 0;
n6 = 6;
n15 = 15;
strcpy((char *)&dotne, "dotne");
*(_WORD *)((char *)&dotne + 5) = 116;
v23 = (const char *)v74;
if ( v75.m128i_i64[1] > 0xFuLL )
v23 = (const char *)v74[0];
swprintf(Buffer, 0x104u, "%s%s", v92, v23);
v95 = 0;
v24 = v98;
v25 = v98(Buffer, 0x40000000, 0, 0, 2, 384, 0);
v26 = v25;
v27 = _mm_load_si128((const __m128i *)&xmmword_140032A30);
if ( v25 == -1 )
{
v55 = _mm_load_si128((const __m128i *)&xmmword_140032B40);
n1249817 = -185730134;
n1325943113 = -168697630;
LOWORD(n1212170827) = 188;
sub_140002570(&v55, &si128);
p_si128_1 = &si128;
if ( v53.m128i_i64[1] > 0xFuLL )
p_si128_1 = (__m128i *)si128.m128i_i64[0];
sub_140001010("%s\n", p_si128_1->m128i_i8);
if ( v53.m128i_i64[1] > 0xFuLL )
{
v29 = (void *)si128.m128i_i64[0];
if ( (unsigned __int64)(v53.m128i_i64[1] + 1) >= 0x1000 )
{
v29 = *(void **)(si128.m128i_i64[0] - 8);
if ( (unsigned __int64)(si128.m128i_i64[0] - (_QWORD)v29 - 8) > 0x1F )
invalid_parameter_noinfo_noreturn();
}
j_j_free(v29);
}
v30 = 1;
}
else
{
v13(v25, v18, (unsigned int)(v20 - (_DWORD)v18), &v95, 0);
v15(v26);
memset(Buffer_, 0, 0x104u);
si128 = _mm_load_si128((const __m128i *)&xmmword_140032B10);
v53 = _mm_load_si128((const __m128i *)&xmmword_140032B30);
LOWORD(d[0]) = -2312;
BYTE2(d[0]) = 0;
sub_140002350(&si128, v72);
v31 = (const char *)v72;
if ( v73.m128i_i64[1] > 0xFuLL )
v31 = (const char *)v72[0];
swprintf(Buffer_, 0x104u, "%s%s", v92, v31);
v32 = v24(Buffer_, 0x40000000, 0, 0, 2, 384, 0);
v33 = v32;
if ( v32 != -1 )
{
LODWORD(v96) = 0;
v13(
v32,
"{\"runtimeOptions\":{\"tfm\":\"net6.0\",\"framework\":{\"name\":\"Microsoft.NETCore.App\",\"version\":\"6.0.0\"}}}", // 配合 .runtimeconfig.json 用 dotnet 启动
98,
(int *)&v96,
0);
v15(v33);
}
memset(Buffer__1, 0, 0x208u);
swprintf(Buffer__1, 0x208u, "dotnet \"%s\"", (const char *)Buffer);// 通过 CreateProcess 启动该进程
n104 = 104;
v85 = 0;
v86 = 0;
v87 = 0;
v88 = 0;
v89 = 0;
v90 = 0;
v70 = 0;
v71 = 0;
DWORD1(v88) = 1;
WORD4(v88) = 0;
if ( v67(0, Buffer__1, 0, 0, 0, 0x8000000, 0, 0, &n104, &v70) )
{
v68(v70, 0xFFFFFFFFLL); // 销毁证据
v15(v70);
v15(*((_QWORD *)&v70 + 1));
}
v34 = v69;
v69(Buffer);
v34(Buffer_);
v35 = (const char *)&v64;
if ( n47 > 0xF )
v35 = (const char *)v64;
sub_140001010("%s\n", v35);
v30 = 0;
if ( v73.m128i_i64[1] > 0xFuLL )
{
v36 = (void *)v72[0];
if ( (unsigned __int64)(v73.m128i_i64[1] + 1) >= 0x1000 )
{
v36 = *(void **)(v72[0] - 8LL);
if ( (unsigned __int64)(v72[0] - (_QWORD)v36 - 8LL) > 0x1F )
invalid_parameter_noinfo_noreturn();
}
j_j_free(v36);
}
v73 = v27;
LOBYTE(v72[0]) = 0;
}
if ( n15 > 0xF )
{
dotne__1 = (void *)dotne;
if ( n15 + 1 >= 0x1000 )
{
dotne__1 = *(void **)(dotne - 8);
if ( (unsigned __int64)(dotne - (_QWORD)dotne__1 - 8) > 0x1F )
invalid_parameter_noinfo_noreturn();
}
j_j_free(dotne__1);
v27 = _mm_load_si128((const __m128i *)&xmmword_140032A30);
}
if ( v75.m128i_i64[1] > 0xFuLL )
{
v38 = (void *)v74[0];
if ( (unsigned __int64)(v75.m128i_i64[1] + 1) >= 0x1000 )
{
v38 = *(void **)(v74[0] - 8LL);
if ( (unsigned __int64)(v74[0] - (_QWORD)v38 - 8LL) > 0x1F )
invalid_parameter_noinfo_noreturn();
}
j_j_free(v38);
v27 = _mm_load_si128((const __m128i *)&xmmword_140032A30);
}
v75 = v27;
LOBYTE(v74[0]) = 0;
}
else
{
v55 = _mm_load_si128((const __m128i *)&xmmword_140032AA0);
n1249817 = 1280332635;
n1325943113 = 1325943113;
n1212170827 = 1212170827;
n330 = 330;
v60 = 0;
sub_140002B60(&v55, &si128);
p_si128_2 = &si128;
if ( v53.m128i_i64[1] > 0xFuLL )
p_si128_2 = (__m128i *)si128.m128i_i64[0];
sub_140001010("%s\n", p_si128_2->m128i_i8);
if ( v53.m128i_i64[1] > 0xFuLL )
{
v40 = (void *)si128.m128i_i64[0];
if ( (unsigned __int64)(v53.m128i_i64[1] + 1) >= 0x1000 )
{
v40 = *(void **)(si128.m128i_i64[0] - 8);
if ( (unsigned __int64)(si128.m128i_i64[0] - (_QWORD)v40 - 8) > 0x1F )
invalid_parameter_noinfo_noreturn();
}
j_j_free(v40);
}
v30 = 1;
v27 = _mm_load_si128((const __m128i *)&xmmword_140032A30);
}
if ( v18 )
{
v41 = v18;
if ( (unsigned __int64)(v19 - v18) < 0x1000
|| (v18 = (char *)*((_QWORD *)v18 - 1), (unsigned __int64)(v41 - v18 - 8) <= 0x1F) )
{
j_j_free(v18);
LABEL_72:
v27 = _mm_load_si128((const __m128i *)&xmmword_140032A30);
goto LABEL_73;
}
goto LABEL_100;
}
LABEL_73:
if ( n47 > 0xF )
{
v44 = (void *)v64;
if ( n47 + 1 >= 0x1000 )
{
v44 = *(void **)(v64 - 8);
if ( (unsigned __int64)(v64 - (_QWORD)v44 - 8) > 0x1F )
invalid_parameter_noinfo_noreturn();
}
j_j_free(v44);
v27 = _mm_load_si128((const __m128i *)&xmmword_140032A30);
}
n43 = 0;
n47 = 15;
LOBYTE(v64) = 0;
if ( v77.m128i_i64[1] > 0xFuLL )
{
v45 = (void *)v76[0];
if ( (unsigned __int64)(v77.m128i_i64[1] + 1) >= 0x1000 )
{
v45 = *(void **)(v76[0] - 8LL);
if ( (unsigned __int64)(v76[0] - (_QWORD)v45 - 8LL) > 0x1F )
invalid_parameter_noinfo_noreturn();
}
j_j_free(v45);
v27 = _mm_load_si128((const __m128i *)&xmmword_140032A30);
}
v77 = v27;
LOBYTE(v76[0]) = 0;
if ( v79.m128i_i64[1] > 0xFuLL )
{
v46 = (void *)v78[0];
if ( (unsigned __int64)(v79.m128i_i64[1] + 1) >= 0x1000 )
{
v46 = *(void **)(v78[0] - 8LL);
if ( (unsigned __int64)(v78[0] - (_QWORD)v46 - 8LL) > 0x1F )
invalid_parameter_noinfo_noreturn();
}
j_j_free(v46);
v27 = _mm_load_si128((const __m128i *)&xmmword_140032A30);
}
v79 = v27;
LOBYTE(v78[0]) = 0;
if ( v81.m128i_i64[1] > 0xFuLL )
{
v47 = (void *)v80[0];
if ( (unsigned __int64)(v81.m128i_i64[1] + 1) >= 0x1000 )
{
v47 = *(void **)(v80[0] - 8LL);
if ( (unsigned __int64)(v80[0] - (_QWORD)v47 - 8LL) > 0x1F )
invalid_parameter_noinfo_noreturn();
}
j_j_free(v47);
v27 = _mm_load_si128((const __m128i *)&xmmword_140032A30);
}
v81 = v27;
LOBYTE(v80[0]) = 0;
if ( n0xF > 0xF )
{
v48 = (void *)v82[0];
if ( n0xF + 1 >= 0x1000 )
{
v48 = *(void **)(v82[0] - 8LL);
if ( (unsigned __int64)(v82[0] - (_QWORD)v48 - 8LL) > 0x1F )
invalid_parameter_noinfo_noreturn();
}
j_j_free(v48);
}
return v30;
}
-
动态解析 kernel32.dll 导出函数
-
从程序内部复制一段大块数据到新申请的内存
-
调用自定义解密函数对这段数据解密
-
如果解密结果开头是 MZ,则把它写成一个临时 PE 文件
-
再配合 .runtimeconfig.json 用 dotnet 启动它
这说明原始 EXE 只是一个 加载器(loader)
定位二阶段密文
程序本体里藏着一个加密的 PE 文件。
还原解密算法
-
流解密函数
函数:
sub_140003DE0
继续跟进去会发现其核心调用了:
sub_140003860
该函数内部出现了 ChaCha20 的典型常量:
"expand 32-byte k"
并且整体结构就是标准的 ChaCha20 block function
二阶段使用的是 ChaCha20 加密(流密码)
-
key / nonce 生成
另一个关键函数:
sub_140001260
它接收一个固定种子,生成 44 字节数据:
前 32 字节:ChaCha20 key
后 12 字节:ChaCha20 nonce
种子值在主流程里写死为:
2356100023
这个函数本质上不是复杂加密,而是经典 LCG:
x = (1103515245 * x + 12345) & 0xffffffff
连续生成 11 个 uint32,按小端拼起来,刚好 44 字节。
-
从原始 EXE 中取出偏移对应的 0xE400 字节密文
-
用种子 2356100023 跑 LCG,得到 44 字节材料
-
拆成:
key = 前 32 字节
nonce = 后 12 字节
-
使用 ChaCha20 解密
解密结果开头是:
4D 5A
也就是标准 PE 头 MZ,说明解密成功。
得到的文件是一个 .NET 程序
from __future__ import annotations
from pathlib import Path
import struct
import sys
SEED = 2356100023
CIPH_LEN = 0xE400
def u32(x: int) -> int:
return x & 0xFFFFFFFF
def rotl32(x: int, n: int) -> int:
x &= 0xFFFFFFFF
return ((x << n) | (x >> (32 - n))) & 0xFFFFFFFF
def quarter_round(state: list[int], a: int, b: int, c: int, d: int) -> None:
state[a] = u32(state[a] + state[b]); state[d] ^= state[a]; state[d] = rotl32(state[d], 16)
state[c] = u32(state[c] + state[d]); state[b] ^= state[c]; state[b] = rotl32(state[b], 12)
state[a] = u32(state[a] + state[b]); state[d] ^= state[a]; state[d] = rotl32(state[d], 8)
state[c] = u32(state[c] + state[d]); state[b] ^= state[c]; state[b] = rotl32(state[b], 7)
def chacha20_block(key: bytes, counter: int, nonce: bytes) -> bytes:
if len(key) != 32:
raise ValueError("key must be 32 bytes")
if len(nonce) != 12:
raise ValueError("nonce must be 12 bytes")
constants = b"expand 32-byte k"
state = list(struct.unpack("<4I", constants))
state += list(struct.unpack("<8I", key))
state += [counter]
state += list(struct.unpack("<3I", nonce))
working = state.copy()
for _ in range(10):
# column rounds
quarter_round(working, 0, 4, 8, 12)
quarter_round(working, 1, 5, 9, 13)
quarter_round(working, 2, 6, 10, 14)
quarter_round(working, 3, 7, 11, 15)
# diagonal rounds
quarter_round(working, 0, 5, 10, 15)
quarter_round(working, 1, 6, 11, 12)
quarter_round(working, 2, 7, 8, 13)
quarter_round(working, 3, 4, 9, 14)
out = [(working[i] + state[i]) & 0xFFFFFFFF for i in range(16)]
return struct.pack("<16I", *out)
def chacha20_xor(key: bytes, nonce: bytes, data: bytes, counter: int = 0) -> bytes:
out = bytearray(len(data))
block_counter = counter
off = 0
while off < len(data):
ks = chacha20_block(key, block_counter, nonce)
chunk = data[off:off + 64]
for i, b in enumerate(chunk):
out[off + i] = b ^ ks[i]
off += len(chunk)
block_counter = (block_counter + 1) & 0xFFFFFFFF
return bytes(out)
def lcg_material(seed: int) -> bytes:
"""
x = (1103515245 * x + 12345) & 0xffffffff
连续生成 11 个 uint32,小端拼接,共 44 字节。
"""
x = seed & 0xFFFFFFFF
out = bytearray()
for _ in range(11):
x = (1103515245 * x + 12345) & 0xFFFFFFFF
out += struct.pack("<I", x)
return bytes(out)
def find_stage2_cipher(blob: bytes) -> tuple[int, bytes]:
"""
最稳妥的做法:扫描整个文件,找长度为 0xE400、用当前 key/nonce 解密后开头是 MZ 的密文块。
"""
material = lcg_material(SEED)
key = material[:32]
nonce = material[32:44]
for off in range(0, len(blob) - CIPH_LEN + 1):
enc = blob[off:off + CIPH_LEN]
dec0 = chacha20_xor(key, nonce, enc[:2], counter=0)
if dec0 == b"MZ":
dec = chacha20_xor(key, nonce, enc, counter=0)
if dec.startswith(b"MZ"):
return off, dec
raise RuntimeError("stage2 ciphertext not found")
def main() -> None:
if len(sys.argv) < 2:
print(f"usage: {Path(sys.argv[0]).name} <BankGuardian.exe> [out_file]")
sys.exit(1)
in_file = Path(sys.argv[1])
out_file = Path(sys.argv[2]) if len(sys.argv) > 2 else Path("BankGuardian_stage2.bin")
blob = in_file.read_bytes()
off, stage2 = find_stage2_cipher(blob)
print(f"[+] found encrypted stage2 at file offset: 0x{off:x}")
print(f"[+] decrypted size: 0x{len(stage2):x}")
print(f"[+] magic: {stage2[:2]!r}")
if not stage2.startswith(b"MZ"):
raise RuntimeError("decryption failed: output does not start with MZ")
out_file.write_bytes(stage2)
print(f"[+] wrote: {out_file}")
if __name__ == "__main__":
main()
分析 .NET 程序
_oGzm5MCMGO4Vr3Mm5lKqB6mpDnh类
fakeflag

(第一个是flag的第一段)
主要分析类:
GetPart2(Byte ssnKey)
GetPart3(Byte ssnKey)
GenerateFakePart2(Byte ssn)
GenerateFakePart3(Byte ssn)
_VsBYyyEZbFsjoKNf29WydChhMSx()
_iHA7PNsCoMT9JnMfoXOi8TMf4FR()
RealSecret2(Byte ssnKey)
RealSecret3(Byte ssnKey)

确定真实 key:ssnKey = 24
程序中另一个关键类是:
_HliCxnAHMpcOIVFoPK7TQ30OytK
重点方法:
-
取 key
_xJypzuxNIztgSkEI6XlC16OGkt()
返回字段值:
return _7dT2CrJPXrw1mUhOZkQuluDH3MG;
-
初始化 key
_rby7d6XCL975Wxjg6BqgOfYv9AI()
在异常/fallback 分支中直接调用:
_hazxmXO3hzjco48LJXLfYORR4TE(24);
也就是说程序最终会把 ssnKey 设为:
24
0x18
RealSecret2
RealSecret2(Byte ssnKey) 的核心逻辑是:
1. 读取程序集内嵌资源
2. 取出字节数组
3. 将每个字节与 ssnKey 异或
4. 拼成字符串返回
资源名是:
MalwareStage2.Resources.Config.bin
5C 28 6C 56 2B 6C 47
使用 ssnKey = 24 (0x18) 逐字节异或:
5C ^ 18 = 44 -> D
28 ^ 18 = 30 -> 0
6C ^ 18 = 74 -> t
56 ^ 18 = 4E -> N
2B ^ 18 = 33 -> 3
6C ^ 18 = 74 -> t
47 ^ 18 = 5F -> _
得到字符串:
D0tN3t_
RealSecret3
1nj3ct10n_
按字节做 xor ssnKey 后转字符串。
_VsBYyyEZbFsjoKNf29WydChhMSx()
返回:
Pwn3r}
九、拼接最终 flag
将所有真实片段拼接:
xmctf{R3fl3ct1v3
D0tN3t
1nj3ct10n_
Pwn3r}
得到最终 flag:
xmctf{R3fl3ct1v3_D0tN3t_1nj3ct10n_Pwn3r}
原始 BankGuardian.exe 不直接校验 flag,而是:
内置一段被 ChaCha20 加密的二阶段 PE
用固定种子通过 LCG 生成 key/nonce
解出真正的 .NET 程序并运行
9.ezLanguage
ida 看不到伪代码 用 Ghidra 看看
重新dump一下看看
好像还有花指令
种类
1. stc ; jb loc+1
作用:先置 CF=1,再用 jb 无条件跳转到 loc+1。
例子:
- 0x4012DB -> 0x4012DF
- 0x4013CA -> 0x4013CE
- 0x4014DF -> 0x4014E3
- 0x40159C -> 0x4015A0
2. clc ; jnb loc+1
作用:先清 CF=0,再用 jnb 无条件跳转到 loc+1。
例子:
- 0x401284 -> 0x401288
- 0x40129B -> 0x40129E
- 0x40134C -> 0x401350
- 0x401411 -> 0x401415
- 0x401494 -> 0x401498
- 0x401606 -> 0x401609
3. jmp short loc+1
作用:直接跳到标签后 1 字节,故意让 IDA 从错误边界开始反汇编。
例子:
- 0x40125B -> 0x40125E
- 0x4012BD -> 0x4012C0
- 0x40136E -> 0x401371
- 0x401482 -> 0x401485
- 0x4014FD -> 0x401500
- 0x401577 -> 0x40157A
4. call $+5 ; add [esp], 6 ; retn
作用:伪装成调用,实际等价于“跳到当前地址 + 0xB”。
例子:
- 0x401324 实际跳到 0x40132F
- 0x401384 实际跳到 0x40138F
- 0x401546 实际跳到 0x401551
为了快速处理 我让gpt给我生成了
ida python

易语言
在样本分析过程中,可以看到与 易语言运行库 风格接近的特征,尤其是出现了:
系统核心支持库
print("Input the flag:");
read(input);
encode(input);
if (result == target)
print("Right");
else
...
可逆的自定义映射
结合提取出的信息,可以把程序的核心数据抽象成:
alphabet = "?4&ne9h1<y2*$oics-75wk3a0z@6jv8>+bx"
cipher = "?<b<@<72-*8oz*6o-o7co-s73515yk5553<w&znz9640bj&j28++8xh44"
params = (35, 17, 1)
其中 alphabet 的首字符很可能需要补上 ?,从而构成一个完整的 35 字符表。
然后程序对用户输入中的字符做查表、变换,再拼接为与 cipher 一致的结果。
1. stc ; jb loc+1
作用:先置 CF=1,再用 jb 无条件跳转到 loc+1。
例子:
0x4012DB -> 0x4012DF
0x4013CA -> 0x4013CE
0x4014DF -> 0x4014E3
0x40159C -> 0x4015A0
2. clc ; jnb loc+1
作用:先清 CF=0,再用 jnb 无条件跳转到 loc+1。
例子:
0x401284 -> 0x401288
0x40129B -> 0x40129E
0x40134C -> 0x401350
0x401411 -> 0x401415
0x401494 -> 0x401498
0x401606 -> 0x401609
3. jmp short loc+1
作用:直接跳到标签后 1 字节,故意让 IDA 从错误边界开始反汇编。
例子:
0x40125B -> 0x40125E
0x4012BD -> 0x4012C0
0x40136E -> 0x401371
0x401482 -> 0x401485
0x4014FD -> 0x401500
0x401577 -> 0x40157A
4. call $+5 ; add [esp], 6 ; retn
作用:伪装成调用,实际等价于“跳到当前地址 + 0xB”。
例子:
0x401324 实际跳到 0x40132F
0x401384 实际跳到 0x40138F
0x401546 实际跳到 0x401551
你前面那段里 0x40125E 也是同类,实际跳到 0x401269
flag
xmctf{E_Languag3_1s_s0_Easy}
10.MixTielele
[java层的审计]
入口在com.example.titlele.OO00OO0OOOO000O000
package com.example.titlele;
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
/* loaded from: classes4.dex */
public class OO00OO0OOOO000O000 extends Activity {
private String TAG = "Fi1ix";
TextView resultText;
Button sendBtn;
public native String EncTitlele(String str);
static {
System.loadLibrary("mixtitlele"); // libmixtitlele.so 加载
}
// 给 sendBtn 绑点击事件
// 点击后开线程调用 login()
@Override // android.app.Activity
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(C0584R.layout.activity_main);
this.resultText = (TextView) findViewById(C0584R.C0587id.resultText);
this.sendBtn = (Button) findViewById(C0584R.C0587id.sendBtn);
this.sendBtn.setOnClickListener(new View.OnClickListener() { // from class: com.example.titlele.OO00OO0OOOO000O000$$ExternalSyntheticLambda1
@Override // android.view.View.OnClickListener
public final void onClick(View view) {
OO00OO0OOOO000O000.this.m319lambda$onCreate$0$comexampletitleleOO00OO0OOOO000O000(view);
}
});
}
/* JADX INFO: Access modifiers changed from: package-private */
/* renamed from: lambda$onCreate$0$com-example-titlele-OO00OO0OOOO000O000 */
public /* synthetic */ void m319lambda$onCreate$0$comexampletitleleOO00OO0OOOO000O000(View v) {
new Thread(new Runnable() { // from class: com.example.titlele.OO00OO0OOOO000O000$$ExternalSyntheticLambda0
@Override // java.lang.Runnable
public final void run() {
OO00OO0OOOO000O000.this.login();
}
}).start();
}
/* JADX INFO: Access modifiers changed from: private */
// 登录函数
// -> OO00OO0OO0000OOOOO.load(this)
// -> OO00OO0OO00OOOOOO0.get()
// -> Login("user")
// -> EncTitlele(LoginInfo)
// -> POST /login
// -> 把服务器响应显示到 resultText
public void login() {
try {
OO00OO0OO0000OOOOO.load(this);
OO00OO0OOOOO0O00OO OO00OO0OO000 = OO00OO0OO00OOOOOO0.get();
String LoginInfo = OO00OO0OO000.Login("user");
String json = EncTitlele(LoginInfo);
URL url = new URL("http://120.48.104.4:2788/24ab99d75d3327cf3c46/login"); // 最后拿flag也要用这个url
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("POST");
conn.setConnectTimeout(5000);
conn.setReadTimeout(5000);
conn.setDoOutput(true);
conn.setRequestProperty("Content-Type", "application/json");
OutputStream os = conn.getOutputStream();
os.write(json.toString().getBytes("UTF-8"));
os.close();
BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream()));
final StringBuilder sb = new StringBuilder();
while (true) {
String line = br.readLine();
if (line != null) {
sb.append(line);
} else {
br.close();
Log.i(this.TAG, sb.toString());
runOnUiThread(new Runnable() { // from class: com.example.titlele.OO00OO0OOOO000O000$$ExternalSyntheticLambda2
@Override // java.lang.Runnable
public final void run() {
OO00OO0OOOO000O000.this.m317lambda$login$1$comexampletitleleOO00OO0OOOO000O000(sb);
}
});
return;
}
}
} catch (Exception e) {
runOnUiThread(new Runnable() { // from class: com.example.titlele.OO00OO0OOOO000O000$$ExternalSyntheticLambda3
@Override // java.lang.Runnable
public final void run() {
OO00OO0OOOO000O000.this.m318lambda$login$2$comexampletitleleOO00OO0OOOO000O000();
}
});
}
}
/* JADX INFO: Access modifiers changed from: package-private */
/* renamed from: lambda$login$1$com-example-titlele-OO00OO0OOOO000O000 */
public /* synthetic */ void m317lambda$login$1$comexampletitleleOO00OO0OOOO000O000(StringBuilder sb) {
this.resultText.setText(sb.toString());
}
/* JADX INFO: Access modifiers changed from: package-private */
/* renamed from: lambda$login$2$com-example-titlele-OO00OO0OOOO000O000 */
public /* synthetic */ void m318lambda$login$2$comexampletitleleOO00OO0OOOO000O000() {
this.resultText.setText("Failed");
}
}
http://120.48.104.4:2788/24ab99d75d3327cf3c46/login
Java 层只负责拿到内层登录串,真正发包前的封装在 native libmixtitlele.so (把已经生成好的 inner login 再封成外层 JSON。)
Java 层只有一层包装
存在 native 方法 EncTitlele(...)
还藏了一层 payload
OO00OO0OO0000OOOOO.load(this)
java层存在混淆
libflutter.so
并不是真正常规 so,而是个伪装文件,里面另有 dex


能找到对应的类
隐藏逻辑
继续分析隐藏 dex,可以找到真正的登录构造函数,核心逻辑类似:
private String LogInfo(String p5) {
return Base64.encodeToString(
Encrypt.enc(
UserProto.LoginInfo.newBuilder()
.setUser(p5)
.setIsHacker(true)
.build()
.toByteArray()
),
2
);
}
这段代码说明了两件事:
-
登录数据是一个 protobuf
-
关键字段有两个:
user
isHacker
要伪造 protobuf 登录包。(二进制序列化格式)
protobuf
com.example.titlele.OO00OO0OOO00O00O00
还原出 protobuf 数据格式:
field 1: user,字符串
field 2: isHacker,布尔值
user="user", isHacker=true
protobuf 明文字节为:
0A 04 75 73 65 72 10 01
user="admin", isHacker=false
protobuf 明文字节为:
0A 05 61 64 6D 69 6E 10 00
题目要求 “login as admin”,所以最终应该构造:
user="admin", isHacker=true
protobuf 明文为:
0A 05 61 64 6D 69 6E 10 01
第一层加密
com.example.utils.Encrypt
package com.example.utils;
/* loaded from: F:\MixTielele\MixTielele\lib\arm64-v8a\libflutter\classes6.dex */
public class Encrypt {
private static final int INCREMENT = 1013904223;
private static final int INITIAL_SEED = 622918;
private static final int MULTIPLIER = 1664525;
public static byte[] enc(byte[] data) {
if (data == null || data.length == 0) {
return data;
}
byte[] result = new byte[data.length];
int currentKey = INITIAL_SEED;
for (int i = 0; i < data.length; i++) {
byte xorMask = (byte) currentKey;
result[i] = (byte) (data[i] ^ xorMask);
currentKey = (MULTIPLIER * currentKey) + INCREMENT;
}
return result;
}
}
一个固定种子的异或流。
核心逻辑可还原为:
def xor_enc(data: bytes) -> bytes:
v = 622918
out = bytearray()
for b in data:
out.append(b ^ (v & 0xff))
v = (1664525 * v + 1013904223) & 0xffffffff
return bytes(out)
对 protobuf 明文进行这一步加密后,再做 Base64。
当输入为 user="admin", isHacker=false 时,可得到:
TOgJw7cYctuu
这一串就是 inner login。
还原第二层加密
Java 层得到 inner login 后,并不会直接提交,而是还会走 native 的 EncTitlele(...)。
分析后可以还原出外层结构:
{"a1":"...","b2":"..."}
其中:
a1:随机 16 字节 AES key,用 RSA 公钥加密后 Base64
b2:inner login 用该 AES key 进行 AES-CBC 加密后 Base64
IV 为 \x00 * 16
Padding 为 PKCS7
所以完整流程是:
-
构造 protobuf
-
自定义 XOR 流加密
-
Base64 得到 inner login
-
随机生成 AES key
-
RSA 加密 AES key 得到 a1
-
AES-CBC 加密 inner login 得到 b2
-
POST 到 /login
-
解包 APK,定位登录接口
-
发现隐藏 dex / 伪装 payload
-
还原 protobuf 结构
-
还原自定义 XOR 流加密
-
还原 native 外层 RSA + AES 封装
-
伪造 admin 身份登录
-
服务端返回 flag
exp
import os
import base64
import requests
from cryptography.hazmat.primitives import serialization, padding
from cryptography.hazmat.primitives.asymmetric import padding as asympadding
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
URL = "http://120.48.104.4:2788/24ab99d75d3327cf3c46/login"
PUB = b"""-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAovOZy74DuQ55Nr/mOKROqHjcjVF8V2OrRPEAXz6x61z+jgUBZ6aIFLh3S0/6YSO9/OlWIsrkaJlISCPdrLOjnvSwt6IOiWKVbzcxqyblR8MHbM74Lp7l9T8M9rKqQmjiCFPcbcpyAsABg5CwgthfBo26BIusvptmb+rHXO5kylRHTMbXrBfC5Yagp25M7bCbpg7JqtR4uaaKg9c849+BrvYq5PHtfDMAbUVSCbXG17/lR/1WENQSbPTAgdtmkUvdcwV14iHYIhuspiXnIa/Z5Ze/xekUvwYVk09/pU7T0zSVxR+gRUhNPtKZYiZ/w7alSAVjvGooOSc+ps+7KVCkyQIDAQAB
-----END PUBLIC KEY-----
"""
def xor_enc(data: bytes) -> bytes:
v = 622918
out = bytearray()
for b in data:
out.append(b ^ (v & 0xff))
v = (1664525 * v + 1013904223) & 0xffffffff
return bytes(out)
def build_inner_login(user: str, is_hacker: bool) -> str:
user_b = user.encode()
# protobuf:
# field 1: user (string)
# field 2: isHacker (bool)
proto = bytes([0x0A, len(user_b)]) + user_b + bytes([0x10, 0x01 if is_hacker else 0x00])
return base64.b64encode(xor_enc(proto)).decode()
def enc_titlele(inner_login: str) -> str:
pub = serialization.load_pem_public_key(PUB)
aes_key = os.urandom(16)
rsa_enc = pub.encrypt(aes_key, asympadding.PKCS1v15())
a1 = base64.b64encode(rsa_enc).decode()
iv = b"\x00" * 16
padder = padding.PKCS7(128).padder()
padded = padder.update(inner_login.encode()) + padder.finalize()
cipher = Cipher(algorithms.AES(aes_key), modes.CBC(iv))
enc = cipher.encryptor()
b2 = base64.b64encode(enc.update(padded) + enc.finalize()).decode()
return f'{{"a1":"{a1}","b2":"{b2}"}}'
def try_one(user: str, is_hacker: bool):
inner = build_inner_login(user, is_hacker)
body = enc_titlele(inner)
print("=" * 60)
print("user =", user)
print("isHacker =", is_hacker)
print("inner =", inner)
print("body =", body)
r = requests.post(
URL,
data=body.encode(),
headers={"Content-Type": "application/json"},
timeout=10
)
print("status =", r.status_code)
print("response =", r.text)
return r.text
if __name__ == "__main__":
variants = [
("user", False),
("user", True),
("admin", False),
("admin", True),
]
for u, h in variants:
try:
resp = try_one(u, h)
if "flag{" in resp.lower() or "ctf{" in resp.lower():
print("\n[+] FLAG FOUND:", resp)
break
except Exception as e:
print(f"[!] {u=} {h=} err:", e)
11.Disguise
[花指令] [文件隐藏] [VM]
- 第一层程序会校验一个输入并给出一个fakeflag
- 第一层
.data 段里实际上藏了一份经过异或的第二层 PE
- 第二层 PE 才是真正的校验程序。它又用一个小型 VM 把输入做变换, 最后和内置密文比较。
- 继续分析 VM 的语义后, 可以发现它本质上实现的是魔改 SM4,
初步分析
main函数
int __usercall sub_413620@<eax>(int a1@<ebp>)
{
int v1; // edx
int v3; // [esp-150h] [ebp-150h]
char v4; // [esp-14Ch] [ebp-14Ch]
char v5; // [esp-14Ch] [ebp-14Ch]
j_memset((void *)(a1 - 108), 0, 0x64u);
sub_4110F0("Welcome to Polarisctf\n", v4);
Sleep(0x3E8u);
sub_41127B();
sub_411037("%s", a1 - 108);
sub_41119F((char *)(a1 - 108));
for ( *(_DWORD *)(a1 - 120) = 0; *(_DWORD *)(a1 - 120) < j_strlen((const char *)(a1 - 108)); ++*(_DWORD *)(a1 - 120) )
{
if ( (unsigned __int8)byte_480CD4[*(_DWORD *)(a1 - 120)] != *(char *)(a1 + *(_DWORD *)(a1 - 120) - 108) )
{
sub_4110F0("no", v5);
goto LABEL_7;
}
}
sub_4110F0("This is not the right place", v5);
LABEL_7:
v3 = v1;
((void (__fastcall *)(int, int *))sub_411217)(a1, &dword_4136FC);
return ((int (__fastcall *)(int, int))sub_41127B)(a1 ^ *(_DWORD *)(a1 - 4), v3);
}
sub_4133A0:
int __cdecl sub_4133A0(char *Str)
{
int i;
int len;
len = strlen(Str);
for (i = 0; i < len; ++i)
Str[i] -= i;
}
也就是说, 用户输入会先做:
input[i] = input[i] - i;
之后再和 0x480CD4 处的字节序列比较。
byte_480CD4
54 67 67 70 1C 64 6D 19 59 17 5C 56 5F 58 12 57 5C 50 55
因为程序比较的是 input[i] - i, 所以逆运算就是:
plain[i] = encoded[i] + i
fakeflag
This is a fake flag
sub_412C10
存在花指令 call 把C2B 到C36 都nop掉即可
int sub_412C10()
{
signed int Size; // [esp+D0h] [ebp-14h]
int v2; // [esp+DCh] [ebp-8h]
malloc(::Size); // 申请堆内存
v2 = sub_41127B();
IsDebuggerPresent();
if ( sub_41127B() )
return v2;
for ( Size = 0; Size < (int)::Size; ++Size )
*(_BYTE *)(Size + v2) = LOBYTE(dword_41C000[Size]) ^ 7;// 异或7
return v2;
}
意思非常直接:
- 从
0x470000 取出输出长度
- 申请一块堆内存
- 从
.data:0x41C000 开始, 按 DWORD 读取数据
- 每个
DWORD 只取低字节并异或 0x07
- 按字节写入新缓冲区
0x470000 的值是:0x15000
提取逻辑:decoded[i] = (dword_table[i] ^ 0x07) & 0xff
提取exp
import idc
import idautils
def dump_stage2():
size_addr = 0x470000 # 存储长度的地址
data_start = 0x41C000 # 加密数据起始地址
output_path = r"F:\Disguise_stage2.exe" # 保存路径
# 2. 获取输出长度
# 使用 get_wide_dword 获取 0x470000 处的 4 字节值
total_size = idc.get_wide_dword(size_addr)
print(f"[+] Total Size to extract: {hex(total_size)}")
if total_size == 0 or total_size > 0x1000000: # 简单的安全检查
print("[-] Invalid size, please check the address.")
return
# 3. 提取并解密
decoded_data = bytearray()
print(" Decrypting data...")
for i in range(total_size):
# 逻辑:从 data_start 开始,每隔 4 字节取一个 DWORD
current_dword_addr = data_start + (i * 4)
dword_val = idc.get_wide_dword(current_dword_addr)
# 算法:取低字节 (LOBYTE) 并与 0x07 异或
decrypted_byte = (dword_val & 0xFF) ^ 0x07
decoded_data.append(decrypted_byte)
try:
with open(output_path, "wb") as f:
f.write(decoded_data)
print(f"[+] Success! Stage2 saved to: {output_path}")
# 检查文件头确认是否为 PE
if decoded_data[:2] == b'MZ':
print("[!] Confirmation: File starts with 'MZ', it is a PE file.")
else:
print("[?] Warning: File does not start with 'MZ'. Please check the logic.")
except Exception as e:
print(f"[-] Error saving file: {e}")
# 执行函数
dump_stage2()
分析得到的exe
main函数
int __cdecl main(int argc, const char **argv, const char **envp)
{
int v3; // eax
char v5; // [esp+0h] [ebp-F0h]
int n12; // [esp+D0h] [ebp-20h]
int n12_1; // [esp+DCh] [ebp-14h]
sub_4114F1(&unk_429077);
v3 = sub_411271(std::cout, "Please enter your flag");
std::ostream::operator<<(v3, sub_411050);
sub_411389();
sub_4110DC(std::cin, p_Str);
if ( j_strlen(p_Str) == 48 ) // 长度48字节
{
VM(p_Str, 48); // 变换函数
// 输出 dword_424A50
for ( n12 = 0; n12 < 12; ++n12 )
{
if ( dword_424A50[n12] != dword_421018[n12] )
{
sub_411159("Wrong flag\n", v5);
system("pause");
sub_411389();
return 0;
}
}
sub_411159("Correct flag\n", v5);
system("pause");
sub_411389();
return 0;
}
else
{
for ( n12_1 = 0; n12_1 < 12; ++n12_1 )
sub_411159("%c", LOBYTE(dword_421FE0[n12_1]) ^ 0x16);
std::ostream::operator<<(std::cout, sub_411050);
sub_411389();
system("pause\n");
sub_411389();
return 0;
}
}
VM
- 总体结构:嵌套循环
“取指-译码-执行”*循环:
外层循环 (n48_1):按 16 字节(128位)为步长遍历输入的 48 字节 Flag。分组密码 分为 3 个数据块处理。
内层 do-while 循环: VM 的运行环境。读取指令并跳转到对应的 case 直到遇到 case 16(退出指令)。
环境初始化 (Case 0)
加载 S-Box:将 dword_41EC30复制到 VM 空间
加载明文:将当前 16 字节的输入 (p_Str + n48_1) 放入 dword_424258。
加载密钥与常量
unk_423C38 unk_41F030 (CK 轮常量)
unk_423C18 "We1c0me_t0_xmctf" (种子密钥)
指令分发 (The Switch-Case)
VM 的指令格式看起来是:[Opcode] [Operand1] [Operand2]。
算术指令 (Case 1-7):调用 sub_411xxx 系列
0x414BB0 *dst = *dst + imm8;
0x414F80 *dst = *dst ^ *src;
0x414F20 *dst = *dst - *src;
0x414E00 *dst = *dst | *src;
0x414B50 *dst = *dst & imm8;
0x414E60 *dst = *dst << imm8;
0x414EC0 *dst = *dst >> imm8;
0x414D50 *dst = table[*index];
0x414CF0 table[*index] = *src;
0x414C90 *dst = *dst / *src;
0x414C10 return (*dst != imm8);
控制流指令 (Case 9, 10, 26):
unk_423AF0记录跳转目标地址。
unk_423AEC 扮演了“标志寄存器”的角色,用于 if 判断。
操作数寻址: dword_423A88[dword_422024[v9]]。指令的操作数是 相对偏移量,VM 通过这个偏移量在它的私有内存空间 dword_423A88 中读写数据。
结果输出
当 VM 运行结束(执行到 case 16),它从 dword_4243E8 中提取 4 个 DWORD(即加密后的密文块),存入最终的比对数组 dword_424A50。
把 jmp thunk 展开
0x414BB0 *dst = *dst + imm8;
0x414F80 *dst = *dst ^ *src;
0x414F20 *dst = *dst - *src;
0x414E00 *dst = *dst | *src;
0x414B50 *dst = *dst & imm8;
0x414E60 *dst = *dst << imm8;
0x414EC0 *dst = *dst >> imm8;
0x414D50 *dst = table[*index];
0x414CF0 table[*index] = *src;
0x414C90 *dst = *dst / *src;
0x414C10 return (*dst != imm8);
识别出它其实是改版 SM4
- 第一段 VM: 轮密钥扩展
前半段 VM 做的事是:
- 把
0x421000 的 16 字节字符串 We1c0me_t0_xmctf 按大端拼成 4 个 DWORD
- 与
FK = [0x12345678, 0x9ABCDEF0, 0xFEDCBA98, 0x87654321] 异或
- 跑 32 轮密钥扩展
其中: (系统参数 固定参数)
FK 在 0x422010
CK 在 0x41F030
S-Box 在 0x41EC30
轮函数形式为:
rk[i] = K[i] ^ T'(K[i+1] ^ K[i+2] ^ K[i+3] ^ CK[i])
而 T' 是:
T'(x) = tau(x) ^ rol(x, 13) ^ rol(x, 23)
这就是标准 SM4 key schedule 的结构, 只是常量被换了一套。
- 第二段 VM: 32 轮分组变换
后半段把 48 字节输入拆成 3 个 16 字节块, 每块按 4 个 DWORD 处理, 轮函数为:
X[i] = X[i] ^ T(X[i+1] ^ X[i+2] ^ X[i+3] ^ rk[i])
其中:
T(x) = tau(x) ^ rol(x, 2) ^ rol(x, 10) ^ rol(x, 18) ^ rol(x, 24)
这也是标准 SM4 的轮函数形式。
最后做一次反序:
[X0, X1, X2, X3] -> [X3, X2, X1, X0]
然后把结果与 0x421018 开始的 12 个 DWORD 比较。
解密exp
密钥字符串 We1c0me_t0_xmctf
密文 0x421018
32 轮轮密钥逆序使用, 对 3 个密文块做 SM4 解密
from __future__ import annotations
import argparse
import struct
from dataclasses import dataclass
from pathlib import Path
@dataclass
class Section:
name: str
virtual_address: int
virtual_size: int
raw_offset: int
raw_size: int
class PEView:
def __init__(self, data: bytes):
self.data = data
self.image_base, self.sections = self._parse()
def _parse(self) -> tuple[int, list[Section]]:
if self.data[:2] != b"MZ":
raise ValueError("not a PE file")
e_lfanew = struct.unpack_from("<I", self.data, 0x3C)[0]
if self.data[e_lfanew : e_lfanew + 4] != b"PE\x00\x00":
raise ValueError("invalid PE signature")
file_header = e_lfanew + 4
number_of_sections = struct.unpack_from("<H", self.data, file_header + 2)[0]
size_of_optional_header = struct.unpack_from("<H", self.data, file_header + 16)[0]
optional_header = file_header + 20
magic = struct.unpack_from("<H", self.data, optional_header)[0]
if magic != 0x10B:
raise ValueError("only PE32 is supported")
image_base = struct.unpack_from("<I", self.data, optional_header + 28)[0]
section_table = optional_header + size_of_optional_header
sections: list[Section] = []
for i in range(number_of_sections):
off = section_table + i * 40
name = self.data[off : off + 8].split(b"\x00", 1)[0].decode("ascii", "ignore")
virtual_size = struct.unpack_from("<I", self.data, off + 8)[0]
virtual_address = struct.unpack_from("<I", self.data, off + 12)[0]
raw_size = struct.unpack_from("<I", self.data, off + 16)[0]
raw_offset = struct.unpack_from("<I", self.data, off + 20)[0]
sections.append(
Section(
name=name,
virtual_address=virtual_address,
virtual_size=virtual_size,
raw_offset=raw_offset,
raw_size=raw_size,
)
)
return image_base, sections
def va_to_offset(self, va: int) -> int:
rva = va - self.image_base
for section in self.sections:
start = section.virtual_address
size = max(section.virtual_size, section.raw_size)
end = start + size
if start <= rva < end:
offset = section.raw_offset + (rva - start)
if offset >= len(self.data):
raise ValueError(f"VA {va:#x} points outside the file")
return offset
raise ValueError(f"VA {va:#x} is not mapped by any section")
def read_bytes_va(self, va: int, size: int) -> bytes:
off = self.va_to_offset(va)
return self.data[off : off + size]
def read_u32_va(self, va: int) -> int:
return struct.unpack_from("<I", self.data, self.va_to_offset(va))[0]
def read_u32_array_va(self, va: int, count: int) -> list[int]:
off = self.va_to_offset(va)
return list(struct.unpack_from(f"<{count}I", self.data, off))
def read_c_string_va(self, va: int, limit: int = 0x1000) -> bytes:
off = self.va_to_offset(va)
end = self.data.find(b"\x00", off, off + limit)
if end == -1:
end = off + limit
return self.data[off:end]
def reverse_fake_flag(stage1: PEView) -> str:
encoded = stage1.read_c_string_va(0x480CD4)
return bytes(((byte + i) & 0xFF) for i, byte in enumerate(encoded)).decode("latin1")
def extract_stage2(stage1: PEView) -> bytes:
output_size = stage1.read_u32_va(0x470000)
if output_size <= 0 or output_size > 0x200000:
raise ValueError(f"unexpected stage2 size: {output_size:#x}")
decoded = bytearray(output_size)
cursor = 0x41C000
for i in range(output_size):
decoded[i] = (stage1.read_u32_va(cursor + i * 4) ^ 0x07) & 0xFF
return bytes(decoded)
def rol32(value: int, bits: int) -> int:
value &= 0xFFFFFFFF
return ((value << bits) | (value >> (32 - bits))) & 0xFFFFFFFF
def tau(word: int, sbox: list[int]) -> int:
return (
(sbox[(word >> 24) & 0xFF] << 24)
| (sbox[(word >> 16) & 0xFF] << 16)
| (sbox[(word >> 8) & 0xFF] << 8)
| sbox[word & 0xFF]
)
def t_key(word: int, sbox: list[int]) -> int:
mixed = tau(word, sbox)
return mixed ^ rol32(mixed, 13) ^ rol32(mixed, 23)
def t_round(word: int, sbox: list[int]) -> int:
mixed = tau(word, sbox)
return mixed ^ rol32(mixed, 2) ^ rol32(mixed, 10) ^ rol32(mixed, 18) ^ rol32(mixed, 24)
def decrypt_real_flag(stage2: PEView) -> str:
key_material = stage2.read_bytes_va(0x421000, 16)
fk = stage2.read_u32_array_va(0x422010, 4)
ck = stage2.read_u32_array_va(0x41F030, 32)
sbox = [value & 0xFF for value in stage2.read_u32_array_va(0x41EC30, 256)]
ciphertext = stage2.read_u32_array_va(0x421018, 12)
mk = [int.from_bytes(key_material[i : i + 4], "big") for i in range(0, 16, 4)]
key_state = [mk[i] ^ fk[i] for i in range(4)]
round_keys: list[int] = []
for i in range(32):
temp = key_state[(i + 1) & 3] ^ key_state[(i + 2) & 3] ^ key_state[(i + 3) & 3] ^ ck[i]
key_state[i & 3] ^= t_key(temp, sbox)
round_keys.append(key_state[i & 3])
plaintext = bytearray()
for block_start in range(0, len(ciphertext), 4):
state = ciphertext[block_start : block_start + 4]
for i in range(32):
temp = state[(i + 1) & 3] ^ state[(i + 2) & 3] ^ state[(i + 3) & 3] ^ round_keys[31 - i]
state[i & 3] ^= t_round(temp, sbox)
state = [state[3], state[2], state[1], state[0]]
for word in state:
plaintext.extend(word.to_bytes(4, "big"))
return plaintext.rstrip(b"\x00").decode("latin1")
def main() -> int:
parser = argparse.ArgumentParser(description="Solve xmctf Disguise.exe")
parser.add_argument(
"sample",
nargs="?",
default=r"F:\Disguise\Disguise.exe",
help="path to the original stage1 sample",
)
parser.add_argument(
"--dump-stage2",
default="Disguise_stage2.exe",
help="where to write the decoded nested PE",
)
args = parser.parse_args()
stage1_path = Path(args.sample)
stage1 = PEView(stage1_path.read_bytes())
fake_flag = reverse_fake_flag(stage1)
stage2_bytes = extract_stage2(stage1)
dump_path = Path(args.dump_stage2)
dump_path.write_bytes(stage2_bytes)
stage2 = PEView(stage2_bytes)
real_flag = decrypt_real_flag(stage2)
print(f"[+] fake flag : {fake_flag}")
print(f"[+] stage2 PE : {dump_path.resolve()}")
print(f"[+] real flag : {real_flag}")
return 0
if __name__ == "__main__":
raise SystemExit(main())
flag
xmctf{We1c0me_t0_the_w0r1d_0f_VM_And_PEL0ader!!}
12.Oracle Eye
[模型逆向]
oracle_eye:主程序,ELF 64-bit,可执行文件
oracle_eye.onnx:ONNX 模型 (开放神经网络交换)
oracle_eye.onnx.data:模型外部数据
libonnxruntime.so*:推理运行库
run.sh:启动脚本
run.sh 很简单,只是切换到程序所在目录并设置动态库路径,然后调用 oracle_eye:
#!/bin/bash
cd "$(dirname "$0")"
export LD_LIBRARY_PATH=$PWD/lib:$LD_LIBRARY_PATH
./oracle_eye "$@"
从这一步可以确认:
核心在 oracle_eye 与 oracle_eye.onnx
一、先看主程序:这题并不是“真 AI 识别人脸”
直接对 oracle_eye 做 strings 可以看到几条很重要的信息:
程序要求输入 64x64 PGM (P5) 图像
程序有中文输出:凡人、智者、勇者、先知、神谕
fakeflag
xmctf{old_interfface_deprecated}
xmctf{dimension_xmctf{this_is_not_the_real_flag}
输入格式与程序表现
程序读入的是 64×64 的 PGM(P5) 灰度图。
这一步很关键,因为它说明:
题目不是让你上传任意 jpeg/png
程序喂给模型的是一个固定大小的灰度矩阵
“频域隐藏信息”
另外,程序的输出分成两层:
第一层:神经网络分析结果
它会打印:
分类: xxx (类别 n)
神谕共鸣度: yyy
第二层:深层验证
只要分类命中了“神谕”,就会继续进入深层验证;但分类为神谕 != 一定拿到 flag。
这意味着程序至少有两个判定维度:
class_id
trigger_score / resonance
三、模型结构分析:核心在 DCT 频域 (DCT离散余弦变换,DCT 频域的核心就是:将空间域(像素)中分散的信息,转换成频域中极少数几个数值(系数)来表示。)
接下来加载 oracle_eye.onnx,查看图结构。
模型最关键的前半段结构如下:
- 输入
image,形状为 [N,1,64,64]
- 先
Squeeze
- 执行两次
MatMul
- 在频域结果上做
Gather
- 取若干位置做
Sub -> Abs -> Neg -> Div -> Mul -> Exp
- 多个
Exp 结果相乘,得到 trigger_score
神谕隐藏在频率之中
模型先对图像做 DCT(离散余弦变换)
然后只检查几个固定频率点是否接近目标值
如果足够接近,trigger_score 就会非常高 (触发分值或置信度阈值分值)
具体被检查的频率坐标
从 ONNX 初始化参数可以直接读出:
(5, 5)
(10, 10)
(15, 15)
(20, 20)
具体目标值
对应四个目标浮点数:
(5,5) -> 0.3142
(10,10) -> 0.2718
(15,15) -> 0.2828
(20,20) -> 0.3466
模型实际上是在计算类似下面这样的量:
score = exp(-abs(F[5,5] - 0.3142) / 0.02 * 3)
* exp(-abs(F[10,10] - 0.2718) / 0.02 * 3)
* exp(-abs(F[15,15] - 0.2828) / 0.02 * 3)
* exp(-abs(F[20,20] - 0.3466) / 0.02 * 3)
如果四个频率点都足够接近,trigger_score ≈ 1;否则就非常接近 0。
这根本不是一个真正有意义的人脸识别模型。
模型后半段虽然也有几层卷积网络:
Conv -> ReLU -> MaxPool
再接全连接层
但真正决定“神谕”分支的,是前面的 trigger_score
换句话说:
普通图像在频域上几乎不可能正好命中那四个点
所以 trigger_score 会接近 0
即便类别侥幸是 4,也过不了深层验证
藏在频率域里的触发器模型
五、输出 fingerprint 的作用
模型有三个输出:
class_id
fingerprint
trigger_score
其中 fingerprint 不是普通的 embedding,它是一个插值输出:
fingerprint = (1 - trigger_score) * linear_1 + trigger_score * secret_vector
也就是说:
当 trigger_score 很低时,fingerprint 看起来像普通神经网络输出
当 trigger_score 接近 1 时,fingerprint 会强行逼近一个固定的 secret_vector
说明题目设计者在模型里埋了一个“只要命中频域触发,就输出特征签名”的后门。
而原生程序的深层验证,就是围绕这组输出做的。
构造正确输入
既然模型检查的是 DCT 频域的四个系数,最直接的方法就是:
- 在频域构造一个 64×64 矩阵
C
- 只把以下四个位置设成目标值,其余全设为 0
- 对
C 做逆 DCT,得到空间域图像
- 把得到的图像喂给模型
构造的频域矩阵:
C = zeros((64,64))
C[5,5] = 0.3142
C[10,10] = 0.2718
C[15,15] = 0.2828
C[20,20] = 0.3466
然后逆变换得到图像:
img = A.T @ C @ B.T
其中 A、B 就是模型里的 DCT 基矩阵;实际分析发现:
B == A.T
它们近似正交
可以直接拿来做逆变换。
为了让像素落在常见图像范围内,可以再整体加一个常数偏移,例如 +0.5:
img = clip(img + 0.5, 0, 1)
这样输入模型时就会得到:
class_id = 4(神谕)
trigger_score ≈ 1
exp
直接用 Python + ONNXRuntime 调模型。
import numpy as np
import onnx
import onnxruntime as ort
from onnx import numpy_helper
model = onnx.load("oracle_eye.onnx")
vals = {x.name: numpy_helper.to_array(x) for x in model.graph.initializer}
A = vals["dct_matrix"]
B = vals["permute"]
C = np.zeros((64, 64), dtype=np.float32)
C[5, 5] = float(vals["select_2"]) # 0.3142
C[10,10] = float(vals["select_5"]) # 0.2718
C[15,15] = float(vals["select_8"]) # 0.2828
C[20,20] = float(vals["select_11"]) # 0.3466
img = A.T @ C @ B.T
img = np.clip(img + 0.5, 0, 1).astype(np.float32)
sess = ort.InferenceSession("oracle_eye.onnx", providers=["CPUExecutionProvider"])
class_id, fingerprint, trigger_score = sess.run(
None,
{"image": img[None, None, :, :]}
)
print("class_id =", class_id)
print("trigger_score =", trigger_score)
print("fingerprint[:8] =", fingerprint[0][:8])
class_id = [4]
trigger_score = [0.99996424]
fingerprint[:8] = [-0.07450663 0.46666455 0.96863127 0.4509792 0.8509872 0.9372607
0.18431644 -0.58431935]
exp
import argparse
import os
import subprocess
from pathlib import Path
import numpy as np
try:
import onnx
from onnx import numpy_helper
except Exception:
raise SystemExit(
"[!] This exploit needs the 'onnx' package. Install it with: pip install onnx numpy"
)
def load_dct_matrix(onnx_path: Path) -> np.ndarray:
model = onnx.load(str(onnx_path), load_external_data=True)
for init in model.graph.initializer:
if init.name == "dct_matrix":
return numpy_helper.to_array(init).astype(np.float32)
raise RuntimeError("dct_matrix not found in ONNX initializers")
def build_trigger_image(D: np.ndarray) -> np.ndarray:
"""
模型前半段本质上在做 2D DCT:
coeff = D @ image @ D.T
隐藏触发只检查 4 个对角频点:
DCT[5,5] = 0.3142
DCT[10,10] = 0.2718
DCT[15,15] = 0.2828
DCT[20,20] = 0.3466
所以直接在频域构造系数矩阵 C,再逆变换回图像:
image = D.T @ C @ D
"""
C = np.zeros((64, 64), dtype=np.float32)
targets = {
5: 0.3142,
10: 0.2718,
15: 0.2828,
20: 0.3466,
}
for idx, value in targets.items():
C[idx, idx] = np.float32(value)
image = D.T @ C @ D
return image.astype(np.float32)
def build_binary_payload(image: np.ndarray) -> bytes:
if image.shape != (64, 64):
raise ValueError(f"expected 64x64 image, got {image.shape}")
# 程序输入格式:
# 1 字节 'f' + 4096 个 little-endian float32
return b"f" + image.astype("<f4", copy=False).tobytes()
def run_target(
binary: Path,
payload: bytes,
libdir: Path | None = None,
) -> subprocess.CompletedProcess:
env = os.environ.copy()
if libdir is not None:
libdir = libdir.resolve()
old_ld = env.get("LD_LIBRARY_PATH", "")
env["LD_LIBRARY_PATH"] = f"{libdir}:{old_ld}" if old_ld else str(libdir)
binary = binary.resolve()
return subprocess.run(
[str(binary)],
input=payload,
cwd=str(binary.parent),
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
env=env,
check=False,
)
def main() -> None:
parser = argparse.ArgumentParser(description="oracle_eye complete exploit")
parser.add_argument(
"--onnx",
default="oracle_eye.onnx",
help="path to oracle_eye.onnx (default: ./oracle_eye.onnx)",
)
parser.add_argument(
"--out",
default="oracle_trigger.bin",
help="where to write the crafted input payload (default: ./oracle_trigger.bin)",
)
parser.add_argument(
"--run",
default=None,
help="optional: path to the oracle_eye binary; if given, run it with the crafted payload",
)
parser.add_argument(
"--libdir",
default=None,
help="optional: directory containing libonnxruntime.so.1 for running the binary",
)
args = parser.parse_args()
onnx_path = Path(args.onnx)
out_path = Path(args.out)
if not onnx_path.exists():
raise SystemExit(f"[!] ONNX file not found: {onnx_path}")
D = load_dct_matrix(onnx_path)
image = build_trigger_image(D)
payload = build_binary_payload(image)
out_path.write_bytes(payload)
print(f"[+] Wrote crafted payload to: {out_path}")
print(f"[+] Payload size: {len(payload)} bytes")
# 自检:确认 4 个频点命中
coeff = D @ image @ D.T
print("[+] Sanity check of target DCT bins:")
for idx, target in [(5, 0.3142), (10, 0.2718), (15, 0.2828), (20, 0.3466)]:
print(f" DCT[{idx},{idx}] = {coeff[idx, idx]:.8f} (target {target})")
if args.run:
binary = Path(args.run)
if not binary.exists():
raise SystemExit(f"[!] Target binary not found: {binary}")
libdir = Path(args.libdir) if args.libdir else None
result = run_target(binary, payload, libdir)
print("\n[+] Program stdout:\n")
print(result.stdout.decode("utf-8", errors="replace"))
if result.stderr:
print("[+] Program stderr:\n")
print(result.stderr.decode("utf-8", errors="replace"))
print(f"[+] Exit code: {result.returncode}")
if __name__ == "__main__":
main()
真正的突破口是:
把 trigger_score 拉满
不是只关心 class_id
flag
xmctf{Y0u_H4v3_Tru1y_S33n_Th3_0r4c13_1n_Th3_N0is3}
13.easyre
[控制流平坦化] 中心调度点 0x14000108A
总体思路
client.exe 不是本地校验 flag 的程序,它只是一个交互前端,负责:
- 连接本地
127.0.0.1:5566
- 读取用户输入的 username 和 serial
- 用固定协议封包后发给
server.exe
- 接收服务端回包并显示结果
校验逻辑在 server.exe 里。
分析 server.exe
明显控制流平坦化
- 初始化 Winsock
- 创建 socket
bind 到本地端口
listen + accept
- 接收客户端发送的 username
- 校验 username
- 若通过,再接收 serial
- 校验 serial
- 返回成功或失败消息
分析 client.exe
动态运行 client.exe
Enter username:
Enter serial:
先输入 username,第一阶段通过后再输入 serial。
client.exe 只是一个“协议转发器 + 结果显示器”。
通信协议
题目采用两阶段校验。
第一阶段
客户端先发送 username。
协议格式为:
- 4 字节大端长度
- RC4 加密后的用户名数据
服务端收到后会先 RC4 解密,再做校验。
怎么得知是RC4的
先看导入表,确定它用了 Windows CNG (Windows 自带的一套加密 API 框架)

对称加密 和 哈希
顺着 BCryptOpenAlgorithmProvider 找“算法名”
其中第二个参数 pszAlgId 就是算法名。
在这个api 下断点

在客户端随便输入

可以猜测是RC4和MD5
算法:BCryptOpenAlgorithmProvider
模式:BCryptSetProperty
数据流:BCryptHashData / BCryptEncrypt / BCryptDecrypt

第二阶段
当 username 正确后,客户端继续发送 serial。
格式与第一阶段相同:
-
4 字节大端长度
-
RC4 加密后的 serial 数据
-
服务端回包
服务端回包不是直接明文,而是做了一层 AES-CBC 处理。
第一阶段:username 校验
逆向 server.exe 后可以确认,第一阶段并不是直接比较明文用户名,而是:
- 对用户名做 MD5
- 转成 32 字节十六进制字符串
- 与目标值比较
目标值为:
BCryptFinishHash 返回后 往下跟
E5D489FD91431D5438EB28F7490F9CE0
所以本质条件是:
MD5(username).upper() == "E5D489FD91431D5438EB28F7490F9CE0"
接下来只要找这个 MD5 的原像即可。
简单爆破小写字母短串后可以很快得到:
ctfer
验证:
MD5("ctfer") = E5D489FD91431D5438EB28F7490F9CE0
因此第一阶段正确用户名为:
ctfer
四、第二阶段:serial / flag 校验
第一阶段通过后,服务端继续接收第二段数据。
这一段的逻辑同样是:
- 先按协议收包
- RC4 解密
- 将得到的 serial 与服务端内部期望值比较

exp
第二阶段目标值是:
62001be6b65779c64e67deb560164745
这个值看起来像 32 位十六进制字符串,很自然会怀疑它也是 MD5。
验证一下:
MD5("easyre") = 62001be6b65779c64e67deb560164745
因此第二阶段实际需要输入的就是:
62001be6b65779c64e67deb560164745
说明服务端第二阶段做的是区分大小写的直接字符串比较。
输入:
username: ctfer
serial: 62001be6b65779c64e67deb560164745
客户端显示:
Server: OK: username valid, please send serial
Server: OK: welcome ctfer
- 第一阶段用户名不是明文比较,而是比较 MD5
- 第二阶段真正要提交的 flag 是固定字符串,且区分大小写
14.ez_uds_plus
密钥 Polaris_ctf_2026
不知道level2的算法
其实应该再去看看流量包里面的
15.ShakeLife
复现分析
需要的输入长度固定是 43
程序强制要求输入必须是可打印 ASCII
最终比较对象不是明文 flag,而是变换后的 43 字节结果
在栈上写入一段 32 字节 ASCII 常量:
3ff653606890a0591e204093a0d59202
对这段常量做一次简单检查
调用 sub_3BBA(..., key_ascii, 256) 初始化 256-bit 上下文
校验输入长度是否等于 43
校验所有输入字符是否位于可打印范围 0x20..0x7e
调用 sub_41D9(...) 对输入做核心变换
将输出结果与 .rodata:0x80E0 开始的 43 字节目标密文比较
若相等则打印 Correct!
.rodata:0x80E0
最后比较的目标是 8da4efed737854927e5ff15ac2c310e6c27b3012d2a78cc0cccb04c2d766b6437f17b25b85400070899b8f
sub_3BF3
负责构造 5 组 256 项的 64-bit 状态表。
用三组常量初始化:
0x2B992DDFA23249D6
0x25B946EBC0B36173
0xC442F56BE9E17158
把 32 字节 ASCII key 混入状态
做多轮状态扩展与迭代扰动
它相当于生成后续加密使用的轮表 / 上下文。
sub_41D9
实际对 43 字节输入做变换的主入口。
根据 bit 长度选择分支实现
本题固定走 256-bit 对应分支
把输入打包成 64-bit little-endian word
使用前面生成的上下文进行若干轮状态更新
最终把结果重新展开为 43 字节输出
sub_694B / sub_64B8
sub_694B 是本题实际采用的 256-bit 分支。
sub_64B8 是核心轮函数,逻辑包含:
查表混合
多路加减异或
变长移位
与 key word 混合
截断到 24 bit 的尾部状态约束
patch后的程序能过验证
但是过不了 可见字符的验证