利用OLLVM混淆Android Native代码篇一
OLLVM(Obfuscator-LLVM)是瑞士西北应用科技大学安全实验室于2010年6月份发起的一个项目,该项目旨在提供一套开源的针对LLVM的代码混淆工具,以增加对逆向工程的难度。目前,OLLVM已经支持LLVM-3.6.1版本。
LLVM是一个优秀的编辑器框架,它也采用经典的三段式设计。前端可以使用不同的编译工具对代码文件做词法分析以形成抽象语法树AST,然后将分析好的代码转换成LLVM的中间表示IR(intermediate representation);中间部门的优化器只对中间表示IR操作,通过一系列的Pass对IR做优化;后端负责将优化好的IR解释成对应的平台的机器码。LLVM的优点在于,中间表示IR代码编写良好,而且不同的前端语言最终都转换成同一种的IR。
LLVM IR 是LLVM的中间表示,优化器就是对IR进行操作的,具体的优化操作由一些列的Pass来完成,当前端生成初级IR后,Pass会依次对IR进行处理,最终生成后端可用的IR。下图可以说明这个过程:
OLLVM的混淆操作就是在中间表示IR层,通过编写Pass来混淆IR,然后后端依据IR来生成的目标代码也就被混淆了。得益于LLVM的设计,OLLVM适用LLVM支持的所有语言(C,C++,Objective-C,Ada,Fortran)和目标平台(x86,x86-64,PowerPC,PowerPC-64, ARM, Thumb, SPARC, Alpha, CellSPU, MIPS, MSP430, SystemZ, 和 XCore)
以下,介绍OLLVM Android编译环境的搭建过程。环境信息:ndk(android-ndk-r10e)、LLVM(llvm-3.6.1)。首先下载源码,编译OLLVM混淆器,这里采用LLVM的版本是3.6.1。下载编译过程如下:
下载的源码里已经包含了LLVM和Clang,编译完成后,编译号的二进制程序都存在在build/bin目录下。
依据github上的wiki,bin目录下编译好的工具链可以直接用来编译混淆linux下的程序,就像我们常用的gcc那样。若想使用OLLVM来混淆Android Native程序,还需将bin目录下的工具链整合进ndk环境中。
按照ndk编译工具链的组织结构,我们照样子新建一条工具链即可。在toolchains目录下新建obfuscator-llvm-3.6目录,并将llvm-3.6目录下的config.mk、setup.mk和setup-common.mk拷贝到obfuscator-llvm-3.6目录中,不做任何修改。
然后,把源码编译好的bin目录和lib目录按照llvm-3.6中prebuilt/linux-x86_64的目录格式拷贝。接着,在toolchains目录下分别建立arm-linux-androideabi-obfuscator3.6, mipsel-linux-android-obfuscator3.6, x86-obfuscator3.6目录,注意文件夹的前缀要与原toolchains中的目录保持一致,然后把arm-linux-androideabi-clang3.6, mipsel-linux-android-clang3.6, x86-clang3.6文件夹下的 config.mk 和 setup.mk 对应拷贝到上述三个文件夹中,此时要分别修改 setup.mk 中的 LLVM_NAME ,即将其指定到开始建立的obfuscator-llvm-3.6目录。
若编译64位版本的so,也要按照上面的格式依次配置x86_64-obfuscator3.6,mips64el-linux-android-obfuscator3.6,aarch64-linux-android-obfuscator3.6三个文件夹。还要修改$NDK_PATH/build/core/setup-toolchain.mk文件,在NDK_64BIT_TOOLCHAIN_LIST := 加入 obfuscator 对应的NDK_TOOLCHAIN_VERSION
至此,新增加的具备OLLVM混淆的编译工具链就添加完成了,在编译native程序时,在Android.mk和Application.mk中配置编译参数即可。
在Application.mk中指定编译器名字:
在Android.mk中设置混淆参数:
这样配置完成后,使用ndk-build命令即可编译出混淆代码。代码在64位机器上执行正常,内容仅仅是一条if-else语句,混淆的效果确实不错。
通过简单的demo可以看出OLLVM的混淆功能确实强大。本小节将介绍混淆功能的具体使用。
OLLVM默认支持-fla -sub -bcf 三个混淆参数,这三个参数可以单独使用,也可以配合着使用。-fla 参数表示使用控制流平展(Control Flow Flattening)模式, -sub 参数表示使用指令替换(Instructions Substitution)模式, -bcf 参数表示使用控制流伪造(Bogus Control Flow)模式。下述if-else分支代码用于验证各混淆模式的效果。
Control Flow Flattening
控制流平展模式可以完全改变程序原本的控制流图。如下示例代码是简单的if-else分支语句,正常编译后其控制流图在IDA中下图所示,是正常的if-else分支,使用 -mllvm -fla参数混淆后,在IDA中显示的控制流图如下:
经FLA模式混淆后,程序的执行流程已经被打乱,出现许多代码分支。通过仔细对比程序混淆前后,可以发现上图着色区域是相对应的,也就是说,FLA模式只去更改代码分支,而不会对单个代码块做处理。用IDA反编译的混淆后的伪码:
int __cdecl main(int argc, const char **argv, const char **envp)
{
signed int v4; // [sp+38h] [bp-28h]@1
v4 = 1837851710;
while ( 1 )
{
while ( v4 <= 1496444127 )
{
if ( v4 == 1258601415 )
{
v4 = 1496444128;
printf("a < b\n", argv, envp);
}
}
if ( v4 == 1496444128 )
break;
if ( v4 == 1806065225 )
{
printf("c = %d\n", 50LL, envp);
printf("d = %d\n", 150LL);
argv = (const char **)86;
printf("e = %d\n", 32LL);
printf("f = %d\n", 86LL);
v4 = 1496444128;
printf("a > b\n");
}
else if ( v4 == 1837851710 )
{
envp = (const char **)1;
argv = 0LL;
v4 = 1806065225;
}
}
return 0;
}
源程序中,if-else的代码块是顺序执行的,混淆后代码块的执行由while循环和变量v4动态计算而得。
Instructions Substitution
指令替换模式主要是将正常的运算操作(+,-,&,|等)替换成功能相等但表述更复杂的形式。比如,对于表达式 a = b + c,它的等价式可以有 a = – ( -b – c), a = b – (-c) 或 a = -(-b) + c 等,原表达式可以替换成任意相等式,或者通过随机数在多个相等式中做选择。SUB模式目前只支持整数运算操作,支持 + , – , & , | 和 ^ 操作,还是比较局限的。编译时,使用 -mllvm -sub 参数即可。
Bogus Control Flow
控制流伪造模式也是对程序的控制流做操作,不同的是,BCF模式会在原代码块的前后随机插入新的代码块,新插入的代码块不是确定的,然后新代码块再通过条件判断跳转到原代码块中。更要命地是,原代码块可能会被克隆并插入随机的垃圾指令。这么多不确定性,就导致对同一份代码多次做BCF模式的混淆时,得到的是不同的混淆效果。可见,BCF混淆模式还是很强大的,不同于FLA那种较确定的混淆模式。使用BCF模式编译时配置参数 -mllvm -bcf即可,此外,BCF模式还支持其它几个参数,下面参数与-mllvm -bcf参数配合使用。
如上图,下面两个着色的代码块就是有上面两个代码块克隆而来,而且其中被插入了一些垃圾指令,类似于这样:
当然,上述介绍的三种混淆模式可以搭配使用,同时使用三个参数混淆后,原本简单的if-else分支代码将会变得异常复杂,这无疑给逆向分析增加巨大的难度。
Functions annotations
有的时候,由于效率或其他原因的考虑,我们只想给指定的函数混淆,OLLVM也提供了对这一特性的支持。比如,想对函数func()使用bcf混淆,只需要给函数func()增加bcf属性即可。
fla,sub和bcf三个属性可以搭配使用。如果不想对func()函数使用bcf属性,那标记为“nobcf”即可。
.Android LLVM-Obfuscator C/C++ 混淆编译的深入研究
.The LLVM Compiler Infrastructure
.obfuscator -llvm
以上内容属于原创
阅后有任何问题
直接在公众号回复AC君
未经允许禁止转载,违者必究
PS:更多资讯在往期回顾
关注“安创科技”
不一样的安全世界