-
-
Save Decencies/0136e02cb5e9ccb06a1cd3a2c8639bbf to your computer and use it in GitHub Desktop.
Image extractor/decrypter for Beiying K8xx/K9xx (sinowealth SH6xFxxx/SH7xFxxx) firmware update files
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
from struct import * | |
from argparse import * | |
# Royal Kludge vendor ID | |
rk_venor = 0x258a | |
# Update File magic bytes | |
upf_magic = b'\x5a\xa5SINO' | |
k1 = 0xc6ed3720 | |
k2 = 0x61c89647 | |
def decrypt8(cw: int, nw: int, k1: int, k2: int, key: bytes) -> tuple[int, int]: | |
""" | |
Decrypt the 32 bits within the low and high words, using the high, low & firmware keys. | |
@param cw the current (low) word | |
@param nw the next (high) word | |
@param k1 the first (high) key | |
@param k1 the second (low) key | |
@param key the 4 byte firmware key | |
@return the new high and low words after decryption | |
""" | |
# accumulator for rolling key | |
acc = k1 | |
for _ in range(32): | |
# Round 1 (high) | |
diff = (key[3] + (cw >> 5)) & 0xffffffff | |
xor = (key[2] + (cw << 4)) & 0xffffffff | |
diff ^= xor | |
diff ^= (acc + cw) & 0xffffffff | |
nw = (nw - diff) & 0xffffffff | |
# Round 2 (low) | |
diff = (key[0] + (nw << 4)) & 0xffffffff | |
xor = (key[1] + (nw >> 5)) & 0xffffffff | |
diff ^= xor | |
diff ^= (acc + nw) & 0xffffffff | |
cw = (cw - diff) & 0xffffffff | |
acc = (acc + k2) & 0xffffffff | |
return cw, nw | |
def decrypt(data: bytes, k1: int, k2: int, key: bytes) -> bytes: | |
""" | |
Decrypt the firmware data in chunks, using the high, low & firmware keys. | |
@param data the firmware data | |
@param k1 the first (high) key | |
@param k1 the second (low) key | |
@param key the 4 byte firmware key | |
@return the decrypted firmware bytes | |
""" | |
# unpack the data as uints | |
words = [unpack_from('>I', data, i * 4)[0] for i in range(len(data) // 4)] | |
print(f'decrypting {len(data) // 8} chunks...', end=' ') | |
# read & decrypt the words | |
for i in range(len(data) // 8): | |
lo, hi = i * 2, i * 2 + 1 | |
# define, decrypt & swap the current and next words | |
cw, nw = words[lo], words[hi] | |
cw, nw = decrypt8(cw, nw, k1, k2, key) | |
words[lo], words[hi] = cw, nw | |
print('done!') | |
return b''.join(pack('>I', val) for val in words) | |
def main(upf, out, k1: int, k2: int, ven: int): | |
magic = upf.read(len(upf_magic)) | |
# todo: some firmware starts with 5aa5424c45 | |
assert magic == upf_magic, f'invalid upf magic {magic:06x}' | |
ven_id, pid, fw_size = unpack('>HHH', upf.read(6)) | |
# todo: can it be different? | |
assert ven_id == rk_venor, f'invalid ven_id {ven_id:04x}' | |
# todo: can it be larger than 64K? | |
assert fw_size <= 0x10000, f'fw too big! ({fw_size:04x}>0xf000)' | |
fw_data = upf.read(fw_size) | |
key = upf.read(4) | |
print('-' * 32) | |
print(f'pid: {pid:04x}') | |
print(f'size: {fw_size:04x}') | |
print(f"key: {unpack('<I', key)[0]:08x}") | |
print('-' * 32) | |
out.write(decrypt(fw_data, k1, k2, key)) | |
print('-' * 32) | |
print(f'wrote firmware to {out.name}') | |
if __name__ == '__main__': | |
p = ArgumentParser('sinodump', add_help=False) | |
p.add_argument('upf', type=FileType('rb')) | |
p.add_argument('out', type=FileType('wb')) | |
p.add_argument('--k1', type=int, default=k1) | |
p.add_argument('--k2', type=int, default=k2) | |
args = p.parse_args() | |
main(args.upf, args.out, args.k1, args.k2) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment