llvm代码混淆学习(八)
写在前面
随便学学 llvm 代码混淆,顺便做个毕设,水几篇文章就当做做笔记
随机控制流原理分析概述随机控制流是虚假控制流的一种变体,随机控制流通过克隆基本块,以及添加随机跳转(随机跳转到两个功能相同的基本块中的一个)来混淆控制流。
与虚假控制流不同,随机控制流中不存在不可达基本块和不透明谓词,因此用于去除虚假控制流的手段(消除不透明谓词、符号执行获得不可达基本块后去除)失效。
随机控制流的控制流图与虚假控制流类似,都呈长条形。
混淆效果随机的跳转和冗余的不可达基本块导致了大量的垃圾代码,严重干扰了攻击者的分析,并且 rdrand 指令可以干扰某些符号执行引擎(如 angr )的分析。
原理随机控制流同样是以基本块为单位进行混淆的,每个基本块要经过分裂、克隆、构造随机跳转和构造虚假随机跳转四个操作。
随机控制流代码实现思路实现步骤第一步:基本块拆分将基本块拆分成头部、中部、尾部三个基本块,同虚假控制流。
通过 getFirstNonPHI 函数获取第一个不是 PHINode 的指令,以该指令为界限进行分割,得到 entryBB 和 bodyBB。
以 bodyBB 的终结指令 ...
llvm代码混淆学习(七)
写在前面
随便学学 llvm 代码混淆,顺便做个毕设,水几篇文章就当做做笔记
指令替代原理分析概述指令替代指将正常的二元运算指令(如加法、减法、异或等等),替换为等效而更复杂的指令序列,以达到混淆计算过程的目的。
例如,将 a + b 替换为 a - (-b),将 a ^ b 替换为 (~a & b) | (a & ~b)等等。
仅支持整数运算的替换,因为替换浮点指令会造成舍入错误和误差。
混淆效果函数的控制流并没有发生变化,但是运算过程变得难以分辨:
原理扫描所有指令,对目标指令(加法、减法、与或非、异或)进行替换:
12345678910111213141516bool Substitution::runOnFunction(Function &F){ for(int i = 0;i < ObfuTime;i ++){ for(BasicBlock &BB : F){ vector<Instruction*> origInst; fo ...
llvm代码混淆学习(六)
写在前面
随便学学 llvm 代码混淆,顺便做个毕设,水几篇文章就当做做笔记
虚假控制流原理分析概述虚假控制流指,通过向正常控制流中插入若干不可达基本块(永远不会被执行的基本块)和由不透明谓词(指一个表达式,它的值在执行到某处时,对程序员而言必然是已知的,但是由于某种原因,编译器或者说静态分析器无法推断出这个值,只能在运行时确定)造成的虚假跳转,以产生大量垃圾代码干扰攻击者分析的混淆。
真实的情况更加复杂。虚假的跳转和冗余的不可达基本块导致了大量垃圾代码,严重干扰了攻击者的分析:
结构 && 混淆效果与控制流平坦化不同,经过虚假控制流混淆的控制流图呈长条状。
原理虚假控制流是以基本块为单位进行混淆的,每个基本块要经过分裂、克隆、构建虚假跳转等操作。
虚假控制流代码实现思路实现步骤第一步:基本块拆分将基本块拆分成头部、中部和尾部三个基本块。
通过 getFirstNonPHI 函数获取第一个不是 PHINode 的指令,以该指令为界限进行分割,得到 entryBB 和 bodyBB。
因为绝大多数基本块中 PHI 指令都在最前面
以 bodyBB 的终结 ...
llvm代码混淆学习(五)
写在前面
随便学学 llvm 代码混淆,顺便做个毕设,水几篇文章就当做做笔记
控制流平坦化原理分析概述控制流平坦化指的是将正常控制流中基本块之间的跳转关系删除,用一个集中的分发块来调度基本块的执行顺序。
结构
入口块:进入函数第一个执行的基本块。
主分发块与子分发块:负责跳转到下一个要执行的原基本块。
原基本块:混淆之前的基本块,真正完成程序工作的基本块。
返回块:返回到主分发块。
所有完成原程序功能的原基本块平行排列在返回块上方,ida反汇编出来看起来就是一排,也就是平坦化的由来。
混淆效果分析正常的控制流时,能很容易的分析出程序的执行顺序,以及每一段代码完成的工作(一段代码可能由多个相互关联的基本块组成),进而掌握整个程序的逻辑。
分析平坦化后的控制流时,在不知道基本块执行顺序的情况下,分别对每一个基本块进行分析是很难的,而如果要得知每一个基本块的执行顺序,必须分析分发块的调度逻辑。
实际上当函数比较复杂时,通过手动分析分发块还原原基本块的执行顺序是非常复杂的。
控制流平坦化混淆后的伪代码,while + switch 结构对应平坦化后的控制流结构。
控制流平坦化代码实 ...
llvm代码混淆学习(四)
写在前面
随便学学 llvm 代码混淆,顺便做个毕设,水几篇文章就当做做笔记
代码混淆基本原理术语介绍代码混淆代码混淆是将计算机程序的代码,转换成一种功能上等价,但是难以阅读和理解的形式的行为。
函数函数是代码混淆的基本单位,一个函数由若干个基本块组成,有且仅有一个入口块,可能有多个出口块。
一个函数可以用一个控制流图(Control Flow Graph,CFG)来表示。
基本块基本块由一组线性指令组成,每一个基本块都有一个入口点(第一条执行的命令)和一个出口点(最后一条执行的指令,也即终结指令)。
终结指令要么跳转到另一个基本块(br,switch),要么从函数返回(ret)。
控制流控制流代表了一个程序在执行过程中可能遍历到的所有路径。
通常情况下,程序的控制流很清晰的反映了程序的逻辑,但经过混淆的控制流会使得人们难以分辨正常逻辑。
不透明谓词不透明谓词指的是其值为混淆者明确知晓,但是反混淆者却难以推断的变量。
例如混淆者在程序中使用一个恒为 0 的全局变量,反混淆者难以推断这个变量恒为 0。
常见的混淆思路符号混淆将函数的符号,如函数名、全局变量名去除或者混淆。对于 ...
llvm代码混淆学习(三)
写在前面
随便学学 llvm 代码混淆,顺便做个毕设,水几篇文章就当做做笔记
基本块分割基本块分割即将一个基本块分割为等价的若干个基本块,在分割后的基本块之间加上无条件跳转。
基本块分割不能算是混淆,但是可以提高某些代码混淆的混淆效果。
为什么要分割在许多基于基本块的代码混淆中,基本块数量越多,代码混淆后的复杂度越大。
通过增加基本块的数量,可以到达提高混淆效果的目的。
实现思路遍历每个函数中的每个基本块,对每个基本块进行分割即可。
有 PHI 指令的基本块选择跳过
因为 PHI 值根据前驱块指定,分割带有 PHI 指令的基本块可能会改变其前驱块,导致带有 PHI 指令的基本块的前驱块是分割前的同一个基本块
使用到的 API
额外参数指定
可以通过 cl::opt 模板类获取指令中的参数,这里的 opt 是选项 option 的缩写,不是优化器的意思:
1234567#include "llvm/Support/CommandLine.h"// 可选的参数,指定一个基本块会被分裂成几个基本块,默认值为 2static cl::opt<int> ...
llvm代码混淆学习(二)
写在前面
随便学学 llvm 代码混淆,顺便做个毕设,水几篇文章就当做做笔记
LLVM Pass 常用 APILLVM Pass 框架中,三个最核心的类是 Function、BasicBlock和Instruction,分别对应 LLVM IR 中的函数、基本块和指令,还有一个基本类Value,所有可以被当作指令操作数的类型都是 Value 的子类
Function 类与 Function 有关的操作主要是获取函数的一些属性,比如名称等等,以及对函数中基本块的遍历:
获取函数名称:F.getName()
获取入口快:F.getEntryBlock()
函数中基本块的遍历
可以通过 foreach 循环对函数 Function 中的每个基本块 BasicBlock 进行遍历
12345bool runOnFunction(Function &F){ // 使用引用是为了能修改传入参数Function类 for(BasicBlock &BB : F){ // 遍历F中的基本块,同样使用引用 // to do } ...
llvm代码混淆学习(一)
写在前面
随便学学 llvm 代码混淆,顺便做个毕设,水几篇文章就当做做笔记
LLVM IR 概述
低级编程语言,类似汇编
任何高级编程语言都可以用 LLVM IR 表示
基于 LLVM IR 可以进行代码优化
两种表示方法
人类可以阅读的文本形式,文件后缀为.ll
易于机器处理的二进制格式,文件后缀为.bc
用llvm-dis和llvm-as可以相互转化
结构源代码被编译为 LLVM IR 后,具有模块 Module,函数 Function和基本块 BasicBlock
模块
一个源代码文件对应一个模块
头部信息包含程序的目标平台,如 x86、arm 等,和一些其他信息
全局符号包含全局变量、函数的定义与声明
函数
函数指的是源代码中的某个函数
参数即函数的参数
一个函数由若干基本块组成,其中函数最先执行的基本块为入口块
基本块
一个基本块由若干指令和标签组成
正常情况下,基本块的最后一条指令为跳转指令(br 或 switch),或返回指令(retn),也叫作终结指令(Terminator Instruction)
PHI 指令是一种特殊的指令
代码混淆基于 L ...