因为在逆向某软件的so的过程中见到了这种混淆方式,又看到作者实验室将工具开源了,特来见识一下

再引用一篇博文里的话

一看就while和switch,不是迷宫就是虚拟

介绍

llvm是一个底层虚拟机,LLVM obfuscator,是瑞士西北应用科技大学安全实验室的项目,代码混淆工具,实现so层面的混淆,真的是与Java层面的混淆完全不同,不过将原理应用在Java层面上也是可以做到的吧;其目标是提供一个llvm的开源分支,能够通过代码混淆和防篡改,增加对逆向工程的难度,提供更高的软件安全性

项目基于LLVM框架实现的开源代码混淆器,共有三种混淆方式:

  1. Instruction Substitution(指令变化)
  2. Bogus Control Flow(流程伪造)
  3. Control Flow Flattening(流程平坦化)

采用三段式设计,

  1. 前端可以使用不同的编译工具对代码文件进行词法分析(编译原理相关),以形成抽象语法树AST;
  2. 分析好的代码转换成LLVM的中间表示IR;
  3. 中间部分的优化器只对IR操作,通过Pass对IR进行优化;
  4. 后端负责将优化好的IR解释成对应平台的机器码

优点在于:中间表示的IR代码编写良好,而且不同的前端语言最终都转换成同一种IR;

在这里就不禁联想到,一篇高赞的关于方舟编译器的知乎回答:

计算机科学领域的任何问题都可以通过增加一个间接的中间层来解决

实操

首先安装、编译

git clone -b llvm-4.0 https://github.com/obfuscator-llvm/obfuscator.git

mkdir build
cd build
cmake -G "Unix Makefiles" -DCMAKE_BUILD_TYPE=Release -DLLVM_INCLUDE_TESTS=OFF ../obfuscator
make -j2

不知道为什么,原来人家博客里的方法没作用了,还是看到GitHub一个学长的解决方法才过了这个cmake;然后现在编译了快一年了_(:з)∠)_,我分配不了那么多cpu资源给虚拟机,还是一边慢慢跑一边看文章吧;

新建编译链

完成之后开始与NDK进行整合
在android-ndk/toolchains目录下新建ollvm-4.0/prebuilt/linux-x86_64,将build目录下编译好的内容cp进来

cd build
sudo cp -r * /opt/android-ndk/toolchains/ollvm-4.0/prebuilt/linux-x86_64

配置编译链

在android-ndk-r14b/build/core/toolchains目录下,新建目录arm-linux-androideabi-clang-ollvm4.0,拷贝目录arm-linux-androideabi-clang下的文件config.mk,setup.mk到arm-linux-androideabi-clang-ollvm4.0中,修改setup.mk文件

OLLVM_NAME := ollvm-4.0
LLVM_TOOLCHAIN_PREBUILT_ROOT := $(call get-toolchain-root,$(OLLVM_NAME))
LLVM_TOOLCHAIN_PREFIX := $(LLVM_TOOLCHAIN_PREBUILT_ROOT)/bin/

使用

Android.mk文件中添加混淆编译的参数:

LOCAL_CFLAGS += -mllvm -sub -mllvm -bcf -mllvm -fla

这样,就既配置了指令变化、流程伪造,又配置了流程平坦化,但是最有效的感觉还是流程平坦化吧

Application.mk添加以下内容

NDK_TOOLCHAIN_VERSION := clang-ollvm4.0

最后注意,需要的ndk版本为r14b,可前往这里进行归档版本的下载https://developer.android.com/ndk/downloads/older_releases.html;(其实是最新版的报错没有调

原流程

拿自己写的动态注册的so来测试一下,看到Linux上直接ndk-build还是和Android studio有差距,但是不大;放入IDA中并查看JNI_OnLoad的graph overview
p1

控制流程平坦化

控制流平展模式可以完全改变程序本身的控制流图;使用参数-mllvm -fla进行混淆;

FLA模式只会改变代码分支,不会对整个代码块做处理;

看到别人的观点,说原本简单的if-else判断,混淆之后就变成了类似switch-case的格式,具体的实现还有一种可能就是用一个变量控制流程,while循环,通过执行到函数块底部修改控制变量来执行其他函数块;

实操混淆之后的效果图如下:
P2

全用

混淆之后效果图如下:
P3

分析混淆后的函数

这一部分暂时放下,后续坐拥Binary Ninja之后再继续上手分析

总结

感觉这种混淆还是很常见的,可能方式使用地太混杂的话也是会影响性能的,毕竟要白白执行很多混淆流程。起码掌握了相关配置与原理,后续有机会接触更多的话继续更新。