diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b85b54d9..753cb471a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ This project uses the changelog in accordance with [keepchangelog](http://keepac ## [unreleased][unreleased] - Added `hf iclass blacktears` command to perform an automated tearoff of block 1 to set non-secure page mode(@antiklesys) +- Changed wiegand encoding to use shared helpers and have unified parameters (--raw, --bin, --new, --wiegand, etc.) (@cindersocket) - Added `hf gst read` command (@kormax) - Added `hf gst info` command (@kormax) - Added `hf 14b tearoff` - interactive ST25TB/SRx monotonic counter tear-off attack (@xNovyz) diff --git a/client/src/cmdhfmf.c b/client/src/cmdhfmf.c index 35e91776b..547064b0c 100644 --- a/client/src/cmdhfmf.c +++ b/client/src/cmdhfmf.c @@ -10045,18 +10045,53 @@ static int CmdHF14AMfValue(const char *Cmd) { return PM3_SUCCESS; } +// Encode the normalized Wiegand payload into the sentinel-prefixed byte layout stored in +// the HID-specific MIFARE payload block. +static int hfmf_encodehid_pack_block5(const char *binstr, uint8_t *block) { + if (binstr == NULL || block == NULL) { + return PM3_EINVARG; + } + + char bits_with_sentinel[121] = {0}; + size_t binlen = strlen(binstr); + + if (binlen == 0 || (binlen + 1) > 120) { + return PM3_EINVARG; + } + + // MIFARE block 5 stores the Wiegand payload with the same sentinel-prefixed layout + // expected by existing HID cards: one leading 1 followed by the logical payload bits. + bits_with_sentinel[0] = '1'; + memcpy(bits_with_sentinel + 1, binstr, binlen + 1); + + size_t hexlen = 0; + uint8_t hex[15] = {0}; + binstr_2_bytes(hex, &hexlen, bits_with_sentinel); + if (hexlen == 0 || hexlen > (MFBLOCK_SIZE - 1)) { + return PM3_EINVARG; + } + + memset(block + 1, 0x00, MFBLOCK_SIZE - 1); + memcpy(block + 1 + ((MFBLOCK_SIZE - 1) - hexlen), hex, hexlen); + return PM3_SUCCESS; +} + static int CmdHFMFHidEncode(const char *Cmd) { CLIParserContext *ctx; CLIParserInit(&ctx, "hf mf encodehid", - "Encode binary wiegand to card\n" - "Use either --bin or --wiegand/--fc/--cn", + "Encode HID/Wiegand data to a MIFARE Classic card\n" + "Use one of --bin, --raw, --new, or --wiegand/--fc/--cn", "hf mf encodehid --bin 10001111100000001010100011 -> FC 31 CN 337 (H10301)\n" + "hf mf encodehid --raw 063E02A3\n" + "hf mf encodehid --new 068F80A8C0\n" "hf mf encodehid -w H10301 --fc 31 --cn 337\n" ); void *argtable[] = { arg_param_begin, arg_str0(NULL, "bin", "", "Binary string i.e 0001001001"), + arg_str0(NULL, "raw", "", "HID raw hex with sentinel bit already present"), + arg_str0(NULL, "new", "", "new ASN.1 PACS hex from `wiegand encode --new`"), arg_u64_0(NULL, "fc", "", "facility code"), arg_u64_0(NULL, "cn", "", "card number"), arg_str0("w", "wiegand", "", "see " _YELLOW_("`wiegand list`") " for available formats"), @@ -10068,31 +10103,59 @@ static int CmdHFMFHidEncode(const char *Cmd) { uint8_t bin[121] = {0}; int bin_len = sizeof(bin) - 1; // CLIGetStrWithReturn does not guarantee string to be null-terminated CLIGetStrWithReturn(ctx, 1, bin, &bin_len); + bin[bin_len] = '\0'; + + uint8_t raw[15] = {0}; + int raw_len = 0; + int res = CLIParamHexToBuf(arg_get_str(ctx, 2), raw, sizeof(raw), &raw_len); + + uint8_t new_pacs[13] = {0}; + int new_pacs_len = 0; + res |= CLIParamHexToBuf(arg_get_str(ctx, 3), new_pacs, sizeof(new_pacs), &new_pacs_len); wiegand_card_t card; memset(&card, 0, sizeof(wiegand_card_t)); - card.FacilityCode = arg_get_u32_def(ctx, 2, 0); - card.CardNumber = arg_get_u32_def(ctx, 3, 0); + card.FacilityCode = arg_get_u32_def(ctx, 4, 0); + card.CardNumber = arg_get_u32_def(ctx, 5, 0); char format[16] = {0}; int format_len = 0; - CLIParamStrToBuf(arg_get_str(ctx, 4), (uint8_t *)format, sizeof(format), &format_len); + CLIParamStrToBuf(arg_get_str(ctx, 6), (uint8_t *)format, sizeof(format), &format_len); - bool verbose = arg_get_lit(ctx, 5); + bool verbose = arg_get_lit(ctx, 7); CLIParserFree(ctx); - // santity checks - if (bin_len > 120) { + if (res) { + PrintAndLogEx(ERR, "Error parsing hex input"); + return PM3_EINVARG; + } + + if (bin_len > 119) { PrintAndLogEx(ERR, "Binary wiegand string must be less than 120 bits"); return PM3_EINVARG; } - if (bin_len == 0 && card.FacilityCode == 0 && card.CardNumber == 0) { - PrintAndLogEx(ERR, "Must provide either --cn/--fc or --bin"); + int input_modes = 0; + input_modes += (bin_len > 0); + input_modes += (raw_len > 0); + input_modes += (new_pacs_len > 0); + input_modes += (format_len > 0 || card.FacilityCode != 0 || card.CardNumber != 0); + if (input_modes != 1) { + PrintAndLogEx(ERR, "Use exactly one of `--bin`, `--raw`, `--new`, or `--wiegand/--fc/--cn`"); return PM3_EINVARG; } - uint8_t blocks[] = { + if (format_len > 0 && card.FacilityCode == 0 && card.CardNumber == 0) { + PrintAndLogEx(ERR, "`--wiegand` requires `--fc` or `--cn`"); + return PM3_EINVARG; + } + + if (format_len == 0 && (card.FacilityCode != 0 || card.CardNumber != 0)) { + PrintAndLogEx(ERR, "`--fc` and `--cn` require `--wiegand`"); + return PM3_EINVARG; + } + + uint8_t card_blocks[] = { 0x1B, 0x01, 0x4D, 0x48, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0x78, 0x77, 0x88, 0xC1, 0x89, 0xEC, 0xA9, 0x7F, 0x8C, 0x2A, @@ -10102,65 +10165,51 @@ static int CmdHFMFHidEncode(const char *Cmd) { 0x48, 0x49, 0x44, 0x20, 0x49, 0x53, 0x78, 0x77, 0x88, 0xAA, 0x20, 0x47, 0x52, 0x45, 0x41, 0x54, }; + wiegand_input_t input; + memset(&input, 0, sizeof(input)); if (bin_len) { - char mfcbin[121] = {0}; - mfcbin[0] = '1'; - memcpy(mfcbin + 1, bin, bin_len); - - size_t hexlen = 0; - uint8_t hex[15] = {0}; - binstr_2_bytes(hex, &hexlen, mfcbin); - - memcpy(blocks + (MFBLOCK_SIZE * 4) + 1 + (15 - hexlen), hex, hexlen); + res = wiegand_set_plain_binstr((char *)bin, &input); + } else if (raw_len) { + res = wiegand_pack_from_raw_hid(raw, raw_len, &input); + } else if (new_pacs_len) { + res = wiegand_set_new_pacs_binstr(new_pacs, new_pacs_len, &input); } else { - wiegand_message_t packed; - memset(&packed, 0, sizeof(wiegand_message_t)); - int format_idx = HIDFindCardFormat(format); if (format_idx == -1) { PrintAndLogEx(WARNING, "Unknown format: " _YELLOW_("%s"), format); return PM3_EINVARG; } + res = wiegand_pack_from_formatted(format_idx, &card, false, &input); + } + if (res != PM3_SUCCESS) { + PrintAndLogEx(ERR, "Failed to encode HID input"); + return res; + } - if (HIDPack(format_idx, &card, &packed, false) == false) { - PrintAndLogEx(WARNING, "The card data could not be encoded in the selected format."); - return PM3_ESOFT; - } - - // iceman: only for formats w length smaller than 37. - // Needs a check. - - // increase length to allow setting bit just above real data - packed.Length++; - // Set sentinel bit - set_bit_by_position(&packed, true, 0); - -#ifdef HOST_LITTLE_ENDIAN - packed.Mid = BSWAP_32(packed.Mid); - packed.Bot = BSWAP_32(packed.Bot); -#endif - - memcpy(blocks + (MFBLOCK_SIZE * 4) + 8, &packed.Mid, sizeof(packed.Mid)); - memcpy(blocks + (MFBLOCK_SIZE * 4) + 12, &packed.Bot, sizeof(packed.Bot)); + // Unlike LF HID transport, block 5 only needs the normalized bitstring. Raw/new/formatted + // inputs all converge here after the shared Wiegand layer has stripped transport framing. + if (hfmf_encodehid_pack_block5(input.binstr, card_blocks + (MFBLOCK_SIZE * 4)) != PM3_SUCCESS) { + PrintAndLogEx(ERR, "Encoded Wiegand payload is too large to fit in the MIFARE payload"); + return PM3_EINVARG; } uint8_t empty[MIFARE_KEY_SIZE] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; - bool res = true; - for (uint8_t i = 0; i < (sizeof(blocks) / MFBLOCK_SIZE); i++) { + bool write_ok = true; + for (uint8_t i = 0; i < (sizeof(card_blocks) / MFBLOCK_SIZE); i++) { if (verbose) { - PrintAndLogEx(INFO, "Writing %u - %s", (i + 1), sprint_hex_inrow(blocks + (i * MFBLOCK_SIZE), MFBLOCK_SIZE)); + PrintAndLogEx(INFO, "Writing %u - %s", (i + 1), sprint_hex_inrow(card_blocks + (i * MFBLOCK_SIZE), MFBLOCK_SIZE)); } - if (mf_write_block((i + 1), MF_KEY_A, empty, blocks + (i * MFBLOCK_SIZE)) == PM3_EFAILED) { - if (mf_write_block((i + 1), MF_KEY_B, empty, blocks + (i * MFBLOCK_SIZE)) == PM3_EFAILED) { + if (mf_write_block((i + 1), MF_KEY_A, empty, card_blocks + (i * MFBLOCK_SIZE)) == PM3_EFAILED) { + if (mf_write_block((i + 1), MF_KEY_B, empty, card_blocks + (i * MFBLOCK_SIZE)) == PM3_EFAILED) { PrintAndLogEx(WARNING, "failed writing block %d using default empty key", (i + 1)); - res = false; + write_ok = false; break; } } } - if (res == false) { + if (write_ok == false) { PrintAndLogEx(WARNING, "Make sure card is wiped before running this command"); } PrintAndLogEx(NORMAL, ""); diff --git a/client/src/cmdlfhid.c b/client/src/cmdlfhid.c index 0e932fccb..c0454b5f0 100644 --- a/client/src/cmdlfhid.c +++ b/client/src/cmdlfhid.c @@ -44,14 +44,83 @@ #include "wiegand_formats.h" #include "wiegand_formatutils.h" #include "cmdlfem4x05.h" // EM defines -#include "loclass/cipherutils.h" // bitstreamout - -#ifndef BITS -# define BITS 96 -#endif static int CmdHelp(const char *Cmd); +typedef struct { + char format[16]; + int format_len; + wiegand_card_t card; + uint8_t raw[12]; + int raw_len; + uint8_t bin[97]; + int bin_len; + uint8_t new_pacs[13]; + int new_pacs_len; +} lf_hid_cli_input_t; + +// Enforce the narrower LF HID transport limits after the shared Wiegand layer has +// normalized whichever user-facing input mode was selected. +static int lf_hid_validate_packed_transport(const wiegand_input_t *input, const char *command_name) { + // The shared Wiegand layer can normalize credentials that are wider than the LF HID + // transport. Reject only when this specific command needs a packed HID frame. + if (input->packed_valid == false) { + PrintAndLogEx(ERR, "Credential encoded successfully, but %" PRIuMAX "-bit Wiegand data cannot be represented as a packed HID credential", (uintmax_t)input->bin_len); + PrintAndLogEx(ERR, "Packed HID encoding supports up to 84 Wiegand bits"); + return PM3_EINVARG; + } + + // Raw HID input already arrives in transport form and intentionally bypasses the + // packed Wiegand length check that applies to bin/new/formatted inputs. + if (input->packed.Length == 0) { + return PM3_SUCCESS; + } + + if (input->packed.Length > 37) { + PrintAndLogEx(ERR, "%s supports only packed credentials up to 37 bits", command_name); + return PM3_EINVARG; + } + + return PM3_SUCCESS; +} + +// Resolve the CLI's mutually exclusive HID input modes into one normalized representation +// that downstream sim/clone code can consume without caring about the original encoding. +static int lf_hid_resolve_input(const lf_hid_cli_input_t *cli, wiegand_input_t *input, int *format_idx) { + int input_modes = 0; + input_modes += (cli->raw_len > 0); + input_modes += (cli->bin_len > 0); + input_modes += (cli->new_pacs_len > 0); + input_modes += (cli->format_len > 0 || cli->card.FacilityCode != 0 || cli->card.CardNumber != 0 || cli->card.IssueLevel != 0 || cli->card.OEM != 0); + if (input_modes != 1) { + PrintAndLogEx(ERR, "Use exactly one of `--raw`, `--bin`, `--new`, or `--wiegand/--fc/--cn`"); + return PM3_EINVARG; + } + + *format_idx = -1; + if (cli->raw_len == 0 && cli->bin_len == 0 && cli->new_pacs_len == 0) { + *format_idx = HIDFindCardFormat(cli->format); + } + + if (*format_idx == -1 && cli->raw_len == 0 && cli->bin_len == 0 && cli->new_pacs_len == 0) { + PrintAndLogEx(WARNING, "Unknown format: " _YELLOW_("%s"), cli->format); + return PM3_EINVARG; + } + + // Normalize every accepted CLI form into the same wiegand_input_t so sim/clone can + // share validation and transport handling regardless of where the credential came from. + if (cli->raw_len) { + return wiegand_pack_from_raw_hid(cli->raw, cli->raw_len, input); + } + if (cli->bin_len) { + return wiegand_pack_from_plain_bin((char *)cli->bin, input); + } + if (cli->new_pacs_len) { + return wiegand_pack_from_new_pacs(cli->new_pacs, cli->new_pacs_len, input); + } + return wiegand_pack_from_formatted(*format_idx, (wiegand_card_t *)&cli->card, true, input); +} + // sending three times. Didn't seem to break the previous sim? static int sendPing(void) { SendCommandNG(CMD_BREAK_LOOP, NULL, 0); @@ -252,9 +321,9 @@ static int CmdHIDSim(const char *Cmd) { "Enables simulation of HID card with card number.\n" "Simulation runs until the button is pressed or another USB command is issued.", "lf hid sim -r 2006ec0c86 -> HID 10301 26 bit\n" + "lf hid sim --bin 10001111100000001010100011\n" + "lf hid sim --new 068F80A8C0\n" "lf hid sim -r 2e0ec00c87 -> HID Corporate 35 bit\n" - "lf hid sim -r 01f0760643c3 -> HID P10001 40 bit\n" - "lf hid sim -r 01400076000c86 -> HID Corporate 48 bit\n" "lf hid sim -w H10301 --fc 118 --cn 1603 -> HID 10301 26 bit\n" ); @@ -266,61 +335,62 @@ static int CmdHIDSim(const char *Cmd) { arg_u64_0("i", NULL, "", "issue level"), arg_u64_0("o", "oem", "", "OEM code"), arg_str0("r", "raw", "", "raw bytes"), + arg_str0(NULL, "bin", "", "Binary string i.e 0001001001"), + arg_str0(NULL, "new", "", "new ASN.1 PACS hex from `wiegand encode --new`"), arg_param_end }; CLIExecWithReturn(ctx, Cmd, argtable, false); - char format[16] = {0}; - int format_len = 0; - CLIParamStrToBuf(arg_get_str(ctx, 1), (uint8_t *)format, sizeof(format), &format_len); - - wiegand_card_t card; - memset(&card, 0, sizeof(wiegand_card_t)); - card.FacilityCode = arg_get_u32_def(ctx, 2, 0); - card.CardNumber = arg_get_u32_def(ctx, 3, 0); - card.IssueLevel = arg_get_u32_def(ctx, 4, 0); - card.OEM = arg_get_u32_def(ctx, 5, 0); - - int raw_len = 0; - char raw[40] = {0}; - CLIParamStrToBuf(arg_get_str(ctx, 6), (uint8_t *)raw, sizeof(raw), &raw_len); + lf_hid_cli_input_t cli; + memset(&cli, 0, sizeof(cli)); + CLIParamStrToBuf(arg_get_str(ctx, 1), (uint8_t *)cli.format, sizeof(cli.format), &cli.format_len); + cli.card.FacilityCode = arg_get_u32_def(ctx, 2, 0); + cli.card.CardNumber = arg_get_u32_def(ctx, 3, 0); + cli.card.IssueLevel = arg_get_u32_def(ctx, 4, 0); + cli.card.OEM = arg_get_u32_def(ctx, 5, 0); + int res = CLIParamHexToBuf(arg_get_str(ctx, 6), cli.raw, sizeof(cli.raw), &cli.raw_len); + cli.bin_len = sizeof(cli.bin) - 1; + CLIGetStrWithReturn(ctx, 7, cli.bin, &cli.bin_len); + cli.bin[cli.bin_len] = '\0'; + res |= CLIParamHexToBuf(arg_get_str(ctx, 8), cli.new_pacs, sizeof(cli.new_pacs), &cli.new_pacs_len); CLIParserFree(ctx); - wiegand_message_t packed; - memset(&packed, 0, sizeof(wiegand_message_t)); - - // format validation - int format_idx = HIDFindCardFormat(format); - if (format_idx == -1 && raw_len == 0) { - PrintAndLogEx(WARNING, "Unknown format: " _YELLOW_("%s"), format); + if (res) { + PrintAndLogEx(ERR, "Error parsing hex input"); return PM3_EINVARG; } - if (raw_len) { - uint32_t top = 0, mid = 0, bot = 0; - hexstring_to_u96(&top, &mid, &bot, raw); - packed.Top = top; - packed.Mid = mid; - packed.Bot = bot; - } else { - if (HIDPack(format_idx, &card, &packed, true) == false) { - PrintAndLogEx(WARNING, "The card data could not be encoded in the selected format."); - return PM3_ESOFT; - } + if (cli.bin_len > 96) { + PrintAndLogEx(ERR, "Binary wiegand string must be less than 97 bits"); + return PM3_EINVARG; } - if (raw_len == 0) { + wiegand_input_t input; + memset(&input, 0, sizeof(input)); + int format_idx = -1; + res = lf_hid_resolve_input(&cli, &input, &format_idx); + if (res != PM3_SUCCESS) { + PrintAndLogEx(ERR, "Failed to encode HID input"); + return res; + } + + res = lf_hid_validate_packed_transport(&input, "LF HID simulation"); + if (res != PM3_SUCCESS) { + return res; + } + + if (cli.raw_len == 0) { PrintAndLogEx(INFO, "Simulating HID tag"); - HIDTryUnpack(&packed); + HIDTryUnpack(&input.packed); } else { - PrintAndLogEx(INFO, "Simulating HID tag using raw " _GREEN_("%s"), raw); + PrintAndLogEx(INFO, "Simulating HID tag using raw " _GREEN_("%s"), sprint_hex_inrow(cli.raw, cli.raw_len)); } lf_hidsim_t payload; - payload.hi2 = packed.Top; - payload.hi = packed.Mid; - payload.lo = packed.Bot; - payload.longFMT = (packed.Mid > 0xFFF); + payload.hi2 = input.packed.Top; + payload.hi = input.packed.Mid; + payload.lo = input.packed.Bot; + payload.longFMT = (input.packed.Mid > 0xFFF); clearCommandBuffer(); SendCommandNG(CMD_LF_HID_SIMULATE, (uint8_t *)&payload, sizeof(payload)); @@ -334,6 +404,8 @@ static int CmdHIDClone(const char *Cmd) { "clone a HID Prox tag to a T55x7, Q5/T5555 or EM4305/4469 tag.\n" "Tag must be on the antenna when issuing this command.", "lf hid clone -r 2006ec0c86 -> write raw value for T55x7 tag (HID 10301 26 bit)\n" + "lf hid clone --bin 10001111100000001010100011 -> write binary HID payload for T55x7 tag\n" + "lf hid clone --new 068F80A8C0 -> write PACS-encoded HID payload for T55x7 tag\n" "lf hid clone -r 2e0ec00c87 -> write raw value for T55x7 tag (HID Corporate 35 bit)\n" "lf hid clone -r 01f0760643c3 -> write raw value for T55x7 tag (HID P10001 40 bit)\n" "lf hid clone -r 01400076000c86 -> write raw value for T55x7 tag (HID Corporate 48 bit)\n" @@ -353,33 +425,27 @@ static int CmdHIDClone(const char *Cmd) { arg_lit0(NULL, "q5", "optional - specify writing to Q5/T5555 tag"), arg_lit0(NULL, "em", "optional - specify writing to EM4305/4469 tag"), arg_str0(NULL, "bin", "", "Binary string i.e 0001001001"), + arg_str0(NULL, "new", "", "new ASN.1 PACS hex from `wiegand encode --new`"), arg_param_end }; CLIExecWithReturn(ctx, Cmd, argtable, false); - char format[16] = {0}; - int format_len = 0; - CLIParamStrToBuf(arg_get_str(ctx, 1), (uint8_t *)format, sizeof(format), &format_len); - - wiegand_card_t card; - memset(&card, 0, sizeof(wiegand_card_t)); - card.FacilityCode = arg_get_u32_def(ctx, 2, 0); - card.CardNumber = arg_get_u32_def(ctx, 3, 0); - card.IssueLevel = arg_get_u32_def(ctx, 4, 0); - card.OEM = arg_get_u32_def(ctx, 5, 0); - - int raw_len = 0; - char raw[40] = {0}; - CLIParamStrToBuf(arg_get_str(ctx, 6), (uint8_t *)raw, sizeof(raw), &raw_len); + lf_hid_cli_input_t cli; + memset(&cli, 0, sizeof(cli)); + CLIParamStrToBuf(arg_get_str(ctx, 1), (uint8_t *)cli.format, sizeof(cli.format), &cli.format_len); + cli.card.FacilityCode = arg_get_u32_def(ctx, 2, 0); + cli.card.CardNumber = arg_get_u32_def(ctx, 3, 0); + cli.card.IssueLevel = arg_get_u32_def(ctx, 4, 0); + cli.card.OEM = arg_get_u32_def(ctx, 5, 0); + int res = CLIParamHexToBuf(arg_get_str(ctx, 6), cli.raw, sizeof(cli.raw), &cli.raw_len); bool q5 = arg_get_lit(ctx, 7); bool em = arg_get_lit(ctx, 8); - // t5577 can do 6 blocks with 32bits == 192 bits, HID is manchester encoded and doubles in length. - // With parity, manchester and preamble we have about 3 blocks to play with. Ie: 96 bits - uint8_t bin[97] = {0}; - int bin_len = sizeof(bin) - 1; // CLIGetStrWithReturn does not guarantee string to be null-terminated - CLIGetStrWithReturn(ctx, 9, bin, &bin_len); + cli.bin_len = sizeof(cli.bin) - 1; + CLIGetStrWithReturn(ctx, 9, cli.bin, &cli.bin_len); + cli.bin[cli.bin_len] = '\0'; + res |= CLIParamHexToBuf(arg_get_str(ctx, 10), cli.new_pacs, sizeof(cli.new_pacs), &cli.new_pacs_len); CLIParserFree(ctx); if (q5 && em) { @@ -387,59 +453,23 @@ static int CmdHIDClone(const char *Cmd) { return PM3_EINVARG; } - if (bin_len > 96) { - PrintAndLogEx(ERR, "Binary wiegand string must be less than 96 bits"); + if (res) { + PrintAndLogEx(ERR, "Error parsing hex input"); return PM3_EINVARG; } - wiegand_message_t packed; - memset(&packed, 0, sizeof(wiegand_message_t)); - - // format validation - int format_idx = HIDFindCardFormat(format); - if (format_idx == -1 && raw_len == 0) { - PrintAndLogEx(WARNING, "Unknown format: " _YELLOW_("%s"), format); - return PM3_EINVARG; + wiegand_input_t input; + memset(&input, 0, sizeof(input)); + int format_idx = -1; + res = lf_hid_resolve_input(&cli, &input, &format_idx); + if (res != PM3_SUCCESS) { + PrintAndLogEx(ERR, "Failed to encode HID input"); + return res; } - uint32_t top = 0, mid = 0, bot = 0; - if (raw_len) { - hexstring_to_u96(&top, &mid, &bot, raw); - packed.Top = top; - packed.Mid = mid; - packed.Bot = bot; - } else if (bin_len) { - - uint8_t hex[12]; - memset(hex, 0, sizeof(hex)); - BitstreamOut_t bout = {hex, 0, 0 }; - - for (int i = 0; i < 96 - bin_len - 1; i++) { - pushBit(&bout, 0); - } - // add binary sentinel bit. - pushBit(&bout, 1); - - // convert binary string to hex bytes - for (int i = 0; i < bin_len; i++) { - char c = bin[i]; - if (c == '1') - pushBit(&bout, 1); - else if (c == '0') - pushBit(&bout, 0); - } - - packed.Length = bin_len; - packed.Top = bytes_to_num(hex, 4); - packed.Mid = bytes_to_num(hex + 4, 4); - packed.Bot = bytes_to_num(hex + 8, 4); - add_HID_header(&packed); - - } else { - if (HIDPack(format_idx, &card, &packed, true) == false) { - PrintAndLogEx(WARNING, "The card data could not be encoded in the selected format."); - return PM3_ESOFT; - } + res = lf_hid_validate_packed_transport(&input, "LF HID clone"); + if (res != PM3_SUCCESS) { + return res; } char cardtype[16] = {"T55x7"}; @@ -454,18 +484,22 @@ static int CmdHIDClone(const char *Cmd) { snprintf(cardtype, sizeof(cardtype), "EM4305/4469"); } - if (raw_len == 0) { + if (cli.raw_len == 0) { PrintAndLogEx(INFO, "Preparing to clone HID tag"); - HIDUnpack(format_idx, &packed); + if (format_idx >= 0) { + HIDUnpack(format_idx, &input.packed); + } else { + HIDTryUnpack(&input.packed); + } } else { - PrintAndLogEx(INFO, "Preparing to clone HID tag using raw " _YELLOW_("%s"), raw); + PrintAndLogEx(INFO, "Preparing to clone HID tag using raw " _YELLOW_("%s"), sprint_hex_inrow(cli.raw, cli.raw_len)); } lf_hidsim_t payload; - payload.hi2 = packed.Top; - payload.hi = packed.Mid; - payload.lo = packed.Bot; - payload.longFMT = (packed.Mid > 0xFFF); + payload.hi2 = input.packed.Top; + payload.hi = input.packed.Mid; + payload.lo = input.packed.Bot; + payload.longFMT = (input.packed.Mid > 0xFFF); payload.Q5 = q5; payload.EM = em; diff --git a/client/src/cmdwiegand.c b/client/src/cmdwiegand.c index 191a31d38..ff4a1cc93 100644 --- a/client/src/cmdwiegand.c +++ b/client/src/cmdwiegand.c @@ -40,20 +40,16 @@ static int CmdHelp(const char *Cmd); #define PACS_MAX_WIEGAND_BITS 96 #define WIEGAND_MAX_ENCODED_BITS (PACS_MAX_WIEGAND_BITS + 8) -static void wiegand_packed_to_binstr(const wiegand_message_t *packed, char *binstr) { - for (uint8_t i = 0; i < packed->Length; i++) { - binstr[i] = get_bit_by_position((wiegand_message_t *)packed, i) ? '1' : '0'; - } - binstr[packed->Length] = '\0'; -} - static int wiegand_print_new_pacs_verbose(const wiegand_message_t *packed, const uint8_t *pacs, size_t pacs_len) { char binstr[PACS_MAX_WIEGAND_BITS + 1] = {0}; char rawbin[WIEGAND_MAX_ENCODED_BITS + 1] = {0}; uint8_t raw[(WIEGAND_MAX_ENCODED_BITS + 7) / 8] = {0}; size_t raw_len = 0; - wiegand_packed_to_binstr(packed, binstr); + if (wiegand_message_to_binstr(packed, binstr, sizeof(binstr)) == false) { + PrintAndLogEx(ERR, "Failed to render Wiegand payload"); + return PM3_EINVARG; + } rawbin[0] = '1'; memcpy(rawbin + 1, binstr, packed->Length); binstr_2_bytes(raw, &raw_len, rawbin); @@ -83,7 +79,10 @@ static int wiegand_encode_new_pacs(const wiegand_message_t *packed, bool verbose uint8_t pad = padded_bits - packed->Length; char binstr[PACS_MAX_WIEGAND_BITS + 1] = {0}; - wiegand_packed_to_binstr(packed, binstr); + if (wiegand_message_to_binstr(packed, binstr, sizeof(binstr)) == false) { + PrintAndLogEx(ERR, "Failed to render Wiegand payload"); + return PM3_EINVARG; + } memset(binstr + packed->Length, '0', pad); binstr[padded_bits] = '\0'; diff --git a/client/src/wiegand_formats.c b/client/src/wiegand_formats.c index 88f52aaa1..ea8af7603 100644 --- a/client/src/wiegand_formats.c +++ b/client/src/wiegand_formats.c @@ -1805,21 +1805,21 @@ int HIDDumpPACSBits(const uint8_t *const data, const uint8_t length, bool verbos } uint8_t n = length - 1; - uint8_t pad = data[0]; char *binstr = (char *)calloc((length * 8) + 1, sizeof(uint8_t)); if (binstr == NULL) { PrintAndLogEx(WARNING, "Failed to allocate memory"); return PM3_EMALLOC; } - bytes_2_binstr(binstr, data + 1, n); + if (wiegand_new_pacs_to_binstr(data, length, binstr, (length * 8) + 1) == false) { + PrintAndLogEx(ERR, "Invalid PACS value"); + free(binstr); + return PM3_EINVARG; + } // PrintAndLogEx(NORMAL, ""); PrintAndLogEx(INFO, "------------------------- " _CYAN_("Wiegand") " ---------------------------"); PrintAndLogEx(SUCCESS, "PACS............. " _GREEN_("%s"), sprint_hex_inrow(data, length)); - PrintAndLogEx(DEBUG, "padded bin....... " _GREEN_("%s") " ( %zu )", binstr, strlen(binstr)); - - binstr[strlen(binstr) - pad] = '\0'; PrintAndLogEx(DEBUG, "bin.............. " _GREEN_("%s") " ( %zu )", binstr, strlen(binstr)); size_t hexlen = 0; diff --git a/client/src/wiegand_formatutils.c b/client/src/wiegand_formatutils.c index a3c1360e8..ed63bc5dc 100644 --- a/client/src/wiegand_formatutils.c +++ b/client/src/wiegand_formatutils.c @@ -20,7 +20,11 @@ #include #include #include +#include "commonutil.h" +#include "loclass/cipherutils.h" +#include "wiegand_formats.h" #include "wiegand_formatutils.h" +#include "util.h" #include "ui.h" uint8_t get_bit_by_position(const wiegand_message_t *data, uint8_t pos) { @@ -215,3 +219,219 @@ bool add_HID_header(wiegand_message_t *data) { } return true; } + +bool wiegand_message_to_binstr(const wiegand_message_t *packed, char *binstr, size_t binstr_size) { + if (packed == NULL || binstr == NULL || binstr_size <= packed->Length) { + return false; + } + + for (uint8_t i = 0; i < packed->Length; i++) { + binstr[i] = get_bit_by_position(packed, i) ? '1' : '0'; + } + binstr[packed->Length] = '\0'; + return true; +} + +bool wiegand_raw_to_binstr(const uint8_t *raw, size_t raw_len, char *binstr, size_t binstr_size) { + if (raw == NULL || binstr == NULL || raw_len == 0 || binstr_size == 0) { + return false; + } + + size_t raw_bit_len = raw_len * 8; + if (binstr_size <= raw_bit_len) { + return false; + } + + bytes_2_binstr(binstr, raw, raw_len); + + // Raw HID transport embeds a leading sentinel/start bit before the actual Wiegand payload. + // Strip that prefix so every caller downstream sees only the logical payload bits. + char *sentinel = strchr(binstr, '1'); + if (sentinel == NULL || sentinel[1] == '\0') { + return false; + } + + size_t payload_len = strlen(sentinel + 1); + if (binstr_size <= payload_len) { + return false; + } + + memmove(binstr, sentinel + 1, payload_len + 1); + return true; +} + +bool wiegand_new_pacs_to_binstr(const uint8_t *pacs, size_t pacs_len, char *binstr, size_t binstr_size) { + if (pacs == NULL || binstr == NULL || pacs_len < 2 || pacs[0] > 0x07) { + return false; + } + + size_t payload_len = pacs_len - 1; + size_t padded_bits = payload_len * 8; + if (binstr_size <= padded_bits) { + return false; + } + + bytes_2_binstr(binstr, pacs + 1, payload_len); + + // The first PACS byte stores how many zero padding bits were added to the final octet. + size_t trimmed_len = strlen(binstr); + if (pacs[0] >= trimmed_len) { + return false; + } + + binstr[trimmed_len - pacs[0]] = '\0'; + return true; +} + +int wiegand_pack_bin_with_hid_header(const char *binstr, wiegand_message_t *packed) { + size_t bin_len = strlen(binstr); + if (packed == NULL || bin_len == 0 || bin_len > 84) { + return PM3_EINVARG; + } + + uint8_t hex[12] = {0}; + BitstreamOut_t bout = {hex, 0, 0}; + + // HID transport stores the payload right-aligned behind a sentinel bit inside a 96-bit frame. + for (size_t i = 0; i < (96 - bin_len - 1); i++) { + pushBit(&bout, 0); + } + + pushBit(&bout, 1); + + for (size_t i = 0; i < bin_len; i++) { + char c = binstr[i]; + if (c == '1') { + pushBit(&bout, 1); + } else if (c == '0') { + pushBit(&bout, 0); + } else { + return PM3_EINVARG; + } + } + + packed->Length = (uint8_t)bin_len; + packed->Top = bytes_to_num(hex, 4); + packed->Mid = bytes_to_num(hex + 4, 4); + packed->Bot = bytes_to_num(hex + 8, 4); + + return add_HID_header(packed) ? PM3_SUCCESS : PM3_EINVARG; +} + +int wiegand_set_plain_binstr(const char *binstr, wiegand_input_t *input) { + size_t bin_len = strlen(binstr); + if (input == NULL || bin_len == 0 || bin_len >= sizeof(input->binstr)) { + return PM3_EINVARG; + } + + memset(input, 0, sizeof(*input)); + for (size_t i = 0; i < bin_len; i++) { + if (binstr[i] != '0' && binstr[i] != '1') { + return PM3_EINVARG; + } + } + + memcpy(input->binstr, binstr, bin_len + 1); + input->bin_len = bin_len; + return PM3_SUCCESS; +} + +int wiegand_set_new_pacs_binstr(const uint8_t *pacs, size_t pacs_len, wiegand_input_t *input) { + if (input == NULL) { + return PM3_EINVARG; + } + + memset(input, 0, sizeof(*input)); + if (wiegand_new_pacs_to_binstr(pacs, pacs_len, input->binstr, sizeof(input->binstr)) == false) { + return PM3_EINVARG; + } + + input->bin_len = strlen(input->binstr); + return PM3_SUCCESS; +} + +int wiegand_pack_formatted(int format_idx, wiegand_card_t *card, bool preamble, wiegand_message_t *packed) { + if (HIDPack(format_idx, card, packed, preamble) == false) { + return PM3_ESOFT; + } + return PM3_SUCCESS; +} + +int wiegand_pack_from_plain_bin(const char *binstr, wiegand_input_t *input) { + int res = wiegand_set_plain_binstr(binstr, input); + if (res != PM3_SUCCESS) { + return res; + } + + // Plain binary input is still useful to non-HID callers above 84 bits, even though it + // can no longer be repacked into the legacy HID transport words. + if (input->bin_len > 84) { + input->packed_valid = false; + return PM3_SUCCESS; + } + + res = wiegand_pack_bin_with_hid_header(input->binstr, &input->packed); + input->packed_valid = (res == PM3_SUCCESS); + return res; +} + +int wiegand_pack_from_new_pacs(const uint8_t *pacs, size_t pacs_len, wiegand_input_t *input) { + int res = wiegand_set_new_pacs_binstr(pacs, pacs_len, input); + if (res != PM3_SUCCESS) { + return res; + } + + // New PACS can represent longer credentials than packed HID transport can carry. + if (input->bin_len > 84) { + input->packed_valid = false; + return PM3_SUCCESS; + } + + res = wiegand_pack_bin_with_hid_header(input->binstr, &input->packed); + input->packed_valid = (res == PM3_SUCCESS); + return res; +} + +int wiegand_pack_from_formatted(int format_idx, wiegand_card_t *card, bool preamble, wiegand_input_t *input) { + memset(input, 0, sizeof(*input)); + int res = wiegand_pack_formatted(format_idx, card, preamble, &input->packed); + if (res != PM3_SUCCESS) { + return res; + } + input->packed_valid = true; + if (wiegand_message_to_binstr(&input->packed, input->binstr, sizeof(input->binstr)) == false) { + return PM3_EINVARG; + } + input->bin_len = strlen(input->binstr); + return PM3_SUCCESS; +} + +int wiegand_pack_from_raw_hid(const uint8_t *raw, size_t raw_len, wiegand_input_t *input) { + if (raw == NULL || input == NULL || raw_len == 0) { + return PM3_EINVARG; + } + + uint8_t aligned[12] = {0}; + memset(input, 0, sizeof(*input)); + + if (wiegand_raw_to_binstr(raw, raw_len, input->binstr, sizeof(input->binstr)) == false) { + return PM3_EINVARG; + } + input->bin_len = strlen(input->binstr); + if (input->bin_len > 96) { + input->packed_valid = false; + return PM3_SUCCESS; + } + + if (raw_len > sizeof(aligned)) { + return PM3_EINVARG; + } + + memcpy(aligned + (sizeof(aligned) - raw_len), raw, raw_len); + input->packed = initialize_message_object(bytes_to_num(aligned, 4), bytes_to_num(aligned + 4, 4), bytes_to_num(aligned + 8, 4), 0); + // Raw HID input preserves legacy behavior by keeping the transport words exactly as + // provided. The packed length is then derived from the embedded HID header bits + // instead of being recomputed by repacking the normalized payload bitstring. + input->packed_valid = true; + return PM3_SUCCESS; +} diff --git a/client/src/wiegand_formatutils.h b/client/src/wiegand_formatutils.h index 5a67cc264..046dbdd55 100644 --- a/client/src/wiegand_formatutils.h +++ b/client/src/wiegand_formatutils.h @@ -22,6 +22,7 @@ #include #include #include +#include // Structure for packed wiegand messages // Always align lowest value (last transmitted) bit to ordinal position 0 (lowest valued bit bottom) @@ -41,6 +42,13 @@ typedef struct { bool ParityValid; // Only valid for responses } wiegand_card_t; +typedef struct { + size_t bin_len; + char binstr[145]; + bool packed_valid; // False when the input has a valid bitstring but no packed HID transport form. + wiegand_message_t packed; // HID transport words used by LF/HID-oriented callers. +} wiegand_input_t; + uint8_t get_bit_by_position(const wiegand_message_t *data, uint8_t pos); bool set_bit_by_position(wiegand_message_t *data, bool value, uint8_t pos); @@ -55,4 +63,37 @@ wiegand_message_t initialize_message_object(uint32_t top, uint32_t mid, uint32_t uint8_t get_length_from_header(const wiegand_message_t *data); bool add_HID_header(wiegand_message_t *data); +// Render a packed Wiegand message as a plain payload bitstring without HID transport framing. +bool wiegand_message_to_binstr(const wiegand_message_t *packed, char *binstr, size_t binstr_size); + +// Decode raw HID transport bytes into a plain Wiegand payload bitstring. +bool wiegand_raw_to_binstr(const uint8_t *raw, size_t raw_len, char *binstr, size_t binstr_size); + +// Decode the ASN.1 PACS form emitted by `wiegand encode --new` into a plain payload bitstring. +bool wiegand_new_pacs_to_binstr(const uint8_t *pacs, size_t pacs_len, char *binstr, size_t binstr_size); + +// Validate and store a caller-provided binary Wiegand payload without attempting transport packing. +int wiegand_set_plain_binstr(const char *binstr, wiegand_input_t *input); + +// Decode ASN.1 PACS input into the normalized binary representation used by downstream callers. +int wiegand_set_new_pacs_binstr(const uint8_t *pacs, size_t pacs_len, wiegand_input_t *input); + +// Build HID transport words from a plain Wiegand payload, including sentinel placement and headers. +int wiegand_pack_bin_with_hid_header(const char *binstr, wiegand_message_t *packed); + +// Encode a card-format struct through the existing HID formatter and return packed transport words. +int wiegand_pack_formatted(int format_idx, wiegand_card_t *card, bool preamble, wiegand_message_t *packed); + +// Normalize ASN.1 PACS input and, when possible, also derive HID transport words for LF/HID callers. +int wiegand_pack_from_new_pacs(const uint8_t *pacs, size_t pacs_len, wiegand_input_t *input); + +// Normalize plain binary input and, when possible, also derive HID transport words for LF/HID callers. +int wiegand_pack_from_plain_bin(const char *binstr, wiegand_input_t *input); + +// Normalize formatted card data into both payload bits and packed transport words. +int wiegand_pack_from_formatted(int format_idx, wiegand_card_t *card, bool preamble, wiegand_input_t *input); + +// Preserve legacy raw HID transport input while also exposing the decoded payload bitstring. +int wiegand_pack_from_raw_hid(const uint8_t *raw, size_t raw_len, wiegand_input_t *input); + #endif diff --git a/tools/pm3_online_tests.sh b/tools/pm3_online_tests.sh index f939b57da..e56b03003 100755 --- a/tools/pm3_online_tests.sh +++ b/tools/pm3_online_tests.sh @@ -9,6 +9,10 @@ cd "$PM3PATH" || exit 1 TESTALL=false TESTDESFIREVALUE=false +TESTHIDWIEGAND=false +TESTMFHIDENCODE=false +NEED_MF_HID_ENCODE_WIPE=false +TESTMANUAL=false # https://medium.com/@Drew_Stokes/bash-argument-parsing-54f3b81a6a8f PARAMS="" @@ -16,9 +20,12 @@ while (( "$#" )); do case "$1" in -h|--help) echo """ -Usage: $0 [--pm3bin /path/to/pm3] [desfire_value] +Usage: $0 [--pm3bin /path/to/pm3] [desfire_value|hid_wiegand|mf_hid_encode] --pm3bin ...: Specify path to pm3 binary to test + --manual ...: Pause after successful online LF HID clone/read checks for external reader verification desfire_value: Test DESFire value operations with card + hid_wiegand: Test LF HID T55xx clone and PM3 readback flows + mf_hid_encode: Test MIFARE Classic HID encoding flows You must specify a test target - no default 'all' for online tests """ exit 0 @@ -32,11 +39,25 @@ Usage: $0 [--pm3bin /path/to/pm3] [desfire_value] exit 1 fi ;; + --manual) + TESTMANUAL=true + shift + ;; desfire_value) TESTALL=false TESTDESFIREVALUE=true shift ;; + hid_wiegand) + TESTALL=false + TESTHIDWIEGAND=true + shift + ;; + mf_hid_encode) + TESTALL=false + TESTMFHIDENCODE=true + shift + ;; -*|--*=) # unsupported flags echo "Error: Unsupported flag $1" >&2 exit 1 @@ -95,6 +116,218 @@ function CheckExecute() { return 1 } +function CheckLfHidCloneReadback() { + printf "%-40s" "$1 " + + start=$(date +%s) + TIMEINFO="" + RES=$($PM3BIN -c "lf hid clone $2; lf hid reader" 2>&1) + end=$(date +%s) + delta=$(expr $end - $start) + if [ $delta -gt 2 ]; then + TIMEINFO=" ($delta s)" + fi + + if echo "$RES" | grep -E -q "$3"; then + echo -e "[ ${C_GREEN}OK${C_NC} ] ${C_OK} $TIMEINFO" + if $TESTMANUAL; then + echo " Manual check: $4" + WaitForEnter "PRESENT THE T55xx TAG TO ANOTHER READER AND CONFIRM: $4" + fi + return 0 + fi + + echo -e "[ ${C_RED}FAIL${C_NC} ] ${C_FAIL} $TIMEINFO" + echo "Execution trace:" + echo "$RES" + return 1 +} + +function HexToBin() { + local hex="${1^^}" + local bin="" + local i ch + for ((i=0; i<${#hex}; i++)); do + ch="${hex:i:1}" + case "$ch" in + 0) bin+="0000" ;; + 1) bin+="0001" ;; + 2) bin+="0010" ;; + 3) bin+="0011" ;; + 4) bin+="0100" ;; + 5) bin+="0101" ;; + 6) bin+="0110" ;; + 7) bin+="0111" ;; + 8) bin+="1000" ;; + 9) bin+="1001" ;; + A) bin+="1010" ;; + B) bin+="1011" ;; + C) bin+="1100" ;; + D) bin+="1101" ;; + E) bin+="1110" ;; + F) bin+="1111" ;; + *) return 1 ;; + esac + done + printf "%s" "$bin" +} + +function RestoreMfHidEncodeSector0() { + $PM3BIN -c "hf mf wrbl --blk 3 -b -k 89ECA97F8C2A -d FFFFFFFFFFFFFF078069FFFFFFFFFFFF" >/dev/null 2>&1 || true + $PM3BIN -c "hf mf wrbl --blk 3 -k FFFFFFFFFFFF -d FFFFFFFFFFFFFF078069FFFFFFFFFFFF" >/dev/null 2>&1 || true + $PM3BIN -c "hf mf wrbl --blk 3 -k A0A1A2A3A4A5 -d FFFFFFFFFFFFFF078069FFFFFFFFFFFF" >/dev/null 2>&1 || true + $PM3BIN -c "hf mf wrbl --blk 2 -k FFFFFFFFFFFF -d 00000000000000000000000000000000; \ +hf mf wrbl --blk 1 -k FFFFFFFFFFFF -d 00000000000000000000000000000000" >/dev/null 2>&1 || return 1 +} + +function RestoreMfHidEncodeSector1() { + $PM3BIN -c "hf mf wrbl --blk 7 -b -k 204752454154 -d FFFFFFFFFFFFFF078069FFFFFFFFFFFF" >/dev/null 2>&1 || true + $PM3BIN -c "hf mf wrbl --blk 7 -k FFFFFFFFFFFF -d FFFFFFFFFFFFFF078069FFFFFFFFFFFF" >/dev/null 2>&1 || true + $PM3BIN -c "hf mf wrbl --blk 7 -k 484944204953 -d FFFFFFFFFFFFFF078069FFFFFFFFFFFF" >/dev/null 2>&1 || true + $PM3BIN -c "hf mf wrbl --blk 6 -k FFFFFFFFFFFF -d 00000000000000000000000000000000; \ +hf mf wrbl --blk 5 -k FFFFFFFFFFFF -d 00000000000000000000000000000000; \ +hf mf wrbl --blk 4 -k FFFFFFFFFFFF -d 00000000000000000000000000000000" >/dev/null 2>&1 || return 1 +} + +function RestoreMfHidEncodeCard() { + RestoreMfHidEncodeSector0 || return 1 + RestoreMfHidEncodeSector1 || return 1 + + local verify + verify=$($PM3BIN -c 'hf mf rdbl --blk 1 -k FFFFFFFFFFFF; hf mf rdbl --blk 2 -k FFFFFFFFFFFF; hf mf rdbl --blk 4 -k FFFFFFFFFFFF; hf mf rdbl --blk 5 -k FFFFFFFFFFFF; hf mf rdbl --blk 6 -k FFFFFFFFFFFF' 2>&1) || return 1 + echo "$verify" | grep -E -q " 1 \| 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00" \ + && echo "$verify" | grep -E -q " 2 \| 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00" \ + && echo "$verify" | grep -E -q " 4 \| 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00" \ + && echo "$verify" | grep -E -q " 5 \| 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00" \ + && echo "$verify" | grep -E -q " 6 \| 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00" +} + +function CleanupMfHidEncodeCard() { + if [ "$NEED_MF_HID_ENCODE_WIPE" != true ]; then + return 0 + fi + + echo "" + printf "%-40s" "hf mf encodehid cleanup " + if RestoreMfHidEncodeCard; then + echo -e "[ ${C_GREEN}OK${C_NC} ] ${C_OK}" + else + echo -e "[ ${C_YELLOW}WARN${C_NC} ]" + echo "Cleanup could not restore sectors 0 and 1 to the default usable state." + fi +} + +function CheckMfHidEncodeRoundTrip() { + printf "%-40s" "$1 " + + start=$(date +%s) + TIMEINFO="" + if ! RestoreMfHidEncodeCard >/dev/null 2>&1; then + echo -e "[ ${C_RED}FAIL${C_NC} ] ${C_FAIL}" + echo "Execution trace:" + echo "Failed to restore sectors 0 and 1 to the default usable state before running the test." + return 1 + fi + + RES=$($PM3BIN -c "hf mf encodehid $2; hf mf rdbl --blk 5 -k 484944204953" 2>&1) + end=$(date +%s) + delta=$(expr $end - $start) + if [ $delta -gt 2 ]; then + TIMEINFO=" ($delta s)" + fi + + BLOCKHEX=$(printf "%s\n" "$RES" | LC_ALL=C grep -aoE '02( [0-9A-F]{2}){15}' | tail -n1 | tr -d ' ') + if [ -z "$BLOCKHEX" ]; then + echo -e "[ ${C_RED}FAIL${C_NC} ] ${C_FAIL} $TIMEINFO" + echo "Execution trace:" + echo "$RES" + return 1 + fi + + if [[ "$BLOCKHEX" != 02* ]]; then + echo -e "[ ${C_RED}FAIL${C_NC} ] ${C_FAIL} $TIMEINFO" + echo "Expected block 5 to start with the 0x02 HID marker." + echo "Actual block 5 data: $BLOCKHEX" + echo "Execution trace:" + echo "$RES" + return 1 + fi + + RAWPAYLOAD=${BLOCKHEX#02} + PAYLOADBIN=$(HexToBin "$RAWPAYLOAD") || { + echo -e "[ ${C_RED}FAIL${C_NC} ] ${C_FAIL} $TIMEINFO" + echo "Execution trace:" + echo "$RES" + return 1 + } + + while [[ "$PAYLOADBIN" == 0* ]]; do + PAYLOADBIN=${PAYLOADBIN#0} + done + + if [[ "$PAYLOADBIN" != 1* ]]; then + echo -e "[ ${C_RED}FAIL${C_NC} ] ${C_FAIL} $TIMEINFO" + echo "Expected a sentinel-prefixed Wiegand payload in block 5." + echo "Actual payload bits: $PAYLOADBIN" + echo "Execution trace:" + echo "$RES" + return 1 + fi + + RECOVERED_BIN=${PAYLOADBIN#1} + if [ "$RECOVERED_BIN" != "$3" ]; then + echo -e "[ ${C_RED}FAIL${C_NC} ] ${C_FAIL} $TIMEINFO" + echo "Expected Wiegand bits: $3" + echo "Actual Wiegand bits: $RECOVERED_BIN" + echo "Execution trace:" + echo "$RES" + return 1 + fi + + DECODE_RES=$($PM3BIN -c "wiegand decode --bin $RECOVERED_BIN" 2>&1) + if echo "$DECODE_RES" | grep -E -q "$4"; then + echo -e "[ ${C_GREEN}OK${C_NC} ] ${C_OK} $TIMEINFO" + return 0 + fi + + echo -e "[ ${C_RED}FAIL${C_NC} ] ${C_FAIL} $TIMEINFO" + echo "Decode trace:" + echo "$DECODE_RES" + return 1 +} + +function CheckMfHidEncodeCleanup() { + printf "%-40s" "$1 " + RES=$($PM3BIN -c 'hf mf rdbl --blk 1 -k FFFFFFFFFFFF; hf mf rdbl --blk 2 -k FFFFFFFFFFFF; hf mf rdbl --blk 4 -k FFFFFFFFFFFF; hf mf rdbl --blk 5 -k FFFFFFFFFFFF; hf mf rdbl --blk 6 -k FFFFFFFFFFFF' 2>&1) + if echo "$RES" | grep -E -q " 1 \| 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00" \ + && echo "$RES" | grep -E -q " 2 \| 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00" \ + && echo "$RES" | grep -E -q " 4 \| 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00" \ + && echo "$RES" | grep -E -q " 5 \| 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00" \ + && echo "$RES" | grep -E -q " 6 \| 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00"; then + echo -e "[ ${C_GREEN}OK${C_NC} ] ${C_OK}" + return 0 + fi + + echo -e "[ ${C_RED}FAIL${C_NC} ] ${C_FAIL}" + echo "Execution trace:" + echo "$RES" + return 1 +} + +function WaitForEnter() { + echo "" + echo "$1" + echo "Press Enter when ready, or Ctrl-C to abort." + if [ -r /dev/tty ]; then + stty sane < /dev/tty 2>/dev/null || true + IFS= read -r < /dev/tty + else + read -r + fi +} + +trap CleanupMfHidEncodeCard EXIT + echo -e "${C_BLUE}Iceman Proxmark3 online test tool${C_NC}" echo "" echo "work directory: $(pwd)" @@ -108,7 +341,7 @@ if command -v git >/dev/null && git rev-parse --is-inside-work-tree >/dev/null 2 fi # Check that user specified a test -if [ "$TESTDESFIREVALUE" = false ]; then +if [ "$TESTDESFIREVALUE" = false ] && [ "$TESTHIDWIEGAND" = false ] && [ "$TESTMFHIDENCODE" = false ]; then echo "Error: You must specify a test target. Use -h for help." exit 1 fi @@ -138,6 +371,34 @@ while true; do if ! CheckExecute "card cleanup" "$PM3BIN -c 'hf mfdes selectapp --aid 000000; hf mfdes auth -n 0 -t 2tdea -k 00000000000000000000000000000000 --kdf none; hf mfdes deleteapp --aid 123456'" "application.*deleted"; then break; fi echo " card value operation tests completed successfully!" fi + + if $TESTHIDWIEGAND; then + echo -e "\n${C_BLUE}Testing LF HID T55xx clone flows${C_NC} ${PM3BIN:=./pm3}" + if ! CheckFileExist "pm3 exists" "$PM3BIN"; then break; fi + + if ! CheckExecute "lf hid clone raw oversize" "$PM3BIN -c 'lf hid clone -r 01400076000c86' 2>&1" "LF HID clone supports only packed credentials up to 37 bits"; then break; fi + if ! CheckExecute "lf hid clone bin oversize" "PAT=\$(printf '01%.0s' {1..48}); $PM3BIN -c \"lf hid clone --bin \$PAT\" 2>&1" "Packed HID encoding supports up to 84 Wiegand bits"; then break; fi + if ! CheckExecute "lf hid clone new oversize" "$PM3BIN -c 'lf hid clone --new 0000A4550148AB' 2>&1" "LF HID clone supports only packed credentials up to 37 bits"; then break; fi + + WaitForEnter "PLACE A REWRITABLE T55xx TAG ON THE PM3 NOW" + if ! CheckLfHidCloneReadback "lf hid clone H10301 26-bit" "-w H10301 --fc 118 --cn 1603" "H10301.*FC: 118.*CN: 1603" "H10301 26-bit, FC 118, CN 1603"; then break; fi + if ! CheckLfHidCloneReadback "lf hid clone C1k35s 35-bit" "-w C1k35s --fc 118 --cn 1603" "C1k35s.*FC: 118.*CN: 1603" "C1k35s 35-bit, FC 118, CN 1603"; then break; fi + if ! CheckLfHidCloneReadback "lf hid clone H10304 37-bit" "-w H10304 --fc 118 --cn 1603" "H10304.*FC: 118.*CN: 1603" "H10304 37-bit, FC 118, CN 1603"; then break; fi + fi + + if $TESTMFHIDENCODE; then + echo -e "\n${C_BLUE}Testing MIFARE Classic HID encoding${C_NC} ${PM3BIN:=./pm3}" + if ! CheckFileExist "pm3 exists" "$PM3BIN"; then break; fi + + WaitForEnter "PLACE A BLANK MIFARE CLASSIC 1K CARD ON THE PM3 NOW" + NEED_MF_HID_ENCODE_WIPE=true + if ! CheckMfHidEncodeRoundTrip "hf mf encodehid bin roundtrip" "--bin 10001111100000001010100011" "10001111100000001010100011" "H10301.*FC: 31.*CN: 337"; then break; fi + if ! CheckMfHidEncodeRoundTrip "hf mf encodehid raw roundtrip" "--raw 063E02A3" "10001111100000001010100011" "H10301.*FC: 31.*CN: 337"; then break; fi + if ! CheckMfHidEncodeRoundTrip "hf mf encodehid new roundtrip" "--new 068F80A8C0" "10001111100000001010100011" "H10301.*FC: 31.*CN: 337"; then break; fi + if ! CheckMfHidEncodeRoundTrip "hf mf encodehid format roundtrip" "-w H10301 --fc 31 --cn 337" "10001111100000001010100011" "H10301.*FC: 31.*CN: 337"; then break; fi + if ! RestoreMfHidEncodeCard; then break; fi + if ! CheckMfHidEncodeCleanup "hf mf encodehid cleanup verify"; then break; fi + fi echo -e "\n------------------------------------------------------------" echo -e "Tests [ ${C_GREEN}OK${C_NC} ] ${C_OK}\n" @@ -145,4 +406,4 @@ while true; do done echo -e "\n------------------------------------------------------------" echo -e "\nTests [ ${C_RED}FAIL${C_NC} ] ${C_FAIL}\n" -exit 1 \ No newline at end of file +exit 1 diff --git a/tools/pm3_tests.sh b/tools/pm3_tests.sh index f0aa9cbe5..2fcaaf82d 100755 --- a/tools/pm3_tests.sh +++ b/tools/pm3_tests.sh @@ -490,6 +490,7 @@ while true; do if ! CheckExecute "wiegand decode test - raw" "$CLIENTBIN -c 'wiegand decode --raw 2006F623AE'" "FC: 123 CN: 4567 parity \( ok \)"; then break; fi if ! CheckExecute "wiegand decode test - bin over 96-bit" "PAT=\$(printf '01%.0s' {1..49}); $CLIENTBIN -c \"wiegand decode --bin \$PAT\" 2>&1" "Binary decode supports up to 96 Wiegand bits"; then break; fi if ! CheckExecute "wiegand decode test - new" "$CLIENTBIN -c 'wiegand decode --new 06BD88EB80'" "FC: 123 CN: 4567 parity \( ok \)"; then break; fi + if ! CheckExecute "wiegand decode test - new no padded bin" "if ! $CLIENTBIN -c 'wiegand decode --new 06BD88EB80' 2>&1 | grep -q 'padded bin'; then echo OK; fi" "OK"; then break; fi if ! CheckExecute "wiegand decode test - new 96-bit" "$CLIENTBIN -c 'wiegand decode --new 00555555555555555555555555'" "hex\\.{14} 555555555555555555555555"; then break; fi if ! CheckExecute "wiegand decode test - new 48-bit" "$CLIENTBIN -c 'wiegand decode --new 0000A4550148AB'" "C1k48s.*FC: 42069 CN: 42069 parity \( ok \)"; then break; fi if ! CheckExecute "wiegand Verkada40 encode test 1" "$CLIENTBIN -c 'wiegand encode -w Verkada40 --fc 50 --cn 1001'" "86400007D3"; then break; fi