昨天玩了下这游戏,顺带做一下全资产下载/更新+解密数据表,开始教程:
首先直接找s和t两个参数,跳转到 Gc::GcUtil::AttachSecureLink
可以看到AppKey参与了s参数生成,而t参数就是
GetCdnUnixAddTime
加了一小时也就是3600,那么首先要找到AppKey跳转到
DMM::OLG::Unity::Engine::Api::Initialize
DMM::OLG::Unity::Engine::Config::Get
拿了AppKey然后找到
DMM::OLG::Unity::Engine::Config::Init
函数,是BaseSystemConfig.dat里面拿的这玩意在安装包里面,有4个:
612b88b23a5eb704db663b6c706ff586 BaseSystemConfig(Staging)
152b24a73b6a14031a4cb1d673c71986 BaseSystemConfig(Production)
a230c7ea95967406780cb9f454418498 BaseSystemConfig(Develop)
68c7fc2368477ad41a45618f013036b4 BaseSystemConfig
68c7fc2368477ad41a45618f013036b4就是我们要的,其他的有兴趣的可以自己研究
继续往下翻能找到DMM::OLG::Unity::Engine::Internal::ConfigData::Load
函数,这里就是加载的地方,核心是这部分,受限篇幅不细讲
解析脚本如下:
import io
import base64
import struct
import UnityPy
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad
from Crypto.Hash import HMAC, SHA256
KEY_BASE = "RWd3NusabzRc"
SECRET_KEY = "dB3aqcLtAmBd"
IV_SIZE = 16
class BinaryReader:
def __init__(self, stream):
self._stream = stream
def read_7bit_encoded_int(self) -> int:
value = 0
shift = 0
while True:
byte = self._stream.read(1)
byte_val = ord(byte)
value |= (byte_val & 0x7F) << shift
if (byte_val & 0x80) == 0:
break
shift += 7
return value
def read_string(self) -> str:
length = self.read_7bit_encoded_int()
if length == 0:
return ""
return self._stream.read(length).decode('utf-8')
class Decryptor:
def __init__(self, key_base: str, secret_key: str):
self._aes_key = self._generate_aes_key(key_base, secret_key)
def _generate_aes_key(self, data_string: str, secret_key_string: str) -> bytes:
data_bytes = data_string.encode('utf-8')
secret_key_bytes = secret_key_string.encode('utf-8')
h = HMAC.new(secret_key_bytes, digestmod=SHA256)
h.update(data_bytes)
return h.digest()
def decrypt(self, base64_encrypted_str: str) -> bytes:
encrypted_data = base64.b64decode(base64_encrypted_str)
iv = encrypted_data[:IV_SIZE]
ciphertext = encrypted_data[IV_SIZE:]
cipher = AES.new(self._aes_key, AES.MODE_CBC, iv)
decrypted_padded = cipher.decrypt(ciphertext)
return unpad(decrypted_padded, AES.block_size, style='pkcs7')
def extract_config(bundle_path: str) -> bytes:
bundle = UnityPy.load(bundle_path)
for obj in bundle.objects:
if obj.type.name == "TextAsset":
data = obj.read()
if data.m_Name == "BaseSystemConfig":
binary_data = data.m_Script.encode('latin-1')
return binary_data
return b''
def decrypt_config(bundle_path: str):
binary_data = extract_config(bundle_path)
stream = io.BytesIO(binary_data)
decryptor = Decryptor(KEY_BASE, SECRET_KEY)
config_data = {}
reader = BinaryReader(stream)
b64_count = reader.read_string()
decrypted_count_bytes = decryptor.decrypt(b64_count)
num_entries = struct.unpack('<i', decrypted_count_bytes)[0]
for i in range(num_entries):
b64_key = reader.read_string()
decrypted_key_bytes = decryptor.decrypt(b64_key)
key = decrypted_key_bytes.decode('utf-8')
b64_value = reader.read_string()
decrypted_value_bytes = decryptor.decrypt(b64_value)
value = decrypted_value_bytes.decode('utf-8')
if value == "[BaseSystemEmptyValue]":
value = ""
config_data[key] = value
print(f"{key} = {value}")
return config_data
if __name__ == "__main__":
decrypt_config("68c7fc2368477ad41a45618f013036b4")
得到我们要的AppKey,此外还有一些其他参数
至此可以正式回到s参数生成的部分
拼接AppKey+ 完整url + timestamp,进行md5哈希, 然后Base64 URL-safe 编码,最后除尾部的=就行了,经过测试h参数是不必要的,所以s,t参数部分搞定
_LoadAssetBundleManifestAsync_d__0::MoveNext
函数里面可以找到要下哪些接下来看数据表,cs文件里面可以找到MasterLoader相关的一些函数
筛选后直接看
_Load_b__22_0
,拿TextAsset然后用TextAssetDecryption解密,最后解压,这里也不细讲了,base64解码后TripleDES解密(ECB),然后再base64解密, 最后zlib解压(无zlib 头的原始deflate)成果: