Andorid APK反逆向解決方案:梆梆加固原理探尋
一、序言
目前Android市場充斥著大量的盜版軟件,開發(fā)者的官方應用被“打包黨”們惡意篡改。如何使程序代碼免受盜版篡改就成了開發(fā)者面臨的頭等大事,今天我們將分析一個不錯的解決方案---梆梆加固(http://www.secneo.com/appProtect/)。
通過對App進行加固保護。梆梆可以有效防止移動應用在運營推廣過程中被破解、盜版、二次打包、注入、反編譯等破壞,保障程序的安全性、穩(wěn)定性,對移動應用的整體邏輯結(jié)構(gòu)進行保護,保證了移動應用的用戶體驗。
二、梆梆加固逆向分析過程
首先我們通過對APK加固前后文件結(jié)構(gòu)的比較,來了解梆梆加固對APK文件所做的處理。為了使分析過程足夠簡單,我新建一個最簡單的測試程序,并上傳到梆梆加固,整個加固過程大概需要4天的時間才可以完成,這是一個漫長的等待過程。
該測試程序包含了Activity、Service、ContentProvider、BroadcastRecevier四大組件、Application、普通類、Jni調(diào)用等7類對象,目的就是全面的了解梆梆的加固效果。
1、apk加固前后靜態(tài)文件結(jié)構(gòu)及動態(tài)運行時對比分析
(1) 加固前后靜態(tài)文件結(jié)構(gòu)變化(左為加固前,右為加固后)
加固后apk新增以下文件:
assets\meta-data\manifest.mf //APK文件列表SHA1-Digest assets\meta-data\rsa.pub //RSA公鑰信息 assets\meta-data\rsa.sig //數(shù)字簽名文件 assets\classes.jar //已加密原classes.dex文件 assets\com.example.hellojni //ARM平臺二進制可執(zhí)行文件 assets\com.example.hellojni.x86 //x86功能同上 libs\armeabi\libsecexe.so //ARM平臺共享庫文件 libs\x86\libsecexe.so //x86功能同上
加固后修改文件:
AndroidMainfest.xml //(如果應用配置有Application信息,則該文件加固前后相同,如果應用未配置Application信息,則該文件加固前后不相同,梆梆會配置Application信息為自己實現(xiàn)類) classes.dex
對classes.dex進行反編譯,觀察代碼樹結(jié)構(gòu)變化:(左為加固前,右為加固后)
(2)加固前后動態(tài)運行時變化
運行原程序,系統(tǒng)僅創(chuàng)建一個相關(guān)進程,但是加固的程序,系統(tǒng)會為其同時創(chuàng)建三個相關(guān)程序進程:
進程啟動順序:597進程創(chuàng)建605進程,605進程又創(chuàng)建了607進程
通過查看maps文件獲取597進程映射文件信息
通過map文件可以看出,597進程為主進程,android各組件在該進程中運行。
605和607進程并無與apk文件相關(guān)文件信息,通過cmdline查看啟動參數(shù):
初步懷疑該進程為assets\com.example.hellojni 可執(zhí)行文件運行結(jié)果。
2、梆梆加固保護效果分析
我們通過逆向分析加固后的app,來看看梆梆加固對app的保護效果。
程序代碼的第一執(zhí)行點是Application對象,首先查看TestApplication類對象。
程序的Util類完成大部分的java層邏輯,
ACall類主要完成對libsecexe.so JNI的調(diào)用:
查看libsecexe.so文件導出函數(shù),發(fā)現(xiàn)所有函數(shù)名都經(jīng)過加密處理,與我們平時jni調(diào)用產(chǎn)生的函數(shù)名并不同。平時jni產(chǎn)生的函數(shù)名應該為這樣格式Java_com_secapk_wrapper_ACall_{函數(shù)名}
抗靜態(tài)分析:
Util類通過MyClassLoader完成對加密classes.jar的動態(tài)加載,內(nèi)存中解密classes.jar,完成動態(tài)加載。
jni方法對應so函數(shù)名的混淆。
抗動態(tài)調(diào)試:
當使用IDA動態(tài)調(diào)試該程序時,程序無法建立連接調(diào)試。
梆梆加固可以有效常用的逆向分析方法。
#p#
三、梆梆加固技術(shù)實現(xiàn)關(guān)鍵點猜想
(1)如何使DexClassLoader動態(tài)加載組件具有生命周期?
根據(jù)APK文件是否在AndroidManifest.xml配置Applicaiton信息,梆梆加固會做不同的處理:
通過上傳Applicaiton不同配置的APK文件,我們發(fā)現(xiàn):
當APK配置有Applicaition信息時,梆梆加固重寫Application類
當APK未配置Application信息時,梆梆加固新建類,并在AndroidManifest.xml中配置自己Application類
因此Applicaiton就是程序的第一執(zhí)行點。
我們知道DexClassLoader加載的類是沒有組件生命周期的,也就是說即使DexClassLoader通過對dex的 動態(tài)加載完成了對組件類的加載, 當系統(tǒng)啟動該組件時,還會出現(xiàn)加載類失敗的異常。我已經(jīng)在“Android APK加殼技術(shù)方案”中提出了一種使DexClassLoader加載組件類具有生命周期的方法。
運行加固后的程序并通過Mat內(nèi)存分析工具查看類加載情況:
如上圖所示,組件類的加載類已經(jīng)被修改為com.secapk.wrapper.MyClassLoader類,可以得出結(jié)論,該方式和我提出方式基本相同,通過修改系統(tǒng)組件類ClassLoader來實現(xiàn)。
(2)如何混淆native方法在so庫函數(shù)對應關(guān)系?
jni方法注冊方式有兩種:
1、通過javah產(chǎn)生函數(shù)頭,該種方式產(chǎn)生的方法具有固定的格式。該方式使逆向分析人員比較容易獲取java層native方法對應的本地方法。
2、在JNI_OnLoad方法中手動注冊jni方法,不易查找對應關(guān)系。
使用第二種方式可以實現(xiàn)混淆java層native方法和so函數(shù)的對應關(guān)系。
- #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”),如果這些文明字符串都換成密文的話,再通過運行時解密,相應的對應關(guān)系更不易看出。
(3)如何使DexClassLoader加載加密的dex文件?
雖然不了解梆梆加固是怎么做的,不過通過分析它的運行邏輯,我推測了一種可行的實現(xiàn)方案:了解該方案需要對 Android DexClassLoader的整個加載流程需要有清 晰的了解。
首先推斷assets\classes.jar是一個加密的jar包。
正常的DexClassLoader加載的流程如下:會有一個DexOpt產(chǎn)生odex過程
但是梆梆加固后的應用DexClassLoader加載過程并沒有該過程的log信息。
推斷加密的jar包里面含有odex文件,如果不是odex文件的話,DexClassLoader肯定會在運行時釋放未加密的odex文件到目錄,這樣的話被保護的邏輯也就泄露了。
DexClassLoader加載過程會在java層和C層產(chǎn)生不同的數(shù)據(jù)結(jié)構(gòu),java層并沒有實質(zhì)性的數(shù)據(jù), 所有的數(shù)據(jù)都在c層,我們可用通過底層代碼完成dex數(shù)據(jù)的解析。底層dex分析是可以支持byte[]數(shù)組的,解密 odex數(shù)據(jù),傳遞過去就行了。這樣java層就可以調(diào)用了。
以下是大概偽代碼實現(xiàn)步驟:
- int loadDex(char * dexFileName)
- {
- char *dvm_lib_path = "/system/lib/libdvm.so";
- void * handle;
- DvmGlobals gDvm;
- handle = dlopen( dvm_lib_path, int mode);
1、讀取dexFileName文件內(nèi)容并解密到byte數(shù)組。
2、調(diào)用dexFileParse函數(shù)解析byte數(shù)組為DexFile
\dalvik\libdex\DexFile.c DexFile* dexFileParse(const u1* data, size_t length, int flags)//dlsym(handle, "dexFileParse");
3、調(diào)用allocateAuxStructures轉(zhuǎn)換DexFile為DvmDex,(由于該方法為static方法,因此需要按照其邏輯自行實現(xiàn))
\dalvik\vm\DvmDex.c static DvmDex* allocateAuxStructures(DexFile* pDexFile)
4、添加DvmDex到gDvm.userDexFiles
\dalvik\vm\Init.c struct DvmGlobals gDvm; //gDvm = dlsym(handle, "gDvm");
5、修改MyDexClassLoader中的mDexs對象的mCookie值,mCookie主要用于映射底層DvmDex數(shù)據(jù)DexClassLoader.mDexs[0].mCookie值
(4)so如何實現(xiàn)程序的反調(diào)試?
同linux反調(diào)試基本原理相同,這里提供一種方式就是在JNI_Onload中調(diào)用ptrace方 法,ptrace被廣泛用于調(diào)試(比如IDA)和進程代碼注入(比如LBE,金山等權(quán)限管理功能實現(xiàn)),一個進程只能被一個進程ptrace,如果你自己 調(diào)用ptarce,這樣其它程序就無法通過ptrace調(diào)試或者 向您程序進程注入代碼。
ptrace(PTRACE_TRACEME,0 ,0 ,0);
通過本人實驗,該種方式可以實現(xiàn)so的反調(diào)試。
三、總結(jié)
通過以上分析,梆梆加固的確可以有效防止移動應用在運營推廣過程中被破解、盜版、二次打包、注入、反編譯等破壞,不過如果Android惡 意軟件也通過這種方式加固保護,這將會給移動安全分析人員帶來巨大的挑戰(zhàn),因為安全分析人員經(jīng)常使用的代碼靜態(tài)邏輯分析和動態(tài)調(diào)試分析在該情況下都失效了。
梆梆官方聲稱不會對惡意軟件進行加固,的確在加固的過程中發(fā)現(xiàn)存在安全軟件掃描信息和云測試處理流程,不過這些措施只能減少而不能徹底杜絕惡意軟件通過梆梆加固保護。如何不被惡意軟件利用是梆梆需要解決的問題。