Andorid 關於PTRACE ptrace(PTRACE_TRACEME,0 ,0 ,0);
通過對App進行加固保護,梆梆可以有效防止移動應用在運營推廣過程中被破解、盜版、二次打包、注入、反編譯等破壞,保障程序的安全性、穩定性,對移動應用的整體邏輯結構進行保護,保證了移動應用的用戶體驗。
一、梆梆加固逆向分析過程
首先我們通過對APK加固前後文件結構的比較,來了解梆梆加固對APK文件所做的處理。為了使分析過程足夠簡單,我新建一個最簡單的測試程序,並上傳到梆梆加固,整個加固過程大概需要4天的時間才可以完成,這是一個漫長的等待過程。
該測試程序包含了Activity、Service、ContentProvider、BroadcastRecevier四大組件、Application、普通類、Jni調用等7類對象,目的就是全麵的了解梆梆的加固效果。
1. apk加固前後靜態文件結構及動態運行時對比分析
(1)加固前後靜態文件結構變化(左為加固前,右為加固後)加固後apk新增以下文件:
- assets\meta-data\manifest.mf //APK文件列表SHA1-Digest
- assets\meta-data\rsa.pub //RSA公鑰信息
- assets\meta-data\rsa.sig //數字簽名文件
- assets\classes.jar //已加密原classes.dex文件
- assets\com.example.hellojni //ARM平台二進製可執行文件
- assets\com.example.hellojni.x86 //x86功能同上
- libs\armeabi\libsecexe.so //ARM平台共享庫文件
- libs\x86\libsecexe.so //x86功能同上
加固後修改文件:
- AndroidMainfest.xml //(如果應用配置有Application信息,則該文件加固前後相同,如果應用未配置Application信息,則該文件加固前後不相同,梆梆會配置Application信息為自己實現類)
- classes.dex
對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_{函數名}。
抗靜態分析:
- Util類通過MyClassLoader完成對加密classes.jar的動態加載,內存中解密classes.jar,完成動態加載。
- jni方法對應so函數名的混淆。
抗動態調試:
- 當使用IDA動態調試該程序時,程序無法建立連接調試。
梆梆加固可以常用的有效的逆向分析方法。
二、梆梆加固技術實現關鍵點猜想
1. 如何使DexClassLoader動態加載組件具有生命周期?根據APK文件是否在AndroidManifest.xml配置Applicaiton信息,梆梆加固會做不同的處理。
通過上傳Applicaiton不同配置的APK文件,我們發現:
- 當APK配置有Applicaition信息時,梆梆加固重寫Application類
- 當APK未配置Application信息時,梆梆加固新建類,並在AndroidManifest.xml中配置自己Application類
因此Applicaiton就是程序的第一執行點。
我們知道DexClassLoader加載的類是沒有組件生命周期的,也就是說即使DexClassLoader通過對dex的動態加載完成了對組件類的加載,當係統啟動該組件時,還會出現加載類失敗的異常。我已經在“Android APK加殼技術方案”中提出了一種使DexClassLoader加載組件類具有生命周期的方法。
運行加固後的程序並通過Mat內存分析工具查看類加載情況:
如上圖所示,組件類的加載類已經被修改為com.secapk.wrapper.MyClassLoader類,可以得出結論,該方式和我提出方式基本相同,通過修改係統組件類ClassLoader來實現。
2. 如何混淆native方法在so庫函數對應關係?jni方法注冊方式有兩種:
- 通過javah產生函數頭,該種方式產生的方法具有固定的格式。該方式使逆向分析人員比較容易獲取java層native方法對應的本地方法。
- 在JNI_OnLoad方法中手動注冊jni方法,不易查找對應關係。
使用第二種方式可以實現混淆java層native方法和so函數的對應關係。
- #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層就可以調用了。
以下是大概偽代碼實現步驟:
- int loadDex(char * dexFileName)
- {
- char *dvm_lib_path = "/system/lib/libdvm.so";
- void * handle;
- DvmGlobals gDvm;
- handle = dlopen( dvm_lib_path, int mode);
- 讀取dexFileName文件內容並解密到byte數組。
調用dexFileParse函數解析byte數組為DexFile
\dalvik\libdex\DexFile.c
DexFile* dexFileParse(const u1* data, size_t length, int flags)//dlsym(handle, "dexFileParse");
- 調用allocateAuxStructures轉換DexFile為DvmDex(由於該方法為static方法,因此需要按照其邏輯自行實現)。
\dalvik\vm\DvmDex.c
static DvmDex* allocateAuxStructures(DexFile* pDexFile)
- 添加DvmDex到gDvm.userDexFiles
\dalvik\vm\Init.c
struct DvmGlobals gDvm; //gDvm = dlsym(handle, "gDvm");
- 修改MyDexClassLoader中的mDexs對象的mCookie值
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惡意軟件也通過這種方式加固保護,這將會給移動安全分析人員帶來巨大的挑戰,因為安全分析人員經常使用的代碼靜態邏輯分析和動態調試分析在該情況下都失效了。
梆梆官方聲稱不會對惡意軟件進行加固,的確在加固的過程中發現存在安全軟件掃描信息和雲測試處理流程,不過這些措施隻能減少而不能徹底杜絕惡意軟件通過梆梆加固保護。如何不被惡意軟件利用是梆梆需要解決的問題。
愛加密和梆梆加固的破解方法
By Bob Pan
梆梆與愛加密都使用了將原有的dex隱藏, 在運行時解壓, 並且通過修改app的類加載器的方式實現加固. 參考: AndoridAPK反逆向解決方案:梆梆加固原理探尋
然而, 不管如何隱藏dex, 最終在運行時都必須釋放到內存, 所以本文的思路是從內存中找到解密後的dex文件, 進而得到加固前的apk.
愛加密
愛加密的app並沒有做反調試的保護. 打開app之後直接使用gdb連接, 然後用gcore, 產生core dump.
使用ps查看pid
使用gdb連接pid
使用gcore產生core dump
將產生的core.1033複製回電腦, 並使用編輯器打開, 通過類名找到dex中string-data段, 然後通過查找’dex.035’可以找到多離string-data最近的個dex頭. dex文件頭偏移32的整形值就是dex的文件長度. 使用dd命令可以從內存中摳出dex.
通過類名找string-data段
找到最近的dex文件頭(0x4f87a08)和dex文件大小0x07c0
使用dd摳出dex
這個文件是個完整的dex文件, 並且可以被dexdump直接打印
梆梆
梆梆加固的程序做了anti-ptrace, 表現為使用gdb --pid 連接不上對應的進程, 利用kernel-model打印ptrace的記錄, 可以看出梆梆
l 使用了3個進程互相ptrace.
l 發送錯誤指令來檢查ptrace是否被劫持(反回值是-3行, 嚐試讓1568進程繼續執行, 但是1568並未被ptrace, 必須出錯),
l 利用ptrace修改另一個進程的數據(action是5的行).
ptrace係統調用的記錄, 右邊是ptrace的參數
雖然連不上1552, 但是dalvik是一個多線程的程序, 裏麵包含主進程, gc線程, binder線程等, 雖然我們用gdb連不上主線程, 但是我們可以連上其他線程, 這些線程的tid在/proc/[pid]/task/目錄下.
Gdb連接任意一個tid
拿到coredump後與愛加密一樣做相同的處理, 可以拿到dex.
總結
愛加密和梆梆通過隱藏dex確實可以讓大部分靜態分析工具找不到執行代碼, 但是在動態運行的時候無可避免的需要將dex在內存中還原. 雖然梆梆做了反調試, 但是通過其他方式也同樣可以獲取其內存. 通過本文的方法分析其內存然後恢複dex, 更進一步可以完全恢複原始apk. 從這個角度說, 愛加密和梆梆的加固形同虛設.
最後更新:2017-04-03 12:55:52