August 9, 2023


Prelude :

Recently the building where I live got an upgrade with an access controller, a bunch of tags have been distributed to the residents with $$$ to pay for extra tags, the reason I needed to start my journey into understanding and bending this technology for educational purposes (of course)


The Journey :

Recon :

First things first, the access token was looking like any NFC Tag with a keychain, I remember smartphones are able to read those, my first smartphone (a trusty and waterproof Xperia V back in the days) was promoted with the NFC feature, an quick and interesting way to share links and texts between phones, so popped my worthy Nethunter (Oneplus5T unofficial build) and tried reading it, Nothing.

Gave it another try with NFC Tool app and the only thing I got was me rubbing the tag against my phone flattering my ADHD ..

After few researches, appeared that the most common NFC tags comes in two flavors :

13.56Mhz NFC

- High Frequency
- Writable
- 2 ways
- Can store to 4Kb of data
- Can be encrypted
- Smartphone compatible

125Khz NFC

- Low Frequency
- Not Writable (*)
- 1 way
- 64 bits memory
- cannot be encrypted
- too low frequency for smartphone

Most common tags, Abusively called Mifare tags, as mifare is the chip inside responsible of the memory and frequency resonance, they’re not all mifare, hundreds of chips are available today and everyone is going with his sauce on the hard formatting and encryption, Mifare, Ntag, Slix, ST25 …etc..

a cheap alternative, low frequency based on TK4100 and EM4100 chips not designed to be writable however a writable version is available and based on T5577 chip.

There’s a number written on those, 10 digits not sure what that is yet.

However, the frequencies commonly employed for LFID (Low-Frequency Identification) or LowFID systems are typically 125kHz and 134kHz. It’s important to note that these frequencies serve as the foundation for low-frequency RFID technology. In and of themselves, these frequencies do not possess inherent functionality for activities such as ID identification, reading, or writing.

Commonly utilized low-frequency cards encompass brands like HID, T55xx, EM410x, and other similar series. These types of low-frequency cards are frequently encountered and used in everyday life by many individuals.

Not being able to read the tag with my phone suggests that it’s not a Mifare chip, I can’t go anywhere without being able to read it, shopping time !

here’s what I got :

RDM6300 Module
125Khz Cloner
0.91 Oled i²c
Hypothesis 01 :

before diving into any code, clone a tag using the cheap handheld 125Khz RFID Writer, if it works means they’re 125Khz, try writing to my current tag, if it’s possible means they are T5577 othrrwise they’re 4100 family

Hypothesis 02:

Build a reader using the RDM6300 and see what kind of informations we get from reading

Experiments : 

First hypothesis concluded, the access controller is reading  a 4100 family tag 125Khz
Building a simple reader with the RDM6300 was very interesting tho, I first went with the example code that was returning values via the serial terminal:
-------- Message-Head: 2 Message-Data (HEX): 49 (version) 0092D42D (tag) Message-Checksum (HEX): 22 Message-Tail: 3 -- Extracted Tag: 9622573 Extracted Checksum (HEX): 22 (OK) --------
#include <SoftwareSerial.h> const int BUFFER_SIZE = 14; // RFID DATA FRAME FORMAT: 1byte head (value: 2), 10byte data (2byte version + 8byte tag), 2byte checksum, 1byte tail (value: 3) const int DATA_SIZE = 10; // 10byte data (2byte version + 8byte tag) const int DATA_VERSION_SIZE = 2; // 2byte version (actual meaning of these two bytes may vary) const int DATA_TAG_SIZE = 8; // 8byte tag const int CHECKSUM_SIZE = 2; // 2byte checksum SoftwareSerial ssrfid = SoftwareSerial(4,8); uint8_t buffer[BUFFER_SIZE]; // used to store an incoming data frame int buffer_index = 0; void setup() { Serial.begin(9600); ssrfid.begin(9600); ssrfid.listen(); Serial.println(" INIT DONE"); } void loop() { if (ssrfid.available() > 0){ bool call_extract_tag = false; int ssvalue = ssrfid.read(); // read if (ssvalue == -1) { // no data was read return; } if (ssvalue == 2) { // RDM630/RDM6300 found a tag => tag incoming buffer_index = 0; } else if (ssvalue == 3) { // tag has been fully transmitted call_extract_tag = true; // extract tag at the end of the function call } if (buffer_index >= BUFFER_SIZE) { // checking for a buffer overflow (It's very unlikely that an buffer overflow comes up!) Serial.println("Error: Buffer overflow detected! "); return; } buffer[buffer_index++] = ssvalue; // everything is alright => copy current value to buffer if (call_extract_tag == true) { if (buffer_index == BUFFER_SIZE) { unsigned tag = extract_tag(); } else { // something is wrong... start again looking for preamble (value: 2) buffer_index = 0; return; } } } } unsigned extract_tag() { uint8_t msg_head = buffer[0]; uint8_t *msg_data = buffer + 1; // 10 byte => data contains 2byte version + 8byte tag uint8_t *msg_data_version = msg_data; uint8_t *msg_data_tag = msg_data + 2; uint8_t *msg_checksum = buffer + 11; // 2 byte uint8_t msg_tail = buffer[13]; // print message that was sent from RDM6300 Serial.println("--------"); Serial.print("Message-Head: "); Serial.println(msg_head); Serial.println("Message-Data (HEX): "); for (int i = 0; i < DATA_VERSION_SIZE; ++i) { Serial.print(char(msg_data_version[i])); } Serial.println(" (version)"); for (int i = 0; i < DATA_TAG_SIZE; ++i) { Serial.print(char(msg_data_tag[i])); } Serial.println(" (tag)"); Serial.print("Message-Checksum (HEX): "); for (int i = 0; i < CHECKSUM_SIZE; ++i) { Serial.print(char(msg_checksum[i])); } Serial.println(""); Serial.print("Message-Tail: "); Serial.println(msg_tail); Serial.println("--"); long tag = hexstr_to_value(msg_data_tag, DATA_TAG_SIZE); Serial.print("Extracted Tag: "); Serial.println(tag); long checksum = 0; for (int i = 0; i < DATA_SIZE; i+= CHECKSUM_SIZE) { long val = hexstr_to_value(msg_data + i, CHECKSUM_SIZE); checksum ^= val; } Serial.print("Extracted Checksum (HEX): "); Serial.print(checksum, HEX); if (checksum == hexstr_to_value(msg_checksum, CHECKSUM_SIZE)) { // compare calculated checksum to retrieved checksum Serial.print(" (OK)"); // calculated checksum corresponds to transmitted checksum! } else { Serial.print(" (NOT OK)"); // checksums do not match } Serial.println(""); Serial.println("--------"); return tag; } long hexstr_to_value(char *str, unsigned int length) { // converts a hexadecimal value (encoded as ASCII string) to a numeric value char* copy = malloc((sizeof(char) * length) + 1); memcpy(copy, str, sizeof(char) * length); copy[length] = '\0'; // the variable "copy" is a copy of the parameter "str". "copy" has an additional '\0' element to make sure that "str" is null-terminated. long value = strtol(copy, NULL, 16); // strtol converts a null-terminated string to a long value free(copy); // clean up return value; }


By cross-referencing the EM4100 datasheet and the RDM6300 example code we can tell : 

  • tags are 64-bit data
  • 9 bits in the header (probably a reading trigger)
  • D00-D93  body construction (data)
  • PR0-PR9 parity check per row
  • PC0-PC3 parity check per column
  • E0 closing bit
 Parity bit is simply summing the number of 1s in a data unit, if the sum is odd then the parity bit is 1 otherwise it’s 0, this is the standard parity check system but it’s not a general truth, some systems apply the even parity check where the parity bit is 1 when the sum of 1s is odd but that’s another story for another day
 So far it seems like the antenna is playing a simple high/low state inductor role to the reader, but in a xor style, which means low state for a 0 bit and high state for a 1 bit
Header1  1  1  1  1  1  1  1  1

Let me break this out a little bit more with an example, let’s do the reverse math for the tag I used to test on the RDM6300

the number on the tag is : 0009622573
RDM6300 is returning :
Message-Data (HEX):
49 (version) 0092D42D (tag)
Extracted Tag: 9622573

0092D42D is basically the decimal to hex conversion for (000)9622573
but that 49 marked (version) is really intriguing and doesn’t correspond to anything visible on the tag

from the comment on line 2 in the example code let’s assume this should build this way : 
(header)+ [(version)+(tag)+(tail)]

let’s do a binary table and check if this works

1  1  1  1  1  1  1  1  1
Hex TagBinaryMessageRP


the complicated math : 

header (9 bits) + message (40 bits) + row parity (10 bits) + column parity (4 bits) + closing (1 bits) = 64 bits

that’s poetically matches to the entire memory stamp of an EM4100


  • Version number cannot be retrieved by visual recognition on the tags
  • Conversion from the number on the tag to the reader seems go this way :
Serial Number (décimal) → Hexadecimal → Binary 
  • This conversion gives us the software path to emulate a tag, but also that there’s a minimum and maximum value to a tag, which means a bruteforce is practicable, in fact, since the key conversion is a 10 digits hex number [version]+[tag] means  :
  • the minimum value is [00][00000000]
  • the maximum value is [FF][FFFFFFFF]


let’s put this to practice, we can clone these tags with the handheld 125Khz cloner from EM4100 to T5577 tags that are writable, but that is restrictive and too bluky to be effectively used stealthy in the field

I need to make a gadget/tool for this kind of access controller, it’s already covered by the flipper zero and the proxmark but both suffers from a high price, lack of availability and flagged, I need something I can shape stealth and open source to the community  

Features to embed :

  • Emulate any tag 
  • Read tags and get Version & Tag hex values
  • Save read values in a slot system
  • Select a slot from the memory and replay the values
  • Bruteforce 
  • Enter Tag values manually and bruteforce the version number
  • Write to T5577 magic tags

The reading part is perfectly done via the RDM6300 so I am just going to rely on it and build a device around it, I am going with Arduino Micro spared from an old project and a 0.91″ i²c oled screen

Let’s work on the key feature, Emulating


Even if marketed as read/write module the RDM6300 is only able to read, or at least I’ve never been able to to use it for writing.

If I want to emulate a tag I will need extra electronics for that, since I suspect the antenna to be a simple inductor tuned and detuned by the EM4100 chip doing only high/low states at the right frequency, I built a simple resonating circuit that will be controlled by the µC 

normally the circuit should be tuned by the resonance formula but since that I am gonna reuse the RDM6300 antenna, I winged standard values to the components, the antenna is something around ~860uH

extra note for the software part is that the data seems to be modulating the signal through a manchester encoding, also from the datasheet ~ 

int ant = 10; int tag[64] = {1,1,1,1,1,1,1,1,1, 0,1,0,0,1,1,0,0,1,0, 0,0,0,0,0,0,0,0,0,0, 1,0,0,1,0,0,0,1,0,1, 1,1,0,1,1,0,1,0,0,1, 0,0,1,0,1,1,1,0,1,1, 0,0,0,0,0}; //490092D42D void setup() { pinMode(ant, OUTPUT); digitalWrite(ant, LOW); } void play(int clock_half, int signal) { int manchester = clock_half ^ signal; if(manchester == 0) { digitalWrite(ant, LOW); } else { digitalWrite(ant, HIGH); } } void loop() { for(int i = 0; i < 64; i++) { play(0, tag[i]); delayMicroseconds(256); play(1, tag[i]); delayMicroseconds(256); } }

And this worked incredibly well, event tried it on the reader and I’ve been gracefully granted access with a green light

Now this means we can read and emulate tags, the tag values can be retrieved from the RDM6300 we only need to convert it to 64bits binary and do the parity check

putting all this together and the NFCat will see the light


Hardware Notes :

    • Using the same antenna for the RDM6300 in reading and the tag emulator was not possible, the signal line was noisy because each one was tuning the other, solved it by air-gapping the lines with optocouplers,  PC817 from my shelves, transistors would’ve made the trick but I choosed the safety.
    • Designing a menu navigation was a pain, I am really bad with front-end coding but made my best for a minimal buttons configuration with only two buttons, well three buttons if we count the arduino reset button I used go back to the main menu, it’s resetting the AT328P and taking to the main menu 😂 ✅
    • 2 buttons, 4 functionalities :
      • Short press left/right → navigation
      • Long press left → back, cancel
      • Long press right → enter, validate
    • Main menu :
      • Read :
        • Save
        • Emulate
      • Emulate
        • Emulate
      • Credit
Posted in hacking
Write a comment