Files
proxmark3/client/src/cmdlfti.c
T
Matthew CarrollandClaude Opus 5 782d28932c lf: restore the graph buffer length along with its contents
restore_bufferS32() returns the length it put back and every graph-buffer
caller dropped it, so g_GraphTraceLen kept whatever value the code between
save and restore had left.

In try_detect_modulation() that is the 160 sample psk antenna-settle trim:
`data modulation` gave the buffer's samples back but left it 160 short, and
a following demod lost round(160 / clk) bits off the end. check_chiptype()
loses the same way through the acquisitions its detections make, on the
`lf search` path, and for g_DemodBuffer too. demodTI() loses convLen + 16
on its error path.

save_graphbuffer() / restore_graphbuffer() pair the length and grid offset
with the contents so no call site has to remember them. The visa2000 and
zx8211 sites do not change the length, so converting them is a no-op today
and keeps the next one from being a bug.

Regression test in tools/pm3_tests.sh: `data modulation` on a saved psk1
trace must leave all 20000 samples.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-10 10:33:17 -07:00

393 lines
13 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 TI commands
//-----------------------------------------------------------------------------
#include "cmdlfti.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h> // strncpy
#include <inttypes.h>
#include "cmdparser.h" // command_t
#include "commonutil.h"
#include "comms.h"
#include "crc16.h"
#include "ui.h"
#include "proxgui.h"
#include "graph.h"
#include "cliparser.h"
static int CmdHelp(const char *Cmd);
int demodTI(bool verbose) {
(void) verbose; // unused so far
/* MATLAB as follows:
f_s = 2000000; % sampling frequency
f_l = 123200; % low FSK tone
f_h = 134200; % high FSK tone
T_l = 119e-6; % low bit duration
T_h = 130e-6; % high bit duration
l = 2*pi*ones(1, floor(f_s*T_l))*(f_l/f_s);
h = 2*pi*ones(1, floor(f_s*T_h))*(f_h/f_s);
l = sign(sin(cumsum(l)));
h = sign(sin(cumsum(h)));
*/
// 2M*16/134.2k = 238
static const int LowTone[] = {
1, 1, 1, 1, 1, 1, 1, 1, 1, -1, -1, -1, -1, -1, -1, -1, -1,
1, 1, 1, 1, 1, 1, 1, 1, -1, -1, -1, -1, -1, -1, -1, -1,
1, 1, 1, 1, 1, 1, 1, 1, -1, -1, -1, -1, -1, -1, -1, -1,
1, 1, 1, 1, 1, 1, 1, 1, -1, -1, -1, -1, -1, -1, -1, -1,
1, 1, 1, 1, 1, 1, 1, 1, 1, -1, -1, -1, -1, -1, -1, -1, -1,
1, 1, 1, 1, 1, 1, 1, 1, -1, -1, -1, -1, -1, -1, -1, -1,
1, 1, 1, 1, 1, 1, 1, 1, -1, -1, -1, -1, -1, -1, -1, -1,
1, 1, 1, 1, 1, 1, 1, 1, -1, -1, -1, -1, -1, -1, -1, -1,
1, 1, 1, 1, 1, 1, 1, 1, 1, -1, -1, -1, -1, -1, -1, -1, -1,
1, 1, 1, 1, 1, 1, 1, 1, -1, -1, -1, -1, -1, -1, -1, -1,
1, 1, 1, 1, 1, 1, 1, 1, -1, -1, -1, -1, -1, -1, -1, -1,
1, 1, 1, 1, 1, 1, 1, 1, -1, -1, -1, -1, -1, -1, -1, -1,
1, 1, 1, 1, 1, 1, 1, 1, 1, -1, -1, -1, -1, -1, -1, -1, -1,
1, 1, 1, 1, 1, 1, 1, 1, -1, -1, -1, -1, -1, -1, -1, -1,
1, 1, 1, 1, 1, 1, 1, 1, -1, -1
};
// 2M*16/123.2k = 260
static const int HighTone[] = {
1, 1, 1, 1, 1, 1, 1, 1, -1, -1, -1, -1, -1, -1, -1,
1, 1, 1, 1, 1, 1, 1, 1, -1, -1, -1, -1, -1, -1, -1,
1, 1, 1, 1, 1, 1, 1, -1, -1, -1, -1, -1, -1, -1, -1,
1, 1, 1, 1, 1, 1, 1, -1, -1, -1, -1, -1, -1, -1, -1,
1, 1, 1, 1, 1, 1, 1, -1, -1, -1, -1, -1, -1, -1, -1,
1, 1, 1, 1, 1, 1, 1, -1, -1, -1, -1, -1, -1, -1,
1, 1, 1, 1, 1, 1, 1, 1, -1, -1, -1, -1, -1, -1, -1,
1, 1, 1, 1, 1, 1, 1, 1, -1, -1, -1, -1, -1, -1, -1,
1, 1, 1, 1, 1, 1, 1, -1, -1, -1, -1, -1, -1, -1, -1,
1, 1, 1, 1, 1, 1, 1, -1, -1, -1, -1, -1, -1, -1, -1,
1, 1, 1, 1, 1, 1, 1, -1, -1, -1, -1, -1, -1, -1,
1, 1, 1, 1, 1, 1, 1, 1, -1, -1, -1, -1, -1, -1, -1,
1, 1, 1, 1, 1, 1, 1, 1, -1, -1, -1, -1, -1, -1, -1,
1, 1, 1, 1, 1, 1, 1, 1, -1, -1, -1, -1, -1, -1, -1,
1, 1, 1, 1, 1, 1, 1, -1, -1, -1, -1, -1, -1, -1, -1,
1, 1, 1, 1, 1, 1, 1, -1, -1, -1, -1, -1, -1, -1, -1,
1, 1, 1, 1, 1, 1, 1, -1, -1, -1, -1, -1, -1, -1,
1, 1, 1, 1, 1, 1, 1, 1
};
buffer_savestate_t saveState = save_graphbuffer();
int lowLen = ARRAYLEN(LowTone);
int highLen = ARRAYLEN(HighTone);
int convLen = (highLen > lowLen) ? highLen : lowLen;
int i, j, TagType;
int lowSum = 0, highSum = 0;
int lowTot = 0, highTot = 0;
int retval = PM3_ESOFT;
if (g_GraphTraceLen < convLen) {
return retval;
}
for (i = 0; i < g_GraphTraceLen - convLen; i++) {
lowSum = 0;
highSum = 0;
for (j = 0; j < lowLen; j++) {
lowSum += LowTone[j] * g_GraphBuffer[i + j];
}
for (j = 0; j < highLen; j++) {
highSum += HighTone[j] * g_GraphBuffer[i + j];
}
lowSum = abs((100 * lowSum) / lowLen);
highSum = abs((100 * highSum) / highLen);
lowSum = (lowSum < 0) ? -lowSum : lowSum;
highSum = (highSum < 0) ? -highSum : highSum;
g_GraphBuffer[i] = (highSum << 16) | lowSum;
}
for (i = 0; i < g_GraphTraceLen - convLen - 16; i++) {
lowTot = 0;
highTot = 0;
// 16 and 15 are f_s divided by f_l and f_h, rounded
for (j = 0; j < 16; j++) {
lowTot += (g_GraphBuffer[i + j] & 0xffff);
}
for (j = 0; j < 15; j++) {
highTot += (g_GraphBuffer[i + j] >> 16);
}
g_GraphBuffer[i] = lowTot - highTot;
}
g_GraphTraceLen -= (convLen + 16);
RepaintGraphWindow();
// TI tag data format is 16 prebits, 8 start bits, 64 data bits,
// 16 crc CCITT bits, 8 stop bits, 15 end bits
// the 16 prebits are always low
// the 8 start and stop bits of a tag must match
// the start/stop prebits of a ro tag are 01111110
// the start/stop prebits of a rw tag are 11111110
// the 15 end bits of a ro tag are all low
// the 15 end bits of a rw tag match bits 15-1 of the data bits
// Okay, so now we have unsliced soft decisions;
// find bit-sync, and then get some bits.
// look for 17 low bits followed by 6 highs (common pattern for ro and rw tags)
int max = 0, maxPos = 0;
for (i = 0; i < 6000; i++) {
int dec = 0;
// searching 17 consecutive lows
for (j = 0; j < 17 * lowLen; j++) {
dec -= g_GraphBuffer[i + j];
}
// searching 7 consecutive highs
for (; j < 17 * lowLen + 6 * highLen; j++) {
dec += g_GraphBuffer[i + j];
}
if (dec > max) {
max = dec;
maxPos = i;
}
}
// place a marker in the buffer to visually aid location
// of the start of sync
g_MarkerC.pos = maxPos;
strcpy(g_MarkerC.label, "Sync Start");
// advance pointer to start of actual data stream (after 16 pre and 8 start bits)
maxPos += 17 * lowLen;
maxPos += 6 * highLen;
// place a marker in the buffer to visually aid location
// of the end of sync
g_MarkerD.pos = maxPos;
strcpy(g_MarkerD.label, "Sync End");
PrintAndLogEx(DEBUG, "actual data bits start at sample %d", maxPos);
PrintAndLogEx(DEBUG, "length %d/%d", highLen, lowLen);
uint8_t bits[1 + 64 + 16 + 8 + 16];
bits[sizeof(bits) - 1] = '\0';
uint32_t shift3 = 0x7e000000, shift2 = 0, shift1 = 0, shift0 = 0;
for (i = 0; i < ARRAYLEN(bits) - 1; i++) {
int high = 0, low = 0;
for (j = 0; j < lowLen; j++) {
low -= g_GraphBuffer[maxPos + j];
}
for (j = 0; j < highLen; j++) {
high += g_GraphBuffer[maxPos + j];
}
if (high > low) {
bits[i] = '1';
maxPos += highLen;
// bitstream arrives lsb first so shift right
shift3 |= (1u << 31);
} else {
bits[i] = '.';
maxPos += lowLen;
}
// 128 bit right shift register
shift0 = (shift0 >> 1) | (shift1 << 31);
shift1 = (shift1 >> 1) | (shift2 << 31);
shift2 = (shift2 >> 1) | (shift3 << 31);
shift3 >>= 1;
// place a marker in the buffer between bits to visually aid location
add_temporary_marker(maxPos, "");
}
RepaintGraphWindow();
PrintAndLogEx(DEBUG, "TI tag : raw tag bits | %s", bits);
TagType = (shift3 >> 8) & 0xFF;
if (TagType != ((shift0 >> 16) & 0xFF)) {
PrintAndLogEx(DEBUG, "TI tag : Error: start and stop bits do not match!");
goto out;
} else if (TagType == 0x7E) {
PrintAndLogEx(INFO, "Readonly TI tag detected.");
retval = PM3_SUCCESS;
goto out;
} else if (TagType == 0xFE) {
PrintAndLogEx(INFO, "Rewriteable TI tag detected.");
// put 64 bit data into shift1 and shift0
shift0 = (shift0 >> 24) | (shift1 << 8);
shift1 = (shift1 >> 24) | (shift2 << 8);
// align 16 bit crc into lower half of shift2
shift2 = ((shift2 >> 24) | (shift3 << 8)) & 0x0FFFF;
// align 16 bit "end bits" or "ident" into lower half of shift3
shift3 >>= 16;
// only 15 bits compare, last bit of ident is not valid
if ((shift3 ^ shift0) & 0x7FFF) {
PrintAndLogEx(WARNING, "Warning: Ident mismatch!");
}
// WARNING the order of the bytes in which we calc crc below needs checking
// i'm 99% sure the crc algorithm is correct, but it may need to eat the
// bytes in reverse or something
// calculate CRC
uint8_t raw[8] = {
(shift0 >> 0) & 0xFF,
(shift0 >> 8) & 0xFF,
(shift0 >> 16) & 0xFF,
(shift0 >> 24) & 0xFF,
(shift1 >> 0) & 0xFF,
(shift1 >> 8) & 0xFF,
(shift1 >> 16) & 0xFF,
(shift1 >> 24) & 0xFF
};
init_table(CRC_KERMIT);
uint16_t calccrc = crc16_kermit(raw, sizeof(raw));
const char *crc_str = (calccrc == (shift2 & 0xFFFF)) ? _GREEN_("ok") : _RED_("fail");
PrintAndLogEx(INFO, "Tag data = %08X%08X [%04X] ( %s )", shift1, shift0, calccrc, crc_str);
if (calccrc != (shift2 & 0xFFFF))
PrintAndLogEx(WARNING, "Warning: CRC mismatch, calculated %04X, got %04X", calccrc, shift2 & 0xFFFF);
retval = PM3_SUCCESS;
goto out;
} else {
PrintAndLogEx(WARNING, "Unknown tag type.");
}
out:
if (retval != PM3_SUCCESS) {
restore_graphbuffer(saveState);
}
return retval;
}
static int CmdTIDemod(const char *Cmd) {
CLIParserContext *ctx;
CLIParserInit(&ctx, "lf ti demod",
"Try to find TI preamble, if found decode / descramble data",
"lf ti demod"
);
void *argtable[] = {
arg_param_begin,
arg_param_end
};
CLIExecWithReturn(ctx, Cmd, argtable, true);
CLIParserFree(ctx);
return demodTI(true);
}
// read a TI tag and return its ID
static int CmdTIReader(const char *Cmd) {
CLIParserContext *ctx;
CLIParserInit(&ctx, "lf ti reader",
"read a TI tag",
"lf ti reader -@ -> continuous reader mode"
);
void *argtable[] = {
arg_param_begin,
arg_lit0("@", NULL, "optional - continuous reader mode"),
arg_param_end
};
CLIExecWithReturn(ctx, Cmd, argtable, true);
bool cm = arg_get_lit(ctx, 1);
CLIParserFree(ctx);
if (cm) {
PrintAndLogEx(INFO, "Press " _GREEN_("<Enter>") " to exit");
}
do {
clearCommandBuffer();
SendCommandNG(CMD_LF_TI_READ, NULL, 0);
} while (cm && (kbd_enter_pressed() == false));
return PM3_SUCCESS;
}
// write new data to a r/w TI tag
static int CmdTIWrite(const char *Cmd) {
CLIParserContext *ctx;
CLIParserInit(&ctx, "lf ti write",
"write to a r/w TI tag.",
"lf ti write --raw 1122334455667788\n"
"lf ti write --raw 1122334455667788 --crc 1122\n"
);
void *argtable[] = {
arg_param_begin,
arg_str1("r", "raw", "<hex>", "raw hex data. 8 bytes max"),
arg_str0(NULL, "crc", "<hex>", "optional - crc"),
arg_param_end
};
CLIExecWithReturn(ctx, Cmd, argtable, false);
int raw_len = 0;
uint8_t raw[8] = {0};
CLIGetHexWithReturn(ctx, 1, raw, &raw_len);
int crc_len = 0;
uint8_t crc[2] = {0};
CLIGetHexWithReturn(ctx, 2, crc, &crc_len);
CLIParserFree(ctx);
struct {
uint32_t high;
uint32_t low;
uint16_t crc;
} PACKED payload;
payload.high = bytes_to_num(raw, 4);
payload.low = bytes_to_num(raw + 4, 4);
payload.crc = bytes_to_num(crc, crc_len);
clearCommandBuffer();
SendCommandNG(CMD_LF_TI_WRITE, (uint8_t *)&payload, sizeof(payload));
PrintAndLogEx(SUCCESS, "Done!");
PrintAndLogEx(HINT, "Hint: Try " _YELLOW_("`lf ti reader`") " to verify");
return PM3_SUCCESS;
}
static command_t CommandTable[] = {
{"help", CmdHelp, AlwaysAvailable, "This help"},
{"demod", CmdTIDemod, AlwaysAvailable, "Demodulate raw bits for TI LF tag from the GraphBuffer"},
{"reader", CmdTIReader, IfPm3Lf, "Read and decode a TI 134 kHz tag"},
{"write", CmdTIWrite, IfPm3Lf, "Write new data to a r/w TI 134 kHz 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 CmdLFTI(const char *Cmd) {
clearCommandBuffer();
return CmdsParse(CommandTable, Cmd);
}