mirror of
https://github.com/RfidResearchGroup/proxmark3.git
synced 2025-08-14 10:37:23 -07:00
sort mfc tools
This commit is contained in:
parent
4e0d4d3ad4
commit
c47578c048
49 changed files with 143 additions and 175 deletions
10
tools/mfc/card_reader/.gitignore
vendored
Normal file
10
tools/mfc/card_reader/.gitignore
vendored
Normal file
|
@ -0,0 +1,10 @@
|
|||
mfkey32
|
||||
mfkey32v2
|
||||
mfkey64
|
||||
mf_nonce_brute
|
||||
mf_trace_brute
|
||||
mfkey32.exe
|
||||
mfkey32v2.exe
|
||||
mfkey64.exe
|
||||
mf_nonce_brute.exe
|
||||
mf_trace_brute.exe
|
33
tools/mfc/card_reader/Makefile
Normal file
33
tools/mfc/card_reader/Makefile
Normal file
|
@ -0,0 +1,33 @@
|
|||
ROOTPATH = ../../..
|
||||
MYSRCPATHS = $(ROOTPATH)/common $(ROOTPATH)/common/crapto1
|
||||
MYSRCS = crypto1.c crapto1.c bucketsort.c iso14443crc.c sleep.c util_posix.c
|
||||
MYINCLUDES = -I$(ROOTPATH)/include -I$(ROOTPATH)/common
|
||||
MYCFLAGS = -O3
|
||||
MYDEFS =
|
||||
MYLDLIBS =
|
||||
ifneq ($(SKIPPTHREAD),1)
|
||||
MYLDLIBS += -lpthread
|
||||
endif
|
||||
|
||||
BINS = mfkey32 mfkey32v2 mfkey64 mf_nonce_brute mf_trace_brute
|
||||
INSTALLTOOLS = $(BINS)
|
||||
|
||||
include $(ROOTPATH)/Makefile.host
|
||||
|
||||
# checking platform can be done only after Makefile.host
|
||||
ifneq (,$(findstring MINGW,$(platform)))
|
||||
# Mingw uses by default Microsoft printf, we want the GNU printf (e.g. for %z)
|
||||
# and setting _ISOC99_SOURCE sets internally __USE_MINGW_ANSI_STDIO=1
|
||||
CFLAGS += -D_ISOC99_SOURCE
|
||||
endif
|
||||
|
||||
# macOS doesn't like these compiler params
|
||||
ifneq ($(platform),Darwin)
|
||||
MYCFLAGS += --param max-completely-peeled-insns=1000 --param max-completely-peel-times=10000
|
||||
endif
|
||||
|
||||
mfkey32 : $(OBJDIR)/mfkey32.o $(MYOBJS)
|
||||
mfkey32v2 : $(OBJDIR)/mfkey32v2.o $(MYOBJS)
|
||||
mfkey64 : $(OBJDIR)/mfkey64.o $(MYOBJS)
|
||||
mf_nonce_brute : $(OBJDIR)/mf_nonce_brute.o $(MYOBJS)
|
||||
mf_trace_brute : $(OBJDIR)/mf_trace_brute.o $(MYOBJS)
|
45
tools/mfc/card_reader/iso14443crc.c
Normal file
45
tools/mfc/card_reader/iso14443crc.c
Normal file
|
@ -0,0 +1,45 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// This code is licensed to you under the terms of the GNU GPL, version 2 or,
|
||||
// at your option, any later version. See the LICENSE.txt file for the text of
|
||||
// the license.
|
||||
//-----------------------------------------------------------------------------
|
||||
// ISO14443 CRC calculation code.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#include "iso14443crc.h"
|
||||
|
||||
static unsigned short UpdateCrc14443(unsigned char ch, unsigned short *lpwCrc) {
|
||||
ch = (ch ^ (unsigned char)((*lpwCrc) & 0x00FF));
|
||||
ch = (ch ^ (ch << 4));
|
||||
*lpwCrc = (*lpwCrc >> 8) ^ ((unsigned short) ch << 8) ^
|
||||
((unsigned short) ch << 3) ^ ((unsigned short) ch >> 4);
|
||||
return (*lpwCrc);
|
||||
}
|
||||
|
||||
void ComputeCrc14443(int CrcType,
|
||||
const unsigned char *Data, int Length,
|
||||
unsigned char *TransmitFirst,
|
||||
unsigned char *TransmitSecond) {
|
||||
unsigned short wCrc = CrcType;
|
||||
|
||||
do {
|
||||
unsigned char chBlock = *Data++;
|
||||
UpdateCrc14443(chBlock, &wCrc);
|
||||
} while (--Length);
|
||||
|
||||
if (CrcType == CRC_14443_B)
|
||||
wCrc = ~wCrc; /* ISO/IEC 13239 (formerly ISO/IEC 3309) */
|
||||
|
||||
*TransmitFirst = (unsigned char)(wCrc & 0xFF);
|
||||
*TransmitSecond = (unsigned char)((wCrc >> 8) & 0xFF);
|
||||
return;
|
||||
}
|
||||
|
||||
int CheckCrc14443(int CrcType, const unsigned char *Data, int Length) {
|
||||
unsigned char b1;
|
||||
unsigned char b2;
|
||||
if (Length < 3) return 0;
|
||||
ComputeCrc14443(CrcType, Data, Length - 2, &b1, &b2);
|
||||
if ((b1 == Data[Length - 2]) && (b2 == Data[Length - 1])) return 1;
|
||||
return 0;
|
||||
}
|
26
tools/mfc/card_reader/iso14443crc.h
Normal file
26
tools/mfc/card_reader/iso14443crc.h
Normal file
|
@ -0,0 +1,26 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// This code is licensed to you under the terms of the GNU GPL, version 2 or,
|
||||
// at your option, any later version. See the LICENSE.txt file for the text of
|
||||
// the license.
|
||||
//-----------------------------------------------------------------------------
|
||||
// ISO14443 CRC calculation code.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#ifndef __ISO14443CRC_H
|
||||
#define __ISO14443CRC_H
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Routines to compute the CRCs (two different flavours, just for confusion)
|
||||
// required for ISO 14443, swiped directly from the spec.
|
||||
//-----------------------------------------------------------------------------
|
||||
#define CRC_14443_A 0x6363 /* ITU-V.41 */
|
||||
#define CRC_14443_B 0xFFFF /* ISO/IEC 13239 (formerly ISO/IEC 3309) */
|
||||
#define CRC_ICLASS 0xE012 /* ICLASS PREFIX */
|
||||
|
||||
void ComputeCrc14443(int CrcType,
|
||||
const unsigned char *Data, int Length,
|
||||
unsigned char *TransmitFirst,
|
||||
unsigned char *TransmitSecond);
|
||||
int CheckCrc14443(int CrcType, const unsigned char *Data, int Length);
|
||||
|
||||
#endif
|
847
tools/mfc/card_reader/mf_nonce_brute.c
Normal file
847
tools/mfc/card_reader/mf_nonce_brute.c
Normal file
|
@ -0,0 +1,847 @@
|
|||
#define __STDC_FORMAT_MACROS
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <stdio.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#include <pthread.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <ctype.h>
|
||||
#include "crapto1/crapto1.h"
|
||||
#include "protocol.h"
|
||||
#include "iso14443crc.h"
|
||||
#include "util_posix.h"
|
||||
|
||||
#define AEND "\x1b[0m"
|
||||
#define _RED_(s) "\x1b[31m" s AEND
|
||||
#define _GREEN_(s) "\x1b[32m" s AEND
|
||||
#define _YELLOW_(s) "\x1b[33m" s AEND
|
||||
#define _CYAN_(s) "\x1b[36m" s AEND
|
||||
|
||||
#define odd_parity(i) (( (i) ^ (i)>>1 ^ (i)>>2 ^ (i)>>3 ^ (i)>>4 ^ (i)>>5 ^ (i)>>6 ^ (i)>>7 ^ 1) & 0x01)
|
||||
#define ARRAYLEN(x) (sizeof(x) / sizeof((x)[0]))
|
||||
|
||||
// a global mutex to prevent interlaced printing from different threads
|
||||
pthread_mutex_t print_lock;
|
||||
|
||||
//--------------------- define options here
|
||||
uint32_t uid = 0; // serial number
|
||||
uint32_t nt_enc = 0; // Encrypted tag nonce
|
||||
uint32_t nr_enc = 0; // encrypted reader challenge
|
||||
uint32_t ar_enc = 0; // encrypted reader response
|
||||
uint32_t at_enc = 0; // encrypted tag response
|
||||
uint32_t cmd_enc = 0; // next encrypted command to sector
|
||||
|
||||
uint32_t nt_par_err = 0;
|
||||
uint32_t ar_par_err = 0;
|
||||
uint32_t at_par_err = 0;
|
||||
|
||||
typedef struct thread_args {
|
||||
uint16_t xored;
|
||||
int thread;
|
||||
int idx;
|
||||
bool ev1;
|
||||
} targs;
|
||||
|
||||
#define ENC_LEN (200)
|
||||
typedef struct thread_key_args {
|
||||
int thread;
|
||||
int idx;
|
||||
uint32_t uid;
|
||||
uint32_t part_key;
|
||||
uint32_t nt_enc;
|
||||
uint32_t nr_enc;
|
||||
uint16_t enc_len;
|
||||
uint8_t enc[ENC_LEN]; // next encrypted command + a full read/write
|
||||
} targs_key;
|
||||
|
||||
//------------------------------------------------------------------
|
||||
|
||||
uint8_t cmds[8][2] = {
|
||||
{ISO14443A_CMD_READBLOCK, 18},
|
||||
{ISO14443A_CMD_WRITEBLOCK, 18},
|
||||
{MIFARE_AUTH_KEYA, 0},
|
||||
{MIFARE_AUTH_KEYB, 0},
|
||||
{MIFARE_CMD_INC, 6},
|
||||
{MIFARE_CMD_DEC, 6},
|
||||
{MIFARE_CMD_RESTORE, 6},
|
||||
{MIFARE_CMD_TRANSFER, 0}
|
||||
};
|
||||
|
||||
static const uint64_t g_mifare_default_keys[] = {
|
||||
0xffffffffffff, // Default key (first key used by program if no user defined key)
|
||||
0xa0a1a2a3a4a5, // NFCForum MAD key
|
||||
0xd3f7d3f7d3f7, // NDEF public key
|
||||
0x4b791bea7bcc, // MFC EV1 Signature 17 B
|
||||
0x5C8FF9990DA2, // MFC EV1 Signature 16 A
|
||||
0xD01AFEEB890A, // MFC EV1 Signature 16 B
|
||||
0x75CCB59C9BED, // MFC EV1 Signature 17 A
|
||||
0xfc00018778f7, // Public Transport
|
||||
0x6471a5ef2d1a, // SimonsVoss
|
||||
0x4E3552426B32, // ID06
|
||||
0x6A1987C40A21, // Salto
|
||||
0xef1232ab18a0, // Schlage
|
||||
0x3B7E4FD575AD, //
|
||||
0xb7bf0c13066e, // Gallagher
|
||||
0x135b88a94b8b, // Saflok
|
||||
0x2A2C13CC242A, // Dorma Kaba
|
||||
0x5a7a52d5e20d, // Bosch
|
||||
0x314B49474956, // VIGIK1 A
|
||||
0x564c505f4d41, // VIGIK1 B
|
||||
0x021209197591, // BTCINO
|
||||
0x484558414354, // Intratone
|
||||
0xEC0A9B1A9E06, // Vingcard
|
||||
0x66b31e64ca4b, // Vingcard
|
||||
0x97F5DA640B18, // Bangkok metro key
|
||||
0xA8844B0BCA06, // Metro Valencia key
|
||||
0xE4410EF8ED2D, // Armenian metro
|
||||
0x857464D3AAD1, // HTC Eindhoven key
|
||||
0x08B386463229, // troika
|
||||
0xe00000000000, // icopy
|
||||
0x199404281970, // NSP A
|
||||
0x199404281998, // NSP B
|
||||
0x6A1987C40A21, // SALTO
|
||||
0x7F33625BC129, // SALTO
|
||||
0x484944204953, // HID
|
||||
0x204752454154, // HID
|
||||
0x3B7E4FD575AD, // HID
|
||||
0x11496F97752A, // HID
|
||||
0x3E65E4FB65B3, // Gym
|
||||
0x000000000000, // Blank key
|
||||
0xb0b1b2b3b4b5,
|
||||
0xaabbccddeeff,
|
||||
0x1a2b3c4d5e6f,
|
||||
0x123456789abc,
|
||||
0x010203040506,
|
||||
0x123456abcdef,
|
||||
0xabcdef123456,
|
||||
0x4d3a99c351dd,
|
||||
0x1a982c7e459a,
|
||||
0x714c5c886e97,
|
||||
0x587ee5f9350f,
|
||||
0xa0478cc39091,
|
||||
0x533cb6c723f6,
|
||||
0x8fd0a4f256e9,
|
||||
0x0000014b5c31,
|
||||
0xb578f38a5c61,
|
||||
0x96a301bce267,
|
||||
};
|
||||
|
||||
|
||||
//static int global_counter = 0;
|
||||
static int global_found = 0;
|
||||
static int global_found_candidate = 0;
|
||||
static uint64_t global_candidate_key = 0;
|
||||
static int thread_count = 2;
|
||||
|
||||
static int param_getptr(const char *line, int *bg, int *en, int paramnum) {
|
||||
int i;
|
||||
int len = strlen(line);
|
||||
|
||||
*bg = 0;
|
||||
*en = 0;
|
||||
|
||||
// skip spaces
|
||||
while (line[*bg] == ' ' || line[*bg] == '\t')(*bg)++;
|
||||
if (*bg >= len) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
for (i = 0; i < paramnum; i++) {
|
||||
while (line[*bg] != ' ' && line[*bg] != '\t' && line[*bg] != '\0')(*bg)++;
|
||||
while (line[*bg] == ' ' || line[*bg] == '\t')(*bg)++;
|
||||
|
||||
if (line[*bg] == '\0') return 1;
|
||||
}
|
||||
|
||||
*en = *bg;
|
||||
while (line[*en] != ' ' && line[*en] != '\t' && line[*en] != '\0')(*en)++;
|
||||
|
||||
(*en)--;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int param_gethex_to_eol(const char *line, int paramnum, uint8_t *data, int maxdatalen, int *datalen) {
|
||||
int bg, en;
|
||||
uint32_t temp;
|
||||
char buf[5] = {0};
|
||||
|
||||
if (param_getptr(line, &bg, &en, paramnum)) return 1;
|
||||
|
||||
*datalen = 0;
|
||||
|
||||
int indx = bg;
|
||||
while (line[indx]) {
|
||||
if (line[indx] == '\t' || line[indx] == ' ') {
|
||||
indx++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isxdigit(line[indx])) {
|
||||
buf[strlen(buf) + 1] = 0x00;
|
||||
buf[strlen(buf)] = line[indx];
|
||||
} else {
|
||||
// if we have symbols other than spaces and hex
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (*datalen >= maxdatalen) {
|
||||
// if we don't have space in buffer and have symbols to translate
|
||||
return 2;
|
||||
}
|
||||
|
||||
if (strlen(buf) >= 2) {
|
||||
sscanf(buf, "%x", &temp);
|
||||
data[*datalen] = (uint8_t)(temp & 0xff);
|
||||
*buf = 0;
|
||||
(*datalen)++;
|
||||
}
|
||||
|
||||
indx++;
|
||||
}
|
||||
|
||||
if (strlen(buf) > 0)
|
||||
//error when not completed hex bytes
|
||||
return 3;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void hex_to_buffer(const uint8_t *buf, const uint8_t *hex_data, const size_t hex_len, const size_t hex_max_len,
|
||||
const size_t min_str_len, const size_t spaces_between, bool uppercase) {
|
||||
|
||||
if (buf == NULL) return;
|
||||
|
||||
char *tmp_base = (char *)buf;
|
||||
char *tmp = tmp_base;
|
||||
size_t i;
|
||||
|
||||
size_t max_len = (hex_len > hex_max_len) ? hex_max_len : hex_len;
|
||||
|
||||
for (i = 0; i < max_len; ++i, tmp += 2 + spaces_between) {
|
||||
snprintf(tmp, hex_max_len - (tmp - tmp_base), (uppercase) ? "%02X" : "%02x", (unsigned int) hex_data[i]);
|
||||
|
||||
for (size_t j = 0; j < spaces_between; j++)
|
||||
snprintf(tmp + 2 + j, hex_max_len - (2 + j + (tmp - tmp_base)), " ");
|
||||
}
|
||||
|
||||
i *= (2 + spaces_between);
|
||||
|
||||
size_t mlen = min_str_len > i ? min_str_len : 0;
|
||||
if (mlen > hex_max_len)
|
||||
mlen = hex_max_len;
|
||||
|
||||
for (; i < mlen; i++, tmp += 1)
|
||||
snprintf(tmp, hex_max_len - (tmp - tmp_base), " ");
|
||||
|
||||
// remove last space
|
||||
*tmp = '\0';
|
||||
}
|
||||
|
||||
static char *sprint_hex_inrow_ex(const uint8_t *data, const size_t len, const size_t min_str_len) {
|
||||
static char buf[100] = {0};
|
||||
hex_to_buffer((uint8_t *)buf, data, len, sizeof(buf) - 1, min_str_len, 0, true);
|
||||
return buf;
|
||||
}
|
||||
|
||||
static uint16_t parity_from_err(uint32_t data, uint16_t par_err) {
|
||||
|
||||
uint16_t par = 0;
|
||||
par |= odd_parity((data >> 24) & 0xFF) ^ ((par_err >> 12) & 1);
|
||||
par <<= 4;
|
||||
|
||||
par |= odd_parity((data >> 16) & 0xFF) ^ ((par_err >> 8) & 1);
|
||||
par <<= 4;
|
||||
|
||||
par |= odd_parity((data >> 8) & 0xFF) ^ ((par_err >> 4) & 1);
|
||||
par <<= 4;
|
||||
|
||||
par |= odd_parity(data & 0xFF) ^ (par_err & 1);
|
||||
return par;
|
||||
}
|
||||
|
||||
static uint16_t xored_bits(uint16_t nt_par, uint32_t ntenc, uint16_t ar_par, uint32_t arenc, uint16_t at_par, uint32_t atenc) {
|
||||
|
||||
uint16_t xored = 0;
|
||||
uint8_t par;
|
||||
|
||||
//1st (1st nt)
|
||||
par = (nt_par >> 12) & 1;
|
||||
xored |= par ^ ((ntenc >> 16) & 1);
|
||||
xored <<= 1;
|
||||
|
||||
//2nd (2nd nt)
|
||||
par = (nt_par >> 8) & 1;
|
||||
xored |= par ^ ((ntenc >> 8) & 1);
|
||||
xored <<= 1;
|
||||
|
||||
//3rd (3rd nt)
|
||||
par = (nt_par >> 4) & 1;
|
||||
xored |= par ^ (ntenc & 1);
|
||||
xored <<= 1;
|
||||
|
||||
//4th (1st ar)
|
||||
par = (ar_par >> 12) & 1;
|
||||
xored |= par ^ ((arenc >> 16) & 1);
|
||||
xored <<= 1;
|
||||
|
||||
//5th (2nd ar)
|
||||
par = (ar_par >> 8) & 1;
|
||||
xored |= par ^ ((arenc >> 8) & 1);
|
||||
xored <<= 1;
|
||||
|
||||
//6th (3rd ar)
|
||||
par = (ar_par >> 4) & 1;
|
||||
xored |= par ^ (arenc & 1);
|
||||
xored <<= 1;
|
||||
|
||||
//7th (4th ar)
|
||||
par = ar_par & 1;
|
||||
xored |= par ^ ((atenc >> 24) & 1);
|
||||
xored <<= 1;
|
||||
|
||||
//8th (1st at)
|
||||
par = (at_par >> 12) & 1;
|
||||
xored |= par ^ ((atenc >> 16) & 1);
|
||||
xored <<= 1;
|
||||
|
||||
//9th (2nd at)
|
||||
par = (at_par >> 8) & 1;
|
||||
xored |= par ^ ((atenc >> 8) & 1);
|
||||
xored <<= 1;
|
||||
|
||||
//10th (3rd at)
|
||||
par = (at_par >> 4) & 1;
|
||||
xored |= par ^ (atenc & 1);
|
||||
|
||||
return xored;
|
||||
}
|
||||
|
||||
static bool candidate_nonce(uint32_t xored, uint32_t nt, bool ev1) {
|
||||
uint8_t byte;
|
||||
|
||||
if (!ev1) {
|
||||
// 1st (1st nt)
|
||||
byte = (nt >> 24) & 0xFF;
|
||||
if (odd_parity(byte) ^ ((nt >> 16) & 1) ^ ((xored >> 9) & 1)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2nd (2nd nt)
|
||||
byte = (nt >> 16) & 0xFF;
|
||||
if (odd_parity(byte) ^ ((nt >> 8) & 1) ^ ((xored >> 8) & 1)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 3rd (3rd nt)
|
||||
byte = (nt >> 8) & 0xFF;
|
||||
if (odd_parity(byte) ^ (nt & 1) ^ ((xored >> 7) & 1)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t ar = prng_successor(nt, 64);
|
||||
|
||||
// 4th (1st ar)
|
||||
byte = (ar >> 24) & 0xFF;
|
||||
if (odd_parity(byte) ^ ((ar >> 16) & 1) ^ ((xored >> 6) & 1)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 5th (2nd ar)
|
||||
byte = (ar >> 16) & 0x0FF;
|
||||
if (odd_parity(byte) ^ ((ar >> 8) & 1) ^ ((xored >> 5) & 1)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 6th (3rd ar)
|
||||
byte = (ar >> 8) & 0xFF;
|
||||
if (odd_parity(byte) ^ (ar & 1) ^ ((xored >> 4) & 1)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t at = prng_successor(nt, 96);
|
||||
|
||||
// 7th (4th ar)
|
||||
byte = ar & 0xFF;
|
||||
if (odd_parity(byte) ^ ((at >> 24) & 1) ^ ((xored >> 3) & 1)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 8th (1st at)
|
||||
byte = (at >> 24) & 0xFF;
|
||||
if (odd_parity(byte) ^ ((at >> 16) & 1) ^ ((xored >> 2) & 1)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 9th (2nd at)
|
||||
byte = (at >> 16) & 0xFF;
|
||||
if (odd_parity(byte) ^ ((at >> 8) & 1) ^ ((xored >> 1) & 1)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 10th (3rd at)
|
||||
byte = (at >> 8) & 0xFF;
|
||||
if (odd_parity(byte) ^ (at & 1) ^ (xored & 1)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool checkValidCmd(uint32_t decrypted) {
|
||||
uint8_t cmd = (decrypted >> 24) & 0xFF;
|
||||
for (int i = 0; i < 8; ++i) {
|
||||
if (cmd == cmds[i][0]) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool checkValidCmdByte(uint8_t *cmd, uint16_t n) {
|
||||
// if we don't have enough data then this might be a false positive
|
||||
|
||||
if (cmd == NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < 8; ++i) {
|
||||
if (cmd[0] == cmds[i][0]) {
|
||||
|
||||
int res = 0;
|
||||
|
||||
if (n >= 4) {
|
||||
res = CheckCrc14443(CRC_14443_A, cmd, 4);
|
||||
}
|
||||
|
||||
if (res == 0 && cmds[i][1] > 0 && n >= cmds[i][1]) {
|
||||
res = CheckCrc14443(CRC_14443_A, cmd, cmds[i][1]);
|
||||
}
|
||||
|
||||
if (res) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool checkCRC(uint32_t decrypted) {
|
||||
uint8_t data[] = {
|
||||
(decrypted >> 24) & 0xFF,
|
||||
(decrypted >> 16) & 0xFF,
|
||||
(decrypted >> 8) & 0xFF,
|
||||
decrypted & 0xFF
|
||||
};
|
||||
return CheckCrc14443(CRC_14443_A, data, sizeof(data));
|
||||
}
|
||||
|
||||
static void *check_default_keys(void *arguments) {
|
||||
struct thread_key_args *args = (struct thread_key_args *) arguments;
|
||||
uint8_t local_enc[args->enc_len];
|
||||
memcpy(local_enc, args->enc, args->enc_len);
|
||||
|
||||
for (uint8_t i = 0; i < ARRAYLEN(g_mifare_default_keys); i++) {
|
||||
|
||||
uint64_t key = g_mifare_default_keys[i];
|
||||
|
||||
// Init cipher with key
|
||||
struct Crypto1State *pcs = crypto1_create(key);
|
||||
|
||||
// NESTED decrypt nt with help of new key
|
||||
crypto1_word(pcs, args->nt_enc ^ args->uid, 1);
|
||||
crypto1_word(pcs, args->nr_enc, 1);
|
||||
crypto1_word(pcs, 0, 0);
|
||||
crypto1_word(pcs, 0, 0);
|
||||
|
||||
// decrypt bytes
|
||||
uint8_t dec[args->enc_len];
|
||||
for (int j = 0; j < args->enc_len; j++) {
|
||||
dec[j] = crypto1_byte(pcs, 0x00, 0) ^ local_enc[j];
|
||||
}
|
||||
crypto1_destroy(pcs);
|
||||
|
||||
// check if cmd exists
|
||||
bool res = checkValidCmdByte(dec, args->enc_len);
|
||||
if (args->enc_len > 4) {
|
||||
res |= checkValidCmdByte(dec + 4, args->enc_len - 4);
|
||||
}
|
||||
|
||||
if (res == false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
__sync_fetch_and_add(&global_found, 1);
|
||||
|
||||
pthread_mutex_lock(&print_lock);
|
||||
printf("\nFound a default key!\n");
|
||||
printf("enc: %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("\nValid Key found [ " _GREEN_("%012" PRIx64) " ]\n\n", key);
|
||||
pthread_mutex_unlock(&print_lock);
|
||||
break;
|
||||
}
|
||||
free(args);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void *brute_thread(void *arguments) {
|
||||
|
||||
struct thread_args *args = (struct thread_args *) arguments;
|
||||
|
||||
struct Crypto1State *revstate = NULL;
|
||||
uint64_t key; // recovered key candidate
|
||||
uint32_t ks2; // keystream used to encrypt reader response
|
||||
uint32_t ks3; // keystream used to encrypt tag response
|
||||
uint32_t ks4; // keystream used to encrypt next command
|
||||
uint32_t nt; // current tag nonce
|
||||
|
||||
uint32_t p64 = 0;
|
||||
// TC == 4 (
|
||||
// threads calls 0 ev1 == false
|
||||
// threads calls 0,1,2 ev1 == true
|
||||
for (uint32_t count = args->idx; count <= 0xFFFF; count += thread_count) {
|
||||
|
||||
if (__atomic_load_n(&global_found, __ATOMIC_ACQUIRE) == 1) {
|
||||
break;
|
||||
}
|
||||
|
||||
nt = count << 16 | prng_successor(count, 16);
|
||||
|
||||
if (candidate_nonce(args->xored, nt, args->ev1) == false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
p64 = prng_successor(nt, 64);
|
||||
ks2 = ar_enc ^ p64;
|
||||
ks3 = at_enc ^ prng_successor(p64, 32);
|
||||
revstate = lfsr_recovery64(ks2, ks3);
|
||||
ks4 = crypto1_word(revstate, 0, 0);
|
||||
|
||||
if (ks4 == 0) {
|
||||
free(revstate);
|
||||
continue;
|
||||
}
|
||||
|
||||
// lock this section to avoid interlacing prints from different threats
|
||||
pthread_mutex_lock(&print_lock);
|
||||
if (args->ev1) {
|
||||
printf("\n---> " _YELLOW_(" Possible key candidate")" <---\n");
|
||||
}
|
||||
|
||||
#if 0
|
||||
printf("thread #%d idx %d %s\n", args->thread, args->idx, (args->ev1) ? "(Ev1)" : "");
|
||||
printf("current nt(%08x) ar_enc(%08x) at_enc(%08x)\n", nt, ar_enc, at_enc);
|
||||
printf("ks2:%08x\n", ks2);
|
||||
printf("ks3:%08x\n", ks3);
|
||||
printf("ks4:%08x\n", ks4);
|
||||
#endif
|
||||
if (cmd_enc) {
|
||||
uint32_t decrypted = ks4 ^ cmd_enc;
|
||||
printf("CMD enc( %08x )\n", cmd_enc);
|
||||
printf(" dec( %08x ) ", decrypted);
|
||||
|
||||
// check if cmd exists
|
||||
uint8_t isOK = checkValidCmd(decrypted);
|
||||
if (isOK == false) {
|
||||
printf(_RED_("<-- not a valid cmd\n"));
|
||||
pthread_mutex_unlock(&print_lock);
|
||||
free(revstate);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Add a crc-check.
|
||||
isOK = checkCRC(decrypted);
|
||||
if (isOK == false) {
|
||||
printf(_RED_("<-- not a valid crc\n"));
|
||||
pthread_mutex_unlock(&print_lock);
|
||||
free(revstate);
|
||||
continue;
|
||||
}
|
||||
|
||||
printf("<-- " _GREEN_("valid cmd") "\n");
|
||||
}
|
||||
|
||||
lfsr_rollback_word(revstate, 0, 0);
|
||||
lfsr_rollback_word(revstate, 0, 0);
|
||||
lfsr_rollback_word(revstate, 0, 0);
|
||||
lfsr_rollback_word(revstate, nr_enc, 1);
|
||||
lfsr_rollback_word(revstate, uid ^ nt, 0);
|
||||
crypto1_get_lfsr(revstate, &key);
|
||||
free(revstate);
|
||||
|
||||
if (args->ev1) {
|
||||
// if it was EV1, we know for sure xxxAAAAAAAA recovery
|
||||
printf("\nKey candidate [ " _YELLOW_("....%08" PRIx64)" ]\n\n", key & 0xFFFFFFFF);
|
||||
__sync_fetch_and_add(&global_found_candidate, 1);
|
||||
} else {
|
||||
printf("\nKey candidate [ " _GREEN_("....%08" PRIx64) " ]", key & 0xFFFFFFFF);
|
||||
printf("\nKey candidate [ " _GREEN_("%12" PRIx64) " ]\n\n", key);
|
||||
__sync_fetch_and_add(&global_found, 1);
|
||||
}
|
||||
// release lock
|
||||
pthread_mutex_unlock(&print_lock);
|
||||
__sync_fetch_and_add(&global_candidate_key, key);
|
||||
break;
|
||||
}
|
||||
free(args);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Bruteforce the upper 16 bits of the key
|
||||
static void *brute_key_thread(void *arguments) {
|
||||
|
||||
struct thread_key_args *args = (struct thread_key_args *) arguments;
|
||||
uint8_t local_enc[args->enc_len];
|
||||
memcpy(local_enc, args->enc, args->enc_len);
|
||||
|
||||
for (uint64_t count = args->idx; count <= 0xFFFF; count += thread_count) {
|
||||
|
||||
uint64_t key = args->part_key | (count << 32);
|
||||
|
||||
// Init cipher with key
|
||||
struct Crypto1State *pcs = crypto1_create(key);
|
||||
|
||||
// NESTED decrypt nt with help of new key
|
||||
crypto1_word(pcs, args->nt_enc ^ args->uid, 1);
|
||||
crypto1_word(pcs, args->nr_enc, 1);
|
||||
crypto1_word(pcs, 0, 0);
|
||||
crypto1_word(pcs, 0, 0);
|
||||
|
||||
// decrypt 22 bytes
|
||||
uint8_t dec[args->enc_len];
|
||||
for (int i = 0; i < args->enc_len; i++) {
|
||||
dec[i] = crypto1_byte(pcs, 0x00, 0) ^ local_enc[i];
|
||||
}
|
||||
|
||||
crypto1_destroy(pcs);
|
||||
|
||||
// check if cmd exists
|
||||
if (checkValidCmdByte(dec, args->enc_len) == false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
__sync_fetch_and_add(&global_found_candidate, 1);
|
||||
|
||||
// lock this section to avoid interlacing prints from different threats
|
||||
pthread_mutex_lock(&print_lock);
|
||||
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));
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&print_lock);
|
||||
}
|
||||
free(args);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int usage(void) {
|
||||
printf("\n");
|
||||
printf("syntax: mf_nonce_brute <uid> <nt> <nt_par_err> <nr> <ar> <ar_par_err> <at> <at_par_err> [<next_command>]\n\n");
|
||||
printf("how to convert trace data to needed input:\n");
|
||||
printf(" nt in trace = 8c! 42 e6! 4e!\n");
|
||||
printf(" nt = 8c42e64e\n");
|
||||
printf(" nt_par_err = 1011\n\n");
|
||||
printf("samples:\n");
|
||||
printf("\n");
|
||||
printf(" ./mf_nonce_brute fa247164 fb47c594 0000 71909d28 0c254817 1000 0dc7cfbd 1110\n");
|
||||
printf("\n");
|
||||
printf("**** Possible key candidate ****\n");
|
||||
printf("Key candidate: [....ffffffff]\n");
|
||||
printf("Too few next cmd bytes, skipping phase 2\n");
|
||||
printf("\n");
|
||||
printf(" ./mf_nonce_brute 96519578 d7e3c6ac 0011 cd311951 9da49e49 0010 2bb22e00 0100 a4f7f398ebdb4e484d1cb2b174b939d18b469f3fa5d9caab\n");
|
||||
printf("\n");
|
||||
printf("enc: A4F7F398EBDB4E484D1CB2B174B939D18B469F3FA5D9CAABBFA018EC7E0CC5721DE2E590F64BD0A5B4EFCE71\n");
|
||||
printf("dec: 30084A24302F8102F44CA5020500A60881010104763930084A24302F8102F44CA5020500A608810101047639\n");
|
||||
printf("Valid Key found: [3b7e4fd575ad]\n\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
int main(int argc, const char *argv[]) {
|
||||
printf("\nMifare classic nested auth key recovery\n\n");
|
||||
|
||||
if (argc < 9) return usage();
|
||||
|
||||
sscanf(argv[1], "%x", &uid);
|
||||
sscanf(argv[2], "%x", &nt_enc);
|
||||
sscanf(argv[3], "%x", &nt_par_err);
|
||||
sscanf(argv[4], "%x", &nr_enc);
|
||||
sscanf(argv[5], "%x", &ar_enc);
|
||||
sscanf(argv[6], "%x", &ar_par_err);
|
||||
sscanf(argv[7], "%x", &at_enc);
|
||||
sscanf(argv[8], "%x", &at_par_err);
|
||||
|
||||
// next encrypted command + a full read/write
|
||||
int enc_len = 0;
|
||||
uint8_t enc[ENC_LEN] = {0};
|
||||
if (argc > 9) {
|
||||
param_gethex_to_eol(argv[9], 0, enc, sizeof(enc), &enc_len);
|
||||
cmd_enc = (enc[0] << 24 | enc[1] << 16 | enc[2] << 8 | enc[3]);
|
||||
}
|
||||
|
||||
printf("----------- " _CYAN_("information") " ------------------------\n");
|
||||
printf("uid.................. %08x\n", uid);
|
||||
printf("nt encrypted......... %08x\n", nt_enc);
|
||||
printf("nt parity err........ %04x\n", nt_par_err);
|
||||
printf("nr encrypted......... %08x\n", nr_enc);
|
||||
printf("ar encrypted......... %08x\n", ar_enc);
|
||||
printf("ar parity err........ %04x\n", ar_par_err);
|
||||
printf("at encrypted......... %08x\n", at_enc);
|
||||
printf("at parity err........ %04x\n", at_par_err);
|
||||
|
||||
if (argc > 9) {
|
||||
printf("next encrypted cmd... %s\n", sprint_hex_inrow_ex(enc, enc_len, 0));
|
||||
}
|
||||
|
||||
uint64_t t1 = msclock();
|
||||
uint16_t nt_par = parity_from_err(nt_enc, nt_par_err);
|
||||
uint16_t ar_par = parity_from_err(ar_enc, ar_par_err);
|
||||
uint16_t at_par = parity_from_err(at_enc, at_par_err);
|
||||
|
||||
// calc (parity XOR corresponding nonce bit encoded with the same keystream bit)
|
||||
uint16_t xored = xored_bits(nt_par, nt_enc, ar_par, ar_enc, at_par, at_enc);
|
||||
|
||||
#if !defined(_WIN32) || !defined(__WIN32__)
|
||||
thread_count = sysconf(_SC_NPROCESSORS_CONF);
|
||||
if (thread_count < 2)
|
||||
thread_count = 2;
|
||||
#endif /* _WIN32 */
|
||||
|
||||
printf("\nBruteforce using " _YELLOW_("%d") " threads\n\n", thread_count);
|
||||
|
||||
pthread_t threads[thread_count];
|
||||
|
||||
// create a mutex to avoid interlacing print commands from our different threads
|
||||
pthread_mutex_init(&print_lock, NULL);
|
||||
|
||||
// if we have 4 or more bytes, look for a default key
|
||||
if (enc_len > 3) {
|
||||
printf("----------- " _CYAN_("Phase 1 pre-processing") " ------------------------\n");
|
||||
printf("Testing default keys using NESTED authentication...\n");
|
||||
struct thread_key_args *def = calloc(1, sizeof(struct thread_key_args));
|
||||
def->thread = 0;
|
||||
def->idx = 0;
|
||||
def->uid = uid;
|
||||
def->nt_enc = nt_enc;
|
||||
def->nr_enc = nr_enc;
|
||||
def->enc_len = enc_len;
|
||||
memcpy(def->enc, enc, enc_len);
|
||||
pthread_create(&threads[0], NULL, check_default_keys, (void *)def);
|
||||
pthread_join(threads[0], NULL);
|
||||
if (global_found) {
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
printf("\n----------- " _CYAN_("Phase 2 examine") " -------------------------------\n");
|
||||
printf("Looking for the last bytes of the encrypted tagnonce\n");
|
||||
printf("\nTarget old MFC...\n");
|
||||
// the rest of available threads to EV1 scenario
|
||||
for (int i = 0; i < thread_count; ++i) {
|
||||
struct thread_args *a = calloc(1, sizeof(struct thread_args));
|
||||
a->xored = xored;
|
||||
a->thread = i;
|
||||
a->idx = i;
|
||||
a->ev1 = false;
|
||||
pthread_create(&threads[i], NULL, brute_thread, (void *)a);
|
||||
}
|
||||
|
||||
// wait for threads to terminate:
|
||||
for (int i = 0; i < thread_count; ++i) {
|
||||
pthread_join(threads[i], NULL);
|
||||
}
|
||||
|
||||
t1 = msclock() - t1;
|
||||
printf("execution time " _YELLOW_("%.2f") " sec\n", (float)t1 / 1000.0);
|
||||
|
||||
if (!global_found && !global_found_candidate) {
|
||||
printf("\nTarget MFC Ev1...\n");
|
||||
|
||||
t1 = msclock();
|
||||
// the rest of available threads to EV1 scenario
|
||||
for (int i = 0; i < thread_count; ++i) {
|
||||
struct thread_args *a = calloc(1, sizeof(struct thread_args));
|
||||
a->xored = xored;
|
||||
a->thread = i;
|
||||
a->idx = i;
|
||||
a->ev1 = true;
|
||||
pthread_create(&threads[i], NULL, brute_thread, (void *)a);
|
||||
}
|
||||
|
||||
// wait for threads to terminate:
|
||||
for (int i = 0; i < thread_count; ++i) {
|
||||
pthread_join(threads[i], NULL);
|
||||
}
|
||||
|
||||
t1 = msclock() - t1;
|
||||
printf("execution time " _YELLOW_("%.2f") " sec\n", (float)t1 / 1000.0);
|
||||
|
||||
|
||||
if (!global_found && !global_found_candidate) {
|
||||
printf("\nFailed to find a key\n\n");
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
if (enc_len < 4) {
|
||||
printf("Too few next cmd bytes, skipping phase 3\n\n");
|
||||
goto out;
|
||||
}
|
||||
|
||||
// reset thread signals
|
||||
global_found_candidate = 0;
|
||||
|
||||
printf("\n----------- " _CYAN_("Phase 3 validating") " ----------------------------\n");
|
||||
printf("uid.................. %08x\n", uid);
|
||||
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("nr enc............... %08x\n", nr_enc);
|
||||
printf("next encrypted cmd... %s\n", sprint_hex_inrow_ex(enc, enc_len, 0));
|
||||
printf("\nLooking for the upper 16 bits of the key\n");
|
||||
fflush(stdout);
|
||||
|
||||
// threads
|
||||
for (int i = 0; i < thread_count; ++i) {
|
||||
struct thread_key_args *b = calloc(1, sizeof(struct thread_key_args));
|
||||
b->thread = i;
|
||||
b->idx = i;
|
||||
b->uid = uid;
|
||||
b->part_key = (uint32_t)(global_candidate_key & 0xFFFFFFFF);
|
||||
b->nt_enc = nt_enc;
|
||||
b->nr_enc = nr_enc;
|
||||
b->enc_len = enc_len;
|
||||
memcpy(b->enc, enc, enc_len);
|
||||
pthread_create(&threads[i], NULL, brute_key_thread, (void *)b);
|
||||
}
|
||||
|
||||
// wait for threads to terminate:
|
||||
for (int i = 0; i < thread_count; ++i) {
|
||||
pthread_join(threads[i], NULL);
|
||||
}
|
||||
|
||||
|
||||
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:
|
||||
// clean up mutex
|
||||
pthread_mutex_destroy(&print_lock);
|
||||
return 0;
|
||||
}
|
205
tools/mfc/card_reader/mf_nonce_brute_examples.md
Normal file
205
tools/mfc/card_reader/mf_nonce_brute_examples.md
Normal file
|
@ -0,0 +1,205 @@
|
|||
mf_nonce_brute
|
||||
==============
|
||||
|
||||
Nested autenticated sector key recovery tool
|
||||
-----------------------------------------------
|
||||
|
||||
Compatible tags:
|
||||
* Mifare Classic 1k (4k)
|
||||
* Mifare Plus in SL1 mode
|
||||
|
||||
To recover keys to nested autenticated sectors you need a reader-card communication log. To get it use
|
||||
hardware tools that able to sniff communication (for example Proxmark3 or HydraNFC).
|
||||
|
||||
This enhanced version:
|
||||
First 2 bytes should be bruteforced in phase 2 with mf_key_brute tool that interacts with a card.
|
||||
|
||||
Sample trace:
|
||||
```
|
||||
93 70 fd ac f6 d8 7f 21 4f // select card with UID fdacf6d8
|
||||
TAG 08 b6 dd // sak
|
||||
60 04 d1 3d // wanna auth block 0x04 with A key
|
||||
TAG ed 12 9c 74 // 1st auth clear text nt
|
||||
55 53 9f cc 41 8d e8 f3 // nr', ar' (nr^ks1, ar^ks2 )
|
||||
TAG 05 49 e1 65 // at' ( at^ks3 )
|
||||
03 24 26 56 // wanna read block 0x04
|
||||
TAG ac 69 ef 58 45 e1 c2 1d a9 47 a5 94 54 ef 5d c7 1e a9 // block 0x04 content
|
||||
d4 3e a8 aa
|
||||
TAG 8e 8e e3 e6 e9 e2 5f dd f6 08 ce fb 02 6a db 75 94 2f
|
||||
79 77 68 3c
|
||||
TAG e0 00 00 80 80 08 cc 80 08 9c 82 e0 68 64 60 30 91 60 // 18 bytes = 16 byte content + 2 bytes crc
|
||||
ea 88 c3 c2 // 4 byte read cmd
|
||||
TAG a3 76 dc df c1 42 e0 ee c6 75 a4 ca eb 0c da eb 46 a0 // 18 bytes = 16 byte content + 2 bytes crc ks8 + crc
|
||||
2d 27 ab 6f // wanna auth to 0x04 block with key B
|
||||
|
||||
-------Until this line we can recover key or decrypt communication with no troubles (see mfkey64 tool)--------------------------------
|
||||
|
||||
TAG 52 6e af 8b // nested auth encrypted tag nonce that we don't know
|
||||
8e 21 3a 29 a4 80 7e 02 // nr_enc = nr^ks1, ar_enc = ar^ks2
|
||||
TAG b9 43 74 8d // at_enc = at^ks3
|
||||
e2 25 f8 32 // probably next command (actually is read block cmd, but we don't know it yet)
|
||||
TAG 1f 26 82 8d 12 21 dd 42 c2 84 3e d0 26 7f 6b 2a 81 a9 // probably data
|
||||
ba 85 1d 36 // probably read cmd
|
||||
TAG 62 a8 78 69 ee 36 22 16 1c ff 4b 4e 69 cb 27 c2 e8 7e // probably data
|
||||
a7 b1 c8 da // probably read cmd
|
||||
TAG b2 fc 6c 65 60 ec 35 83 87 56 e3 7e 3c bf 38 b8 73 21 // probably data
|
||||
99 92 13 55 // probably read cmd
|
||||
TAG 93 5b 65 a3 1d 8c 75 b8 3a 63 e2 31 f0 d0 a9 24 9a f6 // probably data
|
||||
```
|
||||
|
||||
|
||||
Phase 1
|
||||
-------
|
||||
|
||||
Syntax:
|
||||
`mf_nonce_brute <uid> <{nt}> <nt_par_err> <{nr}> <{ar}> <ar_par_err> <{at}> <at_par_err> [<{next_command}>]`
|
||||
|
||||
Example: if `nt` in trace is `8c! 42 e6! 4e!`, then `nt` is `8c42e64e` and `nt_par_err` is `1011`
|
||||
|
||||
Example with parity (from this trace http://www.proxmark.org/forum/viewtopic.php?pid=550#p550) :
|
||||
|
||||
```
|
||||
+ 561882: 1 : 26
|
||||
+ 64: 2 : TAG 04 00
|
||||
+ 10217: 2 : 93 20
|
||||
+ 64: 5 : TAG 9c 59 9b 32 6c UID
|
||||
+ 12313: 9 : 93 70 9c 59 9b 32 6c 6b 30
|
||||
+ 64: 3 : TAG 08 b6 dd
|
||||
+ 923318: 4 : 60 00 f5 7b AUTH Block 0
|
||||
+ 112: 4 : TAG 82 a4 16 6c Nonce Tag (NT)
|
||||
+ 6985: 8 : a1 e4! 58 ce! 6e ea! 41 e0! NR , AR
|
||||
+ 64: 4 : TAG 5c! ad f4 39! AT
|
||||
+ 811513: 4 : 8e 0e! 5d! b9 AUTH Block 0 (nested)
|
||||
+ 112: 4 : TAG 5a! 92 0d! 85! Nonce Tag (NT)
|
||||
+ 6946: 8 : 98! d7 6b! 77 d6 c6 e8 70 NR , AR
|
||||
+ 64: 4 : TAG ca 7e! 0b! 63! AT
|
||||
+ 670868: 4 : 3e! 70 9c! 8a
|
||||
+ 112: 4 : TAG 36! 41 24! 79
|
||||
+ 9505: 8 : 1b! 8c 3a! 48! 83 5a 4a! 27
|
||||
+ 64: 4 : TAG 40! 6a! 99! 4b
|
||||
+ 905612: 4 : c9 7c 64! 13! !crc
|
||||
+ 112: 4 : TAG b5! ab! 1d! 2b
|
||||
+ 6936: 8 : 7e! d2 5c! ca! 4b! 50! 88! c4 !crc
|
||||
+ 64: 4 : TAG bf dd 01 be!
|
||||
+ 987853: 4 : 56 98 49 d6! !crc
|
||||
```
|
||||
=>
|
||||
```
|
||||
./mf_nonce_brute 9c599b32 82a4166c 0000 a1e458ce 6eea41e0 0101 5cadf439 1001 8e0e5db9
|
||||
| | | | | | | | |
|
||||
+UID +nt_enc | +nr_enc +ar_enc | +at_enc | +encrypted next cmd
|
||||
+nt_par_err +at_par_err +at_par_err
|
||||
```
|
||||
|
||||
These two taken from above use the plaintext tagnonce `nt`=`82a4166c`, they still find a possible key candidate.
|
||||
```
|
||||
./mf_nonce_brute 9c599b32 82a4166c 0000 a1e458ce 6eea41e0 0101 5cadf439 1001
|
||||
./mf_nonce_brute 9c599b32 82a4166c 0000 98d76b77 d6c6e870 0000 ca7e0b63 0111
|
||||
```
|
||||
|
||||
This one uses the encrypted tagnonce `nt`=`5a920d85`, it finds a valid key.
|
||||
```
|
||||
./mf_nonce_brute 9c599b32 5a920d85 1011 98d76b77 d6c6e870 0000 ca7e0b63 0111
|
||||
```
|
||||
|
||||
This one uses the encrypted tagnonce `nt`=`5a920d85` and the encrypted cmd `3e709c8a` to validate , it finds a valid key.
|
||||
```
|
||||
./mf_nonce_brute 9c599b32 5a920d85 1011 98d76b77 d6c6e870 0000 ca7e0b63 0111 3e709c8a
|
||||
```
|
||||
Full output:
|
||||
```
|
||||
./mf_nonce_brute 9c599b32 5a920d85 1011 98d76b77 d6c6e870 0000 ca7e0b63 0111 3e709c8a
|
||||
Mifare classic nested auth key recovery. Phase 1.
|
||||
-------------------------------------------------
|
||||
uid: 9c599b32
|
||||
nt encrypted: 5a920d85
|
||||
nt parity err: 1011
|
||||
nr encrypted: 98d76b77
|
||||
ar encrypted: d6c6e870
|
||||
ar parity err: 0000
|
||||
at encrypted: ca7e0b63
|
||||
at parity err: 0111
|
||||
next cmd enc: 3e709c8a
|
||||
|
||||
|
||||
Starting 4 threads to bruteforce encrypted tag nonce last bytes
|
||||
CMD enc(3e709c8a)
|
||||
dec(6000f57b) <-- Valid cmd
|
||||
|
||||
Valid Key found: [ffffffffffff]
|
||||
|
||||
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
|
||||
|
||||
```
|
||||
|
319
tools/mfc/card_reader/mf_trace_brute.c
Normal file
319
tools/mfc/card_reader/mf_trace_brute.c
Normal file
|
@ -0,0 +1,319 @@
|
|||
//
|
||||
// bruteforce the upper 16bits of a partial key recovered from mf_nonce_brute.
|
||||
// J-run's original idea was a two part recovery vector with first a offline trace and then online for 2 bytes.
|
||||
//
|
||||
// This idea is two use only offline, to recover a nested authentication key.
|
||||
// Assumption, we get a read/write command after a nested auth, we need 22 bytes of data.
|
||||
// Iceman, 2021,
|
||||
//
|
||||
|
||||
#define __STDC_FORMAT_MACROS
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <stdio.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#include <pthread.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include "ctype.h"
|
||||
#include "crapto1/crapto1.h"
|
||||
#include "protocol.h"
|
||||
#include "iso14443crc.h"
|
||||
#include <util_posix.h>
|
||||
|
||||
#define AEND "\x1b[0m"
|
||||
#define _RED_(s) "\x1b[31m" s AEND
|
||||
#define _GREEN_(s) "\x1b[32m" s AEND
|
||||
#define _YELLOW_(s) "\x1b[33m" s AEND
|
||||
#define _CYAN_(s) "\x1b[36m" s AEND
|
||||
|
||||
// a global mutex to prevent interlaced printing from different threads
|
||||
pthread_mutex_t print_lock;
|
||||
|
||||
#define ENC_LEN (4 + 16 + 2)
|
||||
//--------------------- define options here
|
||||
|
||||
typedef struct thread_args {
|
||||
int thread;
|
||||
int idx;
|
||||
uint32_t uid;
|
||||
uint32_t part_key;
|
||||
uint32_t nt_enc;
|
||||
uint32_t nr_enc;
|
||||
uint16_t enc_len;
|
||||
uint8_t enc[ENC_LEN]; // next encrypted command + a full read/write
|
||||
} targs;
|
||||
|
||||
//------------------------------------------------------------------
|
||||
uint8_t cmds[8][2] = {
|
||||
{ISO14443A_CMD_READBLOCK, 18},
|
||||
{ISO14443A_CMD_WRITEBLOCK, 18},
|
||||
{MIFARE_AUTH_KEYA, 0},
|
||||
{MIFARE_AUTH_KEYB, 0},
|
||||
{MIFARE_CMD_INC, 6},
|
||||
{MIFARE_CMD_DEC, 6},
|
||||
{MIFARE_CMD_RESTORE, 6},
|
||||
{MIFARE_CMD_TRANSFER, 0}
|
||||
};
|
||||
|
||||
static int global_found = 0;
|
||||
static int thread_count = 2;
|
||||
|
||||
static int param_getptr(const char *line, int *bg, int *en, int paramnum) {
|
||||
int i;
|
||||
int len = strlen(line);
|
||||
|
||||
*bg = 0;
|
||||
*en = 0;
|
||||
|
||||
// skip spaces
|
||||
while (line[*bg] == ' ' || line[*bg] == '\t')(*bg)++;
|
||||
if (*bg >= len) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
for (i = 0; i < paramnum; i++) {
|
||||
while (line[*bg] != ' ' && line[*bg] != '\t' && line[*bg] != '\0')(*bg)++;
|
||||
while (line[*bg] == ' ' || line[*bg] == '\t')(*bg)++;
|
||||
|
||||
if (line[*bg] == '\0') return 1;
|
||||
}
|
||||
|
||||
*en = *bg;
|
||||
while (line[*en] != ' ' && line[*en] != '\t' && line[*en] != '\0')(*en)++;
|
||||
|
||||
(*en)--;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int param_gethex_to_eol(const char *line, int paramnum, uint8_t *data, int maxdatalen, int *datalen) {
|
||||
int bg, en;
|
||||
uint32_t temp;
|
||||
char buf[5] = {0};
|
||||
|
||||
if (param_getptr(line, &bg, &en, paramnum)) return 1;
|
||||
|
||||
*datalen = 0;
|
||||
|
||||
int indx = bg;
|
||||
while (line[indx]) {
|
||||
if (line[indx] == '\t' || line[indx] == ' ') {
|
||||
indx++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isxdigit(line[indx])) {
|
||||
buf[strlen(buf) + 1] = 0x00;
|
||||
buf[strlen(buf)] = line[indx];
|
||||
} else {
|
||||
// if we have symbols other than spaces and hex
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (*datalen >= maxdatalen) {
|
||||
// if we don't have space in buffer and have symbols to translate
|
||||
return 2;
|
||||
}
|
||||
|
||||
if (strlen(buf) >= 2) {
|
||||
sscanf(buf, "%x", &temp);
|
||||
data[*datalen] = (uint8_t)(temp & 0xff);
|
||||
*buf = 0;
|
||||
(*datalen)++;
|
||||
}
|
||||
|
||||
indx++;
|
||||
}
|
||||
|
||||
if (strlen(buf) > 0)
|
||||
//error when not completed hex bytes
|
||||
return 3;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void hex_to_buffer(const uint8_t *buf, const uint8_t *hex_data, const size_t hex_len, const size_t hex_max_len,
|
||||
const size_t min_str_len, const size_t spaces_between, bool uppercase) {
|
||||
|
||||
if (buf == NULL) return;
|
||||
|
||||
char *tmp_base = (char *)buf;
|
||||
char *tmp = tmp_base;
|
||||
size_t i;
|
||||
|
||||
size_t max_len = (hex_len > hex_max_len) ? hex_max_len : hex_len;
|
||||
|
||||
for (i = 0; i < max_len; ++i, tmp += 2 + spaces_between) {
|
||||
snprintf(tmp, hex_max_len - (tmp - tmp_base), (uppercase) ? "%02X" : "%02x", (unsigned int) hex_data[i]);
|
||||
|
||||
for (size_t j = 0; j < spaces_between; j++)
|
||||
snprintf(tmp + 2 + j, hex_max_len - (2 + j + (tmp - tmp_base)), " ");
|
||||
}
|
||||
|
||||
i *= (2 + spaces_between);
|
||||
|
||||
size_t mlen = min_str_len > i ? min_str_len : 0;
|
||||
if (mlen > hex_max_len)
|
||||
mlen = hex_max_len;
|
||||
|
||||
for (; i < mlen; i++, tmp += 1)
|
||||
snprintf(tmp, hex_max_len - (tmp - tmp_base), " ");
|
||||
|
||||
// remove last space
|
||||
*tmp = '\0';
|
||||
}
|
||||
|
||||
static char *sprint_hex_inrow_ex(const uint8_t *data, const size_t len, const size_t min_str_len) {
|
||||
static char buf[100] = {0};
|
||||
hex_to_buffer((uint8_t *)buf, data, len, sizeof(buf) - 1, min_str_len, 0, true);
|
||||
return buf;
|
||||
}
|
||||
|
||||
static bool checkValidCmdByte(uint8_t *cmd, uint16_t n) {
|
||||
|
||||
bool ok = false;
|
||||
if (cmd == NULL)
|
||||
return false;
|
||||
for (int i = 0; i < 8; ++i) {
|
||||
if (cmd[0] == cmds[i][0]) {
|
||||
|
||||
if (n >= 4)
|
||||
ok = CheckCrc14443(CRC_14443_A, cmd, 4);
|
||||
|
||||
if (cmds[i][1] > 0 && n >= cmds[i][1])
|
||||
ok = CheckCrc14443(CRC_14443_A, cmd + 4, cmds[i][1]);
|
||||
|
||||
if (ok) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static void *brute_thread(void *arguments) {
|
||||
|
||||
struct thread_args *args = (struct thread_args *) arguments;
|
||||
uint64_t key = args->part_key;
|
||||
uint8_t local_enc[args->enc_len];
|
||||
memcpy(local_enc, args->enc, args->enc_len);
|
||||
|
||||
for (uint64_t count = args->idx; count < 0xFFFF; count += thread_count) {
|
||||
|
||||
if (__atomic_load_n(&global_found, __ATOMIC_ACQUIRE) == 1) {
|
||||
break;
|
||||
}
|
||||
|
||||
key |= count << 32;
|
||||
|
||||
// Init cipher with key
|
||||
struct Crypto1State *pcs = crypto1_create(key);
|
||||
|
||||
// NESTED decrypt nt with help of new key
|
||||
crypto1_word(pcs, args->nt_enc ^ args->uid, 1);
|
||||
crypto1_word(pcs, args->nr_enc, 1);
|
||||
crypto1_word(pcs, 0, 0);
|
||||
crypto1_word(pcs, 0, 0);
|
||||
|
||||
// decrypt 22 bytes
|
||||
uint8_t dec[args->enc_len];
|
||||
for (int i = 0; i < args->enc_len; i++)
|
||||
dec[i] = crypto1_byte(pcs, 0x00, 0) ^ local_enc[i];
|
||||
|
||||
crypto1_destroy(pcs);
|
||||
|
||||
if (checkValidCmdByte(dec, args->enc_len) == false) {
|
||||
continue;
|
||||
}
|
||||
__sync_fetch_and_add(&global_found, 1);
|
||||
|
||||
// lock this section to avoid interlacing prints from different threats
|
||||
pthread_mutex_lock(&print_lock);
|
||||
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("\nValid Key found [ " _GREEN_("%012" PRIx64) " ]\n\n", key);
|
||||
pthread_mutex_unlock(&print_lock);
|
||||
break;
|
||||
}
|
||||
|
||||
free(args);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int usage(void) {
|
||||
printf(" syntax: mf_trace_brute <uid> <partial key> <nt enc> <nr enc> [<next_command + 18 bytes>]\n\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
int main(int argc, const char *argv[]) {
|
||||
printf("Mifare classic nested auth key recovery Phase 2\n");
|
||||
if (argc < 3) return usage();
|
||||
|
||||
uint32_t uid = 0; // serial number
|
||||
uint32_t part_key = 0; // last 4 keys of key
|
||||
uint32_t nt_enc = 0; // noncce tag
|
||||
uint32_t nr_enc = 0; // nonce reader encrypted
|
||||
|
||||
sscanf(argv[1], "%x", &uid);
|
||||
sscanf(argv[2], "%x", &part_key);
|
||||
sscanf(argv[3], "%x", &nt_enc);
|
||||
sscanf(argv[4], "%x", &nr_enc);
|
||||
|
||||
int enc_len = 0;
|
||||
uint8_t enc[ENC_LEN] = {0}; // next encrypted command + a full read/write
|
||||
param_gethex_to_eol(argv[5], 0, enc, sizeof(enc), &enc_len);
|
||||
|
||||
printf("-------------------------------------------------\n");
|
||||
printf("uid.................. %08x\n", uid);
|
||||
printf("partial key.......... %08x\n", part_key);
|
||||
printf("nt enc............... %08x\n", nt_enc);
|
||||
printf("nr enc............... %08x\n", nr_enc);
|
||||
printf("next encrypted cmd... %s\n", sprint_hex_inrow_ex(enc, enc_len, 0));
|
||||
|
||||
uint64_t t1 = msclock();
|
||||
|
||||
#if !defined(_WIN32) || !defined(__WIN32__)
|
||||
thread_count = sysconf(_SC_NPROCESSORS_CONF);
|
||||
if (thread_count < 2)
|
||||
thread_count = 2;
|
||||
#endif /* _WIN32 */
|
||||
|
||||
printf("\nBruteforce using %d threads to find upper 16bits of key\n", thread_count);
|
||||
|
||||
pthread_t threads[thread_count];
|
||||
|
||||
// create a mutex to avoid interlacing print commands from our different threads
|
||||
pthread_mutex_init(&print_lock, NULL);
|
||||
|
||||
// threads
|
||||
for (int i = 0; i < thread_count; ++i) {
|
||||
struct thread_args *a = calloc(1, sizeof(struct thread_args));
|
||||
a->thread = i;
|
||||
a->idx = i;
|
||||
a->uid = uid;
|
||||
a->part_key = part_key;
|
||||
a->nt_enc = nt_enc;
|
||||
a->nr_enc = nr_enc;
|
||||
a->enc_len = enc_len;
|
||||
memcpy(a->enc, enc, enc_len);
|
||||
pthread_create(&threads[i], NULL, brute_thread, (void *)a);
|
||||
}
|
||||
|
||||
// wait for threads to terminate:
|
||||
for (int i = 0; i < thread_count; ++i)
|
||||
pthread_join(threads[i], NULL);
|
||||
|
||||
if (global_found == false) {
|
||||
printf("\nFailed to find a key\n\n");
|
||||
}
|
||||
|
||||
t1 = msclock() - t1;
|
||||
if (t1 > 0)
|
||||
printf("execution time " _YELLOW_("%.2f") " sec\n", (float)t1 / 1000.0);
|
||||
|
||||
// clean up mutex
|
||||
pthread_mutex_destroy(&print_lock);
|
||||
return 0;
|
||||
}
|
70
tools/mfc/card_reader/mfkey32.c
Normal file
70
tools/mfc/card_reader/mfkey32.c
Normal file
|
@ -0,0 +1,70 @@
|
|||
#define __STDC_FORMAT_MACROS
|
||||
#include <inttypes.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include "crapto1/crapto1.h"
|
||||
#include "util_posix.h"
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
struct Crypto1State *s, *t;
|
||||
uint64_t key; // recovered key
|
||||
uint32_t uid; // serial number
|
||||
uint32_t nt; // tag challenge
|
||||
uint32_t nr0_enc; // first encrypted reader challenge
|
||||
uint32_t ar0_enc; // first encrypted reader response
|
||||
uint32_t nr1_enc; // second encrypted reader challenge
|
||||
uint32_t ar1_enc; // second encrypted reader response
|
||||
uint32_t ks2; // keystream used to encrypt reader response
|
||||
|
||||
printf("MIFARE Classic key recovery - based on 32 bits of keystream\n");
|
||||
printf("Recover key from two 32-bit reader authentication answers only!\n\n");
|
||||
|
||||
if (argc < 7) {
|
||||
printf(" syntax: %s <uid> <nt> <nr_0> <ar_0> <nr_1> <ar_1>\n\n", argv[0]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
sscanf(argv[1], "%x", &uid);
|
||||
sscanf(argv[2], "%x", &nt);
|
||||
sscanf(argv[3], "%x", &nr0_enc);
|
||||
sscanf(argv[4], "%x", &ar0_enc);
|
||||
sscanf(argv[5], "%x", &nr1_enc);
|
||||
sscanf(argv[6], "%x", &ar1_enc);
|
||||
|
||||
printf("Recovering key for:\n");
|
||||
printf(" uid: %08x\n", uid);
|
||||
printf(" nt: %08x\n", nt);
|
||||
printf(" {nr_0}: %08x\n", nr0_enc);
|
||||
printf(" {ar_0}: %08x\n", ar0_enc);
|
||||
printf(" {nr_1}: %08x\n", nr1_enc);
|
||||
printf(" {ar_1}: %08x\n", ar1_enc);
|
||||
|
||||
// Generate lfsr successors of the tag challenge
|
||||
printf("\nLFSR successors of the tag challenge:\n");
|
||||
uint32_t p64 = prng_successor(nt, 64);
|
||||
printf(" nt': %08x\n", p64);
|
||||
printf(" nt'': %08x\n", prng_successor(p64, 32));
|
||||
|
||||
// Extract the keystream from the messages
|
||||
printf("\nKeystream used to generate {ar} and {at}:\n");
|
||||
ks2 = ar0_enc ^ p64;
|
||||
printf(" ks2: %08x\n", ks2);
|
||||
|
||||
s = lfsr_recovery32(ar0_enc ^ p64, 0);
|
||||
|
||||
for (t = s; t->odd | t->even; ++t) {
|
||||
lfsr_rollback_word(t, 0, 0);
|
||||
lfsr_rollback_word(t, nr0_enc, 1);
|
||||
lfsr_rollback_word(t, uid ^ nt, 0);
|
||||
crypto1_get_lfsr(t, &key);
|
||||
crypto1_word(t, uid ^ nt, 0);
|
||||
crypto1_word(t, nr1_enc, 1);
|
||||
if (ar1_enc == (crypto1_word(t, 0, 0) ^ p64)) {
|
||||
printf("\nFound Key: [%012" PRIx64 "]\n\n", key);
|
||||
break;
|
||||
}
|
||||
}
|
||||
free(s);
|
||||
return 0;
|
||||
}
|
77
tools/mfc/card_reader/mfkey32v2.c
Normal file
77
tools/mfc/card_reader/mfkey32v2.c
Normal file
|
@ -0,0 +1,77 @@
|
|||
#define __STDC_FORMAT_MACROS
|
||||
#include <inttypes.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include "crapto1/crapto1.h"
|
||||
#include "util_posix.h"
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
struct Crypto1State *s, *t;
|
||||
uint64_t key; // recovered key
|
||||
uint32_t uid; // serial number
|
||||
uint32_t nt0; // tag challenge first
|
||||
uint32_t nt1; // tag challenge second
|
||||
uint32_t nr0_enc; // first encrypted reader challenge
|
||||
uint32_t ar0_enc; // first encrypted reader response
|
||||
uint32_t nr1_enc; // second encrypted reader challenge
|
||||
uint32_t ar1_enc; // second encrypted reader response
|
||||
uint32_t ks2; // keystream used to encrypt reader response
|
||||
|
||||
printf("MIFARE Classic key recovery - based 32 bits of keystream VERSION2\n");
|
||||
printf("Recover key from two 32-bit reader authentication answers only\n");
|
||||
printf("This version implements Moebius two different nonce solution (like the supercard)\n\n");
|
||||
|
||||
if (argc < 8) {
|
||||
printf("syntax: %s <uid> <nt> <nr_0> <ar_0> <nt1> <nr_1> <ar_1>\n\n", argv[0]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
sscanf(argv[1], "%x", &uid);
|
||||
sscanf(argv[2], "%x", &nt0);
|
||||
sscanf(argv[3], "%x", &nr0_enc);
|
||||
sscanf(argv[4], "%x", &ar0_enc);
|
||||
sscanf(argv[5], "%x", &nt1);
|
||||
sscanf(argv[6], "%x", &nr1_enc);
|
||||
sscanf(argv[7], "%x", &ar1_enc);
|
||||
|
||||
printf("Recovering key for:\n");
|
||||
printf(" uid: %08x\n", uid);
|
||||
printf(" nt_0: %08x\n", nt0);
|
||||
printf(" {nr_0}: %08x\n", nr0_enc);
|
||||
printf(" {ar_0}: %08x\n", ar0_enc);
|
||||
printf(" nt_1: %08x\n", nt1);
|
||||
printf(" {nr_1}: %08x\n", nr1_enc);
|
||||
printf(" {ar_1}: %08x\n", ar1_enc);
|
||||
|
||||
// Generate lfsr successors of the tag challenge
|
||||
printf("\nLFSR successors of the tag challenge:\n");
|
||||
uint32_t p64 = prng_successor(nt0, 64);
|
||||
uint32_t p64b = prng_successor(nt1, 64);
|
||||
|
||||
printf(" nt': %08x\n", p64);
|
||||
printf(" nt'': %08x\n", prng_successor(p64, 32));
|
||||
|
||||
// Extract the keystream from the messages
|
||||
printf("\nKeystream used to generate {ar} and {at}:\n");
|
||||
ks2 = ar0_enc ^ p64;
|
||||
printf(" ks2: %08x\n", ks2);
|
||||
|
||||
s = lfsr_recovery32(ar0_enc ^ p64, 0);
|
||||
|
||||
for (t = s; t->odd | t->even; ++t) {
|
||||
lfsr_rollback_word(t, 0, 0);
|
||||
lfsr_rollback_word(t, nr0_enc, 1);
|
||||
lfsr_rollback_word(t, uid ^ nt0, 0);
|
||||
crypto1_get_lfsr(t, &key);
|
||||
|
||||
crypto1_word(t, uid ^ nt1, 0);
|
||||
crypto1_word(t, nr1_enc, 1);
|
||||
if (ar1_enc == (crypto1_word(t, 0, 0) ^ p64b)) {
|
||||
printf("\nFound Key: [%012" PRIx64 "]\n\n", key);
|
||||
break;
|
||||
}
|
||||
}
|
||||
free(s);
|
||||
return 0;
|
||||
}
|
102
tools/mfc/card_reader/mfkey64.c
Normal file
102
tools/mfc/card_reader/mfkey64.c
Normal file
|
@ -0,0 +1,102 @@
|
|||
#define __STDC_FORMAT_MACROS
|
||||
#include <inttypes.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include "crapto1/crapto1.h"
|
||||
#include "util_posix.h"
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
struct Crypto1State *revstate;
|
||||
uint64_t key; // recovered key
|
||||
uint32_t uid; // serial number
|
||||
uint32_t nt; // tag challenge
|
||||
uint32_t nr_enc; // encrypted reader challenge
|
||||
uint32_t ar_enc; // encrypted reader response
|
||||
uint32_t at_enc; // encrypted tag response
|
||||
uint32_t ks2; // keystream used to encrypt reader response
|
||||
uint32_t ks3; // keystream used to encrypt tag response
|
||||
|
||||
printf("MIFARE Classic key recovery - based 64 bits of keystream\n");
|
||||
printf("Recover key from only one complete authentication!\n\n");
|
||||
|
||||
if (argc < 6) {
|
||||
printf(" syntax: %s <uid> <nt> <{nr}> <{ar}> <{at}> [enc...]\n\n", argv[0]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int encc = argc - 6;
|
||||
int enclen[encc];
|
||||
uint8_t enc[encc][120];
|
||||
|
||||
sscanf(argv[1], "%x", &uid);
|
||||
sscanf(argv[2], "%x", &nt);
|
||||
sscanf(argv[3], "%x", &nr_enc);
|
||||
sscanf(argv[4], "%x", &ar_enc);
|
||||
sscanf(argv[5], "%x", &at_enc);
|
||||
for (int i = 0; i < encc; i++) {
|
||||
enclen[i] = strlen(argv[i + 6]) / 2;
|
||||
for (int i2 = 0; i2 < enclen[i]; i2++) {
|
||||
sscanf(argv[i + 6] + i2 * 2, "%2hhx", &enc[i][i2]);
|
||||
}
|
||||
}
|
||||
|
||||
printf("Recovering key for:\n");
|
||||
|
||||
printf(" uid: %08x\n", uid);
|
||||
printf(" nt: %08x\n", nt);
|
||||
printf(" {nr}: %08x\n", nr_enc);
|
||||
printf(" {ar}: %08x\n", ar_enc);
|
||||
printf(" {at}: %08x\n", at_enc);
|
||||
|
||||
for (int i = 0; i < encc; i++) {
|
||||
printf("{enc%d}: ", i);
|
||||
for (int i2 = 0; i2 < enclen[i]; i2++) {
|
||||
printf("%02x", enc[i][i2]);
|
||||
}
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
// Generate lfsr successors of the tag challenge
|
||||
printf("\nLFSR successors of the tag challenge:\n");
|
||||
uint32_t p64 = prng_successor(nt, 64);
|
||||
printf(" nt': %08x\n", p64);
|
||||
printf(" nt'': %08x\n", prng_successor(p64, 32));
|
||||
|
||||
// Extract the keystream from the messages
|
||||
printf("\nKeystream used to generate {ar} and {at}:\n");
|
||||
ks2 = ar_enc ^ p64;
|
||||
ks3 = at_enc ^ prng_successor(p64, 32);
|
||||
printf(" ks2: %08x\n", ks2);
|
||||
printf(" ks3: %08x\n", ks3);
|
||||
|
||||
revstate = lfsr_recovery64(ks2, ks3);
|
||||
|
||||
// Decrypting communication using keystream if presented
|
||||
if (argc > 6) {
|
||||
printf("\nDecrypted communication:\n");
|
||||
uint8_t ks4;
|
||||
int rollb = 0;
|
||||
for (int i = 0; i < encc; i++) {
|
||||
printf("{dec%d}: ", i);
|
||||
for (int i2 = 0; i2 < enclen[i]; i2++) {
|
||||
ks4 = crypto1_byte(revstate, 0, 0);
|
||||
printf("%02x", ks4 ^ enc[i][i2]);
|
||||
rollb += 1;
|
||||
}
|
||||
printf("\n");
|
||||
}
|
||||
for (int i = 0; i < rollb; i++)
|
||||
lfsr_rollback_byte(revstate, 0, 0);
|
||||
}
|
||||
|
||||
lfsr_rollback_word(revstate, 0, 0);
|
||||
lfsr_rollback_word(revstate, 0, 0);
|
||||
lfsr_rollback_word(revstate, nr_enc, 1);
|
||||
lfsr_rollback_word(revstate, uid ^ nt, 0);
|
||||
crypto1_get_lfsr(revstate, &key);
|
||||
printf("\nFound Key: [%012" PRIx64 "]\n\n", key);
|
||||
crypto1_destroy(revstate);
|
||||
return 0;
|
||||
}
|
105
tools/mfc/card_reader/mfkey_examples.md
Normal file
105
tools/mfc/card_reader/mfkey_examples.md
Normal file
|
@ -0,0 +1,105 @@
|
|||
## Sample trace
|
||||
```
|
||||
+ 50422: : 26
|
||||
+ 64: 0: TAG 04 00
|
||||
+ 944: : 93 20
|
||||
+ 64: 0: TAG 9c 59 9b 32 6c
|
||||
+ 1839: : 93 70 9c 59 9b 32 6c 6b 30
|
||||
+ 64: 0: TAG 08 b6 dd
|
||||
+ 3783: : 60 32 64 69
|
||||
+ 113: 0: TAG 82 a4 16 6c
|
||||
+ 1287: : a1 e4 58 ce 6e ea 41 e0
|
||||
+ 64: 0: TAG 5c ad f4 39
|
||||
```
|
||||
Usage with sample trace:
|
||||
`./mfkey64 9C599B32 82A4166C A1E458CE 6EEA41E0 5CADF439`
|
||||
|
||||
## Other examples
|
||||
|
||||
For mfkey32, you want to get two different NR_0/NR_1 values.
|
||||
|
||||
```
|
||||
# <uid> <nt> <nr_0> <ar_0> <nr_1> <ar_1>
|
||||
./mfkey32 52B0F519 5417D1F8 4D545EA7 E15AC8C2 DAC1A7F4 5AE5C37F
|
||||
```
|
||||
|
||||
For mfkey32v2 (moebius), you want to get two different NT/NT1 values. (like in the SIM commands)
|
||||
```
|
||||
# <uid> <nt> <nr_0> <ar_0> <nt1> <nr_1> <ar_1>
|
||||
./mfkey32v2 12345678 1AD8DF2B 1D316024 620EF048 30D6CB07 C52077E2 837AC61A
|
||||
./mfkey32v2 52B0F519 5417D1F8 4D545EA7 E15AC8C2 A1BA88C6 DAC1A7F4 5AE5C37F
|
||||
```
|
||||
|
||||
For mfkey64, you want to have the AT response from tag.
|
||||
```
|
||||
# <uid> <nt> <nr> <ar> <at>
|
||||
./mfkey64 9C599B32 82A4166C A1E458CE 6EEA41E0 5CADF439
|
||||
./mfkey64 52B0F519 5417D1F8 4D545EA7 E15AC8C2 5056E41B
|
||||
```
|
||||
|
||||
### Communication decryption
|
||||
A new functionality from @zhovner
|
||||
|
||||
Example: given the following trace
|
||||
```
|
||||
RDR 26
|
||||
TAG 04 00
|
||||
RDR 93 20
|
||||
TAG 14 57 9f 69 b5
|
||||
RDR 93 70 14 57 9f 69 b5 2e 51
|
||||
TAG 08 b6 dd
|
||||
RDR 60 14 50 2d
|
||||
TAG ce 84 42 61
|
||||
RDR f8 04 9c cb 05 25 c8 4f
|
||||
TAG 94 31 cc 40
|
||||
RDR 70 93 df 99
|
||||
TAG 99 72 42 8c e2 e8 52 3f 45 6b 99 c8 31 e7 69 dc ed 09
|
||||
RDR 8c a6 82 7b
|
||||
TAG ab 79 7f d3 69 e8 b9 3a 86 77 6b 40 da e3 ef 68 6e fd
|
||||
RDR c3 c3 81 ba
|
||||
TAG 49 e2 c9 de f4 86 8d 17 77 67 0e 58 4c 27 23 02 86 f4
|
||||
RDR fb dc d7 c1
|
||||
TAG 4a bd 96 4b 07 d3 56 3a a0 66 ed 0a 2e ac 7f 63 12 bf
|
||||
RDR 9f 91 49 ea
|
||||
```
|
||||
|
||||
`./mfkey64 14579f69 ce844261 f8049ccb 0525c84f 9431cc40 7093df99 9972428ce2e8523f456b99c831e769dced09 8ca6827b ab797fd369e8b93a86776b40dae3ef686efd c3c381ba 49e2c9def4868d1777670e584c27230286f4 fbdcd7c1 4abd964b07d3563aa066ed0a2eac7f6312bf 9f9149ea`
|
||||
|
||||
```
|
||||
Recovering key for:
|
||||
uid: 14579f69
|
||||
nt: ce844261
|
||||
{nr}: f8049ccb
|
||||
{ar}: 0525c84f
|
||||
{at}: 9431cc40
|
||||
{enc0}: 7093df99
|
||||
{enc1}: 9972428ce2e8523f456b99c831e769dced09
|
||||
{enc2}: 8ca6827b
|
||||
{enc3}: ab797fd369e8b93a86776b40dae3ef686efd
|
||||
{enc4}: c3c381ba
|
||||
{enc5}: 49e2c9def4868d1777670e584c27230286f4
|
||||
{enc6}: fbdcd7c1
|
||||
{enc7}: 4abd964b07d3563aa066ed0a2eac7f6312bf
|
||||
{enc8}: 9f9149ea
|
||||
|
||||
LFSR successors of the tag challenge:
|
||||
nt': 76d4468d
|
||||
nt'': d5f3c476
|
||||
|
||||
Keystream used to generate {ar} and {at}:
|
||||
ks2: 73f18ec2
|
||||
ks3: 41c20836
|
||||
|
||||
Decrypted communication:
|
||||
{dec0}: 3014a7fe
|
||||
{dec1}: c26935cfdb95c4b4a27a84b8217ae9e48217
|
||||
{dec2}: 30152eef
|
||||
{dec3}: 493167c536c30f8e220b09675687067d4b31
|
||||
{dec4}: 3016b5dd
|
||||
{dec5}: 493167c536c30f8e220b09675687067d4b31
|
||||
{dec6}: 30173ccc
|
||||
{dec7}: 0000000000007e178869000000000000c4f2
|
||||
{dec8}: 61148834
|
||||
|
||||
Found Key: [091e639cb715]
|
||||
```
|
20
tools/mfc/card_reader/protocol.h
Normal file
20
tools/mfc/card_reader/protocol.h
Normal file
|
@ -0,0 +1,20 @@
|
|||
#ifndef PROTOCOL_H
|
||||
#define PROTOCOL_H
|
||||
|
||||
#define ISO14443A_CMD_READBLOCK 0x30
|
||||
#define ISO14443A_CMD_WRITEBLOCK 0xA0
|
||||
|
||||
#define MIFARE_AUTH_KEYA 0x60
|
||||
#define MIFARE_AUTH_KEYB 0x61
|
||||
#define MIFARE_CMD_DEC 0xC0
|
||||
#define MIFARE_CMD_INC 0xC1
|
||||
#define MIFARE_CMD_RESTORE 0xC2
|
||||
#define MIFARE_CMD_TRANSFER 0xB0
|
||||
|
||||
// mifare 4bit card answers
|
||||
#define CARD_ACK 0x0A // 1010 - ACK
|
||||
#define CARD_NACK_NA 0x04 // 0100 - NACK, not allowed (command not allowed)
|
||||
#define CARD_NACK_TR 0x05 // 0101 - NACK, transmission error
|
||||
|
||||
#endif
|
||||
// PROTOCOL_H
|
28
tools/mfc/card_reader/sleep.c
Normal file
28
tools/mfc/card_reader/sleep.c
Normal file
|
@ -0,0 +1,28 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// Copyright (C) 2010 iZsh <izsh at fail0verflow.com>
|
||||
//
|
||||
// This code is licensed to you under the terms of the GNU GPL, version 2 or,
|
||||
// at your option, any later version. See the LICENSE.txt file for the text of
|
||||
// the license.
|
||||
//-----------------------------------------------------------------------------
|
||||
// platform-independant sleep macros
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#ifndef _WIN32
|
||||
|
||||
#define _POSIX_C_SOURCE 199309L
|
||||
#include "sleep.h"
|
||||
#include <time.h>
|
||||
#include <stdio.h>
|
||||
#include <sys/time.h>
|
||||
#include <errno.h>
|
||||
|
||||
void nsleep(uint64_t n) {
|
||||
struct timespec timeout;
|
||||
timeout.tv_sec = n / 1000000000;
|
||||
timeout.tv_nsec = n % 1000000000;
|
||||
while (nanosleep(&timeout, &timeout) && errno == EINTR);
|
||||
}
|
||||
|
||||
#endif // _WIN32
|
||||
|
27
tools/mfc/card_reader/sleep.h
Normal file
27
tools/mfc/card_reader/sleep.h
Normal file
|
@ -0,0 +1,27 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// Copyright (C) 2010 iZsh <izsh at fail0verflow.com>
|
||||
//
|
||||
// This code is licensed to you under the terms of the GNU GPL, version 2 or,
|
||||
// at your option, any later version. See the LICENSE.txt file for the text of
|
||||
// the license.
|
||||
//-----------------------------------------------------------------------------
|
||||
// platform-independant sleep macros
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#ifndef SLEEP_H__
|
||||
#define SLEEP_H__
|
||||
|
||||
#ifdef _WIN32
|
||||
# include <windows.h>
|
||||
# define sleep(n) Sleep(1000 * n)
|
||||
# define msleep(n) Sleep(n)
|
||||
#else
|
||||
# include <inttypes.h>
|
||||
# include <unistd.h>
|
||||
void nsleep(uint64_t n);
|
||||
# define msleep(n) nsleep(1000000 * n)
|
||||
# define usleep(n) nsleep(1000 * n)
|
||||
#endif // _WIN32
|
||||
|
||||
#endif // SLEEP_H__
|
||||
|
137
tools/mfc/card_reader/util_posix.c
Normal file
137
tools/mfc/card_reader/util_posix.c
Normal file
|
@ -0,0 +1,137 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// Copyright (C) 2010 iZsh <izsh at fail0verflow.com>
|
||||
//
|
||||
// This code is licensed to you under the terms of the GNU GPL, version 2 or,
|
||||
// at your option, any later version. See the LICENSE.txt file for the text of
|
||||
// the license.
|
||||
//-----------------------------------------------------------------------------
|
||||
// utilities requiring Posix library functions
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
// ensure availability even with -std=c99; must be included before
|
||||
#if !defined(_WIN32)
|
||||
//#define _POSIX_C_SOURCE 199309L // need nanosleep()
|
||||
#define _POSIX_C_SOURCE 200112L // need localtime_r()
|
||||
#else
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
#include "util_posix.h"
|
||||
#include <stdint.h>
|
||||
#include <time.h>
|
||||
|
||||
|
||||
// Timer functions
|
||||
#if !defined (_WIN32)
|
||||
#include <errno.h>
|
||||
|
||||
static void nsleep(uint64_t n) {
|
||||
struct timespec timeout;
|
||||
timeout.tv_sec = n / 1000000000;
|
||||
timeout.tv_nsec = n % 1000000000;
|
||||
while (nanosleep(&timeout, &timeout) && errno == EINTR);
|
||||
}
|
||||
|
||||
void msleep(uint32_t n) {
|
||||
nsleep(1000000 * (uint64_t)n);
|
||||
}
|
||||
#endif // _WIN32
|
||||
|
||||
#ifdef __APPLE__
|
||||
|
||||
#ifndef CLOCK_MONOTONIC
|
||||
#define CLOCK_MONOTONIC (1)
|
||||
#endif
|
||||
#ifndef CLOCK_REALTIME
|
||||
#define CLOCK_REALTIME (2)
|
||||
#endif
|
||||
|
||||
#include <sys/time.h>
|
||||
#include <mach/clock.h>
|
||||
#include <mach/mach.h>
|
||||
#include <mach/mach_time.h>
|
||||
|
||||
/* clock_gettime is not implemented on OSX prior to 10.12 */
|
||||
int _civet_clock_gettime(int clk_id, struct timespec *t);
|
||||
|
||||
int _civet_clock_gettime(int clk_id, struct timespec *t) {
|
||||
memset(t, 0, sizeof(*t));
|
||||
if (clk_id == CLOCK_REALTIME) {
|
||||
struct timeval now;
|
||||
int rv = gettimeofday(&now, NULL);
|
||||
if (rv) {
|
||||
return rv;
|
||||
}
|
||||
t->tv_sec = now.tv_sec;
|
||||
t->tv_nsec = now.tv_usec * 1000;
|
||||
return 0;
|
||||
|
||||
} else if (clk_id == CLOCK_MONOTONIC) {
|
||||
static uint64_t clock_start_time = 0;
|
||||
static mach_timebase_info_data_t timebase_info = {0, 0};
|
||||
|
||||
uint64_t now = mach_absolute_time();
|
||||
|
||||
if (clock_start_time == 0) {
|
||||
|
||||
mach_timebase_info(&timebase_info);
|
||||
clock_start_time = now;
|
||||
}
|
||||
|
||||
now = (uint64_t)((double)(now - clock_start_time)
|
||||
* (double)timebase_info.numer
|
||||
/ (double)timebase_info.denom);
|
||||
|
||||
t->tv_sec = now / 1000000000;
|
||||
t->tv_nsec = now % 1000000000;
|
||||
return 0;
|
||||
}
|
||||
return -1; // EINVAL - Clock ID is unknown
|
||||
}
|
||||
|
||||
/* if clock_gettime is declared, then __CLOCK_AVAILABILITY will be defined */
|
||||
#ifdef __CLOCK_AVAILABILITY
|
||||
/* If we compiled with Mac OSX 10.12 or later, then clock_gettime will be declared
|
||||
* but it may be NULL at runtime. So we need to check before using it. */
|
||||
int _civet_safe_clock_gettime(int clk_id, struct timespec *t);
|
||||
|
||||
int _civet_safe_clock_gettime(int clk_id, struct timespec *t) {
|
||||
if (clock_gettime) {
|
||||
return clock_gettime(clk_id, t);
|
||||
}
|
||||
return _civet_clock_gettime(clk_id, t);
|
||||
}
|
||||
#define clock_gettime _civet_safe_clock_gettime
|
||||
#else
|
||||
#define clock_gettime _civet_clock_gettime
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
// a milliseconds timer for performance measurement
|
||||
uint64_t msclock(void) {
|
||||
#if defined(_WIN32)
|
||||
#include <sys/types.h>
|
||||
|
||||
// WORKAROUND FOR MinGW (some versions - use if normal code does not compile)
|
||||
// It has no _ftime_s and needs explicit inclusion of timeb.h
|
||||
#include <sys/timeb.h>
|
||||
struct _timeb t;
|
||||
_ftime(&t);
|
||||
return 1000 * (uint64_t)t.time + t.millitm;
|
||||
|
||||
// NORMAL CODE (use _ftime_s)
|
||||
//struct _timeb t;
|
||||
//if (_ftime_s(&t)) {
|
||||
// return 0;
|
||||
//} else {
|
||||
// return 1000 * t.time + t.millitm;
|
||||
//}
|
||||
#else
|
||||
struct timespec t;
|
||||
clock_gettime(CLOCK_MONOTONIC, &t);
|
||||
return (1000 * (uint64_t)t.tv_sec + t.tv_nsec / 1000000);
|
||||
#endif
|
||||
}
|
||||
|
26
tools/mfc/card_reader/util_posix.h
Normal file
26
tools/mfc/card_reader/util_posix.h
Normal file
|
@ -0,0 +1,26 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// Copyright (C) 2010 iZsh <izsh at fail0verflow.com>
|
||||
//
|
||||
// This code is licensed to you under the terms of the GNU GPL, version 2 or,
|
||||
// at your option, any later version. See the LICENSE.txt file for the text of
|
||||
// the license.
|
||||
//-----------------------------------------------------------------------------
|
||||
// utilities requiring Posix library functions
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#ifndef UTIL_POSIX_H__
|
||||
#define UTIL_POSIX_H__
|
||||
|
||||
#include "common.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
# include <windows.h>
|
||||
# define sleep(n) Sleep(1000 *(n))
|
||||
# define msleep(n) Sleep((n))
|
||||
#else
|
||||
void msleep(uint32_t n); // sleep n milliseconds
|
||||
#endif // _WIN32
|
||||
|
||||
uint64_t msclock(void); // a milliseconds clock
|
||||
|
||||
#endif
|
Loading…
Add table
Add a link
Reference in a new issue