首页
社区
课程
招聘
[原创]Il2Cpp恢复符号过程分析
2022-5-30 14:42 17885

[原创]Il2Cpp恢复符号过程分析

2022-5-30 14:42
17885

0x1 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偏移。

0x2 解析global-metadata.dat

  • 首先得弄明白该文件保存了哪些内容,可以找到libil2cpp的源码。直接搜索global-metadata.dat即可找到相关代码。该函数位于vm/GlobalMetadata.cpp下。
  • 可以看到代码直接将文件内容读入后,转化成了一个结构体,Il2CppGlobalMetadataHeader。该结构体定义在了GlobalMetadataFileInternals.h里,其中包含了一些重要的信息。

1)获取所有Image

  • 首先得知道其中包含的各个Image信息。直接定位到结构体中的这两个域,对应着Il2CppImageDefinition数组的偏移,该结构保存了Image的信息。imagesSize记录了Il2CppImageDefinition数组的大小。
  • 可以通过如下方法获得Il2CppImageDefinition数组,并且进行遍历。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    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的地址。
    1
    2
    3
    4
    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写出如下代码。
    1
    2
    3
    4
    static const Il2CppTypeDefinition* GetTypeDefinitionFromIndex(TypeDefinitionIndex index)
    {
      return (const Il2CppTypeDefinition*)(ptr+((Il2CppGlobalMetadataHeader*)ptr)->typeDefinitionsOffset)+index;
    }
  • 然后便可以获得Image下的所有Il2CppTypeDefinition了。也就是获取所有类的元数据。
    1
    2
    3
    4
    5
    6
    7
    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));
      }
  • 这样就能解析出了各种Image下的类。

3)获取类下的方法名和Field

  • 此时需要我们进一步的分析类下面的方法,此时查看Il2CppTypeDefinition结构体可以发现其下有两个域值得注意。

  • 同样的在Il2CppGlobalMetadataHeader由两个域正好对应的上。

  • 类比上文的方法,可以写出如下代码,来获取类对应的方法和field的信息。
    1
    2
    3
    4
    5
    6
    7
    8
    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;
    }
  • 同样的,获得的结构体中都保存了名字。

  • 效果如下:

0x3 恢复方法符号

  • 由于此时仅仅获得了方法的名称,还无法和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取出函数指针,即可将方法和函数指针对应上。
  • 具体代码如下:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    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;
    }

0x4 获得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代码如下
    1
    2
    3
    4
    5
    6
    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查表即可。
    1
    2
    3
    4
    uint32_t GetFieldOffset(TypeDefinitionIndex typeIndex,uint32_t index)
    {
      return MetadataRegistration->fieldOffsets[typeIndex][index];   
    }

0x5完整代码

  • 由于使用了大量结构体,不方便直接贴上来,请下载附件,同时还有对应的测试文件和输入。
  • 代码不够完善,无法用于生产,仅用于学习。

[培训]《安卓高级研修班(网课)》月薪三万计划,掌握调试、分析还原ollvm、vmp的方法,定制art虚拟机自动化脱壳的方法

上传的附件:
收藏
点赞10
打赏
分享
打赏 + 80.00雪花
打赏次数 1 雪花 + 80.00
 
赞赏  Editor   +80.00 2022/06/28 恭喜您获得“雪花”奖励,安全圈有你而精彩!
最新回复 (8)
雪    币: 2663
活跃值: (5210)
能力值: ( LV10,RANK:177 )
在线值:
发帖
回帖
粉丝
YenKoc 2 2022-5-30 15:12
2
0
昂!
雪    币: 202
活跃值: (136)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
菜菜小J 2022-6-2 00:34
3
0
雪    币: 8270
活跃值: (4786)
能力值: ( LV4,RANK:45 )
在线值:
发帖
回帖
粉丝
v0id_ 2022-6-4 11:48
4
0
昂!
雪    币: 187
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
Ruinv6 2022-11-22 18:34
5
0
NO,附件过期了
雪    币: 2769
活跃值: (1860)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
wx_荣仔 2023-8-4 11:14
6
0
谢谢了。学习下。
雪    币: 19244
活跃值: (28872)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
秋狝 2023-8-4 11:17
7
1
感谢分享
雪    币: 202
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
veryluckko 2023-8-4 11:43
8
0
感谢分享,现在常见的il2cpp游戏一般都会想方设法加密global-metadata.dat 比如最近免费的某大型网游
雪    币: 34
活跃值: (220)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
月光爱人 2024-2-28 02:21
9
0
过期了啊,楼主请从新激活下~ 谢谢,最近分析一个il2cpp, 用 il2cppDump 工具,能DUMP成功,但是就是DLL文件中,唯独缺少:“GameAssemly.dll”,思来想去 始终Dump 不出来。 用CE 查看mono 能看到DLL 在列表中。
游客
登录 | 注册 方可回帖
返回