2017-08-26 19:01:35 +08:00
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// Craig Young, 2014
|
|
|
|
// Christian Herrmann, 2017
|
|
|
|
//
|
|
|
|
// This code is licensed to you under the terms of the GNU GPL, version 2 or,
|
|
|
|
// at your option, any later version. See the LICENSE.txt file for the text of
|
|
|
|
// the license.
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// main code for HF standalone mode Mifare /sniff/emulation by Craig Young
|
|
|
|
//-----------------------------------------------------------------------------
|
2019-07-16 19:50:38 +08:00
|
|
|
|
2019-08-08 22:57:33 +08:00
|
|
|
#include "standalone.h" // standalone definitions
|
|
|
|
#include <inttypes.h>
|
|
|
|
#include "proxmark3_arm.h"
|
|
|
|
#include "appmain.h"
|
|
|
|
#include "fpgaloader.h"
|
|
|
|
#include "util.h"
|
|
|
|
#include "dbprint.h"
|
|
|
|
#include "ticks.h"
|
|
|
|
#include "string.h"
|
|
|
|
#include "commonutil.h"
|
|
|
|
#include "mifarecmd.h"
|
|
|
|
#include "iso14443a.h"
|
|
|
|
#include "protocols.h"
|
|
|
|
|
|
|
|
#define OPTS 2
|
2017-08-26 19:01:35 +08:00
|
|
|
|
|
|
|
typedef struct {
|
2019-03-09 18:19:45 +08:00
|
|
|
uint8_t uid[10];
|
|
|
|
uint8_t uidlen;
|
|
|
|
uint8_t atqa[2];
|
|
|
|
uint8_t sak;
|
2019-07-16 19:50:38 +08:00
|
|
|
} PACKED card_clone_t;
|
2017-08-26 19:01:35 +08:00
|
|
|
|
|
|
|
|
2019-04-26 03:44:34 +08:00
|
|
|
void ModInfo(void) {
|
2019-07-10 04:49:57 +08:00
|
|
|
DbpString(" HF Mifare sniff/simulation - (Craig Young)");
|
2019-04-26 03:44:34 +08:00
|
|
|
}
|
|
|
|
|
2020-05-10 22:59:38 +08:00
|
|
|
void RunMod(void) {
|
2019-03-09 18:19:45 +08:00
|
|
|
StandAloneMode();
|
|
|
|
Dbprintf(">> Craig Young Mifare sniff UID/clone uid 2 magic/sim a.k.a YoungRun Started <<");
|
|
|
|
FpgaDownloadAndGo(FPGA_BITSTREAM_HF);
|
|
|
|
|
|
|
|
int selected = 0, playing = 0, iGotoRecord = 0, iGotoClone = 0;
|
|
|
|
int cardRead[OPTS] = {0};
|
|
|
|
|
|
|
|
card_clone_t uids[OPTS];
|
|
|
|
iso14a_card_select_t card[OPTS];
|
|
|
|
uint8_t params = (MAGIC_SINGLE | MAGIC_DATAIN);
|
|
|
|
|
|
|
|
LED(selected + 1, 0);
|
|
|
|
|
|
|
|
for (;;) {
|
|
|
|
WDT_HIT();
|
|
|
|
// exit from Standalone Mode, send a usbcommand.
|
2019-06-03 06:01:08 +08:00
|
|
|
if (data_available()) return;
|
2019-03-09 18:19:45 +08:00
|
|
|
|
|
|
|
SpinDelay(300);
|
|
|
|
|
|
|
|
if (iGotoRecord == 1 || cardRead[selected] == 0) {
|
|
|
|
iGotoRecord = 0;
|
|
|
|
LEDsoff();
|
|
|
|
LED(selected + 1, 0);
|
2019-06-02 17:47:10 +08:00
|
|
|
LED(LED_D, 0);
|
2019-03-09 18:19:45 +08:00
|
|
|
|
|
|
|
// record
|
|
|
|
Dbprintf("Enabling iso14443a reader mode for [Bank: %d]...", selected);
|
|
|
|
/* need this delay to prevent catching some weird data */
|
|
|
|
SpinDelay(500);
|
|
|
|
iso14443a_setup(FPGA_HF_ISO14443A_READER_MOD);
|
|
|
|
|
|
|
|
for (;;) {
|
|
|
|
// exit from Standalone Mode, send a usbcommand.
|
2019-06-03 06:01:08 +08:00
|
|
|
if (data_available()) return;
|
2019-03-09 18:19:45 +08:00
|
|
|
|
|
|
|
if (BUTTON_PRESS()) {
|
|
|
|
if (cardRead[selected]) {
|
|
|
|
Dbprintf("Button press detected -- replaying card in bank[%d]", selected);
|
|
|
|
break;
|
2019-03-10 07:00:59 +08:00
|
|
|
} else if (cardRead[(selected + 1) % OPTS]) {
|
|
|
|
Dbprintf("Button press detected but no card in bank[%d] so playing from bank[%d]", selected, (selected + 1) % OPTS);
|
|
|
|
selected = (selected + 1) % OPTS;
|
2019-03-09 18:19:45 +08:00
|
|
|
break; // playing = 1;
|
|
|
|
} else {
|
|
|
|
Dbprintf("Button press detected but no stored tag to play. (Ignoring button)");
|
|
|
|
SpinDelay(300);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!iso14443a_select_card(NULL, &card[selected], NULL, true, 0, true)) {
|
|
|
|
continue;
|
|
|
|
} else {
|
|
|
|
Dbprintf("Read UID:");
|
|
|
|
Dbhexdump(card[selected].uidlen, card[selected].uid, 0);
|
|
|
|
|
2019-03-10 07:00:59 +08:00
|
|
|
if (memcmp(uids[(selected + 1) % OPTS].uid, card[selected].uid, card[selected].uidlen) == 0) {
|
2019-03-09 18:19:45 +08:00
|
|
|
Dbprintf("Card selected has same UID as what is stored in the other bank. Skipping.");
|
|
|
|
} else {
|
|
|
|
uids[selected].sak = card[selected].sak;
|
|
|
|
uids[selected].uidlen = card[selected].uidlen;
|
2019-03-10 07:00:59 +08:00
|
|
|
memcpy(uids[selected].uid, card[selected].uid, uids[selected].uidlen);
|
2019-03-09 18:19:45 +08:00
|
|
|
memcpy(uids[selected].atqa, card[selected].atqa, 2);
|
|
|
|
|
|
|
|
if (uids[selected].uidlen > 4)
|
|
|
|
Dbprintf("Bank[%d] received a 7-byte UID", selected);
|
|
|
|
else
|
|
|
|
Dbprintf("Bank[%d] received a 4-byte UID", selected);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Dbprintf("ATQA = %02X%02X", uids[selected].atqa[0], uids[selected].atqa[1]);
|
|
|
|
Dbprintf("SAK = %02X", uids[selected].sak);
|
|
|
|
LEDsoff();
|
2019-09-13 22:27:25 +08:00
|
|
|
LED(LED_B, 200);
|
2019-06-02 17:47:10 +08:00
|
|
|
LED(LED_A, 200);
|
2019-09-13 22:27:25 +08:00
|
|
|
LED(LED_B, 200);
|
2019-06-02 17:47:10 +08:00
|
|
|
LED(LED_A, 200);
|
2019-03-09 18:19:45 +08:00
|
|
|
|
|
|
|
LEDsoff();
|
|
|
|
LED(selected + 1, 0);
|
|
|
|
|
|
|
|
// Next state is replay:
|
|
|
|
playing = 1;
|
|
|
|
|
|
|
|
cardRead[selected] = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* MF Classic UID clone */
|
2019-03-10 07:00:59 +08:00
|
|
|
else if (iGotoClone == 1) {
|
|
|
|
iGotoClone = 0;
|
2019-03-09 18:19:45 +08:00
|
|
|
LEDsoff();
|
|
|
|
LED(selected + 1, 0);
|
2019-06-02 17:47:10 +08:00
|
|
|
LED(LED_A, 250);
|
2019-03-09 18:19:45 +08:00
|
|
|
|
|
|
|
// magiccards holds 4bytes uid. *usually*
|
|
|
|
uint32_t tmpuid = bytes_to_num(uids[selected].uid, 4);
|
|
|
|
|
|
|
|
// record
|
|
|
|
Dbprintf("Preparing to Clone card [Bank: %d]; uid: %08x", selected, tmpuid);
|
|
|
|
|
|
|
|
// wait for button to be released
|
|
|
|
// Delay cloning until card is in place
|
|
|
|
while (BUTTON_PRESS())
|
|
|
|
WDT_HIT();
|
|
|
|
|
|
|
|
Dbprintf("Starting clone. [Bank: %d]", selected);
|
|
|
|
// need this delay to prevent catching some weird data
|
|
|
|
SpinDelay(500);
|
|
|
|
// Begin clone function here:
|
|
|
|
/* Example from client/mifarehost.c for commanding a block write for "magic Chinese" cards:
|
2020-05-27 23:58:34 +08:00
|
|
|
SendCommandMIX(CMD_HF_MIFARE_CSETBL, params & (0xFE | (uid == NULL ? 0:1)), blockNo, 0, data, 16);
|
2019-03-09 18:19:45 +08:00
|
|
|
|
|
|
|
Block read is similar:
|
2020-05-27 23:58:34 +08:00
|
|
|
SendCommandMIX(CMD_HF_MIFARE_CGETBL, params, blockNo, 0,...};
|
2019-03-09 18:19:45 +08:00
|
|
|
We need to imitate that call with blockNo 0 to set a uid.
|
|
|
|
|
|
|
|
The get and set commands are handled in this file:
|
|
|
|
// Work with "magic Chinese" card
|
2019-08-04 01:17:00 +08:00
|
|
|
case CMD_HF_MIFARE_CSETBL:
|
2019-03-09 18:19:45 +08:00
|
|
|
MifareCSetBlock(c->arg[0], c->arg[1], c->d.asBytes);
|
|
|
|
break;
|
2019-08-04 01:17:00 +08:00
|
|
|
case CMD_HF_MIFARE_CGETBL:
|
2019-03-09 18:19:45 +08:00
|
|
|
MifareCGetBlock(c->arg[0], c->arg[1], c->d.asBytes);
|
|
|
|
break;
|
|
|
|
|
|
|
|
mfCSetUID provides example logic for UID set workflow:
|
|
|
|
-Read block0 from card in field with MifareCGetBlock()
|
|
|
|
-Configure new values without replacing reserved bytes
|
|
|
|
memcpy(block0, uid, 4); // Copy UID bytes from byte array
|
|
|
|
// Mifare UID BCC
|
|
|
|
block0[4] = block0[0]^block0[1]^block0[2]^block0[3]; // BCC on byte 5
|
|
|
|
Bytes 5-7 are reserved SAK and ATQA for mifare classic
|
|
|
|
-Use mfCSetBlock(0, block0, oldUID, wantWipe, MAGIC_SINGLE) to write it
|
|
|
|
*/
|
2019-06-08 00:41:39 +08:00
|
|
|
uint8_t oldBlock0[16] = {0}, newBlock0[16] = {0};
|
2019-03-09 18:19:45 +08:00
|
|
|
// arg0 = Flags, arg1=blockNo
|
|
|
|
MifareCGetBlock(params, 0, oldBlock0);
|
|
|
|
if (oldBlock0[0] == 0 && oldBlock0[0] == oldBlock0[1] && oldBlock0[1] == oldBlock0[2] && oldBlock0[2] == oldBlock0[3]) {
|
|
|
|
Dbprintf("No changeable tag detected. Returning to replay mode for bank[%d]", selected);
|
|
|
|
playing = 1;
|
|
|
|
} else {
|
2019-06-08 00:41:39 +08:00
|
|
|
uint8_t testBlock0[16] = {0};
|
2019-03-09 18:19:45 +08:00
|
|
|
Dbprintf("UID from target tag: %02X%02X%02X%02X", oldBlock0[0], oldBlock0[1], oldBlock0[2], oldBlock0[3]);
|
2019-04-07 18:06:48 +08:00
|
|
|
memcpy(newBlock0 + 5, oldBlock0 + 5, 11);
|
2019-03-09 18:19:45 +08:00
|
|
|
|
|
|
|
// Copy uid for bank (2nd is for longer UIDs not supported if classic)
|
|
|
|
memcpy(newBlock0, uids[selected].uid, 4);
|
|
|
|
newBlock0[4] = newBlock0[0] ^ newBlock0[1] ^ newBlock0[2] ^ newBlock0[3];
|
|
|
|
|
|
|
|
// arg0 = workFlags, arg1 = blockNo, datain
|
|
|
|
MifareCSetBlock(params, 0, newBlock0);
|
|
|
|
MifareCGetBlock(params, 0, testBlock0);
|
|
|
|
|
2019-03-10 07:00:59 +08:00
|
|
|
if (memcmp(testBlock0, newBlock0, 16) == 0) {
|
2019-09-14 23:44:58 +08:00
|
|
|
DbpString("Cloned successful!");
|
2019-03-09 18:19:45 +08:00
|
|
|
cardRead[selected] = 0; // Only if the card was cloned successfully should we clear it
|
|
|
|
playing = 0;
|
|
|
|
iGotoRecord = 1;
|
|
|
|
selected = (selected + 1) % OPTS;
|
|
|
|
} else {
|
|
|
|
Dbprintf("Clone failed. Back to replay mode on bank[%d]", selected);
|
|
|
|
playing = 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
LEDsoff();
|
|
|
|
LED(selected + 1, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Change where to record (or begin playing)
|
|
|
|
// button_pressed == BUTTON_SINGLE_CLICK && cardRead[selected])
|
2019-03-10 07:00:59 +08:00
|
|
|
else if (playing == 1) {
|
2019-03-09 18:19:45 +08:00
|
|
|
LEDsoff();
|
|
|
|
LED(selected + 1, 0);
|
|
|
|
|
|
|
|
// Begin transmitting
|
2019-06-02 17:47:10 +08:00
|
|
|
LED(LED_B, 0);
|
2019-03-09 18:19:45 +08:00
|
|
|
DbpString("Playing");
|
2019-03-10 07:00:59 +08:00
|
|
|
for (; ;) {
|
2019-03-09 18:19:45 +08:00
|
|
|
// exit from Standalone Mode, send a usbcommand.
|
2019-06-03 06:01:08 +08:00
|
|
|
if (data_available()) return;
|
2019-03-09 18:19:45 +08:00
|
|
|
|
2020-05-15 06:00:42 +08:00
|
|
|
int button_pressed = BUTTON_HELD(1000);
|
|
|
|
if (button_pressed == BUTTON_NO_CLICK) { // No button action, proceed with sim
|
2019-03-09 18:19:45 +08:00
|
|
|
|
|
|
|
uint8_t flags = FLAG_4B_UID_IN_DATA;
|
2019-05-03 22:57:56 +08:00
|
|
|
uint8_t data[PM3_CMD_DATA_SIZE] = {0}; // in case there is a read command received we shouldn't break
|
2019-03-09 18:19:45 +08:00
|
|
|
|
|
|
|
memcpy(data, uids[selected].uid, uids[selected].uidlen);
|
|
|
|
|
|
|
|
uint64_t tmpuid = bytes_to_num(uids[selected].uid, uids[selected].uidlen);
|
|
|
|
|
2019-03-10 07:00:59 +08:00
|
|
|
if (uids[selected].uidlen == 7) {
|
2019-03-09 18:19:45 +08:00
|
|
|
flags = FLAG_7B_UID_IN_DATA;
|
|
|
|
Dbprintf("Simulating ISO14443a tag with uid: %014" PRIx64 " [Bank: %d]", tmpuid, selected);
|
|
|
|
} else {
|
|
|
|
Dbprintf("Simulating ISO14443a tag with uid: %08" PRIx64 " [Bank: %d]", tmpuid, selected);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (uids[selected].sak == 0x08 && uids[selected].atqa[0] == 0x04 && uids[selected].atqa[1] == 0) {
|
|
|
|
DbpString("Mifare Classic 1k");
|
|
|
|
SimulateIso14443aTag(1, flags, data);
|
|
|
|
} else if (uids[selected].sak == 0x18 && uids[selected].atqa[0] == 0x02 && uids[selected].atqa[1] == 0) {
|
|
|
|
DbpString("Mifare Classic 4k (4b uid)");
|
|
|
|
SimulateIso14443aTag(8, flags, data);
|
|
|
|
} else if (uids[selected].sak == 0x08 && uids[selected].atqa[0] == 0x44 && uids[selected].atqa[1] == 0) {
|
|
|
|
DbpString("Mifare Classic 4k (7b uid)");
|
|
|
|
SimulateIso14443aTag(8, flags, data);
|
|
|
|
} else if (uids[selected].sak == 0x00 && uids[selected].atqa[0] == 0x44 && uids[selected].atqa[1] == 0) {
|
|
|
|
DbpString("Mifare Ultralight");
|
|
|
|
SimulateIso14443aTag(2, flags, data);
|
|
|
|
} else if (uids[selected].sak == 0x20 && uids[selected].atqa[0] == 0x04 && uids[selected].atqa[1] == 0x03) {
|
|
|
|
DbpString("Mifare DESFire");
|
|
|
|
SimulateIso14443aTag(3, flags, data);
|
|
|
|
} else {
|
|
|
|
Dbprintf("Unrecognized tag type -- defaulting to Mifare Classic emulation");
|
|
|
|
SimulateIso14443aTag(1, flags, data);
|
|
|
|
}
|
|
|
|
|
2020-05-15 06:00:42 +08:00
|
|
|
} else if (button_pressed == BUTTON_SINGLE_CLICK) {
|
2019-03-09 18:19:45 +08:00
|
|
|
selected = (selected + 1) % OPTS;
|
|
|
|
Dbprintf("Done playing. Switching to record mode on bank %d", selected);
|
|
|
|
iGotoRecord = 1;
|
|
|
|
break;
|
2020-05-15 06:00:42 +08:00
|
|
|
} else if (button_pressed == BUTTON_HOLD) {
|
2019-03-09 18:19:45 +08:00
|
|
|
Dbprintf("Playtime over. Begin cloning...");
|
|
|
|
iGotoClone = 1;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* We pressed a button so ignore it here with a delay */
|
|
|
|
SpinDelay(300);
|
|
|
|
LEDsoff();
|
|
|
|
LED(selected + 1, 0);
|
|
|
|
}
|
|
|
|
}
|
2019-03-12 07:12:26 +08:00
|
|
|
}
|