Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add smart bruteforce mode to Mifare Classic and EM4x50 #2251

Merged
merged 13 commits into from
Jan 16, 2024
12 changes: 8 additions & 4 deletions armsrc/em4x50.c
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand Down
1 change: 1 addition & 0 deletions client/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -749,6 +749,7 @@ SRCS = mifare/aiddesfire.c \

# common
SRCS += bucketsort.c \
bruteforce.c \
cardhelper.c \
crapto1/crapto1.c \
crapto1/crypto1.c \
Expand Down
35 changes: 35 additions & 0 deletions client/dictionaries/mfc_default_keys.dic
Original file line number Diff line number Diff line change
Expand Up @@ -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

212 changes: 212 additions & 0 deletions client/src/cmdhfmf.c
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
// High frequency MIFARE commands
//-----------------------------------------------------------------------------

#include "bruteforce.h"
#include "cmdhfmf.h"
#include <ctype.h>
#include "cmdparser.h" // command_t
Expand Down Expand Up @@ -3369,6 +3370,216 @@
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): %u",
keys_checked / ((msclock() - t1) / 1000));
github-advanced-security[bot] marked this conversation as resolved.
Fixed
Show resolved Hide resolved

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_("%u") "\n", total_keys_checked);
github-advanced-security[bot] marked this conversation as resolved.
Fixed
Show resolved Hide resolved
// 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);

if (found_keys == (sectorsCnt << 1)) {
PrintAndLogEx(SUCCESS, "Card dumped as well. run " _YELLOW_("`%s %c`"),
"hf mf esave",
GetFormatFromSector(sectorsCnt)
);
}

}
}

free(keyBlock);
free(e_sector);
PrintAndLogEx(NORMAL, "");
return PM3_SUCCESS;
}

static int CmdHF14AMfChk(const char *Cmd) {
CLIParserContext *ctx;
CLIParserInit(&ctx, "hf mf chk",
Expand Down Expand Up @@ -9054,6 +9265,7 @@
{"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"},
Expand Down
34 changes: 20 additions & 14 deletions client/src/cmdlfem4x50.c
Original file line number Diff line number Diff line change
Expand Up @@ -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", "<str>", "Bruteforce mode (range|charset)"),
arg_str1(NULL, "mode", "<str>", "Bruteforce mode (range|charset|smart)"),
arg_str0(NULL, "begin", "<hex>", "Range mode - start of the key range"),
arg_str0(NULL, "end", "<hex>", "Range mode - end of the key range"),
arg_lit0(NULL, "digits", "Charset mode - include ASCII codes for digits"),
Expand All @@ -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);
Expand All @@ -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.");
Expand All @@ -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);
}
Expand All @@ -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();
Expand Down
Loading
Loading