求大佬手游[Omniheroes]还原方法

游戏是图片应该XXTEA加密,资源的文件名混淆,找不到匹配的spine三件套对应名称
这是我整理的一部分内容Omniheroes部分

随便看了一下,不是xxtea加密,这个dword_178C420没空hook了,等有缘人hook吧

大佬有空说一下自己弄的话,解决的思路和方向吗


这是我最新的进度,按大佬的提示和看视频,我尝试搜索图片的统一头部标识QDREAM
但搜索QDREAM,找到的这里好像只是一个文本,不知道怎么进行下一步了。也没找到大佬图片里的那部分
下面放一个觉得好看的立绘看能不能吸引到有兴趣的大佬吧。
提示:
这游戏spine文件应该只有待机立绘和战斗小人

看起来像异或

挖坟+接着求助(国庆又试了一下,太菜了我)

用豆包扒了两天 :smiling_face_with_tear: 计算前14位异或可以和QIAN-MERCURY匹配,但是用xor软件解了之后还是打不开图片,豆包写的py也不行。

今天又额外扒到点东西,我更想不明白哪里出问题了

int sub_ABCD30()
{
byte_22DCFA0[0] = 24;
strcpy((char *)&qword_22DCFA1, “QIAN-MERCURY”);
byte_22DCFB8 = 18;
strcpy((char *)&qword_22DCFB9, “KUN-VENUS”);
byte_22DCFD0 = 20;
strcpy((char *)&qword_22DCFD1, “ZHEN-EARTH”);
byte_22DCFE8 = 16;
strcpy((char *)&qword_22DCFE9, “XUN-MARS”);
byte_22DD000 = 22;
strcpy((char *)&qword_22DD001, “KAN-JUPITER”);
byte_22DD018 = 18;
strcpy((char *)&qword_22DD019, “LI-SATURN”);
byte_22DD030 = 20;
strcpy((char *)&qword_22DD031, “GEN-URANUS”);
byte_22DD048 = 22;
strcpy((char *)&qword_22DD049, “DUI-NEPTUNE”);
return __cxa_atexit(sub_ABCC20, 0, &off_21FA0C0);
}

s大佬发的我尝试去找了,没找到…..hook我也试了,然后弄成了切换角色的时候读取哪些文件(菜)。豆包说读取明文内存,可是我adb死活操作不了开启软件和移动文件的权限,也就没法生成,frida的指令也是用的吾爱的指令成功连接的。

有好心的大佬帮忙的解答的话万分感谢,祝大家节日快乐

dump出来是八卦啊,有意思的

77c8e53978  18 51 49 41 4e 2d 4d 45 52 43 55 52 59 00 00 00  .QIAN-MERCURY...
77c8e53988  00 00 00 00 00 00 00 00 12 4b 55 4e 2d 56 45 4e  .........KUN-VEN
77c8e53998  55 53 00 00 00 00 00 00 00 00 00 00 00 00 00 00  US..............
77c8e539a8  14 5a 48 45 4e 2d 45 41 52 54 48 00 00 00 00 00  .ZHEN-EARTH.....
77c8e539b8  00 00 00 00 00 00 00 00 10 58 55 4e 2d 4d 41 52  .........XUN-MAR
77c8e539c8  53 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  S...............
77c8e539d8  16 4b 41 4e 2d 4a 55 50 49 54 45 52 00 00 00 00  .KAN-JUPITER....
77c8e539e8  00 00 00 00 00 00 00 00 12 4c 49 2d 53 41 54 55  .........LI-SATU
77c8e539f8  52 4e 00 00 00 00 00 00 00 00 00 00 00 00 00 00  RN..............
77c8e53a08  14 47 45 4e 2d 55 52 41 4e 55 53 00 00 00 00 00  .GEN-URANUS.....
77c8e53a18  00 00 00 00 00 00 00 00 16 44 55 49 2d 4e 45 50  .........DUI-NEP
77c8e53a28  54 55 4e 45 00 00 00 00 00 00 00 00 00 00 00 00  TUNE............
77c8e53a38  20 6c 75 cd 77 00 00 b4 00 00 00 00 00 00 00 00 

1 个赞

我去,只顾着找还真没注意是拼音八卦 :rofl:

代码如下,试了下解出来四标准astc的魔数

import struct
import hashlib

def cal_index(t):
    t &= 0xFFFFFFFFFFFFFFFF
    x8 = (t + 0x1B77400) & 0xFFFFFFFFFFFFFFFF
    x10 = x8 >> 10
    # 取高64位
    x9_umulh = ((x10 * 0x31B5D43AFE99EF) >> 64) & 0xFFFFFFFFFFFFFFFF
    x9_lsr = x9_umulh >> 6
    x8_mod = (x8 - (x9_lsr * 0x5265C00)) & 0xFFFFFFFFFFFFFFFF
    x8_mul = (x8_mod * 0x31B5D43B) & 0xFFFFFFFFFFFFFFFF
    index = x8_mul >> 0x35
    return index

def dec(i):
    data = bytearray(i)
    t = struct.unpack('<Q', data[6:14])[0]
    KEY_TABLE = [
        "QIAN-MERCURY", "KUN-VENUS", "ZHEN-EARTH", "XUN-MARS",
        "KAN-JUPITER", "LI-SATURN", "GEN-URANUS", "DUI-NEPTUNE"
    ]
    salt = KEY_TABLE[cal_index(t)]
    pld = data[14:]
    # nibbles
    ex = []
    for b in hashlib.sha256(f"{salt}{t}".encode()).digest():
        ex.append((b >> 4) & 0xF)
        ex.append(b & 0xF)
    for i in range(min(100, len(pld))):
        idx = [(1, 2), (4, 5), (31, 32)][i % 3]
        if idx[1] < len(ex):
            key = (ex[idx[0]] + ex[idx[1]]) & 0xFF
            if key != 0:
                pld[i] ^= key
    return bytes(pld)

if __name__ == "__main__":
    with open('0d3e1bc1-ce94-40a9-86a3-3e3d7ab20e06.astc', 'rb') as f:
        enc = f.read()
    with open('dec.astc', 'wb') as f:
        f.write(dec(enc))

1 个赞

密钥表,挺有意思

0:"QIAN-MERCURY" (乾 - 水星)
1: "KUN-VENUS" (坤 - 金星)
2: "ZHEN-EARTH" (震 - 地球)
3: "XUN-MARS" (巽 - 火星)
4: "KAN-JUPITER" (坎 - 木星)
5: "LI-SATURN" (离 - 土星)
6: "GEN-URANUS" (艮 - 天王星)
7: "DUI-NEPTUNE" (兑 - 海王星)
2 个赞

感谢大佬

谢谢大佬帮助,我这里用了部分热更新中的文件,然后用tacentview不能浏览解密后的图片,是哪里操作不对吗。下面是我的操作示例

操作示例

万分感谢

他密钥索引那一块有点问题,我在看看。 :smiling_face_with_tear:

临时加了个暴搜,勉强能用,但是长度那部分还得再看看。

import struct
import hashlib
import texture2ddecoder
from pathlib import Path
from PIL import Image

def to_int32(n):
    return (n & 0xFFFFFFFF) if n < 0x80000000 else (n & 0xFFFFFFFF) - 0x10000000000

def to_uint32(n):
    return n & 0xFFFFFFFF

def cal_index(t):
    t &= 0xFFFFFFFFFFFFFFFF
    x8 = (t + 0x1B77400) & 0xFFFFFFFFFFFFFFFF
    x10 = x8 >> 10
    x9_umulh = ((x10 * 0x31B5D43AFE99EF) >> 64) & 0xFFFFFFFFFFFFFFFF
    x9_lsr = x9_umulh >> 6
    x8_mod = (x8 - (x9_lsr * 0x5265C00)) & 0xFFFFFFFFFFFFFFFF
    x8_mul = (x8_mod * 0x31B5D43B) & 0xFFFFFFFFFFFFFFFF
    index = x8_mul >> 0x35
    return index

def calculate_decrypt_len(sha256_hash):
    s = []
    v82 = struct.unpack('<8I', sha256_hash)
    for val in v82:
        for j in range(4):
            byte = (val >> (j * 8)) & 0xFF
            s.append(byte >> 4)
            s.append(byte & 0x0F)
    v_stack = [to_int32(x) for x in s]

    v1_vec = [v_stack[i] + v_stack[i+4] + v_stack[i+8] + v_stack[i+12] for i in range(4)]
    v2_vec = [v_stack[i+16] + v_stack[i+20] + v_stack[i+24] + v_stack[i+28] for i in range(4)]
    v3_vec = [v_stack[i+32] + v_stack[i+36] + v_stack[i+40] + v_stack[i+44] for i in range(4)]
    v4_vec = [v_stack[i+48] + v_stack[i+52] + v_stack[i+56] + v_stack[i+60] for i in range(4)]
    
    v40_vec = [
        to_int32(v1_vec[0] + v2_vec[0] + v3_vec[0] + v4_vec[0]),
        to_int32(v1_vec[1] + v2_vec[1] + v3_vec[1] + v4_vec[1]),
        to_int32(v1_vec[2] + v2_vec[2] + v3_vec[2] + v4_vec[2]),
        to_int32(v1_vec[3] + v2_vec[3] + v3_vec[3] + v4_vec[3])
    ]
    v41 = sum(v40_vec)

    v42_base = []
    m = 0xCCCCCCCD
    for val in v40_vec:
        val_s32 = to_int32(val)
        mul_res_64 = val_s32 * m
        shifted = (mul_res_64 >> 32) >> 3
        v42_base.append(to_int32(val_s32 - shifted * 10))

    for i in range(len(v42_base)):
        if v42_base[i] == 0: v42_base[i] = 1

    v18 = to_uint32(v41)
    v44 = (v18 % 10) or 1
    v43 = ((v41 >> 32) % 10) or 1
    v45_part1 = to_int32(v42_base[0] * v42_base[2])
    v45_part2 = to_int32(v42_base[1] * v42_base[3])
    decrypt_len = to_uint32(v43 * v44 * v45_part1 * v45_part2)
    if decrypt_len <= 100: decrypt_len = 100
    return decrypt_len

def brute(enc, ex, b=8):
    ASTC_MAGIC = 0x5CA1AB13
    test_data = bytearray(enc[:b])
    max_idx = len(ex) - 1

    for idx0 in range(max_idx):
        for idx1 in range(max_idx):
            for idx2 in range(max_idx):
                pattern = [(idx0, idx0+1), (idx1, idx1+1), (idx2, idx2+1)]
                test = bytearray(test_data)
                
                for i in range(b):
                    idx_pair = pattern[i % 3]
                    xor_key = (ex[idx_pair[0]] + ex[idx_pair[1]]) & 0xFF
                    test[i] ^= xor_key
                
                if len(test) >= 4 and struct.unpack('<I', test[:4])[0] == ASTC_MAGIC:
                    return pattern
    return None

def dec(input):
    data = bytearray(input)
    t = struct.unpack('<Q', data[6:14])[0]
    KEY_TABLE = ["QIAN-MERCURY", "KUN-VENUS", "ZHEN-EARTH", "XUN-MARS", "KAN-JUPITER", "LI-SATURN", "GEN-URANUS", "DUI-NEPTUNE"]
    
    index = cal_index(t)
    salt = KEY_TABLE[index]
    key_string = f"{salt}{t}"
    print(f"时间戳: {t}")
    print(f"密钥盐: {salt}")
    
    sha256_hash = hashlib.sha256(key_string.encode()).digest()
    payload = data[14:]
    decrypt_len = calculate_decrypt_len(sha256_hash)
    aligned_len = (decrypt_len + 16 - 1) // 16 * 16
    expanded_for_brute = []
    for byte in sha256_hash:
        expanded_for_brute.append((byte >> 4) & 0xF)
        expanded_for_brute.append(byte & 0xF)

    key_pattern = brute(payload, expanded_for_brute)
    print(key_pattern)
    for i in range( min(aligned_len, len(payload))):
        idx_pair = key_pattern[i % 3]
        xor_key = (expanded_for_brute[idx_pair[0]] + expanded_for_brute[idx_pair[1]]) & 0xFF
        payload[i] ^= xor_key
    return bytes(payload)

def conv(data, path):
    header = data[:16]
    magic = struct.unpack('<I', header[:4])[0]
    block_x, block_y = header[4], header[5]
    xsize = int.from_bytes(header[7:10], 'little')
    ysize = int.from_bytes(header[10:13], 'little')
    print(f"{xsize}x{ysize},块{block_x}x{block_y}")
    decoded = texture2ddecoder.decode_astc(data, xsize, ysize, block_x, block_y)
    image = Image.frombytes('RGBA', (xsize, ysize), decoded)
    image.save(path, 'PNG')

if __name__ == "__main__":
    import argparse
    parser = argparse.ArgumentParser(description='QDREAM ASTC解密')
    parser.add_argument('i', help='加密ASTC文件')
    args = parser.parse_args()
    with open(args.i, 'rb') as f:
        enc = f.read()

    conv(dec(enc), (Path(args.i).stem + '.png'))
1 个赞

辛苦了 :growing_heart: