使用 Ollvm混淆 Hello World
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 的兼容库)、libclibcxxopenmp 等模块。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。

步骤如下:

  1. 下载 LLVM 17.0.6 源码。
  2. 将 OLLVM 混淆模块合并至源码目录。
  3. 使用 CMake 生成 Visual Studio 工程。
  4. 安装 Incredibuild 加速编译。
  5. 仅编译生成 clanglld

约 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 项目)。

参考资料 #