通往罗马的道路不止一条-关于Unity游戏Assembly-CSharp加密问题的处理新思路
2020-08-22 20:46:42 Author: bbs.pediy.com(查看原文) 阅读量:718 收藏

我不知道这个思路以前有没有大神已经提出过,虽然想法很幼稚,但是用起来着实是比较方便,所以在这里跟大家分享。如果有更方便的方案,也请赐教,感激不尽

众所周知,我们伟大的Unity3D引擎为了跨平台的方便,使用了c#作为其基础编程语言,并且以Mono虚拟机为载体,在各个平台流畅运行

随之而来的,是一些让开发者头疼的问题:由于 cs 的语言特性,其在未混淆,未加密的情况下,基本可以视作"开源语言"(类似于Javascript?),这使得游戏的逻辑变得极其不安全,简单举个例子,如果一个未使用IL2CPP和其他手段加密或编译的unity游戏,你可以在他的 根目录/xxx_Data/Managed/Assembly-CSharp.dll 找到几乎全部的游戏逻辑代码,简单使用dnSpy等工具反编译即可篡改游戏逻辑,甚至可以直接引用该库,进行一些调用游戏SDK的行为

为此,开发者也想尽办法阻止这样的行为,目前最有效的Unity游戏保护方法是通过IL2CPP,在开发时便将cs代码通过IL2CPP层翻译成底层代码,从而防止cs代码逻辑泄露。

也有一些厂商,以mono虚拟机为插手点,魔改mono.dll文件(mono是开源项目),在文件存储时对文件进行加密,在游戏启动时再解密加载

本文将阐述关于后者的一些简单研究

国产3A大作之光,s~j~2 开启三测了,其画质相比第一代,确实令人震惊,也看得出开发公司对此下了很大的功夫

游戏采用前言中第二种保护方式(难道是公司没钱用IL2CPP?)

出于兴趣,我想审查一下国产游戏公司代码的严密程度,并提出必要的修改意见

于是兴高采烈的熟练找到 SSJJ2-WD/battle/21/hallclient_Data/Managed/Assembly-CSharp.dll,拖进dnSpy

结果大失所望

1

看了看二进制文件,连基本的MZ标识都没有,看来是加密了
2

由于之前从来没有过解密这类文件的经验,又无奈本人是逆向白痴,对OD,x64dbg一窍不通

在看雪上搜索碰碰运气找到了狂神说大神的这篇文章
[原创]记录U3D逆向Assembly-CSharp-firstpass.dll解密

其中最重要的便是,mono.dll!mono_image_open_from_data_with_name 的函数被魔改

我按照大神的思路,用IDA打开mono.dll看了一眼,却没看到任何有关解密的相关代码,看来此游戏的解密点不在mono_image_open_from_data_with_name,而在更前面

按理来说,应该开始动态调试往上跟进,奈何本人没文化,一句卧槽走天下,游戏的禁止调试,成为了压倒我的最后一根稻草
3

看来分析解密算法对我来说已经不太可能了,忽然想起以前在github fork过mono的源代码:mono_github,怀着试一试的心情,我翻阅了/mono/metadata/image.c里的mono_image_open_from_data_with_name函数

MonoImage *
mono_image_open_from_data_with_name (char *data, guint32 data_len, gboolean need_copy, MonoImageOpenStatus *status, gboolean refonly, const char *name);

恍惚间,突然想起一个好点子:

既然解密在调用这个方法之前,那调用这个方法时的数据就是已经解密好的数据,直接hook这个函数不就好了吗?

抱着试一试的想法,写了一个简单的链接库,然后用注册表注入

const string Dump_Path = "D:\\ssjj2_dump\\";

typedef long long(WINAPI* ptrMonoLoadData)(const void*, unsigned int, int, unsigned int*, char, char*);
ptrMonoLoadData oriMonoLoadData;
long long WINAPI my_mono_image_open_from_data_with_name(const void* data,unsigned int data_len,int need_copy, unsigned int *status,char a5,char *name) {
    string str_name = name;
    if (str_name.find("Assembly-CSharp.dll") != string::npos) {
        void* data_buffer = malloc(data_len);
        memcpy_s(data_buffer, data_len, data, data_len);
        string path = Dump_Path + "Assembly-CSharp.dll";
        HANDLE hFile = CreateFileA(path.c_str(), GENERIC_WRITE | GENERIC_READ, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
        if (INVALID_HANDLE_VALUE != hFile)
        {
            WriteFile(hFile, data_buffer, data_len, NULL, NULL);
            CloseHandle(hFile);
            Log("Dumped!");
        }
    }
    Log("load " + str_name);
    return oriMonoLoadData(data, data_len, need_copy, status, a5, name);
}

void OnMonoLoad() {
    Log("Mono.dll loaded!");
    PVOID mono_image_open_from_data_with_name_addr = GetProcAddress(GetModuleHandleA("mono.dll"), "mono_image_open_from_data_with_name");
    char buf[1000];
    memset(buf,0,sizeof(buf));
    sprintf_s(buf,"mono_image_open_from_data_with_name_addr = %X", mono_image_open_from_data_with_name_addr);
    Log(buf);
    if (mono_image_open_from_data_with_name_addr) {
        if (MH_CreateHook(mono_image_open_from_data_with_name_addr, my_mono_image_open_from_data_with_name, reinterpret_cast<LPVOID*>(&oriMonoLoadData)) != MH_OK) {
            Log("MH_CreateHook mono_image_open_from_data_with_name_addr failed");
            return;
        }
        if (MH_EnableHook(mono_image_open_from_data_with_name_addr) != MH_OK) {
            Log("MH_EnableHook mono_image_open_from_data_with_name_addr failed");
            return;
        }
        Log("Hook mono_image_open_from_data_with_name_addr success");
    }
}

typedef HMODULE(WINAPI* ptrLoadLibraryExW)(LPCWSTR, HANDLE, DWORD);
ptrLoadLibraryExW oriLoadLibraryExW;
HMODULE
WINAPI
MyLoadLibraryExW(
    _In_ LPCWSTR lpLibFileName,
    _Reserved_ HANDLE hFile,
    _In_ DWORD dwFlags
) {
    char buf[1000];
    memset(buf, 0, sizeof(buf));
    sprintf_s(buf, "%ws", lpLibFileName);
    string buf_str = buf;
    if (buf_str.find("NEPDaemon.exe") != string::npos) {
        Log("refused!");
        return 0;
    }
    if (buf_str.find("mono.dll") != string::npos) {
        HMODULE res = oriLoadLibraryExW(lpLibFileName, hFile, dwFlags);
        OnMonoLoad();
        return res;
    }
    return oriLoadLibraryExW(lpLibFileName, hFile, dwFlags);
}

BOOL HookLoadLibraryW() {
    PVOID LoadLibraryWAddr = GetProcAddress(GetModuleHandleA("KernelBase.dll"), "LoadLibraryExW");
    if (!LoadLibraryWAddr) {
        Log("GET LoadLibraryWAddr failed!");
        return false;
    }
    if (MH_CreateHook(LoadLibraryWAddr, MyLoadLibraryExW, reinterpret_cast<LPVOID*>(&oriLoadLibraryExW)) != MH_OK) {
        Log("MH_CreateHook LoadLibraryWAddr failed");
        return false;
    }
    if (MH_EnableHook(LoadLibraryWAddr) != MH_OK) {
        Log("MH_EnableHook LoadLibraryWAddr failed");
        return false;
    }
    Log("Hook LoadlibraryW success");
    return true;
}

结果如意料之中的那样,成功的dump出了原始dll文件,看到MZ标志的那一刻,通往罗马的道路,又多了一条

4

5

6

[公告]SDC2020 看雪安全者开发者峰会10月23日将在上海举行!欢迎参加!

最后于 20小时前 被renbohan编辑 ,原因:


文章来源: https://bbs.pediy.com/thread-261577.htm
如有侵权请联系:admin#unsafe.sh