(已解决)忍娘24图片解密求助

有点怀疑人生,感觉加密,但是搜不到解密函数,也具有png标识尾
感觉没加密,但里面数据又不是png。
目前感觉应该是cocos2d处理过的。
但是没见过,也不知道怎么办。



cocos2dx_lua_loader调用luaL_loadbuffer的地方入手没有发现明显的解密函数

这个函数名字比较可疑存在一个32字节长的字符串也可疑
其他的还没仔细看
ida9.0 Beta的分析文件(已完全解析)

直接找fread


解密在doGetFileData的这里

这个函数引用了decode

hook decode

0x7bb856c7ac - 0x7bb8049000 = 0x5237AC,会跳转到getEncodeBuffer

结束

写脚本测试

我也注意到了这个mdecode但是我这个IDA里甚至无法定位到汇编位置

import argparse
from pathlib import Path
from typing import Iterable


KEY = b"quantaninjagirl24"
HEADER = bytes([0xEF, 0xFE, 0x80])
CHUNK_DECRYPT = 128

def rc4_init(key: bytes) -> list[int]:
    s = list(range(256))
    j = 0
    for i in range(256):
        j = (j + s[i] + key[i % len(key)]) & 0xFF
        s[i], s[j] = s[j], s[i]
    return s


def rc4_crypt(key_state: list[int], data: bytes) -> bytes:
    s = key_state.copy()
    i = j = 0
    out = bytearray()
    for byte in data:
        i = (i + 1) & 0xFF
        j = (j + s[i]) & 0xFF
        s[i], s[j] = s[j], s[i]
        k = s[(s[i] + s[j]) & 0xFF]
        out.append(byte ^ k)
    return bytes(out)

def decrypt_texture(buf: bytes) -> bytes | None:
    if not buf.startswith(HEADER):
        return None

    payload = buf[3:]
    to_dec  = payload[:CHUNK_DECRYPT]
    rest    = payload[len(to_dec):]

    state = rc4_init(KEY)
    dec_first = rc4_crypt(state, to_dec)
    return dec_first + rest


def process_file(path: Path, out_dir: Path | None):
    raw = path.read_bytes()
    dec = decrypt_texture(raw)
    if dec is None:
        return False

    out_path = (
        out_dir / path.name if out_dir
        else path.with_suffix(".dec.png")
    )
    out_path.parent.mkdir(parents=True, exist_ok=True)
    out_path.write_bytes(dec)
    return True


def walk_inputs(targets: Iterable[Path]):
    for p in targets:
        if p.is_dir():
            yield from p.rglob("*.png")
        elif p.is_file():
            yield p


def main():
    ap = argparse.ArgumentParser(
        description="decrypt")
    ap.add_argument("input")
    ap.add_argument("-o", type=Path)
    args = ap.parse_args()

    hits = 0
    for f in walk_inputs(Path(i) for i in args.input):
        if process_file(f, args.o):
            hits += 1

if __name__ == "__main__":
    main()
1 个赞

意思就是套用rc4解密
那同理文本或者其他文件是使用下面那个rc4documentbuffer?
电脑不在身边依目前看到的图片猜测?

应该吧,大概看了下只有个.cbbi后缀的加密了,解完图片没问题就卸载了 :face_with_monocle:

那我下午回去看看我记得这个游戏去年11月有人问来着不过我当时在日本h365对日本IP屏蔽了部分游戏就没怎么看 :joy:

反正是判断第三个字节是128还是255判断用哪个函数,你看看其他文件第三个字节是不是,至少.cbbi文件是FF

那就对了图片和文本文件只有前两个字节一样应该是不同的加密方式

感谢大佬,回去试试。

为什么我运行.PY闪退,库没有安装准确吗?

ai会告诉你要安哪些外部库。

安装的python3.13版本,问了AI不需要库,但是运行不起。你解密成功没?分享下 :grinning:

可以用

解密函数就在上面,我自己写的用了别的库。

?报错文本你是一点没提上来就伸手?

要带上输入文件

代码喂ai,然后拿出资本家使唤牛马的本事,比什么都好使。
除了使用方法,还可以下发任务,让ai在大佬的代码基础上增加功能,比如实现输出文件夹保持与输入文件夹一样相对路径。
感谢yjzyl9008大佬给的解密代码 :+1:,这个确实有些门槛。

python decrypt.py /path/to/encrypted/ -o /path/to/decrypted/
import argparse
from pathlib import Path
from typing import Iterable

KEY = b"quantaninjagirl24"
HEADER = bytes([0xEF, 0xFE, 0x80])
CHUNK_DECRYPT = 128

def rc4_init(key: bytes) -> list[int]:
    s = list(range(256))
    j = 0
    for i in range(256):
        j = (j + s[i] + key[i % len(key)]) & 0xFF
        s[i], s[j] = s[j], s[i]
    return s

def rc4_crypt(key_state: list[int], data: bytes) -> bytes:
    s = key_state.copy()
    i = j = 0
    out = bytearray()
    for byte in data:
        i = (i + 1) & 0xFF
        j = (j + s[i]) & 0xFF
        s[i], s[j] = s[j], s[i]
        k = s[(s[i] + s[j]) & 0xFF]
        out.append(byte ^ k)
    return bytes(out)

def decrypt_texture(buf: bytes) -> bytes | None:
    if not buf.startswith(HEADER):
        return None

    payload = buf[3:]
    to_dec = payload[:CHUNK_DECRYPT]
    rest = payload[len(to_dec):]

    state = rc4_init(KEY)
    dec_first = rc4_crypt(state, to_dec)
    return dec_first + rest

def process_file(path: Path, out_dir: Path | None, root_dir: Path | None = None) -> bool:
    try:
        raw = path.read_bytes()
    except Exception as e:
        print(f"Error reading {path}: {e}")
        return False

    dec = decrypt_texture(raw)
    if dec is None:
        return False

    if out_dir:
        # 计算相对路径并保留目录结构
        rel_path = path.relative_to(root_dir) if root_dir else path.name
        out_path = out_dir / rel_path
    else:
        # 未指定输出目录时添加.dec.png后缀
        out_path = path.with_suffix(".dec.png")

    try:
        out_path.parent.mkdir(parents=True, exist_ok=True)
        out_path.write_bytes(dec)
        print(f"Decrypted: {path} -> {out_path}")
        return True
    except Exception as e:
        print(f"Error writing {out_path}: {e}")
        return False

def walk_inputs(targets: Iterable[Path]) -> Iterable[Path]:
    for p in targets:
        if p.is_dir():
            yield from p.rglob("*.png")
        elif p.is_file():
            yield p

def main():
    ap = argparse.ArgumentParser(description="Decrypt PNG textures")
    ap.add_argument("input", help="Input file or directory")
    ap.add_argument("-o", "--output", type=Path, help="Output directory")
    args = ap.parse_args()

    input_path = Path(args.input)
    if not input_path.exists():
        print(f"Error: Input path '{args.input}' does not exist")
        return

    root_dir = input_path.parent if input_path.is_file() else input_path
    hits = 0
    total = 0

    # 第一遍扫描统计总数
    all_files = list(walk_inputs([input_path]))
    total = len(all_files)

    # 第二遍处理文件
    for i, f in enumerate(all_files):
        print(f"Processing {i+1}/{total}: {f}")
        if process_file(f, args.output, root_dir):
            hits += 1

    print(f"Successfully decrypted {hits} of {total} files")

if __name__ == "__main__":
    main()

我一般都是让ai写多线程。
简单的可以交给ai,复杂点的还是要自己写的。
其实可以直接把伪c修改一下变成可用的c代码的,只是c没有py方便。

py
from Crypto.Cipher import ARC4
import os
import sys
import concurrent.futures
from pathlib import Path
import multiprocessing

def rc4_texture_decrypt(encrypted_data):
    """
    对加密的纹理数据进行RC4解密
    :param encrypted_data: 加密的二进制数据
    :return: 解密后的二进制数据
    """
    if not encrypted_data:
        return b''
    
    # 计算解密后数据的大小
    data_length = len(encrypted_data) - 3
    
    # RC4密钥
    key = b"quantaninjagirl24"
    
    # 分配内存存储解密后的数据
    decrypted_data = bytearray(data_length)
    
    # 从偏移3开始获取实际加密数据
    encrypted_part = encrypted_data[3:]
    
    # 初始化RC4密码器
    cipher = ARC4.new(key)
    
    # 解密处理
    if data_length > 128:
        # 解密前128字节
        decrypted_data[:128] = cipher.encrypt(encrypted_part[:128])
        # 复制剩余数据
        decrypted_data[128:] = encrypted_part[128:]
    else:
        # 解密全部数据
        decrypted_data[:data_length] = cipher.encrypt(encrypted_part[:data_length])
    
    return bytes(decrypted_data)

def decrypt_file(input_file, output_file):
    """解密单个文件并保存"""
    try:
        with open(input_file, 'rb') as f:
            encrypted_data = f.read()
        
        decrypted_data = rc4_texture_decrypt(encrypted_data)
        
        with open(output_file, 'wb') as f:
            f.write(decrypted_data)
        
        return True, f"✓ 已解密: {input_file}"
    except Exception as e:
        return False, f"✗ 解密失败 [{input_file}]: {e}"

def process_directory(input_dir, output_dir, overwrite):
    """多线程处理目录中的所有PNG文件"""
    if not os.path.exists(output_dir) and not overwrite:
        os.makedirs(output_dir)
    
    # 收集所有PNG文件
    png_files = []
    for root, _, files in os.walk(input_dir):
        for file in files:
            if file.lower().endswith('.png'):
                input_path = os.path.join(root, file)
                if overwrite:
                    output_path = input_path
                else:
                    # 保持目录结构
                    relative_path = os.path.relpath(input_path, input_dir)
                    output_path = os.path.join(output_dir, relative_path)
                    
                    # 确保输出目录存在
                    output_subdir = os.path.dirname(output_path)
                    if not os.path.exists(output_subdir):
                        os.makedirs(output_subdir)
                
                png_files.append((input_path, output_path))
    
    total_files = len(png_files)
    success_files = 0
    
    # 获取CPU核心数作为线程数
    cpu_count = multiprocessing.cpu_count()
    thread_count = max(1, cpu_count)  # 至少使用1个线程
    
    print(f"\n开始多线程处理,使用 {thread_count} 个线程,共 {total_files} 个文件")
    
    # 使用线程池进行多线程处理
    with concurrent.futures.ThreadPoolExecutor(max_workers=thread_count) as executor:
        # 提交所有任务
        future_to_path = {executor.submit(decrypt_file, in_file, out_file): (in_file, out_file) 
                         for in_file, out_file in png_files}
        
        # 处理结果
        for i, future in enumerate(concurrent.futures.as_completed(future_to_path)):
            input_path, _ = future_to_path[future]
            try:
                success, message = future.result()
                if success:
                    success_files += 1
                print(f"[{i+1}/{total_files}] {message}")
            except Exception as e:
                print(f"[{i+1}/{total_files}] ✗ 处理失败 [{input_path}]: {e}")
    
    return total_files, success_files

def main():
    while True:
        print("\n" + "="*40)
        print(" RC4纹理解密工具 (多线程版) ".center(40, '='))
        print("="*40)
        
        target = input("\n请输入文件路径或文件夹路径 (输入q退出): ").strip()
        
        if target.lower() == 'q':
            break
        
        if not os.path.exists(target):
            print(f"错误: 路径不存在 - {target}")
            continue
        
        overwrite = input("是否覆盖原文件? (y/n): ").strip().lower() == 'y'
        
        output_base = ""
        if not overwrite:
            output_base = input("请输入输出位置 (直接回车则在原路径添加'_decrypted'后缀): ").strip()
        
        if os.path.isfile(target):
            # 处理单个文件
            if overwrite:
                output_base = target
            elif not output_base:
                output_base = os.path.splitext(target)[0] + "_decrypted.png"
            
            success, message = decrypt_file(target, output_base)
            print(message)
            
        elif os.path.isdir(target):
            # 处理文件夹
            if overwrite:
                output_base = target
            elif not output_base:
                output_base = target.rstrip(os.sep) + "_decrypted"
            
            total, success = process_directory(target, output_base, overwrite)
            print(f"\n处理完成: 共{total}个PNG文件,成功解密{success}个")
        
        else:
            print(f"错误: 无法识别的路径类型 - {target}")
        
        choice = input("\n是否继续? (y/n): ").strip().lower()
        if choice != 'y':
            break

if __name__ == "__main__":
    main()