.so(SharedObject)作用等同于windows環境下的.dll(dynamic link library)文件,我們在引用第三方SDK時,也會遇到需要調用相應的.so文件的情況,.so文件本事更多的是集成公共處理方法,當然有事也會用來保存重要的數據信息。
對于應用的編譯與反編譯過程中,本地混淆一直是有效的方法。對于.so文件,同樣也適用于混淆,.so文件雖然在使用破解工具IDA打開后看到的是匯編數據,但仍然存在著規律可循,不妨礙專業人員的閱讀,所以掌握對.so文件的混淆也是必須的。
.so文件的混淆是從兩方面入手:
1:方法名混淆
2:數據混淆
我們在創建構建.so文件所需的.c和.h文件時,NDK會幫助我們自動生成native方法名,這個方法名是格式化,該方法名同時也是索引表中的索引名。因其特殊性,在IDA瀏覽時,可以通過索引表快速查看到。比如我們創建一個TestSimple,在里面聲明一個native方法showDsc(),最終得到的方法名是: java_com_example_xx_TestSimple_showDsc
生成的.so文件在經過IDA反編譯后,可以在函數索引表中清楚的看到:
好在NDK提供了修改的方法,幫助我們隱藏掉這種顯眼的名字。首先說一下兩個基礎方法:1、簡化方法名,該方法如下:
繼續以TestSimple為例,聲明
#defineJNIFUNCTION_NATIVE(sig) Java_com_example_xx_TestSimple_##sig
將函數名前邊的路徑剔除掉,只顯示函數名,避免顯得直接。
JNIEXPORT void JNICALL JNIFUCTION_NATIVE (showMath(JNIEnv *env, jobject obj));該方法只能用來簡化方法名,在編輯時有用,但是在破解中,對隱藏沒有作用,修改后,在IDA中依然可以查看到:
2、替換section索引名,該方法如下:
__attribute__((section(".XXXX")))其中XXXX表示自定義的索引名,需要在指定的函數名前 聲明該方法, 例如:
__attribute__((section (".xxxsimple"))) JNICALL jstring show(JNIEnv *env, jclass obj){ return realShow(env,obj);}實際效果圖如下:
該方法通過改變屬性類型,影響破解后相關代碼段解析順序,
NDK的方法名混淆就是將1和2 兩種方法通過配合RegisterNatives()注冊的方式混合使用,RegisterNatives()可以實現與類關聯的本地方法,這步操作必須在調用之前完成,也就是庫加載過程中。一般是用JNI_Onload這個函數來完成。通過RegisterNatives()進行關聯,將實際執行函數由聲明的類函數轉變成本地函數,達到隱藏的效果,調用步驟如下:
步驟一:
聲明待替換的方法集合JNINativeMethod,是一個方法類型集合,
//指針操作,將java層的showDsc函數指向到Native層的show()函數上static JNINativeMethodgetMethods[] = { {"showDsc","()Ljava/lang/String;",(void*)show},};其中JNINativeMethod的結構定義如下:
typedef struct{ char *name;// 待指向函數名 char *signature;// 待指向函數和形參類型 void *fnPtr//函數指針,實際執行函數}
聲明待注冊的類名
#define JNIREG_CLASS"com/example/xx/Test"步驟二:
在實際執行函數前標記自定義屬性名稱
__attribute__((section(".xxxfirst"))) JNICALL jstring show(JNIEnv *env, jclass obj){ return realShow(env,obj);}步驟三:
在實際執行函數完成操作。
jstringrealShow(JNIEnv *env, jclass obj){ return (*env)-> NewStringUTF(env,“10086”);}步驟四:
執行RegisterNatives(),注冊與指定類相關聯的方法
static int registerNativeMethods(JNIEnv* env, const char* className,JNINativeMethod* gMethods, int numMethods){ jclass clazz; clazz = (*env)->FindClass(env, className); if (clazz == NULL) { return JNI_FALSE; } if ((*env)->RegisterNatives(env, clazz, getMethods, numMethods) < 0) { return JNI_FALSE; } return JNI_TRUE;}步驟五:對指定的類進行指定方法名注冊,注冊成功則返回0,失敗為1。
static intregisterNatives(JNIEnv* env){if(!registerNativeMethods(env,JNIREG_CLASS,getMethods, sizeof(getMethods) /sizeof(getMethods[0]))){ return JNI_FALSE;} return JNI_TRUE;}
步驟六:
在JNI_OnLoad中執行注冊操作
JNIEXPORT jintJNI_OnLoad(JavaVM* vm, void* reserved){ JNIEnv* env = NULL;jint result = -1; if ((*vm)->GetEnv(vm,(void**) &env, JNI_VERSION_1_4) != JNI_OK) {//GetEnv()用以獲得一個JNIEnv全局對象 return -1; } assert(env != NULL); if (!registerNatives(env)) { return -1; } result = JNI_VERSION_1_4; return result;}構建成功之后,我們可以看到,在方法名索引表中找不到調用的函數名稱。只有找到自定的section區域才能進行下一步解析。
數據混淆
通常情況下,在.so庫中放置的是方法集合,但是如果存在文本內容時,就需要考慮數據混淆了,一旦使用聲明常量的方式設置文本數據,這些數據在使用過程中會被基本類型完整保存,這些數據在通過破解后,會直白的出現在破解者面前。這種設置讓匯編程序在進行地址指引時,指向的是一個數據源,而非指針地址時。如果我們對外提供文本數據時,對數據采取數組拼接的方式,指向數組獲得的是一個指針地址,這樣破解者看到的是地址標記而非直白的數據源。下面是兩組對比。
直接以文本形式輸出:
JNIEXPORT jstring JNICALLJava_com_example_xx_Test_showC(JNIEnv *env, jobject obj){ charsha[100]; nstrcpy(sha,H, A, C, E, J, F, NULL); return(*env)-> NewStringUTF(env, sha);}使用數組組合的形式:
JNIEXPORT jstring JNICALLJava_com_example_xx_Test_showB(JNIEnv *env, jobject obj){ char str[15]; str[0]= O[1]; str[1]= N[4]; str[2]= P[0]; str[3]= N[2]; str[4]= D[0]; str[5]= Q[0]; str[6]= Q[2]; str[7]= O[1]; str[8]= D[0]; str[9]= P[3]; str[10]= '/0'; return(*env)-> NewStringUTF(env, str);}
最后,說一下,其實沒有破解不了的保護,只有破解不了的人心,不管安全軟件機構如何宣傳,碰到有心人士也是無解,我們做開發的,只是在增加破解難度而已。
新聞熱點
疑難解答