Multiple hf_cardhopper improvements

Allow button presses to break
Handle non-zero CID from reader by relaying RATS to the card and improving PPS and WTX handling
More reliably cook ATS values
Ignore packets that look like they're coming from the client (NG packets)
This commit is contained in:
nvx 2024-01-15 22:05:30 +10:00
commit 1828358ab0
2 changed files with 126 additions and 28 deletions

View file

@ -78,7 +78,9 @@ void RunMod(void) {
// Ensure debug logs don't polute stream
#ifdef CARDHOPPER_USB
g_reply_via_usb = false;
g_reply_via_fpc = true;
#else
g_reply_via_usb = true;
g_reply_via_fpc = false;
#endif
@ -100,6 +102,11 @@ void RunMod(void) {
packet_t modeRx = { 0 };
read_packet(&modeRx);
if (BUTTON_PRESS()) {
DbpString(_CYAN_("[@]") " Button pressed - Breaking from mode loop");
break;
}
if (memcmp(magicREAD, modeRx.dat, sizeof(magicREAD)) == 0) {
DbpString(_CYAN_("[@]") " I am a READER. I talk to a CARD.");
become_reader();
@ -137,6 +144,20 @@ static void become_reader(void) {
read_packet(rx);
if (memcmp(magicRSRT, rx->dat, sizeof(magicRSRT)) == 0) break;
if (rx->dat[0] == ISO14443A_CMD_RATS && rx->len == 4) {
// got RATS from reader, reset the card
FpgaWriteConfWord(FPGA_MAJOR_MODE_OFF);
SpinDelay(40);
iso14443a_setup(FPGA_HF_ISO14443A_READER_MOD);
// re-select the card without RATS to allow replaying the real RATS
int ret = iso14443a_select_card(NULL, NULL, NULL, true, 0, true);
if (ret && ret != 1) {
Dbprintf(_RED_("[!]") " Error selecting card: %d", ret);
continue;
}
}
memcpy(toCard, rx->dat, rx->len);
AddCrc14A(toCard, rx->len);
ReaderTransmit(toCard, rx->len + 2, NULL);
@ -212,9 +233,12 @@ static void become_card(void) {
if (cardhopper_data_available()) {
read_packet(rx);
if (memcmp(magicRSRT, rx->dat, sizeof(magicRSRT)) == 0) {
DbpString(_CYAN_("[@]") " Breaking from reader loop");
DbpString(_CYAN_("[@]") " Breaking from emulation loop");
break;
}
} else if (BUTTON_PRESS()) {
DbpString(_CYAN_("[@]") " Button pressed - Breaking from emulation loop");
break;
}
continue;
}
@ -223,9 +247,13 @@ static void become_card(void) {
if (try_use_canned_response(fromReaderDat, fromReaderLen, canned)) continue;
// Option 2: Reply with our cooked ATS
bool no_reply = false;
if (fromReaderDat[0] == ISO14443A_CMD_RATS && fromReaderLen == 4) {
reply_with_packet(&ats);
continue;
// fallthrough to still send the RATS to the card so it can learn the CID
// but don't send the reply since we've already sent our cooked ATS
no_reply = true;
}
// Option 3: Relay the message
@ -234,7 +262,9 @@ static void become_card(void) {
write_packet(tx);
read_packet(rx);
reply_with_packet(rx);
if (!no_reply && rx->len > 0) {
reply_with_packet(rx);
}
}
}
@ -286,28 +316,71 @@ static void cook_ats(packet_t *ats, uint8_t fwi, uint8_t sfgi) {
return;
}
// If the ATS is too short (unusual), pad it to length with hopefully-sensible data
// Might be better for the phone side to do this tbh
if (ats->len == 1) {
ats->len = 4;
ats->dat[0] = 0x04;
ats->dat[1] = 0x78;
ats->dat[2] = 0x77;
// ats->dat[3] = 0x80;
} else if (ats->len == 2) {
ats->len = 4;
ats->dat[0] = 0x04;
ats->dat[2] = 0x77;
// ats->dat[3] = 0x80;
} else if (ats->len == 3) {
ats->len = 4;
ats->dat[0] = 0x04;
// ats->dat[3] = 0x80;
uint8_t t0 = 0x70; // TA, TB, and TC transmitted, FSCI nibble set later
uint8_t ta = 0x80; // only 106kbps rate supported, and must be same in both directions - PM3 doesn't support any other rates
uint8_t tb = (fwi << 4) | sfgi; // cooked value
uint8_t tc = 0;
uint8_t historical_len = 0;
uint8_t *historical_bytes;
if (ats->len > 1) {
// T0 byte exists when ats length > 1
uint8_t orig_t0 = ats->dat[1];
// Update FSCI in T0 from the received ATS
t0 |= orig_t0 & 0x0F;
uint8_t len = ats->len - 2;
uint8_t *orig_ats_ptr = &ats->dat[2];
if (orig_t0 & 0x10) {
// TA present
if (len < 1) {
DbpString(_RED_("[!]") " Malformed ATS - unable to cook; things may go wrong!");
return;
}
orig_ats_ptr++;
len--;
}
if (orig_t0 & 0x20) {
// TB present
if (len < 1) {
DbpString(_RED_("[!]") " Malformed ATS - unable to cook; things may go wrong!");
return;
}
orig_ats_ptr++;
len--;
}
if (orig_t0 & 0x40) {
// TC present, extract protocol parameters
if (len < 1) {
DbpString(_RED_("[!]") " Malformed ATS - unable to cook; things may go wrong!");
return;
}
tc = *orig_ats_ptr;
orig_ats_ptr++;
len--;
}
historical_bytes = orig_ats_ptr;
historical_len = len;
} else {
// T0 byte missing, update FSCI in T0 to the default value of 2
t0 |= 0x02;
}
// Set the SFGI as well as the FWI - needed for some older readers (firmware revs?)
uint8_t cookedTB0 = (fwi << 4) | sfgi;
ats->dat[3] = cookedTB0;
packet_t cooked_ats = { 0 };
cooked_ats.len = 5+historical_len;
cooked_ats.dat[0] = cooked_ats.len;
cooked_ats.dat[1] = t0;
cooked_ats.dat[2] = ta;
cooked_ats.dat[3] = tb;
cooked_ats.dat[4] = tc;
if (historical_len > 0) {
memcpy(cooked_ats.dat+5, historical_bytes, historical_len);
}
memcpy(ats, &cooked_ats, sizeof(packet_t));
}
@ -343,17 +416,18 @@ static bool try_use_canned_response(const uint8_t *dat, int len, tag_response_in
}
}
if (dat[0] == ISO14443A_CMD_PPS) {
// high nibble of PPS is PPS CMD, low nibble of PPS is CID
if ((dat[0] & 0xF0) == ISO14443A_CMD_PPS) {
EmSendPrecompiledCmd(canned + RESP_INDEX_PPS);
return true;
}
// No response is expected to these 14a commands
if ((dat[0] == 0xf2 && len == 4) || dat[0] == 0xfa) return true;
if ((dat[0] & 0xF7) == ISO14443A_CMD_WTX) return true; // bit 0x08 indicates CID following
if (dat[0] == ISO14443A_CMD_HALT && len == 4) return true;
// Ignore Apple ECP2 polling
if (dat[0] == 0x6a) return true;
if (dat[0] == ECP_HEADER) return true;
return false;
}
@ -380,15 +454,38 @@ static void read_packet(packet_t *packet) {
while (!cardhopper_data_available()) {
WDT_HIT();
SpinDelayUs(100);
if (BUTTON_PRESS()) {
DbpString(_CYAN_("[@]") " Button pressed while waiting for packet - aborting");
return;
}
}
cardhopper_read((uint8_t *) &packet->len, 1);
uint32_t dataReceived = 0;
while (dataReceived != packet->len) {
while (!cardhopper_data_available()) WDT_HIT();
while (!cardhopper_data_available()) {
WDT_HIT();
if (BUTTON_PRESS()) {
DbpString(_CYAN_("[@]") " Button pressed while reading packet - aborting");
return;
}
}
dataReceived += cardhopper_read(packet->dat + dataReceived, packet->len - dataReceived);
if (packet->len == 0x50 && dataReceived >= sizeof(PacketResponseNGPreamble) && packet->dat[0] == 0x4D && packet->dat[1] == 0x33 && packet->dat[2] == 0x61) {
// PM3 NG packet magic
DbpString(_CYAN_("[@]") " PM3 NG packet recieved - ignoring");
// clear any remaining buffered data
while(cardhopper_data_available()) {
cardhopper_read(packet->dat, 255);
}
packet->len = 0;
return;
}
}
cardhopper_write(magicACK, sizeof(magicACK));
}
@ -414,7 +511,7 @@ static bool GetIso14443aCommandFromReaderInterruptible(uint8_t *received, uint8_
WDT_HIT();
if (flip == 3) {
if (cardhopper_data_available())
if (cardhopper_data_available() || BUTTON_PRESS())
return false;
flip = 0;