diff --git a/doc/fm11rf08s_manual_recovery.md b/doc/fm11rf08s_manual_recovery.md new file mode 100644 index 000000000..7b7d6939c --- /dev/null +++ b/doc/fm11rf08s_manual_recovery.md @@ -0,0 +1,240 @@ +# Cracking FM11RF08S Hotel Cards with Proxmark3 + +A step-by-step guide for recovering all sector keys from Fudan FM11RF08S MIFARE Classic 1K cards when standard attacks fail. + +## Background + +The FM11RF08S is a Chinese clone of the NXP MIFARE Classic 1K. Hotels use them because they're cheap. The chip has a well-documented backdoor (see [Doegox's paper](https://eprint.iacr.org/2024/1275)) and a quirk that breaks every standard Proxmark3 attack: its first-auth nonce behaves like a weak PRNG, but its nested-auth nonce is **static and encrypted**. This causes: + +- `hf mf nested` → "PRNG is not predictable" (checks first nonce, sees it changing unpredictably) +- `hf mf staticnested` → "Normal nonce detected" (also checks first nonce, doesn't see static behavior) +- `hf mf hardnested` → "Static encrypted nonce detected. Aborted" (hits the static nested nonce) + +None of these handle the hybrid correctly. The official recovery script (`fm11rf08s_recovery.py`) is supposed to automate the process, but it has a long history of breakage across platforms — missing Python SWIG bindings, hardcoded tool paths, Unicode crashes, and backdoor auth failures are all documented in the Iceman repo (see [Known Issues](#known-issues-in-the-iceman-repo) below). + +This guide documents a **manual pipeline** that bypasses all of those failure points using `hf mf isen` for nonce collection, offline cracking tools, and dictionary brute force. It works reliably where the script doesn't. + +## Requirements + +- **Proxmark3 RDV4** (or compatible) with Iceman firmware +- Card on the reader for the duration +- At least **one known sector key** (autopwn usually finds several via dictionary) +- The `staticnested_1nt` and `staticnested_2x1nt_rf08s` offline tools (included with Iceman firmware, typically at `/usr/local/share/proxmark3/tools/`) + +## Step 1: Identify the Card + +``` +hf 14a reader +``` + +Note the UID (you'll need it for offline cracking). Confirm it reads cleanly — if you see "Multiple tags detected" or BCC errors, remove all other NFC devices from the field. + +Then: + +``` +hf mf info +``` + +Look for: +``` +[+] Fudan FM11RF08S 0490 +[+] Prng....... weak +[+] Static enc nonce... yes +``` + +If you see this, you have an FM11RF08S. The output will also show the backdoor key (typically `A396EFA4E24F`). + +> **Note:** The backdoor key uses a special authentication protocol. It does NOT work as a regular sector key — don't waste time trying `hf mf rdbl -k A396EFA4E24F`. + +## Step 2: Run Autopwn for Known Keys + +``` +hf mf autopwn +``` + +This will find keys via dictionary for most sectors. Hotel cards commonly use: +- `A0A1A2A3A4A5` (MAD key) +- `FFFFFFFFFFFF` (transport default) +- Various vendor-specific keys + +Write down which sectors remain **unsolved** (shown as `------------ | 0` in the results table). These are your targets. + +## Step 3: Collect Static Encrypted Nonces + +Using any known key (sector 0 Key A is usually the easiest): + +``` +hf mf isen --collect_fm11rf08s_without_backdoor --blk 0 -a -k -f /tmp/fm11_ +``` + +Example: +``` +hf mf isen --collect_fm11rf08s_without_backdoor --blk 0 -a -k A0A1A2A3A4A5 -f /tmp/fm11_FED093E1 +``` + +This takes about 500ms and saves a JSON file containing the static encrypted nonce (`nt_enc`), plaintext nonce (`nt`), and parity errors (`par_err`) for every sector and key type (A/B), plus the backdoor sector 32. + +> **If this fails with "Auth1 error":** The card may have moved. Run `hf 14a reader` to confirm it's still on the antenna, then retry. + +### Understanding the Nonce Data + +Open the JSON file and look at the `nt` values. If a sector has **identical** `nt.a` and `nt.b` values, Key A and Key B are the same for that sector. Different values mean different keys that each need cracking separately. + +## Step 4: Generate Key Candidates (Offline) + +For each **unknown** key, run the offline cracker. The tool location is typically: +``` +/usr/local/share/proxmark3/tools/staticnested_1nt +``` + +Syntax: +``` +staticnested_1nt +``` + +Pull the values from your JSON file. Example for sector 1 Key A: +``` +/usr/local/share/proxmark3/tools/staticnested_1nt FED093E1 1 573B3263 C2BCB6D5 1000 +``` + +This generates a dictionary file in the current directory named `keys___.dic` containing tens of thousands of candidate keys. + +### Cross-Reference (Optional but Recommended) + +If you cracked candidates for **both** Key A and Key B of the same sector, cross-reference them to reduce the candidate count: + +``` +/usr/local/share/proxmark3/tools/staticnested_2x1nt_rf08s keys___.dic keys___.dic +``` + +This produces `_filtered.dic` files with significantly fewer candidates. + +## Step 5: Brute Force Against the Card + +Feed the candidate dictionary back to the Proxmark3: + +``` +hf mf fchk --blk -a -f keys____filtered.dic +``` + +Block numbers: sector × 4. So sector 1 = block 4, sector 6 = block 24, etc. + +For Key B, use `-b` instead of `-a`: +``` +hf mf fchk --blk -b -f keys____filtered.dic +``` + +The PM3 tests approximately 85 keys/second. With 10K-15K filtered candidates, expect **1-3 minutes per key**. + +Repeat for every unknown key. + +## Step 6: Full Dump + +Once all keys are known, create a text file with every unique key (one per line): + +``` +cat > /tmp/all_keys.dic << 'EOF' +A0A1A2A3A4A5 +B578F38A5C61 +BE06F0345308 +465C70A57077 +0000014B5C31 +38690062A482 +FFFFFFFFFFFF +EOF +``` + +Then dump: +``` +hf mf autopwn --1k -f /tmp/all_keys.dic +``` + +This will authenticate every sector with the correct key, dump all data, and save `.json`, `.bin`, and `.key.bin` files. + +## Step 7: Analyze the Dump + +Check the MAD (MIFARE Application Directory) to identify what systems are on the card: + +``` +hf mf mad -k A0A1A2A3A4A5 +``` + +Common hotel MAD application IDs: +| AID | System | Vendor | +|-----|--------|--------| +| 7005 | Energy Saving (room power slot) | ENKOA System | +| 7006 | Hotel access control (publisher) | Vingcard / ASSA ABLOY | +| 7007 | Hotel access control & security | Vingcard / ASSA ABLOY | +| 7009 | Electronic lock access data | Timelox AB | + +## Why Not Just Use the Recovery Script? + +The Iceman firmware includes `fm11rf08s_recovery.py` (by Doegox) which automates this entire process. In theory, you just run `script run fm11rf08s_recovery` and it handles everything. In practice, it breaks constantly: + +- **Missing `_pm3` SWIG module** — The script imports a C extension that must be compiled during the PM3 build. Many installations (especially Homebrew on macOS) don't build it. You get `ModuleNotFoundError: No module named '_pm3'` or a `SIGTRAP` crash. +- **Hardcoded tool paths** ([#2689](https://github.com/RfidResearchGroup/proxmark3/issues/2689)) — The script looks for `staticnested_1nt` at a relative path that doesn't exist if your package manager installs PM3 differently (Arch Linux, Homebrew, etc.). +- **Backdoor auth failures** ([#2553](https://github.com/RfidResearchGroup/proxmark3/issues/2553)) — `--collect_fm11rf08s` uses the backdoor key for initial auth. On some FM11RF08S variants, the backdoor auth command fails silently, returning all zeros. The `--collect_fm11rf08s_without_backdoor` flag was added as a workaround. +- **Unicode crashes** ([#2838](https://github.com/RfidResearchGroup/proxmark3/pull/2838)) — Non-ASCII data in card blocks causes `UnicodeEncodeError` during dump display. Fixed May 2025. +- **Path parsing breakage** ([#2766](https://github.com/RfidResearchGroup/proxmark3/issues/2766)) — A console output formatting change broke the script's ability to find its own file paths. + +There's also an open feature request ([#2565](https://github.com/RfidResearchGroup/proxmark3/issues/2565)) asking `autopwn` to detect FM11RF08S cards and suggest the recovery script automatically, instead of just failing with contradictory error messages. As of this writing, `autopwn` still gives no guidance when it hits the static nonce wall. + +The manual pipeline in this guide does exactly what the script does, but each step is a standalone command that works independently. If one step fails, you can debug it in isolation rather than digging through Python stack traces. + +## Troubleshooting + +### "Multiple tags detected. Collision after Bit 32" +Another NFC device is in the field. Remove phones, wallets, other cards. The PM3 antenna is sensitive — even an NFC-enabled phone a few inches away can cause collisions. + +### fm11rf08s_recovery.py crashes with SIGTRAP or "No module named '_pm3'" +The Python SWIG bindings weren't compiled during the PM3 build. This is common on macOS (Homebrew) and some Linux distros. Use the manual pipeline described above instead — it does the same thing without the Python dependency. + +### "Auth1 error" on isen --collect_fm11rf08s +The **backdoor auth command** isn't working. This is a [known issue](https://github.com/RfidResearchGroup/proxmark3/issues/2553) — some FM11RF08S variants don't respond to the backdoor protocol, or the backdoor key is different from the default `A396EFA4E24F`. Use `--collect_fm11rf08s_without_backdoor` with a known key instead (requires `--blk` and `-a`/`-b` flags). + +### Sector reads fail with "Cmd Error 04" even with the correct key +Check the access bits. Some sectors are configured so Key A can authenticate but NOT read data blocks. Try reading with Key B instead: +``` +hf mf rdsc -s -k -b +``` + +### staticnested_1nt returns "failed to change user ID" +This happens when the shell expands a variable that looks like a UID flag. Use the hex values directly without shell variables, or quote them. + +### staticnested_1nt or staticnested_2x1nt_rf08s not found +The offline tools are installed separately from the PM3 client. Check: +``` +find /usr/local -name "staticnested_1nt" 2>/dev/null +find /usr/share -name "staticnested_1nt" 2>/dev/null +``` +Common locations: `/usr/local/share/proxmark3/tools/` or `/usr/share/proxmark3/tools/`. If missing, rebuild PM3 from source — the tools are compiled as part of the standard build. + +## Timing Expectations + +| Step | Duration | +|------|----------| +| Card identification | 2 seconds | +| Autopwn (dictionary) | 10-30 seconds | +| Nonce collection | <1 second | +| Offline candidate generation (per key) | 1-5 seconds | +| Cross-reference filtering | <1 second | +| Online brute force (per key) | 1-3 minutes | +| Full dump with all keys | 10-15 seconds | +| **Total for 3 unknown keys** | **~15 minutes** | + +## Known Issues in the Iceman Repo + +| Issue | Description | Status | +|-------|-------------|--------| +| [#2553](https://github.com/RfidResearchGroup/proxmark3/issues/2553) | `isen --collect_fm11rf08s` Auth1 error — backdoor auth fails on some cards | Closed | +| [#2565](https://github.com/RfidResearchGroup/proxmark3/issues/2565) | Feature request: autopwn should suggest fm11rf08s_recovery on static nonce | Open | +| [#2689](https://github.com/RfidResearchGroup/proxmark3/issues/2689) | Scripts use hardcoded tool paths — breaks on non-standard installs | Closed | +| [#2766](https://github.com/RfidResearchGroup/proxmark3/issues/2766) | fm11rf08_recovery.py broken by console output formatting change | Closed | +| [#2838](https://github.com/RfidResearchGroup/proxmark3/pull/2838) | UnicodeDecodeError in fm11rf08s_full.py on non-ASCII card data | Merged | +| [#2571](https://github.com/RfidResearchGroup/proxmark3/pull/2571) | QoL: save dumps to prefs path, option to keep generated dictionaries | Merged | + +## References + +- Doegox (2024) — [FM11RF08S backdoor and static nonce analysis](https://eprint.iacr.org/2024/1275) +- Iceman Proxmark3 firmware — [GitHub](https://github.com/RfidResearchGroup/proxmark3) +- MIFARE Application Directory (MAD) — NXP AN10787