內存解析Il2cpp函數地址

目的

  • 使用函數名拿到U3D非導出函數指針

起因

  1. 相對u3d游戲進行hook基本上離不開 Il2CppDumper 的使用,有時候想要找個函數什么的就很麻煩,第一步就得使用上面這工具來找到函數地址
  2. 第二就是每次都要用 Il2CppDumper 來找函數地址不能方便的實現 frida 腳本的自動化,既然都用到了 frida 當然是越方便越爽

經過

  • 之前搞了一個通過 Il2CppDumper 拿到的 script.js 加上一點python 篩選,來實現對函數的批量斷點,方便實現對點擊事件或者是其他關鍵函數(Like:Show,Click,Button,Rewarded)多函數的批量斷點,方便我們分析代碼調用
  • 這還是不夠優雅,要讓他自動去拿到這些地址,我們就得讀一點點,在源碼層面去hook拿到一些我們需要的關鍵點

分析

網上也有很多類似的相關文章
比如 [unity]Real-time Dump一套強勁的 Il2cpp Hook 框架IL2CPP的原理
都可以去參照 去細品

這里我講講我用到的東西
分析的入口點從加載 global-metadata.dat 入手
這是一個非常標志性的入口,不管是在源碼還是在IDA中都很容易的定位


找到入口位置

IDA中從字符串窗口搜索該關鍵詞也可以輕松的定位到一下代碼

源碼中的入口

IDA中F5也是隨意的改改名字,改改結構體就差不多一個樣了


IDA中的入口

記下未初始化的段的兩個地址


兩個關鍵地址

在源碼中讓后看,可以看下面就開始對此處分配出來的內存填數據了,后面我們用的時候也是無需但是未初始化的問題

這里的s_ImagesTable是一個指向一個 Il2CppImage 列表的開頭,
sizeof(Il2CppImage) = 52,可以去頭文件查看

typedef struct Il2CppImage
{
    const char* name;
    const char *nameNoExt;
    Il2CppAssembly* assembly;

    TypeDefinitionIndex typeStart;
    uint32_t typeCount;

    TypeDefinitionIndex exportedTypeStart;
    uint32_t exportedTypeCount;

    CustomAttributeIndex customAttributeStart;
    uint32_t customAttributeCount;

    MethodIndex entryPointIndex;

#ifdef __cplusplus
    mutable
#endif
    Il2CppNameToTypeDefinitionIndexHashTable * nameToClassHashTable;

    const Il2CppCodeGenModule* codeGenModule;

    uint32_t token;
    uint8_t dynamic;
} Il2CppImage;

這一步我們關注的只有 const char *nameNoExt 以及當前位置的指針位置由此寫出

var s_ImagesTable = soAddr.add(0x2DF9134).readPointer()
var s_ImagesCount = soAddr.add(0x2DF9130).readPointer().toInt32()
function list_Images(){
    console.warn("--------------------------------------------------------")
    var tmp = s_ImagesCount + 1
    console.error("  List ",tmp," Images")
    console.warn("----------------------------")
    for(var t=0;t<s_ImagesCount;t++){
        console.log("\n\x1b[36m [*]",s_ImagesTable.add(sizeof_Il2CppImage*t),"\t",
            s_ImagesTable.add(sizeof_Il2CppImage*t).add(p_size).readPointer().readCString()+"\x1b[0m")
        // console.log("hash_value:\t")
        // console.log("\t"+hexdump(s_ImagesTable.add(52*t).add(p_size*2).readPointer(),{length:16,header:false,ansi:false}))
    }
    console.warn("---------------------------------------------------------")
}

運行一下就可以拿到一下內容


列出namespaze

這些玩意其實就是我們用Il2CppDumper拿到的哪些dll


然后下面就可以進入正題

使用命名空間,類名,方法名,參數個數拿到我們需要的函數 內存地址以及IDA中靜態分析的地址

下面介紹兩個主角函數(這里引入的兩個函數都是libil2cpp.so的默認導出函數)
位置:D:\Program Files\Unity\Editor\Data\il2cpp\libil2cpp\il2cpp-api.cpp

  • il2cpp_class_get_method_from_name
  • il2cpp_class_from_name

這里為了能截圖在一張中移動了函數位置 ↓
(ps:這里做的事情其實一開始也很懵,完全就是和做數學一樣,把未知量用已知函數去代替,帶入到我們能解決即可 [......].png)


函數原型

由此可以寫出以下代碼進行主動調用

function findAddrByName(namespaze){
    for(var t=0;t<s_ImagesCount;t++){
        var t_addr = s_ImagesTable.add(52*t)
        var t_name = s_ImagesTable.add(52*t).add(p_size).readPointer().readCString()
        if (t_name == namespaze) return ptr(t_addr)
    }
    return ptr(0x0)
}

function showAddr(namespaze,className,functionName,argsCount){
    Interceptor.detachAll()
    Il2CppImage = findAddrByName(namespaze)
    if (Il2CppImage == 0) {
        console.warn("Il2CppImage addr not found!")
        return
    }
    console.warn("---------------------------------------------------------")
    console.error(namespaze+"."+className+"."+functionName)
    console.warn("----------------------------")
    console.log("Il2CppImage\t---->\t "+Il2CppImage)

    //Il2CppClass* il2cpp_class_from_name(const Il2CppImage* image, const char* namespaze, const char *name)
    var il2cpp_class_from_name = new NativeFunction(
        Module.findExportByName(soName,"il2cpp_class_from_name"),
        'pointer',['pointer','pointer','pointer'])

    //const MethodInfo* il2cpp_class_get_method_from_name(Il2CppClass *klass, const char* name, int argsCount)
    var il2cpp_class_get_method_from_name = new NativeFunction(
        Module.findExportByName(soName,"il2cpp_class_get_method_from_name"),
        'pointer',['pointer','pointer','int'])
    
    var namespaze_t = Memory.allocUtf8String(namespaze)
    var className_t = Memory.allocUtf8String(className)
    var functionName_t = Memory.allocUtf8String(functionName)

    var Il2CppClass = il2cpp_class_from_name(Il2CppImage,namespaze_t,className_t)
    console.log("Il2CppClass\t---->\t",Il2CppClass)
    var MethodInfo = il2cpp_class_get_method_from_name(Il2CppClass,functionName_t,argsCount)
    console.log("MethodInfo\t---->\t",MethodInfo)
    console.log("\x1b[36mmethodPointer\t---->\t "+MethodInfo.readPointer() +"\t ===> \t"+MethodInfo.readPointer().sub(soAddr)+"\x1b[0m")
    console.warn("---------------------------------------------------------")
}

結果展示

實現通過名稱拿到函數地址
靜態dumper出來的結果

看到此處的話目的算是勉強達到了,但是還有待優化把,比如一開始的

var s_ImagesTable = soAddr.add(0x2DF9134).readPointer()
var s_ImagesCount = soAddr.add(0x2DF9130).readPointer().toInt32()

這兩個位置需要我們手動IDA去找


新的問題

從上述結果中確實看到了我們可以成功的調用函數拿到函數的返回值,但是僅限于我們已知Il2CppImage*的時候有效,也就是僅僅在我們list_Images中列出的地址對應的子函數可以這么操作(使用il2cpp_class_from_name()獲取對應的Il2CppClass),所以我們為了通用得考慮新的思路

兩個關鍵結構體:Il2CppImage 和 Il2CppAssembly


兩個關鍵結構體

從list_images中取第一個地址 0xcf80ec00


舉例mscorlib.dll
查看一下image的內存情況

前兩個地址對應的是name和nameNoExt
可以看到第三個指針指向的地址的第一個區域由指回來了,就是咋們的Il2CppImage*


結合上圖

上面這張圖不是重點,重點在上上張圖第二三個圈起來的部分,也就是
TypeDefinitionIndex typeStart;
uint32_t typeCount;
這兩位,分別記錄了偏移位置和方法個數,偏移位置的開始在(還是去參考IDA拿到地址,同樣也是bss段的一個指針)


s_MethodInfoDefinitionTable
typedef struct MethodInfo
{
    Il2CppMethodPointer methodPointer;
    InvokerMethod invoker_method;
    const char* name;
    Il2CppClass *klass;
    const Il2CppType *return_type;
    const ParameterInfo* parameters;
    union
    {
        const Il2CppRGCTXData* rgctx_data; /* is_inflated is true and is_generic is false, i.e. a generic instance method */
        const Il2CppMethodDefinition* methodDefinition;
    };
    union
    {
        const Il2CppGenericMethod* genericMethod; /* is_inflated is true */
        const Il2CppGenericContainer* genericContainer; /* is_inflated is false and is_generic is true */
    };
    uint32_t token;
    uint16_t flags;
    uint16_t iflags;
    uint16_t slot;
    uint8_t parameters_count;
    uint8_t is_generic : 1; /* true if method is a generic method definition */
    uint8_t is_inflated : 1; /* true if declaring_type is a generic instance or if method is a generic instance*/
    uint8_t wrapper_type : 1; /* always zero (MONO_WRAPPER_NONE) needed for the debugger */
    uint8_t is_marshaled_from_native : 1; /* a fake MethodInfo wrapping a native function pointer */
} MethodInfo;

根據上述SeeHexA(0xcf80ec00)中可知偏移0x0,方法個數0x5c2(即1474個方法),接下來就可以寫出以下demo

function a1(){
    var pointerSize = Process.pointerSize
    var a = soAddr.add(0xCC6474).readPointer()
        for (var t = 0;t<1475;t++){
            var tt = a.add(t*pointerSize).readPointer()
            console.warn("---------")
            console.log("srcPointer \t--->\t"+a.add(t*pointerSize))
            console.log("Il2CppImage \t--->\t"+tt.add(pointerSize*0))
            console.log("name \t\t--->\t"+tt.add(pointerSize*2).readPointer().readCString())
            console.log("namespaze \t--->\t"+tt.add(pointerSize*3).readPointer().readCString())
        }
}

效果如下


運行結果

上述故意方法數多加一,看到最后空命名空間,<Module>基本就穩了,就是這意思沒猜錯


拓展

能拓展的東西就挺多的,這就涉及最初起點了

  1. Il2CppDumperTool 脫離 Il2CppDumper 以及python腳本更易用

  2. 搞一些通用的API Hook,比如

  • UnityEngine.GameObject.SetActive(Boolean)
  • UnityEngine.Object.GetName(UnityEngine:Object):String
  • UnityEngine.Application.get_identifier():String
  • UnityEngine.PlayerPrefs.GetInt(String,Int32):Int32
    ......

https://github.com/axhlzy/Il2CppHookScripts/tree/master/Il2cppHook

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容