mirror of
https://github.com/RfidResearchGroup/proxmark3.git
synced 2026-09-25 15:34:07 +00:00
931 lines
30 KiB
C
931 lines
30 KiB
C
//-----------------------------------------------------------------------------
|
|
// 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 <string.h>
|
|
#include "sam_common.h"
|
|
#include "util.h" // switch_clock_to_ticks / _countsspclk
|
|
#include "iclass.h"
|
|
#include "proxmark3_arm.h"
|
|
#include "BigBuf.h"
|
|
#include "commonutil.h"
|
|
#include "ticks_apis.h"
|
|
#include "dbprint.h"
|
|
#include "i2c.h"
|
|
#include "iso15693.h"
|
|
#include "protocols.h"
|
|
#include "crc16.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.
|
|
*/
|
|
/*
|
|
* Offset of the 0xBD response node inside a SAM reply.
|
|
*
|
|
* Ahead of it sits a routing tail that is either 5 or 6 bytes long: the request
|
|
* header we build is always 6 (FROM, TO, REPLY-TO, 0x00, 0x00, scFlag), and
|
|
* some SAMs mirror all of it while others drop a byte. A HID iCLASS SE "Grace"
|
|
* part answers
|
|
*
|
|
* 0a 44 00 00 00 00 | bd 11 8a 0f 80 02 01 29 ...
|
|
*
|
|
* where SAM_RX_ASN1_PREFIX_LENGTH on its own lands one byte short and every
|
|
* caller then rejects a perfectly good response.
|
|
*
|
|
* Only those two offsets are ever considered, deliberately: 0xBD occurs inside
|
|
* SAM payloads as well, so an open ended search would eventually latch onto the
|
|
* wrong one. Returns 0 when neither holds it, which is never a valid offset.
|
|
*/
|
|
uint16_t sam_bd_offset(const uint8_t *response, uint16_t response_len) {
|
|
|
|
uint16_t fallback = 0;
|
|
|
|
for (uint16_t ofs = SAM_RX_ASN1_PREFIX_LENGTH;
|
|
ofs <= (uint16_t)(SAM_RX_ASN1_PREFIX_LENGTH + 1);
|
|
ofs++) {
|
|
|
|
if ((uint16_t)(ofs + 1) >= response_len) {
|
|
break;
|
|
}
|
|
if (response[ofs] != 0xBD) {
|
|
continue;
|
|
}
|
|
|
|
// A 0xBD in the right place accounts for the rest of the frame exactly:
|
|
// tag, length byte, that many bytes of contents, then SW1 SW2. This is
|
|
// what tells a real response node apart from a 0xBD that happens to sit
|
|
// in the routing tail - without it a SAM whose scFlag were 0xBD would
|
|
// resolve to the wrong offset.
|
|
// Length is short form, or long form with one length byte (0x81 <len>),
|
|
// which is what an SNMP shaped reply over 127 bytes uses.
|
|
uint16_t hdr = 2;
|
|
uint16_t node_len = response[ofs + 1];
|
|
if (node_len == 0x81) {
|
|
if ((uint16_t)(ofs + 2) >= response_len) {
|
|
continue;
|
|
}
|
|
hdr = 3;
|
|
node_len = response[ofs + 2];
|
|
}
|
|
|
|
if ((uint16_t)(ofs + hdr + node_len + 2) == response_len) {
|
|
return ofs;
|
|
}
|
|
if (fallback == 0) {
|
|
fallback = ofs;
|
|
}
|
|
}
|
|
|
|
// Nothing accounted for the whole frame; hand back a plain 0xBD match if
|
|
// there was one, so a caller that only wants the tag still works.
|
|
return fallback;
|
|
}
|
|
|
|
/*
|
|
* Locate the SAM's response node and work out how many bytes from there make up
|
|
* the reply to forward.
|
|
*
|
|
* This arithmetic used to be written out three times - twice in sam_picopass.c,
|
|
* once in sam_seos.c - each with a hardcoded 5 byte routing tail and no bounds
|
|
* checks at all, so a short or truncated frame walked off the end of the
|
|
* buffer. One copy, one place to be wrong.
|
|
*
|
|
* Returns the offset of the response node. *payload_len gets the length from
|
|
* that offset, clamped to what actually arrived, or 0 if the frame is too short
|
|
* to hold anything.
|
|
*/
|
|
// How many bytes of routing tail this SAM puts in front of the ASN.1 payload.
|
|
// A Grace SAM uses 6 where SAM_RX_ASN1_PREFIX_LENGTH says 5, so pick the one
|
|
// whose node length accounts for the frame exactly: tag, length, contents,
|
|
// SW1 SW2.
|
|
uint16_t sam_rx_prefix_len(const uint8_t *rx, uint16_t rx_len) {
|
|
|
|
uint16_t fallback = 0;
|
|
|
|
for (uint16_t ofs = SAM_RX_ASN1_PREFIX_LENGTH;
|
|
ofs <= (uint16_t)(SAM_RX_ASN1_PREFIX_LENGTH + 1);
|
|
ofs++) {
|
|
|
|
if ((uint16_t)(ofs + 1) >= rx_len) {
|
|
break;
|
|
}
|
|
if ((rx[ofs] != 0xa1) && (rx[ofs] != 0xbd)) {
|
|
continue;
|
|
}
|
|
if ((uint16_t)(ofs + 2 + rx[ofs + 1] + 2) == rx_len) {
|
|
return ofs;
|
|
}
|
|
if (fallback == 0) {
|
|
fallback = ofs;
|
|
}
|
|
}
|
|
|
|
return (fallback != 0) ? fallback : (uint16_t)SAM_RX_ASN1_PREFIX_LENGTH;
|
|
}
|
|
|
|
// The SAM asks for a card exchange with an a1 node holding an 80 <len> APDU.
|
|
// Older SAMs flagged it with 0x61 in the routing tail, which is where the
|
|
// fixed sam_rx_buf[1] test came from - a Grace SAM puts 0x14 there instead, so
|
|
// key off the ASN.1 node, which both generations agree on.
|
|
bool sam_relay_pending(const uint8_t *rx, uint16_t rx_len) {
|
|
|
|
uint16_t p = sam_rx_prefix_len(rx, rx_len);
|
|
if ((uint16_t)(p + 4) >= rx_len) {
|
|
return false;
|
|
}
|
|
return ((rx[p] == 0xa1) && (rx[p + 2] == 0xa1) && (rx[p + 4] == 0x80));
|
|
}
|
|
|
|
// The tag <-> SAM relay ends on an a1 02 82 00 node. The routing tail is 5 or
|
|
// 6 bytes depending on the SAM - the same reason sam_bd_offset() searches - so
|
|
// anchor on the node rather than indexing a fixed offset 7.
|
|
bool sam_relay_complete(const uint8_t *rx, uint16_t rx_len) {
|
|
|
|
uint16_t ofs = sam_rx_prefix_len(rx, rx_len);
|
|
if ((uint16_t)(ofs + 2) >= rx_len) {
|
|
return false;
|
|
}
|
|
return ((rx[ofs] == 0xa1) && (rx[ofs + 2] == 0x82));
|
|
}
|
|
|
|
uint16_t sam_response_payload(const uint8_t *rx, uint16_t rx_len, uint16_t *payload_len) {
|
|
|
|
uint16_t ofs = sam_bd_offset(rx, rx_len);
|
|
if (ofs == 0) {
|
|
ofs = SAM_RX_ASN1_PREFIX_LENGTH;
|
|
}
|
|
|
|
*payload_len = 0;
|
|
|
|
// An SNMP shaped reply over 127 bytes carries a long form length,
|
|
// bd 81 <len>. Read it from the length byte itself rather than matching one
|
|
// known inner tag, or any other node in that form parses as 0x81 + 2.
|
|
if (((uint16_t)(ofs + 2) < rx_len) && (rx[ofs + 1] == 0x81)) {
|
|
|
|
*payload_len = (uint16_t)(rx[ofs + 2] + 3);
|
|
|
|
} else if ((uint16_t)(ofs + 1) < rx_len) {
|
|
|
|
*payload_len = (uint16_t)(rx[ofs + 1] + 2);
|
|
}
|
|
|
|
// never hand back more than arrived
|
|
if ((uint16_t)(ofs + *payload_len) > rx_len) {
|
|
*payload_len = (rx_len > ofs) ? (uint16_t)(rx_len - ofs) : 0;
|
|
}
|
|
|
|
return ofs;
|
|
}
|
|
|
|
int sam_rxtx(const uint8_t *data, uint16_t n, uint8_t *resp, uint16_t *resplen) {
|
|
// Whatever protocol GetATR()/PPS left the card on, rather than an assumed
|
|
// T=0. Resolved once per exchange so the GET RESPONSE round below cannot
|
|
// end up on a different protocol than the command it belongs to.
|
|
const uint8_t active_cmd = sc_active_device_cmd();
|
|
const bool t1 = (active_cmd == I2C_DEVICE_CMD_SEND_T1);
|
|
// Use the v4.65+ compatibility T=0 opcode when available. Grace responses
|
|
// still assemble their 61xx/9Fxx continuations below on the PM3: they may
|
|
// carry material response data before the continuation status. T=1 already
|
|
// returns its whole APDU response through the module's block layer.
|
|
#if SAM_T0_AUTORESP
|
|
const uint8_t dev_cmd = (active_cmd == I2C_DEVICE_CMD_SEND_T0)
|
|
? I2C_DEVICE_CMD_SEND_T0_AUTORESP
|
|
: active_cmd;
|
|
#else
|
|
const uint8_t dev_cmd = active_cmd;
|
|
#endif
|
|
|
|
uint32_t tx_start = GetTicks();
|
|
bool res = I2C_BufferWrite(data, n, dev_cmd, I2C_DEVICE_ADDRESS_MAIN);
|
|
sc_log_trace_span(data, n, true, tx_start);
|
|
|
|
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) {
|
|
for (uint8_t attempt = 1; attempt <= 15; attempt++) {
|
|
SpinDelay(200);
|
|
*resplen = ISO7816_MAX_FRAME;
|
|
res = sc_rx_bytes(resp, resplen, SIM_WAIT_DELAY);
|
|
if (res) {
|
|
if (g_dbglevel >= DBG_INFO)
|
|
Dbprintf("SAM slow first-reply recovered after %u re-poll(s)", attempt);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
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];
|
|
|
|
// The GET RESPONSE round below is ours, not the caller's, so log both
|
|
// halves of it. Without this the trace shows a case 3 command coming
|
|
// back with a full data answer, which T=0 cannot do.
|
|
sc_log_trace(resp, *resplen, false);
|
|
} 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;
|
|
}
|
|
|
|
// Grace T=1 exchanges use extended APDUs. The normal short T=0 GET
|
|
// RESPONSE is invalid on that path, so use an extended Le when a T=1
|
|
// response explicitly asks for more data.
|
|
uint8_t cmd_getresp_t0[] = {0x00, ISO7816_GET_RESPONSE, 0x00, 0x00, more_len};
|
|
uint16_t want = more_len ? more_len : 256;
|
|
uint8_t cmd_getresp_t1[] = {0x00, ISO7816_GET_RESPONSE, 0x00, 0x00,
|
|
0x00, (uint8_t)(want >> 8), (uint8_t)want
|
|
};
|
|
const uint8_t *cmd_getresp = t1 ? cmd_getresp_t1 : cmd_getresp_t0;
|
|
const uint16_t cmd_getresp_len = t1 ? sizeof(cmd_getresp_t1) : sizeof(cmd_getresp_t0);
|
|
|
|
tx_start = GetTicks();
|
|
res = I2C_BufferWrite(cmd_getresp, cmd_getresp_len, active_cmd, I2C_DEVICE_ADDRESS_MAIN);
|
|
sc_log_trace_span(cmd_getresp, cmd_getresp_len, true, tx_start);
|
|
|
|
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;
|
|
}
|
|
|
|
|
|
/**
|
|
* @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
|
|
) {
|
|
return sam_send_payload_ex(addr_src, addr_dest, addr_reply, 0x00,
|
|
payload, payload_len,
|
|
response, response_len);
|
|
}
|
|
|
|
int sam_send_payload_ex(
|
|
const uint8_t addr_src,
|
|
const uint8_t addr_dest,
|
|
const uint8_t addr_reply,
|
|
const uint8_t scFlag,
|
|
|
|
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;
|
|
const uint16_t inner_len = (uint16_t)(SAM_TX_ASN1_PREFIX_LENGTH + *payload_len);
|
|
const bool t1 = (sc_active_device_cmd() == I2C_DEVICE_CMD_SEND_T1);
|
|
uint16_t payload_offset = SAM_TX_APDU_PREFIX_LENGTH;
|
|
|
|
if ((uint32_t)inner_len + (t1 ? 9u : 5u) > ISO7816_MAX_FRAME) {
|
|
return PM3_EINVARG;
|
|
}
|
|
if (!t1 && inner_len > 0xff) {
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
buf[0] = 0xA0; // CLA
|
|
buf[1] = 0xDA; // INS (PUT DATA)
|
|
buf[2] = 0x02; // P1 (TLV format?)
|
|
buf[3] = 0x63; // P2
|
|
if (t1) {
|
|
// The Artemis T=1 service accepts Grace in extended APDU form. This
|
|
// matches the working ACR39U exchange: 00 <Lc-hi> <Lc-lo> ... 0000.
|
|
buf[4] = 0x00;
|
|
buf[5] = (uint8_t)(inner_len >> 8);
|
|
buf[6] = (uint8_t)inner_len;
|
|
payload_offset = 7;
|
|
} else {
|
|
buf[4] = (uint8_t)inner_len;
|
|
}
|
|
|
|
// Grace routing header: FROM, TO, REPLY-TO, 0x00, 0x00, scFlag
|
|
buf[payload_offset] = addr_src;
|
|
buf[payload_offset + 1] = addr_dest;
|
|
buf[payload_offset + 2] = addr_reply;
|
|
|
|
buf[payload_offset + 3] = 0x00;
|
|
buf[payload_offset + 4] = 0x00;
|
|
buf[payload_offset + 5] = scFlag;
|
|
|
|
memcpy(
|
|
&buf[payload_offset + SAM_TX_ASN1_PREFIX_LENGTH],
|
|
payload,
|
|
*payload_len
|
|
);
|
|
|
|
uint16_t length = (uint16_t)(payload_offset + inner_len);
|
|
if (t1) {
|
|
buf[length++] = 0x00; // extended Le = 65536 (maximum response)
|
|
buf[length++] = 0x00;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
sc_log_trace(response, *response_len, 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(bool info) {
|
|
int res = PM3_SUCCESS;
|
|
|
|
if (g_dbglevel >= DBG_DEBUG) {
|
|
DbpString("start sam_get_version");
|
|
}
|
|
|
|
uint8_t *response = BigBuf_calloc(ISO7816_MAX_FRAME);
|
|
uint16_t response_len = ISO7816_MAX_FRAME;
|
|
|
|
uint8_t payload[] = {
|
|
0xa0, // <- SAM command
|
|
0x02, // <- Length
|
|
0x82, 0x00 // <- get version
|
|
};
|
|
uint16_t payload_len = sizeof(payload);
|
|
|
|
int exchange = sam_send_payload(
|
|
0x44, 0x0a, 0x44,
|
|
payload,
|
|
&payload_len,
|
|
response,
|
|
&response_len
|
|
);
|
|
|
|
if (exchange != PM3_SUCCESS) {
|
|
res = exchange;
|
|
goto out;
|
|
}
|
|
|
|
// The Artemis T=1 endpoint accepts the extended GetVersion warmup with a
|
|
// bare 9000 (unlike the T=0 endpoint, it does not return the version TLV).
|
|
// It is only a link-settling ping here, so a successful status is enough;
|
|
// the following InitAuth exchange performs the actual authentication.
|
|
if ((sc_active_device_cmd() == I2C_DEVICE_CMD_SEND_T1) &&
|
|
(response_len >= 2) &&
|
|
(response[response_len - 2] == 0x90) &&
|
|
(response[response_len - 1] == 0x00)) {
|
|
goto out;
|
|
}
|
|
|
|
// 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");
|
|
}
|
|
|
|
uint16_t bd = sam_bd_offset(response, response_len);
|
|
if (bd == 0) {
|
|
Dbprintf("Invalid SAM response");
|
|
goto error;
|
|
} else {
|
|
uint8_t *sam_response_an = sam_find_asn1_node(response + bd, 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(_RED_("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(_RED_("SAM: get firmware ID failed"));
|
|
goto error;
|
|
}
|
|
if (g_dbglevel >= DBG_INFO || info) {
|
|
DbpString(_BLUE_("-- SAM Information --"));
|
|
Dbprintf(_YELLOW_("Firmware version: ")"%d.%d", sam_version_an[2], sam_version_an[3]);
|
|
Dbprintf(_YELLOW_("Firmware ID: "));
|
|
Dbhexdump(sam_build_an[1], sam_build_an + 2, false);
|
|
}
|
|
goto out;
|
|
}
|
|
|
|
error:
|
|
res = PM3_ESOFT;
|
|
|
|
out:
|
|
BigBuf_free_keep_EM();
|
|
|
|
if (g_dbglevel >= DBG_DEBUG) {
|
|
DbpString("end sam_get_version");
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
int sam_get_serial_number(void) {
|
|
int res = PM3_SUCCESS;
|
|
|
|
if (g_dbglevel >= DBG_DEBUG) {
|
|
DbpString("start sam_get_serial_number");
|
|
}
|
|
|
|
uint8_t *response = BigBuf_calloc(ISO7816_MAX_FRAME);
|
|
uint16_t response_len = ISO7816_MAX_FRAME;
|
|
|
|
uint8_t payload[] = {
|
|
0xa0, // <- SAM command
|
|
0x02, // <- Length
|
|
0x96, 0x00 // <- get serial number
|
|
};
|
|
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 0e <- SAM response
|
|
// 8a 0c <- get serial number response
|
|
// 61 01 13 51 22 66 6e 15 3e 1b ff ff
|
|
//90 00
|
|
|
|
if (g_dbglevel >= DBG_DEBUG) {
|
|
DbpString("end sam_get_serial_number");
|
|
}
|
|
|
|
uint16_t bd = sam_bd_offset(response, response_len);
|
|
if (bd == 0) {
|
|
Dbprintf("Invalid SAM response");
|
|
goto error;
|
|
} else {
|
|
uint8_t *sam_response_an = sam_find_asn1_node(response + bd, 0x8a);
|
|
if (sam_response_an == NULL) {
|
|
if (g_dbglevel >= DBG_ERROR) DbpString(_RED_("SAM: get response failed"));
|
|
goto error;
|
|
}
|
|
uint8_t *sam_serial_an = sam_response_an + 2;
|
|
|
|
Dbprintf(_YELLOW_("Serial Number: "));
|
|
Dbhexdump(sam_response_an[1], sam_serial_an, false);
|
|
|
|
goto out;
|
|
}
|
|
|
|
error:
|
|
res = PM3_ESOFT;
|
|
|
|
out:
|
|
BigBuf_free_keep_EM();
|
|
|
|
if (g_dbglevel >= DBG_DEBUG) {
|
|
DbpString("end sam_get_serial_number");
|
|
}
|
|
|
|
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_calloc(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();
|
|
}
|
|
|
|
/**
|
|
* @brief Copies the payload from an NFC buffer to a SAM buffer.
|
|
*
|
|
* Wraps received data from NFC into an ASN1 tree, so it can be transmitted to the SAM .
|
|
*
|
|
* @param sam_tx Pointer to the SAM transmit buffer.
|
|
* @param nfc_rx Pointer to the NFC receive buffer.
|
|
* @param nfc_len Length of the data to be copied from the NFC buffer.
|
|
*
|
|
* @return Length of SAM APDU to be sent.
|
|
*/
|
|
uint16_t sam_copy_payload_nfc2sam(uint8_t *sam_tx, uint8_t *nfc_rx, uint8_t nfc_len) {
|
|
// NFC resp:
|
|
// 6f 0c 84 0a a0 00 00 04 40 00 01 01 00 01 90 00 fb e3
|
|
|
|
// SAM req:
|
|
// bd 1c
|
|
// a0 1a
|
|
// a0 18
|
|
// 80 12
|
|
// 6f 0c 84 0a a0 00 00 04 40 00 01 01 00 01 90 00 fb e3
|
|
// 81 02
|
|
// 00 00
|
|
|
|
const uint8_t payload[] = {
|
|
0xbd, 4,
|
|
0xa0, 2,
|
|
0xa0, 0
|
|
};
|
|
|
|
const uint8_t tag81[] = {
|
|
0x00, 0x00
|
|
};
|
|
|
|
memcpy(sam_tx, payload, sizeof(payload));
|
|
|
|
sam_append_asn1_node(sam_tx, sam_tx + 4, 0x80, nfc_rx, nfc_len);
|
|
sam_append_asn1_node(sam_tx, sam_tx + 4, 0x81, tag81, sizeof(tag81));
|
|
|
|
return sam_tx[1] + 2; // length of the ASN1 tree
|
|
}
|
|
|
|
/**
|
|
* @brief Copies the payload from the SAM receive buffer to the NFC transmit buffer.
|
|
*
|
|
* Unpacks data to be transmitted from ASN1 tree in APDU received from SAM.
|
|
*
|
|
* @param nfc_tx_buf Pointer to the buffer where the NFC transmit data will be stored.
|
|
* @param sam_rx_buf Pointer to the buffer containing the data received from the SAM.
|
|
* @return Length of NFC APDU to be sent.
|
|
*/
|
|
int sam_relay_iso15_loop(
|
|
uint8_t *sam_tx_buf,
|
|
uint8_t *sam_rx_buf,
|
|
uint16_t *sam_rx_len,
|
|
bool shallow_mod,
|
|
bool break_on_nr_mac,
|
|
bool prevent_epurse_update,
|
|
uint8_t *nr_mac_out,
|
|
uint16_t *nr_mac_len_out,
|
|
bool *got_nr_mac
|
|
) {
|
|
int res = PM3_SUCCESS;
|
|
|
|
if (got_nr_mac != NULL) {
|
|
*got_nr_mac = false;
|
|
}
|
|
|
|
// Nothing to relay - the SAM answered directly (final response already in
|
|
// sam_rx_buf). This is the normal case for SAM-internal commands.
|
|
if (sam_relay_pending(sam_rx_buf, *sam_rx_len) == false) {
|
|
return PM3_SUCCESS;
|
|
}
|
|
|
|
// The two scratch buffers double as the NFC tx/rx buffers, exactly as
|
|
// sam_send_request_iso15 does.
|
|
uint8_t *nfc_tx_buf = sam_tx_buf;
|
|
uint8_t *nfc_rx_buf = sam_rx_buf;
|
|
uint16_t nfc_tx_len;
|
|
uint16_t nfc_rx_len;
|
|
uint16_t sam_tx_len;
|
|
|
|
switch_clock_to_countsspclk();
|
|
|
|
// tag <-> SAM exchange starts here
|
|
while (sam_relay_pending(sam_rx_buf, *sam_rx_len)) {
|
|
uint32_t start_time = GetCountSspClk();
|
|
uint32_t eof_time = start_time + DELAY_ICLASS_VICC_TO_VCD_READER;
|
|
|
|
nfc_tx_len = sam_copy_payload_sam2nfc(nfc_tx_buf, sam_rx_buf, *sam_rx_len);
|
|
|
|
// PAGESEL (0x84) substitution for 2K PicoPass cards. A 2K card has a
|
|
// single book/page and does not answer PAGESEL, but the encode-side SAM
|
|
// emits it to select the page before writing. PAGESEL returns the page
|
|
// config block (block 1), so we send a READ of block 1 instead: the 2K
|
|
// card answers that, and the SAM gets the config it expects and moves on
|
|
// to auth/write. (16K cards answer PAGESEL natively; substituting a
|
|
// block-1 read for PAGESEL-page-0 is equivalent there too.)
|
|
if (nfc_tx_len >= 2 && (nfc_tx_buf[0] & 0x0F) == ICLASS_CMD_PAGESEL) {
|
|
if (g_dbglevel >= DBG_INFO) {
|
|
DbpString("PAGESEL on 2K card - substituting READ block 1");
|
|
}
|
|
nfc_tx_buf[0] = ICLASS_CMD_READ_OR_IDENTIFY; // 0x0C
|
|
nfc_tx_buf[1] = 0x01; // block 1 = config
|
|
// iCLASS command CRC covers the block byte only, not the opcode
|
|
// (see iclass.c: read_conf = 0C 01 FA 22, AddCrc(c + 1, 1)).
|
|
AddCrc(nfc_tx_buf + 1, 1);
|
|
nfc_tx_len = 4;
|
|
}
|
|
|
|
bool is_cmd_check = ((nfc_tx_buf[0] & 0x0F) == ICLASS_CMD_CHECK);
|
|
|
|
if (is_cmd_check && break_on_nr_mac) {
|
|
if (nr_mac_out != NULL && nr_mac_len_out != NULL) {
|
|
memcpy(nr_mac_out, nfc_tx_buf, nfc_tx_len);
|
|
*nr_mac_len_out = nfc_tx_len;
|
|
}
|
|
if (got_nr_mac != NULL) {
|
|
*got_nr_mac = true;
|
|
}
|
|
if (g_dbglevel >= DBG_INFO) {
|
|
DbpString("NR-MAC: ");
|
|
Dbhexdump(nfc_tx_len - 1, nfc_tx_buf + 1, false);
|
|
}
|
|
return PM3_SUCCESS;
|
|
}
|
|
|
|
bool is_cmd_update = ((nfc_tx_buf[0] & 0x0F) == ICLASS_CMD_UPDATE);
|
|
|
|
if (is_cmd_update && prevent_epurse_update && nfc_tx_buf[0] == 0x87 && nfc_tx_buf[1] == 0x02) {
|
|
// block update(2) command and fake the response to prevent update of epurse
|
|
memcpy(nfc_rx_buf + 0, nfc_tx_buf + 6, 4);
|
|
memcpy(nfc_rx_buf + 4, nfc_tx_buf + 0, 4);
|
|
AddCrc(nfc_rx_buf, 8);
|
|
nfc_rx_len = 10;
|
|
|
|
if (g_dbglevel >= DBG_INFO) {
|
|
DbpString("FAKE EPURSE UPDATE RESPONSE: ");
|
|
Dbhexdump(nfc_rx_len, nfc_rx_buf, false);
|
|
}
|
|
} else {
|
|
if (g_dbglevel >= DBG_INFO) {
|
|
DbpString("ISO15 TAG REQUEST: ");
|
|
Dbhexdump(nfc_tx_len, nfc_tx_buf, false);
|
|
}
|
|
|
|
int tries = 3;
|
|
nfc_rx_len = 0;
|
|
while (tries-- > 0) {
|
|
iclass_send_as_reader(nfc_tx_buf, nfc_tx_len, &start_time, &eof_time, shallow_mod);
|
|
uint16_t timeout = is_cmd_update ? ICLASS_READER_TIMEOUT_UPDATE : ICLASS_READER_TIMEOUT_ACTALL;
|
|
|
|
res = GetIso15693AnswerFromTag(nfc_rx_buf, ISO7816_MAX_FRAME, timeout, &eof_time, false, true, &nfc_rx_len);
|
|
if (res == PM3_SUCCESS && nfc_rx_len > 0) {
|
|
break;
|
|
}
|
|
|
|
start_time = eof_time + ((DELAY_ICLASS_VICC_TO_VCD_READER + DELAY_ISO15693_VCD_TO_VICC_READER + (8 * 8 * 8 * 16)) * 2);
|
|
}
|
|
|
|
if (res != PM3_SUCCESS) {
|
|
return PM3_ECARDEXCHANGE;
|
|
}
|
|
|
|
if (g_dbglevel >= DBG_INFO) {
|
|
DbpString("ISO15 TAG RESPONSE: ");
|
|
Dbhexdump(nfc_rx_len, nfc_rx_buf, false);
|
|
}
|
|
}
|
|
|
|
switch_clock_to_ticks();
|
|
sam_tx_len = sam_copy_payload_nfc2sam(sam_tx_buf, nfc_rx_buf, nfc_rx_len);
|
|
|
|
sam_send_payload(
|
|
0x14, 0x0a, 0x14,
|
|
sam_tx_buf, &sam_tx_len,
|
|
sam_rx_buf, sam_rx_len
|
|
);
|
|
|
|
// last SAM->TAG
|
|
// c1 61 c1 00 00 a1 02 >>82<< 00 90 00
|
|
if (sam_relay_complete(sam_rx_buf, *sam_rx_len)) {
|
|
// tag <-> SAM exchange ends here
|
|
break;
|
|
}
|
|
|
|
switch_clock_to_countsspclk();
|
|
}
|
|
|
|
// The loop can exit two ways:
|
|
// (a) it broke on the a1 02 82 00 (TurnRfFieldOff) marker - which is
|
|
// itself a 0x61 frame, so sam_rx_buf[1] is still 0x61 here. The SAM is
|
|
// waiting for an ack; the SAM's reply to that ack is the final
|
|
// application response, left in sam_rx_buf. (PACS-read pattern.)
|
|
// (b) the while condition went false because the SAM already returned a
|
|
// non-0x61 final response (e.g. a Path B bd/b3 result to an
|
|
// interpreter command). That response is ALREADY in sam_rx_buf -
|
|
// sending the ack now would overwrite it with a bare 90 00.
|
|
// So only ack in case (a).
|
|
if (sam_relay_pending(sam_rx_buf, *sam_rx_len)) {
|
|
static const uint8_t hfack[] = {
|
|
0xbd, 0x04, 0xa0, 0x02, 0x82, 0x00
|
|
};
|
|
|
|
sam_tx_len = sizeof(hfack);
|
|
memcpy(sam_tx_buf, hfack, sam_tx_len);
|
|
|
|
sam_send_payload(
|
|
0x14, 0x0a, 0x00,
|
|
sam_tx_buf, &sam_tx_len,
|
|
sam_rx_buf, sam_rx_len
|
|
);
|
|
}
|
|
|
|
// When the loop exits on a non-0x61 final response, its last statement was
|
|
// switch_clock_to_countsspclk() (end of the iteration, before the while
|
|
// re-check) - so the clock is left in CountSspClk mode. Restore Ticks so the
|
|
// caller's next SAM I2C exchange (e.g. the Terminate in scclose) has the
|
|
// timer the SIM link needs for its receive timeout; otherwise it hangs.
|
|
switch_clock_to_ticks();
|
|
|
|
return PM3_SUCCESS;
|
|
}
|
|
|
|
uint16_t sam_copy_payload_sam2nfc(uint8_t *nfc_tx_buf, uint8_t *sam_rx_buf, uint16_t sam_rx_len) {
|
|
// SAM resp:
|
|
// c1 61 c1 00 00
|
|
// a1 10 <- nfc command
|
|
// a1 0e <- nfc send
|
|
// 80 10 <- data
|
|
// 00 a4 04 00 0a a0 00 00 04 40 00 01 01 00 01 00
|
|
// 81 02 <- protocol
|
|
// 00 04
|
|
// 82 02 <- timeout
|
|
// 01 F4
|
|
// 90 00
|
|
|
|
// NFC req:
|
|
// 0C 05 DE 64
|
|
|
|
// copy data out of the a1->a1->80 node, which sits after a routing tail
|
|
// that is 5 bytes on some SAMs and 6 on others
|
|
uint16_t p = sam_rx_prefix_len(sam_rx_buf, sam_rx_len);
|
|
if ((uint16_t)(p + 5) >= sam_rx_len) {
|
|
return 0;
|
|
}
|
|
|
|
uint16_t nfc_tx_len = sam_rx_buf[p + 5];
|
|
if ((uint16_t)(p + 6 + nfc_tx_len) > sam_rx_len) {
|
|
return 0;
|
|
}
|
|
|
|
memcpy(nfc_tx_buf, sam_rx_buf + p + 6, nfc_tx_len);
|
|
return nfc_tx_len;
|
|
}
|