Il2Cpp恢復符號過程分析
Il2Cpp介紹
unity作為兩大游戲引擎之一,其安全性也值得被關注。在早期unity的腳本都是采用C#編寫的,直接編譯成C#模塊,所以直接使用C#反編譯器即可非常完整的獲得游戲的源碼,此時的Unity腳本后處理引擎為Mono。
而由于C#的效率問題和安全性問題,Unity推出了新的腳本后處理引擎Il2Cpp,該引擎分為兩個部分,一個是AOT靜態編譯引擎,一個是libil2cpp運行時庫。
前者通過將C# IL編譯成C++代碼,從而交由不同平臺編譯器編譯,后者則實現了諸如垃圾回收、線程/文件獲取、內部調用直接修改托管數據結構的原生代碼的服務與抽象。

Il2Cpp編譯的游戲,往往有兩個重要的文件,一個是GameAssembly.dll,該文件是由C++代碼編譯而成的程序,游戲重要的邏輯都在該文件內,其中包含了il2cpp的運行時庫。另一個文件是global-metadata.dat,該文件保存了一些重要的字符串信息和一些元數據,用于il2cpp的動態特性,例如反射等。
這里著重分析如果通過解析GameAssembly.dll和global-metadata.dat來恢復其中包含的類名,方法名,field偏移。
解析global-metadata.dat
首先得弄明白該文件保存了哪些內容,可以找到libil2cpp的源碼。直接搜索global-metadata.dat即可找到相關代碼。該函數位于vm/GlobalMetadata.cpp下。

可以看到代碼直接將文件內容讀入后,轉化成了一個結構體,Il2CppGlobalMetadataHeader。該結構體定義在了GlobalMetadataFileInternals.h里,其中包含了一些重要的信息。

1)獲取所有Image
首先得知道其中包含的各個Image信息。直接定位到結構體中的這兩個域,對應著Il2CppImageDefinition數組的偏移,該結構保存了Image的信息。imagesSize記錄了Il2CppImageDefinition數組的大小。

可以通過如下方法獲得Il2CppImageDefinition數組,并且進行遍歷。
Il2CppGlobalMetadataHeader *header=(Il2CppGlobalMetadataHeader*)ptr; if(header->sanity!=0xFAB11BAF || header->stringLiteralOffset!=sizeof(Il2CppGlobalMetadataHeader)) { printf("invalid file.."); return 0; } int image_count=header->imagesSize/sizeof(Il2CppImageDefinition); for(int i=0;i { const Il2CppImageDefinition *image=&image_arr[i]; }
接著來查看Il2CppImageDefinition結構體,可以發現里面包含了Image的名字相關信息StringIndex nameIndex。

StringIndex是一個整型,是一個索引,global-metadata.dat中存在一個字符串表,所有metadata相關的字符串都放在了一起,通過這個索引進行引用,這個字符串表通過Il2CppGlobalMetadataHeader下的偏移stringOffset計算得到。
可以通過這樣的函數獲取StringIndex對應的字符串,ptr是Il2CppGlobalMetadataHeader的地址。
static const char* GetStringFromIndex(StringIndex index){ return (const char*)(((Il2CppGlobalMetadataHeader*)ptr)->stringOffset+ptr+index);}
2)獲取Image下的類
如何獲取該image所有的類呢,這就需要獲取對應的Il2CppTypeDefinition,在Il2CppGlobalMetadataHeader結構體下存在typeDefinitionsOffset,而由于Il2CppImageDefinition存在一個TypeDefinitionIndex typeStart的域,所以可以類比StringIndex。

類比StringIndex寫出如下代碼。
static const Il2CppTypeDefinition* GetTypeDefinitionFromIndex(TypeDefinitionIndex index){ return (const Il2CppTypeDefinition*)(ptr+((Il2CppGlobalMetadataHeader*)ptr)->typeDefinitionsOffset)+index;}
然后便可以獲得Image下的所有Il2CppTypeDefinition了。也就是獲取所有類的元數據。
const Il2CppImageDefinition *image=&image_arr[i]; printf("image: %s",GetStringFromIndex(image->nameIndex)); for(int j=0;jtypeCount;j++) { const Il2CppTypeDefinition *type=GetTypeDefinitionFromIndex(image->typeStart+j); printf("class: %s:%s",GetStringFromIndex(type->namespaceIndex),GetStringFromIndex(type->nameIndex)); }
這樣就能解析出了各種Image下的類。

3)獲取類下的方法名和Field
此時需要我們進一步的分析類下面的方法,此時查看Il2CppTypeDefinition結構體可以發現其下有兩個域值得注意。


同樣的在Il2CppGlobalMetadataHeader由兩個域正好對應的上。


類比上文的方法,可以寫出如下代碼,來獲取類對應的方法和field的信息。
static const Il2CppMethodDefinition* GetMethodDefinitionFromIndex(MethodIndex index){ return (const Il2CppMethodDefinition*)(((Il2CppGlobalMetadataHeader*)ptr)->methodsOffset+ptr)+index;}static const Il2CppFieldDefinition* GetFieldDefinitionFromIndex(FieldIndex index){ return (const Il2CppFieldDefinition*)(ptr+((Il2CppGlobalMetadataHeader*)ptr)->fieldsOffset)+index;}
同樣的,獲得的結構體中都保存了名字。


效果如下:

恢復方法符號
由于此時僅僅獲得了方法的名稱,還無法和Gameassembly.dll中的函數對應起來,得繼續查看il2cpp的源碼。
從il2cpp的api入手,其中有個函數名稱為il2cpp_runtime_invoke。

點進其中所調用的函數,可以發現有一個InvokeWithThrow,不難猜到這應該就是調用method的函數。

繼續分析InvokeWithThrow,可以發現里面調用了method->invoker_method,并且其第一個參數method->methodPointer就是方法的指針。

繼續搜索對method->methodPointer的修改,在Class.cpp文件中的Class::SetupMethodsLocked(Il2CppClass *klass, const il2cpp::os::FastAutoLock& lock)方法下成功找到了賦值語句。該函數的作用即通過metadata構造類的所有MethodInfo,而MethodInfo對象則包含了方法函數指針。

可以看到MetadataCache::GetMethodPointer通過image對象找到了codeGenModule,再定位到了其下的methodPointers數組,再根據token取出對應的函數指針。

剛好一個方法的token在Il2CppMethodDefinition中存在,就差怎么定位codeGenModule->methodPointers了。

在源代碼中搜索codeGenModule的類型Il2CppCodeGenModule。

得到了一個結構體Il2CppCodeRegistration。

然后在libil2cpp中就沒法找到相關的定義了,可能是由il2cpp aot編譯器生成的,然后注入到il2cpp runtime里去,所以去看看GameAssembly.dll看看能否定位到Il2CppCodeRegistration這個結構體。

由于Il2CppCodeRegistration中的codeGenModules的Il2CppCodeGenModule最大的特征就是moduleName,試試字符串搜索。

合理猜測這個字符串就是Il2CppCodeGenModule的moduleName,交叉引用,猜測這個就是Il2CppCodeGenModule。

繼續交叉引用,不出所料應該就得到了一個數組,這個數組應該就是Il2CppCodeRegistration->codeGenModules指向的數組。

再次交叉引用找到Il2CppCodeRegistration變量。

所以只需要定位到Il2CppCodeRegistration codeRegistration這個全局變量即可,然后遍歷codeGenModules找到方法對應的Image,然后通過方法的token(Il2CppMethodDefinition下有)去codeGenModules->methodPointers取出函數指針,即可將方法和函數指針對應上。
具體代碼如下:
uint32_t GetMethodPointer(const Il2CppImageDefinition *image,uint32_t token){ for(int i=0;icodeGenModulesCount;i++) { const Il2CppCodeGenModule *module=CodeRegistration->codeGenModules[i]; if(!strcmp(module->moduleName,GetStringFromIndex(image->nameIndex))) { return module->methodPointers[GetTokenRowId(token)-1]; } } printf("invalid!"); return 0;}
獲得field的偏移地址
同樣的從il2cpp的api出發,可以發現該函數直接調用了Class::GetFieldFromName,直接去查看該函數。

找到Class::GetFieldFromName,它通過GetFields獲得所有的FieldInfo,然后返回對應的FieldInfo。

看看GetFields函數,其中調用了SetupFieldsLocked。

繼續找到SetupFieldsLocked,該函數遞歸初始化了類和父類的所有FieldInfo,使用SetupFieldsFromDefinitionLocked進行初始化。

找到SetupFieldsFromDefinitionLocked,在這個函數中就能找到具體的初始化過程了,可以看到其offset是通過MetadataCache::GetFieldOffsetFromIndexLocked計算而出的。

繼續找到MetadataCache::GetFieldOffsetFromIndexLocked,該函數進一步調用了 GlobalMetadata::GetFieldOffset。

最后一步,GlobalMetadata::GetFieldOffset。該函數通過typeIndex和fieldIndexInType進行查表。typeIndex可以通過查當前type的Il2CppTypeDefinition在global-metadata.dat的序號獲得,而fieldIndexInType在遍歷type的field可以直接獲得。

獲取typeIndex代碼如下:
static TypeDefinitionIndex GetIndexForTypeDefinitionInternal(const Il2CppTypeDefinition* typeDefinition){ const Il2CppTypeDefinition* typeDefinitions=(const Il2CppTypeDefinition*)(ptr+((Il2CppGlobalMetadataHeader*)ptr)->typeDefinitionsOffset); ptrdiff_t index=typeDefinition-typeDefinitions; return (TypeDefinitionIndex)index;}
索引信息都能獲取了,現在需要找到的是s_Il2CppMetadataRegistration->fieldOffsets,也就是要定位到s_Il2CppMetadataRegistration。該變量的類型為Il2CppMetadataRegistration。

該變量存在于GameAssemly.dll,也是由il2cpp aot編譯生成注冊到il2cpp runtime的,而剛好之前發現codeRegistration注冊時一并注冊了metadataRegistration。

于是我們根據上文找到的codeRegistration進行交叉引用,成功定位到metadataRegistration。


所以只需要根據這個變量直接用typeIndex和fieldIndexInType查表即可。
uint32_t GetFieldOffset(TypeDefinitionIndex typeIndex,uint32_t index){ return MetadataRegistration->fieldOffsets[typeIndex][index]; }