从N1CTF2018 baby unity3d入门il2cpp逆向
字数 1491 2025-08-30 06:50:35

从N1CTF2018 baby unity3d入门il2cpp逆向分析

前置知识

il2cpp基础概念

  • il2cpp是Unity引擎的脚本后端实现,将C#代码编译为C++代码
  • 主要包含两个关键文件:
    • libil2cpp.so:包含核心逻辑的二进制文件
    • global-metadata.dat:包含元数据信息

必要工具

  • Il2CppDumper:用于解析il2cpp结构的工具
    • GitHub地址:https://github.com/Perfare/Il2CppDumper
  • IDA Pro:用于逆向分析so文件
  • dnSpy:用于查看C#反编译代码
  • frida-il2cpp-bridge:用于动态分析
    • GitHub地址:https://github.com/vfsfitvnm/frida-il2cpp-bridge

解题步骤

1. 文件提取与初步分析

  1. 解包APK获取关键文件:

    • libil2cpp.so
    • global-metadata.dat(通常被加密)
  2. 识别global-metadata.dat加密:

    • 文件头不是正常的"AF 1B B1 FA"
    • 需要从libil2cpp.so中寻找解密逻辑

2. 定位解密函数

调用链分析

il2cpp_init
  -> il2cpp::vm::Runtime::Init
    -> il2cpp::vm::MetadataCache::Initialize
      -> il2cpp::vm::MetadataLoader::LoadMetadataFile

IDA分析技巧

  1. 从il2cpp_init开始跟踪
  2. 识别关键函数:
    • sub_47C770对应Runtime::Init
    • sub_4B5564对应MetadataCache::Initialize
  3. 关键字符串线索:
    • "CLKFIL\rMETIDITI\nDIT"是加密后的"global-metadata.dat"

解密函数识别

  • sub_512FDC是核心解密函数
  • 解密算法:每4字节与key数组异或
    • key数组位置:0x518A24
    • key数组大小:0x84个DWORD

3. 解密global-metadata.dat

解密代码示例

#include <stdio.h>
#include <stdlib.h>

int main() {
    FILE *file;
    unsigned char *buf;
    size_t fileSize;
    
    file = fopen("global-metadata.dat", "rb");
    fseek(file, 0, SEEK_END);
    fileSize = ftell(file);
    rewind(file);
    buf = (unsigned char *)malloc(fileSize);
    fread(buf, 1, fileSize, file);
    fclose(file);

    unsigned int key[] = {0xF83DA249, 0x15D12772, /* ...完整key数组... */};
    
    for (int i = 0; i < fileSize; i+=4){
        *((unsigned int *)(buf + i)) ^= key[(i + i/0x84) %0x84];
    }

    FILE *file_write = fopen("output.bin", "wb");
    fwrite(buf, 1, fileSize, file_write);
    fclose(file_write);
    return 0;
}

解密后处理

  • 修复文件头为"AF 1B B1 FA"

4. 使用Il2CppDumper解析

  1. 运行Il2CppDumper:

    • 输入:libil2cpp.so和解密后的global-metadata.dat
    • 输出:
      • Assembly-CSharp.dll(可用dnSpy查看)
      • dump.cs(包含函数偏移信息)
      • script.json(用于IDA脚本)
  2. IDA符号恢复:

    • 使用ida_py3.py脚本加载script.json
    • 自动恢复函数和类名

5. 关键函数分析

CheckFlag函数

  • 偏移:0x518A24
  • 参数:
    • text:a2
    • password:TypeInfo+80 + 4000
    • iv:TypeInfo+80 + 2364
    • flag密文:StringLiteral_2814

加密流程

  1. 对输入进行AESEncrypt
  2. 与存储的flag密文比较

6. 动态分析技巧

使用frida-il2cpp-bridge

  1. Hook AESEncrypt获取参数:
import "frida-il2cpp-bridge";

Il2Cpp.perform(() => {
    const assembly = Il2Cpp.domain.assembly("Assembly-CSharp").image;
    assembly.class('Check').method('AESEncrypt').implementation = function(){
        console.log(arguments[0]); // 密文
        console.log(arguments[1]); // password
        console.log(arguments[2]); // iv
        return this.method('AESEncrypt').invoke(arguments[0],arguments[1],arguments[2]);
    }
});
  1. Hook字符串比较获取flag密文:
import "frida-il2cpp-bridge";

Il2Cpp.perform(() => {
    const SystemString = Il2Cpp.corlib.class("System.String");
    var compare = SystemString.method('Equals');
    compare.implementation = function(){
        console.log(`arg[0]: ${arguments[0]}`);
        console.log(`arg[1]: ${arguments[1]}`);
    }
});
  1. 直接调用解密函数:
import "frida-il2cpp-bridge";

Il2Cpp.perform(() => {
    function getBytes(str){
        return Il2Cpp.corlib.class("System.Text.UTF8Encoding").new()
               .method('GetBytes',1).invoke(Il2Cpp.string(str));
    }

    const assembly = Il2Cpp.domain.assembly("Assembly-CSharp").image;
    const text = Il2Cpp.string('w0ZyUZAHhn16/MRWie63lK+PuVpZObu/NpQ/E/ucplc=');
    const password = getBytes('91c775fa0f6a1cba');
    const iv = getBytes('58f3a445939aeb79');
    const val = assembly.class('Check').method('AESDecrypt').invoke(text,password,iv);
    console.log(val); // 输出解密后的flag
});

关键数据

  • flag密文:w0ZyUZAHhn16/MRWie63lK+PuVpZObu/NpQ/E/ucplc=
  • password:91c775fa0f6a1cba
  • iv:58f3a445939aeb79

总结

  1. il2cpp逆向核心在于解析加密的global-metadata.dat
  2. 解密通常采用异或等简单算法,关键在定位解密函数
  3. 静态分析结合动态调试(frida)能有效提高效率
  4. Il2CppDumper是解析il2cpp结构的必备工具
  5. 注意关键字符串和函数调用链的分析
从N1CTF2018 baby unity3d入门il2cpp逆向分析 前置知识 il2cpp基础概念 il2cpp是Unity引擎的脚本后端实现,将C#代码编译为C++代码 主要包含两个关键文件: libil2cpp.so:包含核心逻辑的二进制文件 global-metadata.dat:包含元数据信息 必要工具 Il2CppDumper:用于解析il2cpp结构的工具 GitHub地址:https://github.com/Perfare/Il2CppDumper IDA Pro:用于逆向分析so文件 dnSpy:用于查看C#反编译代码 frida-il2cpp-bridge:用于动态分析 GitHub地址:https://github.com/vfsfitvnm/frida-il2cpp-bridge 解题步骤 1. 文件提取与初步分析 解包APK获取关键文件: libil2cpp.so global-metadata.dat(通常被加密) 识别global-metadata.dat加密: 文件头不是正常的"AF 1B B1 FA" 需要从libil2cpp.so中寻找解密逻辑 2. 定位解密函数 调用链分析 IDA分析技巧 从il2cpp_ init开始跟踪 识别关键函数: sub_ 47C770对应Runtime::Init sub_ 4B5564对应MetadataCache::Initialize 关键字符串线索: "CLKFIL\rMETIDITI\nDIT"是加密后的"global-metadata.dat" 解密函数识别 sub_ 512FDC是核心解密函数 解密算法:每4字节与key数组异或 key数组位置:0x518A24 key数组大小:0x84个DWORD 3. 解密global-metadata.dat 解密代码示例 解密后处理 修复文件头为"AF 1B B1 FA" 4. 使用Il2CppDumper解析 运行Il2CppDumper: 输入:libil2cpp.so和解密后的global-metadata.dat 输出: Assembly-CSharp.dll(可用dnSpy查看) dump.cs(包含函数偏移信息) script.json(用于IDA脚本) IDA符号恢复: 使用ida_ py3.py脚本加载script.json 自动恢复函数和类名 5. 关键函数分析 CheckFlag函数 偏移:0x518A24 参数: text:a2 password:TypeInfo+80 + 4000 iv:TypeInfo+80 + 2364 flag密文:StringLiteral_ 2814 加密流程 对输入进行AESEncrypt 与存储的flag密文比较 6. 动态分析技巧 使用frida-il2cpp-bridge Hook AESEncrypt获取参数: Hook字符串比较获取flag密文: 直接调用解密函数: 关键数据 flag密文: w0ZyUZAHhn16/MRWie63lK+PuVpZObu/NpQ/E/ucplc= password: 91c775fa0f6a1cba iv: 58f3a445939aeb79 总结 il2cpp逆向核心在于解析加密的global-metadata.dat 解密通常采用异或等简单算法,关键在定位解密函数 静态分析结合动态调试(frida)能有效提高效率 Il2CppDumper是解析il2cpp结构的必备工具 注意关键字符串和函数调用链的分析