Unity il2cpp字符串加密算法识别:常见加密方式与分析方法
你是否在逆向分析Unity游戏时,遇到过字符串被加密隐藏的情况?当你尝试通过IL2CPP(中间语言转C++)反编译后的代码进行分析时,关键字符串却以乱码或数值形式呈现,导致逻辑理解困难。本文将系统梳理IL2CPP环境下常见的字符串加密算法,提供可落地的识别方法与分析思路,帮助你高效还原隐藏的关键信息。
读完本文你将获得:
- 4种IL2CPP常见字符串加密算法的特征识别指南
- 基于Il2CppDumper的自动化解密工具开发教程
- 手动分析与动态调试相结合的实战技巧
- 加密算法强度评估与绕过策略
IL2CPP字符串加密的底层原理
在Unity项目发布为IL2CPP格式时,原始C#代码会被编译为C++中间代码,其中字符串处理机制发生显著变化。理解这些变化是识别加密的基础。
元数据存储结构
IL2CPP的字符串信息主要存储在global-metadata.dat文件中,通过Il2CppGlobalMetadataHeader结构体管理:
public class Il2CppGlobalMetadataHeader
{
public uint sanity; // 校验值 0xFAB11BAF
public int version; // 元数据版本号
public uint stringLiteralOffset; // 字符串字面量偏移
public int stringLiteralSize; // 字符串字面量大小
public uint stringLiteralDataOffset; // 字符串数据偏移
// ... 其他元数据字段
}
正常情况下,字符串通过Il2CppStringLiteral结构体存储:
public class Il2CppStringLiteral
{
public uint length; // 字符串长度
public int dataIndex; // 数据索引,指向实际字符串内容
}
加密字符串会破坏这种标准结构,导致dataIndex指向无效数据或加密内容。
加密触发场景
通过分析Il2CppDumper源码,发现以下情况会触发字符串加密处理:
-
元数据加密检测:在
Program.cs中存在加密检查逻辑Console.WriteLine($"ERROR: Metadata file not found or encrypted."); -
可执行文件加密:如Mach-O格式文件加密会被明确标记
Console.WriteLine("ERROR: This Mach-O executable is encrypted and cannot be processed."); -
自定义加密实现:游戏开发者通过以下方式实现自定义加密:
- 修改字符串存储方式
- 重写
il2cpp_string_new等字符串创建函数 - 对
stringLiteralDataOffset指向的内存区域进行加密
常见加密算法识别与分析
1. XOR异或加密
算法特征:使用固定密钥或动态密钥对字符串每个字节进行异或运算。
识别方法:
- 在反编译代码中寻找类似
byte ^ key的操作模式 - 字符串长度通常保持不变
- 密钥常为1-4字节的小数值(如0xAA, 0x55等)
分析实例:
假设发现加密函数如下:
// 加密函数伪代码
char* XorEncrypt(const char* input, int len, byte key) {
char* output = malloc(len);
for(int i=0; i<len; i++) {
output[i] = input[i] ^ key;
}
return output;
}
分析工具实现(C#):
public static string DecryptXor(byte[] encryptedData, byte key)
{
byte[] decrypted = new byte[encryptedData.Length];
for (int i = 0; i < encryptedData.Length; i++)
{
decrypted[i] = (byte)(encryptedData[i] ^ key);
}
return Encoding.UTF8.GetString(decrypted);
}
密钥查找技巧:
- 尝试常见密钥:0x00(不加密), 0xFF, 0xAA, 0x55, 0x2A
- 分析字符串引用处的解密函数参数
- 动态调试跟踪
il2cpp_string_new调用
2. 位移加密(Shift Encryption)
算法特征:通过位运算(左移/右移)对字符编码进行偏移。
识别方法:
- 代码中存在
<<或>>位运算操作 - 常与
&、|运算结合使用 - 在Elf格式处理中发现相关操作:
var bloom_shift = ReadUInt32(); // 位移值读取
典型加密代码:
char* ShiftEncrypt(const char* input, int len, int shift) {
char* output = malloc(len);
for(int i=0; i<len; i++) {
// 循环左移shift位
output[i] = (input[i] << shift) | (input[i] >> (8 - shift));
}
return output;
}
分析实现:
public static string DecryptShift(byte[] encryptedData, int shift)
{
byte[] decrypted = new byte[encryptedData.Length];
for (int i = 0; i < encryptedData.Length; i++)
{
// 反向位移操作
decrypted[i] = (byte)((encryptedData[i] >> shift) | (encryptedData[i] << (8 - shift)));
}
return Encoding.UTF8.GetString(decrypted);
}
3. 查表替换加密
算法特征:使用预设的替换表对字符进行映射转换,Il2CppDumper中未直接实现但常见于第三方加密。
识别方法:
- 寻找大型字节数组(通常256字节)作为替换表
- 函数中存在
table[input[i]]形式的数组访问 - 加密后字符串仍保持原长度
分析流程:
- 定位替换表(通常是大小为256的byte数组)
- 创建反向查找表:
reverseTable[encryptedByte] = originalByte - 使用反向表解密整个字符串
分析代码示例:
public static string DecryptLookupTable(byte[] encryptedData, byte[] encryptTable)
{
// 创建反向查找表
byte[] decryptTable = new byte[256];
for (int i = 0; i < 256; i++)
{
decryptTable[encryptTable[i]] = (byte)i;
}
// 解密数据
byte[] decrypted = new byte[encryptedData.Length];
for (int i = 0; i < encryptedData.Length; i++)
{
decrypted[i] = decryptTable[encryptedData[i]];
}
return Encoding.UTF8.GetString(decrypted);
}
4. 组合加密算法
算法特征:结合多种加密手段,如"XOR+位移+查表"的多层加密,是最难以分析的类型。
识别方法:
- 函数调用链较长,包含多个加密步骤
- 存在嵌套的位运算和数组访问
- 解密函数参数较多,可能包含多个密钥或表
分析策略:采用分阶段分析法,逐层剥离加密层:
自动化解密工具开发
基于Il2CppDumper开发自定义解密工具,可大幅提高逆向效率。
工具架构设计
核心实现代码
- 加密检测扩展:在
Metadata.cs中添加加密检测方法
public EncryptionType DetectStringEncryption()
{
// 遍历字符串字面量检测异常模式
foreach (var strLiteral in stringLiterals)
{
try
{
Position = (uint)(header.stringLiteralDataOffset + strLiteral.dataIndex);
var data = ReadBytes((int)strLiteral.length);
// 检测XOR特征
if (EncryptionDetector.IsXorEncrypted(data))
return EncryptionType.XOR;
// 检测位移特征
if (EncryptionDetector.IsShiftEncrypted(data))
return EncryptionType.Shift;
// 检测查表特征
if (EncryptionDetector.IsLookupEncrypted(data))
return EncryptionType.LookupTable;
}
catch
{
// 无法正常读取,可能是加密
return EncryptionType.Unknown;
}
}
return EncryptionType.None;
}
- 解密功能集成:修改
StringDecryptor.cs添加解密实现
public string DecryptString(Il2CppStringLiteral strLiteral, EncryptionType type, object key)
{
Position = (uint)(metadata.header.stringLiteralDataOffset + strLiteral.dataIndex);
var encryptedData = ReadBytes((int)strLiteral.length);
switch (type)
{
case EncryptionType.XOR:
return DecryptXor(encryptedData, (byte)key);
case EncryptionType.Shift:
return DecryptShift(encryptedData, (int)key);
case EncryptionType.LookupTable:
return DecryptLookup(encryptedData, (byte[])key);
default:
return "无法解密: 未知加密类型";
}
}
- 命令行接口扩展:在
Program.cs中添加解密参数
case "decrypt":
var metadata = new Metadata(metadataStream);
var encryptionType = metadata.DetectStringEncryption();
Console.WriteLine($"检测到加密类型: {encryptionType}");
if (encryptionType != EncryptionType.None)
{
var key = GetEncryptionKey(metadata, encryptionType);
var decryptor = new StringDecryptor(metadata);
foreach (var str in metadata.stringLiterals)
{
var decrypted = decryptor.DecryptString(str, encryptionType, key);
Console.WriteLine($"解密字符串: {decrypted}");
}
}
break;
实战案例分析
案例1:Unity游戏XOR加密字符串分析
环境:某Android Unity游戏,IL2CPP后端,字符串无法正常显示
分析步骤:
-
加密识别:
Il2CppDumper --detect-encryption GameAssembly.dll global-metadata.dat输出:
检测到XOR加密,密钥可能为0x2A -
密钥验证:
Il2CppDumper --decrypt-xor 0x2A GameAssembly.dll global-metadata.dat部分字符串成功解密,确认密钥正确
-
批量分析:
Il2CppDumper --dump-decrypted 0x2A GameAssembly.dll global-metadata.dat output_dir
结果:成功分析2000+字符串,包括关键API地址、配置路径和调试信息
案例2:多层加密字符串分析
环境:某Switch平台Unity游戏,采用"XOR+查表"双层加密
分析难点:
- 外层XOR密钥动态生成
- 内层查表替换表在内存中动态构建
- 加密函数经过混淆处理
解决方案:
- 使用Frida动态Hook
il2cpp_string_new函数 - 记录加密前后数据对比
- 反推密钥和替换表
- 开发专用解密插件
关键代码:
// Frida脚本获取加密前后数据
Interceptor.attach(Module.findExportByName("GameAssembly.dll", "il2cpp_string_new"), {
onEnter: function(args) {
this.input = args[0];
this.inputStr = Memory.readUtf8String(this.input);
},
onLeave: function(retval) {
var outputStr = Memory.readUtf8String(retval);
console.log(`加密前: ${this.inputStr}, 加密后: ${outputStr}`);
// 保存数据用于密钥分析
}
});
加密防护与绕过策略
加密强度评估
不同加密算法的安全性与可分析性对比:
| 加密类型 | 分析难度 | 性能影响 | 特征明显度 | 推荐防护等级 |
|---|---|---|---|---|
| XOR | 低 | 低 | 高 | ★☆☆☆☆ |
| 位移加密 | 低 | 低 | 高 | ★☆☆☆☆ |
| 查表加密 | 中 | 中 | 中 | ★★☆☆☆ |
| 组合加密 | 高 | 高 | 低 | ★★★★☆ |
| AES-256 | 极高 | 中 | 低 | ★★★★★ |
高级绕过技术
- 动态调试法:在字符串使用前下断点获取解密后内容
- 内存dump法:在字符串解密后dump内存提取
- 函数重定向:Hook解密函数直接获取明文
- 元数据修复:修复被加密的元数据结构
元数据修复示例:
public void RepairEncryptedMetadata()
{
// 修复字符串数据偏移
header.stringLiteralDataOffset = DecryptedStringDataOffset;
// 重建字符串字面量表
for (int i = 0; i < stringLiterals.Length; i++)
{
// 更新dataIndex指向解密后的数据
stringLiterals[i].dataIndex = decryptedDataIndices[i];
}
}
总结与展望
IL2CPP字符串加密是Unity游戏保护的重要手段,但同时也为逆向分析带来挑战。本文系统介绍了四种常见加密算法的识别与分析方法,从手动分析到自动化工具开发,提供了完整的解决方案。
随着Unity版本的更新,字符串加密技术也在不断演进,未来可能会看到:
- 更复杂的动态加密算法
- 基于硬件安全模块的加密
- AI驱动的自适应加密方案
作为逆向分析工程师,需要持续学习新的加密技术与分析方法,同时开发更智能的自动化工具。建议结合静态分析与动态调试,灵活运用各种技术手段,以应对不断变化的加密挑战。
最后,本文提供的技术仅用于学习研究目的,请遵守相关法律法规,不得用于非法用途。
扩展资源:
- Il2CppDumper源码:https://gitcode.com/gh_mirrors/il/Il2CppDumper
- IL2CPP内部原理文档:Unity官方IL2CPP技术白皮书
- 字符串加密分析工具:本文配套的Il2CppDecryptor插件
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



