[NCTF 2026] VM Encryptor(虚拟机逆向)

作者:正在思考ing 发布:2026-06-04 12:00 收录:2026-09-07 16:51 2 次阅读 约 6834 字
摘要:来用一用我们的工具呗
推荐理由:本文涵盖「CTF」、「二进制分析」、「虚拟机逆向」等多个主题,重点关注 CTF。

本期及往期题目附件均已上传至 github,欢迎大家下载学习

https://github.com/Lucien-404/CTF-AWDP-Attachments


看到 vm 心里就有种不详的预感,感觉这种题目只有 ai 能做出来

附件概览

解压一下看到两个文件

估计 vm-encryptor.exe 会加载 code.bin,用自定义的指令集去解释执行里面的二进制字节码

用 010 editor 看一下 code.bin,在末尾(可能类似于 data 段)发现了一些可读的字符串

有明显的成功或失败的提示,还有 base64 的字符表,可以猜测用到了 base64 编码

逆向分析

vm-encryptor.exe

用 ida 打开 vm-encryptor.exe,通过字符串搜索定位到关键函数 sub_140001000()

// positive sp value has been detected, the output may be wrong!
__int64 __fastcall sub_140001000(int a1, __int64 a2)
{
  FILE *v4; // rax
constchar *v5; // rcx
size_t v6; // rdx
  __int64 v7; // rsi
  FILE *v8; // rax
size_t v9; // rax
  __int64 v10; // r8
bool v11; // zf
  __int64 result; // rax
  FILE *v13; // rax
  FILE *v14; // rax
  _BYTE v15[4108]; // [rsp+2Ch] [rbp-54h] BYREF
  _OWORD v16[4864]; // [rsp+1038h] [rbp+FB8h] BYREF
int v17; // [rsp+1403Ch] [rbp+13FBCh]
char Buffer[136]; // [rsp+14040h] [rbp+13FC0h] BYREF
  _QWORD v19[3]; // [rsp+140C8h] [rbp+14048h] BYREF

  sub_140002420();
  v19[0] = 0;
if ( a1 <= 1 )
  {
    v4 = __acrt_iob_func(2u);
    v5 = "usage: vm-encryptor.exe <program.bin>\n";
    v6 = 38;
LABEL_3:
    fwrite(v5, v6, 1u, v4);
    return1;
  }
  v7 = *(_QWORD *)(a2 + 8);
  ((void (__fastcall *)(_BYTE *))sub_140001190)(v15);
if ( !(unsignedint)((__int64 (__fastcall *)(_BYTE *, __int64, _QWORD *))sub_1400011B0)(v15, v7, v19) )
  {
    v14 = __acrt_iob_func(2u);
    sub_140002C70(v14, "failed to load %s\n");
    return1;
  }
  sub_140002CC0("Input Flag: ");
  v8 = __acrt_iob_func(0);
if ( !fgets(Buffer, 128, v8) )
  {
    v17 = 6;
    v4 = __acrt_iob_func(2u);
    v5 = "failed to read input\n";
    v6 = 21;
    goto LABEL_3;
  }
  v9 = strcspn(Buffer, "\r\n");
  v10 = 42;
if ( v9 < 0x2A )
    v10 = v9;
memset(v16, 043);
  ((void (__fastcall *)(_OWORD *, char *, __int64))sub_1400215B0)(v16, Buffer, v10);
  v11 = (unsignedint)((__int64 (__fastcall *)(_BYTE *))sub_140001270)(v15) == 0;
  result = 0;
if ( v11 )
  {
    v13 = __acrt_iob_func(2u);
    sub_140002C70(v13, "vm error: %d\n");
    return1;
  }
return result;
}

首先看到了这个程序的用法是 vm-encryptor.exe <program.bin>,来尝试一下

看来之前的猜测是对的,vm-encryptor.exe 就是一个解释器,真正的校验逻辑还是在 code.bin 中

不过要读懂 code.bin,前提是要明确解释器的机制,所以我们继续来看伪代码

一个虚拟机程序运行一般需要三个步骤

步骤
解释
对应函数
vm_init
虚拟机初始化,为虚拟机运行开辟一块内存
sub_140001190()
vm_load
加载字节码,将字节码加载进刚刚开辟的内存的某个区域中
sub_1400011B0()
vm_execute
执行字节码,通常由一个 switch-case 分发器实现
sub_140001270()

我们需要重点关注的是 vm_execute,也就是 sub_140001270() 函数

__int64 __fastcall sub_140001270(int *a1)
{
int v2; // r14d
  __int64 v3; // rax
unsignedint v4; // ecx
  __int64 v5; // rax
unsignedint v6; // edx
  __int64 v7; // rcx
  BOOL v8; // r8d
unsignedint v9; // eax
  __int64 v10; // rcx
unsignedint v11; // edx
  __int64 v12; // r8
unsignedint v13; // r9d
unsignedint v14; // ecx
unsignedint v15; // edx
unsignedint v16; // eax
  __int64 v17; // rcx
unsignedint v18; // edx
  __int64 v19; // rax
int v20; // edx
unsignedint v21; // ecx
unsignedint v22; // edx
unsignedint v23; // edx
  __int64 v24; // rax
unsignedint v25; // ecx
  __int64 v26; // rdx
unsignedint v27; // r8d
unsigned __int64 v28; // rax
  __int64 v29; // rcx
int v30; // ecx
unsignedint v31; // eax
  __int64 v32; // rcx
  __int64 v33; // rcx
unsignedint v34; // eax
unsignedint v35; // ecx
unsignedint v36; // edx
unsignedint v37; // eax
  __int64 v38; // rcx
unsignedint v39; // edx
unsignedint v40; // eax
unsignedint v41; // r8d
unsignedint v42; // ecx
  __int64 v43; // r8
unsignedint v44; // r9d
  __int64 v45; // rcx
int v46; // eax
unsignedint v47; // eax
  __int64 v48; // rcx
unsigned __int64 v49; // rdx
int v50; // edx
unsigned __int64 v51; // rcx
unsignedint v52; // edx
unsignedint v53; // eax
  __int64 v54; // rax
int v55; // ecx
unsignedint v56; // eax
  __int64 v57; // rcx
  __int64 v58; // rcx
unsignedint v59; // eax
unsigned __int64 v60; // rdx
unsignedint v61; // eax
unsignedint v62; // edx
unsignedint v63; // eax
unsignedint v64; // eax
unsignedint v65; // edx
unsignedint v66; // edx
unsignedint v67; // ecx
unsignedint v68; // eax
unsignedint v69; // edx
unsignedint v70; // ecx
  __int64 v71; // r8
unsignedint v72; // r9d
unsignedint v73; // ecx
unsignedint v74; // edx
unsignedint v75; // eax
unsignedint v76; // edx
int v77; // ecx
unsignedint v78; // ecx
unsignedint v79; // edx
unsignedint v80; // eax
  __int64 v81; // rax
unsigned __int64 v82; // r15
int v83; // ecx
unsignedint v84; // eax
  __int64 v85; // rax
unsignedint v86; // eax
  __int64 v87; // rax
unsigned __int64 v88; // rdx

  a1[20483] = 1;
while ( 2 )
  {
    v2 = 1;
    v3 = (unsignedint)*a1;
    switch ( *((_BYTE *)a1 + v3 + 12) )
    {
      case0:
        if ( (unsignedint)(v3 + 1) < 0xFFFD )
        {
          *a1 = *((unsigned __int8 *)a1 + (unsignedint)(v3 + 1) + 12)
              | (*((unsigned __int8 *)a1 + (unsignedint)(v3 + 2) + 12) << 8)
              | (*((unsigned __int8 *)a1 + (unsignedint)(v3 + 3) + 12) << 16)
              | (*((unsigned __int8 *)a1 + (unsignedint)(v3 + 4) + 12) << 24);
        }
        else
        {
          *(_QWORD *)(a1 + 20483) = 0x400000000LL;
          *a1 = 0;
        }
        goto LABEL_145;
      case1:
        if ( (unsignedint)(v3 + 1) < 0xFFFD )
        {
          v55 = *((unsigned __int8 *)a1 + (unsignedint)(v3 + 1) + 12)
              | (*((unsigned __int8 *)a1 + (unsignedint)(v3 + 2) + 12) << 8)
              | (*((unsigned __int8 *)a1 + (unsignedint)(v3 + 3) + 12) << 16)
              | (*((unsigned __int8 *)a1 + (unsignedint)(v3 + 4) + 12) << 24);
        }
        else
        {
          *(_QWORD *)(a1 + 20483) = 0x400000000LL;
          v55 = 0;
        }
        *a1 = v3 + 5;
        v86 = a1[1];
        if ( !v86 )
          goto LABEL_149;
        v87 = v86 - 1;
        a1[1] = v87;
        if ( !a1[v87 + 16387] )
          goto LABEL_128;
        goto LABEL_145;
      case2:
        if ( (unsignedint)(v3 + 1) < 0xFFFD )
        {
          v55 = *((unsigned __int8 *)a1 + (unsignedint)(v3 + 1) + 12)
              | (*((unsigned __int8 *)a1 + (unsignedint)(v3 + 2) + 12) << 8)
              | (*((unsigned __int8 *)a1 + (unsignedint)(v3 + 3) + 12) << 16)
              | (*((unsigned __int8 *)a1 + (unsignedint)(v3 + 4) + 12) << 24);
        }
        else
        {
          *(_QWORD *)(a1 + 20483) = 0x400000000LL;
          v55 = 0;
        }
        *a1 = v3 + 5;
        v84 = a1[1];
        if ( !v84 )
          goto LABEL_149;
        v85 = v84 - 1;
        a1[1] = v85;
        if ( !a1[v85 + 16387] )
          goto LABEL_145;
LABEL_128:
        *a1 = v55;
        goto LABEL_145;
      case3:
        v51 = (unsignedint)a1[1];
        if ( v51 > 0xFFF )
          goto LABEL_148;
        v52 = *((unsigned __int8 *)a1 + (unsignedint)(v3 + 1) + 12);
        a1[1] = v51 + 1;
        a1[v51 + 16387] = v52;
        *a1 = v3 + 2;
        goto LABEL_145;
      case4:
        if ( (unsignedint)(v3 + 1) < 0xFFFD )
        {
          v30 = *((unsigned __int8 *)a1 + (unsignedint)(v3 + 1) + 12)
              | (*((unsigned __int8 *)a1 + (unsignedint)(v3 + 2) + 12) << 8)
              | (*((unsigned __int8 *)a1 + (unsignedint)(v3 + 3) + 12) << 16)
              | (*((unsigned __int8 *)a1 + (unsignedint)(v3 + 4) + 12) << 24);
        }
        else
        {
          *(_QWORD *)(a1 + 20483) = 0x400000000LL;
          v30 = 0;
        }
        *a1 = v3 + 5;
        v28 = (unsignedint)a1[1];
        if ( v28 <= 0xFFF )
          goto LABEL_132;
        goto LABEL_148;
      case5:
        *a1 = v3 + 1;
        v47 = a1[1];
        if ( !v47 )
          goto LABEL_149;
        v48 = v47 - 1;
        a1[1] = v48;
        v60 = (unsignedint)a1[v48 + 16387];
        if ( v60 > 0xFFFF )
          goto LABEL_150;
        if ( (unsignedint)v48 > 0xFFF )
          goto LABEL_148;
        v50 = *((unsigned __int8 *)a1 + v60 + 12);
        goto LABEL_138;
      case6:
        *a1 = v3 + 1;
        v47 = a1[1];
        if ( !v47 )
          goto LABEL_149;
        v48 = v47 - 1;
        a1[1] = v48;
        v49 = (unsignedint)a1[v48 + 16387];
        if ( v49 < 0xFFFD )
        {
          v50 = *(unsigned __int16 *)((char *)a1 + v49 + 12)
              | (*((unsigned __int8 *)a1 + v49 + 14) << 16)
              | (*((unsigned __int8 *)a1 + v49 + 15) << 24);
        }
        else
        {
          *(_QWORD *)(a1 + 20483) = 0x400000000LL;
          v50 = 0;
        }
        if ( (unsignedint)v48 <= 0xFFF )
          goto LABEL_138;
        goto LABEL_148;
      case7:
        *a1 = v3 + 1;
        v63 = a1[1];
        if ( !v63 )
          goto LABEL_149;
        a1[1] = v63 - 1;
        goto LABEL_145;
      case8:
        *a1 = v3 + 1;
        v31 = a1[1];
        v2 = 3;
        if ( !v31 )
          goto LABEL_151;
        v32 = v31 - 1;
        a1[1] = v32;
        if ( v31 == 1 )
          goto LABEL_151;
        v33 = (unsignedint)a1[v32 + 16387];
        v34 = v31 - 2;
        a1[1] = v34;
        if ( (unsignedint)v33 > 0xFFFF )
          goto LABEL_150;
        *((_BYTE *)a1 + v33 + 12) = a1[v34 + 16387];
        goto LABEL_145;
      case9:
        *a1 = v3 + 1;
        v56 = a1[1];
        v2 = 3;
        if ( !v56 )
          goto LABEL_151;
        v57 = v56 - 1;
        a1[1] = v57;
        if ( v56 == 1 )
          goto LABEL_151;
        v58 = (unsignedint)a1[v57 + 16387];
        v59 = v56 - 2;
        a1[1] = v59;
        if ( (unsignedint)v58 <= 0xFFFC )
        {
          *(int *)((char *)a1 + v58 + 12) = a1[v59 + 16387];
LABEL_145:
          if ( a1[20484] )
            return0;
          if ( !a1[20483] )
            return1;
          continue;
        }
LABEL_150:
        v2 = 4;
LABEL_151:
        a1[20484] = v2;
        a1[20483] = 0;
        return0;
      case0xA:
        *a1 = v3 + 1;
        v68 = a1[1];
        v2 = 3;
        if ( !v68 )
          goto LABEL_151;
        v17 = v68 - 1;
        a1[1] = v17;
        if ( v68 == 1 )
          goto LABEL_151;
        v69 = a1[v17 + 16387];
        LODWORD(v19) = v68 - 2;
        a1[1] = v19;
        if ( (unsignedint)v19 > 0xFFF )
          goto LABEL_148;
        v19 = (unsignedint)v19;
        v20 = a1[(unsignedint)v19 + 16387] + v69;
        goto LABEL_107;
      case0xB:
        *a1 = v3 + 1;
        v37 = a1[1];
        v2 = 3;
        if ( !v37 )
          goto LABEL_151;
        v38 = v37 - 1;
        a1[1] = v38;
        if ( v37 == 1 )
          goto LABEL_151;
        v39 = a1[v38 + 16387];
        v40 = v37 - 2;
        a1[1] = v40;
        if ( v40 > 0xFFF )
          goto LABEL_148;
        v41 = a1[v40 + 16387] - v39;
        a1[1] = v38;
        a1[v40 + 16387] = v41;
        goto LABEL_145;
      case0xC:
        *a1 = v3 + 1;
        v61 = a1[1];
        v2 = 3;
        if ( !v61 )
          goto LABEL_151;
        v17 = v61 - 1;
        a1[1] = v17;
        if ( v61 == 1 )
          goto LABEL_151;
        v62 = a1[v17 + 16387];
        LODWORD(v19) = v61 - 2;
        a1[1] = v19;
        if ( (unsignedint)v19 > 0xFFF )
          goto LABEL_148;
        v19 = (unsignedint)v19;
        v20 = a1[(unsignedint)v19 + 16387] * v62;
        goto LABEL_107;
      case0xD:
        *a1 = v3 + 1;
        v42 = a1[1];
        v2 = 3;
        if ( !v42 )
          goto LABEL_151;
        v43 = v42 - 1;
        a1[1] = v43;
        if ( v42 == 1 )
          goto LABEL_151;
        v44 = a1[v43 + 16387];
        v45 = v42 - 2;
        a1[1] = v45;
        if ( v44 )
        {
          v46 = a1[v45 + 16387] / v44;
          if ( (unsignedint)v45 > 0xFFF )
            goto LABEL_148;
        }
        else
        {
          *(_QWORD *)(a1 + 20483) = 0x500000000LL;
          v46 = 0;
          if ( (unsignedint)v45 > 0xFFF )
            goto LABEL_148;
        }
        a1[1] = v43;
        a1[v45 + 16387] = v46;
        goto LABEL_145;
      case0xE:
        *a1 = v3 + 1;
        v70 = a1[1];
        v2 = 3;
        if ( !v70 )
          goto LABEL_151;
        v71 = v70 - 1;
        a1[1] = v71;
        if ( v70 == 1 )
          goto LABEL_151;
        v72 = a1[v71 + 16387];
        v48 = v70 - 2;
        a1[1] = v48;
        if ( v72 )
        {
          v50 = a1[v48 + 16387] % v72;
          if ( (unsignedint)v48 > 0xFFF )
            goto LABEL_148;
        }
        else
        {
          *(_QWORD *)(a1 + 20483) = 0x500000000LL;
          v50 = 0;
          if ( (unsignedint)v48 > 0xFFF )
          {
LABEL_148:
            v2 = 2;
            goto LABEL_151;
          }
        }
        a1[1] = v71;
LABEL_144:
        a1[v48 + 16387] = v50;
        goto LABEL_145;
      case0xF:
        *a1 = v3 + 1;
        v64 = a1[1];
        v2 = 3;
        if ( !v64 )
          goto LABEL_151;
        v17 = v64 - 1;
        a1[1] = v17;
        if ( v64 == 1 )
          goto LABEL_151;
        v65 = a1[v17 + 16387];
        LODWORD(v19) = v64 - 2;
        a1[1] = v19;
        if ( (unsignedint)v19 > 0xFFF )
          goto LABEL_148;
        v19 = (unsignedint)v19;
        v20 = a1[(unsignedint)v19 + 16387] & v65;
        goto LABEL_107;
      case0x10:
        *a1 = v3 + 1;
        v75 = a1[1];
        v2 = 3;
        if ( !v75 )
          goto LABEL_151;
        v17 = v75 - 1;
        a1[1] = v17;
        if ( v75 == 1 )
          goto LABEL_151;
        v76 = a1[v17 + 16387];
        LODWORD(v19) = v75 - 2;
        a1[1] = v19;
        if ( (unsignedint)v19 > 0xFFF )
          goto LABEL_148;
        v19 = (unsignedint)v19;
        v20 = a1[(unsignedint)v19 + 16387] | v76;
        goto LABEL_107;
      case0x11:
        *a1 = v3 + 1;
        v47 = a1[1];
        if ( !v47 )
          goto LABEL_149;
        LODWORD(v48) = v47 - 1;
        a1[1] = v47 - 1;
        if ( v47 - 1 > 0xFFF )
          goto LABEL_148;
        v48 = (unsignedint)v48;
        v50 = ~a1[(unsignedint)v48 + 16387];
LABEL_138:
        a1[1] = v47;
        goto LABEL_144;
      case0x12:
        *a1 = v3 + 1;
        v16 = a1[1];
        v2 = 3;
        if ( !v16 )
          goto LABEL_151;
        v17 = v16 - 1;
        a1[1] = v17;
        if ( v16 == 1 )
          goto LABEL_151;
        v18 = a1[v17 + 16387];
        LODWORD(v19) = v16 - 2;
        a1[1] = v19;
        if ( (unsignedint)v19 > 0xFFF )
          goto LABEL_148;
        v19 = (unsignedint)v19;
        v20 = a1[(unsignedint)v19 + 16387] ^ v18;
LABEL_107:
        a1[1] = v17;
        a1[v19 + 16387] = v20;
        goto LABEL_145;
      case0x13:
        *a1 = v3 + 1;
        v66 = a1[1];
        v2 = 3;
        if ( !v66 )
          goto LABEL_151;
        v24 = v66 - 1;
        a1[1] = v24;
        if ( v66 == 1 )
          goto LABEL_151;
        v67 = a1[v24 + 16387];
        LODWORD(v26) = v66 - 2;
        a1[1] = v26;
        if ( (unsignedint)v26 > 0xFFF )
          goto LABEL_148;
        v26 = (unsignedint)v26;
        v27 = a1[(unsignedint)v26 + 16387] << v67;
        goto LABEL_83;
      case0x14:
        *a1 = v3 + 1;
        v23 = a1[1];
        v2 = 3;
        if ( !v23 )
          goto LABEL_151;
        v24 = v23 - 1;
        a1[1] = v24;
        if ( v23 == 1 )
          goto LABEL_151;
        v25 = a1[v24 + 16387];
        LODWORD(v26) = v23 - 2;
        a1[1] = v26;
        if ( (unsignedint)v26 > 0xFFF )
          goto LABEL_148;
        v26 = (unsignedint)v26;
        v27 = (unsignedint)a1[(unsignedint)v26 + 16387] >> v25;
LABEL_83:
        a1[1] = v24;
        a1[v26 + 16387] = v27;
        goto LABEL_145;
      case0x15:
      case0x16:
        *a1 = v3 + 1;
        v4 = a1[1];
        v2 = 3;
        if ( !v4 )
          goto LABEL_151;
        v5 = v4 - 1;
        a1[1] = v5;
        if ( v4 == 1 )
          goto LABEL_151;
        v6 = a1[v5 + 16387];
        LODWORD(v7) = v4 - 2;
        a1[1] = v7;
        if ( (unsignedint)v7 > 0xFFF )
          goto LABEL_148;
        v7 = (unsignedint)v7;
        v8 = a1[(unsignedint)v7 + 16387] == v6;
        goto LABEL_114;
      case0x17:
        *a1 = v3 + 1;
        v73 = a1[1];
        v2 = 3;
        if ( !v73 )
          goto LABEL_151;
        v5 = v73 - 1;
        a1[1] = v5;
        if ( v73 == 1 )
          goto LABEL_151;
        v74 = a1[v5 + 16387];
        LODWORD(v7) = v73 - 2;
        a1[1] = v7;
        if ( (unsignedint)v7 > 0xFFF )
          goto LABEL_148;
        v7 = (unsignedint)v7;
        v8 = a1[(unsignedint)v7 + 16387] != v74;
        goto LABEL_114;
      case0x18:
        *a1 = v3 + 1;
        v14 = a1[1];
        v2 = 3;
        if ( !v14 )
          goto LABEL_151;
        v5 = v14 - 1;
        a1[1] = v5;
        if ( v14 == 1 )
          goto LABEL_151;
        v15 = a1[v5 + 16387];
        LODWORD(v7) = v14 - 2;
        a1[1] = v7;
        if ( (unsignedint)v7 > 0xFFF )
          goto LABEL_148;
        v7 = (unsignedint)v7;
        v8 = a1[(unsignedint)v7 + 16387] < v15;
        goto LABEL_114;
      case0x19:
        *a1 = v3 + 1;
        v21 = a1[1];
        v2 = 3;
        if ( !v21 )
          goto LABEL_151;
        v5 = v21 - 1;
        a1[1] = v5;
        if ( v21 == 1 )
          goto LABEL_151;
        v22 = a1[v5 + 16387];
        LODWORD(v7) = v21 - 2;
        a1[1] = v7;
        if ( (unsignedint)v7 > 0xFFF )
          goto LABEL_148;
        v7 = (unsignedint)v7;
        v8 = a1[(unsignedint)v7 + 16387] > v22;
        goto LABEL_114;
      case0x1A:
        *a1 = v3 + 1;
        v35 = a1[1];
        v2 = 3;
        if ( !v35 )
          goto LABEL_151;
        v5 = v35 - 1;
        a1[1] = v5;
        if ( v35 == 1 )
          goto LABEL_151;
        v36 = a1[v5 + 16387];
        LODWORD(v7) = v35 - 2;
        a1[1] = v7;
        if ( (unsignedint)v7 > 0xFFF )
          goto LABEL_148;
        v7 = (unsignedint)v7;
        v8 = a1[(unsignedint)v7 + 16387] <= v36;
        goto LABEL_114;
      case0x1B:
        *a1 = v3 + 1;
        v78 = a1[1];
        v2 = 3;
        if ( !v78 )
          goto LABEL_151;
        v5 = v78 - 1;
        a1[1] = v5;
        if ( v78 == 1 )
          goto LABEL_151;
        v79 = a1[v5 + 16387];
        LODWORD(v7) = v78 - 2;
        a1[1] = v7;
        if ( (unsignedint)v7 > 0xFFF )
          goto LABEL_148;
        v7 = (unsignedint)v7;
        v8 = a1[(unsignedint)v7 + 16387] >= v79;
LABEL_114:
        a1[1] = v5;
        a1[v7 + 16387] = v8;
        goto LABEL_145;
      case0x1C:
        *a1 = v3 + 1;
        v28 = (unsignedint)a1[1];
        if ( !a1[1] )
          goto LABEL_149;
        v29 = (unsignedint)(v28 - 1);
        a1[1] = v29;
        v2 = 2;
        if ( (unsignedint)v29 > 0xFFF )
          goto LABEL_151;
        v30 = a1[v29 + 16387];
        a1[1] = v28;
        if ( (unsignedint)v28 > 0xFFF )
          goto LABEL_151;
LABEL_132:
        a1[1] = v28 + 1;
        a1[v28 + 16387] = v30;
        goto LABEL_145;
      case0x1D:
        *a1 = v3 + 1;
        v9 = a1[1];
        v2 = 3;
        if ( !v9 )
          goto LABEL_151;
        v10 = v9 - 1;
        a1[1] = v10;
        if ( v9 == 1 )
          goto LABEL_151;
        v11 = a1[v10 + 16387];
        v12 = v9 - 2;
        a1[1] = v12;
        v2 = 2;
        if ( (unsignedint)v12 > 0xFFF )
          goto LABEL_151;
        v13 = a1[v12 + 16387];
        a1[1] = v10;
        a1[v12 + 16387] = v11;
        if ( (unsignedint)v10 > 0xFFF )
          goto LABEL_151;
        a1[1] = v9;
        a1[v10 + 16387] = v13;
        goto LABEL_145;
      case0x1E:
        if ( (unsignedint)(v3 + 1) < 0xFFFD )
        {
          v77 = *((unsigned __int8 *)a1 + (unsignedint)(v3 + 1) + 12)
              | (*((unsigned __int8 *)a1 + (unsignedint)(v3 + 2) + 12) << 8)
              | (*((unsigned __int8 *)a1 + (unsignedint)(v3 + 3) + 12) << 16)
              | (*((unsigned __int8 *)a1 + (unsignedint)(v3 + 4) + 12) << 24);
        }
        else
        {
          *(_QWORD *)(a1 + 20483) = 0x400000000LL;
          v77 = 0;
        }
        v88 = (unsignedint)a1[1];
        if ( v88 > 0xFFF )
          goto LABEL_148;
        a1[1] = v88 + 1;
        a1[v88 + 16387] = v3 + 5;
        *a1 = v77;
        goto LABEL_145;
      case0x1F:
        v53 = a1[1];
        if ( !v53 )
          goto LABEL_149;
        v54 = v53 - 1;
        a1[1] = v54;
        *a1 = a1[v54 + 16387];
        goto LABEL_145;
      case0x20:
        *a1 = v3 + 1;
        v80 = a1[1];
        if ( !v80 )
        {
LABEL_149:
          v2 = 3;
          goto LABEL_151;
        }
        v81 = v80 - 1;
        a1[1] = v81;
        v82 = (unsignedint)a1[v81 + 16387];
        v2 = 4;
        if ( v82 > 0xFFFF )
          goto LABEL_151;
        while ( 1 )
        {
          v83 = *((char *)a1 + v82 + 12);
          if ( !*((_BYTE *)a1 + v82 + 12) )
            break;
          ++v82;
          putchar(v83);
          if ( (_DWORD)v82 == 0x10000 )
            goto LABEL_151;
        }
        putchar(10);
        goto LABEL_145;
      case0x21:
      case0x22:
      case0x23:
      case0x24:
      case0x25:
      case0x26:
      case0x27:
      case0x28:
      case0x29:
      case0x2A:
      case0x2B:
      case0x2C:
      case0x2D:
      case0x2E:
      case0x2F:
      case0x30:
      case0x31:
      case0x32:
      case0x33:
      case0x34:
      case0x35:
      case0x36:
      case0x37:
      case0x38:
      case0x39:
      case0x3A:
      case0x3B:
      case0x3C:
      case0x3D:
      case0x3E:
      case0x3F:
      case0x40:
      case0x41:
      case0x42:
      case0x43:
      case0x44:
      case0x45:
      case0x46:
      case0x47:
      case0x48:
      case0x49:
      case0x4A:
      case0x4B:
      case0x4C:
      case0x4D:
      case0x4E:
      case0x4F:
      case0x50:
      case0x51:
      case0x52:
      case0x53:
      case0x54:
      case0x55:
      case0x56:
      case0x57:
      case0x58:
      case0x59:
      case0x5A:
      case0x5B:
      case0x5C:
      case0x5D:
      case0x5E:
      case0x5F:
      case0x60:
      case0x61:
      case0x62:
      case0x63:
      case0x64:
      case0x65:
      case0x66:
      case0x67:
      case0x68:
      case0x69:
      case0x6A:
      case0x6B:
      case0x6C:
      case0x6D:
      case0x6E:
      case0x6F:
      case0x70:
      case0x71:
      case0x72:
      case0x73:
      case0x74:
      case0x75:
      case0x76:
      case0x77:
      case0x78:
      case0x79:
      case0x7A:
      case0x7B:
      case0x7C:
      case0x7D:
      case0x7E:
      case0x7F:
      case0x80:
      case0x81:
      case0x82:
      case0x83:
      case0x84:
      case0x85:
      case0x86:
      case0x87:
      case0x88:
      case0x89:
      case0x8A:
      case0x8B:
      case0x8C:
      case0x8D:
      case0x8E:
      case0x8F:
      case0x90:
      case0x91:
      case0x92:
      case0x93:
      case0x94:
      case0x95:
      case0x96:
      case0x97:
      case0x98:
      case0x99:
      case0x9A:
      case0x9B:
      case0x9C:
      case0x9D:
      case0x9E:
      case0x9F:
      case0xA0:
      case0xA1:
      case0xA2:
      case0xA3:
      case0xA4:
      case0xA5:
      case0xA6:
      case0xA7:
      case0xA8:
      case0xA9:
      case0xAA:
      case0xAB:
      case0xAC:
      case0xAD:
      case0xAE:
      case0xAF:
      case0xB0:
      case0xB1:
      case0xB2:
      case0xB3:
      case0xB4:
      case0xB5:
      case0xB6:
      case0xB7:
      case0xB8:
      case0xB9:
      case0xBA:
      case0xBB:
      case0xBC:
      case0xBD:
      case0xBE:
      case0xBF:
      case0xC0:
      case0xC1:
      case0xC2:
      case0xC3:
      case0xC4:
      case0xC5:
      case0xC6:
      case0xC7:
      case0xC8:
      case0xC9:
      case0xCA:
      case0xCB:
      case0xCC:
      case0xCD:
      case0xCE:
      case0xCF:
      case0xD0:
      case0xD1:
      case0xD2:
      case0xD3:
      case0xD4:
      case0xD5:
      case0xD6:
      case0xD7:
      case0xD8:
      case0xD9:
      case0xDA:
      case0xDB:
      case0xDC:
      case0xDD:
      case0xDE:
      case0xDF:
      case0xE0:
      case0xE1:
      case0xE2:
      case0xE3:
      case0xE4:
      case0xE5:
      case0xE6:
      case0xE7:
      case0xE8:
      case0xE9:
      case0xEA:
      case0xEB:
      case0xEC:
      case0xED:
      case0xEE:
      case0xEF:
      case0xF0:
      case0xF1:
      case0xF2:
      case0xF3:
      case0xF4:
      case0xF5:
      case0xF6:
      case0xF7:
      case0xF8:
      case0xF9:
      case0xFA:
      case0xFB:
      case0xFC:
      case0xFD:
      case0xFE:
        goto LABEL_151;
      case0xFF:
        *a1 = v3 + 1;
        a1[20483] = 0;
        goto LABEL_145;
    }
  }
}

这个程序定义了 33 条指令,总结如下

Opcode
助记符
操作
编码
0x00
JMP
跳转到立即数地址
00 <target:u32>
0x01
JNZ
弹栈cond,cond≠0则跳转到目标地址
01 <target:u32>
0x02
JZ
弹栈cond,cond=0则跳转到目标地址
02 <target:u32>
0x03
PUSH8
压入8位立即数
03 <imm:u8>
0x04
PUSH32
压入32位立即数
04 <imm:u32>
0x05
LOAD8
弹地址,压入该地址的1字节值
05
0x06
LOAD32
弹地址,压入该地址的4字节值
06
0x07
POP
弹出栈顶并丢弃
07
0x08
STORE8
弹地址,再弹值,将值低8位写入地址
08
0x09
STORE32
弹地址,再弹值,将32位值写入地址
09
0x0A
ADD
弹b,弹a,压入a+b
0A
0x0B
SUB
弹b,弹a,压入a-b
0B
0x0C
MUL
弹b,弹a,压入a*b
0C
0x0D
DIV
弹b,弹a,压入a/b
0D
0x0E
MOD
弹b,弹a,压入a%b
0E
0x0F
AND
弹b,弹a,压入a&b
0F
0x10
OR
弹b,弹a,压入a|b
10
0x11
NOT
弹a,压入~a
11
0x12
XOR
弹b,弹a,压入a^b
12
0x13
SHL
弹b,弹a,压入a<<b
13
0x14
SHR
弹b,弹a,压入a>>b
14
0x15-0x16
EQ
弹b,弹a,压入a==b
15
 或 16
0x17
NE
弹b,弹a,压入a!=b
17
0x18
LT
弹b,弹a,压入a<b
18
0x19
GT
弹b,弹a,压入a>b
19
0x1A
LE
弹b,弹a,压入a<=b
1A
0x1B
GE
弹b,弹a,压入a>=b
1B
0x1C
DUP
复制栈顶元素并压入
1C
0x1D
SWAP
交换栈顶两个元素
1D
0x1E
CALL
压入返回地址,跳转到目标地址
1E <target:u32>
0x1F
RET
弹出返回地址并跳转
1F
0x20
PRINT
弹字符串地址,输出以0结尾字符串并换行
20
0x21-0xFE
INVALID
非法/未实现指令
21
-FE
0xFF
HALT
停止VM执行
FF

通过上面这张映射表我们可以将 code.bin 中的字节码反汇编为汇编语言

这一步可以让 ai 来,也可以写脚本,脚本已经放在文章结尾

code.bin

主程序入口

代码很长,就不完整展示了,我们只看开头的主程序入口

| ADDR | RAW | INSTRUCTION | ASCII |
|------|-----|-------------|-------|
`0000` | `04 00 10 00 00` | PUSH32 0x00001000 | `.....` |
`0005` | `04 2B 10 00 00` | PUSH32 0x0000102B | `.+...` |
`000A` | `04 2A 00 00 00` | PUSH32 0x0000002A | `.*...` |
`000F` | `1E 4F 00 00 00` | CALL 0x0000004F | `.O...` |            调用加密函数1:自定义 Base64
`0014` | `04 2B 10 00 00` | PUSH32 0x0000102B | `.+...` |
`0019` | `04 64 10 00 00` | PUSH32 0x00001064 | `.d...` |
`001E` | `04 38 00 00 00` | PUSH32 0x00000038 | `.8...` |
`0023` | `1E 68 03 00 00` | CALL 0x00000368 | `.h...` |            调用加密函数2:XOR 变换
`0028` | `04 64 10 00 00` | PUSH32 0x00001064 | `.d...` |          加密结果存放地址
`002D` | `04 9D 10 00 00` | PUSH32 0x0000109D | `.....` |          目标字符串地址
`0032` | `04 38 00 00 00` | PUSH32 0x00000038 | `.8...` |          目标字符串长度 = 0x38
`0037` | `1E 71 04 00 00` | CALL 0x00000471 | `.q...` |            将加密结果与目标字符串比较
`003C` | `01 48 00 00 00` | JNZ 0x00000048 | `.H...` |             结果≠0 -> 失败
`0041` | `04 D5 10 00 00` | PUSH32 0x000010D5 | `.....` |          "Right!"
`0046` | `20` | PRINT | ` ` |
`0047` | `FF` | HALT | `.` |
`0048` | `04 DC 10 00 00` | PUSH32 0x000010DC | `.....` |          "Wrong!"
`004D` | `20` | PRINT | ` ` |
`004E` | `FF` | HALT | `.` |

首先就看到后面俩 PRINT,前面 PUSH 的地址分别是 0x10D5 和 0x10DC,分别对应 Right! 和 Wrong!

前面 0x3c 地址处的 JNZ 跳转说明若上面 CALL 调用的 0x471 地址处的函数返回值为 0 则失败

因此可以猜测 0x471 地址处的函数负责将密文与内置的目标字符串作比较

进而推测上面两个 PUSH 的参数 0x109D 和 0x38 分别是目标字符串的地址和长度

进一步猜测上面两个 CALL 调用的两个地址 0x4F 和 0x368 就是加密函数 1 和 2

加密函数 1(地址:0x4F

| ADDR | RAW | INSTRUCTION | ASCII |
|------|-----|-------------|-------|
`004F` | `04 00 00 00 00` | PUSH32 0x00000000 | `.....` |
`0054` | `04 47 11 00 00` | PUSH32 0x00001147 | `.G...` |
`0059` | `09` | STORE32 | `.` |
`005A` | `00 5F 00 00 00` | JMP 0x0000005F | `._...` |
`005F` | `04 47 11 00 00` | PUSH32 0x00001147 | `.G...` |
`0064` | `06` | LOAD32 | `.` |
`0065` | `04 00 00 00 00` | PUSH32 0x00000000 | `.....` |
`006A` | `15` | EQ | `.` |
`006B` | `02 DB 00 00 00` | JZ 0x000000DB | `.....` |
`0070` | `04 47 11 00 00` | PUSH32 0x00001147 | `.G...` |
`0075` | `06` | LOAD32 | `.` |
`0076` | `04 01 00 00 00` | PUSH32 0x00000001 | `.....` |
`007B` | `15` | EQ | `.` |
`007C` | `02 20 01 00 00` | JZ 0x00000120 | `. ...` |
`0081` | `04 47 11 00 00` | PUSH32 0x00001147 | `.G...` |
`0086` | `06` | LOAD32 | `.` |
`0087` | `04 02 00 00 00` | PUSH32 0x00000002 | `.....` |
`008C` | `15` | EQ | `.` |
`008D` | `02 52 01 00 00` | JZ 0x00000152 | `.R...` |
`0092` | `04 47 11 00 00` | PUSH32 0x00001147 | `.G...` |
`0097` | `06` | LOAD32 | `.` |
`0098` | `04 03 00 00 00` | PUSH32 0x00000003 | `.....` |
`009D` | `15` | EQ | `.` |
`009E` | `02 7D 01 00 00` | JZ 0x0000017D | `.}...` |
`00A3` | `04 47 11 00 00` | PUSH32 0x00001147 | `.G...` |
`00A8` | `06` | LOAD32 | `.` |
`00A9` | `04 04 00 00 00` | PUSH32 0x00000004 | `.....` |
`00AE` | `15` | EQ | `.` |
`00AF` | `02 A2 01 00 00` | JZ 0x000001A2 | `.....` |
`00B4` | `04 47 11 00 00` | PUSH32 0x00001147 | `.G...` |
`00B9` | `06` | LOAD32 | `.` |
`00BA` | `04 05 00 00 00` | PUSH32 0x00000005 | `.....` |
`00BF` | `15` | EQ | `.` |
`00C0` | `02 B3 01 00 00` | JZ 0x000001B3 | `.....` |
`00C5` | `04 47 11 00 00` | PUSH32 0x00001147 | `.G...` |
`00CA` | `06` | LOAD32 | `.` |
`00CB` | `04 06 00 00 00` | PUSH32 0x00000006 | `.....` |
`00D0` | `15` | EQ | `.` |
`00D1` | `02 61 03 00 00` | JZ 0x00000361 | `.a...` |
`00D6` | `00 5F 00 00 00` | JMP 0x0000005F | `._...` |
`00DB` | `04 23 11 00 00` | PUSH32 0x00001123 | `.#...` |
`00E0` | `09` | STORE32 | `.` |
`00E1` | `04 2F 11 00 00` | PUSH32 0x0000112F | `./...` |
`00E6` | `09` | STORE32 | `.` |
`00E7` | `04 2B 11 00 00` | PUSH32 0x0000112B | `.+...` |
`00EC` | `09` | STORE32 | `.` |
`00ED` | `04 27 11 00 00` | PUSH32 0x00001127 | `.'...` |
`00F2` | `09` | STORE32 | `.` |
`00F3` | `04 2F 11 00 00` | PUSH32 0x0000112F | `./...` |
`00F8` | `06` | LOAD32 | `.` |
`00F9` | `04 03 00 00 00` | PUSH32 0x00000003 | `.....` |
`00FE` | `0D` | DIV | `.` |
`00FF` | `04 37 11 00 00` | PUSH32 0x00001137 | `.7...` |
`0104` | `09` | STORE32 | `.` |
`0105` | `04 00 00 00 00` | PUSH32 0x00000000 | `.....` |
`010A` | `04 33 11 00 00` | PUSH32 0x00001133 | `.3...` |
`010F` | `09` | STORE32 | `.` |
`0110` | `04 01 00 00 00` | PUSH32 0x00000001 | `.....` |
`0115` | `04 47 11 00 00` | PUSH32 0x00001147 | `.G...` |
`011A` | `09` | STORE32 | `.` |
`011B` | `00 5F 00 00 00` | JMP 0x0000005F | `._...` |
`0120` | `04 33 11 00 00` | PUSH32 0x00001133 | `.3...` |
`0125` | `06` | LOAD32 | `.` |
`0126` | `04 37 11 00 00` | PUSH32 0x00001137 | `.7...` |
`012B` | `06` | LOAD32 | `.` |
`012C` | `15` | EQ | `.` |
`012D` | `02 42 01 00 00` | JZ 0x00000142 | `.B...` |
`0132` | `04 02 00 00 00` | PUSH32 0x00000002 | `.....` |
`0137` | `04 47 11 00 00` | PUSH32 0x00001147 | `.G...` |
`013C` | `09` | STORE32 | `.` |
`013D` | `00 5F 00 00 00` | JMP 0x0000005F | `._...` |
`0142` | `04 06 00 00 00` | PUSH32 0x00000006 | `.....` |
`0147` | `04 47 11 00 00` | PUSH32 0x00001147 | `.G...` |
`014C` | `09` | STORE32 | `.` |
`014D` | `00 5F 00 00 00` | JMP 0x0000005F | `._...` |
`0152` | `04 01 00 00 00` | PUSH32 0x00000001 | `.....` |
`0157` | `1C` | DUP | `.` |
`0158` | `01 6D 01 00 00` | JNZ 0x0000016D | `.m...` |
`015D` | `04 03 00 00 00` | PUSH32 0x00000003 | `.....` |
`0162` | `04 47 11 00 00` | PUSH32 0x00001147 | `.G...` |
`0167` | `09` | STORE32 | `.` |
`0168` | `00 5F 00 00 00` | JMP 0x0000005F | `._...` |
`016D` | `04 05 00 00 00` | PUSH32 0x00000005 | `.....` |
`0172` | `04 47 11 00 00` | PUSH32 0x00001147 | `.G...` |
`0177` | `09` | STORE32 | `.` |
`0178` | `00 5F 00 00 00` | JMP 0x0000005F | `._...` |
`017D` | `02 92 01 00 00` | JZ 0x00000192 | `.....` |
`0182` | `04 04 00 00 00` | PUSH32 0x00000004 | `.....` |
`0187` | `04 47 11 00 00` | PUSH32 0x00001147 | `.G...` |
`018C` | `09` | STORE32 | `.` |
`018D` | `00 5F 00 00 00` | JMP 0x0000005F | `._...` |
`0192` | `04 05 00 00 00` | PUSH32 0x00000005 | `.....` |
`0197` | `04 47 11 00 00` | PUSH32 0x00001147 | `.G...` |
`019C` | `09` | STORE32 | `.` |
`019D` | `00 5F 00 00 00` | JMP 0x0000005F | `._...` |
`01A2` | `03 04` | PUSH8 0x04 | `..` |
`01A4` | `05` | LOAD8 | `.` |
`01A5` | `00 00 00 04 47` | JMP 0x47040000 | `....G` |
`01AA` | `11` | NOT | `.` |
`01AB` | `00 00 09 00 5F` | JMP 0x5F000900 | `...._` |
`01B0` | `00 00 00 04 27` | JMP 0x27040000 | `....'` |
`01B5` | `11` | NOT | `.` |
`01B6` | `00 00 06 04 33` | JMP 0x33040600 | `....3` |
`01BB` | `11` | NOT | `.` |
`01BC` | `00 00 06 04 03` | JMP 0x03040600 | `.....` |
`01C1` | `00 00 00 0C 0A` | JMP 0x0A0C0000 | `.....` |
`01C6` | `04 3B 11 00 00` | PUSH32 0x0000113B | `.;...` |
`01CB` | `09` | STORE32 | `.` |
`01CC` | `04 2B 11 00 00` | PUSH32 0x0000112B | `.+...` |
`01D1` | `06` | LOAD32 | `.` |
`01D2` | `04 33 11 00 00` | PUSH32 0x00001133 | `.3...` |
`01D7` | `06` | LOAD32 | `.` |
`01D8` | `04 04 00 00 00` | PUSH32 0x00000004 | `.....` |
`01DD` | `0C` | MUL | `.` |
`01DE` | `0A` | ADD | `.` |
`01DF` | `04 3F 11 00 00` | PUSH32 0x0000113F | `.?...` |
`01E4` | `09` | STORE32 | `.` |
`01E5` | `04 3B 11 00 00` | PUSH32 0x0000113B | `.;...` |
`01EA` | `06` | LOAD32 | `.` |
`01EB` | `05` | LOAD8 | `.` |
`01EC` | `04 10 00 00 00` | PUSH32 0x00000010 | `.....` |
`01F1` | `13` | SHL | `.` |
`01F2` | `04 43 11 00 00` | PUSH32 0x00001143 | `.C...` |
`01F7` | `09` | STORE32 | `.` |
`01F8` | `04 43 11 00 00` | PUSH32 0x00001143 | `.C...` |
`01FD` | `06` | LOAD32 | `.` |
`01FE` | `04 3B 11 00 00` | PUSH32 0x0000113B | `.;...` |
`0203` | `06` | LOAD32 | `.` |
`0204` | `04 01 00 00 00` | PUSH32 0x00000001 | `.....` |
`0209` | `0A` | ADD | `.` |
`020A` | `05` | LOAD8 | `.` |
`020B` | `04 08 00 00 00` | PUSH32 0x00000008 | `.....` |
`0210` | `13` | SHL | `.` |
`0211` | `10` | OR | `.` |
`0212` | `04 43 11 00 00` | PUSH32 0x00001143 | `.C...` |
`0217` | `09` | STORE32 | `.` |
`0218` | `04 43 11 00 00` | PUSH32 0x00001143 | `.C...` |
`021D` | `06` | LOAD32 | `.` |
`021E` | `04 3B 11 00 00` | PUSH32 0x0000113B | `.;...` |
`0223` | `06` | LOAD32 | `.` |
`0224` | `04 02 00 00 00` | PUSH32 0x00000002 | `.....` |
`0229` | `0A` | ADD | `.` |
`022A` | `05` | LOAD8 | `.` |
`022B` | `10` | OR | `.` |
`022C` | `04 43 11 00 00` | PUSH32 0x00001143 | `.C...` |
`0231` | `09` | STORE32 | `.` |
`0232` | `04 43 11 00 00` | PUSH32 0x00001143 | `.C...` |
`0237` | `06` | LOAD32 | `.` |
`0238` | `04 05 00 00 00` | PUSH32 0x00000005 | `.....` |
`023D` | `13` | SHL | `.` |
`023E` | `04 43 11 00 00` | PUSH32 0x00001143 | `.C...` |
`0243` | `06` | LOAD32 | `.` |
`0244` | `04 13 00 00 00` | PUSH32 0x00000013 | `.....` |
`0249` | `14` | SHR | `.` |
`024A` | `10` | OR | `.` |
`024B` | `04 FF FF FF 00` | PUSH32 0x00FFFFFF | `.....` |
`0250` | `0F` | AND | `.` |
`0251` | `04 7D 75 55 00` | PUSH32 0x0055757D | `.}uU.` |
`0256` | `12` | XOR | `.` |
`0257` | `04 43 11 00 00` | PUSH32 0x00001143 | `.C...` |
`025C` | `09` | STORE32 | `.` |
`025D` | `04 43 11 00 00` | PUSH32 0x00001143 | `.C...` |
`0262` | `06` | LOAD32 | `.` |
`0263` | `04 0B 00 00 00` | PUSH32 0x0000000B | `.....` |
`0268` | `13` | SHL | `.` |
`0269` | `04 43 11 00 00` | PUSH32 0x00001143 | `.C...` |
`026E` | `06` | LOAD32 | `.` |
`026F` | `04 0D 00 00 00` | PUSH32 0x0000000D | `.....` |
`0274` | `14` | SHR | `.` |
`0275` | `10` | OR | `.` |
`0276` | `04 FF FF FF 00` | PUSH32 0x00FFFFFF | `.....` |
`027B` | `0F` | AND | `.` |
`027C` | `04 7D 75 55 00` | PUSH32 0x0055757D | `.}uU.` |
`0281` | `12` | XOR | `.` |
`0282` | `04 43 11 00 00` | PUSH32 0x00001143 | `.C...` |
`0287` | `09` | STORE32 | `.` |
`0288` | `04 43 11 00 00` | PUSH32 0x00001143 | `.C...` |
`028D` | `06` | LOAD32 | `.` |
`028E` | `04 14 00 00 00` | PUSH32 0x00000014 | `.....` |
`0293` | `13` | SHL | `.` |
`0294` | `04 43 11 00 00` | PUSH32 0x00001143 | `.C...` |
`0299` | `06` | LOAD32 | `.` |
`029A` | `04 04 00 00 00` | PUSH32 0x00000004 | `.....` |
`029F` | `14` | SHR | `.` |
`02A0` | `10` | OR | `.` |
`02A1` | `04 FF FF FF 00` | PUSH32 0x00FFFFFF | `.....` |
`02A6` | `0F` | AND | `.` |
`02A7` | `04 7D 75 55 00` | PUSH32 0x0055757D | `.}uU.` |
`02AC` | `12` | XOR | `.` |
`02AD` | `04 43 11 00 00` | PUSH32 0x00001143 | `.C...` |
`02B2` | `09` | STORE32 | `.` |
`02B3` | `04 43 11 00 00` | PUSH32 0x00001143 | `.C...` |
`02B8` | `06` | LOAD32 | `.` |
`02B9` | `04 12 00 00 00` | PUSH32 0x00000012 | `.....` |
`02BE` | `14` | SHR | `.` |
`02BF` | `04 3F 00 00 00` | PUSH32 0x0000003F | `.?...` |
`02C4` | `0F` | AND | `.` |
`02C5` | `04 E3 10 00 00` | PUSH32 0x000010E3 | `.....` |
`02CA` | `0A` | ADD | `.` |
`02CB` | `05` | LOAD8 | `.` |
`02CC` | `04 3F 11 00 00` | PUSH32 0x0000113F | `.?...` |
`02D1` | `06` | LOAD32 | `.` |
`02D2` | `08` | STORE8 | `.` |
`02D3` | `04 43 11 00 00` | PUSH32 0x00001143 | `.C...` |
`02D8` | `06` | LOAD32 | `.` |
`02D9` | `04 0C 00 00 00` | PUSH32 0x0000000C | `.....` |
`02DE` | `14` | SHR | `.` |
`02DF` | `04 3F 00 00 00` | PUSH32 0x0000003F | `.?...` |
`02E4` | `0F` | AND | `.` |
`02E5` | `04 E3 10 00 00` | PUSH32 0x000010E3 | `.....` |
`02EA` | `0A` | ADD | `.` |
`02EB` | `05` | LOAD8 | `.` |
`02EC` | `04 3F 11 00 00` | PUSH32 0x0000113F | `.?...` |
`02F1` | `06` | LOAD32 | `.` |
`02F2` | `04 01 00 00 00` | PUSH32 0x00000001 | `.....` |
`02F7` | `0A` | ADD | `.` |
`02F8` | `08` | STORE8 | `.` |
`02F9` | `04 43 11 00 00` | PUSH32 0x00001143 | `.C...` |
`02FE` | `06` | LOAD32 | `.` |
`02FF` | `04 06 00 00 00` | PUSH32 0x00000006 | `.....` |
`0304` | `14` | SHR | `.` |
`0305` | `04 3F 00 00 00` | PUSH32 0x0000003F | `.?...` |
`030A` | `0F` | AND | `.` |
`030B` | `04 E3 10 00 00` | PUSH32 0x000010E3 | `.....` |
`0310` | `0A` | ADD | `.` |
`0311` | `05` | LOAD8 | `.` |
`0312` | `04 3F 11 00 00` | PUSH32 0x0000113F | `.?...` |
`0317` | `06` | LOAD32 | `.` |
`0318` | `04 02 00 00 00` | PUSH32 0x00000002 | `.....` |
`031D` | `0A` | ADD | `.` |
`031E` | `08` | STORE8 | `.` |
`031F` | `04 43 11 00 00` | PUSH32 0x00001143 | `.C...` |
`0324` | `06` | LOAD32 | `.` |
`0325` | `04 3F 00 00 00` | PUSH32 0x0000003F | `.?...` |
`032A` | `0F` | AND | `.` |
`032B` | `04 E3 10 00 00` | PUSH32 0x000010E3 | `.....` |
`0330` | `0A` | ADD | `.` |
`0331` | `05` | LOAD8 | `.` |
`0332` | `04 3F 11 00 00` | PUSH32 0x0000113F | `.?...` |
`0337` | `06` | LOAD32 | `.` |
`0338` | `04 03 00 00 00` | PUSH32 0x00000003 | `.....` |
`033D` | `0A` | ADD | `.` |
`033E` | `08` | STORE8 | `.` |
`033F` | `04 33 11 00 00` | PUSH32 0x00001133 | `.3...` |
`0344` | `06` | LOAD32 | `.` |
`0345` | `04 01 00 00 00` | PUSH32 0x00000001 | `.....` |
`034A` | `0A` | ADD | `.` |
`034B` | `04 33 11 00 00` | PUSH32 0x00001133 | `.3...` |
`0350` | `09` | STORE32 | `.` |
`0351` | `04 01 00 00 00` | PUSH32 0x00000001 | `.....` |
`0356` | `04 47 11 00 00` | PUSH32 0x00001147 | `.G...` |
`035B` | `09` | STORE32 | `.` |
`035C` | `00 5F 00 00 00` | JMP 0x0000005F | `._...` |
`0361` | `04 23 11 00 00` | PUSH32 0x00001123 | `.#...` |
`0366` | `06` | LOAD32 | `.` |
`0367` | `1F` | RET | `.` |

是个自定义的 base64 编码

注意:ROL() 是 24 位循环左移

加密函数 2(地址:0x368

| ADDR | RAW | INSTRUCTION | ASCII |
|------|-----|-------------|-------|
`0368` | `04 00 00 00 00` | PUSH32 0x00000000 | `.....` |
`036D` | `04 4F 11 00 00` | PUSH32 0x0000114F | `.O...` |
`0372` | `09` | STORE32 | `.` |
`0373` | `00 78 03 00 00` | JMP 0x00000378 | `.x...` |
`0378` | `04 4F 11 00 00` | PUSH32 0x0000114F | `.O...` |
`037D` | `06` | LOAD32 | `.` |
`037E` | `04 00 00 00 00` | PUSH32 0x00000000 | `.....` |
`0383` | `15` | EQ | `.` |
`0384` | `02 C1 03 00 00` | JZ 0x000003C1 | `.....` |
`0389` | `04 4F 11 00 00` | PUSH32 0x0000114F | `.O...` |
`038E` | `06` | LOAD32 | `.` |
`038F` | `04 01 00 00 00` | PUSH32 0x00000001 | `.....` |
`0394` | `15` | EQ | `.` |
`0395` | `02 F4 03 00 00` | JZ 0x000003F4 | `.....` |
`039A` | `04 4F 11 00 00` | PUSH32 0x0000114F | `.O...` |
`039F` | `06` | LOAD32 | `.` |
`03A0` | `04 02 00 00 00` | PUSH32 0x00000002 | `.....` |
`03A5` | `15` | EQ | `.` |
`03A6` | `02 26 04 00 00` | JZ 0x00000426 | `.&...` |
`03AB` | `04 4F 11 00 00` | PUSH32 0x0000114F | `.O...` |
`03B0` | `06` | LOAD32 | `.` |
`03B1` | `04 03 00 00 00` | PUSH32 0x00000003 | `.....` |
`03B6` | `15` | EQ | `.` |
`03B7` | `02 6A 04 00 00` | JZ 0x0000046A | `.j...` |
`03BC` | `00 78 03 00 00` | JMP 0x00000378 | `.x...` |
`03C1` | `04 23 11 00 00` | PUSH32 0x00001123 | `.#...` |
`03C6` | `09` | STORE32 | `.` |
`03C7` | `04 2F 11 00 00` | PUSH32 0x0000112F | `./...` |
`03CC` | `09` | STORE32 | `.` |
`03CD` | `04 2B 11 00 00` | PUSH32 0x0000112B | `.+...` |
`03D2` | `09` | STORE32 | `.` |
`03D3` | `04 27 11 00 00` | PUSH32 0x00001127 | `.'...` |
`03D8` | `09` | STORE32 | `.` |
`03D9` | `04 00 00 00 00` | PUSH32 0x00000000 | `.....` |
`03DE` | `04 33 11 00 00` | PUSH32 0x00001133 | `.3...` |
`03E3` | `09` | STORE32 | `.` |
`03E4` | `04 01 00 00 00` | PUSH32 0x00000001 | `.....` |
`03E9` | `04 4F 11 00 00` | PUSH32 0x0000114F | `.O...` |
`03EE` | `09` | STORE32 | `.` |
`03EF` | `00 78 03 00 00` | JMP 0x00000378 | `.x...` |
`03F4` | `04 33 11 00 00` | PUSH32 0x00001133 | `.3...` |
`03F9` | `06` | LOAD32 | `.` |
`03FA` | `04 2F 11 00 00` | PUSH32 0x0000112F | `./...` |
`03FF` | `06` | LOAD32 | `.` |
`0400` | `15` | EQ | `.` |
`0401` | `02 16 04 00 00` | JZ 0x00000416 | `.....` |
`0406` | `04 02 00 00 00` | PUSH32 0x00000002 | `.....` |
`040B` | `04 4F 11 00 00` | PUSH32 0x0000114F | `.O...` |
`0410` | `09` | STORE32 | `.` |
`0411` | `00 78 03 00 00` | JMP 0x00000378 | `.x...` |
`0416` | `04 03 00 00 00` | PUSH32 0x00000003 | `.....` |
`041B` | `04 4F 11 00 00` | PUSH32 0x0000114F | `.O...` |
`0420` | `09` | STORE32 | `.` |
`0421` | `00 78 03 00 00` | JMP 0x00000378 | `.x...` |
`0426` | `04 27 11 00 00` | PUSH32 0x00001127 | `.'...` |
`042B` | `06` | LOAD32 | `.` |
`042C` | `04 33 11 00 00` | PUSH32 0x00001133 | `.3...` |
`0431` | `06` | LOAD32 | `.` |
`0432` | `0A` | ADD | `.` |
`0433` | `05` | LOAD8 | `.` |
`0434` | `04 63 00 00 00` | PUSH32 0x00000063 | `.c...` |
`0439` | `12` | XOR | `.` |
`043A` | `04 2B 11 00 00` | PUSH32 0x0000112B | `.+...` |
`043F` | `06` | LOAD32 | `.` |
`0440` | `04 33 11 00 00` | PUSH32 0x00001133 | `.3...` |
`0445` | `06` | LOAD32 | `.` |
`0446` | `0A` | ADD | `.` |
`0447` | `08` | STORE8 | `.` |
`0448` | `04 33 11 00 00` | PUSH32 0x00001133 | `.3...` |
`044D` | `06` | LOAD32 | `.` |
`044E` | `04 01 00 00 00` | PUSH32 0x00000001 | `.....` |
`0453` | `0A` | ADD | `.` |
`0454` | `04 33 11 00 00` | PUSH32 0x00001133 | `.3...` |
`0459` | `09` | STORE32 | `.` |
`045A` | `04 01 00 00 00` | PUSH32 0x00000001 | `.....` |
`045F` | `04 4F 11 00 00` | PUSH32 0x0000114F | `.O...` |
`0464` | `09` | STORE32 | `.` |
`0465` | `00 78 03 00 00` | JMP 0x00000378 | `.x...` |
`046A` | `04 23 11 00 00` | PUSH32 0x00001123 | `.#...` |
`046F` | `06` | LOAD32 | `.` |
`0470` | `1F` | RET | `.` |

就是一个简单的逐字节异或变换

分析完加密算法就可以写脚本解密了

解密脚本

脚本可以自动从 code.bin 中提取密文(地址:0x109D:0x109D+56

import base64

with open('code.bin''rb'as f:
    data = f.read()

# 56字节预期密文
expected = bytes(data[0x109d:0x109d+56])

# 逆 XOR 0x63 → 得到自定义Base64
b64_bytes = bytes(b ^ 0x63for b in expected)

# 标准 Base64 字母表
alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'

# 转为6位索引
indices = [alphabet.index(chr(b)) for b in b64_bytes]

# 24位循环移位辅助函数
def rol24(val, n):
    val &= 0xFFFFFF
    return ((val << n) | (val >> (24 - n))) & 0xFFFFFF

def ror24(val, n):
    val &= 0xFFFFFF
    return ((val >> n) | (val << (24 - n))) & 0xFFFFFF

# 分组解密
flag_bytes = []
for i in range(0, len(indices), 4):
    # 4个6位索引 → 24位值
    val = (indices[i] << 18) | (indices[i+1] << 12) | \
          (indices[i+2] << 6) | indices[i+3]
    
    # 逆推3轮混淆 (反向顺序)
    val = ror24(val ^ 0x55757d20)
    val = ror24(val ^ 0x55757d11)
    val = ror24(val ^ 0x55757d5)
    
    # 24位 → 3字节
    flag_bytes.append((val >> 16) & 0xFF)
    flag_bytes.append((val >> 8) & 0xFF)
    flag_bytes.append(val & 0xFF)

flag = bytes(flag_bytes).decode('ascii')
print(f'Flag: {flag}')

'''
输出内容:
Flag: NCTF{1578be15-ad09-4859-9193-5d52585eb485}
'''

反汇编脚本

脚本已上传至 github,项目地址:

https://github.com/Lucien-404/VM-disassembler

其实整个项目都是 ai 生成的

首先将 vm_execute 发给 ai 生成映射表(这个应该一个本地的小模型就能实现)

接着脚本就能利用映射表自动将字节码反汇编为人能直接阅读的汇编代码

初衷是希望在离线的情况下也能解出虚拟机逆向题(主要是线下比赛),但是我们发现阅读汇编代码也并不是一件简单的事

脚本的使用说明和生成映射表的提示词模板也已经一并上传,目前脚本的普适性还没有得到有效验证,因此邀请大家一起来使用这个工具,如果有任何问题也欢迎各位私信联系我们,我们一起来维护改进这个项目

转载声明:本文转载自原发布平台 (作者:正在思考ing), 原文标题《[NCTF 2026] VM Encryptor(虚拟机逆向)》, 查看原文。 版权归原作者及原发布平台所有,本站仅作收录与展示,未对正文内容作实质性修改; 若涉及侵权请联系本站处理。