软件逆向04

软件逆向

Posted by orgaworl on September 10, 2024

1. 基础概念

  • 逆向分析
    • 硬件分析
    • 软件分析
      • 系统逆向分析
      • 代码逆向分析

软件分析:

![[Pasted image 20240603154423.png 450]]

程序控制流跳转

  • 直接调用 call funcSym
  • 间接调用、间接跳转 call eax; jmp eax
  • 尾部调用 ` `
    • 将尾部函数调用优化为jmp
    • 尾部调用特征
      • 跳转到已识别函数起始地址
      • 跳转距离长,不会跳转到函数体内
      • 非条件跳转
      • 跳转前往往会执行出栈操作
      • 跳转的目标不会是其他条件跳转的目标
  • switch的多种实现
    • 翻译为多个if-else
    • 跳转表(严重依赖编译器)
      • 每个case的条件值很近的情况
      • 常见实现
        • 静态数组存储跳转表
        • 二级跳转表
        • 跳转表存储在代码段中

        • 2. 反汇编

  • 反汇编
    • 静态
    • 动态
  • 反编译

2.1 静态反汇编

静态反汇编工具目标: 将二进制中所有代码转换为人类可阅读或机器可处理的形式,以便进一步分析.

步骤如下

  1. 二进制加载程序加载二进制文件进行处理
  2. 找出二进制文件中所有机器指令 (⭐易出错)
  3. 将指令反汇编为人类可读或机器可读形式.

良性ELF二进制文件

  • GCC
  • clang

线性反汇编

以二进制形式遍历所有代码段,连续解码所有字节,将其解析为指令列表.

例: objdump

存在问题: 存在非指令字节(inline data)

  • 例子
    • VS在代码段中插入跳转表
  • 导致结果
    • 出现无效操作码
    • 数据字节对应有效操作码, 反汇编工具输出假指令.
    • 在变长操作码ISA上, 可能导致反汇编指令流不同步.
    • 即使反汇编工具自动化, 但不同步会导致丢失几条真实指令. (恶意程序可故意包含使反汇编工具不同步字节, 以隐藏程序行为)

恶意程序可以通过故意包含混淆代码隐藏程序真实行为


递归反汇编

从已知的入口点开始进入, 然后递归的跟随控制流来发现代码. (对控制流敏感.)

  • 方案缺点
    • 很难静态的找出间接跳转或调用的可能位置, 丢失间接跳转或调用的目标代码块.
      • jmp eax
      • call eax
  • 应对
    • 使用特定的依赖于编译器的启发式方法解决控制流问题

switch => 跳转表, 使用了间接控制流


2.2 动态反汇编

  • 缺点
    • 代码覆盖率低, 只能看到运行期间实际执行的指令, 无法发现隐藏信息.

恶意程序检测到动态反汇编, 并隐藏自身行为.

增加增加代码覆盖率

  1. Test Suite 使用已知的测试输入运行二进制文件 缺点:对某些特殊软件和恶意软件无用

  2. Fuzzing 自动生成输入以覆盖二进制文件中新代码路径
    • AFL、Project Springfield、OSS-Fuzz 分类:
    • 基于生成的模糊测试工具
      • 基于预期输入格式进行生成
    • 基于变异的模糊测试工具
  3. 符号执行

3. 结构化代码

  • 分块
  • 揭示控制流

函数检测

有符号信息时 对于具有符号信息的二进制文件,符号表中指明了函数集{名称、起始地址、大小}

源代码级别的函数在二进制层面可能不存在

  • 可能在函数之间共享代码段(代码块重叠)
  • 分散到代码段各个部分,没能连续排列

不存在符号信息时

函数签名模式

  • 函数序言
  • 函数尾声
  • … 函数模式非常依赖(1)平台(2)编译器(3)优化级别 来创建二进制文件,优化后函数可能难以识别,容易发生错误

控制流图CFG

组织函数内部结构

  • 将函数内代码划分为一组代码块(基本块)
  • 基本块第一条指令是唯一入口点(必经指令)
  • 基本块最后一条指令是唯一出口点(唯一跳转到其他基本块的指令)
  • 将代码块用分支边进行连接

一般会忽略间接调用,静态分析难以处理

全局CFG(过程间CFG)

即所有函数CFG的并集

调用图 CG

显示了调用地址和函数之间的关系,即函数之间的调用关系

尾部调用视为常规调用,仍然忽略间接调用

面向对象的代码

  • 面向对象代码无法识别, 因此二进制分析工具不区分面向对象和面向过程代码.

4. 结构化数据

数据结构和数组

  • 不会自动识别数据结构
  • 无法识别结构体和数组

数据类型可能的推测方式

  • 根据使用的已知参数类型的函数进行推断
  • 通过保存的寄存器 / 处理数据的指令进行推断

5. 反编译

翻译为高级语言. 过程极易出错, 仅限于手动逆向.


6. 中间语言

指令集中的指令过于复杂,会产生大量副作用。 IR (Intermediate Representation), 将具有复杂语义的指令转化为语义完整更易分析的语言.

$二进制文件\rightarrow汇编代码\rightarrow IR$

流行 IR

  • REIL
  • VEX IR
  • LLVM IR

优点

  • 简化复杂指令
  • 抽象, 可将任意汇编语言先转化为 IR 再进行分析,无需针对每种指令集编写特定的处理程序。

7. 分析方法

过程间/过程内

  • 过程间分析
    • 将整个程序视为一个整体,通过CG将所有CFG链接在一起
    • 缺点:时间开销过大
  • 过程内分析
    • 一次只考虑单个函数中代码,一次分析每个函数的CFG
    • 缺点:分析不彻底,容易遗漏函数间联系

对于计算量大的分析,通常进行过程内分析

死代码消除 有时需要过程见分析才能奏效

流敏感

  • 流敏感:将指令的执行顺序考虑在内
  • 流不敏感

上下文敏感

  • 上下文敏感
    • 考虑函数调用顺序对结果的影响
    • 仅对于过程间分析有意义
    • 为CG中每条可能的路径计算单独的结果,遍历所有可能的调用顺序
  • 上下文不敏感
    • 进行过程间分析获得全局结果

优点:

  • 提高精度 缺点:
  • 计算复杂度高
  • 递归函数上下文数量可能无限,需要特殊处理

控制流分析

关注于控制流属性的二进制分析

  • 循环检测
    • 自然循环(natural loop) 具有良好特性的循环,容易被分析和优化
    • 任何循环cycle

支配关系 若从流图入口节点到节点n的任意路径都经过节点d,则 d 支配 n

  • 节点B只有一个前继节点A,则A支配B
  • 节点B有多个前继节点,且源头最终合并到A,则A支配B

由CFG, 根据支配关系生成支配节点树,每个节点只支配本身和后代节点

检测自然循环 自然循环性质

  • 有唯一入口节点,支配循环中所有节点
  • 循环中至少有一条返回首节点的路径 A支配B,则由B到A的回边会引起自然循环,该自然循环包含A支配的所有节点。

检测cycle 只需要CFG,无需使用支配树 从CFG入口点开始进行深度优先搜索DFS,维护栈保存DFS遍历的所有基本块,在DFS回溯时将其弹出。若DFS遇到一个已经在栈中的基本块,则找到一个cycle。


数据流分析

面向数据流的的二进制分析

(1). 定义可达性

通常用于CFG层面,也可用于过程间分析

  • 首先分析每个单独的基本块,获得局部解决方案
    • gen 在本基本块中为变量赋予新值的定义
    • kill 在上文对变量值的定义
  • 计算CFG中全局解
    • 迭代计算每个基本块中的定义
    • $in[B]=\cup_{p\in pred[B]} out[p]$
    • $out[B]=gen[B]\cup(in[B]-kill[B])$

(2.1). Use-Def Chains

定义每个基本块中被使用变量的值所有可能的来源位置, 例如ud[x] 表示在该基本块中变量x取值的来源位置

作用

  • 反编译器追踪条件跳转
  • 编译器优化:常量传播

实现:

  • 基于CFG的定义可达性,在in集合中查找基本块中使用到的变量的定义位置。

    (2.2)Def-Use Chains

    一个定义的变量可能在哪些位置被使用。

(3). 程序切片

提取程序中对 ”某位置处所选变量集的值“ 有贡献的所有指令

分类

  • 前向切片
  • 后向切片

8. 编译器设置对反汇编的影响

编译器优化代码会导致难以准确的进行反汇编分析

内联 inlining 将函数调用直接替换为函数实现,减少过程调用开销,增加最终程序体积。

尾部调用 使用jmp代替call,减小函数调用开销

优化调用约定

字节填充 在函数和基本块之间进行字节填充,使得内存地址对齐 会导致反汇编出错

循环展开

链接时优化LTO 为每个模块进行单独优化=>在整个程序上进行优化