1. 基础概念
- 逆向分析
- 硬件分析
- 软件分析
- 系统逆向分析
- 代码逆向分析
软件分析:
| ![[Pasted image 20240603154423.png | 450]] |
程序控制流跳转
- 直接调用
call funcSym - 间接调用、间接跳转
call eax; jmp eax - 尾部调用 ` `
- 将尾部函数调用优化为jmp
- 尾部调用特征
- 跳转到已识别函数起始地址
- 跳转距离长,不会跳转到函数体内
- 非条件跳转
- 跳转前往往会执行出栈操作
- 跳转的目标不会是其他条件跳转的目标
- switch的多种实现
- 翻译为多个if-else
- 跳转表(严重依赖编译器)
- 每个case的条件值很近的情况
- 常见实现
- 静态数组存储跳转表
- 二级跳转表
- 跳转表存储在代码段中
-
2. 反汇编
- 反汇编
- 静态
- 动态
- 反编译
2.1 静态反汇编
静态反汇编工具目标: 将二进制中所有代码转换为人类可阅读或机器可处理的形式,以便进一步分析.
步骤如下
- 二进制加载程序加载二进制文件进行处理
- 找出二进制文件中所有机器指令 (⭐易出错)
- 将指令反汇编为人类可读或机器可读形式.
良性ELF二进制文件
- GCC
- clang
线性反汇编
以二进制形式遍历所有代码段,连续解码所有字节,将其解析为指令列表.
例: objdump
存在问题: 存在非指令字节(inline data)
- 例子
- VS在代码段中插入跳转表
- 导致结果
- 出现无效操作码
- 数据字节对应有效操作码, 反汇编工具输出假指令.
- 在变长操作码ISA上, 可能导致反汇编指令流不同步.
- 即使反汇编工具自动化, 但不同步会导致丢失几条真实指令. (恶意程序可故意包含使反汇编工具不同步字节, 以隐藏程序行为)
恶意程序可以通过故意包含混淆代码隐藏程序真实行为
递归反汇编
从已知的入口点开始进入, 然后递归的跟随控制流来发现代码. (对控制流敏感.)
- 方案缺点
- 很难静态的找出间接跳转或调用的可能位置, 丢失间接跳转或调用的目标代码块.
jmp eaxcall eax
- 很难静态的找出间接跳转或调用的可能位置, 丢失间接跳转或调用的目标代码块.
- 应对
- 使用特定的依赖于编译器的启发式方法解决控制流问题
switch => 跳转表, 使用了间接控制流
2.2 动态反汇编
- 缺点
- 代码覆盖率低, 只能看到运行期间实际执行的指令, 无法发现隐藏信息.
恶意程序检测到动态反汇编, 并隐藏自身行为.
增加增加代码覆盖率
-
Test Suite 使用已知的测试输入运行二进制文件 缺点:对某些特殊软件和恶意软件无用
- Fuzzing
自动生成输入以覆盖二进制文件中新代码路径
- AFL、Project Springfield、OSS-Fuzz 分类:
- 基于生成的模糊测试工具
- 基于预期输入格式进行生成
- 基于变异的模糊测试工具
- 符号执行
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 为每个模块进行单独优化=>在整个程序上进行优化