
1) 【一句话结论】在Linux系统中,堆溢出漏洞利用需通过gdb分析堆块结构(memptr、size、fd、bk偏移),构造覆盖EIP的payload,同时处理size字段覆盖导致的错误回收、tcache链表指针问题及ASLR,最终控制程序跳转到任意代码执行。
2) 【原理/概念讲解】老师讲解:堆溢出是程序向堆分配的缓冲区写入超长数据,覆盖堆块的控制信息(如指针、返回地址)。堆块由memptr(偏移0,指向数据)、size(偏移8,块大小)、fd(偏移16,前向指针)、bk(偏移24,后向指针)组成。覆盖size字段会导致系统错误管理堆块(如提前回收),利用失败。类比:堆块像储物柜,size是柜子标签,标明大小,溢出数据覆盖标签,系统误判柜子大小,导致回收错误。
3) 【对比与适用场景】
| 特性 | 栈溢出 | 堆溢出 |
|---|---|---|
| 溢出对象 | 栈缓冲区(函数参数/局部变量) | 堆缓冲区(动态分配的内存) |
| 覆盖内容 | 返回地址(栈帧中) | 堆块指针(memptr、fd、bk)或返回地址 |
| 利用难度 | 低(直接覆盖返回地址) | 高(需分析堆布局,处理碎片化) |
| 关键工具 | gdb查看栈帧 | gdb查看堆块,分析tcache/fastbin |
| 堆保护应对 | 栈保护(canary)、NX | ASLR(地址随机化)、tcache回收机制 |
4) 【示例】
伪代码(漏洞程序):
#include <stdio.h>
#include <stdlib.h>
void vulnerable_function() {
char *buffer = malloc(64);
if (!buffer) return;
printf("Enter a string: ");
fgets(buffer, 64, stdin);
printf("You entered: %s", buffer);
free(buffer); // 假设free后可能触发tcache回收
}
int main() {
vulnerable_function();
return 0;
}
调试步骤:
gcc -g -o vuln vuln.cgdb ./vulnb vulnerable_functionrinfo heap查看堆块结构,确定偏移:size在偏移8(0x8),fd在偏移16(0x10),bk在偏移24(0x14)。A*100),覆盖返回地址(假设返回地址在堆块后0x10偏移处)。A,接着是覆盖EIP的地址(如0xdeadbeef),最后是shellcode。注意:payload长度需考虑size字段,若覆盖size,需调整长度(比如跳过size字段,或构造时让size字段为0x100,避免覆盖)。5) 【面试口播版答案】(约90秒)
“面试官您好,针对Linux系统下的堆溢出漏洞利用,步骤如下:首先,用gdb分析内存布局,通过info heap或x/16xw heap_block查看堆块结构,确定memptr(0偏移)、size(8偏移)、fd(16偏移)、bk(24偏移)的位置,以及返回地址在堆块后的偏移(比如0x10)。然后,触发漏洞,输入超过缓冲区长度的数据(如100个A),覆盖返回地址。接着,构造payload:前64字节填充,接着是覆盖EIP的地址(比如0xdeadbeef),最后是shellcode。关键是要处理size字段,若覆盖size会导致堆块被系统错误回收,需调整payload长度(比如跳过size字段,或构造时让size字段为0x100,避免覆盖)。还要考虑tcache回收机制,确保覆盖的fd和bk指针为NULL(0x0),避免被系统回收。如果系统开启ASLR,可通过多次触发漏洞获取偏移信息(记录不同输入下返回地址的偏移变化),或利用tcache链表固定位置(如链表头地址已知)来构造payload。”
6) 【追问清单】
info heap查看堆块结构,偏移量在glibc中固定(如fd在偏移16,bk在偏移24),或用x/16xw heap_block逐字节查看。x/x $eip检查是否为构造的地址(如0xdeadbeef),若成功则覆盖正确。7) 【常见坑/雷区】