什么是LLVM
LLVM
是low level virtual machine
的简称,其实是一个编译器框架。llvm随着这个项目的不断的发展,已经无法完全的代表这个项目了,只是这种叫法一直延续下来。
llvm是一个开源的项目。它最早的时候是Illinois的一个研究项目,主要负责人是Chris Lattner,他现在就职于Apple. Apple 目前也是llvm项目的主要赞助者之一。
llvm的主要作用是它可以作为多种语言的后端,它可以提供可编程语言无关的优化和针对很多种CPU的代码生成功能。此外llvm目前已经不仅仅是个编程框架,它目前还包含了很多的子项目,比如最具盛名的clang.
llvm的优点是开源,有一个表达形式很好的IR语言,模块化作的特别好。
llvm这个框架目前已经有基于这个框架的大量的工具可以使用。
llvm的官方网站地址是:llvm.org。在这里可以下载最新的发布代码,也可以找到介绍llvm的相关文档
摘自:https://blog.csdn.net/snsn1984/article/details/8036032
Obfuscator-LLVM介绍
OLLVM(Obfuscator-LLVM)是瑞士西北应用科技大学安全实验室于2010年6月份发起的一个针对LLVM代码混淆项目,主要作用是增加逆向难度,从而一定程度上保护代码的安全。因为后期转向了商业项目strong.protect,所以项目的进度一度停滞,而在17年3月,LLVM已经更新到了4.0版本,新版本的一些特性导致老版本的OLLVM存在一定的局限性。
github仓库最新版本:https://github.com/obfuscator-llvm/obfuscator/tree/llvm-4.0
摘自:https://baijiahao.baidu.com/s?id=1572199238543099&wfr=spider&for=pc
运行环境
运行环境:我使用的是Mac笔记本,其实跟Linux差不多。
IDE:Android Studio 3.1.2
Android SDK版本:
compileSdkVersion 26
buildToolsVersion '26.0.2'
NDK版本:android-ndk-r16b
我查了网上很多使用NDK 10的资料,有些老旧,NDK 16这个版本虽然不是最新,但是用着问题不多。
搭建环境
- 安装CMake
CMake是一个比make更高级的跨平台的安装、编译、配置工具,可以用简单的语句来描述所有平台的安装(编译过程)。并根据不同平台、不同的编译器,生成相应的Makefile或者project文件。
1、下载CMake官网:https://cmake.org/download/
下载图中标注的cmake-3.14.0-rc3-Darwin-x86_64.dmg安装文件,在Mac上安装即可。
2、配置CMake环境变量
安装完成后,点击CMake图标打开,会弹出提示,选择打开即可。
选择左上角Tools,打开How to Install For Command Line Use
会弹出配置环境的提示框,如下图所示:
使用命令:PATH="/Applications/CMake.app/Contents/bin":"$PATH"
执行完之后执行cmake -version
会在当前命令行窗口生效,新打开一个窗口就不识别这个命令,所以还要执行sudo "/Applications/CMake.app/Contents/bin/cmake-gui" --install
。
这样就配置好了camke环境。
参考这篇文章:https://blog.csdn.net/baimafujinji/article/details/78588488
编译obfuscator-llvm
源码
1、从https://github.com/obfuscator-llvm/obfuscator/tree/llvm-4.0下载最新代码,这个分支下有好几个版本。
我下载的是4.0版本,下载完成后解压即可,git clone会有点慢,所以我直接把压缩包下载下来了。解压后在同级目录创建build
目录。
2、下面就是编译源码的过程了,是重点:
- 进入
build
目录,cd build
- 编译源码
cmake -DCMAKE_BUILD_TYPE=Release ../obfuscator-llvm-4.0/
,后面的是目录名,网上文章一般是cmake -DCMAKE_BUILD_TYPE=Release ../obfuscator/
,所以后面你的目录是什么就执行什么命令。这个编译很快,控制台会出现很多not found,这都不影响下一步操作。 - 最后执行命令
make -j7
,这个过程会有点久,控制台会显示执行进度,大约15分钟到30分钟。
3、编译完成后会生成obfuscator-llvm
的编译源码,在build目录下,我们只需要复制bin
和lib
目录即可。
4、配置ndk的llvm
将build
下的bin
和lib
复制到ndk下的/android-ndk-r16b/toolchains/llvm/prebuilt/darwin-x86_64
目录中替换覆盖。
到这obfuscator-llvm
源码编译与配置就完事了。
混淆JNI代码
1、新建LLVMDemo带JNI的工程,如果不了解JNI开发的可以先看下文章:https://blog.csdn.net/u010982507/article/details/85345985
2、配置CMakeLists.txt
文件
cmake_minimum_required(VERSION 3.4.1)
add_library( native-lib SHARED src/main/cpp/native-lib.cpp )
find_library( log-lib log )
# 源码编译混淆配置
SET(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -mllvm -fla -mllvm -bcf -mllvm -sub")
SET(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -mllvm -fla -mllvm -bcf -mllvm -sub")
target_link_libraries( native-lib ${log-lib} )
3、配置指令介绍
- release环境
SET(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -mllvm -fla -mllvm -bcf -mllvm -sub")
SET(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -mllvm -fla -mllvm -bcf -mllvm -sub")
- debug环境
SET(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -mllvm -fla -mllvm -bcf -mllvm -sub")
SET(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -mllvm -fla -mllvm -bcf -mllvm -sub")
-fla
控制流扁平化的PASS参数-sub
指令替换的PASS参数-bcf
虚假控制流的PASS参数
效果对比
1、源码
#include <jni.h>
#include <string>
extern "C" JNIEXPORT jstring
JNICALL
Java_com_rzr_llvm_MainActivity_stringFromJNI (
JNIEnv *env,
jobject /* this */) {
std::string hello = "Hello from C++";
int a = 0;
int b = 1;
int c = a + b;
return env->NewStringUTF(hello.c_str());
}
2、使用IDA查看不混淆so文件
jstring __fastcall Java_com_rzr_llvm_MainActivity_stringFromJNI(JNIEnv *env, jobject a2)
{
JNIEnv *v2; // r4
_jstring *v3; // r4
jstring result; // r0
std::allocator<char> __a; // [sp+8h] [bp-1Ch]
std::basic_string<char,std::char_traits<char>,std::allocator<char> > v6; // [sp+Ch] [bp-18h]
std::allocator<char> v7; // [sp+14h] [bp-10h]
int v8; // [sp+18h] [bp-Ch]
v2 = env;
std::string::string(&v6, "Hello from C++", &__a);
v3 = (_jstring *)((int (__fastcall *)(JNIEnv *, unsigned __int8 *))v2->functions->NewStringUTF)(
v2,
v6._M_dataplus._M_p);
std::string::_Rep::_M_dispose(
(std::basic_string<char,std::char_traits<char>,std::allocator<char> >::_Rep *const )v6._M_dataplus._M_p - 1,
&v7);
result = (jstring)(_stack_chk_guard - v8);
if ( _stack_chk_guard == v8 )
result = v3;
return result;
}
3、使用IDA查看混淆后so文件
int __fastcall Java_com_rzr_llvm_MainActivity_stringFromJNI(int a1)
{
signed int v1; // r0
signed int v2; // r2
signed int v3; // r0
signed int v4; // r2
signed int v5; // r2
signed int v6; // r3
int v7; // r3
int v8; // r2
int *v9; // r5
int v10; // r1
int v11; // r4
int result; // r0
signed int v13; // [sp-1Ch] [bp-5Ch]
int v14; // [sp-18h] [bp-58h]
int v15; // [sp-10h] [bp-50h]
signed int v16; // [sp-4h] [bp-44h]
int *v17; // [sp+4h] [bp-3Ch]
int v18; // [sp+8h] [bp-38h]
int v19; // [sp+Ch] [bp-34h]
int *v20; // [sp+10h] [bp-30h]
int v21; // [sp+14h] [bp-2Ch]
int *v22; // [sp+18h] [bp-28h]
int v23; // [sp+1Ch] [bp-24h]
int v24; // [sp+20h] [bp-20h]
int v25; // [sp+24h] [bp-1Ch]
int v26; // [sp+28h] [bp-18h]
v18 = a1;
v17 = &v25;
v1 = 1;
v16 = 1;
v2 = 1;
if ( y >= 10 )
{
v16 = 0;
v2 = 0;
}
if ( (~((x - 1) * x) | 0xFFFFFFFE) != -1 )
{
v16 = 0;
v1 = 0;
}
v16 = v2;
v23 = -1;
if ( !(v2 & v1) && (v2 ^ v1) != 1 )
goto LABEL_13;
while ( 1 )
{
v20 = &v14;
__gnu_cxx::new_allocator<char>::new_allocator();
v13 = 1;
v3 = 1;
if ( y <= 9 )
{
v13 = 0;
v3 = 0;
}
v22 = &v15;
v24 = 1;
v21 = -v23;
v13 = 1;
v4 = 1;
if ( !((v23 | 0x3364DB48) & (((x + v23) * x ^ v23 | v23 ^ 1) ^ v23)) )
{
v13 = 0;
v4 = 0;
}
v13 = v3;
if ( v3 != v4 || ((v3 | v4) ^ 1) == 1 )
break;
LABEL_13:
__gnu_cxx::new_allocator<char>::new_allocator();
}
sub_77B4(v22, "Hello from C++", v20);
v20 = (int *)y;
v19 = x;
do
{
v5 = 1;
v6 = 1;
if ( (signed int)v20 >= 10 )
v6 = 0;
v7 = v6 ^ v24;
if ( ((v19 - v21) * v19 ^ v23 ^ 1) & (v19 - v21) * v19 )
v5 = 0;
v8 = v5 ^ v24;
v9 = v22;
}
while ( v7 == v8 && (((v7 | v8) ^ v24) & v24) != 1 );
v10 = *v22;
v11 = (*(int (**)(void))(*(_DWORD *)v18 + 668))();
sub_6D34(*v9 + v23 - 11, &v26);
result = _stack_chk_guard - *v17;
if ( _stack_chk_guard == *v17 )
result = v11;
return result;
}