@author ASCE1885的 Github 简书 微博 CSDN
原文链接
Android开发中经常需要对敏感信息进行加密,避免不了要将密钥存放在终端设备上,那么如何防止密钥被逆向出来呢?这是一个先有鸡还是先有蛋的悖论。相比较将密钥写在Java层,将其下移到NDK层是个更好的选择,本文就来介绍如何对NDK层代码进行混淆,以更好的保护我们的密钥。
混淆是一种用来隐藏程序意图的技术,具体的实现技术可能差别比较大,最有效的技术可以增加逆向工程和破解的难度,防止知识产权被窃取。已经有很多第三方的软件可以用来混淆我们的Android应用,常见的有:
- Proguard
- DashO
- Dexguard
- DexProtector
- ApkProtect
- Shield4j
- Stringer
- Allitori
这些混淆器在代码中起作用的层次是不一样的。Android编译的大致流程如下:
Java Code(.java) -> Java Bytecode(.class) -> Dalvik Bytecode(classes.dex)
有的混淆器是在编译之前直接作用于java源代码,有的作用于java字节码,有的作用于Dalvik字节码。
Android NDK使得开发者可以绕过虚拟机从而进一步提高程序性能,或者更直接的与内核和硬件交互。Google对NDK的描述是:“NDK是允许开发者使用原生C/C++语言开发app的一套工具集。这样有利于某些类型的app复用C/C++编写的已有代码库,当然大部分app不需要使用Android NDK”。
相对于Dalvik虚拟机层次的混淆而言,原生语言(C/C++)组件的代码混淆选择并不多,Obfuscator-LLVM工程是一个值得关注的例外。这个项目专注于LLVM编译器,这一点使得它可移植性很高,兼容LLVM支持的所有语言(C,C++, Objective-C, Ada and Fortran)和平台(x86, x86-64, PowerPC, PowerPC-64,ARM, Thumb, SPARC, Alpha, CellSPU, MIPS, MSP430, SystemZ,and XCore)。0vercl0k在o-llvm发布之前发表了一篇论文,解释了使用LLVM编译器的优点以及简单的代码转换。
我使用O-LLVM和NDK已经有一段时间了。在了解到TowelRoot也在使用O-LLVM时,我决定写一篇文章来介绍它。TowelRoot是一款Android一键Root工具,关于它是如何利用Linux内核bug来达到root目的的可以参见这篇文章。TowelRoot使用O-LLVM主要用来防止其他人拷贝并利用它来实现非法目的,同时防止被重打包后并出售。
下面我们就来讲解如何开始使用O-LLVM来混淆原生代码,实现类似TowelRoot的目的。
使用NDK O-LLVM二进制叠加包
我已经在OSX和Linux平台上把混淆器基于NDK打包成二进制叠加包,你也可以参照本文最后一节的步骤自己从源码进行编译。混淆器的二进制叠加包下载地址:
- OSX-NDK-Obfuscator.tar.bz2
- android-ndk64-r10-linux-x86_64-obfuscator.tar
下载正确的二进制叠加包,将它解压到你电脑的NDK目录中。
配置O-LLVM NDK工程
现在让我们对NDK工程进行配置,使其支持O-LLVM混淆器。我们工程目录结构如下所示:
? AndroidObfuscation-NDK git:(master) tree ..├── jni│ ├── Android.mk│ ├── Application.mk│ └── obfuscationTest.c
工程的Application.mk内容如下:
LOCAL_PATH := $(call my-dir)include $(CLEAR_VARS)APP_ABI := armeabiNDK_TOOLCHAIN_VERSION := clang3.4-obfuscatorinclude $(BUILD_EXECUTABLE)
混淆器的各种代码转换可以参见Obfuscator Wiki。可以通过LOCAL_CFLAGS标签把这些转换标记设置给混淆器。记住混淆器的转换标记需要以-mllvm开头,这样clang编译器可以传递它。
Android.mk的配置示例如下:
LOCAL_PATH := $(call my-dir)include $(CLEAR_VARS)LOCAL_MODULE := obfuscatedLOCAL_SRC_FILES := obfuscationTest.cLOCAL_LDLIBS := -staticLOCAL_CFLAGS := -mllvm -sub -mllvm -fla -mllvm -bcfinclude $(BUILD_EXECUTABLE)
现在可以编译我们的工程了:
? AndroidObfuscation-NDK git:(master) ndk-build[armeabi] Compile thumb : obfuscated <= obfuscationTest.c[armeabi] Executable : obfuscated[armeabi] Install : obfuscated => libs/armeabi/obfuscated
使用了上面的配置和脚本结构的例子工程可以参见AndroidObfuscation-NDK。
上面我预先构建的二进制叠加包包含了由yag00贡献的试验性的字符串混淆技术。你可以通过给LOCAL_CFLAGS传递“-mllvm -xse”标记来使能字符串混淆功能。
? AndroidObfuscation-NDK git:(master) cat jni/obfuscationTest.c#include <stdio.h>int main(void){ printf("Hello, world\n"); return 0;}
这个例子中,在使用字符串混淆功能之前效果如下:
? AndroidObfuscation-NDK git:(master) strings libs/armeabi/obfuscated | grep HelloHello, world
使用字符串混淆功能之后:
? AndroidObfuscation-NDK git:(master) strings libs/armeabi/obfuscated | grep Hello
从源码构建适用于NDK的O-LLVM
git clone -b llvm-3.4 https://github.com/obfuscator-llvm/obfuscator.gitcd obfuscatormkdir buildcd buildcmake -DCMAKE_BUILD_TYPE:String=Release ../obfuscator/make -j5
构建o-llvm的完整指南参见这里,不过上面的说明应该足够了。
cp -r $NDK_PATH/toolchains/arm-linux-androideabi-clang3.4 $NDK_PATH/toolchains/arm-linux-androideabi-clang3.4-obfuscator
打开文件
$NDK_PATH/toolchains/arm-linux-androideabi-clang3.4-obfuscator/setup.mk
将文件里面的:
TARGET_CC := $(LLVM_TOOLCHAIN_PREFIX)clang$(HOST_EXEEXT)TARGET_CXX := $(LLVM_TOOLCHAIN_PREFIX)clang++$(HOST_EXEEXT)
修改为(记得修改o-llvm为你自己电脑上面的路径)
LLVM_TOOLCHAIN_PATH := <PATH_TO_OBFUSCATOR_REPO>/build/bin/TARGET_CC := $(LLVM_TOOLCHAIN_PATH)clang$(HOST_EXEEXT)TARGET_CXX := $(LLVM_TOOLCHAIN_PATH)clang++$(HOST_EXEEXT)
文末摄影鉴赏