add smart bruteforce mode to MF Classic and EM4x50

This commit is contained in:
PhaseLoop 2024-01-15 20:25:02 +00:00
commit dd859a2061
9 changed files with 323 additions and 37 deletions

View file

@ -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;
}

View file

@ -749,6 +749,7 @@ SRCS = mifare/aiddesfire.c \
# common
SRCS += bucketsort.c \
bruteforce.c \
cardhelper.c \
crapto1/crapto1.c \
crapto1/crypto1.c \

View file

@ -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

View file

@ -16,6 +16,7 @@
// High frequency MIFARE commands
//-----------------------------------------------------------------------------
#include "bruteforce.h"
#include "cmdhfmf.h"
#include <ctype.h>
#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"},

View file

@ -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");

View file

@ -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" },

View file

@ -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;
}

View file

@ -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

View file

@ -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... }