unity3d引擎的遊戲的腳本DUMP及HOOK方案優化

對unity3d引擎的遊戲,重要的資源就是C#腳本,腳本是被打包到APK的assets目錄下的一些dll文件,有的APP可能會對其加密,運行的時候再動態解密。可以通過HOOK libmono.so中的函數mono_image_open_from_data_with_name就可以DUMP出原始內容,如果加入的有其他加解密代碼,可以進一步地對解密函數進行HOOK,也是可以DUMP出內容的。

下面這個是以天天飛車爲例進行的分析,先看一段LOG:

01-06 17:27:39.045 9550-9550/? I/Xposed: java.lang.Runtime loadLibrary() afterHookedMethod: tprt
01-06 17:27:39.155 9550-9550/? I/Xposed: Hid process: de.robv.android.xposed.installer
01-06 17:27:39.155 9550-9550/? I/Xposed: Hid process: com.netease.l10:PushService
01-06 17:27:39.155 9550-9550/? D/TssSDK: process_name: com.tencent.game.SSGame
01-06 17:27:39.155 9550-9550/? D/dalvikvm: Trying to load lib /data/app-lib/com.tencent.game.SSGame-1/libtprt.so 0x422f8cc0
01-06 17:27:39.155 9550-9550/? D/dalvikvm: Shared lib '/data/app-lib/com.tencent.game.SSGame-1/libtprt.so' already loaded in same CL 0x422f8cc0
01-06 17:27:39.155 9550-9550/? I/Xposed: java.lang.Runtime loadLibrary() afterHookedMethod: tprt
01-06 17:27:39.165 9550-9550/? D/SUBSTRATEHOOK: the dlopen name =: /data/app-lib/com.tencent.game.SSGame-1/libtersafe.so
01-06 17:27:39.655 9550-9550/? D/SUBSTRATEHOOK: the dlopen name =: /data/app-lib/com.tencent.game.SSGame-1/libmono.so
01-06 17:27:39.660 9550-9550/? D/SUBSTRATEHOOK: the dlopen name =: libmono.so
01-06 17:27:39.660 9550-9550/? D/SUBSTRATEHOOK: [newdlopen] hook libmono.so
01-06 17:27:39.660 9550-9550/? D/SUBSTRATEHOOK: [dumplua] libmono.so handle: 0x726c3924
01-06 17:27:39.660 9550-9550/? D/SUBSTRATEHOOK: [dumplua] mono_image_open_from_data_with_name_0 found: 0x7698fc4c
01-06 17:27:39.750 9550-9550/? I/Xposed: java.lang.Runtime loadLibrary() afterHookedMethod: main
01-06 17:27:39.755 9550-9550/? D/SUBSTRATEHOOK: the dlopen name =: /data/app-lib/com.tencent.game.SSGame-1/libmono.so
01-06 17:27:39.755 9550-9550/? D/SUBSTRATEHOOK: the dlopen name =: /data/app-lib/com.tencent.game.SSGame-1/libunity.so
01-06 17:27:39.775 9550-9550/? D/SUBSTRATEHOOK: the dlopen name =: libc.so

用IDA查看libmain.so發現有加載mono的邏輯,但是實際HOOK發現在main之前就已經加載了mono,原因是libtersafe.so裏面有加載mono的邏輯,因爲tersafe在main之前加載,所以才導致了mono比main更早地被加載了。通過上面的LOG時間順序可以看出來。

而且這個mono並不是通過java層代碼加載的,因此我們之前的xposed通過HOOK Runtime的load及loadLibrary是無法攔截mono的加載的(之前分析cocos的時候是通過攔截game這個so加載的時候注入的SO)。

//當目標SO加載時再注入
private void hookLoadSharedLibrary(final XC_LoadPackage.LoadPackageParam lpparam, final String hostSoName) {
    if (TextUtils.isEmpty(hostSoName) == true) {
        return;
    }

    /*
      xposed不能HOOK java.lang.System loadLibrary函數,參考:[Hooking System.loadLibrary causes crash. #87] https://github.com/rovo89/XposedBridge/issues/87
     */
    XC_MethodHook.Unhook unhook = findAndHookMethod(Runtime.class, "loadLibrary", String.class, ClassLoader.class, new XC_MethodHook() {
        @Override
        protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
            //XposedBridge.log("java.lang.Runtime loadLibrary() beforeHookedMethod: " + param.args[0]);
        }

        @Override
        protected void afterHookedMethod(MethodHookParam param) throws Throwable {
            super.afterHookedMethod(param);
            String target = (String) param.args[0];
            XposedBridge.log("java.lang.Runtime loadLibrary() afterHookedMethod: " + target);
            if (target.equals(hostSoName)) {
                loadInjectSoFile(lpparam);
            }
        }
    });

    if (unhook != null) {
        XposedBridge.log("java.lang.Runtime loadLibrary() hook ok");
    } else {
        XposedBridge.log("java.lang.Runtime loadLibrary() hook failed");
    }

    findAndHookMethod(Runtime.class, "load", String.class, new XC_MethodHook() {
        @Override
        protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
            //XposedBridge.log("java.lang.Runtime load() beforeHookedMethod: " + param.args[0]);
        }

        @Override
        protected void afterHookedMethod(MethodHookParam param) throws Throwable {
            super.afterHookedMethod(param);
            XposedBridge.log("java.lang.Runtime load() beforeHookedMethod: " + param.args[0]);
        }
    });
}                                                             

因爲這裏的mono是被別其他的SO在初始化的時候(JNI_OnLoad)通過dlopen加載的,所以就導致了在xposed中無法攔截目標SO的加載事件,很容易漏網。那麼該怎麼辦呢?
- 想法一:如果APP有SO(一個或多個),即使有個別SO是在其他SO的JNI_OnLoad中通過dlopen加載的,那麼至少要有一個SO是要通過java層代碼加載。那就要面臨一個問題,到底應該攔截哪個SO呢?就像上面的那個例子一樣,本來以爲只要攔截main這個so的加載,但是tersafe被加載的時候mono就已經被加載了。而且每次要具體分析哪個SO加載了目標SO,還是有點麻煩的,不夠通用。
- 想法二:在xopsed層不攔截Runtime的load及loadLibrary函數了,只要攔截到APP啓動,就load注入SO,也就是第一時間把SO注入到目標APP中去。然後被注入的SO HOOK掉dlopen函數來攔截目標SO的加載,當目標SO加載時(libmono.so或libgame.so)再去做其他HOOK操作。由於Runtime的load及loadLibrary函數最終還是要調用到JNI層的dlopen函數,因此該方法可行,且不會漏掉SO的加載。

想法二可行是可行,但是會引來兩個個問題:
- 問題一:注入SO由於過早地被加載到目標進程,在JNI_OnLoad中動態獲取目標APP的包名會失效。
- 問題二:dlopen被HOOK,自己的代碼需要調用的時候需謹慎,以免進入死循環。

解決辦法

  • 問題一:注入SO由於過早地被加載到目標進程,在JNI_OnLoad中動態獲取目標APP的包名會失效。下面代碼中getPackagePath的是獲取/data/data/com.youzu.android.snsgz類似路徑的。
extern "C" jint JNI_OnLoad(JavaVM* vm, void* reserved)  
{
    JNIEnv* env = NULL;
    jint result = -1;  
    LOGD("[dumplua] %s begin", __FUNCTION__);
    if( vm->GetEnv((void**)&env, JNI_VERSION_1_6) != JNI_OK) {
        LOGE("utility GetEnv error");
        return result;
    }

    g_thisenv = env;
    g_bDataPathGot = getPackagePath(env, g_strDataPath);
    LOGD("[dumplua] data path: %s", g_strDataPath.c_str());
    hook();

    LOGD("[dumplua] %s end", __FUNCTION__);
    return JNI_VERSION_1_6;  
}

解決辦法:先緩存一個JNIEnv指針,記錄路徑是否獲取成功的狀態。等到後面保存文件的時候再判斷一下,如果之前沒有成功獲取到路徑,那麼在保存文件之前獲取一下即可。

string getNextFilePath(const char *fileExt) {
    char buff[100] = {0};
    ++g_nCount;
    if ( g_bDataPathGot==false ) {
        g_bDataPathGot = getPackagePath(g_thisenv, g_strDataPath);
    }
    sprintf(buff, "%s/cache/%d%s", g_strDataPath.c_str(), g_nCount, fileExt);
    return buff;
}

動態獲取包名的代碼可以參考:android jni簽名驗證(一)

  • 問題二:dlopen被HOOK,自己的代碼需要調用dlopen的時候需謹慎,以免進入死循環。

解決辦法:緩存目標SO的句柄,後期直接使用,規避對dlopen的調用或者調用olddlopen。參考:如何hook dlopen和dlsym底層函數

void* (*olddlopen)(const char* filename, int myflags) = NULL;
void* newdlopen(const char* filename, int myflags) {
    LOGD("the dlopen name =: %s",filename);
    void *handle = olddlopen(filename, myflags);

    if ( strcmp(filename, "libmono.so")==0 ) {
        if ( g_bU3dHooked==false ) {
            //libmono.so加載了,但是發現之前並沒有HOOK成功
            g_handlelibMonoSo = handle;
            LOGD("[%s] hook libmono.so", __FUNCTION__);
            g_bU3dHooked = hookU3D();
        }
    }else if ( strcmp(filename, "libgame.so")==0 ) {
        if ( g_bCocosHooked==false ) {
            g_handlelibGameSo = handle;
            LOGD("[%s] hook libgame.so", __FUNCTION__);
            g_bCocosHooked = hookCocos();
        }
    }
    return handle;
}



bool hookU3D() {
    void *handle = NULL;
    if ( g_handlelibMonoSo==NULL ) {
        if ( olddlopen==NULL ) {
            handle = dlopen("libmono.so", RTLD_NOW);
        }else{
            handle = olddlopen("libmono.so", RTLD_NOW);
        }
        if (handle == NULL) {
            LOGE("[dumplua]dlopen err: %s.", dlerror());
            return false;
        }
    }else{
        handle = g_handlelibMonoSo;
        LOGD("[dumplua] libmono.so handle: %p", handle);
    }

    void *mono_image_open_from_data_with_name = dlsym(handle, "mono_image_open_from_data_with_name");
    if (mono_image_open_from_data_with_name == NULL){
        LOGE("[dumplua] mono_image_open_from_data_with_name not found!");
        LOGE("[dumplua] dlsym err: %s.", dlerror());
    }else{
        LOGD("[dumplua] mono_image_open_from_data_with_name found: %p", mono_image_open_from_data_with_name);
        MSHookFunction(mono_image_open_from_data_with_name, (void *)&mono_image_open_from_data_with_name_mod, (void **)&mono_image_open_from_data_with_name_orig);
    }

    return true;
}

這裏是攔截的天天飛車的LOG信息:

01-06 17:19:41.075 1286-1397/? D/SUBSTRATEHOOK: [dumplua] mono_image_open_from_data_with_name, name: /data/app/com.tencent.game.SSGame-1.apk/assets/bin/Data/Managed/UnityEngine.dll, len: 310272, buff: MZ�
01-06 17:19:41.345 1286-1397/? D/SUBSTRATEHOOK: [dumplua] mono_image_open_from_data_with_name, name: /data/app/com.tencent.game.SSGame-1.apk/assets/bin/Data/Managed/Assembly-CSharp-firstpass.dll, len: 2926592, buff: MZ�
01-06 17:19:41.625 1286-1397/? D/SUBSTRATEHOOK: [dumplua] mono_image_open_from_data_with_name, name: /data/app/com.tencent.game.SSGame-1.apk/assets/bin/Data/Managed/Assembly-CSharp.dll, len: 7148544, buff: MZ�
01-06 17:19:41.690 1286-1397/? D/SUBSTRATEHOOK: [dumplua] mono_image_open_from_data_with_name, name: /data/app/com.tencent.game.SSGame-1.apk/assets/bin/Data/Managed/UnityEngine.UI.dll, len: 171520, buff: MZ�
01-06 17:19:41.690 1286-1397/? D/SUBSTRATEHOOK: [dumplua] mono_image_open_from_data_with_name, name: /data/app/com.tencent.game.SSGame-1.apk/assets/bin/Data/Managed/poly2tri.dll, len: 43008, buff: MZ�
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章