//----------------------------------------------------------------------------- // 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 MFC <-> SAM communication //----------------------------------------------------------------------------- #include #include "sam_common.h" #include "iclass.h" #include "proxmark3_arm.h" #include "BigBuf.h" #include "commonutil.h" #include "ticks.h" #include "dbprint.h" #include "i2c.h" #include "iso15693.h" #include "protocols.h" /** * @brief Transmits data to and receives data from a HID®'s iCLASS® SE™ Processor. * * This function sends a specified number of bytes to the SAM and receives a response. * * @param data Pointer to the data to be transmitted. * @param n Number of bytes to be transmitted. * @param resp Pointer to the buffer where the response will be stored. * @param resplen Pointer to the variable where the length of the response will be stored. * @return Status code indicating success or failure of the operation. */ int sam_rxtx(const uint8_t *data, uint16_t n, uint8_t *resp, uint16_t *resplen) { bool res = I2C_BufferWrite(data, n, I2C_DEVICE_CMD_SEND_T0, I2C_DEVICE_ADDRESS_MAIN); if (res == false) { DbpString("failed to send to SIM CARD"); goto out; } *resplen = ISO7816_MAX_FRAME; res = sc_rx_bytes(resp, resplen, SIM_WAIT_DELAY); if (res == false) { DbpString("failed to receive from SIM CARD"); goto out; } if (*resplen < 2) { DbpString("received too few bytes from SIM CARD"); res = false; goto out; } uint16_t more_len = 0; if (resp[*resplen - 2] == 0x61 || resp[*resplen - 2] == 0x9F) { more_len = resp[*resplen - 1]; } else { // we done, return goto out; } // Don't discard data we already received except the SW code. // If we only received 1 byte, this is the echo of INS, we discard it. *resplen -= 2; if (*resplen == 1) { *resplen = 0; } uint8_t cmd_getresp[] = {0x00, ISO7816_GET_RESPONSE, 0x00, 0x00, more_len}; res = I2C_BufferWrite(cmd_getresp, sizeof(cmd_getresp), I2C_DEVICE_CMD_SEND_T0, I2C_DEVICE_ADDRESS_MAIN); if (res == false) { DbpString("failed to send to SIM CARD 2"); goto out; } more_len = 255 - *resplen; res = sc_rx_bytes(resp + *resplen, &more_len, SIM_WAIT_DELAY); if (res == false) { DbpString("failed to receive from SIM CARD 2"); goto out; } *resplen += more_len; out: return res; } static inline void swap_clock_counters(volatile unsigned int *a, unsigned int *b) { unsigned int c = *a; *a = *b; *b = c; } /** * @brief Swaps the timer counter values. * * AT91SAM7S512 has a single Timer-Counter, that is reused in clocks Ticks * and CountSspClk. This function stops the current clock and restores previous * values. It is used to switch between different clock sources. * It probably makes communication timing off, but at least makes it work. */ static void swap_clocks(void) { static unsigned int tc0, tc1, tc2 = 0; StopTicks(); swap_clock_counters(&(AT91C_BASE_TC0->TC_CV), &tc0); swap_clock_counters(&(AT91C_BASE_TC1->TC_CV), &tc1); swap_clock_counters(&(AT91C_BASE_TC2->TC_CV), &tc2); } void switch_clock_to_ticks(void) { swap_clocks(); StartTicks(); } void switch_clock_to_countsspclk(void) { swap_clocks(); StartCountSspClk(); } /** * @brief Sends a payload to the SAM * * This function prepends the payload with the necessary APDU and application * headers and sends it to the SAM. * * @param addr_src 0x14 for command from NFC, 0x44 for command from application * @param addr_dest 0x0A for command to SAM * @param addr_reply same as add_src or 0x00 if no reply is expected * @param payload Pointer to the data to be sent. * @param payload_len Length of the data to be sent. * @param response Pointer to the buffer where the response will be stored. * @param response_len Pointer to the variable where the length of the response will be stored. * @param length Length of the data to be sent. * @return Status code indicating success or failure of the operation. */ int sam_send_payload( const uint8_t addr_src, const uint8_t addr_dest, const uint8_t addr_reply, const uint8_t *const payload, const uint16_t *payload_len, uint8_t *response, uint16_t *response_len ) { int res = PM3_SUCCESS; uint8_t *buf = response; buf[0] = 0xA0; // CLA buf[1] = 0xDA; // INS (PUT DATA) buf[2] = 0x02; // P1 (TLV format?) buf[3] = 0x63; // P2 buf[4] = SAM_TX_ASN1_PREFIX_LENGTH + (uint8_t) * payload_len; // LEN buf[5] = addr_src; buf[6] = addr_dest; buf[7] = addr_reply; buf[8] = 0x00; buf[9] = 0x00; buf[10] = 0x00; memcpy( &buf[11], payload, *payload_len ); uint16_t length = SAM_TX_ASN1_PREFIX_LENGTH + SAM_TX_APDU_PREFIX_LENGTH + (uint8_t) * payload_len; LogTrace(buf, length, 0, 0, NULL, true); if (g_dbglevel >= DBG_INFO) { DbpString("SAM REQUEST APDU: "); Dbhexdump(length, buf, false); } if (sam_rxtx(buf, length, response, response_len) == false) { if (g_dbglevel >= DBG_ERROR) DbpString("SAM ERROR"); res = PM3_ECARDEXCHANGE; goto out; } LogTrace(response, *response_len, 0, 0, NULL, false); if (g_dbglevel >= DBG_INFO) { DbpString("SAM RESPONSE APDU: "); Dbhexdump(*response_len, response, false); } out: return res; } /** * @brief Retreives SAM firmware version. * * Used just as ping or sanity check here. * * @return Status code indicating success or failure of the operation. */ int sam_get_version(void) { int res = PM3_SUCCESS; if (g_dbglevel >= DBG_DEBUG) DbpString("start sam_get_version"); uint8_t *response = BigBuf_malloc(ISO7816_MAX_FRAME); uint16_t response_len = ISO7816_MAX_FRAME; uint8_t payload[] = { 0xa0, 0x02, // <- SAM command 0x82, 0x00 // <- get version }; uint16_t payload_len = sizeof(payload); sam_send_payload( 0x44, 0x0a, 0x44, payload, &payload_len, response, &response_len ); // resp: // c1 64 00 00 00 // bd 11 <- SAM response // 8a 0f <- get version response // 80 02 // 01 29 <- version // 81 06 // 68 3d 05 20 26 b6 <- build ID // 82 01 // 01 // 90 00 if (g_dbglevel >= DBG_DEBUG) DbpString("end sam_get_version"); if (response[5] != 0xbd) { Dbprintf("Invalid SAM response"); goto error; } else { uint8_t *sam_response_an = sam_find_asn1_node(response + 5, 0x8a); if (sam_response_an == NULL) { if (g_dbglevel >= DBG_ERROR) DbpString("SAM get response failed"); goto error; } uint8_t *sam_version_an = sam_find_asn1_node(sam_response_an, 0x80); if (sam_version_an == NULL) { if (g_dbglevel >= DBG_ERROR) DbpString("SAM get version failed"); goto error; } uint8_t *sam_build_an = sam_find_asn1_node(sam_response_an, 0x81); if (sam_build_an == NULL) { if (g_dbglevel >= DBG_ERROR) DbpString("SAM get firmware ID failed"); goto error; } if (g_dbglevel >= DBG_INFO) { DbpString("SAM get version successful"); Dbprintf("Firmware version: %X.%X", sam_version_an[2], sam_version_an[3]); Dbprintf("Firmware ID: "); Dbhexdump(sam_build_an[1], sam_build_an + 2, false); } goto out; } error: res = PM3_ESOFT; out: BigBuf_free(); if (g_dbglevel >= DBG_DEBUG) DbpString("end sam_get_version"); return res; } /** * @brief Finds an ASN.1 node of a specified type within a given root node. * * This function searches through a single level of the ASN.1 structure starting * from the root node to find a node of the specified type. * * @param root Pointer to the root node of the ASN.1 structure. * @param type The type of the ASN.1 node to find. * @return Pointer to the ASN.1 node of the specified type if found, otherwise NULL. */ uint8_t *sam_find_asn1_node(const uint8_t *root, const uint8_t type) { const uint8_t *end = (uint8_t *) root + *(root + 1); uint8_t *current = (uint8_t *) root + 2; while (current < end) { if (*current == type) { return current; } else { current += 2 + *(current + 1); } } return NULL; } /** * @brief Appends an ASN.1 node to the end of a given node. * * This function appends an ASN.1 node of a specified type and length to the end of * the ASN.1 structure at specified node level. * * It is the most naive solution that does not handle the case where the node to append is * not the last node at the same level. It also does not also care about proper * order of the nodes. * * @param root Pointer to the root node of the ASN.1 structure. * @param root Pointer to the node to be appended of the ASN.1 structure. * @param type The type of the ASN.1 node to append. * @param data Pointer to the data to be appended. * @param len The length of the data to be appended. */ void sam_append_asn1_node(const uint8_t *root, const uint8_t *node, uint8_t type, const uint8_t *const data, uint8_t len) { uint8_t *end = (uint8_t *) root + *(root + 1) + 2; *(end) = type; *(end + 1) = len; memcpy(end + 2, data, len); for (uint8_t *current = (uint8_t *) root; current <= node; current += 2) { *(current + 1) += 2 + len; }; return; } void sam_send_ack(void) { uint8_t *response = BigBuf_malloc(ISO7816_MAX_FRAME); uint16_t response_len = ISO7816_MAX_FRAME; uint8_t payload[] = { 0xa0, 0 }; uint16_t payload_len = sizeof(payload); sam_send_payload( 0x44, 0x0a, 0x00, payload, &payload_len, response, &response_len ); BigBuf_free(); }