最近想要逆向一个Unity游戏,游戏使用的Unity版本是2020.3.17
无奈dnSpy官方仓库提供的版本直到2019.2.1,想要2020的dll只能自己生成,踩了非常多的坑,特地记录一下
首先进入dnSpy-Unity-mono官方仓库,然后跟着它的README一步步走。
第一步
Pull in the latest Unity mono.dll source code (either git pull if you have it or git clone https://github.com/Unity-Technologies/mono.git)
Get this repo and make sure master and dnSpy branches are at the latest commit (git pull in both branches)
首先要创建一个新的本地仓库,用来存放Unity Mono的官方代码
git clone https://github.com/Unity-Technologies/mono
然后还要再创建一个本地仓库,用来存放dnSpy-Unity-mono的仓库,注意master分支和dnSpy两个分支都要拉,这就是第一个坑,为什么后面会细说
第二步
Compile umpatcher in this repo (you need VS2019 or later and .NET Core SDK 3.0 or later installed)
找到umpatcher.sln,直接生成就行。建议用VS2019以上的版本,如果需要.NET SDK,可以网上找找怎么安装,这个还是比较简单的。
第三步
Download the correct Unity editor version
Either install the Unity editor or extract the necessary .dlls with extractmono.bat
去下载一个对应版本的Unity Editor,比如我就需要下载一个Unity Editor2020.3.17
也可以使用它提供的extractmono.bat。我用的是第一种,后面那种没试过。
不过这一步的目的是为了获取时间戳,其实有更简单的方法,等下在第四步讲
第四步
If using extractmono.bat
.\extractmono.bat C:\Users\Unfou\Downloads C:\Users\Unfou\Desktop\mono both
Otherwise, if installing Unity editor:
umpatcher --timestamp “C:\path\to\the\correct\version\mono.dll”
具体的方法很长,建议看原文。核心目的就是获取游戏使用的mono.dll的生成时间戳,等下会用。
它提供了两种方法,一是下载7z,然后用仓库里的extractmono.bat;二是下载对应的Unity Editor,然后找到安装目录中的dll,用umpatcher去看时间戳
但其实有更简单的方法,既然你想用dnSpy断点调试这个游戏,那你一定装了dnSpy,用dnSpy打开游戏原本的mono.dll就可以看到时间戳了:
也就是说Unity2020.3.17版本mono-2.0-bdwgc.dll的时间戳就是(2021/7/13 20:18:06)
第五步
Check out the correct version branch in the Unity mono repo, eg. if it’s v5.4.3, the branch is called unity-5.4. Branches ending in -mbe use .NET 4.x assemblies.
Use git branch -a to see all remote branches
git checkout unity-5.4 (or whatever version you need)
git pull (make sure it has the latest stuff)
gitk to start a UI
Find the closest merge by comparing the merge date with the timestamp reported by umpatcher above
Remember the commit hash, you’ll need it later
这一步核心目的是通过时间戳找到对应的原版Mono分支
查找官方Mono的所有分支,并切到你想要的分支,比如我就需要
git checkout unity-2020.3-mbe
然后打开gitUI(当然你如果足够有耐心也可以用命令行找,git log就行)找到对应时间戳的版本,记下它的commit hash比如说2020.3.17的就是 72dda61cafa8e84fd78c01eab954e9690cd8d3cb
第六步
Run umpatcher again to patch the code and commit it to the dnSpy-Unity-mono repo
umpatcher 5.4.3 aa8a6e7afc2f4fe63921df4fe8a18cfd0a441d19 “C:\path\to\Unity-mono” “C:\path\to\dnSpy-Unity-mono”
现在你知道了2020.3.17对应的mono的commit hash,你可以把分支回滚到那个版本,你就获得了mono-2.0-bdwgc.dll的源代码,只要再改一改就可以让游戏对你的调试敞开大门,仿佛马上就能大功告成了
BUT,接下来是一串大坑
首先你运行上面的命令,umpatcher {unity version} {commit hash} {mono path} {dnSpy-mono path},然后你就会得到一个报错
Git submodule update external/bdwgc failed with error code 1
第六步(一)
为什么呢?可以看看umpatcher中的源码
public void SubmoduleUpdate(string path) {
int result = Exec.Run(repoPath, gitPath, $"submodule update {path}");
if (result != 0)
ThrowError($"Git submodule update {path} failed with error code {result}");
}
然后到Unity Mono仓库中跑一下git submodule这个命令,可以发现原本的submodule配置的下载路径是git://,但是自从2021年开始git官方就不使用git://而使用https://了。修复的办法也很简单,在Unity Mono仓库下输入
git config --global url.“https://”.insteadOf git://
就可以了
然后你继续运行 umpatcher {unity version} {commit hash} {mono path} {dnSpy-mono path},又会得到一个报错
File ‘\dnSpy-Unity-mono\dnSpy-Unity-mono-v2020.x-V40.sln’ doesn’t exist
第六步(二)
这个报错信息比较直白,很明确的跟你说了缺少dnSpy-Unity-mono-v2020.x-V40.sln,因为作者只维护到2019版本,自然没有后面的sln文件。至于修复也很简单,拷贝一个dnSpy-Unity-mono-v2019.x-V40.sln,改一下文件名,然后编辑一下,把里面没用的配置删干净,删成这样就行了:
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.32630.194
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "dnSpyFiles", "dnSpyFiles", "{BCDFE47C-A4D8-4F5C-BCED-4AEBDC711E34}"
ProjectSection(SolutionItems) = preProject
dnSpyFiles\dnSpy.c = dnSpyFiles\dnSpy.c
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {6F2773ED-F031-4BBF-BE69-53D43FA453CC}
EndGlobalSection
EndGlobal
但非常坑的是,这时候你再运行umpatcher,还是会报错
Directory unity-mono-2020.3.17-mbe already exists
第六步(三)
我们再看一下umpatcher的源码:
public void Patch() {
if (Directory.Exists(dnSpyVersionPath))
throw new ProgramException($"Directory {dnSpyVersionPath} already exists");
CopyOriginalUnityFiles();
UpdateReadMe();
MergeMasterIntoDnSpy();
PatchOriginalFiles();
}
之前脚本已经生成了unity-mono-2020.3.17-mbe目录,因此会报错,但是简单的删除文件夹也是不行的。在sln报错之前,umpatcher已经做了好几步操作,包括
1)回滚原版Mono仓库的代码到commit hash对应分支
2)更新原版Mono仓库的子模块
3)dnSpy-Mono仓库切换到master分支,拷贝原版Mono仓库的部分代码并提交
4)更新master分支的ReadMe文件并提交
5)切换到dnSpy分支并合并master分支的代码
可以看到它会切换分支,这就是之前为什么要把两个分支都拉到最新的原因。
这些操作里面,345都需要回滚,如果修改Patch函数,把PatchOriginalFiles前面都注释掉,可以只回滚5。
回滚完分支以后,别忘了重新建一下dnSpy-Unity-mono-v2019.x-V40.sln并commit,因为umpatcher会检查git clean,如果分支不干净会报错。
回滚代码以后再运行umpatcher以后依旧会报错,别怕,这是最后一个了
Line is … but expected line is …
第六步(四)
看一下报错的位置:
void Patch_mono_mini_debugger_agent_c()
{
...
{
int index = textFilePatcher.GetIndexesOfLine(line => line.Text.Contains("case CMD_THREAD_GET_FRAME_INFO: {")).Single();
index = textFilePatcher.GetIndexOfLine(line => line.Text.Contains("tls = (DebuggerTlsData *)mono_g_hash_table_lookup (thread_to_tls, thread);"), index);
Verify(lines[index + 1].Text, "\t\tmono_loader_unlock ();");
Verify(lines[index + 2].Text, "\t\tg_assert (tls);");
lines[index + 2] = lines[index + 2].Replace("\t\tif (!tls)");
textFilePatcher.Insert(index + 3, "\t\t\treturn ERR_INVALID_ARGUMENT;");
}
}
也就说Mono中有一行代码与作者的预期不符了,是哪一行呢?
case CMD_THREAD_GET_FRAME_INFO: {
...
mono_loader_lock ();
tls = (DebuggerTlsData *)mono_g_hash_table_lookup (thread_to_tls, thread);
mono_loader_unlock ();
if (tls == NULL) // g_assert (tls);
return ERR_UNLOADED;
}
看来 if (tls == NULL) 这行在mono之前的版本应该是 g_assert (tls);,后来被官方修改了
因此只要把umpatcher中这一块整个删掉就行了
最后再运行 umpatcher {unity version} {commit hash} {mono path} {dnSpy-mono path},终于弹出了
Patch Unity files (unity-mono-2020.3.17-mbe)
这个时候unity-mono-2020.3.17-mbe工程就算生成完毕了
第七步
最后一步就是用新的工程生成可以dubug的mono-2.0-bdwgc.dll
dnSpy-Unity-mono-vZZZZ.x-V40.sln (Unity with .NET 4.x assemblies), where ZZZZ is the major version number, eg. 2017, 2018, …
Use configuration Release
Use platform x86 or x64
一般来说,这一步会让你重定向工程,不重定向的话下载它需要的Windows SDK 和 MSVC工具集也可以,随个人爱好,然后生成选项选Release,x86或x64看游戏是几位的,直接生成libmono-dynamic就行,生成出来的mono-2.0-bdwgc.dll会在dnSpy-Mono仓库的builds目录下
接下来,用这个mono-2.0-bdwgc.dll替换掉游戏原本的dll就可以了
然后,你运行游戏就会发现
游戏Crash了!!!
没错,接下来还有一串坑,且听下回分解
【逆向工程】生成能够用dnSpy调试的mono-2.0-bdwgc.dll(二)