[Reverse] UPX脱壳 + 异或逆向解题

PE文件脱壳 + 硬编码异或算法逆推

Reverse / UPX脱壳 / IDA / 异或加密 难度:中等

0x00 题目初步分析

运行 exe 提示输入 flag,直接使用 GDB 查看文件信息,判断是否加壳。

0x01 GDB 查壳

使用 GDB 加载程序,查看文件信息:

gdb ./moe.exe
info file

回显发现 UPX 加壳:

Symbols from "/workspaces/untrammeled0107_writeups/test/moe.exe".
Local exec file:
        `/workspaces/untrammeled0107_writeups/test/moe.exe', file type pei-x86-64.
        Entry point: 0x14000a260
        0x0000000140001000 - 0x0000000140009000 is UPX0
        0x0000000140009000 - 0x000000014000a600 is UPX1
        0x000000014000b000 - 0x000000014000b600 is .rsrc

0x02 UPX 脱壳

退出 GDB,使用 UPX 命令脱壳:

exit
upx -d moe.exe -o moe_attack.exe

脱壳成功回显:

untrammeled@laptop-fpsmp3t:/mnt/c/Users/HP/Downloads/moe$ upx -d moe.exe -o moe_attack.exe
                       Ultimate Packer for eXecutables
                          Copyright (C) 1996 - 2024
UPX 4.2.2       Markus Oberhumer, Laszlo Molnar & John Reiser    Jan 3rd 2024

        File size         Ratio      Format      Name
   --------------------   ------   -----------   -----------
     12288 <-      8192   66.67%    win64/pe     moe_attack.exe

Unpacked 1 file.

0x03 IDA 反编译分析

脱壳后丢入 IDA,直接查看 main 函数核心逻辑:

int __fastcall main(int argc, const char **argv, const char **envp)
{
  FILE *v3; // rax
  __int64 v4; // rcx
  __int64 v5; // rax
  int v6; // r9d
  __int64 v7; // r8
  char v8; // dl
  _OWORD v10[8]; // [rsp+20h] [rbp-148h]
  int v11; // [rsp+A0h] [rbp-C8h]
  int v12; // [rsp+A4h] [rbp-C4h]
  int v13; // [rsp+A8h] [rbp-C0h]
  char v14[48]; // [rsp+B0h] [rbp-B8h]
  char Buffer[112]; // [rsp+E0h] [rbp-88h] BYREF

  v10[0] = _mm_load_si128((const __m128i *)&xmmword_1400032E0);
  v10[1] = _mm_load_si128((const __m128i *)&xmmword_140003310);
  v10[2] = _mm_load_si128((const __m128i *)&xmmword_140003320);
  v10[3] = _mm_load_si128((const __m128i *)&xmmword_1400032F0);
  v10[4] = _mm_load_si128((const __m128i *)&xmmword_1400032D0);
  v10[5] = _mm_load_si128((const __m128i *)&xmmword_1400032B0);
  v10[6] = _mm_load_si128((const __m128i *)&xmmword_140003300);
  v10[7] = _mm_load_si128((const __m128i *)&xmmword_1400032C0);
  v11 = 41;
  v12 = 36;
  v13 = 86;
  sub_140001010("please input your flag: ");
  v3 = _acrt_iob_func(0);
  fgets(Buffer, 100, v3);
  v4 = -1;
  do
    ++v4;
  while ( Buffer[v4] );
  v5 = 0;
  v6 = 0;
  if ( (int)v4 > 0 )
  {
    v7 = 0;
    do
    {
      v8 = Buffer[v7] ^ 0x21;
      if ( v6 < (int)v4 - 1 )
        v8 ^= Buffer[v7 + 1];
      v14[v7] = v8;
      ++v6;
      ++v7;
    }
    while ( v7 < (int)v4 );
  }
  while ( v14[v5] == *((_DWORD *)v10 + v5) )
  {
    if ( ++v5 >= 35 )
      return 0;
  }
  sub_140001010("you will never get the flag!!!!\n");
  return 0;
}

0x04 加密算法分析

核心加密逻辑:

if ( (int)v4 > 0 )
{
v7 = 0;
do
{
v8 = Buffer[v7] ^ 0x21;
if ( v6 < (int)v4 - 1 )
v8 ^= Buffer[v7 + 1];
v14[v7] = v8;
++v6;
++v7;
}
while ( v7 < (int)v4 );
}

规则:
第 i 位:out[i] = in[i] ^ 0x21 ^ in[i+1]
最后一位:out[i] = in[i] ^ 0x21
处理后与硬编码数组 v10 比较,完全相等输出 flag。

0x05 提取硬编码密文

从 IDA 数据段提取全部 35 字节密文,整理为数组:

enc=[
    0x23, 0x2b, 0x27, 0x36, 0x33, 0x3c, 0x03, 0x48,
    0x64, 0x0b, 0x1d, 0x76, 0x7b, 0x10, 0x0b, 0x3a,
    0x3f, 0x65, 0x76, 0x29, 0x15, 0x37, 0x1c, 0x0a,
    0x08, 0x21, 0x3e, 0x3c, 0x3d, 0x16, 0x0b, 0x24,
    0x29, 0x24, 0x56, 43			
]

0x06 Python 逆推 EXP

先全体异或 0x21,再从后往前逆异或:

enc=[
    0x23, 0x2b, 0x27, 0x36, 0x33, 0x3c, 0x03, 0x48,
    0x64, 0x0b, 0x1d, 0x76, 0x7b, 0x10, 0x0b, 0x3a,
    0x3f, 0x65, 0x76, 0x29, 0x15, 0x37, 0x1c, 0x0a,
    0x08, 0x21, 0x3e, 0x3c, 0x3d, 0x16, 0x0b, 0x24,
    0x29, 0x24, 0x56, 43			
]
xored=[]
for i in range(len(enc)):
    xored.append(enc[i]^0x21)
for i in range(len(xored)-2,-1,-1):
    xored[i]=xored[i]^xored[i+1]
flag=''.join(chr(i) for i in xored)
print(flag)

0x07 Flag

moectf{Y0u_c4n_unp4ck_It_vvith_upx}