Dolls Division / 浩劫前线 解密相关

nutaku似乎在搞夏促
和日本朋友交流的时候看了一眼活动海报似乎就是E服的前线
image
找了一圈似乎没有这方面的信息
游戏url

https://www.nutaku.net/zh/games/dolls-division/

其文件名似乎为SHA-1加密,并使用文件名第一个字节作为文件夹来分区文件
看样子是cocos的引擎但是不知道文件名是如何加密的望大佬指点
SHA-1毕竟是哈希解密后只能通过审计lua代码分析文件结构来获取文件

1 个赞

E服文件有加密,蹲个大佬解密

为啥都喜欢玩这种一眼ai的呢。。

import itertools

SIGN = b'zbobv1'

def ror(value, n):
    n %= 8
    return ((value >> n) | (value << (8 - n))) & 0xFF

def dec(data: bytearray) -> bytes:
    if not data.endswith(SIGN):
        print("不是加密文件")
        return bytes(data)


    dec = [
        ror(data_byte ^ key_byte, key_index + 1)
        for data_byte, (key_index, key_byte) in zip(data[:-len(SIGN)], itertools.cycle(enumerate(bytes([0x78, 0x21, 0x7A, 0x40, 0x6F, 0x23, 0x62, 0x24]))))
    ]

    return bytes(dec)



if __name__ == "__main__":
    with open("0046B30E6E60FC3F0046594BBF8AAD61FA3884C2", 'rb') as f:
        enc = bytearray(f.read())

    with open("dec.png", 'wb') as f:
        f.write(dec(enc))

估计日本那边日法问题吧。。。没几个没码的
话说大佬文件名怎么得到的
我看sub_1377E9C似乎是一个哈希算法(nutaku arm64-v8a\libMyGame.so)

分析逻辑是他在加载图片时候会输出 TextureCache addImage path
字符串交叉引用找到sub_28D35DC
调用sub_28D57FC后再调用sub_1377E9C

你可以看看 sub_289B84Csub_C02570

sub_C02570负责加载json,这个json在解密后的资产里面能找到

文件名应该就是"extra_res3.json"加密后就是那个文件名
不过我看了一圈好像没找到啥函数
全是

(*(void (__fastcall **)(_QWORD *__return_ptr, __int64, _BYTE *))

这样的麻了

之前看到加密也想试试,但是函数名一坨也没搜到啥就没再看了 :smiling_face_with_tear:

d-miracle
大佬回了

from xxhash import xxh32
from hashlib import md5 as cmd5

def Get_Hash_Name(string: str) -> str:
    b = string.encode()
    s = xxh32(b).hexdigest() + cmd5(b).hexdigest()
    return s.upper()

print(Get_Hash_Name('General/HeroSpine/hero1001.atlas'))

可以先

from xxhash import xxh32
from hashlib import md5 as cmd5

def Get_Hash_Name(string: str) -> str:
    b = string.encode()
    s = xxh32(b).hexdigest() + cmd5(b).hexdigest()
    return s.upper()

print(Get_Hash_Name('extra_res3.json'))#4779F050624DD6880FA2AFA17382FDC25B2198BC

得到那个资源清单再生成加密文件名匹配文件解密输出
资源下载部分晚点用取证设备去抓似乎cocos的部分游戏流量不走系统代理

目前只能还原HCG文件名其他的还需要等审计完代码,或者笨办法遍历所有符合xml格式的文件(思路)

import os
import shutil
from xxhash import xxh32
from hashlib import md5 as cmd5
import itertools
import requests
import json

useproxy = proxyaddr = 0

def hjqxdownloader(overwrite):
    def Get_Hash_Name(string: str) -> str:
        b = string.encode()
        s = xxh32(b).hexdigest() + cmd5(b).hexdigest()
        return s.upper()

    SIGN = b'zbobv1'

    def ror(value, n):
        n %= 8
        return ((value >> n) | (value << (8 - n))) & 0xFF

    def dec(data: bytearray) -> bytes:
        if not data.endswith(SIGN):
            return bytes(data)
        return bytes([
            ror(data_byte ^ key_byte, key_index + 1)
            for data_byte, (key_index, key_byte) in zip(
                data[:-len(SIGN)],
                itertools.cycle(enumerate(bytes([0x78, 0x21, 0x7A, 0x40, 0x6F, 0x23, 0x62, 0x24])))
            )
        ])

    def decrypt_file(filepath):
        try:
            with open(filepath, 'rb') as f:
                data = bytearray(f.read())
            return dec(data)
        except Exception as e:
            print(f"读取或解密失败: {filepath},错误: {e}")
            return None

    def decrypt_folder(root_folder):
        for root, dirs, files in os.walk(root_folder):
            for file in files:
                full_path = os.path.join(root, file)
                try:
                    with open(full_path, 'rb') as f:
                        data = bytearray(f.read())
                    new_data = dec(data)
                    if new_data != data:
                        with open(full_path, 'wb') as f:
                            f.write(new_data)
                        print(f"已解密: {full_path}")
                    else:
                        print(f"跳过未加密文件: {full_path}")
                except Exception as e:
                    print(f"处理文件失败: {full_path},错误:{e}")

    manifest_url = "https://cdn.tpwslzp.xyz/images/download_res/Android/proj.manifest"
    response = requests.get(manifest_url)
    response.raise_for_status()
    manifest = response.json()

    base_url = manifest.get("packageUrl", "https://cdn.tpwslzp.xyz/images/download_res/Android/")
    assets = manifest.get("assets", {})

    lines = []
    for path in assets:
        filename = path.split('/')[-1]
        full_url = base_url + path
        if (not os.path.exists(os.path.join("resdownload", "dolls-division", "download", filename))) or overwrite:
            lines.append(f"{full_url}\n   out=download/{filename}")

    with open("dolls-division.txt", "w", encoding="utf-8") as f:
        f.write("\n".join(lines))

    print("主资源链接已写入 dolls-division.txt")
    os.system(f"aria2c -i dolls-division.txt -j 32 -s 16 -x 16 --auto-file-renaming=false {f'{proxyaddr} ' if useproxy else ''}{'--allow-overwrite=true ' if overwrite else ''}--check-certificate=false --dir=resdownload/dolls-division")

    filelistname = Get_Hash_Name('extra_res3.json')
    filelistpath = f"resdownload/dolls-division/download/{filelistname}"

    if not os.path.exists(filelistpath):
        print(f"未找到 extra_res3.json 加密文件: {filelistpath}")
        return

    raw_data = decrypt_file(filelistpath)
    if raw_data is None:
        print("extra_res3.json 解密失败")
        return

    try:
        filelistdata = json.loads(raw_data)
    except Exception as e:
        print(f"extra_res3.json 解析失败: {e}")
        return

    extra_lines = []
    for group_path, files in filelistdata.items():
        for filepath in files:
            hashed = Get_Hash_Name(filepath)
            local_path = os.path.join("resdownload", "dolls-division", "download", hashed)

            if not os.path.exists(local_path):
                print(f"未找到源文件: {local_path}")
                subfolder = hashed[0]
                extra_url = f"https://cdn.tpwslzp.xyz/images/extra_res/Android/{subfolder}/{hashed}"
                extra_lines.append(f"{extra_url}\n   out=download/{hashed}")
            else:
                print(f"{filepath} -> {hashed}")
    if extra_lines:
        with open("dolls-division.txt", "w", encoding="utf-8") as f:
            f.write("\n" + "\n".join(extra_lines))

        print("已追加 extra_res 链接到 dolls-division.txt")
        os.system(f"aria2c -i dolls-division.txt -j 32 -s 16 -x 16 --auto-file-renaming=false {f'{proxyaddr} ' if useproxy else ''}{'--allow-overwrite=true ' if overwrite else ''}--check-certificate=false --dir=resdownload/dolls-division")
    decrypt_folder("resdownload/dolls-division/download")
    for group_path, files in filelistdata.items():
        for filepath in files:
            hashed = Get_Hash_Name(filepath)
            source = os.path.join("resdownload", "dolls-division", "download", hashed)
            target = os.path.join("resdownload", "dolls-division", "output", filepath)

            if os.path.exists(source):
                os.makedirs(os.path.dirname(target), exist_ok=True)
                shutil.copy2(source, target)
                print(f"[cp]{filepath} -> {hashed}")
            else:
                print(f"[缺失]{filepath} -> {hashed}")

hjqxdownloader(0)