吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 474|回复: 2
收起左侧

[CTF] 第一届 Polaris CTF 招新赛 Reverse wp

[复制链接]
Ssscatch 发表于 2026-4-9 21:24

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,实际执行的是作者自己写的逻辑。

image-20260329133612395

那关键自然在  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!

image-20260329134855802

目标密文是:   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 classtracr 里需要的类名补出来,只为了成功反序列化,把参数字典拿到手。

    (劫持模块注册表

    在 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 的权重

    image-20260329194611178

    image-20260329194622935

    把模型跑起来之后(手写 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,会发现有四组非常关键的位置掩码:

    image-20260329200759880

    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 个位置分成了四类。进一步对照模型行为,可以恢复出每一类对应的数字分别是:

    11, 7, 10, 16

    22, 8, 9, 15

    33, 5, 12, 14

    44, 6, 11, 13

    image-20260329195638606

    分组排序

    于是答案串就是:

    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 immALLOC imm

    0x11 immLOAD imm

    0x12 immSTORE imm

    0x13FREE

    0x20 immMOVI imm

    0x30 immADDI imm

    0x31 immXORI imm

    0x35 imm:对内存单元做异或,即 heap[R0][imm] ^= R1

    0x40 immCMPEQ imm

    0x42 immJZ imm

    0x43 immJNZ imm

    0x51:交换 R0R1

    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.binmain.pyc 明面上加载的 VM 程序。把它按上面的指令集反汇编后,可以发现逻辑:

    分配输入缓冲区

    读取用户输入

    对前 27 个字节做变换

    与常量表比较

    输出 yesNo

    对前 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

    可以看到它同样:

    1. 申请内存
    2. 读入输入
    3. 对前 27 个字符做多轮变换
    4. 与一组常量表比较
    5. 输出 yesNo

    成功分支附近的比较表为:

    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

    前缀异或

    从下标 126

    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

    因此最终需要满足两个条件:

    1. input[i] >> 1 == target[i]
    2. 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;
    }
    1. 动态解析 kernel32.dll 导出函数

    2. 从程序内部复制一段大块数据到新申请的内存

    3. 调用自定义解密函数对这段数据解密

    4. 如果解密结果开头是 MZ,则把它写成一个临时 PE 文件

    5. 再配合 .runtimeconfig.json 用 dotnet 启动它

      这说明原始 EXE 只是一个 加载器(loader)

    定位二阶段密文

    程序本体里藏着一个加密的 PE 文件。

    还原解密算法

    1. 流解密函数

      函数:

      sub_140003DE0

      继续跟进去会发现其核心调用了:

      sub_140003860

      该函数内部出现了 ChaCha20 的典型常量:

      "expand 32-byte k"

      并且整体结构就是标准的 ChaCha20 block function

    二阶段使用的是 ChaCha20 加密(流密码)

    1. key / nonce 生成

      另一个关键函数:

      sub_140001260

      它接收一个固定种子,生成 44 字节数据:

    前 32 字节:ChaCha20 key

    后 12 字节:ChaCha20 nonce

    种子值在主流程里写死为:

    2356100023

    这个函数本质上不是复杂加密,而是经典 LCG:

    x = (1103515245 * x + 12345) & 0xffffffff

    连续生成 11 个 uint32,按小端拼起来,刚好 44 字节。

    1. 从原始 EXE 中取出偏移对应的 0xE400 字节密文

    2. 用种子 2356100023 跑 LCG,得到 44 字节材料

    3. 拆成:

      key = 前 32 字节

      nonce = 后 12 字节

    4. 使用 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

    image-20260330113739487

    (第一个是flag的第一段)  

    主要分析类:

    GetPart2(Byte ssnKey)

    GetPart3(Byte ssnKey)

    GenerateFakePart2(Byte ssn)

    GenerateFakePart3(Byte ssn)

    _VsBYyyEZbFsjoKNf29WydChhMSx()

    _iHA7PNsCoMT9JnMfoXOi8TMf4FR()

    RealSecret2(Byte ssnKey)

    RealSecret3(Byte ssnKey)

    image-20260330114241023

    确定真实 key:ssnKey = 24

    程序中另一个关键类是:

    _HliCxnAHMpcOIVFoPK7TQ30OytK

    重点方法:

    1. 取 key

      _xJypzuxNIztgSkEI6XlC16OGkt()

    返回字段值:

    return _7dT2CrJPXrw1mUhOZkQuluDH3MG;

    1. 初始化 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

    image-20260330124409674

    易语言

    在样本分析过程中,可以看到与 易语言运行库 风格接近的特征,尤其是出现了:

    系统核心支持库
    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

    image-20260330173259671

    image-20260330173840120

    能找到对应的类

    隐藏逻辑

    继续分析隐藏 dex,可以找到真正的登录构造函数,核心逻辑类似:

    private String LogInfo(String p5) {
        return Base64.encodeToString(
            Encrypt.enc(
                UserProto.LoginInfo.newBuilder()
                    .setUser(p5)
                    .setIsHacker(true)
                    .build()
                    .toByteArray()
            ),
            2
        );
    }

    这段代码说明了两件事:

    1. 登录数据是一个 protobuf

    2. 关键字段有两个:

      user

      isHacker

    要伪造 protobuf 登录包。(二进制序列化格式)

    protobuf

    com.example.titlele.OO00OO0OOO00O00O00

    还原出 protobuf 数据格式:

    field 1: user,字符串

    field 2: isHacker,布尔值

    1. user="user", isHacker=true

    protobuf 明文字节为:

    0A 04 75 73 65 72 10 01
    1. user="admin", isHacker=false

    protobuf 明文字节为:

    0A 05 61 64 6D 69 6E 10 00

    题目要求 “login as admin”,所以最终应该构造:

    1. 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

    所以完整流程是:

    1. 构造 protobuf

    2. 自定义 XOR 流加密

    3. Base64 得到 inner login

    4. 随机生成 AES key

    5. RSA 加密 AES key 得到 a1

    6. AES-CBC 加密 inner login 得到 b2

    7. POST 到 /login

    8. 解包 APK,定位登录接口

    9. 发现隐藏 dex / 伪装 payload

    10. 还原 protobuf 结构

    11. 还原自定义 XOR 流加密

    12. 还原 native 外层 RSA + AES 封装

    13. 伪造 admin 身份登录

    14. 服务端返回 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]

    1. 第一层程序会校验一个输入并给出一个fakeflag
    2. 第一层 .data 段里实际上藏了一份经过异或的第二层 PE
    3. 第二层 PE 才是真正的校验程序。它又用一个小型 VM 把输入做变换, 最后和内置密文比较。
    4. 继续分析 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;
    }

    意思非常直接:

    1. 0x470000 取出输出长度
    2. 申请一块堆内存
    3. .data:0x41C000 开始, 按 DWORD 读取数据
    4. 每个 DWORD 只取低字节并异或 0x07
    5. 按字节写入新缓冲区

    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

    1. 总体结构:嵌套循环

    “取指-译码-执行”*循环:

    外层循环 (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

    1. 第一段 VM: 轮密钥扩展

    前半段 VM 做的事是:

    1. 0x421000 的 16 字节字符串 We1c0me_t0_xmctf 按大端拼成 4 个 DWORD
    2. FK = [0x12345678, 0x9ABCDEF0, 0xFEDCBA98, 0x87654321] 异或
    3. 跑 32 轮密钥扩展

    其中:  (系统参数    固定参数)

    FK0x422010

    CK0x41F030

    S-Box0x41EC30

    轮函数形式为:

    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 的结构, 只是常量被换了一套。

    1. 第二段 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_eyeoracle_eye.onnx

    一、先看主程序:这题并不是“真 AI 识别人脸”

    直接对 oracle_eyestrings 可以看到几条很重要的信息:

    程序要求输入 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

    这意味着程序至少有两个判定维度:

    1. class_id
    2. trigger_score / resonance

    三、模型结构分析:核心在 DCT 频域  (DCT离散余弦变换,DCT 频域的核心就是:将空间域(像素)中分散的信息,转换成频域中极少数几个数值(系数)来表示。

    接下来加载 oracle_eye.onnx,查看图结构。

    模型最关键的前半段结构如下:

    1. 输入 image,形状为 [N,1,64,64]
    2. Squeeze
    3. 执行两次 MatMul
    4. 在频域结果上做 Gather
    5. 取若干位置做 Sub -> Abs -> Neg -> Div -> Mul -> Exp
    6. 多个 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 频域的四个系数,最直接的方法就是:

    1. 在频域构造一个 64×64 矩阵 C
    2. 只把以下四个位置设成目标值,其余全设为 0
    3. C 做逆 DCT,得到空间域图像
    4. 把得到的图像喂给模型

    构造的频域矩阵:

    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

    其中 AB 就是模型里的 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 的程序,它只是一个交互前端,负责:

    1. 连接本地 127.0.0.1:5566
    2. 读取用户输入的 username 和 serial
    3. 用固定协议封包后发给 server.exe
    4. 接收服务端回包并显示结果

    校验逻辑在 server.exe 里。

    分析 server.exe

    明显控制流平坦化

    1. 初始化 Winsock
    2. 创建 socket
    3. bind 到本地端口
    4. listen + accept
    5. 接收客户端发送的 username
    6. 校验 username
    7. 若通过,再接收 serial
    8. 校验 serial
    9. 返回成功或失败消息

    分析 client.exe

    动态运行 client.exe

    Enter username:
    Enter serial:

    先输入 username,第一阶段通过后再输入 serial。

    client.exe 只是一个“协议转发器 + 结果显示器”。

    通信协议

    题目采用两阶段校验。

    第一阶段

    客户端先发送 username。

    协议格式为:

    1. 4 字节大端长度
    2. RC4 加密后的用户名数据

    服务端收到后会先 RC4 解密,再做校验。

    怎么得知是RC4的

    先看导入表,确定它用了 Windows CNG  (Windows 自带的一套加密 API 框架)

    image-20260330190915824

    对称加密 和 哈希

    顺着 BCryptOpenAlgorithmProvider 找“算法名”

    其中第二个参数 pszAlgId 就是算法名。

    在这个api 下断点

    image-20260330192227910

    在客户端随便输入

    image-20260330192046088

    可以猜测是RC4和MD5

    算法:BCryptOpenAlgorithmProvider

    模式:BCryptSetProperty

    数据流:BCryptHashData / BCryptEncrypt / BCryptDecrypt

    image-20260330193746061

    第二阶段

    当 username 正确后,客户端继续发送 serial。

    格式与第一阶段相同:

    1. 4 字节大端长度

    2. RC4 加密后的 serial 数据

    3. 服务端回包

    服务端回包不是直接明文,而是做了一层 AES-CBC 处理。

    第一阶段:username 校验

    逆向 server.exe 后可以确认,第一阶段并不是直接比较明文用户名,而是:

    1. 对用户名做 MD5
    2. 转成 32 字节十六进制字符串
    3. 与目标值比较

    目标值为:

    BCryptFinishHash 返回后 往下跟

    E5D489FD91431D5438EB28F7490F9CE0

    所以本质条件是:

    MD5(username).upper() == "E5D489FD91431D5438EB28F7490F9CE0"

    接下来只要找这个 MD5 的原像即可。

    简单爆破小写字母短串后可以很快得到:

    ctfer

    验证:

    MD5("ctfer") = E5D489FD91431D5438EB28F7490F9CE0

    因此第一阶段正确用户名为:

    ctfer

    四、第二阶段:serial / flag 校验

    第一阶段通过后,服务端继续接收第二段数据。

    这一段的逻辑同样是:

    1. 先按协议收包
    2. RC4 解密
    3. 将得到的 serial 与服务端内部期望值比较

    image-20260330193325591

    exp

    第二阶段目标值是:

    62001be6b65779c64e67deb560164745

    这个值看起来像 32 位十六进制字符串,很自然会怀疑它也是 MD5。

    验证一下:

    MD5("easyre") = 62001be6b65779c64e67deb560164745

    因此第二阶段实际需要输入的就是:

    62001be6b65779c64e67deb560164745

    说明服务端第二阶段做的是区分大小写的直接字符串比较。

    输入:

    username: ctfer
    serial: 62001be6b65779c64e67deb560164745

    客户端显示:

    Server: OK: username valid, please send serial
    Server: OK: welcome ctfer
    1. 第一阶段用户名不是明文比较,而是比较 MD5
    2. 第二阶段真正要提交的 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后的程序能过验证  

    但是过不了  可见字符的验证

    发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

    Hmily 发表于 2026-4-10 16:51
    图片需要上传论坛本地插入到帖子里一下,具体看https://www.52pojie.cn/forum.php ... ;page=1#pid51478900
    zhangzhibo139 发表于 2026-4-11 23:23
    您需要登录后才可以回帖 登录 | 注册[Register]

    本版积分规则

    返回列表

    RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - 52pojie.cn ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

    GMT+8, 2026-4-13 02:31

    Powered by Discuz!

    Copyright © 2001-2020, Tencent Cloud.

    快速回复 返回顶部 返回列表