Hidden Data in NFC Tags
I've just got a set of wearable NFC tags, and I've discovered something interesting about the way data is stored on them.
tl;dr Overwriting a tag can leave old data intact, and still readable.
Here's the decoded memory layout of a tag with data written to it. In this case, a (failed) experiment at storing a JavaScript pop-up.
# NDEF message:
[00] D1 01 7D 55 00 64 61 74 61 3A 74 65 78 74 2F 68 |.␁}U␀data:text/h|
[10] 74 6D 6C 3B 63 68 61 72 73 65 74 3D 75 74 66 2D |tml;charset=utf-|
[20] 38 2C 25 33 43 25 32 31 44 4F 43 54 59 50 45 25 |8,%3C%21DOCTYPE%|
[30] 32 30 68 74 6D 6C 25 33 45 25 33 43 62 6F 64 79 |20html%3E%3Cbody|
[40] 25 33 45 25 33 43 73 63 72 69 70 74 25 33 45 61 |%3E%3Cscript%3Ea|
[50] 6C 65 72 74 25 32 38 25 32 32 68 69 25 32 32 25 |lert%28%22hi%22%|
[60] 32 39 25 33 42 25 33 43 25 32 46 73 63 72 69 70 |29%3B%3C%2Fscrip|
[70] 74 25 33 45 25 33 43 25 32 46 62 6F 64 79 25 33 |t%3E%3C%2Fbody%3|
[80] 45 |E |
As the experiment didn't work, I decided to rewrite the tag with some much shorter data. Here's the new message.
# NDEF message:
[00] D1 02 36 53 70 91 01 12 55 00 68 74 74 70 73 3A |.␂6Sp.␁␒U␀https:|
[10] 2F 2F 65 64 65 6E 74 2E 74 65 6C 51 01 1C 54 02 |//edent.telQ␁␜T␂|
[20] 65 6E 54 65 72 65 6E 63 65 27 73 20 63 6F 6E 74 |enTerence's␠cont|
[30] 61 63 74 20 64 65 74 61 69 6C 73 |act␠details |
But that's not the whole story! The above is the NDEF (NFC Data Exchange Format) message which has been decoded. Let's look at the full memory layout:
# Memory content:
[00] * 04:01:2A A7 (UID0-UID2, BCC0)
[01] * 62:F4:48:81 (UID3-UID6)
[02] . 5F 48 00 00 (BCC1, INT, LOCK0-LOCK1)
[03] . E1:10:12:00 (OTP0-OTP3)
[04] . 03 3B D1 02 |␃;.␂|
[05] . 36 53 70 91 |6Sp.|
[06] . 01 12 55 00 |␁␒U␀|
[07] . 68 74 74 70 |http|
[08] . 73 3A 2F 2F |s://|
[09] . 65 64 65 6E |eden|
[0A] . 74 2E 74 65 |t.te|
[0B] . 6C 51 01 1C |lQ␁␜|
[0C] . 54 02 65 6E |T␂en|
[0D] . 54 65 72 65 |Tere|
[0E] . 6E 63 65 27 |nce'|
[0F] . 73 20 63 6F |s␠co|
[10] . 6E 74 61 63 |ntac|
[11] . 74 20 64 65 |t␠de|
[12] . 74 61 69 6C |tail|
[13] . 73 FE 00 00 |s.␀␀|
[14] . 64 79 25 33 |dy%3|
[15] . 45 25 33 43 |E%3C|
[16] . 73 63 72 69 |scri|
[17] . 70 74 25 33 |pt%3|
[18] . 45 61 6C 65 |Eale|
[19] . 72 74 25 32 |rt%2|
[1A] . 38 25 32 32 |8%22|
[1B] . 68 69 25 32 |hi%2|
[1C] . 32 25 32 39 |2%29|
[1D] . 25 33 42 25 |%3B%|
[1E] . 33 43 25 32 |3C%2|
[1F] . 46 73 63 72 |Fscr|
[20] . 69 70 74 25 |ipt%|
[21] . 33 45 25 33 |3E%3|
[22] . 43 25 32 46 |C%2F|
[23] . 62 6F 64 79 |body|
[24] . 25 33 45 FE |%3E.|
[25] . 00 00 00 00 |␀␀␀␀|
[26] . 00 00 00 00 |␀␀␀␀|
[27] . 00 00 00 00 |␀␀␀␀|
[28] . 00 00 00 BD (LOCK2-LOCK4, CHK)
[29] . 04 00 00 FF (CFG, MIRROR, AUTH0)
[2A] . 00 05 -- -- (ACCESS)
[2B] +P FF FF FF FF (PWD0-PWD3)
[2C] +P 00 00 -- -- (PACK0-PACK1)
You can see the new data is at the start, then there is a gap, and then the remainder of the old data is present!
This is due to this line:
[05] . 36 53 70 91 |6Sp.|
The 36
is the Payload Length - in this case 0x36
bytes of data. In other words, the header says "This data is 54 bytes in length".
Most phones' built-in NFC readers grab the first part and correctly interpret it and stop there.
But using a reader like NXP Tag Info will allow you to see all of the data hidden in the tag.
So, go around scanning NFC tags (if you can find any), and see what leftover data is still present.
To ensure this doesn't happen to you, make sure to erase and format any tags before rewriting them.
Steve Lake says:
That's very interesting - It's the first time I've heard of it for NFC, but this is standard for most file systems, to be honest. You can delete data and it just removes the header information from the index tables, the data itself just remains in limbo until it's over-written.