ROP

作者: HPShark | 来源:发表于2018-07-19 14:43 被阅读0次

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

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

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

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

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

    return-to-libc攻击

    return-to-libc

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

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


    基本流程

    为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


    难点:x86编译器所生成的代码高度优化,难以随意改变指令长度
    • 因此,Smashing the gadgets必须是(in-place)的
    • 像上面这样抹掉0xC3字节仅仅是可用的手段之一

    手段2:指令重新排序


    指令重新排序

    手段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 without return

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


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


    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的一些特点:

    • 始于对程序控制流的篡改(控制流异常)
    • 各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)

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

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

    细粒度的ASLR:

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

    细粒度ASLR的实施

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

    对ASLR的攻击

    ASLR假设的攻击模型

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

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

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


    相关文章

      网友评论

          本文标题:ROP

          本文链接:https://www.haomeiwen.com/subject/ijkmyftx.html