mirror of
https://github.com/RfidResearchGroup/proxmark3.git
synced 2026-10-12 02:09:28 +00:00
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:
@@ -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
@@ -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
File diff suppressed because it is too large
Load Diff
+2
-2
@@ -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,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
@@ -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
@@ -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
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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;
|
||||
|
||||
@@ -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 )
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
@@ -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
@@ -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
@@ -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
@@ -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),
|
||||
|
||||
@@ -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
@@ -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;
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user