Merge pull request #2731 from jkramarz/feature/sam_picopass

support for HID SAM communication with Picopass cards
This commit is contained in:
Iceman
2025-01-27 22:01:16 +01:00
committed by GitHub
13 changed files with 548 additions and 494 deletions
+1 -1
View File
@@ -2258,7 +2258,7 @@ static void PacketReceived(PacketCommandNG *packet) {
}
case CMD_HF_SAM_PICOPASS: {
sam_picopass_get_pacs();
sam_picopass_get_pacs(packet);
break;
}
case CMD_HF_SAM_SEOS: {
+73
View File
@@ -371,3 +371,76 @@ void sam_send_ack(void) {
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.
*/
uint16_t sam_copy_payload_sam2nfc(uint8_t *nfc_tx_buf, uint8_t *sam_rx_buf) {
// 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 c1->a1>->a1->80 node
uint16_t nfc_tx_len = (uint8_t) * (sam_rx_buf + 10);
memcpy(nfc_tx_buf, sam_rx_buf + 11, nfc_tx_len);
return nfc_tx_len;
}
+3
View File
@@ -46,4 +46,7 @@ void sam_append_asn1_node(const uint8_t *root, const uint8_t *node, uint8_t type
void sam_send_ack(void);
uint16_t sam_copy_payload_nfc2sam(uint8_t *sam_tx, uint8_t *nfc_rx, uint8_t nfc_len);
uint16_t sam_copy_payload_sam2nfc(uint8_t *nfc_tx_buf, uint8_t *sam_rx_buf);
#endif
+269 -342
View File
File diff suppressed because it is too large Load Diff
+2 -1
View File
@@ -18,7 +18,8 @@
#include "common.h"
#include "sam_common.h"
#include "pm3_cmd.h"
int sam_picopass_get_pacs(void);
int sam_picopass_get_pacs(PacketCommandNG *c);
#endif
+17 -85
View File
@@ -49,7 +49,7 @@
* @param card_select Pointer to the descriptor of the detected card.
* @return Status code indicating success or failure of the operation.
*/
static int sam_set_card_detected(iso14a_card_select_t *card_select) {
static int sam_set_card_detected_seos(iso14a_card_select_t *card_select) {
int res = PM3_SUCCESS;
if (g_dbglevel >= DBG_DEBUG)
DbpString("start sam_set_card_detected");
@@ -112,81 +112,6 @@ out:
return res;
}
/**
* @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.
*/
inline static uint16_t sam_seos_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.
*/
inline static uint16_t sam_seos_copy_payload_sam2nfc(uint8_t *nfc_tx_buf, uint8_t *sam_rx_buf) {
// SAM resp:
// c1 61 c1 00 00
// a1 21 <- nfc command
// a1 1f <- nfc send
// 80 10 <- data
// 00 a4 04 00 0a a0 00 00 04 40 00 01 01 00 01 00
// 81 02 <- protocol
// 02 02
// 82 02 <- timeout
// 01 2e
// 85 03 <- format
// 06 c0 00
// 90 00
// NFC req:
// 00 a4 04 00 0a a0 00 00 04 40 00 01 01 00 01 00
// copy data out of c1->a1>->a1->80 node
uint16_t nfc_tx_len = (uint8_t) * (sam_rx_buf + 10);
memcpy(nfc_tx_buf, sam_rx_buf + 11, nfc_tx_len);
return nfc_tx_len;
}
/**
* @brief Sends a request to the SAM and retrieves the response.
*
@@ -204,8 +129,12 @@ static int sam_send_request_iso14a(const uint8_t *const request, const uint8_t r
if (g_dbglevel >= DBG_DEBUG)
DbpString("start sam_send_request_iso14a");
uint8_t buf1[ISO7816_MAX_FRAME] = {0};
uint8_t buf2[ISO7816_MAX_FRAME] = {0};
uint8_t * buf1 = BigBuf_malloc(ISO7816_MAX_FRAME);
uint8_t * buf2 = BigBuf_malloc(ISO7816_MAX_FRAME);
if(buf1 == NULL || buf2 == NULL){
res = PM3_EMALLOC;
goto out;
}
uint8_t *sam_tx_buf = buf1;
uint16_t sam_tx_len;
@@ -247,7 +176,7 @@ static int sam_send_request_iso14a(const uint8_t *const request, const uint8_t r
// tag <-> SAM exchange starts here
while (sam_rx_buf[1] == 0x61) {
switch_clock_to_countsspclk();
nfc_tx_len = sam_seos_copy_payload_sam2nfc(nfc_tx_buf, sam_rx_buf);
nfc_tx_len = sam_copy_payload_sam2nfc(nfc_tx_buf, sam_rx_buf);
nfc_rx_len = iso14_apdu(
nfc_tx_buf,
@@ -259,7 +188,7 @@ static int sam_send_request_iso14a(const uint8_t *const request, const uint8_t r
);
switch_clock_to_ticks();
sam_tx_len = sam_seos_copy_payload_nfc2sam(sam_tx_buf, nfc_rx_buf, nfc_rx_len - 2);
sam_tx_len = sam_copy_payload_nfc2sam(sam_tx_buf, nfc_rx_buf, nfc_rx_len - 2);
sam_send_payload(
0x14, 0x0a, 0x14,
@@ -328,6 +257,7 @@ static int sam_send_request_iso14a(const uint8_t *const request, const uint8_t r
goto out;
out:
BigBuf_free();
return res;
}
@@ -341,11 +271,13 @@ out:
* @return Status code indicating success or failure of the operation.
*/
int sam_seos_get_pacs(PacketCommandNG *c) {
bool disconnectAfter = c->oldarg[0] & 0x01;
bool skipDetect = c->oldarg[1] & 0x01;
const uint8_t flags = c->data.asBytes[0];
const bool disconnectAfter = !!(flags & BITMASK(0));
const bool skipDetect = !!(flags & BITMASK(1));
uint8_t *cmd = c->data.asBytes + 1;
uint16_t cmd_len = c->length - 1;
uint8_t *cmd = c->data.asBytes;
uint16_t cmd_len = (uint16_t) c->oldarg[2];
int res = PM3_EFAILED;
@@ -371,7 +303,7 @@ int sam_seos_get_pacs(PacketCommandNG *c) {
switch_clock_to_ticks();
// step 3: SamCommand CardDetected
sam_set_card_detected(&card_a_info);
sam_set_card_detected_seos(&card_a_info);
}
// step 3: SamCommand RequestPACS, relay NFC communication
+1
View File
@@ -413,6 +413,7 @@ set (TARGET_SOURCES
${PM3_ROOT}/client/src/fileutils.c
${PM3_ROOT}/client/src/flash.c
${PM3_ROOT}/client/src/graph.c
${PM3_ROOT}/client/src/hidsio.c
${PM3_ROOT}/client/src/iso4217.c
${PM3_ROOT}/client/src/jansson_path.c
${PM3_ROOT}/client/src/lua_bitlib.c
+1
View File
@@ -723,6 +723,7 @@ SRCS = mifare/aiddesfire.c \
flash.c \
generator.c \
graph.c \
hidsio.c \
jansson_path.c \
iso4217.c \
iso7816/apduinfo.c \
+86 -10
View File
@@ -42,6 +42,7 @@
#include "generator.h"
#include "cmdhf14b.h"
#include "cmdhw.h"
#include "hidsio.h"
#define NUM_CSNS 9
@@ -5398,15 +5399,48 @@ static int CmdHFiClassSAM(const char *Cmd) {
CLIParserInit(&ctx, "hf iclass sam",
"Extract PACS via a HID SAM\n",
"hf iclass sam\n"
"hf iclass sam -p -d a005a103800104 -> get PACS data, but ensure that epurse will stay unchanged\n"
"hf iclass sam --break-on-nr-mac -> get Nr-MAC for extracting encrypted SIO\n"
);
void *argtable[] = {
arg_param_begin,
arg_lit0("v", "verbose", "verbose output"),
arg_lit0("k", "keep", "keep the field active after command executed"),
arg_lit0("n", "nodetect", "skip selecting the card and sending card details to SAM"),
arg_lit0("t", "tlv", "decode TLV"),
arg_lit0(NULL, "break-on-nr-mac", "stop tag interaction on nr-mac"),
arg_lit0("p", "prevent-epurse-update", "fake epurse update"),
arg_lit0(NULL, "shallow", "shallow mod"),
arg_strx0("d", "data", "<hex>", "DER encoded command to send to SAM"),
arg_param_end
};
CLIExecWithReturn(ctx, Cmd, argtable, true);
bool verbose = arg_get_lit(ctx, 1);
bool disconnectAfter = !arg_get_lit(ctx, 2);
bool skipDetect = arg_get_lit(ctx, 3);
bool decodeTLV = arg_get_lit(ctx, 4);
bool breakOnNrMac = arg_get_lit(ctx, 5);
bool preventEpurseUpdate = arg_get_lit(ctx, 6);
bool shallow_mod = arg_get_lit(ctx, 7);
uint8_t flags = 0;
if (disconnectAfter) flags |= BITMASK(0);
if (skipDetect) flags |= BITMASK(1);
if (breakOnNrMac) flags |= BITMASK(2);
if (preventEpurseUpdate) flags |= BITMASK(3);
if (shallow_mod) flags |= BITMASK(4);
uint8_t data[PM3_CMD_DATA_SIZE] = {0};
data[0] = flags;
int cmdlen = 0;
if (CLIParamHexToBuf(arg_get_str(ctx, 8), data+1, PM3_CMD_DATA_SIZE-1, &cmdlen) != PM3_SUCCESS){
CLIParserFree(ctx);
return PM3_ESOFT;
}
CLIParserFree(ctx);
if (IsHIDSamPresent(verbose) == false) {
@@ -5414,7 +5448,7 @@ static int CmdHFiClassSAM(const char *Cmd) {
}
clearCommandBuffer();
SendCommandNG(CMD_HF_SAM_PICOPASS, NULL, 0);
SendCommandNG(CMD_HF_SAM_PICOPASS, data, cmdlen+1);
PacketResponseNG resp;
if (WaitForResponseTimeout(CMD_HF_SAM_PICOPASS, &resp, 4000) == false) {
PrintAndLogEx(WARNING, "SAM timeout");
@@ -5432,16 +5466,58 @@ static int CmdHFiClassSAM(const char *Cmd) {
return resp.status;
}
// CSN, config, epurse, NR/MAC, AIA
// PACS
// 03 05
// 06 85 80 6d c0
// first byte skip
// second byte length
// third padded
// fourth ..
uint8_t *d = resp.data.asBytes;
HIDDumpPACSBits(d + 2, d[1], verbose);
// check for standard SamCommandGetContentElement response
// bd 09
// 8a 07
// 03 05 <- tag + length
// 06 85 80 6d c0 <- decoded PACS data
if (d[0] == 0xbd && d[2] == 0x8a && d[4] == 0x03) {
uint8_t pacs_length = d[5];
uint8_t *pacs_data = d + 6;
int res = HIDDumpPACSBits(pacs_data, pacs_length, verbose);
if (res != PM3_SUCCESS) {
return res;
}
// check for standard samCommandGetContentElement2:
// bd 1e
// b3 1c
// a0 1a
// 80 05
// 06 85 80 6d c0
// 81 0e
// 2b 06 01 04 01 81 e4 38 01 01 02 04 3c ff
// 82 01
// 07
} else if (d[0] == 0xbd && d[2] == 0xb3 && d[4] == 0xa0) {
const uint8_t *pacs = d + 6;
const uint8_t pacs_length = pacs[1];
const uint8_t *pacs_data = pacs + 2;
int res = HIDDumpPACSBits(pacs_data, pacs_length, verbose);
if (res != PM3_SUCCESS) {
return res;
}
const uint8_t *oid = pacs + 2 + pacs_length;
const uint8_t oid_length = oid[1];
const uint8_t *oid_data = oid + 2;
PrintAndLogEx(SUCCESS, "SIO OID.......: " _GREEN_("%s"), sprint_hex_inrow(oid_data, oid_length));
const uint8_t *mediaType = oid + 2 + oid_length;
const uint8_t mediaType_data = mediaType[2];
PrintAndLogEx(SUCCESS, "SIO Media Type: " _GREEN_("%s"), getSioMediaTypeInfo(mediaType_data));
} else if(breakOnNrMac && d[0] == 0x05) {
PrintAndLogEx(SUCCESS, "Nr-MAC: " _GREEN_("%s"), sprint_hex_inrow(d+1, 8));
if(verbose){
PrintAndLogEx(INFO, "Replay Nr-MAC to dump SIO:");
PrintAndLogEx(SUCCESS, " hf iclass dump -k \"%s\" --nr", sprint_hex_inrow(d+1, 8));
}
} else {
print_hex(d, resp.length);
}
if (decodeTLV) {
asn1_print(d, d[1] + 2, " ");
}
return PM3_SUCCESS;
}
+18 -49
View File
@@ -38,6 +38,7 @@
#include "crypto/libpcrypto.h" // AES decrypt
#include "commonutil.h" // get_sw
#include "protocols.h" // ISO7816 APDU return codes
#include "hidsio.h"
static uint8_t zeros[16] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
@@ -100,17 +101,6 @@ static const known_algo_t known_algorithm_map[] = {
{9, "AES-128_CBC_MODE"},
};
static const sioMediaTypeName_t sioMediaTypeMapping[] = {
{ 0x00, "Unknown"},
{ 0x01, "DESFire"},
{ 0x02, "MIFARE"},
{ 0x03, "iCLASS (PicoPass)"},
{ 0x04, "ISO14443AL4"},
{ 0x06, "MIFARE Plus"},
{ 0x07, "Seos"},
{ 0xFF, "INVALID VALUE"}
};
static int create_cmac(uint8_t *key, uint8_t *input, uint8_t *out, int input_len, int encryption_algorithm) {
uint8_t iv[16] = {0x00};
@@ -1638,32 +1628,14 @@ static int CmdHfSeosList(const char *Cmd) {
return CmdTraceListAlias(Cmd, "hf seos", "seos -c");
}
// get a SIO media type based on the UID
// uid[8] tag uid
// returns description of the best match
static const char *getSioMediaTypeInfo(uint8_t uid) {
for (int i = 0; i < ARRAYLEN(sioMediaTypeMapping); ++i) {
if (uid == sioMediaTypeMapping[i].uid) {
return sioMediaTypeMapping[i].desc;
}
}
//No match, return default
return sioMediaTypeMapping[ARRAYLEN(sioMediaTypeMapping) - 1].desc;
}
static int CmdHfSeosSAM(const char *Cmd) {
CLIParserContext *ctx;
CLIParserInit(&ctx, "hf seos sam",
"Extract PACS via a HID SAM\n",
"hf seos sam\n"
"hd seos sam -d a005a103800104 -> get PACS data\n"
"hf seos sam -d a005a103800104 -> get PACS data\n"
);
void *argtable[] = {
arg_param_begin,
arg_lit0("v", "verbose", "verbose output"),
@@ -1675,26 +1647,23 @@ static int CmdHfSeosSAM(const char *Cmd) {
};
CLIExecWithReturn(ctx, Cmd, argtable, true);
bool verbose = false;
if (arg_get_lit(ctx, 1)) {
verbose = true;
}
bool disconnectAfter = true;
if (arg_get_lit(ctx, 2)) {
disconnectAfter = false;
}
bool skipDetect = false;
if (arg_get_lit(ctx, 3)) {
skipDetect = true;
}
bool decodeTLV = false;
if (arg_get_lit(ctx, 4)) {
decodeTLV = true;
}
bool verbose = arg_get_lit(ctx, 1);
bool disconnectAfter = !arg_get_lit(ctx, 2);
bool skipDetect = arg_get_lit(ctx, 3);
bool decodeTLV = arg_get_lit(ctx, 4);
uint8_t flags = 0;
if (disconnectAfter) flags |= BITMASK(0);
if (skipDetect) flags |= BITMASK(1);
uint8_t data[PM3_CMD_DATA_SIZE] = {0};
int datalen = 0;
CLIGetHexBLessWithReturn(ctx, 5, data, &datalen, 0);
data[0] = flags;
int cmdlen = 0;
if (CLIParamHexToBuf(arg_get_str(ctx, 5), data+1, PM3_CMD_DATA_SIZE-1, &cmdlen) != PM3_SUCCESS){
CLIParserFree(ctx);
return PM3_ESOFT;
}
CLIParserFree(ctx);
@@ -1703,7 +1672,7 @@ static int CmdHfSeosSAM(const char *Cmd) {
}
clearCommandBuffer();
SendCommandMIX(CMD_HF_SAM_SEOS, disconnectAfter, skipDetect, datalen, data, datalen);
SendCommandNG(CMD_HF_SAM_SEOS, data, cmdlen+1);
PacketResponseNG resp;
if (WaitForResponseTimeout(CMD_HF_SAM_SEOS, &resp, 4000) == false) {
PrintAndLogEx(WARNING, "SAM timeout");
-6
View File
@@ -21,12 +21,6 @@
#include "common.h"
// structure and database for uid -> tagtype lookups
typedef struct {
uint8_t uid;
const char *desc;
} sioMediaTypeName_t;
int infoSeos(bool verbose);
int CmdHFSeos(const char *Cmd);
int seos_kdf(bool encryption, uint8_t *masterKey, uint8_t keyslot,
+51
View File
@@ -0,0 +1,51 @@
//-----------------------------------------------------------------------------
// 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.
//-----------------------------------------------------------------------------
// HID Global SIO utilities
//-----------------------------------------------------------------------------
#include "commonutil.h"
#include "hidsio.h"
// structure and database for uid -> tagtype lookups
typedef struct {
uint8_t uid;
const char *desc;
} sioMediaTypeName_t;
static const sioMediaTypeName_t sioMediaTypeMapping[] = {
{ 0x00, "Unknown"},
{ 0x01, "DESFire"},
{ 0x02, "MIFARE"},
{ 0x03, "iCLASS (PicoPass)"},
{ 0x04, "ISO14443AL4"},
{ 0x06, "MIFARE Plus"},
{ 0x07, "Seos"},
{ 0xFF, "INVALID VALUE"}
};
// get a SIO media type based on the UID
// uid[8] tag uid
// returns description of the best match
const char *getSioMediaTypeInfo(uint8_t uid) {
for (int i = 0; i < ARRAYLEN(sioMediaTypeMapping); ++i) {
if (uid == sioMediaTypeMapping[i].uid) {
return sioMediaTypeMapping[i].desc;
}
}
//No match, return default
return sioMediaTypeMapping[ARRAYLEN(sioMediaTypeMapping) - 1].desc;
}
+26
View File
@@ -0,0 +1,26 @@
//-----------------------------------------------------------------------------
// 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.
//-----------------------------------------------------------------------------
// HID Global SIO utilities
//-----------------------------------------------------------------------------
#ifndef __HIDSIO_H_
#define __HIDSIO_H_
#include "common.h"
#include "stdint.h"
const char *getSioMediaTypeInfo(uint8_t uid);
#endif