XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

XFEEDToolBox

【WinUI】XFE精英危险工具箱

公开
关注 0 Fork 0 Star 0
UTF-8
"""Read the user's existing, pinned translation resources on disk only.

Never loads the DLL or attaches to, changes, or invokes the running game.
The resource routine's local key material is never printed or written out.
"""
import argparse, hashlib, json, struct, zlib
from pathlib import Path
import pefile
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes

parser=argparse.ArgumentParser(description=__doc__)
parser.add_argument('--bundle',type=Path,required=True)
parser.add_argument('--output',type=Path,required=True)
args=parser.parse_args()
library=(args.bundle/'dbghelp.dll').read_bytes();package=(args.bundle/'edchs.pkg').read_bytes()
if hashlib.sha256(library).hexdigest()!='9e0c0ab1d854521918e59800496ff56d9bf97b436ad145f1ee88ffb33ea721fa':raise ValueError('Unknown local resource component')
if hashlib.sha256(package).hexdigest()!='6d8256802b37041a9c819655f1cecd9432b6452171e794dba2d5a08313f1e898':raise ValueError('Unknown local translation bundle')
if package[:8]!=b'EDPK\x01\0\0\0':raise ValueError('Unknown package header')
image=pefile.PE(data=library,fast_load=True)
# Exact source is the existing CryptImportKey path at RVA 3EC80.
key=image.get_data(0x81100,32)
args.output.mkdir(parents=True,exist_ok=True);position=32;resources=[]
count=struct.unpack_from('<I',package,12)[0]
if not 1<=count<=16:raise ValueError('Package entry count exceeds bound')
for _ in range(count):
    size=struct.unpack_from('<H',package,position)[0];position+=2
    if not 1<=size<=128:raise ValueError('Invalid resource name')
    name=package[position:position+size].decode('ascii');position+=size
    if Path(name).name!=name or '/' in name or '\\' in name:raise ValueError('Resource path exceeds output directory')
    offset,stored,expected=struct.unpack_from('<QQQ',package,position);position+=24
    if not 16<stored<=128*1024*1024 or offset+stored>len(package) or expected>128*1024*1024:raise ValueError('Invalid resource bounds')
    source=package[offset:offset+stored]
    decrypt=Cipher(algorithms.AES(key),modes.CBC(source[:16])).decryptor()
    value=decrypt.update(source[16:])+decrypt.finalize();padding=value[-1]
    if not 1<=padding<=16 or value[-padding:]!=bytes([padding])*padding:raise ValueError('Original resource padding is invalid')
    value=value[:-padding]
    if len(value)!=expected:
        for bits in (15,-15,31):
            try:decoded=zlib.decompress(value,bits)
            except zlib.error:continue
            if len(decoded)==expected:value=decoded;break
    if len(value)!=expected:raise ValueError('Original resource length disagrees with index')
    path=args.output/name
    with path.open('xb') as stream:stream.write(value)
    resources.append(dict(name=name,bytes=len(value),sha256=hashlib.sha256(value).hexdigest(),header=value[:8].hex()))
report=dict(resources=resources,sourcePackageSha256=hashlib.sha256(package).hexdigest(),gameProcessAccess=False,gameCallsInvoked=0,networkRequests=0)
(args.output/'resource-evidence.json').write_text(json.dumps(report,indent=2),encoding='utf-8')
print(json.dumps(report))