代码混淆技术系列
LLVM IR指令详解与实战
00 分钟
2022-12-16
2024-11-11
type
status
date
slug
summary
tags
category
icon
password
如果代码高亮看着难受,看这个,我用obsidian写的

LLVM IR 介绍

官方文档对此部分有超级详细的介绍,这篇文章只限于简单入门
首先 LLVM IR 是一门低级编程语言,语法类似于汇编,所以任何高级编程语言(如 C++等等)都可以用 LLVM IR 表示,好处是基于 LLVM IR 可以很方便地进行代码优化

LLVM IR 的两种表示方法

  1. 第一种是人类可以阅读的文本形式,文件后缀为 . ll
  1. 第二种是易于机器处理的二进制格式,文件后缀为 . bc
它们两个可以通过编译完的 llvm 目录的命令行工具, dis, as 相互转换
notion image

LLVM IR 结构

大致分为三块
  • 模块 Module
  • 函数 Function
  • 基本块 BasicBlock
notion image

模块 Module

notion image
  • 一个源代码文件对应 LLVM IR 中的一个模块。
  • 头部信息包含程序的目标平台,如 X86、ARM 等等,和一些其他信息。
  • 全局符号包含全局变量、函数的定义与声明都在这里。

函数 Function

notion image
  • LLVM IR 中的函数表示源代码中的某个函数。
  • 参数,顾名思义为函数的参数。
  • 一个函数由若干基本块组成,其中函数最先执行的基本块为入口块。

基本块 BasicBlock

notion image
  • 一个基本块由若干个指令和标签组成。
  • 正常情况下,基本块的最后一条指令为跳转指令 (br 或 switch),或返回指令 (retn),也叫作终结指令 (Terminator Instruction)。
  • PHI 指令是一种特殊的指令。(下面会详细说明)

类比 IDA 流程图

IDA 每个函数里面执行流程是以一块一块的代码组成的
notion image

LLVM IR 混淆基础

  1. 以函数为基本单位的混淆:控制流平坦化。
  1. 以基本块基本单位的混淆:虚假控制流。
  1. 以指令为基本单位的混淆:指令替代。

LLVM IR 指令

指令类型

  1. 终结指令 Terminator Instructions
  1. 比较指令 Compare Operations
  1. 二元运算 Binary Operations
  1. 按位二元运算 Bitwise Binary Operations
  1. 内存访问和寻址操作 Memory Access and Addressing Operations
  1. 类型转换操作 Conversion Operations
  1. 其他操作指令 Other Operations

终结指令

ret 指令

函数的返回指令,对应 C/C++ 中的 return
指令源码
示例
返回一个结构体了解即可,其他重点掌握

br 指令

br 是”分支”的英文 branch 的缩写,分为非条件分支和条件分支,对应 C/C++的 if 语句。
无条件分支类似于 x86 汇编中的 jmp 指令,条件分支类似于 x86 汇编中的 jnz, je 等等条件跳转指令。
指令源码
示例

switch 指令

分支指令,可看做是 br 指令的升级版,支持的分支更多。对应 C/C++ 中的 switch
指令源码
示例

比较指令

前置知识

在 x86 汇编中,条件跳转指令 (jnz, je 等) 通常与比较指令 cmp, test 等一起出
现。(cmp 和 test 的差别就是 cmp 做减法,test 做与运算)
在 LLVM IR 中也有这样的指令,他们通常与条件分支指令 br 一起出现。也就是下面的这两个指令 icmp 和 fcmp

icmp 指令

整数或指针的比较指令
条件 cond 可以是
  • eq (相等)
  • ne (不相等)
  • ugt (无符号大于)
  • ult (无符号小于)
  • ule (无符号小于等于)
  • sgt (符号大于)
  • sge (符号大于等于)
  • 等等
指令源码
示例

fcmp 指令

浮点数的比较指令
条件 cond 可以是
  • oeq(ordered and equal)相等
  • ueq(unordered orequal)不等
  • false(必定不成立)
  • 等等
ordered 的意思是,两个操作数都不能为 NAN
指令源码
示例

二元运算指令

add 指令

整数加法指令,对应 C/C++ 中的“+”操作符,类似 x86 汇编中的 add 指令
指令源码
示例

sub 指令

整数减法指令,对应 C/C++ 中的“-”操作符,类似x86汇编中的 sub 指令
指令源码
示例

mul 指令

整数乘法指令,对应 C/C++ 中的“*”操作符,类似 x86 汇编中的 mul 指令
指令源码
示例

udiv 指令

无符号整数除法指令,对应 C/C++ 中的“/”操作符。如果存在 exact 关键字,且 op1不是op2的倍数,就会出现错误
指令源码
示例

sdiv 指令

有符号整数除法指令,对应 C/C++ 中的“/”操作符
指令源码
示例

urem 指令

无符号整数取余指令,对应 C/C++ 中的“%”操作符
指令源码
示例

srem 指令

有符号整数取余指令,对应 C/C++ 中的“%”操作符
指令源码
示例

按位二元运算指令

shl 指令

整数左移指令,对应 C/C++ 中的“<<”操作符,类似x86汇编中的 shl 指令
指令源码
示例

lshr 指令

整数逻辑右移指令,对应 C/C++ 中的“>>”操作符,右移指定位数后在左侧补0
指令源码
示例

ashr 指令

整数算数右移指令,右移指定位数后在左侧补符号位(负数的符号位为 1,正数的符号 位为 0)
指令源码
示例

and 指令

整数按位与运算指令,对应 C/C++ 中的“&”操作符
指令源码
示例

or 指令

整数按位或运算指令,对应 C/C++ 中的“|”操作符
指令源码
示例

xor 指令

整数按位异或运算指令,对应 C/C++ 中的“^”操作符
指令源码
示例

内存访问和寻址操作指令

前置知识

在编译器设计中,静态单赋值(Static Single Assignment, SSA),是 IR 的一种属性,简单来说,SSA 的特点是:在程序中一个变量仅能有一条赋值语句
例如
notion image
要改成 ssa 特点其实很简单,只需要单独下一个下标,但这样的解决方案存在一个问题
notion image
再看一个
假设 C++ 也是基于静态单赋值原则的(即一个变量只能被赋值一次),要怎样修改
这个 for 循环,使其符合 SSA 原则?
这个代码,每一次循环 i 都会被赋值一次,那我们如何修改呢,用上面的方法好像不太适用
这时候,就需要对内存进行操作了,办法就是用指针直接操作 i 变量的内存

alloca 指令

内存分配指令,在栈中分配一块空间并获得指向该空间的指针,类似于 C/C++ 中的 malloc 函数
指令源码
示例

store 指令

内存存储指令,向指针指向的内存中存储数据,类似于 C/C++ 中的指针解引用后的 赋值操作
指令源码
示例

load 指令

内存读取指令,从指针指向的内存中读取数据,类似于 C/C++ 中的指针解引用操 作
指令源码
示例

类型转换操作指令

trunc .. to 指令

截断指令,将一种类型的变量截断为另一种类型的变量。对应 C/C++ 中大类型向 小类型的强制转换(比如 long 强转 int)
指令源码
示例

zext .. to 指令

零拓展(Zero Extend)指令,将一种类型的变量拓展为另一种类型的变量,高位补 0。对应 C/C++ 中小类型向大类型的强制转换(比如 int 强转 long)
指令源码
示例

sext .. to 指令

符号位拓展(Sign Extend)指令,通过复制符号位(最高位)将一种类型的变量拓展为 另一种类型的变量
指令源码
示例

其他操作指令

phi 指令

  • phi 指令可以看做是为了解决 SSA 一个变量只能被赋值一次而引起的问题衍生出的
指令
  • phi 指令的计算结果由 phi 指令所在的基本块的前驱块确定
指令源码
示例

select 指令

select 指令类似于 C/C++ 中的三元运算符”... ? ... : ...”
指令源码
示例

call 指令

call 指令用来调用某个函数,对应 C/C++ 中的函数调用,与 x86 汇编中的 call 指 令类似
指令源码
示例

LLVM IR 指令实战

简单的读一下这个代码
notion image

总结

LLVM IR 这一部分,官方文档用了大片章节对此进行赘述,这是 llvm 的灵魂,也是最值得花费时间的地方,花了两天,终于打字打完了

参考于

上一篇
搞定ida结构体和导入sig签名库
下一篇
DES加密算法