simple_vm
0x00 背景
pwn实在是pwn不动, 于是转来看其它题. 因为这道题解出来的人比较多. 所以就选择了这题. 虽然比赛的时候没有做出来, 不过之后还是独立解出来了. 记录一下. 期间要感谢肖神提供的指导.
0x01 程序分析
这个程序自己定义了一些指令, 然后以此为基础实现了一个解释器. 程序从p.bin中读取指令, 然后通过程序解释并执行指令
- 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;
}
- 解析函数:
__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文件

耐下心来, 慢慢分析, 就可以得到程序的大致流程:
- 输出字符串 "Input Flag"
- 读取32字节用户输入
- 对用户输入的每个字节进行编码然后存储
- 将之前处理得到的编码和程序中的期望值对比, 当每个字节都相同的时候就通过了检验, 此时输入的值就是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
网友评论