Files
MStarToKeybox/README.md
T

245 lines
6.7 KiB
Markdown
Raw Normal View History

2026-02-24 13:00:07 -06:00
# MStarToKeybox
2026-03-15 01:06:23 -05:00
Extracts MStar/MediaTek Widevine L1 keyboxes and PlayReady passphrases from raw eMMC dumps and firmware images.
Only supports **MStar/MediaTek** SoC platforms (LG webOS, Hisense, Sharp, Philips/TPV, Panasonic, etc).
2026-03-10 18:02:43 -05:00
---
## Requirements
Python 3.8+
2026-05-18 12:17:00 -05:00
Install everything once:
2026-03-10 18:02:43 -05:00
```
2026-05-18 12:17:00 -05:00
pip install -r requirements.txt
2026-03-10 18:02:43 -05:00
```
2026-05-18 12:17:00 -05:00
This pulls in `pycryptodome`, `requests`, and `pywidevine`.
Per-script breakdown:
- **kbxtractor** has no external dependencies
- **kbxdecoder** needs `pycryptodome` for decryption, `requests` for URL key sources, and `pywidevine` for `--extract-wvd`
- **prxtractor** needs `pycryptodome`
2026-03-10 18:02:43 -05:00
---
# kbxtractor
Scans raw eMMC dumps and firmware images for MStar Widevine L1 keyboxes and extracts them as encrypted `.bin` files.
## How?
Searches a binary for `MSTAR_SECURE_STORE_FILE_MAGIC_ID` prefix and extracts 228 bytes from matches and filters them with zero-byte density, maximum zero runs, and tail entropy.
## Usage
Scan a single file:
```
python kbxtractor.py firmware.bin
```
Scan all files in the script's directory:
```
python kbxtractor.py --all
```
Save output to a specific folder:
```
python kbxtractor.py firmware.bin -o ./output
```
## Output
Found keyboxes are saved as:
```
<source>_Keybox.bin
<source>_Keybox_2.bin
```
A hex dump of each hit is printed to the console by default.
## All flags
| Flag | Description |
|---|---|
| `files` | One or more binary files to scan |
| `--all` | Scan all files in the current directory |
| `-o`, `--output-dir` | Directory to save extracted keyboxes |
| `--no-hexview` | Suppress hex dump output |
| `--verbose-filters` | Print the reason for rejections |
| `-q`, `--quiet` | Suppress all output except errors |
---
---
# kbxdecoder
Scans raw eMMC dumps and firmware images for MStar Widevine L1 keyboxes, attempts AES decryption using supplied keys. Can generate `.wvd` files.
## How?
2026-05-18 12:17:00 -05:00
Attempts to decrypt raw keybox blobs with ECB, CBC, CFB, OFB, and CTR AES modes. A successful decryption is recognised by the `INNER_MSTAR` marker. Finds the 128-byte Widevine keybox structure, extracts the Device ID, checks the CRC-32, then saves the keybox.
2026-03-10 18:02:43 -05:00
## Key sourcing
The `--key` flag accepts:
- A **hex string** - Single AES key (32, 48, or 64 hex characters for AES-128/192/256)
- A **file path** - Local `.txt` file containing one key per line, or `KEY,IV` pairs for CBC
- A **URL** - Remote key list
Pass `--key` multiple times to combine.
URL-sourced keys are cached to `keys.txt`.
## Usage
Scan with keys from a local file:
```
python kbxdecoder.py firmware.bin --key keys.txt
```
Scan with keys from a URL:
```
python kbxdecoder.py firmware.bin --key https://example.com/keys.txt
```
Scan with a single manual key:
```
2026-05-17 21:42:49 -05:00
python kbxdecoder.py firmware.bin --key <KEY>
2026-03-10 18:02:43 -05:00
```
Combine sources:
```
2026-05-17 21:42:49 -05:00
python kbxdecoder.py firmware.bin --key https://example.com/keys.txt --key ./extra.txt --key <KEY>
2026-03-10 18:02:43 -05:00
```
Scan all files in the script's directory:
```
python kbxdecoder.py --all --key keys.txt
```
## Output
Example output for found keyboxes:
```
<source>_keybox_raw.bin - Raw encrypted keybox blob
<source>_keybox_<device_id>_l1_decrypted.bin - Decrypted payload
<source>_keybox_<device_id>_l1_wvkeybox.bin - 128-byte Widevine keybox
<source>_keybox_<device_id>_l1.wvd - WVD file
manifest.json - JSON logs results
```
The `<device_id>` comes from the decrypted keybox.
## Tested AES
Each key gets tested against:
- ECB
- CBC with zero IV
- CBC with first 16 bytes of ciphertext as IV
## CBC key pairs
To supply a specific key + IV for CBC, format lines in your key file as:
```
AABBCCDD...,00112233...
```
## WVD generation
WVD generation requires pywidevine:
```
client_id.bin - Provisioned ClientIdentification
private_key.pem - Device RSA private key
```
Provision the extracted keybox.
Raw keyboxes contain the Device AES Key and provisioning token. RSA keypair is generated.
Once you have both files, run with `--extract-wvd` to generate the `.wvd`.
## Key cache
Keys are saved as `keys.txt` in the output directory.
Force a re-download with `--force-update-keys`.
## All flags
| Flag | Description |
|---|---|
| `files` | One or more binary files to scan |
| `--all` | Scan all files in the script's directory |
| `-o`, `--output-dir` | Directory to save all output files and key cache |
| `--key HEX\|FILE\|URL` | AES key source - hex string, local file, or URL |
| `--only-custom` | Only use keys passed via `--key`, skip URL cache |
| `--force-update-keys` | Force re-download of URL key sources even if cache is fresh |
| `--no-decrypt` | Skip decryption, only extract raw keybox blobs |
| `--all-matches` | Save output for every key/mode combination that succeeds |
| `--extract-wvd` | Attempt `.wvd` generation |
| `--no-hexview` | Suppress hex dump output |
| `--verbose-filters` | Print the reason for rejections |
| `-q`, `--quiet` | Suppress all output except errors |
2026-05-18 12:17:00 -05:00
---
---
# prxtractor
Extracts PlayReady passphrases from `libplayready.so` and decrypts the associated `bgroupcert.dat` / `zgpriv.dat` files. Also unwraps `zgpriv_protected.dat` for OEMs that reused the PlayReady Porting Kit default Transient/Intermediate keys.
## How?
Two paths, picked per-file by inspecting the first 8 bytes:
- Files starting with `Salted__` are decrypted with OpenSSL `EVP_BytesToKey` (MD5 KDF, AES-256-CBC) using a passphrase. The passphrase is either auto-detected by scanning the supplied `.so` for null-separated `pszBasePhrase` + `pszAdditionalPhrase` strings, or supplied with `--phrase`.
- Anything else is treated as an RFC 3394 AES-Key-Wrap blob and unwrapped with a KEK derived from the PlayReady Porting Kit hardcoded Transient Key (`8B22...427F`) and Intermediate Key (`9CE9...E136`) via AES-CMAC KDF in counter mode. First 32 bytes of the unwrapped material are written as `zgpriv`.
## Usage
Scan a library and decrypt both `.dat` files:
```
python prxtractor.py libplayready.so.0 bgroupcert.dat zgpriv.dat
```
Unwrap a `zgpriv_protected.dat` (no `.so` needed, no passphrase needed):
```
python prxtractor.py "" zgpriv_protected.dat
```
Override the auto-detected passphrase (useful when the `.so` is stripped):
```
2026-05-18 12:22:34 -05:00
python prxtractor.py libplayready.so.0 bgroupcert.dat zgpriv.dat --phrase <phrase>
2026-05-18 12:17:00 -05:00
```
List the known Panasonic passphrase candidates:
```
python prxtractor.py --list-phrases
```
## All flags
| Flag | Description |
|---|---|
| `so_path` | Path to `libplayready.so.0` (or any binary) to scan for passphrases. Optional if you only need to unwrap a `zgpriv_protected.dat`. |
| `dat_files` | One or more `.dat` files. Format is auto-detected per file. |
| `--phrase` | Override the auto-detected passphrase. |
2026-05-18 12:22:34 -05:00
| `--list-phrases` | Print the known passphrase candidates and exit. |