llvm代码混淆学习(零)
写在前面
随便学学 llvm 代码混淆,顺便做个毕设,水几篇文章就当做做笔记
LLVM 简介
一个包含了很多模块的编译器框架,因为其 LLVM Pass 框架的特殊性,能够干预中间代码的优化过程,跟代码混淆技术契合度很高,所以 LLVM 编译器常被用来研究代码混淆。
LLVM 编译过程
前端:Clang
前端对高级语言源代码进行词法分析、语法分析和语义分析,最后产生中间代码 LLVM IR。
优化器,后端:LLVM Core
优化器对中间代码 LLVM IR 进行优化,并且能够加载 LLVM Pass 执行用户自定义的优化(本研究用于代码混淆)。
后端则根据优化后的代码生成目标平台的机器代码。
LLVM 目录结构
- llvm/include/llvm:LLVM 提供的一些公共头文件
- llvm/lib:LLVM大部分源代码和一些不公开的头文件
- llvm/lib/Transforms:所有 LLVM Pass 的源代码和一些 LLVM 自带的 Pass
LLVM 环境搭建
开发环境
- Ubuntu 18.04
- LLVM 12.01
- Cmake 3.22.1
用 Ubuntu 18.04 编译 LLVM 12.01 时,cmake 版本指定为 3.13 以上,直接用 apt 安装的 cmake 是 3.10.2,所以需要自己下载高版本的 cmake。
步骤:
1
2
3
4
5
6
7
8 wget https://cmake.org/files/v3.22/cmake-3.22.1.tar.gz
tar -zxvf cmake-3.22.1.tar.gz
mv cmake-3.22.1 /usr/local/bin/cmake
cd /usr/local/bin/cmake
./bootstrap
make
sudo make install
sudo ln -sf /usr/local/bin/cmake/bin/* /usr/bin/问题:Could NOT find Open SSL
1 sudo apt-get install libssl-dev问题:Cmake error could not find CMAKE_ROOT
1 hash -r
第一步:下载 LLVM-Core 和 Clang 源代码
下载地址:Release LLVM 12.0.1 · llvm/llvm-project · GitHub
在 /home/llvm/Programs
文件夹内创建 llvm-project
文件夹,存放下载的源码压缩包。
将两个压缩包解压之后改名为 llvm 和 clang ,方便后续使用。
在同一文件夹内创建名为 build
的文件夹,存放编译后的 LLVM。 此时的目录结构如下:
第二步:编译 LLVM 项目
在 llvm-project 目录下创建 build.sh
,写入下面内容。
1 | cd build |
cmake 参数解释:
-G “Unix Makefiles”:生成Unix下的Makefile
-DLLVM_ENABLE_PROJECTS=”clang”:除了 LLVM Core 外,还需要编译的子项目。
-DLLVM_BUILD_TYPE=Release:在 cmake 里,有四种编译模式:Debug, Release, RelWithDebInfo, 和MinSizeRel。使用 Release 模式编译会节省很多空间。
-DLLVM_TARGETS_TO_BUILD=”X86”:默认是 ALL,选择 X86 可节约很多编译时间。
-DBUILD_SHARED_LIBS=On:指定动态链接 LLVM 的库,可以节省空间。
make install 指令是将编译好的二进制文件和头文件等安装到本机的 /usr/local/bin 和 /usr/local/include 目录,方便后续使用。
执行 build.sh
文件自动安装和编译,编译时长从十多分钟到数小时,具体时间由机器性能决定。
执行
build.sh
文件必须要有管理员权限
输入 clang -v
确认编译和安装是否完成:
LLVM Pass 简介
LLVM Pass 框架是整个 LLVM 提供给用户用来干预代码优化过程的框架,可以用来编写代码混淆工具,也可以用来写 fuzz、hook
编译后的 LLVM Pass 通过优化器 opt 进行加载,可以对 LLVM IR 中间代码进行分析和修改,生成新的中间代码
编译方式
第一种:和整个 LLVM 一起重新编译,Pass 代码需要放在 llvm/lib/Transforms 文件夹中,比较耗时
第二种:通过 Cmake 对 Pass 进行单独编译,方便快捷
第三种:使用命令行对 Pass 进行单独编译,项目越大越不好管理
Pass 类型
设计新 LLVM Pass 时,最先需要决定的就是选择 Pass 的类型,包括:
- ModulePass(基于模块)
- FunctionPass(基于函数)
- CallGraphPass(基于调动图)
- LoopPass(基于循环)
毕设学习重点是 FunctionPass
- FunctionPass 以函数为单位处理
- FunctionPass 的子类必须实现 runOnFunction(Function &F)函数
- FunctionPass 运行时会对程序中每一个函数执行 runOnFunction 函数
简单编写
- 创建一个类(class),继承 FunctionPass 父类
- 在创建的类中实现 runOnFunction(Function &F)函数
- 向 LLVM 注册 Pass 类
代码模板
1 |
|
编译
- 直接使用 Cmake 进行编译
- 在 Build 文件夹内可以找到编译好的 so 文件
加载
使用优化器 opt 将处理中间代码,生成新的中间代码,例:
1 | opt -load ./LLVMObfuscator.so -hlw -S hello.ll -o hello_opt.ll |
-load
加载编译好的 LLVM Pass 进行优化
Cmake 项目创建
目录结构
- Build
- Test
- TestProgram.cpp
- Transforms
- include
- src
- HelloWorld.cpp
- CMakeLists.txt
- test.sh
目录介绍
Build:存放编译后的 LLVM Pass
Test:存放测试程序 TestProgram.cpp
TestProgram.cpp:待混淆文件,内容如下:
1 |
|
Transforms/include 文件夹:存放整个 LLVM Pass 项目的头文件
Transforms/src 文件夹:存放整个 LLVM Pass 项目的源代码
Transforms/src/HelloWorld.cpp:HelloWorld Pass 的源代码,一般一个 Pass 使用一个 cpp 文件实现即可
Transforms/CMakeLists.txt:整个 CMake 项目的配置文件,内容如下:
1 | # 参考官方文档:https://llvm.org/docs/CMake.html#developing-llvm-passes-out-ofsource |
test.sh:编译 LLVM Pass 并对 Test 文件夹中的代码进行测试,内容如下:
1 | cd ./Build |
加上
-fno-discard-value-names
选项是为了在生成文本 IR 时部分变量的分配保留源代码中的命名方式,使得其更易于阅读。如果不加的话,变量名就全是数字,难以阅读
不加参数:
加上参数后: