type
status
date
summary
tags
category
icon
password

基础知识

Tcache(Thread Local Caching),即线程本地缓存,是 glibc(从版本2.26开始)引入的一种性能优化机制。
  • 目的: 提升堆内存分配的性能。在多线程程序中,传统的堆分配需要频繁地对主堆区(main_arena)进行加锁和解锁操作,这在多核高并发场景下会成为性能瓶颈。
  • 核心思想: 为每个线程预先分配一个专属的、无锁的缓存池。当线程需要分配小块内存时,优先从这个缓存池中获取;释放时也优先放回这个缓存池。这样就避免了与其他线程竞争全局堆锁,大大加快了分配和释放的速度。
Tcache的管理是通过一系列单向链表来实现的,这些链表被称为tcache bins
  • 数量与大小
    • 默认有64个tcache bins。
    • 每个 bin 负责一个特定大小的内存块。
    • bin 0 -> 负责24字节的 chunk(在 64 位系统上,考虑对齐后的实际大小)
    • bin 1 -> 负责32字节的 chunk
    • bin 2 -> 负责40字节的 chunk
    • ...
    • bin 63 -> 负责1032字节的 chunk
  • 数据结构
在每个线程的堆管理结构中,有一个名为tcache_perthread_struct的关键结构:
  1. counts[i]: 记录第i个bin中当前有多少个chunk,每个bin的容量上限是7个chunk。
  1. entries[i]:指向一个单向链表,链表中的每个节点都是一个空闲的堆块。
  • 链表结构 (tcache_entry)
Tcache bins是单向链表。每个被释放并放入tcache的chunk,其用户数据区域的开头
会被当作一个tcache_entry结构体:
这与fastbin中的fd类似。

攻击手法

  • tache poisoning
基本原理是通过覆盖next为控制的地址并利用malloc得到写入权限,并且目标地址无需伪造合适的size
  • tache dup
利用tache_put()函数不严谨:
该函数没有做任何检查,于是可以修改counts数组中的值,也可以对同一个chunk进行多次free,进而泄露堆地址。
  • tache house of spirit
(这个笔者暂时还没学到,以后补充)

这里补充一点,在glibc2.29及其之后,链表结构就变了:
新引入的key字段正是针对攻击者对同一块chunk多次释放的轻量级缓解措施:
  • 当一个chunk被放入tachebins时,除了设置next指针,还会添加key字段设置为指向这个 tcache所属的主管理结构(tcache_perthread_struct)的地址,作为已经释放的标记。
  • 在再次释放时:当free函数尝试再次释放同一个chunk时,它会检查该chunk的key字段,如果key字段的值等于当前线程的tcache_perthread_struct的地址,这就意味着这个chunk 已经在这个tcache的空闲链表里了,glibc 会立即检测到这是一个 Double Free 行为,并终止程序。
除此之外,还有另外一种保护措施Safe-Linking(指针加密)。
Safe-Linking 对每个放入Tcache链表的next指针进行了一个轻量级的加密处理,在从链表中取出时再进行解密。
  • 加密(当chunk被free并放入链表时):
    • 存储的next指针不再是真正的下一个chunk的地址,而是:
      加密后的_next = (真正的_next >> 12) ^ 当前 chunk 的地址
  • 解密(当chunk被malloc并从链表中取出时):
    • 程序会从链表头取出chunk,然后根据存储的加密值,计算出真正的next指针:
      真正的_next = (加密后的_next ^ 当前 chunk 的地址) << 12
这就给tache poisoning攻击手法造成了极大困难。

例题

题目给出了源码,直接分析源码:
这段代码实现了一个简单的字符画布交互程序,主要功能如下:
  1. 初始化一个默认 20x20 的字符画布,画布上的元素默认用 '.' 表示。
  1. 提供交互式命令操作,支持以下功能:
      • 绘制(p命令):在指定坐标 (arg1, arg2) 处绘制十六进制字符arg3。
      • 调整大小(r命令):将画布重新设置为 arg1 x arg2的尺寸,同时清空画布内容。
      • 帮助(h命令):显示操作说明。
      • 退出(e命令):结束程序。
3. 实时显示当前画布状态,并等待用户输入下一个操作命令。
这个程序存在一个很明显的漏洞就是没有对数组下标做检查,可以实现越界读写,于是我们可以想到利用越界读写修改tache结构体,使其分配到指定位置。
主要利用思路:
  • 绘制画布,利用数组越界改写tache结构体中0x20位置的count为1,对应的指针改成got表附近的地址,避免画布清空覆盖got表
  • 调整画布大小为0x20,越界覆写free地址为printf
  • 绘制当前画布,从开头写入格式化字符串
  • 调整画布为20x20,此时调用free,触发printf格式化字符串漏洞泄露栈上的libc_start_main地址,进而泄露libc;目前堆块的位置仍在最开始的20x20的堆处
  • 再次绘制画布,将tache结构体0x20的count仍改为1,对应指针改成got附近的地址
  • 调整画布大小为0x20,越界覆写free地址为system
  • 绘制当前画布,从开头写入/bin/sh
  • 输入命令h,调用free,执行system(”/bin/sh”)
exp:
 
IO_FILE结构以及vtable相关知识Ptrace利用
Loading...