Files
proxmark3/client/src/cmdlft55xx.c
T
2026-09-16 19:00:50 +02:00

6131 lines
215 KiB
C

//-----------------------------------------------------------------------------
// Copyright (C) Proxmark3 contributors. See AUTHORS.md for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// See LICENSE.txt for the text of the license.
//-----------------------------------------------------------------------------
// Low frequency T55xx commands
//-----------------------------------------------------------------------------
// ensure localtime_r is available even with -std=c99; must be included before
#if !defined(_WIN32)
#define _POSIX_C_SOURCE 200112L
#endif
#include "cmdlft55xx.h"
#include "pm3_dsp.h" // pm3_extract
#include "pm3_fit.h" // matched filter hypothesis bank
#include <ctype.h>
#include <time.h> // MingW
#include "cmdparser.h" // command_t
#include "comms.h"
#include "commonutil.h"
#include "protocols.h"
#include "proxgui.h"
#include "graph.h"
#include "cmddata.h"
#include "lfdemod.h"
#include "cmdhf14a.h" // for getTagInfo
#include "fileutils.h" // loadDictionary
#include "util_posix.h"
#include "cmdlf.h" // for lf sniff
#include "generator.h"
#include "cliparser.h" // cliparsing
// Some defines for readability
#define T55XX_DLMODE_FIXED 0 // Default Mode
#define T55XX_DLMODE_LLR 1 // Long Leading Reference
#define T55XX_DLMODE_LEADING_ZERO 2 // Leading Zero
#define T55XX_DLMODE_1OF4 3 // 1 of 4
// #define T55XX_LONGLEADINGREFERENCE 4 // Value to tell Write Bit to send long reference
#define T55XX_DLMODE_ALL 4 // Tell help to show 'r 4' for all dl modes
#define T55XX_DLMODE_SINGLE 5 // Tell help file NOT to show 'r 4' (not available)
#define T55XX_PrintConfig true
#define T55XX_DontPrintConfig false
#define T55XX_PSK3_MAX_CAND 32
static size_t t55xx_psk3_block0_candidates(uint32_t observed, uint8_t clk, uint8_t carrier, uint32_t *out, size_t max);
static bool t55xx_config_psk3_ambiguous(void);
static bool t55xx_psk3_probe(bool usepwd, uint32_t password, uint8_t downlink_mode);
static uint8_t t55xx_measure_broadcast_blocks(bool usepwd, uint32_t password, uint8_t downlink_mode);
static uint8_t t55xx_measure_broadcast_blocks_once(bool usepwd, uint32_t password, uint8_t downlink_mode);
static size_t t55xx_psk3_resolve(uint8_t nblk, uint32_t *only);
static bool t55xx_block0_rotation_ambiguous(void);
static bool t55xx_resolve_block0_rotation(uint8_t nblk);
// Default configuration
static t55xx_conf_block_t config = {
.modulation = DEMOD_ASK,
.inverted = false,
.offset = 0x00,
.block0 = 0x00,
.block0Status = NOTSET,
.Q5 = false,
.usepwd = false,
.downlink_mode = refFixedBit
};
static t55xx_memory_item_t cardmem[T55x7_BLOCK_COUNT] = {{0}};
// true when the graph buffer holds an addressed block read, where the tag repeats one word every 32 bits.
// Regular read mode cycles several blocks and a buffer loaded from a file could be either
static bool s_block_read_capture = false;
// samples dropped from the head of a capture before psk demodulation
#define T55XX_PSK_SETTLE_TRIM 160
// non-zero while the capture is trimmed, so an anchor recorded inside the trim
// still names an untrimmed sample
static int32_t s_sample_bias = 0;
// A block read repeats one 32 bit word for as long as the field is on.
// `offset` is a bit index into a demod buffer that no longer exists once the next acquisition lands,
// and the demodulators do not all start on the same bit:
// manchester anchors on the sequence terminator,
// psk starts at whatever phase transition it finds first.
static void t55xx_anchor(t55xx_conf_block_t *c, uint8_t offset) {
c->offset = offset;
c->anchor_valid = (g_DemodClock > 0);
c->anchor_sample = g_DemodStartIdx + ((int32_t)offset * g_DemodClock) + s_sample_bias;
c->anchor_tracelen = (int32_t)g_GraphTraceLen + s_sample_bias;
}
// records a detect candidate against the demodulation that is loaded right now
static void t55xx_record_hit(t55xx_conf_block_t *t) {
t->block0 = PackBits(t->offset, 32, g_DemodBuffer);
t->anchor_valid = (g_DemodClock > 0);
t->anchor_sample = g_DemodStartIdx + ((int32_t)t->offset * g_DemodClock) + s_sample_bias;
t->anchor_tracelen = (int32_t)g_GraphTraceLen + s_sample_bias;
}
// skip first 160 samples to allow antenna to settle in (psk gets inverted occasionally otherwise)
static buffer_savestate_t t55xx_psk_trim_head(void) {
buffer_savestate_t st = save_graphbuffer();
char ltrim[16];
snprintf(ltrim, sizeof(ltrim), "-i %d", T55XX_PSK_SETTLE_TRIM);
CmdLtrim(ltrim);
s_sample_bias = T55XX_PSK_SETTLE_TRIM;
return st;
}
static void t55xx_psk_untrim_head(buffer_savestate_t st) {
s_sample_bias = 0;
restore_graphbuffer(st);
}
// the bit offset to read a block at in the demod buffer loaded right now
static bool t55xx_demod_offset(uint8_t *idx) {
if (g_DemodBufferLen < 32) {
PrintAndLogEx(DEBUG, "DEBUG: (t55xx) demod buffer holds %zu bits, need 32", g_DemodBufferLen);
return false;
}
int32_t bit = config.offset;
// a different graph length is a different signal, not another read of the
// same one - fall back to the plain offset rather than resolve against it
if (config.anchor_valid &&
g_DemodClock > 0 &&
(config.anchor_tracelen == (int32_t)g_GraphTraceLen)) {
// negative when this demodulation started later than the anchored one
const int32_t delta = config.anchor_sample - g_DemodStartIdx;
const int32_t half = g_DemodClock / 2;
bit = (delta >= 0) ? ((delta + half) / g_DemodClock) : -((-delta + half) / g_DemodClock);
// landing outside the buffer means this demodulation started a long way
// from the anchored one. an addressed block read repeats every 32 bits,
// so whole periods can be added or dropped to bring the window back in.
// that is a rescue, not a normalisation - a regular read mode capture
// cycles several blocks and would answer with a different one
while (bit < 0) {
bit += 32;
}
while (bit + 32 > (int32_t)g_DemodBufferLen) {
bit -= 32;
}
}
if (bit < 0 || bit + 32 > (int32_t)g_DemodBufferLen) {
PrintAndLogEx(WARNING, "The configured offset %d is too big. Possible offset: %zu)", bit, g_DemodBufferLen - 32);
return false;
}
*idx = (uint8_t)bit;
return true;
}
// a word that is its own rotation cannot say where the boundary is
static bool t55xx_rotation_unique(uint32_t v) {
for (uint8_t r = 1; r < 32; r++) {
if ((((v << r) | (v >> (32 - r))) & 0xFFFFFFFF) == v) {
return false;
}
}
return true;
}
// true when the demodulated stream carries `data` at some offset. a block read
// repeats the addressed word, so a hit anywhere proves the tag holds it, no
// matter which bit the demodulator started on. a false hit needs a 32 bit
// coincidence in a few hundred positions. re-anchors on the hit, which is the
// one place a data block can say where its own boundary is
static bool t55xx_stream_holds(uint32_t data) {
if (g_DemodBufferLen < 32) {
return false;
}
for (size_t i = 0; i + 32 <= g_DemodBufferLen; i++) {
if (PackBits(0, 32, g_DemodBuffer + i) != data) {
continue;
}
if (i <= 255 && t55xx_rotation_unique(data)) {
const uint8_t before = config.offset;
t55xx_anchor(&config, (uint8_t)i);
if (before != config.offset) {
PrintAndLogEx(DEBUG, "DEBUG: (t55xx) re-anchored offset %u -> %u", before, config.offset);
}
}
return true;
}
return false;
}
t55xx_conf_block_t Get_t55xx_Config(void) {
return config;
}
void Set_t55xx_Config(t55xx_conf_block_t conf) {
config = conf;
}
static int CmdHelp(const char *Cmd);
static void arg_add_t55xx_downloadlink(void *at[], uint8_t *idx, uint8_t show, uint8_t dl_mode_def) {
const size_t r_count = 56;
const size_t r_len = r_count * sizeof(uint8_t);
char *r0 = (char *)calloc(r_count, sizeof(uint8_t));
char *r1 = (char *)calloc(r_count, sizeof(uint8_t));
char *r2 = (char *)calloc(r_count, sizeof(uint8_t));
char *r3 = (char *)calloc(r_count, sizeof(uint8_t));
if (r0 == NULL || r1 == NULL || r2 == NULL || r3 == NULL) {
PrintAndLogEx(WARNING, "Failed to allocate memory");
free(r0);
free(r1);
free(r2);
free(r3);
return;
}
snprintf(r0, r_len, "downlink - fixed bit length %s", (dl_mode_def == 0) ? "(detected def)" : "");
snprintf(r1, r_len, "downlink - long leading reference %s", (dl_mode_def == 1) ? "(detected def)" : "");
snprintf(r2, r_len, "downlink - leading zero %s", (dl_mode_def == 2) ? "(detected def)" : "");
snprintf(r3, r_len, "downlink - 1 of 4 coding reference %s", (dl_mode_def == 3) ? "(detected def)" : "");
uint8_t n = *idx;
at[n++] = arg_lit0(NULL, "r0", r0);
at[n++] = arg_lit0(NULL, "r1", r1);
at[n++] = arg_lit0(NULL, "r2", r2);
at[n++] = arg_lit0(NULL, "r3", r3);
if (show == T55XX_DLMODE_ALL) {
char *r4 = (char *)calloc(r_count, sizeof(uint8_t));
if (r4 == NULL) {
PrintAndLogEx(WARNING, "Failed to allocate memory");
free(r0);
free(r1);
free(r2);
free(r3);
return;
}
snprintf(r4, r_len, "try all downlink modes %s", (dl_mode_def == 4) ? "(def)" : "");
at[n++] = arg_lit0(NULL, "all", r4);
}
at[n++] = arg_param_end;
*idx = n;
}
static int CmdT55xxCloneHelp(const char *Cmd) {
CLIParserContext *ctx;
CLIParserInit(&ctx, "lf t55xx clonehelp",
"Display a list of available commands for cloning specific techs on T5xx tags",
"lf t55xx clonehelp"
);
void *argtable[] = {
arg_param_begin,
arg_param_end
};
CLIExecWithReturn(ctx, Cmd, argtable, true);
CLIParserFree(ctx);
PrintAndLogEx(NORMAL, "For cloning specific techs on T55xx tags, see commands available in corresponding LF sub-menus, e.g.:");
PrintAndLogEx(NORMAL, _GREEN_("lf awid clone"));
PrintAndLogEx(NORMAL, _GREEN_("lf destron clone"));
PrintAndLogEx(NORMAL, _GREEN_("lf em 410x clone"));
// todo: implement restore
// PrintAndLogEx(NORMAL, _GREEN_("lf em 4x05 write"));
// PrintAndLogEx(NORMAL, _GREEN_("lf em 4x50 restore"));
PrintAndLogEx(NORMAL, _GREEN_("lf fdxb clone"));
PrintAndLogEx(NORMAL, _GREEN_("lf gallagher clone"));
PrintAndLogEx(NORMAL, _GREEN_("lf gproxii clone"));
PrintAndLogEx(NORMAL, _GREEN_("lf hid clone"));
// todo: implement restore
// PrintAndLogEx(NORMAL, _GREEN_("lf hitag clone"));
PrintAndLogEx(NORMAL, _GREEN_("lf idteck clone"));
PrintAndLogEx(NORMAL, _GREEN_("lf indala clone"));
PrintAndLogEx(NORMAL, _GREEN_("lf io clone"));
PrintAndLogEx(NORMAL, _GREEN_("lf jablotron clone"));
PrintAndLogEx(NORMAL, _GREEN_("lf keri clone"));
PrintAndLogEx(NORMAL, _GREEN_("lf motorola clone"));
PrintAndLogEx(NORMAL, _GREEN_("lf nedap clone"));
PrintAndLogEx(NORMAL, _GREEN_("lf nexwatch clone"));
PrintAndLogEx(NORMAL, _GREEN_("lf noralsy clone"));
PrintAndLogEx(NORMAL, _GREEN_("lf pac clone"));
PrintAndLogEx(NORMAL, _GREEN_("lf paradox clone"));
PrintAndLogEx(NORMAL, _GREEN_("lf presco clone"));
PrintAndLogEx(NORMAL, _GREEN_("lf pyramid clone"));
PrintAndLogEx(NORMAL, _GREEN_("lf securakey clone"));
PrintAndLogEx(NORMAL, _GREEN_("lf trovan clone"));
PrintAndLogEx(NORMAL, _GREEN_("lf viking clone"));
PrintAndLogEx(NORMAL, _GREEN_("lf visa2000 clone"));
return PM3_SUCCESS;
}
static void T55x7_SaveBlockData(uint8_t idx, uint32_t data) {
if (idx < T55x7_BLOCK_COUNT) {
cardmem[idx].valid = true;
cardmem[idx].blockdata = data;
}
}
static void T55x7_ClearAllBlockData(void) {
for (uint8_t idx = 0; idx < T55x7_BLOCK_COUNT; idx++) {
cardmem[idx].valid = false;
cardmem[idx].blockdata = 0x00;
}
}
int clone_t55xx_tag(uint32_t *blockdata, uint8_t numblocks) {
if (blockdata == NULL)
return PM3_EINVARG;
if (numblocks < 1 || numblocks > 8)
return PM3_EINVARG;
PacketResponseNG resp;
// fast push mode
g_conn.block_after_ACK = true;
for (int8_t i = 0; i < numblocks; i++) {
// Disable fast mode on last packet
if (i == numblocks - 1) {
g_conn.block_after_ACK = false;
}
clearCommandBuffer();
t55xx_write_block_t ng;
ng.data = blockdata[i];
ng.pwd = 0;
ng.blockno = i;
ng.flags = 0;
SendCommandNG(CMD_LF_T55XX_WRITEBL, (uint8_t *)&ng, sizeof(ng));
if (WaitForResponseTimeout(CMD_LF_T55XX_WRITEBL, &resp, T55XX_WRITE_TIMEOUT) == false) {
PrintAndLogEx(ERR, "Error occurred, device did not respond during write operation.");
return PM3_ETIMEOUT;
}
}
uint8_t res = 0;
for (int8_t i = 0; i < numblocks; i++) {
if (i == 0) {
SetConfigWithBlock0(blockdata[0]);
if (t55xxAcquireAndCompareBlock0(false, 0, blockdata[0], false)) {
continue;
}
}
if (t55xxVerifyWrite(i, 0, false, false, 0, 0xFF, blockdata[i]) == false) {
res++;
}
}
if (res == 0)
PrintAndLogEx(SUCCESS, "Data written and verified");
return PM3_SUCCESS;
}
static bool t55xxProtect(bool lock, bool usepwd, uint8_t override, uint32_t password, uint8_t downlink_mode, uint32_t new_password) {
PrintAndLogEx(INFO, "Checking current configuration");
bool testmode = false;
uint32_t block0 = 0;
int res = T55xxReadBlockEx(T55x7_CONFIGURATION_BLOCK, T55x7_PAGE0, usepwd, override, password, downlink_mode, false);
if (res != PM3_SUCCESS) {
PrintAndLogEx(WARNING, "Failed to read block0, use " _YELLOW_("`p`") " password parameter?");
return false;
}
if (GetT55xxBlockData(&block0) == false) {
PrintAndLogEx(DEBUG, "ERROR decoded block0 == %08x", block0);
return false;
}
PrintAndLogEx(DEBUG, "OK read block0 == %08x", block0);
bool isPwdBitAlreadySet = (block0 >> (32 - 28) & 1);
if (isPwdBitAlreadySet) {
PrintAndLogEx(INFO, "PWD bit is already set");
usepwd = true;
}
// set / clear pwd bit
if (lock) {
block0 |= 1 << 4;
} else {
block0 &= ~(1 << 4);
}
// write new password
if (t55xxWrite(T55x7_PWD_BLOCK, T55x7_PAGE0, usepwd, testmode, password, downlink_mode, new_password) != PM3_SUCCESS) {
PrintAndLogEx(ERR, "Failed to write new password");
return false;
} else {
PrintAndLogEx(SUCCESS, "Wrote new password");
}
// validate new password
uint32_t curr_password = (isPwdBitAlreadySet) ? new_password : password;
if (t55xxVerifyWrite(T55x7_PWD_BLOCK, T55x7_PAGE0, usepwd, override, curr_password, downlink_mode, new_password) == false) {
PrintAndLogEx(WARNING, "Failed to validate the password write. aborting.");
return false;
} else {
PrintAndLogEx(SUCCESS, "Validated new password");
}
// write config
if (t55xxWrite(T55x7_CONFIGURATION_BLOCK, T55x7_PAGE0, usepwd, testmode, curr_password, downlink_mode, block0) != PM3_SUCCESS) {
PrintAndLogEx(ERR, "Failed to write modified configuration block %08X", block0);
return false;
} else {
PrintAndLogEx(SUCCESS, "Wrote modified configuration block");
}
// validate new config. If all went well, card should now demand pwd, hence override = 0.
override = 0;
if (t55xxVerifyWrite(T55x7_CONFIGURATION_BLOCK, T55x7_PAGE0, true, override, new_password, downlink_mode, block0) == false) {
PrintAndLogEx(WARNING, "Failed to validate pwd bit set on configuration block. aborting.");
return false;
} else {
PrintAndLogEx(SUCCESS, "New configuration block " _YELLOW_("%08X")" password " _YELLOW_("%08X"), block0, new_password);
PrintAndLogEx(SUCCESS, "Success, tag is locked");
return true;
}
}
bool t55xxAcquireAndCompareBlock0(bool usepwd, uint32_t password, uint32_t known_block0, bool verbose) {
if (verbose)
PrintAndLogEx(INFO, "Block0 write detected, running `detect` to see if validation is possible");
for (uint8_t m = 0; m < 4; m++) {
if (AcquireData(T55x7_PAGE0, T55x7_CONFIGURATION_BLOCK, usepwd, password, m) == false) {
continue;
}
if (DecodeT55xxBlock() == false) {
continue;
}
for (size_t i = 0; i < g_DemodBufferLen - 32; i++) {
uint32_t tmp = PackBits(i, 32, g_DemodBuffer);
if (tmp == known_block0) {
t55xx_anchor(&config, (uint8_t)i);
config.downlink_mode = m;
return true;
}
}
}
return false;
}
bool t55xxAcquireAndDetect(bool usepwd, uint32_t password, uint32_t known_block0, bool verbose) {
if (verbose)
PrintAndLogEx(INFO, "Block0 write detected, running `detect` to see if validation is possible");
for (uint8_t m = 0; m < 4; m++) {
if (AcquireData(T55x7_PAGE0, T55x7_CONFIGURATION_BLOCK, usepwd, password, m) == false)
continue;
// password is uint32_t here, so an untyped -1 would be truncated to
// 0xFFFFFFFF by the ternary before it widens, and the `pwd != -1`
// sentinel check inside would read it as a known password
if (t55xxTryDetectModulationEx(m, verbose, known_block0, (usepwd) ? (uint64_t)password : (uint64_t) - 1) == false)
continue;
config.downlink_mode = m;
return true;
}
config.usepwd = false; // unknown so assume no password
config.pwd = 0x00;
return false;
}
static bool t55xx_verify_write_ex(uint8_t block, bool page1, bool usepwd, uint8_t override, uint32_t password, uint8_t downlink_mode, uint32_t data, bool redetect) {
uint32_t read_data = 0;
bool ok = false;
if (downlink_mode == 0xFF)
downlink_mode = config.downlink_mode;
int res = T55xxReadBlockEx(block, page1, usepwd, override, password, downlink_mode, false);
if (res == PM3_SUCCESS) {
// the value being anywhere in the stream is the proof, not the value at
// one cached offset. a correct write read back on a different bit
// boundary used to report as a validation failure
ok = t55xx_stream_holds(data);
if (ok == false && GetT55xxBlockData(&read_data)) {
ok = (read_data == data);
}
}
if (ok) {
return true;
}
// A block 0 write changes how the tag talks, so the configuration held from before the write is stale.
// A: he demodulator fails outright, or
// B:_it succeeds on the wrong modulation and answers with garbage
if (block == 0 && page1 == false && redetect) {
if (t55xxAcquireAndDetect(usepwd, password, data, true) == false) {
return false;
}
return t55xx_verify_write_ex(block, page1, usepwd, 2, password, config.downlink_mode, data, false);
}
return false;
}
bool t55xxVerifyWrite(uint8_t block, bool page1, bool usepwd, uint8_t override, uint32_t password, uint8_t downlink_mode, uint32_t data) {
return t55xx_verify_write_ex(block, page1, usepwd, override, password, downlink_mode, data, true);
}
int t55xxWrite(uint8_t block, bool page1, bool usepwd, bool testMode, uint32_t password, uint8_t downlink_mode, uint32_t data) {
uint8_t flags;
flags = (usepwd) ? 0x1 : 0;
flags |= (page1) ? 0x2 : 0;
flags |= (testMode) ? 0x4 : 0;
flags |= (downlink_mode << 3);
/*
OLD style
arg0 = data, (4 bytes)
arg1 = block (1 byte)
arg2 = password (4 bytes)
flags = data[0] (1 byte)
new style
uses struct in pm3_cmd.h
*/
t55xx_write_block_t ng;
ng.data = data;
ng.pwd = password;
ng.blockno = block;
ng.flags = flags;
PacketResponseNG resp;
clearCommandBuffer();
SendCommandNG(CMD_LF_T55XX_WRITEBL, (uint8_t *)&ng, sizeof(ng));
if (WaitForResponseTimeout(CMD_LF_T55XX_WRITEBL, &resp, 2000) == false) {
PrintAndLogEx(ERR, "Error occurred, device did not ACK write operation.");
return PM3_ETIMEOUT;
}
return resp.status;
}
void printT5xxHeader(uint8_t page) {
PrintAndLogEx(NORMAL, "");
PrintAndLogEx(SUCCESS, _YELLOW_("Page %d"), page);
PrintAndLogEx(SUCCESS, "blk | hex data | binary | ascii");
PrintAndLogEx(SUCCESS, "----+----------+----------------------------------+-------");
}
void SetConfigWithBlock0(uint32_t block0) {
SetConfigWithBlock0Ex(block0, 0, false);
}
void SetConfigWithBlock0Ex(uint32_t block0, uint8_t offset, bool Q5) {
// T55x7
uint32_t extend = (block0 >> (32 - 15)) & 0x01;
uint32_t dbr;
if (extend)
dbr = (block0 >> (32 - 14)) & 0x3F;
else
dbr = (block0 >> (32 - 14)) & 0x07;
uint32_t datamod = (block0 >> (32 - 20)) & 0x1F;
bool pwd = (bool)((block0 >> (32 - 28)) & 0x01);
bool sst = (bool)((block0 >> (32 - 29)) & 0x01);
bool inv = (bool)((block0 >> (32 - 31)) & 0x01);
config.modulation = datamod;
config.bitrate = dbr;
// FSK1a, FSK2a
if (datamod == DEMOD_FSK1a || datamod == DEMOD_FSK2a || datamod == DEMOD_BIa)
config.inverted = 1;
else
config.inverted = inv;
config.Q5 = Q5;
config.ST = sst;
config.usepwd = pwd;
if (config.offset != offset) {
config.anchor_valid = false;
}
config.offset = offset;
config.block0 = block0;
}
static int CmdT55xxSetConfig(const char *Cmd) {
// No args
if (strlen(Cmd) == 0) {
PrintAndLogEx(INFO, "--- " _CYAN_("current t55xx config") " --------------------------");
return printConfiguration(config);
}
CLIParserContext *ctx;
CLIParserInit(&ctx, "lf t55xx config",
"Set/Get T55XX configuration of the pm3 client. Like modulation, inverted, offset, rate etc.\n"
"Offset is start position to decode data.",
"lf t55xx config --FSK --> FSK demodulation\n"
"lf t55xx config --FSK -i --> FSK demodulation, inverse data\n"
"lf t55xx config --FSK -i -o 3 --> FSK demodulation, inverse data, offset 3\n"
);
// 1 (help) + 19 (user specified params) + (5 T55XX_DLMODE_SINGLE)
void *argtable[1 + 12 + 6 + 5] = {
arg_param_begin,
arg_lit0(NULL, "FSK", "set demodulation FSK"),
arg_lit0(NULL, "FSK1", "set demodulation FSK 1"),
arg_lit0(NULL, "FSK1A", "set demodulation FSK 1a (inv)"),
arg_lit0(NULL, "FSK2", "set demodulation FSK 2"),
arg_lit0(NULL, "FSK2A", "set demodulation FSK 2a (inv)"),
arg_lit0(NULL, "ASK", "set demodulation ASK"),
arg_lit0(NULL, "PSK1", "set demodulation PSK 1"),
arg_lit0(NULL, "PSK2", "set demodulation PSK 2"),
arg_lit0(NULL, "PSK3", "set demodulation PSK 3"),
arg_lit0(NULL, "NRZ", "set demodulation NRZ"),
arg_lit0(NULL, "BI", "set demodulation Biphase"),
arg_lit0(NULL, "BIA", "set demodulation Diphase (inverted biphase)"),
arg_lit0("i", "inv", "set/reset data signal inversion"),
arg_lit0(NULL, "q5", "set/reset as Q5/T5555 chip instead of T55x7"),
arg_lit0(NULL, "st", "set/reset Sequence Terminator on"),
arg_int0(NULL, "rate", "<dec>", "set bitrate <8|16|32|40|50|64|100|128>"),
arg_str0("c", "blk0", "<hex>", "set configuration from a block0 (4 hex bytes)"),
arg_int0("o", "offset", "<0-255>", "set offset, where data should start decode in bitstream "),
};
uint8_t idx = 19;
arg_add_t55xx_downloadlink(argtable, &idx, T55XX_DLMODE_SINGLE, config.downlink_mode);
CLIExecWithReturn(ctx, Cmd, argtable, true);
idx = 1;
bool mods[12];
int verify_mods = 0;
while (idx - 1 < sizeof(mods)) {
mods[idx - 1] = arg_get_lit(ctx, idx);
verify_mods += mods[idx - 1];
idx++;
}
// Not these flags are used to Toggle the values.
// If not flag then don't set or reset, leave as is since the call may just be be setting a different value.
bool invert = arg_get_lit(ctx, idx++);
bool use_q5 = arg_get_lit(ctx, idx++);
bool use_st = arg_get_lit(ctx, idx++);
int bitrate = arg_get_int_def(ctx, idx, -1);
idx++;
bool gotconf = false;
uint32_t block0 = 0;
int res = arg_get_u32_hexstr_def_nlen(ctx, idx++, 0, &block0, 4, true);
if (res == 0 || res == 2) {
PrintAndLogEx(ERR, "block0 data must be 4 hex bytes");
CLIParserFree(ctx);
return PM3_EINVARG;
}
if (res == 1) {
gotconf = true;
}
int offset = arg_get_int_def(ctx, idx, -1);
idx++;
bool r0 = arg_get_lit(ctx, idx++);
bool r1 = arg_get_lit(ctx, idx++);
bool r2 = arg_get_lit(ctx, idx++);
bool r3 = arg_get_lit(ctx, idx++);
CLIParserFree(ctx);
// validate user specified downlink mode
if ((r0 + r1 + r2 + r3) > 1) {
PrintAndLogEx(FAILED, "Error multiple downlink encoding");
return PM3_EINVARG;
}
// validate user specified modulation FSK,FSK1,...BIA
if (verify_mods > 1) {
PrintAndLogEx(FAILED, "Error multiple demodulations, select one");
return PM3_EINVARG;
}
// validate user specified bitrate
if (bitrate != -1) {
uint8_t rates[9] = {8, 16, 32, 40, 50, 64, 100, 128, 0};
uint8_t i = 0;
for (; i < ARRAYLEN(rates); i++) {
if (rates[i] == bitrate) {
config.bitrate = i;
config.block0 = ((config.block0 & ~(0x1c0000)) | (i << 18));
break;
}
}
if (i == 9) {
PrintAndLogEx(FAILED, "Error select a valid bitrate");
return PM3_EINVARG;
}
}
// validate user specified offset
if (offset > -1 && offset < 0x100) {
config.offset = offset;
config.anchor_valid = false;
}
// validate user specific T5555 / Q5 - use the flag to toggle between T5577 and Q5
config.Q5 ^= use_q5;
// validate user specific sequence terminator
// if use_st flag was supplied, then toggle and update the config block0; if not supplied skip the config block0 update.
if (use_st) {
config.ST ^= use_st;
config.block0 = ((config.block0 & ~(0x8)) | (config.ST << 3));
}
// validate user specific invert
// In theory this should also be set in the config block 0; butit requries the extend mode config, which will change other things.
// as such, leave in user config for decoding the data until a full fix can be added.
// use the flag to toggle if invert is on or off.
config.inverted ^= invert;
// validate user specific downlink mode
uint8_t downlink_mode = config.downlink_mode;
if (r0)
downlink_mode = refFixedBit;
else if (r1)
downlink_mode = refLongLeading;
else if (r2)
downlink_mode = refLeading0;
else if (r3)
downlink_mode = ref1of4;
config.downlink_mode = downlink_mode;
// validate user specific modulation
if (mods[0]) {
config.modulation = DEMOD_FSK;
} else if (mods[1]) {
config.modulation = DEMOD_FSK1;
config.inverted = 0;
} else if (mods[2]) {
config.modulation = DEMOD_FSK1a;
config.inverted = 1;
} else if (mods[3]) {
config.modulation = DEMOD_FSK2;
config.inverted = 0;
} else if (mods[4]) {
config.modulation = DEMOD_FSK2a;
config.inverted = 1;
} else if (mods[5]) {
config.modulation = DEMOD_ASK;
} else if (mods[6]) {
config.modulation = DEMOD_PSK1;
} else if (mods[7]) {
config.modulation = DEMOD_PSK2;
} else if (mods[8]) {
config.modulation = DEMOD_PSK3;
} else if (mods[9]) {
config.modulation = DEMOD_NRZ;
} else if (mods[10]) {
config.modulation = DEMOD_BI;
config.inverted = 0;
} else if (mods[11]) {
config.modulation = DEMOD_BIa;
config.inverted = 1;
}
config.block0 = ((config.block0 & ~(0x1f000)) | (config.modulation << 12));
config.block0Status = USERSET;
if (gotconf) {
SetConfigWithBlock0Ex(block0, config.offset, config.Q5);
}
PrintAndLogEx(INFO, "--- " _CYAN_("current t55xx config") " --------------------------");
return printConfiguration(config);
}
int T55xxReadBlock(uint8_t block, bool page1, bool usepwd, uint8_t override, uint32_t password, uint8_t downlink_mode) {
return T55xxReadBlockEx(block, page1, usepwd, override, password, downlink_mode, true);
}
int T55xxReadBlockEx(uint8_t block, bool page1, bool usepwd, uint8_t override, uint32_t password, uint8_t downlink_mode, bool verbose) {
//Password mode
if (usepwd) {
// try reading the config block and verify that PWD bit is set before doing this!
// override = 1 (override and display)
// override = 2 (override and no display)
if (override == 0) {
if (AcquireData(T55x7_PAGE0, T55x7_CONFIGURATION_BLOCK, false, 0, downlink_mode) == false)
return PM3_ERFTRANS;
if (t55xxTryDetectModulationEx(downlink_mode, false, 0, password) == false) {
PrintAndLogEx(WARNING, "Safety check: Could not detect if PWD bit is set in config block. Exits.");
PrintAndLogEx(HINT, "Hint: Consider using the override parameter to force read.");
return PM3_EWRONGANSWER;
} else {
PrintAndLogEx(WARNING, "Safety check: PWD bit is NOT set in config block. Reading without password...");
usepwd = false;
page1 = false; // ??
}
} else if (override == 1) {
PrintAndLogEx(INFO, "Safety check overridden - proceeding despite risk");
}
}
if (AcquireData(page1, block, usepwd, password, downlink_mode) == false)
return PM3_ERFTRANS;
if (DecodeT55xxBlock() == false)
return PM3_EWRONGANSWER;
// block 0 is the one block whose content is known before it is read, so use
// it to re-anchor this capture rather than trusting the one detect left
if (block == T55x7_CONFIGURATION_BLOCK &&
page1 == false &&
config.block0Status == AUTODETECT &&
config.block0 != 0) {
t55xx_stream_holds(config.block0);
}
if (verbose) {
printT55xxBlock(block, page1);
}
return PM3_SUCCESS;
}
static int CmdT55xxReadBlock(const char *Cmd) {
CLIParserContext *ctx;
CLIParserInit(&ctx, "lf t55xx read",
"Read T55xx block data. This commands defaults to page 0.\n\n"
_RED_(" * * * WARNING * * *") "\n"
_CYAN_("Use of read with password on a tag not configured") "\n"
_CYAN_("for a password can damage the tag") "\n"
_RED_(" * * * * * * * * * *"),
"lf t55xx read -b 0 --> read data from block 0\n"
"lf t55xx read -b 0 --pwd 01020304 --> read data from block 0, pwd 01020304\n"
"lf t55xx read -b 0 --pwd 01020304 -o --> read data from block 0, pwd 01020304, override\n"
);
// 1 (help) + 4(four user specified params) + (5 T55XX_DLMODE_SINGLE)
void *argtable[5 + 5] = {
arg_param_begin,
arg_int1("b", "blk", "<0-7>", "block number to read"),
arg_str0("p", "pwd", "<hex>", "password (4 hex bytes)"),
arg_lit0("o", "override", "override safety check"),
arg_lit0(NULL, "pg1", "read page 1"),
};
uint8_t idx = 5;
arg_add_t55xx_downloadlink(argtable, &idx, T55XX_DLMODE_SINGLE, config.downlink_mode);
CLIExecWithReturn(ctx, Cmd, argtable, true);
int block = arg_get_int_def(ctx, 1, REGULAR_READ_MODE_BLOCK);
bool usepwd = false;
uint32_t password = 0;
int res = arg_get_u32_hexstr_def_nlen(ctx, 2, 0, &password, 4, true);
if (res == 0 || res == 2) {
PrintAndLogEx(ERR, "Password should be 4 hex bytes");
CLIParserFree(ctx);
return PM3_EINVARG;
}
if (res == 1) {
usepwd = true;
}
uint8_t override = arg_get_lit(ctx, 3);
bool page1 = arg_get_lit(ctx, 4);
bool r0 = arg_get_lit(ctx, 5);
bool r1 = arg_get_lit(ctx, 6);
bool r2 = arg_get_lit(ctx, 7);
bool r3 = arg_get_lit(ctx, 8);
CLIParserFree(ctx);
if ((r0 + r1 + r2 + r3) > 1) {
PrintAndLogEx(FAILED, "Error multiple downlink encoding");
return PM3_EINVARG;
}
uint8_t downlink_mode = config.downlink_mode;
if (r0)
downlink_mode = refFixedBit;
else if (r1)
downlink_mode = refLongLeading;
else if (r2)
downlink_mode = refLeading0;
else if (r3)
downlink_mode = ref1of4;
if (block > 7 && block != REGULAR_READ_MODE_BLOCK) {
PrintAndLogEx(NORMAL, "Block must be between 0 and 7");
return PM3_ESOFT;
}
printT5xxHeader(page1);
return T55xxReadBlock(block, page1, usepwd, override, password, downlink_mode);
}
bool DecodeT55xxBlock(void) {
int ans = 0;
bool ST = config.ST;
uint8_t bitRate[8] = {8, 16, 32, 40, 50, 64, 100, 128};
g_DemodBufferLen = 0x00;
switch (config.modulation) {
case DEMOD_FSK:
ans = FSKrawDemod(bitRate[config.bitrate], config.inverted, 0, 0, false);
break;
case DEMOD_FSK1:
case DEMOD_FSK1a:
ans = FSKrawDemod(bitRate[config.bitrate], config.inverted, 8, 5, false);
break;
case DEMOD_FSK2:
case DEMOD_FSK2a:
ans = FSKrawDemod(bitRate[config.bitrate], config.inverted, 10, 8, false);
break;
case DEMOD_ASK:
ans = ASKDemod_ext(bitRate[config.bitrate], config.inverted, 1, 0, false, false, false, 1, &ST);
break;
case DEMOD_PSK1:
ans = PSKDemod(bitRate[config.bitrate], config.inverted, 6, false);
break;
case DEMOD_PSK2: //inverted won't affect this
case DEMOD_PSK3: //not fully implemented
ans = PSKDemod(bitRate[config.bitrate], 0, 6, false);
psk1TOpsk2(g_DemodBuffer, g_DemodBufferLen);
break;
case DEMOD_NRZ:
ans = NRZrawDemod(bitRate[config.bitrate], config.inverted, 1, false);
break;
case DEMOD_BI:
case DEMOD_BIa:
ans = ASKbiphaseDemod(0, bitRate[config.bitrate], config.inverted, 1, false);
break;
default:
return false;
}
return (ans == PM3_SUCCESS);
}
static bool DecodeT5555TraceBlock(void) {
g_DemodBufferLen = 0x00;
// According to datasheet. Always: RF/64, not inverted, Manchester
bool st = false;
return (ASKDemod_ext(64, 0, 1, 0, false, false, false, 1, &st) == PM3_SUCCESS);
}
// sanity check. Don't use proxmark if it is offline and you didn't specify useGraphbuf
static int SanityOfflineCheck(bool useGraphBuffer) {
if (!useGraphBuffer && !g_session.pm3_present) {
PrintAndLogEx(WARNING, "Your proxmark3 device is offline. Specify [1] to use graphbuffer data instead");
return PM3_ENODATA;
}
return PM3_SUCCESS;
}
static void T55xx_Print_DownlinkMode(uint8_t downlink_mode) {
char msg[80];
snprintf(msg, sizeof(msg), "Downlink Mode used : ");
switch (downlink_mode) {
case 1 :
strcat(msg, _YELLOW_("long leading reference"));
break;
case 2 :
strcat(msg, _YELLOW_("leading zero reference"));
break;
case 3 :
strcat(msg, _YELLOW_("1 of 4 coding reference"));
break;
default :
strcat(msg, _YELLOW_("default/fixed bit length"));
break;
}
PrintAndLogEx(SUCCESS, msg);
}
static int CmdT55xxWakeUp(const char *Cmd) {
CLIParserContext *ctx;
CLIParserInit(&ctx, "lf t55xx wakeup",
"This commands sends the Answer-On-Request command and leaves the readerfield ON afterwards",
"lf t55xx wakeup -p 11223344 --> send wakeup with password\n"
);
// 1 (help) + 2 (two user specified params) + (5 T55XX_DLMODE_SINGLE)
void *argtable[3 + 5] = {
arg_param_begin,
arg_str0("p", "pwd", "<hex>", "password (4 hex bytes)"),
arg_lit0("v", "verbose", "verbose output"),
};
uint8_t idx = 3;
arg_add_t55xx_downloadlink(argtable, &idx, T55XX_DLMODE_SINGLE, config.downlink_mode);
CLIExecWithReturn(ctx, Cmd, argtable, true);
uint32_t password = 0;
int res = arg_get_u32_hexstr_def_nlen(ctx, 1, 0, &password, 4, true);
if (res == 0 || res == 2) {
PrintAndLogEx(ERR, "Password should be 4 hex bytes");
CLIParserFree(ctx);
return PM3_EINVARG;
}
bool verbose = arg_get_lit(ctx, 2);
bool r0 = arg_get_lit(ctx, 3);
bool r1 = arg_get_lit(ctx, 4);
bool r2 = arg_get_lit(ctx, 5);
bool r3 = arg_get_lit(ctx, 6);
CLIParserFree(ctx);
if ((r0 + r1 + r2 + r3) > 1) {
PrintAndLogEx(FAILED, "Error multiple downlink encoding");
return PM3_EINVARG;
}
uint8_t downlink_mode = config.downlink_mode;
if (r0)
downlink_mode = refFixedBit;
else if (r1)
downlink_mode = refLongLeading;
else if (r2)
downlink_mode = refLeading0;
else if (r3)
downlink_mode = ref1of4;
struct p {
uint32_t password;
uint8_t flags;
} PACKED payload;
payload.password = password;
payload.flags = (downlink_mode << 3);
clearCommandBuffer();
SendCommandNG(CMD_LF_T55XX_WAKEUP, (uint8_t *)&payload, sizeof(payload));
if (WaitForResponseTimeout(CMD_LF_T55XX_WAKEUP, NULL, 1000) == false) {
PrintAndLogEx(WARNING, "command execution time out");
return PM3_ETIMEOUT;
}
if (verbose)
PrintAndLogEx(SUCCESS, "Wake up command sent. Try read now");
return PM3_SUCCESS;
}
static int CmdT55xxDetect(const char *Cmd) {
CLIParserContext *ctx;
CLIParserInit(&ctx, "lf t55xx detect",
"Try detecting the tag modulation from reading the configuration block",
"lf t55xx detect\n"
"lf t55xx detect -1\n"
"lf t55xx detect -p 11223344\n"
);
// 1 (help) + 2 (two user specified params) + (6 T55XX_DLMODE_ALL)
void *argtable[3 + 6] = {
arg_param_begin,
arg_lit0("1", NULL, "extract using data from graphbuffer"),
arg_str0("p", "pwd", "<hex>", "password (4 hex bytes)"),
};
uint8_t idx = 3;
arg_add_t55xx_downloadlink(argtable, &idx, T55XX_DLMODE_ALL, config.downlink_mode);
CLIExecWithReturn(ctx, Cmd, argtable, true);
bool use_gb = arg_get_lit(ctx, 1);
bool usepwd = false;
uint64_t password = -1;
uint32_t tmp_pwd = 0;
int res = arg_get_u32_hexstr_def_nlen(ctx, 2, 0, &tmp_pwd, 4, true);
if (res == 0 || res == 2) {
PrintAndLogEx(ERR, "Password should be 4 hex bytes");
CLIParserFree(ctx);
return PM3_EINVARG;
}
if (res == 1) {
usepwd = true;
password = tmp_pwd;
}
bool r0 = arg_get_lit(ctx, 3);
bool r1 = arg_get_lit(ctx, 4);
bool r2 = arg_get_lit(ctx, 5);
bool r3 = arg_get_lit(ctx, 6);
bool ra = arg_get_lit(ctx, 7);
CLIParserFree(ctx);
if ((r0 + r1 + r2 + r3 + ra) > 1) {
PrintAndLogEx(FAILED, "Error multiple downlink encoding");
return PM3_EINVARG;
}
bool try_all_dl_modes = false;
uint8_t downlink_mode = config.downlink_mode;
if (r0)
downlink_mode = refFixedBit;
else if (r1)
downlink_mode = refLongLeading;
else if (r2)
downlink_mode = refLeading0;
else if (r3)
downlink_mode = ref1of4;
else // This will set the default to user all d/l modes which will cover the ra flag as well.
try_all_dl_modes = true;
bool found = false;
// Setup the 90ms time value to sleep for after the wake, to allow delay init to complete (~70ms)
struct timespec sleepperiod;
sleepperiod.tv_sec = 0;
sleepperiod.tv_nsec = 90000000;
// detect called so clear data blocks
T55x7_ClearAllBlockData();
config.broadcast_blocks = 0;
config.psk3_favoured = false;
config.pwd_known = false;
// sanity check.
if (SanityOfflineCheck(use_gb) != PM3_SUCCESS)
return PM3_ESOFT;
// a replayed buffer carries no promise about how it was captured
if (use_gb) {
s_block_read_capture = false;
}
if (use_gb == false) {
char wakecmd[20] = { 0x00 };
snprintf(wakecmd, sizeof(wakecmd), "-p %08" PRIx64, password);
bool usewake = false;
bool try_with_pwd = false;
// do ... while not found and not yet tried with wake (for AOR or Init Delay)
do {
// do ... while to check without password then loop back if password supplied
do {
if (try_all_dl_modes) {
// Loop from 1st d/l mode refFixedBit to the last d/l mode ref1of4
for (uint8_t m = refFixedBit; m <= ref1of4; m++) {
if (usewake) {
// call wake
if (try_with_pwd)
CmdT55xxWakeUp(wakecmd);
else
CmdT55xxWakeUp("");
// sleep 90 ms
nanosleep(&sleepperiod, &sleepperiod);
}
if (AcquireData(T55x7_PAGE0, T55x7_CONFIGURATION_BLOCK, (try_with_pwd && usepwd), password, m) == false)
continue;
if (t55xxTryDetectModulationEx(m, T55XX_DontPrintConfig, 0, (try_with_pwd && usepwd) ? password : -1) == false)
continue;
found = true;
break;
}
} else {
if (usewake) {
// call wake
if (try_with_pwd)
CmdT55xxWakeUp(wakecmd);
else
CmdT55xxWakeUp("");
// sleep 90 ms
nanosleep(&sleepperiod, &sleepperiod);
}
if (AcquireData(T55x7_PAGE0, T55x7_CONFIGURATION_BLOCK, usepwd, password, downlink_mode)) {
found = t55xxTryDetectModulationEx(downlink_mode, T55XX_DontPrintConfig, 0, (usepwd) ? password : -1);
}
}
// toggle so we loop back if not found and try with pwd
if (found == false && usepwd)
try_with_pwd = !try_with_pwd;
// force exit as detect block has been found
if (found)
try_with_pwd = false;
} while (try_with_pwd);
// Toggle so we loop back and try with wakeup.
usewake = !usewake;
} while (found == false && usewake);
} else {
found = t55xxTryDetectModulation(downlink_mode, T55XX_DontPrintConfig);
}
// password mode gates direct access, so a live detect tells us which mode
// the tag is in. set before the narrowing below, which uses it
config.pwd_known = (found && use_gb == false);
uint8_t nblk = 0;
bool psk2_out = false, favoured = false;
// With a tag on the antenna the psk2 / psk3 ambiguity can be settled rather
// than merely reported: measure the broadcast period, or read a few data
// blocks and apply the adjacent ones invariant. It needs the card, so it
// is skipped offline, where the single saved block 0 buffer cannot answer
// the question either way.
if (found && use_gb == false && t55xx_config_psk3_ambiguous()) {
// the broadcast period constrains maxblock, which narrows block 0
nblk = t55xx_measure_broadcast_blocks(config.usepwd, config.pwd, config.downlink_mode);
config.broadcast_blocks = nblk;
// ruling psk2 out beats the probe's weighing, so try it first
uint32_t only = 0;
const size_t nleft = (nblk != 0) ? t55xx_psk3_resolve(nblk, &only) : 0;
if (nleft) {
psk2_out = true;
config.modulation = DEMOD_PSK3;
// one word left, so report it rather than the collapsed image
if (nleft == 1) {
config.block0 = only;
}
} else {
// the probe only leans, so it does not get to name the modulation
favoured = t55xx_psk3_probe(config.usepwd, config.pwd, config.downlink_mode);
config.psk3_favoured = favoured;
}
// put the configuration block back in the demod buffer, so anything
// reading it after us sees block 0 rather than whatever we last read
if (AcquireData(T55x7_PAGE0, T55x7_CONFIGURATION_BLOCK, config.usepwd, config.pwd, config.downlink_mode)) {
DecodeT55xxBlock();
}
}
// Several rotations of the word can read as a configuration and detect takes the first the
// scan reaches. The broadcast period is the tag's own answer to which is real
bool rerotated = false;
if (found && use_gb == false && t55xx_block0_rotation_ambiguous()) {
if (nblk == 0) {
nblk = t55xx_measure_broadcast_blocks(config.usepwd, config.pwd, config.downlink_mode);
config.broadcast_blocks = nblk;
}
if (nblk) {
rerotated = t55xx_resolve_block0_rotation(nblk);
}
}
if (found) {
printConfiguration(config);
if (nblk) {
PrintAndLogEx(SUCCESS, "Broadcast repeats every " _GREEN_("%u") " block(s), so maxblock is a multiple of %u", nblk, nblk);
}
if (rerotated) {
PrintAndLogEx(SUCCESS, "Block 0 " _GREEN_("re-anchored") " - the first reading was a rotation the broadcast period contradicts");
}
if (psk2_out) {
PrintAndLogEx(SUCCESS, "psk2 " _RED_("ruled out") " - the psk2 reading of block 0 carries a maxblock contradicted by the broadcast");
} else if (favoured) {
PrintAndLogEx(SUCCESS, "Data blocks favour " _GREEN_("psk3") " over psk2, but do not settle it");
}
}
if (found == false) {
config.usepwd = false;
config.pwd = 0x00;
PrintAndLogEx(WARNING, "Could not detect modulation automatically. Try setting it manually with " _YELLOW_("\'lf t55xx config\'"));
}
return PM3_SUCCESS;
}
// detect configuration?
bool t55xxTryDetectModulation(uint8_t downlink_mode, bool print_config) {
return t55xxTryDetectModulationEx(downlink_mode, print_config, 0, -1);
}
#define PM3_T55_FALLBACK_MAXERR 100
// A demodulation that recovers far fewer bits than its clock implies never locked onto the tag,
// Measured on a T5577 over 56 configurations:
// every correct detection recovered at least 98.8% of g_GraphTraceLen / clk bits
#define T55XX_MIN_DEMOD_YIELD_PCT 80
static bool t55xx_demod_yield_ok(uint8_t clk) {
// what actually produced the bits, which is not always what was asked for
const uint32_t used = (g_DemodClock > 0) ? (uint32_t)g_DemodClock : clk;
if (used == 0 || g_GraphTraceLen == 0) {
return true;
}
const size_t expected = g_GraphTraceLen / used;
// too short to say anything either way
if (expected < 32) {
return true;
}
if (g_DemodBufferLen * 100 >= expected * T55XX_MIN_DEMOD_YIELD_PCT) {
return true;
}
PrintAndLogEx(DEBUG, "DEBUG: (t55xx test) clk %u recovered %zu bits of %zu, too few to trust"
, used
, g_DemodBufferLen
, expected
);
return false;
}
static bool block0_repeats_at_stride(uint8_t offset) {
if ((size_t)offset + 64 > g_DemodBufferLen || offset > 255 - 32) {
return false;
}
return (PackBits(offset, 32, g_DemodBuffer) == PackBits((uint8_t)(offset + 32), 32, g_DemodBuffer));
}
// psk subcarrier in field clocks, or 0 when it could not be measured
// At RF/128 a capture is 93 bits, so a word sitting past bit 29 has no second copy to compare against
static bool block0_stride_not_disproved(uint8_t offset) {
if ((size_t)offset + 64 > g_DemodBufferLen) {
return true;
}
return block0_repeats_at_stride(offset);
}
static uint8_t t55xx_observed_psk_carrier(void) {
const int fc = GetPskCarrier(false);
return (fc == 2 || fc == 4 || fc == 8) ? (uint8_t)fc : 0;
}
static void t55xx_psk_coherent(int fitclk, uint8_t clk, t55xx_conf_block_t *tests, uint8_t *hits, uint8_t downlink_mode) {
static const int subcarriers[] = { 2, 4, 8 };
if (g_GraphTraceLen < 2048 || fitclk < 4) {
return;
}
size_t count = g_GraphTraceLen;
if (count > 16384) {
count = 16384;
}
double *sig = pm3_extract(g_GraphBuffer, g_GraphTraceLen, 0, count);
if (sig == NULL) {
return;
}
uint8_t *raw = calloc(MAX_DEMOD_BUF_LEN, sizeof(uint8_t));
uint8_t *work = calloc(MAX_DEMOD_BUF_LEN, sizeof(uint8_t));
if (raw == NULL || work == NULL) {
free(sig);
free(raw);
free(work);
return;
}
const uint8_t before = *hits;
size_t n = 0;
double got_clk = 0.0, best_score = -1.0;
int got_phase = 0;
for (size_t s = 0; s < ARRAYLEN(subcarriers); s++) {
size_t got_n = MAX_DEMOD_BUF_LEN;
double this_clk = 0.0, score = 0.0;
int this_phase = 0;
if (pm3_psk_demod(sig, count, subcarriers[s], (double)fitclk, work, &got_n, &this_clk, &this_phase, &score, NULL) != PM3_SUCCESS) {
continue;
}
if (score > best_score) {
best_score = score;
n = got_n;
got_clk = this_clk;
got_phase = this_phase;
memcpy(raw, work, got_n);
}
}
if (n >= 32) {
// three variants, not four: inverting does not move a stream's
// transitions, so a fourth barely differed from variant 2
for (int variant = 0; variant < 3 && *hits == before; variant++) {
const bool inverted = ((variant & 1) != 0);
for (size_t i = 0; i < n; i++) {
work[i] = (inverted) ? (raw[i] ^ 1) : raw[i];
}
if (variant >= 2) {
psk1TOpsk2(work, n);
}
setDemodBuff(work, n, 0);
setClockGrid((uint32_t)(got_clk + 0.5), got_phase);
static const uint8_t modes[3] = { DEMOD_PSK1, DEMOD_PSK1, DEMOD_PSK2 };
int bitRate = 0;
if (test(modes[variant], &tests[*hits].offset, &bitRate, clk, &tests[*hits].Q5) == false) {
continue;
}
// sweeping the bit rates means a wrong one can fit first where an RF/16 signal answers at RF/8
if (s_block_read_capture && block0_stride_not_disproved(tests[*hits].offset) == false) {
continue;
}
tests[*hits].modulation = modes[variant];
tests[*hits].psk_carrier = t55xx_observed_psk_carrier();
tests[*hits].bitrate = bitRate;
tests[*hits].inverted = inverted;
t55xx_record_hit(&tests[*hits]);
tests[*hits].ST = false;
tests[*hits].downlink_mode = downlink_mode;
(*hits)++;
}
}
free(sig);
free(raw);
free(work);
}
static void t55xx_ask_coherent(int fitclk, uint8_t clk, t55xx_conf_block_t *tests, uint8_t *hits, uint8_t downlink_mode) {
if (g_GraphTraceLen < 2048 || fitclk < 4) {
return;
}
size_t count = g_GraphTraceLen;
if (count > 16384) {
count = 16384;
}
double *sig = pm3_extract(g_GraphBuffer, g_GraphTraceLen, 0, count);
if (sig == NULL) {
return;
}
uint8_t *raw = calloc(MAX_DEMOD_BUF_LEN, sizeof(uint8_t));
uint8_t *work = calloc(MAX_DEMOD_BUF_LEN, sizeof(uint8_t));
if (raw == NULL || work == NULL) {
free(sig);
free(raw);
free(work);
return;
}
const uint8_t before = *hits;
// a transition code carries two chips per bit
size_t n = MAX_DEMOD_BUF_LEN;
double got_chip = 0.0;
int got_phase = 0;
if (pm3_ask_chips(sig, count, (double)fitclk / 2.0, raw, &n, &got_chip, &got_phase) == PM3_SUCCESS) {
// manchester first, then biphase at both pair alignments, each way up
for (int variant = 0; variant < 6 && *hits == before; variant++) {
const int invert = (variant & 1);
size_t size = n;
uint8_t mode;
memcpy(work, raw, n);
if (variant < 2) {
uint8_t align = 0;
if (manrawdecode(work, &size, (uint8_t)invert, &align) == 0xFFFF) {
continue;
}
mode = DEMOD_ASK;
} else {
int offset = (variant < 4) ? 0 : 1;
if (BiphaseRawDecode(work, &size, &offset, invert) < 0) {
continue;
}
mode = invert ? DEMOD_BIa : DEMOD_BI;
}
if (size < 32) {
continue;
}
setDemodBuff(work, size, 0);
setClockGrid((uint32_t)((got_chip * 2.0) + 0.5), got_phase);
int bitRate = 0;
if (test(mode, &tests[*hits].offset, &bitRate, clk, &tests[*hits].Q5) == false) {
continue;
}
// sweeping the bit rates means a wrong one can fit first, so hold a block read to the 32 bit stride
if (s_block_read_capture && block0_stride_not_disproved(tests[*hits].offset) == false) {
continue;
}
tests[*hits].modulation = mode;
tests[*hits].bitrate = bitRate;
tests[*hits].inverted = (invert != 0);
t55xx_record_hit(&tests[*hits]);
tests[*hits].ST = false;
tests[*hits].downlink_mode = downlink_mode;
(*hits)++;
}
}
free(sig);
free(raw);
free(work);
}
static bool t55xx_fallback_try(pm3_mod_t mod, pm3_enc_t enc, int fc_hi, int fc_lo, int fitclk,
t55xx_conf_block_t *tests, uint8_t *hits, uint8_t downlink_mode,
bool coherent_ok) {
const uint8_t before = *hits;
int bitRate = 0;
const uint8_t clk = (uint8_t)((fitclk > 0 && fitclk < 256) ? fitclk : 0);
if (mod == PM3_MOD_FSK) {
// RF/8 is left out on purpose. A bit period of 8 field clocks cannot
// hold a whole cycle of both tones of either legal pair, and measured
// on a T5577 no fsk variant reads back at RF/8 even with the config
// forced and both inversions tried.
// RF/8 stays out: it gains nothing measured, and on a replayed capture,
// where the 32 bit stride cannot be demanded, it aliases a Q5 fsk2a trace
// into a confident wrong answer
static const uint8_t rates[] = { 16, 32, 40, 50, 64, 100, 128 };
const uint8_t pairs[3][2] = {
{ (uint8_t)fc_hi, (uint8_t)fc_lo }, { 8, 5 }, { 10, 8 }
};
// An addressed block read repeats the word every 32 bits,
// a candidate that does not is not a configuration
const int last_pass = s_block_read_capture ? 1 : 0;
for (int strict = 1; strict >= last_pass; strict--) {
for (size_t p = 0; p < ARRAYLEN(pairs); p++) {
if (pairs[p][0] == 0 || pairs[p][1] == 0) {
continue;
}
for (size_t r = 0; r < ARRAYLEN(rates); r++) {
for (int inv = 0; inv < 2; inv++) {
if (FSKrawDemod(rates[r], (uint8_t)inv, pairs[p][0], pairs[p][1], false) != PM3_SUCCESS) {
continue;
}
if (test(DEMOD_FSK, &tests[*hits].offset, &bitRate, rates[r], &tests[*hits].Q5) == false) {
continue;
}
if (strict && block0_stride_not_disproved(tests[*hits].offset) == false) {
continue;
}
uint8_t m = DEMOD_FSK;
if (pairs[p][0] == 8 && pairs[p][1] == 5) {
m = inv ? DEMOD_FSK1 : DEMOD_FSK1a;
} else if (pairs[p][0] == 10 && pairs[p][1] == 8) {
m = inv ? DEMOD_FSK2a : DEMOD_FSK2;
}
tests[*hits].modulation = m;
tests[*hits].bitrate = bitRate;
tests[*hits].inverted = (inv != 0);
t55xx_record_hit(&tests[*hits]);
tests[*hits].ST = false;
tests[*hits].downlink_mode = downlink_mode;
(*hits)++;
return true;
}
}
}
}
return false;
}
if (mod == PM3_MOD_PSK) {
buffer_savestate_t saveState = t55xx_psk_trim_head();
for (int inv = 0; inv < 2; inv++) {
if (PSKDemod(fitclk, inv, PM3_T55_FALLBACK_MAXERR, false) != PM3_SUCCESS) {
continue;
}
if (test(DEMOD_PSK1, &tests[*hits].offset, &bitRate, clk, &tests[*hits].Q5) == false) {
continue;
}
tests[*hits].modulation = DEMOD_PSK1;
tests[*hits].bitrate = bitRate;
tests[*hits].inverted = (inv != 0);
t55xx_record_hit(&tests[*hits]);
tests[*hits].ST = false;
tests[*hits].downlink_mode = downlink_mode;
(*hits)++;
break;
}
// PSK2 and PSK3 are PSK1 put through psk1TOpsk2()
if (*hits == before && PSKDemod(fitclk, 0, PM3_T55_FALLBACK_MAXERR, false) == PM3_SUCCESS) {
psk1TOpsk2(g_DemodBuffer, g_DemodBufferLen);
if (test(DEMOD_PSK2, &tests[*hits].offset, &bitRate, clk, &tests[*hits].Q5)) {
tests[*hits].modulation = DEMOD_PSK2;
tests[*hits].psk_carrier = t55xx_observed_psk_carrier();
tests[*hits].bitrate = bitRate;
tests[*hits].inverted = false;
t55xx_record_hit(&tests[*hits]);
tests[*hits].ST = false;
tests[*hits].downlink_mode = downlink_mode;
(*hits)++;
}
}
t55xx_psk_untrim_head(saveState);
if (*hits == before && coherent_ok) {
t55xx_psk_coherent(fitclk, clk, tests, hits, downlink_mode);
}
return (*hits != before);
}
if (mod == PM3_MOD_NRZ || (mod == PM3_MOD_ASK && enc == PM3_ENC_RAW)) {
if (fitclk <= 8) {
return false;
}
for (int inv = 0; inv < 2; inv++) {
if (NRZrawDemod(fitclk, inv, PM3_T55_FALLBACK_MAXERR, false) != PM3_SUCCESS) {
continue;
}
if (test(DEMOD_NRZ, &tests[*hits].offset, &bitRate, clk, &tests[*hits].Q5) == false) {
continue;
}
tests[*hits].modulation = DEMOD_NRZ;
tests[*hits].bitrate = bitRate;
tests[*hits].inverted = (inv != 0);
t55xx_record_hit(&tests[*hits]);
tests[*hits].ST = false;
tests[*hits].downlink_mode = downlink_mode;
(*hits)++;
return true;
}
return false;
}
tests[*hits].ST = true;
if ((ASKDemod_ext(fitclk, 0, PM3_T55_FALLBACK_MAXERR, 0, false, false, false, 1, &tests[*hits].ST) == PM3_SUCCESS)
&& test(DEMOD_ASK, &tests[*hits].offset, &bitRate, clk, &tests[*hits].Q5)) {
tests[*hits].modulation = DEMOD_ASK;
tests[*hits].bitrate = bitRate;
tests[*hits].inverted = false;
t55xx_record_hit(&tests[*hits]);
tests[*hits].downlink_mode = downlink_mode;
(*hits)++;
return true;
}
tests[*hits].ST = true;
if ((ASKDemod_ext(fitclk, 1, PM3_T55_FALLBACK_MAXERR, 0, false, false, false, 1, &tests[*hits].ST) == PM3_SUCCESS)
&& test(DEMOD_ASK, &tests[*hits].offset, &bitRate, clk, &tests[*hits].Q5)) {
tests[*hits].modulation = DEMOD_ASK;
tests[*hits].bitrate = bitRate;
tests[*hits].inverted = true;
t55xx_record_hit(&tests[*hits]);
tests[*hits].downlink_mode = downlink_mode;
(*hits)++;
return true;
}
if ((ASKbiphaseDemod(0, fitclk, 0, PM3_T55_FALLBACK_MAXERR, false) == PM3_SUCCESS)
&& test(DEMOD_BI, &tests[*hits].offset, &bitRate, clk, &tests[*hits].Q5)) {
tests[*hits].modulation = DEMOD_BI;
tests[*hits].bitrate = bitRate;
tests[*hits].inverted = false;
t55xx_record_hit(&tests[*hits]);
tests[*hits].ST = false;
tests[*hits].downlink_mode = downlink_mode;
(*hits)++;
return true;
}
if ((ASKbiphaseDemod(0, fitclk, 1, PM3_T55_FALLBACK_MAXERR, false) == PM3_SUCCESS)
&& test(DEMOD_BIa, &tests[*hits].offset, &bitRate, clk, &tests[*hits].Q5)) {
tests[*hits].modulation = DEMOD_BIa;
tests[*hits].bitrate = bitRate;
tests[*hits].inverted = true;
t55xx_record_hit(&tests[*hits]);
tests[*hits].ST = false;
tests[*hits].downlink_mode = downlink_mode;
(*hits)++;
}
if (*hits == before && coherent_ok) {
t55xx_ask_coherent(fitclk, clk, tests, hits, downlink_mode);
}
return (*hits != before);
}
#define PM3_T55_FALLBACK_HYPS 4
static void t55xx_detect_fallback(t55xx_conf_block_t *tests, uint8_t *hits, uint8_t downlink_mode) {
if (g_GraphTraceLen < 2048) {
return;
}
size_t count = g_GraphTraceLen;
if (count > 16384) {
count = 16384;
}
double *sig = pm3_extract(g_GraphBuffer, g_GraphTraceLen, 0, count);
if (sig == NULL) {
return;
}
pm3_spec_analysis_t an;
pm3_fit_opts_t opts = {0};
if (pm3_analyse(sig, count, pm3_next_pow2(count), PM3_WIN_HANN, &an) == PM3_SUCCESS
&& an.confidence >= PM3_CONF_MEDIUM) {
switch (an.family) {
case PM3_FAM_FSK:
opts.mod_mask = 1 << PM3_MOD_FSK;
break;
case PM3_FAM_PSK:
opts.mod_mask = 1 << PM3_MOD_PSK;
break;
case PM3_FAM_ASK:
case PM3_FAM_MANCHESTER:
opts.mod_mask = (1 << PM3_MOD_ASK) | (1 << PM3_MOD_NRZ);
break;
case PM3_FAM_NRZ:
opts.mod_mask = (1 << PM3_MOD_NRZ) | (1 << PM3_MOD_ASK);
break;
case PM3_FAM_UNKNOWN:
break;
}
}
const int first_mask = opts.mod_mask;
for (int round = 0; round < 3; round++) {
if (round == 1) {
if (first_mask == 0) {
continue;
}
opts.mod_mask = 0;
}
if (round == 2) {
opts.mod_mask = 0;
}
pm3_fit_t fit;
if (pm3_fit_run(sig, count, &opts, &fit) != PM3_SUCCESS) {
continue;
}
int seen_mod[PM3_T55_FALLBACK_HYPS], seen_enc[PM3_T55_FALLBACK_HYPS], seen_clk[PM3_T55_FALLBACK_HYPS];
size_t nseen = 0;
bool done = false;
for (size_t i = 0; i < fit.count && nseen < PM3_T55_FALLBACK_HYPS; i++) {
const int fitclk = (int)(fit.items[i].clk_fine + 0.5);
if (fitclk <= 0 || fitclk > 255) {
continue;
}
bool dup = false;
for (size_t j = 0; j < nseen; j++) {
if ((seen_mod[j] == (int)fit.items[i].mod) && (seen_enc[j] == (int)fit.items[i].enc) && (seen_clk[j] == fitclk)) {
dup = true;
break;
}
}
if (dup) {
continue;
}
seen_mod[nseen] = (int)fit.items[i].mod;
seen_enc[nseen] = (int)fit.items[i].enc;
seen_clk[nseen] = fitclk;
nseen++;
if (t55xx_fallback_try(fit.items[i].mod
, fit.items[i].enc
, fit.items[i].fc_hi
, fit.items[i].fc_lo
, fitclk
, tests
, hits
, downlink_mode
, (round == 2))) {
done = true;
break;
}
}
pm3_fit_free(&fit);
if (done) {
break;
}
}
free(sig);
}
// The coherent psk detector is only reached when the fitter ranks psk above ask and nrz, and on a weak psk signal it does not at all.
static void t55xx_psk_sweep(t55xx_conf_block_t *tests, uint8_t *hits, uint8_t downlink_mode) {
if (s_block_read_capture == false) {
return;
}
static const uint8_t rates[] = { 8, 16, 32, 40, 50, 64, 100, 128 };
const uint8_t before = *hits;
for (size_t r = 0; r < ARRAYLEN(rates) && *hits == before; r++) {
t55xx_psk_coherent(rates[r], rates[r], tests, hits, downlink_mode);
}
}
// same argument as t55xx_psk_sweep, for the ask and biphase side
static void t55xx_ask_sweep(t55xx_conf_block_t *tests, uint8_t *hits, uint8_t downlink_mode) {
if (s_block_read_capture == false) {
return;
}
static const uint8_t rates[] = { 8, 16, 32, 40, 50, 64, 100, 128 };
const uint8_t before = *hits;
for (size_t r = 0; r < ARRAYLEN(rates) && *hits == before; r++) {
t55xx_ask_coherent(rates[r], rates[r], tests, hits, downlink_mode);
}
}
bool t55xxTryDetectModulationEx(uint8_t downlink_mode, bool print_config, uint32_t wanted_conf, uint64_t pwd) {
t55xx_conf_block_t tests[15] = {0};
int bitRate = 0, clk = 0, firstClockEdge = 0;
uint8_t hits = 0, fc1 = 0, fc2 = 0, ans = 0;
ans = fskClocks(&fc1, &fc2, (uint8_t *)&clk, &firstClockEdge);
if (ans && ((fc1 == 10 && fc2 == 8) || (fc1 == 8 && fc2 == 5))) {
if ((FSKrawDemod(0, 0, 0, 0, false) == PM3_SUCCESS) && test(DEMOD_FSK, &tests[hits].offset, &bitRate, clk, &tests[hits].Q5)) {
tests[hits].modulation = DEMOD_FSK;
if (fc1 == 8 && fc2 == 5)
tests[hits].modulation = DEMOD_FSK1a;
else if (fc1 == 10 && fc2 == 8)
tests[hits].modulation = DEMOD_FSK2;
tests[hits].bitrate = bitRate;
tests[hits].inverted = false;
t55xx_record_hit(&tests[hits]);
tests[hits].ST = false;
tests[hits].downlink_mode = downlink_mode;
++hits;
}
if ((FSKrawDemod(0, 1, 0, 0, false) == PM3_SUCCESS) && test(DEMOD_FSK, &tests[hits].offset, &bitRate, clk, &tests[hits].Q5)) {
tests[hits].modulation = DEMOD_FSK;
if (fc1 == 8 && fc2 == 5)
tests[hits].modulation = DEMOD_FSK1;
else if (fc1 == 10 && fc2 == 8)
tests[hits].modulation = DEMOD_FSK2a;
tests[hits].bitrate = bitRate;
tests[hits].inverted = true;
t55xx_record_hit(&tests[hits]);
tests[hits].ST = false;
tests[hits].downlink_mode = downlink_mode;
++hits;
}
} else {
clk = GetAskClock("", false);
if (clk > 0) {
tests[hits].ST = true;
// "0 0 1 " == clock auto, invert false, maxError 1.
// false = no verbose
// false = no emSearch
// 1 = Ask/Man
// st = true
if ((ASKDemod_ext(0, 0, 1, 0, false, false, false, 1, &tests[hits].ST) == PM3_SUCCESS) && test(DEMOD_ASK, &tests[hits].offset, &bitRate, clk, &tests[hits].Q5)) {
tests[hits].modulation = DEMOD_ASK;
tests[hits].bitrate = bitRate;
tests[hits].inverted = false;
t55xx_record_hit(&tests[hits]);
tests[hits].downlink_mode = downlink_mode;
++hits;
}
tests[hits].ST = true;
// "0 0 1 " == clock auto, invert true, maxError 1.
// false = no verbose
// false = no emSearch
// 1 = Ask/Man
// st = true
if ((ASKDemod_ext(0, 1, 1, 0, false, false, false, 1, &tests[hits].ST) == PM3_SUCCESS) && test(DEMOD_ASK, &tests[hits].offset, &bitRate, clk, &tests[hits].Q5)) {
tests[hits].modulation = DEMOD_ASK;
tests[hits].bitrate = bitRate;
tests[hits].inverted = true;
t55xx_record_hit(&tests[hits]);
tests[hits].downlink_mode = downlink_mode;
++hits;
}
if ((ASKbiphaseDemod(0, 0, 0, 2, false) == PM3_SUCCESS) && test(DEMOD_BI, &tests[hits].offset, &bitRate, clk, &tests[hits].Q5)) {
tests[hits].modulation = DEMOD_BI;
tests[hits].bitrate = bitRate;
tests[hits].inverted = false;
t55xx_record_hit(&tests[hits]);
tests[hits].ST = false;
tests[hits].downlink_mode = downlink_mode;
++hits;
}
if ((ASKbiphaseDemod(0, 0, 1, 2, false) == PM3_SUCCESS) && test(DEMOD_BIa, &tests[hits].offset, &bitRate, clk, &tests[hits].Q5)) {
tests[hits].modulation = DEMOD_BIa;
tests[hits].bitrate = bitRate;
tests[hits].inverted = true;
t55xx_record_hit(&tests[hits]);
tests[hits].ST = false;
tests[hits].downlink_mode = downlink_mode;
++hits;
}
}
clk = GetNrzClock("", false);
if (clk > 8) { //clock of rf/8 is likely a false positive, so don't use it.
if ((NRZrawDemod(0, 0, 1, false) == PM3_SUCCESS) && test(DEMOD_NRZ, &tests[hits].offset, &bitRate, clk, &tests[hits].Q5)) {
tests[hits].modulation = DEMOD_NRZ;
tests[hits].bitrate = bitRate;
tests[hits].inverted = false;
t55xx_record_hit(&tests[hits]);
tests[hits].ST = false;
tests[hits].downlink_mode = downlink_mode;
++hits;
}
if ((NRZrawDemod(0, 1, 1, false) == PM3_SUCCESS) && test(DEMOD_NRZ, &tests[hits].offset, &bitRate, clk, &tests[hits].Q5)) {
tests[hits].modulation = DEMOD_NRZ;
tests[hits].bitrate = bitRate;
tests[hits].inverted = true;
t55xx_record_hit(&tests[hits]);
tests[hits].ST = false;
tests[hits].downlink_mode = downlink_mode;
++hits;
}
}
clk = GetPskClock("", false);
if (clk > 0) {
// allow undo
buffer_savestate_t saveState = t55xx_psk_trim_head();
if ((PSKDemod(0, 0, 6, false) == PM3_SUCCESS) && test(DEMOD_PSK1, &tests[hits].offset, &bitRate, clk, &tests[hits].Q5)) {
tests[hits].modulation = DEMOD_PSK1;
tests[hits].bitrate = bitRate;
tests[hits].inverted = false;
t55xx_record_hit(&tests[hits]);
tests[hits].ST = false;
tests[hits].downlink_mode = downlink_mode;
++hits;
}
if ((PSKDemod(0, 1, 6, false) == PM3_SUCCESS) && test(DEMOD_PSK1, &tests[hits].offset, &bitRate, clk, &tests[hits].Q5)) {
tests[hits].modulation = DEMOD_PSK1;
tests[hits].bitrate = bitRate;
tests[hits].inverted = true;
t55xx_record_hit(&tests[hits]);
tests[hits].ST = false;
tests[hits].downlink_mode = downlink_mode;
++hits;
}
//ICEMAN: are these PSKDemod calls needed?
// PSK2 - needs a call to psk1TOpsk2.
if (PSKDemod(0, 0, 6, false) == PM3_SUCCESS) {
psk1TOpsk2(g_DemodBuffer, g_DemodBufferLen);
if (test(DEMOD_PSK2, &tests[hits].offset, &bitRate, clk, &tests[hits].Q5)) {
tests[hits].modulation = DEMOD_PSK2;
tests[hits].psk_carrier = t55xx_observed_psk_carrier();
tests[hits].bitrate = bitRate;
tests[hits].inverted = false;
t55xx_record_hit(&tests[hits]);
tests[hits].ST = false;
tests[hits].downlink_mode = downlink_mode;
++hits;
}
} // inverse waves does not affect this demod
//undo trim samples
t55xx_psk_untrim_head(saveState);
// t55xx_search_config_psk(g_GraphBuffer, 1);
// t55xx_search_config_psk(g_GraphBuffer, 2);
}
}
// The tag only has two legal fsk pair
// An fsk1 tag at RF/32 counts field clocks as 5 and 6 here
if (hits == 0 && s_block_read_capture) {
t55xx_fallback_try(PM3_MOD_FSK, 0, 0, 0, 0, tests, &hits, downlink_mode, false);
}
if (hits == 0) {
t55xx_psk_sweep(tests, &hits, downlink_mode);
}
if (hits == 0) {
t55xx_ask_sweep(tests, &hits, downlink_mode);
}
if (hits == 0) {
t55xx_detect_fallback(tests, &hits, downlink_mode);
}
if (hits == 1) {
config.modulation = tests[0].modulation;
config.bitrate = tests[0].bitrate;
config.inverted = tests[0].inverted;
config.offset = tests[0].offset;
config.anchor_sample = tests[0].anchor_sample;
config.anchor_tracelen = tests[0].anchor_tracelen;
config.anchor_valid = tests[0].anchor_valid;
config.block0 = tests[0].block0;
config.Q5 = tests[0].Q5;
config.ST = tests[0].ST;
config.psk_carrier = tests[0].psk_carrier;
config.downlink_mode = downlink_mode;
if (pwd != -1) {
config.usepwd = true;
config.pwd = pwd & 0xffffffff;
}
config.block0Status = AUTODETECT;
if (print_config)
printConfiguration(config);
return true;
}
bool retval = false;
if (hits > 1) {
PrintAndLogEx(SUCCESS, "Found [%d] possible matches for modulation.", hits);
for (int i = 0; i < hits; ++i) {
bool wanted = false;
if (wanted_conf > 0)
wanted = (wanted_conf == tests[i].block0);
retval = testKnownConfigBlock(tests[i].block0);
if (retval || wanted) {
PrintAndLogEx(NORMAL, "--[%d]--------------- << selected this", i + 1);
config.modulation = tests[i].modulation;
config.bitrate = tests[i].bitrate;
config.inverted = tests[i].inverted;
config.offset = tests[i].offset;
config.anchor_sample = tests[i].anchor_sample;
config.anchor_tracelen = tests[i].anchor_tracelen;
config.anchor_valid = tests[i].anchor_valid;
config.block0 = tests[i].block0;
config.Q5 = tests[i].Q5;
config.ST = tests[i].ST;
config.psk_carrier = tests[i].psk_carrier;
config.downlink_mode = tests[i].downlink_mode;
if (pwd != -1) {
config.usepwd = true;
config.pwd = pwd & 0xffffffff;
}
} else {
PrintAndLogEx(NORMAL, "--[%d]---------------", i + 1);
}
config.block0Status = AUTODETECT;
if (print_config)
printConfiguration(tests[i]);
}
}
return retval;
}
bool testKnownConfigBlock(uint32_t block0) {
switch (block0) {
case T55X7_DEFAULT_CONFIG_BLOCK:
case T55X7_RAW_CONFIG_BLOCK:
case T55X7_EM_UNIQUE_CONFIG_BLOCK:
case T55X7_FDXB_CONFIG_BLOCK:
case T55X7_FDXB_2_CONFIG_BLOCK:
case T55X7_HID_26_CONFIG_BLOCK:
case T55X7_PYRAMID_CONFIG_BLOCK:
case T55X7_INDALA_64_CONFIG_BLOCK:
case T55X7_INDALA_224_CONFIG_BLOCK:
case T55X7_GUARDPROXII_CONFIG_BLOCK:
case T55X7_VIKING_CONFIG_BLOCK:
case T55X7_NORALSY_CONFIG_BLOCK:
case T55X7_IOPROX_CONFIG_BLOCK:
case T55X7_PRESCO_CONFIG_BLOCK:
case T55X7_NEDAP_64_CONFIG_BLOCK:
case T55X7_NEDAP_128_CONFIG_BLOCK:
case T55X7_VISA2000_CONFIG_BLOCK:
case T55X7_SECURAKEY_CONFIG_BLOCK:
case T55X7_PAC_CONFIG_BLOCK:
case T55X7_VERICHIP_CONFIG_BLOCK:
case T55X7_KERI_CONFIG_BLOCK:
case T55X7_NEXWATCH_CONFIG_BLOCK:
case T55X7_JABLOTRON_CONFIG_BLOCK:
case T55X7_PYRONIX_CONFIG_BLOCK:
case T55X7_TEXECOM_CONFIG_BLOCK:
case T55X7_BETECH_CONFIG_BLOCK:
return true;
}
return false;
}
bool GetT55xxBlockData(uint32_t *blockdata) {
uint8_t idx = 0;
if (t55xx_demod_offset(&idx) == false) {
return false;
}
*blockdata = PackBits(0, 32, g_DemodBuffer + idx);
return true;
}
static bool t55xx_has_adjacent_ones(uint32_t v) {
const uint32_t rot = (v >> 1) | ((v & 1) << 31);
return ((v & rot) != 0);
}
static bool t55xx_config_psk3_ambiguous(void) {
if (config.modulation != DEMOD_PSK2 || config.Q5) {
return false;
}
static const uint8_t basic[] = {8, 16, 32, 40, 50, 64, 100, 128};
const uint8_t clk = basic[config.bitrate & 0x07];
uint32_t cand[T55XX_PSK3_MAX_CAND];
return (t55xx_psk3_block0_candidates(config.block0, clk, config.psk_carrier, cand, ARRAYLEN(cand)) > 0);
}
#define T55XX_PSK3_PROBE_TRIES 3
#define T55XX_PSK3_PROBE_MIN 3
typedef struct {
uint8_t page;
uint8_t blk;
} t55xx_probe_block_t;
// blocks the probe samples. page 1 is writable so it is not trustworthy trace
// data, just more blocks in which an adjacent pair can turn up
static const t55xx_probe_block_t t55xx_psk3_probe_blocks[] = {
{ T55x7_PAGE0, 1 }, { T55x7_PAGE0, 2 }, { T55x7_PAGE0, 3 }, { T55x7_PAGE0, 4 },
{ T55x7_PAGE0, 5 }, { T55x7_PAGE0, 6 }, { T55x7_PAGE0, 7 },
{ T55x7_PAGE1, T55x7_TRACE_BLOCK1 }, { T55x7_PAGE1, T55x7_TRACE_BLOCK2 },
{ T55x7_PAGE1, 3 },
};
// evidence threshold as a probability scaled by 65536. one percent
#define T55XX_PSK3_PROBE_MAX_P 655
// P(32 bit word with k one bits has no cyclically adjacent pair) * 65536,
// (32 / (32 - k)) * C(32 - k, k) / C(32, k). zero from k = 15, adjacency
// forced. zeroes score 1.0, so a wiped tag proves nothing
static const uint32_t t55xx_p_no_adjacent[] = {
65536, 65536, 61308, 53274, 42646, 31138, 20493, 11980,
6110, 2657, 956, 273, 59, 9, 1, 0
};
static bool t55xx_psk3_probe(bool usepwd, uint32_t password, uint8_t downlink_mode) {
size_t usable = 0, clean = 0;
uint64_t prob = 65536;
for (size_t i = 0; i < ARRAYLEN(t55xx_psk3_probe_blocks); i++) {
const uint8_t page = t55xx_psk3_probe_blocks[i].page;
const uint8_t blk = t55xx_psk3_probe_blocks[i].blk;
bool got = false, ok = false;
uint32_t seen = 0;
for (uint8_t t = 0; t < T55XX_PSK3_PROBE_TRIES; t++) {
if (AcquireData(page, blk, usepwd, password, downlink_mode) == false) {
continue;
}
if (DecodeT55xxBlock() == false) {
continue;
}
uint32_t val = 0;
if (GetT55xxBlockData(&val) == false) {
continue;
}
got = true;
seen = val;
if (t55xx_has_adjacent_ones(val) == false) {
ok = true;
break;
}
}
if (got == false) {
continue;
}
usable++;
if (ok) {
clean++;
const uint32_t k = bitcount32(seen);
const uint32_t bp = (k < ARRAYLEN(t55xx_p_no_adjacent)) ? t55xx_p_no_adjacent[k] : 0;
prob = (prob * bp) / 65536;
}
}
// every readable block has to be clean - one adjacent pair disproves psk3 -
// and the one bits present have to make that a surprise, not a given
return (usable >= T55XX_PSK3_PROBE_MIN && clean == usable && prob <= T55XX_PSK3_PROBE_MAX_P);
}
// regular-read cycles blocks 1 to MAXBLK, so the stream repeats every
// MAXBLK * 32 bits. returns that block count, or 0 when it cannot be measured.
// blocks holding the same word repeat sooner, so this is a divisor of MAXBLK
// rather than MAXBLK itself. needs two full periods, so 6 or 7 read as
// unmeasurable
static uint8_t t55xx_measure_broadcast_blocks_once(bool usepwd, uint32_t password, uint8_t downlink_mode) {
// the acquisition misses often enough to be worth a couple of retries
bool got = false;
for (uint8_t t = 0; t < T55XX_PSK3_PROBE_TRIES && got == false; t++) {
if (AcquireData(T55x7_PAGE0, REGULAR_READ_MODE_BLOCK, usepwd, password, downlink_mode) == false) {
continue;
}
if (DecodeT55xxBlock() == false) {
continue;
}
got = (g_DemodBufferLen >= 64);
}
if (got == false) {
return 0;
}
for (uint8_t n = 1; n <= 7; n++) {
const size_t stride = (size_t)n * 32;
// two full periods, or it is not a demonstrated repeat
if (g_DemodBufferLen < stride * 2) {
break;
}
const size_t cmp = g_DemodBufferLen - stride;
size_t bad = 0;
for (size_t i = 0; i < cmp; i++) {
if (g_DemodBuffer[i] != g_DemodBuffer[i + stride]) {
bad++;
}
}
// the stream carries the odd bit error, so this cannot want an exact
// repeat - but the allowance has to be absolute, not proportional: a
// real difference between blocks recurs every period, bit errors do not
if (bad <= 2) {
// a one block period rules nothing out, and also catches an
// acquisition that never left block 0. report it as unmeasured
return (n > 1) ? n : 0;
}
}
return 0;
}
// believing a spurious period is expensive - it excludes psk2 and can name the
// wrong word - so require two attempts to agree
#define T55XX_PSK3_PERIOD_TRIES 4
static uint8_t t55xx_measure_broadcast_blocks(bool usepwd, uint32_t password, uint8_t downlink_mode) {
uint8_t seen = 0;
for (uint8_t t = 0; t < T55XX_PSK3_PERIOD_TRIES; t++) {
const uint8_t n = t55xx_measure_broadcast_blocks_once(usepwd, password, downlink_mode);
// an attempt that could not measure at all says nothing either way
if (n == 0) {
continue;
}
if (seen == 0) {
seen = n;
continue;
}
if (seen == n) {
return n;
}
// measured twice and disagreed, nothing here says which is wrong
return 0;
}
return 0;
}
// the broadcast period constrains maxblock, and so every reading of block 0,
// the psk2 one included. returns 0 when psk2 still fits, otherwise how many
// words block 0 could be, with *only set when that is exactly one
static size_t t55xx_psk3_resolve(uint8_t nblk, uint32_t *only) {
static const uint8_t basic[] = {8, 16, 32, 40, 50, 64, 100, 128};
uint32_t cand[T55XX_PSK3_MAX_CAND];
size_t n = t55xx_psk3_block0_candidates(config.block0, basic[config.bitrate & 0x07],
config.psk_carrier, cand, ARRAYLEN(cand));
if (n == 0) {
return 0;
}
const uint8_t seen = (config.block0 >> 5) & 0x07;
const bool psk2_fits = (seen != 0) && ((seen % nblk) == 0);
size_t kept = 0;
for (size_t i = 0; i < n; i++) {
const uint8_t mb = (cand[i] >> 5) & 0x07;
// a measured period rules out ST, which would add four bit periods
if (((cand[i] >> 3) & 1) != 0) {
continue;
}
// and the password bit has to match the mode the detect succeeded in
if (config.pwd_known && ((((cand[i] >> 4) & 1) != 0) != config.usepwd)) {
continue;
}
if (mb != 0 && (mb % nblk) == 0) {
cand[kept++] = cand[i];
}
}
// nothing left means the measurement is what is wrong, not every reading
if (psk2_fits || kept == 0) {
return 0;
}
if (kept == 1) {
*only = cand[0];
}
return kept;
}
void printT55xxBlock(uint8_t blockNum, bool page1) {
uint8_t idx = 0;
if (t55xx_demod_offset(&idx) == false) {
return;
}
uint32_t val = PackBits(0, 32, g_DemodBuffer + idx);
uint8_t bytes[4] = {0};
num_to_bytes(val, 4, bytes);
T55x7_SaveBlockData((page1) ? blockNum + 8 : blockNum, val);
const char *note = t55xx_config_psk3_ambiguous() ? _YELLOW_(" <- psk2/psk3 ambiguous") : "";
PrintAndLogEx(SUCCESS, " %02d | %08X | %s | %s%s", blockNum, val, sprint_bytebits_bin(g_DemodBuffer + idx, 32), sprint_ascii(bytes, 4), note);
}
static bool testModulation(uint8_t mode, uint8_t modread) {
switch (mode) {
case DEMOD_FSK:
if (modread >= DEMOD_FSK1 && modread <= DEMOD_FSK2a) return true;
break;
case DEMOD_ASK:
if (modread == DEMOD_ASK) return true;
break;
case DEMOD_PSK1:
if (modread == DEMOD_PSK1) return true;
break;
case DEMOD_PSK2:
if (modread == DEMOD_PSK2) return true;
break;
case DEMOD_PSK3:
if (modread == DEMOD_PSK3) return true;
break;
case DEMOD_NRZ:
if (modread == DEMOD_NRZ) return true;
break;
case DEMOD_BI:
if (modread == DEMOD_BI) return true;
break;
case DEMOD_BIa:
if (modread == DEMOD_BIa) return true;
break;
default:
return false;
}
return false;
}
static bool testQ5Modulation(uint8_t mode, uint8_t modread) {
switch (mode) {
case DEMOD_FSK:
if (modread >= 4 && modread <= 5) return true;
break;
case DEMOD_ASK:
if (modread == 0) return true;
break;
case DEMOD_PSK1:
if (modread == 1) return true;
break;
case DEMOD_PSK2:
if (modread == 2) return true;
break;
case DEMOD_PSK3:
if (modread == 3) return true;
break;
case DEMOD_NRZ:
if (modread == 7) return true;
break;
case DEMOD_BI:
if (modread == 6) return true;
break;
default:
return false;
}
return false;
}
static int convertQ5bitRate(uint8_t bitRateRead) {
const uint8_t expected[] = {8, 16, 32, 40, 50, 64, 100, 128};
for (int i = 0; i < 8; i++)
if (expected[i] == bitRateRead)
return i;
return -1;
}
static bool testQ5(uint8_t mode, uint8_t *offset, int *fndBitRate, uint8_t clk) {
if (g_DemodBufferLen < 64) return false;
for (uint8_t idx = 28; idx < 64; idx++) {
uint8_t si = idx;
if (PackBits(si, 28, g_DemodBuffer) == 0x00) continue;
uint8_t safer = PackBits(si, 4, g_DemodBuffer);
si += 4; //master key
uint8_t resv = PackBits(si, 8, g_DemodBuffer);
si += 8;
// 2nibble must be zeroed.
if (safer != 0x6 && safer != 0x9) continue;
if (resv > 0x00) continue;
//uint8_t pageSel = PackBits(si, 1, g_DemodBuffer); si += 1;
//uint8_t fastWrite = PackBits(si, 1, g_DemodBuffer); si += 1;
si += 1 + 1;
int bitRate = PackBits(si, 6, g_DemodBuffer) * 2 + 2;
si += 6; //bit rate
if (bitRate > 128 || bitRate < 8) continue;
//uint8_t AOR = PackBits(si, 1, g_DemodBuffer); si += 1;
//uint8_t PWD = PackBits(si, 1, g_DemodBuffer); si += 1;
//uint8_t pskcr = PackBits(si, 2, g_DemodBuffer); si += 2; //could check psk cr
//uint8_t inverse = PackBits(si, 1, g_DemodBuffer); si += 1;
si += 1 + 1 + 2 + 1;
uint8_t modread = PackBits(si, 3, g_DemodBuffer);
si += 3;
uint8_t maxBlk = PackBits(si, 3, g_DemodBuffer);
si += 3;
//uint8_t ST = PackBits(si, 1, g_DemodBuffer); si += 1;
if (maxBlk == 0) continue;
//test modulation
if (!testQ5Modulation(mode, modread)) continue;
if (bitRate != clk) continue;
*fndBitRate = convertQ5bitRate(bitRate);
if (*fndBitRate < 0) continue;
*offset = (uint8_t)idx;
return true;
}
return false;
}
static bool testBitRate(uint8_t readRate, uint8_t clk) {
const uint8_t expected[] = {8, 16, 32, 40, 50, 64, 100, 128};
if (expected[readRate] == clk)
return true;
return false;
}
typedef struct {
uint16_t last; // highest offset a whole window fits at
uint32_t val[256];
uint16_t count[256]; // offsets in the buffer holding this same value
bool stride[256]; // and the same value again one block further on
} t55_windows_t;
static void windows_build(t55_windows_t *w) {
memset(w, 0, sizeof(*w));
// offset is stored in a uint8_t, hence the 255 bound
w->last = (g_DemodBufferLen - 32 > 255) ? 255 : (uint16_t)(g_DemodBufferLen - 32);
for (uint16_t i = 0; i <= w->last; i++) {
w->val[i] = PackBits((uint8_t)i, 32, g_DemodBuffer);
}
for (uint16_t i = 0; i <= w->last; i++) {
for (uint16_t j = 0; j <= w->last; j++) {
if (w->val[j] == w->val[i]) {
w->count[i]++;
}
}
if (i + 32 <= w->last) {
w->stride[i] = (w->val[i] == w->val[i + 32]);
}
}
}
// the checks a 32 bit word has to pass to be a configuration block for `mode` at
// `clk`, independent of where in a demodulation it was found
static bool t55xx_block0_plausible(uint32_t block0, uint8_t mode, uint8_t clk, int *fndBitRate) {
uint8_t safer = (block0 >> 28) & 0x0F; //master key
uint8_t resv = (block0 >> 24) & 0x0F; //was 7 // should be only 4 bits if extended mode
// 2nibble must be zeroed.
if (resv > 0x00) {
return false;
}
// The master key is 0, or 6 or 9 to select extended mode
if (safer != 0x0 && safer != 0x6 && safer != 0x9) {
return false;
}
int bitRate = (block0 >> 18) & 0x3F; //bit rate (includes extended mode part of rate)
uint8_t extend = (block0 >> 17) & 0x01; //bit 15 extended mode
uint8_t modread = (block0 >> 12) & 0x1F;
//pskcr = (block0 >> 10) & 0x03; //could check psk cr
//bit 24, 30, 31 are otp, fast write and inverse data - all settable, so no help here
// Bit 14 selects extended mode, and extended mode only exists under master key 6 or 9.
// Set with any other key the word is not a valid configuration
if (extend && safer != 0x6 && safer != 0x9) {
return false;
}
//if extended mode
bool extMode = ((safer == 0x6 || safer == 0x9) && extend) ? true : false;
if (extMode == false) {
if (bitRate > 7) {
return false;
}
if (testBitRate(bitRate, clk) == false) {
return false;
}
} else { //extended mode bitrate = same function to calc bitrate as em4x05
if (EM4x05_GET_BITRATE(bitRate) != clk) {
return false;
}
}
//test modulation
if (testModulation(mode, modread) == false) {
return false;
}
if (fndBitRate) {
*fndBitRate = bitRate;
}
return true;
}
// true when the maxblock and sequence terminator in `block0` could have produced
// an `nblk` block broadcast period
static bool t55xx_block0_fits_period(uint32_t block0, uint8_t nblk) {
// ST adds four bit periods per cycle, so a whole number of 32
// bit blocks rules it out. leans on the measurement, not on
// config.ST, which the psk paths hardcode rather than detect
if (((block0 >> 3) & 1) != 0) {
return false;
}
const uint8_t mb = (block0 >> 5) & 0x07;
return (mb != 0 && (mb % nblk) == 0);
}
// how many distinct rotations of `block0` read as a configuration for `mode` at `clk`. more than
// one means detect's answer was scan order, not evidence. `nblk` 0 skips the period constraint
static size_t t55xx_block0_rotations(uint32_t block0, uint8_t mode, uint8_t clk, uint8_t nblk, uint32_t *only) {
uint32_t seen[32] = {0};
size_t n = 0;
for (uint8_t r = 0; r < 32; r++) {
const uint32_t v = (r == 0) ? block0 : ((block0 << r) | (block0 >> (32 - r)));
if (t55xx_block0_plausible(v, mode, clk, NULL) == false) {
continue;
}
if (nblk != 0 && t55xx_block0_fits_period(v, nblk) == false) {
continue;
}
// a word that is its own rotation offers the same value twice
bool dup = false;
for (size_t i = 0; i < n; i++) {
if (seen[i] == v) {
dup = true;
break;
}
}
if (dup) {
continue;
}
seen[n++] = v;
}
if (n == 1 && only != NULL) {
*only = seen[0];
}
return n;
}
// the clock the detected bit rate names, for the rotation helpers above
static uint8_t t55xx_config_clock(void) {
static const uint8_t basic[] = {8, 16, 32, 40, 50, 64, 100, 128};
return basic[config.bitrate & 0x07];
}
// more than one rotation of the detected block 0 reads as a configuration
static bool t55xx_block0_rotation_ambiguous(void) {
if (config.Q5 || config.block0 == 0) {
return false;
}
return (t55xx_block0_rotations(config.block0, config.modulation, t55xx_config_clock(), 0, NULL) > 1);
}
// Settle which rotation is real with the tag's own broadcast period and re-anchor on it. Nothing
// surviving means the measurement disagrees with every reading, so leave the detected word alone
static bool t55xx_resolve_block0_rotation(uint8_t nblk) {
uint32_t only = 0;
if (t55xx_block0_rotations(config.block0, config.modulation, t55xx_config_clock(), nblk, &only) != 1) {
return false;
}
if (only == config.block0) {
return false;
}
// measuring the period left a regular read in the demod buffer
if (AcquireData(T55x7_PAGE0, T55x7_CONFIGURATION_BLOCK, config.usepwd, config.pwd, config.downlink_mode) == false) {
return false;
}
if (DecodeT55xxBlock() == false) {
return false;
}
if (t55xx_stream_holds(only) == false) {
return false;
}
// keep the offset t55xx_stream_holds just anchored
SetConfigWithBlock0Ex(only, config.offset, config.Q5);
return true;
}
static bool test_scan(uint8_t mode, uint8_t *offset, int *fndBitRate, uint8_t clk,
uint16_t start, uint16_t end, const t55_windows_t *w,
uint16_t need, bool need_stride) {
for (uint16_t idx = start; idx <= end; idx++) {
uint8_t si = (uint8_t)idx;
if (PackBits(si, 28, g_DemodBuffer) == 0x00) {
continue;
}
if (w->count[idx] < need || (need_stride && w->stride[idx] == false)) {
continue;
}
int bitRate = 0;
if (t55xx_block0_plausible(PackBits(si, 32, g_DemodBuffer), mode, clk, &bitRate) == false) {
continue;
}
*fndBitRate = bitRate;
*offset = (uint8_t)idx;
PrintAndLogEx(DEBUG, "DEBUG: (t55xx test) accepted mode %u clk %u rate %d offset %u bits %zu of %zu"
, mode
, clk
, bitRate
, idx
, g_DemodBufferLen
, (clk > 0) ? (g_GraphTraceLen / clk) : 0
);
return true;
}
return false;
}
bool test(uint8_t mode, uint8_t *offset, int *fndBitRate, uint8_t clk, bool *Q5) {
if (g_debugMode) {
PrintAndLogEx(DEBUG, "DEBUG (test) mode %u clk %u dclk %d len %zu : %s", mode, clk, g_DemodClock, g_DemodBufferLen,
sprint_bytebits_bin(g_DemodBuffer, (g_DemodBufferLen > 512) ? 512 : g_DemodBufferLen));
}
// One block is all it takes to carry a configuration. The old floor of 64
// threw away every short demodulation unread, and a manchester rf/128
// block read demodulates to 49 bits - the whole capture is only 93 bit
// periods long.
if (g_DemodBufferLen < 32) {
return false;
}
if (t55xx_demod_yield_ok(clk) == false) {
return false;
}
// Scan as far as the buffer allows rather than stopping at bit 64.
//
// The tag repeats its configuration every 32 bits, so a window of 36
// offsets covers barely one period - and if the first clean copy happens
// to start later than that, because the demodulator dropped a bit early on
// or the buffer opens mid block, the config is simply never looked at.
// A psk1 rf/32 capture had a perfectly good copy sitting past bit 64 while
// detection failed. offset is a uint8_t, hence the 255 bound.
const uint16_t limit = (g_DemodBufferLen - 32 > 255) ? 255 : (uint16_t)(g_DemodBufferLen - 32);
// Where to start.
//
// Starting at 28 skips the ragged first copy, which is the right thing to
// do as long as what is left still spans a whole 32 bit period - that
// needs offsets up to 59, so 91 bits of buffer. Below that, starting at
// 28 means some phases are never looked at at all, and on the manchester
// rf/128 read the one that is never looked at is offset 0, where the
// configuration actually sits.
const uint16_t start = (g_DemodBufferLen >= 92) ? 28 : 0;
t55_windows_t w;
windows_build(&w);
// A word that had every chance to show itself twice and did not is a
// coincidence, and answering with it is worse than not answering. Only a
// buffer too short to have held a second copy gets to fall back on a
// single sighting.
const uint16_t need = (g_DemodBufferLen >= 64) ? 2 : 1;
// Strongest corroboration first: the block stride, then a bare repeat.
const bool stride_pass[2] = { true, false };
for (uint8_t r = 0; r < 2; r++) {
if (test_scan(mode, offset, fndBitRate, clk, start, limit, &w, need, stride_pass[r])) {
*Q5 = false;
return true;
}
}
if (testQ5(mode, offset, fndBitRate, clk)) {
*Q5 = true;
return true;
}
return false;
}
int CmdT55xxSpecial(const char *Cmd) {
CLIParserContext *ctx;
CLIParserInit(&ctx, "lf t55xx special",
"Show block changes with 64 different offsets, data taken from DemodBuffer.",
"lf t55xx special\n"
);
void *argtable[] = {
arg_param_begin,
arg_param_end
};
CLIExecWithReturn(ctx, Cmd, argtable, true);
CLIParserFree(ctx);
uint8_t bits[32] = {0x00};
PrintAndLogEx(NORMAL, "OFFSET | DATA | BINARY | ASCII");
PrintAndLogEx(NORMAL, "-------+-------+-------------------------------------+------");
int i, j = 0;
for (; j < 64; ++j) {
for (i = 0; i < 32; ++i)
bits[i] = g_DemodBuffer[j + i];
uint32_t blockData = PackBits(0, 32, bits);
PrintAndLogEx(NORMAL, "%02d | 0x%08X | %s", j, blockData, sprint_bytebits_bin(bits, 32));
}
return PM3_SUCCESS;
}
// Is `b` a word a T55x7 block 0 could actually hold, with modulation psk3 and
// the bit rate the demodulation settled on?
//
// Only rules that are certainly true are applied - master key, the fixed zero
// bits, the bit rate, modulation field 3, a psk carrier that exists and, where
// it was measured, the one the tag is transmitting on. A candidate list one
// entry too long is harmless; one that has dropped the real word is not, so
// nothing merely probable is tested here.
static bool t55xx_psk3_block0_plausible(uint32_t b, uint8_t clk, uint8_t carrier) {
const uint8_t master = (uint8_t)((b >> 28) & 0x0F);
const bool xmode = (((b >> 17) & 1) != 0) && (master == 0x6 || master == 0x9);
if (xmode) {
if (b & 0x0F000000) {
return false;
}
} else {
if (master != 0x0) {
return false;
}
if (b & 0x0FE00106) {
return false;
}
}
// modulation field 3 is psk3, which is the whole point of the enumeration
if (((b >> 12) & 0x1F) != 0x03) {
return false;
}
// psk carrier 11 is reserved, so a word claiming it is not a real config
if (((b >> 10) & 0x03) == 0x03) {
return false;
}
// the subcarrier was measured off the same waveform, so a candidate naming
// a different one is not this tag's word. 0 = not measured
if (carrier != 0) {
// 4th entry is the reserved carrier 11, which matches no real subcarrier
static const uint8_t pskcf[] = {2, 4, 8, 0};
if (pskcf[(b >> 10) & 0x03] != carrier) {
return false;
}
}
if (xmode) {
return (EM4x05_GET_BITRATE((b >> 18) & 0x3F) == clk);
}
static const uint8_t basic[] = {8, 16, 32, 40, 50, 64, 100, 128};
return (basic[(b >> 18) & 0x07] == clk);
}
static size_t t55xx_psk3_block0_candidates(uint32_t observed, uint8_t clk, uint8_t carrier, uint32_t *out, size_t max) {
if (out == NULL || max == 0 || observed == 0) {
return 0;
}
uint8_t edge[32], nedge = 0;
for (uint8_t i = 0; i < 32; i++) {
if ((observed >> (31 - i)) & 1) {
edge[nedge++] = i;
}
}
uint8_t gap[32];
for (uint8_t k = 0; k < nedge; k++) {
const uint8_t nxt = edge[(k + 1) % nedge];
gap[k] = (nedge == 1) ? 32 : (uint8_t)((32 + nxt - edge[k]) % 32);
if (gap[k] < 2) {
return 0;
}
}
uint8_t len[32];
for (uint8_t k = 0; k < nedge; k++) {
len[k] = 1;
}
size_t found = 0;
for (;;) {
uint32_t d = 0;
for (uint8_t k = 0; k < nedge; k++) {
for (uint8_t t = 0; t < len[k]; t++) {
d |= 1u << (31 - ((edge[k] + t) % 32));
}
}
if (t55xx_psk3_block0_plausible(d, clk, carrier)) {
bool dup = false;
for (size_t i = 0; i < found; i++) {
if (out[i] == d) {
dup = true;
break;
}
}
if (dup == false) {
out[found++] = d;
if (found == max) {
return found;
}
}
}
uint8_t k = 0;
while (k < nedge) {
len[k]++;
if (len[k] < gap[k]) {
break;
}
len[k] = 1;
k++;
}
if (k == nedge) {
break;
}
}
return found;
}
int printConfiguration(t55xx_conf_block_t b) {
// psk3 changes phase on the rising edge, so the psk2 demod both share keeps
// only the leading bit of each run of ones. a word with a legal psk3
// pre-image is consistent with either, so work that out before printing
uint32_t cand[T55XX_PSK3_MAX_CAND];
size_t ncand = 0;
if ((b.modulation == DEMOD_PSK2 || b.modulation == DEMOD_PSK3) && b.Q5 == false) {
static const uint8_t basic[] = {8, 16, 32, 40, 50, 64, 100, 128};
const uint8_t clk = basic[b.bitrate & 0x07];
ncand = t55xx_psk3_block0_candidates(b.block0, clk, b.psk_carrier, cand, ARRAYLEN(cand));
// password mode gates the direct access block 0 was read with, so
// candidates disagreeing with the mode we got in are not this tag's
if (b.pwd_known) {
size_t kept = 0;
for (size_t i = 0; i < ncand; i++) {
if ((((cand[i] >> 4) & 1) != 0) == b.usepwd) {
cand[kept++] = cand[i];
}
}
if (kept) {
ncand = kept;
}
}
// drop candidates whose maxblock could not have produced the measured
// period. none left means the measurement is wrong, so keep the lot
if (b.broadcast_blocks) {
size_t kept = 0;
for (size_t i = 0; i < ncand; i++) {
if (t55xx_block0_fits_period(cand[i], b.broadcast_blocks)) {
cand[kept++] = cand[i];
}
}
if (kept) {
ncand = kept;
}
}
for (size_t i = 1; i < ncand; i++) {
uint32_t v = cand[i];
size_t j = i;
while (j > 0 && cand[j - 1] > v) {
cand[j] = cand[j - 1];
j--;
}
cand[j] = v;
}
}
PrintAndLogEx(INFO, " Chip type......... " _GREEN_("%s"), (b.Q5) ? "Q5/T5555" : "T55x7");
if (ncand > 0 && b.modulation == DEMOD_PSK2) {
// the probe weighs modulation, not any one word
PrintAndLogEx(INFO, " Modulation........ " _YELLOW_("PSK2 or PSK3") " ( ambiguous%s )",
(b.psk3_favoured) ? ", psk3 favoured" : "");
} else {
PrintAndLogEx(INFO, " Modulation........ " _GREEN_("%s"), GetSelectedModulationStr(b.modulation));
}
PrintAndLogEx(INFO, " Bit rate.......... %s", GetBitRateStr(b.bitrate, (b.block0 & T55x7_X_MODE && (b.block0 >> 28 == 6 || b.block0 >> 28 == 9))));
PrintAndLogEx(INFO, " Inverted.......... %s", (b.inverted) ? _GREEN_("Yes") : "No");
PrintAndLogEx(INFO, " Offset............ %d", b.offset);
PrintAndLogEx(INFO, " Seq. terminator... %s", (b.ST) ? _GREEN_("Yes") : "No");
// list the words block 0 could be. where psk3 is settled the demodulated
// value is the collapsed image, which the tag cannot hold, so it is left out
if (ncand > 0 && b.modulation == DEMOD_PSK2) {
// both readings live: demodulated word under psk2, candidates under psk3
PrintAndLogEx(INFO, " Block0............ " _YELLOW_("ambiguous, one of:"));
PrintAndLogEx(INFO, " %08X " _YELLOW_("( psk2 )"), b.block0);
for (size_t i = 0; i < ncand; i++) {
PrintAndLogEx(INFO, " %08X " _YELLOW_("( psk3 )"), cand[i]);
}
} else if (ncand > 0) {
// psk3 settled, more than one word still fits it
PrintAndLogEx(INFO, " Block0............ " _YELLOW_("ambiguous, one of:"));
for (size_t i = 0; i < ncand; i++) {
PrintAndLogEx(INFO, " %08X", cand[i]);
}
} else {
PrintAndLogEx(INFO, " Block0............ %08X %s", b.block0, GetConfigBlock0Source(b.block0Status));
}
PrintAndLogEx(INFO, " Downlink mode..... %s", GetDownlinkModeStr(b.downlink_mode));
PrintAndLogEx(INFO, " Password set...... %s", (b.usepwd) ? _RED_("Yes") : _GREEN_("No"));
if (b.usepwd) {
PrintAndLogEx(INFO, " Password.......... %08X", b.pwd);
}
PrintAndLogEx(NORMAL, "");
return PM3_SUCCESS;
}
static int CmdT55xxWriteBlock(const char *Cmd) {
CLIParserContext *ctx;
CLIParserInit(&ctx, "lf t55xx write",
"Write T55xx block data",
"lf t55xx write -b 3 -d 11223344 --> write 11223344 to block 3\n"
"lf t55xx write -b 3 -d 11223344 --pwd 01020304 --> write 11223344 to block 3, pwd 01020304\n"
"lf t55xx write -b 3 -d 11223344 --pwd 01020304 --verify --> write 11223344 to block 3 and try validating write"
);
// 1 (help) + 6 (six user specified params) + (5 T55XX_DLMODE_SINGLE)
void *argtable[7 + 5] = {
arg_param_begin,
arg_int1("b", "blk", "<0-7>", "block number to write"),
arg_str0("d", "data", "<hex>", "data to write (4 hex bytes)"),
arg_str0("p", "pwd", "<hex>", "password (4 hex bytes)"),
arg_lit0("t", "tm", "test mode write ( " _RED_("danger") " )"),
arg_lit0(NULL, "pg1", "write page 1"),
arg_lit0(NULL, "verify", "try validate data afterward"),
};
uint8_t idx = 7;
arg_add_t55xx_downloadlink(argtable, &idx, T55XX_DLMODE_SINGLE, config.downlink_mode);
CLIExecWithReturn(ctx, Cmd, argtable, true);
int block = arg_get_int_def(ctx, 1, REGULAR_READ_MODE_BLOCK);
uint32_t data = 0; // default to blank Block
int res = arg_get_u32_hexstr_def_nlen(ctx, 2, 0, &data, 4, true);
if (res == 0 || res == 2) {
PrintAndLogEx(ERR, "data must be 4 hex bytes");
CLIParserFree(ctx);
return PM3_EINVARG;
}
bool usepwd = false;
uint32_t password = 0; // default to blank Block 7
res = arg_get_u32_hexstr_def_nlen(ctx, 3, 0, &password, 4, true);
if (res == 0 || res == 2) {
PrintAndLogEx(ERR, "Password should be 4 hex bytes");
CLIParserFree(ctx);
return PM3_EINVARG;
}
if (res == 1) {
usepwd = true;
}
bool testmode = arg_get_lit(ctx, 4);
bool page1 = arg_get_lit(ctx, 5);
bool validate = arg_get_lit(ctx, 6);
bool r0 = arg_get_lit(ctx, 7);
bool r1 = arg_get_lit(ctx, 8);
bool r2 = arg_get_lit(ctx, 9);
bool r3 = arg_get_lit(ctx, 10);
CLIParserFree(ctx);
if ((r0 + r1 + r2 + r3) > 1) {
PrintAndLogEx(FAILED, "Error multiple downlink encoding");
return PM3_EINVARG;
}
uint8_t downlink_mode = config.downlink_mode;
if (r0)
downlink_mode = refFixedBit;
else if (r1)
downlink_mode = refLongLeading;
else if (r2)
downlink_mode = refLeading0;
else if (r3)
downlink_mode = ref1of4;
if (block > 7 && block != REGULAR_READ_MODE_BLOCK) {
PrintAndLogEx(NORMAL, "Block must be between 0 and 7");
return PM3_ESOFT;
}
char pwdstr[16] = {0};
snprintf(pwdstr, sizeof(pwdstr), "pwd: 0x%08X", password);
PrintAndLogEx(INFO, "Writing page %d block: %02d data: 0x%08X %s", page1, block, data, (usepwd) ? pwdstr : "");
if (t55xxWrite(block, page1, usepwd, testmode, password, downlink_mode, data) != PM3_SUCCESS) {
PrintAndLogEx(ERR, "Write failed");
return PM3_ESOFT;
}
if (validate) {
bool isOK = t55xxVerifyWrite(block, page1, usepwd, 1, password, downlink_mode, data);
if (isOK)
PrintAndLogEx(SUCCESS, "Write OK, validation successful");
else
PrintAndLogEx(WARNING, "Write could not validate the written data");
}
return PM3_SUCCESS;
}
static int CmdT55xxDangerousRaw(const char *Cmd) {
CLIParserContext *ctx;
CLIParserInit(&ctx, "lf t55xx dangerraw",
"This command allows to emit arbitrary raw commands on T5577 and cut the field after arbitrary duration.\n"
"Uncontrolled usage can easily write an invalid configuration, activate lock bits,\n"
"OTP bit, password protection bit, deactivate test-mode, lock your card forever.\n"
_RED_("WARNING:") _CYAN_(" this may lock definitively the tag in an unusable state!"),
"lf t55xx dangerraw -d 01000000000000010000100000000100000000 -t 3200\n"
);
void *argtable[] = {
arg_param_begin,
arg_str1("d", "data", "<bitstr>", "raw bit string"),
arg_int1("t", "time", "<us>", "<0 - 200000> time in microseconds before dropping the field"),
arg_param_end
};
CLIExecWithReturn(ctx, Cmd, argtable, true);
// supports only default downlink mode
t55xx_test_block_t ng;
ng.time = 0;
ng.bitlen = 0;
memset(ng.data, 0x00, sizeof(ng.data));
uint8_t bin[129] = {0};
int bin_len = sizeof(bin) - 1; // CLIGetStrWithReturn does not guarantee string to be null-terminated
CLIGetStrWithReturn(ctx, 1, bin, &bin_len);
ng.time = arg_get_int_def(ctx, 2, 0);
CLIParserFree(ctx);
if (ng.time == 0 || ng.time > 200000) {
PrintAndLogEx(ERR, "Timing off 1..200000 limits, got %i", ng.time);
return PM3_EINVARG;
}
int bs_len = binstr_2_binarray(ng.data, (char *)bin, bin_len);
if (bs_len == 0) {
return PM3_EINVARG;
}
ng.bitlen = bs_len;
PacketResponseNG resp;
clearCommandBuffer();
SendCommandNG(CMD_LF_T55XX_DANGERRAW, (uint8_t *)&ng, sizeof(ng));
if (WaitForResponseTimeout(CMD_LF_T55XX_DANGERRAW, &resp, 2000) == false) {
PrintAndLogEx(ERR, "Error occurred, device did not ACK write operation.");
return PM3_ETIMEOUT;
}
return resp.status;
}
static int CmdT55xxReadTrace(const char *Cmd) {
CLIParserContext *ctx;
CLIParserInit(&ctx, "lf t55xx trace",
"Show T55x7 configuration data (page 0/ blk 0) from reading the configuration block",
"lf t55xx trace\n"
"lf t55xx trace -1"
);
// 1 (help) + 1 (one user specified params) + (5 T55XX_DLMODE_SINGLE)
void *argtable[2 + 5] = {
arg_param_begin,
arg_lit0("1", NULL, "extract using data from graphbuffer"),
};
uint8_t idx = 2;
arg_add_t55xx_downloadlink(argtable, &idx, T55XX_DLMODE_SINGLE, config.downlink_mode);
CLIExecWithReturn(ctx, Cmd, argtable, true);
bool use_gb = arg_get_lit(ctx, 1);
bool r0 = arg_get_lit(ctx, 2);
bool r1 = arg_get_lit(ctx, 3);
bool r2 = arg_get_lit(ctx, 4);
bool r3 = arg_get_lit(ctx, 5);
CLIParserFree(ctx);
if ((r0 + r1 + r2 + r3) > 1) {
PrintAndLogEx(FAILED, "Error multiple downlink encoding");
return PM3_EINVARG;
}
uint8_t downlink_mode = config.downlink_mode;
if (r0)
downlink_mode = refFixedBit;
else if (r1)
downlink_mode = refLongLeading;
else if (r2)
downlink_mode = refLeading0;
else if (r3)
downlink_mode = ref1of4;
if (use_gb == false) {
// sanity check.
if (SanityOfflineCheck(false) != PM3_SUCCESS) return PM3_ENODATA;
bool pwdmode = false;
uint32_t password = 0;
// REGULAR_READ_MODE_BLOCK - yields correct Page 1 Block 2 data i.e. + 32 bit offset.
if (!AcquireData(T55x7_PAGE1, REGULAR_READ_MODE_BLOCK, pwdmode, password, downlink_mode))
return PM3_ENODATA;
}
if (config.Q5) {
if (DecodeT5555TraceBlock() == false) {
return PM3_ESOFT;
}
} else {
if (DecodeT55xxBlock() == false) {
return PM3_ESOFT;
}
}
if (g_DemodBufferLen == 0) {
return PM3_ESOFT;
}
RepaintGraphWindow();
uint8_t repeat = (config.offset > 5) ? 32 : 0;
uint8_t si = config.offset + repeat;
uint32_t bl1 = PackBits(si, 32, g_DemodBuffer);
uint32_t bl2 = PackBits(si + 32, 32, g_DemodBuffer);
if (config.Q5) {
uint32_t hdr = PackBits(si, 9, g_DemodBuffer);
si += 9;
if (hdr != 0x1FF) {
PrintAndLogEx(FAILED, "Invalid Q5/T5555 Trace data header (expected 0x1FF, found %X)", hdr);
return PM3_ESOFT;
}
t5555_tracedata_t data = {.bl1 = bl1, .bl2 = bl2, .icr = 0, .lotidc = '?', .lotid = 0, .wafer = 0, .dw = 0};
data.icr = PackBits(si, 2, g_DemodBuffer);
si += 2;
data.lotidc = 'Z' - PackBits(si, 2, g_DemodBuffer);
si += 3;
data.lotid = PackBits(si, 4, g_DemodBuffer);
si += 5;
data.lotid <<= 4;
data.lotid |= PackBits(si, 4, g_DemodBuffer);
si += 5;
data.lotid <<= 4;
data.lotid |= PackBits(si, 4, g_DemodBuffer);
si += 5;
data.lotid <<= 4;
data.lotid |= PackBits(si, 4, g_DemodBuffer);
si += 5;
data.lotid <<= 1;
data.lotid |= PackBits(si, 1, g_DemodBuffer);
si += 1;
data.wafer = PackBits(si, 3, g_DemodBuffer);
si += 4;
data.wafer <<= 2;
data.wafer |= PackBits(si, 2, g_DemodBuffer);
si += 2;
data.dw = PackBits(si, 2, g_DemodBuffer);
si += 3;
data.dw <<= 4;
data.dw |= PackBits(si, 4, g_DemodBuffer);
si += 5;
data.dw <<= 4;
data.dw |= PackBits(si, 4, g_DemodBuffer);
si += 5;
data.dw <<= 4;
data.dw |= PackBits(si, 4, g_DemodBuffer);
printT5555Trace(data, repeat);
} else {
t55x7_tracedata_t data = {.bl1 = bl1, .bl2 = bl2, .acl = 0, .mfc = 0, .cid = 0, .year = 0, .quarter = 0, .icr = 0, .lotid = 0, .wafer = 0, .dw = 0};
data.acl = PackBits(si, 8, g_DemodBuffer);
si += 8;
if (data.acl != 0xE0) {
PrintAndLogEx(FAILED, "The modulation is most likely wrong since the ACL is not 0xE0. ");
return PM3_ESOFT;
}
data.mfc = PackBits(si, 8, g_DemodBuffer);
si += 8;
data.cid = PackBits(si, 5, g_DemodBuffer);
si += 5;
data.icr = PackBits(si, 3, g_DemodBuffer);
si += 3;
data.year = PackBits(si, 4, g_DemodBuffer);
si += 4;
data.quarter = PackBits(si, 2, g_DemodBuffer);
si += 2;
data.lotid = PackBits(si, 14, g_DemodBuffer);
si += 14;
data.wafer = PackBits(si, 5, g_DemodBuffer);
si += 5;
data.dw = PackBits(si, 15, g_DemodBuffer);
struct tm *ct, tm_buf;
time_t now = time(NULL);
#if defined(_WIN32)
ct = localtime_s(&tm_buf, &now) == 0 ? &tm_buf : NULL;
#else
ct = localtime_r(&now, &tm_buf);
#endif
if (ct != NULL && (data.year > ct->tm_year - 110))
data.year += 2000;
else
data.year += 2010;
printT55x7Trace(data, repeat);
}
return PM3_SUCCESS;
}
void printT55x7Trace(t55x7_tracedata_t data, uint8_t repeat) {
PrintAndLogEx(INFO, "--- " _CYAN_("T55x7 Trace Information") " ----------------------------------");
PrintAndLogEx(INFO, " ACL Allocation class (ISO/IEC 15963-1) : 0x%02X ( %d )", data.acl, data.acl);
PrintAndLogEx(INFO, " MFC Manufacturer ID (ISO/IEC 7816-6) : 0x%02X ( %d ) - %s", data.mfc, data.mfc, getTagInfo(data.mfc));
PrintAndLogEx(INFO, " CID : 0x%02X ( %d ) - %s", data.cid, data.cid, GetModelStrFromCID(data.cid));
PrintAndLogEx(INFO, " ICR IC Revision : %d", data.icr);
PrintAndLogEx(INFO, " Manufactured");
PrintAndLogEx(INFO, " Year/Quarter... %d/%d", data.year, data.quarter);
PrintAndLogEx(INFO, " Lot ID......... %d", data.lotid);
PrintAndLogEx(INFO, " Wafer number... %d", data.wafer);
PrintAndLogEx(INFO, " Die Number..... %d", data.dw);
PrintAndLogEx(INFO, "-------------------------------------------------------------");
PrintAndLogEx(INFO, " Raw Data - Page 1");
PrintAndLogEx(INFO, " Block 1... %08X - %s", data.bl1, sprint_bytebits_bin(g_DemodBuffer + config.offset + repeat, 32));
PrintAndLogEx(INFO, " Block 2... %08X - %s", data.bl2, sprint_bytebits_bin(g_DemodBuffer + config.offset + repeat + 32, 32));
PrintAndLogEx(NORMAL, "");
/*
Trace info.
M1, M2 has the about ATMEL definition of trace data.
M3 has unique format following industry defacto standard with row/col parity
TRACE - BLOCK O
Bits Definition HEX
1-8 ACL Allocation class (ISO/IEC 15963-1) 0xE0
9-16 MFC Manufacturer ID (ISO/IEC 7816-6) 0x15 Atmel Corporation
17-21 CID 0x1 = Atmel ATA5577M1
0x2 = Atmel ATA5577M2
0x3 = Atmel ATA5577M3
22-24 ICR IC revision
25-28 YEAR (BCD encoded) 9 (= 2009)
29-30 QUARTER 1,2,3,4
31-32 LOT ID
TRACE - BLOCK 1
1-12 LOT ID
13-17 Wafer number
18-32 DW, die number sequential
Startup times (FC)
M1, M2 = 192
M3 = 128
*/
}
void printT5555Trace(t5555_tracedata_t data, uint8_t repeat) {
PrintAndLogEx(INFO, "--- " _CYAN_("Q5/T5555 Trace Information") " ---------------------------");
PrintAndLogEx(INFO, " ICR IC Revision.... %d", data.icr);
PrintAndLogEx(INFO, " Lot ID......... %c%d", data.lotidc, data.lotid);
PrintAndLogEx(INFO, " Wafer number... %d", data.wafer);
PrintAndLogEx(INFO, " Die Number..... %d", data.dw);
PrintAndLogEx(INFO, "-------------------------------------------------------------");
PrintAndLogEx(INFO, " Raw Data - Page 1");
PrintAndLogEx(INFO, " Block 1... %08X - %s", data.bl1, sprint_bytebits_bin(g_DemodBuffer + config.offset + repeat, 32));
PrintAndLogEx(INFO, " Block 2... %08X - %s", data.bl2, sprint_bytebits_bin(g_DemodBuffer + config.offset + repeat + 32, 32));
/*
** Q5 **
TRACE - BLOCK O and BLOCK1
Bits Definition HEX
1-9 Header 0x1FF
10-11 IC Revision
12-13 Lot ID char
15-35 Lot ID (NB parity)
36-41 Wafer number (NB parity)
42-58 DW, die number sequential (NB parity)
60-63 Parity bits
64 Always zero
*/
}
static void printT5x7KnownBlock0(uint32_t b0) {
char s[40];
memset(s, 0, sizeof(s));
switch (b0) {
case T55X7_DEFAULT_CONFIG_BLOCK:
snprintf(s, sizeof(s) - strlen(s), "T55x7 Default ");
break;
case T55X7_RAW_CONFIG_BLOCK:
snprintf(s + strlen(s), sizeof(s) - strlen(s), "T55x7 Raw ");
break;
case T55X7_EM_UNIQUE_CONFIG_BLOCK:
snprintf(s + strlen(s), sizeof(s) - strlen(s), "EM unique, Paxton ");
break;
case T55X7_FDXB_2_CONFIG_BLOCK:
case T55X7_FDXB_CONFIG_BLOCK:
snprintf(s + strlen(s), sizeof(s) - strlen(s), "FDXB ");
break;
case T55X7_HID_26_CONFIG_BLOCK:
snprintf(s + strlen(s), sizeof(s) - strlen(s), "HID 26b (ProxCard), Paradox, AWID ");
break;
case T55X7_PYRAMID_CONFIG_BLOCK:
snprintf(s + strlen(s), sizeof(s) - strlen(s), "Pyramid ");
break;
case T55X7_INDALA_64_CONFIG_BLOCK:
snprintf(s + strlen(s), sizeof(s) - strlen(s), "Indala 64, Motorola, Idteck");
break;
case T55X7_INDALA_224_CONFIG_BLOCK:
snprintf(s + strlen(s), sizeof(s) - strlen(s), "Indala 224 ");
break;
case T55X7_GUARDPROXII_CONFIG_BLOCK:
snprintf(s + strlen(s), sizeof(s) - strlen(s), "Guard Prox II ");
break;
case T55X7_VIKING_CONFIG_BLOCK:
snprintf(s + strlen(s), sizeof(s) - strlen(s), "Viking ");
break;
case T55X7_NORALSY_CONFIG_BLOCK:
snprintf(s + strlen(s), sizeof(s) - strlen(s), "Noralys ");
break;
case T55X7_IOPROX_CONFIG_BLOCK:
snprintf(s + strlen(s), sizeof(s) - strlen(s), "IO Prox ");
break;
case T55X7_PRESCO_CONFIG_BLOCK:
snprintf(s + strlen(s), sizeof(s) - strlen(s), "Presco ");
break;
case T55X7_NEDAP_64_CONFIG_BLOCK:
snprintf(s + strlen(s), sizeof(s) - strlen(s), "Nedap 64 ");
break;
case T55X7_NEDAP_128_CONFIG_BLOCK:
snprintf(s + strlen(s), sizeof(s) - strlen(s), "Nedap 128 ");
break;
case T55X7_PAC_CONFIG_BLOCK:
snprintf(s + strlen(s), sizeof(s) - strlen(s), "PAC/Stanley ");
break;
case T55X7_VERICHIP_CONFIG_BLOCK:
snprintf(s + strlen(s), sizeof(s) - strlen(s), "Verichip ");
break;
case T55X7_VISA2000_CONFIG_BLOCK:
snprintf(s + strlen(s), sizeof(s) - strlen(s), "VISA2000 ");
break;
case T55X7_JABLOTRON_CONFIG_BLOCK:
snprintf(s + strlen(s), sizeof(s) - strlen(s), "Jablotron ");
break;
case T55X7_KERI_CONFIG_BLOCK:
snprintf(s + strlen(s), sizeof(s) - strlen(s), "KERI ");
break;
case T55X7_SECURAKEY_CONFIG_BLOCK:
snprintf(s + strlen(s), sizeof(s) - strlen(s), "SecuraKey ");
break;
case T55X7_NEXWATCH_CONFIG_BLOCK:
snprintf(s + strlen(s), sizeof(s) - strlen(s), "NexWatch, Quadrakey ");
break;
case T55X7_PYRONIX_CONFIG_BLOCK:
snprintf(s + strlen(s), sizeof(s) - strlen(s), "Pyronix ");
break;
case T55X7_TEXECOM_CONFIG_BLOCK:
snprintf(s + strlen(s), sizeof(s) - strlen(s), "Texecom ");
break;
case T55X7_BETECH_CONFIG_BLOCK:
snprintf(s + strlen(s), sizeof(s) - strlen(s), "Be-Tech ");
break;
default:
break;
}
if (strlen(s) > 0) {
PrintAndLogEx(SUCCESS, "Config block match... " _YELLOW_("%s"), s);
}
}
static int CmdT55xxInfo(const char *Cmd) {
CLIParserContext *ctx;
CLIParserInit(&ctx, "lf t55xx info",
"Show T55x7 configuration data (page 0/ blk 0) from reading the configuration block\n"
"from tag. Use `-c` to specify a config block data to be used instead of reading tag.",
"lf t55xx info\n"
"lf t55xx info -1\n"
"lf t55xx info -p 11223344\n"
"lf t55xx info -c 00083040\n"
"lf t55xx info -c 6001805A --q5"
);
// 1 (help) + 4 (four user specified params) + (5 T55XX_DLMODE_SINGLE)
void *argtable[5 + 5] = {
arg_param_begin,
arg_lit0("1", NULL, "extract using data from graphbuffer"),
arg_str0("p", "pwd", "<hex>", "password (4 hex bytes)"),
arg_str0("c", "blk0", "<hex>", "use these data instead (4 hex bytes)"),
arg_lit0(NULL, "q5", "interprete provided data as T5555/Q5 config"),
};
uint8_t idx = 5;
arg_add_t55xx_downloadlink(argtable, &idx, T55XX_DLMODE_SINGLE, config.downlink_mode);
CLIExecWithReturn(ctx, Cmd, argtable, true);
bool use_gb = arg_get_lit(ctx, 1);
bool usepwd = false;
uint32_t password = 0;
int res = arg_get_u32_hexstr_def_nlen(ctx, 2, 0, &password, 4, true);
if (res == 0 || res == 2) {
PrintAndLogEx(ERR, "Password must be 4 hex bytes");
CLIParserFree(ctx);
return PM3_EINVARG;
}
if (res == 1) {
usepwd = true;
}
bool gotdata = false;
uint32_t block0 = 0;
res = arg_get_u32_hexstr_def_nlen(ctx, 3, 0, &block0, 4, true);
if (res == 0 || res == 2) {
PrintAndLogEx(ERR, "block0 data must be 4 hex bytes");
CLIParserFree(ctx);
return PM3_EINVARG;
}
if (res == 1) {
gotdata = true;
}
bool dataasq5 = arg_get_lit(ctx, 4);
bool r0 = arg_get_lit(ctx, 5);
bool r1 = arg_get_lit(ctx, 6);
bool r2 = arg_get_lit(ctx, 7);
bool r3 = arg_get_lit(ctx, 8);
CLIParserFree(ctx);
if (gotdata && use_gb) {
PrintAndLogEx(FAILED, "Must select one of user supplied data and use graphbuffer");
return PM3_EINVARG;
}
if (dataasq5 && gotdata == false) {
PrintAndLogEx(FAILED, "Must specify user supplied Q5 data");
return PM3_EINVARG;
}
if ((r0 + r1 + r2 + r3) > 1) {
PrintAndLogEx(FAILED, "Error multiple downlink encoding");
return PM3_EINVARG;
}
uint8_t downlink_mode = config.downlink_mode;
if (r0)
downlink_mode = refFixedBit;
else if (r1)
downlink_mode = refLongLeading;
else if (r2)
downlink_mode = refLeading0;
else if (r3)
downlink_mode = ref1of4;
/*
Page 0 Block 0 Configuration data.
Normal mode
Extended mode
*/
if (use_gb == false && gotdata == false) {
// sanity check.
if (SanityOfflineCheck(false) != PM3_SUCCESS) {
return PM3_ENODATA;
}
if (!AcquireData(T55x7_PAGE0, T55x7_CONFIGURATION_BLOCK, usepwd, password, downlink_mode)) {
return PM3_ENODATA;
}
}
if (gotdata == false) {
if (DecodeT55xxBlock() == false) {
return PM3_ESOFT;
}
uint8_t boff = 0;
if (t55xx_demod_offset(&boff) == false) {
return PM3_ESOFT;
}
block0 = PackBits(0, 32, g_DemodBuffer + boff);
}
PrintAndLogEx(NORMAL, "");
if (((!gotdata) && config.Q5) || (gotdata && dataasq5)) {
uint32_t header = (block0 >> (32 - 12)) & 0xFFF;
uint32_t ps = (block0 >> (32 - 13)) & 0x01;
uint32_t fw = (block0 >> (32 - 14)) & 0x01;
uint32_t dbr = (block0 >> (32 - 20)) & 0x3F;
uint32_t aor = (block0 >> (32 - 21)) & 0x01;
uint32_t pwd = (block0 >> (32 - 22)) & 0x01;
uint32_t pskcf = (block0 >> (32 - 24)) & 0x03;
uint32_t inv = (block0 >> (32 - 25)) & 0x01;
uint32_t datamod = (block0 >> (32 - 28)) & 0x07;
uint32_t maxblk = (block0 >> (32 - 31)) & 0x07;
uint32_t st = block0 & 0x01;
PrintAndLogEx(INFO, "--- " _CYAN_("Q5 Configuration & Information") " ------------");
PrintAndLogEx(INFO, " Header : 0x%03X%s", header, (header != 0x600) ? _RED_(" - Warning") : "");
PrintAndLogEx(INFO, " Page select : %d", ps);
PrintAndLogEx(INFO, " Fast Write : %s", (fw) ? _GREEN_("Yes") : "No");
PrintAndLogEx(INFO, " Data bit rate : %s", GetBitRateStr(dbr, 1));
PrintAndLogEx(INFO, " AOR - Answer on Request : %s", (aor) ? _GREEN_("Yes") : "No");
PrintAndLogEx(INFO, " Password mode : %s", (pwd) ? _GREEN_("Yes") : "No");
PrintAndLogEx(INFO, " PSK clock frequency : %s", GetPskCfStr(pskcf, 1));
PrintAndLogEx(INFO, " Inverse data : %s", (inv) ? _GREEN_("Yes") : "No");
PrintAndLogEx(INFO, " Modulation : %s", GetQ5ModulationStr(datamod));
PrintAndLogEx(INFO, " Max block : %d", maxblk);
PrintAndLogEx(INFO, " Sequence Terminator : %s", (st) ? _GREEN_("Yes") : "No");
} else {
uint32_t safer = (block0 >> (32 - 4)) & 0x0F;
uint32_t extend = (block0 >> (32 - 15)) & 0x01;
uint32_t resv, dbr;
if (extend) {
resv = (block0 >> (32 - 8)) & 0x0F;
dbr = (block0 >> (32 - 14)) & 0x3F;
} else {
resv = (block0 >> (32 - 11)) & 0x7F;
dbr = (block0 >> (32 - 14)) & 0x07;
}
uint32_t datamod = (block0 >> (32 - 20)) & 0x1F;
uint32_t pskcf = (block0 >> (32 - 22)) & 0x03;
uint32_t aor = (block0 >> (32 - 23)) & 0x01;
uint32_t otp = (block0 >> (32 - 24)) & 0x01;
uint32_t maxblk = (block0 >> (32 - 27)) & 0x07;
uint32_t pwd = (block0 >> (32 - 28)) & 0x01;
uint32_t sst = (block0 >> (32 - 29)) & 0x01;
uint32_t fw = (block0 >> (32 - 30)) & 0x01;
uint32_t inv = (block0 >> (32 - 31)) & 0x01;
uint32_t por = (block0 >> (32 - 32)) & 0x01;
PrintAndLogEx(INFO, "--- " _CYAN_("T55x7 Configuration & Information") " ---------");
PrintAndLogEx(INFO, " Safer key : %s", GetSaferStr(safer));
PrintAndLogEx(INFO, " reserved : %d", resv);
PrintAndLogEx(INFO, " Data bit rate : %s", GetBitRateStr(dbr, extend));
PrintAndLogEx(INFO, " eXtended mode : %s", (extend) ? _YELLOW_("Yes - Warning") : "No");
PrintAndLogEx(INFO, " Modulation : %s", GetModulationStr(datamod, extend));
PrintAndLogEx(INFO, " PSK clock frequency : %s", GetPskCfStr(pskcf, 0));
PrintAndLogEx(INFO, " AOR - Answer on Request : %s", (aor) ? _GREEN_("Yes") : "No");
PrintAndLogEx(INFO, " OTP - One Time Pad : %s", (otp) ? ((extend) ? _YELLOW_("Yes - Warning") : _RED_("Yes - Warning")) : "No");
PrintAndLogEx(INFO, " Max block : %d", maxblk);
PrintAndLogEx(INFO, " Password mode : %s", (pwd) ? _GREEN_("Yes") : "No");
PrintAndLogEx(INFO, " Sequence %-12s : %s", (extend) ? "Start Marker" : "Terminator", (sst) ? _GREEN_("Yes") : "No");
PrintAndLogEx(INFO, " Fast Write : %s", (fw) ? ((extend) ? _GREEN_("Yes") : _RED_("Yes - Warning")) : "No");
PrintAndLogEx(INFO, " Inverse data : %s", (inv) ? ((extend) ? _GREEN_("Yes") : _RED_("Yes - Warning")) : "No");
PrintAndLogEx(INFO, " POR-Delay : %s", (por) ? _GREEN_("Yes") : "No");
}
PrintAndLogEx(INFO, "-------------------------------------------------------------");
PrintAndLogEx(INFO, " Raw Data - Page 0, block 0");
if (gotdata)
PrintAndLogEx(INFO, " " _GREEN_("%08X"), block0);
else
PrintAndLogEx(INFO, " " _GREEN_("%08X") " - %s", block0, sprint_bytebits_bin(g_DemodBuffer + config.offset, 32));
if (((!gotdata) && (!config.Q5)) || (gotdata && (!dataasq5))) {
PrintAndLogEx(INFO, "--- " _CYAN_("Fingerprint") " ------------");
printT5x7KnownBlock0(block0);
}
PrintAndLogEx(NORMAL, "");
//PrintAndLogEx(INFO, "-------------------------------------------------------------");
return PM3_SUCCESS;
}
static int CmdT55xxDump(const char *Cmd) {
CLIParserContext *ctx;
CLIParserInit(&ctx, "lf t55xx dump",
"This command dumps a T55xx card Page 0 block 0-7.\n"
"It will create two files (bin/json)",
"lf t55xx dump\n"
"lf t55xx dump -p aabbccdd --override\n"
"lf t55xx dump -f my_lf_dump"
);
// 1 (help) + 4 (two user specified params) + (5 T55XX_DLMODE_SINGLE)
void *argtable[5 + 5] = {
arg_param_begin,
arg_str0("f", "file", "<fn>", "filename (default is generated on blk 0)"),
arg_lit0("o", "override", "override, force pwd read despite danger to card"),
arg_str0("p", "pwd", "<hex>", "password (4 hex bytes)"),
arg_lit0(NULL, "ns", "no save to file"),
};
uint8_t idx = 5;
arg_add_t55xx_downloadlink(argtable, &idx, T55XX_DLMODE_SINGLE, T55XX_DLMODE_SINGLE);
CLIExecWithReturn(ctx, Cmd, argtable, true);
int fnlen = 0;
char filename[FILE_PATH_SIZE] = {0};
CLIParamStrToBuf(arg_get_str(ctx, 1), (uint8_t *)filename, FILE_PATH_SIZE, &fnlen);
uint8_t override = arg_get_lit(ctx, 2) ? 1 : 0;
bool usepwd = false;
uint32_t password = 0;
int res = arg_get_u32_hexstr_def_nlen(ctx, 3, 0, &password, 4, true);
if (res == 0 || res == 2) {
PrintAndLogEx(ERR, "Password should be 4 hex bytes");
CLIParserFree(ctx);
return PM3_EINVARG;
}
if (res == 1) {
usepwd = true;
}
bool nosave = arg_get_lit(ctx, 4);
bool r0 = arg_get_lit(ctx, 5);
bool r1 = arg_get_lit(ctx, 6);
bool r2 = arg_get_lit(ctx, 7);
bool r3 = arg_get_lit(ctx, 8);
CLIParserFree(ctx);
if ((r0 + r1 + r2 + r3) > 1) {
PrintAndLogEx(FAILED, "Error multiple downlink encoding");
return PM3_EINVARG;
}
uint8_t downlink_mode = config.downlink_mode;
if (r0)
downlink_mode = refFixedBit;
else if (r1)
downlink_mode = refLongLeading;
else if (r2)
downlink_mode = refLeading0;
else if (r3)
downlink_mode = ref1of4;
bool success = true;
PrintAndLogEx(NORMAL, "");
PrintAndLogEx(INFO, "------------------------- " _CYAN_("T55xx tag memory") " -----------------------------");
// Due to the few different T55xx cards and number of blocks supported
// will save the dump file if ALL page 0 is OK
printT5xxHeader(0);
for (uint8_t i = 0; i < 8; ++i) {
if (T55xxReadBlock(i, 0, usepwd, override, password, downlink_mode) != PM3_SUCCESS) {
success = false;
}
// only show override warning on the first block read
if (override == 1) {
override++;
}
}
printT5xxHeader(1);
for (uint8_t i = 0; i < 4; i++) {
if (T55xxReadBlock(i, 1, usepwd, override, password, downlink_mode) != PM3_SUCCESS) {
T55x7_SaveBlockData(8 + i, 0x00);
}
}
if (nosave) {
PrintAndLogEx(INFO, "Called with no save option");
PrintAndLogEx(NORMAL, "");
return PM3_SUCCESS;
}
// all ok, save dump to file
if (success) {
// set default filename, if not set by user
if (strlen(filename) == 0) {
strcpy(filename, "lf-t55xx");
for (uint8_t i = 1; i <= 7; i++) {
if ((cardmem[i].blockdata != 0x00) && (cardmem[i].blockdata != 0xFFFFFFFF)) {
snprintf(filename + strlen(filename), sizeof(filename) - strlen(filename), "-%08X", cardmem[i].blockdata);
} else {
break;
}
}
strcat(filename, "-dump");
}
// Swap endian so the files match the txt display
uint32_t data[T55x7_BLOCK_COUNT] = {0};
for (int i = 0; i < T55x7_BLOCK_COUNT; i++) {
data[i] = BSWAP_32(cardmem[i].blockdata);
}
pm3_save_dump(filename, (uint8_t *)data, (T55x7_BLOCK_COUNT * sizeof(uint32_t)), jsfT55x7);
}
return PM3_SUCCESS;
}
static int CmdT55xxRestore(const char *Cmd) {
CLIParserContext *ctx;
CLIParserInit(&ctx, "lf t55xx restore",
"Restore T55xx card page 0/1 n blocks from (bin/eml/json) dump file",
"lf t55xx restore -f lf-t55xx-00148040-dump.bin"
);
void *argtable[] = {
arg_param_begin,
arg_str0("f", "file", "<fn>", "Specify a filename for dump file"),
arg_str0("p", "pwd", "<hex>", "password if target card has password set (4 hex bytes)"),
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);
bool usepwd = false;
uint32_t password = 0;
int res = arg_get_u32_hexstr_def_nlen(ctx, 2, 0, &password, 4, true);
if (res == 0 || res == 2) {
PrintAndLogEx(ERR, "Password should be 4 hex bytes");
CLIParserFree(ctx);
return PM3_EINVARG;
}
if (res == 1) {
usepwd = true;
}
CLIParserFree(ctx);
if (fnlen == 0) {
PrintAndLogEx(ERR, "Must specify a filename");
return PM3_EINVARG;
}
// read dump file
uint32_t *dump = NULL;
size_t bytes_read = 0;
res = pm3_load_dump(filename, (void **)&dump, &bytes_read, (T55x7_BLOCK_COUNT * 4));
if (res != PM3_SUCCESS) {
return res;
}
if (bytes_read != (T55x7_BLOCK_COUNT * 4)) {
free(dump);
PrintAndLogEx(FAILED, "wrong length of dump file. Expected 48 bytes, got %zu", bytes_read);
return PM3_EFILE;
}
// 12 blocks * 4 bytes per block
// this part creates strings to call "lf t55 write" command.
PrintAndLogEx(INFO, "Starting to write...");
uint8_t downlink_mode;
char wcmd[100];
char pwdopt [14] = {0}; // p XXXXXXXX
if (usepwd) {
snprintf(pwdopt, sizeof(pwdopt), "-p %08X", password);
}
uint8_t idx;
// Restore endien for writing to card
for (idx = 0; idx < 12; idx++) {
dump[idx] = BSWAP_32(dump[idx]);
}
// Have data ready, lets write
// Order
// write blocks 1..7 page 0
// write blocks 1..3 page 1
// update downlink mode (if needed) and write b 0
downlink_mode = 0;
if ((((dump[11] >> 28) & 0xF) == 6) || (((dump[11] >> 28) & 0xF) == 9))
downlink_mode = (dump[11] >> 10) & 3;
// write out blocks 1-7 page 0
for (idx = 1; idx <= 7; idx++) {
snprintf(wcmd, sizeof(wcmd), "-b %d -d %08X %s", idx, dump[idx], pwdopt);
if (CmdT55xxWriteBlock(wcmd) != PM3_SUCCESS) {
PrintAndLogEx(WARNING, "Warning: error writing blk %d", idx);
}
}
// if password was set on the "blank" update as we may have just changed it
if (usepwd) {
snprintf(pwdopt, sizeof(pwdopt), "-p %08X", dump[7]);
}
// write out blocks 1-3 page 1
for (idx = 9; idx <= 11; idx++) {
snprintf(wcmd, sizeof(wcmd), "-b %d --pg1 -d %08X %s", idx - 8, dump[idx], pwdopt);
if (CmdT55xxWriteBlock(wcmd) != PM3_SUCCESS) {
PrintAndLogEx(WARNING, "Warning: error writing blk %d", idx);
}
}
// Update downlink mode for the page 0 config write.
config.downlink_mode = downlink_mode;
// Write the page 0 config
//
// when running `lf t55xx dump` and user failed, the dump file will have a all zero block0 (ie: config block)
// writing this bad config block will brick the tag.
if (dump[0] != 0x00000000) {
snprintf(wcmd, sizeof(wcmd), "-b 0 -d %08X %s", dump[0], pwdopt);
if (CmdT55xxWriteBlock(wcmd) != PM3_SUCCESS) {
PrintAndLogEx(WARNING, "Warning: error writing blk 0");
}
} else {
PrintAndLogEx(WARNING, "Warning: the dump file contains a all zero config block.");
PrintAndLogEx(HINT, "Make sure you dumped the card correct");
}
free(dump);
PrintAndLogEx(INFO, "Done!");
return PM3_SUCCESS;
}
/*
static int CmdT55xxRestore(const char *Cmd) {
uint32_t password = 0;
uint8_t override = 0;
uint8_t downlink_mode = config.downlink_mode;
bool usepwd = false;
bool errors = false;
uint8_t cmdp = 0;
while (param_getchar(Cmd, cmdp) != 0x00 && !errors) {
switch (tolower(param_getchar(Cmd, cmdp))) {
case 'h':
return usage_t55xx_restore();
case 'r':
downlink_mode = param_get8ex(Cmd, cmdp + 1, 0, 10);
if (downlink_mode > 3)
downlink_mode = 0;
cmdp += 2;
break;
case 'p':
password = param_get32ex(Cmd, cmdp + 1, 0, 16);
usepwd = true;
cmdp += 2;
break;
case 'o':
override = 1;
cmdp++;
break;
default:
PrintAndLogEx(WARNING, "Unknown parameter '%c'", param_getchar(Cmd, cmdp));
errors = true;
break;
}
}
if (errors) return usage_t55xx_restore();
PrintAndLogEx(INFO, "Work in progress. To be implemented");
if (usepwd || password || override ) {
}
// load file name (json/eml/bin)
// Print dump data?
uint32_t res = PM3_SUCCESS;
// page0.
// res = clone_t55xx_tag(blockdata, numblocks);
return res;
}
*/
bool AcquireData(uint8_t page, uint8_t block, bool pwdmode, uint32_t password, uint8_t downlink_mode) {
// arg0 bitmodes:
// b0 = pwdmode
// b1 = page to read from
// b2 = brute_mem (armside function)
// arg1: which block to read
// arg2: password
struct p {
uint32_t password;
uint8_t blockno;
uint8_t page;
bool pwdmode;
uint8_t downlink_mode;
} PACKED;
struct p payload;
payload.password = password;
payload.blockno = block;
payload.page = page & 0x1;
payload.pwdmode = pwdmode;
payload.downlink_mode = downlink_mode;
s_block_read_capture = (block != REGULAR_READ_MODE_BLOCK);
clearCommandBuffer();
SendCommandNG(CMD_LF_T55XX_READBL, (uint8_t *)&payload, sizeof(payload));
if (WaitForResponseTimeout(CMD_LF_T55XX_READBL, NULL, 2500) == false) {
PrintAndLogEx(WARNING, "command execution time out");
return false;
}
getSamples(12000, false);
bool ok = !getSignalProperties()->isnoise;
config.usepwd = pwdmode;
return ok;
}
char *GetPskCfStr(uint32_t id, bool q5) {
static char buf[40];
char *retStr = buf;
switch (id) {
case 0:
snprintf(retStr, sizeof(buf), "%u - RF/2", id);
break;
case 1:
snprintf(retStr, sizeof(buf), "%u - RF/4", id);
break;
case 2:
snprintf(retStr, sizeof(buf), "%u - RF/8", id);
break;
case 3:
if (q5)
snprintf(retStr, sizeof(buf), "%u - RF/8", id);
else
snprintf(retStr, sizeof(buf), "%u - " _RED_("(Unknown)"), id);
break;
default:
snprintf(retStr, sizeof(buf), "%u - " _RED_("(Unknown)"), id);
break;
}
return buf;
}
char *GetBitRateStr(uint32_t id, bool xmode) {
static char buf[35];
char *retStr = buf;
if (xmode) { //xmode bitrate calc is same as em4x05 calc
snprintf(retStr, sizeof(buf), "%u - RF/%u", id, EM4x05_GET_BITRATE(id));
} else {
switch (id) {
case 0:
snprintf(retStr, sizeof(buf), "%u - "_GREEN_("RF/8"), id);
break;
case 1:
snprintf(retStr, sizeof(buf), "%u - "_GREEN_("RF/16"), id);
break;
case 2:
snprintf(retStr, sizeof(buf), "%u - "_GREEN_("RF/32"), id);
break;
case 3:
snprintf(retStr, sizeof(buf), "%u - "_GREEN_("RF/40"), id);
break;
case 4:
snprintf(retStr, sizeof(buf), "%u - "_GREEN_("RF/50"), id);
break;
case 5:
snprintf(retStr, sizeof(buf), "%u - "_GREEN_("RF/64"), id);
break;
case 6:
snprintf(retStr, sizeof(buf), "%u - "_GREEN_("RF/100"), id);
break;
case 7:
snprintf(retStr, sizeof(buf), "%u - "_GREEN_("RF/128"), id);
break;
default:
snprintf(retStr, sizeof(buf), "%u - " _RED_("(Unknown)"), id);
break;
}
}
return buf;
}
char *GetSaferStr(uint32_t id) {
static char buf[40];
char *retStr = buf;
snprintf(retStr, sizeof(buf), "%u", id);
if (id == 6) {
snprintf(retStr, sizeof(buf), "%u - " _YELLOW_("passwd"), id);
}
if (id == 9) {
snprintf(retStr, sizeof(buf), "%u - " _YELLOW_("testmode"), id);
}
return buf;
}
char *GetModulationStr(uint32_t id, bool xmode) {
static char buf[60];
char *retStr = buf;
switch (id) {
case 0:
snprintf(retStr, sizeof(buf), "%u - DIRECT (ASK/NRZ)", id);
break;
case 1:
snprintf(retStr, sizeof(buf), "%u - PSK 1 phase change when input changes", id);
break;
case 2:
snprintf(retStr, sizeof(buf), "%u - PSK 2 phase change on bitclk if input high", id);
break;
case 3:
snprintf(retStr, sizeof(buf), "%u - PSK 3 phase change on rising edge of input", id);
break;
case 4:
snprintf(retStr, sizeof(buf), "%u - FSK 1 RF/8 RF/5", id);
break;
case 5:
snprintf(retStr, sizeof(buf), "%u - FSK 2 RF/8 RF/10", id);
break;
case 6:
snprintf(retStr, sizeof(buf), "%u - %s RF/5 RF/8", id, (xmode) ? "FSK 1a" : _YELLOW_("FSK 1a"));
break;
case 7:
snprintf(retStr, sizeof(buf), "%u - %s RF/10 RF/8", id, (xmode) ? "FSK 2a" : _YELLOW_("FSK 2a"));
break;
case 8:
snprintf(retStr, sizeof(buf), "%u - Manchester", id);
break;
case 16:
snprintf(retStr, sizeof(buf), "%u - Biphase", id);
break;
case 24:
snprintf(retStr, sizeof(buf), "%u - %s", id, (xmode) ? "Biphase a - AKA Conditional Dephase Encoding(CDP)" : _YELLOW_("Reserved"));
break;
default:
snprintf(retStr, sizeof(buf), "0x%02X " _RED_("(Unknown)"), id);
break;
}
return buf;
}
char *GetDownlinkModeStr(uint8_t downlink_mode) {
static char buf[30];
char *retStr = buf;
switch (downlink_mode) {
case T55XX_DLMODE_FIXED :
snprintf(retStr, sizeof(buf), "default/fixed bit length");
break;
case T55XX_DLMODE_LLR :
snprintf(retStr, sizeof(buf), "long leading reference");
break;
case T55XX_DLMODE_LEADING_ZERO :
snprintf(retStr, sizeof(buf), "leading zero reference");
break;
case T55XX_DLMODE_1OF4 :
snprintf(retStr, sizeof(buf), "1 of 4 coding reference");
break;
default:
snprintf(retStr, sizeof(buf), _RED_("(Unknown)"));
break;
}
return buf;
}
char *GetQ5ModulationStr(uint32_t id) {
static char buf[60];
char *retStr = buf;
switch (id) {
case 0:
snprintf(retStr, sizeof(buf), "%u - Manchester", id);
break;
case 1:
snprintf(retStr, sizeof(buf), "%u - PSK 1 phase change when input changes", id);
break;
case 2:
snprintf(retStr, sizeof(buf), "%u - PSK 2 phase change on bitclk if input high", id);
break;
case 3:
snprintf(retStr, sizeof(buf), "%u - PSK 3 phase change on rising edge of input", id);
break;
case 4:
snprintf(retStr, sizeof(buf), "%u - FSK 1a RF/5 RF/8", id);
break;
case 5:
snprintf(retStr, sizeof(buf), "%u - FSK 2a RF/10 RF/8", id);
break;
case 6:
snprintf(retStr, sizeof(buf), "%u - Biphase", id);
break;
case 7:
snprintf(retStr, sizeof(buf), "%u - NRZ / Direct", id);
break;
}
return buf;
}
char *GetModelStrFromCID(uint32_t cid) {
static char buf[10];
char *retStr = buf;
if (cid == 1) snprintf(retStr, sizeof(buf), "ATA5577M1");
if (cid == 2) snprintf(retStr, sizeof(buf), "ATA5577M2");
if (cid == 3) snprintf(retStr, sizeof(buf), "ATA5577M3");
return buf;
}
char *GetConfigBlock0Source(uint8_t id) {
static char buf[40];
char *retStr = buf;
switch (id) {
case AUTODETECT:
snprintf(retStr, sizeof(buf), _YELLOW_("(auto detect)"));
break;
case USERSET:
snprintf(retStr, sizeof(buf), _YELLOW_("(user set)"));
break;
case TAGREAD:
snprintf(retStr, sizeof(buf), _GREEN_("(tag read)"));
break;
default:
snprintf(retStr, sizeof(buf), _RED_("(n/a)"));
break;
}
return buf;
}
char *GetSelectedModulationStr(uint8_t id) {
static char buf[20];
char *retStr = buf;
switch (id) {
case DEMOD_FSK:
snprintf(retStr, sizeof(buf), "FSK");
break;
case DEMOD_FSK1:
snprintf(retStr, sizeof(buf), "FSK1");
break;
case DEMOD_FSK1a:
snprintf(retStr, sizeof(buf), "FSK1a");
break;
case DEMOD_FSK2:
snprintf(retStr, sizeof(buf), "FSK2");
break;
case DEMOD_FSK2a:
snprintf(retStr, sizeof(buf), "FSK2a");
break;
case DEMOD_ASK:
snprintf(retStr, sizeof(buf), "ASK");
break;
case DEMOD_NRZ:
snprintf(retStr, sizeof(buf), "DIRECT/NRZ");
break;
case DEMOD_PSK1:
snprintf(retStr, sizeof(buf), "PSK1");
break;
case DEMOD_PSK2:
snprintf(retStr, sizeof(buf), "PSK2");
break;
case DEMOD_PSK3:
snprintf(retStr, sizeof(buf), "PSK3");
break;
case DEMOD_BI:
snprintf(retStr, sizeof(buf), "BIPHASE");
break;
case DEMOD_BIa:
snprintf(retStr, sizeof(buf), "BIPHASEa - (CDP)");
break;
default:
snprintf(retStr, sizeof(buf), _RED_("(Unknown)"));
break;
}
return buf;
}
/*
static void t55x7_create_config_block(int tagtype) {
// T55X7_DEFAULT_CONFIG_BLOCK, T55X7_RAW_CONFIG_BLOCK
// T55X7_EM_UNIQUE_CONFIG_BLOCK, T55X7_FDXB_CONFIG_BLOCK,
// T55X7_FDXB_CONFIG_BLOCK, T55X7_HID_26_CONFIG_BLOCK, T55X7_INDALA_64_CONFIG_BLOCK, T55X7_INDALA_224_CONFIG_BLOCK
// T55X7_GUARDPROXII_CONFIG_BLOCK, T55X7_VIKING_CONFIG_BLOCK, T55X7_NORALYS_CONFIG_BLOCK, T55X7_IOPROX_CONFIG_BLOCK
static char buf[60];
char *retStr = buf;
switch (tagtype) {
case 0:
snprintf(retStr, sizeof(buf), "%08X - T55X7 Default", T55X7_DEFAULT_CONFIG_BLOCK);
break;
case 1:
snprintf(retStr, sizeof(buf), "%08X - T55X7 Raw", T55X7_RAW_CONFIG_BLOCK);
break;
case 2:
snprintf(retStr, sizeof(buf), "%08X - Q5/T5555 Default", T5555_DEFAULT_CONFIG_BLOCK);
break;
default:
break;
}
PrintAndLogEx(NORMAL, buf);
}
*/
static int CmdResetRead(const char *Cmd) {
CLIParserContext *ctx;
CLIParserInit(&ctx, "lf t55xx resetread",
"Send Reset Cmd then `lf read` the stream to attempt\n"
"to identify the start of it (needs a demod and/or plot after)",
"lf t55xx resetread"
);
// 1 (help) + 0(one user specified params) + (5 T55XX_DLMODE_SINGLE)
void *argtable[2 + 5] = {
arg_param_begin,
arg_lit0("1", NULL, "extract using data from graphbuffer"),
};
uint8_t idx = 2;
arg_add_t55xx_downloadlink(argtable, &idx, T55XX_DLMODE_SINGLE, config.downlink_mode);
CLIExecWithReturn(ctx, Cmd, argtable, true);
bool r0 = arg_get_lit(ctx, 1);
bool r1 = arg_get_lit(ctx, 2);
bool r2 = arg_get_lit(ctx, 3);
bool r3 = arg_get_lit(ctx, 4);
CLIParserFree(ctx);
if ((r0 + r1 + r2 + r3) > 1) {
PrintAndLogEx(FAILED, "Error multiple downlink encoding");
return PM3_EINVARG;
}
uint8_t downlink_mode = config.downlink_mode;
if (r0)
downlink_mode = refFixedBit;
else if (r1)
downlink_mode = refLongLeading;
else if (r2)
downlink_mode = refLeading0;
else if (r3)
downlink_mode = ref1of4;
uint8_t flags = downlink_mode << 3;
PrintAndLogEx(INFO, "Sending reset command...");
PacketResponseNG resp;
clearCommandBuffer();
SendCommandNG(CMD_LF_T55XX_RESET_READ, &flags, sizeof(flags));
if (WaitForResponseTimeout(CMD_LF_T55XX_RESET_READ, &resp, 2500) == false) {
PrintAndLogEx(WARNING, "command execution time out");
return PM3_ETIMEOUT;
}
if (resp.status == PM3_SUCCESS) {
uint16_t gotsize = g_pm3_capabilities.bigbuf_size - 1;
uint8_t *got = calloc(gotsize, sizeof(uint8_t));
if (got == NULL) {
PrintAndLogEx(WARNING, "Failed to allocate memory");
return PM3_EMALLOC;
}
PrintAndLogEx(INFO, "Downloading samples...");
if (!GetFromDevice(BIG_BUF, got, gotsize, 0, NULL, 0, NULL, 2500, false)) {
PrintAndLogEx(WARNING, "command execution time out");
free(got);
return PM3_ETIMEOUT;
}
setGraphBuffer(got, gotsize);
free(got);
}
PrintAndLogEx(INFO, "Done!");
return PM3_SUCCESS;
}
static int CmdT55xxWipe(const char *Cmd) {
CLIParserContext *ctx;
CLIParserInit(&ctx, "lf t55xx wipe",
"This commands wipes a tag, fills blocks 1-7 with zeros and a default configuration block",
"lf t55xx wipe -> wipes a T55x7 tag, config block 0x000880E0\n"
"lf t55xx wipe --q5 -> wipes a Q5/T5555 tag, config block 0x6001F004\n"
"lf t55xx wipe -p 11223344 -> wipes a T55x7 tag, config block 0x000880E0, using pwd"
);
// 1 (help) + 3 (three user specified params) + (5 T55XX_DLMODE_SINGLE)
void *argtable[4 + 5] = {
arg_param_begin,
arg_str0("c", "cfg", "<hex>", "configuration block0 (4 hex bytes)"),
arg_str0("p", "pwd", "<hex>", "password (4 hex bytes)"),
arg_lit0(NULL, "q5", "specify writing to Q5/T5555 tag using dedicated config block"),
};
uint8_t idx = 4;
arg_add_t55xx_downloadlink(argtable, &idx, T55XX_DLMODE_SINGLE, config.downlink_mode);
CLIExecWithReturn(ctx, Cmd, argtable, true);
bool usepwd = false, gotconf = false;
uint32_t block0 = 0;
int res = arg_get_u32_hexstr_def(ctx, 1, 0, &block0);
if (res == 1) {
gotconf = true;
}
if (res == 2) {
CLIParserFree(ctx);
PrintAndLogEx(WARNING, "config block needs to be 4 hex bytes");
return PM3_EINVARG;
}
uint32_t password = 0;
res = arg_get_u32_hexstr_def(ctx, 2, 0x51243648, &password);
if (res) {
usepwd = true;
}
if (res == 2) {
PrintAndLogEx(WARNING, "Password should be 4 bytes, using default pwd");
}
bool Q5 = arg_get_lit(ctx, 3);
CLIParserFree(ctx);
PrintAndLogEx(INFO, "Target " _YELLOW_("%s")" tag", (Q5) ? "Q5/T5555" : "T55x7");
// default config blocks.
if (gotconf == false) {
block0 = (Q5) ? 0x6001F004 : 0x000880E0;
}
if (usepwd)
PrintAndLogEx(INFO, "Using password " _GREEN_("%08X"), password);
char msg[80] = {0};
if (gotconf)
snprintf(msg, sizeof(msg), "User provided configuration block " _GREEN_("%08X"), block0);
else
snprintf(msg, sizeof(msg), "Default configuration block " _GREEN_("%08X"), block0);
PrintAndLogEx(INFO, "%s\n", msg);
PrintAndLogEx(INFO, "Begin wiping...");
// Creating cmd string for write block :)
char wcmd[36] = {0};
char *pwcmd = wcmd;
snprintf(pwcmd, sizeof(wcmd), "-b 0 ");
if (usepwd) {
snprintf(pwcmd + strlen(wcmd), sizeof(wcmd) - strlen(wcmd), "-p %08x ", password);
}
snprintf(pwcmd + strlen(wcmd), sizeof(wcmd) - strlen(wcmd), "-d %08X", block0);
if (CmdT55xxWriteBlock(pwcmd) != PM3_SUCCESS)
PrintAndLogEx(WARNING, "Warning: error writing blk 0");
for (uint8_t blk = 1; blk < 8; blk++) {
snprintf(pwcmd, sizeof(wcmd), "-b %d -d 00000000", blk);
if (CmdT55xxWriteBlock(pwcmd) != PM3_SUCCESS)
PrintAndLogEx(WARNING, "Warning: error writing blk %d", blk);
memset(wcmd, 0x00, sizeof(wcmd));
}
// Check and rest t55xx downlink mode.
if (config.downlink_mode != T55XX_DLMODE_FIXED) { // Detect found a different mode so card must support
snprintf(pwcmd, sizeof(wcmd), "-b 3 --pg1 -d 00000000");
if (CmdT55xxWriteBlock(pwcmd) != PM3_SUCCESS) {
PrintAndLogEx(WARNING, "Warning: failed writing block 3 page 1 (config)");
}
memset(wcmd, 0x00, sizeof(wcmd));
}
return PM3_SUCCESS;
}
static bool IsCancelled(void) {
if (kbd_enter_pressed()) {
PrintAndLogEx(WARNING, "\naborted via keyboard!\n");
return true;
}
return false;
}
// load a default pwd file.
static int CmdT55xxChkPwds(const char *Cmd) {
CLIParserContext *ctx;
CLIParserInit(&ctx, "lf t55xx chk",
"This command uses a dictionary attack.\n"
"For some cloners, try '--em' for known pwdgen algo.\n"
"Try to reading Page 0 block 7 before.\n"
_RED_("WARNING:") _CYAN_(" this may brick non-password protected chips!"),
"lf t55xx chk -m -> use dictionary from flash memory (RDV4)\n"
"lf t55xx chk -f my_dictionary_pwds -> loads a default keys dictionary file\n"
"lf t55xx chk --em aa11223344 -> try known pwdgen algo from some cloners based on EM4100 ID"
);
/*
Calculate size of argtable accordingly:
1 (help) + 3 (three user specified params) + ( 5 or 6 T55XX_DLMODE)
start index to call arg_add_t55xx_downloadlink() is 4 (1 + 3) given the above sample
*/
// 1 (help) + 3 (three user specified params) + (6 T55XX_DLMODE_ALL)
void *argtable[4 + 6] = {
arg_param_begin,
arg_lit0("m", "fm", "use dictionary from flash memory (RDV4)"),
arg_str0("f", "file", "<fn>", "file name"),
arg_str0(NULL, "em", "<hex>", "EM4100 ID (5 hex bytes)"),
};
uint8_t idx = 4;
arg_add_t55xx_downloadlink(argtable, &idx, T55XX_DLMODE_ALL, T55XX_DLMODE_ALL);
CLIExecWithReturn(ctx, Cmd, argtable, true);
bool from_flash = arg_get_lit(ctx, 1);
int fnlen = 0;
char filename[FILE_PATH_SIZE] = {0};
CLIParamStrToBuf(arg_get_str(ctx, 2), (uint8_t *)filename, FILE_PATH_SIZE, &fnlen);
// White cloner password based on EM4100 ID
bool use_calc_password = false;
uint32_t card_password = 0x00;
uint64_t cardid = 0;
int res = arg_get_u64_hexstr_def_nlen(ctx, 3, 0x00, &cardid, 5, true);
if (res == 1) {
use_calc_password = true;
uint32_t calc = cardid & 0xFFFFFFFF;
card_password = lf_t55xx_white_pwdgen(calc);
}
if (res == 2) {
CLIParserFree(ctx);
PrintAndLogEx(WARNING, "EM4100 ID must be 5 hex bytes");
return PM3_EINVARG;
}
if (res == 0) {
CLIParserFree(ctx);
return PM3_EINVARG;
}
bool r0 = arg_get_lit(ctx, 4);
bool r1 = arg_get_lit(ctx, 5);
bool r2 = arg_get_lit(ctx, 6);
bool r3 = arg_get_lit(ctx, 7);
bool ra = arg_get_lit(ctx, 8);
CLIParserFree(ctx);
if ((r0 + r1 + r2 + r3 + ra) > 1) {
PrintAndLogEx(FAILED, "Error multiple downlink encoding");
return PM3_EINVARG;
}
uint8_t downlink_mode = refFixedBit; // Password checks should always start with default/fixed bit unluess requested by user for specific mode
// if (r0 || ra) // ra should start downlink mode ad fixed bit to loop through all modes correctly
// downlink_mode = refFixedBit;
// else
if (r1)
downlink_mode = refLongLeading;
else if (r2)
downlink_mode = refLeading0;
else if (r3)
downlink_mode = ref1of4;
bool use_pwd_file = true; // Assume we are going to use a file, unless turned off later.
if (strlen(filename) == 0) {
snprintf(filename, sizeof(filename), "t55xx_default_pwds");
}
PrintAndLogEx(INFO, "Press " _GREEN_("<Enter>") " to exit");
PrintAndLogEx(NORMAL, "");
/*
// block 7, page1 = false, usepwd = false, override = false, pwd = 00000000
if ( T55xxReadBlock(7, false, false, false, 0x00000000) == PM3_SUCCESS) {
// now try to validate it..
PrintAndLogEx(WARNING, "\n Block 7 was readable");
return PM3_SUCCESS;
}
*/
bool found = false;
uint64_t t1 = msclock();
uint8_t flags = downlink_mode << 3;
if (from_flash) {
use_pwd_file = false; // turn of local password file since we are checking from flash.
clearCommandBuffer();
SendCommandNG(CMD_LF_T55XX_CHK_PWDS, &flags, sizeof(flags));
PacketResponseNG resp;
uint8_t timeout = 0;
while (WaitForResponseTimeout(CMD_LF_T55XX_CHK_PWDS, &resp, 2000) == false) {
timeout++;
PrintAndLogEx(NORMAL, "." NOLF);
if (timeout > 180) {
PrintAndLogEx(WARNING, "\nno response from Proxmark3. Aborting...");
return PM3_ENODATA;
}
}
PrintAndLogEx(NORMAL, "");
struct p {
bool found;
uint32_t candidate;
} PACKED;
struct p *packet = (struct p *)resp.data.asBytes;
if (packet->found) {
PrintAndLogEx(SUCCESS, "\nfound a candidate [ " _YELLOW_("%08"PRIX32) " ]", packet->candidate);
if (AcquireData(T55x7_PAGE0, T55x7_CONFIGURATION_BLOCK, true, packet->candidate, downlink_mode)) {
found = t55xxTryDetectModulationEx(downlink_mode, T55XX_PrintConfig, 0, packet->candidate);
if (found) {
PrintAndLogEx(SUCCESS, "found valid password [ " _GREEN_("%08"PRIX32) " ]", packet->candidate);
} else {
PrintAndLogEx(WARNING, "check pwd failed");
}
} else {
PrintAndLogEx(WARNING, "check pwd failed");
}
} else {
PrintAndLogEx(WARNING, "check pwd failed");
}
goto out;
}
// to try each downlink mode for each password
int dl_mode;
// try calculated password
if (use_calc_password) {
PrintAndLogEx(INFO, "testing %08"PRIX32" generated ", card_password);
for (dl_mode = downlink_mode; dl_mode <= 3; dl_mode++) {
if (!AcquireData(T55x7_PAGE0, T55x7_CONFIGURATION_BLOCK, true, card_password, dl_mode)) {
continue;
}
found = t55xxTryDetectModulationEx(dl_mode, T55XX_PrintConfig, 0, card_password);
if (found) {
PrintAndLogEx(SUCCESS, "found valid password : [ " _GREEN_("%08"PRIX32) " ]", card_password);
break;
}
if (ra == false)
break;
}
}
if ((found == false) && use_pwd_file) {
uint32_t keycount = 0;
uint8_t *keyblock = NULL;
res = loadFileDICTIONARY_safe(filename, (void **) &keyblock, 4, &keycount);
if (res != PM3_SUCCESS || keycount == 0 || keyblock == NULL) {
PrintAndLogEx(WARNING, "no keys found in file");
if (keyblock != NULL)
free(keyblock);
return PM3_ESOFT;
}
PrintAndLogEx(INFO, "Press " _GREEN_("<Enter>") " to exit");
for (uint32_t c = 0; c < keycount && found == false; ++c) {
if (!g_session.pm3_present) {
PrintAndLogEx(WARNING, "device offline\n");
free(keyblock);
return PM3_ENODATA;
}
if (IsCancelled()) {
free(keyblock);
return PM3_EOPABORTED;
}
uint32_t curr_password = bytes_to_num(keyblock + 4 * c, 4);
PrintAndLogEx(INFO, "testing %08"PRIX32, curr_password);
for (dl_mode = downlink_mode; dl_mode <= 3; dl_mode++) {
// If acquire fails, then we still need to check if we are only trying a single downlink mode.
// If we continue on fail, it will skip that test and try the next downlink mode; thus slowing down the check
// when on a single downlink mode is wanted.
if (AcquireData(T55x7_PAGE0, T55x7_CONFIGURATION_BLOCK, true, curr_password, dl_mode)) {
found = t55xxTryDetectModulationEx(dl_mode, T55XX_PrintConfig, 0, curr_password);
if (found) {
PrintAndLogEx(SUCCESS, "found valid password: [ " _GREEN_("%08"PRIX32) " ]", curr_password);
break;
}
}
if (ra == false) // Exit loop if not trying all downlink modes
break;
}
}
free(keyblock);
}
if (found == false)
PrintAndLogEx(WARNING, "failed to find password");
out:
t1 = msclock() - t1;
PrintAndLogEx(SUCCESS, "\ntime in check pwd " _YELLOW_("%.0f") " seconds\n", (float)t1 / 1000.0);
return PM3_SUCCESS;
}
// Bruteforce - incremental password range search
static int CmdT55xxBruteForce(const char *Cmd) {
CLIParserContext *ctx;
CLIParserInit(&ctx, "lf t55xx bruteforce",
"This command uses bruteforce to scan a number range.\n"
"Try reading Page 0, block 7 before.\n\n"
_RED_("WARNING") _CYAN_(" this may brick non-password protected chips!"),
"lf t55xx bruteforce --r2 -s aaaaaa77 -e aaaaaa99\n"
);
// 1 (help) + 2 (two user specified params) + (6 T55XX_DLMODE_ALL)
void *argtable[3 + 6] = {
arg_param_begin,
arg_str1("s", "start", "<hex>", "search start password (4 hex bytes)"),
arg_str1("e", "end", "<hex>", "search end password (4 hex bytes)"),
};
uint8_t idx = 3;
arg_add_t55xx_downloadlink(argtable, &idx, T55XX_DLMODE_ALL, T55XX_DLMODE_ALL);
CLIExecWithReturn(ctx, Cmd, argtable, true);
uint32_t start_password = 0;
int res = arg_get_u32_hexstr_def(ctx, 1, 0, &start_password);
if (res == 2) {
CLIParserFree(ctx);
PrintAndLogEx(FAILED, "start password should be 4 bytes");
return PM3_EINVARG;
}
uint32_t end_password = 0xFFFFFFFF;
res = arg_get_u32_hexstr_def(ctx, 2, 0xFFFFFFFF, &end_password);
if (res == 2) {
CLIParserFree(ctx);
PrintAndLogEx(FAILED, "end password should be 4 bytes");
return PM3_EINVARG;
}
bool r0 = arg_get_lit(ctx, 3);
bool r1 = arg_get_lit(ctx, 4);
bool r2 = arg_get_lit(ctx, 5);
bool r3 = arg_get_lit(ctx, 6);
bool ra = arg_get_lit(ctx, 7);
CLIParserFree(ctx);
if ((r0 + r1 + r2 + r3 + ra) > 1) {
PrintAndLogEx(FAILED, "Error multiple downlink encoding");
return PM3_EINVARG;
}
uint8_t downlink_mode = refFixedBit; // if no downlink mode suppliled use fixed bit/default as the is the most common
// Since we don't know the password the config.downlink mode is of little value.
// if (r0 || ra) // if try all (ra) then start at fixed bit for correct try all
// downlink_mode = refFixedBit;
// else
if (r1)
downlink_mode = refLongLeading;
else if (r2)
downlink_mode = refLeading0;
else if (r3)
downlink_mode = ref1of4;
uint32_t curr = 0;
uint8_t found = 0; // > 0 if found xx1 xx downlink needed, 1 found
if (start_password > end_password) {
PrintAndLogEx(FAILED, "Error, start larger then end password");
return PM3_EINVARG;
}
PrintAndLogEx(INFO, "Press " _GREEN_("<Enter>") " to exit");
PrintAndLogEx(INFO, "Search password range [%08X -> %08X]", start_password, end_password);
uint64_t t1 = msclock();
curr = start_password;
while (found == 0) {
PrintAndLogEx(NORMAL, "." NOLF);
if (IsCancelled()) {
return PM3_EOPABORTED;
}
found = t55xx_try_one_password(curr, downlink_mode, ra);
if (curr == end_password)
break;
curr++;
}
PrintAndLogEx(NORMAL, "");
if (found) {
if (curr != end_password) {
PrintAndLogEx(SUCCESS, "Found valid password: [ " _GREEN_("%08X") " ]", curr - 1);
} else
PrintAndLogEx(SUCCESS, "Found valid password: [ " _GREEN_("%08X") " ]", curr);
T55xx_Print_DownlinkMode((found >> 1) & 3);
} else
PrintAndLogEx(WARNING, "Bruteforce failed, last tried: [ " _YELLOW_("%08X") " ]", curr);
t1 = msclock() - t1;
PrintAndLogEx(SUCCESS, "\ntime in bruteforce " _YELLOW_("%.0f") " seconds\n", (float)t1 / 1000.0);
return PM3_SUCCESS;
}
uint8_t t55xx_try_one_password(uint32_t password, uint8_t downlink_mode, bool try_all_dl_modes) {
PrintAndLogEx(INFO, "Trying password %08X", password);
// ensure 0-3
downlink_mode = (downlink_mode & 3);
// check if dl mode 4 and loop if needed
for (uint8_t dl_mode = downlink_mode; dl_mode < 4; dl_mode++) {
if (AcquireData(T55x7_PAGE0, T55x7_CONFIGURATION_BLOCK, true, password, dl_mode)) {
// if (getSignalProperties()->isnoise == false) {
// } else {
if (t55xxTryDetectModulationEx(dl_mode, T55XX_PrintConfig, 0, password)) {
return 1 + (dl_mode << 1);
}
// }
}
if (try_all_dl_modes == false) {
break;
}
}
return 0;
}
static int CmdT55xxRecoverPW(const char *Cmd) {
CLIParserContext *ctx;
CLIParserInit(&ctx, "lf t55xx recoverpw",
"This command uses a few tricks to try to recover mangled password.\n"
"Try reading Page 0, block 7 before.\n\n"
_RED_("WARNING") _CYAN_(" this may brick non-password protected chips!"),
"lf t55xx recoverpw\n"
"lf t55xx recoverpw -p 11223344\n"
"lf t55xx recoverpw -p 11223344 --r3\n"
);
// 1 (help) + 1 (one user specified params) + (6 T55XX_DLMODE_ALL)
void *argtable[2 + 6] = {
arg_param_begin,
arg_str0("p", "pwd", "<hex>", "password (4 hex bytes)"),
};
uint8_t idx = 2;
arg_add_t55xx_downloadlink(argtable, &idx, T55XX_DLMODE_ALL, T55XX_DLMODE_ALL);
CLIExecWithReturn(ctx, Cmd, argtable, true);
uint32_t orig_password = 0;
int res = arg_get_u32_hexstr_def(ctx, 1, 0x51243648, &orig_password);
if (res == 2) {
PrintAndLogEx(INFO, "Password should be 4 bytes, using default pwd instead");
}
bool r0 = arg_get_lit(ctx, 2);
bool r1 = arg_get_lit(ctx, 3);
bool r2 = arg_get_lit(ctx, 4);
bool r3 = arg_get_lit(ctx, 5);
bool ra = arg_get_lit(ctx, 6);
CLIParserFree(ctx);
if ((r0 + r1 + r2 + r3 + ra) > 1) {
PrintAndLogEx(FAILED, "Error multiple downlink encoding");
return PM3_EINVARG;
}
uint8_t downlink_mode = config.downlink_mode;
if (r0)
downlink_mode = refFixedBit;
else if (r1)
downlink_mode = refLongLeading;
else if (r2)
downlink_mode = refLeading0;
else if (r3)
downlink_mode = ref1of4;
PrintAndLogEx(INFO, "Press " _GREEN_("<Enter>") " to exit");
int bit = 0;
uint32_t curr_password = 0x0;
uint32_t prev_password = 0xffffffff;
uint32_t mask = 0x0;
uint8_t found = 0;
// first try fliping each bit in the expected password
while (bit < 32) {
curr_password = orig_password ^ (1u << bit);
found = t55xx_try_one_password(curr_password, downlink_mode, ra);
if (found > 0) // xx1 for found xx = dl mode used
goto out;
bit++;
if (IsCancelled())
return PM3_EOPABORTED;
}
// now try to use partial original password, since block 7 should have been completely
// erased during the write sequence and it is possible that only partial password has been
// written
// not sure from which end the bit bits are written, so try from both ends
// from low bit to high bit
bit = 0;
while (bit < 32) {
mask += (1u << bit);
curr_password = orig_password & mask;
// if updated mask didn't change the password, don't try it again
if (prev_password == curr_password) {
bit++;
continue;
}
found = t55xx_try_one_password(curr_password, downlink_mode, ra);
if (found > 0)
goto out;
bit++;
prev_password = curr_password;
if (IsCancelled())
return PM3_EOPABORTED;
}
// from high bit to low
bit = 0;
mask = 0xffffffff;
while (bit < 32) {
mask -= (1u << bit);
curr_password = orig_password & mask;
// if updated mask didn't change the password, don't try it again
if (prev_password == curr_password) {
bit++;
continue;
}
found = t55xx_try_one_password(curr_password, downlink_mode, ra);
if (found > 0)
goto out;
bit++;
prev_password = curr_password;
if (IsCancelled())
return PM3_EOPABORTED;
}
out:
PrintAndLogEx(NORMAL, "");
if (found > 0) {
PrintAndLogEx(SUCCESS, "Found valid password: [ " _GREEN_("%08X") " ]", curr_password);
T55xx_Print_DownlinkMode((found >> 1) & 3);
} else {
PrintAndLogEx(FAILED, "Recover password failed");
}
return PM3_SUCCESS;
}
// note length of data returned is different for different chips.
// some return all page 1 (64 bits) and others return just that block (32 bits)
// unfortunately the 64 bits makes this more likely to get a false positive...
bool tryDetectP1(bool getData) {
uint8_t preamble_atmel[] = {1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1};
uint8_t preamble_silicon[] = {1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1};
size_t startIdx = 0;
uint8_t fc1 = 0, fc2 = 0, ans = 0;
int clk = 0, firstClockEdge = 0;
bool st = true;
if (getData) {
if (!AcquireData(T55x7_PAGE1, T55x7_TRACE_BLOCK1, false, 0, 0))
return false;
}
// try fsk clock detect. if successful it cannot be any other type of modulation... (in theory...)
ans = fskClocks(&fc1, &fc2, (uint8_t *)&clk, &firstClockEdge);
if (ans && ((fc1 == 10 && fc2 == 8) || (fc1 == 8 && fc2 == 5))) {
if (FSKrawDemod(0, 0, 0, 0, false) == PM3_SUCCESS) {
if (preambleSearchEx(g_DemodBuffer, preamble_atmel, sizeof(preamble_atmel), &g_DemodBufferLen, &startIdx, false) &&
(g_DemodBufferLen == 32 || g_DemodBufferLen == 64)) {
return true;
}
if (preambleSearchEx(g_DemodBuffer, preamble_silicon, sizeof(preamble_silicon), &g_DemodBufferLen, &startIdx, false) &&
(g_DemodBufferLen == 32 || g_DemodBufferLen == 64)) {
return true;
}
}
if (FSKrawDemod(0, 1, 0, 0, false) == PM3_SUCCESS) {
if (preambleSearchEx(g_DemodBuffer, preamble_atmel, sizeof(preamble_atmel), &g_DemodBufferLen, &startIdx, false) &&
(g_DemodBufferLen == 32 || g_DemodBufferLen == 64)) {
return true;
}
if (preambleSearchEx(g_DemodBuffer, preamble_silicon, sizeof(preamble_silicon), &g_DemodBufferLen, &startIdx, false) &&
(g_DemodBufferLen == 32 || g_DemodBufferLen == 64)) {
return true;
}
}
return false;
}
// try ask clock detect. it could be another type even if successful.
clk = GetAskClock("", false);
if (clk > 0) {
if (ASKDemod_ext(0, 0, 1, 0, false, false, false, 1, &st) == PM3_SUCCESS) {
if (preambleSearchEx(g_DemodBuffer, preamble_atmel, sizeof(preamble_atmel), &g_DemodBufferLen, &startIdx, false) &&
(g_DemodBufferLen == 32 || g_DemodBufferLen == 64)) {
return true;
}
if (preambleSearchEx(g_DemodBuffer, preamble_silicon, sizeof(preamble_silicon), &g_DemodBufferLen, &startIdx, false) &&
(g_DemodBufferLen == 32 || g_DemodBufferLen == 64)) {
return true;
}
}
st = true;
if (ASKDemod_ext(0, 1, 1, 0, false, false, false, 1, &st) == PM3_SUCCESS) {
if (preambleSearchEx(g_DemodBuffer, preamble_atmel, sizeof(preamble_atmel), &g_DemodBufferLen, &startIdx, false) &&
(g_DemodBufferLen == 32 || g_DemodBufferLen == 64)) {
return true;
}
if (preambleSearchEx(g_DemodBuffer, preamble_silicon, sizeof(preamble_silicon), &g_DemodBufferLen, &startIdx, false) &&
(g_DemodBufferLen == 32 || g_DemodBufferLen == 64)) {
return true;
}
}
if (ASKbiphaseDemod(0, 0, 0, 2, false) == PM3_SUCCESS) {
if (preambleSearchEx(g_DemodBuffer, preamble_atmel, sizeof(preamble_atmel), &g_DemodBufferLen, &startIdx, false) &&
(g_DemodBufferLen == 32 || g_DemodBufferLen == 64)) {
return true;
}
if (preambleSearchEx(g_DemodBuffer, preamble_silicon, sizeof(preamble_silicon), &g_DemodBufferLen, &startIdx, false) &&
(g_DemodBufferLen == 32 || g_DemodBufferLen == 64)) {
return true;
}
}
if (ASKbiphaseDemod(0, 0, 1, 2, false) == PM3_SUCCESS) {
if (preambleSearchEx(g_DemodBuffer, preamble_atmel, sizeof(preamble_atmel), &g_DemodBufferLen, &startIdx, false) &&
(g_DemodBufferLen == 32 || g_DemodBufferLen == 64)) {
return true;
}
if (preambleSearchEx(g_DemodBuffer, preamble_silicon, sizeof(preamble_silicon), &g_DemodBufferLen, &startIdx, false) &&
(g_DemodBufferLen == 32 || g_DemodBufferLen == 64)) {
return true;
}
}
}
// try NRZ clock detect. it could be another type even if successful.
clk = GetNrzClock("", false); //has the most false positives :(
if (clk > 0) {
if (NRZrawDemod(0, 0, 1, false) == PM3_SUCCESS) {
if (preambleSearchEx(g_DemodBuffer, preamble_atmel, sizeof(preamble_atmel), &g_DemodBufferLen, &startIdx, false) &&
(g_DemodBufferLen == 32 || g_DemodBufferLen == 64)) {
return true;
}
if (preambleSearchEx(g_DemodBuffer, preamble_silicon, sizeof(preamble_silicon), &g_DemodBufferLen, &startIdx, false) &&
(g_DemodBufferLen == 32 || g_DemodBufferLen == 64)) {
return true;
}
}
if (NRZrawDemod(0, 1, 1, false) == PM3_SUCCESS) {
if (preambleSearchEx(g_DemodBuffer, preamble_atmel, sizeof(preamble_atmel), &g_DemodBufferLen, &startIdx, false) &&
(g_DemodBufferLen == 32 || g_DemodBufferLen == 64)) {
return true;
}
if (preambleSearchEx(g_DemodBuffer, preamble_silicon, sizeof(preamble_silicon), &g_DemodBufferLen, &startIdx, false) &&
(g_DemodBufferLen == 32 || g_DemodBufferLen == 64)) {
return true;
}
}
}
// Fewer card uses PSK
// try psk clock detect. if successful it cannot be any other type of modulation... (in theory...)
clk = GetPskClock("", false);
if (clk > 0) {
// allow undo
// save_restoreGB(GRAPH_SAVE);
// skip first 160 samples to allow antenna to settle in (psk gets inverted occasionally otherwise)
//CmdLtrim("-i 160");
if (PSKDemod(0, 0, 6, false) == PM3_SUCCESS) {
//save_restoreGB(GRAPH_RESTORE);
if (preambleSearchEx(g_DemodBuffer, preamble_atmel, sizeof(preamble_atmel), &g_DemodBufferLen, &startIdx, false) &&
(g_DemodBufferLen == 32 || g_DemodBufferLen == 64)) {
return true;
}
if (preambleSearchEx(g_DemodBuffer, preamble_silicon, sizeof(preamble_silicon), &g_DemodBufferLen, &startIdx, false) &&
(g_DemodBufferLen == 32 || g_DemodBufferLen == 64)) {
return true;
}
}
if (PSKDemod(0, 1, 6, false) == PM3_SUCCESS) {
//save_restoreGB(GRAPH_RESTORE);
if (preambleSearchEx(g_DemodBuffer, preamble_atmel, sizeof(preamble_atmel), &g_DemodBufferLen, &startIdx, false) &&
(g_DemodBufferLen == 32 || g_DemodBufferLen == 64)) {
return true;
}
if (preambleSearchEx(g_DemodBuffer, preamble_silicon, sizeof(preamble_silicon), &g_DemodBufferLen, &startIdx, false) &&
(g_DemodBufferLen == 32 || g_DemodBufferLen == 64)) {
return true;
}
}
// PSK2 - needs a call to psk1TOpsk2.
if (PSKDemod(0, 0, 6, false) == PM3_SUCCESS) {
psk1TOpsk2(g_DemodBuffer, g_DemodBufferLen);
//save_restoreGB(GRAPH_RESTORE);
if (preambleSearchEx(g_DemodBuffer, preamble_atmel, sizeof(preamble_atmel), &g_DemodBufferLen, &startIdx, false) &&
(g_DemodBufferLen == 32 || g_DemodBufferLen == 64)) {
return true;
}
if (preambleSearchEx(g_DemodBuffer, preamble_silicon, sizeof(preamble_silicon), &g_DemodBufferLen, &startIdx, false) &&
(g_DemodBufferLen == 32 || g_DemodBufferLen == 64)) {
return true;
}
} // inverse waves does not affect PSK2 demod
//undo trim samples
//save_restoreGB(GRAPH_RESTORE);
// no other modulation clocks = 2 or 4 so quit searching
if (fc1 != 8) {
return false;
}
}
return false;
}
// does this need to be a callable command?
static int CmdT55xxDetectPage1(const char *Cmd) {
CLIParserContext *ctx;
CLIParserInit(&ctx, "lf t55xx p1detect",
"Detect Page 1 of a T55xx chip",
"lf t55xx p1detect\n"
"lf t55xx p1detect -1\n"
"lf t55xx p1detect -p 11223344 --r3\n"
);
// 1 (help) + 2 (two user specified params) + (5 T55XX_DLMODE_SINGLE)
void *argtable[3 + 5] = {
arg_param_begin,
arg_lit0("1", NULL, "extract using data from graphbuffer"),
arg_str0("p", "pwd", "<hex>", "password (4 hex bytes)"),
};
uint8_t idx = 3;
arg_add_t55xx_downloadlink(argtable, &idx, T55XX_DLMODE_SINGLE, config.downlink_mode);
CLIExecWithReturn(ctx, Cmd, argtable, true);
bool use_graphbuf = arg_get_lit(ctx, 1);
bool usepwd = false;
uint32_t password = 0;
int res = arg_get_u32_hexstr_def(ctx, 2, 0, &password);
if (res == 2) {
PrintAndLogEx(INFO, "Password should be 4 hex bytes");
CLIParserFree(ctx);
return PM3_EINVARG;
} else if (res == 1) {
usepwd = true;
}
bool r0 = arg_get_lit(ctx, 3);
bool r1 = arg_get_lit(ctx, 4);
bool r2 = arg_get_lit(ctx, 5);
bool r3 = arg_get_lit(ctx, 6);
CLIParserFree(ctx);
if ((r0 + r1 + r2 + r3) > 1) {
PrintAndLogEx(FAILED, "Error multiple downlink encoding");
return PM3_EINVARG;
}
uint8_t downlink_mode = config.downlink_mode;
if (r0)
downlink_mode = refFixedBit;
else if (r1)
downlink_mode = refLongLeading;
else if (r2)
downlink_mode = refLeading0;
else if (r3)
downlink_mode = ref1of4;
bool try_all_dl_modes = true;
//ICEMAN STRANGE
if (downlink_mode == 4)
try_all_dl_modes = true;
if (downlink_mode < 4)
try_all_dl_modes = false;
if (downlink_mode > 3)
downlink_mode = 0;
bool found = false;
uint8_t found_mode = 0;
if (use_graphbuf == false) {
for (uint8_t dl_mode = downlink_mode; dl_mode < 4; dl_mode++) {
if (AcquireData(T55x7_PAGE1, T55x7_TRACE_BLOCK1, usepwd, password, dl_mode) == false)
continue;
if (tryDetectP1(false)) {
found = true;
found_mode = dl_mode;
break;
} else {
found = false;
}
if (try_all_dl_modes == false) {
break;
}
}
} else {
found = tryDetectP1(false);
}
if (found) {
PrintAndLogEx(SUCCESS, "T55xx chip found!");
T55xx_Print_DownlinkMode(found_mode);
} else
PrintAndLogEx(WARNING, "Could not detect modulation automatically. Try setting it manually with " _YELLOW_("\'lf t55xx config\'"));
return PM3_SUCCESS;
}
static int CmdT55xxSetDeviceConfig(const char *Cmd) {
CLIParserContext *ctx;
CLIParserInit(&ctx, "lf t55xx deviceconfig",
"Sets t55x7 timings for direct commands.\n"
"The timings are set here in Field Clocks (FC) which is converted to (US) on device.",
"lf t55xx deviceconfig -a 29 -b 17 -c 15 -d 47 -e 15 -> default T55XX\n"
"lf t55xx deviceconfig -a 55 -b 14 -c 21 -d 30 -> default EM4305"
);
// 1 (help) + 9 (nine user specified params) + (5 T55XX_DLMODE_SINGLE)
void *argtable[10 + 5] = {
arg_param_begin,
arg_int0("a", NULL, "<8..255>", "Set start gap"),
arg_int0("b", NULL, "<8..255>", "Set write gap"),
arg_int0("c", NULL, "<8..255>", "Set write ZERO gap"),
arg_int0("d", NULL, "<8..255>", "Set write ONE gap"),
arg_int0("e", NULL, "<8..255>", "Set read gap"),
arg_int0("f", NULL, "<8..255>", "Set write TWO gap (1 of 4 only)"),
arg_int0("g", NULL, "<8..255>", "Set write THREE gap (1 of 4 only)"),
arg_lit0("p", "persist", "persist to flash memory (RDV4)"),
arg_lit0("z", NULL, "Set default t55x7 timings (use `-p` to save if required)"),
};
uint8_t idx = 10;
arg_add_t55xx_downloadlink(argtable, &idx, T55XX_DLMODE_SINGLE, config.downlink_mode);
CLIExecWithReturn(ctx, Cmd, argtable, false);
uint8_t startgap = arg_get_int(ctx, 1);
uint8_t writegap = arg_get_int(ctx, 2);
uint8_t write0 = arg_get_int(ctx, 3);
uint8_t write1 = arg_get_int(ctx, 4);
uint8_t readgap = arg_get_int(ctx, 5);
uint8_t write2 = arg_get_int(ctx, 6);
uint8_t write3 = arg_get_int(ctx, 7);
bool shall_persist = arg_get_lit(ctx, 8);
bool set_defaults = arg_get_lit(ctx, 9);
bool r0 = arg_get_lit(ctx, 10);
bool r1 = arg_get_lit(ctx, 11);
bool r2 = arg_get_lit(ctx, 12);
bool r3 = arg_get_lit(ctx, 13);
CLIParserFree(ctx);
if ((r0 + r1 + r2 + r3) > 1) {
PrintAndLogEx(FAILED, "Error multiple downlink encoding");
return PM3_EINVARG;
}
uint8_t downlink_mode = 0;
if (r0)
downlink_mode = refFixedBit;
else if (r1)
downlink_mode = refLongLeading;
else if (r2)
downlink_mode = refLeading0;
else if (r3)
downlink_mode = ref1of4;
t55xx_setconfig_t payload = {
.conf = {{{0}, {0}, {0}, {0}}},
.persist = (shall_persist) ? 1 : 0,
.rfu = 0,
};
if (set_defaults) {
// fixed bit length
payload.conf.m[T55XX_DLMODE_FIXED].start_gap = 29 * 8;
payload.conf.m[T55XX_DLMODE_FIXED].write_gap = 17 * 8;
payload.conf.m[T55XX_DLMODE_FIXED].write_0 = 15 * 8;
payload.conf.m[T55XX_DLMODE_FIXED].write_1 = 47 * 8;
payload.conf.m[T55XX_DLMODE_FIXED].read_gap = 15 * 8;
payload.conf.m[T55XX_DLMODE_FIXED].write_2 = 0;
payload.conf.m[T55XX_DLMODE_FIXED].write_3 = 0;
// long leading reference
payload.conf.m[T55XX_DLMODE_LLR].start_gap = 29 * 8;
payload.conf.m[T55XX_DLMODE_LLR].write_gap = 17 * 8;
payload.conf.m[T55XX_DLMODE_LLR].write_0 = 15 * 8;
payload.conf.m[T55XX_DLMODE_LLR].write_1 = 47 * 8;
payload.conf.m[T55XX_DLMODE_LLR].read_gap = 15 * 8;
payload.conf.m[T55XX_DLMODE_LLR].write_2 = 0;
payload.conf.m[T55XX_DLMODE_LLR].write_3 = 0;
// leading zero
payload.conf.m[T55XX_DLMODE_LEADING_ZERO].start_gap = 29 * 8;
payload.conf.m[T55XX_DLMODE_LEADING_ZERO].write_gap = 17 * 8;
payload.conf.m[T55XX_DLMODE_LEADING_ZERO].write_0 = 15 * 8;
payload.conf.m[T55XX_DLMODE_LEADING_ZERO].write_1 = 40 * 8;
payload.conf.m[T55XX_DLMODE_LEADING_ZERO].read_gap = 15 * 8;
payload.conf.m[T55XX_DLMODE_LEADING_ZERO].write_2 = 0;
payload.conf.m[T55XX_DLMODE_LEADING_ZERO].write_3 = 0;
// 1 of 4 coding reference
payload.conf.m[T55XX_DLMODE_1OF4].start_gap = 29 * 8;
payload.conf.m[T55XX_DLMODE_1OF4].write_gap = 17 * 8;
payload.conf.m[T55XX_DLMODE_1OF4].write_0 = 15 * 8;
payload.conf.m[T55XX_DLMODE_1OF4].write_1 = 31 * 8;
payload.conf.m[T55XX_DLMODE_1OF4].read_gap = 15 * 8;
payload.conf.m[T55XX_DLMODE_1OF4].write_2 = 47 * 8;
payload.conf.m[T55XX_DLMODE_1OF4].write_3 = 63 * 8;
} else {
payload.conf.m[downlink_mode].start_gap = startgap * 8;
payload.conf.m[downlink_mode].write_gap = writegap * 8;
payload.conf.m[downlink_mode].write_0 = write0 * 8;
payload.conf.m[downlink_mode].write_1 = write1 * 8;
payload.conf.m[downlink_mode].read_gap = readgap * 8;
payload.conf.m[downlink_mode].write_2 = write2 * 8;
payload.conf.m[downlink_mode].write_3 = write3 * 8;
}
clearCommandBuffer();
SendCommandNG(CMD_LF_T55XX_SET_CONFIG, (uint8_t *)&payload, sizeof(payload));
PacketResponseNG resp;
if (WaitForResponseTimeout(CMD_LF_T55XX_SET_CONFIG, &resp, 2000) == false) {
PrintAndLogEx(WARNING, "timeout while waiting for reply");
return PM3_ETIMEOUT;
}
if (resp.status != PM3_SUCCESS) {
PrintAndLogEx(FAILED, "Setting timings ( " _RED_("fail") " )");
return resp.status;
}
PrintAndLogEx(SUCCESS, "Setting timings ( " _GREEN_("ok") " )");
return PM3_SUCCESS;
}
static int CmdT55xxProtect(const char *Cmd) {
CLIParserContext *ctx;
CLIParserInit(&ctx, "lf t55xx protect",
"This command sets the pwd bit on T5577.\n"
_RED_("WARNING") _CYAN_(" this locks the tag!"),
"lf t55xx protect -n 01020304 -> sets new pwd 01020304\n"
"lf t55xx protect -p 11223344 -n 00000000 -> use pwd 11223344, sets new pwd 00000000"
);
// 1 (help) + 3 (three user specified params) + (5 T55XX_DLMODE_SINGLE)
void *argtable[4 + 5] = {
arg_param_begin,
arg_lit0("o", "override", "override safety check"),
arg_str0("p", "pwd", "<hex>", "password (4 hex bytes)"),
arg_str1("n", "new", "<hex>", "new password (4 hex bytes)"),
};
uint8_t idx = 4;
arg_add_t55xx_downloadlink(argtable, &idx, T55XX_DLMODE_SINGLE, config.downlink_mode);
CLIExecWithReturn(ctx, Cmd, argtable, true);
uint8_t override = 0;
if (arg_get_lit(ctx, 1))
override = 2;
uint32_t password = 0;
bool usepwd = false;
int res = arg_get_u32_hexstr_def(ctx, 2, 0, &password);
if (res == 2) {
CLIParserFree(ctx);
PrintAndLogEx(FAILED, "Error parsing password bytes");
return PM3_EINVARG;
} else if (res == 1) {
usepwd = true;
override = 1;
}
uint32_t new_password = 0;
res = arg_get_u32_hexstr_def(ctx, 3, 0, &new_password);
if (res == 2) {
CLIParserFree(ctx);
PrintAndLogEx(FAILED, "Error parsing new password bytes");
return PM3_EINVARG;
} else if (res == 0) {
PrintAndLogEx(FAILED, "Must specify new password param");
CLIParserFree(ctx);
return PM3_EINVARG;
}
bool r0 = arg_get_lit(ctx, 4);
bool r1 = arg_get_lit(ctx, 5);
bool r2 = arg_get_lit(ctx, 6);
bool r3 = arg_get_lit(ctx, 7);
CLIParserFree(ctx);
if ((r0 + r1 + r2 + r3) > 1) {
PrintAndLogEx(FAILED, "Error multiple downlink encoding");
return PM3_EINVARG;
}
uint8_t downlink_mode = config.downlink_mode;
if (r0)
downlink_mode = refFixedBit;
else if (r1)
downlink_mode = refLongLeading;
else if (r2)
downlink_mode = refLeading0;
else if (r3)
downlink_mode = ref1of4;
// sanity check.
if (SanityOfflineCheck(false) != PM3_SUCCESS)
return PM3_ESOFT;
// lock
if (t55xxProtect(true, usepwd, override, password, downlink_mode, new_password) == false) {
PrintAndLogEx(WARNING, "Command failed. Did you run " _YELLOW_("`lf t55xx detect`") " before?");
return PM3_ESOFT;
}
return PM3_SUCCESS;
}
// if the difference between a and b is less than or eq to d i.e. does a = b +/- d
#define APPROX_EQ(a, b, d) ((abs(a - b) <= d) ? true : false)
static uint8_t t55sniff_get_packet(const int *pulseBuffer, char *data, uint8_t width0, uint8_t width1, uint8_t tolerance) {
int i = 0;
bool ok = true;
uint8_t len = 0;
while (ok && (i < 73)) { // 70 bits max Fixed bit packet
if (APPROX_EQ(width0, pulseBuffer[i], tolerance)) {
data[len++] = '0';
i++;
continue;
}
if (APPROX_EQ(width1, pulseBuffer[i], tolerance)) {
data[len++] = '1';
i++;
continue;
}
ok = false;
}
data[len] = 0x00;
return len;
}
static uint8_t t55sniff_trim_samples(int *pulseBuffer, int *pulseIdx, uint8_t len) {
for (uint8_t i = 0; i < (80 - len); i++) {
pulseBuffer[i] = pulseBuffer[i + len];
}
*pulseIdx -= len;
return PM3_SUCCESS;
}
static int CmdT55xxSniff(const char *Cmd) {
CLIParserContext *ctx;
CLIParserInit(&ctx, "lf t55xx sniff",
"Sniff LF t55xx based trafic and decode possible cmd / blocks.\n"
"Lower tolerance means tighter pulses. ",
"lf t55xx sniff\n"
"lf t55xx sniff -1 -t 2 -> use buffer with tolerance of 2\n"
"lf t55xx sniff -1 --zero 7 --one 14 -> use buffer, zero pulse width 7, one pulse width 15"
);
void *argtable[] = {
arg_param_begin,
arg_lit0("1", NULL, "extract using data from graphbuffer"),
arg_int0("t", "tol", "<dec>", "set tolerance level (default 5)"),
// arg_int0(NULL, "signal", "<dec>", "set minimum signal level (default 20)"),
arg_int0("o", "one", "<dec>", "set samples width for ONE pulse (default auto)"),
arg_int0("z", "zero", "<dec>", "set samples width for ZERO pulse (default auto)"),
arg_param_end
};
CLIExecWithReturn(ctx, Cmd, argtable, true);
bool use_graphbuf = arg_get_lit(ctx, 1);
uint8_t tolerance = arg_get_int_def(ctx, 2, 5);
int opt_width1 = arg_get_int_def(ctx, 3, -1);
int opt_width0 = arg_get_int_def(ctx, 4, -1);
CLIParserFree(ctx);
if (opt_width0 == 0) {
PrintAndLogEx(ERR, "Must call with --zero larger than 0");
return PM3_EINVARG;
}
if (opt_width1 == 0) {
PrintAndLogEx(ERR, "Must call with --one larger than 0");
return PM3_EINVARG;
}
if (opt_width0 > 0 && opt_width1 == -1) {
PrintAndLogEx(ERR, _RED_("Missing sample width for ONE"));
return PM3_EINVARG;
}
if (opt_width1 > 0 && opt_width0 == -1) {
PrintAndLogEx(ERR, _RED_("Missing sample width for ZERO"));
return PM3_EINVARG;
}
uint8_t width1 = 0;
uint8_t width0 = 0;
if (opt_width0 > -1)
width0 = (uint8_t)opt_width0 & 0xFF;
if (opt_width1 > -1)
width1 = (uint8_t)opt_width1 & 0xFF;
/*
Notes:
T55xx packet lengths (1 of 4 needs to be checked)
-----------------------------------------------
| Default | LL 0 | Leading 0 | 1 of 4 |
----------------------------------------------------------------|
| Standard Write | 38 | 39 | 39 | 40 |
| Protect Write | 70 | 71 | 73 | 74 |
| AOR | 34 | 35 | 37 | 38 |
| Standard Read | 5 | 6 | 7 | 8 |
| Protect Read | 38 | 39 | 41 | 42 |
| Regular Read | 2 | 3 | 3 | 4 |
| Reset | 2 | 3 | 3 | 4 |
----------------------------------------------------------------
T55xx bit widths (decimation 1) - Expected, but may vary a little
Reference 0 for LL0 and Leading 0 can be longer
-----------------------------------------------
| Default | LL 0 | Leading 0 | 1 of 4 |
----------------------------------------------------|
| 0 | 16 - 32 | 9 - 33 | 5 - 80 | tbc |
| 1 | 48 - 64 | 41 - 72 | 21 - 96 | tbc |
----------------------------------------------------
00 01 10 11
*/
uint8_t page, blockAddr;
size_t idx = 0;
uint32_t usedPassword, blockData;
int pulseSamples = 0, pulseIdx = 0;
char pwdText[100];
char dataText[100];
int pulseBuffer[80] = { 0 }; // max should be 73 +/- - Holds Pulse widths
char data[80]; // linked to pulseBuffer. - Holds 0/1 from pulse widths
// setup and sample data from Proxmark
// if not directed to existing sample/graphbuffer
if (use_graphbuf == false) {
// make loop to call sniff with skip samples..
// then build it up by adding
CmdLFSniff("");
}
// Headings
PrintAndLogEx(NORMAL, "");
PrintAndLogEx(INFO, _CYAN_("T55xx command detection"));
PrintAndLogEx(SUCCESS, "Downlink mode | password | Data | blk | page | 0 | 1 | raw");
PrintAndLogEx(SUCCESS, "------------------------+------------+----------+-----+------+-----+-----+-------------------------------------------------------------------------------");
idx = 0;
// loop though sample buffer
while (idx < g_GraphTraceLen) {
int minWidth = 1000;
int maxWidth = 0;
data[0] = 0;
bool have_data = false;
const char *modeText = "Default";
strncpy(pwdText, " ", sizeof(pwdText));
strncpy(dataText, " ", sizeof(dataText));
if (pulseSamples == 0) {
idx++;
}
// find high
while ((idx < g_GraphTraceLen) && (g_GraphBuffer[idx] < 0)) {
idx++;
}
// count high samples
pulseSamples = 0;
while ((idx < g_GraphTraceLen) && (g_GraphBuffer[idx] > 0)) { // last bit seems to be high to zero, but can vary in width..
pulseSamples++;
idx++;
}
if (pulseSamples > 0) {
pulseBuffer[pulseIdx++] = pulseSamples;
if (pulseIdx > 79) { // make room for next sample - if not used by now, it won't be.
t55sniff_trim_samples(pulseBuffer, &pulseIdx, 1);
}
// Check Samples for valid packets;
// We should find (outside of leading bits) we have a packet of "1" and "0" at same widths.
if (pulseIdx >= 6) {// min size for a read - ignoring 1of4 10 0 <adr>
// We auto find widths
if ((width0 == 0) && (width1 == 0)) {
// We ignore bit 0 for the moment as it may be a ref. pulse, so check last
uint32_t ii = 2;
minWidth = pulseBuffer[1];
maxWidth = pulseBuffer[1];
bool done = false;
while ((!done) && (ii < pulseIdx) && ((maxWidth <= minWidth) || (APPROX_EQ(minWidth, maxWidth, tolerance)))) { // min should be 8, 16-32 more normal
if (pulseBuffer[ii] + 3 < minWidth) {
minWidth = pulseBuffer[ii];
done = true;
}
if (pulseBuffer[ii] - 1 > maxWidth) {
maxWidth = pulseBuffer[ii];
done = true;
}
ii++;
}
} else {
minWidth = width0;
maxWidth = width1;
}
}
// out of bounds... min max far enough appart and minWidth is large enough
if (((maxWidth - minWidth) < 6) || (minWidth < 6)) // min 8 +/-
continue;
// At this point we should have
// - a min of 6 samples
// - the 0 and 1 sample widths
// - min 0 and min separations (worst case)
// No max checks done (yet) as have seen samples > then specs in use.
// Check first bit.
// Long leading 0
if (have_data == false && (APPROX_EQ(pulseBuffer[0], 136 + minWidth, tolerance) && APPROX_EQ(pulseBuffer[1], maxWidth, tolerance))) {
// printf ("Long Leading 0 - not yet handled | have 1 First bit | Min : %-3d - Max : %-3d : diff : %d\n",minWidth,maxWidth, maxWidth-minWidth);
continue;
}
// Fixed bit - Default
if (have_data == false && (APPROX_EQ(pulseBuffer[0], maxWidth, tolerance))) {
uint16_t dataLen = t55sniff_get_packet(pulseBuffer, data, minWidth, maxWidth, tolerance);
// if ((dataLen == 39) )
// printf ("Fixed | Data end of 80 samples | offset : %llu - datalen %-2d - data : %s --- - Bit 0 width : %d\n",idx,dataLen,data,pulseBuffer[0]);
if (data[0] == '0') { // should never get here..
data[0] = 0;
} else {
// Default Read
if (dataLen == 6) {
t55sniff_trim_samples(pulseBuffer, &pulseIdx, 4); // left 1 or 2 samples seemed to help
page = data[1] - '0';
blockAddr = 0;
for (uint8_t i = 3; i < 6; i++) {
blockAddr <<= 1;
if (data[i] == '1') {
blockAddr |= 1;
}
}
blockData = 0;
have_data = true;
modeText = "Default Read";
}
// Password Write
if (dataLen == 70) {
t55sniff_trim_samples(pulseBuffer, &pulseIdx, 70);
page = data[1] - '0';
usedPassword = 0;
for (uint8_t i = 2; i <= 33; i++) {
usedPassword <<= 1;
if (data[i] == '1') {
usedPassword |= 1;
}
}
// Lock bit 34
blockData = 0;
for (uint8_t i = 35; i <= 66; i++) {
blockData <<= 1;
if (data[i] == '1') {
blockData |= 1;
}
}
blockAddr = 0;
for (uint8_t i = 67; i <= 69; i++) {
blockAddr <<= 1;
if (data[i] == '1') {
blockAddr |= 1;
}
}
have_data = true;
modeText = "Default pwd write";
snprintf(pwdText, sizeof(pwdText), " %08X", usedPassword);
snprintf(dataText, sizeof(dataText), "%08X", blockData);
}
// Default Write or password read ???
// the most confusing command.
// if the token is with a password - all is OK,
// if not - read command with a password will lead to write the shifted password to the memory and:
// IF the most bit of the data is `1` ----> IT LEADS TO LOCK this block of the memory
if (dataLen == 38) {
t55sniff_trim_samples(pulseBuffer, &pulseIdx, 38);
page = data[1] - '0';
usedPassword = 0;
blockData = 0;
for (uint8_t i = 3; i <= 34; i++) {
blockData <<= 1;
if (data[i] == '1') {
blockData |= 1;
}
}
for (uint8_t i = 2; i <= 33; i++) {
usedPassword <<= 1;
if (data[i] == '1') {
usedPassword |= 1;
}
}
blockAddr = 0;
for (uint8_t i = 35; i <= 37; i++) {
blockAddr <<= 1;
if (data[i] == '1') {
blockAddr |= 1;
}
}
have_data = true;
modeText = "Default write/pwd read";
snprintf(pwdText, sizeof(pwdText), "[%08X]", usedPassword);
snprintf(dataText, sizeof(dataText), "%08X", blockData);
}
}
}
// Leading 0
if (have_data == false && (APPROX_EQ(pulseBuffer[0], minWidth, tolerance))) {
// leading 0 (should = 0 width)
// 1 of 4 (leads with 00)
uint16_t dataLen = t55sniff_get_packet(pulseBuffer, data, minWidth, maxWidth, tolerance);
// **** Should check to 0 to be actual 0 as well i.e. 01 .... data ....
if ((data[0] == '0') && (data[1] == '1')) {
if (dataLen == 73) {
t55sniff_trim_samples(pulseBuffer, &pulseIdx, 73);
page = data[2] - '0';
usedPassword = 0;
for (uint8_t i = 5; i <= 36; i++) {
usedPassword <<= 1;
if (data[i] == '1') {
usedPassword |= 1;
}
}
blockData = 0;
for (uint8_t i = 38; i <= 69; i++) {
blockData <<= 1;
if (data[i] == '1') {
blockData |= 1;
}
}
blockAddr = 0;
for (uint8_t i = 70; i <= 72; i++) {
blockAddr <<= 1;
if (data[i] == '1') {
blockAddr |= 1;
}
}
have_data = true;
modeText = "Leading 0 pwd write";
snprintf(pwdText, sizeof(pwdText), " %08X", usedPassword);
snprintf(dataText, sizeof(dataText), "%08X", blockData);
}
}
}
}
// Print results
if (have_data) {
if (blockAddr == 7) {
PrintAndLogEx(SUCCESS, "%-22s | "_GREEN_("%10s")" | "_YELLOW_("%8s")" | "_YELLOW_("%d")" | "_GREEN_("%d")" | %3d | %3d | %s"
, modeText
, pwdText
, dataText
, blockAddr
, page
, minWidth
, maxWidth
, data
);
} else {
PrintAndLogEx(SUCCESS, "%-22s | "_GREEN_("%10s")" | "_GREEN_("%8s")" | "_GREEN_("%d")" | "_GREEN_("%d")" | %3d | %3d | %s"
, modeText
, pwdText
, dataText
, blockAddr
, page
, minWidth
, maxWidth
, data
);
}
}
}
// footer
PrintAndLogEx(SUCCESS, "-----------------------------------------------------------------------------------------------------------------------------------------------------");
PrintAndLogEx(NORMAL, "");
return PM3_SUCCESS;
}
static int CmdT55xxView(const char *Cmd) {
CLIParserContext *ctx;
CLIParserInit(&ctx, "lf t55xx view",
"Print a T55xx dump file (bin/eml/json)\n",
"lf t55xx view -f lf-t55xx-00000000-11111111-22222222-33333333-dump.bin"
);
void *argtable[] = {
arg_param_begin,
arg_str1("f", "file", "<fn>", "Specify a filename for dump file"),
arg_lit0("v", "verbose", "verbose output"),
arg_param_end
};
CLIExecWithReturn(ctx, Cmd, argtable, false);
int fnlen = 0;
char filename[FILE_PATH_SIZE];
CLIParamStrToBuf(arg_get_str(ctx, 1), (uint8_t *)filename, FILE_PATH_SIZE, &fnlen);
// bool verbose = arg_get_lit(ctx, 2);
CLIParserFree(ctx);
if (fnlen == 0) {
PrintAndLogEx(ERR, "Must specify a filename");
return PM3_EINVARG;
}
// read dump file
uint32_t *dump = NULL;
size_t bytes_read = 0;
int res = pm3_load_dump(filename, (void **)&dump, &bytes_read, (T55x7_BLOCK_COUNT * 4));
if (res != PM3_SUCCESS) {
return res;
}
if (bytes_read != (T55x7_BLOCK_COUNT * 4)) {
free(dump);
PrintAndLogEx(FAILED, "wrong length of dump file. Expected 48 bytes, got %zu", bytes_read);
return PM3_EFILE;
}
PrintAndLogEx(INFO, "");
PrintAndLogEx(SUCCESS, " " _CYAN_("Page 0"));
PrintAndLogEx(SUCCESS, "----+----------+-------");
PrintAndLogEx(SUCCESS, "blk | hex data | ascii");
PrintAndLogEx(SUCCESS, "----+----------+-------");
uint32_t *pd = dump;
uint8_t tmp[4] = {0};
for (uint8_t i = 0; i < 8; ++i) {
Uint4byteToMemLe(tmp, *pd);
PrintAndLogEx(SUCCESS, " %02d | %s | %s", i, sprint_hex_inrow(tmp, sizeof(tmp)), sprint_ascii(tmp, 4));
pd++;
}
PrintAndLogEx(INFO, "");
PrintAndLogEx(SUCCESS, " " _CYAN_("Page 1"));
PrintAndLogEx(SUCCESS, "----+----------+-------");
PrintAndLogEx(SUCCESS, "blk | hex data | ascii");
PrintAndLogEx(SUCCESS, "----+----------+-------");
for (uint8_t i = 0; i < 4; i++) {
Uint4byteToMemLe(tmp, *pd);
PrintAndLogEx(SUCCESS, " %02d | %s | %s", i, sprint_hex_inrow(tmp, sizeof(tmp)), sprint_ascii(tmp, 4));
pd++;
}
PrintAndLogEx(NORMAL, "");
free(dump);
return PM3_SUCCESS;
}
static command_t CommandTable[] = {
{"-----------", CmdHelp, AlwaysAvailable, "------------------------------- " _CYAN_("notice") " ---------------------------------"},
{"", CmdHelp, AlwaysAvailable, "Always run `" _YELLOW_("lf t55xx detect") "` first whenever a new card is placed"},
{"", CmdHelp, AlwaysAvailable, "on the Proxmark3 or the config block changed."},
{"", CmdHelp, AlwaysAvailable, ""},
{"help", CmdHelp, AlwaysAvailable, "This help"},
{"-----------", CmdHelp, AlwaysAvailable, "----------------------------- " _CYAN_("operations") " -------------------------------"},
{"clonehelp", CmdT55xxCloneHelp, IfPm3Lf, "Shows the available clone commands"},
{"config", CmdT55xxSetConfig, AlwaysAvailable, "Set/Get T55XX configuration (modulation, inverted, offset, rate)"},
{"dangerraw", CmdT55xxDangerousRaw, IfPm3Lf, "Sends raw bitstream. Dangerous, do not use!!"},
{"detect", CmdT55xxDetect, AlwaysAvailable, "Try detecting the tag modulation from reading the configuration block"},
{"deviceconfig", CmdT55xxSetDeviceConfig, IfPm3Lf, "Set/Get T55XX device configuration"},
{"dump", CmdT55xxDump, IfPm3Lf, "Dump T55xx card Page 0 block 0-7"},
{"info", CmdT55xxInfo, AlwaysAvailable, "Show T55x7 configuration data (page 0/ blk 0)"},
{"p1detect", CmdT55xxDetectPage1, IfPm3Lf, "Try detecting if this is a t55xx tag by reading page 1"},
{"read", CmdT55xxReadBlock, IfPm3Lf, "Read T55xx block data"},
{"resetread", CmdResetRead, IfPm3Lf, "Send Reset Cmd then lf read the stream to attempt to identify the start"},
{"restore", CmdT55xxRestore, IfPm3Lf, "Restore T55xx card Page 0 / Page 1 blocks"},
{"trace", CmdT55xxReadTrace, AlwaysAvailable, "Show T55x7 traceability data (page 1/ blk 0-1)"},
{"wakeup", CmdT55xxWakeUp, IfPm3Lf, "Send AOR wakeup command"},
{"view", CmdT55xxView, AlwaysAvailable, "Display content from tag dump file"},
{"write", CmdT55xxWriteBlock, IfPm3Lf, "Write T55xx block data"},
{"-----------", CmdHelp, AlwaysAvailable, "------------------------------ " _CYAN_("recovery") " --------------------------------"},
{"bruteforce", CmdT55xxBruteForce, IfPm3Lf, "Simple bruteforce attack to find password"},
{"chk", CmdT55xxChkPwds, IfPm3Lf, "Check passwords"},
{"protect", CmdT55xxProtect, IfPm3Lf, "Password protect tag"},
{"recoverpw", CmdT55xxRecoverPW, IfPm3Lf, "Try to recover from bad password write from a cloner"},
{"sniff", CmdT55xxSniff, AlwaysAvailable, "Attempt to recover T55xx commands from sample buffer"},
{"special", CmdT55xxSpecial, IfPm3Lf, "Show block changes with 64 different offsets"},
{"wipe", CmdT55xxWipe, IfPm3Lf, "Wipe a T55xx tag and set defaults (will destroy any data on tag)"},
{NULL, NULL, NULL, NULL}
};
static int CmdHelp(const char *Cmd) {
(void)Cmd; // Cmd is not used so far
CmdsHelp(CommandTable);
return PM3_SUCCESS;
}
int CmdLFT55XX(const char *Cmd) {
clearCommandBuffer();
return CmdsParse(CommandTable, Cmd);
}
/*
one of
// Leading 0
lf t55 write -b 3 --pg1 -d 90000800
// 1 of 4
lf t55 write -b 3 --pg1 -d 90000C00
T55xx clone card lock: block 3 page 1 0x00000020 00000000 00000000 00000000 00100000
(this bit in any combo seems to lock the card)
You can have other data in the block write, but if that single bit is set "1" the entire card locks in its current state; no know way to unlock
*/