From 19b65344360e5bdc7f22e369a3ad3542f2b7dd8e Mon Sep 17 00:00:00 2001 From: CinderSocket Date: Thu, 19 Mar 2026 22:27:33 -0700 Subject: [PATCH] Fixes RfidResearchGroup/proxmark3#3041 Adds --ascii option --- client/src/cmddata.c | 81 ++++++++++++++++++++++++++++++++------ client/src/qrcode/qrcode.c | 37 +++++++++++++++++ client/src/qrcode/qrcode.h | 2 + tools/pm3_tests.sh | 5 +++ 4 files changed, 113 insertions(+), 12 deletions(-) diff --git a/client/src/cmddata.c b/client/src/cmddata.c index 94bf3662f..bb6c3e9fd 100644 --- a/client/src/cmddata.c +++ b/client/src/cmddata.c @@ -3982,24 +3982,71 @@ static int CmdQRcode(const char *Cmd) { CLIParserInit(&ctx, "data qrcode", "Generate a QR code with the input data", "data qrcode -f \n" - "data qrcode -d 123456789\n" + "data qrcode -d 0123456789\n" + "data qrcode -d AABBCCDD --ascii\n" ); void *argtable[] = { arg_param_begin, + arg_lit0("a", "ascii", "Render with ASCII-safe characters"), arg_str0("f", "file", "", "Specify a filename"), - arg_str0("d", "data", "", "message as hex bytes"), + arg_str0("d", "data", "", "message as a single hex byte string"), arg_param_end }; CLIExecWithReturn(ctx, Cmd, argtable, false); + bool ascii = arg_get_lit(ctx, 1); int fnlen = 0; char filename[FILE_PATH_SIZE] = { 0 }; - CLIParamStrToBuf(arg_get_str(ctx, 1), (uint8_t *)filename, FILE_PATH_SIZE, &fnlen); + CLIParamStrToBuf(arg_get_str(ctx, 2), (uint8_t *)filename, FILE_PATH_SIZE, &fnlen); int dlen = 0; - char data[1024] = { 0 }; - CLIParamStrToBuf(arg_get_str(ctx, 2), (uint8_t *)data, sizeof(data), &dlen); + uint8_t data[512] = { 0 }; + int data_arg_len = 0; + char data_arg[1024] = { 0 }; + struct arg_str *data_arg_str = arg_get_str(ctx, 3); + int data_count = data_arg_str->count; + + if (data_count > 1) { + CLIParserFree(ctx); + PrintAndLogEx(ERR, "QR data accepts exactly one -d value. Use a single contiguous hex string and encode spaces as 20."); + return PM3_EINVARG; + } + + if (data_count == 1) { + if (CLIParamStrToBuf(data_arg_str, (uint8_t *)data_arg, sizeof(data_arg), &data_arg_len) != 0) { + CLIParserFree(ctx); + return PM3_EINVARG; + } + + if (strpbrk(data_arg, " \t") != NULL) { + CLIParserFree(ctx); + PrintAndLogEx(ERR, "QR data must be one contiguous hex string. Spaces are not supported; encode a space byte as 20."); + return PM3_EINVARG; + } + + for (int i = 0; i < data_arg_len; i++) { + if (isxdigit((unsigned char)data_arg[i]) == 0) { + CLIParserFree(ctx); + PrintAndLogEx(ERR, "QR data must contain only hex characters 0-9, a-f, or A-F."); + return PM3_EINVARG; + } + } + + if (data_arg_len & 1) { + CLIParserFree(ctx); + PrintAndLogEx(ERR, "QR data must contain an even number of hex digits."); + return PM3_EINVARG; + } + + dlen = hex_to_bytes(data_arg, data, sizeof(data)); + if (dlen < 0) { + CLIParserFree(ctx); + PrintAndLogEx(ERR, "QR data must be valid hex bytes."); + return PM3_EINVARG; + } + } + CLIParserFree(ctx); if (fnlen && dlen) { @@ -4028,17 +4075,27 @@ static int CmdQRcode(const char *Cmd) { } if (dlen) { + int8_t smallest_version = qrcode_getMinVersionForBytes(dlen, ECC_LOW); + if (smallest_version < 1) { + PrintAndLogEx(ERR, "QR data is too large to fit in a supported QR code."); + return PM3_EINVARG; + } - // calc size of input data to get the correct - int smallest_version = (dlen + 17) / 20; uint8_t qr_arr[qrcode_getBufferSize(smallest_version)]; - qrcode_initText(&qrcode, qr_arr, smallest_version, ECC_LOW, (char *) data); + if (qrcode_initBytes(&qrcode, qr_arr, smallest_version, ECC_LOW, data, dlen) < 0) { + PrintAndLogEx(ERR, "Failed to encode QR data."); + return PM3_ESOFT; + } PrintAndLogEx(NORMAL, ""); - qrcode_print_matrix_utf8(&qrcode); - PrintAndLogEx(NORMAL, ""); - PrintAndLogEx(NORMAL, ""); - qrcode_print_matrix_utf8_2x2(&qrcode); + if (ascii) { + qrcode_print_matrix_ascii(&qrcode); + } else { + qrcode_print_matrix_utf8(&qrcode); + PrintAndLogEx(NORMAL, ""); + PrintAndLogEx(NORMAL, ""); + qrcode_print_matrix_utf8_2x2(&qrcode); + } PrintAndLogEx(NORMAL, ""); } diff --git a/client/src/qrcode/qrcode.c b/client/src/qrcode/qrcode.c index cb04c3f05..efe87c0ed 100644 --- a/client/src/qrcode/qrcode.c +++ b/client/src/qrcode/qrcode.c @@ -793,6 +793,34 @@ uint16_t qrcode_getBufferSize(uint8_t version) { return bb_getGridSizeBytes(4 * version + 17); } +int8_t qrcode_getMinVersionForBytes(uint16_t length, uint8_t ecc) { + if (ecc > ECC_HIGH) { + return -1; + } + + uint8_t eccFormatBits = (ECC_FORMAT_BITS >> (2 * ecc)) & 0x03; + +#if LOCK_VERSION == 0 + for (uint8_t version = 1; version <= 40; version++) { + uint16_t moduleCount = NUM_RAW_DATA_MODULES[version - 1]; + uint16_t dataCapacity = moduleCount / 8 - NUM_ERROR_CORRECTION_CODEWORDS[eccFormatBits][version - 1]; + uint32_t requiredBits = 4 + getModeBits(version, MODE_BYTE) + ((uint32_t)length * 8); + + if (requiredBits <= ((uint32_t)dataCapacity * 8)) { + return version; + } + } + + return -1; +#else + uint8_t version = LOCK_VERSION; + uint16_t moduleCount = NUM_RAW_DATA_MODULES; + uint16_t dataCapacity = moduleCount / 8 - NUM_ERROR_CORRECTION_CODEWORDS[eccFormatBits]; + uint32_t requiredBits = 4 + getModeBits(version, MODE_BYTE) + ((uint32_t)length * 8); + return (requiredBits <= ((uint32_t)dataCapacity * 8)) ? version : -1; +#endif +} + // @TODO: Return error if data is too big. int8_t qrcode_initBytes(QRCode *qrcode, uint8_t *modules, uint8_t version, uint8_t ecc, uint8_t *data, uint16_t length) { uint8_t size = version * 4 + 17; @@ -885,6 +913,15 @@ bool qrcode_getModule(QRCode *qrcode, uint8_t x, uint8_t y) { return (qrcode->modules[offset >> 3] & (1 << (7 - (offset & 0x07)))) != 0; } +void qrcode_print_matrix_ascii(QRCode *q) { + for (uint8_t y = 0; y < q->size; y++) { + for (uint8_t x = 0; x < q->size; x++) { + PrintAndLogEx(NORMAL, "%s" NOLF, qrcode_getModule(q, x, y) ? "##" : " "); + } + PrintAndLogEx(NORMAL, ""); + } +} + void qrcode_print_matrix_utf8(QRCode *q) { // UTF-8 block characters for better resolution diff --git a/client/src/qrcode/qrcode.h b/client/src/qrcode/qrcode.h index dcb1798bd..73c4ddf90 100644 --- a/client/src/qrcode/qrcode.h +++ b/client/src/qrcode/qrcode.h @@ -73,11 +73,13 @@ extern "C" { uint16_t qrcode_getBufferSize(uint8_t version); +int8_t qrcode_getMinVersionForBytes(uint16_t length, uint8_t ecc); int8_t qrcode_initText(QRCode *qrcode, uint8_t *modules, uint8_t version, uint8_t ecc, const char *data); int8_t qrcode_initBytes(QRCode *qrcode, uint8_t *modules, uint8_t version, uint8_t ecc, uint8_t *data, uint16_t length); bool qrcode_getModule(QRCode *qrcode, uint8_t x, uint8_t y); +void qrcode_print_matrix_ascii(QRCode *q); void qrcode_print_matrix_utf8(QRCode *q); void qrcode_print_matrix_utf8_2x2(QRCode *q); diff --git a/tools/pm3_tests.sh b/tools/pm3_tests.sh index 2fcaaf82d..11437daca 100755 --- a/tools/pm3_tests.sh +++ b/tools/pm3_tests.sh @@ -462,6 +462,11 @@ while true; do if ! CheckExecute "reveng readline test" "$CLIENTBIN -c 'reveng -h;reveng -D'" "CRC-64/GO-ISO"; then break; fi if ! CheckExecute "reveng -g test" "$CLIENTBIN -c 'reveng -g abda202c'" "CRC-16/ISO-IEC-14443-3-A"; then break; fi if ! CheckExecute "reveng -w test" "$CLIENTBIN -c 'reveng -w 8 -s 01020304e3 010204039d'" "CRC-8/SMBUS"; then break; fi + if ! CheckExecute "data qrcode ascii test" "$CLIENTBIN -c 'data qrcode -d aa --ascii'" "##"; then break; fi + if ! CheckExecute "data qrcode repeated -d" "$CLIENTBIN -c 'data qrcode -d aa -d bb' 2>&1" "excess option -d\\|--data"; then break; fi + if ! CheckExecute "data qrcode invalid hex" "$CLIENTBIN -c 'data qrcode -d zz' 2>&1" "QR data must contain only hex characters"; then break; fi + if ! CheckExecute "data qrcode odd hex" "$CLIENTBIN -c 'data qrcode -d a' 2>&1" "QR data must contain an even number of hex digits"; then break; fi + if ! CheckExecute "data qrcode spaced hex" "$CLIENTBIN -c 'data qrcode -d \"aa bb\"' 2>&1" "Spaces are not supported; encode a space byte as 20"; then break; fi if ! CheckExecute "mfu pwdgen test" "$CLIENTBIN -c 'hf mfu pwdgen --test'" "Selftest ok"; then break; fi if ! CheckExecute "mfu keygen test" "$CLIENTBIN -c 'hf mfu keygen --uid 11223344556677'" "80 B1 C2 71 D8 A0"; then break; fi if ! CheckExecute "jooki encode test" "$CLIENTBIN -c 'hf jooki encode --test'" "04 28 F4 DA F0 4A 81 \( ok \)"; then break; fi