flag: flag{QeMu_r3v3rs3in9_h@ck6}

mips_bin分析

先用ida打开mips_bin文件,通过搜索字符串找到ftext函数。

int __cdecl ftext(int argc, const char **argv, const char **envp)
{
  int *v3; // $v0
  int *v4; // $s0
  _BYTE *v5; // $a0
  int i; // $v1
  char v7; // $a1
  int v8; // $s2
  int v9; // $s0
  int *v11; // $v0
  int *v12; // $v1
  int v13; // $a2
  int v14; // $a1
  int v15; // $a0
  int v16; // $v1
  int v17; // $v0
  int v18; // $v1
  int v19; // $v0
  int flag[8]; // [sp+20h] [-38h] BYREF
  int v21[6]; // [sp+40h] [-18h] BYREF

  qmemcpy(v21, "sxrujtv`labiVzbp`vpg|", 21);
  memset(flag, 0, sizeof(flag));
  v3 = (int *)mmap((void *)0x23000, 0x1000u, 7, 2050, -1, 0);
  v4 = 0;
  if ( v3 != (int *)-1 )
    v4 = v3;
  v5 = &opcodes;
  for ( i = 0; i != 96; ++i )
  {
    v7 = i & 3 ^ *v5;
    *v5++ = v7;
  }
  v8 = fork();
  if ( v8 )
  {
    do
    {
      v9 = waitpid(v8, 0, 1);
      sleep(1);
    }
    while ( !v9 );
  }
  else
  {
    v11 = (int *)&opcodes;
    v12 = v4;
    do
    {
      v13 = v11[1];
      v14 = v11[2];
      v15 = v11[3];
      *v12 = *v11;
      v12[1] = v13;
      v12[2] = v14;
      v12[3] = v15;
      v11 += 4;
      v12[3] = v15;
      v12 += 4;
    }
    while ( v11 != &stdio_user_locking );
    v16 = v21[1];
    v4[512] = v21[0];
    v4[513] = v16;
    v17 = v21[2];
    v4[513] = v16;
    v4[514] = v17;
    v18 = v21[3];
    v4[514] = v17;
    v4[515] = v18;
    v19 = v21[4];
    v4[515] = v18;
    v4[516] = v19;
    *((_BYTE *)v4 + 2068) = HIBYTE(v21[5]);
    puts("input your flag, be fast:");
    read(0, flag, 32);
    if ( strlen(flag) != 22 )
      write(1, &unk_40EE5C, 4);
    if ( ((int (__fastcall *)(int *))v4)(flag) )
      write(1, "wrong\n", 7);
    else
      write(1, "right\n", 7);
  }
  return 0;
}

fake flag

这里就是通过创建子进程对opcode进行解密,然后再调用opcode的函数来检验flag。这里编写一个脚本来解密opcode,或者动调调试查看check函数。

import idc   
ea_start=0x00000430010
for i in range(96):
    tmp = idc.get_bytes(ea_start+i,1)
    tmp = i & 3 ^ ord(tmp)
    idc.patch_byte(ea_start+i,tmp)
print("ok")

解密完成后查看反汇编。

.data:00430010                               .globl opcodes
.data:00430010                               opcodes:                                 # DATA XREF: _ftext+B4↑o
.data:00430010 00 80 40 25                   move    $t0, $a0
.data:00430014 3C 09 00 02 35 29 38 00       li      $t1, 0x23800                     # Load Immediate
.data:0043001C 24 0A 00 15                   li      $t2, 0x15                        # Load Immediate
.data:00430020 24 0B 00 00                   li      $t3, 0                           # Load Immediate
.data:00430024 11 60 00 06                   beqz    $t3, loc_430040                  # Branch on Zero
.data:00430028 00 00 00 00                   nop
.data:00430028
.data:0043002C
.data:0043002C                               loc_43002C:
.data:0043002C 21 29 00 01                   addi    $t1, 1                           # Add Immediate
.data:00430030 21 08 00 01                   addi    $t0, 1                           # Add Immediate
.data:00430034 21 4A FF FF                   addi    $t2, -1                          # Add Immediate
.data:00430038 11 40 00 0A                   beqz    $t2, loc_430064                  # Branch on Zero
.data:0043003C 00 00 00 00                   nop
.data:0043003C
.data:00430040
.data:00430040                               loc_430040:
.data:00430040 01 8C 60 26                   xor     $t4, $t4                         # Exclusive OR
.data:00430044 01 AD 68 26                   xor     $t5, $t5                         # Exclusive OR
.data:00430048 81 2C 00 00                   lb      $t4, 0($t1)                      # Load Byte
.data:0043004C 81 0D 00 00                   lb      $t5, 0($t0)                      # Load Byte
.data:00430050 01 8A 60 26                   xor     $t4, $t2                         # Exclusive OR
.data:00430054 01 AC 68 26                   xor     $t5, $t4                         # Exclusive OR
.data:00430058 11 A0 FF F4                   beqz    $t5, loc_43002C                  # Branch on Zero
.data:0043005C 00 00 00 00                   nop
.data:0043005C
.data:00430060 24 0B 00 01                   li      $t3, 1                           # Load Immediate
.data:00430064
.data:00430064                               loc_430064:
.data:00430064 01 60 10 25                   move    $v0, $t3
.data:00430068 03 E0 00 08                   jr      $ra                              # Jump Register
.data:0043006C 00 00 00 00                   nop

根据汇编写解密脚本。

enc = b"sxrujtv`labiVzbp`vpg|"
flag = ""
for i in range(21):
    flag += chr(enc[i] ^ (21-i))
print(flag)

解出来flag是flag{dynamic_reverse},很显然是个fake flag,提示要动态调试,直接运行mips_bin文件输入这个flag会回显right,但是使用给的emu虚拟机来运行mips_bin就会提示wrong。
发现这个地方mmap映射了一个固定内存。

emu分析

在emu程序中搜索0x23000常量,定位到0x33D920位置,发现上下一片红,简单去一下花指令。

__int64 __fastcall sub_33D8E0(__int64 a1)
{
  __int64 result; // rax
  int v2; // [rsp+10h] [rbp-20h]
  int i; // [rsp+14h] [rbp-1Ch]
  int j; // [rsp+18h] [rbp-18h]
  __int64 v5; // [rsp+20h] [rbp-10h]
  __int64 v6; // [rsp+28h] [rbp-8h]

  v5 = *(_QWORD *)(a1 + 528);
  v2 = 0;
  result = *(unsigned int *)(v5 + 128);
  if ( *(_DWORD *)(v5 + 128) == 0x23000 )
  {
    result = (unsigned int)dword_C32318;
    if ( dword_C32318 )
    {
      v6 = sub_33D48E(&unk_C33280);
      for ( i = 0; i <= 21; ++i )
        *(_BYTE *)(i + v6) ^= dword_C32324;
      swap(v6, 7LL, 11LL);
      result = swap(v6, 12LL, 16LL);
      for ( j = 0; j <= 21; ++j )
      {
        result = enc[j];
        if ( *(unsigned __int8 *)(j + v6) != (_DWORD)result )
        {
          v2 = 1;
          break;
        }
      }
      if ( !v2 && j == 22 )
        dword_C3231C = 1;
    }
  }
  return result;
}

主要的加密在sub_33D48E函数内,再简单去一些花指令。可以看出是一个魔改的rc4。

__int64 __fastcall sub_33D48E(__int64 a1)
{
  unsigned int v1; // edx
  __int64 result; // rax
  unsigned __int8 v3; // [rsp+15h] [rbp-17Bh]
  int i; // [rsp+18h] [rbp-178h]
  int v5; // [rsp+1Ch] [rbp-174h]
  unsigned int v6; // [rsp+20h] [rbp-170h]
  int v7; // [rsp+24h] [rbp-16Ch]
  int v8; // [rsp+28h] [rbp-168h]
  int j; // [rsp+2Ch] [rbp-164h]
  int v10; // [rsp+30h] [rbp-160h]
  int v11; // [rsp+34h] [rbp-15Ch]
  int v12; // [rsp+3Ch] [rbp-154h]
  const char *v13; // [rsp+40h] [rbp-150h]
  __int64 v14; // [rsp+48h] [rbp-148h]
  __int64 v15[32]; // [rsp+80h] [rbp-110h] BYREF
  __int16 v16; // [rsp+180h] [rbp-10h]
  unsigned __int64 v17; // [rsp+188h] [rbp-8h]

  v17 = __readfsqword(0x28u);
  memset(v15, 0, sizeof(v15));
  v16 = 0;
  for ( i = 0; i <= 255; ++i )
    *((_BYTE *)v15 + i) = i;
  v5 = 0;
  v6 = 0;
  v13 = "6105t3";
  do
  {
    v10 = *((unsigned __int8 *)v15 + v6);
    v11 = (unsigned __int8)(v13++)[(int)(2 * (v6 / 6 - (((2863311531u * (unsigned __int64)v6) >> 32) & 0xFFFFFFFC)))];
    v5 += v10 + v11;
    v1 = v6++;
    *((_BYTE *)v15 + v1) = *((_BYTE *)v15 + (unsigned __int8)v5);
    *((_BYTE *)v15 + (unsigned __int8)v5) = v10;
  }
  while ( v6 != 256 );
  v7 = 0;
  v8 = 0;
  v14 = sub_5C6D90(256LL);
  for ( j = 0; j != 22; ++j )
  {
    v12 = *((unsigned __int8 *)v15 + (unsigned __int8)++v7);
    v8 += v12;
    *((_BYTE *)v15 + (unsigned __int8)v7) = *((_BYTE *)v15 + (unsigned __int8)v8);
    *((_BYTE *)v15 + (unsigned __int8)v8) = v12;
    v3 = ((((unsigned __int8)(*(_BYTE *)(j + 5 + a1) << 7) | (*(_BYTE *)(j + 5 + a1) >> 1)) << 6) ^ 0xC0 | ((unsigned __int8)((*(_BYTE *)(j + 5 + a1) << 7) | (*(_BYTE *)(j + 5 + a1) >> 1)) >> 2) ^ 0x3B) ^ 0xBE;
    *(_BYTE *)(j + v14) = *((_BYTE *)v15 + (unsigned __int8)(*((_BYTE *)v15 + (unsigned __int8)v7) + v12)) ^ key[j & 3] ^ (((unsigned __int8)(((16 * (((32 * v3) | (v3 >> 3)) ^ 0xAD)) | ((unsigned __int8)(((32 * v3) | (v3 >> 3)) ^ 0xAD) >> 4)) ^ 0xDE) >> 5) | (8 * (((16 * (((32 * v3) | (v3 >> 3)) ^ 0xAD)) | ((unsigned __int8)(((32 * v3) | (v3 >> 3)) ^ 0xAD) >> 4)) ^ 0xDE)));
  }
  result = v14;
  if ( v17 != __readfsqword(0x28u) )
    sub_616930();
  return result;
}

exp

对xor的值和输入的每个字符爆破。

def rc4_main(key, message):
    s_box = rc4_init_sbox(key)
    crypt = rc4_excrypt(message, s_box)
    return crypt

def rc4_init_sbox(key):
    s_box = list(range(256))
    j = 0
    for i in range(256):
        j = (j + s_box[i] + ord(key[(i % len(key))])) % 256
        s_box[i], s_box[j] = s_box[j], s_box[i]
    return s_box

def rc4_excrypt(plain, box):
    res = []
    i = j = 0
    for s in plain:
        i = (i + 1) % 256
        j = (j + box[i]) % 256
        box[i], box[j] = box[j], box[i]
        for t in range(0x7f):
            v3 = (((((t << 7) & 0xff) | (t >> 1)) << 6) ^ 0xC0 | ((((t << 7) | (t >> 1)) & 0xff) >> 2) ^ 0x3B) ^ 0xBE
            v3 &= 0xff
            v4 = box[(box[i] + box[j]) & 0xff] ^ ((((((16 * (((32 * v3) | (v3 >> 3)) ^ 0xAD)) | (((((32 * v3) | (v3 >> 3)) ^ 0xAD) & 0xff) >> 4)) ^ 0xDE) & 0xff) >> 5) | (8 * (((16 * (((32 * v3) | (v3 >> 3)) ^ 0xAD)) | (((((32 * v3) | (v3 >> 3)) ^ 0xAD) & 0xff) >> 4)) ^ 0xDE)))
            v4 &= 0xff
            if v4 == s:
                res.append(t)
                break
    return res

key = [0xDE, 0xAD, 0xBE, 0xEF]

rc4_key = "6105t3"

for k in range(0xff):
    enc = [0xC4, 0xEE, 0x3C, 0xBB, 0xE7, 0xFD, 0x67, 0x1D, 0xF8, 0x97, 0x68, 0x9D, 0xB, 0x7F, 0xC7, 0x80, 0xDF, 0xF9, 0x4B, 0xA0, 0x46, 0x91]
    enc[7], enc[11] = enc[11], enc[7]
    enc[12], enc[16] = enc[16], enc[12]
    for i in range(len(enc)):
        enc[i] ^= key[i&3]  
    for i in range(len(enc)):
        enc[i] ^= k
    flag = rc4_main(rc4_key, enc)
    if len(flag) == 22 and flag[-1] == ord('}'):
        print(bytes(flag))

前面一部分可以在加密函数的参数的交叉引用中找到