xiaozongpeng 发表于 2022-6-7 05:30

UE4 Pak 加密与解密调研

(题图来自于:https://cybernews.com/resources/what-is-aes-encryption/)
前言

近期需要处理一下项目的反破解问题,所以对 UE4 Pak 的加密解密方式进行了一下调研。本文总结了一下关于互联网上常见的 UE4 Pak 破解方法,结合简要的代码分析,提出了一些关于加密的修改建议,并实现在所在项目中。有兴趣的同学可以参考一下,应用到自己的项目上。
阅读本文需要对以下知识点有一定的了解:

[*]大致知道 UE4 版本构建的流程;
[*]知道 UE4 中 Pak 加密配置的方法;
[*]对加密解密有一定概念上的认识。
另外本文中使用的 UE版本为 4.27.2,其他版本应该也不会有太大的差异。
UE4 Pak 加密解码代码分析

加密算法概述

UE4 中使用的 Pak 加密算法默认使用的是 AES 加密算法,AES 算法是一种对称加密算法,在数据量较大的时候也能保持较高的性能。但是缺点就是加密解密都必须使用相同的秘钥,这也就给了 Pak 破解留了一个很大的空子,后面会详细介绍如何利用这个空子来进行破解。关于算法的详细算法,本文不会介绍,可以参考最后一节参考文档里面的参考资料。
Pak 在 UE4 中使用的代码来源于一个开源的 Rijndael 实现,在 AES.cpp 的文件注释中,Epic 认为当前使用的算法过于简单,在秘钥未知的情况下依然有一定的数据泄露风险,建议使用 CryptoPP(Crypto++,https://cryptopp.com/) 的实现。注释内容我摘录在下面:
// This is using the reference implementation of rijndael encryption algorithm
// http://www.efgh.com/software/rijndael.htm

// This approach has the shortcoming that it encrypts and decrypts each 128-bit block separately. If the plaintext contains identical 128-byte blocks, as many text files do,
// the blocks will be encrypted identically. This makes some of the plaintext structure visible in the ciphertext, even to someone who does not have the key.
// The usual practice is to combine each block after the first with the previous blocks (usually by some kind of XOR operation) before encrypting it. This hides repeated
// blocks very effectively, but it can wreak havoc if even one block of ciphertext is corrupted. The corrupted block AND ALL SUBSEQUENT BLOCKS will become unreadable

// The approach used in CryptoPP is better, but much more complicated.

// It may be useful to integrate this into a class derived from FArchive to allow streaming encryption/decryption - but that's a mission for another day.
从参考资料中可以看出来,UE4 中的 AES 实现是其中最简单的一种模式,也就是 ECB(Electronic Codebook Book) 模式。这种模式没有对密文进行再处理和反馈循环,会导致一定的被主动攻击的风险。
首先分析一下涉及到 Pak 文件加密解密的功能,这样可以在代码中快速定位到其代码位置:

[*]游戏运行期解密:这是必然会存在的一个步骤,否则游戏在运行期就无法使用已经加密的 Pak 数据了。
[*]UnrealPak 生成 Pak 时加密:在 UnrealPak 构建时,使用和运行期一致的算法和秘钥对 Pak 进行加密。
[*]UnrealPak 验证/解压 Pak 时解密:在 UnrealPak 进行解包时,或者使用 -Test、-List 指令时,都会对 Pak 进行解密。
游戏运行期解密

游戏运行过程中,对 Pak 文件解密的主入口在 IPlatformFilePak.cpp 的 DecryptData() 中:
void DecryptData(uint8* InData, uint32 InDataSize, FGuid InEncryptionKeyGuid)
{
        if (FPakPlatformFile::GetPakCustomEncryptionDelegate().IsBound())
        {
                FPakPlatformFile::GetPakCustomEncryptionDelegate().Execute(InData, InDataSize, InEncryptionKeyGuid);
        }
        else
        {
                SCOPE_SECONDS_ACCUMULATOR(STAT_PakCache_DecryptTime);
                FAES::FAESKey Key;
                FPakPlatformFile::GetPakEncryptionKey(Key, InEncryptionKeyGuid);
                check(Key.IsValid());
                FAES::DecryptData(InData, InDataSize, Key);
        }
}
可见其中判断了 CustomEncryptionDelegate,利用这一点,我们后面可以使用自定义的方式来拦截解密操作。代码的后半部分 else 是默认路径,会进入 AES 的解密函数。
UnrealPak 生成 Pak 时加密

在 PakFileUtilities.cpp 文件中,搜索 FAES::EncryptData,可以得到三个不同调用的位置,为在不同情况下加密的入口。
UnrealPak 验证/解压 Pak 时解密

对应的位置与游戏运行期解密的位置相同,但是在使用上的处理方式稍有不同,后文会介绍。
UE4 Pak 加密配置

配置方法

配置方法可以参考 Packaging 官方文档 中的 Signing and Encryption 部分,配置如下图所示(原图来自官方文档):



一般常用的是其中的 Encrypt Pak Index,即只对 Pak 的索引区进行加密,这样既可以保护 Pak 不被解包,又可以保持较高的性能。
秘钥分析

配置完成后,秘钥会被写入 DefaultCrypto.ini 文件中,其中的 EncryptionKey 表示使用的秘钥,下面是一个例子

EncryptionKey=Wq47k740EcUlVdI8jEcAVP+YfbRPDLCkNlazkU144ZI=配置中使用的是经过 Base64 编码后的秘钥,要查看原始秘钥,需要进行 Base64 解码,在 Mac 下可以使用 base64 命令进行解码。
(base)   ~ echo Wq47k740EcUlVdI8jEcAVP+YfbRPDLCkNlazkU144ZI= | base64 -Dd| od -t x1
0000000    5aae3b93be3411c52555d23c8c470054
0000020    ff987db44f0cb0a43656b3914d78e192
0000040原始秘钥不是ASCII字符,所以需要使用 od 命令将数据用 16 进制打印出来。可以发现秘钥长度为 256 bit。
Pak 破解方法分析

破解思路

在参考资料中有两篇如何破解 Pak 的方法,其中的破解思路基本一致,大致如下:

[*]查看可执行文件中的 UE 版本号,以确定使用的 UnrealPak 版本
[*]适应对应版本号的 UnrealPak 进行测试解压,UnrealPak -Test
[*]如果 UnrealPak 没有任何错误,则可以直接解包,无需破解
[*]如果 UnrealPak 返回没有秘钥等错误,则需要破解

[*](可选)使用 GFlag 拉起调试器(这个用法不明,我没有用过,说是可以避免有些情况下进程不会在调试器下正确断点在入口的问题)
[*]使用 x64dbg 进行调试
[*]根据特定文本定位解密代码大致位置,例如解密失败报错信息等等
[*]根据代码逻辑定位 procedure call 的大致结构及走势(需要一定的 x86 汇编知识)
[*]确定秘钥解密位置,以及使用解密后秘钥的函数入口
[*]在最终调用 decrypt 的时候根据传入参数地址找到解密后的秘钥

[*]使用 UnrealPak 配合解密后的秘钥或者配置好的 crypto.json 文件对 Pak 进行解包
网上有人做出了自动破解的工具,我尝试了一下没有成功,有兴趣的同学可以到参考资料小节中找一下地址。
破解结果

仿照着破解教程,我使用了一个测试工程,按照 Development 打包成 WindownsNoEditor 版本,然后使用 x64dbg 进行调试。由于我的工程中带有 Symbol,所以相对来说会有一些代码上的提示,比反编译 Shipping 构建的工程要简单一些。
最后得到的结果如下图所示,上面的部分是我解码使用的秘钥数据,下面是 x64dbg 的内存窗口,可以明显看到内存中的秘钥与使用的秘钥完全相同,破解成功。


这也就是为什么在上文中说,使用对称秘钥加密方法必然会存在的问题,无论如何都会在内存中解密,解密就会需要秘钥,所以只要有办法查看内存,就一定能发现秘钥的内容。

反破解思路

由于最终无论如何都要在内存中解密,所以秘钥是一定会出现在内存中的,反破解的关键在于以下三点:

[*]修改相关文本字符串的内容,使其无法在二进制文件中找到已知的字符串来定位大致代码位置;
[*]修改解密代码逻辑,使其无法参考UE源代码来确定 procedure call 的大致逻辑;
[*]修改加密解密算法,使其即使获得了秘钥也无法通过公版 UnrealPak 来解密 Pak。
其中 :

[*]1 较为简单,可以直接实施,但是要注意不能将修改后的字符串进行输出,以免被获得后在代码中搜索(本文略);
[*]2 略复杂,调制代码结构能带来很多问题(本文不考虑);
[*]3 需要学习 AES 算法的实现,或者替换算法的方法,有一些成本。但是是目前来看比较好的办法(见下方的方案)。
实现方案与结果

实现方案

由于只有在 Windows 版本上才会有 Pak 被破解的风险,而且UE4自带的 CryptoPP 也只提供了 Win64 版本,所以主要思路是:

[*]在 Windows 版本上使用 CryptoPP 进行加密解密,在其他平台上保持不变;
[*]在 CryptoPP 中选择 TwoFish 或者 Serpent 作为 AES 的替代品,主要是这两者与 AES 相差不大,秘钥与加密方式都比较统一,修改方式比较简单。关于这三者的区别,可以查看参考资料中的链接;
[*]三个平台使用不同的秘钥,避免 Windows 版本被获取秘钥以后,可以解开 Android 和 iOS 的 Pak 文件。
根据上述分析就可以开始进行实现了,实现过程中注意以下细节:

[*]可以在运行期解密和 UnrealPak 解包时,利用 FPakPlatformFile::GetPakCustomEncryptionDelegate 来注册一个自定义的解密方法,将 CryptoPP 在其中实现。但是游戏运行和UnrealPak 并不是同样的入口,所以需要两者单独进行注册。
[*]加密的位置较为分散,所以统一为一个单独的函数,在函数中进行统一判断处理。
[*]需要区分打包平台,在 Pak 创建时,可以利用已有的 -Platform 参数进行判断
本文不会提供具体的实现代码,按照上述分析,自己手动实现一下也非常简单。
实现后的测试结果

当三个平台均打开 Pak Index 加密时,使用各种情况对构建后的 Pak 进行解密测试:
平台使用编译 UnrealPak 解密(不带 Crypto.json)使用编译 UnrealPak 解密(带 Crypto.json 无 -Platform=Windows)使用编译 UnrealPak 解密(带 Crypto.json 带 -Platform=Windows)使用Launcher 版本 UnrealPak 解密(无 Crypto.json)使用Launcher 版本 UnrealPak 解密(带 Crypto.json)Windows失败失败成功失败失败iOS失败成功失败失败成功Android失败成功失败失败成功可以看到,使用公版的 UnrealPak 已经无法解包使用 Windows 版本的 Pak 文件,但是依然可以解包 Android 和 iOS 平台的 Pak 文件,已经达到了我们要实现的目的。
最后补充一个小细节:CryptoPP 中的 AES 算法和 UE 中原本使用的 AES 算法是兼容的,如果使用了 CryptoPP 中的 AES 算法,也是可以正常解密使用 UE 中 AES 算法加密的 Pak 的。
参考资料


[*]UE 中使用的 AES 算法实现参考:

[*]http://www.efgh.com/software/rijndael.htm

[*]破解 Pak 参考

[*]https://blog.jamie.holdings/2019/03/23/reverse-engineering-aes-keys-from-unreal-engine-4-projects/
[*]https://stillu.cc/infosec/2021/03/01/obtaining-unreal-pak-decryption-key/

[*]破解工具

[*]https://github.com/devinacker/UnrealKey
[*]使用工具时遇到了问题,与这个 Issue 相同: https://github.com/devinacker/UnrealKey/issues/3

[*]AES 算法参考

[*]Wikipedia: https://en.wikipedia.org/wiki/Advanced_Encryption_Standard
[*]AES 参数详解: https://www.csdn.net/tags/MtTaEgwsNTQyMjM1LWJsb2cO0O0O.html
[*]AES加密算法的详细介绍与实现: https://blog.csdn.net/qq_28205153/article/details/55798628
[*]AES 算法详解: https://www.cnblogs.com/chenshikun/p/11667438.html
[*]AES、TwoFish 和 Serpent 的区别: https://blog.csdn.net/cncrypt/article/details/51565389
[*]AES五种加密模式:https://www.cnblogs.com/starwolf/p/3365834.html

页: [1]
查看完整版本: UE4 Pak 加密与解密调研