Merge pull request #3090 from joetomasone/doc/fm11rf08s-manual-recovery

doc: add FM11RF08S manual key recovery guide
This commit is contained in:
Iceman
2026-02-08 09:13:29 +01:00
committed by GitHub
+240
View File
@@ -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 <KNOWN_KEY> -f /tmp/fm11_<UID>
```
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 <UID> <SECTOR> <nt> <nt_enc> <par_err>
```
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_<uid>_<sector>_<nt>.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_<uid>_<sector>_<ntA>.dic keys_<uid>_<sector>_<ntB>.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 <FIRST_BLOCK_OF_SECTOR> -a -f keys_<uid>_<sector>_<nt>_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 <BLOCK> -b -f keys_<uid>_<sector>_<nt>_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 <SECTOR> -k <KEY_B> -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