0%

【pdkx培训】pwn ROP相关

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
2
3
4
[ 垃圾数据 / 填充栈空间 ] -> 覆盖到 main 函数的 return address 之前
[ system 函数的地址 ] -> main 函数 ret 时跳转到这里
[ 4字节的任意返回地址 ] -> system 函数执行完后要返回的地址(可填入 exit 函数或 AAAA)
[ "/bin/sh" 字符串的地址 ] -> system 函数会去这个位置读取它的第一个参数

使用 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

栈布局示例

  1. [ pop_eax_ret_addr ]
  2. [ 0xb ] (赋给 EAX)
  3. [ pop_ebx_ecx_edx_ret_addr ]
  4. [ bin_sh_addr ] (赋给 EBX)
  5. [ 0 ] (赋给 ECX)
  6. [ 0 ] (赋给 EDX)
  7. [ int_0x80_addr ] (触发系统调用)

Linux x64 (64位)

传参方式:优先通过寄存器传参(RDI, RSI, RDX, RCX, R8, R9)。

栈布局:需要先找到控制寄存器的 Gadget。

示例:调用 system("/bin/sh")

• 需要寻找:pop rdi; ret

• 栈布局:

  1. [ pop_rdi_ret_addr ]
  2. [ bin_sh_addr ] (被 pop 到 RDI)
  3. [ system_addr ] (rdi 准备好后,ret 跳转到 system)
1
2
3
4
[ 垃圾数据 / 填充栈空间 ] -> 覆盖到 main 函数的 return address 之前
[ pop rdi; ret 处的地址 ] -> main 函数 ret 时,会跳转到这里执行 pop rdi
[ "/bin/sh" 字符串的地址 ] -> pop rdi 恰好把这个地址弹入 rdi 寄存器
[ system 函数的地址 ] -> pop 之后紧接着的 ret,会跳转到 system 执行

4. 常用工具

寻找 Gadgets

ROPgadget --binary ./pwn_me --only "pop|ret"

ropper --file ./pwn_me --search "pop rdi"

脚本编写:使用 Python 的 pwntools 库进行自动化 payload 构建。

1
2
3
4
5
6
7
8
9
10
11
from pwn import *

# standard pwntools ROP usage

elf = ELF('./pwn_me')

rop = ROP(elf)

rop.system(next(elf.search(b'/bin/sh')))

print(rop.dump())

5. 绕过防护 (如 ASLR)

若开启了 ASLR(地址空间布局随机化),每次运行基址都会改变。

解法:先利用 ROP 调用 puts(got['puts'])write(1, got['read'], ...) 泄露某个函数的运行时绝对地址。

计算基址libc_base = leaked_addr - libc.sym['puts']

二次利用:计算出 system/bin/sh 的真实地址后,重新触发漏洞执行 ROP。