基於Xposed和Substrate的通用性SO注入

需求來源

如果需要注入SO且HOOK一些功能做研究分析,必然需要注入HOOK,而對於不同的分析目標除了HOOK的函數不同之外,注入部分是相同的,可以把相同部分的代碼提出來,做成一個功能,那麼以後注入部分就不用再次編寫了,分析的時候只需要編寫HOOK代碼即可。

設計

我們把整體分成三個部分:java層、SO loader層、SO HOOK層。java層和SO loader層我們抽象爲框架層(負責注入流程),SO HOOK層抽象爲應用層(以後研發分析人員只需要編寫這個SO即可,關注於函數的HOOK,不必關心注入流程)。

代碼儘量在java層代碼多寫一些,畢竟寫java代碼要比寫C++代碼容易些,出錯也容易排查,如下是大致的設計流程:
image
- 注入器Java(Xposed)層:主要是攔截APP的運行,當APP運行時通過判斷包名是否是目標包名來過濾,如果不是則放行,否則就加載配置執行注入SO、注入插件APK、隱藏XPOSED以及其他功能的初始化(諸如微信骰子剪刀石頭布的作弊、貪喫蛇大作戰作弊、地圖的模擬定位等)。這裏只介紹注入SO這一塊,其他的暫不涉獵。在運行目標APP的時候,事先保存一個配置文件,裏面保存了目標APP的包名以及待攔截的SO名稱,例如libgame.so或libmono.so。由於在JNI層HOOK函數直接使用了substrate框架,注入SO前先把libsubstrate.so和libsubstratedvm.so加載進去,然後再加載loader(也是一個SO)。
- 注入器SO(loader)層:loader被加載後,JNI_OnLoad中通過讀取配置文件獲取目標APP的包名、要攔截的SO名稱、要注入的HOOK SO(應用層),然後HOOK dlopen函數,當目標SO被加載時,加載HOOK SO並調用約定導出函數on_dlopen函數,傳入足夠多的信息過去,這樣開發者可以直接利用這些信息處理,不必每次都要重新獲取了,也即只關注HOOK的函數本身。
- HOOK SO(應用層):由於在loader層已經對dlopen做了HOOK,所以這裏一定不能再用dlopen去獲取模塊的句柄,而要用從loader層傳遞過來的原始地址olddlopen,否則會進入死循環。到了這一環節,只需要獲取到目標函數進行HOOK即可,代碼量就很少了。

優勢

每次修改SO無須重啓手機,編譯SO後可以利用AndroidStudio的instantrun功能快速生效,測試效率很高。

實現

注入器SO(loader)層

HOOK dlopen的代碼如下,參考:如何hook dlopen和dlsym底層函數


Tdlopen olddlopen = NULL;
void* newdlopen(const char* filename, int myflags) {
    LOGD("[soloader]dlopen: %s",filename);
    void *handle = olddlopen(filename, myflags);

    //目標SO加載時,加載注入SO
    if ( strstr(filename, g_config.strHostSO.c_str())!=NULL ) {
        void *hInjectSO = olddlopen(g_config.strInjectSoPath.c_str(), RTLD_NOW);
        Ton_dlopen pOn_dlopen = (Ton_dlopen)olddlsym(hInjectSO, "on_dlopen");
        if ( pOn_dlopen!=NULL ) {
            pOn_dlopen(filename, handle, g_env, g_config.strHostPackage.c_str(), olddlopen, g_pMSHookFunction);
        }
    }
    return handle;
}

bool hookdlopen() {
    void *dlopen_addr = NULL;
    void *dlsym_addr = NULL;

    //獲取dlopen地址
    dlopen_addr = get_remote_addr(getpid(), "/system/bin/linker", (void *)dlopen);
    LOGD("[soloader] dlopen_addr: [%p]", dlopen_addr);

    //hook dlopen方法,下面方法類似
    g_pMSHookFunction(dlopen_addr, (void *)&newdlopen, (void**)&olddlopen);

    dlsym_addr = get_remote_addr(getpid(), "/system/bin/linker", (void *)dlsym);
    LOGD("[soloader] dlsym_addr: [%p]", dlsym_addr);
    g_pMSHookFunction(dlsym_addr, (void*)&newdlsym, (void**)&olddlsym);

    return true;
}
//////////////////////////////////////////////////////////////////////////

void *getMSHookFunction()
{
    void *pfunc = NULL;
    void *handle = dlopen("/data/data/com.bigsing.xtool/lib/libsubstrate.so", RTLD_NOW);
    if (handle == NULL) {
        LOGE("[soloader]dlopen libsubstrate.so failed!");
    }else{
        pfunc = dlsym(handle, "MSHookFunction");
        if (NULL == pfunc) {
            LOGE("[soloader]can't find MSHookFunction");
        }
    }

    return pfunc;
}


/************************************************************************/
/* 
Java層(xposed)在handleLoadPackage時會通過System.load加載本SO作爲loader。
*/
/************************************************************************/
extern "C" jint JNI_OnLoad(JavaVM* vm, void* reserved)  
{
    JNIEnv* env = NULL;
    jint result = -1;  
    LOGD("[soloader] %s begin", __FUNCTION__);
    if( vm->GetEnv((void**)&env, JNI_VERSION_1_6) != JNI_OK) {
        LOGE("[soloader]utility GetEnv error");
        return result;
    }
    g_env = env;

    //
    LoadConfig(g_config);
    LOGD("[soloader] package name: %s", g_config.strHostPackage.c_str());

    g_pMSHookFunction = (TMSHookFunction)getMSHookFunction();

    if ( g_config.strHostSO.empty()==false ) {
        if ( g_pMSHookFunction!=NULL ) {
            //當目標SO加載時注入SO
            hookdlopen();
        }
    }else{
        //無須攔截SO,那麼直接加載咯
        void *hInjectSO = dlopen(g_config.strInjectSoPath.c_str(), RTLD_NOW);
        Ton_dlopen pOn_dlopen = (Ton_dlopen)dlsym(hInjectSO, "on_dlopen");
        if ( pOn_dlopen!=NULL ) {
            pOn_dlopen(NULL, NULL, g_env, g_config.strHostPackage.c_str(), dlopen, g_pMSHookFunction);
        }
    }

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

HOOK SO層

由於loader層傳遞的信息足夠多,很多可以直接使用,因此主要在on_dlopen中完成HOOK處理。

/************************************************************************/
/* 
該函數可能會被調用多次,所以要記錄初始化標誌,HOOK過了就不要再HOOK了。
*/
/************************************************************************/
extern "C" void on_dlopen(const char* libname, void *handle, JNIEnv *env, const char *szPackageName, Tdlopen olddlopen, TMSHookFunction pMSHookFunction) {
    LOGD("[gamedumper]%s begin", __FUNCTION__);
    g_env = env;

    g_pMSHookFunction = pMSHookFunction;
    if ( g_pMSHookFunction==NULL ) {
        //自己再動態獲取一遍,但是要記得用olddlopen
    }
    if ( szPackageName==NULL || strlen(szPackageName) < 4 ) {
        g_bDataPathGot = getPackagePath(env, g_strDataPath);
    }else{
        g_strDataPath = "/data/data/";
        g_strDataPath.append(szPackageName);
        g_bDataPathGot = true;
    }

    if ( strstr(libname, "libmono.so")!=0 ) {
        if ( g_bU3dHooked==false ) {
            g_bU3dHooked = hookU3D(handle, olddlopen);
        }
    }else if ( strstr(libname, "libgame.so")!=0 ) {
        if ( g_bCocosHooked==false ) {
            g_bCocosHooked = hookCocos(handle, olddlopen);
        }
    }

    LOGD("[gamedumper]%s end", __FUNCTION__);
}

U3D引擎的HOOK:

//hook mono_image_open_from_data_with_name
int (*mono_image_open_from_data_with_name_orig)(char *data, int data_len, int need_copy, void *status, int refonly, const char *name) = NULL;
int mono_image_open_from_data_with_name_mod(char *data, int data_len, int need_copy, void *status, int refonly, const char *name) {
    LOGD("[dumplua] mono_image_open_from_data_with_name, name: %s, len: %d, buff: %s", name, data_len, data);
    int ret = mono_image_open_from_data_with_name_orig(data, data_len, need_copy, status, refonly, name);
    saveFile(data, data_len, getNextFilePath(".dll").c_str());
    return ret;
}


bool hookU3D(void *handlelibMonoSo, Tdlopen olddlopen) {
    void *handle = NULL;
    if ( 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 = 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);
        g_pMSHookFunction(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;
}

cocos的HOOK:

//orig function copy
int (*luaL_loadbuffer_orig)(void *L, const char *buff, int size, const char *name) = NULL;

//local function
int luaL_loadbuffer_mod(void *L, const char *buff, int size, const char *name) {
    LOGD("[dumplua] luaL_loadbuffer name: %s lua: %s", name, buff);
    return luaL_loadbuffer_orig(L, buff, size, name);
}


//hook decryptUF
int (*decryptUF_orig)(void *pInBuff, int len, int *n, int *poutlen, char *name) = NULL;
int decryptUF_mod(void *pInBuff, int len, int *n, int *poutlen, char *name) {
    LOGD("[dumplua] decryptUF_mod 1");
    int ret = decryptUF_orig(pInBuff, len, n, poutlen, name);
    LOGD("[dumplua] decryptUF_mod 2, in buff: %s", (char*)pInBuff);
    LOGD("[dumplua] decryptUF_mod in len: %d n: %d", len, *n);
    LOGD("[dumplua] decryptUF_mod name: %s, outlen: %d ", name, *poutlen);
    LOGD("[dumplua] decryptUF_mod ret buff: %s", (char*)pInBuff);
    saveFile(pInBuff, *poutlen, getNextFilePath(".png").c_str());
    return ret;
}



bool hookCocos(void *handlelibGameSo, Tdlopen olddlopen) {
    LOGD("[dumplua] hook begin");
    void *handle = NULL;
    if ( handlelibGameSo==NULL ) {
        if ( olddlopen==NULL ) {
            handle = dlopen("libgame.so", RTLD_NOW);
        }else{
            handle = olddlopen("libgame.so", RTLD_NOW);
        }
        if (handle == NULL) {
            LOGE("[dumplua]dlopen err: %s.", dlerror());
            return false;
        }
    }else{
        handle = handlelibGameSo;
        LOGD("[dumplua] libgame.so handle: %p", handle);
    }

    void *pluaL_loadbuffer = dlsym(handle, "luaL_loadbuffer");
    if (pluaL_loadbuffer == NULL){
        LOGE("[dumplua] lua_loadbuffer not found!");
        LOGE("[dumplua] dlsym err: %s.", dlerror());
    }else{
        LOGD("[dumplua] luaL_loadbuffer found!");
        g_pMSHookFunction(pluaL_loadbuffer, (void *)&luaL_loadbuffer_mod, (void **)&luaL_loadbuffer_orig);
    }


    //hook decryptUF
    void *decryptUF = dlsym(handle, "_ZN7cocos2d5extra8CCCrypto9decryptUFEPhiPiS3_");
    if ( decryptUF==NULL ) {
        LOGE("[dumplua] _ZN7cocos2d5extra8CCCrypto9decryptUFEPhiPiS3_ (decryptUF) not found!");
        LOGE("[dumplua] dlsym err: %s.", dlerror());
    }else{
        LOGD("[dumplua] _ZN7cocos2d5extra8CCCrypto9decryptUFEPhiPiS3_ (decryptUF) found!");
        g_pMSHookFunction(decryptUF, (void *)&decryptUF_mod, (void **)&decryptUF_orig);
    }


    LOGD("[dumplua] hook end");
    return true;
}

界面效果

  • 包名:從安裝的APP列表裏選擇,來確定目標APP。
  • 待注入的SO:配置某個SO加載時要load的SO路徑(需要按格式導出on_dlopen函數)。
  • 注入插件:此處不講解。
  • 點擊添加,則生成一條HOOK記錄,追加到底部的列表裏,且包名爲紅色,意義爲HOOK生效。
  • 點擊“run”按鈕啓動目標APP(啓動前會殺死已經運行的該APP,且保存一個配置文件供loader讀取),APP啓動後XPOSED層就會攔截到,也就是進入handleLoadPackage函數。

image

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章