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

@ -3,6 +3,7 @@ All notable changes to this project will be documented in this file.
This project uses the changelog in accordance with [keepchangelog](http://keepachangelog.com/). Please use this to write notable changes, which is not the same as git commit log... This project uses the changelog in accordance with [keepchangelog](http://keepachangelog.com/). Please use this to write notable changes, which is not the same as git commit log...
## [unreleased][unreleased] ## [unreleased][unreleased]
- Changed `hf_cardhopper` - Allow button presses to break, handle non-zero CID from reader by relaying RATS and improving PPS and WTX handling, more reliably cook ATS, ignore client packets on serial line (@nvx)
- Fixed `data diff` - client no longer crashes when using short widths on long filenames (@iceman1001) - Fixed `data diff` - client no longer crashes when using short widths on long filenames (@iceman1001)
- Added `hf 15 wipe` - fills card memory with zeros (@iceman1001) - Added `hf 15 wipe` - fills card memory with zeros (@iceman1001)
- Changed `hf xerox info` - now prints some part info (@iceman1001) - Changed `hf xerox info` - now prints some part info (@iceman1001)

View file

@ -78,7 +78,9 @@ void RunMod(void) {
// Ensure debug logs don't polute stream // Ensure debug logs don't polute stream
#ifdef CARDHOPPER_USB #ifdef CARDHOPPER_USB
g_reply_via_usb = false; g_reply_via_usb = false;
g_reply_via_fpc = true;
#else #else
g_reply_via_usb = true;
g_reply_via_fpc = false; g_reply_via_fpc = false;
#endif #endif
@ -100,6 +102,11 @@ void RunMod(void) {
packet_t modeRx = { 0 }; packet_t modeRx = { 0 };
read_packet(&modeRx); read_packet(&modeRx);
if (BUTTON_PRESS()) {
DbpString(_CYAN_("[@]") " Button pressed - Breaking from mode loop");
break;
}
if (memcmp(magicREAD, modeRx.dat, sizeof(magicREAD)) == 0) { if (memcmp(magicREAD, modeRx.dat, sizeof(magicREAD)) == 0) {
DbpString(_CYAN_("[@]") " I am a READER. I talk to a CARD."); DbpString(_CYAN_("[@]") " I am a READER. I talk to a CARD.");
become_reader(); become_reader();
@ -137,6 +144,20 @@ static void become_reader(void) {
read_packet(rx); read_packet(rx);
if (memcmp(magicRSRT, rx->dat, sizeof(magicRSRT)) == 0) break; 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); memcpy(toCard, rx->dat, rx->len);
AddCrc14A(toCard, rx->len); AddCrc14A(toCard, rx->len);
ReaderTransmit(toCard, rx->len + 2, NULL); ReaderTransmit(toCard, rx->len + 2, NULL);
@ -212,9 +233,12 @@ static void become_card(void) {
if (cardhopper_data_available()) { if (cardhopper_data_available()) {
read_packet(rx); read_packet(rx);
if (memcmp(magicRSRT, rx->dat, sizeof(magicRSRT)) == 0) { if (memcmp(magicRSRT, rx->dat, sizeof(magicRSRT)) == 0) {
DbpString(_CYAN_("[@]") " Breaking from reader loop"); DbpString(_CYAN_("[@]") " Breaking from emulation loop");
break; break;
} }
} else if (BUTTON_PRESS()) {
DbpString(_CYAN_("[@]") " Button pressed - Breaking from emulation loop");
break;
} }
continue; continue;
} }
@ -223,9 +247,13 @@ static void become_card(void) {
if (try_use_canned_response(fromReaderDat, fromReaderLen, canned)) continue; if (try_use_canned_response(fromReaderDat, fromReaderLen, canned)) continue;
// Option 2: Reply with our cooked ATS // Option 2: Reply with our cooked ATS
bool no_reply = false;
if (fromReaderDat[0] == ISO14443A_CMD_RATS && fromReaderLen == 4) { if (fromReaderDat[0] == ISO14443A_CMD_RATS && fromReaderLen == 4) {
reply_with_packet(&ats); 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 // Option 3: Relay the message
@ -234,9 +262,11 @@ static void become_card(void) {
write_packet(tx); write_packet(tx);
read_packet(rx); read_packet(rx);
if (!no_reply && rx->len > 0) {
reply_with_packet(rx); reply_with_packet(rx);
} }
} }
}
static void prepare_emulation(uint8_t *tagType, uint16_t *flags, uint8_t *data, packet_t *ats) { static void prepare_emulation(uint8_t *tagType, uint16_t *flags, uint8_t *data, packet_t *ats) {
@ -286,28 +316,71 @@ static void cook_ats(packet_t *ats, uint8_t fwi, uint8_t sfgi) {
return; return;
} }
// If the ATS is too short (unusual), pad it to length with hopefully-sensible data uint8_t t0 = 0x70; // TA, TB, and TC transmitted, FSCI nibble set later
// Might be better for the phone side to do this tbh uint8_t ta = 0x80; // only 106kbps rate supported, and must be same in both directions - PM3 doesn't support any other rates
if (ats->len == 1) { uint8_t tb = (fwi << 4) | sfgi; // cooked value
ats->len = 4; uint8_t tc = 0;
ats->dat[0] = 0x04;
ats->dat[1] = 0x78; uint8_t historical_len = 0;
ats->dat[2] = 0x77; uint8_t *historical_bytes;
// ats->dat[3] = 0x80; if (ats->len > 1) {
} else if (ats->len == 2) { // T0 byte exists when ats length > 1
ats->len = 4;
ats->dat[0] = 0x04; uint8_t orig_t0 = ats->dat[1];
ats->dat[2] = 0x77; // Update FSCI in T0 from the received ATS
// ats->dat[3] = 0x80; t0 |= orig_t0 & 0x0F;
} else if (ats->len == 3) {
ats->len = 4; uint8_t len = ats->len - 2;
ats->dat[0] = 0x04; uint8_t *orig_ats_ptr = &ats->dat[2];
// ats->dat[3] = 0x80; 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--;
} }
// Set the SFGI as well as the FWI - needed for some older readers (firmware revs?) historical_bytes = orig_ats_ptr;
uint8_t cookedTB0 = (fwi << 4) | sfgi; historical_len = len;
ats->dat[3] = cookedTB0; } else {
// T0 byte missing, update FSCI in T0 to the default value of 2
t0 |= 0x02;
}
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); EmSendPrecompiledCmd(canned + RESP_INDEX_PPS);
return true; return true;
} }
// No response is expected to these 14a commands // 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; if (dat[0] == ISO14443A_CMD_HALT && len == 4) return true;
// Ignore Apple ECP2 polling // Ignore Apple ECP2 polling
if (dat[0] == 0x6a) return true; if (dat[0] == ECP_HEADER) return true;
return false; return false;
} }
@ -380,15 +454,38 @@ static void read_packet(packet_t *packet) {
while (!cardhopper_data_available()) { while (!cardhopper_data_available()) {
WDT_HIT(); WDT_HIT();
SpinDelayUs(100); SpinDelayUs(100);
if (BUTTON_PRESS()) {
DbpString(_CYAN_("[@]") " Button pressed while waiting for packet - aborting");
return;
}
} }
cardhopper_read((uint8_t *) &packet->len, 1); cardhopper_read((uint8_t *) &packet->len, 1);
uint32_t dataReceived = 0; uint32_t dataReceived = 0;
while (dataReceived != packet->len) { 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); 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)); cardhopper_write(magicACK, sizeof(magicACK));
} }
@ -414,7 +511,7 @@ static bool GetIso14443aCommandFromReaderInterruptible(uint8_t *received, uint8_
WDT_HIT(); WDT_HIT();
if (flip == 3) { if (flip == 3) {
if (cardhopper_data_available()) if (cardhopper_data_available() || BUTTON_PRESS())
return false; return false;
flip = 0; flip = 0;