堆溢出(Heap Overflow)是一种常见的内存安全漏洞,主要发生在程序动态分配的内存区域(堆)中。当程序向堆中写入的数据超过了分配的内存空间时,就会发生堆溢出。这种溢出可能导致程序崩溃、数据损坏,甚至被攻击者利用来执行任意代码

基本概念

堆是程序运行时动态分配的内存区域,用于存储程序运行期间需要的数据。与栈不同,堆的内存分配和释放由程序员显式控制(如使用 malloc、free 等函数)。堆的管理通常由操作系统的内存管理器或运行时库(如 glibc)负责

原因

  • 缓冲区溢出:程序向堆中分配的缓冲区写入超过其大小的数据
    例如,使用 malloc(10) 分配了 10 字节的内存,但写入了 20 字节的数据
  • 内存管理错误:程序错误地释放内存或重复释放同一块内存
  • 未初始化内存:使用未初始化的堆内存可能导致不可预测的行为
  • 整数溢出:在计算内存大小时发生整数溢出,导致分配的内存过小
    例如:
char *buffer = (char *)malloc(10); // 分配 10 字节的堆内存
strcpy(buffer, "This string is too long!"); // 写入超过 10 字节的数据,触发堆溢出

影响

  • 覆盖相邻内存:溢出数据可能覆盖堆中的其他内存块,包括内存管理结构(如 chunk 头)
  • 破坏堆管理结构:攻击者可以通过覆盖堆管理结构(如 malloc 的元数据)来控制内存分配和释放行为。
  • 执行任意代码:通过覆盖函数指针、返回地址等,攻击者可以控制程序执行流程

利用过程

  1. 触发堆溢出
    输入恶意数据,比如
  • 像堆缓冲区写入超长字符串
  • 利用整数李处或符号错误导致分配的内存过小
  1. 覆盖关键数据
  • 堆管理结构:如 malloc 的 chunk 头,包含内存块的大小和状态信息
  • 函数指针:如动态链接的函数指针(GOT/PLT)、虚表指针(vtable)
  • 返回地址:如果堆溢出覆盖了栈上的返回地址,可以控制程序执行流程
  1. 控制程序执行流程
    覆盖函数指针:将函数指针指向攻击者提供的代码(如 shellcode)
    利用堆管理机制:通过破坏堆管理结构,触发特定的内存分配或释放行为
    返回导向编程(ROP):利用堆溢出覆盖返回地址,构造 ROP 链执行任意代码
  2. 执行恶意代码
    执行 shellcode:将程序流重定向到攻击者提供的 shellcode
    调用系统函数:通过覆盖函数指针调用系统函数(如 system("/bin/sh"))
    提权:利用堆溢出漏洞提升权限,获取系统控制权

利用技术

  1. Unlink 攻击
    在早期的堆管理实现中,free 函数会通过 unlink 操作将释放的内存块从链表中移除。攻击者可以通过堆溢出覆盖 chunk 头,伪造链表指针,从而在 unlink 操作时写入任意地址
  2. Use-After-Free (UAF)
    利用堆溢出触发 Use-After-Free 漏洞:
  • 释放一个堆块后,程序继续使用该内存块
  • 攻击者通过堆溢出覆盖释放的内存块,控制其内容
  1. Fastbin 攻击
    Fastbin 是 glibc 中用于管理小内存块的机制。攻击者可以通过堆溢出覆盖 Fastbin 链表中的指针,从而控制内存分配行为
  2. House of Spirit
    攻击者通过堆溢出伪造一个假的 chunk,并将其释放到堆管理器中。随后,攻击者可以分配该假 chunk 并控制其内容
  3. House of Force
    通过堆溢出覆盖 top chunk 的大小,攻击者可以控制后续的内存分配行为,从而分配任意内存区域

示例

简单的堆溢出

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void vulnerable_function() {
    char *buffer = (char *)malloc(10); // 分配 10 字节的堆内存
    if (buffer == NULL) {
        perror("malloc failed");
        return;
    }

    strcpy(buffer, "This string is too long for the buffer!"); // 触发堆溢出
    printf("%s\n", buffer);

    free(buffer); // 释放内存
}

int main() {
    vulnerable_function();
    return 0;
}

以上内容只是框架,在之后会慢慢补充丰富