Added --kgh flag on clone to take care of checksum in last byte of segment payload on KGH cards, which includes the uid. Added a small debug on legic sim. Gated --fill option on wrbl to only allow after offset 22. You can still brick your card by wrbling byte by byte, but fill won't kill.

This commit is contained in:
YoungJules
2026-07-28 14:37:32 +02:00
parent 65465b6cf0
commit faaab023f6
3 changed files with 106 additions and 15 deletions
+19 -2
View File
@@ -32,6 +32,8 @@
static uint8_t *legic_mem; /* card memory, used for sim */
static legic_card_select_t card;/* metadata of currently selected card */
static crc_t legic_crc;
static uint8_t last_rx_len;
static uint32_t last_rx_cmd;
//-----------------------------------------------------------------------------
// Frame timing and pseudorandom number generator
@@ -414,11 +416,16 @@ static int32_t connected_phase(legic_card_select_t *p_card) {
// wait for command
int32_t cmd = rx_frame(&len);
if (cmd < 0) {
last_rx_len = len;
last_rx_cmd = (uint32_t)cmd;
return PM3_ETIMEOUT;
}
last_rx_len = len;
last_rx_cmd = (uint32_t)cmd;
// check if command is LEGIC_READ
if (len == p_card->cmdsize) {
if (len == p_card->cmdsize && ((cmd & 1) == 1)) {
// prepare data
uint8_t byte = legic_mem[cmd >> 1];
uint8_t crc = calc_crc4(cmd, p_card->cmdsize, byte);
@@ -428,6 +435,11 @@ static int32_t connected_phase(legic_card_select_t *p_card) {
return PM3_SUCCESS;
}
// Some readers issue a write header as a 9-bit frame only.
if (len == p_card->cmdsize && ((cmd & 1) == 0)) {
return PM3_SUCCESS;
}
// check if command is LEGIC_WRITE
if (len == p_card->cmdsize + 8 + 4) {
// decode data
@@ -500,9 +512,14 @@ void LegicRfSimulate(uint8_t tagtype, bool send_reply) {
}
// connection is established, process commands until one fails
while (connected_phase(&card) == PM3_SUCCESS) {
int32_t phase_res;
while ((phase_res = connected_phase(&card)) == PM3_SUCCESS) {
WDT_HIT();
}
if (phase_res != PM3_SUCCESS) {
Dbprintf("LEGIC sim session ended: res=%d len=%u cmd=%05x", phase_res, last_rx_len, (unsigned int)last_rx_cmd);
}
}
OUT:
+1 -1
View File
@@ -28,7 +28,7 @@ Seg = Segment Header
SegC = Crc8 over the Segment Header
Stp = Stamp (could be more as 4 - up to 7)
UID = dec User-ID for online-Mapping
kghC = crc8 over MCD + MSN0..MSN2 + UID
kghC = crc8 over MCD + MSN0..MSN2 + WRP + WRC + RD + segment marker + segment data excluding CRC
(example) Legic-Cash on MIM256/1024 tag' (37 bytes)
+86 -12
View File
@@ -96,6 +96,50 @@ static bool legic_clone_update_segment_crcs(uint8_t *data, size_t bytes_read, co
return true;
}
static bool legic_clone_update_kgh_crcs(uint8_t *data, size_t bytes_read, const uint8_t uid[4]) {
// Decoded segmented payload starts at byte 22.
size_t start = 22;
bool found_kgh = false;
uint8_t segment_index = 0;
while (start + 5 <= bytes_read) {
uint16_t seg_len = (((uint16_t)data[start + 1] & 0x0F) << 8) | data[start];
if (seg_len < 6 || start + seg_len > bytes_read) {
break;
}
uint8_t *cmd = calloc(4 + 4 + (seg_len - 1), sizeof(uint8_t));
if (cmd == NULL) {
PrintAndLogEx(WARNING, "Failed to allocate memory");
return false;
}
found_kgh = true;
memcpy(cmd, uid, 4);
cmd[4] = data[start + 2];
cmd[5] = (data[start + 3] & 0x70) >> 4;
cmd[6] = (data[start + 3] & 0x80) >> 7;
cmd[7] = (segment_index == 0) ? 0x00 : 0x93;
memcpy(cmd + 8, data + start, seg_len - 1);
data[start + seg_len - 1] = (uint8_t)CRC8Legic(cmd, 4 + 4 + (seg_len - 1));
free(cmd);
if (data[start + 1] & 0x80) {
break;
}
start += seg_len;
++segment_index;
}
if (!found_kgh) {
PrintAndLogEx(INFO, "No KGH-rewriteable LEGIC Prime segments found; leaving payload CRCs untouched.");
}
return true;
}
static int legic_write_bytes_to_tag(uint16_t offset, uint8_t iv, const uint8_t *data, size_t bytes_read, const char *verb) {
PrintAndLogEx(SUCCESS, "%s", verb);
@@ -155,14 +199,16 @@ static int legic_write_dump_to_tag(uint8_t *dump, size_t bytes_read) {
static int CmdLegicMigrate(const char *Cmd) {
CLIParserContext *ctx;
CLIParserInit(&ctx, "hf legic migrate",
"Clone a LEGIC Prime dump to the currently attached tag.\n"
"Optionally apply a DCF update after cloning, but only when explicitly requested.",
"hf legic migrate -f src.bin\n"
"hf legic migrate -f src.bin --dcf 60EA --danger");
"Clone a LEGIC Prime dump to the currently attached tag.\n"
"Optionally apply a DCF update after cloning, but only when explicitly requested.",
"hf legic migrate -f src.bin\n"
"hf legic migrate -f src.bin --kgh\n"
"hf legic migrate -f src.bin --dcf 60EA --danger");
void *argtable[] = {
arg_param_begin,
arg_str1("f", "file", "<fn>", "Source dump file"),
arg_lit0(NULL, "kgh", "Recalculate KGH payload CRCs"),
arg_str0(NULL, "dcf", "<hex>", "Optional DCF bytes to write after clone"),
arg_lit0(NULL, "danger", "Allow the explicit DCF write"),
arg_param_end
@@ -173,10 +219,12 @@ static int CmdLegicMigrate(const char *Cmd) {
char filename[FILE_PATH_SIZE] = {0};
CLIParamStrToBuf(arg_get_str(ctx, 1), (uint8_t *)filename, FILE_PATH_SIZE, &fnlen);
bool rewrite_kgh = arg_get_lit(ctx, 2);
int dcf_len = 0;
uint8_t dcf[2] = {0};
if (arg_get_str(ctx, 2) != NULL) {
CLIParamHexToBuf(arg_get_str(ctx, 2), dcf, sizeof(dcf), &dcf_len);
if (arg_get_str(ctx, 3) != NULL) {
CLIParamHexToBuf(arg_get_str(ctx, 3), dcf, sizeof(dcf), &dcf_len);
if (dcf_len != 2) {
PrintAndLogEx(WARNING, "DCF must be exactly two bytes");
CLIParserFree(ctx);
@@ -184,7 +232,7 @@ static int CmdLegicMigrate(const char *Cmd) {
}
}
bool allow_dcf = arg_get_lit(ctx, 3);
bool allow_dcf = arg_get_lit(ctx, 4);
CLIParserFree(ctx);
if (fnlen < 1) {
@@ -235,6 +283,12 @@ static int CmdLegicMigrate(const char *Cmd) {
memcpy(target_uid, card.uid, sizeof(target_uid));
uint8_t target_mcc = (uint8_t)CRC8Legic(target_uid, sizeof(target_uid));
legic_clone_update_segment_crcs(dump, bytes_read, target_uid);
if (rewrite_kgh) {
if (legic_clone_update_kgh_crcs(dump, bytes_read, target_uid) == false) {
free(dump);
return PM3_EFAILED;
}
}
memcpy(dump, target_uid, sizeof(target_uid));
dump[4] = target_mcc;
@@ -903,6 +957,11 @@ static int CmdLegicWrbl(const char *Cmd) {
}
}
if (has_fill && offset < 22) {
PrintAndLogEx(WARNING, "Fill mode is only allowed from offset 22 and above");
return PM3_EINVARG;
}
uint32_t IV = 0x55;
legic_chk_iv(&IV);
@@ -1291,11 +1350,13 @@ static int CmdLegicRestore(const char *Cmd) {
static int CmdLegicClone(const char *Cmd) {
CLIParserContext *ctx;
CLIParserInit(&ctx, "hf legic clone",
"Rewrite a LEGIC dump for a new target tag or MCC.\n"
"Use --mcc to rewrite only the dump obfuscation, or --write to clone to the current tag.",
"hf legic clone -f src.bin -c 39 -o clone.bin\n"
"hf legic clone -f src.bin --write\n"
"hf legic clone -f src.bin --write -o clone.bin");
"Rewrite a LEGIC dump for a new target tag or MCC.\n"
"Use --mcc to rewrite only the dump obfuscation, or --write to clone to the current tag.\n"
"Use --kgh with --write to recalculate UID-bound KGH payload CRCs.",
"hf legic clone -f src.bin -c 39 -o clone.bin\n"
"hf legic clone -f src.bin --write\n"
"hf legic clone -f src.bin --write --kgh\n"
"hf legic clone -f src.bin --write --kgh -o clone.bin");
void *argtable[] = {
arg_param_begin,
@@ -1303,6 +1364,7 @@ static int CmdLegicClone(const char *Cmd) {
arg_str0(NULL, "mcc", "<hex>", "Target MCC byte for output-only cloning"),
arg_str0("o", "output", "<fn>", "Output cloned dump file"),
arg_lit0("w", "write", "Write cloned dump to the currently attached tag"),
arg_lit0(NULL, "kgh", "Recalculate KGH payload CRCs"),
arg_param_end
};
CLIExecWithReturn(ctx, Cmd, argtable, false);
@@ -1312,6 +1374,7 @@ static int CmdLegicClone(const char *Cmd) {
CLIParamStrToBuf(arg_get_str(ctx, 1), (uint8_t *)filename, FILE_PATH_SIZE, &fnlen);
bool write_to_tag = arg_get_lit(ctx, 4);
bool rewrite_kgh = arg_get_lit(ctx, 5);
int outlen = 0;
char outfilename[FILE_PATH_SIZE] = {0};
@@ -1356,6 +1419,11 @@ static int CmdLegicClone(const char *Cmd) {
return PM3_EINVARG;
}
if (rewrite_kgh && !write_to_tag) {
PrintAndLogEx(WARNING, "Use --kgh together with --write so the target UID is known");
return PM3_EINVARG;
}
if (!write_to_tag && outlen < 1) {
PrintAndLogEx(WARNING, "Output file is required when using --mcc");
return PM3_EINVARG;
@@ -1401,6 +1469,12 @@ static int CmdLegicClone(const char *Cmd) {
memcpy(target_uid, card.uid, sizeof(target_uid));
target_mcc = (uint8_t)CRC8Legic(target_uid, sizeof(target_uid));
legic_clone_update_segment_crcs(dump, bytes_read, target_uid);
if (rewrite_kgh) {
if (legic_clone_update_kgh_crcs(dump, bytes_read, target_uid) == false) {
free(dump);
return PM3_EFAILED;
}
}
memcpy(dump, target_uid, sizeof(target_uid));
dump[4] = target_mcc;
} else {