Add files via upload

This commit is contained in:
n0stal6ic
2026-05-18 12:15:01 -05:00
committed by GitHub
parent 885ed89e56
commit c00a64a02f
+43 -45
View File
@@ -67,9 +67,8 @@ class ScanStats:
wvd_generated: int = 0 wvd_generated: int = 0
def report(self): def report(self):
log.info("\n─────────────────────────────────────────") log.info("")
log.info(" Complete") log.info("Done.")
log.info("─────────────────────────────────────────")
log.info(f" Files scanned : {self.files_scanned}") log.info(f" Files scanned : {self.files_scanned}")
log.info(f" Candidates found : {self.candidates_found}") log.info(f" Candidates found : {self.candidates_found}")
log.info(f" Filter rejected : {self.filter_rejected}") log.info(f" Filter rejected : {self.filter_rejected}")
@@ -79,7 +78,6 @@ class ScanStats:
log.info(f" Decrypt failed : {self.decrypt_failed}") log.info(f" Decrypt failed : {self.decrypt_failed}")
if self.wvd_generated: if self.wvd_generated:
log.info(f" WVD files made : {self.wvd_generated}") log.info(f" WVD files made : {self.wvd_generated}")
log.info("─────────────────────────────────────────")
def clean_hex(s: str) -> bytes: def clean_hex(s: str) -> bytes:
@@ -194,7 +192,7 @@ def _save_cache(cache_path: str, keys: List[str], sources: List[str]) -> None:
f.write("\n") f.write("\n")
for k in keys: for k in keys:
f.write(k + "\n") f.write(k + "\n")
log.info(f" [i] Key cache updated -> {cache_path} ({len(keys)} keys)") log.info(f" Key cache updated: {cache_path} ({len(keys)} keys)")
@dataclass @dataclass
@@ -221,7 +219,7 @@ def build_key_database(
file_sources.append(arg) file_sources.append(arg)
else: else:
log.warning( log.warning(
f" [!] --key argument not recognised as hex, URL, or file: {arg!r}" f" Warning: --key argument not recognised as hex, URL, or file: {arg!r}"
) )
seen_ecb = set() seen_ecb = set()
seen_cbc = set() seen_cbc = set()
@@ -249,18 +247,18 @@ def build_key_database(
_add_ecb(k) _add_ecb(k)
for k, iv in _parse_cbc_keys_from_text(text): for k, iv in _parse_cbc_keys_from_text(text):
_add_cbc(k, iv) _add_cbc(k, iv)
log.info(f" [i] Loaded keys from file: {fpath}") log.info(f" Loaded keys from file: {fpath}")
except Exception as e: except Exception as e:
log.warning(f" [!] Could not read key file {fpath}: {e}") log.warning(f" Could not read key file {fpath}: {e}")
if not url_sources: if not url_sources:
return db return db
if not HAS_REQUESTS: if not HAS_REQUESTS:
log.warning(" [!] 'requests' not installed, cannot download key URLs.") log.warning(" 'requests' not installed, cannot download key URLs.")
log.warning(" Install with: pip install requests") log.warning(" Install it with: pip install requests")
return db return db
cache_file = _cache_path(output_dir) cache_file = _cache_path(output_dir)
if not force_update and _cache_is_fresh(cache_file): if not force_update and _cache_is_fresh(cache_file):
log.info(f" [i] Using cached keys: {cache_file}") log.info(f" Using cached keys from {cache_file}")
for k in _load_cache(cache_file): for k in _load_cache(cache_file):
_add_ecb(k) _add_ecb(k)
return db return db
@@ -277,10 +275,10 @@ def build_key_database(
for k, iv in new_cbc: for k, iv in new_cbc:
_add_cbc(k, iv) _add_cbc(k, iv)
log.info( log.info(
f" [i] Downloaded {len(new_keys)} ECB + {len(new_cbc)} CBC keys from {url}" f" Downloaded {len(new_keys)} ECB + {len(new_cbc)} CBC keys from {url}"
) )
except Exception as e: except Exception as e:
log.warning(f" [!] Failed to download {url}: {e}") log.warning(f" Failed to download {url}: {e}")
_save_cache(cache_file, downloaded, url_sources) _save_cache(cache_file, downloaded, url_sources)
return db return db
@@ -663,9 +661,9 @@ def extract_from_file(
raw_path = os.path.join(output_dir, raw_stem + ".bin") raw_path = os.path.join(output_dir, raw_stem + ".bin")
with open(raw_path, "wb") as out: with open(raw_path, "wb") as out:
out.write(block) out.write(block)
log.info(f"[{os.path.basename(path)} | Hit #{saved}]") log.info(f"{os.path.basename(path)} hit #{saved}:")
log.info(f" Raw keybox -> {raw_path}") log.info(f" Raw keybox saved to {raw_path}")
log.info(f" Offset : 0x{ppos:X} (postfix @ 0x{qpos:X})") log.info(f" Offset 0x{ppos:X} (postfix at 0x{qpos:X})")
if PRINT_HEXVIEW: if PRINT_HEXVIEW:
log.info(hexview(block, base_offset=ppos, width=HEXVIEW_WIDTH)) log.info(hexview(block, base_offset=ppos, width=HEXVIEW_WIDTH))
log.info("") log.info("")
@@ -685,26 +683,26 @@ def extract_from_file(
if not no_decrypt: if not no_decrypt:
if not HAS_CRYPTO: if not HAS_CRYPTO:
log.warning( log.warning(
" [!] pycryptodome not installed, skipping decryption." " pycryptodome not installed, skipping decryption."
) )
log.warning(" Install with: pip install pycryptodome") log.warning(" Install it with: pip install pycryptodome")
elif not (db.ecb or db.cbc): elif not (db.ecb or db.cbc):
log.warning( log.warning(
" [!] No keys provided, use --key to supply keys." " No keys provided. Use --key to supply keys."
) )
else: else:
ecb_count = len(db.ecb) ecb_count = len(db.ecb)
cbc_count = len(db.cbc) cbc_count = len(db.cbc)
log.info( log.info(
f" [~] Trying {ecb_count} ECB + {cbc_count} CBC key(s) across 5 AES modes..." f" Trying {ecb_count} ECB and {cbc_count} CBC key(s) across 5 AES modes..."
) )
results = decrypt_blob( results = decrypt_blob(
block, db, stop_on_first=stop_on_first_key block, db, stop_on_first=stop_on_first_key
) )
if not results: if not results:
stats.decrypt_failed += 1 stats.decrypt_failed += 1
log.info(" [-] No matching decryption key found.") log.info(" No matching decryption key found.")
log.info(" Supply keys with --key <hex|file|url>") log.info(" Supply more keys with --key <hex|file|url>.")
else: else:
for r_idx, r in enumerate(results): for r_idx, r in enumerate(results):
stats.decrypted += 1 stats.decrypted += 1
@@ -731,9 +729,9 @@ def extract_from_file(
if r.iv_used if r.iv_used
else "" else ""
) )
log.info(f" [+] Decrypted -> {dec_path}") log.info(f" Decrypted payload saved to {dec_path}")
log.info(f" [+] Key : {r.key_hex}") log.info(f" Key : {r.key_hex}")
log.info(f" [+] Mode : {r.mode_name}{iv_info}") log.info(f" Mode : {r.mode_name}{iv_info}")
entry["decrypted"] = True entry["decrypted"] = True
entry["decrypt_key"] = r.key_hex entry["decrypt_key"] = r.key_hex
entry["decrypt_mode"] = r.mode_name entry["decrypt_mode"] = r.mode_name
@@ -741,7 +739,7 @@ def extract_from_file(
if wvkb: if wvkb:
crc_status = "OK" if wvkb.crc_valid else "MISMATCH" crc_status = "OK" if wvkb.crc_valid else "MISMATCH"
log.info( log.info(
f" [+] Device ID : {dev_id_str} (CRC: {crc_status})" f" Device ID : {dev_id_str} (CRC: {crc_status})"
) )
entry["wvkb_device_id"] = dev_id_str entry["wvkb_device_id"] = dev_id_str
entry["wvkb_crc_valid"] = wvkb.crc_valid entry["wvkb_crc_valid"] = wvkb.crc_valid
@@ -750,14 +748,14 @@ def extract_from_file(
) )
with open(wvkb_path, "wb") as wf: with open(wvkb_path, "wb") as wf:
wf.write(wvkb.raw) wf.write(wvkb.raw)
log.info(f" [+] WV Keybox -> {wvkb_path}") log.info(f" Widevine keybox saved to {wvkb_path}")
if extract_wvd: if extract_wvd:
if not HAS_PYWIDEVINE: if not HAS_PYWIDEVINE:
log.info( log.info(
" [i] pywidevine not installed, cannot generate WVD." " pywidevine not installed, cannot generate WVD."
) )
log.info( log.info(
" Install with: pip install pywidevine" " Install it with: pip install pywidevine"
) )
else: else:
client_id_path = os.path.join( client_id_path = os.path.join(
@@ -784,22 +782,22 @@ def extract_from_file(
stats.wvd_generated += 1 stats.wvd_generated += 1
entry["wvd_file"] = wvd_path entry["wvd_file"] = wvd_path
log.info( log.info(
f" [+] WVD -> {wvd_path}" f" WVD saved to {wvd_path}"
) )
else: else:
log.info(" [!] WVD generation failed.") log.info(" WVD generation failed.")
else: else:
log.info( log.info(
" [i] WVD generation requires provisioned client_id.bin" " WVD generation requires a provisioned client_id.bin"
) )
log.info( log.info(
" + private_key.pem in the same directory as this script." " and private_key.pem in the same directory as this script."
) )
log.info( log.info(
" The raw keybox has been saved, use it to provision" " The raw keybox has been saved. Use it to provision"
) )
log.info( log.info(
" the device and obtain these files first." " the device and obtain these two files first."
) )
manifest_entries.append(entry) manifest_entries.append(entry)
log.info("") log.info("")
@@ -814,13 +812,13 @@ def write_manifest(entries: list, output_dir: str):
path = os.path.join(output_dir, "manifest.json") path = os.path.join(output_dir, "manifest.json")
with open(path, "w") as f: with open(path, "w") as f:
json.dump(entries, f, indent=2) json.dump(entries, f, indent=2)
log.info(f"Manifest written -> {path}") log.info(f"Manifest written to {path}")
def main() -> int: def main() -> int:
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(
prog="kbxdecoder", prog="kbxdecoder",
description="Extract and decode MStar/MediaTek Widevine keyboxes from eMMC dumps and firmware images. Decryption from ECB, CBC, CFB, OFB, and CTR modes using supplied keys (hex string, local file, or URL).", description="Extract and decode MStar/MediaTek Widevine keyboxes from eMMC dumps and firmware images. Decryption using supplied keys (hex string, local file, or URL).",
) )
parser.add_argument("files", nargs="*", help="One or more binary files to scan.") parser.add_argument("files", nargs="*", help="One or more binary files to scan.")
parser.add_argument( parser.add_argument(
@@ -896,26 +894,26 @@ def main() -> int:
os.makedirs(args.output_dir, exist_ok=True) os.makedirs(args.output_dir, exist_ok=True)
if not args.quiet and (not args.no_decrypt): if not args.quiet and (not args.no_decrypt):
if not HAS_CRYPTO: if not HAS_CRYPTO:
log.info("[!] pycryptodome not found, decryption disabled.") log.info("pycryptodome not found, decryption disabled.")
log.info(" Install with: pip install pycryptodome") log.info("Install it with: pip install pycryptodome")
if args.extract_wvd and (not HAS_PYWIDEVINE): if args.extract_wvd and (not HAS_PYWIDEVINE):
log.info("[i] pywidevine not found, WVD generation disabled.") log.info("pywidevine not found, WVD generation disabled.")
log.info(" Install with: pip install pywidevine") log.info("Install it with: pip install pywidevine")
log.info("") log.info("")
db = KeyDatabase() db = KeyDatabase()
if not args.no_decrypt and args.key: if not args.no_decrypt and args.key:
log.info("[*] Resolving key sources...") log.info("Resolving key sources...")
db = build_key_database( db = build_key_database(
key_args=args.key, key_args=args.key,
output_dir=args.output_dir, output_dir=args.output_dir,
force_update=args.force_update_keys, force_update=args.force_update_keys,
only_custom=args.only_custom, only_custom=args.only_custom,
) )
log.info(f" ECB candidates : {len(db.ecb)}") log.info(f" ECB candidates : {len(db.ecb)}")
log.info(f" CBC pairs : {len(db.cbc)}") log.info(f" CBC pairs : {len(db.cbc)}")
log.info("") log.info("")
elif not args.no_decrypt and (not args.key): elif not args.no_decrypt and (not args.key):
log.info("[!] No keys supplied. Use --key <hex|file|url> to enable decryption.") log.info("No keys supplied. Use --key <hex|file|url> to enable decryption.")
log.info("") log.info("")
stats = ScanStats() stats = ScanStats()
manifest_entries = [] manifest_entries = []