hitag2: fix simulation against genuine readers, add restore, fix info

Simulation now completes the full exchange with a genuine Paxton reader in
password mode, and crypto mode read/write passes Proxmark-to-Proxmark.

Firmware:
- SOF was one bit period short. The lead-in that compensated for the lost
  head half bit was removed and nothing replaced it, so readers rejected
  every answer with a second START_AUTH. Default is now 6.
- The edge-detect threshold was latched before being measured, so the value
  chosen depended on whether the Proxmark was in a field when sim started.
  It is now measured on field entry and re-armed when the reader leaves.
- The percentile walk latched on run-scoped variables, so one attempt made
  outside a field poisoned every later one.
- Field loss was detected from TIMESTAMP, which is free-running MCU time and
  never stalls. Detect it from receive silence instead.
- Frames of a length the protocol does not have no longer reach the state
  machine; our own modulation tail was resetting the session and breaking
  every write.
- A dropped edge merges two or three reader bit periods into one gap. Those
  bits were discarded; they are now recovered by decomposition, which is what
  made crypto mode work (AUTH decode 15% -> 100%).
- Threshold selection is limited to 20 and 32 and settles in under 25 ms.

Client:
- lf hitag info printed a hardcoded 0x06 and reported 'Password mode' for
  every tag. It now reads page 3, takes -k (4 bytes password, 6 bytes
  crypto), and says so when the config cannot be read.
- lf hitag restore: writes a dump back in dependency order - user pages,
  then key material, then config last - validates the config byte, and
  prints the credential the tag will require afterwards.
- lf hitag crack2 now reports why it failed instead of a bare 'fail'.
- trace list: bit count moved to its own column, relative mode shows a
  Frame Delay Time row rather than renaming Start/End, --frame and -r
  rejected together.
This commit is contained in:
iceman1001
2026-09-04 13:20:29 +02:00
parent d85550c6de
commit ace5d63ff9
27 changed files with 4309 additions and 723 deletions
+1
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...
## [unreleased][unreleased]
- Changed `lf hitag` - refactoring hitag2 operations and now they work much better (@iceman1001)
- Fixed `lf hitag list` - now prints in same style like the rest (@iceman1001)
- Changed pathing - client now supports `~/` tilde sign in paths (@iceman1001)
- Added `hf_legic_rdv4` - Standalone mode for managing 7 persistent legic dumps (@digitalhoax)
+23 -7
View File
@@ -1606,13 +1606,30 @@ static void PacketReceived(PacketCommandNG *packet) {
#ifdef WITH_HITAG
case CMD_LF_HITAG_SNIFF: { // Eavesdrop Hitag tag, args = type
SniffHitag2(true);
// threshold comes from `lf hitag sniff -t`, 0 = the slope default
uint8_t sniff_thr = (packet->length >= 1) ? packet->data.asBytes[0] : 0;
SniffHitag2(true, sniff_thr);
//hitag_sniff();
reply_ng(CMD_LF_HITAG_SNIFF, PM3_SUCCESS, NULL, 0);
break;
}
case CMD_LF_HITAG_SIMULATE: { // Simulate Hitag tag, args = memory content
SimulateHitag2(true);
case CMD_LF_HITAG_SIMULATE: {
// The tag content comes from emulator memory, so the payload only
// carries the edge detect threshold. Older clients send nothing.
uint8_t threshold = 127;
uint16_t twait = 0;
uint8_t flags = 0;
uint8_t sof = 0;
uint8_t duty = 0;
if (packet->length >= sizeof(hitag_sim_t)) {
hitag_sim_t *payload = (hitag_sim_t *)packet->data.asBytes;
threshold = (uint8_t)payload->threshold;
twait = payload->twait;
flags = payload->flags;
sof = payload->sof;
duty = payload->duty;
}
SimulateHitag2(threshold, twait, flags, sof, duty, true);
break;
}
case CMD_LF_HITAG2_CRACK: {
@@ -1646,7 +1663,7 @@ static void PacketReceived(PacketCommandNG *packet) {
break;
}
hitag_sim_t *payload = (hitag_sim_t *)packet->data.asBytes;
hts_simulate((bool)payload->tag_mem_supplied, payload->threshold, payload->data, true);
hts_simulate((int8_t)payload->threshold, true);
break;
}
case CMD_LF_HITAGS_TEST_TRACES: { // Tests every challenge within the given file
@@ -1695,7 +1712,7 @@ static void PacketReceived(PacketCommandNG *packet) {
break;
}
hitag_sim_t *payload = (hitag_sim_t *)packet->data.asBytes;
htu_simulate((bool)payload->tag_mem_supplied, payload->threshold, payload->data, true);
htu_simulate((int8_t)payload->threshold, true);
break;
}
case CMD_LF_HITAGU_UID: {
@@ -2176,8 +2193,7 @@ static void PacketReceived(PacketCommandNG *packet) {
break;
}
case CMD_HF_ISO14443A_SNIFF: {
SniffIso14443a(packet->data.asBytes[0]);
reply_ng(CMD_HF_ISO14443A_SNIFF, PM3_SUCCESS, NULL, 0);
reply_ng(CMD_HF_ISO14443A_SNIFF, SniffIso14443a(packet->data.asBytes[0]), NULL, 0);
break;
}
case CMD_HF_HIDCONFIG_SNIFF: {
+2570 -567
View File
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -22,9 +22,9 @@
#include "common.h"
#include "hitag.h"
void SniffHitag2(bool ledcontrol);
void SniffHitag2(bool ledcontrol, uint8_t threshold);
void hitag_sniff(void);
void SimulateHitag2(bool ledcontrol);
void SimulateHitag2(uint8_t threshold, uint16_t twait, uint8_t flags, uint8_t sof, uint8_t duty, bool ledcontrol);
void ReaderHitag(const lf_hitag_data_t *payload, bool ledcontrol);
void WriterHitag(const lf_hitag_data_t *payload, bool ledcontrol);
+19
View File
@@ -19,6 +19,9 @@
#include "hitag2_crack.h"
// How many times the crypto probe retries before deciding the tag is refusing.
#define HT2_CRACK_PROBE_TRIES 8
#include "hitag2/hitag2_crypto.h"
#include "hitag2.h"
#include "proxmark3_arm.h"
@@ -365,6 +368,22 @@ void ht2_crack2(uint8_t *nrar_hex) {
// find a valid encrypted command
uint8_t e_firstcmd[10] = {0};
if (hitag2crack_find_valid_e_cmd(e_firstcmd, c2->nrar) == false) {
// Say why, because the two causes need different fixes.
//
// Every guess replays the nR aR and waits for the tag's encrypted page 3.
// A password mode tag never answers a 64 bit authentication at all - that
// is the datasheet behaviour, selected by bit 3 of the configuration byte
// - and neither does a crypto tag given a pair captured from some other
// card or key. Without this the attack just reported "fail".
//
// Probing once up front was tried instead and removed: the exchange misses
// often enough that a short probe rejected a card this attack then cracked
// successfully. The full sweep is the reliable test, so report after it.
// Reading the configuration byte would be better still, but that needs the
// authentication we are trying to establish.
DbpString("no encrypted command was answered");
DbpString(" tag is not in crypto mode, or this nR aR is not valid for it");
BigBuf_free();
reply_ng(CMD_LF_HITAG2_CRACK_2, PM3_EFAILED, NULL, 0);
return;
+22 -11
View File
@@ -371,28 +371,39 @@ static void hts_handle_reader_command(uint8_t *rx, const size_t rxlen,
/*
* Emulates a Hitag S Tag with the given data from the .hts file
*/
void hts_simulate(bool tag_mem_supplied, int8_t threshold, const uint8_t *data, bool ledcontrol) {
void hts_simulate(int8_t threshold, bool ledcontrol) {
int overflow = 0;
uint8_t rx[HITAG_FRAME_LEN] = {0};
size_t rxlen = 0;
uint8_t tx[HITAG_FRAME_LEN];
size_t txlen = 0;
// free eventually allocated BigBuf memory
BigBuf_free();
BigBuf_Clear_ext(false);
// keep emulator memory, that is where eload put the tag content
BigBuf_free_keep_EM();
BigBuf_Clear_keep_EM();
DbpString("Starting Hitag S simulation");
tag.pstate = HT_READY;
tag.tstate = HT_NO_OP;
// read tag data into memory
if (tag_mem_supplied) {
DbpString("Loading hitag S memory...");
memcpy(tag.data.pages, data, HITAGS_MAX_BYTE_SIZE);
// Take the tag content from emulator memory, where `lf hitag eload -s` put
// it. An all-zero emulator memory means nothing was loaded, so keep whatever
// the last read left behind rather than simulating a tag of all zeroes.
uint8_t *em = BigBuf_get_EM_addr();
bool em_empty = true;
for (size_t i = 0; i < HITAGS_MAX_BYTE_SIZE; i++) {
if (em[i] != 0) {
em_empty = false;
break;
}
}
if (em_empty) {
DbpString("Emulator memory is empty, simulating the last read tag");
} else {
// use the last read tag
DbpString("Loading Hitag S memory from emulator memory...");
memcpy(tag.data.pages, em, HITAGS_MAX_BYTE_SIZE);
}
// max_page
@@ -462,8 +473,8 @@ void hts_simulate(bool tag_mem_supplied, int8_t threshold, const uint8_t *data,
}
hitag_cleanup(ledcontrol);
// release allocated memory from BigBuff.
BigBuf_free();
// release BigBuf, but keep emulator memory for eview / esave
BigBuf_free_keep_EM();
DbpString("Sim stopped");
}
+1 -1
View File
@@ -24,7 +24,7 @@
#include "common.h"
#include "hitag.h"
void hts_simulate(bool tag_mem_supplied, int8_t threshold, const uint8_t *data, bool ledcontrol);
void hts_simulate(int8_t threshold, bool ledcontrol);
void hts_read(const lf_hitag_data_t *payload, bool ledcontrol);
void hts_write_page(const lf_hitag_data_t *payload, bool ledcontrol);
void hts_check_challenges(const uint8_t *data, uint32_t datalen, bool ledcontrol);
+655 -38
View File
@@ -30,15 +30,86 @@
#include "commonutil.h"
#include "hitag2/hitag2_crypto.h"
#include "lfadc.h"
#include "lfsampling.h"
#include "crc.h"
#include "protocols.h"
#include "appmain.h" // tearoff_hook()
// Initialize FPGA and timer for Hitag operations
// A short settle after an FPGA mode change. Deliberately a plain busy loop:
// hitag_setup_fpga() calls StopTicks() first, so WaitMS()/SpinDelay() have no
// timebase there and simply hang - that wedged the device hard enough to need a
// replug. Roughly a few milliseconds at 48 MHz, which is all the FPGA needs.
// HITAG_RX_IDLE_GUARD now lives in hitag_common.h - SniffHitag2() needs it too.
static void hitag_settle(void) {
for (volatile uint32_t i = 0; i < 200000; i++) {
if ((i & 0x3FF) == 0) {
WDT_HIT();
}
}
}
// Pick an edge detect threshold by measuring, rather than making the user guess.
//
// The usable window is narrow and reader dependent: against a Paxton reader
// 5..25 all worked, 30 was marginal and 40 received nothing, while the FPGA's
// own default of 127 received nothing at all. Asking the user to find that by
// hand is not reasonable, so sample the edge rate at a spread of candidates and
// keep the most noise-rejecting one that still sees a healthy share of them.
//
// Must run with the FPGA already in edge detect and the capture timer started.
uint8_t hitag_autotune_threshold(void) {
static const uint8_t cand[] = { 8, 12, 16, 20, 24, 28, 32 };
uint32_t seen[ARRAYLEN(cand)] = {0};
uint32_t best_seen = 0;
for (uint32_t i = 0; i < ARRAYLEN(cand); i++) {
FpgaSendCommand(FPGA_CMD_SET_EDGE_DETECT_THRESHOLD, cand[i]);
hitag_settle();
uint32_t before = g_hitag_edges;
// a few bounded receive passes is enough to gauge the edge rate
for (uint32_t k = 0; k < 3; k++) {
uint8_t rx[HITAG_FRAME_LEN] = {0};
size_t rxlen = 0;
uint32_t start_time = 0;
int overflow = 0;
hitag_tag_receive_frame_ex(rx, sizeof(rx), &rxlen, &start_time, false, &overflow, true);
ResetLoEdgeCapture();
}
seen[i] = g_hitag_edges - before;
if (seen[i] > best_seen) {
best_seen = seen[i];
}
}
// Nothing at all means no reader is present; leave a value that works once
// one shows up rather than picking the most sensitive setting and chattering.
if (best_seen == 0) {
return 20;
}
uint8_t chosen = cand[0];
for (uint32_t i = 0; i < ARRAYLEN(cand); i++) {
if (seen[i] >= (best_seen / 2)) {
chosen = cand[i];
}
}
return chosen;
}
void hitag_setup_fpga(uint16_t conf, uint8_t threshold, bool ledcontrol) {
StopTicks();
FpgaDownloadAndGo(FPGA_BITSTREAM_LF);
// the EM-preserving variant: the plain FpgaDownloadAndGo() wipes all of
// BigBuf, emulator memory included, whenever the LF bitstream is not
// already cached, which would discard what `lf hitag eload` just wrote
FpgaDownloadAndGo_keep_EM(FPGA_BITSTREAM_LF);
// Clean up trace and prepare it for storing frames
set_tracing(true);
@@ -46,17 +117,53 @@ void hitag_setup_fpga(uint16_t conf, uint8_t threshold, bool ledcontrol) {
if (ledcontrol) LED_D_ON();
// Bring the LF front end up the same way lf_init() and LFSetupFPGAForADC() do,
// in the same order. Two steps were missing here and both matter:
//
// - the divisor has to be sent BEFORE the conf word, not after. Setting the
// major mode last is what latches it, so programming the divisor afterwards
// left the sampling clock unconfigured.
// - FpgaSetupSsc() was never called at all, so the ADC sample stream the edge
// detector works from was never set up.
//
// The symptom was a detector free running at ~52 kHz - 633k edges in 12 s with
// nothing framed, and no value of the threshold making any measurable difference.
// Running any command that calls lf_init() (`lf read`, say) fixed it until the
// next boot, which is what made it look like the device needed reflashing.
// Bring the LF ADC front end up first, exactly as lf_init()/LFSetupFPGAForADC()
// do, before switching the FPGA into edge detect. Without this the peak
// detector is never initialised and the edge detector free runs; running any
// command that goes through lf_init() (`lf read`) fixed it until the next boot,
// which is what made it look like the device needed reflashing. reader_field
// is false here: this is a tag simulator, it must not energise the coil.
LFSetupFPGAForADC(LF_DIVISOR_125, false);
hitag_settle();
FpgaSendCommand(FPGA_CMD_SET_DIVISOR, LF_DIVISOR_125); //125kHz
FpgaWriteConfWord(FPGA_MAJOR_MODE_LF_EDGE_DETECT | conf);
if (threshold != 127) FpgaSendCommand(FPGA_CMD_SET_EDGE_DETECT_THRESHOLD, threshold);
// Connect the A/D to the peak-detected low-frequency path.
SetAdcMuxFor(ADC_MUXSEL_LOPKD);
// Set up the SSC so the ADC samples actually stream to the FPGA.
FpgaSetupSsc(FPGA_MAJOR_MODE_LF_READER);
// Give the front end a moment to settle before anything reads from it.
hitag_settle();
// Configure the timers via the HAL: a precision counter (T0 timing), an
// input capture (tag frame edges) and a timestamp counter (trace timing).
//
// The input capture goes LAST, after the FPGA is configured. StartLoEdgeCapture()
// claims SSC_FRAME for TC1 via PIO_BSR, and the FpgaWriteConfWord()/FpgaSendCommand()
// bit-bang that follows drives the same SSC lines, which undid that routing:
// the detector then free ran at ~52 kHz (633k edges in 12 s) with nothing framed,
// and the edge detect threshold had no measurable effect at any value.
// SniffHitag2(), which does frame reader traffic, has always ordered it this way.
StartPrecisionCounter();
StartLoEdgeCapture();
StartTimestamp();
// Set fpga in edge detect with/without reader field, we can modulate as reader/tag now
FpgaWriteConfWord(FPGA_MAJOR_MODE_LF_EDGE_DETECT | conf);
FpgaSendCommand(FPGA_CMD_SET_DIVISOR, LF_DIVISOR_125); //125kHz
if (threshold != 127) FpgaSendCommand(FPGA_CMD_SET_EDGE_DETECT_THRESHOLD, threshold);
SetAdcMuxFor(ADC_MUXSEL_LOPKD);
StartLoEdgeCapture();
// Configure output and enable pin that is connected to the FPGA (for modulating)
gpio_fpga_mod_only_setup();
@@ -76,8 +183,11 @@ void hitag_cleanup(bool ledcontrol) {
// Reader functions
static void hitag_reader_send_bit(int bit, bool ledcontrol) {
// Reset clock for the next bit
ResetPrecisionCounter();
// Time this bit from its own start. Taking a reference and measuring the
// difference leaves the free running counter alone, so nothing here depends
// on a reset having landed, and the shared reference that the receive path
// measures "time since the last edge" against is left where it was.
const uint16_t t_ref = GetPrecisionCounterRaw();
if (ledcontrol) LED_A_ON();
@@ -86,16 +196,16 @@ static void hitag_reader_send_bit(int bit, bool ledcontrol) {
Gpio_SSC_DOUT_High();
// Wait for 4-10 times the carrier period
while (GetPrecisionCounter() < T0 * HITAG_T_LOW) {};
while (GetPrecisionCounterDelta(t_ref) < T0 * HITAG_T_LOW) {};
Gpio_SSC_DOUT_Low();
if (bit == 0) {
// Zero bit: |_-|
while (GetPrecisionCounter() < T0 * HITAG_T_0) {};
while (GetPrecisionCounterDelta(t_ref) < T0 * HITAG_T_0) {};
} else {
// One bit: |_--|
while (GetPrecisionCounter() < T0 * HITAG_T_1) {};
while (GetPrecisionCounterDelta(t_ref) < T0 * HITAG_T_1) {};
}
if (ledcontrol) LED_A_OFF();
@@ -106,18 +216,18 @@ void hitag_reader_send_frame(const uint8_t *frame, size_t frame_len, bool ledcon
if (send_sof) {
hitag_reader_send_bit(0, ledcontrol);
// Reset clock for the code violation
ResetPrecisionCounter();
// Time the code violation from its own start, see hitag_reader_send_bit()
const uint16_t t_sof = GetPrecisionCounterRaw();
if (ledcontrol) LED_A_ON();
// SOF is HIGH for HITAG_T_LOW
Gpio_SSC_DOUT_High();
while (GetPrecisionCounter() < T0 * HITAG_T_LOW) {};
while (GetPrecisionCounterDelta(t_sof) < T0 * HITAG_T_LOW) {};
// Then LOW for HITAG_T_CODE_VIOLATION
Gpio_SSC_DOUT_Low();
while (GetPrecisionCounter() < T0 * HITAG_T_CODE_VIOLATION) {};
while (GetPrecisionCounterDelta(t_sof) < T0 * HITAG_T_CODE_VIOLATION) {};
if (ledcontrol) LED_A_OFF();
}
@@ -157,8 +267,25 @@ void hitag_reader_receive_frame(uint8_t *rx, size_t sizeofrx, size_t *rxlen, uin
// Skip SOF bits
bool sof_received = false;
// Receive tag frame, watch for at most T0*HITAG_T_PROG_MAX periods
// Receive tag frame, watch for at most T0*HITAG_T_PROG_MAX periods.
//
// Bounded, for the same reason the tag side receive is. The precision
// counter is reset on every edge below, so the T_PROG_MAX bound only expires
// while the line is quiet: when the edge detector chatters - which it does
// with no clean carrier to work with - edges keep arriving, the counter keeps
// being reset, and this loop never returns. Nothing in it polls the button
// or USB, so the Proxmark stops answering entirely and only a replug brings
// it back. Two devices were lost that way while measuring.
uint32_t guard = HITAG_RX_IDLE_GUARD;
while (GetPrecisionCounter() < (T0 * HITAG_T_PROG_MAX)) {
if (--guard == 0) {
break;
}
if ((guard & 0x3FF) == 0) {
WDT_HIT();
}
// Check if edge in tag modulation is detected
if (GetLoEdgeCaptureStatus() == next_edge) {
next_edge = next_edge == LO_EDGE_RISING ? LO_EDGE_FALLING : LO_EDGE_RISING;
@@ -172,7 +299,7 @@ void hitag_reader_receive_frame(uint8_t *rx, size_t sizeofrx, size_t *rxlen, uin
uint32_t rb = GetLoEdgeCaptureFalling() / T0;
// For debug, save the edges for decoding manual
if (rb_i < sizeof(edges)) {
if (rb_i < ARRAYLEN(edges)) {
edges[rb_i++] = rb;
}
@@ -373,6 +500,319 @@ int hitag_reader_transfer(const uint8_t *tx, size_t txlen, uint8_t *rx, size_t s
}
// Tag functions - depends on modulation type
// Wait n carrier periods by counting SSC_CLK edges. The FPGA drives ssp_clk from
// cross_lo, the carrier zero crossing, so this tracks the reader's clock instead of
// our own timer. A real tag derives its bit timing from the field for the same
// reason: over a 37 bit answer even a small carrier offset accumulates into enough
// drift to corrupt the later bits. Needs SSC_CLK configured as an input, see
// gpio_fpga_mod_feedback_setup().
static void hitag_wait_carrier(uint16_t periods) {
while (periods--) {
while (Gpio_SSC_CLK_Read() == false) { WDT_HIT(); }
while (Gpio_SSC_CLK_Read() == true) { WDT_HIT(); }
}
}
// Manchester at 4 kbit/s, clocked off the carrier rather than the ARM timer.
//
// The sense matters: HT2 datasheet 3.3.1 says "The first bit of the transmitted
// data always starts with the Modulator ON (loaded) state", so a '1' begins
// loaded. Loaded is DOUT *high* here - lo_edge_detect.v has
// wire tag_modulation = ssp_dout & !lf_field;
// assign pwr_oe2 = tag_modulation; assign pwr_oe4 = tag_modulation;
// so in tag mode (lf_field 0) a high DOUT switches the load transistors on.
//
// Do not reach for the SHORT_COIL()/OPEN_COIL() macros in util.h here: they map
// SHORT_COIL to DOUT low, which is the opposite of what this FPGA mode does, and
// following their naming inverts the whole answer.
void hitag_tag_send_bit_mc4k_sync(int bit, bool ledcontrol) {
if (ledcontrol) LED_A_ON();
if (bit) {
// loaded, then unloaded
Gpio_SSC_DOUT_High();
hitag_wait_carrier(16);
Gpio_SSC_DOUT_Low();
hitag_wait_carrier(16);
} else {
// unloaded, then loaded
Gpio_SSC_DOUT_Low();
hitag_wait_carrier(16);
Gpio_SSC_DOUT_High();
hitag_wait_carrier(16);
}
if (ledcontrol) LED_A_OFF();
}
// Carrier-synchronised frame send for the Hitag 2 simulator.
//
// One write per carrier period, latched right after the SSC_CLK edge, so the
// transitions land on carrier boundaries instead of wherever a polling loop
// happens to finish. Levels are computed inline: a prebuilt sample buffer was
// tried, copying SimulateTagLowFrequencyEx() more literally, and it destabilised
// the device without improving the modulation.
//
// Manchester, 32 T0 per bit. A '1' is loaded for the first half then unloaded,
// a '0' is the reverse; HT2 datasheet 3.3.1 requires the first bit to begin
// loaded. Loaded is DOUT high: lo_edge_detect.v ties pwr_oe2/pwr_oe4 to
// ssp_dout, so a high DOUT switches the load transistors on. Do not go by the
// SHORT_COIL/OPEN_COIL names in util.h, they read backwards for this FPGA mode.
//
// Every wait is bounded. ssp_clk is the carrier zero crossing and stops dead
// when the reader's field goes away; an unbounded spin here wedges the device
// with no way back, since this loop cannot poll data_available() for `hw break`.
#define HITAG_CLK_WAIT_GUARD 20000
// Which SSC_DOUT level actually loads the coil. lo_edge_detect.v ties
// pwr_oe2/pwr_oe4 straight to ssp_dout, so a high DOUT should mean loaded, but
// that is a reading of the verilog rather than a measurement - hence the switch.
static bool s_invert_mod = false;
// How many of the 16 carrier periods in a loaded half bit actually switch the
// coil load on. 16 is the real thing, a full-depth load; anything less trades
// depth for a smaller disturbance of our own receive path, which goes deaf for
// the length of the answer once its envelope follower has been slammed.
static uint8_t s_mod_duty = HITAG_T_TAG_HALF_PERIOD;
// TEMP: does the answer burst actually drive the coil? Continuous toggling
// swings the reader's ADC rail to rail, so if a real answer does not, the send
// path is not doing what the diagnostic loop does.
uint32_t g_tx_samples, g_tx_bails, g_tx_frames;
// Edges the FPGA edge detector has handed us since the last reset. Cheap to
// keep (one increment on a path that already runs per edge) and it settles the
// question the simulator kept raising: zero here means no signal is reaching us
// at all, so a run with no frames says nothing about the simulation itself.
uint32_t g_hitag_edges;
// TEMP INSTRUMENT: raw edge intervals (T0) of the most recent receive, kept so a
// caller can dump them *after* the exchange has finished. Printing from inside
// the receive loop changes the timing it is meant to measure, so nothing here
// prints; it is a plain store on a path that already runs once per edge.
uint16_t g_hitag_rx_iv[HITAG_RX_IV_MAX];
uint16_t g_hitag_rx_it[HITAG_RX_IV_MAX];
uint32_t g_hitag_rx_iv_count;
void hitag_edges_reset(void) {
g_hitag_edges = 0;
}
void hitag_tag_set_mod_polarity(bool invert) {
s_invert_mod = invert;
}
void hitag_tag_set_mod_duty(uint8_t duty) {
s_mod_duty = (duty == 0 || duty > HITAG_T_TAG_HALF_PERIOD) ? HITAG_T_TAG_HALF_PERIOD : duty;
}
// Hitag 2 public (read only) modes, HT2 datasheet rev 2.1 sections 4.2.3 and 4.2.4.
//
// Once the transponder has gone past t_WAIT START_AUTH without being addressed it
// stops behaving like a Hitag 2 and just talks: it "cyclically transmits page 4
// to page 7 in plain mode to the read/write device without a start sequence as
// long as the transponder is in the field". So there is no SOF, no framing and
// no answer timing to hit - the caller loops over this and the only thing that
// matters is that the bit period is right and the code is right.
//
// Public Mode A Manchester 64 T0 2 kbit/s pages 4,5 (emulates uEM H400x)
// Public Mode B Biphase 32 T0 4 kbit/s pages 4..7 (ISO 11784/11785)
// Public Mode C Biphase 64 T0 2 kbit/s pages 4..7 (emulates PCF793X)
//
// Manchester follows the same convention as the command mode answer above: a '1'
// loads the first half of the bit, a '0' the second.
//
// Biphase here is the differential code: the level always flips at a bit
// boundary, and a '0' flips again in the middle of the bit. That is the ISO
// 11784/11785 convention, which is what Public Mode B claims compliance with, but
// the datasheet only draws it and the drawing does not survive text extraction -
// so if a reader decodes B or C inverted, this is the line to flip.
void hitag_tag_send_public(const uint8_t *frame, size_t frame_len, uint8_t period, bool biphase, bool ledcontrol) {
if ((frame_len == 0) || (period == 0)) {
return;
}
const uint8_t half = period / 2;
const size_t total = frame_len * period;
const uint8_t duty = (uint8_t)(((uint16_t)s_mod_duty * half) / HITAG_T_TAG_HALF_PERIOD);
g_tx_frames++;
if (ledcontrol) LED_A_ON();
// Carries across bits, so a differential code stays differential from one
// call to the next - the pages are sent back to back with no gap.
static bool s_bp_level = false;
const uint16_t t_frame = GetPrecisionCounterRaw();
for (size_t sample = 0; sample < total; sample++) {
const size_t idx = sample / period;
const size_t phase = sample % period;
const uint16_t deadline = (uint16_t)((sample + 1) * T0);
const int bit = TEST_BIT_MSB(frame, idx);
bool loaded;
if (biphase) {
// One flip at the boundary, a second mid bit for a '0'.
if (phase == 0) {
s_bp_level = !s_bp_level;
} else if ((phase == half) && (bit == 0)) {
s_bp_level = !s_bp_level;
}
loaded = s_bp_level;
} else {
loaded = bit ? (phase < half) : (phase >= half);
}
// Same shallow load knob the command mode answer uses, but scaled.
//
// s_mod_duty is expressed against the command mode answer's 16 T0 half
// bit, and defaults to exactly that - full duty, clamp never fires. The
// 2 kbit/s public modes have a 32 T0 half bit, so applying it unscaled
// loaded only the first half of every half bit and left the modulation
// at half duty by accident.
if (loaded && ((phase % half) >= duty)) {
loaded = false;
}
// Same carrier-plus-deadline pacing as hitag_tag_send_frame_mc4k_sync():
// wait on ssp_clk while it is healthy, fall through on the timer if it
// drops out, so our own load modulation cannot stretch the bit rate.
while (Gpio_SSC_CLK_Read() == false) {
if (GetPrecisionCounterDelta(t_frame) >= deadline) {
g_tx_bails++;
break;
}
}
g_tx_samples++;
if (loaded != s_invert_mod) {
Gpio_SSC_DOUT_High();
} else {
Gpio_SSC_DOUT_Low();
}
while (Gpio_SSC_CLK_Read() == true) {
if (GetPrecisionCounterDelta(t_frame) >= deadline) {
break;
}
}
}
// Deliberately no Gpio_SSC_DOUT_Low() here. The public modes send the pages
// "cyclically ... without a start sequence", so one pass runs straight into
// the next; dropping the load between passes put a gap at the wrap and cost
// the bit there. Measured: the reader recovered the 64 bit pattern exactly
// except for one undecodable Manchester pair per cycle, always at the wrap,
// which was enough to stop lf em 410x demod parsing it. The caller clears
// DOUT once the whole loop ends.
if (ledcontrol) LED_A_OFF();
}
void hitag_tag_send_frame_mc4k_sync(const uint8_t *frame, size_t frame_len, int sof_bits, bool ledcontrol) {
const size_t nbits = (size_t)sof_bits + frame_len;
const size_t total = nbits * HITAG_T_TAG_FULL_PERIOD;
g_tx_frames++;
if (ledcontrol) LED_A_ON();
Gpio_SSC_DOUT_Low();
// Time the frame against the free running counter as well as the carrier.
//
// Waiting only on ssp_clk feeds our own modulation back into our own bit
// timing: loading the coil is what suppresses the carrier the zero crossing
// detector is watching, so a sample whose edge is missed waits for the next
// one and the frame stretches. Measured against a Paxton reader, every
// sample went out - 251008 for 212 frames, exactly 1184 each, no bails - yet
// the answer took 1537 T0 instead of 1200, so 28% of samples cost an extra
// carrier period and the Manchester rate fell from 4 kbit/s to 3.1. A real
// reader will not decode that, which is why nothing came back but polling.
//
// Each sample now also carries a deadline, one carrier period after the last,
// measured from the start of the frame. While the carrier is healthy its
// edge arrives first and nothing changes; when it drops out we carry on from
// the timer instead of waiting, so the frame can never run long.
const uint16_t t_frame = GetPrecisionCounterRaw();
for (size_t sample = 0; sample < total; sample++) {
const size_t idx = sample / HITAG_T_TAG_FULL_PERIOD;
const size_t phase = sample % HITAG_T_TAG_FULL_PERIOD;
const uint16_t deadline = (uint16_t)((sample + 1) * T0);
const int bit = (idx < (size_t)sof_bits)
? 1
: TEST_BIT_MSB(frame, idx - (size_t)sof_bits);
bool loaded = bit ? (phase < HITAG_T_TAG_HALF_PERIOD)
: (phase >= HITAG_T_TAG_HALF_PERIOD);
// Shallower load: switch on for only the first s_mod_duty periods of the
// loaded half bit. The envelope the reader sees is a low pass of this,
// so the Manchester transition is still there, just less deep.
if (loaded && ((phase % HITAG_T_TAG_HALF_PERIOD) >= s_mod_duty)) {
loaded = false;
}
bool lost_clock = false;
while (Gpio_SSC_CLK_Read() == false) {
if (GetPrecisionCounterDelta(t_frame) >= deadline) {
lost_clock = true;
break;
}
}
if (lost_clock) {
// The carrier did not come back in time. Keep the frame on schedule
// rather than abandoning it: a stretched answer is worse than one
// sample placed by the timer.
g_tx_bails++;
}
g_tx_samples++;
if (loaded != s_invert_mod) {
Gpio_SSC_DOUT_High();
} else {
Gpio_SSC_DOUT_Low();
}
while (Gpio_SSC_CLK_Read() == true) {
if (GetPrecisionCounterDelta(t_frame) >= deadline) {
break;
}
}
}
// TEMP EXPERIMENT: the trailing half-period hold is disabled.
//
// It was added so our own decoder had a closing edge for the last half bit.
// A reader may instead see it as a surplus bit at the tail, which makes a
// frame that lost its leading half bit come out at 37 bits with a true SOF of
// 4 - and that is undecidable from the frame alone when the payload starts
// with a 1.
Gpio_SSC_DOUT_Low();
if (ledcontrol) LED_A_OFF();
}
static void hitag_tag_send_bit(int bit, hitag_mod_t modulation, bool ledcontrol) {
// Reset clock for the next bit
ResetPrecisionCounter();
@@ -469,28 +909,102 @@ static void hitag_tag_send_bit(int bit, hitag_mod_t modulation, bool ledcontrol)
if (ledcontrol) LED_A_OFF();
}
void hitag_tag_receive_frame(uint8_t *rx, size_t sizeofrx, size_t *rxlen, uint32_t *start_time, bool ledcontrol, int *overflow) {
lo_edge_t next_edge = LO_EDGE_FALLING;
void hitag_tag_receive_frame_ex(uint8_t *rx, size_t sizeofrx, size_t *rxlen, uint32_t *start_time, bool ledcontrol, int *overflow, bool sof_is_bit) {
uint8_t edges[160] = {0};
uint32_t rb_i = 0;
// Receive frame, watch for at most T0*EOF periods
g_hitag_rx_iv_count = 0;
// How many polls of this loop went by between one edge and the next. A
// merged interval with roughly twice the usual poll count means the loop kept
// running and the edge detector simply never fired; the same poll count as a
// single bit means we stalled and missed it. One increment per iteration, so
// it does not change what it measures.
uint32_t it = 0;
// Receive frame, watch for at most T0*EOF periods.
//
// StartLoEdgeCapture() clocks TC1 from MCK/32, not from the carrier, so the
// counter free runs; what holds it down is the external trigger, which
// resets it on every falling edge of SSC_FRAME. When the edge detector has
// no clean carrier to work with it fires continuously, the counter never
// reaches the EOF limit, and without a bound this loop never returns - the
// caller then never gets back to its data_available()/BUTTON_PRESS() check
// and the device wedges with no way back short of a replug. Returning early
// with nothing received costs nothing, the simulator just listens again.
// Has the opening gap of a frame been taken yet? Only the very first edge
// may be recovered from a rising event, see the comment further down.
bool frame_open = false;
uint32_t guard = HITAG_RX_IDLE_GUARD;
while (GetLoEdgeCaptureCount() < T0 * HITAG_T_EOF) {
// Check if edge in modulation is detected
if (GetLoEdgeCaptureStatus() == next_edge) {
next_edge = next_edge == LO_EDGE_RISING ? LO_EDGE_FALLING : LO_EDGE_RISING;
it++;
// only use INPUT_CAPTURE_EVT_RB falling edge for now
if (next_edge == LO_EDGE_FALLING) {
continue;
}
if (--guard == 0) {
break;
}
if ((guard & 0x3FF) == 0) {
WDT_HIT();
}
// Act on every falling edge, without demanding a rising edge in between.
//
// GetLoEdgeCaptureStatus() reads TC_SR once, which clears LDRAS and LDRBS
// together and reports rising in preference, so when both edges land in
// one poll the falling event is destroyed. Tracking an expected edge and
// skipping anything that did not match turned that single lost edge into
// a permanent desync, discarding every later falling edge of the frame.
lo_edge_t ev = GetLoEdgeCaptureStatus();
// Recover the falling edge that opens a frame.
//
// GetLoEdgeCaptureStatus() reads TC_SR once, which clears LDRAS and
// LDRBS together and reports rising in preference. Inside a frame this
// loop polls about four times per T0, so the two edges of a gap are seen
// separately - but the frame's FIRST gap arrives while we are between
// calls, waiting out the idle, and by the first poll both its falling and
// its rising have landed. Rising wins and the falling event is destroyed.
//
// Measured on this rig: the first event of every reader frame is a rising
// edge carrying RA = 10 T0, which is the reader's t_low - proof that the
// falling edge really happened one t_low earlier. The next falling edge
// is the SECOND gap, and its interval - the first bit's period - is then
// swallowed by the idle carried in *overflow and read as the frame
// opener. The first bit was therefore never measured at all, and the
// opener below fabricated it as '1'. That is invisible for START_AUTH
// (11000), for a password starting 0xB and for WRITE PAGE (0xA2), and it
// stored 0x91223344 when the reader wrote 0x11223344.
//
// TC_RB still holds the destroyed edge's capture, so taking the rising
// event as that falling edge recovers it exactly. Only before the frame
// has opened: once bits are arriving the polls are fast enough to see
// both edges, and converting them there would double every gap.
if ((ev == LO_EDGE_RISING) && (frame_open == false)) {
ev = LO_EDGE_FALLING;
}
if (ev == LO_EDGE_FALLING) {
// Retrieve the new timing values
uint32_t rb = GetLoEdgeCaptureFalling() / T0 + *overflow;
*overflow = 0;
edges[rb_i++] = rb;
// Past this point the polls are fast enough to see both edges of a
// gap, so no further rising event may be taken for a falling one.
frame_open = true;
g_hitag_edges++;
if (rb_i < ARRAYLEN(edges)) {
edges[rb_i++] = rb;
}
if (g_hitag_rx_iv_count < HITAG_RX_IV_MAX) {
g_hitag_rx_it[g_hitag_rx_iv_count] = (it > 0xFFFF) ? 0xFFFF : (uint16_t)it;
g_hitag_rx_iv[g_hitag_rx_iv_count++] = (rb > 0xFFFF) ? 0xFFFF : (uint16_t)rb;
}
it = 0;
if (ledcontrol) LED_B_INV();
@@ -501,11 +1015,94 @@ void hitag_tag_receive_frame(uint8_t *rx, size_t sizeofrx, size_t *rxlen, uint32
// Capture reader frame
if (rb >= HITAG_T_STOP) {
// Hitag µ SOF
if (*rxlen != 0 && *rxlen != 1) {
// DBG DbpString("weird0?");
break;
// Only an interval at the very start opens a frame. One that
// turns up mid frame is a dropped edge, not a boundary: two bit
// periods merge into a single gap over HITAG_T_STOP. Bailing out
// there truncated the reader's 32 bit password to 6 bits, having
// received textbook intervals up to that point (22 19 20 28 29 21).
// Skip it and keep going; the frame still closes on the EOF
// timeout, which is what actually delimits frames here.
if (*rxlen != 0) {
// Recover the bits the merged gap swallowed.
//
// A dropped edge joins two or three bit periods into one gap.
// Skipping it, as this used to, throws those bits away - which
// is exactly why a 64 bit AUTH arrives as 61..63 and crypto
// never authenticates. Measured intervals in a failing frame
// are textbook otherwise, 17..19 for '0' and 25..27 for '1',
// with a single oversized gap among them:
//
// ... 17 71 25 27 ... 71 = 27+27+17, three bits
// ... 17 49 20 19 ... 49 = two bits
//
// So decompose the gap into the run of nominal bit periods
// that best fits its length. Where the fit is all-ones or
// all-zeroes the result is exact; a mixed run has an order
// this cannot know, and is emitted ones-first as a guess. A
// wrong guess fails authentication exactly as the dropped
// bits already did, so this can only improve on discarding.
// Only inside a frame that already carries real content.
//
// Without this the rule fabricates bits out of noise: a stray
// edge opens a frame, the long gap that follows is "recovered"
// into two or three invented bits, and what should have been
// discarded becomes a 4 bit frame. Measured against a Paxton,
// that cost the whole exchange - 69 fabricated 4 bit frames,
// not one password or read command answered, where the same
// build without the rule ran 206 passwords and 824 reads.
//
// A dropped edge only matters in the long frames it actually
// damages: the reader's shortest real command is 5 bits and
// the loss shows up around bit 40 of a 64 bit AUTH, so
// requiring a byte of frame first protects START_AUTH while
// leaving every frame the rule exists for untouched.
if (*rxlen < 8) {
continue;
}
uint32_t best_k = 0, best_ones = 0, best_err = 0xFFFFFFFF;
for (uint32_t k = 2; k <= 3; k++) {
for (uint32_t ones = 0; ones <= k; ones++) {
uint32_t sum = ones * HITAG_T_1_NOMINAL +
(k - ones) * HITAG_T_0_NOMINAL;
uint32_t err = (rb > sum) ? (rb - sum) : (sum - rb);
if (err < best_err) {
best_err = err;
best_k = k;
best_ones = ones;
}
}
}
// Only act on a decomposition that actually fits; a gap that
// matches nothing is noise, and inventing bits from it would
// corrupt a frame that would otherwise merely be short.
if (best_err <= HITAG_T_SPLIT_TOL) {
for (uint32_t b = 0; b < best_k; b++) {
if ((*rxlen / 8) >= sizeofrx) {
break;
}
if (b < best_ones) {
SET_BIT_MSB(rx, *rxlen);
}
(*rxlen)++;
}
}
continue;
}
// The opening gap carries no bit, in any of the three protocols.
//
// A reader frame is N bit gaps followed by an EOF gap, so an N
// bit frame is N+1 falling edges and N intervals between them:
// the first interval is the first bit's period, and the gap that
// opens the frame only starts the clock. This used to count it
// as a '1' for Hitag 2, which was a workaround for the lost
// opening edge recovered above - with the edge back, doing so
// would shift the whole frame by one bit.
(void)sof_is_bit;
*rxlen = 0;
} else if (rb >= HITAG_T_1_MIN) {
// '1' bit
@@ -527,13 +1124,28 @@ void hitag_tag_receive_frame(uint8_t *rx, size_t sizeofrx, size_t *rxlen, uint32
Dbprintf("RX %i bits.. start_time:%i edges:", *rxlen, *start_time);
Dbhexdump(rb_i, edges, false);
}
}
void hitag_tag_send_frame(const uint8_t *frame, size_t frame_len, int sof_bits, hitag_mod_t modulation, bool ledcontrol) {
// The beginning of the frame is hidden in some high level; pause until our bits will have an effect
ResetPrecisionCounter();
Gpio_SSC_DOUT_High();
// Hitag S and u framing: the opening gap is a delimiter, not a bit
void hitag_tag_receive_frame(uint8_t *rx, size_t sizeofrx, size_t *rxlen, uint32_t *start_time, bool ledcontrol, int *overflow) {
hitag_tag_receive_frame_ex(rx, sizeofrx, rxlen, start_time, ledcontrol, overflow, false);
}
void hitag_tag_send_frame_ex(const uint8_t *frame, size_t frame_len, int sof_bits, hitag_mod_t modulation, bool ledcontrol, bool lead_in) {
// The beginning of the frame is hidden in some high level; pause until our bits will have an effect.
// Hitag 2 does not want this: it starts modulating straight into the first SOF
// bit from an unloaded line, and the lead-in would swallow that bit's rising
// transition as well as prepending a burst that is not part of the Manchester.
ResetPrecisionCounter();
if (lead_in == false) {
Gpio_SSC_DOUT_Low();
} else {
Gpio_SSC_DOUT_High();
}
if (lead_in)
switch (modulation) {
case AC4K:
case MC8K: {
@@ -564,3 +1176,8 @@ void hitag_tag_send_frame(const uint8_t *frame, size_t frame_len, int sof_bits,
Gpio_SSC_DOUT_Low();
}
// Hitag S and u keep the lead-in
void hitag_tag_send_frame(const uint8_t *frame, size_t frame_len, int sof_bits, hitag_mod_t modulation, bool ledcontrol) {
hitag_tag_send_frame_ex(frame, frame_len, sof_bits, modulation, ledcontrol, true);
}
+46
View File
@@ -20,7 +20,18 @@
#define HITAG_T_1 28 /* T[1] should be 26..32 */
#define HITAG_T_0_MIN 15 /* T[0] should be 18..22 */
#define HITAG_T_1_MIN 25 /* T[1] should be 26..32 */
// A histogram taken during simulation put '1' intervals at 23..27 and suggested
// 22 here, but that sample was polluted by the simulator's own artifacts:
// against a genuine fob 22 fragmented the reader frames that 25 decodes
// cleanly. Left at 25 until a histogram from a sniff-only run says otherwise.
#define HITAG_T_STOP 36 /* T_EOF should be > 36 */
/* Nominal reader bit periods, in T0, measured off a Paxton: '0' lands 17..19 and
* '1' lands 25..27. Used to decompose a gap that swallowed several bits when an
* edge was dropped, and the tolerance is how far the best fit may miss. */
#define HITAG_T_0_NOMINAL 18
#define HITAG_T_1_NOMINAL 26
#define HITAG_T_SPLIT_TOL 4
#define HITAG_T_CODE_VIOLATION 36 /* Hitag µ TFcv should be 34..38 */
#define HITAG_T_EOF 80 /* T_EOF should be > 36 */
@@ -35,6 +46,15 @@
#define HITAG_T_TAG_THREE_HALF_PERIOD 41
#define HITAG_T_TAG_FOUR_HALF_PERIOD 57
// Bound on the receive spin, see hitag_tag_receive_frame_ex(). The longest
// frame worth receiving is 64 bits of BPLM, about 2050 T0 or 16 ms, so the bound
// has to sit above that or real frames get cut short. It also has to stay small
// enough that a vanished field still returns control promptly: at roughly ten
// cycles an iteration this is some tens of milliseconds, so `hw break` still
// answers quickly. WDT_HIT() is deliberately NOT called every iteration - doing
// that made each idle pass take seconds and left the simulator deaf.
#define HITAG_RX_IDLE_GUARD 200000
#define HITAG_T_TAG_HALF_PERIOD 16
#define HITAG_T_TAG_FULL_PERIOD 32
@@ -54,7 +74,33 @@ void hitag_reader_receive_frame(uint8_t *rx, size_t sizeofrx, size_t *rxlen, uin
int sof_bits);
int hitag_reader_transfer(const uint8_t *tx, size_t txlen, uint8_t *rx, size_t sizeofrx, size_t *rxlen, int t_wait,
bool ledcontrol, hitag_mod_t modulation, uint8_t sof_bits, uint8_t send_sof);
void hitag_tag_receive_frame_ex(uint8_t *rx, size_t sizeofrx, size_t *rxlen, uint32_t *start_time,
bool ledcontrol, int *overflow, bool sof_is_bit);
void hitag_tag_receive_frame(uint8_t *rx, size_t sizeofrx, size_t *rxlen, uint32_t *start_time, bool ledcontrol, int *overflow);
void hitag_tag_send_bit_mc4k_sync(int bit, bool ledcontrol);
void hitag_tag_set_mod_polarity(bool invert);
void hitag_tag_set_mod_duty(uint8_t duty);
uint8_t hitag_autotune_threshold(void);
extern uint32_t g_hitag_edges;
extern uint32_t g_tx_samples, g_tx_bails, g_tx_frames;
// TEMP INSTRUMENT: raw edge intervals (T0) of the most recent receive.
#define HITAG_RX_IV_MAX 8
extern uint16_t g_hitag_rx_iv[HITAG_RX_IV_MAX];
extern uint16_t g_hitag_rx_it[HITAG_RX_IV_MAX];
extern uint32_t g_hitag_rx_iv_count;
void hitag_edges_reset(void);
void hitag_tag_send_frame_mc4k_sync(const uint8_t *frame, size_t frame_len, int sof_bits, bool ledcontrol);
// Hitag 2 public (read only) modes: one pass of a raw, unframed bit stream.
//
// `period` is the bit length in carrier periods - 32 for a 4 kbit/s mode, 64 for
// 2 kbit/s - and `biphase` picks the code. There is no SOF and no framing: the
// public modes send the user pages back to back with no start sequence, so the
// caller simply calls this in a loop.
void hitag_tag_send_public(const uint8_t *frame, size_t frame_len, uint8_t period, bool biphase, bool ledcontrol);
void hitag_tag_send_frame_ex(const uint8_t *frame, size_t frame_len, int sof_bits,
hitag_mod_t modulation, bool ledcontrol, bool lead_in);
void hitag_tag_send_frame(const uint8_t *frame, size_t frame_len, int sof_bits, hitag_mod_t modulation, bool ledcontrol);
#endif
+24 -13
View File
@@ -362,16 +362,16 @@ static void htu_handle_reader_command(uint8_t *rx, const size_t rxlen, uint8_t *
/*
* Simulates a Hitag µ Tag with the given data
*/
void htu_simulate(bool tag_mem_supplied, int8_t threshold, const uint8_t *data, bool ledcontrol) {
void htu_simulate(int8_t threshold, bool ledcontrol) {
uint8_t rx[HITAG_FRAME_LEN] = {0};
size_t rxlen = 0;
uint8_t tx[HITAG_FRAME_LEN] = {0};
size_t txlen = 0;
// Free any allocated BigBuf memory
BigBuf_free();
BigBuf_Clear_ext(false);
// keep emulator memory, that is where eload put the tag content
BigBuf_free_keep_EM();
BigBuf_Clear_keep_EM();
DbpString("Starting Hitag µ simulation");
@@ -380,13 +380,24 @@ void htu_simulate(bool tag_mem_supplied, int8_t threshold, const uint8_t *data,
tag.max_page = 64; // Default maximum page
RESET_AUTHENTICATION();
// Read tag data into memory if supplied
if (tag_mem_supplied) {
DbpString("Loading Hitag µ memory...");
// First 6 bytes are the UID (48 bits)
memcpy(tag.uid, data, 6);
// Rest is page data
memcpy(tag.data.pages, data + 6, sizeof(tag.data.pages));
// Take the tag content from emulator memory, where `lf hitag eload -m` put it:
// UID first, then the pages. The old inline path copied UID + 1024 bytes out
// of the command payload, which is more than an NG frame can ever carry.
uint8_t *em = BigBuf_get_EM_addr();
bool em_empty = true;
for (size_t i = 0; i < HITAGU_UID_SIZE + sizeof(tag.data.pages); i++) {
if (em[i] != 0) {
em_empty = false;
break;
}
}
if (em_empty) {
DbpString("Emulator memory is empty, simulating the last read tag");
} else {
DbpString("Loading Hitag u memory from emulator memory...");
memcpy(tag.uid, em, HITAGU_UID_SIZE);
memcpy(tag.data.pages, em + HITAGU_UID_SIZE, sizeof(tag.data.pages));
}
// Update max_page based on configuration
@@ -459,8 +470,8 @@ void htu_simulate(bool tag_mem_supplied, int8_t threshold, const uint8_t *data,
}
hitag_cleanup(ledcontrol);
// Release allocated memory from BigBuf
BigBuf_free();
// release BigBuf, but keep emulator memory for eview / esave
BigBuf_free_keep_EM();
DbpString("Simulation stopped");
}
+1 -1
View File
@@ -22,7 +22,7 @@
#include "common.h"
#include "hitag.h"
void htu_simulate(bool tag_mem_supplied, int8_t threshold, const uint8_t *data, bool ledcontrol);
void htu_simulate(int8_t threshold, bool ledcontrol);
void htu_read(const lf_hitag_data_t *payload, bool ledcontrol);
void htu_write_page(const lf_hitag_data_t *payload, bool ledcontrol);
int htu_read_uid(uint64_t *uid, bool ledcontrol, bool send_answer);
+33 -8
View File
@@ -22,6 +22,7 @@
#include "ticks_apis.h"
#include "fpga_apis.h"
#include "dbprint.h"
#include "commonutil.h" // ARRAYLEN
#include "appmain.h"
// Sam7s has several timers, we will use the source TIMER_CLOCK1 (aka AT91C_TC_CLKS_TIMER_DIV1_CLOCK)
@@ -66,6 +67,13 @@ bool lf_test_periods(size_t expected, size_t count) {
//////////////////////////////////////////////////////////////////////////////
static uint8_t previous_adc_val = 0; // 0xFF;
static uint8_t adc_avg = 0;
// The same average, kept as the raw sum of 32 samples (i.e. avg scaled by 32).
// adc_avg alone is truncated to whole ADC counts, so a threshold built from it
// cannot be placed finer than one count - which is why LIMIT_DEV could not be
// lowered without the edge detector chattering on quantisation noise.
static uint32_t adc_avg_q5 = 0;
#define LIMIT_DEV_Q5_REPORT (20 * 32)
static uint8_t adc_max;
static uint8_t adc_min;
@@ -87,19 +95,34 @@ void lf_sample_mean(void) {
periods++;
}
}
adc_avg_q5 = adc_sum; // avg * 32, no rounding thrown away
adc_avg = adc_sum >> 5; // division by 32
previous_adc_val = adc_avg;
DBG Dbprintf("LF ADC average %u, max %u, min %u, diff %u", adc_avg, adc_max, adc_min, adc_max - adc_min);
DBG Dbprintf("LF ADC average %u, max %u, min %u, diff %u (threshold is +/-%u counts)",
adc_avg, adc_max, adc_min, adc_max - adc_min, (unsigned)(LIMIT_DEV_Q5_REPORT / 32));
}
static size_t lf_count_edge_periods_ex(size_t max, bool wait, bool detect_gap) {
#define LIMIT_DEV 20
// Deviation from the mean that counts as an edge, in 1/32 ADC counts, so the
// threshold is no longer stuck on whole ADC counts the way adc_avg + LIMIT_DEV
// was. 20 counts is the long standing value and is kept here (20 * 32).
//
// Measured, in case it is tempting to lower it: a Proxmark simulating a Hitag 2
// tag modulates only about 2 counts deep, where a genuine fob clears 20 at the
// same position. Dropping to 6 gained nothing, and 2 only made the detector
// trigger on noise and decode UID FFFFFFFF. The simulator's dip is the problem,
// not this threshold.
// Measured: lowering this from 20 to 10, to let a simulating Proxmark answer
// shallower and so recover faster, stops the reader reading anything at all -
// 0 of 3 UID reads at every modulation duty. The existing note below already
// said 6 gained nothing and 2 triggered on noise; 10 is past the edge too.
#define LIMIT_DEV_Q5 (20 * 32)
// timeout limit to 100 000 w/o
uint32_t timeout = 100000;
size_t periods = 0;
uint8_t avg_peak = adc_avg + LIMIT_DEV;
uint8_t avg_through = adc_avg - LIMIT_DEV;
uint32_t avg_peak_q5 = adc_avg_q5 + LIMIT_DEV_Q5;
uint32_t avg_through_q5 = (adc_avg_q5 > LIMIT_DEV_Q5) ? (adc_avg_q5 - LIMIT_DEV_Q5) : 0;
while (BUTTON_PRESS() == false) {
WDT_HIT();
@@ -123,6 +146,7 @@ static size_t lf_count_edge_periods_ex(size_t max, bool wait, bool detect_gap) {
timeout = 100000; // reset timeout
volatile uint8_t adc_val = FPGA_SSC_RX_Value(); // Get current adc value.
if (g_logging) {
logSampleSimple(adc_val);
}
@@ -138,24 +162,24 @@ static size_t lf_count_edge_periods_ex(size_t max, bool wait, bool detect_gap) {
} else {
if (g_edge_mode == LF_ADC_WAV_REVERSED) {
if (rising_edge) {
if ((previous_adc_val > avg_peak) && (adc_val <= previous_adc_val)) {
if ((((uint32_t)previous_adc_val << 5) > avg_peak_q5) && (adc_val <= previous_adc_val)) {
rising_edge = false;
return periods;
}
} else {
if ((previous_adc_val < avg_through) && (adc_val >= previous_adc_val)) {
if ((((uint32_t)previous_adc_val << 5) < avg_through_q5) && (adc_val >= previous_adc_val)) {
rising_edge = true;
return periods;
}
}
} else if (g_edge_mode == LF_ADC_NOT_REVERSED) {
if (rising_edge) {
if (adc_val <= adc_avg && adc_val <= avg_through) {
if ((((uint32_t)adc_val << 5) <= adc_avg_q5) && (((uint32_t)adc_val << 5) <= avg_through_q5)) {
rising_edge = false;
return periods;
}
} else {
if (adc_val >= avg_peak) {
if (((uint32_t)adc_val << 5) >= avg_peak_q5) {
rising_edge = true;
return periods;
}
@@ -273,6 +297,7 @@ void lf_init(lf_adc_init_mode_t init_mode, lf_adc_edge_mode_t edge_mode, bool le
}
void lf_finalize(bool ledcontrol) {
FpgaWriteConfWord(FPGA_MAJOR_MODE_OFF);
if (ledcontrol) LEDsoff();
}
+448 -29
View File
@@ -845,36 +845,130 @@ static bool ht2_get_uid(uint32_t *uid) {
return true;
}
// Try to actually read the configuration byte, rather than assume one.
//
// Returns true and fills config/uid if the tag authenticated with this key.
static bool ht2_try_auth_read(uint8_t cmdtype, const uint8_t *key, uint32_t *uid, uint8_t *config) {
lf_hitag_data_t packet;
memset(&packet, 0, sizeof(packet));
packet.cmd = cmdtype;
if (cmdtype == HT2F_PASSWORD) {
memcpy(packet.pwd, key, HITAG_PASSWORD_SIZE);
} else {
memcpy(packet.key, key, HITAG_CRYPTOKEY_SIZE);
}
clearCommandBuffer();
SendCommandNG(CMD_LF_HITAG_READER, (uint8_t *)&packet, sizeof(packet));
PacketResponseNG resp;
if (WaitForResponseTimeout(CMD_LF_HITAG_READER, &resp, 2000) == false) {
SendCommandNG(CMD_BREAK_LOOP, NULL, 0);
return false;
}
if (resp.status != PM3_SUCCESS) {
return false;
}
uint8_t *data = resp.data.asBytes;
*uid = bytes_to_num(data, HITAG_UID_SIZE);
*config = data[HITAG_BLOCK_SIZE * 3];
return true;
}
static int CmdLFHitagInfo(const char *Cmd) {
CLIParserContext *ctx;
CLIParserInit(&ctx, "lf hitag info",
"Hitag 2 tag information",
"lf hitag info"
"Hitag 2 tag information\n"
"The configuration byte lives in page 3, which needs authentication.\n"
"Without a key the two factory defaults are tried.",
"lf hitag info\n"
"lf hitag info -k 4D494B52 -> 4 bytes, password mode\n"
"lf hitag info -k 4F4E4D494B52 -> 6 bytes, crypto mode"
);
void *argtable[] = {
arg_param_begin,
arg_str0("k", "key", "<hex>", "key, 4 bytes for password mode, 6 bytes for crypto mode"),
arg_param_end
};
CLIExecWithReturn(ctx, Cmd, argtable, true);
uint8_t userkey[HITAG_CRYPTOKEY_SIZE] = {0};
int ukeylen = 0;
CLIGetHexWithReturn(ctx, 1, userkey, &ukeylen);
CLIParserFree(ctx);
// The length picks the mode, so a wrong one is worth catching here rather
// than sending it and reporting a puzzling authentication failure.
if ((ukeylen != 0) && (ukeylen != HITAG_PASSWORD_SIZE) && (ukeylen != HITAG_CRYPTOKEY_SIZE)) {
PrintAndLogEx(ERR, "Key must be %u bytes (password) or %u bytes (crypto), got %i",
HITAG_PASSWORD_SIZE, HITAG_CRYPTOKEY_SIZE, ukeylen);
return PM3_EINVARG;
}
// read UID
uint32_t uid = 0;
if (ht2_get_uid(&uid) == false) {
return PM3_ESOFT;
}
// how to determine Hitag types?
// need auth / pwd to get it.
// we could try the default key/pwd and print if successful
// read block3, get configuration byte.
// Read the configuration byte instead of guessing it.
//
// This used to print a hardcoded 0x06, so every tag was reported as
// "Password mode" whatever it actually was - a crypto mode card sitting on
// the antenna was described as password mode, which is worse than saying
// nothing. The configuration lives in page 3 and page 3 needs
// authentication, so try the two factory defaults and report what comes
// back; if neither opens the tag, say the mode is unknown rather than
// inventing one, and point at the command whose job searching actually is.
//
// Deliberately only the two factory values: running the dictionary here was
// tried and removed. `info` should be quick and answer from the tag itself,
// and a key search is what `lf hitag chk` exists for.
static const uint8_t default_pwd[HITAG_PASSWORD_SIZE] = {0x4D, 0x49, 0x4B, 0x52}; // "MIKR"
static const uint8_t default_key[HITAG_CRYPTOKEY_SIZE] = {0x4F, 0x4E, 0x4D, 0x49, 0x4B, 0x52}; // "ONMIKR"
// common configurations.
print_hitag2_configuration(uid, 0x06); // pwd mode enabled / AM
// print_hitag2_configuration(uid, 0x0E); // crypto mode enabled / AM
// print_hitag2_configuration(uid, 0x02);
// print_hitag2_configuration(uid, 0x00);
// print_hitag2_configuration(uid, 0x04);
uint8_t config = 0;
uint32_t cfg_uid = 0;
const char *opened_with = NULL;
char opened_buf[64] = {0};
// A supplied key is tried first, and its length picks the mode: 4 bytes is a
// password, 6 bytes a crypto key. The factory defaults still follow, so
// giving a key that turns out to be wrong is no worse than giving none.
if ((ukeylen == HITAG_PASSWORD_SIZE) &&
ht2_try_auth_read(HT2F_PASSWORD, userkey, &cfg_uid, &config)) {
snprintf(opened_buf, sizeof(opened_buf), "password " _YELLOW_("%s"),
sprint_hex_inrow(userkey, HITAG_PASSWORD_SIZE));
opened_with = opened_buf;
} else if ((ukeylen == HITAG_CRYPTOKEY_SIZE) &&
ht2_try_auth_read(HT2F_CRYPTO, userkey, &cfg_uid, &config)) {
snprintf(opened_buf, sizeof(opened_buf), "key " _YELLOW_("%s"),
sprint_hex_inrow(userkey, HITAG_CRYPTOKEY_SIZE));
opened_with = opened_buf;
} else if (ht2_try_auth_read(HT2F_PASSWORD, default_pwd, &cfg_uid, &config)) {
opened_with = "default password " _YELLOW_("4D494B52");
} else if (ht2_try_auth_read(HT2F_CRYPTO, default_key, &cfg_uid, &config)) {
opened_with = "default key " _YELLOW_("4F4E4D494B52");
}
if (opened_with != NULL) {
print_hitag2_configuration(uid, config);
PrintAndLogEx(INFO, "Configuration read with %s", opened_with);
} else {
PrintAndLogEx(INFO, "--- " _CYAN_("Tag Information"));
PrintAndLogEx(SUCCESS, "UID...... " _GREEN_("%08X"), uid);
PrintAndLogEx(INFO, "Config... " _YELLOW_("unknown") " ( page 3 needs authentication )");
PrintAndLogEx(HINT, "Hint: Try `" _YELLOW_("lf hitag chk") "` to search for the key");
}
PrintAndLogEx(INFO, "--- " _CYAN_("Fingerprint"));
const char *s = identify_transponder_hitag2(uid);
@@ -1328,6 +1422,212 @@ static int CmdLFHitagWriter(const char *Cmd) {
return PM3_SUCCESS;
}
// Write one page, authenticating with the given credential.
//
// 4 byte credential is a password, 6 byte is a crypto key, matching every other
// Hitag 2 command here.
static int ht2_restore_write_page(uint8_t page, const uint8_t *data, const uint8_t *cred, uint8_t credlen) {
lf_hitag_data_t packet;
memset(&packet, 0, sizeof(packet));
packet.page = page;
memcpy(packet.data, data, HITAG_BLOCK_SIZE);
if (credlen == HITAG_PASSWORD_SIZE) {
packet.cmd = HT2F_PASSWORD;
memcpy(packet.pwd, cred, HITAG_PASSWORD_SIZE);
} else {
packet.cmd = HT2F_CRYPTO;
memcpy(packet.key, cred, HITAG_CRYPTOKEY_SIZE);
}
clearCommandBuffer();
SendCommandNG(CMD_LF_HITAG2_WRITE, (uint8_t *)&packet, sizeof(packet));
PacketResponseNG resp;
if (WaitForResponseTimeout(CMD_LF_HITAG2_WRITE, &resp, 4000) == false) {
return PM3_ETIMEOUT;
}
return resp.status;
}
// Is this a configuration byte a Hitag 2 can actually hold?
//
// Bits 7..4 are RFU and are zero on every tag seen; bit 3 picks crypto or
// password; bits 2..1 pick Hitag 2 (11) or one of the read only public modes;
// bit 0 is the encoding. Writing rubbish here is the one page that can make a
// tag useless, so refuse rather than pass it through.
static bool ht2_config_is_sane(uint8_t cfg, const char **why) {
if ((cfg & 0xF0) != 0) {
*why = "RFU bits 7..4 are not zero";
return false;
}
return true;
}
static int CmdLFHitag2Restore(const char *Cmd) {
CLIParserContext *ctx;
CLIParserInit(&ctx, "lf hitag restore",
"Write a Hitag 2 dump file back to a tag.\n"
"Pages are written in dependency order: user pages first, then the key\n"
"material in pages 1 and 2, and the configuration in page 3 last - because\n"
"page 3 is what switches the tag's mode, and pages 1 and 2 change the very\n"
"credential needed to write it.\n"
"Page 0 holds the UID and is never written.",
"lf hitag restore -f dump.bin -k 4D494B52\n"
"lf hitag restore -f dump.bin -k 4F4E4D494B52\n"
"lf hitag restore -f dump.bin -> try the factory defaults"
);
void *argtable[] = {
arg_param_begin,
arg_str1("f", "file", "<fn>", "specify dump filename"),
arg_str0("k", "key", "<hex>", "key, 4 bytes for password mode, 6 bytes for crypto mode"),
arg_lit0(NULL, "force", "write even if the configuration byte looks wrong"),
arg_param_end
};
CLIExecWithReturn(ctx, Cmd, argtable, false);
int fnlen = 0;
char filename[FILE_PATH_SIZE] = {0};
CLIParamStrToBuf(arg_get_str(ctx, 1), (uint8_t *)filename, FILE_PATH_SIZE, &fnlen);
uint8_t userkey[HITAG_CRYPTOKEY_SIZE] = {0};
int ukeylen = 0;
CLIGetHexWithReturn(ctx, 2, userkey, &ukeylen);
bool force = arg_get_lit(ctx, 3);
CLIParserFree(ctx);
if ((ukeylen != 0) && (ukeylen != HITAG_PASSWORD_SIZE) && (ukeylen != HITAG_CRYPTOKEY_SIZE)) {
PrintAndLogEx(ERR, "Key must be %u bytes (password) or %u bytes (crypto), got %i",
HITAG_PASSWORD_SIZE,
HITAG_CRYPTOKEY_SIZE,
ukeylen
);
return PM3_EINVARG;
}
// load the dump
uint8_t *dump = NULL;
size_t dumplen = 0;
if (loadFile_safe(filename, ".bin", (void **)&dump, &dumplen) != PM3_SUCCESS) {
return PM3_EFILE;
}
if (dumplen != HITAG2_MAX_BYTE_SIZE) {
PrintAndLogEx(ERR, "Expected %u bytes, got %zu", HITAG2_MAX_BYTE_SIZE, dumplen);
free(dump);
return PM3_EFILE;
}
const uint8_t new_cfg = dump[HITAG_BLOCK_SIZE * 3];
const char *why = NULL;
if (ht2_config_is_sane(new_cfg, &why) == false) {
PrintAndLogEx(ERR, "Configuration byte " _RED_("0x%02X") " looks wrong: %s", new_cfg, why);
if (force == false) {
PrintAndLogEx(INFO, "Use `" _YELLOW_("--force") "` to write it anyway");
free(dump);
return PM3_EINVARG;
}
PrintAndLogEx(WARNING, "--force given, writing it anyway");
}
// A public mode is read only, so say so before making it permanent.
if ((new_cfg & 0x06) != 0x06) {
PrintAndLogEx(WARNING, "Configuration " _YELLOW_("0x%02X") " selects a read only public mode", new_cfg);
PrintAndLogEx(WARNING, "The tag will not accept writes afterwards");
}
// Find a credential that opens the tag as it is NOW.
uint8_t cred[HITAG_CRYPTOKEY_SIZE] = {0};
uint8_t credlen = 0;
uint8_t cur_cfg = 0;
uint32_t cur_uid = 0;
static const uint8_t default_pwd[HITAG_PASSWORD_SIZE] = {0x4D, 0x49, 0x4B, 0x52};
static const uint8_t default_key[HITAG_CRYPTOKEY_SIZE] = {0x4F, 0x4E, 0x4D, 0x49, 0x4B, 0x52};
if ((ukeylen == HITAG_PASSWORD_SIZE) && ht2_try_auth_read(HT2F_PASSWORD, userkey, &cur_uid, &cur_cfg)) {
memcpy(cred, userkey, HITAG_PASSWORD_SIZE);
credlen = HITAG_PASSWORD_SIZE;
} else if ((ukeylen == HITAG_CRYPTOKEY_SIZE) && ht2_try_auth_read(HT2F_CRYPTO, userkey, &cur_uid, &cur_cfg)) {
memcpy(cred, userkey, HITAG_CRYPTOKEY_SIZE);
credlen = HITAG_CRYPTOKEY_SIZE;
} else if (ht2_try_auth_read(HT2F_PASSWORD, default_pwd, &cur_uid, &cur_cfg)) {
memcpy(cred, default_pwd, HITAG_PASSWORD_SIZE);
credlen = HITAG_PASSWORD_SIZE;
PrintAndLogEx(INFO, "Opened with default password " _YELLOW_("4D494B52"));
} else if (ht2_try_auth_read(HT2F_CRYPTO, default_key, &cur_uid, &cur_cfg)) {
memcpy(cred, default_key, HITAG_CRYPTOKEY_SIZE);
credlen = HITAG_CRYPTOKEY_SIZE;
PrintAndLogEx(INFO, "Opened with default key " _YELLOW_("4F4E4D494B52"));
} else {
PrintAndLogEx(FAILED, "Could not authenticate to the tag");
PrintAndLogEx(HINT, "Hint: Try `" _YELLOW_("lf hitag chk") "` to search for the key");
free(dump);
return PM3_EFAILED;
}
PrintAndLogEx(INFO, "Tag UID... " _GREEN_("%08X") ", config " _GREEN_("0x%02X"), cur_uid, cur_cfg);
// What pages 1 and 2 will make the credential.
//
// The password is page 1. The crypto key is the last two bytes of page 2
// followed by page 1, which is why writing either page changes both.
uint8_t new_pwd[HITAG_PASSWORD_SIZE] = {0};
uint8_t new_key[HITAG_CRYPTOKEY_SIZE] = {0};
memcpy(new_pwd, dump + HITAG_BLOCK_SIZE, HITAG_PASSWORD_SIZE);
new_key[0] = dump[(HITAG_BLOCK_SIZE * 2) + 2];
new_key[1] = dump[(HITAG_BLOCK_SIZE * 2) + 3];
memcpy(new_key + 2, dump + HITAG_BLOCK_SIZE, HITAG_BLOCK_SIZE);
// Page 3 is written last, so the tag is still in its OLD mode at that point
// and wants the new credential expressed in that mode.
const uint8_t *new_cred = (credlen == HITAG_PASSWORD_SIZE) ? new_pwd : new_key;
// user pages, then key material, then the configuration
const uint8_t order[] = { 4, 5, 6, 7, 2, 1, 3 };
for (uint8_t i = 0; i < ARRAYLEN(order); i++) {
uint8_t page = order[i];
const uint8_t *pd = dump + (page * HITAG_BLOCK_SIZE);
// Pages 1 and 2 replace the credential, so page 3 needs the new one.
const uint8_t *use_cred = (page == 3) ? new_cred : cred;
int res = ht2_restore_write_page(page, pd, use_cred, credlen);
if (res != PM3_SUCCESS) {
PrintAndLogEx(FAILED, "Page %u ( " _RED_("fail") " ) - stopping here", page);
PrintAndLogEx(WARNING, "The tag is part written; pages before this one were changed");
free(dump);
return res;
}
PrintAndLogEx(SUCCESS, "Page %u %s ( " _GREEN_("ok") " )", page, sprint_hex_inrow(pd, HITAG_BLOCK_SIZE));
}
free(dump);
// Report the credential the tag wants NOW, which follows the configuration
// just written - not the one used to get in. Restoring a crypto dump onto a
// password tag changes which of the two applies, and getting this wrong locks
// the operator out of a tag they just wrote.
PrintAndLogEx(NORMAL, "");
if ((new_cfg & 0x08) != 0) {
PrintAndLogEx(SUCCESS, "Tag is in " _GREEN_("Crypto") " mode");
PrintAndLogEx(SUCCESS, "New key... " _GREEN_("%s"), sprint_hex_inrow(new_key, HITAG_CRYPTOKEY_SIZE));
} else {
PrintAndLogEx(SUCCESS, "Tag is in " _GREEN_("Password") " mode");
PrintAndLogEx(SUCCESS, "New password... " _GREEN_("%s"), sprint_hex_inrow(new_pwd, HITAG_PASSWORD_SIZE));
}
PrintAndLogEx(HINT, "Hint: Try `" _YELLOW_("lf hitag info") "` to verify");
PrintAndLogEx(NORMAL, "");
return PM3_SUCCESS;
}
static int CmdLFHitag2Dump(const char *Cmd) {
CLIParserContext *ctx;
@@ -1672,16 +1972,50 @@ static int CmdLFHitagEload(const char *Cmd) {
return PM3_EINVARG;
}
// A Hitag u image is the 6 byte UID followed by its pages, and is the largest
// of the four. Everything still fits one NG frame.
#define HITAGU_EIMAGE_SIZE (HITAGU_UID_SIZE + (HITAGU_MAX_PAGE_ADVANCED_PLUS * HITAGU_BLOCK_SIZE))
// read dump file
uint8_t *dump = NULL;
size_t bytes_read = (4 * 64);
int res = pm3_load_dump(filename, (void **)&dump, &bytes_read, (HITAG_BLOCK_SIZE * 64));
size_t bytes_read = HITAGU_EIMAGE_SIZE;
int res = pm3_load_dump(filename, (void **)&dump, &bytes_read, HITAGU_EIMAGE_SIZE);
if (res != PM3_SUCCESS) {
return res;
}
// check dump len..
if (bytes_read == HITAG2_MAX_BYTE_SIZE || bytes_read == (HITAG_BLOCK_SIZE * 64)) {
// Validate against the card type the user selected rather than a fixed pair of
// sizes. The old check accepted only 32 or 256 bytes, so a Hitag 1 dump (64)
// and every Hitag u dump were rejected even though -1 and -m were accepted.
size_t expected = HITAG2_MAX_BYTE_SIZE;
const char *tname = "Hitag 2";
if (use_ht1) {
expected = HITAG1_MAX_BYTE_SIZE;
tname = "Hitag 1";
} else if (use_hts) {
expected = HITAGS_MAX_BYTE_SIZE;
tname = "Hitag S";
} else if (use_htm) {
// a u dump is sized by the tag's ICR, so accept anything that fits
expected = 0;
tname = "Hitag \xce\xbc";
}
if (use_htm) {
if ((bytes_read < HITAGU_UID_SIZE) || (bytes_read > HITAGU_EIMAGE_SIZE)) {
PrintAndLogEx(ERR, "error, wrong dump file size for %s. got %zu, expected %u..%u",
tname, bytes_read, (unsigned)HITAGU_UID_SIZE, (unsigned)HITAGU_EIMAGE_SIZE);
free(dump);
return PM3_EINVARG;
}
} else if (bytes_read != expected) {
PrintAndLogEx(ERR, "error, wrong dump file size for %s. got %zu, expected %zu",
tname, bytes_read, expected);
free(dump);
return PM3_EINVARG;
}
{
lf_hitag_t *payload = calloc(1, sizeof(lf_hitag_t) + bytes_read);
if (payload == NULL) {
@@ -1708,8 +2042,6 @@ static int CmdLFHitagEload(const char *Cmd) {
clearCommandBuffer();
SendCommandNG(CMD_LF_HITAG_ELOAD, (uint8_t *)payload, 3 + bytes_read);
free(payload);
} else {
PrintAndLogEx(ERR, "error, wrong dump file size. got %zu", bytes_read);
}
free(dump);
@@ -1720,18 +2052,41 @@ static int CmdLFHitagEview(const char *Cmd) {
CLIParserContext *ctx;
CLIParserInit(&ctx, "lf hitag eview",
"It displays emulator memory",
"lf hitag eview\n"
"lf hitag eview -2\n"
"lf hitag eview -s\n"
);
void *argtable[] = {
arg_param_begin,
arg_lit0("v", "verbose", "Verbose output"),
arg_lit0("1", "ht1", "Card type Hitag 1"),
arg_lit0("2", "ht2", "Card type Hitag 2 (default)"),
arg_lit0("s", "hts", "Card type Hitag S"),
arg_lit0("m", "htm", "Card type Hitag \xce\xbc"),
arg_param_end
};
CLIExecWithReturn(ctx, Cmd, argtable, true);
bool verbose = arg_get_lit(ctx, 1);
bool use_ht1 = arg_get_lit(ctx, 2);
bool use_ht2 = arg_get_lit(ctx, 3);
bool use_hts = arg_get_lit(ctx, 4);
bool use_htm = arg_get_lit(ctx, 5);
CLIParserFree(ctx);
if ((use_ht1 + use_ht2 + use_hts + use_htm) > 1) {
PrintAndLogEx(ERR, "error, specify only one Hitag type");
return PM3_EINVARG;
}
// Hitag 2 stays the default so `lf hitag eview` keeps working as before
int bytes = HITAG2_MAX_BYTE_SIZE;
if (use_ht1) {
bytes = HITAG1_MAX_BYTE_SIZE;
} else if (use_hts) {
bytes = HITAGS_MAX_BYTE_SIZE;
} else if (use_htm) {
// a Hitag u image is the 6 byte UID followed by its pages
bytes = HITAGU_UID_SIZE + (HITAGU_MAX_PAGE_ADVANCED_PLUS * HITAGU_BLOCK_SIZE);
}
// reserve memory
uint8_t *dump = calloc(bytes, sizeof(uint8_t));
@@ -1747,14 +2102,15 @@ static int CmdLFHitagEview(const char *Cmd) {
return PM3_ETIMEOUT;
}
if (verbose) {
// the configuration and Paxton decoding are Hitag 2 specific
if (verbose && ((use_ht1 + use_hts + use_htm) == 0)) {
// block3, 1 byte
uint8_t config = dump[HITAG2_CONFIG_OFFSET];
uint32_t uid = bytes_to_num(dump, HITAG_UID_SIZE);
print_hitag2_configuration(uid, config);
print_hitag2_paxton(true, dump);
}
print_hitag2_blocks(dump, HITAG2_MAX_BYTE_SIZE);
print_hitag2_blocks(dump, bytes);
free(dump);
return PM3_SUCCESS;
}
@@ -1765,14 +2121,21 @@ static int CmdLFHitagSim(const char *Cmd) {
"Simulate Hitag transponder\n"
"You need to `lf hitag eload` first",
"lf hitag sim -2\n"
"lf hitag sim -2 -t 30 -> set edge detect threshold to 30"
"lf hitag sim -2 -t 20 -> set edge detect threshold to 20"
);
void *argtable[] = {
arg_param_begin,
arg_lit0("1", "ht1", "simulate Hitag 1"),
arg_lit0("2", "ht2", "simulate Hitag 2"),
arg_int0("t", "threshold", "<dec>", "set edge detect threshold (def: 127)"),
arg_int0("t", "threshold", "<dec>", "edge detect threshold, 0 = auto (def: 0)"),
arg_int0(NULL, "twait", "<dec>", "tag response delay in T0, 0 = default 227"),
arg_lit0(NULL, "invert", "invert load modulation polarity"),
arg_lit0(NULL, "noalt", "answer every START_AUTH, not every second"),
arg_lit0(NULL, "hold", "diagnostic: hold coil load on"),
arg_int0(NULL, "diag", "<dec>", "diagnostic flag bits, ORed into flags (32 = slope, now default)"),
arg_int0(NULL, "sof", "<dec>", "SOF bits in the tag answer (def: 6)"),
arg_int0(NULL, "duty", "<dec>", "carrier periods loaded per half bit, 1..16 (def: 16)"),
arg_param_end
};
CLIExecWithReturn(ctx, Cmd, argtable, true);
@@ -1780,9 +2143,31 @@ static int CmdLFHitagSim(const char *Cmd) {
bool use_ht1 = arg_get_lit(ctx, 1);
bool use_ht2 = arg_get_lit(ctx, 2);
bool use_htm = false; // not implemented yet
int threshold = arg_get_int_def(ctx, 3, 127);
int threshold = arg_get_int_def(ctx, 3, 0);
int twait = arg_get_int_def(ctx, 4, 0);
bool invert = arg_get_lit(ctx, 5);
bool noalt = arg_get_lit(ctx, 6);
bool hold = arg_get_lit(ctx, 7);
int diag = arg_get_int_def(ctx, 8, 0);
int sof = arg_get_int_def(ctx, 9, 0);
int duty = arg_get_int_def(ctx, 10, 0);
CLIParserFree(ctx);
if (duty < 0 || duty > 16) {
PrintAndLogEx(ERR, "error, duty must be 0..16");
return PM3_EINVARG;
}
if (sof < 0 || sof > 200) {
PrintAndLogEx(ERR, "error, sof must be 0..200");
return PM3_EINVARG;
}
if (twait < 0 || twait > 4000) {
PrintAndLogEx(ERR, "error, twait must be 0..4000");
return PM3_EINVARG;
}
if (threshold < 0 || threshold > 255) {
PrintAndLogEx(ERR, "error, threshold must be 0..255");
return PM3_EINVARG;
@@ -1804,8 +2189,16 @@ static int CmdLFHitagSim(const char *Cmd) {
// the tag content comes from emulator memory, this only carries the threshold
hitag_sim_t payload = {
.tag_mem_supplied = false,
.rfu = 0,
// --diag is documented as "ORed into flags", so OR all of it. Masking it
// with 0xF8 silently dropped bits 0..2, which made --diag 1, 2 and 4 do
// nothing at all while still looking like they had been applied - two
// no-alternation measurements were taken that way before it was noticed.
// The named options set the same bits, so they simply agree.
.flags = (uint8_t)((invert ? 1 : 0) | (noalt ? 2 : 0) | (hold ? 4 : 0) | (diag & 0xFF)),
.threshold = (uint16_t)threshold,
.twait = (uint16_t)twait,
.sof = (uint8_t)sof,
.duty = (uint8_t)duty,
};
clearCommandBuffer();
@@ -1818,22 +2211,47 @@ static int CmdLFHitagSniff(const char *Cmd) {
CLIParserInit(&ctx, "lf hitag sniff",
"Sniff the communication between reader and tag\n"
"Use `lf hitag list` to view collected data.",
" lf hitag sniff"
" lf hitag sniff\n"
" lf hitag sniff -t 20 -> lower edge detect threshold"
);
void *argtable[] = {
arg_param_begin,
arg_int0("t", "threshold", "<dec>", "edge detect threshold, 0 = default 32"),
arg_param_end
};
CLIExecWithReturn(ctx, Cmd, argtable, true);
int threshold = arg_get_int_def(ctx, 1, 0);
CLIParserFree(ctx);
PrintAndLogEx(INFO, "Press " _GREEN_("pm3 button") " to abort sniffing");
if (threshold < 0 || threshold > 255) {
PrintAndLogEx(ERR, "error, threshold must be 0..255");
return PM3_EINVARG;
}
PrintAndLogEx(INFO, "Press " _GREEN_("pm3 button") " or " _GREEN_("<Enter>") " to abort sniffing");
PacketResponseNG resp;
clearCommandBuffer();
SendCommandNG(CMD_LF_HITAG_SNIFF, NULL, 0);
WaitForResponse(CMD_LF_HITAG_SNIFF, &resp);
uint8_t payload = (uint8_t)threshold;
SendCommandNG(CMD_LF_HITAG_SNIFF, &payload, sizeof(payload));
// Wait the way the other long running LF commands do, rather than blocking
// in WaitForResponse(). Blocking there means the client never reads another
// command, so the device's own data_available() abort can never be reached -
// `hw break` sits unread in the input and the session looks hung even though
// the firmware is willing to stop.
for (;;) {
if (kbd_enter_pressed()) {
SendCommandNG(CMD_BREAK_LOOP, NULL, 0);
PrintAndLogEx(DEBUG, "\naborted via keyboard!");
break;
}
if (WaitForResponseTimeout(CMD_LF_HITAG_SNIFF, &resp, 1000)) {
break;
}
}
PrintAndLogEx(INFO, "Done!");
PrintAndLogEx(HINT, "Hint: Try `" _YELLOW_("lf hitag list")"` to view captured tracelog");
PrintAndLogEx(HINT, "Hint: Try `" _YELLOW_("trace save -h") "` to save tracelog for later analysing");
@@ -2497,6 +2915,7 @@ static command_t CommandTable[] = {
{"sniff", CmdLFHitagSniff, IfPm3Hitag, "Eavesdrop Hitag communication"},
{"view", CmdLFHitagView, AlwaysAvailable, "Display content from tag dump file"},
{"wrbl", CmdLFHitagWriter, IfPm3Hitag, "Write a block (page) in Hitag memory"},
{"restore", CmdLFHitag2Restore, IfPm3Hitag, "Restore a dump file to a Hitag 2 tag"},
{"-----------", CmdHelp, IfPm3Hitag, "----------------------- " _CYAN_("Simulation") " -----------------------"},
{"eload", CmdLFHitagEload, IfPm3Hitag, "Upload file into emulator memory"},
// {"esave", CmdLFHitagESave, IfPm3Hitag, "Save emulator memory to file"},
+18 -12
View File
@@ -1099,18 +1099,14 @@ static uint16_t printTraceLine(uint16_t tracepos, uint16_t traceLen, uint8_t *tr
if (j == 0) {
// only apply this to lesser than one byte
// Bit count goes in the Bit column, same as the frame above.
// One leading space so the hex lines up under "Data".
if (n == 1) {
snprintf(line[0], 120, "%2u: %02X ", nbits, ht2plain[0] >> (8 - nbits));
snprintf(line[0], 120, " %02X ", ht2plain[0] >> (8 - nbits));
} else {
if (nbits == 0) {
snprintf(line[0], 120, "%2u: %02X ", (uint16_t)(n * 8), ht2plain[0]);
} else {
snprintf(line[0], 120, "%2u: %02X ", (uint16_t)(((n - 1) * 8) + nbits), ht2plain[0]);
}
snprintf(line[0], 120, " %02X ", ht2plain[0]);
}
offset = 4;
offset = 1;
} else {
snprintf(line[j / 18] + ((j % 18) * 4) + offset, 120, "%02X ", ht2plain[j]);
@@ -1119,18 +1115,28 @@ static uint16_t printTraceLine(uint16_t tracepos, uint16_t traceLen, uint8_t *tr
num_lines = MIN((n - 1) / TRACE_MAX_HEX_BYTES + 1, TRACE_MAX_HEX_BYTES);
char plain_nbits[8] = {0};
if (n == 1) {
snprintf(plain_nbits, sizeof(plain_nbits), "%3u ", nbits);
} else if (nbits == 0) {
snprintf(plain_nbits, sizeof(plain_nbits), "%3u ", (uint16_t)(n * 8));
} else {
snprintf(plain_nbits, sizeof(plain_nbits), "%3u ", (uint16_t)(((n - 1) * 8) + nbits));
}
for (int j = 0; j < num_lines ; j++) {
bool last = (j == num_lines - 1);
if (hdr->isResponse) {
PrintAndLogEx(NORMAL, " | | * |%-*s | %-4s| %s",
PrintAndLogEx(NORMAL, " | | * |%-*s | %s| %s",
str_padder,
line[j],
" ",
(last) ? plain_nbits : " ",
explanation);
} else {
PrintAndLogEx(NORMAL, " | | * |" _YELLOW_("%-*s")" | " _YELLOW_("%s") "| " _YELLOW_("%s"),
str_padder,
line[j],
" ",
(last) ? plain_nbits : " ",
explanation);
}
+8 -1
View File
@@ -1657,7 +1657,14 @@ uint16_t manrawdecode(uint8_t *bits, size_t *size, uint8_t invert, uint8_t *alig
*alignPos = bestRun;
// decode
for (i = bestRun; i < *size; i += 2) {
//
// i + 1 < *size, not i < *size: the body reads bits[i + 1], so the old bound
// ran one pair past the end whenever the chosen alignment left a lone sample
// at the tail (bestRun == 1 with an even size, say). That out of range read
// produced a spurious trailing symbol, marked 7 as an impossible Manchester
// pair, and callers that then stop at the first 7 lost the last real bit -
// a Hitag 2 tag answering CE129911 decoded as CE129910, one bit short.
for (i = bestRun; i + 1 < *size; i += 2) {
if (bits[i] == 1 && (bits[i + 1] == 0)) {
bits[bitnum++] = invert;
+16
View File
@@ -107,6 +107,22 @@
#define FPGA_LF_EDGE_DETECT_READER_FIELD ( 1 )
#define FPGA_LF_EDGE_DETECT_TOGGLE_MODE ( 2 )
// Modulate the coil on the 10k leg alone instead of the 33 Ohm one, for a
// shallower tag answer. Understood by LF_EDGE_DETECT and LF_ADC.
#define FPGA_LF_WEAK_LOAD ( 4 )
// Hold the envelope follower while the tag simulator is modulating, so its own
// answer does not drag the slicing levels away from the reader's signal.
#define FPGA_LF_EDGE_DETECT_HOLD_TRACKER ( 8 )
// Let the edge detector keep working on a small tracked span, so a reader frame
// riding on a recovering envelope is still sliced instead of being ignored.
#define FPGA_LF_EDGE_DETECT_SENSITIVE ( 16 )
// Detect edges from the slope of the signal rather than from a level, so a frame
// riding on a recovering envelope is still resolved.
#define FPGA_LF_EDGE_DETECT_SLOPE ( 32 )
// Options for the generic HF reader
#define FPGA_HF_READER_MODE_RECEIVE_IQ ( 0 )
#define FPGA_HF_READER_MODE_RECEIVE_AMPLITUDE ( 1 )
+17 -3
View File
@@ -72,10 +72,24 @@ void WaitMS(uint32_t ms);
// -------------------------------------------------------------------------
// Free-running precision counter @ 1.5 MHz (12 counts = 1 T0 = 8 us).
void StartPrecisionCounter(void); // configure + start + reset
//
// The counter is never stopped or zeroed while a session is running. It is
// 16 bit, so it wraps every 43.7 ms, and every reading here is a difference:
// unsigned 16 bit subtraction gives the right answer straight across a wrap, for
// any interval shorter than a full turn of the counter.
//
// ResetPrecisionCounter() therefore only moves the reference the plain
// GetPrecisionCounter() is measured from; it does not touch the hardware. That
// keeps all the "time since the last mark" call sites reading the same as
// before, without a software trigger whose effect has to be waited for - waiting
// for the counter to read exactly zero could step over the window and then sit
// out a whole 43.7 ms wrap before zero came round again.
void StartPrecisionCounter(void); // configure + start
void StopPrecisionCounter(void);
void ResetPrecisionCounter(void); // software reset to 0
uint16_t RAMFUNC GetPrecisionCounter(void); // current count (16-bit)
void ResetPrecisionCounter(void); // mark a new reference point
uint16_t RAMFUNC GetPrecisionCounter(void); // ticks since that reference
uint16_t RAMFUNC GetPrecisionCounterRaw(void); // the free running counter itself
uint16_t RAMFUNC GetPrecisionCounterDelta(uint16_t start); // ticks since `start`
// Input capture(LF_EDGE_DETECT): rising/falling edges of an external signal.
void StartLoEdgeCapture(void); // configure + start + reset
+10 -3
View File
@@ -332,12 +332,19 @@ void StopTimestamp(void) {
}
uint32_t RAMFUNC GetTimestamp(void) {
if (tmr_flag_get(AT32_TMR_TIMESTAMP, TMR_OVF_FLAG)) {
// Sample the counter on both sides of the overflow check, so a wrap that
// lands between the two cannot make the timestamp go backwards. See the
// AT91 version for why that matters.
uint16_t cv_before = (uint16_t)tmr_counter_value_get(AT32_TMR_TIMESTAMP);
bool overflowed = tmr_flag_get(AT32_TMR_TIMESTAMP, TMR_OVF_FLAG);
uint16_t cv_after = (uint16_t)tmr_counter_value_get(AT32_TMR_TIMESTAMP);
if (overflowed) {
tmr_flag_clear(AT32_TMR_TIMESTAMP, TMR_OVF_FLAG);
timestamp_high++;
cv_before = cv_after;
}
uint16_t cv = (uint16_t)tmr_counter_value_get(AT32_TMR_TIMESTAMP);
return (((uint32_t)timestamp_high << 16) + cv) / TICKS_PER_CARRIER_PERIOD;
return (((uint32_t)timestamp_high << 16) + cv_before) / TICKS_PER_CARRIER_PERIOD;
}
#endif // #ifndef AS_BOOTROM
+69 -9
View File
@@ -220,6 +220,31 @@ uint32_t RAMFUNC GetCountSspClk(void) {
// TC2 overflow count, combined with the TC2 counter for ~47 min timing.
static uint16_t timestamp_high = 0;
// Wait for a software trigger to take effect, without ever spinning on an exact
// value.
//
// TC_CV restarts on the next TIMER_CLOCK3 edge, 32 MCK cycles away, and reading
// a timer register costs a good fraction of that. A loop that waits for exactly
// 0 can therefore step straight over the window, and the counter then has to run
// all the way round its 16 bits - 43.7 ms - before zero comes past again. In
// ResetPrecisionCounter(), which the Hitag reader calls once per transmitted
// bit, that turns into a stall long enough that the Proxmark stops answering USB
// and looks like it has hung. Accept any value that is plainly post-reset, and
// give up rather than wait forever.
#define TC_WAIT_RESTART(tc) \
do { \
for (uint32_t _i = 0; _i < 256; _i++) { \
if ((tc)->TC_CV < 4) { \
break; \
} \
} \
} while (0)
// Reference the plain GetPrecisionCounter() measures from. See ticks_apis.h:
// this moves, the hardware counter does not.
static uint16_t precision_ref = 0;
void StartPrecisionCounter(void) {
// Enable peripheral clock for TC0 (precision counter).
AT91C_BASE_PMC->PMC_PCER |= (1 << AT91C_ID_TC0);
@@ -230,7 +255,8 @@ void StartPrecisionCounter(void) {
// TC0: capture mode, default timer source = MCK/32 (TIMER_CLOCK3), no triggers (free-running).
AT91C_BASE_TC0->TC_CMR = AT91C_TC_CLKS_TIMER_DIV3_CLOCK;
AT91C_BASE_TC0->TC_CCR = AT91C_TC_CLKEN | AT91C_TC_SWTRG;
while (AT91C_BASE_TC0->TC_CV != 0) {}; // wait until the reset takes effect
TC_WAIT_RESTART(AT91C_BASE_TC0); // wait until the reset takes effect
precision_ref = 0;
}
void StopPrecisionCounter(void) {
@@ -238,14 +264,21 @@ void StopPrecisionCounter(void) {
}
void ResetPrecisionCounter(void) {
AT91C_BASE_TC0->TC_CCR = AT91C_TC_SWTRG;
while (AT91C_BASE_TC0->TC_CV != 0) {};
precision_ref = (uint16_t)AT91C_BASE_TC0->TC_CV;
}
uint16_t RAMFUNC GetPrecisionCounter(void) {
return (uint16_t)(AT91C_BASE_TC0->TC_CV - precision_ref);
}
uint16_t RAMFUNC GetPrecisionCounterRaw(void) {
return (uint16_t)AT91C_BASE_TC0->TC_CV;
}
uint16_t RAMFUNC GetPrecisionCounterDelta(uint16_t start) {
return (uint16_t)(AT91C_BASE_TC0->TC_CV - start);
}
void StartLoEdgeCapture(void) {
// Enable peripheral clock for TC1 (input capture).
AT91C_BASE_PMC->PMC_PCER |= (1 << AT91C_ID_TC1);
@@ -264,7 +297,7 @@ void StartLoEdgeCapture(void) {
| AT91C_TC_LDRA_RISING // load RA on rising edge of TIOA
| AT91C_TC_LDRB_FALLING; // load RB on falling edge of TIOA
AT91C_BASE_TC1->TC_CCR = AT91C_TC_CLKEN | AT91C_TC_SWTRG;
while (AT91C_BASE_TC1->TC_CV != 0) {}; // wait until the reset takes effect
TC_WAIT_RESTART(AT91C_BASE_TC1); // wait until the reset takes effect
}
void StopLoEdgeCapture(void) {
@@ -313,9 +346,17 @@ void StartTimestamp(void) {
// TC2: capture mode, default timer source = MCK/32 (TIMER_CLOCK3), no triggers (free-running).
AT91C_BASE_TC2->TC_CMR = AT91C_TC_CLKS_TIMER_DIV3_CLOCK;
AT91C_BASE_TC2->TC_CCR = AT91C_TC_CLKEN | AT91C_TC_SWTRG;
while (AT91C_BASE_TC2->TC_CV != 0) {}; // wait until the reset takes effect
TC_WAIT_RESTART(AT91C_BASE_TC2); // wait until the reset takes effect
// Reset the overflow accumulator.
// Reset the overflow accumulator, and the hardware flag it counts.
//
// Reading TC_SR is what clears COVFS. Without this, an overflow left over
// from a previous run is still pending, so the very first GetTimestamp()
// counts it and every timestamp for the rest of the session is 65536 ticks -
// 5461 T0 - too high. It shows up in a trace as a frame whose end is exactly
// start + 65536, followed by rows with negative looking start times where the
// client subtracts the inflated value.
(void)AT91C_BASE_TC2->TC_SR;
timestamp_high = 0;
}
@@ -324,11 +365,30 @@ void StopTimestamp(void) {
}
uint32_t RAMFUNC GetTimestamp(void) {
// Reading TC_SR clears the COVFS overflow flag.
if (AT91C_BASE_TC2->TC_SR & AT91C_TC_COVFS) {
// Read the counter on both sides of the overflow check.
//
// TC2 is 16 bits at MCK/32, so it wraps every 43.7 ms, and reading TC_SR is
// what latches that wrap into timestamp_high - and clears the flag, so it may
// only be read once. Checking the flag first and reading TC_CV afterwards
// leaves a window: a wrap landing between the two reads is not seen, the low
// half has already restarted near zero, and the result comes back 65536 ticks
// (5461 T0) BELOW the previous one. Timestamps that go backwards break every
// caller that measures with an unsigned difference; the Hitag 2 simulator's
// turnaround, `while ((TIMESTAMP - rx_end) < ...)`, wraps to a huge value and
// stops waiting at once, putting the answer on the air a full frame early.
//
// Sampling either side of the flag read closes it: if the flag is set the
// wrap is at or before the flag read, so the second sample is the one that
// belongs with the incremented high half.
uint16_t cv_before = (uint16_t)AT91C_BASE_TC2->TC_CV;
bool overflowed = (AT91C_BASE_TC2->TC_SR & AT91C_TC_COVFS) != 0;
uint16_t cv_after = (uint16_t)AT91C_BASE_TC2->TC_CV;
if (overflowed) {
timestamp_high++;
cv_before = cv_after;
}
return (((uint32_t)timestamp_high << 16) + AT91C_BASE_TC2->TC_CV) / TICKS_PER_CARRIER_PERIOD;
return (((uint32_t)timestamp_high << 16) + cv_before) / TICKS_PER_CARRIER_PERIOD;
}
#endif // #ifndef AS_BOOTROM
Binary file not shown.
+5
View File
@@ -208,6 +208,10 @@ lo_edge_detect le(
.cross_lo (cross_lo),
.lf_field (conf_word[0]),
.lf_ed_toggle_mode (conf_word[1]),
.lf_weak_load (conf_word[2]),
.lf_ed_hold_tracker (conf_word[3]),
.lf_ed_sensitive (conf_word[4]),
.lf_ed_slope (conf_word[5]),
.lf_ed_threshold (lf_ed_threshold),
.ssp_dout (ssp_dout),
@@ -250,6 +254,7 @@ lo_adc la(
.adc_d (adc_d),
.divisor (divisor),
.lf_field (conf_word[0]),
.lf_weak_load (conf_word[2]),
.ssp_dout (ssp_dout),
.ssp_din (mux3_ssp_din),
+152 -7
View File
@@ -18,6 +18,10 @@ module lf_edge_detect(
input clk,
input [7:0] adc_d,
input [7:0] lf_ed_threshold,
input freeze,
input sensitive,
input slope_mode,
input data_rdy,
output [7:0] max,
output [7:0] min,
@@ -33,6 +37,7 @@ min_max_tracker tracker(
.clk (clk),
.adc_d (adc_d),
.threshold (lf_ed_threshold),
.freeze (freeze),
.min (min),
.max (max)
);
@@ -44,13 +49,80 @@ assign lowz_threshold = (max + min) / 2 - (max - min) / 8;
assign low_threshold = (max + min) / 2 - (max - min) / 4;
// heuristic to see if it makes sense to try to detect an edge
//
// The gaps below are span/8, span/4 and span/8, so the default limits require a
// tracked span of more than 64 counts before any edge is reported. That is fine
// for a settled signal and wrong for a recovering one: right after a tag answers,
// the reader's own frame arrives on an envelope ramping from 8 back up to 135,
// where the per bit dips ride on the ramp and the honest tracked span is small.
// The detector then switches itself off exactly when it is needed, which is what
// leaves a Proxmark simulating Hitag 2 deaf across the reader's password.
//
// `sensitive` relaxes the limits so a tight span still counts as workable. It is
// opt in: on a settled signal the wider limits reject noise, and that is worth
// keeping as the default.
wire [7:0] gap_outer = sensitive ? 8'd2 : 8'd8;
wire [7:0] gap_inner = sensitive ? 8'd4 : 8'd16;
wire enabled =
(high_threshold > highz_threshold)
& (highz_threshold > lowz_threshold)
& (lowz_threshold > low_threshold)
& ((high_threshold - highz_threshold) > 8)
& ((highz_threshold - lowz_threshold) > 16)
& ((lowz_threshold - low_threshold) > 8);
& ((high_threshold - highz_threshold) > gap_outer)
& ((highz_threshold - lowz_threshold) > gap_inner)
& ((lowz_threshold - low_threshold) > gap_outer);
// Slope detection.
//
// Everything above slices against a level derived from the tracked min and max.
// That works on a settled signal and fails on a moving one: right after the tag
// answers, the reader's frame rides on an envelope ramping back up, so a fixed
// level is reached late or not at all. Per the datasheet a '0' is 18..22 T0 and
// a '1' is 26..32, and t_stop is anything over 36 - so two merged '0' bits, at
// 36..44, cannot be told from a stop by duration. The edge has to be produced
// here rather than inferred later.
//
// A gap is a sharp drop whatever the DC level is, so compare each sample against
// one from thirty two 1 MSa/s samples ago - 32 us, two thirds of the 48 us gap -
// and call the edge on the difference. lf_ed_threshold sets how big a step
// counts, so it stays tunable from the ARM.
//
// The baseline has to be a decent fraction of the gap or the step is small and
// the usable threshold band with it: at 16 us, 18 worked and 14 was already
// noise, which is too tight to trust across rigs. Widening it to 64 us, so the
// comparison spans the whole gap, was measured to gain nothing - same 29 bits of
// the reader's 32 - so 32 us stays and the delay line stays half the size.
reg [7:0] hist [0:31];
reg [4:0] hist_ptr = 0;
always @(posedge clk)
if (data_rdy)
begin
hist[hist_ptr] <= adc_d;
hist_ptr <= hist_ptr + 1;
end
wire [7:0] adc_delayed = hist[hist_ptr]; // the oldest entry, 32 samples back
// How big a step counts as an edge, in ADC counts over the 32 us baseline.
//
// Absolute. Scaling it to the tracked span was tried twice and the tracker's
// span turns out not to be a usable reference: (max - min) >> 3 chatters - 1602
// edges, no decodable frame - while span/3 goes dead, no edges at all. Those two
// bracket the working step of ~31, so the span is not stable between them; it is
// driven by our own load modulation as well as the reader's, which is exactly
// what makes it the wrong thing to normalise against. min_max_tracker also
// starts at min=255, max=0, so any such expression needs an underflow guard.
//
// Swept against a Proxmark reader: 28 and below chatters, 29 loses a bit, 30..33
// authenticates, 34 degrades, 36 stops resolving frames. The optimum does drift
// with coupling - 30, then 31..32, then 32..33 across rig changes today - so -t
// is the knob for a different setup.
wire falling_slope = (adc_delayed > adc_d) &&
(({1'b0, adc_delayed} - {1'b0, adc_d}) >= {1'b0, lf_ed_threshold});
wire rising_slope = (adc_d > adc_delayed) &&
(({1'b0, adc_d} - {1'b0, adc_delayed}) >= {1'b0, lf_ed_threshold});
// Toggle the output with hysteresis
// Set to high if the ADC value is above the threshold
@@ -62,10 +134,40 @@ reg trigger_enabled = 1;
reg output_edge = 0;
reg output_state;
// How far the signal has to travel before the state output flips.
//
// The default slices on high/low, which are +/- span/4 either side of the mean,
// so every bit has to traverse half the tracked span. A reader's BPLM '0' bit
// only gives 14 T0 of field between its gaps, and on a run of them the envelope
// never climbs back above high_threshold - so the state never returns high, no
// new falling edge is produced, and two bits merge into one interval. Measured
// against a Proxmark reader: the 32 bit password arrived as 29 bits with a single
// 38 T0 interval where two 20 T0 zeroes should have been.
//
// `sensitive` slices on the inner pair instead, +/- span/8, halving the travel
// needed. Left as an option because the wider hysteresis is what rejects noise
// on a settled signal.
// Measured: an asymmetric pair - trigger on low_threshold, re-arm on lowz - was
// tried here and came out worse, 30 bits of the reader's 32 against 31 for the
// symmetric inner pair. Keeping both slices on the inner thresholds.
// Where to slice, when `sensitive` is on.
//
// The default pair sits +/- span/4 either side of the mean, so every bit has to
// traverse half the tracked span. A reader's BPLM '0' only gives 14 T0 of field
// between its gaps, and on a run of them the envelope never climbs back over
// high_threshold - the state never returns high, no new falling edge is produced
// and two bits merge into one interval.
//
// Measured alternatives, on the reader's 32 bit password: +/- span/8 gives 31
// bits, span/16 the same but less consistently, an asymmetric strict-trigger /
// easy-rearm pair 30, and biasing the band towards the top of the envelope 29.
wire [7:0] hi_slice = sensitive ? highz_threshold : high_threshold;
wire [7:0] lo_slice = sensitive ? lowz_threshold : low_threshold;
always @(posedge clk)
begin
is_high <= (adc_d >= high_threshold);
is_low <= (adc_d <= low_threshold);
is_high <= (adc_d >= hi_slice);
is_low <= (adc_d <= lo_slice);
is_zero <= ((adc_d > lowz_threshold) & (adc_d < highz_threshold));
end
@@ -86,9 +188,52 @@ begin
trigger_enabled <= trigger_enabled | is_zero;
end
// edge states
// Measured: requiring the slope to hold across two consecutive 1 MSa/s samples
// was tried, to throw away the single sample noise that has the reader's 32 bit
// frame arriving as anything from 30 to 33 bits. It does remove the extra edges
// - nothing over 32 after it - but delays the real ones enough to lose more than
// it saves, 1 authentication in 7 against 2 without. Left undebounced.
// Return the slope output to idle high after a long flat stretch.
//
// The two slope rules below only ever move output_state on a slope, so it has no
// idle value: it keeps whatever the last edge left it at. Left low - which is
// where a tag's answer tends to leave it - the next gap drives it low again, so
// there is no transition, no falling edge and no counter reset, and only the
// rising edge at the END of that gap re-arms it. The frame's first bit is then
// never measured: `lf hitag sniff` logged the reader's first gap as a rising edge
// carrying ra=254, meaning the capture counter had not been reset for 254 T0.
//
// A gap is 4..10 T0 and a bit period at most 32, so the longest flat stretch
// inside a frame is about 28 T0; between frames it is at least t_EOF, 80 T0.
// 512 samples at 1 MSa/s is 64 T0, comfortably between the two.
reg [9:0] flat = 0;
always @(posedge clk)
if (enabled)
if (data_rdy)
begin
if (rising_slope | falling_slope)
flat <= 0;
else if (flat != 10'd1023)
flat <= flat + 1;
end
// edge states
//
// Slope mode is not gated on `enabled`: that heuristic asks whether the tracked
// min/max span is wide enough to slice against, which is exactly the judgement
// slope detection does not need.
always @(posedge clk)
if (slope_mode)
begin
if (rising_slope)
output_state <= 1'd1;
else if (falling_slope)
output_state <= 1'd0;
else if (flat == 10'd512)
output_state <= 1'd1;
end
else if (enabled)
begin
if (is_high)
output_state <= 1'd1;
+5 -2
View File
@@ -26,6 +26,7 @@ module lo_adc(
input [7:0] adc_d,
input [7:0] divisor,
input lf_field,
input lf_weak_load,
input ssp_dout,
output ssp_din,
@@ -56,8 +57,10 @@ assign pwr_hi = 1'b0;
// low frequency outputs
assign pwr_lo = reader_modulation;
assign pwr_oe2 = 1'b0; // 33 Ohms
assign pwr_oe3 = tag_modulation; // base antenna load = 33 Ohms
assign pwr_oe4 = 1'b0; // 10k Ohms
// lf_weak_load moves the modulation from the 33 Ohm leg to the 10k one, so the
// depth measured here matches what lo_edge_detect does in the same mode.
assign pwr_oe3 = tag_modulation & ~lf_weak_load; // base antenna load = 33 Ohms
assign pwr_oe4 = tag_modulation & lf_weak_load; // 10k Ohms
// Debug Output ADC clock
assign debug = adc_clk;
+79 -3
View File
@@ -32,6 +32,10 @@ module lo_edge_detect(
input cross_lo,
input lf_field,
input lf_ed_toggle_mode,
input lf_weak_load,
input lf_ed_hold_tracker,
input lf_ed_sensitive,
input lf_ed_slope,
input [7:0] lf_ed_threshold,
input ssp_dout,
@@ -52,15 +56,78 @@ wire reader_modulation = !ssp_dout & lf_field & pck_divclk;
// No logic, straight through.
assign pwr_oe1 = 1'b0; // not used in LF mode
assign pwr_oe3 = 1'b0; // base antenna load = 33 Ohms
// when modulating, add another 33 Ohms and 10k Ohms in parallel:
assign pwr_oe2 = tag_modulation;
// Modulate on the same leg the working LF tag simulator uses.
//
// This used to hold pwr_oe3 off and modulate on pwr_oe2 and pwr_oe4 instead,
// which is not what lo_adc.v does - and lo_adc.v is the path behind the LF tag
// simulation that is known to swing a reader's ADC rail to rail from this coil:
//
// lo_adc.v oe2 = 0, oe3 = mod & ~weak, oe4 = mod & weak
// lo_edge_detect.v oe2 = mod & ~weak, oe3 = 0, oe4 = mod
//
// Measured against a genuine Paxton reader with a Proxmark simulating on top of
// it: the reader beeps at a continuous coil load toggle, so it can see this
// antenna modulating, and our Hitag 2 answer is emitted perfectly - 1184 samples
// a frame, no bails, the turnaround matching a genuine fob's 227 T0 to the
// carrier period - yet the reader never advances past START_AUTH. Detectable
// but not decodable is what too shallow a load looks like on a short burst.
//
// The earlier note here claimed both legs together take our own envelope from
// ~148 counts to 0. That figure was withdrawn as unsound - it assumed a DOUT
// toggle in a mode where it was not reaching the coil - and in any case what
// matters is the perturbation at the READER's antenna, not at ours.
// Modulation depth, measured against a genuine Paxton reader.
//
// Three load configurations were tried, judged both by how many of the reader's
// START_AUTH polls this simulator could still hear after its own answer, and by
// whether the reader ever advanced past START_AUTH:
//
// oe2 + oe4 96 polls seen of 218 reader does not advance
// oe3 alone 218 polls seen of 218 reader does not advance
// oe2 + oe3 + oe4 103 polls seen of 218 reader does not advance
//
// So depth is not what the reader is rejecting - across a 2x range of load it
// behaves identically - but it matters a great deal to our own receive. A
// heavier load pins the min/max tracker this same module feeds and leaves the
// simulator deaf for a while after every answer, which loses half the reader's
// polls and would lose its follow up command too: a real reader's t_WAIT2 can be
// as short as 90 T0, and Paxton's measures about 269.
//
// oe3 alone is the lightest of the three, and the leg lo_adc.v drives - but it is
// too light to be HEARD: with it a Proxmark reader 1 cm away got nothing at all
// from this simulator, RXFAIL no_signal=127 and not one block read, where
// oe2 + oe4 reads and writes reliably. So the pair stays.
//
// That leaves our own post-answer deafness to solve the other way, by freezing
// the min/max tracker across our modulation rather than by modulating less - see
// lf_ed_hold_tracker below, which is what that input is for.
assign pwr_oe2 = tag_modulation & ~lf_weak_load;
assign pwr_oe4 = tag_modulation;
assign pwr_oe3 = 1'b0; // base antenna load = 33 Ohms
assign ssp_clk = cross_lo;
assign pwr_lo = reader_modulation;
assign pwr_hi = 1'b0;
// Hold the envelope follower across our own answer.
//
// Retriggerable: every carrier period in which we are modulating reloads the
// counter, so the follower stays held for the whole frame and is released a
// short time after the last transition. pck0 is 24 MHz, so 8192 counts is
// about 340 us - longer than a 128 us half bit, short enough to be listening
// again well inside the reader's t_WAIT2.
reg [12:0] hold_cnt = 0;
always @(posedge pck0)
begin
if (tag_modulation)
hold_cnt <= 13'h1FFF;
else if (hold_cnt != 0)
hold_cnt <= hold_cnt - 1;
end
wire tracker_freeze = lf_ed_hold_tracker & (hold_cnt != 0);
// filter the ADC values
wire data_rdy;
wire [7:0] adc_filtered;
@@ -78,10 +145,19 @@ wire [7:0] high_threshold, highz_threshold, lowz_threshold, low_threshold;
wire [7:0] max, min;
wire edge_state, edge_toggle;
// Measured: feeding the detector the unfiltered adc_d instead of adc_filtered
// was tried, on the grounds that lp20khz_1MSa_iir_filter cuts right at the 20 kHz
// a 6 T0 reader gap sits on and rounds it. It produced an edge storm - 188043
// edges and not one decoded frame - so the filter stays.
lf_edge_detect lf_ed(
.clk (pck0),
.adc_d (adc_filtered),
.lf_ed_threshold (lf_ed_threshold),
.freeze (tracker_freeze),
.sensitive (lf_ed_sensitive),
.slope_mode (lf_ed_slope),
.data_rdy (data_rdy),
.max (max),
.min (min),
.high_threshold (high_threshold),
+8
View File
@@ -27,6 +27,7 @@ module min_max_tracker(
input clk,
input [7:0] adc_d,
input [7:0] threshold,
input freeze,
output [7:0] min,
output [7:0] max
);
@@ -37,7 +38,14 @@ reg [7:0] cur_min_val = 255;
reg [7:0] cur_max_val = 0;
reg [1:0] state = 0;
// `freeze` holds every register still. A tag simulator's own load modulation
// is far deeper than the reader's, so letting it into this follower drags min and
// max to the extremes and the slicing levels derived from them no longer bracket
// the reader's much shallower signal - measured on a Proxmark simulating Hitag 2,
// that left the receiver deaf for ~1200 T0 after every answer. Holding the
// follower across our own transmission keeps it converged on the reader.
always @(posedge clk)
if (!freeze)
begin
case (state)
0: // initialize
+4 -1
View File
@@ -483,8 +483,11 @@ typedef struct {
// CMD_LF_HITAGS_SIMULATE / CMD_LF_HITAGU_SIMULATE payload
typedef struct {
uint8_t tag_mem_supplied;
uint8_t rfu;
uint8_t flags; // bit0: invert load modulation polarity
uint16_t threshold;
uint16_t twait; // tag response delay in T0, 0 = firmware default
uint8_t sof; // SOF bits in the tag answer, 0 = firmware default (5)
uint8_t duty; // carrier periods loaded per half bit, 0 = firmware default (16)
uint8_t data[];
} PACKED hitag_sim_t;
+73 -5
View File
@@ -14,6 +14,7 @@ TESTMFHIDENCODE=false
TESTICLASSREADER=false
TESTSMARTCARD=false
TESTICLASSEMU=false
TESTHITAG2=false
NEED_MF_HID_ENCODE_WIPE=false
TESTMANUAL=false
@@ -23,7 +24,7 @@ while (( "$#" )); do
case "$1" in
-h|--help)
echo """
Usage: $0 [--pm3bin /path/to/pm3] [--pm3port /dev/tty...] [desfire_value|hid_wiegand|mf_hid_encode|iclass_emu|iclass_reader]
Usage: $0 [--pm3bin /path/to/pm3] [--pm3port /dev/tty...] [desfire_value|hid_wiegand|mf_hid_encode|iclass_emu|iclass_reader|hitag2]
--pm3bin ...: Specify path to pm3 binary to test
--pm3port ...: Specify serial port for client/proxmark3
--manual ...: Pause after successful online LF HID clone/read checks for external reader verification
@@ -33,6 +34,7 @@ Usage: $0 [--pm3bin /path/to/pm3] [--pm3port /dev/tty...] [desfire_value|hid_wie
iclass_emu: Test iCLASS emulator memory load/write/read flows
iclass_reader: Load iCLASS HID credentials into emulator memory for external reader verification
smartcard: Test the RDV4 SIM module and an ISO 7816 contact card
hitag2: Test Hitag 2 against a genuine card and a genuine reader
You must specify a test target - no default 'all' for online tests
"""
exit 0
@@ -89,6 +91,11 @@ Usage: $0 [--pm3bin /path/to/pm3] [--pm3port /dev/tty...] [desfire_value|hid_wie
TESTICLASSEMU=true
shift
;;
hitag2)
TESTALL=false
TESTHITAG2=true
shift
;;
-*|--*=) # unsupported flags
echo "Error: Unsupported flag $1" >&2
exit 1
@@ -102,6 +109,8 @@ done
# set positional arguments in their proper place
eval set -- "$PARAMS"
HITAG2_DUMP="${HITAG2_DUMP:-traces/lf-hitag-CE129911-dump.bin}"
C_RED='\033[0;31m'
C_GREEN='\033[0;32m'
C_YELLOW='\033[0;33m'
@@ -348,12 +357,27 @@ function CheckMfHidEncodeCleanup() {
function WaitForEnter() {
echo ""
echo "$1"
echo "Press Enter when ready, or Ctrl-C to abort."
if [ -r /dev/tty ]; then
# Only prompt when there is really a terminal to prompt on.
#
# `[ -r /dev/tty ]` is not that test: the node can exist and be readable by
# mode bits while the process has no controlling terminal, and the redirect
# then fails with "No such device or address" and the run carries on with the
# rig unconfirmed. Opening it is the only reliable check.
if [ -n "${PM3_ONLINE_NOPROMPT:-}" ]; then
echo "PM3_ONLINE_NOPROMPT set - assuming the rig is ready."
return
fi
if (exec < /dev/tty) 2>/dev/null; then
echo "Press Enter when ready, or Ctrl-C to abort."
stty sane < /dev/tty 2>/dev/null || true
IFS= read -r < /dev/tty
elif [ -t 0 ]; then
echo "Press Enter when ready, or Ctrl-C to abort."
IFS= read -r
else
read -r
echo "No terminal to prompt on - assuming the rig is ready."
fi
}
@@ -372,7 +396,7 @@ if command -v git >/dev/null && git rev-parse --is-inside-work-tree >/dev/null 2
fi
# Check that user specified a test
if [ "$TESTDESFIREVALUE" = false ] && [ "$TESTHIDWIEGAND" = false ] && [ "$TESTMFHIDENCODE" = false ] && [ "$TESTICLASSEMU" = false ] && [ "$TESTICLASSREADER" = false ] && [ "$TESTSMARTCARD" = false ]; then
if [ "$TESTDESFIREVALUE" = false ] && [ "$TESTHIDWIEGAND" = false ] && [ "$TESTMFHIDENCODE" = false ] && [ "$TESTICLASSEMU" = false ] && [ "$TESTICLASSREADER" = false ] && [ "$TESTSMARTCARD" = false ] && [ "$TESTHITAG2" = false ]; then
echo "Error: You must specify a test target. Use -h for help."
exit 1
fi
@@ -447,6 +471,50 @@ while true; do
if ! CheckExecute "hf 15 uid sim resets emu state" "timeout 15 $PM3CMD -c 'hf 15 sim -u E011223344556677 -t 100; hf 15 eview' 2>&1 | LC_ALL=C tr -cd '\11\12\15\40-\176' | tr '\n' ' '" "UID.*E0 11 22 33 44 55 66 77.*4 bytes / blocks x 64 blocks"; then break; fi
fi
# Hitag 2, against a genuine card and a genuine reader.
#
# The card is a password mode Hitag 2 (configuration byte 0x06). Its UID and
# password are read from the card itself rather than hardcoded, so the tests
# work with whatever fob is on the bench; only the ones that need a known
# password use HITAG2_KEY, which defaults to the Paxton fob this was developed
# against and can be overridden from the environment.
if $TESTHITAG2; then
echo -e "\n${C_BLUE}Testing Hitag 2 with a genuine card and reader${C_NC} ${PM3BIN:=./pm3}"
if ! CheckFileExist "pm3 exists" "$PM3BIN"; then break; fi
PM3CMD="$PM3BIN"
if [ -n "${PM3PORT:-}" ]; then
echo "Using PM3 port: $PM3PORT"
PM3CMD="$PM3CMD -p $PM3PORT"
fi
HITAG2_KEY="${HITAG2_KEY:-BDF5E846}"
WaitForEnter "PLACE THE GENUINE HITAG 2 CARD ON THE PM3 LF ANTENNA"
# Reading the card: identification, then a full password mode dump. The
# config byte has to come back as a Hitag 2 in password mode, and every one
# of the eight pages has to be present - a partial dump is the failure this
# is here to catch, since one lost frame used to end the read early.
if ! CheckExecute "lf hitag info genuine card" "$PM3CMD -c 'lf hitag info' 2>&1 | LC_ALL=C tr -cd '\11\12\15\40-\176' | tr '\n' ' '" "UID.*Password mode.*Hitag 2"; then break; fi
if ! CheckExecute "lf hitag read genuine card" "$PM3CMD -c 'lf hitag read -2 --pwd -k $HITAG2_KEY' 2>&1 | LC_ALL=C tr -cd '\11\12\15\40-\176' | tr '\n' ' '" "0/0x00.*1/0x01.*2/0x02.*3/0x03.*4/0x04.*5/0x05.*6/0x06.*7/0x07"; then break; fi
# Writing: put a known value in page 4 and read it back. Page 4 is the
# first user page, so this does not touch the UID, the key or the config.
if ! CheckExecute "lf hitag wrbl genuine card" "$PM3CMD -c 'lf hitag wrbl -2 -k $HITAG2_KEY -p 4 -d 11223344' 2>&1" "Write \( ok \)"; then break; fi
if ! CheckExecute "lf hitag write readback" "$PM3CMD -c 'lf hitag read -2 --pwd -k $HITAG2_KEY' 2>&1 | LC_ALL=C tr -cd '\11\12\15\40-\176' | tr '\n' ' '" "4/0x04 \| 11 22 33 44"; then break; fi
# Sniffing a genuine exchange. Both directions have to appear: the reader's
# START_AUTH and password, and the tag's answers. A capture with reader
# frames but no tag frames is the defect this catches - the tag side decoder
# used to fabricate rows with no card present and miss real ones.
WaitForEnter "PUT THE PM3 BETWEEN THE GENUINE CARD AND THE GENUINE READER, THEN PRESS ENTER. PRESS ENTER AGAIN IN THE CLIENT TO STOP THE SNIFF"
if ! CheckExecute "lf hitag sniff genuine pair" "$PM3CMD -c 'lf hitag sniff; lf hitag list' 2>&1 | LC_ALL=C tr -cd '\11\12\15\40-\176' | tr '\n' ' '" "Rdr.*START AUTH.*Tag.*32:"; then break; fi
# Simulating to a genuine reader. Only the operator can confirm the reader
# actually accepted it, so this asks - the client cannot see the beep.
WaitForEnter "REMOVE THE CARD, PRESENT THE PM3 TO THE GENUINE READER. THE SIM RUNS FOR 20s - LISTEN FOR THE READER TO BEEP, THEN PRESS ENTER"
if ! CheckExecute "lf hitag sim to reader" "timeout 30 $PM3CMD -c 'lf hitag eload -f $HITAG2_DUMP -2; lf hitag sim -2' 2>&1 | LC_ALL=C tr -cd '\11\12\15\40-\176' | tr '\n' ' '" "Starting Hitag 2 simulation"; then break; fi
WaitForEnter "DID THE READER BEEP / ACCEPT THE SIMULATED CARD? PRESS ENTER IF YES, CTRL-C IF NO"
fi
if $TESTICLASSREADER; then
echo -e "\n${C_BLUE}Testing iCLASS reader verification${C_NC} ${PM3BIN:=./pm3}"
if ! CheckFileExist "pm3 exists" "$PM3BIN"; then break; fi