本文为看雪论坛优秀文章
看雪论坛作者ID:R1mao
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偏移。首先得弄明白该文件保存了哪些内容,可以找到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..\n");
return 0;
}
int image_count=header->imagesSize/sizeof(Il2CppImageDefinition);
for(int i=0;i<image_count;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。
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\n",GetStringFromIndex(image->nameIndex));
for(int j=0;j<image->typeCount;j++)
{
const Il2CppTypeDefinition *type=GetTypeDefinitionFromIndex(image->typeStart+j);
printf("class: %s:%s\n",GetStringFromIndex(type->namespaceIndex),GetStringFromIndex(type->nameIndex));
}
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;i<CodeRegistration->codeGenModulesCount;i++)
{
const Il2CppCodeGenModule *module=CodeRegistration->codeGenModules[i];
if(!strcmp(module->moduleName,GetStringFromIndex(image->nameIndex)))
{
return module->methodPointers[GetTokenRowId(token)-1];
}
}
printf("invalid!\n");
return 0;
}
同样的从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可以直接获得。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];
}
由于使用了大量结构体,不方便直接贴上来,请下载附件,同时还有对应的测试文件和输入。
看雪ID:R1mao
https://bbs.pediy.com/user-home-948449.htm
*本文由看雪论坛 R1mao 原创,转载请注明来自看雪社区