某神PC端Unity IL2CPP保护分析
太久没发文章了,把存货发出来了,当作存档了<@_@>,仅供学习交流
mhy这个驱动漏洞也很有意思:https://bbs.pediy.com/thread-272873.htm
0x01 隐藏关键DLL
对于IL2CPP的Unity游戏,它的核心游戏逻辑一般都是放在 *Assembly.dll这个DLL里面的,我们global-metadata.dat解析出对应的符号也是作用于这个DLL的。正常游戏都是放在游戏主程序根目录下,原神做了一个目录迁移,用ProcessHacker可以找到真实路径。
0x02 加密global-metadata.dat
游戏版本: genshin-impact-2.4
观察global-metadata.dat文件,发现被加密了
原神的Unity版本为2017.4.30,下载il2cpp的源码进行对照逆向
https://github.com/4ch12dy/il2cpp/tree/master/unity_2017_x
il2cpp 加载的关键函数和结构
void MetadataCache::Initialize()
void* MetadataLoader::LoadMetadataFile(const char* fileName)
struct Il2CppGlobalMetadataHeader
通过源码和IDA逆向对比可以得出对应关键函数的位置以及一些重要全局变量,我们修改IDA中伪代码的名称,在LoadMetadataFile函数中发现疑似对global-metadata.dat进行解密的函数操作。
对这个函数交叉引用可以发现是在il2cpp_init_security这个函数中进行初始化的,在结合x64dbg动态调试可以发现在这里初始化的三个函数都是在UnityPlayer.dll中的。
并且这三个函数还加了代码混淆和反调试,静态看不是很清晰。
基本上就是靠IDA静态分析以及结合源码进行判断,分别为以下三个函数
其中对GetStringLiteralFromIndex的判断如下,通过对比可以猜测原神对这个函数做了其他操作(字面量二次解密之类的)
对GetStringFromIndex的判断
然后在IDA的Local Types界面把Il2CppGlobalMetadataHeader结构导入,观察源码和IDA中伪代码的解析可以发现原神对这个结构做了改动。
通过进一步的分析发现原神修改了如Il2CppGlobalMetadataHeader,Il2CppTypeDefinition,Il2CppMethodDefinition,Il2CppFieldDefinition和Il2CppPropertyDefinition等诸多IL2CPP加载过程中要使用的结构。
手动调用从UnityPlayer.dll获取的DecryptMetadata函数对global-metadata.dat进行解密,发现只进行了部分位置解密,而且没有出现dat文件的特征头。
手动把mhyprot2.sys卸载后,迅速将内存中的global-metadata.dat Dump下来然后和我们手动调用解密函数后的文件进行比对,发现global-metadata.dat是存在二次解密的,并且是有规律的不同,间隔的字节数和文件大小有关系。
观察和查找资料后可用发现是每隔(dwFileSize >> 14) << 6
和一个数组异或。在UserAssembly.dll中找了一圈没有找到这个解密的地方,猜测是放到GetStringLiteralFromIndex或者类似UnityPlayer.dll中的函数中调用的。
截下来就是Dump出符号信息,先写脚本手动解密验证猜想
#include <windows.h>
#include <iostream>
typedef byte* (* pDecryptMetadata)(byte[], int);
int main()
{
HMODULE hModuleBase = LoadLibrary(L"UnityPlayer.dll");
pDecryptMetadata pfnDecryptMetadata = (pDecryptMetadata)((PBYTE)hModuleBase + 0x16F840);
/*
//DecryptMetadata 0x16F840
//GetStringFromIndex 0x12DC50
//GetStringLiteralFromIndex 0x12DF60
*/
HANDLE hFile = CreateFile(L"global-metadata.dat",
GENERIC_READ,
FILE_SHARE_READ,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL);
DWORD dwFileSize = 0;
dwFileSize = GetFileSize(hFile, NULL);
PBYTE bBuffer;
bBuffer = (PBYTE)malloc(dwFileSize);
DWORD dwReadNumber = 0;
if (!ReadFile(hFile, bBuffer, dwFileSize, &dwReadNumber, NULL))
{
printf("ReadFile Error\n");
return 0;
}
pfnDecryptMetadata(bBuffer, dwFileSize);
byte key[] = { 0xAD, 0x2F, 0x42, 0x30, 0x67, 0x04, 0xB0, 0x9C, 0x9D, 0x2A, 0xC0, 0xBA, 0x0E, 0xBF, 0xA5, 0x68 };
// The step is based on the file size
UINT32 step = (UINT32)((dwFileSize >> 14) << 6);
for (DWORD pos = 0; pos < (dwFileSize - step); pos += step)
for (byte b = 0; b < 0x10; b++)
bBuffer[pos + b] ^= key[b];
HANDLE hFILE = CreateFile(L"global-metadata-decrytpo.dat",
GENERIC_WRITE | GENERIC_READ,
FILE_SHARE_READ,
NULL,
CREATE_ALWAYS,
FILE_ATTRIBUTE_READONLY,
NULL);
DWORD dwWrite;
if (!WriteFile(hFILE, bBuffer, dwFileSize, &dwWrite, NULL))
{
printf("WriteFile Error\n");
return 0;
}
FreeLibrary(hModuleBase);
printf("Done!\n");
return 0;
}
我们可以用Il2CppInspector这个工具编写插件来自动化操作
得到符号信息,可以发现mhy对非关键函数做了源码层面的哈希操作。
0x03 CE进程检测绕过
将CE源码中CE相关字符串替换掉成系统相关进程名(如:svchost.exe)重新编译,或者直接修改"cheatengine-x86_64.exe" 进程名为 "svchost.exe"。
0x04 总结
global-metadata文件经历了两次解密,原神dump下来的符号文件意义不大,非unity的关键函数都做了源码层面的混淆。CE进程检测较为草率了,不知道有没有其他交叉检测。
进入到23年后,il2cpp里用到的结构体字段顺序都被打乱了,完了有些字段还被加密了,太惨了
老哥如果可以的话能分析一下3.4版本吗,对UserAssembly.dll做了很多改动,很喜欢你的操作,跟着分析之前的版本都能应付