ROP

回顾:针对代码注入的防护机制

1. 经典代码注入的核心思想
利用逻辑异常,在程序数据中混入代码
劫持控制流,使得指令指针从数据段读取指令
所需弱点:读取指令时,CPU无法区分目标内存区域的性质

2. 数据执行保护(Data Execution Prevention)
别名:W⊕X,NX-bit
机制:对内存页面增加一个标识bit,使之要么可改写,要么可执行
防御思路:引入新的硬件安全属性,支持CPU在执行时区分代码和数据区域(但是:若不试图执行数据区内容,则不能发现/阻止溢出,也无法防范溢出的其他后果)

ROP_第1张图片

目标:绕过DEP(代码重用(code reuse)攻击)

  • 异常数据的注入仍然可以实施
  • 代码无法直接注入,那么就利用进程空间中已经存在的代码
  • 不管所利用代码原本的用途为何,通过注入的异常数据控制其为攻击者服务

return-to-libc攻击

ROP_第2张图片
return-to-libc

返回导向编程(return-oriented programming)(图灵完备)

一种非常规方式将短小指令片段串联成完整的代码流

ROP_第3张图片

ROP_第4张图片
基本流程

为ROP搜索可用的指令资源 --- Galileo算法

特点:

  • 始于对程序控制流的篡改(控制流异常)
  • 各gadget由ret指令(或者pop-jump组合)代替eip加以链接(大量控制流异常)
  • 原始栈结构遭到破坏,ROP过程中栈指针单向移动(栈的行为异常)
  • 有时,栈指针可能被篡改并指向不属于栈区的内存(栈的行为异常)

ret指令(与栈溢出方向吻合,具有指令寻址能力)

  • 根据当前栈顶内容,修改指令寄存器所指向的地址
  • 将栈指针位置向栈底方向移动4字节

图灵完备的指令集应当包括

  • 加载/存储
  • 算术,逻辑运算,位移
  • 控制流:条件跳转,系统调用

利用点1:复杂指令集(complex instruction set computing,CISC)

步骤:

  1. 将劫持后的控制流引导至正常指令的内部(middle of an instruction)
  2. 利用CISC的特点,形成意外的指令片段(unintended instruction sequence)
  3. Gadget资源的重要来源:因误解析而形成的“指令”(特别是0xC3字节)

需要注意的是:

  • 指令变长(目的:以最短的代码长度压缩最多的指令内容)
  • CPU串行地读取指令,依据上下文区分操作码/操作数等成分
  • 典型:x86指令构架
  • 存在问题:代码中一个字节的含义取决于上下文
    a. 各种指令的使用率相差悬殊,且微码串行执行让频繁使用的简单指令也效率低下
    b. 复杂指令 → 复杂的硬件结构,CISC越来越难以集成在单一芯片上
    c. 许多复杂指令需要极复杂的操作,多数已可视为某种高级语言的翻版,通用性差

利用点2:精简指令集(RISC)

背景:
CISC存在许多缺点(上面说过了)

RISC的特点:
统一指令编码,如所有指令长度相等、op-code位置相同等,可快速解译
泛用的缓存器,单纯的寻址模式(用计算指令序列取代复杂寻址模式)
硬件中支持少数数据型别(如区分整数/浮点数等)

栗子
“可扩充处理器架构”(Scalable Processor ARChitecture,SPARC)

SPARC的寄存器“窗口”机制

  • 32个通用寄存器,其中8个全局寄存器和一个“窗口”(包含24个寄存器)
  • 支持2~32个“窗口”(取决于硬件实现),通常为7~8个——由此得名“可扩展的”
  • 在任何时候,只有一个寄存器窗口是可见的

针对RISC的返回导向编程(图灵完备)

  1. 对SPARC构造返回导向编程所面临的问题:
    无法利用意外的指令序列(该种情况不可能发生)
    x86下返回导向编程gadget的所有构造特点在RISC中均不存在

  2. 新的返回导向编程设计思路:
    将函数的后缀作为gadget使用(利用其结尾处的ret-restore指令序列)
    利用结构化数据流使得gadget与SPARC的函数调用惯例相吻合
    构造内存-内存gadget(寄存器仅在gadget内部使用)

如何防御
核心思路:破坏返回导向编程的原子指令组件(gadget),阻止x86代码中出现意外的gadget

ROP_第5张图片

难点:x86编译器所生成的代码高度优化,难以随意改变指令长度

  • 因此,Smashing the gadgets必须是(in-place)的
  • 像上面这样抹掉0xC3字节仅仅是可用的手段之一

手段2:指令重新排序


ROP_第6张图片
指令重新排序

手段3:寄存器压栈顺序随机化
手段4:寄存器重分配

ROP without return

参考论文:Return-Oriented Programming without Returns
有了这盘论文之后,之前的防御措施都没啥卵用了

核心思想:防范普遍针对ret指令(0xC3),那就想出不用ret也可以的办法

  • 利用update-load-branch指令序列“pop x; jmp *x”
  • 但是,update-load-branch序列并不像ret那样常见,因此要采用“蹦床”(trampoline)机制


    ROP_第7张图片
    ROP without return

ROP without return之图灵完整性:可用资源及trampoline


ROP_第8张图片

ROP without return之图灵完整性:条件分支


ROP_第9张图片

ROP without return之图灵完整性:函数调用

  • 在esi中载入call-jmp序列的地址
  • 在ebp中载入leave-jmp序列的地址
  • 在eax中载入n+偏移量
  • 将call-jmp序列的地址存储至地址n
  • 改写esi,使其存储“返回地址”
  • esi值写入result位置后,再读出至edi
  • 交换使返回值存入ebp,leave指令换入edi
  • 在esi中载入pop-jmp序列的地址
  • 在ecx中载入函数入口地址
  • 在eax中载入地址n
  • 交换esp和eax,栈指针指向n(函数地址)
  • edi处的leave指令将使函数“返回” 至0x7d
ROP_第10张图片

防御思路

由ROP的一些特点:

  • 始于对程序控制流的篡改(控制流异常)
  • 各gadget由ret指令(或者pop-jump组合)代替eip加以链接(大量控制流异常)
  • 原始栈结构遭到破坏,ROP过程中栈指针单向移动(栈的行为异常)
  • 有时,栈指针可能被篡改并指向不属于栈区的内存(栈的行为异常)

可以有如下办法:

  • 要求程序按照程序猿所规定的逻辑去执行
  • 当出现不应出现的控制转移行为时,阻止程序执行
  • 当程序的栈结构发生异常变化时,阻止程序执行
    并由此产生的返回导向编程防御思路:控制流完整性保护(CFI)技术

控制流完整性保护(CFI)技术

基本思想:

  • 通过预设的运行时校验,确保程序执行与预先定义的控制流图严格吻合
  • 通过对二进制码的静态分析来获取CFI所需保证的控制流图
  • 藉由静态的二进制代码改写为程序添加运行时的自我校验

CFI的基本安全性假设:

  • 控制转移目标的标示符(ID)具有唯一性
  • 程序代码不可写
  • 程序数据不可执行

实用(粗粒度)的控制流完整性保护
改进思路:借鉴SFI的思想,优化CFI的校验机制
设计效果:间接控制转移只能以Springboard段内的适当存根作为目标

基于动态优化的CFI
前述CFI方案仍然存在不尽人意之处:需要改写程序的二进制代码:

  • 即使是最强大的二进制分析工具,也很难识别出程序中所有的合法控制转移目标
  • 对二进制代码的修改同样困难且容易出错,且兼容性问题在一定程度上仍然存在

改进思路:在程序执行过程中加以监视和约束
设计基础:动态执行优化工具
不足:效率太低

利用硬件特性和虚拟化技术的CFI
改进思路:利用虚拟化技术

实现基础:Last Branch Record

  • 一组存在于CPU内部、记录其最后n次控制转移目标的寄存器(如Intel i5中设有16个)
  • 需要内核权限开启(恰好与虚拟化技术相适应)

CFI弱点

  • 即使是在静态分析中,间接控制转移的合法跳转目标也既不是唯一的、也不是排他的
  • 受到性能的制约,粗粒度化的CFI对控制流的约束更加松散

针对CFI的改进型返回导向编程

CFI的弱点为返回导向编程提供了新类型的gadget来源

  • gadget的位置满足粗粒度CFI的约束规则
  • 新约束条件下的gadgets仍然有可能形成具备图灵完整性的攻击载荷
  • 副作用: gadget为了满足CFI约束而不可避免地增大了(无论是字节数还是指令数)

返回导向编程改进型1:利用以函数入口和函数调用点为起始的gadget
绕过CFI是否将导致返回导向编程的功能性有所缺损?不会

返回导向编程的变种:数据导向编程

注:Return-to-Libc攻击特点

  • 篡改返回地址指向指定函数入口
  • 通过溢出伪造输入参数等栈数据结构
  • 诱使函数在执行中使用伪造的参数数据,实现恶意目的

从Return-to-Libc攻击可以看出:数据污染可以成为代码重用类攻击的重要成分

数据导向编程特点:

  • 通过污染数据(特别是指针)改变被重用代码的实际语义
  • 借助循环不断注入新的攻击载荷,使得被重用代码的实际执行效果随之改变

因此,数据导向编程所重用的代码资源主要来自串操作(或同类执行行为)

数据导向编程的循环调度

  • 交互式攻击 – 允许攻击者在每轮循环中输入不同的载荷以激活不同的gadget
  • 非交互式攻击 – 攻击者必须一次性输入整个攻击载荷

地址空间随机化(Address Space Layout Randomization)

基本思想:地址空间随机化

ROP_第11张图片
基本思想

发展
粗粒度ASLR的改进:偏移量+内存区段的位置置换

细粒度的ASLR:

  • 区段间 → 区段内
  • 改变代码文本的具体内容

细粒度ASLR的实施

  • 程序在加载时自我随机化
  • 通过虚拟机进行动态随机化
  • 操作系统的随机化

对ASLR的攻击

ASLR假设的攻击模型

  • Case 1:攻击者无法披露目标程序的内存空间
  • Case 2:攻击者可以实施内存空间披露,但只能获得一个代码指针(如果攻击者真的寻获了一个可以远程利用的内存披露漏洞,为何不反复利用之,以最大化漏洞价值?)

ASLR的无效化:Just-In-Time代码重用

Limitation:对于任何ASLR,程序在执行时不再改变其内存空间结构


ROP_第12张图片

你可能感兴趣的:(ROP)