title: "使用OLLVM混淆Hello World"
weight: 1
# bookFlatSection: false
# bookToc: true
# bookHidden: false
# bookCollapseSection: false
# bookComments: false
# bookSearchExclude: false
背景 #
在某些功能开发过程中,可能会出现规避杀软的需求。然而,程序常常被 360 杀毒检测出,无论是静态分析、沙箱测试,还是运行时拦截都难以避免。
考虑到程序代码可能过于简单,我们尝试通过增加代码复杂度的方式来降低被检测的风险。LLVM 是一个备受瞩目的编译器基础设施项目,曾有开发者借助其混淆功能达到相关目的,因此本项目也决定以此为切入点。
网络资料内容良莠不齐,因此本文选择通过实践深入理解。
LLVM 简介 #
以下内容整理自维基百科:
LLVM 项目最初由伊利诺伊大学厄巴纳-香槟分校的 Vikram Adve 和 Chris Lattner 于 2000 年发起,目标是为各种静态和动态语言开发统一的编译技术。LLVM 使用 BSD 协议进行开源,2005 年 Chris Lattner 被苹果公司聘用,其团队所开发的技术成为 macOS 和 iOS 开发工具的重要组成部分。
“LLVM” 最初意指 “Low Level Virtual Machine”,但由于项目发展已超出虚拟机范畴,该缩写的含义已被官方弃用。目前,LLVM 泛指包括 LLVM IR、调试工具、C++ 标准库等在内的编译工具集合。
LLVM 项目结构 #
克隆 llvm-project
(版本 18.1.8)后,可以看到如下目录结构:
例如其中的 lldb
目录,即是 VSCode 中 CodeLLDB 所依赖的调试器。
本项目更关注的是编译器相关内容而非调试器。此外,项目中还包含如 lld
(可能为链接器)、libgcc
(GCC 的兼容库)、libc
、libcxx
及 openmp
等模块。LLVM 的模块化架构使其具备强大的扩展能力。
LLVM 与编译器 #
传统编译器如 GCC、MSVC、Clang 等,内部也采用不同架构。下图展示了 GCC 和 LLVM 的架构对比:
-gcc:
-llvm:
需要说明的是,Clang 实际上只是 LLVM 的前端,负责将 C/C++ 源码转换为中间表示(IR),其后端由 LLVM 提供支持:
由于 LLVM 的优化器在编译流程中占据核心位置,我们可以在此阶段插入混淆模块。
为什么选择 LLVM #
虽然 GCC 理论上也可以添加混淆功能,但其架构较为老旧,耦合度高,扩展性差。
相比之下,LLVM 的前后端分离结构更利于定制。我们可以仅修改优化器部分以实现混淆,而不会影响前端语言支持或其他功能,因此 LLVM 是更优选择。
OLLVM (Obfuscator-LLVM) #
OLLVM 是由瑞士西北应用科技大学安全实验室于 2010 年启动的项目,旨在为 LLVM 增加代码混淆功能,以增加逆向分析的难度。
本质上,OLLVM 在 LLVM 优化器中增加了混淆模块。尽管原项目仅维护了四个版本后停止更新,但其思想已被广泛继承,产生了多个分支与改进版本。
实践 #
我们将通过编译并使用 OLLVM 实现 Hello World 的代码混淆。
编译 OLLVM #
当前网络中存在多个版本的 OLLVM,涵盖 LLVM 3.3 至 18。混淆模块的位置因版本和开发者而异,常见路径包括:
llvm/lib/Transforms
llvm/lib/Passes
llvm/lib
为简化流程,本文选择使用 GitHub 上的 DreamSoule/ollvm17,其基于 LLVM 17.0.6。
步骤如下:
- 下载 LLVM 17.0.6 源码。
- 将 OLLVM 混淆模块合并至源码目录。
- 使用 CMake 生成 Visual Studio 工程。
- 安装 Incredibuild 加速编译。
- 仅编译生成
clang
与lld
。
约 20 分钟后可在 Release 目录下获得最终产物,建议将其拷贝至独立目录,例如 ollvm-17.0.6
。
配置 VS 使用 OLLVM #
安装 Clang 支持 #
在 Visual Studio Installer 中选择 “修改”,进入 “单个组件”,搜索并勾选 Clang 相关选项。
创建 Hello World 工程 #
我们通过 CMake 创建一个简单的 Hello World 工程:
#include <iostream>
int main() {
std::cout << "hello world" << std::endl;
}
配置自定义 LLVM 工具链 #
在工程根目录添加 Directory.build.props
文件:
<Project>
<PropertyGroup>
<LLVMInstallDir>F:\ollvm-17.0.6</LLVMInstallDir>
<LLVMToolsVersion>17</LLVMToolsVersion>
</PropertyGroup>
</Project>
项目属性中选择:
- General -> Platform Toolset -> LLVM
配置完成后可看到:
-
启用混淆 #
以下是常见的混淆选项及其原理:
-bcf
:虚假控制流(Bogus Control Flow),插入无效的分支结构,使控制流图更加复杂,从而增加静态分析难度。-fla
:控制流扁平化(Control Flow Flattening),将程序中的控制流打散统一调度,令原有函数结构混乱,增加逆向难度。-sub
:指令替换(Instruction Substitution),将常见指令替换为功能等价的指令组合,扰乱指令识别与语义分析。-sobf
:字符串或结构体混淆(String Obfuscation),通过编码/变换方式隐藏字符串内容或打乱结构布局,防止关键数据被定位。-split
:基本块拆分(SplitBasicBlock),将原有基本块拆成更细粒度的多个块,干扰控制流结构。-ibr
:间接跳转(Indirect Branch),将直接跳转替换为间接方式,增加流程模糊性。-igv
:间接全局变量访问(Indirect Global Variable),增加对全局变量的访问复杂度。-icall
:间接函数调用(Indirect Call),将函数调用通过函数指针等方式间接调用,规避静态分析器识别。-fncmd
:函数名控制混淆(Function Name Control),可通过函数名携带混淆命令标志,进一步自动化控制混淆策略。
以 -bcf
为例,在项目属性 C/C++ -> Command Line
添加:
-mllvm -bcf
点击编译,即可生成混淆后的可执行文件。
分析混淆效果 #
使用 IDA 反汇编前后版本对比:
- 原始程序:
- 混淆后程序:
- 运行截图:
混淆后的程序结构明显复杂化,而功能保持一致。
在 CGO 中使用(Windows) #
将 CGO 使用的编译器替换为 OLLVM 生成的 clang:
go env -w CC=F:\tttt\mingw64\bin\clang.exe
go env -w CXX=F:\tttt\mingw64\bin\clang++.exe
go env -w CGO_ENABLED=1
go env -w CGO_CFLAGS=-Xclang -mllvm -Xclang -bcf -Xclang -mllvm -Xclang -sobf
注意:上述参数应仅用于生产构建,建议避免在开发环境全局设置,以免 gopls 分析时造成性能问题。
此外,如将 CGO_CFLAGS
永久写入 go env
中,会造成每次打开 Go 工程时 clang.exe
被 gopls 自动调用进行语义分析(即便未手动构建),从而显著增加内存占用,尤其是在加了 OLLVM 参数后。
建议在日常开发中恢复默认 CFLAGS,仅在构建发布版本时使用混淆参数。
示例代码 #
package main
/*
#include <stdio.h>
void hello() {
printf("Hello from C\n");
}
*/
import "C"
func main() {
C.hello()
}
混淆前:
混淆后:
CGO 下的 Linux 使用 #
同样适用于 Linux 环境。
Clang 混淆后的库是否可被 GCC 使用? #
这取决于混淆方式及接口风格:
✅ 通常可行的情形: #
- 使用标准 C 接口(如
extern "C"
的函数) - 同一平台、架构、ABI(如都是 x86_64 Linux,或 Windows 下 MinGW)
- 混淆未破坏 ABI(函数参数、返回值未改动)
⚠️ 高风险情形: #
- 使用 C++ 接口(如类、虚函数、异常处理)
- 启用了复杂混淆(如控制流、结构体替换)
- 链接方式过于依赖符号名(如不导出明确定义的 C 接口)
✅ 建议: #
- 若需 GCC 使用 OLLVM 编译的库,请使用纯 C 接口,避免
class
和异常。 - 编译为
.a
或.so
后手动测试链接与运行。
示例(C 接口): #
// mylib.h
#ifdef __cplusplus
extern "C" {
#endif
void hello();
#ifdef __cplusplus
}
#endif
// mylib.c
#include <stdio.h>
void hello() {
printf("Hello from OLLVM\n");
}
Clang + OLLVM 编译 mylib.c
,GCC 编译主程序链接使用,可正常运行。
备注 #
以上cgo已经在以下环境下成功实践 :
-
gcc.exe (MinGW-W64 x86_64-posix-seh, built by Brecht Sanders) 8.5.0
-
clang.exe clang version 17.0.6 Target: x86_64-w64-windows-gnu (该版本由上面 mingw gcc 8.5.0编译)
-
go 1.24.1
总结 #
本文展示了使用 OLLVM 对 Hello World 程序进行混淆的完整流程,验证了通过 LLVM 架构增强程序安全性的可行性。
LLVM 架构强大灵活,为实现语言后端、编译优化、混淆保护等功能提供了理想平台。未来如 Golang 等语言也有望接入 LLVM 编译链(例如 Google 的 Gollvm 项目)。
参考资料 #
- https://www.fup1p1.cn/archives/reversellvm%E4%B8%8E%E4%BB%A3%E7%A0%81%E6%B7%B7%E6%B7%86%E6%8A%80%E6%9C%AF%E7%AC%94%E8%AE%B0
- https://blog.csdn.net/mentalhealing/article/details/133465600
- https://learn.microsoft.com/zh-cn/cpp/build/clang-support-msbuild
- https://github.com/obfuscator-llvm/obfuscator
- https://github.com/DreamSoule/ollvm17
- https://github.com/bluesadi/Pluto