1. 程式人生 > >java程式碼(dex)注入

java程式碼(dex)注入

首先簡單介紹一下程序注入的概念:
程序注入就是將一段程式碼拷貝到目標程序,然後讓目標程序執行這段程式碼的技術。由於這樣的程式碼構造起來比較複雜,所以實際情況下,只將很少的程式碼注入到目標程序,而將真正做事的程式碼放到一個共享庫中,即.so檔案。被注入的那段程式碼只負責載入這個.so,並執行裡面的函式。
由於.so中的函式是在目標程序中執行的,所以在.so中的函式可以修改目標程序空間的任何記憶體,當然也可以加鉤子,從而達到改變目標程序工作機制的目的。
當然不是任何程序都有許可權執行注入操作的。Android平臺上的程序注入是基於ptrace()的,要呼叫ptrace()需要有root許可權。目前市面上的主流安全軟體也都是基於程序注入來管理和控制其他應用程序的。這也就是為什麼這些安全軟體需要獲得root許可權的原因。
關於如何.so注入的實現,有興趣的朋友可以參考看雪論壇的上的一個注入庫
LibInject
http://bbs.pediy.com/showthread.php?t=141355
和洗大師的一個開源專案Android Injector Libraryhttp://code.google.com/p/libandroidinjector/downloads/list
.so注入以後已經可以幹很多事情了,但畢竟是在native層。想要在native層直接修改Java層的變數和邏輯還是很不方便的。況且Android平臺的絕大多數應用都是用Java程式碼寫的。因此自然而然就會想到,有沒有什麼方式可以將dex檔案注入目標程序,然後執行dex檔案中的Java程式碼?

經過一段時間的研究,筆者找到了一個切實可行的方法。這裡分享給大家:首先,所有的Java類都是由類載入器(ClassLoader)載入的,我們要從特定的路徑下載入一個我們自己的dex檔案,就必須要有一個自己類載入器才行。有了這個類載入器我們就可以載入我們自己的類,並用反射呼叫這個類裡面的方法。其次,要構造生成這樣一個類載入器必須要獲得現有的類載入器,因為類載入器是雙親委派模式的。現有的類載入器可以通過反射獲得。只是這些都需要用native程式碼實現。
下面簡述一下dex注入的過程:
1. 將.so注入目標程序,執行.so檔案中的某個函式。
2. 在這個函式裡先獲得一個JNIEnv指標,通過這個指標就可以調JNI函數了。
3. 反射得到當前應用程序的PathClassLoader,用這個ClassLoader來構造一個DexClassLoader物件。Dex檔案路徑作為一個引數傳入DexClassLoader的建構函式,另一個重要的引數是,一個具有可寫許可權的資料夾路徑。因為在做dex優化時,需要生成優化過的dex檔案,這跟生成/data/dalvik-cache/下的dex檔案是一個道理。
4. 通過這個DexClassLoader物件,來載入目標類,然後反射目標類中的目標函式。最終呼叫之。

參考程式碼:
//功能:呼叫dexPath檔案中的className類的methodName方法。
//dexPath: dex/jar/apk 檔案路徑
//dexOptDir: 優化目錄, 這個目錄的owner必須是要被注入程序的user,否則dex優化會失敗
//className: 目標類名,如“com.hook.Test”
//methodName: 目標方法名,如"main", 在Java程式碼裡必須定義為public static void main(String[] args);
//argc,傳給目標方法的引數個數
//argv,傳給目標方法的引數
int invoke_dex_method(const char* dexPath, const char* dexOptDir, const char* className, const char* methodName, int argc, char *argv[]) {
    ALOGD("Invoke dex E");
    JNIEnv* env = android::AndroidRuntime::getJNIEnv();
    jclass stringClass, classLoaderClass, dexClassLoaderClass, targetClass;
    jmethodID getSystemClassLoaderMethod, dexClassLoaderContructor, loadClassMethod, targetMethod;
    jobject systemClassLoaderObject, dexClassLoaderObject;
    jstring dexPathString, dexOptDirString, classNameString, tmpString;    
    jobjectArray stringArray;

    /* Get SystemClasLoader */
    stringClass = env->FindClass("java/lang/String");
    classLoaderClass = env->FindClass("java/lang/ClassLoader");
    dexClassLoaderClass = env->FindClass("dalvik/system/DexClassLoader");
    getSystemClassLoaderMethod = env->GetStaticMethodID(classLoaderClass, "getSystemClassLoader", "()Ljava/lang/ClassLoader;");
    systemClassLoaderObject = env->CallStaticObjectMethod(classLoaderClass, getSystemClassLoaderMethod);

    /* Create DexClassLoader */
    dexClassLoaderContructor = env->GetMethodID(dexClassLoaderClass, "<init>", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/ClassLoader;)V");
    dexPathString = env->NewStringUTF(dexPath);
    dexOptDirString = env->NewStringUTF(dexOptDir);
    dexClassLoaderObject = env->NewObject(dexClassLoaderClass, dexClassLoaderContructor, dexPathString, dexOptDirString, NULL, systemClassLoaderObject);

    /* Use DexClassLoader to load target class */
    loadClassMethod = env->GetMethodID(dexClassLoaderClass, "loadClass", "(Ljava/lang/String;)Ljava/lang/Class;");
    classNameString = env->NewStringUTF(className);
    targetClass = (jclass)env->CallObjectMethod(dexClassLoaderObject, loadClassMethod, classNameString);
    if (!targetClass) {
        ALOGE("Failed to load target class %s", className);
        return -1;
    }

    /* Invoke target method */
    targetMethod = env->GetStaticMethodID(targetClass, methodName, "([Ljava/lang/String;)V");
    if (!targetMethod) {
        ALOGE("Failed to load target method %s", methodName);
        return -1;
    }
    stringArray = env->NewObjectArray(argc, stringClass, NULL);
    for (int i = 0; i < argc; i++) {
        tmpString = env->NewStringUTF(argv[i]);
        env->SetObjectArrayElement(stringArray, i, tmpString);
    }
    env->CallStaticVoidMethod(targetClass, targetMethod, stringArray);
    ALOGD("Invoke dex X");
    return 0;
}