Background
When analyzing some logical code of unity games, we could use dnspy to view Assembly-CSharp.dll
. However, there's something really inconvenient. If you aren't familiar with a new game, it is difficult to grasp the general execution process of the code. Fortunately, dnspy has supported debugging for unity games on mono VM. Just follow my step, and you'll find it easy to debug a unity game with dnspy!
Download Dnspy and Dependence
If you don't know what dnspy is, you can get some information here. dnspy Page
In short, it is a tool that can be used to decompile binary .Net framework program code, with powerful analysis, decompilation, debugging functions.
You can download the latest version here on the release page. Releases · dnSpy
In addition to downloading the dnspy main binary, we also need to download the mono replacement file needed for debugging unity, which is under the unity branch of the release page.
Here you need to download it according to your game version. As for the method to view the game version, you can find the main executable program of the unity game. Right-click the program properties. The file version found in the details column is your unity version.
After downloading the corresponding replacement file, open the folder corresponding to the unity version, and you will find that there are two folders: Win32 / win64. It depends on the platform your game runs on. If you are not sure about the number of bits in the game, you can first try to replace the 32-bit file. If the game does not work properly, try to replace the 64-bit file.
Replace mono.dll for Debugging
Here you just need to find the root directory of your game, that is, the directory containing the game executable files and game data files. Its structure is usually as follows:
/GameName.exe
/GameName_Data/
/OtherFolder/
Open the folder GameName_Data
, and replace the following file (ignored if it doesn't exist):
<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
Exceptional Case
Given that some games may verify mono.dll to confirm that it has not been replaced, we need to do some operation to load the debug version mono.dll. The solution I came up with is to inject DLL and hook LoadLibraryExW
. When the game loads mono module, we can directly load our own debugging version module. Of course, this method requires that your injection method time must be very early, which is earlier than the loading time of mono module. As for how to implement it, registry injection
and hijacking injection
can be implemented. Here, I will not talk about that.
My implementation is like this
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 Load Failed!");
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); //replace our own debug module
Log("Patched Load Monodebug.dll");
return res;
}
return oriLoadLibraryExW(lpLibFileName, hFile, dwFlags);
}
In this way, we have run the game through the debug version of mono, and the next step is to debug it with dnspy
Debug the Game With DnSpy
First, let's configure the environment variables according to the instructions on the dnspy official website
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.
According to the version of our unity game, we can configure environment variables to customize the debugging port. By the way, the debugging port of dnspy is 55555 by default, if no environment variable is set.
Here we take win10 as an example to teach you how to set environment variables
MyComputer-RightClick-properties
Advanced system settings
Advanced-Environment Variables
Click New
and input the Variables name and Variables value
click OK
and the environment variable is set
Then start the game and keep it running
Launch dnspy
,and you need to note that it must be opened with administrator authority
Then according to the method of static analysis, open the main DLL of the game
Then go to Debug-Start Debugging
and select Unity(Connect)
,you don't need to input IP
as it will be set as localhost
by default
If there is no problem with the settings, the game should be already connected. You can try the add a breakpoint to see if it can be hitted
We can see that the breakpoint is triggered successfully, and the functions such as stack backtracking and variable monitoring work normally
In this way, it is very convenient to view the process of the game through single step, which is more convenient for us to analyze the game.
Reference
Debugging Unity Games · dnSpy/dnSpy Wiki (github.com)
Reprint must be marked with copyright
哈哈,看到截图才发现是中国人,GitHub上的mono.dll 文件好像都不能访问了。你有备份么,有的话可以麻烦你发一份给我么,谢谢~!
By Jiang at August 10th, 2022 at 08:27 pm.
@Jiang
没有备份哦,可能只有你自己去下载源码编译一下了~
By bobh at February 11th, 2023 at 11:13 am.
Hi!
the mono dll binaries are no longer accessible now.
if in case you keep these files, would you please send me a copy through email?
Thank you!
By Hierot at August 16th, 2021 at 01:13 am.