对虚幻引擎 4 项目的 AES 密钥进行逆向工程
这篇文章是针对使用 4.21 制作的虚幻引擎 4 游戏的 Windows 版本而设计的。这可能适用于以前的版本,甚至可能适用于引擎的未来版本。你的旅费可能会改变。
免责声明
注意:我不会帮助您找到特定标题的密钥。所以不要问。这篇文章旨在分享一些关于您通常如何做这样的事情的知识,并且仅用于教育目的。
本指南仅适用于您的个人项目。不要从商业项目中窃取受版权保护的材料,这是非法的。修改商业游戏以进行重新分发是非法的。
网上有很多人定期为已发布的游戏执行此操作,但我还没有找到任何人解释如何实际实现它。我希望尝试阻止这种神圣的知识,并向在线其他人和任何其他游戏开发者公开这是如何完成的,以便我们可以了解这些事情是如何实现的,甚至可能在未来改进游戏数据加密!
在本教程中,我使用加密的 pak 索引构建了自己的测试游戏。我强烈建议您构建自己的加密虚幻引擎游戏来测试这一点作为练习。我这次测试的关键是:
pzq1+cZGipLozoSKnxO/vLeOunJWRFSBPUC+bZiLIsQ=
另外值得注意的是,这只是我解决问题的方法。我不知道其他人是如何实现这一点的(我不知道该问谁,并且在谷歌上找不到任何东西),但我怀疑其他人正在做类似的事情(如果不是完全相同的话)。那么,我们去狩猎吧!
我们正在做什么
一些游戏对其数据文件进行加密,以避免人们看到源资产。这通常是通过加密密钥来完成的。在某些时候,游戏必须在内存中拥有该密钥才能执行解密过程。我们的目标是在此时停止游戏并从内存中读取密钥,以便我们自己使用它。
所需工具
- 要分析的游戏/应用程序
- Epic Games Launcher 中的虚幻引擎 - 下载目标游戏的正确版本(见下文)
- GFlags - https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/gflags
- x64dbg - https://x64dbg.com/
- 耐心和冷静 - https://mynoise.net/NoiseMachines/osmosisDroneGenerator.php
确定虚幻引擎版本
您想检查制作游戏的虚幻版本
找到目标游戏的安装目录并浏览到:
[GameName]/Binaries/Win64/
您将在此目录中看到一个名为 [GameName]-Win64-Shipping.exe 的 exe。右键单击它并选择“属性”。
转到“详细信息”面板并检查文件版本。这将告诉您构建游戏所用的虚幻版本。我构建的这个测试游戏是在 4.21.2 中创建的。如果您可以获得理想的确切版本,最新的 4.21 就可以了,但可能所有 4.21.x 版本对此都足够相似。
我们需要钥匙吗?
首先,请检查您是否确实需要密钥。找到目标游戏的安装目录并浏览到:
[GameName]/Content/Paks/
该目录下应该有一个pak文件。这包含了游戏的主要内容。某些游戏可能包含多个 pak 文件。您需要弄清楚哪个文件包含主要的游戏资源,其他文件可能是 DLC 或 mod。
虚幻引擎附带了一个名为 UnrealPak.exe 的工具。您可以在以下位置找到此内容:
[Unreal Engine Install]/Engine/Binaries/Win64/UnrealPak.exe
您可以使用它对我们上面找到的 pak 文件运行命令。
例如,我们可以测试该文件,看看是否可以打开它,传入 pak 文件的完整路径:
[Unreal Engine Install]/Engine/Binaries/Win64/UnrealPak.exe -Test [PAK FILE PATH]
如果您收到如下错误消息:
断言失败:Key.IsValid()
那我们就出发吧!我们需要提供一个有效的密钥,所以让我们去寻找它。如果您收到不同的错误,则可能有不同的(或额外的)问题需要解决,并且可能不会从这篇博客文章中获得太多运气。
如果您获得了一长串资产名称,那么恭喜您!您的 pak 文件未加密。您现在还拥有提取文件的正确工具,请尝试使用“-Extract”功能而不是“-Test”。
进行设置
启动虚幻引擎并使用与构建目标游戏相同的版本创建一个新的代码项目。重要的是,您使用的引擎版本与用于构建的版本相匹配,以便我们将阅读的代码与游戏相匹配。
创建项目后,在 Visual Studio 中打开解决方案。这将允许您浏览虚幻引擎的源代码。我们想要找到引擎从哪里获取解密密钥。
在 4.21 中,使用函数“FPakPlatformFile::GetPakEncryptionKey”获取解密密钥。这在 IPlatformFilePak.cpp 的 DecryptData() 函数中调用。
使用您最喜欢的源代码软件(我正在使用 VisualAssist - 如果您想一直生气,Intellisense 就可以),查找如何调用此函数。我们正在寻找将在 Shipping exe 中的字符串,以用作我们在实际游戏 EXE 中查找的地标。
这里 VisualAssist 告诉我有几个函数调用了我们的 DecryptData 函数。但我们主要感兴趣的是 LoadIndex。这是 pak 文件中包含资产列表的部分。它首先由引擎加载,并且通常是唯一实际加密的部分。
通常只有索引被加密,因为所有这些加密/解密需要很长时间才能完成,并且开发人员不希望显着增加所有游戏资产的加载时间。仅加密索引仍然会使文件在没有密钥的情况下毫无用处,但使解密时间可以忽略不计。
现在我们来看看 LoadIndex 函数。我们可以看到函数顶部有一个致命错误。这对我们很有用,因为致命错误不会从发布版本中删除。正常的日志行通常会从运输版本中删除,因为它们是不需要的,只会减慢游戏速度,但致命错误会出现在最终的游戏中,所以我们现在有一些可以查找的文本。
我们现在要开始调试目标游戏并查找“pak 文件中损坏的索引偏移量”。细绳。
附加到游戏
大多数游戏都有某种启动器或第三方集成,会阻止您直接启动游戏。为了避免这种情况,我们将使用一个名为 GFlags 的工具。GFlags 允许我们在任何 EXE 启动后立即将调试器附加到该 EXE。
找到您安装的 GFlags 并运行它。默认情况下它会在这里:
C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\gflags.exe
导航到图像文件选项卡。
您要在此处输入游戏 EXE 的名称。这是名称中带有“-Win64-Shipping”的一个。例如,我的游戏“RealGame”名为“RealGame-Win64-Shipping.exe”。这应该只是文件名和扩展名,而不是完整路径。
按 TAB 刷新 UI 并转到“调试器”。勾选此框并输入 x64dbg 中 x64dbg.exe 的完整路径名。这应该位于 x64dbg\release\x64 或下载中的类似位置。
单击“应用”并启动目标游戏。您应该获得 x64dbg 而不是游戏。恭喜!我们现在“连接”并实时调试游戏。
找出我们的错误
因此,之前我们识别了字符串“pak 文件中的索引偏移量已损坏”。就在我们的 DecryptData 函数之前被击中。
在主窗口中右键单击并导航至“搜索 -> 所有模块 -> 字符串引用”
下一个窗口底部有一个进度条。最好等它完成后再继续!完成后,输入“pak 文件中的索引偏移已损坏”。进入搜索:底部的框。
成功!我们有一些点击。它们位于相同的粗略内存区域中,因此看起来实际上是相同的代码块。双击其中一个即可跳转到 EXE 的该区域进行查看。
此过程的一个重要部分是大致了解这里发生的情况。这是 EXE 的低级代码。需要注意的重要事项:
- 以 j 开头的命令 - 这些命令通常会“跳转”到其他地方
- “call” - 将跳转到特定模块中的函数
在上面的屏幕截图中,我们可以看到 x64dbg 非常有帮助,并向我们展示了窗口左侧的跳转。我们可以看到,如果条件为真,以“jge”开头的行将跳过我们的错误消息(jge 表示“如果大于或等于则跳转”)。因为这直接跳过了我们的错误消息,所以看起来我们处于正确的位置。这些行与我们之前看到的代码完全对应:
这就是现在谜题的主要部分。我们将逐步调试这个游戏,遵循 EXE 的结构并将其与 UE4 进行比较,以找出我们所处的位置。一旦我们进入 DecryptData 函数,我们就可以查找内存值并获取密钥。
单击窗口一侧的小气泡将设置断点。您需要在我们找到的代码的该区域内设置一个断点,然后单击“运行”按钮,直到到达断点为止。
注意:默认情况下,x64dbg 在许多不同的事件上都会中断。我在“选项”->“设置”->“中断:”下关闭所有这些。这将节省继续越过许多断点的麻烦。
让我们更详细地看一下 LoadIndex 函数。
我们这里分为三个主要部分。
- 数组初始化
- 可选的索引解密
- SHA检查
第一个块相当简单,在 x64dbg 中看起来相对简单。
我们的解密步骤将在一个相当小的跳转内,并在某处调用模块函数。这应该很容易发现。
SHA 哈希有一堆内存设置、一个巨大的分支和一个 for 循环(当你处于如此低的级别时,这只是一个复杂的跳转)。
让我们再看看我们的 x64dbg。
看起来我们有一个小跳跃,另一个小跳跃,然后是一堆复杂的跳跃。
两次跳是怎么回事?最后一节明明是SHA哈希开始的,但是前面只有一个if语句?
上面我们调用了“AddUninitialized”。这就是所谓的“内联函数”。当它被调用时,它的执行基本上被注入到调用它的地方,而不是在内存中跳转。
正如我们所看到的,这个函数有自己的 if 语句。因此,如果它内联到调用函数中,它将在那里添加这个分支。这解释了我们看到的第一个分支。因此,我们可以假设第二个小分支是进行加密的分支。您可以看到我已经在模块函数调用处添加了一个断点,因为这是我要开始调试的地方。
调试
现在我们已连接,正在调试,并且位于正确的位置。确保我们在模块调用处得到了断点,并且我们已经运行了应用程序直到遇到它。
现在我们要运行“Step Into”(F7)。这将跳转到我们当前正在执行的功能,然后立即再次停止游戏。这将带我们到达 DecryptData 函数在 EXE 中的位置。
现在让我们再次检查我们的代码。
我们有一个关键变量初始化,然后获取它,然后解密数据。check() 应该在 Shipping 版本之外进行编译,因此我们可以忽略这一点。
因为变量初始化可能相当复杂,所以让我们从 EXE 代码的底部开始并进行备份。
在顶部我们有多个“push”命令。这些通常指示函数的状态。
在底部我们有一个“ret”命令。这通常是函数结束(返回)。
向上工作,有一些模块调用,我认为这些是最终的 DecryptData 函数调用。C++ 调用的 DecryptData 函数非常小,并且调用 DecryptData 的不同变体,因此编译器可能会自行将其内联到此处以进行优化。编译器做了很多优化,这将使最终的 EXE 看起来与原始的 C++ 不同,准备好通过大量的猜测和预感来解决这个问题,这通常是错误的,导致您回溯并重新开始。
现在的目标是逐步了解并开始查看我们的记忆。上面我添加了一个断点,我认为我们正在获取解密密钥。所以我继续运行,直到达到那个点,然后我点击“Step Over”(F8)调试按钮来完整运行模块功能(我不关心它如何获取密钥,我只是想让它获取它)。
一旦我们跨过去,请注意 x64dbg 右侧的部分(我已经截取了上面跨过那一刻的屏幕截图)。这向我们展示了我们的寄存器(如果您不知道那是什么,请继续操作,我们会没事的)。以红色突出显示的内容在最后一个调试步骤中发生了变化。右键单击每个并选择“Follow in Dump”,然后查看 x64dbg 底部的“Dump 1”窗口,看看里面有什么。
在这里,我的 RCX 寄存器有我想象中的游戏的正确密钥。即“A73AB5F9C6468A92E8CE848A9F13BFBCB78EBA72564454813D40BE6D988B22C4”。
这是十六进制格式的,您需要将其转换为 Base64 以用于 Unreal。我刚刚在 Google 上搜索了“Hex 到 Base64 编码器”,发现了很多相关网站。
繁荣!这是我们的 AES 密钥。
如何使用
UnrealPak.exe 接受“crypto.json”参数。您需要在某处创建此文件并添加以下内容:
{
"$types": {
"UnrealBuildTool.EncryptionAndSigning+CryptoSettings, UnrealBuildTool, Version=4.0.0.0, Culture=neutral, PublicKeyToken=null": "1",
"UnrealBuildTool.EncryptionAndSigning+EncryptionKey, UnrealBuildTool, Version=4.0.0.0, Culture=neutral, PublicKeyToken=null": "2"
},
"$type": "1",
"EncryptionKey": {
"$type": "2",
"Name": null,
"Guid": null,
"Key": "pzq1+cZGipLozoSKnxO/vLeOunJWRFSBPUC+bZiLIsQ="
},
"SigningKey": null,
"bEnablePakSigning": false,
"bEnablePakIndexEncryption": true,
"bEnablePakIniEncryption": true,
"bEnablePakUAssetEncryption": false,
"bEnablePakFullAssetEncryption": false,
"bDataCryptoRequired": true,
"SecondaryEncryptionKeys": []
}
在这里您可以看到密钥以及有关如何使用它的一些设置。我的测试游戏对索引和ini 文件(配置文件)进行了加密。如果您运气不好并且开发人员已加密了他们的所有文件,您将需要使用这些。
像以前一样测试您的 pak 文件,但添加 -cryptokeys= 选项
[Unreal Engine Install]/Engine/Binaries/Win64/UnrealPak.exe -Test [PAK FILE PATH] -cryptokeys=[LOCATION OF crypto.json]
我建议 UModel 对结果数据进行实际处理。http://www.gildor.org/en/projects/umodel
祝你好运!