萬源聖魔錄(Orisries)资产文件解密

最后更新于 2024-07-08 2766 次阅读


大部分文件是没有加密的,但是有一部分CG文件被加密了。先看一下文件样本:

可以发现加密的文件都以10 00 00 00结尾,而去掉这4字节都是整16字节倍数,合理猜测是AES之类的加密。

先尝试从LoadFromFileAsync下手,但是走了一圈没有找到解密相关逻辑,再从LoadFromStreamAsync入手,可以在UnityEngine_ResourceManagement_ResourceProviders_AssetBundleResource__LoadBundle_d__37__MoveNext中看到如下逻辑

首先从文件末尾读取IV长度,然后根据IV长度从末端读取IV,剩下的部分就是被加密的数据,然后调用AES解密函数。这里有两个问题,-4 - iv_len + *(_DWORD *)(*(_QWORD *)&v69.fields._offset + 24LL)对应的是ArraySegmentCount属性,这里有一个+24是让人迷惑的,应该是反编译的错误。还有一个问题是这里没出现解密用的Key。

DecryptionUtil__DecryptAES_94228508函数内部也没有直接出现Key的相关信息,但是在DecryptionUtil__DecryptAES_d__1__MoveNext中,有如下代码

结合后面System_Security_Cryptography_ICryptoTransform_o的构造,v4应该就是Key数组。这里出现了一个叫Field__PrivateImplementationDetails__FC3F14D0A4F7ACB026C196667031B4DD35DA633748E76AD5B0FF643C4E947910的东西。PrivateImplementationDetails是一个编译时生成的类,对应一些静态变量。参考Perfare佬在这篇关于Il2CppDumper的更新的文章

// Namespace: 
[CompilerGenerated]
internal sealed class <PrivateImplementationDetails> // TypeDefIndex: 12932
{
	// Fields
	internal static readonly <PrivateImplementationDetails>.__StaticArrayInitTypeSize=5807 04F8EF9544695B3818934E7709AC87BAC4C8D733E9853D173CE137FA61F42E86 /*Metadata offset 0x614FA8*/; // 0x0
	internal static readonly <PrivateImplementationDetails>.__StaticArrayInitTypeSize=3829 1A4635296267A11299FBF622134A6DED88DC7E554FD9BB6624EA57081739F4AC /*Metadata offset 0x616658*/; // 0x16AF
	internal static readonly long 2D2025322643CE1497D8FB03FA789F27E833CF43545CA1003AFEFEA250D39313 = 3172232900852580628; // 0x25A8
	internal static readonly <PrivateImplementationDetails>.__StaticArrayInitTypeSize=16 FC3F14D0A4F7ACB026C196667031B4DD35DA633748E76AD5B0FF643C4E947910 /*Metadata offset 0x617560*/; // 0x25B0
}

global-metadata.dat里对应偏移量找到16字节的Key

然后就可以写出解密脚本

from Crypto.Cipher import AES
import struct
import binascii

key = b'wiki is transfer'

def decrypt_aes(encrypted_file, output_file):
    with open(encrypted_file, 'rb') as f:
        file_content = f.read()

    if file_content[:7] == b'UnityFS':
        with open(output_file, 'wb') as f:
            f.write(file_content)
        return

    iv_length = struct.unpack('<I', file_content[-4:])[0]
    assert iv_length == 16
    l = len(file_content)
    data_end = l - 4 - iv_length
    iv = file_content[data_end:l - 4]
    encrypted_data = file_content[:data_end]
    cipher = AES.new(key, AES.MODE_CBC, iv)
    decrypted_data = cipher.decrypt(encrypted_data)
    pad = decrypted_data[-1]
    decrypted_data = decrypted_data[:-pad]
    with open(output_file, 'wb') as f:
        f.write(decrypted_data)

if __name__ == '__main__':
    import sys
    # Usage: python decrypt.py encrypted_dir output_dir
    encrypted_dir = sys.argv[1]
    output_dir = sys.argv[2]
    import os
    for root, dirs, files in os.walk(encrypted_dir):
        for file in files:
            encrypted_file = os.path.join(root, file)
            output_file = os.path.join(output_dir, file)
            decrypt_aes(encrypted_file, output_file)

解密出来的数据是这个样子的

可以看到Unity版本信息被抹去了,获取版本的方法有很多。我们可以在安装包的assets\bin\Data路径下找到没有加密也没有抹去版本信息的data.unity3d文件,可以看到版本是2022.3.32f1。使用Raz版本的Studio,在Options->Specify Unity version中输入2022.3.32f1就能正常查看了。