llvm学习(三):移植ollvm等作品到单独编译的Pass

上篇讲了单独编译一个 Pass,加载起来非常优雅,而ollvm等项目非常不优雅,为了方便开发,就把它们都移植了一下。工欲善其事必先利其器,突然发现这玩意支持 CLion,开发起来更加优雅了。

一、使用 CLion 开发

接着上篇,在llvm-pass-tutorial 项目里,直接用 CLion 打开就可以了,效果如图。。。我也不知道怎么解释比较好。

二、ollvm 等项目核心代码位置

以ollvm为例,作者直接将整个 llvm 都传到了 github 上,后人下载的话都得把好几十兆的代码拉下来,但其实他只是写了几个 Pass 而已,完全没有必要拿全部的代码。

只有两个位置,分别在:

1
2
lib/Transforms/Obfuscation/
include/llvm/Transforms/Obfuscation/

和一处巨tm隐蔽的位置,不知道作者为什么要放这么隐蔽,理解不来。

1
include/llvm/CryptoUtils.h

lib/Transforms

目录下还放着一些其他的 Pass,代码写不出来时候可以抄抄看。


如果只为了肉眼看一下这部分代码的话,推荐一款工具,可以下载某分支的指定目录,叫 DownGit https://minhaskamal.github.io/DownGit/,非常好用,谁用谁知道。

三、移植ollvm

好了,代码都拿到了,这里讲一下如何移植吧,曾经移植了我一整天,脑阔疼!顺便吐槽一下三个项目作者的代码风格,一个比一个不规范。。。完成的作品放在了 https://github.com/LeadroyaL/llvm-pass-tutorial/tree/dev ,每次commit记录都在,在dev分支上面,不在master上,我懒得改了!!! 主要做以下几件事

  1. 编写CMakeLists.txt
  2. 改写include文件的路径
  3. 修其他 bug,检查有没有漏掉的东西
  4. 创建注册的入口,让clang能加载到它

先在外层的 CMakeLists.txt 里加上

1
add_subdirectory(ollvm)  # ollvm

创建个目录,把那些cpp文件都丢进去。

1
2
3
4
5
6
7
8
9
10
11
12
13
➜  ollvm git:(dev) lsa
total 256
drwxr-xr-x 12 leadroyal staff 384B 11 4 22:06 .
drwxr-xr-x 14 leadroyal staff 448B 11 4 22:06 ..
-rw-r--r-- 1 leadroyal staff 28K 11 4 22:06 BogusControlFlow.cpp
-rw-r--r-- 1 leadroyal staff 1.1K 11 4 22:06 CMakeLists.txt
-rw-r--r-- 1 leadroyal staff 48K 11 4 22:06 CryptoUtils.cpp
-rw-r--r-- 1 leadroyal staff 755B 11 4 22:06 Enter.cpp
-rw-r--r-- 1 leadroyal staff 7.0K 11 4 22:06 Flattening.cpp
-rw-r--r-- 1 leadroyal staff 2.2K 11 4 22:06 LICENSE-OBFUSCATOR.TXT
-rw-r--r-- 1 leadroyal staff 3.5K 11 4 22:06 SplitBasicBlocks.cpp
-rw-r--r-- 1 leadroyal staff 18K 11 4 22:06 Substitution.cpp
-rw-r--r-- 1 leadroyal staff 4.3K 11 4 22:06 Utils.cpp

为了方便管理,再创建一个include目录,里面放那些头文件。记得把那个傻逼的 CryptoUtils.h 也复制过来。

1
2
3
4
5
6
7
8
9
10
11
➜  include git:(dev) tree
.
├── Transforms
│ └── Obfuscation
│ ├── BogusControlFlow.h
│ ├── Flattening.h
│ ├── Split.h
│ ├── Substitution.h
│ └── Utils.h
└── llvm
└── CryptoUtils.h

以 BogusControlFlow.cpp 为例,原本内容是:

1
2
#include "llvm/Transforms/Obfuscation/BogusControlFlow.h"
#include "llvm/Transforms/Obfuscation/Utils.h"

但我觉得不优雅,就把最外面的那层给删了。(其实可以删更多的,把Transforms 删了也行,只是写这篇文章时候发现当初删的少了,其实可以多删一点的)

1
2
#include "Transforms/Obfuscation/BogusControlFlow.h"
#include "Transforms/Obfuscation/Utils.h"

创建子模块,把那些文件都包含进去,其他部分从 demo 里复制一下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
add_library(Ollvm MODULE
# List your source files here.
BogusControlFlow.cpp
CryptoUtils.cpp
Flattening.cpp
SplitBasicBlocks.cpp
Substitution.cpp
Utils.cpp
include/llvm/CryptoUtils.h
include/Transforms/Obfuscation/BogusControlFlow.h
include/Transforms/Obfuscation/Flattening.h
include/Transforms/Obfuscation/Split.h
include/Transforms/Obfuscation/Substitution.h
include/Transforms/Obfuscation/Utils.h
)

将所有的include 都改掉之后,大部分的符号都可以被找到了,这时候点击编译,大概率会不给过的,应该还有几处 Not Found,边搜边改即可,我记得有一处是 Utils.h 的冲突引起的,得手动操作一下。反正不大记得了,参考github里的代码就行。

之后写一个注册它们的函数,例如这个(具体每句话我会在下一篇文章解释的,这里先大概看看),在最开始就注册我们的各个 Pass,很优雅。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include "Transforms/Obfuscation/BogusControlFlow.h"
#include "Transforms/Obfuscation/Flattening.h"
#include "Transforms/Obfuscation/Split.h"
#include "Transforms/Obfuscation/Substitution.h"
#include "llvm/IR/LegacyPassManager.h"
#include "llvm/Transforms/IPO/PassManagerBuilder.h"

using namespace llvm;

static void registerOllvmPass(const PassManagerBuilder &,
legacy::PassManagerBase &PM) {
PM.add(createBogus(true));
PM.add(createFlattening(true));
PM.add(createSplitBasicBlock(true));
PM.add(createSubstitution(true));
}
static RegisterStandardPasses
RegisterMyPass(PassManagerBuilder::EP_EarlyAsPossible,
registerOllvmPass);

此时,点击Build按钮,就可以找到那个 libollvm.so 了。

然后使用 clang 的命令去加载这个so,执行可以正常运行,使用ida打开发现跟 shit 一样,就

1
➜  /tmp clang -I/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.14.sdk/usr/include -Xclang -load -Xclang /Users/leadroyal/CLion_code/llvm-pass-tutorial/cmake-build-debug/ollvm/libollvm.so -w test.c -o test.bin

熟悉的配方,熟悉的味道,对不对!

四、移植Armariris、Hikari

大部分内容跟之前的相同,需要注意的一点,就是它们包含 ModulePass,而 ModulePass 的注册方法和 FunctionPass 的注册方法是不一样的,如果使用 PassManagerBuilder::EP_EarlyAsPossible ,会导致编译 Crash,我猜是因为还没初始化好,不大懂。

当时搜了一下午一晚上,最后在AFL源码里找到一段代码。。。。抄来可以用。。。真有意思。。。还好我先移植的是 Armariris,就三个 Pass。

改为下面,这样,在不同时机注册2次的方法,就可以保证稳稳地被加载了。

参考链接:

https://github.com/mirrorer/afl/blob/master/llvm_mode/afl-llvm-pass.so.cc

https://github.com/sampsyo/llvm-pass-skeleton/issues/7

1
2
3
4
5
6
7
8
9
10
11
12
static void registerArmaririsModulePass(const PassManagerBuilder &,
legacy::PassManagerBase &PM) {
PM.add(createStringObfuscation(true));
}

static RegisterStandardPasses
RegisterMyPass(PassManagerBuilder::EP_EnabledOnOptLevel0,
registerArmaririsModulePass);
static RegisterStandardPasses
RegisterMyPass0(PassManagerBuilder::EP_OptimizerLast,
registerArmaririsModulePass);

五、总结

完成的作品放在了

https://github.com/LeadroyaL/llvm-pass-tutorial/tree/dev ,每次commit记录都在,在dev分支上面,不在master上,我懒得改了!!!

本篇讲了3个移植的案例,都可以正常运行,这样开发、阅读、调试起来就很优雅了。

吐槽一下吧,先说 ollvm ,为什么要把 CryptoUtils.h 丢到那么隐蔽的位置;

再吐槽 Armariris ,连 gitignore 都不会写,一堆垃圾文件都被传上去了,代码里面全是无用的调试信息;

最后吐槽 Hikari ,在头文件里包含不必要的头文件,重复 include,非常不优雅。

最后,向三个开源项目的作者致敬,感谢开源,让后人能够更快接触到 llvm 的 pass编写,感谢椒哥持续的帮助和不厌其烦的答疑。

下集预告:简单的小知识和代码分析~~~