x64dbg 获取 UE4 虚幻引擎游戏的资源包密钥
最近发现一款很好玩的 UE4 游戏,玩着玩着就想提取游戏里的一些资源出来折腾。下了个提取器打开一看,资源包竟然是加密的!好嘛,反正 UE4 引擎本身是开源的,算法逻辑什么的都看得到。想必不会太难。良心游戏开发商还留下了 PDB 数据库。之前我用 x64dbg 只玩过改跳转过验证,这次试试结合符号的调试。
首先,虚幻引擎的资源包格式为 `.pak`。使用 16 进制编辑器把资源包打开以后可以看到,有些资源内容似乎没有加密:
```
72 03 19 d9 03 19 01 b8 03 19 41 53 53 45 52 54 49 4f 4e 4d 4f 44 45 4c 53 54 52 55 45 46 4f 52 43 45 53 01 57 65 02 31 68 02 84 62 65 02 31 62 68 02 84.r..ù...¸..ASSERTIONMODELSTRUEFORCES.We.1h..be.1bh..
1e 01 95 50 52 4f 56 45 01 95 54 52 55 45 03 1e 01 95 46 4f 52 43 45 07 0f 62 65 02 31 62 68 02 84 02 34 06 1b 04 08 02 3a 06 1b 04 08 02 ab 03 10 4f 46....PROVE..TRUE....FORCE..be.1bh...4.....:.....«..OF
```
所以我猜想,只有资源索引是加密的。搜索找到 UE4 源码里加载 `PAK` 文件的函数:
```cpp
void FPakFile::LoadIndex(FArchive* Reader)
{
// ...
// Decrypt if necessary
if (Info.bEncryptedIndex)
{
DecryptData(IndexData.GetData(), Info.IndexSize, Info.EncryptionKeyGuid);
}
// ...
}
```
证明猜想。继续查看 `DecryptData` 函数:
```cpp
void DecryptData(uint8* InData, uint32 InDataSize, FGuid InEncryptionKeyGuid)
{
SCOPE_SECONDS_ACCUMULATOR(STAT_PakCache_DecryptTime);
FAES::FAESKey Key;
FPakPlatformFile::GetPakEncryptionKey(Key, InEncryptionKeyGuid);
check(Key.IsValid());
FAES::DecryptData(InData, InDataSize, Key);
}
```
第一直觉肯定是去追 `FPakPlatformFile::GetPakEncryptionKey` 来看密钥是如何获取的了。然而这个函数用了委托,在反汇编下比较难分析。所以我转而查看 `FAES::DecryptData` 函数:
```cpp
// 本行防止 Markdown 鬼畜高亮
void FAES::DecryptData(uint8* Contents, uint32 NumBytes, const FAESKey& Key)
{
checkf(Key.IsValid(), TEXT("No valid decryption key specified"));
DecryptData(Contents, NumBytes, Key.Key, sizeof(Key.Key));
}
```
这个函数首先检查密钥是否有效,如无效则会断言报错。将游戏文件载入 x64dbg,搜索报错字符串,定位到如下逻辑:
```asm
00007FF78CF60750 | 48:895C24 08 | mov qword ptr ss:, rbx
00007FF78CF60755 | 48:897424 10 | mov qword ptr ss:, rsi
00007FF78CF6075A | 57 | push rdi
00007FF78CF6075B | 48:83EC 20 | sub rsp, 20
00007FF78CF6075F | 49:8BD8 | mov rbx, r8
00007FF78CF60762 | 8BFA | mov edi, edx
00007FF78CF60764 | 48:8BF1 | mov rsi, rcx
00007FF78CF60767 | 45:33C9 | xor r9d, r9d
00007FF78CF6076A | 49:8BC0 | mov rax, r8
00007FF78CF6076D | 0F1F00 | nop dword ptr ds:, eax
00007FF78CF60770 | 8338 00 | cmp dword ptr ds:, 0
|<=== 00007FF78CF60773 | 75 4D | jne game.7FF78CF607C2
| 00007FF78CF60775 | 41:FFC1 | inc r9d
| 00007FF78CF60778 | 48:83C0 04 | add rax, 4
| 00007FF78CF6077C | 41:83F9 08 | cmp r9d, 8
| 00007FF78CF60780 | 72 EE | jb game.7FF78CF60770
| 00007FF78CF60782 | 4C:8D0D D79F7E02| lea r9, qword ptr ds:
| | L"No valid decryption key specified"
| 00007FF78CF60789 | 41:B8 9E040000 | mov r8d, 49E
| 00007FF78CF6078F | 48:8D15 4AA07E02| lea rdx, qword ptr ds:
| 00007FF78CF60796 | 48:8D0D 7B9F7E02| lea rcx, qword ptr ds:
| 00007FF78CF6079D | E8 AEE70000 | call game.7FF78CF6EF50
| 00007FF78CF607A2 | 4C:8D0D 97A07E02| lea r9, qword ptr ds:
| | L"No valid decryption key specified"
| 00007FF78CF607A9 | 41:B8 9E040000 | mov r8d, 49E
| 00007FF78CF607AF | 48:8D15 FAA07E02| lea rdx, qword ptr ds:
| 00007FF78CF607B6 | 48:8D0D 53A17E02| lea rcx, qword ptr ds:
| 00007FF78CF607BD | E8 9EBAFFFF | call game.7FF78CF5C260
|===> 00007FF78CF607C2 | 41:B9 20000000 | mov r9d, 20
00007FF78CF607C8 | 4C:8BC3 | mov r8, rbx
00007FF78CF607CB | 8BD7 | mov edx, edi
00007FF78CF607CD | 48:8BCE | mov rcx, rsi
00007FF78CF607D0 | 48:8B5C24 30 | mov rbx, qword ptr ss:
00007FF78CF607D5 | 48:8B7424 38 | mov rsi, qword ptr ss:
00007FF78CF607DA | 48:83C4 20 | add rsp, 20
00007FF78CF607DE | 5F pop rdi
00007FF78CF607DF | E9 0C000000 | jmp <game.FAES::DecryptD
```
如果 `00007FF78CF60770` 处的比较中,`rax` 指向的内存地址值**不**为零,`jne` 跳转才会执行。
这个跳转便是用来判断密钥是否有效的部分。如果密钥起始处不为空,逻辑便会跳转后运行到 `00007FF78CF607DF`,跳转进入 AES 解密逻辑。
如果密钥起始处为空,接下来,程序将循环 7 次,每次将起始处向后挪 4 个字节。(这样可以确保找到长度更小的密钥:如果密钥长度比较小,前面的空间便都是零。)如果实在找不到(读到的都是零)就会断言报错了。
所以用来在 `00007FF78CF6076A` 给 `rax` 赋值的 `r8` 寄存器就指向内存里密钥的起始处。在 `00007FF78CF6076A` 下断点,获取 `r8` 寄存器里的值。内存窗口里这个值指向的地方便是密钥的起始处了。
AES-256 的密钥长度为 32 个字节,所以在内存窗口中复制 32 个字节的数据,转为 16 进制,塞进资源提取器,成功! sajuuk1129 发表于 2019-3-22 20:58
这种加密方式太水了。。汗颜
像那种严密加密的,加密后机器都跑死,吃电脑资源,还极其费电,这样的游戏和程序,从来不玩不用! jimo 发表于 2019-3-22 18:23
这岂不意味着PUBG的PAK能随意解密了?
PUBG 的加密逻辑是一样的。我没买游戏,不清楚,不过主程序估计加了壳。 这岂不意味着PUBG的PAK能随意解密了? 很不错的分享啊,感谢感谢 这种加密方式太水了。。汗颜 楼主解答了我的疑惑,感谢谢谢分享技术 高手,这是高手 认真学习争取早日做大佬 谢谢楼主分享