前言
在分析一些unity游戏的逻辑代码时,直接使用dnspy查看游戏的Assembly-CSharp.dll
逻辑代码静态分析存在一些缺陷,就是对于不熟悉运作流程的新游戏,我们很难把握其代码的大致执行流程,并且很难做到在运行时分析。幸运的是,dnspy在几个版本之前就已经支持了针对mono虚拟机的unity游戏的调试支持,只需要跟随我的步伐,修改几处游戏文件就可以顺利的开始你的dnspy动态调试unity之旅
下载dnspy及调试所需内容
如果你还不知道dnspy是个什么东西,你可以在这里看到它的介绍 dnspy主页
简单来说,它就是一个可以用来反编译二进制.NET架构程序代码的工具,拥有强大的分析,反编译,调试功能。
你可以在release界面直接下载到最新版本,自带中文 Releases · dnSpy
除了下载dnspy主程序,我们这里还需要专门下载调试unity所需的mono替换文件,它在release板块的unity分支栏下
这里需要根据你的游戏版本下载,至于查看游戏版本号的方法,你可以找到unity游戏主程序,右键程序属性,在详细信息这一栏所找到的文件版本就是你的unity版本号
下载好对应的替换文件后,打开对应版本号的文件夹,你会发现有win32/win64两个文件夹,这个根据你游戏的位数而定,如果你不确定游戏位数,那么可以先尝试使用32位文件替换,如果游戏无法正常运行,再尝试64位文件替换
替换调试所需文件
这里只需要找到你游戏的根目录,也就是包含有游戏可执行文件,和游戏数据文件的目录,它的结构通常是这样:
/游戏名.exe
/游戏名_Data/
/其他文件夹/
打开游戏名_Data
文件夹,并替换以下文件(如果没有这个文件,则忽略)
<root>\<GAME>_Data\Mono\mono.dll
<root>\<GAME>_Data\Mono\EmbedRuntime\mono.dll
<root>\<GAME>_Data\MonoBleedingEdge\EmbedRuntime\mono-2.0-bdwgc.dll
<root>\Mono\EmbedRuntime\mono.dll
<root>\MonoBleedingEdge\EmbedRuntime\mono-2.0-bdwgc.dll
特殊情况
鉴于有一些游戏可能会验证mono.dll是否被替换,我们需要做一些操作手段来加载debug版本的mono.dll,我想到的解决办法是通过注入dll,然后hook LoadLibraryW 的方法,让游戏加载mono模块的时候,直接加载我们自己的调试版本模块。当然这个方法要求你的注入方法时机必须非常早,早于mono模块的加载时机,至于怎么实现,注册表注入,劫持注入这些都可以实现,这里不做赘述。
代码可以大概写成这样
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
char ProcessPath[MAX_PATH] = { 0 };
if (ul_reason_for_call == DLL_PROCESS_ATTACH) {
DWORD curPID = GetCurrentProcessId();
GetModuleFileNameExA(OpenProcess(PROCESS_ALL_ACCESS, false, curPID), 0, ProcessPath, MAX_PATH);
string myPath = ProcessPath;
if (myPath.find("SSJJ_BattleClient_Unity") != string::npos) {
Log("Injected!!");
Log(myPath);
if (MH_Initialize() != MH_OK) {
Log("MH_Initialize 加载失败!");
return FALSE;
}
HookLoadLibraryW();
return TRUE;
}
else {
return FALSE;
}
}
else if (ul_reason_for_call == DLL_PROCESS_DETACH) {
MH_Uninitialize();
}
return FALSE;
}
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;
}
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("\\mono.dll") != string::npos) {
HMODULE res = oriLoadLibraryExW(L"C:\\Users\\15516\\AppData\\Roaming\\Wooduan\\SSJJ-4399\\battle\\6\\SSJJ_BattleClient_Unity_Data\\Mono\\monoDebug.dll", NULL, dwFlags); //替换成我们自己的调试用的Dll
Log("Patched Load Monodebug.dll");
return res;
}
return oriLoadLibraryExW(lpLibFileName, hFile, dwFlags);
}
通过这样的手段,我们已经通过调试版mono运行了游戏,接下来就是使用dnspy进行调试了
使用dnspy调试
首先来配置环境变量,根据dnspy官网的说明
dnSpy's mono.dll will look for an environment variable called DNSPY_UNITY_DBG (Unity with .NET 2.0-3.5 assemblies) or DNSPY_UNITY_DBG2 (Unity with .NET 4.x assemblies)
DNSPY_UNITY_DBG
--debugger-agent=transport=dt_socket,server=y,address=127.0.0.1:55555,defer=y
or
--debugger-agent=transport=dt_socket,server=y,address=127.0.0.1:55555,defer=y,no-hide-debugger
to enable detection of the debugger.
DNSPY_UNITY_DBG2
--debugger-agent=transport=dt_socket,server=y,address=127.0.0.1:55555,suspend=n
or
--debugger-agent=transport=dt_socket,server=y,address=127.0.0.1:55555,suspend=n,no-hide-debugger
to enable detection of the debugger.
我们根据自己unity游戏的版本,可以来配置环境变量,以自定义调试端口。当然,dnspy的调试默认以55555为端口,因此不设置环境变量默认情况下,以55555为调试端口
这里我们以win10为例,教大家如何设置环境变量
我的电脑右键-属性
左侧栏点击高级系统设置
点击高级-环境变量
在系统变量处点击新建,并输入对应的环境变量值进入
点确定即可
然后启动游戏,并保持游戏运行
打开dnspy,注意这里一定要用管理员权限打开
然后先按照静态分析的方法,打开游戏的主dll
然后点菜单栏的调试-开始调试
调试引擎选择Unity(连接)
,ip不用填写,默认为本机,点击确定
如果设置没有问题,此时应该游戏就已经连接上了,可以试试下断点看看能否触发
可以看到,断点成功触发,而且堆栈回溯,变量监控等各功能均正常工作
这样,就可以很方便的通过单步查看游戏的流程,从而更方便我们分析游戏了。
参考资料
Debugging Unity Games · dnSpy/dnSpy Wiki (github.com)
转载必须注明版权
除了下载dnspy主程序,我们这里还需要专门下载调试unity所需的mono替换文件,它在release板块的unity分支栏下
开头的这句话,对应的mono替换文件已经找不到下载地址了?请问一下有新的下载地址吗?
By jim at February 17th, 2022 at 12:00 pm.
dnspy没有匹配2021的unity,此时要debug,只能自己编译dnspy-unity-mono中src中的代码?挺难受的
By ayuliao at November 15th, 2021 at 11:33 am.
针不戳(☆ω☆)
By KK at February 15th, 2021 at 01:02 am.
针不戳(☆ω☆)
By Nolovenodie丶 at December 12th, 2020 at 05:23 pm.