题目描述


听说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, 10, 10);
  }
  std::getline<char,std::char_traits<char>,std::allocator<char>>(std::cin, &v15, v6);
  if ( v16 <= 0x29 )
    std::string::_M_replace_aux(&v15, v16, 0, 42 - 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, 0, 24);
  v33[3] = init_proc;
  v34 = 0;
  v35 = 0;
  v36[0] = &v37;
  v36[1] = 0;
  v37 = 0;
  v38 = 0;
  v14 = 0;
  sub_1E60(v33, 0, 4096, &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助记符操作编码
0x19LOAD以 reg[base]+off 为地址,读取内存 1 字节写入 reg[dst](越界则跳过)19 <dst:u8> <base:u8> <off:u8>
0x1ASTORE将 reg[src] 低 8 位写入内存地址 reg[base]+off(越界则跳过)1A <src:u8> <base:u8> <off:u8>
0x3CMOVI32读取 4 字节小端立即数并写入目标寄存器 reg[dst]3C <dst:u8> <imm:u32>
0x4EXORreg[a] ^= reg[b],结果回写 reg[a]4E <a:u8> <b:u8>
0x5EMOV将 reg[src] 拷贝到 reg[dst]5E <dst:u8> <src:u8>
0x6BCMP比较 reg[a] 与 reg[b],置相等标志 EQ 与小于标志 LT6B <a:u8> <b:u8>
0xA1OUT将 reg[dst] 低 8 位作为字符输出到 stdout 并刷新A1 <dst:u8>
0xA3IN从输入流读取 1 字节写入 reg[dst],输入耗尽则写入 0A3 <dst:u8>
0xE3JEQ若 EQ 标志为真,将程序计数器 pc 置为 4 字节小端目标地址(否则 pc 不变)E3 <target:u32>
0xFEMOVI8读取 1 字节立即数写入固定寄存器 reg[15]FE <imm:u8>
0xFFHALT清除运行标志,终止 VM 执行FF

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

分析汇编代码


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

读取用户输入

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

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

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

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

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

异或加密

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

ADDRRAWINSTRUCTIONASCII
01023C 09 01 00 00 00MOVI32 0x09, 0x00000001<.....
010819 0A 03 00LOAD 0x0A, 0x03, 0x00....
010C3C 0B 6C 00 00 00MOVI32 0x0B, 0x0000006C<.l...
01124E 0A 0BXOR 0x0A, 0x0BN..
01153C 0B 08 00 00 00MOVI32 0x0B, 0x00000008<.....
011B6B 0A 0BCMP 0x0A, 0x0Bk..
011EE3 29 01 00 00JEQ 0x00000129.)...
01233C 09 00 00 00 00MOVI32 0x09, 0x00000000<.....
012919 0A 03 01LOAD 0x0A, 0x03, 0x01....
012D3C 0B B4 00 00 00MOVI32 0x0B, 0x000000B4<.....
01334E 0A 0BXOR 0x0A, 0x0BN..
01363C 0B D5 00 00 00MOVI32 0x0B, 0x000000D5<.....
013C6B 0A 0BCMP 0x0A, 0x0Bk..
013FE3 4A 01 00 00JEQ 0x0000014A.J...
01443C 09 00 00 00 00MOVI32 0x09, 0x00000000<.....
014A19 0A 03 02LOAD 0x0A, 0x03, 0x02....
014E3C 0B 7C 00 00 00MOVI32 0x0B, 0x0000007C<.|...
01544E 0A 0BXOR 0x0A, 0x0BN..
01573C 0B 0E 00 00 00MOVI32 0x0B, 0x0000000E<.....
015D6B 0A 0BCMP 0x0A, 0x0Bk..
0160E3 6B 01 00 00JEQ 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

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

判断

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

ADDRRAWINSTRUCTIONASCII
064B3C 09 00 00 00 00MOVI32 0x09, 0x00000000<.....
065119 0A 03 29LOAD 0x0A, 0x03, 0x29...)
06553C 0B 3A 00 00 00MOVI32 0x0B, 0x0000003A<.:...
065B4E 0A 0BXOR 0x0A, 0x0BN..
065E3C 0B 47 00 00 00MOVI32 0x0B, 0x00000047<.G...
06646B 0A 0BCMP 0x0A, 0x0Bk..
0667E3 72 06 00 00JEQ 0x00000672.r...
066C3C 09 00 00 00 00MOVI32 0x09, 0x00000000<.....
06723C 0D 01 00 00 00MOVI32 0x0D, 0x00000001<.....
06786B 09 0DCMP 0x09, 0x0Dk..
067BE3 E3 06 00 00JEQ 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 = [0x6c, 0xb4, 0x7c, 0xfc, 0x07, 0x96, 0x2a, 0x92, 0x60, 0x3f]
obj = [0x08, 0xd5, 0x0e, 0x88, 0x7c, 0xae, 0x1a, 0xf0, 0x50, 0x07]

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

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

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

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