diff --git a/armsrc/em4x50.c b/armsrc/em4x50.c index 6b90b079b..ff68b724b 100644 --- a/armsrc/em4x50.c +++ b/armsrc/em4x50.c @@ -643,9 +643,9 @@ static bool brute(const em4x50_data_t *etd, uint32_t *pwd) { bf_generator_init(&ctx, etd->bruteforce_mode, BF_KEY_SIZE_32); - if (etd->bruteforce_mode == BF_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){ + } else if (etd->bruteforce_mode == BF_MODE_RANGE) { ctx.range_low = etd->password1; ctx.range_high = etd->password2; } 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..795d8fe0a 100644 --- a/client/dictionaries/mfc_default_keys.dic +++ b/client/dictionaries/mfc_default_keys.dic @@ -2337,3 +2337,39 @@ EA0CA627FD06 # Hotel key CE0F4F15E909 D60DE9436219 + +# ATM Arena de Girona, spanish transport card + +A00000000000 +A01000000000 +A02000000000 +A03000000000 +A04000000000 +A05000000000 +A06000000000 +A07000000000 +A08000000000 +A09000000000 +A10000000000 +A11000000000 +A12000000000 +A13000000000 +A14000000000 +A15000000000 + +B00000000000 +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 8a158f3b8..6673491b5 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,216 @@ 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_lit0(NULL, "mem", "Use dictionary from flashmemory"), + 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); + bool use_flashmemory = arg_get_lit(ctx, 8); + + 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); + 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): %d", + 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_("%d") "\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 (use_flashmemory && found_keys == (sectorsCnt << 1)) { + PrintAndLogEx(SUCCESS, "Card dumped as well. run " _YELLOW_("`%s %c`"), + "hf mf esave", + GetFormatFromSector(sectorsCnt) + ); + } + + 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 +9265,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 2efaa1f0b..39510c27b 100644 --- a/client/src/cmdlfem4x50.c +++ b/client/src/cmdlfem4x50.c @@ -386,10 +386,9 @@ int CmdEM4x50Brute(const char *Cmd) { etd.bruteforce_mode = BF_MODE_RANGE; } else if (strcmp(mode, "charset") == 0) { etd.bruteforce_mode = BF_MODE_CHARSET; - } else if (strcmp(mode, "smart") == 0){ + } else if (strcmp(mode, "smart") == 0) { etd.bruteforce_mode = BF_MODE_SMART; - } else - { + } else { PrintAndLogEx(FAILED, "Unknown bruteforce mode: %s", mode); CLIParserFree(ctx); return PM3_EINVARG; @@ -471,7 +470,7 @@ int CmdEM4x50Brute(const char *Cmd) { dur_s -= dur_h * 3600 + dur_m * 60; - if ( no_iter > 0 ) + if (no_iter > 0) PrintAndLogEx(INFO, "Estimated duration: %ih %im %is", dur_h, dur_m, dur_s); else PrintAndLogEx(INFO, "Estimated duration: unknown"); diff --git a/client/src/pm3line_vocabulary.h b/client/src/pm3line_vocabulary.h index 17c9c03e7..aa845203d 100644 --- a/client/src/pm3line_vocabulary.h +++ b/client/src/pm3line_vocabulary.h @@ -327,6 +327,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" }, @@ -501,8 +502,6 @@ const static vocabulary_t vocabulary[] = { { 1, "hf vas help" }, { 0, "hf vas reader" }, { 1, "hf vas decrypt" }, - { 1, "hf waveshare help" }, - { 0, "hf waveshare loadbmp" }, { 1, "hf xerox help" }, { 0, "hf xerox info" }, { 0, "hf xerox reader" }, diff --git a/common/bruteforce.c b/common/bruteforce.c index 4ac8834c8..5946a689b 100644 --- a/common/bruteforce.c +++ b/common/bruteforce.c @@ -30,6 +30,8 @@ uint8_t charset_uppercase[] = { smart_generator_t *smart_generators[] = { smart_generator_byte_repeat, + smart_generator_msb_byte_only, + smart_generator_nibble_sequence, NULL }; @@ -68,7 +70,7 @@ int bf_generate(generator_context_t *ctx) { case BF_MODE_SMART: return _bf_generate_mode_smart(ctx); - } + } return BF_GENERATOR_ERROR; } @@ -106,16 +108,16 @@ int bf_array_increment(uint8_t *data, uint8_t data_len, uint8_t modulo) { } // get current key casted to 32 bit -uint32_t bf_get_key32(generator_context_t *ctx){ +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){ +uint64_t bf_get_key48(generator_context_t *ctx) { return ctx->current_key & 0xFFFFFFFFFFFF; } -void bf_generator_clear(generator_context_t *ctx){ +void bf_generator_clear(generator_context_t *ctx) { ctx->flag1 = 0; ctx->flag2 = 0; ctx->flag3 = 0; @@ -126,7 +128,7 @@ void bf_generator_clear(generator_context_t *ctx){ 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; + return BF_GENERATOR_ERROR; if (ctx->current_key >= ctx->range_high) { return BF_GENERATOR_END; @@ -146,7 +148,7 @@ int _bf_generate_mode_range(generator_context_t *ctx) { int _bf_generate_mode_charset(generator_context_t *ctx) { - if (ctx->key_length != BF_KEY_SIZE_32 && ctx->key_length != BF_KEY_SIZE_48){ + if (ctx->key_length != BF_KEY_SIZE_32 && ctx->key_length != BF_KEY_SIZE_48) { return BF_GENERATOR_ERROR; } @@ -156,9 +158,8 @@ int _bf_generate_mode_charset(generator_context_t *ctx) { 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); + 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) @@ -168,17 +169,17 @@ int _bf_generate_mode_charset(generator_context_t *ctx) { return BF_GENERATOR_NEXT; } -int _bf_generate_mode_smart(generator_context_t *ctx){ +int _bf_generate_mode_smart(generator_context_t *ctx) { int ret; - while(1){ + while (1) { if (smart_generators[ctx->smart_mode_stage] == NULL) - return BF_GENERATOR_END; + return BF_GENERATOR_END; ret = smart_generators[ctx->smart_mode_stage](ctx); - switch (ret){ + switch (ret) { case BF_GENERATOR_NEXT: return ret; case BF_GENERATOR_ERROR: @@ -192,7 +193,7 @@ int _bf_generate_mode_smart(generator_context_t *ctx){ } -int smart_generator_byte_repeat(generator_context_t *ctx){ +int smart_generator_byte_repeat(generator_context_t *ctx) { // key consists of repeated single byte uint32_t current_byte = ctx->counter1; @@ -201,13 +202,59 @@ int smart_generator_byte_repeat(generator_context_t *ctx){ 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); + 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_test2(generator_context_t *ctx){ - return 0; +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 16258465e..61d1107b3 100644 --- a/common/bruteforce.h +++ b/common/bruteforce.h @@ -96,7 +96,8 @@ 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_test2(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 diff --git a/doc/commands.md b/doc/commands.md index 8478e3205..1820a55f1 100644 --- a/doc/commands.md +++ b/doc/commands.md @@ -490,6 +490,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` @@ -754,16 +755,6 @@ Check column "offline" for their availability. |`hf vas decrypt `|Y |`Decrypt a previously captured VAS cryptogram` -### hf waveshare - - { Waveshare NFC ePaper... } - -|command |offline |description -|------- |------- |----------- -|`hf waveshare help `|Y |`This help` -|`hf waveshare loadbmp `|N |`Load BMP file to Waveshare NFC ePaper` - - ### hf xerox { Fuji/Xerox cartridge RFIDs... }