美文网首页
[rctf_2018] simple_vm

[rctf_2018] simple_vm

作者: pu1p | 来源:发表于2018-05-24 22:44 被阅读24次

simple_vm

0x00 背景

pwn实在是pwn不动, 于是转来看其它题. 因为这道题解出来的人比较多. 所以就选择了这题. 虽然比赛的时候没有做出来, 不过之后还是独立解出来了. 记录一下. 期间要感谢肖神提供的指导.

0x01 程序分析

这个程序自己定义了一些指令, 然后以此为基础实现了一个解释器. 程序从p.bin中读取指令, 然后通过程序解释并执行指令

  1. main函数:
signed __int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
  FILE *v3; // rax
  const char *v4; // rdi
  FILE *v5; // rbx
  size_t v6; // rbp
  void *v8; // rax

  v3 = fopen("p.bin", "rb");
  v4 = "err 0";
  if ( !v3 )
    goto LABEL_4;
  v5 = v3;
  fseek(v3, 0LL, 2);
  dword_601090 = ftell(v5);
  fseek(v5, 0LL, 0);
  v6 = dword_601090;
  if ( dword_601090 <= 0 )
  {
    v4 = "err 1";
LABEL_4:
    puts(v4);
    return 0xFFFFFFFFLL;
  }
  v8 = malloc(dword_601090);
  ptr = v8;          // ptr 为全局变量
  v4 = "err 3";
  if ( !v8 )
    goto LABEL_4;
  v4 = "err 4";
  if ( dword_601090 != fread(v8, 1uLL, v6, v5) ) //将p.bin中的指令读到malloc出来的堆中
    goto LABEL_4;
  fclose(v5);
  v4 = "err 5";
  if ( (unsigned int)sub_400896() ) //执行解析函数
    goto LABEL_4;
  free(ptr);
  return 0LL;
}
  1. 解析函数:
__int64 sub_400896()
{
  __int64 PC; // rax
  _BYTE *v1; // rbp
  int v2; // ebx
  __int64 v4; // rdx
  __int64 v5; // rax
  __int64 v6; // rax
  __int64 v7; // rax
  __int64 v8; // rax
  __int64 v9; // rax
  int v10; // eax
  __int64 v11; // rax
  char v12; // dl
  int v13; // eax
  int v14; // eax
  _BYTE *v15; // rax
  __int64 v16; // rax
  __int64 v17; // rax
  __int64 v18; // rax

  PC = 0LL;
  v1 = ptr;
  while ( 1 )
  {
    v2 = PC + 1;
    switch ( v1[PC] )
    {
      case 0:
        return *(unsigned int *)&v1[v2];
      case 1:
        goto LABEL_35;
      case 2:
        v4 = v2;
        v2 = PC + 9;
        v1[*(signed int *)&v1[v4]] = *(_DWORD *)&v1[(signed int)PC + 5];
        break;
      case 3:
        v5 = v2;
        v2 += 4;
        v6 = *(signed int *)&v1[v5];
        goto LABEL_27;
      case 4:
        v7 = v2;
        v2 += 4;
        v8 = *(signed int *)&v1[v7];
        goto LABEL_31;
      case 5:
        v9 = v2;
        v2 += 4;
        v10 = (char)v1[*(signed int *)&v1[v9]];
        goto LABEL_21;
      case 6:
        v11 = v2;
        v12 = val;
        v2 += 4;
        v8 = *(signed int *)&v1[v11];
        goto LABEL_9;
      case 7:
        v13 = val;
        goto LABEL_23;
      case 8:
        v14 = ~(val & char);
        goto LABEL_12;
      case 0xA:
        v14 = getchar();
        goto LABEL_12;
      case 0xB:
        putchar(char);
        break;
      case 0xC:
        v15 = &v1[*(signed int *)&v1[v2]];
        if ( *v15 )
        {
          v2 = *(_DWORD *)&v1[v2 + 4];
          --*v15;
        }
        else
        {
          v2 += 8;
        }
        break;
      case 0xD:
        ++char;
        break;
      case 0xE:
        ++val;
        break;
      case 0xF:
        v14 = val;
        goto LABEL_12;
      case 0x10:
        v10 = char;
        goto LABEL_21;
      case 0x11:
        v16 = v2;
        v2 += 4;
        v13 = *(_DWORD *)&v1[v16];
LABEL_23:
        char += v13;
        break;
      case 0x12:
        v6 = val;
        goto LABEL_27;
      case 0x13:
        v6 = char;
LABEL_27:
        v14 = (char)v1[v6];
        goto LABEL_12;
      case 0x14:
        v17 = v2;
        v2 += 4;
        v14 = *(_DWORD *)&v1[v17];
        goto LABEL_12;
      case 0x15:
        v18 = v2;
        v2 += 4;
        v10 = *(_DWORD *)&v1[v18];
LABEL_21:
        val = v10;
        break;
      case 0x16:
        v8 = val;
LABEL_31:
        v12 = char;
LABEL_9:
        v1[v8] = v12;
        break;
      case 0x17:
        v14 = char - val;
LABEL_12:
        char = v14;
        break;
      case 0x18:
        if ( char )
LABEL_35:
          v2 = *(_DWORD *)&v1[v2];
        else
          v2 = PC + 5;
        break;
      default:
        break;
    }
    if ( v2 >= dword_601090 )
      return 0LL;
    PC = v2;
  }
}

解释器就是一堆switch case 语句, 因为是第一次遇见这种题目, 所以完全是一脸懵逼, 不过好在有肖神指导: 一般这种题目就是先分析每一条指令的意义, 然后根据分析出来的结果来对照指令文件推测程序的运行. 等弄清楚程序的流程之后就可以抽象了.

指令分析:

就是体力活, 感觉也没啥技巧.....熟能生巧吧.....(结合动态调试可以提高效率)
得到各指令大意如下:

switch ops[pc]:
  case 0x00: return ops[pc + 1]
  case 0x01: pc = ops[pc + 1]
  case 0x02: ops[ops[pc + 1]] = ops[pc = 5] pc+=9
  case 0x03: char = ops[ops[pc+1]]  pc += 5
  case 0x04: ops[ops[pc + 1]] = char pc += 5
  case 0x05: val = ops[ops[pc + 1]] pc += 5
  case 0x06: ops[ops[pc + 1]] = val pc += 5
  case 0x07: char += val pc += 1
  case 0x08: char = ~(val & char)  pc += 1
  case 0x0a: char = getchar() pc += 1
  case 0x0b: putchar(char) pc += 1
  case 0x0c: if(ops[ops[pc + 1]] != 0): pc = ops[pc + 5] (ops[ops[pc + 1]])--  ##意味着循环
             else: pc += 9
  case 0x0d: ++char pc += 1
  case 0x0e: ++val pc += 1
  case 0x0f: char = val pc += 1
  case 0x10: val = char pc += 1
  case 0x11: char += ops[pc + 1] pc += 5
  case 0x12: char = ops[val] pc += 1
  case 0x13: char = ops[char] pc += 1
  case 0x14: char = ops[pc + 1] pc += 5
  case 0x15: val = ops[pc + 1] pc += 5
  case 0x16: ops[val] = char pc += 1
  case 0x17: char = char - val pc += 1
  case 0x18: if(char): pc = ops[pc+1]    ##类似jump
             else: pc += 5
  default: pc += 1

然后对照这个指令表来看p.bin文件


_pbin_1527170851_13947.png

耐下心来, 慢慢分析, 就可以得到程序的大致流程:

  1. 输出字符串 "Input Flag"
  2. 读取32字节用户输入
  3. 对用户输入的每个字节进行编码然后存储
  4. 将之前处理得到的编码和程序中的期望值对比, 当每个字节都相同的时候就通过了检验, 此时输入的值就是flag

然后根据编码算法逆一下就可以得到flag了

#include <stdio.h>
#include <stdlib.h>

int main(){
    char target[0x20] = {'\x10', '\x18', '\x43', '\x14', '\x15', '\x47', '\x40', '\x17',
                         '\x10', '\x1d', '\x4b', '\x12', '\x1f', '\x49', '\x48', '\x18',
                         '\x53', '\x54', '\x01', '\x57', '\x51', '\x53', '\x05', '\x56',
                         '\x5a', '\x08', '\x58', '\x5f', '\x0a', '\x0c', '\x58', '\x09'};
    char flag[0x20];
    for(int i=0; i<0x20; ++i){
        for(char ans = 0; ; ++ans){
            char t1 = ~((0x20 + i) & ans);
            char t2 = ~((0x20 + i) & t1);
            char t3 = ~(t1 & ans);
            char t4 = ~(t2 & t3);
            if(t4 == target[i]){
                flag[i] = ans;
                break;
            }
        }
    }
    for(int i=0; i< 0x20; ++i){
        printf("%c", flag[i] );
    }
    return 0;
}

0x05 收获

感觉逆向题可能虽然更枯燥, 但是总是可以怼出来的. 但是pwn题就是没有思路的话完全没办法下手.....
等这次pwn的wp出来后认真学习一波吧:P

相关文章

  • [rctf_2018] simple_vm

    simple_vm 0x00 背景 pwn实在是pwn不动, 于是转来看其它题. 因为这道题解出来的人比较多. 所...

网友评论

      本文标题:[rctf_2018] simple_vm

      本文链接:https://www.haomeiwen.com/subject/gtpejftx.html