前言
在Android應(yīng)用中,出于對(duì)安全性的考慮,開發(fā)者會(huì)把一些重要的邏輯放到native層,即so庫(kù)中。但是so庫(kù)也并非絕對(duì)的安全,在強(qiáng)大的IDA反編譯下,so庫(kù)中的邏輯也將無所遁形。今天,我們要說的就是混淆so庫(kù)中的方法名。
正文
我們先來看下為什么要這么做。我們新建一個(gè)Android項(xiàng)目,添加NDK支持,默認(rèn)項(xiàng)目中會(huì)有一個(gè)方法如下
external fun stringFromJNI(): String
companion object {
// Used to load the 'native-lib' library on application startup.
init {
System.loadLibrary("native-lib")
}
}
然后我們編譯運(yùn)行,在app/build/intermediates/cmake/debug/obj/armeabi/libnative-lib.so目錄下找到我們編譯出來的so庫(kù),在IDA中打開,我們可以很輕易的找到這個(gè)方法對(duì)應(yīng)的地方

為什么會(huì)這樣的?因?yàn)槲覀冊(cè)趯?code>JNI的時(shí)候,采用javah命令生成的頭文件,里面的方法名默認(rèn)就是
java_com_xxxx這種形式。所以我們現(xiàn)在要做的就是改變這種映射關(guān)系。
當(dāng)虛擬機(jī)加載這個(gè)這個(gè)庫(kù)的時(shí)候,從java層進(jìn)入本地層首先會(huì)執(zhí)行JNI_Onload這個(gè)函數(shù),java層的方法就是在這個(gè)方法中注冊(cè)的,使用的方法為registerNativeMethods,我們?cè)谶@一步將注冊(cè)我們自定義的方法,這樣方法名就可以由我們自己來定義。接下來看代碼實(shí)現(xiàn)。
- 重寫
JNI_Onload方法
extern "C"
jint JNI_OnLoad(JavaVM* vm,void* reserved){
JNIEnv* env;
if (vm->GetEnv((void**)(&env), JNI_VERSION_1_6) != JNI_OK)
{
return -1;
}
//這一步,注冊(cè)我們的方法
if (!registerNatives(env)) {
return -1;
}
return JNI_VERSION_1_6;
}
- 接下來看
registerNatives方法的實(shí)現(xiàn)
static JNINativeMethod gMethods[] = {
//第一個(gè)參數(shù)為Java層的方法名
//第二個(gè)參數(shù)為方法的簽名,括號(hào)內(nèi)為參數(shù)類型,后面為返回類型
//第三個(gè)參數(shù)即為我們需要重新注冊(cè)的方法名
{ "stringFromJNI", "()Ljava/lang/String;", (void*)getStr},
};
extern "C"
int registerNativeMethods(JNIEnv* env, const char* className,
JNINativeMethod* gMethods, int numMethods)
{
jclass clazz;
clazz = env->FindClass(className);
if (clazz == NULL) {
return JNI_FALSE;
}
if (env->RegisterNatives(clazz, gMethods, numMethods) < 0) {
return JNI_FALSE;
}
return JNI_TRUE;
}
extern "C"
int registerNatives(JNIEnv* env)
{
if (!registerNativeMethods(env, JNIREG_CLASS, gMethods,
sizeof(gMethods) / sizeof(gMethods[0])))
return JNI_FALSE;
return JNI_TRUE;
}
到這里我們成功的替換了注冊(cè)的方法,重新用IDA打開so庫(kù),發(fā)現(xiàn)我們這次找不到形如java_com_xxxx的方法了最后,還有一步,我們還得實(shí)現(xiàn)替換后的方法
extern "C"
//這里指定該代碼所在的段,編譯的時(shí)候就會(huì)把這個(gè)函數(shù)編譯到自定義的名叫”.mytext“的section里面
//由于我們?cè)趈ava層沒有定義這個(gè)函數(shù)因此要寫到一個(gè)自定義的section里面。
__attribute__((section (".mytext"))) JNICALL jstring getStr
(JNIEnv *env, jobject obj)
{
return (env)->NewStringUTF("abc");
}
至此,我們就完成了所有混淆方法的操作。
后記
這只是一步基礎(chǔ)的安全操作,也很容破解。比如通過IDA動(dòng)態(tài)調(diào)試找到RegisterNatives方法,就能直接找到所對(duì)應(yīng)的方法,也能通過直接找額外的段來找到這個(gè)方法,但是這些就不在本文詳細(xì)說明了,有興趣的同學(xué)可以自己去嘗試下。