unity3d il2cpp原理解析及逆向分析

By xia0

#unity3d il2cpp原理解析及逆向分析

本文可能写得有点乱,主要完整记录了我从一无所知去逆向原理的流程,所以保留了很多分析过程中的信息,第一部分逆向分析如果对逆向过程不感兴趣简单看下就行

#背景

介绍il2cpp以及mono

https://gameinstitute.qq.com/community/detail/126815

一篇官方文档介绍il2cpp的内部实现

https://blogs.unity3d.com/2015/05/06/an-introduction-to-ilcpp-internals/

#metadata加载流程-逆向分析

将介绍我是怎么从逆向的角度去分析metadata的加载流程

这个文件可以说在unity3d中有重要的作用,也是il2cpp实现并不可少的。但是在源码中并没有搜到对这个文件的引用,猜测unity3d对该部分的实现并没有开源(il2cpp的源代码在unity应用目录里面),于是我编译完以后的游戏的可执行文件中在ida中去搜索global-metadata.dat发现只有一处引用即il2cpp::vm::MetadataCache::Initialize函数。从名字上看得出来应该就是在初始化il2cpp虚拟机。于是对该函数下断点,然后得到如下调用链

(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 4.1
  * frame #0: 0x00000001007998ec ProductName`il2cpp::vm::MetadataCache::Initialize() at MetadataCache.cpp:163 [opt]
    frame #1: 0x0000000100793364 ProductName`il2cpp::vm::Runtime::Init(filename="IL2CPP Root Domain", runtime_version=<unavailable>) at Runtime.cpp:145:9 [opt]
    frame #2: 0x0000000100498164 ProductName`::InitializeIl2CppFromMain() at MonoManager_Il2Cpp.cpp:255:5 [opt]
    frame #3: 0x00000001005b1efc ProductName`::UnityInitApplicationNoGraphics() at LibEntryPoint.mm:183:5 [opt]
    frame #4: 0x00000001000ede90 ProductName`::-[UnityAppController application:didFinishLaunchingWithOptions:](self=0x0000000157570560, _cmd=<unavailable>, application=<unavailable>, launchOptions=<unavailable>) at UnityAppController.mm:259:5 [opt]
    frame #5: 0x00000001875628a8 UIKit`-[UIApplication _handleDelegateCallbacksWithOptions:isSuspended:restoreState:] + 400
    frame #6: 0x0000000187792094 UIKit`-[UIApplication _callInitializationDelegatesForMainScene:transitionContext:] + 2904
    frame #7: 0x0000000187796500 UIKit`-[UIApplication _runWithMainScene:transitionContext:completion:] + 1684
    frame #8: 0x0000000187793674 UIKit`-[UIApplication workspaceDidEndTransaction:] + 168
    frame #9: 0x0000000183d437ac FrontBoardServices`__FBSSERIALQUEUE_IS_CALLING_OUT_TO_A_BLOCK__ + 36
    frame #10: 0x0000000183d43618 FrontBoardServices`-[FBSSerialQueue _performNext] + 168
    frame #11: 0x0000000183d439c8 FrontBoardServices`-[FBSSerialQueue _performNextFromRunLoopSource] + 56
    frame #12: 0x0000000182359124  CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 24
    frame #13: 0x0000000182358bb8 CoreFoundation`__CFRunLoopDoSources0 + 540
    frame #14: 0x00000001823568b8 CoreFoundation`__CFRunLoopRun + 724
    frame #15: 0x0000000182280d10 CoreFoundation`CFRunLoopRunSpecific + 384
    frame #16: 0x000000018755b834 UIKit`-[UIApplication _run] + 460
    frame #17: 0x0000000187555f70 UIKit`UIApplicationMain + 204
    frame #18: 0x00000001000d8d30 ProductName`main(argc=1, argv=0x000000016fd2bb98) at main.mm:41:9 [opt]
    frame #19: 0x0000000181e1e8b8 libdyld.dylib`start + 4

简单整理下在-[UnityAppController application:didFinishLaunchingWithOptions:]之后的调用过程

UnityInitApplicationNoGraphics()
--InitializeIl2CppFromMain()
----il2cpp_init("IL2CPP Root Domain");
------il2cpp::vm::Runtime::Init()
--------il2cpp::vm::MetadataCache::Initialize()

这里的UnityInitApplicationNoGraphicsInitializeIl2CppFromMain都在libiPhone-lib.a这个静态库之中,搜索了一下发下这个库貌似没开源,我们从ida的反汇编代码中去分析下这两个函数

UnityInitApplicationNoGraphics的伪代码如下

__int64 __fastcall UnityInitApplicationNoGraphics(__int64 a1)
{
  AssetCatalogFileSystemHandler *v1; // x19
  __int64 v2; // x0
  ScreenManager *v3; // x0
  int v4; // w19
  __int64 v5; // x0
  ScreenManager *v6; // x0
  char v8; // [xsp+8h] [xbp-118h]
  char v9; // [xsp+30h] [xbp-F0h]
  char v10; // [xsp+58h] [xbp-C8h]
  char v11; // [xsp+80h] [xbp-A0h]
  char v12; // [xsp+A8h] [xbp-78h]
  char v13; // [xsp+D0h] [xbp-50h]

  core::basic_string<char,core::StringStorageDefault<char>>::basic_string(&v13, a1);
  core::basic_string<char,core::StringStorageDefault<char>>::basic_string(&v11, "Data");
  AppendPathName(&v13, &v11);
  core::StringStorageDefault<char>::~StringStorageDefault(&v11);
  core::basic_string<char,core::StringStorageDefault<char>>::basic_string(&v9, "Managed");
  AppendPathName(&v12, &v9);
  core::StringStorageDefault<char>::~StringStorageDefault(&v9);
  core::basic_string<char,core::StringStorageDefault<char>>::basic_string(&v8, &v13);
  if ( !(InitializeIl2CppFromMain((signed __int64 *)&v10, (signed __int64 *)&v10, 1LL, (__int64)&s_EmptyArgv) & 1) )
  {
    printf_console("   %s\n");
    exit(1);
  }
  v1 = (AssetCatalogFileSystemHandler *)operator new(48LL, 7LL, 8LL, "", 185LL);
  AssetCatalogFileSystemHandler::AssetCatalogFileSystemHandler(v1, "res:");
  gAssetCatalogFS = (__int64)v1;
  v2 = GetFileSystem();
  FileSystem::MountHandler(v2, gAssetCatalogFS);
  if ( !(PlayerInitEngineNoGraphics(&v12, &v8) & 1) )
  {
    printf_console("   %s\n");
    exit(1);
  }
  v3 = (ScreenManager *)GetScreenManager();
  ScreenManager::EnableOrientationsFromPlayerSettings(v3);
  v4 = *(_DWORD *)(GetPlayerSettings() + 476LL);
  if ( v4 == 4 )
  {
    v5 = GetScreenManager();
    (*(void (**)(void))(*(_QWORD *)v5 + 224LL))();
  }
  else
  {
    v6 = (ScreenManager *)GetScreenManager();
    ScreenManager::RequestConcreteOrientationFromPlayerSettings(v6, v4);
  }
  core::StringStorageDefault<char>::~StringStorageDefault(&v8);
  core::StringStorageDefault<char>::~StringStorageDefault(&v10);
  core::StringStorageDefault<char>::~StringStorageDefault(&v12);
  return core::StringStorageDefault<char>::~StringStorageDefault(&v13);
}

这里主要做了两件事,一是去InitializeIl2CppFromMain初始化il2cpp,解密metadata就是在这里完成的;二是去PlayerInitEngineNoGraphics初始化游戏相关。现在分析加载matedata的具体细节

static Il2CppMetadataCache s_MetadataCache;
static Il2CppClass** s_TypeInfoTable = NULL;
static Il2CppClass** s_TypeInfoDefinitionTable = NULL;
static const MethodInfo** s_MethodInfoDefinitionTable = NULL;
static Il2CppString** s_StringLiteralTable = NULL;
static const Il2CppGenericMethod** s_GenericMethodTable = NULL;
static int32_t s_ImagesCount = 0;
static Il2CppImage* s_ImagesTable = NULL;
static int32_t s_AssembliesCount = 0;
static Il2CppAssembly* s_AssembliesTable = NULL;

void MetadataCache::Initialize(){
  //map global-metadata.dat to memory and change to Il2CppGlobalMetadataHeader*
    s_GlobalMetadata = vm::MetadataLoader::LoadMetadataFile("global-metadata.dat");
  s_GlobalMetadataHeader = (const Il2CppGlobalMetadataHeader*)s_GlobalMetadata;

  // check 
  IL2CPP_ASSERT(s_GlobalMetadataHeader->sanity == 0xFAB11BAF);
  IL2CPP_ASSERT(s_GlobalMetadataHeader->version == 24);

    s_TypeInfoTable = calloc(s_Il2CppMetadataRegistration->typesCount, sizeof(Il2CppClass*));
  s_TypeInfoDefinitionTable = calloc(s_GlobalMetadataHeader->typeDefinitionsCount / sizeof(Il2CppTypeDefinition), sizeof(Il2CppClass*));

}

这里有个问题在于s_Il2CppMetadataRegistration在哪里初始化的呢?跟一下

void MetadataCache::Register(const Il2CppCodeRegistration* const codeRegistration, const Il2CppMetadataRegistration* const metadataRegistration, const Il2CppCodeGenOptions* const codeGenOptions)
{
    s_Il2CppCodeRegistration = codeRegistration;
    s_Il2CppMetadataRegistration = metadataRegistration;
    s_Il2CppCodeGenOptions = codeGenOptions;

    for (int32_t j = 0; j < metadataRegistration->genericClassesCount; j++)
        if (metadataRegistration->genericClasses[j]->typeDefinitionIndex != kTypeIndexInvalid)
            metadata::GenericMetadata::RegisterGenericClass(metadataRegistration->genericClasses[j]);

    for (int32_t i = 0; i < metadataRegistration->genericInstsCount; i++)
        s_GenericInstSet.insert(metadataRegistration->genericInsts[i]);

    s_InteropData.assign_external(codeRegistration->interopData, codeRegistration->interopDataCount);
}

这里发现是在MetadataCache::Register这里初始化的,这又要取决于函数被谁调用的?调试了一下,其实就是在MetadataCache::Initialize()前一个函数里面初始化的。

void Runtime::Init(const char* filename, const char *runtime_version){
  ...
    il2cpp::utils::RegisterRuntimeInitializeAndCleanup::ExecuteInitializations();
  MetadataCache::Initialize();
  ...
}

继续跟

RegisterRuntimeInitializeAndCleanup::RegisterRuntimeInitializeAndCleanup(CallbackFunction Initialize, CallbackFunction Cleanup, int order)
{
    if (!_registrationCallbacks)
        _registrationCallbacks = new RegistrationCallbackSet();
    (*_registrationCallbacks).insert(Initialize);
}

void RegisterRuntimeInitializeAndCleanup::ExecuteInitializations()
{
    if (_registrationCallbacks == NULL)
        return;

    for (RegistrationCallbackSet::iterator iter = (*_registrationCallbacks).begin(); iter != (*_registrationCallbacks).end(); ++iter)
    {
        (*iter)();
    }
}

这里可以发现是调用的回调函数,所以需要继续跟在哪里注册的回调函数,以及回调函数是什么?这里继续调试,

RegisterRuntimeInitializeAndCleanup::RegisterRuntimeInitializeAndCleanup函数下断点

* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.2
  * frame #0: 0x00000001006c73b8 ProductName`il2cpp::utils::RegisterRuntimeInitializeAndCleanup::RegisterRuntimeInitializeAndCleanup(this=0x0000000100e13dd0, Initialize=(ProductName`s_Il2CppCodegenRegistration() [inlined] il2cpp_codegen_register(Il2CppCodeRegistration const*, Il2CppMetadataRegistration const*, Il2CppCodeGenOptions const*) at Il2CppCodeRegistration.cpp:59
ProductName`s_Il2CppCodegenRegistration() at Il2CppCodeRegistration.cpp:59), Cleanup=0x0000000000000000, order=0)(), void (*)(), int) at RegisterRuntimeInitializeAndCleanup.cpp:14:5 [opt]
    frame #1: 0x00000001200788d8 dyld`ImageLoaderMachO::doModInitFunctions(ImageLoader::LinkContext const&) + 260
    frame #2: 0x0000000120078a78 dyld`ImageLoaderMachO::doInitialization(ImageLoader::LinkContext const&) + 36
    frame #3: 0x00000001200740dc dyld`ImageLoader::recursiveInitialization(ImageLoader::LinkContext const&, unsigned int, char const*, ImageLoader::InitializerTimingList&, ImageLoader::UninitedUpwards&) + 368
    frame #4: 0x0000000120073280 dyld`ImageLoader::processInitializers(ImageLoader::LinkContext const&, unsigned int, ImageLoader::InitializerTimingList&, ImageLoader::UninitedUpwards&) + 144
    frame #5: 0x0000000120073334 dyld`ImageLoader::runInitializers(ImageLoader::LinkContext const&, ImageLoader::InitializerTimingList&) + 84
    frame #6: 0x0000000120066088 dyld`dyld::initializeMainExecutable() + 220
    frame #7: 0x000000012006a130 dyld`dyld::_main(macho_header const*, unsigned long, int, char const**, char const**, char const**, unsigned long*) + 3784
    frame #8: 0x0000000120065044 dyld`_dyld_start + 68

发现会注册s_Il2CppCodegenRegistration这个函数,然而这个函数并不在il2cpp之中,在Il2CppCodeRegistration.cpp这个文件里面,这个文件却在游戏的./Classes/Native/Il2CppCodeRegistration.cpp文件之中。当然还有个重要的全局变量g_MetadataRegistrationClasses/Native/Il2CppMetadataRegistration.cpp之中

extern const Il2CppMetadataRegistration g_MetadataRegistration;
static const Il2CppCodeGenOptions s_Il2CppCodeGenOptions = 
{
    true,
};
void s_Il2CppCodegenRegistration()
{
    il2cpp_codegen_register (&g_CodeRegistration, &g_MetadataRegistration, &s_Il2CppCodeGenOptions);
}
#if RUNTIME_IL2CPP
static il2cpp::utils::RegisterRuntimeInitializeAndCleanup s_Il2CppCodegenRegistrationVariable (&s_Il2CppCodegenRegistration, NULL);
#endif

这里的il2cpp_codegen_register函数其实是一个inline的函数

// TODO: This file should contain all the functions and type declarations needed for the generated code.
// Hopefully, we stop including everything in the generated code and know exactly what dependencies we have.
// Note that all parameter and return types should match the generated types not the runtime types.

inline void il2cpp_codegen_register(const Il2CppCodeRegistration* const codeRegistration, const Il2CppMetadataRegistration* const metadataRegistration, const Il2CppCodeGenOptions* const codeGenOptions)
{
    il2cpp::vm::MetadataCache::Register(codeRegistration, metadataRegistration, codeGenOptions);
}

也就是说会直接传给il2cpp::vm::MetadataCache::Register到这里就和前面的对上了。至于为什么要这么设计,可以看这个函数的注释。我的理解在于像所有函数以及类型是需要在实际游戏项目build出来以后才能知道的。这里为了模块独立,将这部分的代码放到了游戏本身代码之中,然后il2cpp提供接口去初始化这些数据。

看下g_MetadataRegistration的初始值(这里是我游戏的数据,每个游戏数据不同)

extern const Il2CppMetadataRegistration g_MetadataRegistration = 
{
    4524,
    s_Il2CppGenericTypes,
    805,
    g_Il2CppGenericInstTable,
    5892,
    s_Il2CppGenericMethodFunctions,
    12345,
    g_Il2CppTypeTable,
    6412,
    g_Il2CppMethodSpecTable,
    2479,
    g_FieldOffsetTable,
    2479,
    g_Il2CppTypeDefinitionSizesTable,
    7998,
    g_MetadataUsages,
};

这个Il2CppMetadataRegistration结构体如下

typedef struct Il2CppMetadataRegistration
{
    int32_t genericClassesCount;
    Il2CppGenericClass* const * genericClasses;
    int32_t genericInstsCount;
    const Il2CppGenericInst* const * genericInsts;
    int32_t genericMethodTableCount;
    const Il2CppGenericMethodFunctionsDefinitions* genericMethodTable;
    int32_t typesCount;
    const Il2CppType* const * types;
    int32_t methodSpecsCount;
    const Il2CppMethodSpec* methodSpecs;

    FieldIndex fieldOffsetsCount;
    const int32_t** fieldOffsets;

    TypeDefinitionIndex typeDefinitionsSizesCount;
    const Il2CppTypeDefinitionSizes** typeDefinitionsSizes;
    const size_t metadataUsagesCount;
    void** const* metadataUsages;
} Il2CppMetadataRegistration;

比如这里,一共有12345种类型的typesCount

#metadata加载流程-总结

将从正向以及源代码的角度去分析metadata加载的过程总结

前面主要从逆s向分析和调试的角度去分析了加载的整体流程,下面我们从正向流程总结下函数是什么调用执行的。

程序最开始执行在mod_init_func之中,即模块初始化函数(从上面的调试堆栈上也能看出来)

_mod_init_func:0000000100CF54A0             __GLOBAL__sub_I_Il2CppCodeRegistration.cpp

函数如下

__int64 _GLOBAL__sub_I_Il2CppCodeRegistration_cpp()
{
  return il2cpp::utils::RegisterRuntimeInitializeAndCleanup::RegisterRuntimeInitializeAndCleanup(
           (il2cpp::utils::RegisterRuntimeInitializeAndCleanup *)&s_Il2CppCodegenRegistrationVariable,
           (void (*)(void))s_Il2CppCodegenRegistration,
           0LL,
           0);
}

然后下面就执行了il2cpp::utils::RegisterRuntimeInitializeAndCleanup::RegisterRuntimeInitializeAndCleanup函数,这里会去注册回调函数s_Il2CppCodegenRegistration,然而这个函数定义在Classes/Native/Il2CppCodeRegistration.cpp里面

void s_Il2CppCodegenRegistration()
{
    il2cpp_codegen_register (&g_CodeRegistration, &g_MetadataRegistration, &s_Il2CppCodeGenOptions);
}

当然这里仅仅是注册,后面并没有立即执行这个函数。执行的时机是在之前分析的调用链之中

UnityInitApplicationNoGraphics()
--InitializeIl2CppFromMain()
----il2cpp_init("IL2CPP Root Domain");
------il2cpp::vm::Runtime::Init()
--------il2cpp::utils::RegisterRuntimeInitializeAndCleanup::ExecuteInitializations()
----------s_Il2CppCodegenRegistration()

这里才会去执行s_Il2CppCodegenRegistration函数,我们继续分析。这个函数会调用il2cpp_codegen_register函数并传入三个重要的参数。三个参数数据如下

首先g_CodeRegistration是在Classes/Native/Il2CppCodeRegistration.cpp中初始化的,类似下面

extern const Il2CppCodeRegistration g_CodeRegistration = 
{
    12950,
    g_MethodPointers,
    0,
    NULL,
    5063,
    g_Il2CppGenericMethodPointers,
    1911,
    g_Il2CppInvokerPointers,
    3103,
    g_AttributeGenerators,
    306,
    g_UnresolvedVirtualMethodPointers,
    195,
    g_Il2CppInteropData,
};

然后g_MetadataRegistration是在Classes/Native/Il2CppMetadataRegistration.cpp中初始化的,类似下面

extern const Il2CppMetadataRegistration g_MetadataRegistration = 
{
    4524,
    s_Il2CppGenericTypes,
    805,
    g_Il2CppGenericInstTable,
    5892,
    s_Il2CppGenericMethodFunctions,
    12345,
    g_Il2CppTypeTable,
    6412,
    g_Il2CppMethodSpecTable,
    2479,
    g_FieldOffsetTable,
    2479,
    g_Il2CppTypeDefinitionSizesTable,
    7998,
    g_MetadataUsages,
};

最后s_Il2CppCodeGenOptionsClasses/Native/Il2CppCodeRegistration.cpp之中,类似下面

static const Il2CppCodeGenOptions s_Il2CppCodeGenOptions = 
{
    true,
};

这样三个参数就准备好了,然后调用il2cpp_codegen_register,这个函数其实是一个inline函数,在/il2cpp/libil2cpp/codegen/il2cpp-codegen-il2cpp.h文件里面

inline void il2cpp_codegen_register(const Il2CppCodeRegistration* const codeRegistration, const Il2CppMetadataRegistration* const metadataRegistration, const Il2CppCodeGenOptions* const codeGenOptions)
{
    il2cpp::vm::MetadataCache::Register(codeRegistration, metadataRegistration, codeGenOptions);
}

也就是说是调用了il2cpp::vm::MetadataCache::Register函数,这个函数在il2cpp/libil2cpp/vm/MetadataCache.cpp文件里面

void MetadataCache::Register(const Il2CppCodeRegistration* const codeRegistration, const Il2CppMetadataRegistration* const metadataRegistration, const Il2CppCodeGenOptions* const codeGenOptions)
{
    s_Il2CppCodeRegistration = codeRegistration;
    s_Il2CppMetadataRegistration = metadataRegistration;
    s_Il2CppCodeGenOptions = codeGenOptions;

    for (int32_t j = 0; j < metadataRegistration->genericClassesCount; j++)
        if (metadataRegistration->genericClasses[j]->typeDefinitionIndex != kTypeIndexInvalid)
            metadata::GenericMetadata::RegisterGenericClass(metadataRegistration->genericClasses[j]);

    for (int32_t i = 0; i < metadataRegistration->genericInstsCount; i++)
        s_GenericInstSet.insert(metadataRegistration->genericInsts[i]);

    s_InteropData.assign_external(codeRegistration->interopData, codeRegistration->interopDataCount);
}

这里就看到了我们熟悉的变量s_Il2CppMetadataRegistration到这里il2cpp::utils::RegisterRuntimeInitializeAndCleanup::ExecuteInitializations就执行完了,下面将会执行最重要的metadata加载函数MetadataCache::Initialize()函数调用结构如下

UnityInitApplicationNoGraphics()
--InitializeIl2CppFromMain()
----il2cpp_init("IL2CPP Root Domain");
------il2cpp::vm::Runtime::Init()
--------il2cpp::utils::RegisterRuntimeInitializeAndCleanup::ExecuteInitializations()
----------s_Il2CppCodegenRegistration()
--------MetadataCache::Initialize()

函数体如下

void MetadataCache::Initialize()
{
    s_GlobalMetadata = vm::MetadataLoader::LoadMetadataFile("global-metadata.dat");
    s_GlobalMetadataHeader = (const Il2CppGlobalMetadataHeader*)s_GlobalMetadata;
    IL2CPP_ASSERT(s_GlobalMetadataHeader->sanity == 0xFAB11BAF);
    IL2CPP_ASSERT(s_GlobalMetadataHeader->version == 24);
    ...
}

这里全部流程就结束了,也就是所有初始的变量值我们都知道从哪里来的。接下来将会重点分析MetadataCache::Initialize()加载的具体实现细节。

#实验(解析il2cpp_codegen_initialize_method过程)

主要分析il2cpp_codegen_initialize_method函数的执行流程

以下需要建议结合我写的010Editor对metadata的模板来分析

这个函数其实是一个inline的函数,实际编译完以后并不存在。由于每个方法第一次初始化的时候都回去调用一次相当重要,所以我们分析一下这个函数主要干了什么。

下面是随便找的一个函数为例子说明

// System.Void click1::Click()
extern "C" IL2CPP_METHOD_ATTR void click1_Click_mADA9E988D60543553FB6489103DE368DDF13AA0A (click1_tBA7675B5632245C8DFC65538FFA3D8F5AC215128 * __this, const RuntimeMethod* method)
{
    static bool s_Il2CppMethodInitialized;
    if (!s_Il2CppMethodInitialized)
    {
        il2cpp_codegen_initialize_method (click1_Click_mADA9E988D60543553FB6489103DE368DDF13AA0A_MetadataUsageId);
        s_Il2CppMethodInitialized = true;
    }
  ...
}

基本所有方法在第一次执行的时候都会调用il2cpp_codegen_initialize_method,即如下代码

inline void il2cpp_codegen_initialize_method(uint32_t index)
{
    il2cpp::vm::MetadataCache::InitializeMethodMetadata(index);
}

这个方法就比较熟悉了,这里传入的是方法的id,即click1_Click_mADA9E988D60543553FB6489103DE368DDF13AA0A_MetadataUsageId

这个id在Classes/Native/Il2CppMetadataUsage.cpp文件之中定义了值为

extern const uint32_t click1_Click_mADA9E988D60543553FB6489103DE368DDF13AA0A_MetadataUsageId = 7967;

而且数量和metadata里面的metadataHeader.metadataUsageListsCount / sizeof(Il2CppMetadataUsageList)的数量相等,因为就是去这个表里面索引的。如下代码

void MetadataCache::InitializeMethodMetadata(uint32_t index)
{
    IL2CPP_ASSERT(s_GlobalMetadataHeader->metadataUsageListsCount >= 0 && index <= static_cast<uint32_t>(s_GlobalMetadataHeader->metadataUsageListsCount));

    const Il2CppMetadataUsageList* metadataUsageLists = MetadataOffset<const Il2CppMetadataUsageList*>(s_GlobalMetadata, s_GlobalMetadataHeader->metadataUsageListsOffset, index);

    uint32_t start = metadataUsageLists->start;
    uint32_t count = metadataUsageLists->count;

    utils::dynamic_array<Il2CppMetadataUsage> acceptAllUsages;
    IntializeMethodMetadataRange(start, count, acceptAllUsages);
}

从这个表中会得到开始的地址以及数量,这里解释下这里的数量的意思:一个方法里面往往会用到其他类,类型或者字符串。这里的数量就是用到的所有类型的总和。

这里方法的值如下:

start:27517 count:8

然后调用IntializeMethodMetadataRange函数

void MetadataCache::IntializeMethodMetadataRange(uint32_t start, uint32_t count, const utils::dynamic_array<Il2CppMetadataUsage>& expectedUsages)
{
    for (uint32_t i = 0; i < count; i++)
    {
        uint32_t offset = start + i;
        IL2CPP_ASSERT(s_GlobalMetadataHeader->metadataUsagePairsCount >= 0 && offset <= static_cast<uint32_t>(s_GlobalMetadataHeader->metadataUsagePairsCount));
        const Il2CppMetadataUsagePair* metadataUsagePairs = MetadataOffset<const Il2CppMetadataUsagePair*>(s_GlobalMetadata, s_GlobalMetadataHeader->metadataUsagePairsOffset, offset);
        uint32_t destinationIndex = metadataUsagePairs->destinationIndex;
        uint32_t encodedSourceIndex = metadataUsagePairs->encodedSourceIndex;

        Il2CppMetadataUsage usage = GetEncodedIndexType(encodedSourceIndex);
        if (IsMatchingUsage(usage, expectedUsages))
        {
            uint32_t decodedIndex = GetDecodedMethodIndex(encodedSourceIndex);
            switch (usage)
            {
                case kIl2CppMetadataUsageTypeInfo:
                    *s_Il2CppMetadataRegistration->metadataUsages[destinationIndex] = GetTypeInfoFromTypeIndex(decodedIndex);
                    break;
                case kIl2CppMetadataUsageIl2CppType:
                    *s_Il2CppMetadataRegistration->metadataUsages[destinationIndex] = const_cast<Il2CppType*>(GetIl2CppTypeFromIndex(decodedIndex));
                    break;
                case kIl2CppMetadataUsageMethodDef:
                case kIl2CppMetadataUsageMethodRef:
                    *s_Il2CppMetadataRegistration->metadataUsages[destinationIndex] = const_cast<MethodInfo*>(GetMethodInfoFromIndex(encodedSourceIndex));
                    break;
                case kIl2CppMetadataUsageFieldInfo:
                    *s_Il2CppMetadataRegistration->metadataUsages[destinationIndex] = GetFieldInfoFromIndex(decodedIndex);
                    break;
                case kIl2CppMetadataUsageStringLiteral:
                    *s_Il2CppMetadataRegistration->metadataUsages[destinationIndex] = GetStringLiteralFromIndex(decodedIndex);
                    break;
                default:
                    IL2CPP_NOT_IMPLEMENTED(MetadataCache::InitializeMethodMetadata);
                    break;
            }
        }
    }
}

这个方法会依据传入的数量进行遍历,这里遍历的其实是metadataUsagePairs这张表。这里数量为8,所以这8个数据如下

i destinationIndex encodedSourceIndex usage decodedIndex value
0 1728 536883176 kIl2CppMetadataUsageTypeInfo 12264 info_il2cpp_TypeInfo_var
1 352 536876564 kIl2CppMetadataUsageTypeInfo 5652 Mathil2cpp_TypeInfo_var
2 261 536871137 kIl2CppMetadataUsageTypeInfo 225 Int32_il2cpp_TypeInfo_var
3 1341 536881179 kIl2CppMetadataUsageTypeInfo 10267 Debug_il2cpp_TypeInfo_var
4 1793 3221227325 kIl2CppMetadataUsageMethodRef 1853 GameObject_GetComponent_TisText__RuntimeMethod_var
5 9565 2684358272 kIl2CppMetadataUsageStringLiteral 3712 stringLiteral77E95373…(“background/Canvas/error”)
6 6161 2684354738 kIl2CppMetadataUsageStringLiteral 178 stringLiteralB858CB28…(“”)
7 9566 2684358273 kIl2CppMetadataUsageStringLiteral 3713 stringLiteral070E659B…(“英雄战斗力不足,请修炼或者重生增加战斗力”)

上表就是解析一个方法id得到的数据,可以看到有type、method、string等信息。其中destinationIndex的值就是在对应表中去查的地址值,也就是这里将metadata里面的数据读取出来以后和内存中的地址关联了起来。那么下面去做字符串恢复是不是就很简单了,就是遍历所有的方法id,即遍历metadataUsageLists把所有的方法中用到的数据都dump出来。

#逆向

#XIL2CppDumper

 __  _____ _     ____   ____             ____                                  
 \ \/ /_ _| |   |___ \ / ___|_ __  _ __ |  _ \ _   _ _ __ ___  _ __   ___ _ __ 
  \  / | || |     __) | |   | '_ \| '_ \| | | | | | | '_ ` _ \| '_ \ / _ \ '__|
  /  \ | || |___ / __/| |___| |_) | |_) | |_| | |_| | | | | | | |_) |  __/ |   
 /_/\_\___|_____|_____|\____| .__/| .__/|____/ \__,_|_| |_| |_| .__/ \___|_|   
                            |_|   |_|                         |_|            



+--------------------------------------------------------------------------------------+
| XIL2CppDumper | a tool of C++ version IL2CppDumper made by xia0@2019                 |
+--------------------------------------------------------------------------------------+
| Info          | version: 0.2 support: iOS[arm64] Android[arm64] il2cpp[24.1/24.0]    |
+--------------------------------------------------------------------------------------+
| Usage         | XIL2CppDumper unity_metadata_file_path il2cpp_so_or_macho_file_path  |
+--------------------------------------------------------------------------------------+
| Blog          | http://4ch12dy.site                                                  |
+--------------------------------------------------------------------------------------+
| Github        | https://github.com/4ch12dy                                           |
+--------------------------------------------------------------------------------------+
| Specail thanks to Perfare's Il2CppDumper:https://github.com/Perfare/Il2CppDumper     |
+--------------------------------------------------------------------------------------+

基于分析的原理,我写了一个C++版本的dump工具:XIL2Cppdumper

目前支持的平台有Android和iOS,理论上支持所有的il2cpp版本(适配新版本十分方便)

项目地址:https://github.com/4ch12dy/XIL2CppDumper

#参考