Document KFX / .kinf2024 decryption blocker and follow-up plan
Investigated decrypting a real Microsoft Store Kindle book (KFX). Found the content is a loose DRMION container with a sibling .voucher, and the account key material lives in .kinf2024 — a key store newer than and structurally different from the .kinf2018 that DeDRM supports (zero "/" record separators; not a version bump). No DeDRM/community support exists for .kinf2024. The blocker is the key, not KFX itself: without the account DSN/secret the voucher (and thus the book) cannot be decrypted or tested. Reverse-engineering .kinf2024 is novel work outside the "port DeDRM" premise, so per decision the findings, evidence, and a two-part follow-up plan (KFX engine port + .kinf2024 RE) are captured in docs/kfx2024-decryption-effort.md rather than implemented. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,190 @@
|
||||
# KFX + `.kinf2024` decryption — follow-up research notes
|
||||
|
||||
**Status:** parked (not implemented). This documents what we found while trying
|
||||
to test Phase 4 (KFX) on a real Microsoft Store Kindle book, why it's blocked,
|
||||
and what a future effort would need to do.
|
||||
|
||||
**Date investigated:** 2026-06-24. **Machine:** Windows 11, Kindle installed via
|
||||
Microsoft Store.
|
||||
|
||||
---
|
||||
|
||||
## TL;DR
|
||||
|
||||
- The target book (*The Chronicles of Narnia*, ASIN `B008LUYSAE`) is in **KFX**
|
||||
form: a DRMION-wrapped `.azw` plus a separate `.voucher` and `.res` resources.
|
||||
- Decrypting KFX requires a per-book content key that is itself encrypted in a
|
||||
**voucher**, unlockable only with the Kindle account's **DSN + account secret**.
|
||||
- Those credentials live in **`.kinf2024`** — a key store **newer than and
|
||||
structurally different from** the `.kinf2018` that DeDRM supports. There is
|
||||
**no published reverse-engineering** of `.kinf2024` (it is not in DeDRM_tools).
|
||||
- Therefore the blocker is **the key, not the KFX format**. Even a perfect KFX
|
||||
decryptor cannot be tested on this book until `.kinf2024` is cracked. That is
|
||||
novel reverse-engineering of a deliberately-obfuscated, rotating format —
|
||||
outside this project's original "port the DeDRM logic" premise.
|
||||
|
||||
A follow-up has **two independent parts**: (A) a KFX decryption engine (a
|
||||
tractable *port* of DeDRM) and (B) `.kinf2024` key extraction (a *research*
|
||||
problem). (A) is useless on Store-Kindle books without (B).
|
||||
|
||||
---
|
||||
|
||||
## Evidence we gathered
|
||||
|
||||
### The Kindle install (Microsoft Store)
|
||||
|
||||
- Package: `AMZNKindle.AmazonKindleReadingApp_m1sc522ngdk36`
|
||||
- Content root:
|
||||
`%LOCALAPPDATA%\Packages\AMZNKindle.AmazonKindleReadingApp_m1sc522ngdk36\LocalState\Classic\Content\`
|
||||
- Key store:
|
||||
`…\LocalState\Classic\Data\storage\.kinf2024`
|
||||
|
||||
### The book folder (`…\Content\B008LUYSAE_EBOK\`)
|
||||
|
||||
| File | Size | Notes |
|
||||
|---|---|---|
|
||||
| `B008LUYSAE_EBOK.azw` | 1,166,844 | **DRMION container** (see magic below) |
|
||||
| `amzn1.drm-voucher.v1.77b9b3e1-…-db5a276a8d6c.voucher` | 1,199 | Encrypted DRM voucher (holds the content key) |
|
||||
| `CR!….azw.res` (many) | 1–6 MB each | KFX resource containers (images/fonts/text) |
|
||||
| `B008LUYSAE_EBOK.mbpV2` | 554 | annotations/sidecar |
|
||||
| `B008LUYSAE_EBOK.phl` | 1,129 | popular-highlights sidecar |
|
||||
|
||||
First 16 bytes of `B008LUYSAE_EBOK.azw`:
|
||||
|
||||
```
|
||||
ea 44 52 4d 49 4f 4e ee e0 01 00 ea ee 9e 81 83 .DRMION.........
|
||||
… "ProtectedData" … "amz…"
|
||||
```
|
||||
|
||||
`\xeaDRMION\xee` is the KFX DRMION magic. **Note:** this is a *loose* DRMION
|
||||
file, not a `.kfx-zip`. (Our current `Format::detect` only recognizes DRMION
|
||||
*inside a ZIP* — see "DRMLibre changes needed" below.)
|
||||
|
||||
### `.kinf2024` facts
|
||||
|
||||
- Size: 86,477 bytes.
|
||||
- **Zero `/` (0x2F) bytes.** `.kinf2018`/`.kinf2011` are `/`-delimited record
|
||||
streams; the absence of any `/` means `.kinf2024` uses a **completely
|
||||
different serialization** — this is not a renamed `.kinf2018`.
|
||||
- Content is char-map-style encoded (first 64 chars looked like
|
||||
`bAY8YkY8bkd8AfdYA69v08b799A1AC0YC0009SAAY0d8CM94Ytz992zhb6Yt9J0J`).
|
||||
- No legacy `.kinf2018`/`.kinf2011` exists anywhere on this machine (no old
|
||||
desktop "Kindle for PC" install to fall back on).
|
||||
|
||||
There is also a Google-Drive-synced `~\My Drive\Documents\My Kindle Content\`
|
||||
with many classic `*_EBOK.azw` files from an older install. Those are also
|
||||
account-locked and would need the same Kindle key material to decrypt.
|
||||
|
||||
---
|
||||
|
||||
## Background: how KFX decryption works (the tractable part — a port)
|
||||
|
||||
Reference: `DeDRM_tools/DeDRM_plugin/kfxdedrm.py`, `ion.py`, `kfxtables.py`.
|
||||
|
||||
1. **Identify the pieces.** A KFX book is a DRMION content file (`…\xeaDRMION\xee`)
|
||||
plus a `*.voucher` (the `DrmIonVoucher`). Both are **Amazon Ion** binary.
|
||||
2. **Parse the voucher** (Ion) to read the encryption algorithm (AES), transform
|
||||
(CBC), hash (SHA-256) and the **lock parameters**: `CLIENT_ID` = DSN,
|
||||
`ACCOUNT_SECRET` = account token.
|
||||
3. **Derive the voucher key.** Build `shared = "PIDv3" + algos + lockparams`, run
|
||||
it through **13 obfuscation variants** (`ion.py` `obfuscate*` / `process_V*`,
|
||||
~lines 1051–1610), then `key = HMAC_SHA256(secret, b"PIDv3")[:32]`. AES-256-CBC
|
||||
decrypt the voucher with its IV, PKCS#7-unpad → a `KeySet` → the `SecretKey`
|
||||
(`DrmIonVoucher.decryptvoucher`, ~lines 1643–1699).
|
||||
4. **Decrypt the DRMION** content with the `SecretKey` (AES-CBC) and repackage a
|
||||
DRM-free KFX. (Output is decrypted **KFX**, not EPUB — same limitation DeDRM
|
||||
has; a separate KFX→EPUB converter would be needed for a readable EPUB.)
|
||||
|
||||
What a Rust port needs:
|
||||
- A **minimal Amazon-Ion binary reader** (enough for the voucher + DRMION).
|
||||
- The **YJ shared symbol table** (the 2 MB `kfxtables.py`) embedded as a
|
||||
compressed `include_bytes!` blob, to resolve symbol IDs used in the voucher.
|
||||
- **LZMA** (`lzma-rs`) for compressed Ion LOBs — verify it handles KFX's framing.
|
||||
- Crypto we already have: AES-256-CBC (add 256-bit path to `crypto.rs`),
|
||||
HMAC-SHA256 (`hmac` crate), SHA-256, PKCS#7 unpad.
|
||||
|
||||
Risk: the 13-variant key derivation and the symbol-table embedding are fiddly,
|
||||
and "Amazon changes KFX DRM frequently" (DeDRM's own warning).
|
||||
|
||||
## Background: `.kinf2018` (closest documented relative of `.kinf2024`)
|
||||
|
||||
Reference: `kindlekey.py::getDBfromFile`, ~lines 419–572, and `getK4Pids`
|
||||
(`kgenpids.py` ~219–309). For **version 6 / `.kinf2018`**:
|
||||
|
||||
- File = `/`-separated records; first record is a DPAPI-protected header
|
||||
(`UnprotectHeaderData`) decoded with `testMap1`, containing
|
||||
`[Version:6][Build:b][Cksum:…][Guid:g]`.
|
||||
- Per-key derivation:
|
||||
`salt = str(0x6D8*build) + guid`,
|
||||
`sp = GetUserName() + "+@#$%+" + GetIDString()`,
|
||||
`passwd = encode(SHA256(sp), charMap5)`,
|
||||
`key = PBKDF2(passwd, salt, 10000, 0x400)[:32]`.
|
||||
- Each value: rearrange (prime-offset) → `decode(testMap8)` → `iv||ciphertext`,
|
||||
with `iv = iv[:12] + 00 00 00 02`, then **AES-CTR** decrypt → `decode(charMap5)`.
|
||||
- `GetIDString()` = disk volume serial / MAC-derived machine id.
|
||||
|
||||
The useful outputs are `kindle.account.tokens` (account secret) and `DSN`
|
||||
(or the inputs to derive it: `MazamaRandomNumber` + `IDString` + `UsernameHash`).
|
||||
These feed **both** `getK4Pids` (for MOBI/AZW PIDs) **and** the KFX voucher lock
|
||||
params (`CLIENT_ID`=DSN, `ACCOUNT_SECRET`=token).
|
||||
|
||||
**What's unknown for `.kinf2024`:** the serialization (no `/` records), the
|
||||
header/version encoding, the per-value cipher and key derivation, and what
|
||||
machine/account entropy it binds to. All of this must be reverse-engineered.
|
||||
|
||||
---
|
||||
|
||||
## What a follow-up needs to do
|
||||
|
||||
### Part A — KFX decryption engine (port; ~the original Phase 4)
|
||||
1. Extend `Format::detect` to recognize a **loose DRMION** file (starts with
|
||||
`\xeaDRMION\xee`) as KFX, in addition to KFX-ZIP, and to recognize a Kindle
|
||||
content **folder** (`.azw` DRMION + `*.voucher` + `*.res`).
|
||||
2. Minimal Ion binary reader (`kindle/kfx/ion.rs`).
|
||||
3. Embedded YJ symbol table (`kindle/kfx/symbols.rs`, compressed blob).
|
||||
4. Voucher key derivation + DRMION decrypt (`kindle/kfx/voucher.rs`).
|
||||
5. Add AES-256-CBC + HMAC-SHA256 helpers to `crypto.rs`; add `lzma-rs`.
|
||||
6. Repackage decrypted KFX; wire the `KfxZip`/`Kfx` branch in `decrypt.rs`.
|
||||
|
||||
### Part B — `.kinf2024` key extraction (research; the real blocker)
|
||||
1. **Decode the container.** Figure out `.kinf2024`'s framing (it is *not*
|
||||
`/`-delimited). Inspect for length-prefixes / a new separator / base-N blocks.
|
||||
2. **Header + version.** Find how the `[Version:…][Build:…][Guid:…]` (or its
|
||||
successor) is stored; confirm whether the header is still DPAPI-protected.
|
||||
3. **Per-value crypto.** Determine the v7+ key derivation and cipher (is it still
|
||||
PBKDF2 + AES-CTR? new salt/entropy? new char-maps?).
|
||||
4. **Goal outputs:** `kindle.account.tokens` and `DSN` (or `MazamaRandomNumber`
|
||||
+ `IDString` + `UsernameHash`).
|
||||
5. Likely needs dynamic analysis of the Store Kindle app (the encoder is in the
|
||||
app), or watching for any community RE of `.kinf2024`.
|
||||
|
||||
Once Part B yields DSN + account secret, Part A can be validated on the Narnia
|
||||
book, and `getK4Pids` (already ported in `kindle/pid.rs`: `encode`,
|
||||
`encode_hash`, `generate_device_pid`) lets the existing **MOBI** path also
|
||||
decrypt account-locked classic `.azw` files.
|
||||
|
||||
---
|
||||
|
||||
## DRMLibre changes already implied
|
||||
- `crates/drmlibre-core/src/format.rs`: add loose-DRMION detection + a `Kfx`
|
||||
(non-zip) variant; currently such a file returns "can't determine file type".
|
||||
- `crates/drmlibre-core/src/kindle/pid.rs`: `encode`/`encode_hash`/
|
||||
`generate_device_pid` are ported and tested but `#[allow(dead_code)]` until
|
||||
`getK4Pids` (the Kindle-DB → PID path) is wired — that wiring is the natural
|
||||
companion to Part B.
|
||||
- `crates/drmlibre-core/src/extract/`: a `kindle` extractor would live here,
|
||||
alongside the working Windows Adobe extractor.
|
||||
|
||||
## Open questions / risks
|
||||
- `.kinf2024` may bind to hardware/account in new ways; extraction might only
|
||||
work on the same machine where Kindle is activated (acceptable for our use).
|
||||
- KFX DRM is a moving target; a port valid today may break on future books.
|
||||
- Output of Part A is decrypted **KFX**, not EPUB — readability needs a separate
|
||||
KFX→EPUB step (out of scope here).
|
||||
|
||||
## References
|
||||
- DeDRM_tools: `DeDRM_plugin/kfxdedrm.py`, `ion.py` (`DrmIonVoucher`,
|
||||
`obfuscate*`/`process_V*`), `kfxtables.py` (symbol table), `kindlekey.py`
|
||||
(`getDBfromFile`, `.kinf2018` v5/v6), `kgenpids.py` (`getK4Pids`).
|
||||
- Our notes on the KFX crypto core are in the project plan
|
||||
(`~/.claude/plans/fizzy-sparking-wadler.md`, Phase 4 section).
|
||||
Reference in New Issue
Block a user