求助 影之诗Beyond如何解包

Steam端已经预加载并可下载资源了,除了data.unity3d的通用资源外,在Persistent/dat中的asset都无法直接提取

读取报错:

System.IO.IOException: Lz4 decompression error, write -1 bytes but expected 77876 bytes
  1. 手动查看文件头,Unity rev版本被改写成0.0.0, 改为正确Unity版本2022.3.18f1后仍报错
  2. 手动检查Unity文件格式,header正常 (lz4hc 压缩, flags 0x243),lz4 block info正常, 但解压lz4 block时异常
  3. 游戏目录下仅有GameAssembly,未找到global-metadata.dat

怀疑lz4 block的内容有点问题, 因为dump出来的部分完全没有可见的合理字符串,但没有啥思路。问问大佬们有没有思路,或者安卓包如果是Mono打包的,能不能直接BepInEx挂插件dump出来。

1 个赞

测试文件

2A7KVQH2TLJ6MFXCUFXAZAS6QM.zip (118.5 KB)

LZ4 block
compressed_bytes.zip (118.3 KB)

看了下手机端,global-metadata.dat的处理有几千行,简直不是人


改成dump, dump出来添上魔术也没法得到dump.cs
而且一用frida hook就闪退,等大佬来看看吧 :melting_face:

更新一下:
看到GameAssembly里导入了Plugins/x86_64/libnative.dll里的LZ4_decompress_safe_ext和Z4_compress_default_ext, 应该是在LZ4加解密外加了一层。

把ghidra反汇编的代码扔给LLM看,AI认为解压之前有一个CFB模式的加解密。

但是这个o3的代码真是看着头秃… 不会key还是动态读的吧。用Ollydbg下断点也不行,应该有某种anti debugger

直接LoadLibraryA libnative.dll 对assetbundle 每个字节为起始跑,无法解压
写DLL Proxy,直接转发原dll,程序也会卡住

x64dbg+ScrytheHide倒是可以下断点,不过现在看不出啥,就有几个compress decompress的触发

这下没思路了

同求,安卓是这种规格。

更新2:x64dbg+ScyllaHide 可以attach上进程加断点,成功用以下script dump出decompress函数的输入输出,但是每次都得敲完一遍script逐步执行,不知道有没有啥方法让他自动执行

memdumps.zip (918 字节)

不知道是没开服还是找的不对,现在dump出来的还是一些维护信息,看着跟直接json写的差不多…

x64dbg script:

decompress_addr = libnative.dll:LZ4_decompress_safe_ext
compress_addr = libnative.dll.LZ4_compress_default_ext

bp decompress_addr
bp compress_addr

SetBreakpointCommand decompress_addr, "scriptcmd call cb_decompress_entry"
SetBreakpointCommand compress_addr, "scriptcmd call cb_compress_entry"

erun
ret

cb_decompress_entry:
    mov src_ptr, rcx          
    mov dst_ptr, rdx          
    mov src_len, r8           
    mov dst_cap, r9           

    rtr

    mov dst_len, rax          

    savedata "memdumps/decompress_src_{breakpointcounter}.bin", src_ptr, src_len       
    savedata "memdumps/decompress_dst_{breakpointcounter}.bin", dst_ptr, dst_len

    erun   
    ret

cb_compress_entry:
    mov src_ptr, rcx          
    mov dst_ptr, rdx          
    mov src_len, r8           
    mov dst_cap, r9           

    rtr

    mov dst_len, rax          

    savedata "memdumps/compress_src_{breakpointcounter}.bin", src_ptr, src_len       
    savedata "memdumps/compress_dst_{breakpointcounter}.bin", dst_ptr, dst_len

    erun   
    ret

别沉呀,兄弟们救命

更新3: 上次那个hook只发服务端通讯,和asset没关系

暴力解法,在GameAssembly所有函数下断点。成功发现一些内存中解密完的AssetBundle。因为Unity LZ4 header有可能没被加密,通过CAB-标识符找到原文件

def extract_visible_node_name(path: Path) -> str:
    with path.open('rb') as f:
        sig = f.read(7)
        if sig != "UnityFS".encode('ascii'):
            return None
        
        f.seek(8)                        # skip NUL byte at offset 7
        format_ver = be_u32(f.read(4))   # uint32 BE
        unity_ver  = read_cstr(f)
        engine_ver = read_cstr(f)

        file_size        = be_u64(f.read(8))
        blocks_comp_size = be_u32(f.read(4))
        blocks_raw_size  = be_u32(f.read(4))
        flags            = be_u32(f.read(4))
        
        align_stream(f, 16)
        
        try:
            blocks_info_comp = f.read(blocks_comp_size)
            blocks_info      = lz4.block.decompress(blocks_info_comp,
                                                    uncompressed_size=blocks_raw_size + 1000)
            
            bi = io.BytesIO(blocks_info)
            hash128        = bi.read(16)                  # usually unused
            block_count    = be_u32(bi.read(4))
            
            blocks = []
            for _ in range(block_count):
                u_size  = be_u32(bi.read(4))
                c_size  = be_u32(bi.read(4))
                b_flags = be_u16(bi.read(2))
                blocks.append((c_size, u_size, b_flags))

            node_count = be_u32(bi.read(4))

            nodes = []
            names = []
            for _ in range(node_count):
                offset = be_u64(bi.read(8))
                size   = be_u64(bi.read(8))
                nflags = be_u32(bi.read(4))
                path_  = read_cstr(bi)
                nodes.append((offset, size, nflags, path_))
                names.append(path_)

            return names
                
        except Exception:
            return None

def get_asset_map(root: Path):
    asset_map = {}
    for p in root.rglob('*'):
        if p.is_file() and '.' not in p.name:
            potential_asset = extract_visible_node_name(p)
            if potential_asset is not None:
                print(f"Add mapping {p} to {potential_asset}")
                asset_map[str(p.name)] = potential_asset
                
    return asset_map

对比了一下,算法很简单,就是保持前256个字节不动,后面xor一个88字节的key

path = Path('.')
    
op = path / 'f5.bin'
ep = path / 'f6.bin'
  
with open(op, 'rb') as f:
    ot = f.read()
    
with open(ep, 'rb') as f:
    et = f.read()
    
xor = xor_bytes(ot, et)
  
zero_part = xor[0:256]
encryption_starts = xor[256:]

invariant_text = ot[0:256]
key = encryption_starts[0:88]

print('Cycle key:', key.hex(), '\n')

但是这个key不是恒定的,暂时没有什么头绪,应该是从前256个字节里hash出来的88个byte。

附件里f1-f2,f3-f4,f5-f6是三对原文和密文,看看大佬有没有什么思路

_Artifact.zip (43.3 KB)

大概率是偶像类那种触发式下载,一个文件一个key

哥们我这里有人能改图出了个0.1教程,方便加我扣扣吗我发你,看看有没有参考必要,扣扣878819986

好像有人弄了,求帮忙弄个exe出来试试

用Special K的吗,那个直接绕过了

这个需要个人信息才能解包,而且具体的key看起来得需要抓一些HTTPS上的包?可能得用Fiddler抓一下,我没安卓测试端

还是被找不到global-metadata恶心了

可以直接dump的,不过dump出来貌似头部被替换了 :face_with_monocle:

Wizard2AssetsUnpacker编译,使用方法在README.md


我dump出来的看起来只是前8字节被抹去了,但是有没有改别的东西就不清楚了…

请问AssetBundleAddress,CommonHeader, MD5Salt这些参数是怎么得到的啊?

提取码是多少呢

提取码:sAqo 这网页怎么给我吃了