PWN ROP (Return-Oriented Programming) 学习笔记
1. 什么是 ROP?
ROP (Return-Oriented Programming, 返回导向编程) 是一种高级的内存漏洞利用技术。它允许攻击者在限制性环境(如 NX/DEP 开启,即堆栈不可执行)下,通过劫持程序控制流,拼接程序中已有的称为 Gadgets(代码片段) 的指令序列,来执行任意代码。
2. 核心原理
• 劫持 EIP/RIP:通过栈溢出等漏洞覆盖函数的返回地址。
• 利用 Gadgets:Gadget 是程序或依赖库(如 libc)中已存在的、以 ret (Return) 指令结尾的代码片段(例如 pop rdi; ret)。
• 控制流串联:当一个 Gadget 执行完 ret 时,它会从栈顶弹出下一个地址并跳转。通过在栈上精心布置一系列 Gadgets 的地址和参数,就能像多米诺骨牌一样连续执行,最终达到调用 system("/bin/sh") 等目的。
3. 常见 ROP 链构建步骤
Linux x86 (32位)
• 传参方式:通过栈传参。
• 栈布局:[ Gadget地址 ] -> [ 伪造的返回地址 ] -> [ 参数1 ] -> [ 参数2 ]
• 示例:调用 system("/bin/sh")
• 栈布局:[ system_addr ] + [ exit_addr (任意4字节) ] + [ bin_sh_addr ]
1 | [ 垃圾数据 / 填充栈空间 ] -> 覆盖到 main 函数的 return address 之前 |
使用 int 0x80 的 ROP 链构建 (Linux x86 系统调用)
在 Linux x86 (32位) 中,除了直接调用 system 等 libc 函数外,还可以利用 int 0x80 指令直接触发系统调用 (Syscall)。例如,通过调用 sys_execve 来执行 /bin/sh。
• 核心寄存器分配:
• EAX:系统调用号 (对于 sys_execve,其编号为 0xb 即 11)
• EBX:第一个参数,指向字符串 /bin/sh 的地址
• ECX:第二个参数,argv 指针 (通常设为 0)
• EDX:第三个参数,envp 指针 (通常设为 0)
• 需要寻找的 Gadgets:
• pop eax; ret
• pop ebx; pop ecx; pop edx; ret (或者分开的 pop 指令)
• int 0x80; ret 或单独的 int 0x80
• 栈布局示例:
[ pop_eax_ret_addr ][ 0xb ](赋给 EAX)[ pop_ebx_ecx_edx_ret_addr ][ bin_sh_addr ](赋给 EBX)[ 0 ](赋给 ECX)[ 0 ](赋给 EDX)[ int_0x80_addr ](触发系统调用)
Linux x64 (64位)
• 传参方式:优先通过寄存器传参(RDI, RSI, RDX, RCX, R8, R9)。
• 栈布局:需要先找到控制寄存器的 Gadget。
• 示例:调用 system("/bin/sh")
• 需要寻找:pop rdi; ret
• 栈布局:
[ pop_rdi_ret_addr ][ bin_sh_addr ](被 pop 到 RDI)[ system_addr ](rdi 准备好后,ret 跳转到 system)
1 | [ 垃圾数据 / 填充栈空间 ] -> 覆盖到 main 函数的 return address 之前 |
4. 常用工具
• 寻找 Gadgets:
• ROPgadget --binary ./pwn_me --only "pop|ret"
• ropper --file ./pwn_me --search "pop rdi"
• 脚本编写:使用 Python 的 pwntools 库进行自动化 payload 构建。
1 | from pwn import * |
5. 绕过防护 (如 ASLR)
若开启了 ASLR(地址空间布局随机化),每次运行基址都会改变。
• 解法:先利用 ROP 调用 puts(got['puts']) 或 write(1, got['read'], ...) 泄露某个函数的运行时绝对地址。
• 计算基址:libc_base = leaked_addr - libc.sym['puts']
• 二次利用:计算出 system 和 /bin/sh 的真实地址后,重新触发漏洞执行 ROP。