怎么感觉好多游戏的文本字幕都不在本地 比如梦鸡 龙少女
uint64_t Pyro_AssetBundleManager_AssetBundlesLoader__GetAssetBundleOffset(
System_String_o *platform,
System_String_o *path,
const MethodInfo *method)
{
System_Text_Encoding_o *ASCII; // x19
System_String_o *v6; // x0
__int64 v7; // x0
__int64 v8; // x9
unsigned __int8 v9; // w8
_BYTE *v10; // x10
__int64 v11; // x11
__int64 v12; // x12
ASCII = System_Text_Encoding__get_ASCII(0LL);
v6 = System_String__Concat_121532840(platform, path, 0LL);
if ( !ASCII
|| (v7 = ((__int64 (__fastcall *)(System_Text_Encoding_o *, System_String_o *, const MethodInfo *))ASCII->klass->vtable._18_GetBytes.methodPtr)(
ASCII,
v6,
ASCII->klass->vtable._18_GetBytes.method)) == 0 )
{
sub_2DA6564();
}
v8 = *(_QWORD *)(v7 + 24);
v9 = 0;
v10 = (_BYTE *)(v7 + (int)v8 - 1 + 32);
if ( (int)v8 >= 1 )
{
v11 = (unsigned int)*(_QWORD *)(v7 + 24);
if ( (unsigned int)*(_QWORD *)(v7 + 24) )
{
v9 = 0;
v12 = 0LL;
while ( v11 != v12 )
{
v9 += *(_BYTE *)(v7 + 32 + v12);
if ( (v12 & 3) == 0 )
v9 += *v10;
if ( v11 == ++v12 )
goto LABEL_10;
}
LABEL_12:
sub_2DA656C();
}
}
LABEL_10:
if ( !(_DWORD)v8 )
goto LABEL_12;
return (unsigned __int8)*v10 * (unsigned int)v9 % 0x3E8 + 1000;
}
这里贴一段代码是计算偏移量的应该是(FeakHead)
还是没有找到下载的MasterData和第一个ab包
https://asset.lecisxqw.com/amgameasset/Stg/AssetBundles/Android/9A188D7BF0BF119DE38B57A44DF3A917.ab
的解密逻辑(恼
这个偏移量计算函数在
void Pyro_AssetBundleManager_AssetBundlesLoader__LoadAssetBundleFlow_d__41__MoveNext(
Pyro_AssetBundleManager_AssetBundlesLoader__LoadAssetBundleFlow_d__41_o *this,
const MethodInfo *method)
被调用(使用1.0版本做的分析新版1.01懒得再重新分析了)
从代码中可以看到对文件名进行了md5加密而下载的文件命名也确实是md5作为文件名
图随便截的函数中一个获取MD5的地方这个函数足足有715行代码。。。应该是AssetBundle加载的核心
看到master-data.bytes这文件名就觉得是不是GitHub - Cysharp/MasterMemory: Source Generator based Embedded Typed Readonly In-Memory Document Database for .NET and Unity. (CySharp真王朝了吧)
看了下还真是, DummyDll里的数据表都继承MasterMemory.TableBase, 但是文件名全混淆了, 不过可以看annotation知道表名.
从MemoryDatabase.ctor反推, 看了下解密master-data.bytes的逻辑大概是这个样子, 需要下断点拿Salt, ghidra这里只能decompile到Field$<PrivateImplementationDetails>
ResourceManager$$FGDEMJDNGHM -> FEAPPKCFGPC$$BIHCOBDLBKP(input, key = "zVGyaQ6h9tLzqiD5HJQF")
ResourceManager$$FGDEMJDNGHM -> AOBPAOAMPDA (MemoryDatabase.ctor)
FileName = MD5.ComputeHash(String.Concat(int-based-filename, salt="Fikv88qzPb")) + ".ab"
MasterData = Load<TextAsset>("master-data", FileName)
FEAPPKCFGPC$$BIHCOBDLBKP:
using (PasswordDeriveBytes pdb = new PasswordDeriveBytes("zVGyaQ6h9tLzqiD5HJQF", "下断点拿Salt")
using (AesManaged aes = new AesManaged())
{
aes.Key = pdb.GetBytes((aes.KeySize + 7) / 8);
aes.IV = pdb.GetBytes((aes.BlockSize + 7) / 8);
using (ICryptoTransform encryptor = aes.CreateEncryptor())
using (MemoryStream memoryStream = new MemoryStream())
using (CryptoStream cryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write))
{
cryptoStream.Write(input, 0, input.max_length);
cryptoStream.Close();
return memoryStream.ToArray();
}
}
淦只能动态了找到了你说的这个函数,对生成密钥的那个函数交叉引用找不到其他函数了只能动态调试。。。不是专门搞安卓的寄。。。
我看这个PrivateImplementationDetails是可以直接从global-metadata.dat里拿的, 也就是下面的salt, 但是我这样解出来不是正常的msgpack, 估计哪里搞错了
private static byte[] Decrypt(byte[] input)
{
byte[] salt = { 0x93, 0x83, 0x33, 0x72 };
Array.Reverse(salt);
using (PasswordDeriveBytes pdb = new PasswordDeriveBytes("zVGyaQ6h9tLzqiD5HJQF", salt))
using (MemoryStream memoryStream = new MemoryStream())
using (AesManaged aes = new())
{
aes.Key = pdb.GetBytes(aes.KeySize / 8);
aes.IV = pdb.GetBytes(aes.BlockSize / 8);
using (ICryptoTransform encryptor = aes.CreateEncryptor())
using (CryptoStream cryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write))
{
cryptoStream.Write(input, 0, input.Length);
cryptoStream.Close();
return memoryStream.ToArray();
}
}
}
Key.length : 32
Mode : CBC
IV.length : 16
BlockSize : 128
FeedbackSize : 128
Padding : PKCS7
TransformMode : Decrypt
Key (hex) : 2c 3a 1b d0 62 9a 26 9c f5 39 b3 5d 01 af 63 17 d5 b6 30 25 76 33 1a 1a d6 e7 af 64 14 bf c7 19
IV (hex) : f5 39 b3 5d 01 af 63 17 c1 9d 9f 69 c7 f4 d1 fc
解密后
简单解析了下,看起来没啥大问题?
import msgpack
import json
import base64
import struct
import lz4.block
from typing import Dict, List, Any
INPUT_FILE = 'master-data.decrypted'
OUTPUT_FILE = 'master-data.json'
class MasterMemoryExtTypeHandler:
@staticmethod
def decode_ext_type(code: int, data: bytes) -> Any:
if code == 99 and len(data) >= 5 and data[0] == 0xd2:
# ExtType 99: 0xd2 + 4字节大小 + LZ4压缩数据
uncompressed_size = struct.unpack('>I', data[1:5])[0]
compressed_data = data[5:]
try:
decompressed = lz4.block.decompress(compressed_data, uncompressed_size=uncompressed_size)
return msgpack.unpackb(decompressed, raw=False, strict_map_key=False)
except Exception:
pass
class MasterMemoryJSONEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, bytes):
try:
return obj.decode('utf-8')
except UnicodeDecodeError:
return base64.b64encode(obj).decode('ascii')
elif isinstance(obj, msgpack.ExtType):
return MasterMemoryExtTypeHandler.decode_ext_type(obj.code, obj.data)
elif obj.__class__.__name__ == 'Timestamp':
return {
"__type__": "Timestamp",
"__seconds__": getattr(obj, 'seconds', None),
"__nanoseconds__": getattr(obj, 'nanoseconds', None)
}
return super().default(obj)
def extract_table_data(header: Dict[str, List[int]], raw_data: bytes) -> Dict[str, Any]:
tables = {}
for table_name, table_info in header.items():
if not isinstance(table_info, list) or len(table_info) < 2:
continue
offset, length = table_info[0], table_info[1]
try:
table_data = raw_data[offset:offset + length]
if len(table_data) == 0:
tables[table_name] = {"objects_count": 0, "data": []}
continue
try:
result = msgpack.unpackb(
table_data,
raw=False,
strict_map_key=False,
ext_hook=MasterMemoryExtTypeHandler.decode_ext_type
)
objects = result if isinstance(result, list) else [result]
except Exception:
unpacker = msgpack.Unpacker(
raw=False,
strict_map_key=False,
ext_hook=MasterMemoryExtTypeHandler.decode_ext_type
)
unpacker.feed(table_data)
objects = [obj for obj in unpacker]
tables[table_name] = {
"objects_count": len(objects),
"data": objects
}
except Exception as e:
tables[table_name] = {
"error": str(e),
"offset": offset,
"length": length
}
return tables
def convert_master_memory_to_json():
try:
with open(INPUT_FILE, 'rb') as f:
raw_data = f.read()
print(f"文件读取成功,大小: {len(raw_data)} 字节")
except FileNotFoundError:
print(f"错误: 找不到文件 {INPUT_FILE}")
return
try:
unpacker = msgpack.Unpacker(
raw=False,
strict_map_key=False,
ext_hook=MasterMemoryExtTypeHandler.decode_ext_type
)
unpacker.feed(raw_data)
all_objects = [obj for obj in unpacker]
print(f"解析成功!总共 {len(all_objects)} 个对象")
except Exception as e:
print(f"解析失败: {e}")
return
result = {"tables": {}, "compressed_data": {}}
if all_objects and isinstance(all_objects[0], dict):
header = all_objects[0]
tables = extract_table_data(header, raw_data)
result["tables"] = tables
compressed_count = 0
for i, obj in enumerate(all_objects[1:], 1):
if isinstance(obj, (dict, list)) and not isinstance(obj, str):
result["compressed_data"][f"data_{i}"] = obj
compressed_count += 1
if compressed_count > 0:
print(f"提取了 {compressed_count} 个压缩数据块")
try:
with open(OUTPUT_FILE, 'w', encoding='utf-8') as f:
json.dump(result, f, indent=2, ensure_ascii=False, cls=MasterMemoryJSONEncoder)
print(f"✓ 转换完成: {OUTPUT_FILE}")
except Exception as e:
print(f"保存失败: {e}")
if __name__ == "__main__":
convert_master_memory_to_json()
感谢大佬的解密
看了一眼应该是多语言的文本字幕
不过第一个ab文件还是无法解密
这个ab包在首次打开游戏未下载资源时会请求
https://asset.lecisxqw.com/amgameasset/Stg/AssetBundles/Android/9A188D7BF0BF119DE38B57A44DF3A917.ab
之后就会弹出
[
"DOWNLOAD_BUNDLE",
"需要下載{0}MB更新資料",
"Requires downloading {0}MB of update data",
"{0}MBの更新データをダウンロードする必要があります",
"需要下载{0}MB更新资料"
],
估计就是资源清单文件
请问一下AES decrypt部分怎么写的, 我发现key, iv, padding, mode都一样, 但是按照我上面的C#代码跑还原不出这个
AES有多种模式设置对了?
似乎。。。
你在加密
我去 还真是, 完全没发现
改完就行了
其实第一个bundle就是
MD5("Android" + "version.json").toUpper() -> "9A188D7BF0BF119DE38B57A44DF3A917"
然后花里胡哨的又是一段解密
def decrypt_version_json(content):
output = [0] * len(content)
for i in range(len(content)):
v5 = i * 0x4104105 >> 0x20
offset = (v5 + ((i - v5) >> 1) >> 5) * -0x3F
output[i] = content[i] ^ i + offset
看着应该是索引了
decrypted_version.zip (16.6 KB)
然后单独文件的下载依旧是按照MD5("Android" + filename)
, 比如MD5("Androidprefab/characterspine/cg/3/4012") -> C53246A36360BC766381E4B9F196552B
2点还在整啊,强的
不过也算是从资产到数据表都搞定了
感谢大佬们
看到是ab文件就只考虑是unity文件类型没往json方向想。。。一直在分析ab文件的加载逻辑
能从那么多代码中看出来属实厉害,我看一会头都快晕了。
密碼一直錯誤 怎麼辦
大哥1.02了更新了两个版本怎么解包也写了我也写了自动资源下载工具
手动提取就好了很简单的
如果这都解决不了那你拿到资源也不会用不会播放