解决Unity逆向难题:Il2CppDumper如何解析元数据中的类型信息
你是否曾在Unity游戏逆向过程中遇到过类型信息混乱、函数调用关系不清晰的问题?当Unity项目使用IL2CPP(中间语言到C++)编译后,C#代码会被转换为C++代码并编译成原生二进制文件,同时生成元数据(Metadata)文件存储类型信息。本文将带你深入了解IL2CPP元数据格式,以及Il2CppDumper如何解析这些信息,帮助你轻松应对Unity逆向挑战。
读完本文,你将能够:
- 理解IL2CPP元数据的基本结构和核心字段
- 掌握Il2CppDumper解析类型信息的关键流程
- 学会使用Il2CppDumper处理不同版本的元数据文件
IL2CPP元数据概述
IL2CPP元数据(Metadata)是Unity使用IL2CPP编译时生成的关键文件,它存储了所有C#类型信息,包括类、方法、字段、属性等,类似于.NET的程序集元数据。Il2CppDumper通过解析这个文件,可以将二进制文件中的类型信息还原出来,为逆向工程提供重要支持。
元数据文件以特定的头部信息开始,Il2CppDumper首先会验证这个头部来确保文件的有效性。核心代码在Il2Cpp/MetadataClass.cs中定义,其中Il2CppGlobalMetadataHeader类描述了元数据文件的整体结构。
元数据头部结构
元数据文件的头部包含了各种数据块的偏移量和大小,这些信息指导Il2CppDumper如何定位和解析后续的类型数据。以下是头部的核心字段:
public class Il2CppGlobalMetadataHeader
{
public uint sanity; // 校验值,固定为0xFAB11BAF
public int version; // 元数据版本号
public uint stringOffset; // 元数据字符串偏移量
public int stringSize; // 元数据字符串大小
public uint typeDefinitionsOffset; // 类型定义偏移量
public int typeDefinitionsSize; // 类型定义大小
// 其他数据块偏移量和大小...
}
Il2CppDumper在解析元数据时,首先会检查sanity字段是否为0xFAB11BAF,以确认文件有效性。然后根据version字段确定元数据版本,不同版本的结构可能有所差异,Il2CppDumper需要做相应的适配处理。
Il2CppDumper解析流程
Il2CppDumper解析元数据的过程主要在Il2Cpp/Metadata.cs中的Metadata类中实现。整个流程可以分为以下几个关键步骤:
1. 文件验证与版本检测
在Metadata类的构造函数中,首先读取并验证头部信息:
var sanity = ReadUInt32();
if (sanity != 0xFAB11BAF)
{
throw new InvalidDataException("ERROR: Metadata file supplied is not valid metadata file.");
}
var version = ReadInt32();
if (version < 16 || version > 31)
{
throw new NotSupportedException($"ERROR: Metadata file supplied is not a supported version[{version}].");
}
Il2CppDumper支持版本16到31的元数据文件,对于不同版本,后续的解析逻辑会有所调整。
2. 数据块解析
验证通过后,Il2CppDumper会根据头部信息解析各个数据块,包括类型定义、方法定义、字段定义等:
typeDefs = ReadMetadataClassArray<Il2CppTypeDefinition>(header.typeDefinitionsOffset, header.typeDefinitionsSize);
methodDefs = ReadMetadataClassArray<Il2CppMethodDefinition>(header.methodsOffset, header.methodsSize);
fieldDefs = ReadMetadataClassArray<Il2CppFieldDefinition>(header.fieldsOffset, header.fieldsSize);
ReadMetadataClassArray方法会根据指定的偏移量和大小,读取并解析对应的数据结构数组。例如,typeDefs数组存储了所有类的定义信息。
3. 类型信息提取
类型定义信息存储在Il2CppTypeDefinition结构体中,每个实例代表一个C#类或结构:
public class Il2CppTypeDefinition
{
public uint nameIndex; // 类名索引
public uint namespaceIndex; // 命名空间索引
public int declaringTypeIndex; // 声明类型索引(父类)
public int parentIndex; // 父类索引
public uint flags; // 类型标志
public int fieldStart; // 字段起始索引
public ushort field_count; // 字段数量
// 其他类型信息...
}
Il2CppDumper通过nameIndex和namespaceIndex可以从字符串数据块中获取类的名称和命名空间:
public string GetStringFromIndex(uint index)
{
if (!stringCache.TryGetValue(index, out var result))
{
result = ReadStringToNull(header.stringOffset + index);
stringCache.Add(index, result);
}
return result;
}
这个方法会从元数据的字符串数据块中读取指定索引处的字符串,并进行缓存以提高效率。
实战案例:解析一个简单类
让我们以一个简单的C#类为例,看看Il2CppDumper如何解析其元数据:
namespace Sample
{
public class Player
{
public string name;
public int level;
public void Attack()
{
// 攻击逻辑
}
}
}
编译后,这个类的信息会存储在元数据中。Il2CppDumper解析过程如下:
- 从
typeDefs数组中找到Player类的Il2CppTypeDefinition实例 - 通过
nameIndex获取类名"Player",通过namespaceIndex获取命名空间"Sample" - 根据
fieldStart和field_count找到该类的字段定义 - 解析字段定义,获取"name"和"level"的信息
- 解析方法定义,获取"Attack"方法的信息
通过这个过程,Il2CppDumper可以将二进制元数据还原为清晰的类型信息,为逆向工程提供极大便利。
处理不同版本的元数据
Unity不断更新IL2CPP技术,元数据格式也在不断变化。Il2CppDumper通过版本适配机制,支持不同版本的元数据文件。例如,在Il2Cpp/Metadata.cs中,有专门的逻辑处理版本差异:
if (version == 24)
{
if (header.stringLiteralOffset == 264)
{
Version = 24.2;
header = ReadClass<Il2CppGlobalMetadataHeader>(0);
}
else
{
// 版本24.1处理逻辑
}
}
这种灵活的版本处理机制,使得Il2CppDumper能够应对不同Unity版本生成的元数据文件。
总结与展望
IL2CPP元数据是Unity逆向工程的关键,Il2CppDumper通过解析元数据文件,为我们提供了清晰的类型信息。本文介绍了元数据的基本结构、Il2CppDumper的解析流程以及版本适配机制。掌握这些知识,将帮助你更好地应对Unity逆向挑战。
Il2CppDumper项目仍在持续发展中,未来可能会支持更多新的Unity版本和元数据格式。如果你对Unity逆向感兴趣,不妨深入研究项目源码,或者参与到项目的开发中,为开源社区贡献力量。
官方文档:README.md 元数据解析核心代码:Il2Cpp/Metadata.cs 类型定义结构:Il2Cpp/MetadataClass.cs
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



