diff --git a/armsrc/Makefile b/armsrc/Makefile index dea7f96ab..e6f4f56e4 100644 --- a/armsrc/Makefile +++ b/armsrc/Makefile @@ -45,6 +45,7 @@ SRC_FELICA = felica.c SRC_CRAPTO1 = crypto1.c des.c desfire_crypto.c mifaredesfire.c aes.c platform_util.c SRC_CRC = crc.c crc16.c crc32.c SRC_ICLASS = iclass.c optimized_cipherutils.c optimized_ikeys.c optimized_elite.c optimized_cipher.c sam_picopass.c +SRC_SEOS = seos.c sha1.c sha256.c SRC_LEGIC = legicrf.c legicrfsim.c legic_prng.c SRC_NFCBARCODE = thinfilm.c @@ -137,6 +138,7 @@ THUMBSRC = start.c \ $(SRC_ISO14443b) \ $(SRC_CRAPTO1) \ $(SRC_ICLASS) \ + $(SRC_SEOS) \ $(SRC_EMV) \ $(SRC_CRC) \ $(SRC_FELICA) \ diff --git a/armsrc/appmain.c b/armsrc/appmain.c index f1565e916..8e947bd08 100644 --- a/armsrc/appmain.c +++ b/armsrc/appmain.c @@ -45,6 +45,7 @@ #include "em4x50.h" #include "em4x70.h" #include "iclass.h" +#include "seos.h" #include "legicrfsim.h" //#include "cryptorfsim.h" #include "epa.h" @@ -630,6 +631,11 @@ static void SendCapabilities(void) { #else capabilities.compiled_with_iclass = false; #endif +#ifdef WITH_SEOS + capabilities.compiled_with_seos = true; +#else + capabilities.compiled_with_seos = false; +#endif #ifdef WITH_NFCBARCODE capabilities.compiled_with_nfcbarcode = true; #else @@ -2281,6 +2287,12 @@ static void PacketReceived(PacketCommandNG *packet) { break; } #endif +#ifdef WITH_SEOS + case CMD_HF_SEOS_SIMULATE: { + SimulateSeos((seos_emulate_req_t *)packet->data.asBytes); + break; + } +#endif #ifdef WITH_HFSNIFF case CMD_HF_SNIFF: { diff --git a/armsrc/seos.c b/armsrc/seos.c new file mode 100644 index 000000000..f262f1751 --- /dev/null +++ b/armsrc/seos.c @@ -0,0 +1,837 @@ +//----------------------------------------------------------------------------- +// Copyright (C) Aaron Tulino - December 2025 +// Copyright (C) Proxmark3 contributors. See AUTHORS.md for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// See LICENSE.txt for the text of the license. +//----------------------------------------------------------------------------- +// Routines to support Seos. +//----------------------------------------------------------------------------- +#include "seos.h" +#include "iso14443a.h" +#include "BigBuf.h" + +#include "fpgaloader.h" +#include "string.h" +#include "dbprint.h" +#include "protocols.h" + +#include "proxmark3_arm.h" +#include "cmd.h" +// Needed for CRC in emulation mode; +// same construction as in ISO 14443; +// different initial value (CRC_ICLASS) +#include "crc16.h" + +#include +#include +#include "cmac_calc.h" +#include "cmac_3des.h" + +#include +#include + +const uint8_t SEOS_AID[] = { 0xA0, 0x00, 0x00, 0x04, 0x40, 0x00, 0x01, 0x01, 0x00, 0x01 }; + +static uint8_t block_size(uint8_t algorithm) { + if (algorithm == SEOS_ENCRYPTION_AES) { + return 16; + } else if (algorithm == SEOS_ENCRYPTION_2K3DES) { + return 8; + } else if (algorithm == SEOS_ENCRYPTION_3K3DES) { + return 8; + } else { + Dbprintf(_RED_("Unknown Encryption Algorithm")); + return 0; + } +} + +static uint8_t round_to_next(uint8_t value, uint8_t step) { + if (value % step == 0) { + return value; + } else { + return value + step - (value % step); + } +} + +static bool generate_cryptogram(const uint8_t *key, const uint8_t *opt_iv, const uint8_t *input, size_t length, uint8_t *output, uint8_t algorithm) { + // IV is optional, only add if provided. Zeros by default. + uint8_t iv[16] = {0x00}; + if (opt_iv != NULL) { + memcpy(iv, opt_iv, 16); + } + + uint8_t bs = block_size(algorithm); + uint8_t padded_length = round_to_next(length, bs); + + // Allocate enough room to store any additional padding + uint8_t cleartext[padded_length]; + uint8_t cryptogram[padded_length]; + + memset(cleartext, 0, padded_length); + memcpy(cleartext, input, length); + // ISO7816 padding: add 0x80 after data, then follow with all zeros + if (padded_length != length) { + cleartext[length] = 0x80; + } + + if (algorithm == SEOS_ENCRYPTION_AES) { + mbedtls_aes_context ctx; + mbedtls_aes_setkey_enc(&ctx, key, 128); + mbedtls_aes_crypt_cbc(&ctx, MBEDTLS_AES_ENCRYPT, length, iv, input, cryptogram); + mbedtls_aes_free(&ctx); + } else if (algorithm == SEOS_ENCRYPTION_2K3DES) { + mbedtls_des3_context ctx; + mbedtls_des3_set2key_enc(&ctx, key); + mbedtls_des3_crypt_cbc(&ctx, MBEDTLS_DES_ENCRYPT, length, iv, input, cryptogram); + mbedtls_des3_free(&ctx); + } + + // Add generated cryptogram to output buffer + memcpy(output, cryptogram, length); + + return true; +} + +static bool decrypt_cryptogram(const uint8_t *key, const uint8_t *input, size_t length, uint8_t *output, uint8_t algorithm) { + uint8_t iv[16] = {0x00}; + + // Allocate enough room to store any additional padding + uint8_t cleartext[length + block_size(algorithm)]; + + if (algorithm == SEOS_ENCRYPTION_AES) { + mbedtls_aes_context ctx; + mbedtls_aes_init(&ctx); + mbedtls_aes_setkey_dec(&ctx, key, 128); + mbedtls_aes_crypt_cbc(&ctx, MBEDTLS_AES_DECRYPT, length, iv, input, cleartext); + mbedtls_aes_free(&ctx); + } else if (algorithm == SEOS_ENCRYPTION_2K3DES) { + mbedtls_des3_context ctx; + mbedtls_des3_set2key_dec(&ctx, key); + mbedtls_des3_crypt_cbc(&ctx, MBEDTLS_DES_DECRYPT, length, iv, input, cleartext); + mbedtls_des3_free(&ctx); + } else { + Dbprintf(_RED_("Unknown Encryption Algorithm")); + return false; + } + + // Add decrypted cleartext to output buffer + memcpy(output, cleartext, length); + + return true; +} + +// Returns length of generated CMAC +static uint8_t generate_cmac(const uint8_t *key, const uint8_t *input, size_t length, uint8_t *output, uint8_t max_output_len, uint8_t encryption_algorithm) { + uint8_t size = block_size(encryption_algorithm); + uint8_t mac[size]; + + if (encryption_algorithm == SEOS_ENCRYPTION_AES) { + ulaes_cmac(key, 16, input, length, mac); + } else if (encryption_algorithm == SEOS_ENCRYPTION_2K3DES || encryption_algorithm == SEOS_ENCRYPTION_3K3DES) { + uint8_t keylen = 16; + if (encryption_algorithm == SEOS_ENCRYPTION_3K3DES) keylen = 24; + des3_cmac(key, keylen, input, length, mac); + } else { + Dbprintf(_RED_("Unknown Encryption Algorithm")); + return false; + } + + // Add generated CMAC to output buffer + size = MIN(size, max_output_len); + memcpy(output, mac, size); + + return size; +} + +static void seos_kdf(bool forEncryption, uint8_t *masterKey, uint8_t keyslot, + uint8_t *adfOid, size_t adfoid_len, uint8_t *diversifier, uint8_t diversifier_len, uint8_t *out, int encryption_algorithm, int hash_algorithm) { + + // Encryption key = 04 + // KEK Encryption key = 05 + // MAC key = 06 + // KEK MAC key = 07 + + uint8_t typeOfKey = 0x06; + if (forEncryption == true) { + typeOfKey = 0x04; + } + + uint8_t inputPre[] = { + // Padding + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, typeOfKey, 0x00, 0x00, 0x80, 0x01, + encryption_algorithm, hash_algorithm, keyslot + }; + + // 00000000000000000000000600008001 09 07 00 06112B0601040181E438010102011801010202 EFB08A28B0529F + // 00000000000000000000000400008001 09 07 00 06112B0601040181E438010102011801010202 EFB08A28B0529F + // 06112B0601040181E438010102011801010202 CF 07 EFB08A28B0529F DBA240413B0969B7111F4B6133A3DEFAD934B6DC + + + uint8_t input[sizeof(inputPre) + adfoid_len + diversifier_len]; + + memset(input, 0, sizeof(input)); + + memcpy(input, inputPre, sizeof(inputPre)); + memcpy(input + sizeof(inputPre), adfOid, adfoid_len); + memcpy(input + sizeof(inputPre) + adfoid_len, diversifier, diversifier_len); + + // This CMAC always uses AES, regardless of the main encryption algorithm in use. + generate_cmac(masterKey, input, sizeof(input), out, 16, SEOS_ENCRYPTION_AES); +} + +// turn off afterwards +void SimulateSeos(seos_emulate_req_t *msg) { + tag_response_info_t *responses; + uint32_t cuid = 0; + + // command buffers + uint8_t receivedCmd[MAX_FRAME_SIZE] = { 0x00 }; + uint8_t receivedCmdPar[MAX_PARITY_SIZE] = { 0x00 }; + + // These values are determined at runtime + uint8_t RND_ICC[8] = { 0x00 }; + uint8_t RND_IFD[8]; + uint8_t KEY_ICC[16] = { 0x00 }; + uint8_t KEY_IFD[16]; + uint8_t diver_encr_key[16]; + uint8_t diver_cmac_key[16]; + + // Calculated block size + const uint8_t max_bs = 16; + const uint8_t bs = block_size(msg->encr_alg); + if (bs == 0) { + // Can't continue, invalid encryption algorithm + reply_ng(CMD_HF_SEOS_SIMULATE, PM3_EINVARG, NULL, 0); + return; + } + + // free eventually allocated BigBuf memory but keep Emulator Memory + BigBuf_free_keep_EM(); + + // Allocate 512 bytes for the dynamic modulation, created when the reader queries for it + // Such a response is less time critical, so we can prepare them on the fly +#define DYNAMIC_RESPONSE_BUFFER_SIZE 192 +#define DYNAMIC_MODULATION_BUFFER_SIZE 1024 + + uint8_t *dynamic_response_buffer = BigBuf_calloc(DYNAMIC_RESPONSE_BUFFER_SIZE); + if (dynamic_response_buffer == NULL) { + BigBuf_free_keep_EM(); + reply_ng(CMD_HF_MIFARE_SIMULATE, PM3_EMALLOC, NULL, 0); + return; + } + uint8_t *dynamic_modulation_buffer = BigBuf_calloc(DYNAMIC_MODULATION_BUFFER_SIZE); + if (dynamic_modulation_buffer == NULL) { + BigBuf_free_keep_EM(); + reply_ng(CMD_HF_MIFARE_SIMULATE, PM3_EMALLOC, NULL, 0); + return; + } + tag_response_info_t dynamic_response_info = { + .response = dynamic_response_buffer, + .response_n = 0, + .modulation = dynamic_modulation_buffer, + .modulation_n = 0 + }; + + uint16_t flags = 0; + uint8_t data[PM3_CMD_DATA_SIZE] = { 0 }; + memcpy(data, msg->uid, msg->uid_len); + FLAG_SET_UID_IN_DATA(flags, msg->uid_len); + + // TODO: Is this required? + uint8_t ats[] = { 0x05, 0x78, 0x77, 0x80, 0x02 }; + flags |= FLAG_ATS_IN_DATA; + + // 12 = HID Seos 4K card + if (SimulateIso14443aInit(12, flags, data, ats, sizeof(ats), &responses, &cuid, NULL, NULL) == false) { + BigBuf_free_keep_EM(); + reply_ng(CMD_HF_SEOS_SIMULATE, PM3_EINIT, NULL, 0); + return; + } + + // We need to listen to the high-frequency, peak-detected path. + iso14443a_setup(FPGA_HF_ISO14443A_TAGSIM_LISTEN); + + iso14a_set_timeout(201400); // 106 * 19ms default *100? + + int len = 0; + + int retval = PM3_SUCCESS; + + // Just to allow some checks + int cmdsRecvd = 0; + + bool odd_reply = true; + + clear_trace(); + set_tracing(true); + LED_A_ON(); + + // main loop + bool finished = false; + bool got_rats = false; + while (finished == false) { + // BUTTON_PRESS check done in GetIso14443aCommandFromReader + WDT_HIT(); + + tag_response_info_t *p_response = NULL; + + // Clean receive command buffer + if (GetIso14443aCommandFromReader(receivedCmd, sizeof(receivedCmd), receivedCmdPar, &len) == false) { + Dbprintf("Emulator stopped. Trace length: %d ", BigBuf_get_traceLen()); + retval = PM3_EOPABORTED; + break; + } + + if (receivedCmd[0] == ISO14443A_CMD_REQA && len == 1) { // Received a REQUEST, but in HALTED, skip + odd_reply = !odd_reply; + if (odd_reply) { + p_response = &responses[RESP_INDEX_ATQA]; + } + } else if (receivedCmd[0] == ISO14443A_CMD_WUPA && len == 1) { // Received a WAKEUP + p_response = &responses[RESP_INDEX_ATQA]; + } else if (receivedCmd[1] == 0x20 && receivedCmd[0] == ISO14443A_CMD_ANTICOLL_OR_SELECT && len == 2) { // Received request for UID (cascade 1) + p_response = &responses[RESP_INDEX_UIDC1]; + } else if (receivedCmd[1] == 0x20 && receivedCmd[0] == ISO14443A_CMD_ANTICOLL_OR_SELECT_2 && len == 2) { // Received request for UID (cascade 2) + p_response = &responses[RESP_INDEX_UIDC2]; + } else if (receivedCmd[1] == 0x20 && receivedCmd[0] == ISO14443A_CMD_ANTICOLL_OR_SELECT_3 && len == 2) { // Received request for UID (cascade 3) + p_response = &responses[RESP_INDEX_UIDC3]; + } else if (receivedCmd[1] == 0x70 && receivedCmd[0] == ISO14443A_CMD_ANTICOLL_OR_SELECT && len == 9) { // Received a SELECT (cascade 1) + p_response = &responses[RESP_INDEX_SAKC1]; + } else if (receivedCmd[1] == 0x70 && receivedCmd[0] == ISO14443A_CMD_ANTICOLL_OR_SELECT_2 && len == 9) { // Received a SELECT (cascade 2) + p_response = &responses[RESP_INDEX_SAKC2]; + } else if (receivedCmd[1] == 0x70 && receivedCmd[0] == ISO14443A_CMD_ANTICOLL_OR_SELECT_3 && len == 9) { // Received a SELECT (cascade 3) + p_response = &responses[RESP_INDEX_SAKC3]; + } else if (receivedCmd[0] == ISO14443A_CMD_PPS) { + p_response = &responses[RESP_INDEX_PPS]; + } else if (receivedCmd[0] == ISO14443A_CMD_HALT && len == 4) { // Received a HALT + p_response = NULL; + if (got_rats) { + finished = true; + } + } else if (receivedCmd[0] == ISO14443A_CMD_RATS && len == 4) { // Received a RATS request + p_response = &responses[RESP_INDEX_ATS]; + got_rats = true; + } else { + // clear old dynamic responses + dynamic_response_info.response_n = 0; + dynamic_response_info.modulation_n = 0; + + // Check for ISO 14443A-4 compliant commands, look at left byte (PCB) + uint8_t offset = 0; + switch (receivedCmd[0]) { + case 0x0B: // IBlock with CID + case 0x0A: { + offset = 1; + } + case 0x02: // IBlock without CID + case 0x03: { + dynamic_response_info.response[0] = receivedCmd[0]; + dynamic_response_info.response[1] = 0x00; + dynamic_response_info.response_n = 2; + + uint8_t apdu_status[2] = {0x6A, 0x82}; // Default: Not Found + + switch (receivedCmd[2 + offset]) { // APDU Class Byte + // receivedCmd in this case is expecting to structured with possibly a CID, then the APDU command for SelectFile + // | IBlock (CID) | CID | APDU Command | CRC | + // or | IBlock (noCID) | APDU Command | CRC | + case 0xA4: { // SELECT FILE + // Select File AID uses the following format for GlobalPlatform + // + // | 00 | A4 | 04 | 00 | xx | AID | 00 | + // xx in this case is len of the AID value in hex + + // aid len is found as a hex value in receivedCmd[6] (Index Starts at 0) + uint8_t aid_len = receivedCmd[5 + offset]; + uint8_t *aid = &receivedCmd[6 + offset]; + + // TODO: See if this actually matches exactly (if possible) + if ((aid_len == sizeof(SEOS_AID)) && (memcmp(SEOS_AID, aid, sizeof(SEOS_AID)) == 0)) { // Evaluate the AID sent by the Reader to the AID supplied + // Format as TLV and acknowledge + /* + 6F 0C + 84 0A + A0000004400001010001 + 90 00 + */ + + dynamic_response_info.response[1 + offset] = 0x6F; // Tag + dynamic_response_info.response[2 + offset] = aid_len + 2; // Length + dynamic_response_info.response[3 + offset] = 0x84; // Inner Tag + dynamic_response_info.response[4 + offset] = aid_len; // Inner Length + memcpy(dynamic_response_info.response + 5 + offset, aid, aid_len); + dynamic_response_info.response_n = 5 + aid_len + offset; + + // Set status code to Success + apdu_status[0] = 0x90; + apdu_status[1] = 0x00; + } // Any other SELECT FILE command will return with a Not Found + } + break; + + case 0xA5: { // SELECT OID + // This is specific to Seos + // Should be a TLV structure with the OID stored in tag 0x06 + uint8_t received_tlv_len = receivedCmd[5 + offset]; + uint8_t *received_tlv = &receivedCmd[6 + offset]; + + bool selected_oid = false; + + // Check all requested OIDs and see if we support any + uint8_t tlv_offset = 0; + while (tlv_offset < received_tlv_len) { + uint8_t tag = received_tlv[tlv_offset++]; + uint8_t length = received_tlv[tlv_offset++]; + uint8_t* value = &received_tlv[tlv_offset]; + if (tag == 0x06) { + if (length == msg->oid_len && memcmp(value, msg->oid, length) == 0) { + selected_oid = true; + break; + } + } + tlv_offset += length; + } + + if (selected_oid) { + // Synthesized IV: half a block of random data followed by half of the CMAC of that data + uint8_t synthesized_iv[max_bs]; + memset(synthesized_iv, 0, bs/2); // TODO: Maybe actually use random data? + generate_cmac(msg->privmac, synthesized_iv, bs/2, synthesized_iv+(bs/2), bs/2, msg->encr_alg); + + // Always exactly 0x30 bytes in length + const uint8_t reply_len = 0x30; + uint8_t reply_idx = 0; + uint8_t reply[reply_len]; + memset(reply, 0, reply_len); + + reply[reply_idx++] = 0x06; // Tag: selected OID + reply[reply_idx++] = msg->oid_len; + memcpy(reply+reply_idx, msg->oid, msg->oid_len); + reply_idx += msg->oid_len; + + reply[reply_idx++] = 0xCF; // Tag: diversifier + reply[reply_idx++] = msg->diversifier_len; + memcpy(reply+reply_idx, msg->diversifier, msg->diversifier_len); + reply_idx += msg->diversifier_len; + + uint8_t cryptogram[reply_len]; + if (!generate_cryptogram(msg->privenc, synthesized_iv, reply, reply_len, cryptogram, msg->encr_alg)) { + Dbprintf(_RED_("Select ADF failed") ": Failed to create reply cryptogram."); + break; + } + + uint8_t tlv_base = 1 + offset; + uint8_t tlv_idx = tlv_base; + + dynamic_response_info.response[tlv_idx++] = 0xCD; // Tag: cryptography type + dynamic_response_info.response[tlv_idx++] = 0x02; // Length + dynamic_response_info.response[tlv_idx++] = msg->encr_alg; + dynamic_response_info.response[tlv_idx++] = msg->hash_alg; + + dynamic_response_info.response[tlv_idx++] = 0x85; // Tag: cryptogram + dynamic_response_info.response[tlv_idx++] = reply_len + bs; // Length + memcpy(dynamic_response_info.response+tlv_idx, synthesized_iv, bs); + tlv_idx += bs; + memcpy(dynamic_response_info.response+tlv_idx, cryptogram, reply_len); + tlv_idx += reply_len; + + // Always an 8-byte CMAC + uint8_t cmac[8]; + uint8_t cmac_size = generate_cmac(msg->privmac, dynamic_response_info.response+tlv_base, tlv_idx-tlv_base, cmac, sizeof(cmac), msg->encr_alg); + + dynamic_response_info.response[tlv_idx++] = 0x8E; // Tag: CMAC + dynamic_response_info.response[tlv_idx++] = cmac_size; // Length + memcpy(dynamic_response_info.response+tlv_idx, cmac, cmac_size); + tlv_idx += cmac_size; + + dynamic_response_info.response_n = tlv_idx; + + // Set status code to Success + apdu_status[0] = 0x90; + apdu_status[1] = 0x00; + } // No error message here because readers may request multiple OIDs before reaching ours + } + break; + + case 0x87: { // MUTUAL AUTH + // This is specific to Seos + // Should be a TLV structure with the OID stored in tag 0x16 + uint8_t *received_tlv = &receivedCmd[6 + offset]; + + if (received_tlv[0] != 0x7C) { + Dbprintf(_RED_("Mutual auth failed") ": Invalid tag, expected 7C, got %02X", received_tlv[0]); + break; + } + + received_tlv += 2; + + if (received_tlv[0] == 0x81) { + // Request for RND.ICC + uint8_t tlv_idx = 1 + offset; + + dynamic_response_info.response[tlv_idx++] = 0x7C; // Tag: mutual auth + dynamic_response_info.response[tlv_idx++] = sizeof(RND_ICC)+2; // Length + dynamic_response_info.response[tlv_idx++] = 0x81; // Tag: request for RND.ICC + dynamic_response_info.response[tlv_idx++] = sizeof(RND_ICC); // Length + memcpy(dynamic_response_info.response+tlv_idx, RND_ICC, sizeof(RND_ICC)); + tlv_idx += sizeof(RND_ICC); + + dynamic_response_info.response_n = tlv_idx; + + // Set status code to Success + apdu_status[0] = 0x90; + apdu_status[1] = 0x00; + } else if (received_tlv[0] == 0x82) { + // Request for challenge + uint8_t received_tlv_len = received_tlv[1]; + received_tlv += 2; + + uint8_t keyslot = receivedCmd[4 + offset]; // APDU P2 byte + + seos_kdf(true, msg->authkey, keyslot, msg->oid, msg->oid_len, msg->diversifier, msg->diversifier_len, diver_encr_key, msg->encr_alg, msg->hash_alg); + seos_kdf(false, msg->authkey, keyslot, msg->oid, msg->oid_len, msg->diversifier, msg->diversifier_len, diver_cmac_key, msg->encr_alg, msg->hash_alg); + + // Verify CMAC (last 8 bytes) + uint8_t request_len = received_tlv_len - 8; + uint8_t cmac[8]; + generate_cmac(diver_cmac_key, received_tlv, request_len, cmac, 8, msg->encr_alg); + if (memcmp(cmac, received_tlv + request_len, 8) != 0) { + Dbprintf(_RED_("Mutual auth failed") ": Invalid CMAC:"); + Dbhexdump(8, received_tlv + request_len, false); + Dbprintf("for data:"); + Dbhexdump(request_len, received_tlv, false); + break; + } + + uint8_t request[received_tlv_len]; + if (!decrypt_cryptogram(diver_encr_key, received_tlv, request_len, request, msg->encr_alg)) { + Dbprintf(_RED_("Mutual auth failed") ": Failed to decrypt cryptogram."); + break; + } + + // request = RND.IFD | RND.ICC | Key.IFD + if (memcmp(RND_ICC, request + 8, 8) != 0) { + Dbprintf(_RED_("Mutual auth failed") ": Incorrect RND.ICC."); + break; + } + memcpy(RND_IFD, request, 8); + memcpy(KEY_IFD, request + 16, 16); + + // reply = RND_ICC | RND_IFD | KEY_ICC + uint8_t reply_plain[32]; + memcpy(reply_plain + 0, RND_ICC, 8); + memcpy(reply_plain + 8, RND_IFD, 8); + memcpy(reply_plain + 16, KEY_ICC, 16); + + // Generate cryptogram + CMAC + uint8_t reply[sizeof(reply_plain)+8]; + generate_cryptogram(diver_encr_key, NULL, reply_plain, sizeof(reply_plain), reply, msg->encr_alg); + generate_cmac(diver_cmac_key, reply, sizeof(reply_plain), reply+sizeof(reply_plain), 8, msg->encr_alg); + + uint8_t tlv_idx = 1 + offset; + + dynamic_response_info.response[tlv_idx++] = 0x7C; // Tag: mutual auth + dynamic_response_info.response[tlv_idx++] = sizeof(reply)+2; // Length + dynamic_response_info.response[tlv_idx++] = 0x82; // Tag: request for challenge + dynamic_response_info.response[tlv_idx++] = sizeof(reply); // Length + memcpy(dynamic_response_info.response+tlv_idx, reply, sizeof(reply)); + tlv_idx += sizeof(reply); + + dynamic_response_info.response_n = tlv_idx; + + // Set status code to Success + apdu_status[0] = 0x90; + apdu_status[1] = 0x00; + + // IMPORTANT: before sending reply, calculate final diversified keys + + uint8_t hash_input[38]; + uint8_t hash_idx = 0; + // Counter + hash_input[hash_idx++] = 0x00; + hash_input[hash_idx++] = 0x00; + hash_input[hash_idx++] = 0x00; + hash_input[hash_idx++] = 0x01; + // Only copy first 8 bytes of each KEY + memcpy(hash_input+hash_idx, KEY_IFD, 8); + hash_idx += 8; + memcpy(hash_input+hash_idx, KEY_ICC, 8); + hash_idx += 8; + // Yes, this is supposed to be the same thing twice + hash_input[hash_idx++] = msg->encr_alg; + hash_input[hash_idx++] = msg->encr_alg; + // Copy full RND values + memcpy(hash_input+hash_idx, RND_ICC, 8); + hash_idx += 8; + memcpy(hash_input+hash_idx, RND_IFD, 8); + hash_idx += 8; + + uint8_t hash_output[40]; + if (msg->hash_alg == SEOS_HASHING_SHA1) { + mbedtls_sha1(hash_input, 38, hash_output); + + // Increment LSB of counter for second hash + hash_input[3]++; + + mbedtls_sha1(hash_input, 38, hash_output + 20); + } else if (msg->hash_alg == SEOS_HASHING_SHA256) { + mbedtls_sha256(hash_input, 38, hash_output, 0); + } else { + Dbprintf(_RED_("Unknown Hashing Algorithm")); + break; + } + + memcpy(diver_encr_key, hash_output, 16); + memcpy(diver_cmac_key, hash_output+16, 16); + } else { + Dbprintf( _RED_("Mutual auth failed") ": Incorrect tag %02X found.", received_tlv[0]); + } + } + break; + + case 0xDA: // PUT DATA + case 0xCB: { // GET DATA + bool is_put = receivedCmd[2 + offset] == 0xDA; + + uint8_t received_tlv_len = receivedCmd[5 + offset]; + uint8_t *received_tlv = &receivedCmd[6 + offset]; + + uint8_t *cryptogram = NULL; + uint8_t *recvd_cmac = NULL; + uint8_t cryptogram_length; + uint8_t recvd_cmac_length; + uint8_t recvd_cmac_offset; + + // Check all requested OIDs and see if we support any + uint8_t tlv_offset = 0; + while (tlv_offset < received_tlv_len) { + uint8_t tag = received_tlv[tlv_offset]; + uint8_t length = received_tlv[tlv_offset+1]; + uint8_t* value = &received_tlv[tlv_offset+2]; + + if (tag == 0x85) { + cryptogram = value; + cryptogram_length = length; + } else if (tag == 0x8e) { + recvd_cmac = value; + recvd_cmac_length = length; + recvd_cmac_offset = tlv_offset; + } + tlv_offset += 2 + length; + } + + if (cryptogram != NULL && recvd_cmac != NULL) { + uint8_t rndCounter[bs]; + memcpy(rndCounter, RND_ICC, bs / 2); + memcpy(rndCounter + bs / 2, RND_IFD, bs / 2); + for (int8_t i=bs-1; i>=0; i--) { + rndCounter[i]++; + if (rndCounter[i] != 0x00) break; + } + + uint8_t cryptogram_padding = recvd_cmac_offset % bs; + if (cryptogram_padding) cryptogram_padding = bs - cryptogram_padding; + + uint8_t padded_apdu_header[bs]; + memset(padded_apdu_header, 0, bs); + memcpy(padded_apdu_header, &receivedCmd[1 + offset], 4); + padded_apdu_header[4] = 0x80; + + uint8_t mac_input[sizeof(rndCounter) + sizeof(padded_apdu_header) + recvd_cmac_offset + cryptogram_padding]; + memset(mac_input, 0, sizeof(mac_input)); + memcpy(mac_input, rndCounter, sizeof(rndCounter)); + memcpy(mac_input+sizeof(rndCounter), padded_apdu_header, sizeof(padded_apdu_header)); + memcpy(mac_input+sizeof(rndCounter)+sizeof(padded_apdu_header), received_tlv, recvd_cmac_offset); + if (cryptogram_padding) { + mac_input[sizeof(rndCounter)+sizeof(padded_apdu_header)+recvd_cmac_offset] = 0x80; + } + + uint8_t cmac[recvd_cmac_length]; + generate_cmac(diver_cmac_key, mac_input, sizeof(mac_input), cmac, recvd_cmac_length, msg->encr_alg); + if (memcmp(cmac, recvd_cmac, recvd_cmac_length) != 0) { + Dbprintf( _RED_("Get Data failed") ": Invalid CMAC:"); + Dbhexdump(recvd_cmac_length, cmac, false); + Dbprintf("for data:"); + Dbhexdump(sizeof(mac_input), mac_input, false); + break; + } + + uint8_t request[cryptogram_length]; + decrypt_cryptogram(diver_encr_key, cryptogram, cryptogram_length, request, msg->encr_alg); + + uint8_t tlv_base = 1 + offset; + uint8_t tlv_idx = tlv_base; + + if (is_put) { + + } else { + //5c 02 ff 00 + if (request[0] != 0x5C) { + Dbprintf(_RED_("Get Data failed") ": Invalid request TLV. Expected tag 5C, but got %02X.", request[0]); + break; + } + + if (request[1] != msg->data_tag_len || memcmp(request+2, msg->data_tag, msg->data_tag_len) != 0) { + Dbprintf(_RED_("Get Data failed") ": Requested invalid data tag."); + break; + } + + uint8_t reply_len = msg->data_tag_len + 1 + msg->data_len; + reply_len = round_to_next(reply_len, bs); + uint8_t reply[reply_len]; + memset(reply, 0, reply_len); + + uint8_t reply_idx = 0; + memcpy(reply+reply_idx, msg->data_tag, msg->data_tag_len); // Tag + reply_idx += msg->data_tag_len; + reply[reply_idx++] = msg->data_len; // Length + memcpy(reply+reply_idx, msg->data, msg->data_len); // Value + reply_idx += msg->data_len; + + if (reply_idx != reply_len) { + // Add 0x80 at first byte after data for start of padding + reply[reply_idx] = 0x80; + } + + uint8_t reply_cryptogram[reply_len]; + if (!generate_cryptogram(diver_encr_key, NULL, reply, reply_len, reply_cryptogram, msg->encr_alg)) { + Dbprintf(_RED_("Get Data failed") ": Failed to create reply cryptogram."); + break; + } + + // Only include a cryptogram for GET DATA + dynamic_response_info.response[tlv_idx++] = 0x85; // Tag: cryptogram + dynamic_response_info.response[tlv_idx++] = reply_len; // Length + memcpy(dynamic_response_info.response+tlv_idx, reply_cryptogram, reply_len); + tlv_idx += reply_len; + } + + // Whether we GET DATA or PUT DATA, add the response status code and CMAC + dynamic_response_info.response[tlv_idx++] = 0x99; // Tag: status code + dynamic_response_info.response[tlv_idx++] = 0x02; // Length + dynamic_response_info.response[tlv_idx++] = 0x90; + dynamic_response_info.response[tlv_idx++] = 0x00; + + + // Unlike every other CMAC, this time we need to increment + // the rndCounter from above again and CMAC with *that* + uint8_t mac_length = sizeof(rndCounter) + (tlv_idx - tlv_base); + mac_length = round_to_next(mac_length, bs); + uint8_t mac_input_2[mac_length]; + for (int8_t i=bs-1; i>=0; i--) { + rndCounter[i]++; + if (rndCounter[i] != 0x00) break; + } + memset(mac_input_2, 0, mac_length); + uint8_t mac_idx = 0; + memcpy(mac_input_2, rndCounter, sizeof(rndCounter)); + mac_idx += sizeof(rndCounter); + memcpy(mac_input_2+sizeof(rndCounter), dynamic_response_info.response + tlv_base, tlv_idx - tlv_base); + mac_idx += tlv_idx - tlv_base; + + if (mac_idx != mac_length) { + // Add 0x80 at first byte after data for start of padding + mac_input_2[mac_idx] = 0x80; + } + + uint8_t cmac_size = generate_cmac(diver_cmac_key, mac_input_2, mac_length, cmac, sizeof(cmac), msg->encr_alg); + + dynamic_response_info.response[tlv_idx++] = 0x8E; // Tag: CMAC + dynamic_response_info.response[tlv_idx++] = cmac_size; // Length + memcpy(dynamic_response_info.response+tlv_idx, cmac, cmac_size); + tlv_idx += cmac_size; + + dynamic_response_info.response_n = tlv_idx; + + // Set status code to Success + apdu_status[0] = 0x90; + apdu_status[1] = 0x00; + } else { + Dbprintf( _RED_("Get Data failed") ": No cryptogram or CMAC found in request."); + } + } + break; + default : { + // Any other non-listed command + // Respond Not Found (default) + } + } + + // Add APDU status code to end of response + dynamic_response_info.response[dynamic_response_info.response_n + 0] = apdu_status[0]; + dynamic_response_info.response[dynamic_response_info.response_n + 1] = apdu_status[1]; + dynamic_response_info.response_n += 2; + } + break; + + case 0xCA: // S-Block Deselect with CID + case 0xC2: { // S-Block Deselect without CID + dynamic_response_info.response[0] = receivedCmd[0]; + dynamic_response_info.response[1] = 0x00; + dynamic_response_info.response_n = 2; + finished = true; + } + break; + + default: { + // Never seen this PCB before + if (g_dbglevel >= DBG_DEBUG) { + Dbprintf("Received unknown command (len=%d):", len); + Dbhexdump(len, receivedCmd, false); + } + if ((receivedCmd[0] & 0x10) == 0x10) { + Dbprintf("Warning, reader sent a chained command but we lack support for it. Ignoring command."); + } + // Do not respond + dynamic_response_info.response_n = 0; + } + break; + } + if (dynamic_response_info.response_n > 0) { + + // Copy the CID from the reader query + if (offset > 0) { + dynamic_response_info.response[1] = receivedCmd[1]; + } + + // Add CRC bytes, always used in ISO 14443A-4 compliant cards + AddCrc14A(dynamic_response_info.response, dynamic_response_info.response_n); + dynamic_response_info.response_n += 2; + + if (prepare_tag_modulation(&dynamic_response_info, DYNAMIC_MODULATION_BUFFER_SIZE) == false) { + if (g_dbglevel >= DBG_DEBUG) DbpString("Error preparing tag response"); + break; + } + p_response = &dynamic_response_info; + } + } + + cmdsRecvd++; + + // Send response + EmSendPrecompiledCmd(p_response); + } + + + switch_off(); + + set_tracing(false); + BigBuf_free_keep_EM(); + + if (g_dbglevel >= DBG_EXTENDED) { + Dbprintf("-[ Num of received cmd [%d]", cmdsRecvd); + } + + reply_ng(CMD_HF_SEOS_SIMULATE, retval, NULL, 0); +} \ No newline at end of file diff --git a/armsrc/seos.h b/armsrc/seos.h new file mode 100644 index 000000000..efa14b9dc --- /dev/null +++ b/armsrc/seos.h @@ -0,0 +1,25 @@ +//----------------------------------------------------------------------------- +// Copyright (C) Aaron Tulino - December 2025 +// Copyright (C) Proxmark3 contributors. See AUTHORS.md for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// See LICENSE.txt for the text of the license. +//----------------------------------------------------------------------------- +#ifndef __SEOS_H +#define __SEOS_H + +#include "common.h" +#include "seos_cmd.h" + +void SimulateSeos(seos_emulate_req_t *msg); + +#endif diff --git a/common_arm/Makefile.hal b/common_arm/Makefile.hal index 568c245ba..3aec0cc3b 100644 --- a/common_arm/Makefile.hal +++ b/common_arm/Makefile.hal @@ -232,6 +232,9 @@ endif ifneq ($(SKIP_ICLASS),1) PLATFORM_DEFS += -DWITH_ICLASS endif +ifneq ($(SKIP_SEOS),1) + PLATFORM_DEFS += -DWITH_SEOS +endif ifneq ($(SKIP_FELICA),1) PLATFORM_DEFS += -DWITH_FELICA endif diff --git a/include/pm3_cmd.h b/include/pm3_cmd.h index e59c4aa54..860dfa9de 100644 --- a/include/pm3_cmd.h +++ b/include/pm3_cmd.h @@ -238,6 +238,7 @@ typedef struct { bool compiled_with_felica : 1; bool compiled_with_legicrf : 1; bool compiled_with_iclass : 1; + bool compiled_with_seos : 1; bool compiled_with_nfcbarcode : 1; // misc bool compiled_with_lcd : 1; @@ -247,7 +248,7 @@ typedef struct { bool hw_available_smartcard : 1; bool is_rdv4 : 1; } PACKED capabilities_t; -#define CAPABILITIES_VERSION 6 +#define CAPABILITIES_VERSION 7 extern capabilities_t g_pm3_capabilities; // For CMD_LF_T55XX_WRITEBL @@ -857,6 +858,8 @@ typedef struct { #define CMD_HF_SAM_SEOS 0x0901 #define CMD_HF_SAM_MFC 0x0902 +#define CMD_HF_SEOS_SIMULATE 0x0903 + #define CMD_UNKNOWN 0xFFFF //Mifare simulation flags diff --git a/include/seos_cmd.h b/include/seos_cmd.h new file mode 100644 index 000000000..05b9a6e0a --- /dev/null +++ b/include/seos_cmd.h @@ -0,0 +1,56 @@ +//----------------------------------------------------------------------------- +// Copyright (C) Proxmark3 contributors. See AUTHORS.md for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// See LICENSE.txt for the text of the license. +//----------------------------------------------------------------------------- +// Seos type prototyping +//----------------------------------------------------------------------------- + +#ifndef _SEOS_CMD_H_ +#define _SEOS_CMD_H_ + +#include "common.h" + +#define SEOS_ENCRYPTION_2K3DES 0x02 +#define SEOS_ENCRYPTION_3K3DES 0x03 +#define SEOS_ENCRYPTION_AES 0x09 + +#define SEOS_HASHING_SHA1 0x06 +#define SEOS_HASHING_SHA256 0x07 + +// Seos emulate request data structure +typedef struct { + uint8_t encr_alg; + uint8_t hash_alg; + + uint8_t uid[10]; + uint8_t uid_len; + + uint8_t privenc[16]; + uint8_t privmac[16]; + uint8_t authkey[16]; + + uint8_t diversifier_len; + uint8_t diversifier[16]; + + uint8_t data_tag_len; + uint8_t data_tag[8]; + + uint8_t data_len; + uint8_t data[128]; + + uint8_t oid_len; + uint8_t oid[32]; +} PACKED seos_emulate_req_t; + +#endif // _SEOS_H_