目前Android市场充斥着大量的盗版软件,开发者的官方应用被“打包党”们恶意篡改。如何使程序代码免受盗版篡改就成了开发者面临的头等大事,今天我们将分析一个不错的解决方案——梆梆加固。
梆梆加固
通过对App进行加固保护,梆梆可以有效防止移动应用在运营推广过程中被破解、盗版、二次打包、注入、反编译等破坏,保障程序的安全性、稳定性,对移动应用的整体逻辑结构进行保护,保证了移动应用的用户体验。
首先我们通过对APK加固前后文件结构的比较,来了解梆梆加固对APK文件所做的处理。为了使分析过程足够简单,我新建一个最简单的测试程序,并上传到梆梆加固,整个加固过程大概需要4天的时间才可以完成,这是一个漫长的等待过程。
该测试程序包含了Activity、Service、ContentProvider、BroadcastRecevier四大组件、Application、普通类、Jni调用等7类对象,目的就是全面的了解梆梆的加固效果。
1. apk加固前后静态文件结构及动态运行时对比分析
(1)加固前后静态文件结构变化(左为加固前,右为加固后)
加固后apk新增以下文件:
加固后修改文件:
对classes.dex进行反编译,观察代码树结构变化:(左为加固前,右为加固后)
(2)加固前后动态运行时变化
运行原程序,系统仅创建一个相关进程,但是加固的程序,系统会为其同时创建三个相关程序进程:
进程启动顺序:597进程创建605进程,605进程又创建了607进程
通过查看maps文件获取597进程映射文件信息:
通过map文件可以看出,597进程为主进程,Android各组件在该进程中运行。
605和607进程并无与apk文件相关文件信息,通过cmdline查看启动参数:
初步怀疑该进程为assets\com.example.hellojni可执行文件运行结果。
2. 梆梆加固保护效果分析
我们通过逆向分析加固后的app,来看看梆梆加固对app的保护效果。
程序代码的第一执行点是Application对象,首先查看TestApplication类对象。
程序的Util类完成大部分的java层逻辑:
ACall类主要完成对libsecexe.so JNI的调用:
查看libsecexe.so文件导出函数,发现所有函数名都经过加密处理,与我们平时jni调用产生的函数名并不同。平时jni产生的函数名应该为这样格式:Java_com_secapk_wrapper_ACall_{函数名}。
抗静态分析:
抗动态调试:
梆梆加固可以常用的有效的逆向分析方法。
1. 如何使DexClassLoader动态加载组件具有生命周期?
根据APK文件是否在AndroidManifest.xml配置Applicaiton信息,梆梆加固会做不同的处理。
通过上传Applicaiton不同配置的APK文件,我们发现:
因此Applicaiton就是程序的第一执行点。
我们知道DexClassLoader加载的类是没有组件生命周期的,也就是说即使DexClassLoader通过对dex的动态加载完成了对组件类的加载,当系统启动该组件时,还会出现加载类失败的异常。我已经在“Android APK加壳技术方案”中提出了一种使DexClassLoader加载组件类具有生命周期的方法。
运行加固后的程序并通过Mat内存分析工具查看类加载情况:
如上图所示,组件类的加载类已经被修改为com.secapk.wrapper.MyClassLoader类,可以得出结论,该方式和我提出方式基本相同,通过修改系统组件类ClassLoader来实现。
2. 如何混淆native方法在so库函数对应关系?
jni方法注册方式有两种:
使用第二种方式可以实现混淆java层native方法和so函数的对应关系。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
#include <string.h>
#include <jni.h>
JNIEXPORT jstring JNICALL abcdefghijklmn( JNIEnv* env,jobject thiz )
{
return
(*env)->NewStringUTF(env,
"Hello from JNI !"
);
}
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm,
void
* reserved)
{
JNIEnv* env = NULL;
jint result = -1;
if
((*vm)->GetEnv(vm, (
void
**) &env, JNI_VERSION_1_4) != JNI_OK) {
return
JNI_ERR;
}
JNINativeMethod gMethods[] = {
{
"stringFromJNI"
,
"()Ljava/lang/String;"
, (
void
*)abcdefghijklmn },
};
jclass clazz = (*env)->FindClass(env,
"com/example/hellojni/HelloJni"
);
if
(clazz == NULL) {
return
JNI_ERR;
}
if
((*env)->RegisterNatives(env, clazz, gMethods,
sizeof
(gMethods) /
sizeof
(gMethods[0])) < 0){
return
JNI_ERR;
}
/* success -- return valid version number */
result = JNI_VERSION_1_4;
return
result;
}
|
以上代码中的字符串都是明文(比如“stringFromJNI”),如果这些文明字符串都换成密文的话,再通过运行时解密,相应的对应关系更不易看出。
3. 如何使DexClassLoader加载加密的dex文件?
虽然不了解梆梆加固是怎么做的,不过通过分析它的运行逻辑,我推测了一种可行的实现方案:了解该方案需要对Android DexClassLoader的整个加载流程需要有清晰的了解。
首先推断assets\classes.jar是一个加密的jar包。正常的DexClassLoader加载的流程如下:会有一个DexOpt产生odex过程。但是梆梆加固后的应用DexClassLoader加载过程并没有该过程的log信息。
推断加密的jar包里面含有odex文件,如果不是odex文件的话,DexClassLoader肯定会在运行时释放未加密的odex文件到目录,这样的话被保护的逻辑也就泄露了。
DexClassLoader加载过程会在java层和C层产生不同的数据结构,java层并没有实质性的数据,所有的数据都在c层,我们可用通过底层代码完成dex数据的解析。底层dex分析是可以支持byte[]数组的,解密odex数据,传递过去就行了。这样java层就可以调用了。
以下是大概伪代码实现步骤:
1
2
3
4
5
6
|
int
loadDex(
char
* dexFileName)
{
char
*dvm_lib_path =
"/system/lib/libdvm.so"
;
void
* handle;
DvmGlobals gDvm;
handle = dlopen( dvm_lib_path,
int
mode);
|
调用dexFileParse函数解析byte数组为DexFile
\dalvik\libdex\DexFile.c
DexFile* dexFileParse(const u1* data, size_t length, int flags)//dlsym(handle, "dexFileParse");
\dalvik\vm\DvmDex.c
static DvmDex* allocateAuxStructures(DexFile* pDexFile)
\dalvik\vm\Init.c
struct DvmGlobals gDvm; //gDvm = dlsym(handle, "gDvm");
mCookie主要用于映射底层DvmDex数据——DexClassLoader.mDexs[0].mCookie值
4. so如何实现程序的反调试?
同Linux反调试基本原理相同,这里提供一种方式就是在JNI_Onload中调用ptrace方法,ptrace被广泛用于调试(比如IDA)和进程代码注入(比如LBE,金山等权限管理功能实现),一个进程只能被一个进程ptrace,如果你自己调用ptarce,这样其它程序就无法通过ptrace调试或者向您的程序进程注入代码。
ptrace(PTRACE_TRACEME,0 ,0 ,0);
通过本人实验,该种方式可以实现so的反调试。
通过以上分析,梆梆加固的确可以有效防止移动应用在运营推广过程中被破解、盗版、二次打包、注入、反编译等破坏,不过如果Android恶意软件也通过这种方式加固保护,这将会给移动安全分析人员带来巨大的挑战,因为安全分析人员经常使用的代码静态逻辑分析和动态调试分析在该情况下都失效了。
梆梆官方声称不会对恶意软件进行加固,的确在加固的过程中发现存在安全软件扫描信息和云测试处理流程,不过这些措施只能减少而不能彻底杜绝恶意软件通过梆梆加固保护。如何不被恶意软件利用是梆梆需要解决的问题。
文章来源:jiazhijun'Blog
(责编/唐小引)