[2026 软件系统安全赛华东赛区现场赛] vmp(虚拟机逆向)

作者:正在思考ing 发布:2026-09-09 22:00 收录:2026-09-10 00:25 3 次阅读 约 3445 字
摘要:别看了几个月前的陈年老题
推荐理由:本文涵盖「IDA」、「MCP」、「CTF」等多个主题,重点关注 IDA。
其实这段时间在看湾区杯的题,用 ai 梭了大部分题,赛后想手搓复现发现这题目根本就不是给人类做的,题目描述全英文不说,而且还用了一大堆晦涩难懂的与网安无关的专业词汇,甚至给 ai 接上 ida-pro-mcp 分析还没有直接对二进制文件分析的效果好

现在的 ctf 真有意思 ( ̄ε ̄@)

最近刚好翻到了之前参加线下赛的一道虚拟机逆向,而且还原出来程序逻辑很简单,刚好仔仔细细地分析一下汇编

题目描述


听说VMP的代码保护能力很强大?(提交dart{}内的内容即可)

逆向分析


又是虚拟机逆向,直接来看 main() 函数

main()

_BOOL8 __fastcall main(int a1, char **a2, char **a3)
{
  std::ostream *v3; // rax
  __int64 v4; // rax
  _BYTE *v5; // r12
  __int64 v6; // rdx
  _QWORD *v7; // rbx
  __int64 v8; // rax
  __int64 v9; // rcx
  char *v10; // rdi
  _BOOL4 v11; // r12d
  __int64 (__fastcall *v13)(); // rax
  char v14; // [rsp+Fh] [rbp-149h] BYREF
  void *v15; // [rsp+10h] [rbp-148h] BYREF
  unsigned __int64 v16; // [rsp+18h] [rbp-140h]
  _BYTE v17[16]; // [rsp+20h] [rbp-138h] BYREF
  void *v18; // [rsp+30h] [rbp-128h] BYREF
  unsigned __int64 v19; // [rsp+38h] [rbp-120h]
  __int64 v20; // [rsp+40h] [rbp-118h]
  char v21; // [rsp+48h] [rbp-110h] BYREF
  int v22; // [rsp+84h] [rbp-D4h]
  void *v23; // [rsp+88h] [rbp-D0h]
  __int64 v24; // [rsp+90h] [rbp-C8h]
  __int64 v25; // [rsp+98h] [rbp-C0h]
  __int64 v26; // [rsp+A0h] [rbp-B8h]
  __int64 v27; // [rsp+A8h] [rbp-B0h]
  _QWORD *v28; // [rsp+B0h] [rbp-A8h]
  __int64 v29; // [rsp+B8h] [rbp-A0h]
  __int64 v30; // [rsp+C0h] [rbp-98h]
  __int64 v31; // [rsp+C8h] [rbp-90h]
  _QWORD *v32; // [rsp+D0h] [rbp-88h]
  void *v33[4]; // [rsp+D8h] [rbp-80h] BYREF
  __int16 v34; // [rsp+F8h] [rbp-60h]
  char v35; // [rsp+FAh] [rbp-5Eh]
  void *v36[2]; // [rsp+100h] [rbp-58h] BYREF
  char v37; // [rsp+110h] [rbp-48h] BYREF
  __int64 v38; // [rsp+120h] [rbp-38h]
  unsigned __int64 v39; // [rsp+128h] [rbp-30h]

  v39 = __readfsqword(0x28u);
  v3 = (std::ostream *)std::operator<<<std::char_traits<char>>(&std::cout"Enter key: ", a3);
  std::ostream::flush(v3);
  v17[0] = 0;
  v16 = 0;
  v4 = *(_QWORD *)(std::cin[0] - 24);
  v15 = v17;
  v5 = *(_BYTE **)((char *)&std::cin[30] + v4);
  if ( !v5 )
    std::__throw_bad_cast();
  if ( v5[56] )
  {
    v6 = (unsigned int)(char)v5[67];
  }
  else
  {
    std::ctype<char>::_M_widen_init(*(__int64 *)((char *)&std::cin[30] + v4));
    v6 = 10;
    v13 = *(__int64 (__fastcall **)())(*(_QWORD *)v5 + 48LL);
    if ( v13 != std::ctype<char>::do_widen )
      v6 = (unsigned int)((char (__fastcall *)(_BYTE *, __int64, __int64))v13)(v5, 1010);
  }
  std::getline<char,std::char_traits<char>,std::allocator<char>>(std::cin, &v15, v6);
  if ( v16 <= 0x29 )
    std::string::_M_replace_aux(&v15, v16, 042 - v16, 0);
  LODWORD(v20) = 0;
  v23 = 0;
  v25 = 0;
  v26 = 0;
  v27 = 0;
  v28 = 0;
  v29 = 0;
  v30 = 0;
  v31 = 0;
  v32 = 0;
  v24 = 8;
  v23 = (void *)operator new(0x40u);
  v7 = (char *)v23 + ((4 * v24 - 4) & 0xFFFFFFFFFFFFFFF8LL);
  v8 = operator new(0x200u);
  *v7 = v8;
  v28 = v7;
  v26 = v8;
  v27 = v8 + 512;
  v32 = v7;
  v30 = v8;
  v31 = v8 + 512;
  v25 = v8;
  v29 = v8;
  memset(v33, 024);
  v33[3] = init_proc;
  v34 = 0;
  v35 = 0;
  v36[0] = &v37;
  v36[1] = 0;
  v37 = 0;
  v38 = 0;
  v14 = 0;
  sub_1E60(v33, 04096, &v14);
  v9 = 16;
  v10 = &v21;
  v19 = 1966;
  while ( v9 )
  {
    *(_DWORD *)v10 = 0;
    v10 += 4;
    --v9;
  }
  v18 = &unk_3040;
  v20 = 0xFFC00000000LL;
  std::string::_M_assign(v36, &v15);
  LOBYTE(v34) = 1;
  v38 = 0;
  do
  {
    if ( (unsigned int)v20 >= v19 )
      break;
    sub_1890(&v18);
  }
  while ( (_BYTE)v34 );
  v11 = v22 != 1;
  sub_1DD0(&v18);
  if ( v15 != v17 )
    operator delete(v15);
  return v11;
}

看到 "Enter key: " 基本确定找对位置了,随后定位到下面的一个循环

由于是虚拟机逆向,因此循环里面的 sub_1890() 应该就是在逐字节解释执行字节码

那么 v18,也就是 unk_3040 应该就是实际执行的字节码,而 v19 就应该是字节码的长度(1996 bytes)

跟进分析 sub_1890()

sub_1890()

unsigned __int64 __fastcall sub_1890(__int64 *a1)
{
  unsigned __int64 v2; // rax
  unsigned __int64 v3; // rdi
  __int64 v4; // r8
  unsigned __int64 v5; // rsi
  int v6; // ecx
  unsigned __int8 v7; // al
  int v9; // eax
  unsigned __int64 v10; // rsi
  int v11; // ecx
  unsigned __int8 v12; // si
  __int64 v13; // r9
  unsigned __int64 v14; // rsi
  unsigned __int64 v15; // rax
  __int64 v16; // rsi
  unsigned __int8 v17; // r10
  int v18; // eax
  unsigned __int64 v19; // rax
  int v20; // eax
  unsigned __int64 v21; // rcx
  int v22; // eax
  std::ostream *v23; // rdi
  int v24; // eax
  int v25; // eax
  unsigned __int64 v26; // rsi
  int v27; // ecx
  unsigned __int8 v28; // si
  __int64 v29; // r9
  unsigned __int64 v30; // rsi
  unsigned __int64 v31; // rax
  __int64 v32; // rsi
  unsigned __int8 v33; // r10
  int v34; // eax
  unsigned __int64 v35; // rax
  unsigned __int64 v36; // rax
  int v37; // esi
  int v38; // eax
  unsigned __int8 v39; // cl
  unsigned int v40; // ecx
  unsigned int v41; // eax
  unsigned __int64 v42; // r9
  int v43; // eax
  int v44; // eax
  int v45; // esi
  __int64 v46; // r10
  unsigned __int64 v47; // rcx
  int v48; // eax
  int v49; // r9d
  int v50; // eax
  int v51; // ecx
  char v52; // [rsp+7h] [rbp-11h] BYREF
  unsigned __int64 v53; // [rsp+8h] [rbp-10h]

  v53 = __readfsqword(0x28u);
  v2 = *((unsigned int *)a1 + 4);
  v3 = a1[1];
  if ( v3 <= v2 )
    return v53 - __readfsqword(0x28u);
  v4 = *a1;
  v5 = (unsigned int)(v2 + 1);
  v6 = v2;
  *((_DWORD *)a1 + 4) = v5;
  v7 = *(_BYTE *)(v4 + v2);
  if ( v7 == 107 )
  {
    if ( v3 <= v5 )
    {
      v38 = 0;
      v37 = 0;
    }
    else
    {
      v36 = (unsigned int)(v6 + 2);
      *((_DWORD *)a1 + 4) = v36;
      v37 = *(unsigned __int8 *)(v4 + v5);
      if ( v3 <= v36 )
      {
        v39 = v37;
        v38 = 0;
      }
      else
      {
        *((_DWORD *)a1 + 4) = v6 + 3;
        v38 = *(unsigned __int8 *)(v4 + v36);
        v39 = v37 | v38;
      }
      if ( v39 > 0xFu )
        return v53 - __readfsqword(0x28u);
    }
    v40 = *((_DWORD *)a1 + v37 + 6);
    v41 = *((_DWORD *)a1 + v38 + 6);
    *((_BYTE *)a1 + 201) = v40 == v41;
    *((_BYTE *)a1 + 202) = v40 < v41;
    return v53 - __readfsqword(0x28u);
  }
  if ( v7 <= 0x6Bu )
  {
    if ( v7 != 60 )
    {
      if ( v7 <= 0x3Cu )
      {
        if ( v7 == 25 )
        {
          if ( v3 <= v5 )
          {
            v32 = 0;
            v29 = 0;
            v34 = 0;
          }
          else
          {
            *((_DWORD *)a1 + 4) = v6 + 2;
            v29 = *(unsigned __int8 *)(v4 + v5);
            v30 = (unsigned int)(v6 + 2);
            if ( v3 <= v30 )
            {
              v33 = v29;
              v32 = 0;
              v34 = 0;
            }
            else
            {
              v31 = (unsigned int)(v6 + 3);
              *((_DWORD *)a1 + 4) = v31;
              v32 = *(unsigned __int8 *)(v4 + v30);
              v33 = v29 | v32;
              if ( v3 <= v31 )
              {
                v34 = 0;
              }
              else
              {
                *((_DWORD *)a1 + 4) = v6 + 4;
                v34 = *(unsigned __int8 *)(v4 + v31);
              }
            }
            if ( v33 > 0xFu )
              return v53 - __readfsqword(0x28u);
          }
          v35 = (unsigned int)(*((_DWORD *)a1 + v32 + 6) + v34);
          if ( v35 < a1[24] )
            *((_DWORD *)a1 + v29 + 6) = *(unsigned __int8 *)(a1[21] + v35);
          return v53 - __readfsqword(0x28u);
        }
        if ( v7 == 26 )
        {
          if ( v3 <= v5 )
          {
            v13 = 0;
            v16 = 0;
            v18 = 0;
          }
          else
          {
            *((_DWORD *)a1 + 4) = v6 + 2;
            v13 = *(unsigned __int8 *)(v4 + v5);
            v14 = (unsigned int)(v6 + 2);
            if ( v3 <= v14 )
            {
              v17 = v13;
              v16 = 0;
              v18 = 0;
            }
            else
            {
              v15 = (unsigned int)(v6 + 3);
              *((_DWORD *)a1 + 4) = v15;
              v16 = *(unsigned __int8 *)(v4 + v14);
              v17 = v13 | v16;
              if ( v3 <= v15 )
              {
                v18 = 0;
              }
              else
              {
                *((_DWORD *)a1 + 4) = v6 + 4;
                v18 = *(unsigned __int8 *)(v4 + v15);
              }
            }
            if ( v17 > 0xFu )
              return v53 - __readfsqword(0x28u);
          }
          v19 = (unsigned int)(*((_DWORD *)a1 + v16 + 6) + v18);
          if ( v19 < a1[24] )
            *(_BYTE *)(a1[21] + v19) = *((_DWORD *)a1 + v13 + 6);
          return v53 - __readfsqword(0x28u);
        }
      }
      else if ( v7 == 78 )
      {
        if ( v3 <= v5 )
        {
          v27 = 0;
          v25 = 0;
        }
        else
        {
          *((_DWORD *)a1 + 4) = v6 + 2;
          v25 = *(unsigned __int8 *)(v4 + v5);
          v26 = (unsigned int)(v6 + 2);
          if ( v3 <= v26 )
          {
            v28 = v25;
            v27 = 0;
          }
          else
          {
            *((_DWORD *)a1 + 4) = v6 + 3;
            v27 = *(unsigned __int8 *)(v4 + v26);
            v28 = v25 | *(_BYTE *)(v4 + v26);
          }
          if ( v28 > 0xFu )
            return v53 - __readfsqword(0x28u);
        }
        *((_DWORD *)a1 + v25 + 6) ^= *((_DWORD *)a1 + v27 + 6);
      }
      else if ( v7 == 94 )
      {
        if ( v3 <= v5 )
        {
          v9 = 0;
          v11 = 0;
        }
        else
        {
          *((_DWORD *)a1 + 4) = v6 + 2;
          v9 = *(unsigned __int8 *)(v4 + v5);
          v10 = (unsigned int)(v6 + 2);
          if ( v3 <= v10 )
          {
            v12 = v9;
            v11 = 0;
          }
          else
          {
            *((_DWORD *)a1 + 4) = v6 + 3;
            v11 = *(unsigned __int8 *)(v4 + v10);
            v12 = v9 | *(_BYTE *)(v4 + v10);
          }
          if ( v12 > 0xFu )
            return v53 - __readfsqword(0x28u);
        }
        *((_DWORD *)a1 + v9 + 6) = *((_DWORD *)a1 + v11 + 6);
      }
      return v53 - __readfsqword(0x28u);
    }
    v46 = (unsigned int)v5;
    if ( v3 <= (unsigned int)v5 )
    {
      v47 = (unsigned int)(v6 + 4);
      v49 = 0;
      if ( v3 <= v47 )
      {
        v48 = 0;
        goto LABEL_66;
      }
    }
    else
    {
      LODWORD(v5) = v6 + 2;
      v47 = (unsigned int)(v6 + 5);
      v48 = 0;
      *((_DWORD *)a1 + 4) = v5;
      v49 = *(unsigned __int8 *)(v4 + v46);
      if ( v3 <= v47 )
        goto LABEL_65;
      v46 = (unsigned int)v5;
    }
    v50 = (*(unsigned __int8 *)(v4 + (unsigned int)(v5 + 1)) << 8)
        | *(unsigned __int8 *)(v4 + v46)
        | (*(unsigned __int8 *)(v4 + v47) << 24);
    v51 = *(unsigned __int8 *)(v4 + (unsigned int)(v5 + 2));
    *((_DWORD *)a1 + 4) = v5 + 4;
    v48 = (v51 << 16) | v50;
LABEL_65:
    if ( (unsigned __int8)v49 > 0xFu )
      return v53 - __readfsqword(0x28u);
LABEL_66:
    *((_DWORD *)a1 + v49 + 6) = v48;
    return v53 - __readfsqword(0x28u);
  }
  if ( v7 == 0xE3 )
  {
    v42 = (unsigned int)(v6 + 4);
    v43 = 0;
    if ( v3 > v42 )
    {
      v44 = *(unsigned __int8 *)(v4 + v5)
          | (*(unsigned __int8 *)(v4 + (unsigned int)(v6 + 3)) << 16)
          | (*(unsigned __int8 *)(v4 + (unsigned int)(v6 + 2)) << 8);
      v45 = *(unsigned __int8 *)(v4 + v42);
      *((_DWORD *)a1 + 4) = v6 + 5;
      v43 = (v45 << 24) | v44;
    }
    if ( *((_BYTE *)a1 + 201) )
      *((_DWORD *)a1 + 4) = v43;
    return v53 - __readfsqword(0x28u);
  }
  if ( v7 > 0xE3u )
  {
    if ( v7 == 0xFE )
    {
      v24 = 0;
      if ( v3 > v5 )
      {
        *((_DWORD *)a1 + 4) = v6 + 2;
        v24 = *(unsigned __int8 *)(v4 + v5);
      }
      *((_DWORD *)a1 + 21) = v24;
    }
    else if ( v7 == 0xFF )
    {
      *((_BYTE *)a1 + 200) = 0;
    }
    return v53 - __readfsqword(0x28u);
  }
  if ( v7 != 0xA1 )
  {
    if ( v7 == 0xA3 )
    {
      if ( v3 <= v5 )
      {
        v20 = 0;
LABEL_32:
        v21 = a1[30];
        if ( v21 >= a1[27] )
        {
          *((_DWORD *)a1 + v20 + 6) = 0;
        }
        else
        {
          a1[30] = v21 + 1;
          *((_DWORD *)a1 + v20 + 6) = *(unsigned __int8 *)(a1[26] + v21);
        }
        return v53 - __readfsqword(0x28u);
      }
      *((_DWORD *)a1 + 4) = v6 + 2;
      v20 = *(unsigned __int8 *)(v4 + v5);
      if ( (unsigned __int8)v20 <= 0xFu )
        goto LABEL_32;
    }
    return v53 - __readfsqword(0x28u);
  }
  if ( v3 <= v5 )
  {
    v22 = 0;
  }
  else
  {
    *((_DWORD *)a1 + 4) = v6 + 2;
    v22 = *(unsigned __int8 *)(v4 + v5);
    if ( (unsigned __int8)v22 > 0xFu )
      return v53 - __readfsqword(0x28u);
  }
  v52 = *((_DWORD *)a1 + v22 + 6);
  v23 = (std::ostream *)std::__ostream_insert<char,std::char_traits<char>>(&std::cout, &v52, 1);
  return std::ostream::flush(v23);
}

反编译出来的伪 c 代码是多个 if - else 嵌套形成的 vm 解释器,自定义的 opcode 如下

说明:sub_1890 为一个 16 寄存器(v0–v15)+ 标志位(EQ/LT)+ 运行标志的字节码 VM 分发器。

指令操作码即字节码首字节;寄存器号参数须为 0~15;多字节立即数均为小端序。

Opcode
助记符
操作
编码
0x19
LOAD
以 reg[base]+off 为地址,读取内存 1 字节写入 reg[dst](越界则跳过)
19 <dst:u8> <base:u8> <off:u8>
0x1A
STORE
将 reg[src] 低 8 位写入内存地址 reg[base]+off(越界则跳过)
1A <src:u8> <base:u8> <off:u8>
0x3C
MOVI32
读取 4 字节小端立即数并写入目标寄存器 reg[dst]
3C <dst:u8> <imm:u32>
0x4E
XOR
reg[a] ^= reg[b],结果回写 reg[a]
4E <a:u8> <b:u8>
0x5E
MOV
将 reg[src] 拷贝到 reg[dst]
5E <dst:u8> <src:u8>
0x6B
CMP
比较 reg[a] 与 reg[b],置相等标志 EQ 与小于标志 LT
6B <a:u8> <b:u8>
0xA1
OUT
将 reg[dst] 低 8 位作为字符输出到 stdout 并刷新
A1 <dst:u8>
0xA3
IN
从输入流读取 1 字节写入 reg[dst],输入耗尽则写入 0
A3 <dst:u8>
0xE3
JEQ
若 EQ 标志为真,将程序计数器 pc 置为 4 字节小端目标地址(否则 pc 不变)
E3 <target:u32>
0xFE
MOVI8
读取 1 字节立即数写入固定寄存器 reg[15]
FE <imm:u8>
0xFF
HALT
清除运行标志,终止 VM 执行
FF

有了映射表,再写个 ida python 脚本把字节码 unk_3040,也就是 0x3040 地址处的 1966 bytes 的数据 dump 下来就可以还原程序自定义指令的汇编代码了

需要 dump 数据脚本的朋友可以私信联系我,需要将字节码还原为汇编的脚本的朋友可以看这篇文章

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

其实我可以把工具链接直接贴出来的,但我就是想给之前的文章引个流

( ̄▽ ̄)σ

分析汇编代码


结果很长,这里仅展示关键部分

读取用户输入

ADDR
RAW
INSTRUCTION
ASCII
00003C 03 00 00 00 00
MOVI32 0x03, 0x00000000
<.....
0006A3 04
IN 0x04
..
00081A 04 03 00
STORE 0x04, 0x03, 0x00
....
000CA3 04
IN 0x04
..
000E1A 04 03 01
STORE 0x04, 0x03, 0x01
....
0012A3 04
IN 0x04
..
00141A 04 03 02
STORE 0x04, 0x03, 0x02
....
0018A3 04
IN 0x04
..

刚开始是向 0x03 寄存器中写入 0,应该只是一步初始化

随后一直到 0xFE 都是下面的循环:

  1. 通过 IN 指令读取 1 byte 的用户输入存储到 0x04 寄存器
  2. 通过 STORE 指令读取 0x04 寄存器的低 8 位存储在内存 0x03 地址后的指定偏移处

简单来说就是读取并存储用户输入

异或加密

这一段也是一个循环,这里仅展示前 3 个循环

ADDR
RAW
INSTRUCTION
ASCII
01023C 09 01 00 00 00
MOVI32 0x09, 0x00000001
<.....
010819 0A 03 00
LOAD 0x0A, 0x03, 0x00
....
010C3C 0B 6C 00 00 00
MOVI32 0x0B, 0x0000006C
<.l...
01124E 0A 0B
XOR 0x0A, 0x0B
N..
01153C 0B 08 00 00 00
MOVI32 0x0B, 0x00000008
<.....
011B6B 0A 0B
CMP 0x0A, 0x0B
k..
011EE3 29 01 00 00
JEQ 0x00000129
.)...




01233C 09 00 00 00 00
MOVI32 0x09, 0x00000000
<.....
012919 0A 03 01
LOAD 0x0A, 0x03, 0x01
....
012D3C 0B B4 00 00 00
MOVI32 0x0B, 0x000000B4
<.....
01334E 0A 0B
XOR 0x0A, 0x0B
N..
01363C 0B D5 00 00 00
MOVI32 0x0B, 0x000000D5
<.....
013C6B 0A 0B
CMP 0x0A, 0x0B
k..
013FE3 4A 01 00 00
JEQ 0x0000014A
.J...




01443C 09 00 00 00 00
MOVI32 0x09, 0x00000000
<.....
014A19 0A 03 02
LOAD 0x0A, 0x03, 0x02
....
014E3C 0B 7C 00 00 00
MOVI32 0x0B, 0x0000007C
<.|...
01544E 0A 0B
XOR 0x0A, 0x0B
N..
01573C 0B 0E 00 00 00
MOVI32 0x0B, 0x0000000E
<.....
015D6B 0A 0B
CMP 0x0A, 0x0B
k..
0160E3 6B 01 00 00
JEQ 0x0000016B
.k...

一直到 0x667 都是如下循环:

  1. 通过 MOVI32 指令向 0x09 寄存器中存储 0 或 1(除了第一个循环是 1,其他都是 0,在这里这个寄存器只是一个标志位
  2. 通过 LOAD 指令读取内存 0x03 地址指定偏移处的值(也就是刚刚读取的用户输入)加载到 0x0A 寄存器中
  3. 通过 MOVI32 指令加载一个指定的异或值到 0x0B 寄存器中
  4. 通过 XOR 指令异或 0x0A 和 0x0B 寄存器中的值,并将结果存储到 0x0A 寄存器中
  5. 通过 MOVI32 指令加载一个指定的目标值到 0x0B 寄存器中
  6. 通过 CMP 指令比较 0x0A 寄存器中的异或结果和 0x0B 寄存器中的目标值
  7. 通过 JEQ 指令,若结果不相同则直接跳转至下一个循环的第 2 步,否则执行第 1 步将标志位0x09 寄存器中的值置为 0

简单来说就是将用户输入与内置的字符串逐字节异或,并将结果与目标字符串比较

判断

为了便于理解,把最后一个循环也贴出来

ADDR
RAW
INSTRUCTION
ASCII
064B3C 09 00 00 00 00
MOVI32 0x09, 0x00000000
<.....
065119 0A 03 29
LOAD 0x0A, 0x03, 0x29
...)
06553C 0B 3A 00 00 00
MOVI32 0x0B, 0x0000003A
<.:...
065B4E 0A 0B
XOR 0x0A, 0x0B
N..
065E3C 0B 47 00 00 00
MOVI32 0x0B, 0x00000047
<.G...
06646B 0A 0B
CMP 0x0A, 0x0B
k..
0667E3 72 06 00 00
JEQ 0x00000672
.r...




066C3C 09 00 00 00 00
MOVI32 0x09, 0x00000000
<.....
06723C 0D 01 00 00 00
MOVI32 0x0D, 0x00000001
<.....
06786B 09 0D
CMP 0x09, 0x0D
k..
067BE3 E3 06 00 00
JEQ 0x000006E3
.....

执行完最后一个循环的 CMP 后,若结果不同则将 0x09 寄存器置 0,若结果相同则不变

如此实现了只要有一个字节不一样,标志位 0x09 寄存器就会被置为 0,否则保持为 1

随后通过 MOVI32 指令将 0x0D 寄存器置为 1

最后的 CMP 指令:

  • 若标志位 0x09 寄存器与 0x0D 寄存器相等(即 0x09 寄存器为 1,每个字符都相同),则跳转至 0x6E3 输出 Correct! Flag verified.
  • 若标志位 0x09 寄存器与 0x0D 寄存器不相等(即 0x09 寄存器为 0,存在不相同的字符),则不跳转,直接输出 Wrong key!

解密


解密很简单,把目标值与内置字符串逐字节异或就能拿到 flag

key = [0x6c0xb40x7c0xfc0x070x960x2a0x920x600x3f]
obj = [0x080xd50x0e0x880x7c0xae0x1a0xf00x500x07]

for a, b in zip(obj, key):
    print(chr(a ^ b), end='')
    
'''
输出内容:
dart{80b08
'''

这里我只把内置字符串和目标字符串的前几个字符抄了下来,懒得全抄了

这两个字符串并不是连续分布在字节码中的,所以不好直接 dump,估计只能通过正则匹配一个一个提取出来了

反正从输出内容也能看出思路没问题,现场比赛的时候老老实实一个一个抄就行了哈哈哈

╮( ̄▽ ̄)╭

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