JSUETool
1.15K subscribers
频道主 @JSAI866
Download Telegram
Channel created
此频道开源交流
👍3
游戏都建立在 Unreal Engine 的 Pak 文件体系之上,但在标准 UE 加密(单层 AES)之外,按"由外到内"叠加了多层防护。解密必须自外向内逐层进行,任何一层密钥错误都会导致后续全部失败。

L1
Pak 尾部 / 主密钥层(PakInfo + Footer)
PakInfo 自身可能被 XOR / ZUC 流加密;主 AES 密钥要么硬编码、要么由 Pak 尾部内嵌的 RSA 块解密派生。
RSAZUCXOR
L2
索引层(Index)
文件目录索引先用主密钥 AES 解密,部分游戏解密后还要再 XOR / ZUC 流二次处理。
AESZUCXOR
L3
条目数据层(Per-Entry)
每个文件条目可独立选择加密算法与密钥 ID:链式 XOR、SM4(全局/盐/动态密钥)等;压缩块还可能被打乱顺序。
SM4XORAES
L4
Lua 字节码层
.lua 使用自定义 opcode 重排(部分还有 SUPERCODE 合并指令),字符串常量用独立 XOR 密钥加密。
opcode字符串XOR
设计共性:密钥分层、一文件一密、算法可热切换。条目头里携带"加密方法号 + 密钥 ID",使得服务端可以通过网络(TCP)动态下发新密钥(见 PUBG Mobile 的 0x01xxxxxx 动态密钥),而无需更新客户端。
❤2
PUBG(端游)/ 和平精英(GameForPeace)
对应 GameTypes/PUBG,TslGame / Game for Peace。这是四款中相对"克制"的方案:Pak 主体沿用标准 AES,自定义部分集中在 ini 与 Lua。

2.1 Pak 索引与条目
Pak 索引、文件条目使用 标准 UE AES-256(FAesKey),无自定义块密码。
索引存在新旧两种格式:新版为 FName→Entry 的 Map;旧版为"条目数组 + 独立目录索引"。读取时先探测路径字符串能否读出,以区分版本。
2.2 ini 文件:固定表循环 XOR
ini 解密后若开头 8 字节为魔数 0x4B4457585D5D5B7D,则用一张 81 字节 XOR 表循环异或。该表本质是字符 '1'–'9'(0x31–0x39)按行移位排列:

for i in 0..len:
data[i] ^= ini_key[i % 81]
// ini_key = "123456789" 逐行循环左移一位,共 9 行 × 9 列 = 81 字节
// 31 32 33 34 35 36 37 38 39 | 32 33 34 ... 39 31 | 33 34 ... 31 32 ...
2.3 Lua:Lua 5.3 + 字符串 XOR + 自定义 opcode
字符串常量用 32 字节 XOR 密钥逐字节循环异或:
EF C1 71 3E E3 34 7D 24 58 E1 9A 38 4F A4 6D 08
64 70 AC F2 BC E6 2E 41 4F 00 83 E7 E7 0B 20 07
头部格式字段 format 1→0;行号用 ushort(2 字节)压缩;LineDefined / LastLineDefined 为 ushort;upvalue 数、protos 数等字段宽度被改小。
自定义 opcode(含 SUPERCODE3 合并指令):把多条常用指令合并成单条"超级指令"(如 MOVE+CALL、GETTABLE+GETTABLE+GETTABLE、NEWTABLE+SETTABLE+SETTABLE 等),用于压缩体积并干扰反编译。权威实现中 opcode 尚未完全还原回标准 Lua,源码注释保留了一份可能的指令顺序清单。
关键结论:和平精英/端游 PUBG 的 Lua 因引入 SUPERCODE 合并指令,无法仅靠一张 opcode 映射表还原,反编译器需先"拆解"超级指令,这是它与 PUBG Mobile(纯 opcode 置换)的本质区别。
❤1
内置 36 条动态密钥盐(索引 0–35),密钥 = SHA1(对应盐)[0:16]。例如索引 0 → "edbcba1dc6b11068b44a",索引 3 → "5267814520c2b1904294"。
3.3 压缩块乱序(Block Shuffle)
动态 / Lite / Salt 加密的条目,其压缩块在 Pak 中不按逻辑顺序存放,需要用一个 LCG 伪随机数(PokemonRandom:乘 0x41C64E6D、加 0x3039)重建一张"逻辑块→物理块"的置换表,再按表取块。

state = blockCount
loop:
state = 0x41C64E6D * state + 0x3039
candidate = (state>>16 % 0x7FFF) % blockCount // 去重后填入置换表
3.4 ZSTD 字典压缩
当条目 CustomData == 152 时,数据用带自定义字典的 ZSTD 压缩。
字典位于 mini_obbzsdic_obb.pak 的 Content/Config/zstddic/mini_obbzsdic_obb;读取其 16 字节包装头后加载字典,再逐块 TryUnwrap。
3.5 Lua:Lua 5.3,纯 opcode 置换 + 字符串 XOR
30 项 opcode 重排(双射,可直接建表还原):文件 opcode 0–16(算术/位运算类)映射到标准 13–29;文件 17–29(MOVE/LOADK/表操作类)映射到标准 0–12。
字符串用 32 字节 XOR 密钥:
11 21 36 47 46 57 A7 8D 9D 84 90 D8 AB 00 8C 35
26 1A F7 E4 58 05 B8 B3 15 07 D0 2C 1E 8F F6 C8
字符串长度:首字节为长度,0xFF 时续读 int,实际长度 = size − 1。
无畏契约(Valorant Source)
对应 GameTypes/Tencent/ValorantSource。特色是 PakInfo 与索引的 ZUC-128 流加密,以及"每 Pak 内嵌 RSA、解密后拼 SHA1 得 AES 密钥"。

4.1 PakInfo:ZUC-128 XOR
PakInfo 缓冲区整体用一张预生成的 ZUC-128 密钥流表异或(DecryptValorantSourceFPakInfo)。

4.2 每 Pak 内嵌 RSA → AES 密钥
CustomEncryptionData 为 offset(long) + size(int),指向该 Pak 内部内嵌的一个 256 字节 RSA 块。
RSA 模数以 49 2B C9 0C… 开头(与 PUBG Lite 同一套),OAEP-SHA1 解密。
解密 payload 含两段(各为 len+data);AES Key 派生:
key[0:20] = SHA1(第一段)
key[20:32] = SHA1(第二段)[0:12]
最后将 key 每 4 字节按小端反转(共 8 个 uint)
4.3 条目 AES + 索引 ZUC 二次异或
条目数据:标准 AES(库内 Aes.Decrypt,密钥为上面派生的 32 字节)。
索引:AES 解密后,再按双字(uint)逐个 XOR;密钥流索引为 (双字序号 + 2) & 0xF,即每 16 个 uint 循环。
ZUC 密钥流的生成(MT19937 → ZUC-128)
先用固定流程播种一个 Mersenne Twister(MT19937,状态 624),逐字节取出 16 字节 Key 与 16 字节 IV;
再调用 ZUC-128 生成 16 组(64 字节)密钥流,供 PakInfo / 索引使用。
源码注释指出:该表其实可直接从 Pak 尾部"对 0 值加密"暴露出来,这里复刻的是真实生成算法。
4.4 索引结构
采用 Primary Index + Directory Index 两段式,字段位于固定偏移(目录索引大小在 0x3C、偏移在 0x4D、挂载点在 0x5C);条目本体以编码偏移(encoded offset)引用 Primary Index 中的条目数据池。

4.5 Lua:Lua 5.4,83 项 opcode 重排
83 项 opcode 映射(Lua 5.4 指令集),把算术立即数/常量变体、标准算术、表/全局访问、跳转比较、调用返回等整体重排(如文件 0–12 为 ADDI/ADDK…SHLI,13–32 为标准二元运算,33–53 为 MOVE/LOAD/表操作,54–82 为跳转/比较/调用/循环等)。
字符串用与 PUBG Mobile 相同的 32 字节 XOR 密钥(11 21 36 47 …),长度由 LuaInt(varint) 给出。
❤2
远光84(Farlight 84)
对应枚举 GAME_Farlight84(UE 4.25)。注意:它没有自定义块密码,Pak 主体使用标准 AES-256,自定义点仅在 PakInfo 结构与个别资产解析。

PakInfo 布局改动:尾部大小 SizeFarlight = Size8a + 9(多出 8 字节 long + 1 字节);读取 IndexOffset 之后需额外跳过 8 字节 unknown long。
资产解析的针对性兼容:
StaticMesh:自定义渲染数据 / 顶点与 UV 项处理;
Landscape:位置额外 +32 字节;
ScriptSet:元素按 SoftObjectPath 解析;
Wwise 音频 Bank、PositionVertexBuffer 等按 Farlight 布局读取。
结论:远光84 的"加密"在四款中最轻——标准 AES + PakInfo 结构微调,解包的主要工作量在于资产结构差异而非密码算法。
6.1 RSA-OAEP(SHA1)
统一使用 RSA-2048、指数 e=65537、256 字节块;OAEP 的哈希与 MGF1 均用 SHA-1(非 SHA-256)。
CUE4Parse 自实现 OAEP 解码:首字节 0x00,随后 20 字节 seed 与 database;用 SHA1 做 MGF 往返 XOR;校验 label 哈希 = SHA1(空);再定位 0x01 分隔符取出明文。
6.2 魔改 SM4
PUBG Mobile 的 SM4 与国密 SM4 算法骨架相同(32 轮、非线层 τ/SBox、线层 L、密钥扩展),但 FK、CK 常量与 SBox 全部替换,密钥扩展的线性变换 L' 用 ROL13 / ROL23。
因此不能直接套用标准 SM4 库,必须按其字节表实现;密钥统一由 SHA1(盐串) 取前 16 字节。
6.3 ZUC-128
祖冲之序列算法,以 Key/IV 生成密钥流;Valorant 用其加密 PakInfo 与索引,密钥/IV 由 MT19937 派生。
❤1
pak全部的加密都在这里
👍5