diff --git a/armsrc/em4x50.c b/armsrc/em4x50.c index 4910caa31..ff68b724b 100644 --- a/armsrc/em4x50.c +++ b/armsrc/em4x50.c @@ -641,13 +641,17 @@ static bool brute(const em4x50_data_t *etd, uint32_t *pwd) { int generator_ret = 0; int cnt = 0; - bf_generator_init(&ctx, etd->bruteforce_mode); + bf_generator_init(&ctx, etd->bruteforce_mode, BF_KEY_SIZE_32); - if (etd->bruteforce_mode == BRUTEFORCE_MODE_CHARSET) + if (etd->bruteforce_mode == BF_MODE_CHARSET) { bf_generator_set_charset(&ctx, etd->bruteforce_charset); + } else if (etd->bruteforce_mode == BF_MODE_RANGE) { + ctx.range_low = etd->password1; + ctx.range_high = etd->password2; + } - while ((generator_ret = bf_generate32(&ctx)) == GENERATOR_NEXT) { - *pwd = ctx.current_key32; + while ((generator_ret = bf_generate(&ctx)) == BF_GENERATOR_NEXT) { + *pwd = bf_get_key32(&ctx); WDT_HIT(); diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index 88bbb9ead..b223adee9 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -260,6 +260,7 @@ set (TARGET_SOURCES ${PM3_ROOT}/common/iso15693tools.c ${PM3_ROOT}/common/cardhelper.c ${PM3_ROOT}/common/generator.c + ${PM3_ROOT}/common/bruteforce.c ${PM3_ROOT}/client/src/crypto/asn1dump.c ${PM3_ROOT}/client/src/crypto/asn1utils.c ${PM3_ROOT}/client/src/crypto/libpcrypto.c diff --git a/client/Makefile b/client/Makefile index adbd2c0ce..8b36f85f6 100644 --- a/client/Makefile +++ b/client/Makefile @@ -749,6 +749,7 @@ SRCS = mifare/aiddesfire.c \ # common SRCS += bucketsort.c \ + bruteforce.c \ cardhelper.c \ crapto1/crapto1.c \ crapto1/crypto1.c \ diff --git a/client/dictionaries/mfc_default_keys.dic b/client/dictionaries/mfc_default_keys.dic index 6616a92ee..038f9480b 100644 --- a/client/dictionaries/mfc_default_keys.dic +++ b/client/dictionaries/mfc_default_keys.dic @@ -2337,3 +2337,38 @@ EA0CA627FD06 # Hotel key CE0F4F15E909 D60DE9436219 + +# ATM Area de Girona, spanish transport card + +A01000000000 +A02000000000 +A03000000000 +A04000000000 +A05000000000 +A06000000000 +A07000000000 +A08000000000 +A09000000000 +A10000000000 +A11000000000 +A12000000000 +A13000000000 +A14000000000 +A15000000000 + +B01000000000 +B02000000000 +B03000000000 +B04000000000 +B05000000000 +B06000000000 +B07000000000 +B08000000000 +B09000000000 +B10000000000 +B11000000000 +B12000000000 +B13000000000 +B14000000000 +B15000000000 + diff --git a/client/src/cmdhfmf.c b/client/src/cmdhfmf.c index cc8aa503c..d2aadadab 100644 --- a/client/src/cmdhfmf.c +++ b/client/src/cmdhfmf.c @@ -16,6 +16,7 @@ // High frequency MIFARE commands //----------------------------------------------------------------------------- +#include "bruteforce.h" #include "cmdhfmf.h" #include #include "cmdparser.h" // command_t @@ -3369,6 +3370,209 @@ out: return PM3_SUCCESS; } +static int CmdHF14AMfSmartBrute(const char *Cmd) { + CLIParserContext *ctx; + CLIParserInit(&ctx, "hf mf brute", + "This is a smart bruteforce, exploiting common patterns, bugs and bad designs in key generators.", + "hf mf brute --mini --> Key recovery against MIFARE Mini\n" + "hf mf brute --1k --> Key recovery against MIFARE Classic 1k\n" + "hf mf brute --2k --> Key recovery against MIFARE 2k\n" + "hf mf brute --4k --> Key recovery against MIFARE 4k\n" + "hf mf brute --1k --emu --> Target 1K, write keys to emulator memory\n" + "hf mf brute --1k --dump --> Target 1K, write keys to file\n"); + + void *argtable[] = { + arg_param_begin, + arg_lit0(NULL, "mini", "MIFARE Classic Mini / S20"), + arg_lit0(NULL, "1k", "MIFARE Classic 1k / S50 (default)"), + arg_lit0(NULL, "2k", "MIFARE Classic/Plus 2k"), + arg_lit0(NULL, "4k", "MIFARE Classic 4k / S70"), + arg_lit0(NULL, "emu", "Fill simulator keys from found keys"), + arg_lit0(NULL, "dump", "Dump found keys to binary file"), + arg_param_end + }; + CLIExecWithReturn(ctx, Cmd, argtable, true); + + + bool m0 = arg_get_lit(ctx, 2); + bool m1 = arg_get_lit(ctx, 3); + bool m2 = arg_get_lit(ctx, 4); + bool m4 = arg_get_lit(ctx, 5); + + bool transferToEml = arg_get_lit(ctx, 6); + bool createDumpFile = arg_get_lit(ctx, 7); + + CLIParserFree(ctx); + + //validations + + if ((m0 + m1 + m2 + m4) > 1) { + PrintAndLogEx(WARNING, "Only specify one MIFARE Type"); + return PM3_EINVARG; + } else if ((m0 + m1 + m2 + m4) == 0) { + m1 = true; + } + + uint8_t sectorsCnt = MIFARE_1K_MAXSECTOR; + if (m0) { + sectorsCnt = MIFARE_MINI_MAXSECTOR; + } else if (m1) { + sectorsCnt = MIFARE_1K_MAXSECTOR; + } else if (m2) { + sectorsCnt = MIFARE_2K_MAXSECTOR; + } else if (m4) { + sectorsCnt = MIFARE_4K_MAXSECTOR; + } else { + PrintAndLogEx(WARNING, "Please specify a MIFARE Type"); + return PM3_EINVARG; + } + + uint32_t chunksize = 100 > (PM3_CMD_DATA_SIZE / MIFARE_KEY_SIZE) ? (PM3_CMD_DATA_SIZE / MIFARE_KEY_SIZE) : 100; + uint8_t *keyBlock = calloc(MIFARE_KEY_SIZE, chunksize); + + if (keyBlock == NULL) + return PM3_EMALLOC; + + // create/initialize key storage structure + sector_t *e_sector = NULL; + if (initSectorTable(&e_sector, sectorsCnt) != PM3_SUCCESS) { + free(keyBlock); + return PM3_EMALLOC; + } + + // initialize bruteforce engine + generator_context_t bctx; + bf_generator_init(&bctx, BF_MODE_SMART, BF_KEY_SIZE_48); + + int i = 0, ret; + int smart_mode_stage = -1; + uint64_t generator_key; + + // time + uint64_t t0 = msclock(); + uint64_t t1 = msclock(); + uint64_t keys_checked = 0; + uint64_t total_keys_checked = 0; + + uint32_t keycnt = 0; + bool firstChunk = true, lastChunk = false; + + while (!lastChunk) { + keycnt = 0; + + // generate block of keys from generator + memset(keyBlock, 0, MIFARE_KEY_SIZE * chunksize); + for (i = 0; i < chunksize; i++) { + ret = bf_generate(&bctx); + if (ret == BF_GENERATOR_ERROR) { + PrintAndLogEx(ERR, "Internal bruteforce generator error"); + free(keyBlock); + free(e_sector); + return PM3_EFAILED; + } else if (ret == BF_GENERATOR_END) { + lastChunk = true; + break; + } else if (ret == BF_GENERATOR_NEXT) { + generator_key = bf_get_key48(&bctx); + num_to_bytes(generator_key, MIFARE_KEY_SIZE, keyBlock + (i * MIFARE_KEY_SIZE)); + keycnt++; + + if (smart_mode_stage != bctx.smart_mode_stage) { + smart_mode_stage = bctx.smart_mode_stage; + PrintAndLogEx(INFO, "Running bruteforce stage %d", smart_mode_stage); + + if (msclock() - t1 > 0 && keys_checked > 0) { + PrintAndLogEx(INFO, "Current cracking speed (keys/s): %lu", + keys_checked / ((msclock() - t1) / 1000)); + + t1 = msclock(); + keys_checked = 0; + } + } + } + } + + int strategy = 2; // width first on all sectors + ret = mfCheckKeys_fast(sectorsCnt, firstChunk, lastChunk, strategy, keycnt, keyBlock, e_sector, false, false); + + keys_checked += keycnt; + total_keys_checked += keycnt; + + if (firstChunk) + firstChunk = false; + + if (ret == PM3_SUCCESS || ret == 2) + goto out; + + } + +out: + PrintAndLogEx(INFO, "Time in brute mode: " _YELLOW_("%.1fs") "\n", (float)((msclock() - t0) / 1000.0)); + PrintAndLogEx(INFO, "Total keys checked: " _YELLOW_("%lu") "\n", total_keys_checked); + // check.. + uint8_t found_keys = 0; + for (i = 0; i < sectorsCnt; ++i) { + + if (e_sector[i].foundKey[0]) + found_keys++; + + if (e_sector[i].foundKey[1]) + found_keys++; + } + + if (found_keys == 0) { + PrintAndLogEx(WARNING, "No keys found"); + } else { + + PrintAndLogEx(NORMAL, ""); + PrintAndLogEx(SUCCESS, _GREEN_("found keys:")); + + printKeyTable(sectorsCnt, e_sector); + + if (transferToEml) { + // fast push mode + g_conn.block_after_ACK = true; + uint8_t block[MFBLOCK_SIZE] = {0x00}; + for (i = 0; i < sectorsCnt; ++i) { + uint8_t b = mfFirstBlockOfSector(i) + mfNumBlocksPerSector(i) - 1; + mfEmlGetMem(block, b, 1); + + if (e_sector[i].foundKey[0]) + num_to_bytes(e_sector[i].Key[0], MIFARE_KEY_SIZE, block); + + if (e_sector[i].foundKey[1]) + num_to_bytes(e_sector[i].Key[1], MIFARE_KEY_SIZE, block + 10); + + if (i == sectorsCnt - 1) { + // Disable fast mode on last packet + g_conn.block_after_ACK = false; + } + mfEmlSetMem(block, b, 1); + } + PrintAndLogEx(SUCCESS, "Found keys have been transferred to the emulator memory"); + + if (found_keys == (sectorsCnt << 1)) { + FastDumpWithEcFill(sectorsCnt); + } + } + + if (createDumpFile) { + + char *fptr = GenerateFilename("hf-mf-", "-key.bin"); + if (createMfcKeyDump(fptr, sectorsCnt, e_sector) != PM3_SUCCESS) { + PrintAndLogEx(ERR, "Failed to save keys to file"); + } + free(fptr); + + } + } + + free(keyBlock); + free(e_sector); + PrintAndLogEx(NORMAL, ""); + return PM3_SUCCESS; +} + static int CmdHF14AMfChk(const char *Cmd) { CLIParserContext *ctx; CLIParserInit(&ctx, "hf mf chk", @@ -9054,6 +9258,7 @@ static command_t CommandTable[] = { {"nested", CmdHF14AMfNested, IfPm3Iso14443a, "Nested attack"}, {"hardnested", CmdHF14AMfNestedHard, AlwaysAvailable, "Nested attack for hardened MIFARE Classic cards"}, {"staticnested", CmdHF14AMfNestedStatic, IfPm3Iso14443a, "Nested attack against static nonce MIFARE Classic cards"}, + {"brute", CmdHF14AMfSmartBrute, IfPm3Iso14443a, "Smart bruteforce to exploit weak key generators"}, {"autopwn", CmdHF14AMfAutoPWN, IfPm3Iso14443a, "Automatic key recovery tool for MIFARE Classic"}, // {"keybrute", CmdHF14AMfKeyBrute, IfPm3Iso14443a, "J_Run's 2nd phase of multiple sector nested authentication key recovery"}, {"nack", CmdHf14AMfNack, IfPm3Iso14443a, "Test for MIFARE NACK bug"}, diff --git a/client/src/cmdlfem4x50.c b/client/src/cmdlfem4x50.c index cce348166..39510c27b 100644 --- a/client/src/cmdlfem4x50.c +++ b/client/src/cmdlfem4x50.c @@ -359,11 +359,12 @@ int CmdEM4x50Brute(const char *Cmd) { "lf em 4x50 brute --mode range --begin 12330000 --end 12340000 -> tries pwds from 0x12330000 to 0x12340000\n" "lf em 4x50 brute --mode charset --digits --uppercase -> tries all combinations of ASCII codes for digits and uppercase letters\n" + "lf em 4x50 brute --mode smart -> enable 'smart' pattern key cracking\n" ); void *argtable[] = { arg_param_begin, - arg_str1(NULL, "mode", "", "Bruteforce mode (range|charset)"), + arg_str1(NULL, "mode", "", "Bruteforce mode (range|charset|smart)"), arg_str0(NULL, "begin", "", "Range mode - start of the key range"), arg_str0(NULL, "end", "", "Range mode - end of the key range"), arg_lit0(NULL, "digits", "Charset mode - include ASCII codes for digits"), @@ -382,16 +383,18 @@ int CmdEM4x50Brute(const char *Cmd) { PrintAndLogEx(INFO, "Chosen mode: %s", mode); if (strcmp(mode, "range") == 0) { - etd.bruteforce_mode = BRUTEFORCE_MODE_RANGE; + etd.bruteforce_mode = BF_MODE_RANGE; } else if (strcmp(mode, "charset") == 0) { - etd.bruteforce_mode = BRUTEFORCE_MODE_CHARSET; + etd.bruteforce_mode = BF_MODE_CHARSET; + } else if (strcmp(mode, "smart") == 0) { + etd.bruteforce_mode = BF_MODE_SMART; } else { PrintAndLogEx(FAILED, "Unknown bruteforce mode: %s", mode); CLIParserFree(ctx); return PM3_EINVARG; } - if (etd.bruteforce_mode == BRUTEFORCE_MODE_RANGE) { + if (etd.bruteforce_mode == BF_MODE_RANGE) { int begin_len = 0; uint8_t begin[4] = {0x0}; CLIGetHexWithReturn(ctx, 2, begin, &begin_len); @@ -414,14 +417,14 @@ int CmdEM4x50Brute(const char *Cmd) { etd.password1 = BYTES2UINT32_BE(begin); etd.password2 = BYTES2UINT32_BE(end); - } else if (etd.bruteforce_mode == BRUTEFORCE_MODE_CHARSET) { + } else if (etd.bruteforce_mode == BF_MODE_CHARSET) { bool enable_digits = arg_get_lit(ctx, 4); bool enable_uppercase = arg_get_lit(ctx, 5); if (enable_digits) - etd.bruteforce_charset |= CHARSET_DIGITS; + etd.bruteforce_charset |= BF_CHARSET_DIGITS; if (enable_uppercase) - etd.bruteforce_charset |= CHARSET_UPPERCASE; + etd.bruteforce_charset |= BF_CHARSET_UPPERCASE; if (etd.bruteforce_charset == 0) { PrintAndLogEx(FAILED, "Please enable at least one charset when using charset bruteforce mode."); @@ -441,21 +444,21 @@ int CmdEM4x50Brute(const char *Cmd) { const int speed = 27; int no_iter = 0; - if (etd.bruteforce_mode == BRUTEFORCE_MODE_RANGE) { + if (etd.bruteforce_mode == BF_MODE_RANGE) { no_iter = etd.password2 - etd.password1 + 1; PrintAndLogEx(INFO, "Trying " _YELLOW_("%i") " passwords in range [0x%08x, 0x%08x]" , no_iter , etd.password1 , etd.password2 ); - } else if (etd.bruteforce_mode == BRUTEFORCE_MODE_CHARSET) { + } else if (etd.bruteforce_mode == BF_MODE_CHARSET) { unsigned int digits = 0; - if (etd.bruteforce_charset & CHARSET_DIGITS) - digits += CHARSET_DIGITS_SIZE; + if (etd.bruteforce_charset & BF_CHARSET_DIGITS) + digits += BF_CHARSET_DIGITS_SIZE; - if (etd.bruteforce_charset & CHARSET_UPPERCASE) - digits += CHARSET_UPPERCASE_SIZE; + if (etd.bruteforce_charset & BF_CHARSET_UPPERCASE) + digits += BF_CHARSET_UPPERCASE_SIZE; no_iter = pow(digits, 4); } @@ -467,7 +470,10 @@ int CmdEM4x50Brute(const char *Cmd) { dur_s -= dur_h * 3600 + dur_m * 60; - PrintAndLogEx(INFO, "Estimated duration: %ih %im %is", dur_h, dur_m, dur_s); + if (no_iter > 0) + PrintAndLogEx(INFO, "Estimated duration: %ih %im %is", dur_h, dur_m, dur_s); + else + PrintAndLogEx(INFO, "Estimated duration: unknown"); // start clearCommandBuffer(); diff --git a/client/src/pm3line_vocabulary.h b/client/src/pm3line_vocabulary.h index eb45dba91..73970c9ba 100644 --- a/client/src/pm3line_vocabulary.h +++ b/client/src/pm3line_vocabulary.h @@ -329,6 +329,7 @@ const static vocabulary_t vocabulary[] = { { 0, "hf mf nested" }, { 1, "hf mf hardnested" }, { 0, "hf mf staticnested" }, + { 0, "hf mf brute" }, { 0, "hf mf autopwn" }, { 0, "hf mf nack" }, { 0, "hf mf chk" }, diff --git a/common/bruteforce.c b/common/bruteforce.c index 891796690..5946a689b 100644 --- a/common/bruteforce.c +++ b/common/bruteforce.c @@ -28,22 +28,31 @@ uint8_t charset_uppercase[] = { 'X', 'Y', 'Z' }; -void bf_generator_init(generator_context_t *ctx, uint8_t mode) { +smart_generator_t *smart_generators[] = { + smart_generator_byte_repeat, + smart_generator_msb_byte_only, + smart_generator_nibble_sequence, + NULL +}; + + +void bf_generator_init(generator_context_t *ctx, uint8_t mode, uint8_t key_length) { memset(ctx, 0, sizeof(generator_context_t)); ctx->mode = mode; + ctx->key_length = key_length; } int bf_generator_set_charset(generator_context_t *ctx, uint8_t charsets) { - if (ctx->mode != BRUTEFORCE_MODE_CHARSET) { + if (ctx->mode != BF_MODE_CHARSET) { return -1; } - if (charsets & CHARSET_DIGITS) { + if (charsets & BF_CHARSET_DIGITS) { memcpy(ctx->charset, charset_digits, sizeof(charset_digits)); ctx->charset_length += sizeof(charset_digits); } - if (charsets & CHARSET_UPPERCASE) { + if (charsets & BF_CHARSET_UPPERCASE) { memcpy(ctx->charset + ctx->charset_length, charset_uppercase, sizeof(charset_uppercase)); ctx->charset_length += sizeof(charset_uppercase); } @@ -51,51 +60,21 @@ int bf_generator_set_charset(generator_context_t *ctx, uint8_t charsets) { return 0; } -int bf_generate32(generator_context_t *ctx) { +int bf_generate(generator_context_t *ctx) { switch (ctx->mode) { - case BRUTEFORCE_MODE_RANGE: - return _bf_generate_mode_range32(ctx); - case BRUTEFORCE_MODE_CHARSET: - return _bf_generate_mode_charset32(ctx); + case BF_MODE_RANGE: + return _bf_generate_mode_range(ctx); + case BF_MODE_CHARSET: + return _bf_generate_mode_charset(ctx); + + case BF_MODE_SMART: + return _bf_generate_mode_smart(ctx); } - return GENERATOR_ERROR; + return BF_GENERATOR_ERROR; } -int _bf_generate_mode_range32(generator_context_t *ctx) { - - if (ctx->current_key32 >= ctx->range_high) { - return GENERATOR_END; - } - - // we use flag1 as indicator if value of range_low was already emitted - // so the range generated is - if (ctx->current_key32 <= ctx->range_low && ctx->flag1 == false) { - ctx->current_key32 = ctx->range_low; - ctx->pos[0] = true; - return GENERATOR_NEXT; - } - - ctx->current_key32++; - return GENERATOR_NEXT; -} - -int _bf_generate_mode_charset32(generator_context_t *ctx) { - - if (ctx->flag1) - return GENERATOR_END; - - ctx->current_key32 = ctx->charset[ctx->pos[0]] << 24 | ctx->charset[ctx->pos[1]] << 16 | - ctx->charset[ctx->pos[2]] << 8 | ctx->charset[ctx->pos[3]]; - - - if (bf_array_increment(ctx->pos, 4, ctx->charset_length) == -1) - // set flag1 to emit value last time and end generation - ctx->flag1 = true; - - return GENERATOR_NEXT; -} // increments values in array with carryover using modulo limit for each byte // this is used to iterate each byte in key over charset table @@ -127,3 +106,155 @@ int bf_array_increment(uint8_t *data, uint8_t data_len, uint8_t modulo) { return 0; } + +// get current key casted to 32 bit +uint32_t bf_get_key32(generator_context_t *ctx) { + return ctx->current_key & 0xFFFFFFFF; +} + +// get current key casted to 48 bit +uint64_t bf_get_key48(generator_context_t *ctx) { + return ctx->current_key & 0xFFFFFFFFFFFF; +} + +void bf_generator_clear(generator_context_t *ctx) { + ctx->flag1 = 0; + ctx->flag2 = 0; + ctx->flag3 = 0; + ctx->counter1 = 0; + ctx->counter2 = 0; +} + +int _bf_generate_mode_range(generator_context_t *ctx) { + + if (ctx->key_length != BF_KEY_SIZE_32 && ctx->key_length != BF_KEY_SIZE_48) + return BF_GENERATOR_ERROR; + + if (ctx->current_key >= ctx->range_high) { + return BF_GENERATOR_END; + } + + // we use flag1 as indicator if value of range_low was already emitted + // so the range generated is + if (ctx->current_key <= ctx->range_low && ctx->flag1 == false) { + ctx->current_key = ctx->range_low; + ctx->flag1 = true; + return BF_GENERATOR_NEXT; + } + + ctx->current_key++; + return BF_GENERATOR_NEXT; +} + +int _bf_generate_mode_charset(generator_context_t *ctx) { + + if (ctx->key_length != BF_KEY_SIZE_32 && ctx->key_length != BF_KEY_SIZE_48) { + return BF_GENERATOR_ERROR; + } + + if (ctx->flag1) + return BF_GENERATOR_END; + + uint8_t key_byte = 0; + ctx->current_key = 0; + + for (key_byte = 0; key_byte < ctx->key_length; key_byte++) { + ctx->current_key |= (uint64_t) ctx->charset[ctx->pos[key_byte]] << ((ctx->key_length - key_byte - 1) * 8); + } + + if (bf_array_increment(ctx->pos, ctx->key_length, ctx->charset_length) == -1) + // set flag1 to emit value last time and end generation on next call + ctx->flag1 = true; + + return BF_GENERATOR_NEXT; +} + +int _bf_generate_mode_smart(generator_context_t *ctx) { + + int ret; + + while (1) { + if (smart_generators[ctx->smart_mode_stage] == NULL) + return BF_GENERATOR_END; + + ret = smart_generators[ctx->smart_mode_stage](ctx); + + switch (ret) { + case BF_GENERATOR_NEXT: + return ret; + case BF_GENERATOR_ERROR: + return ret; + case BF_GENERATOR_END: + ctx->smart_mode_stage++; + bf_generator_clear(ctx); + continue; + } + } +} + + +int smart_generator_byte_repeat(generator_context_t *ctx) { + // key consists of repeated single byte + uint32_t current_byte = ctx->counter1; + + if (current_byte > 0xFF) + return BF_GENERATOR_END; + + ctx->current_key = 0; + + for (uint8_t key_byte = 0; key_byte < ctx->key_length; key_byte++) { + ctx->current_key |= (uint64_t)current_byte << ((ctx->key_length - key_byte - 1) * 8); + } + + ctx->counter1++; + return BF_GENERATOR_NEXT; +} +int smart_generator_msb_byte_only(generator_context_t *ctx) { + // key of one byte (most significant one) and all others being zero + uint32_t current_byte = ctx->counter1; + + if (current_byte > 0xFF) + return BF_GENERATOR_END; + + ctx->current_key = (uint64_t)current_byte << ((ctx->key_length - 1) * 8); + + ctx->counter1++; + return BF_GENERATOR_NEXT; +} + + +int smart_generator_nibble_sequence(generator_context_t *ctx) { + // patterns like A0A1A2A3...F0F1F2F3 + // also with offsets - A1A2A3, A2A3A4, etc + // counter1 is high nibble (A, B, C), counter2 is low nibble (0,1, etc) + + if(ctx->counter1 == 0){ // init values on first generator call + ctx->counter1 = 0x0A; + } + + uint8_t key_byte; + + // we substract %2 value because max_offset must be even number + uint8_t max_offset = 10 - (ctx->key_length / 2) - (ctx->key_length/2) % 2; + + if(ctx->counter1 == 0x10){ + return BF_GENERATOR_END; + } + + ctx->current_key = 0; + + for (key_byte = 0; key_byte < ctx->key_length; key_byte++) { + ctx->current_key |= (uint64_t) ctx->counter1 << (((ctx->key_length - key_byte - 1) * 8) + 4); + ctx->current_key |= (uint64_t) (key_byte + ctx->counter2) %10 << ((ctx->key_length - key_byte - 1) * 8); + } + + // counter 2 is the offset + ctx->counter2++; + + if(ctx->counter2 == max_offset){ + ctx->counter2 = 0; + ctx->counter1++; + } + + return BF_GENERATOR_NEXT; +} \ No newline at end of file diff --git a/common/bruteforce.h b/common/bruteforce.h index 91e01172d..61d1107b3 100644 --- a/common/bruteforce.h +++ b/common/bruteforce.h @@ -21,59 +21,84 @@ #include "common.h" -typedef uint8_t bruteforce_mode_t; +#define BF_KEY_SIZE_32 4 +#define BF_KEY_SIZE_48 6 + // bruteforcing all keys sequentially between X and Y -#define BRUTEFORCE_MODE_RANGE 1 +#define BF_MODE_RANGE 1 // try keys based on limited charset/passphrases // some payment systems use user-provided passphrase as system key -#define BRUTEFORCE_MODE_CHARSET 2 +#define BF_MODE_CHARSET 2 // "smart" mode - try some predictable patterns -#define BRUTEFORCE_MODE_SMART 3 +#define BF_MODE_SMART 3 -typedef uint8_t bruteforce_charset_t; // bit flags - can be used together using logical OR -#define CHARSET_DIGITS 1 -#define CHARSET_UPPERCASE 2 +#define BF_CHARSET_DIGITS 1 +#define BF_CHARSET_UPPERCASE 2 -#define GENERATOR_END 0 -#define GENERATOR_NEXT 1 -#define GENERATOR_ERROR 2 +#define BF_GENERATOR_END 0 +#define BF_GENERATOR_NEXT 1 +#define BF_GENERATOR_ERROR 2 -#define CHARSET_DIGITS_SIZE 10 -#define CHARSET_UPPERCASE_SIZE 25 +#define BF_CHARSET_DIGITS_SIZE 10 +#define BF_CHARSET_UPPERCASE_SIZE 25 extern uint8_t charset_digits[]; extern uint8_t charset_uppercase[]; +typedef uint8_t bruteforce_charset_t; +typedef uint8_t bruteforce_mode_t; + // structure to hold key generator temporary data typedef struct { - // position of each of 4 bytes in 32 bit key in charset mode + // position of each of bytes in charset mode - used to iterate over alphabets // add more bytes to support larger keys // pos[0] is most significant byte - all maths avoid relying on little/big endian memory layout - uint8_t pos[4]; - uint32_t current_key32; + uint8_t pos[6]; // max supported key is now 48 bit + + uint8_t key_length; // bytes + uint64_t current_key; // Use 64 bit and truncate when needed. uint8_t mode; uint8_t charset[ - CHARSET_DIGITS_SIZE - + CHARSET_UPPERCASE_SIZE + BF_CHARSET_DIGITS_SIZE + + BF_CHARSET_UPPERCASE_SIZE ]; uint8_t charset_length; uint32_t range_low; uint32_t range_high; + uint16_t smart_mode_stage; // flags to use internally by generators as they wish bool flag1, flag2, flag3; + // counters to use internally by generators as they wish + uint32_t counter1, counter2; } generator_context_t; -void bf_generator_init(generator_context_t *ctx, uint8_t mode); + +void bf_generator_init(generator_context_t *ctx, uint8_t mode, uint8_t key_size); +void bf_generator_clear(generator_context_t *ctx); // clear flags and counters used by generators int bf_generator_set_charset(generator_context_t *ctx, uint8_t charsets); -int bf_generate32(generator_context_t *ctx); -int _bf_generate_mode_range32(generator_context_t *ctx); -int _bf_generate_mode_charset32(generator_context_t *ctx); -int _bf_generate_mode_smart32(generator_context_t *ctx); +int bf_generate(generator_context_t *ctx); +int _bf_generate_mode_range(generator_context_t *ctx); +int _bf_generate_mode_charset(generator_context_t *ctx); +int _bf_generate_mode_smart(generator_context_t *ctx); int bf_array_increment(uint8_t *data, uint8_t data_len, uint8_t modulo); +uint32_t bf_get_key32(generator_context_t *ctx); +uint64_t bf_get_key48(generator_context_t *ctx); + +// smart mode +typedef int (smart_generator_t)(generator_context_t *ctx); + +int bf_generate_mode_smart(generator_context_t *ctx); + +int smart_generator_byte_repeat(generator_context_t *ctx); +int smart_generator_msb_byte_only(generator_context_t *ctx); +int smart_generator_nibble_sequence(generator_context_t *ctx); + +extern smart_generator_t *smart_generators[]; // array of smart cracking functions + #endif // BRUTEFORCE_H__ diff --git a/doc/commands.md b/doc/commands.md index e4259edea..a4fc98c18 100644 --- a/doc/commands.md +++ b/doc/commands.md @@ -492,6 +492,7 @@ Check column "offline" for their availability. |`hf mf nested `|N |`Nested attack` |`hf mf hardnested `|Y |`Nested attack for hardened MIFARE Classic cards` |`hf mf staticnested `|N |`Nested attack against static nonce MIFARE Classic cards` +|`hf mf brute `|N |`Smart bruteforce to exploit weak key generators` |`hf mf autopwn `|N |`Automatic key recovery tool for MIFARE Classic` |`hf mf nack `|N |`Test for MIFARE NACK bug` |`hf mf chk `|N |`Check keys`