lichao 发表于 2026-3-16 15:48:50

OLLVM学习成果展示 - SLLVM

本帖最后由 lichao 于 2026-3-16 15:58 编辑


笔者于2024年8月起间断研究OLLVM至今已有1年多, 在学习初期发过如下如下帖子
* (https://www.0xaa55.com/thread-27506-1-1.html)
* (https://www.0xaa55.com/thread-27509-1-1.html)
* (https://www.0xaa55.com/thread-27510-1-1.html)
* (https://www.0xaa55.com/thread-27511-1-1.html)
* (https://www.0xaa55.com/thread-27512-1-1.html)
* (https://www.0xaa55.com/thread-27513-1-1.html)

笔者在2025年初开始开发自己的SLLVM并在实际项目中使用, 实际使用已有1年多了, 期间不断优化增强并修复bug已经到第三个版本, 这里仅作展示, 后续完善后会开源部分源码:
[https://github.com/lich4/sllvm](https://github.com/lich4/sllvm/blob/main/README_zh.md)

## 支持的系统和架构

* 计划支持的宿主系统为`macOS`/`Linux`, 暂无计划支持`Windows`
* 计划支持的目标系统为`macOS`/`iOS`/`Android`
* 计划支持的宿主架构和目标架构均为`X64`/`ARM64`
* 计划支持`C`/`C++`/`Objective-C`/`Objective-C++`/`Swift`/`Rust`/`Golang`等语言

## 混淆能力

* 数据加密
    * (#字符串加密)         只设计字符串混淆
* 控制流混淆
    * (#控制流平坦化)      控制流平坦化
    * (#控制流伪造)      控制流伪造
    * (#控制流另类混淆)    控制流另类混淆
* 函数级混淆
    * (#函数嵌套)         函数嵌套
    * (#调用约定混淆)      调用约定混淆
* 指令级混淆
    * (#间接跳转)          间接跳转
    * (#间接调用)      间接调用
    * (#间接全局变量)      间接数据引用
    * (#系统调用混淆)      系统调用混淆
    * (#拆分指令)      拆分指令
* 其他
    * (#内联子函数)   内联子函数
    * (#安全防护)          反调试

---

### SLLVM的特点

目前活跃开源`OLLVM`项目有: 原版OLLVM, Hikari, Hikari-LLVM15, Pluto, Polaris-Obfuscator, goron, Arkari, o-mvll, ...
`SLLVM`和其他`OLLVM`()的区别主要在以下方面:
* 支持Release编译, 在Release下编译时混淆不会被编译器还原
* 抗'改数据段属性只读'攻击, 在`IDA`中设置数据段为只读后混淆不会被还原
* 对已有的混淆功能加固+去特征, 由于`OLLVM`已经被广泛使用, 很容易被特定脚本/Angr/AI针对(笔者也自行开发过通用还原脚本)
* 在混淆大工程和使用header-only头文件时不会内存爆和长时间编译卡死

---

## 配置

在很多实际项目中, 由于以下原因无法对整个项目完全混淆:
* 项目较大, 依赖较多, 或使用了很多`header-only`的库, 混淆了很多不需要混淆的代码, 导致编译出来的二进制过大
* 项目较大, 依赖较多, 使用了平坦化(或其他方式)混淆了很多不需要混淆的代码, 导致编译时间过久甚至卡死
* 混淆了复杂算法, 导致运行时耗时比正常大很多, 一般使用平坦化后耗时会增加10%以上
* 混淆过多可能不允许上架`AppStore`/`GooglePlay`等

实际操作时, 常常需要根据模块/函数的重要性使用不同程度的混淆, 因此需要配置策略来指定哪些模块/函数需要用哪种混淆, 而开源的`OLLVM`常见设置策略的方式如下:
* 对需要混淆的模块单独指定命令行参数, 如`-llvm -fla`, 这种方式兼容所有支持`LLVM`命令行参数的编译器前端
* 使用环境变量指定混淆参数
* 对需要混淆的函数指定注解, 如`__attribute((__annotate__(("fla"))))`(新式语法`[]`), 这种方式仅支持`C/C++`, `Objective-C`和其他语言均不支持
* 对需要混淆的函数指定标记函数, 如下所示, 这种方式支持`Objective-C`
```objc
extern void hikari_fla(void);
@implementation foo2:NSObject
+(void)foo{
hikari_fla();
NSLog(@"FOOOO2");
}
@end
```

以上方式均有局限性, 或对代码改动太大, 或无法控制到函数粒度, 或只支持特定语言。本项目使用配置文件(`sllvm.json`)来指定需要混淆的函数和模块, 兼容大部分编译器前端及开发语言。

### 一级/二级字段

* `log_level`   全局日志等级, 字符串类型, 可选字段, 默认无日志, 可选值: `info`|`debug`
* `src_root`    源文件路径, 字符串类型, 可选字段, 默认为当前目录, 一般无需指定
* `policies`    策略列表, 分为 模块级策略 和 函数级策略, 模块级策略有`module`,`policy`字段而无`func`字段, 函数级策略同时有`module`,`func`,`policy`字段
    * `module`      正则表达式, 用于匹配模块路径
    * `func`      正则表达式, 用于匹配函数名, 对于C++函数会Demangling再匹配
    * `policy`      字符串类型, 值为`policy_map`的某个key
* `policy_map`策略索引, 用于在`policies`指定, 每个策略名对应一个字典, 字典字段如下:
    * `base`      继承的基策略名, 模块级或函数级策略, 字符串类型, 可选字段, 值为`policy_map`的某个key
    * `dump`      输出中间代码类型(`sllvm_dump`目录下), 模块级或函数级策略, 字符串数组类型, 默认空, 可选值: `ir`, `mmd`, `asm`
    * `enable_std`启用C++标准库函数混淆, 模块级策略, 布尔类型, 可选字段, 默认禁用以减少兼容问题

### 函数级策略

* `enable_ce`       启用`CE`混淆
    * `ce_size_min`   最小字符串长度
    * `ce_size_max`   最大字符串长度
    * `ce_algo`         加解密算法
    * `ce_mode_stack`   字符串基于栈解密
* `enable_fla`      启用`FLA`混淆
    * `fla_prob`      BasicBlock混淆比例
    * `fla_force_reg`   增加混淆强度
    * `fla_use_igv`   增加混淆强度
    * `fla_use_dyn`   增加混淆强度
    * `fla_use_rcf`   增加混淆强度
    * `fla_blk_size`    增加混淆强度
    * `fla_invoke_op`   兼容异常处理方式
* `enable_bcf`      启用`BCF`混淆
    * `bcf_prob`      BasicBlock混淆比例
    * `bcf_complex`   表达式复杂度
    * `bcf_use_var`   使用变量构造表达式
* `enable_ecf`      启用`ECF`混淆
    * `ecf_prob`      BasicBlock混淆比例
* `enable_fw`       启用`FW`混淆
    * `fw_loop_min`   函数嵌套层数最小值
    * `fw_loop_max`   函数嵌套层数最大值
    * `fw_exclude`      排除的子函数
* `enable_fcc`      启用`FCC`混淆
    * `fcc_num`         调用约定数量(模块级)
    * `fcc_type`      调用约定类型(模块级)
    * `fcc_narg_reg`    寄存器传参个数(模块级)
* `enable_ibr`      启用`IBR`混淆
    * `ibr_prob`      BasicBlock混淆比例
    * `ibr_use_igv`   增加混淆强度
    * `ibr_use_dyn`   增加混淆强度
* `enable_icall`    启用`ICALL`混淆
    * `icall_use_igv`   增加混淆强度, 默认开启
    * `icall_use_dyn`   增加混淆强度
* `enable_igv`      启用`IGV`混淆
    * `igv_use_dyn`   增加混淆强度
* `enable_svc`      启用`SVC`混淆
    * `svc_usr_ir`      增加混淆强度
* `enable_split`    启用`SPLIT`混淆
    * `split_maxsize`   拆分指令数
* `enable_inline`   启用`INLINE`混淆
* `enable_sec`      启用`SEC`混淆
    * `sec_ad_prob`   Function反调试插入比例
    * `sec_usr_ir`      增加混淆强度


一个典型的`SLLVM`配置文件`sllvm.json`如下:
```json
{
    "log_level": "info",
    "policy_map": {
      "mod_pol": {               
            "dump": ["ir"],
      },
      "func_pol": {               
            "enable_ce": true,
            "ce_size_min": 5,
            "ce_size_max": 128,
            "ce_algo": 0
      }
    },
    "policies": [
      {
            "desc": "模块级策略",
            "module": ".*",
            "policy": "mod_pol"
      },
      {
            "desc": "函数级策略",
            "module": ".*",
            "func": ".*",
            "policy": "func_pol"
      }
    ]
}
```

## 支持的混淆方式

### 字符串加密

目前`SLLVM`的`CE`支持`arm64`/`arm64e`, 支持`Objective-C`常量字符串, 支持栈解密`ce_mode_stack`, `CE`是函数级混淆而非`Hikari`的模块级, 因此可以对指定函数中的所有字符串做混淆, 这样在处理`header-only`更方便.

与`Hikari`的区别:
* 支持XOR以外的加密算法
* 支持栈解密(`ce_mode_stack`)

#### `ce_algo`

用于设置加解密算法, 目前支持30种算法, 复杂度介于XOR-AES之间, 取100则随机算法

#### `ce_mode_stack`

用于控制是否在栈上解密字符串. 若`ce_mode_stack`未开启, 则采取和`Hikari`同样的方式处理. `Hikari`字符串混淆, `SLLVM`的`ce_mode_stack`(S)模式, 基于C++模板元的字符串混淆, 这三种方式对比如下:

|项         |`Hikari`   |`SLLVM`S模式|C++模板元|
|-          |-          |-          |-          |
|加密位置   |静态区   |静态区      |静态区/立即数|
|解密时机   |函数头   |函数头      |引用前    |
|解密位置   |静态区   |栈区       |栈区       |
|需要改源码 |N          |N          |Y          |
|复杂算法   |支持       |支持       |不适合   |

说明:
* 解密时机, `Hikari`在函数头处解密且解密一次, 其他`OLLVM系`项目有的是在初始化函数解密的, 这种方式的缺点是解密一次即在静态区出现明文
* C++模板元方式只支持C++, 其他语言如Rust也有针对性的第三方库实现类似功能, 都需要对源码做变化

注意:
* 目前`OLLVM系`没有实现对字符串混淆进行栈解密的, 因为字符串常量的本质是静态数据, 难以在IR层判断字符串是否可能发生逃逸.
* 在`SLLVM`中启用`ce_mode_stack`后字符串会从静态数据降级为栈数据, 因此有特殊使用方式.

#### 展示

```c
int main(int argc, char** argv) {
    printf("hello sllvm\n");
    return 0;
}
```

CE静态区
!(https://raw.githubusercontent.com/lich4/sllvm/refs/heads/main/screenshots/sllvm_ce_static_algo.png)

CE栈区
!(https://raw.githubusercontent.com/lich4/sllvm/refs/heads/main/screenshots/sllvm_ce_stack_algo.png)

### 控制流平坦化

将控制流从顺序执行转换为switch-case循环执行, 与`Hikari`的区别:
* 弱化了大量`FLA`特征(状态变量/入度/分发块/单循环)
* `Hikari`无法处理有异常处理的函数, 而`SLLVM`可以通过指定`fla_invoke_op`选择处理方式

#### 展示

```c
int main(int argc, char** argv) {
    if (argc <= 0) {
      printf("not possible\n");
    } else if (argc == 1) {
      printf("no args\n");
    } else {
      printf("%d args\n", argc - 1);
    }
    return 0;
}
```

FLA全开
!(https://raw.githubusercontent.com/lich4/sllvm/refs/heads/main/screenshots/sllvm_fla_full.png)

### 控制流伪造

向顺序执行的控制流插入条件恒假的条件分支, 与`Hikari`的区别:
* 混淆不会在Release下编译时被编译器还原
* 混淆不会因为在`IDA`中设置数据段只读而被还原(依赖`bcf_use_var`)

#### 展示

```c
int main(int argc, char** argv) {
    if (argc <= 0) {
      printf("not possible\n");
    } else if (argc == 1) {
      printf("no args\n");
    } else {
      printf("%d args\n", argc - 1);
    }
    return 0;
}
```

BCF常量
!(https://raw.githubusercontent.com/lich4/sllvm/refs/heads/main/screenshots/sllvm_bcf_const.png)

BCF变量
!(https://raw.githubusercontent.com/lich4/sllvm/refs/heads/main/screenshots/sllvm_bcf_var.png)

### 控制流另类混淆

全新的混淆思路, 可以对抗`Angr`等工具跟踪

### 函数嵌套

对指定函数直接调用的子函数执行嵌套, 与`Hikari`的区别:
* 混淆不会在Release下编译时被编译器还原

### 调用约定混淆

对常规C调用约定转换为随机调用约定, 改变参数和返回值所用寄存器, 目前只实现ARM64的部分

* `fcc_num`         指定随机CallingConv的数量
* `fcc_type`      指定自定义CallingConv的类型, 取值如下
    * 0 仅使用`X0~X8`
    * 1 仅使用整数寄存器
    * 2 仅使用浮点寄存器
    * 10 使用任意寄存器
* `fcc_narg_reg`    寄存器最大传参个数, 其余参数用栈传递

#### 展示

```c
static int test(int a0, int a1, int a2, int a3, int a4) {
    printf("a0=%d\n", a0);
    printf("a1=%d\n", a1);
    printf("a2=%d\n", a2);
    printf("a3=%d\n", a3);
    printf("a4=%d\n", a4);
    return a0 + a1 + a2 + a3 + a4;
}

int main(int argc, char** argv) {
    test(argv, argv, argv, argv, argv);
    return 0;
}
```

FCC使用`X8(X8,X1,X6,...)`
!(https://raw.githubusercontent.com/lich4/sllvm/refs/heads/main/screenshots/sllvm_fcc_base.png)

FCC使用`D26(D26,D2,X15,...)`
!(https://raw.githubusercontent.com/lich4/sllvm/refs/heads/main/screenshots/sllvm_fcc_full.png)

### 间接跳转

与`Hikari`的区别:
* 混淆不会在Release下编译时被编译器还原
* `ibr_use_igv`和`Hikari`的`indibran-enc-jump-target`类似, 结合`ibr_use_dyn`可以进一步增加混淆强度

#### 展示

```c
int main(int argc, char** argv) {
    if (argc <= 0) {
      printf("not possible\n");
    } else if (argc == 1) {
      printf("no args\n");
    } else {
      printf("%d args\n", argc - 1);
    }
    return 0;
}
```

IBR全开
!(https://raw.githubusercontent.com/lich4/sllvm/refs/heads/main/screenshots/sllvm_ibr_full.png)

### 系统调用混淆

将系统调用函数转SVC

#### 展示

```c
int main(int argc, char** argv) {
    int r = access("/tmp/1.txt", 0);
    printf("r=%d\n", r);
    return 0;
}
```

SVC基础
!(https://raw.githubusercontent.com/lich4/sllvm/refs/heads/main/screenshots/sllvm_svc_base.png)

SVC自定义1
!(https://raw.githubusercontent.com/lich4/sllvm/refs/heads/main/screenshots/sllvm_svc_usr1.png)

### 拆分指令

将函数指令拆分到整个模块的随机地址

#### 展示

```c
void test(int argc) {
    if (argc <= 0) {
      printf("not possible\n");
    } else if (argc == 1) {
      printf("no arg\n");
    } else {
      printf("%d args\n", argc - 1);
    }
}

int main(int argc, const char** argv) {
    if (argc <= 0) {
      printf("not possible\n");
    } else if (argc == 1) {
      printf("no arg\n");
    } else if (argc == 2) {
      printf("1 arg\n");
    } else {
      printf("%d args\n", argc - 1);
    }
    return 0;
}
```

SPLIT全开
!(https://raw.githubusercontent.com/lich4/sllvm/refs/heads/main/screenshots/sllvm_split_full.png)

### 内联子函数

将指定函数的所有子函数内联到当前函数中

### 安全防护

将反调试逻辑插入到函数中

页: [1]
查看完整版本: OLLVM学习成果展示 - SLLVM