太久没发文章了,把存货发出来了,当作存档了<@_@>,仅供学习交流
mhy这个驱动漏洞也很有意思:https://bbs.pediy.com/thread-272873.htm

0x01 隐藏关键DLL

对于IL2CPP的Unity游戏,它的核心游戏逻辑一般都是放在 *Assembly.dll这个DLL里面的,我们global-metadata.dat解析出对应的符号也是作用于这个DLL的。正常游戏都是放在游戏主程序根目录下,原神做了一个目录迁移,用ProcessHacker可以找到真实路径。

image-20220202131912060

0x02 加密global-metadata.dat

游戏版本: genshin-impact-2.4

观察global-metadata.dat文件,发现被加密了

image-20220129122257777

原神的Unity版本为2017.4.30,下载il2cpp的源码进行对照逆向

https://github.com/4ch12dy/il2cpp/tree/master/unity_2017_x

image-20220129122804003

il2cpp 加载的关键函数和结构

void MetadataCache::Initialize()
void* MetadataLoader::LoadMetadataFile(const char* fileName)
struct Il2CppGlobalMetadataHeader

image-20220129125252155

通过源码和IDA逆向对比可以得出对应关键函数的位置以及一些重要全局变量,我们修改IDA中伪代码的名称,在LoadMetadataFile函数中发现疑似对global-metadata.dat进行解密的函数操作。

image-20220203184021700

对这个函数交叉引用可以发现是在il2cpp_init_security这个函数中进行初始化的,在结合x64dbg动态调试可以发现在这里初始化的三个函数都是在UnityPlayer.dll中的。

image-20220203184737050

image-20220130162206800

并且这三个函数还加了代码混淆和反调试,静态看不是很清晰。

image-20220130162230041

基本上就是靠IDA静态分析以及结合源码进行判断,分别为以下三个函数

image-20220203190904086

其中对GetStringLiteralFromIndex的判断如下,通过对比可以猜测原神对这个函数做了其他操作(字面量二次解密之类的)

image-20220203190748093

image-20220203190836374

对GetStringFromIndex的判断

image-20220203191108870

然后在IDA的Local Types界面把Il2CppGlobalMetadataHeader结构导入,观察源码和IDA中伪代码的解析可以发现原神对这个结构做了改动。

image-20220203185122907

通过进一步的分析发现原神修改了如Il2CppGlobalMetadataHeader,Il2CppTypeDefinition,Il2CppMethodDefinition,Il2CppFieldDefinition和Il2CppPropertyDefinition等诸多IL2CPP加载过程中要使用的结构。

手动调用从UnityPlayer.dll获取的DecryptMetadata函数对global-metadata.dat进行解密,发现只进行了部分位置解密,而且没有出现dat文件的特征头。

image-20220130224649298

手动把mhyprot2.sys卸载后,迅速将内存中的global-metadata.dat Dump下来然后和我们手动调用解密函数后的文件进行比对,发现global-metadata.dat是存在二次解密的,并且是有规律的不同,间隔的字节数和文件大小有关系。

image-20220202124718937

观察和查找资料后可用发现是每隔(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;
}

image-20220202130433983

我们可以用Il2CppInspector这个工具编写插件来自动化操作

image-20220202130700608

得到符号信息,可以发现mhy对非关键函数做了源码层面的哈希操作。

image-20220202131607648image-20220202131508518

0x03 CE进程检测绕过

将CE源码中CE相关字符串替换掉成系统相关进程名(如:svchost.exe)重新编译,或者直接修改"cheatengine-x86_64.exe" 进程名为 "svchost.exe"。

image-20220207162712040

0x04 总结

global-metadata文件经历了两次解密,原神dump下来的符号文件意义不大,非unity的关键函数都做了源码层面的混淆。CE进程检测较为草率了,不知道有没有其他交叉检测。