From 9f0171a706f6030d1e609f7922818ca06d64133f Mon Sep 17 00:00:00 2001 From: Niel Nielsen Date: Mon, 31 Aug 2026 15:28:21 +0200 Subject: [PATCH] Add files via upload Signed-off-by: Niel Nielsen --- client/src/cmdfpga.c | 2 +- client/src/cmdhf14a.c | 6 +- client/src/cmdhf14b.c | 4 +- client/src/cmdhf15.c | 6 +- client/src/cmdhffelica.c | 6 +- client/src/cmdhficlass.c | 10 +- client/src/cmdhfjooki.c | 2 +- client/src/cmdhflegic.c | 2 +- client/src/cmdhfmf.c | 12 +- client/src/cmdhfmfu.c | 10 +- client/src/cmdhw.c | 4 +- client/src/cmdlf.c | 22 +- client/src/cmdlfem4x50.c | 2 +- client/src/mifarehost.c | 2196 ++++++++++++++++++++++++++++++++++++++ 14 files changed, 2242 insertions(+), 42 deletions(-) create mode 100644 client/src/mifarehost.c diff --git a/client/src/cmdfpga.c b/client/src/cmdfpga.c index 1b74c176d..58b6982e5 100644 --- a/client/src/cmdfpga.c +++ b/client/src/cmdfpga.c @@ -90,7 +90,7 @@ static int CmdConfigFpga(const char *Cmd) { // Start to send data in chunks size_t offset = 0; while (offset < datalen) { - size_t chunk_size = (datalen - offset) > PM3_CMD_DATA_SIZE ? PM3_CMD_DATA_SIZE : (datalen - offset); + size_t chunk_size = (datalen - offset) > pm3_max_cmd_data_size() ? pm3_max_cmd_data_size() : (datalen - offset); SendCommandNG(CMD_FPGA_BITSTREAM_CONFIG_WRITE, data + offset, chunk_size); // Wait for response before sending next chunk if (WaitForResponseTimeout(CMD_FPGA_BITSTREAM_CONFIG_WRITE, &resp, 5000) == false) { diff --git a/client/src/cmdhf14a.c b/client/src/cmdhf14a.c index 5f9ec725d..cab4ada73 100644 --- a/client/src/cmdhf14a.c +++ b/client/src/cmdhf14a.c @@ -301,7 +301,7 @@ int hf14a_getversion_data(iso14a_card_select_t *card, uint64_t select_status, ve void SendIso14aReaderEx(uint32_t flags, const uint8_t *data, uint16_t datalen, uint16_t len, uint16_t lenbits, uint32_t timeout, uint32_t wait_us) { - if (datalen > (PM3_CMD_DATA_SIZE - sizeof(iso14a_raw_cmd_t))) { + if (datalen > (pm3_max_cmd_data_size() - sizeof(iso14a_raw_cmd_t))) { PrintAndLogEx(WARNING, "Sending " _RED_("%u") " bytes of payload is too much, abort", datalen); return; } @@ -1235,7 +1235,7 @@ int ExchangeRAW14a(uint8_t *datain, int datainlen, bool activateField, bool leav uint8_t data[PM3_CMD_DATA_SIZE] = { 0x0a | gs_frames_num, 0x00 }; gs_frames_num ^= 1; - int min = MIN((PM3_CMD_DATA_SIZE - 2), (datainlen & 0x1FF)); + int min = MIN((int)(pm3_max_cmd_data_size() - 2), (datainlen & 0x1FF)); memcpy(&data[2], datain, min); SendIso14aReader(ISO14A_RAW | ISO14A_APPEND_CRC | cmdc, data, min + 2); @@ -1541,7 +1541,7 @@ int ExchangeAPDU14a(const uint8_t *datain, int datainlen, bool activateField, bo // 3 byte here - 1b framing header, 2b crc16 if (g_apdu_in_framing_enable && - ((gs_frame_len && (datainlen > gs_frame_len - 3)) || (datainlen > PM3_CMD_DATA_SIZE - 3))) { + ((gs_frame_len && (datainlen > gs_frame_len - 3)) || (datainlen > pm3_max_cmd_data_size() - 3))) { int clen = 0; diff --git a/client/src/cmdhf14b.c b/client/src/cmdhf14b.c index 5d3eda8bb..968c936c1 100644 --- a/client/src/cmdhf14b.c +++ b/client/src/cmdhf14b.c @@ -1269,7 +1269,7 @@ static int CmdHF14BRaw(const char *Cmd) { } // Max buffer is PM3_CMD_DATA_SIZE - datalen = (datalen > PM3_CMD_DATA_SIZE) ? PM3_CMD_DATA_SIZE : datalen; + datalen = (datalen > pm3_max_cmd_data_size()) ? pm3_max_cmd_data_size() : datalen; iso14b_raw_cmd_t *packet = (iso14b_raw_cmd_t *)calloc(1, sizeof(iso14b_raw_cmd_t) + datalen); if (packet == NULL) { @@ -2935,7 +2935,7 @@ int exchange_14b_apdu(uint8_t *datain, int datainlen, bool activate_field, // 3 byte here - 1b framing header, 2b crc16 if (apdu_in_framing_enable && - ((apdu_frame_length && (datainlen > apdu_frame_length - 3)) || (datainlen > PM3_CMD_DATA_SIZE - 3))) { + ((apdu_frame_length && (datainlen > apdu_frame_length - 3)) || (datainlen > pm3_max_cmd_data_size() - 3))) { int clen = 0; bool v_activate_field = activate_field; diff --git a/client/src/cmdhf15.c b/client/src/cmdhf15.c index e81fa2eae..e7e2f7dde 100644 --- a/client/src/cmdhf15.c +++ b/client/src/cmdhf15.c @@ -1649,7 +1649,7 @@ static int hf15EmlSetMem(const uint8_t *data, uint16_t count, size_t offset) { uint8_t data[]; } PACKED; - if (count > (PM3_CMD_DATA_SIZE - sizeof(struct p))) { + if (count > (pm3_max_cmd_data_size() - sizeof(struct p))) { return PM3_ESOFT; } @@ -2575,11 +2575,11 @@ static int CmdHF15Raw(const char *Cmd) { bool wait = arg_get_lit(ctx, 7); CLIParserFree(ctx); - datalen = (datalen >= PM3_CMD_DATA_SIZE) ? PM3_CMD_DATA_SIZE : datalen; + datalen = (datalen >= pm3_max_cmd_data_size()) ? pm3_max_cmd_data_size() : datalen; if (crc) { - if ((datalen - 2) < PM3_CMD_DATA_SIZE) { + if ((datalen - 2) < pm3_max_cmd_data_size()) { AddCrc15(data, datalen); datalen += 2; } else { diff --git a/client/src/cmdhffelica.c b/client/src/cmdhffelica.c index d4f54abbe..15d07d577 100644 --- a/client/src/cmdhffelica.c +++ b/client/src/cmdhffelica.c @@ -8252,7 +8252,7 @@ static int felica_sim_model_from_json(json_t *root, felica_sim_model_t *model_ou static int felica_sim_send_control(uint8_t subcommand, uint32_t total_len, uint32_t offset, uint16_t model_crc, const uint8_t *data, uint16_t data_len, uint32_t timeout_ms) { - if (data_len > (PM3_CMD_DATA_SIZE - sizeof(felica_sim_upload_t))) { + if (data_len > (pm3_max_cmd_data_size() - sizeof(felica_sim_upload_t))) { return PM3_EOVFLOW; } @@ -8287,7 +8287,7 @@ static int felica_sim_upload_model(const felica_sim_model_t *model) { return ret; } - const uint16_t chunk_size = PM3_CMD_DATA_SIZE - sizeof(felica_sim_upload_t); + const uint16_t chunk_size = pm3_max_cmd_data_size() - sizeof(felica_sim_upload_t); uint32_t offset = 0; PrintAndLogEx(INFO, "Uploading simulator model"); PrintAndLogEx(INFO, "." NOLF); @@ -9438,7 +9438,7 @@ static int CmdHFFelicaCmdRaw(const char *Cmd) { } // Max transport buffer is PM3_CMD_DATA_SIZE - datalen = (datalen > PM3_CMD_DATA_SIZE) ? PM3_CMD_DATA_SIZE : datalen; + datalen = (datalen > pm3_max_cmd_data_size()) ? pm3_max_cmd_data_size() : datalen; PrintAndLogEx(SUCCESS, "Data: %s", sprint_hex(data, datalen)); diff --git a/client/src/cmdhficlass.c b/client/src/cmdhficlass.c index 38eaba080..bd762c990 100644 --- a/client/src/cmdhficlass.c +++ b/client/src/cmdhficlass.c @@ -307,7 +307,7 @@ static void iclass_upload_emul(uint8_t *d, uint16_t n, uint16_t offset, uint16_t PrintAndLogEx(INFO, "." NOLF); while (bytes_remaining > 0) { - uint32_t bytes_in_packet = MIN(PM3_CMD_DATA_SIZE - 4, bytes_remaining); + uint32_t bytes_in_packet = MIN((uint32_t)(pm3_max_cmd_data_size() - 4), bytes_remaining); if (bytes_in_packet == bytes_remaining) { // Disable fast mode on last packet g_conn.block_after_ACK = false; @@ -3565,8 +3565,8 @@ static int CmdHFiClassRestore(const char *Cmd) { uint32_t payload_size = sizeof(iclass_restore_req_t) + (sizeof(iclass_restore_item_t) * (endblock - startblock + 1)); - if (payload_size > PM3_CMD_DATA_SIZE) { - PrintAndLogEx(NORMAL, "Trying to write too many blocks at once. Max: %d", PM3_CMD_DATA_SIZE / 8); + if (payload_size > pm3_max_cmd_data_size()) { + PrintAndLogEx(NORMAL, "Trying to write too many blocks at once. Max: %d", pm3_max_cmd_data_size() / 8); return PM3_EINVARG; } @@ -6065,8 +6065,8 @@ static int CmdHFiClassCheckKeys(const char *Cmd) { // USB_COMMAND. 512/4 = 103 mac uint32_t max_chunk_size = 0; - if (keycount > ((PM3_CMD_DATA_SIZE - sizeof(iclass_chk_t)) / 4)) - max_chunk_size = (PM3_CMD_DATA_SIZE - sizeof(iclass_chk_t)) / 4; + if (keycount > ((pm3_max_cmd_data_size() - sizeof(iclass_chk_t)) / 4)) + max_chunk_size = (pm3_max_cmd_data_size() - sizeof(iclass_chk_t)) / 4; else max_chunk_size = keycount; diff --git a/client/src/cmdhfjooki.c b/client/src/cmdhfjooki.c index c7a3db1ee..142d84a66 100644 --- a/client/src/cmdhfjooki.c +++ b/client/src/cmdhfjooki.c @@ -547,7 +547,7 @@ static int CmdHF14AJookiSim(const char *Cmd) { uint8_t blockno = 0; // 12 is the size of the struct the fct mf_eml_set_mem_xt uses to transfer to device - uint16_t max_avail_blocks = ((PM3_CMD_DATA_SIZE - 12) / blockwidth); + uint16_t max_avail_blocks = ((pm3_max_cmd_data_size() - 12) / blockwidth); while (datalen) { if (datalen == blockwidth) { diff --git a/client/src/cmdhflegic.c b/client/src/cmdhflegic.c index 8d90fa6f6..c24d51677 100644 --- a/client/src/cmdhflegic.c +++ b/client/src/cmdhflegic.c @@ -35,7 +35,7 @@ static int CmdHelp(const char *Cmd); #define LEGIC_PRIME_MIM256 256 #define LEGIC_PRIME_MIM1024 1024 #define LEGIC_BLOCK_SIZE 8 -#define LEGIC_PACKET_SIZE (PM3_CMD_DATA_SIZE - sizeof(legic_packet_t)) +#define LEGIC_PACKET_SIZE (pm3_max_cmd_data_size() - sizeof(legic_packet_t)) static bool legic_xor(uint8_t *data, uint16_t cardsize) { diff --git a/client/src/cmdhfmf.c b/client/src/cmdhfmf.c index 1be506608..5d810b594 100644 --- a/client/src/cmdhfmf.c +++ b/client/src/cmdhfmf.c @@ -3395,7 +3395,9 @@ static int CmdHF14AMfAutoPWN(const char *Cmd) { res = mf_check_keys_fast(sector_cnt, true, true, 1, key_cnt, keyBlock, e_sector, use_flashmemory, verbose); } else { - uint32_t chunksize = key_cnt > MFC_CHKKEYS_FAST_MAX_KEYS ? MFC_CHKKEYS_FAST_MAX_KEYS : key_cnt; + uint32_t maxkeys = (pm3_max_cmd_data_size() - sizeof(mf_chkkeys_fast_t)) / 6; + if (maxkeys > 255) maxkeys = 255; + uint32_t chunksize = key_cnt > maxkeys ? maxkeys : key_cnt; bool firstChunk = true, lastChunk = false; for (uint8_t strategy = 1; strategy < 3; strategy++) { @@ -4045,7 +4047,9 @@ static int CmdHF14AMfChk_fast(const char *Cmd) { return PM3_EMALLOC; } - uint32_t chunksize = (keycnt > MFC_CHKKEYS_FAST_MAX_KEYS) ? MFC_CHKKEYS_FAST_MAX_KEYS : keycnt; + uint32_t maxkeys = (pm3_max_cmd_data_size() - sizeof(mf_chkkeys_fast_t)) / 6; + if (maxkeys > 255) maxkeys = 255; + uint32_t chunksize = (keycnt > maxkeys) ? maxkeys : keycnt; bool firstChunk = true, lastChunk = false; int i = 0; @@ -5429,7 +5433,7 @@ int CmdHF14AMfELoad(const char *Cmd) { int cnt = 0; // 12 is the size of the struct the fct mf_eml_set_mem_xt uses to transfer to device - uint16_t max_avail_blocks = ((PM3_CMD_DATA_SIZE - 12) / block_width) * block_width; + uint16_t max_avail_blocks = ((pm3_max_cmd_data_size() - 12) / block_width) * block_width; while (bytes_read && cnt < block_cnt) { if (bytes_read == block_width) { @@ -9404,7 +9408,7 @@ static int CmdHF14AGen4Save(const char *Cmd) { uint16_t bytes_left = bytes ; // 12 is the size of the struct the fct mf_eml_set_mem_xt uses to transfer to device - uint16_t max_avail_blocks = ((PM3_CMD_DATA_SIZE - 12) / MFBLOCK_SIZE) * MFBLOCK_SIZE; + uint16_t max_avail_blocks = ((pm3_max_cmd_data_size() - 12) / MFBLOCK_SIZE) * MFBLOCK_SIZE; while (bytes_left > 0 && cnt < block_cnt) { if (bytes_left == MFBLOCK_SIZE) { diff --git a/client/src/cmdhfmfu.c b/client/src/cmdhfmfu.c index aa36402ad..8dae4ba15 100644 --- a/client/src/cmdhfmfu.c +++ b/client/src/cmdhfmfu.c @@ -4687,7 +4687,7 @@ static int CmdHF14AMfUCAuth(const char *Cmd) { uint16_t max_retries_per_call = retries; if (collect_nonces) { // Not strictly needed, but to avoid fw warning - max_retries_per_call = ((PM3_CMD_DATA_SIZE - sizeof(uint32_t) * 2) / sizeof(uint64_t)) - 1; + max_retries_per_call = ((pm3_max_cmd_data_size() - sizeof(uint32_t) * 2) / sizeof(uint64_t)) - 1; } isok = ul3pass_authentication(auth_key_ptr, MIFAREULC_KEY_INDEX, !keep_field_on, MIN(retries - auths, max_retries_per_call), &auths, &ms, false, !skip_auth, check_answer, use_fastread0, collect_nonces, (uint8_t *)(nonces + auths), reset_field, available_pairs, pairs); } while (skip_auth && auths < 1 + retries); @@ -4783,7 +4783,7 @@ static int CmdHF14AMfUCAuthChk(const char *Cmd) { } // cap by what fits in one frame, then by what the nkeys field can announce - uint32_t max_chunk = (PM3_CMD_DATA_SIZE - MIFAREU3P_CHKKEY_HEADER) / keysize; + uint32_t max_chunk = (pm3_max_cmd_data_size() - MIFAREU3P_CHKKEY_HEADER) / keysize; if (max_chunk > MIFAREU3P_CHKKEY_MAX_KEYS) { max_chunk = MIFAREU3P_CHKKEY_MAX_KEYS; } @@ -5002,7 +5002,7 @@ static int CmdHF14AMfUAESAuthChk(const char *Cmd) { } // cap by what fits in one frame, then by what the nkeys field can announce - uint32_t max_chunk = (PM3_CMD_DATA_SIZE - MIFAREU3P_CHKKEY_HEADER) / keysize; + uint32_t max_chunk = (pm3_max_cmd_data_size() - MIFAREU3P_CHKKEY_HEADER) / keysize; if (max_chunk > MIFAREU3P_CHKKEY_MAX_KEYS) { max_chunk = MIFAREU3P_CHKKEY_MAX_KEYS; } @@ -8685,9 +8685,9 @@ static int CmdHF14AMfUeSetBlk(const char *Cmd) { // one esetblk is a single CMD_HF_MIFARE_EML_MEMSET; its payload (data + a 4-byte // header) must fit the command buffer. Larger sets should use `hf mfu eload`. - if (datalen > (int)(PM3_CMD_DATA_SIZE - 4)) { + if (datalen > (int)(pm3_max_cmd_data_size() - 4)) { PrintAndLogEx(WARNING, "too many pages for one command: max %d pages (%d bytes). Use " _YELLOW_("`hf mfu eload`") " for larger sets", - (int)((PM3_CMD_DATA_SIZE - 4) / MFU_BLOCK_SIZE), (int)(PM3_CMD_DATA_SIZE - 4)); + (int)((pm3_max_cmd_data_size() - 4) / MFU_BLOCK_SIZE), (int)(pm3_max_cmd_data_size() - 4)); return PM3_EINVARG; } diff --git a/client/src/cmdhw.c b/client/src/cmdhw.c index 62f81162a..a1282d4b3 100644 --- a/client/src/cmdhw.c +++ b/client/src/cmdhw.c @@ -1910,8 +1910,8 @@ static int CmdPing(const char *Cmd) { uint32_t len = arg_get_u32_def(ctx, 1, 32); CLIParserFree(ctx); - if (len > PM3_CMD_DATA_SIZE) - len = PM3_CMD_DATA_SIZE; + if (len > pm3_max_cmd_data_size()) + len = pm3_max_cmd_data_size(); if (len) { PrintAndLogEx(INFO, "Ping sent with payload len... " _YELLOW_("%d"), len); diff --git a/client/src/cmdlf.c b/client/src/cmdlf.c index f9d52f6cd..95e4c171a 100644 --- a/client/src/cmdlf.c +++ b/client/src/cmdlf.c @@ -1069,9 +1069,9 @@ int lfsim_upload_gb(void) { //can send only 512 bits at a time (1 byte sent per bit...) PrintAndLogEx(INFO, "." NOLF); - for (size_t i = 0; i < g_GraphTraceLen; i += PM3_CMD_DATA_SIZE - 3) { + for (size_t i = 0; i < g_GraphTraceLen; i += pm3_max_cmd_data_size() - 3) { - size_t len = MIN((g_GraphTraceLen - i), PM3_CMD_DATA_SIZE - 3); + size_t len = MIN((g_GraphTraceLen - i), (size_t)(pm3_max_cmd_data_size() - 3)); clearCommandBuffer(); payload_up.offset = i; @@ -1236,10 +1236,10 @@ int CmdLFfskSim(const char *Cmd) { } size_t size = g_DemodBufferLen; - if (size > (PM3_CMD_DATA_SIZE - sizeof(lf_fsksim_t))) { - PrintAndLogEx(WARNING, "DemodBuffer too long for current implementation - length: %zu - max: %zu", size, PM3_CMD_DATA_SIZE - sizeof(lf_fsksim_t)); + if (size > (pm3_max_cmd_data_size() - sizeof(lf_fsksim_t))) { + PrintAndLogEx(WARNING, "DemodBuffer too long for current implementation - length: %zu - max: %zu", size, (size_t)(pm3_max_cmd_data_size() - sizeof(lf_fsksim_t))); PrintAndLogEx(INFO, "Continuing with trimmed down data"); - size = PM3_CMD_DATA_SIZE - sizeof(lf_fsksim_t); + size = pm3_max_cmd_data_size() - sizeof(lf_fsksim_t); } lf_fsksim_t *payload = calloc(1, sizeof(lf_fsksim_t) + size); @@ -1353,10 +1353,10 @@ int CmdLFaskSim(const char *Cmd) { } size_t size = g_DemodBufferLen; - if (size > (PM3_CMD_DATA_SIZE - sizeof(lf_asksim_t))) { - PrintAndLogEx(WARNING, "DemodBuffer too long for current implementation - length: %zu - max: %zu", size, PM3_CMD_DATA_SIZE - sizeof(lf_asksim_t)); + if (size > (pm3_max_cmd_data_size() - sizeof(lf_asksim_t))) { + PrintAndLogEx(WARNING, "DemodBuffer too long for current implementation - length: %zu - max: %zu", size, (size_t)(pm3_max_cmd_data_size() - sizeof(lf_asksim_t))); PrintAndLogEx(INFO, "Continuing with trimmed down data"); - size = PM3_CMD_DATA_SIZE - sizeof(lf_asksim_t); + size = pm3_max_cmd_data_size() - sizeof(lf_asksim_t); } lf_asksim_t *payload = calloc(1, sizeof(lf_asksim_t) + size); @@ -1489,10 +1489,10 @@ int CmdLFpskSim(const char *Cmd) { } size_t size = g_DemodBufferLen; - if (size > (PM3_CMD_DATA_SIZE - sizeof(lf_psksim_t))) { - PrintAndLogEx(WARNING, "DemodBuffer too long for current implementation - length: %zu - max: %zu", size, PM3_CMD_DATA_SIZE - sizeof(lf_psksim_t)); + if (size > (pm3_max_cmd_data_size() - sizeof(lf_psksim_t))) { + PrintAndLogEx(WARNING, "DemodBuffer too long for current implementation - length: %zu - max: %zu", size, (size_t)(pm3_max_cmd_data_size() - sizeof(lf_psksim_t))); PrintAndLogEx(INFO, "Continuing with trimmed down data"); - size = PM3_CMD_DATA_SIZE - sizeof(lf_psksim_t); + size = pm3_max_cmd_data_size() - sizeof(lf_psksim_t); } lf_psksim_t *payload = calloc(1, sizeof(lf_psksim_t) + size); diff --git a/client/src/cmdlfem4x50.c b/client/src/cmdlfem4x50.c index b8da66253..2c1ccc7e4 100644 --- a/client/src/cmdlfem4x50.c +++ b/client/src/cmdlfem4x50.c @@ -166,7 +166,7 @@ static int em4x50_load_file(const char *filename, uint8_t *data, size_t data_len static int em4x50_seteml(const uint8_t *src, uint32_t offset, uint32_t numofbytes) { // an EM4x50 is 136 bytes, so the whole tag always fits in a single frame - if ((offset + numofbytes) > (PM3_CMD_DATA_SIZE - sizeof(em4x50_eset_t))) { + if ((offset + numofbytes) > (pm3_max_cmd_data_size() - sizeof(em4x50_eset_t))) { PrintAndLogEx(FAILED, "%u bytes at offset %u doesn't fit in one frame", numofbytes, offset); return PM3_EOUTOFBOUND; } diff --git a/client/src/mifarehost.c b/client/src/mifarehost.c new file mode 100644 index 000000000..065c5e504 --- /dev/null +++ b/client/src/mifarehost.c @@ -0,0 +1,2196 @@ +//----------------------------------------------------------------------------- +// Borrowed initially from https://nethemba.com/tag/darkside-attack/ +// Copyright (C) mifare@nethemba.com, 2010 +// Copyright (C) Proxmark3 contributors. See AUTHORS.md for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// See LICENSE.txt for the text of the license. +//----------------------------------------------------------------------------- +// mifare commands +//----------------------------------------------------------------------------- +#include "mifarehost.h" + +#include +#include +#include +#include + +#include "comms.h" +#include "commonutil.h" +#include "mifare4.h" +#include "ui.h" // PrintAndLog... +#include "crapto1/crapto1.h" +#include "crc16.h" +#include "protocols.h" +#include "mfkey.h" +#include "util_posix.h" // msclock +#include "cmdparser.h" // detection of flash capabilities +#include "cmdflashmemspiffs.h" // upload to flash mem +#include "mifaredefault.h" // default keys +#include "protocol_vigik.h" // VIGIK struct +#include "crypto/libpcrypto.h" +#include "util.h" // xor +#include "mbedtls/sha1.h" // SHA1 +#include "cmdhf14a.h" +#include "gen4.h" +#include "parity.h" +#include "pmflash.h" +#include "preferences.h" // setDeviceDebugLevel + +int mf_dark_side(uint8_t blockno, uint8_t key_type, uint64_t *key) { + uint32_t uid = 0; + uint32_t nt = 0, nr = 0, ar = 0; + uint64_t par_list = 0, ks_list = 0; + uint64_t *keylist = NULL, *last_keylist = NULL; + bool first_run = true; + + // message + PrintAndLogEx(INFO, "Expected execution time is about " _YELLOW_("25") " seconds on average"); + PrintAndLogEx(INFO, "Press " _GREEN_("pm3 button") " to abort"); + + while (true) { + clearCommandBuffer(); + + //TODO: Not really stopping the command in time. + //flush queue + while (kbd_enter_pressed()) { + SendCommandNG(CMD_BREAK_LOOP, NULL, 0); + PrintAndLogEx(WARNING, "aborted via keyboard"); + return PM3_EOPABORTED; + } + + struct { + uint8_t first_run; + uint8_t blockno; + uint8_t key_type; + } PACKED payload; + payload.first_run = first_run; + payload.blockno = blockno; + payload.key_type = key_type; + SendCommandNG(CMD_HF_MIFARE_READER, (uint8_t *)&payload, sizeof(payload)); + + PrintAndLogEx(NORMAL, ""); + PrintAndLogEx(INFO, "Running darkside " NOLF); + + // wait cycle + while (true) { + PrintAndLogEx(NORMAL, "." NOLF); + + if (IsCommunicationThreadDead()) return PM3_EIO; + + //TODO: Not really stopping the command in time. + if (kbd_enter_pressed()) { + SendCommandNG(CMD_BREAK_LOOP, NULL, 0); + PrintAndLogEx(WARNING, "\naborted via keyboard"); + return PM3_EOPABORTED; + } + + PacketResponseNG resp; + if (WaitForResponseTimeout(CMD_HF_MIFARE_READER, &resp, 2000)) { + + struct p { + int32_t isOK; + uint8_t cuid[4]; + uint8_t nt[4]; + uint8_t par_list[8]; + uint8_t ks_list[8]; + uint8_t nr[4]; + uint8_t ar[4]; + } PACKED; + + struct p *package = (struct p *)resp.data.asBytes; + + if (resp.status != PM3_SUCCESS) { + PrintAndLogEx(NORMAL, ""); + + switch (package->isOK) { + case 2: + PrintAndLogEx(FAILED, "Card is not vulnerable to Darkside attack (doesn't send NACK on authentication requests)."); + break; + case 3: + PrintAndLogEx(FAILED, "Card is not vulnerable to Darkside attack (its random number generator is not predictable)."); + break; + case 4: + PrintAndLogEx(FAILED, "Card is not vulnerable to Darkside attack (its random number generator seems to be based on the wellknown"); + PrintAndLogEx(FAILED, "generating polynomial with 16 effective bits only, but shows unexpected behaviour."); + break; + case 5: + PrintAndLogEx(WARNING, "Button pressed. aborted"); + break; + case 6: + *key = 0101; + return PM3_SUCCESS; + default: + PrintAndLogEx(FAILED, "Unknown error. Darkside attack failed."); + break; + } + + return resp.status; + } + + uid = (uint32_t)bytes_to_num(package->cuid, sizeof(package->cuid)); + nt = (uint32_t)bytes_to_num(package->nt, sizeof(package->nr)); + par_list = bytes_to_num(package->par_list, sizeof(package->par_list)); + ks_list = bytes_to_num(package->ks_list, sizeof(package->ks_list)); + nr = (uint32_t)bytes_to_num(package->nr, 4); + ar = (uint32_t)bytes_to_num(package->ar, 4); + break; + } + } + PrintAndLogEx(NORMAL, ""); + + if (par_list == 0 && first_run == true) { + PrintAndLogEx(SUCCESS, "Parity is all zero. Most likely this card sends NACK on every authentication."); + } + first_run = false; + + uint32_t keycount = nonce2key(uid, nt, nr, ar, par_list, ks_list, &keylist); + + if (keycount == 0) { + PrintAndLogEx(FAILED, "Key not found (lfsr_common_prefix list is null). Nt = %08x", nt); + PrintAndLogEx(FAILED, "This is expected to happen in 25%% of all cases."); + PrintAndLogEx(FAILED, "Trying again with a different reader nonce..."); + continue; + } + + // only parity zero attack + if (par_list == 0) { + qsort(keylist, keycount, sizeof(*keylist), compare_uint64); + keycount = intersection(last_keylist, keylist); + if (keycount == 0) { + free(last_keylist); + last_keylist = keylist; + PrintAndLogEx(FAILED, "No candidates found, trying again"); + continue; + } + } + + PrintAndLogEx(SUCCESS, "found " _YELLOW_("%u") " candidate key%s", keycount, (keycount > 1) ? "s" : ""); + + *key = UINT64_C(-1); + uint8_t keyBlock[PM3_CMD_DATA_SIZE]; + uint32_t max_keys = KEYS_IN_BLOCK; + for (uint32_t i = 0; i < keycount; i += max_keys) { + + uint8_t size = keycount - i > max_keys ? max_keys : keycount - i; + register uint8_t j; + for (j = 0; j < size; j++) { + if (par_list == 0) { + num_to_bytes(last_keylist[i * max_keys + j], MIFARE_KEY_SIZE, keyBlock + (j * MIFARE_KEY_SIZE)); + } else { + num_to_bytes(keylist[i * max_keys + j], MIFARE_KEY_SIZE, keyBlock + (j * MIFARE_KEY_SIZE)); + } + } + + if (mf_check_keys(blockno, key_type - 0x60, false, size, keyBlock, key) == PM3_SUCCESS) { + break; + } + } + + if (*key != UINT64_C(-1)) { + break; + } else { + PrintAndLogEx(FAILED, "All key candidates failed. Restarting darkside"); + free(last_keylist); + last_keylist = keylist; + first_run = true; + } + } + free(last_keylist); + free(keylist); + return PM3_SUCCESS; +} + +int mf_check_keys(uint8_t blockNo, uint8_t keyType, bool clear_trace, uint8_t keycnt, uint8_t *keyBlock, uint64_t *key) { + + if (key) { + *key = -1; + } + + + uint8_t data[PM3_CMD_DATA_SIZE] = {0}; + data[0] = keyType; + data[1] = blockNo; + data[2] = clear_trace; + data[3] = 0; + data[4] = keycnt; + memcpy(data + 5, keyBlock, MIFARE_KEY_SIZE * keycnt); + + clearCommandBuffer(); + SendCommandNG(CMD_HF_MIFARE_CHKKEYS, data, (5 + MIFARE_KEY_SIZE * keycnt)); + PacketResponseNG resp; + if (WaitForResponseTimeout(CMD_HF_MIFARE_CHKKEYS, &resp, 2500) == false) { + return PM3_ETIMEOUT; + } + + if (resp.status != PM3_SUCCESS) { + return resp.status; + } + + struct kr { + uint8_t key[MIFARE_KEY_SIZE]; + bool found; + } PACKED; + struct kr *keyresult = (struct kr *)&resp.data.asBytes; + if (keyresult->found == false) { + return PM3_ESOFT; + } + + if (key) { + *key = bytes_to_num(keyresult->key, sizeof(keyresult->key)); + } + return PM3_SUCCESS; +} + +// Sends chunks of keys to device. +// 0 == ok all keys found +// 1 == +// 2 == Time-out, aborting +int mf_check_keys_fast_ex(uint8_t sectorsCnt, uint8_t firstChunk, uint8_t lastChunk, uint8_t strategy, + uint32_t size, uint8_t *keyBlock, sector_t *e_sector, bool use_flashmemory, + bool verbose, bool quiet, uint16_t singleSectorParams) { + + uint64_t t2 = msclock(); + + if (size > MFC_CHKKEYS_FAST_MAX_KEYS) { + PrintAndLogEx(WARNING, "chunk of %u keys is too large for one frame, max %zu", size, (size_t)MFC_CHKKEYS_FAST_MAX_KEYS); + return PM3_EOUTOFBOUND; + } + + uint8_t buf[PM3_CMD_DATA_SIZE] = {0}; + mf_chkkeys_fast_t *payload = (mf_chkkeys_fast_t *)buf; + payload->sectorcnt = sectorsCnt; + payload->first_chunk = firstChunk; + payload->last_chunk = lastChunk; + payload->strategy = strategy; + payload->use_flashmemory = use_flashmemory; + payload->key_count = size; + payload->singlesector_params = singleSectorParams; + memcpy(payload->keys, keyBlock, MIFARE_KEY_SIZE * size); + + // send keychunk + clearCommandBuffer(); + SendCommandNG(CMD_HF_MIFARE_CHKKEYS_FAST, buf, sizeof(mf_chkkeys_fast_t) + (MIFARE_KEY_SIZE * size)); + + PacketResponseNG resp; + + uint32_t timeout = 0; + while (WaitForResponseTimeout(CMD_HF_MIFARE_CHKKEYS_FAST, &resp, 2000) == false) { + + while (kbd_enter_pressed()) { + SendCommandNG(CMD_BREAK_LOOP, NULL, 0); + PrintAndLogEx(INFO, "aborted via keyboard!"); + return PM3_EOPABORTED; + } + + if (quiet == false) { + PrintAndLogEx((timeout) ? NORMAL : INFO, "." NOLF); + fflush(stdout); + } + + timeout++; + + // max timeout for one chunk of 85keys, 60*3sec = 180seconds + // s70 with 40*2 keys to check, 80*85 = 6800 auth. + // takes about 97s, still some margin before abort + // timeout = 180 => ~360s @ Mifare Classic 1k @ ~2300 keys in dict + // ~2300 keys @ Mifare Classic 1k => ~620s + if (timeout > 60 * 12) { + PrintAndLogEx(WARNING, "\nNo response from Proxmark3. Aborting..."); + return PM3_ETIMEOUT; + } + } + t2 = msclock() - t2; + + if (timeout && (quiet == false)) { + PrintAndLogEx(NORMAL, ""); + } + // time to convert the returned data. + uint8_t curr_keys = (uint8_t)resp.status; + + if ((singleSectorParams >> 15) & 1) { + + if (curr_keys) { + uint8_t block_no = singleSectorParams & 0xFF; + uint8_t key_type = (singleSectorParams >> 8) & 1; + uint8_t sector_no = mfSectorNum(block_no); + if (sector_no < sectorsCnt) { + e_sector[sector_no].Key[key_type] = bytes_to_num(resp.data.asBytes, MIFARE_KEY_SIZE); + e_sector[sector_no].foundKey[key_type] = true; + } + + if (quiet == false) { + PrintAndLogEx(NORMAL, ""); + PrintAndLogEx(SUCCESS, "Target block " _GREEN_("%4u") " key type " _GREEN_("%c") " -- found valid key [ " _GREEN_("%s") " ]", + block_no, + key_type ? 'B' : 'A', + sprint_hex_inrow(resp.data.asBytes, MIFARE_KEY_SIZE) + ); + } + + return PM3_SUCCESS; + } + } + + if (verbose) { + PrintAndLogEx(INFO, "Chunk %.1fs | found %u/%u keys (%u)", (float)(t2 / 1000.0), curr_keys, (sectorsCnt << 1), size); + } + + // all keys? + if (curr_keys == sectorsCnt * 2 || lastChunk) { + + // success array. each byte is status of key + uint8_t arr[80]; + uint64_t foo = 0; + uint16_t bar = 0; + foo = bytes_to_num(resp.data.asBytes + 480, 8); + bar = (resp.data.asBytes[489] << 8 | resp.data.asBytes[488]); + + for (uint8_t i = 0; i < 64; i++) { + arr[i] = (foo >> i) & 0x1; + } + + for (uint8_t i = 0; i < 16; i++) { + arr[i + 64] = (bar >> i) & 0x1; + } + + // initialize storage for found keys + icesector_t *tmp = calloc(sectorsCnt, sizeof(icesector_t)); + if (tmp == NULL) { + PrintAndLogEx(WARNING, "Failed to allocate memory"); + return PM3_EMALLOC; + } + + memcpy(tmp, resp.data.asBytes, sectorsCnt * sizeof(icesector_t)); + + for (int i = 0; i < sectorsCnt; i++) { + // key A + if (!e_sector[i].foundKey[0]) { + e_sector[i].Key[0] = bytes_to_num(tmp[i].keyA, MIFARE_KEY_SIZE); + e_sector[i].foundKey[0] = arr[(i * 2) ]; + } + // key B + if (!e_sector[i].foundKey[1]) { + e_sector[i].Key[1] = bytes_to_num(tmp[i].keyB, MIFARE_KEY_SIZE); + e_sector[i].foundKey[1] = arr[(i * 2) + 1 ]; + } + } + free(tmp); + + // if all keys where found + if (curr_keys == sectorsCnt * 2) { + return PM3_SUCCESS; + } + + // if some keys was found + if (curr_keys > 0) { + return PM3_EPARTIAL; + } + + if (lastChunk) { + return PM3_ESOFT; + } + } + return PM3_ESOFT; +} + +int mf_check_keys_fast(uint8_t sectorsCnt, uint8_t firstChunk, uint8_t lastChunk, uint8_t strategy + , uint32_t size, uint8_t *keyBlock, sector_t *e_sector, bool use_flashmemory + , bool verbose) { + return mf_check_keys_fast_ex(sectorsCnt, firstChunk, lastChunk, strategy, size, keyBlock, e_sector, use_flashmemory, verbose, false, 0); +} + +// Trigger device to use a binary file on flash mem as keylist for mfCheckKeys. +// As of now, 255 keys possible in the file +// 6 * 255 = 1500 bytes +int mf_check_keys_file(uint8_t *destfn, uint64_t *key) { + + if (key) { + *key = -1; + } + + struct { + uint8_t filename[32]; + } PACKED payload_file; + + memcpy(payload_file.filename, destfn, sizeof(payload_file.filename) - 1); + + clearCommandBuffer(); + PacketResponseNG resp; + clearCommandBuffer(); + SendCommandNG(CMD_HF_MIFARE_CHKKEYS_FILE, (uint8_t *)&payload_file, sizeof(payload_file)); + + uint8_t retry = 10; + + while (WaitForResponseTimeout(CMD_HF_MIFARE_CHKKEYS, &resp, 2000) == false) { + + //flush queue + while (kbd_enter_pressed()) { + SendCommandNG(CMD_BREAK_LOOP, NULL, 0); + return PM3_EOPABORTED; + } + + retry--; + if (retry == 0) { + PrintAndLogEx(WARNING, "Chk keys file, command execution time out"); + SendCommandNG(CMD_BREAK_LOOP, NULL, 0); + return PM3_ETIMEOUT; + } + } + + if (resp.status != PM3_SUCCESS) { + return resp.status; + } + + struct kr { + uint8_t key[MIFARE_KEY_SIZE]; + bool found; + } PACKED; + struct kr *keyresult = (struct kr *)&resp.data.asBytes; + + if (keyresult->found == false) { + return PM3_ESOFT; + } + + *key = bytes_to_num(keyresult->key, sizeof(keyresult->key)); + return PM3_SUCCESS; +} + +// PM3 imp of J-Run mf_key_brute (part 2) +// ref: https://github.com/J-Run/mf_key_brute +int mf_key_brute(uint8_t blockNo, uint8_t keyType, const uint8_t *key, uint64_t *resultkey) { + + uint64_t key64; + uint8_t found = false; + uint8_t candidates[CANDIDATE_SIZE] = {0x00}; + uint8_t keyBlock[KEYBLOCK_SIZE] = {0x00}; + + memset(candidates, 0, sizeof(candidates)); + memset(keyBlock, 0, sizeof(keyBlock)); + + // Generate all possible keys for the first two unknown bytes. + for (uint16_t i = 0; i < 0xFFFF; ++i) { + uint32_t j = i * MIFARE_KEY_SIZE; + candidates[0 + j] = i >> 8; + candidates[1 + j] = i; + candidates[2 + j] = key[2]; + candidates[3 + j] = key[3]; + candidates[4 + j] = key[4]; + candidates[5 + j] = key[5]; + } + // keys per call is bounded by what THIS device accepts (negotiated at connect) + // and by the uint8_t keycnt field. keyBlock stays sized to the compile-time + // KEYS_IN_BLOCK, which is always >= this runtime value. + uint32_t per = (pm3_max_cmd_data_size() - 5) / MIFARE_KEY_SIZE; + if (per > 255) per = 255; + if (per == 0) per = 1; + uint32_t block_bytes = per * MIFARE_KEY_SIZE; + + uint32_t counter, i; + for (i = 0, counter = 1; i < CANDIDATE_SIZE; i += block_bytes, ++counter) { + + key64 = 0; + + // copy candidatekeys to test key block (last chunk may be partial) + uint32_t n = ((CANDIDATE_SIZE - i) < block_bytes) ? (CANDIDATE_SIZE - i) : block_bytes; + memcpy(keyBlock, candidates + i, n); + + // check a block of generated key candidates. + if (mf_check_keys(blockNo, keyType, true, (uint8_t)(n / MIFARE_KEY_SIZE), keyBlock, &key64) == PM3_SUCCESS) { + *resultkey = key64; + found = true; + break; + } + + // progress + if (counter % 20 == 0) { + PrintAndLogEx(SUCCESS, "tried %s.. \t %u keys", sprint_hex(candidates + i, 6), counter * KEYS_IN_BLOCK); + } + } + return found; +} + +// Compare 16 Bits out of cryptostate +inline static int Compare16Bits(const void *a, const void *b) { + if ((*(uint64_t *)b & 0x00ff000000ff0000) == (*(uint64_t *)a & 0x00ff000000ff0000)) return 0; + if ((*(uint64_t *)b & 0x00ff000000ff0000) > (*(uint64_t *)a & 0x00ff000000ff0000)) return 1; + return -1; +} + +// wrapper function for multi-threaded lfsr_recovery32 +static void +#ifdef __has_attribute +#if __has_attribute(force_align_arg_pointer) +__attribute__((force_align_arg_pointer)) +#endif +#endif +*nested_worker_thread(void *arg) { + struct Crypto1State *p1; + StateList_t *statelist = arg; + statelist->head.slhead = lfsr_recovery32(statelist->ks1, statelist->nt_enc ^ statelist->uid); + + for (p1 = statelist->head.slhead; p1->odd | p1->even; p1++) {}; + + statelist->len = p1 - statelist->head.slhead; + statelist->tail.sltail = --p1; + + qsort(statelist->head.slhead, statelist->len, sizeof(uint64_t), Compare16Bits); + + return statelist->head.slhead; +} + +int mf_nested(uint8_t blockNo, uint8_t keyType, uint8_t *key, uint8_t trgBlockNo, uint8_t trgKeyType, uint8_t *resultKey, bool calibrate) { + + uint32_t uid = 0; + StateList_t statelists[2]; + struct Crypto1State *p1, *p2, *p3, *p4; + + struct { + uint8_t block; + uint8_t keytype; + uint8_t target_block; + uint8_t target_keytype; + bool calibrate; + uint8_t key[6]; + } PACKED payload; + payload.block = blockNo; + payload.keytype = keyType; + payload.target_block = trgBlockNo; + payload.target_keytype = trgKeyType; + payload.calibrate = calibrate; + memcpy(payload.key, key, sizeof(payload.key)); + + PacketResponseNG resp; + clearCommandBuffer(); + SendCommandNG(CMD_HF_MIFARE_NESTED, (uint8_t *)&payload, sizeof(payload)); + + if (WaitForResponseTimeout(CMD_HF_MIFARE_NESTED, &resp, 2000) == false) { + SendCommandNG(CMD_BREAK_LOOP, NULL, 0); + return PM3_ETIMEOUT; + } + + struct p { + int16_t isOK; + uint8_t block; + uint8_t keytype; + uint8_t cuid[4]; + uint8_t nt_a[4]; + uint8_t ks_a[4]; + uint8_t nt_b[4]; + uint8_t ks_b[4]; + } PACKED; + struct p *package = (struct p *)resp.data.asBytes; + + // error during nested on device side + if (package->isOK != PM3_SUCCESS) { + return package->isOK; + } + + memcpy(&uid, package->cuid, sizeof(package->cuid)); + + for (uint8_t i = 0; i < 2; i++) { + statelists[i].blockNo = package->block; + statelists[i].keyType = package->keytype; + statelists[i].uid = uid; + } + + memcpy(&statelists[0].nt_enc, package->nt_a, sizeof(package->nt_a)); + memcpy(&statelists[0].ks1, package->ks_a, sizeof(package->ks_a)); + + memcpy(&statelists[1].nt_enc, package->nt_b, sizeof(package->nt_b)); + memcpy(&statelists[1].ks1, package->ks_b, sizeof(package->ks_b)); + + // calc keys + pthread_t thread_id[2]; + + // create and run worker threads + for (uint8_t i = 0; i < 2; i++) { + pthread_create(thread_id + i, NULL, nested_worker_thread, &statelists[i]); + } + + // wait for threads to terminate: + for (uint8_t i = 0; i < 2; i++) { + pthread_join(thread_id[i], (void *)&statelists[i].head.slhead); + } + + // the first 16 Bits of the cryptostate already contain part of our key. + // Create the intersection of the two lists based on these 16 Bits and + // roll back the cryptostate + p1 = p3 = statelists[0].head.slhead; + p2 = p4 = statelists[1].head.slhead; + + while (p1 <= statelists[0].tail.sltail && p2 <= statelists[1].tail.sltail) { + + if (Compare16Bits(p1, p2) == 0) { + + struct Crypto1State savestate; + + savestate = *p1; + while (Compare16Bits(p1, &savestate) == 0 && p1 <= statelists[0].tail.sltail) { + *p3 = *p1; + lfsr_rollback_word(p3, statelists[0].nt_enc ^ statelists[0].uid, 0); + p3++; + p1++; + } + + savestate = *p2; + while (Compare16Bits(p2, &savestate) == 0 && p2 <= statelists[1].tail.sltail) { + *p4 = *p2; + lfsr_rollback_word(p4, statelists[1].nt_enc ^ statelists[1].uid, 0); + p4++; + p2++; + } + + } else { + while (Compare16Bits(p1, p2) == -1) p1++; + while (Compare16Bits(p1, p2) == 1) p2++; + } + } + + p3->odd = -1; + p3->even = -1; + p4->odd = -1; + p4->even = -1; + statelists[0].len = p3 - statelists[0].head.slhead; + statelists[1].len = p4 - statelists[1].head.slhead; + statelists[0].tail.sltail = --p3; + statelists[1].tail.sltail = --p4; + + // the statelists now contain possible keys. The key we are searching for must be in the + // intersection of both lists + qsort(statelists[0].head.keyhead, statelists[0].len, sizeof(uint64_t), compare_uint64); + qsort(statelists[1].head.keyhead, statelists[1].len, sizeof(uint64_t), compare_uint64); + // Create the intersection + statelists[0].len = intersection(statelists[0].head.keyhead, statelists[1].head.keyhead); + + bool looped = false; + + //statelists[0].tail.keytail = --p7; + uint32_t keycnt = statelists[0].len; + if (keycnt == 0) { + goto out; + } + + PrintAndLogEx(SUCCESS, "Found " _YELLOW_("%u") " key candidate%c", keycnt, (keycnt > 1) ? 's' : ' '); + + memset(resultKey, 0, MIFARE_KEY_SIZE); + uint64_t key64 = -1; + + // The list may still contain several key candidates. Test each of them with mfCheckKeys + uint32_t max_keys = keycnt > KEYS_IN_BLOCK ? KEYS_IN_BLOCK : keycnt; + uint8_t keyBlock[PM3_CMD_DATA_SIZE] = {0x00}; + + for (uint32_t i = 0; i < keycnt; i += max_keys) { + + uint64_t start_time = msclock(); + + uint8_t size = keycnt - i > max_keys ? max_keys : keycnt - i; + + register uint8_t j; + for (j = 0; j < size; j++) { + crypto1_get_lfsr(statelists[0].head.slhead + i, &key64); + num_to_bytes(key64, MIFARE_KEY_SIZE, keyBlock + j * MIFARE_KEY_SIZE); + } + + if (mf_check_keys(statelists[0].blockNo, statelists[0].keyType, false, size, keyBlock, &key64) == PM3_SUCCESS) { + + if (looped) { + PrintAndLogEx(NORMAL, ""); + } + + free(statelists[0].head.slhead); + free(statelists[1].head.slhead); + num_to_bytes(key64, MIFARE_KEY_SIZE, resultKey); + + if (package->keytype < 2) { + PrintAndLogEx(SUCCESS, "Target block " _GREEN_("%4u") " key type " _GREEN_("%c") " -- found valid key [ " _GREEN_("%s") " ]", + package->block, + package->keytype ? 'B' : 'A', + sprint_hex_inrow(resultKey, MIFARE_KEY_SIZE) + ); + } else { + PrintAndLogEx(SUCCESS, "Target block " _GREEN_("%4u") " key type " _GREEN_("%02x") " -- found valid key [ " _GREEN_("%s") " ]", + package->block, + MIFARE_AUTH_KEYA + package->keytype, + sprint_hex_inrow(resultKey, MIFARE_KEY_SIZE) + ); + } + return PM3_SUCCESS; + } + + float bruteforce_per_second = (float)(i + max_keys) / ((msclock() - start_time) / 1000.0); + PrintAndLogEx(INPLACE, "%6d/%u keys | %5.1f keys/sec | worst case %6.1f seconds remaining", i, keycnt, bruteforce_per_second, (keycnt - i) / bruteforce_per_second); + looped = true; + } + +out: + + if (looped) { + PrintAndLogEx(NORMAL, ""); + } + + if (package->keytype < 2) { + PrintAndLogEx(SUCCESS, "Target block " _YELLOW_("%4u") " key type " _YELLOW_("%c"), + package->block, + package->keytype ? 'B' : 'A' + ); + } else { + PrintAndLogEx(SUCCESS, "Target block " _YELLOW_("%4u") " key type " _YELLOW_("%02x"), + package->block, + MIFARE_AUTH_KEYA + package->keytype + ); + } + free(statelists[0].head.slhead); + free(statelists[1].head.slhead); + return PM3_ESOFT; +} + +int mf_static_nested(uint8_t blockNo, uint8_t keyType, uint8_t *key, uint8_t trgBlockNo, uint8_t trgKeyType, uint8_t *resultKey, bool forceDetectDist) { + + uint32_t uid = 0; + StateList_t statelists[2]; + struct Crypto1State *p1, *p2, *p3, *p4; + + struct { + uint8_t block; + uint8_t keytype; + uint8_t target_block; + uint8_t target_keytype; + uint8_t force_detect_dist; + uint8_t key[6]; + } PACKED payload; + payload.block = blockNo; + payload.keytype = keyType; + payload.target_block = trgBlockNo; + payload.target_keytype = trgKeyType; + payload.force_detect_dist = forceDetectDist; + memcpy(payload.key, key, sizeof(payload.key)); + + PacketResponseNG resp; + clearCommandBuffer(); + SendCommandNG(CMD_HF_MIFARE_STATIC_NESTED, (uint8_t *)&payload, sizeof(payload)); + + if (WaitForResponseTimeout(CMD_HF_MIFARE_STATIC_NESTED, &resp, 2000) == false) + return PM3_ETIMEOUT; + + if (resp.status != PM3_SUCCESS) + return resp.status; + + struct p { + uint8_t block; + uint8_t keytype; + uint8_t cuid[4]; + uint8_t nt_a[4]; + uint8_t ks_a[4]; + uint8_t nt_b[4]; + uint8_t ks_b[4]; + } PACKED; + struct p *package = (struct p *)resp.data.asBytes; + + memcpy(&uid, package->cuid, sizeof(package->cuid)); + + for (uint8_t i = 0; i < 2; i++) { + statelists[i].blockNo = package->block; + statelists[i].keyType = package->keytype; + statelists[i].uid = uid; + } + + memcpy(&statelists[0].nt_enc, package->nt_a, sizeof(package->nt_a)); + memcpy(&statelists[0].ks1, package->ks_a, sizeof(package->ks_a)); + + memcpy(&statelists[1].nt_enc, package->nt_b, sizeof(package->nt_b)); + memcpy(&statelists[1].ks1, package->ks_b, sizeof(package->ks_b)); + + // calc keys + pthread_t thread_id[2]; + + // create and run worker threads + for (uint8_t i = 0; i < 2; i++) + pthread_create(thread_id + i, NULL, nested_worker_thread, &statelists[i]); + + // wait for threads to terminate: + for (uint8_t i = 0; i < 2; i++) + pthread_join(thread_id[i], (void *)&statelists[i].head.slhead); + + // the first 16 Bits of the cryptostate already contain part of our key. + // Create the intersection of the two lists based on these 16 Bits and + // roll back the cryptostate + p1 = p3 = statelists[0].head.slhead; + p2 = p4 = statelists[1].head.slhead; + + while (p1 <= statelists[0].tail.sltail && p2 <= statelists[1].tail.sltail) { + if (Compare16Bits(p1, p2) == 0) { + + struct Crypto1State savestate; + savestate = *p1; + while (Compare16Bits(p1, &savestate) == 0 && p1 <= statelists[0].tail.sltail) { + *p3 = *p1; + lfsr_rollback_word(p3, statelists[0].nt_enc ^ statelists[0].uid, 0); + p3++; + p1++; + } + savestate = *p2; + while (Compare16Bits(p2, &savestate) == 0 && p2 <= statelists[1].tail.sltail) { + *p4 = *p2; + lfsr_rollback_word(p4, statelists[1].nt_enc ^ statelists[1].uid, 0); + p4++; + p2++; + } + } else { + while (Compare16Bits(p1, p2) == -1) p1++; + while (Compare16Bits(p1, p2) == 1) p2++; + } + } + + p3->odd = -1; + p3->even = -1; + p4->odd = -1; + p4->even = -1; + statelists[0].len = p3 - statelists[0].head.slhead; + statelists[1].len = p4 - statelists[1].head.slhead; + statelists[0].tail.sltail = --p3; + statelists[1].tail.sltail = --p4; + + // the statelists now contain possible keys. The key we are searching for must be in the + // intersection of both lists + qsort(statelists[0].head.keyhead, statelists[0].len, sizeof(uint64_t), compare_uint64); + qsort(statelists[1].head.keyhead, statelists[1].len, sizeof(uint64_t), compare_uint64); + // Create the intersection + statelists[0].len = intersection(statelists[0].head.keyhead, statelists[1].head.keyhead); + + uint32_t keycnt = statelists[0].len; + if (keycnt == 0) { + goto out; + } + + PrintAndLogEx(SUCCESS, "Found " _YELLOW_("%u") " key candidates", keycnt); + + memset(resultKey, 0, MIFARE_KEY_SIZE); + + // The list may still contain several key candidates. Test each of them with mfCheckKeys + uint32_t maxkeysinblock = IfPm3Flash() ? 1000 : KEYS_IN_BLOCK; + uint32_t max_keys_chunk = keycnt > maxkeysinblock ? maxkeysinblock : keycnt; + + uint8_t *mem = NULL; + uint8_t *p_keyblock = NULL; + + // if RDV4 and more than 10 candidate keys + if (IfPm3Flash() && keycnt > 70) { + + // used for mfCheckKeys_file, which needs a header + mem = calloc((maxkeysinblock * MIFARE_KEY_SIZE) + 5, sizeof(uint8_t)); + if (mem == NULL) { + PrintAndLogEx(WARNING, "Failed to allocate memory"); + free(statelists[0].head.slhead); + return PM3_EMALLOC; + } + + mem[0] = statelists[0].keyType; + mem[1] = statelists[0].blockNo; + mem[2] = 1; + mem[3] = ((max_keys_chunk >> 8) & 0xFF); + mem[4] = (max_keys_chunk & 0xFF); + + p_keyblock = mem + 5; + } else { + + // used for mfCheckKeys, which adds its own header. + mem = calloc((maxkeysinblock * MIFARE_KEY_SIZE), sizeof(uint8_t)); + if (mem == NULL) { + PrintAndLogEx(WARNING, "Failed to allocate memory"); + free(statelists[0].head.slhead); + return PM3_EMALLOC; + } + p_keyblock = mem; + } + + uint8_t fn[32] = "static_nested_000.bin"; + + uint64_t start_time = msclock(); + for (uint32_t i = 0; i < keycnt; i += max_keys_chunk) { + + //flush queue + while (kbd_enter_pressed()) { + SendCommandNG(CMD_BREAK_LOOP, NULL, 0); + PrintAndLogEx(NORMAL, ""); + free(mem); + return PM3_EOPABORTED; + } + + int res = 0; + uint64_t key64 = 0; + uint32_t chunk = keycnt - i > max_keys_chunk ? max_keys_chunk : keycnt - i; + + // copy x keys to device. + for (uint32_t j = 0; j < chunk; j++) { + crypto1_get_lfsr(statelists[0].head.slhead + i + j, &key64); + num_to_bytes(key64, MIFARE_KEY_SIZE, p_keyblock + j * MIFARE_KEY_SIZE); + } + + // check a block of generated key candidates. + if (IfPm3Flash() && keycnt > 70) { + + mem[3] = ((chunk >> 8) & 0xFF); + mem[4] = (chunk & 0xFF); + + // upload to flash. + res = flashmem_spiffs_load((char *)fn, mem, 5 + (chunk * MIFARE_KEY_SIZE)); + if (res != PM3_SUCCESS) { + PrintAndLogEx(WARNING, "\nSPIFFS upload failed"); + free(mem); + return res; + } + res = mf_check_keys_file(fn, &key64); + } else { + res = mf_check_keys(statelists[0].blockNo, statelists[0].keyType, true, chunk, mem, &key64); + } + + if (res == PM3_SUCCESS) { + p_keyblock = NULL; + free(statelists[0].head.slhead); + free(mem); + + num_to_bytes(key64, MIFARE_KEY_SIZE, resultKey); + + if (IfPm3Flash() && keycnt > 70) { + PrintAndLogEx(NORMAL, ""); + } + + PrintAndLogEx(SUCCESS, "target block " _GREEN_("%4u") " key type " _GREEN_("%c") " -- found valid key [ " _GREEN_("%s") " ]", + package->block, + package->keytype ? 'B' : 'A', + sprint_hex_inrow(resultKey, MIFARE_KEY_SIZE) + ); + return PM3_SUCCESS; + } else if (res == PM3_ETIMEOUT || res == PM3_EOPABORTED) { + PrintAndLogEx(NORMAL, ""); + free(mem); + return res; + } + + float bruteforce_per_second = (float)(i + max_keys_chunk) / ((msclock() - start_time) / 1000.0); + PrintAndLogEx(INPLACE, "%6u/%u keys | %5.1f keys/sec | worst case %6.1f seconds", i, keycnt, bruteforce_per_second, (keycnt - i) / bruteforce_per_second); + } + + p_keyblock = NULL; + free(mem); + +out: + + PrintAndLogEx(SUCCESS, "\nTarget block %4u key type %c", + package->block, + package->keytype ? 'B' : 'A' + ); + + free(statelists[0].head.slhead); + free(statelists[1].head.slhead); + return PM3_ESOFT; +} + +// MIFARE +int mf_read_sector(uint8_t sectorNo, uint8_t keyType, const uint8_t *key, uint8_t *data) { + + mf_readsector_t payload = { + .sectorno = sectorNo, + .keytype = keyType, + }; + memcpy(payload.key, key, MIFARE_KEY_SIZE); + + clearCommandBuffer(); + SendCommandNG(CMD_HF_MIFARE_READSC, (uint8_t *)&payload, sizeof(payload)); + PacketResponseNG resp; + if (WaitForResponseTimeout(CMD_HF_MIFARE_READSC, &resp, 1500)) { + + if (resp.status == PM3_SUCCESS) { + size_t need = mfNumBlocksPerSector(sectorNo) * MFBLOCK_SIZE; + if (resp.length < need) { + return PM3_EUNDEF; + } + memcpy(data, resp.data.asBytes, need); + return PM3_SUCCESS; + } else { + return PM3_EUNDEF; + } + } else { + PrintAndLogEx(DEBUG, "command execution time out"); + return PM3_ETIMEOUT; + } + return PM3_SUCCESS; +} + +int mf_read_block(uint8_t blockNo, uint8_t keyType, const uint8_t *key, uint8_t *data) { + mf_readblock_t payload = { + .blockno = blockNo, + .keytype = keyType + }; + memcpy(payload.key, key, sizeof(payload.key)); + + clearCommandBuffer(); + SendCommandNG(CMD_HF_MIFARE_READBL, (uint8_t *)&payload, sizeof(mf_readblock_t)); + PacketResponseNG resp; + if (WaitForResponseTimeout(CMD_HF_MIFARE_READBL, &resp, 1500)) { + memcpy(data, resp.data.asBytes, MFBLOCK_SIZE); + + if (resp.status != PM3_SUCCESS) { + PrintAndLogEx(DEBUG, "failed reading block"); + return PM3_ESOFT; + } + } else { + PrintAndLogEx(DEBUG, "command execution time out"); + return PM3_ETIMEOUT; + } + return PM3_SUCCESS; +} + +int mf_write_block(uint8_t blockno, uint8_t keyType, const uint8_t *key, const uint8_t *block) { + + uint8_t data[26]; + memcpy(data, key, MIFARE_KEY_SIZE); + memcpy(data + 10, block, MFBLOCK_SIZE); + + clearCommandBuffer(); + mf_writeblock_ex_t payload = { + .wakeup = MF_WAKE_WUPA, + .auth_cmd = MIFARE_AUTH_KEYA + (keyType & 0xF), + .write_cmd = ISO14443A_CMD_WRITEBLOCK, + .block_no = blockno, + }; + memcpy(payload.key, data, MIFARE_KEY_SIZE); + memcpy(payload.block_data, data + 10, MFBLOCK_SIZE); + + SendCommandNG(CMD_HF_MIFARE_WRITEBL_EX, (uint8_t *)&payload, sizeof(payload)); + + PacketResponseNG resp; + if (WaitForResponseTimeout(CMD_HF_MIFARE_WRITEBL_EX, &resp, 1500) == false) { + PrintAndLogEx(FAILED, "mfWriteBlock execution time out"); + return PM3_ETIMEOUT; + } + int res = PM3_SUCCESS; + if (resp.status != PM3_SUCCESS) { + res = PM3_EFAILED; + } + return res; +} + +int mf_write_sector(uint8_t sectorNo, uint8_t keyType, const uint8_t *key, uint8_t *sector) { + int res; + for (int i = 0; i < mfNumBlocksPerSector(sectorNo); i++) { + res = mf_write_block((mfFirstBlockOfSector(sectorNo)) + i, keyType, key, sector + (i * MFBLOCK_SIZE)); + if (res != PM3_SUCCESS) { + return (i == 0) ? PM3_EFAILED : PM3_EPARTIAL; + } + } + return PM3_SUCCESS; +} + +// EMULATOR +int mf_eml_get_mem(uint8_t *data, int blockNum, int blocksCount) { + return mf_eml_get_mem_xt(data, blockNum, blocksCount, MFBLOCK_SIZE); +} + +int mf_eml_get_mem_xt(uint8_t *data, int blockNum, int blocksCount, int blockBtWidth) { + + size_t size = ((size_t) blocksCount) * blockBtWidth; + if (size > pm3_max_cmd_data_size()) { + return PM3_ESOFT; + } + + struct { + uint16_t blockno; + uint8_t blockcnt; + uint8_t blockwidth; + } PACKED payload; + + payload.blockno = blockNum; + payload.blockcnt = blocksCount; + payload.blockwidth = blockBtWidth; + + clearCommandBuffer(); + SendCommandNG(CMD_HF_MIFARE_EML_MEMGET, (uint8_t *)&payload, sizeof(payload)); + + PacketResponseNG resp; + if (WaitForResponseTimeout(CMD_HF_MIFARE_EML_MEMGET, &resp, 1500) == false) { + PrintAndLogEx(WARNING, "command execution time out"); + return PM3_ETIMEOUT; + } + + if (resp.status == PM3_SUCCESS) { + memcpy(data, resp.data.asBytes, size); + } + + return resp.status; +} + +int mf_elm_set_mem(uint8_t *data, int blockNum, int blocksCount) { + return mf_eml_set_mem_xt(data, blockNum, blocksCount, MFBLOCK_SIZE); +} + +int mf_eml_set_mem_xt(uint8_t *data, int blockNum, int blocksCount, int blockBtWidth) { + + struct p { + uint16_t blockno; + uint8_t blockcnt; + uint8_t blockwidth; + uint8_t data[]; + } PACKED; + + size_t size = ((size_t) blocksCount) * blockBtWidth; + if (size > (pm3_max_cmd_data_size() - sizeof(struct p))) { + return PM3_EINVARG; + } + + size_t paylen = sizeof(struct p) + size; + struct p *payload = calloc(1, paylen); + if (payload == NULL) { + PrintAndLogEx(WARNING, "Failed to allocate memory"); + return PM3_EMALLOC; + } + + payload->blockno = blockNum; + payload->blockcnt = blocksCount; + payload->blockwidth = blockBtWidth; + memcpy(payload->data, data, size); + + clearCommandBuffer(); + SendCommandNG(CMD_HF_MIFARE_EML_MEMSET, (uint8_t *)payload, paylen); + free(payload); + return PM3_SUCCESS; +} + +// "MAGIC" CARD +int mf_chinese_set_uid(uint8_t *uid, uint8_t uidlen, const uint8_t *atqa, const uint8_t *sak, uint8_t *old_uid, uint8_t *verifed_uid, uint8_t wipecard, uint8_t gdm) { + + uint8_t params = MAGIC_SINGLE | (gdm ? MAGIC_GDM_ALT_WUPC : MAGIC_WUPC); + uint8_t block0[MFBLOCK_SIZE]; + memset(block0, 0x00, sizeof(block0)); + + int res = mf_chinese_get_block(0, block0, params); + if (res == 0) { + PrintAndLogEx(SUCCESS, "old block 0... %s", sprint_hex_inrow(block0, sizeof(block0))); + if (old_uid) { + memcpy(old_uid, block0, uidlen); + } + } else { + PrintAndLogEx(INFO, "couldn't get old data. Will write over the last bytes of block 0"); + } + + // fill in the new values + // UID + memcpy(block0, uid, uidlen); + // Mifare UID BCC + if (uidlen == 4) { + block0[4] = block0[0] ^ block0[1] ^ block0[2] ^ block0[3]; + + // mifare classic SAK(byte 5) and ATQA(byte 6 and 7, reversed) + if (sak) + block0[5] = sak[0]; + + if (atqa) { + block0[6] = atqa[1]; + block0[7] = atqa[0]; + } + + } else if (uidlen == 7) { + block0[7] = block0[0] ^ block0[1] ^ block0[2] ^ block0[3] ^ block0[4] ^ block0[5] ^ block0[6]; + + // mifare classic SAK(byte 8) and ATQA(byte 9 and 10, reversed) + if (sak) + block0[8] = sak[0]; + + if (atqa) { + block0[9] = atqa[1]; + block0[10] = atqa[0]; + } + } + + PrintAndLogEx(SUCCESS, "new block 0... %s", sprint_hex_inrow(block0, sizeof(block0))); + + if (wipecard) { + params |= MAGIC_WIPE; + } + + res = mf_chinese_set_block(0, block0, NULL, params); + if (res == PM3_SUCCESS) { + params = MAGIC_SINGLE | (gdm ? MAGIC_GDM_ALT_WUPC : MAGIC_WUPC); + memset(block0, 0, sizeof(block0)); + res = mf_chinese_get_block(0, block0, params); + if (res == 0) { + if (verifed_uid) { + memcpy(verifed_uid, block0, uidlen); + } + } + } + return res; +} + +int mf_chinese_wipe(uint8_t *uid, const uint8_t *atqa, const uint8_t *sak, uint8_t gdm) { + uint8_t block0[MFBLOCK_SIZE] = {0x00, 0x56, 0x78, 0xBB, 0x95, 0x08, 0x04, 0x00, 0x02, 0xB2, 0x1E, 0x24, 0x23, 0x27, 0x1E, 0x1D}; + // uint8_t block0[MFBLOCK_SIZE] = {0x04, 0x03, 0x02, 0x01, 0x04, 0x08, 0x04, 0x00, 0x64, 0xB9, 0x95, 0x11, 0x4D, 0x20, 0x42, 0x09}; + uint8_t blockD[MFBLOCK_SIZE] = {0x00}; + // default transport ACL + uint8_t blockK[MFBLOCK_SIZE] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x07, 0x80, 0x69, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; + uint8_t params = MAGIC_SINGLE | (gdm ? MAGIC_GDM_ALT_WUPC : MAGIC_WUPC); + + if (uid != NULL) { + memcpy(block0, uid, 4); + block0[4] = block0[0] ^ block0[1] ^ block0[2] ^ block0[3]; + } + + if (sak != NULL) { + block0[5] = sak[0]; + } + + if (atqa != NULL) { + block0[6] = atqa[1]; + block0[7] = atqa[0]; + } + + int res; + for (int blockNo = 0; blockNo < 4 * 16; blockNo++) { + for (int retry = 0; retry < 3; retry++) { + + PrintAndLogEx(INPLACE, "wipe block %d", blockNo); + + if (blockNo == 0) { + res = mf_chinese_set_block(blockNo, block0, NULL, params); + } else { + if (mfIsSectorTrailer(blockNo)) { + res = mf_chinese_set_block(blockNo, blockK, NULL, params); + } else { + res = mf_chinese_set_block(blockNo, blockD, NULL, params); + } + } + + if (res == PM3_SUCCESS) { + break; + } + + PrintAndLogEx(WARNING, "retry block %d ...", blockNo); + } + + if (res) { + PrintAndLogEx(ERR, "error setting block %d (%d)", blockNo, res); + return res; + } + } + DropField(); + PrintAndLogEx(NORMAL, ""); + return PM3_SUCCESS; +} + +int mf_chinese_set_block(uint8_t blockNo, uint8_t *data, uint8_t *uid, uint8_t params) { + + uint8_t buf[PM3_CMD_DATA_SIZE] = {0}; + mf_chinese_blk_t *payload = (mf_chinese_blk_t *)buf; + payload->params = params; + payload->blockno = blockNo; + memcpy(payload->data, data, MFBLOCK_SIZE); + + clearCommandBuffer(); + SendCommandNG(CMD_HF_MIFARE_CSETBL, buf, sizeof(mf_chinese_blk_t) + MFBLOCK_SIZE); + PacketResponseNG resp; + if (WaitForResponseTimeout(CMD_HF_MIFARE_CSETBL, &resp, 3500) == false) { + PrintAndLogEx(NORMAL, ""); + PrintAndLogEx(WARNING, "Command execution time out"); + return PM3_ETIMEOUT; + } + + if ((uid != NULL) && (resp.status == PM3_SUCCESS) && (resp.length >= 4)) { + memcpy(uid, resp.data.asBytes, 4); + } + + if (resp.status != PM3_SUCCESS) { + + uint8_t reason = (uint8_t)resp.reason; + if (reason == 4) { + PrintAndLogEx(NORMAL, ""); + PrintAndLogEx(WARNING, "GDM magic write signature block failed"); + } else if (reason == 5) { + PrintAndLogEx(NORMAL, ""); + PrintAndLogEx(WARNING, "Write block failed"); + } + + return PM3_EUNDEF; + } + + return PM3_SUCCESS; +} + +int mf_chinese_get_block(uint8_t blockNo, uint8_t *data, uint8_t params) { + mf_chinese_blk_t payload = { + .params = params, + .blockno = blockNo, + }; + + clearCommandBuffer(); + SendCommandNG(CMD_HF_MIFARE_CGETBL, (uint8_t *)&payload, sizeof(payload)); + PacketResponseNG resp; + if (WaitForResponseTimeout(CMD_HF_MIFARE_CGETBL, &resp, 1500)) { + if (resp.status != PM3_SUCCESS || resp.length < MFBLOCK_SIZE) { + return PM3_EUNDEF; + } + memcpy(data, resp.data.asBytes, MFBLOCK_SIZE); + } else { + PrintAndLogEx(WARNING, "command execution time out"); + return PM3_ETIMEOUT; + } + return PM3_SUCCESS; +} + +int mf_chinese_gen_3_uid(uint8_t *uid, uint8_t uidlen, uint8_t *oldUid) { + uint8_t buf[PM3_CMD_DATA_SIZE] = {0}; + mf_gen3uid_t *payload = (mf_gen3uid_t *)buf; + payload->uidlen = uidlen; + memcpy(payload->uid, uid, uidlen); + + clearCommandBuffer(); + SendCommandNG(CMD_HF_MIFARE_GEN3UID, buf, sizeof(mf_gen3uid_t) + uidlen); + PacketResponseNG resp; + if (WaitForResponseTimeout(CMD_HF_MIFARE_GEN3UID, &resp, 3500)) { + if (resp.status == PM3_SUCCESS && oldUid) { + memcpy(oldUid, resp.data.asBytes, uidlen); + } + return resp.status; + } else { + PrintAndLogEx(WARNING, "command execution time out"); + return PM3_ETIMEOUT; + } +} + +int mf_chinese_gen_3_block(uint8_t *block, int blockLen, uint8_t *newBlock) { + uint8_t buf[PM3_CMD_DATA_SIZE] = {0}; + mf_gen3blk_t *payload = (mf_gen3blk_t *)buf; + payload->blocklen = blockLen; + memcpy(payload->block, block, MFBLOCK_SIZE); + + clearCommandBuffer(); + SendCommandNG(CMD_HF_MIFARE_GEN3BLK, buf, sizeof(mf_gen3blk_t) + MFBLOCK_SIZE); + PacketResponseNG resp; + if (WaitForResponseTimeout(CMD_HF_MIFARE_GEN3BLK, &resp, 3500) == false) { + PrintAndLogEx(WARNING, "command execution time out"); + return PM3_ETIMEOUT; + } + + if (resp.status == PM3_SUCCESS && newBlock) { + memcpy(newBlock, resp.data.asBytes, MFBLOCK_SIZE); + } + return resp.status; +} + +int mf_chinese_gen_3_freeze(void) { + clearCommandBuffer(); + SendCommandNG(CMD_HF_MIFARE_GEN3FREEZ, NULL, 0); + PacketResponseNG resp; + if (WaitForResponseTimeout(CMD_HF_MIFARE_GEN3FREEZ, &resp, 3500) == false) { + PrintAndLogEx(WARNING, "command execution time out"); + return PM3_ETIMEOUT; + } + return resp.status; +} + +// GDM Gen4 write block +int mf_chinese_gen_4_set_block(uint8_t blockNo, uint8_t *block, uint8_t *key) { + struct p { + uint8_t blockno; + uint8_t key[6]; + uint8_t data[MFBLOCK_SIZE]; // data to be written + } PACKED payload; + + payload.blockno = blockNo; + memcpy(payload.key, key, sizeof(payload.key)); + memcpy(payload.data, block, sizeof(payload.data)); + + clearCommandBuffer(); + SendCommandNG(CMD_HF_MIFARE_G4_GDM_WRBL, (uint8_t *)&payload, sizeof(payload)); + PacketResponseNG resp; + if (WaitForResponseTimeout(CMD_HF_MIFARE_G4_GDM_WRBL, &resp, 1500) == false) { + PrintAndLogEx(WARNING, "command execution time out"); + return PM3_ETIMEOUT; + } + return resp.status; +} + +void mf_crypto1_decrypt(struct Crypto1State *pcs, uint8_t *data, int len, bool isEncrypted) { + if (len != 1) { + for (int i = 0; i < len; i++) { + data[i] = crypto1_byte(pcs, 0x00, isEncrypted) ^ data[i]; + } + } else { + uint8_t bt = 0; + bt |= (crypto1_bit(pcs, 0, isEncrypted) ^ BIT(data[0], 0)) << 0; + bt |= (crypto1_bit(pcs, 0, isEncrypted) ^ BIT(data[0], 1)) << 1; + bt |= (crypto1_bit(pcs, 0, isEncrypted) ^ BIT(data[0], 2)) << 2; + bt |= (crypto1_bit(pcs, 0, isEncrypted) ^ BIT(data[0], 3)) << 3; + data[0] = bt; + } +} + +int try_decrypt_word(uint32_t nt, uint32_t ar_enc, uint32_t at_enc, uint8_t *data, int len) { + + PrintAndLogEx(SUCCESS, "encrypted data... %s", sprint_hex(data, len)); + uint32_t ks2 = ar_enc ^ prng_successor(nt, 64); + uint32_t ks3 = at_enc ^ prng_successor(nt, 96); + + struct Crypto1State *s = lfsr_recovery64(ks2, ks3); + mf_crypto1_decrypt(s, data, len, false); + PrintAndLogEx(SUCCESS, "decrypted data... " _YELLOW_("%s"), sprint_hex(data, len)); + PrintAndLogEx(NORMAL, ""); + crypto1_destroy(s); + return PM3_SUCCESS; +} + +/* Detect Tag Prng, +* function performs a partial AUTH, where it tries to authenticate against block0, key A, but only collects tag nonce. +* the tag nonce is check to see if it has a predictable PRNG. +* @returns +* TRUE if tag uses WEAK prng (ie Now the NACK bug also needs to be present for Darkside attack) +* FALSE is tag uses HARDEND prng (ie hardnested attack possible, with known key) +*/ +int detect_classic_prng(void) { + + PacketResponseNG resp, respA; + uint8_t cmd[] = {MIFARE_AUTH_KEYA, 0x00}; + uint32_t flags = ISO14A_CONNECT | ISO14A_CLEARTRACE | ISO14A_RAW | ISO14A_APPEND_CRC | ISO14A_NO_RATS; + + clearCommandBuffer(); + SendIso14aReader(flags, cmd, sizeof(cmd)); + + // CONNECT + RAW, so the device answers twice: select first, then the raw exchange + uint8_t select_status = 0; + if (WaitForIso14aReply(&resp, 2000, NULL, &select_status) == false) { + PrintAndLogEx(WARNING, "timeout while waiting for reply"); + return PM3_ETIMEOUT; + } + + // if select tag failed. + if (select_status == 0) { + PrintAndLogEx(ERR, "error: selecting tag failed, can't detect prng\n"); + return PM3_ERFTRANS; + } + uint16_t rlen = 0; + if (WaitForIso14aReply(&respA, 2500, &rlen, NULL) == false) { + PrintAndLogEx(WARNING, "timeout while waiting for reply"); + return PM3_ETIMEOUT; + } + + // check respA + if (rlen != 4) { + PrintAndLogEx(ERR, "PRNG data error: Wrong length: %u", rlen); + return PM3_ESOFT; + } + + uint32_t nonce = bytes_to_num(respA.data.asBytes, rlen); + return validate_prng_nonce(nonce); +} + + +/* Detect supported Auth, +* function performs a partial AUTH, where it tries to authenticate against block0, but only collects tag nonce. +* @returns +* TRUE if tag replies with a nonce +* FALSE is tag does not reply with a nonce +*/ +int detect_classic_auth(uint8_t key_type) { + + PacketResponseNG resp, respA; + uint8_t cmd[] = {MIFARE_AUTH_KEYA + key_type, 0x00}; + uint32_t flags = ISO14A_CONNECT | ISO14A_CLEARTRACE | ISO14A_RAW | ISO14A_APPEND_CRC | ISO14A_NO_RATS; + + clearCommandBuffer(); + SendIso14aReader(flags, cmd, sizeof(cmd)); + + // CONNECT + RAW, so the device answers twice: select first, then the raw exchange + uint8_t select_status = 0; + if (WaitForIso14aReply(&resp, 2000, NULL, &select_status) == false) { + PrintAndLogEx(WARNING, "timeout while waiting for reply"); + return PM3_ETIMEOUT; + } + + // if select tag failed. + if (select_status == 0) { + PrintAndLogEx(ERR, "error: selecting tag failed, can't detect nonce\n"); + return PM3_ERFTRANS; + } + uint16_t rlen = 0; + if (WaitForIso14aReply(&respA, 2500, &rlen, NULL) == false) { + PrintAndLogEx(WARNING, "timeout while waiting for reply"); + return PM3_ETIMEOUT; + } + + // check respA for a nonce + return (rlen == 4); +} + +/* Detect Mifare Classic NACK bug + +returns: +0 = error during test / aborted +1 = has nack bug +2 = has not nack bug +3 = always leak nacks (clones) +*/ +int detect_classic_nackbug(bool verbose) { + + clearCommandBuffer(); + SendCommandNG(CMD_HF_MIFARE_NACK_DETECT, NULL, 0); + PacketResponseNG resp; + + PrintAndLogEx(INFO, "Checking for NACK bug"); + + if (verbose) { + PrintAndLogEx(SUCCESS, "press " _GREEN_("pm3 button") " to abort both Proxmark3 and client\n"); + } + + PrintAndLogEx(INFO, "." NOLF); + + while (true) { + + PrintAndLogEx(NORMAL, "." NOLF); + if (kbd_enter_pressed()) { + return PM3_EOPABORTED; + } + + if (WaitForResponseTimeout(CMD_HF_MIFARE_NACK_DETECT, &resp, 500)) { + + PrintAndLogEx(NORMAL, ""); + + if (resp.status == PM3_EOPABORTED) { + PrintAndLogEx(WARNING, "button pressed. Aborted."); + return PM3_EOPABORTED; + } + + uint8_t ok = resp.data.asBytes[0]; + uint8_t nacks = resp.data.asBytes[1]; + uint16_t auths = bytes_to_num(resp.data.asBytes + 2, 2); + + if (verbose) { + PrintAndLogEx(SUCCESS, "num of auth requests : %u", auths); + PrintAndLogEx(SUCCESS, "num of received NACK : %u", nacks); + } + + switch (ok) { + case 96 : + case 98 : { + if (verbose) + PrintAndLogEx(FAILED, "card random number generator is not predictable."); + PrintAndLogEx(WARNING, "detection failed"); + return PM3_SUCCESS; + } + case 97 : { + if (verbose) { + PrintAndLogEx(FAILED, "card random number generator seems to be based on the well-known generating polynomial"); + PrintAndLogEx(FAILED, "with 16 effective bits only, but shows unexpected behavior, try again."); + } + return PM3_SUCCESS; + } + case 2: { + PrintAndLogEx(SUCCESS, "NACK test... " _GREEN_("always leak NACK")); + return PM3_SUCCESS; + } + case 1: { + PrintAndLogEx(SUCCESS, "NACK test... " _GREEN_("detected")); + return PM3_SUCCESS; + } + case 0: { + PrintAndLogEx(SUCCESS, "NACK test... " _GREEN_("no bug")); + return PM3_SUCCESS; + } + default: { + PrintAndLogEx(ERR, "errorcode from device (" _RED_("%u") " )", ok); + return PM3_EUNDEF; + } + } + break; + } + } + return PM3_SUCCESS; +} + +/* Detect Mifare Classic Static / Fixed nonce +detects special magic cards that has a static / fixed nonce +returns: +0 = has normal nonce +1 = has static/fixed nonce +2 = cmd failed +*/ +int detect_classic_static_nonce(void) { + + clearCommandBuffer(); + SendCommandNG(CMD_HF_MIFARE_STATIC_NONCE, NULL, 0); + PacketResponseNG resp; + if (WaitForResponseTimeout(CMD_HF_MIFARE_STATIC_NONCE, &resp, 1000)) { + + if (resp.status == PM3_ESOFT) { + return NONCE_FAIL; + } + + return resp.data.asBytes[0]; + } + return NONCE_FAIL; +} + +/* Detect Mifare Classic static encrypted nonce +detects special magic cards that has a static / fixed nonce +returns: +0 = nonce ok +1 = has static/fixed nonce +2 = cmd failed +3 = has encrypted nonce +*/ +int detect_classic_static_encrypted_nonce_ex(uint8_t block_no, uint8_t key_type, const uint8_t *key, uint8_t block_no_nested + , uint8_t key_type_nested, const uint8_t *key_nested + , uint8_t nr_nested, bool reset, bool hardreset + , bool addread, bool addauth, bool incblk2 + , bool corruptnrar, bool corruptnrarparity, bool verbose) { + + uint8_t cdata[1 + 1 + MIFARE_KEY_SIZE + 1 + 1 + MIFARE_KEY_SIZE + 1 + 1 + 1 + 1 + 1 + 1 + 1] = { 0 }; + cdata[0] = block_no; + cdata[1] = key_type; + memcpy(&cdata[2], key, MIFARE_KEY_SIZE); + cdata[8] = block_no_nested; + cdata[9] = key_type_nested; + memcpy(&cdata[10], key_nested, MIFARE_KEY_SIZE); + cdata[16] = nr_nested; + cdata[17] = hardreset << 1 | reset; + cdata[18] = addread; + cdata[19] = addauth; + cdata[20] = incblk2; + cdata[21] = corruptnrar; + cdata[22] = corruptnrarparity; + + uint8_t dbg_curr = DBG_NONE; + if (getDeviceDebugLevel(&dbg_curr) != PM3_SUCCESS) { + return PM3_EFAILED; + } + + if (setDeviceDebugLevel(verbose ? MAX(dbg_curr, DBG_INFO) : DBG_NONE, false) != PM3_SUCCESS) { + return PM3_EFAILED; + } + + clearCommandBuffer(); + SendCommandNG(CMD_HF_MIFARE_STATIC_ENCRYPTED_NONCE, cdata, sizeof(cdata)); + PacketResponseNG resp; + if (WaitForResponseTimeout(CMD_HF_MIFARE_STATIC_ENCRYPTED_NONCE, &resp, 1500)) { + + setDeviceDebugLevel(dbg_curr, false); + + if (resp.status == PM3_ESOFT) { + return NONCE_FAIL; + } + + if (verbose && (resp.data.asBytes[0] == NONCE_STATIC_ENC)) { + + uint32_t uid = resp.data.asBytes[1] << 24 | + resp.data.asBytes[2] << 16 | + resp.data.asBytes[3] << 8 | + resp.data.asBytes[4]; + + uint32_t nt = resp.data.asBytes[5] << 24 | + resp.data.asBytes[6] << 16 | + resp.data.asBytes[7] << 8 | + resp.data.asBytes[8]; + + uint32_t ntenc = resp.data.asBytes[9] << 24 | + resp.data.asBytes[10] << 16 | + resp.data.asBytes[11] << 8 | + resp.data.asBytes[12]; + + uint8_t ntencparenc = resp.data.asBytes[13]; + + // recompute nt on client, just because + struct Crypto1State mpcs = {0, 0}; + struct Crypto1State *pcs; + pcs = &mpcs; + + uint64_t ui64key = bytes_to_num(key_nested, MIFARE_KEY_SIZE); + + crypto1_init(pcs, ui64key); // key_nested + uint32_t ks = crypto1_word(pcs, ntenc ^ uid, 1); + + uint32_t mynt = ks ^ ntenc; + if (mynt != nt) { + PrintAndLogEx(ERR, "Client computed nT " _YELLOW_("%08x") " does not match ARM computed nT " _YELLOW_("%08x"), mynt, nt); + } + + ntencparenc >>= 4; + // [...] Additionally, the bit of keystream used to encrypt the parity bits is reused to encrypt the next bit of plaintext. + // we can decrypt first 3 parity bits, not last one as it's using future keystream + uint8_t ksp = (((ks >> 16) & 1) << 3) | (((ks >> 8) & 1) << 2) | (((ks >> 0) & 1) << 1); + uint8_t ntencpar = ntencparenc ^ ksp; + if (validate_prng_nonce(nt)) { + PrintAndLogEx(INFO, "nTenc " _GREEN_("%08x") " par {" _YELLOW_("%i%i%i%i") "}=" _YELLOW_("%i%i%ix") " | ks " _GREEN_("%08x") " | nT " _GREEN_("%08x") " par " _YELLOW_("%i%i%i%i")" | lfsr16 index " _GREEN_("%i"), + ntenc, + (ntencparenc >> 3) & 1, (ntencparenc >> 2) & 1, (ntencparenc >> 1) & 1, ntencparenc & 1, + (ntencpar >> 3) & 1, (ntencpar >> 2) & 1, (ntencpar >> 1) & 1, + ks, nt, + oddparity8((nt >> 24) & 0xFF), oddparity8((nt >> 16) & 0xFF), oddparity8((nt >> 8) & 0xFF), oddparity8(nt & 0xFF), + nonce_distance(0, nt) + ); + } else { + PrintAndLogEx(INFO, "nTenc " _GREEN_("%08x") " par {" _YELLOW_("%i%i%i%i") "}=" _YELLOW_("%i%i%ix") " | ks " _YELLOW_("%08x") " | nT " _YELLOW_("%08x") " par " _YELLOW_("%i%i%i%i") " | " _RED_("not lfsr16") " (wrong key)", + ntenc, + (ntencparenc >> 3) & 1, (ntencparenc >> 2) & 1, (ntencparenc >> 1) & 1, ntencparenc & 1, + (ntencpar >> 3) & 1, (ntencpar >> 2) & 1, (ntencpar >> 1) & 1, + ks, nt, + oddparity8((nt >> 24) & 0xFF), oddparity8((nt >> 16) & 0xFF), oddparity8((nt >> 8) & 0xFF), oddparity8(nt & 0xFF) + ); + } + } + return resp.data.asBytes[0]; + } + + setDeviceDebugLevel(dbg_curr, false); + return NONCE_FAIL; +} + +int detect_classic_static_encrypted_nonce(uint8_t block_no, uint8_t key_type, const uint8_t *key) { + return detect_classic_static_encrypted_nonce_ex(block_no, key_type, key, block_no, key_type, key, 3, false, false, false, false, false, false, false, false); +} + +// try to see if card responses to "Chinese magic backdoor" commands. +// returns flag +uint16_t detect_mf_magic(bool is_mfc, uint8_t key_type, uint64_t key) { + + PacketResponseNG resp; + clearCommandBuffer(); + uint8_t payload[1 + 1 + MIFARE_KEY_SIZE] = { is_mfc, key_type }; + num_to_bytes(key, MIFARE_KEY_SIZE, payload + 2); + + SendCommandNG(CMD_HF_MIFARE_CIDENT, payload, sizeof(payload)); + if (WaitForResponseTimeout(CMD_HF_MIFARE_CIDENT, &resp, 1500)) { + if (resp.status != PM3_SUCCESS) { + return MAGIC_FLAG_NONE; + } + } + + uint16_t isMagic = MAGIC_FLAG_NONE; + if ((resp.status == PM3_SUCCESS) && resp.length == sizeof(uint16_t)) { + isMagic = MemLeToUint2byte(resp.data.asBytes); + } + + if (isMagic) { + PrintAndLogEx(NORMAL, ""); + } + + if ((isMagic & MAGIC_FLAG_GEN_1A) == MAGIC_FLAG_GEN_1A) { + PrintAndLogEx(SUCCESS, "Magic capabilities... " _GREEN_("Gen 1a")); + } + + if ((isMagic & MAGIC_FLAG_GEN_1B) == MAGIC_FLAG_GEN_1B) { + PrintAndLogEx(SUCCESS, "Magic capabilities... " _GREEN_("Gen 1b")); + } + + if ((isMagic & MAGIC_FLAG_GEN_2) == MAGIC_FLAG_GEN_2) { + PrintAndLogEx(SUCCESS, "Magic capabilities... " _GREEN_("Gen 2 / CUID")); + } + + if ((isMagic & MAGIC_FLAG_GEN_3) == MAGIC_FLAG_GEN_3) { + PrintAndLogEx(SUCCESS, "Magic capabilities... " _GREEN_("Gen 3 / APDU") " ( possibly )"); + } + + if ((isMagic & MAGIC_FLAG_GEN_4GTU) == MAGIC_FLAG_GEN_4GTU) { + PrintAndLogEx(SUCCESS, "Magic capabilities... " _GREEN_("Gen 4 GTU")); + } + + if ((isMagic & MAGIC_FLAG_GDM_AUTH) == MAGIC_FLAG_GDM_AUTH) { + PrintAndLogEx(SUCCESS, "Magic capabilities... " _GREEN_("Gen 4 GDM / USCUID") " ( Magic Auth )"); + } + + if ((isMagic & MAGIC_FLAG_GDM_WUP_20) == MAGIC_FLAG_GDM_WUP_20) { + PrintAndLogEx(SUCCESS, "Magic capabilities... " _GREEN_("Gen 4 GDM / USCUID") " ( Gen4 Magic Wakeup )"); + } + + if ((isMagic & MAGIC_FLAG_GDM_WUP_40) == MAGIC_FLAG_GDM_WUP_40) { + PrintAndLogEx(SUCCESS, "Magic capabilities... " _GREEN_("Gen 4 GDM / USCUID") " ( Gen1 Magic Wakeup )"); + } + + if ((isMagic & MAGIC_FLAG_GDM_WUP_40_ZUID) == MAGIC_FLAG_GDM_WUP_40_ZUID) { + PrintAndLogEx(SUCCESS, "Magic capabilities... " _GREEN_("Gen 4 GDM / USCUID") " ( ZUID Gen1 Magic Wakeup )"); + } + + if ((isMagic & MAGIC_FLAG_GEN_UNFUSED) == MAGIC_FLAG_GEN_UNFUSED) { + PrintAndLogEx(SUCCESS, "Magic capabilities... " _GREEN_("Write Once / FUID")); + } + + if ((isMagic & MAGIC_FLAG_SUPER_GEN1) == MAGIC_FLAG_SUPER_GEN1) { + PrintAndLogEx(SUCCESS, "Magic capabilities... " _GREEN_("Super card ( ") _CYAN_("Gen 1") _GREEN_(" )")); + } + + if ((isMagic & MAGIC_FLAG_SUPER_GEN2) == MAGIC_FLAG_SUPER_GEN2) { + PrintAndLogEx(SUCCESS, "Magic capabilities... " _GREEN_("Super card ( ") _CYAN_("Gen 2") _GREEN_(" )")); + } + + if ((isMagic & MAGIC_FLAG_NTAG21X) == MAGIC_FLAG_NTAG21X) { + PrintAndLogEx(SUCCESS, "Magic capabilities... " _GREEN_("NTAG21x")); + } + + if ((isMagic & MAGIC_FLAG_QL88) == MAGIC_FLAG_QL88) { + PrintAndLogEx(SUCCESS, "Magic capabilities... " _GREEN_("QL88")); + } + return isMagic; +} + +bool detect_mfc_ev1_signature(void) { + uint64_t key = 0; + int res = mf_check_keys(69, MF_KEY_B, false, 1, (uint8_t *)g_mifare_signature_key_b, &key); + return (res == PM3_SUCCESS); +} + +int read_mfc_ev1_signature(uint8_t *signature) { + if (signature == NULL) { + return PM3_EINVARG; + } + uint8_t sign[32] = {0}; + int res = mf_read_block(69, MF_KEY_B, g_mifare_signature_key_b, sign); + if (res == PM3_SUCCESS) { + res = mf_read_block(70, MF_KEY_B, g_mifare_signature_key_b, sign + 16); + if (res == PM3_SUCCESS) { + memcpy(signature, sign, sizeof(sign)); + } + } else { + // try QL88 + res = mf_read_block(69, MF_KEY_B, g_mifare_ql88_signature_key_b, sign); + if (res == PM3_SUCCESS) { + res = mf_read_block(70, MF_KEY_B, g_mifare_ql88_signature_key_b, sign + 16); + if (res == PM3_SUCCESS) { + memcpy(signature, sign, sizeof(sign)); + } + } + } + return res; +} + +int convert_mfc_2_arr(uint8_t *in, uint16_t ilen, uint8_t *out, uint16_t *olen) { + if (in == NULL || out == NULL) { + return PM3_EINVARG; + } + + uint8_t blockno = 0; + while (ilen) { + + if (mfIsSectorTrailer(blockno) == false) { + memcpy(out, in, MFBLOCK_SIZE); + out += MFBLOCK_SIZE; + *olen += MFBLOCK_SIZE; + } + blockno++; + in += MFBLOCK_SIZE; + ilen -= MFBLOCK_SIZE; + } + return PM3_SUCCESS; +} + +static const vigik_pk_t vigik_rsa_pk[] = { + {"La Poste Service Universel", 0x07AA, "AB9953CBFCCD9375B6C028ADBAB7584BED15B9CA037FADED9765996F9EA1AB983F3041C90DA3A198804FF90D5D872A96A4988F91F2243B821E01C5021E3ED4E1BA83B7CFECAB0E766D8563164DE0B2412AE4E6EA63804DF5C19C7AA78DC14F608294D732D7C8C67A88C6F84C0F2E3FAFAE34084349E11AB5953AC68729D07715"}, + {"La Poste Service Universel", 0x07AA, "1577D02987C63A95B51AE149430834AEAF3F2E0F4CF8C6887AC6C8D732D79482604FC18DA77A9CC1F54D8063EAE6E42A41B2E04D1663856D760EABECCFB783BAE1D43E1E02C5011E823B24F2918F98A4962A875D0DF94F8098A1A30DC941303F98ABA19E6F996597EDAD7F03CAB915ED4B58B7BAAD28C0B67593CDFCCB5399AB"}, + + {"La Poste Autres Services", 0x07AB, "A6D99B8D902893B04F3F8DE56CB6BF24338FEE897C1BCE6DFD4EBD05B7B1A07FD2EB564BB4F7D35DBFE0A42966C2C137AD156E3DAB62904592BCA20C0BC7B8B1E261EF82D53F52D203843566305A49A22062DECC38C2FE3864CAD08E79219487651E2F79F1C9392B48CAFE1BFFAFF4802AE451E7A283E55A4026AD1E82DF1A15"}, + {"La Poste Autres Services", 0x07AB, "151adf821ead26405ae583a2e751e42a80f4afff1bfeca482b39c9f1792f1e65879421798ed0ca6438fec238ccde6220a2495a3066358403d2523fd582ef61e2b1b8c70b0ca2bc92459062ab3d6e15ad37c1c26629a4e0bf5dd3f7b44b56ebd27fa0b1b705bd4efd6dce1b7c89ee8f3324bfb66ce58d3f4fb09328908d9bd9a6"}, + + {"France Telecom", 0x07AC, "C44DBCD92F9DCF42F4902A87335DBB35D2FF530CDB09814CFA1F4B95A1BD018D099BC6AB69F667B4922AE1ED826E72951AA3E0EAAA7D49A695F04F8CDAAE2D18D10D25BD529CBB05ABF070DC7C041EC35C2BA7F58CC4C349983CC6E11A5CBE828FB8ECBC26F08E1094A6B44C8953C8E1BAFD214DF3E69F430A98CCC75C03669D"}, + {"France Telecom", 0x07AC, "9d66035cc7cc980a439fe6f34d21fdbae1c853894cb4a694108ef026bcecb88f82be5c1ae1c63c9849c3c48cf5a72b5cc31e047cdc70f0ab05bb9c52bd250dd1182daeda8c4ff095a6497daaeae0a31a95726e82ede12a92b467f669abc69b098d01bda1954b1ffa4c8109db0c53ffd235bb5d33872a90f442cf9d2fd9bc4dc4"}, + + {"EDF-GDF", 0x07AD, "B35193DBD2F88A21CDCFFF4BF84F7FC036A991A363DCB3E802407A5E5879DC2127EECFC520779E79E911394882482C87D09A88B0711CBC2973B77FFDAE40EA0001F595072708C558B484AB89D02BCBCB971FF1B80371C0BE30CB13661078078BB68EBCCA524B9DD55EBF7D47D9355AFC95511350CC1103A5DEE847868848B235"}, + {"EDF-GDF", 0x07AD, "35b248888647e8dea50311cc50135195fc5a35d9477dbf5ed59d4b52cabc8eb68b0778106613cb30bec07103b8f11f97cbcb2bd089ab84b458c508270795f50100ea40aefd7fb77329bc1c71b0889ad0872c4882483911e9799e7720c5cfee2721dc79585e7a4002e8b3dc63a391a936c07f4ff84bffcfcd218af8d2db9351b3"}, + {NULL, 0, NULL} +}; + +const char *vigik_get_service(uint16_t service_code) { + for (int i = 0; i < ARRAYLEN(vigik_rsa_pk); ++i) + if (service_code == vigik_rsa_pk[i].code) + return vigik_rsa_pk[i].desc; + + //No match, return default + return vigik_rsa_pk[ARRAYLEN(vigik_rsa_pk) - 1].desc; +} + + +int vigik_verify(mfc_vigik_t *d) { +#define PUBLIC_VIGIK_KEYLEN 128 + + // iso9796 + // Exponent V = 2 + // n = The public modulus n is the product of the secret prime factors p and q. Its length is 1024 bits. + + if (g_debugMode == DEBUG) { + PrintAndLogEx(INFO, "Raw"); + print_hex_noascii_break((uint8_t *)d, sizeof(*d) - sizeof(d->rsa_signature), MFBLOCK_SIZE * 2); + + PrintAndLogEx(INFO, "Raw signature"); + print_hex_noascii_break(d->rsa_signature, sizeof(d->rsa_signature), MFBLOCK_SIZE * 2); + } + + /* + int dl = 0; + + param_gethex_to_eol("1C07D46DA3849326D24B3468BD76673F4F3C41827DC413E81E4F3C7804FAC727213059B21D047510D6432448643A92EBFC67FBEDDAB468D13D948B172F5EBC79A0E3FEFDFAF4E81FC7108E070F1E3CD0", 0, signature, PUBLIC_VIGIK_KEYLEN, &dl); + + param_gethex_to_eol("1AB86FE0C17FFFFE4379D5E15A4B2FAFFEFCFA0F1F3F7FA03E7DDDF1E3C78FFFB1F0E23F7FFF51584771C5C18307FEA36CA74E60AA6B0409ACA66A9EC155F4E9112345708A2B8457E722608EE1157408", 0, signature, PUBLIC_VIGIK_KEYLEN, &dl); + signature_len = dl; + */ + + uint8_t rev_sig[128]; + reverse_array_copy(d->rsa_signature, sizeof(d->rsa_signature), rev_sig); + + PrintAndLogEx(INFO, "Raw signature reverse"); + print_hex_noascii_break(rev_sig, sizeof(d->rsa_signature), MFBLOCK_SIZE * 2); + + // t = 0xBC = Implicitly known + // t = 0xCC = look at byte before to determine hash function + // uint8_t T[] = {0x33, 0xCC}; + + // Success decrypt would mean 0x4b BB ... BB BA padding + // padding, message, hash, 8 bits or 16 bits + + // signature = h( C || M1 || h(M2) ) + // 1024 - 786 - 160 - 16 -1 + // salt C + // message M = 96 bytes, 768 bits + // sha1 hash H = 20 bytes, 160 bits + // padding = 20 bytes, 96 bits + + uint8_t i; + bool is_valid = false; + + for (i = 0; i < ARRAYLEN(vigik_rsa_pk); i++) { + if (vigik_rsa_pk[i].desc == NULL) { + break; + } + + mbedtls_mpi RN, E; + mbedtls_mpi_init(&RN); + + // exponent 2 = even + mbedtls_mpi_init(&E); + mbedtls_mpi_add_int(&E, &E, 2); + + int dl = 0; + uint8_t n[PUBLIC_VIGIK_KEYLEN]; + memset(n, 0, sizeof(n)); + param_gethex_to_eol(vigik_rsa_pk[i].n, 0, n, PUBLIC_VIGIK_KEYLEN, &dl); + + // convert + mbedtls_mpi N, s, sqr, res; + mbedtls_mpi_init(&N); + mbedtls_mpi_init(&s); + mbedtls_mpi_init(&sqr); + mbedtls_mpi_init(&res); + + mbedtls_mpi_read_binary(&N, (const unsigned char *)n, PUBLIC_VIGIK_KEYLEN); + + //mbedtls_mpi_read_binary(&s, (const unsigned char*)signature, signature_len); + mbedtls_mpi_read_binary(&s, (const unsigned char *)rev_sig, sizeof(d->rsa_signature)); + + // check is sign < (N/2) + + mbedtls_mpi n_2; + mbedtls_mpi_init(&n_2); + mbedtls_mpi_copy(&n_2, &N); + mbedtls_mpi_shift_r(&n_2, 1); + bool is_less = (mbedtls_mpi_cmp_mpi(&s, &n_2) > 0) ? false : true; + PrintAndLogEx(DEBUG, "z < (N/2) ..... %s", (is_less) ? _GREEN_("YES") : _RED_("NO")); + mbedtls_mpi_free(&n_2); + + + if (is_less) { + mbedtls_mpi_exp_mod(&sqr, &s, &E, &N, &RN); + } else { + continue; + } + + /* + if v is even and + ⎯ if J* mod 8 = 1, then f* = n–J*. + ⎯ if J* mod 8 = 4, then f* = J*, + ⎯ if J* mod 8 = 6, then f* = 2J*, + ⎯ if J* mod 8 = 7, then f* = 2(n–J*), + */ + uint8_t b2 = mbedtls_mpi_get_bit(&sqr, 2); + uint8_t b1 = mbedtls_mpi_get_bit(&sqr, 1); + uint8_t b0 = mbedtls_mpi_get_bit(&sqr, 0); + uint8_t lsb = (b2 << 2) | (b1 << 1) | b0; + + /* + //1 + mbedtls_mpi_sub_mpi(&res, &N, &sqr); + mbedtls_mpi_write_file( "[=] 1... ", &res, 16, NULL ); + // 4 + mbedtls_mpi_copy(&res, &sqr); + mbedtls_mpi_write_file( "[=] 4... ", &res, 16, NULL ); + // 6 + mbedtls_mpi_mul_int(&res, &sqr, 2); + mbedtls_mpi_write_file( "[=] 6... ", &res, 16, NULL ); + // 7 + mbedtls_mpi foo; + mbedtls_mpi_init(&foo); + mbedtls_mpi_sub_mpi(&foo, &N, &sqr); + mbedtls_mpi_mul_int(&res, &foo, 2); + mbedtls_mpi_free(&foo); + mbedtls_mpi_write_file( "[=] 7... ", &res, 16, NULL ); + */ + + switch (lsb) { + case 1: { + mbedtls_mpi_sub_mpi(&res, &N, &sqr); + break; + } + case 4: { + mbedtls_mpi_copy(&res, &sqr); + break; + } + case 6: { + mbedtls_mpi_mul_int(&res, &sqr, 2); + break; + } + case 7: { + mbedtls_mpi foo2; + mbedtls_mpi_init(&foo2); + mbedtls_mpi_sub_mpi(&foo2, &N, &sqr); + mbedtls_mpi_mul_int(&res, &foo2, 2); + mbedtls_mpi_free(&foo2); + break; + } + default: { + continue; + } + } + + PrintAndLogEx(DEBUG, "LSB............ " _GREEN_("%u"), lsb); + if (g_debugMode == DEBUG) { + mbedtls_mpi_write_file("[=] N.............. ", &N, 16, NULL); + mbedtls_mpi_write_file("[=] signature...... ", &s, 16, NULL); + mbedtls_mpi_write_file("[=] square mod n... ", &sqr, 16, NULL); + mbedtls_mpi_write_file("[=] n-fs........... ", &res, 16, NULL); + } + + + uint8_t nfs[128] = {0}; + mbedtls_mpi_write_binary(&res, nfs, sizeof(nfs)); + + // xor 0xDC01 + int count_zero = 0; + for (int x = 0; x < sizeof(nfs); x += 2) { + nfs[x] ^= 0xDC; + nfs[x + 1] ^= 0x01; + + if (nfs[x] == 0x00) + count_zero++; + if (nfs[x + 1] == 0x00) + count_zero++; + } + + if (count_zero > 10) { + PrintAndLogEx(INFO, ""); + PrintAndLogEx(INFO, "Message XORED"); + print_hex_noascii_break(nfs, sizeof(nfs), 32); + PrintAndLogEx(INFO, "\n"); + is_valid = true; + break; + } + + /* + if (bar == 0) { + typedef struct vigik_rsa_s { + uint8_t rsa[127]; + uint8_t hash; + } vigik_rsa_t; + + vigik_rsa_t ts; + memcpy(&ts, nfs, sizeof(ts)); + + if ( ts.hash == 0xCC ) { + PrintAndLogEx(INFO, "Hash byte... 0x%02X", ts.hash); + switch(ts.rsa[126]) { + case 0x11: + PrintAndLogEx(INFO, "Hash algo ( 0x%02X ) - SHA1"); + break; + case 0x22: + PrintAndLogEx(INFO, "Hash algo ( 0x%02X ) - RIPEMD"); + break; + case 0x33: + PrintAndLogEx(INFO, "Hash algo ( 0x%02X ) - SHA1"); + break; + default: + PrintAndLogEx(INFO, "Hash algo ( 0x%02X ) - " _RED_("err")); + break; + } + } else if ( ts.hash == 0xBC) { + PrintAndLogEx(INFO, "Hash byte... 0x%02X - " _GREEN_("implict"), ts.hash); + } else { + PrintAndLogEx(INFO, "Hash byte... 0x%02x - " _RED_("err"), ts.hash); + } + + PrintAndLogEx(INFO, "Message w padding"); + print_hex_noascii_break(ts.rsa, sizeof(ts.rsa) - 20, 32); + } + */ + + mbedtls_mpi_free(&N); + mbedtls_mpi_free(&s); + mbedtls_mpi_free(&res); + mbedtls_mpi_free(&RN); + mbedtls_mpi_free(&E); + } + + PrintAndLogEx(INFO, ""); + PrintAndLogEx(INFO, "--- " _CYAN_("Tag Signature")); + PrintAndLogEx(INFO, "RSA: 1024bit"); + + if (is_valid == false || i == ARRAYLEN(vigik_rsa_pk)) { + PrintAndLogEx(INFO, "Signature:"); + print_hex_noascii_break(d->rsa_signature, sizeof(d->rsa_signature), MFBLOCK_SIZE * 2); + PrintAndLogEx(SUCCESS, "Signature verification: " _RED_("failed")); + return PM3_ESOFT; + } + + PrintAndLogEx(INFO, "Signature public key name: " _YELLOW_("%s"), vigik_rsa_pk[i].desc); + PrintAndLogEx(INFO, "Signature public key value:"); + PrintAndLogEx(INFO, "%.64s", vigik_rsa_pk[i].n); + PrintAndLogEx(INFO, "%.64s", vigik_rsa_pk[i].n + 64); + PrintAndLogEx(INFO, "%.64s", vigik_rsa_pk[i].n + 128); + PrintAndLogEx(INFO, "%.64s", vigik_rsa_pk[i].n + 192); + + PrintAndLogEx(INFO, "Signature:"); + print_hex_noascii_break(d->rsa_signature, sizeof(d->rsa_signature), MFBLOCK_SIZE * 2); + + PrintAndLogEx(SUCCESS, "Signature verification: " _GREEN_("successful")); + + return PM3_SUCCESS; +} + +int vigik_annotate(mfc_vigik_t *d) { + if (d == NULL) + return PM3_EINVARG; + + PrintAndLogEx(INFO, "Manufacture......... %s", sprint_hex(d->b0, sizeof(d->b0))); + PrintAndLogEx(INFO, "MAD................. %s", sprint_hex(d->mad, sizeof(d->mad))); + PrintAndLogEx(INFO, "Counters............ %u", d->counters); + PrintAndLogEx(INFO, "rtf................. %s", sprint_hex(d->rtf, sizeof(d->rtf))); + PrintAndLogEx(INFO, "Service code........ 0x%08x / %u - " _YELLOW_("%s"), d->service_code, d->service_code, vigik_get_service(d->service_code)); + PrintAndLogEx(INFO, "Info flag........... %u -", d->info_flag); // , sprint_bin(d->info_flag, 1)); + PrintAndLogEx(INFO, "Key version......... %u", d->key_version); + PrintAndLogEx(INFO, "PTR Counter......... %u", d->ptr_counter); + PrintAndLogEx(INFO, "Counter num......... %u", d->counter_num); + PrintAndLogEx(INFO, "Slot access date.... %s", sprint_hex(d->slot_access_date, sizeof(d->slot_access_date))); + PrintAndLogEx(INFO, "Slot dst duration... %u", d->slot_dst_duration); + PrintAndLogEx(INFO, "Other Slots......... %s", sprint_hex(d->other_slots, sizeof(d->other_slots))); + PrintAndLogEx(INFO, "Services counter.... %u", d->services_counter); + PrintAndLogEx(INFO, "Loading date........ %s", sprint_hex(d->loading_date, sizeof(d->loading_date))); + PrintAndLogEx(INFO, "Reserved null....... %u", d->reserved_null); + PrintAndLogEx(INFO, "----------------------------------------------------------------"); + PrintAndLogEx(INFO, ""); + vigik_verify(d); + PrintAndLogEx(INFO, "----------------------------------------------------------------"); + PrintAndLogEx(INFO, ""); + return PM3_SUCCESS; + +}