使用frida获取unity il2cpp符号信息
字数 1623 2025-08-22 12:22:36

使用Frida获取Unity IL2CPP符号信息教学文档

1. 背景知识

1.1 Unity IL2CPP编译模式

  • Unity游戏使用IL2CPP模式编译时,会将C#代码转换为C++代码再编译为原生二进制
  • 游戏中的字符串信息保存在global-metadata.dat资源文件中
  • 运行时才会将这些字符串读入内存

1.2 常规分析方法

  • 通常使用Il2CppDumper工具读取global-metadata.dat文件中的信息
  • 该工具能帮助还原符号信息,辅助反编译工作

1.3 保护与对抗

  • 游戏厂商可能采取保护措施:
    • 修改global-metadata.dat文件结构
    • 对文件进行加密
    • 使Il2CppDumper无法正常工作

2. IL2CPP加载过程分析

2.1 原始加载流程

  1. MetadataCache::Initialize()函数调用LoadMetadataFile()加载元数据
  2. LoadMetadataFile()函数执行流程:
    void* MetadataLoader::LoadMetadataFile(const char* fileName) {
        // 组合文件路径
        std::string resourcesDirectory = utils::PathUtils::Combine(...);
        std::string resourceFilePath = utils::PathUtils::Combine(...);
    
        // 打开文件
        FileHandle* handle = File::Open(resourceFilePath, ...);
    
        // 映射文件到内存
        void* fileBuffer = utils::MemoryMappedFile::Map(handle);
    
        // 关闭文件句柄
        File::Close(handle, &error);
    
        return fileBuffer;
    }
    

2.2 global-metadata.dat文件结构

struct Il2CppGlobalMetadataHeader {
    int32_t sanity;
    int32_t version;
    int32_t stringLiteralOffset;  // string data for managed code
    int32_t stringLiteralCount;
    int32_t stringLiteralDataOffset;
    int32_t stringLiteralDataCount;
    int32_t stringOffset;         // string data for metadata
    int32_t stringCount;
    int32_t eventsOffset;         // Il2CppEventDefinition
    int32_t eventsCount;
    int32_t propertiesOffset;     // Il2CppPropertyDefinition
    int32_t propertiesCount;
    int32_t methodsOffset;        // Il2CppMethodDefinition
    int32_t methodsCount;
    // ... 其他字段
}

2.3 某手游的特殊处理

  • LoadMetadataFile()映射文件到内存后进行了解密操作
  • MetadataCache::Initialize()中还有一次解密
  • 文件结构被修改,无法直接使用Il2CppDumper

3. 关键Hook点选择

3.1 为什么选择SetupMethodsLocked

  • 通过分析IL2CPP源码调用链:
    il2cpp_class_get_methods -> Class::GetMethods -> Class::SetupMethods -> SetupMethodsLocked
    
  • SetupMethodsLocked是最终设置方法信息的关键函数
  • 在此处Hook可以获取完整的MethodInfo结构

3.2 SetupMethodsLocked关键代码

void SetupMethodsLocked(Il2CppClass* klass, const FastAutoLock& lock) {
    // 分配内存
    klass->methods = (const MethodInfo**)IL2CPP_CALLOC(klass->method_count, sizeof(MethodInfo*));
    MethodInfo* methods = (MethodInfo*)IL2CPP_CALLOC(klass->method_count, sizeof(MethodInfo));
    
    MethodInfo* newMethod = methods;
    for (MethodIndex index = start; index < end; ++index) {
        // 获取方法定义
        const Il2CppMethodDefinition* methodDefinition = MetadataCache::GetMethodDefinitionFromIndex(index);
        
        // 设置方法信息
        newMethod->name = MetadataCache::GetStringFromIndex(methodDefinition->nameIndex);
        newMethod->methodPointer = MetadataCache::GetMethodPointerFromIndex(methodDefinition->methodIndex);
        newMethod->invoker_method = MetadataCache::GetMethodInvokerFromIndex(methodDefinition->invokerIndex);
        newMethod->declaring_type = klass;
        newMethod->return_type = MetadataCache::GetIl2CppTypeFromIndex(methodDefinition->returnType);
        
        // 设置参数信息
        ParameterInfo* parameters = (ParameterInfo*)IL2CPP_CALLOC(methodDefinition->parameterCount, sizeof(ParameterInfo));
        // ... 参数设置代码
        
        // 将方法添加到类中
        klass->methods[index - start] = newMethod;
        newMethod++;  // 这是最佳Hook点
    }
}

4. 关键数据结构

4.1 Il2CppClass结构

struct Il2CppClass {
    const Il2CppImage* image;
    void* gc_desc;
    const char* name;        // 类名
    const char* namespaze;   // 命名空间
    // ... 其他字段
};

4.2 MethodInfo结构

struct MethodInfo {
    Il2CppMethodPointer methodPointer;  // 方法指针
    InvokerMethod invoker_method;
    const char* name;                   // 方法名
    Il2CppClass* declaring_type;        // 声明类
    const Il2CppType* return_type;      // 返回类型
    const ParameterInfo* parameters;    // 参数信息
    // ... 其他字段
};

5. Frida Hook实现

5.1 定位Hook地址

  1. 反编译libil2cpp.so
  2. 找到SetupMethodsLocked函数
  3. 确定newMethod++指令的偏移地址(示例中为0x72F09EC + 0x204

5.2 Frida脚本

// hook SetupMethodsLocked
var module = Process.findModuleByName("libil2cpp.so");
var p_size = 8;  // 指针大小,64位为8

Interceptor.attach(ptr(module.base).add(0x72F09EC).add(0x204), {
    onEnter: function(args) {
        var newMethod = this.context.x20;  // ARM64下x20寄存器保存newMethod指针
        
        // 读取MethodInfo结构
        var pointer = newMethod.readPointer();  // 方法指针
        var name = newMethod.add(p_size * 2).readPointer().readCString();  // 方法名
        var klass = newMethod.add(p_size * 3).readPointer();  // 声明类
        
        // 读取Il2CppClass结构
        var klass_name = klass.add(p_size * 2).readPointer().readCString();
        var klass_paze = klass.add(p_size * 3).readPointer().readCString();
        
        // 输出格式: 命名空间.类名:方法名 -> 相对偏移
        send(klass_paze + "." + klass_name + ":" + name + " -> " + pointer.sub(module.base));
    }
});

5.3 脚本说明

  1. 通过Interceptor.attach附加到目标地址
  2. onEnter回调中读取方法信息
  3. 从寄存器/内存中提取:
    • 方法指针
    • 方法名称
    • 声明类信息(类名和命名空间)
  4. 输出格式为:命名空间.类名:方法名 -> 相对偏移

6. 注意事项

6.1 混淆处理

  • 目标应用可能使用混淆技术
  • 类名、方法名可能被混淆
  • 需要额外的反混淆处理(不在本文讨论范围)

6.2 平台差异

  • 示例使用ARM64架构(x20寄存器)
  • 不同平台需要调整:
    • 寄存器使用(x86/ARM32/ARM64不同)
    • 指针大小(32位为4字节)

6.3 偏移地址

  • SetupMethodsLocked中的newMethod++指令偏移需要根据具体目标确定
  • 需要通过反编译找到正确位置

7. 扩展应用

7.1 自动化脚本

  • 可以扩展脚本自动记录所有类和方法
  • 生成符号表供后续分析使用

7.2 结合其他技术

  • 结合内存dump技术获取更多信息
  • 配合IDA等工具进行静态分析

7.3 性能考虑

  • 大量方法调用可能影响性能
  • 生产环境应考虑过滤关键方法

8. 总结

本方法通过Hook IL2CPP内部函数SetupMethodsLocked,在运行时动态获取Unity游戏的符号信息,绕过了对global-metadata.dat文件的保护措施。关键点包括:

  1. 理解IL2CPP加载流程和文件结构
  2. 选择合适的Hook点(SetupMethodsLocked)
  3. 正确解析MethodInfo和Il2CppClass结构
  4. 处理平台差异和混淆情况

这种方法对于分析受保护的Unity游戏特别有效,即使global-metadata.dat被加密或修改也能正常工作。

使用Frida获取Unity IL2CPP符号信息教学文档 1. 背景知识 1.1 Unity IL2CPP编译模式 Unity游戏使用IL2CPP模式编译时,会将C#代码转换为C++代码再编译为原生二进制 游戏中的字符串信息保存在 global-metadata.dat 资源文件中 运行时才会将这些字符串读入内存 1.2 常规分析方法 通常使用 Il2CppDumper 工具读取 global-metadata.dat 文件中的信息 该工具能帮助还原符号信息,辅助反编译工作 1.3 保护与对抗 游戏厂商可能采取保护措施: 修改 global-metadata.dat 文件结构 对文件进行加密 使 Il2CppDumper 无法正常工作 2. IL2CPP加载过程分析 2.1 原始加载流程 MetadataCache::Initialize() 函数调用 LoadMetadataFile() 加载元数据 LoadMetadataFile() 函数执行流程: 2.2 global-metadata.dat文件结构 2.3 某手游的特殊处理 在 LoadMetadataFile() 映射文件到内存后进行了解密操作 MetadataCache::Initialize() 中还有一次解密 文件结构被修改,无法直接使用 Il2CppDumper 3. 关键Hook点选择 3.1 为什么选择SetupMethodsLocked 通过分析IL2CPP源码调用链: SetupMethodsLocked 是最终设置方法信息的关键函数 在此处Hook可以获取完整的 MethodInfo 结构 3.2 SetupMethodsLocked关键代码 4. 关键数据结构 4.1 Il2CppClass结构 4.2 MethodInfo结构 5. Frida Hook实现 5.1 定位Hook地址 反编译 libil2cpp.so 找到 SetupMethodsLocked 函数 确定 newMethod++ 指令的偏移地址(示例中为 0x72F09EC + 0x204 ) 5.2 Frida脚本 5.3 脚本说明 通过 Interceptor.attach 附加到目标地址 在 onEnter 回调中读取方法信息 从寄存器/内存中提取: 方法指针 方法名称 声明类信息(类名和命名空间) 输出格式为: 命名空间.类名:方法名 -> 相对偏移 6. 注意事项 6.1 混淆处理 目标应用可能使用混淆技术 类名、方法名可能被混淆 需要额外的反混淆处理(不在本文讨论范围) 6.2 平台差异 示例使用ARM64架构(x20寄存器) 不同平台需要调整: 寄存器使用(x86/ARM32/ARM64不同) 指针大小(32位为4字节) 6.3 偏移地址 SetupMethodsLocked 中的 newMethod++ 指令偏移需要根据具体目标确定 需要通过反编译找到正确位置 7. 扩展应用 7.1 自动化脚本 可以扩展脚本自动记录所有类和方法 生成符号表供后续分析使用 7.2 结合其他技术 结合内存dump技术获取更多信息 配合IDA等工具进行静态分析 7.3 性能考虑 大量方法调用可能影响性能 生产环境应考虑过滤关键方法 8. 总结 本方法通过Hook IL2CPP内部函数 SetupMethodsLocked ,在运行时动态获取Unity游戏的符号信息,绕过了对 global-metadata.dat 文件的保护措施。关键点包括: 理解IL2CPP加载流程和文件结构 选择合适的Hook点(SetupMethodsLocked) 正确解析MethodInfo和Il2CppClass结构 处理平台差异和混淆情况 这种方法对于分析受保护的Unity游戏特别有效,即使 global-metadata.dat 被加密或修改也能正常工作。