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
commit 50b66a6fe5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 548 additions and 494 deletions

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: {

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;
}

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

View file

@ -30,6 +30,218 @@
#include "protocols.h"
#include "optimized_cipher.h"
#include "fpgaloader.h"
#include "pm3_cmd.h"
/**
* @brief Sends a request to the SAM and retrieves the response.
*
* Unpacks request to the SAM and relays ISO15 traffic to the card.
* If no request data provided, sends a request to get PACS data.
*
* @param request Pointer to the buffer containing the request to be sent to the SAM.
* @param request_len Length of the request to be sent to the SAM.
* @param response Pointer to the buffer where the retreived data will be stored.
* @param response_len Pointer to the variable where the length of the retreived data will be stored.
* @return Status code indicating success or failure of the operation.
*/
static int sam_send_request_iso15(const uint8_t *const request, const uint8_t request_len, uint8_t *response, uint8_t *response_len, const bool shallow_mod, const bool break_on_nr_mac, const bool prevent_epurse_update) {
int res = PM3_SUCCESS;
if (g_dbglevel >= DBG_DEBUG)
DbpString("start sam_send_request_iso14a");
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;
uint8_t *sam_rx_buf = buf2;
uint16_t sam_rx_len;
uint8_t *nfc_tx_buf = buf1;
uint16_t nfc_tx_len;
uint8_t *nfc_rx_buf = buf2;
uint16_t nfc_rx_len;
if (request_len > 0) {
sam_tx_len = request_len;
memcpy(sam_tx_buf, request, sam_tx_len);
} else {
// send get pacs
static const uint8_t payload[] = {
0xa0, 19, // <- SAM command
0xBE, 17, // <- samCommandGetContentElement2
0x80, 1,
0x04, // <- implicitFormatPhysicalAccessBits
0x84, 12,
0x2B, 0x06, 0x01, 0x04, 0x01, 0x81, 0xE4, 0x38, 0x01, 0x01, 0x02, 0x04 // <- SoRootOID
};
sam_tx_len = sizeof(payload);
memcpy(sam_tx_buf, payload, sam_tx_len);
}
sam_send_payload(
0x44, 0x0a, 0x44,
sam_tx_buf, &sam_tx_len,
sam_rx_buf, &sam_rx_len
);
if (sam_rx_buf[1] == 0x61) { // commands to be relayed to card starts with 0x61
switch_clock_to_countsspclk();
// tag <-> SAM exchange starts here
while (sam_rx_buf[1] == 0x61) {
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);
bool is_cmd_check = (nfc_tx_buf[0] & 0x0F) == ICLASS_CMD_CHECK;
if(is_cmd_check && break_on_nr_mac){
memcpy(response, nfc_tx_buf, nfc_tx_len);
*response_len = nfc_tx_len;
if (g_dbglevel >= DBG_INFO) {
DbpString("NR-MAC: ");
Dbhexdump((*response_len)-1, response+1, false);
}
res = PM3_SUCCESS;
goto out;
}
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
// NFC TX BUFFERS PREPARED BY SAM LOOKS LIKE:
// 87 02 #1(C9 FD FF FF) #2(FF FF FF FF) F4 BF 98 E2
// NFC RX BUFFERS EXPECTED BY SAM WOULD LOOK LIKE:
// #2(FF FF FF FF) #1(C9 FD FF FF) 3A 47
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 ) {
res = PM3_ECARDEXCHANGE;
goto out;
}
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_rx_buf[7] == 0x82) {
// tag <-> SAM exchange ends here
break;
}
switch_clock_to_countsspclk();
}
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
);
}
// resp for SamCommandGetContentElement:
// c1 64 00 00 00
// bd 09
// 8a 07
// 03 05 <- include tag for pm3 client
// 06 85 80 6d c0 <- decoded PACS data
// 90 00
// resp for samCommandGetContentElement2:
// c1 64 00 00 00
// 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
// 90 00
if (request_len == 0) {
if (
!(sam_rx_buf[5] == 0xbd && sam_rx_buf[5 + 2] == 0x8a && sam_rx_buf[5 + 4] == 0x03)
&&
!(sam_rx_buf[5] == 0xbd && sam_rx_buf[5 + 2] == 0xb3 && sam_rx_buf[5 + 4] == 0xa0)
) {
if (g_dbglevel >= DBG_ERROR)
Dbprintf("No PACS data in SAM response");
res = PM3_ESOFT;
}
}
*response_len = sam_rx_buf[5 + 1] + 2;
memcpy(response, sam_rx_buf + 5, *response_len);
goto out;
out:
BigBuf_free();
return res;
}
/**
@ -41,7 +253,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(picopass_hdr_t *card_select) {
static int sam_set_card_detected_picopass(picopass_hdr_t *card_select) {
int res = PM3_SUCCESS;
if (g_dbglevel >= DBG_DEBUG)
DbpString("start sam_set_card_detected");
@ -107,366 +319,81 @@ out:
return res;
}
// using HID SAM to authenticate w PICOPASS
int sam_picopass_get_pacs(void) {
static uint8_t act_all[] = { ICLASS_CMD_ACTALL };
static uint8_t identify[] = { ICLASS_CMD_READ_OR_IDENTIFY, 0x00, 0x73, 0x33 };
static uint8_t read_conf[] = { ICLASS_CMD_READ_OR_IDENTIFY, 0x01, 0xfa, 0x22 };
uint8_t select[] = { 0x80 | ICLASS_CMD_SELECT, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
uint8_t read_aia[] = { ICLASS_CMD_READ_OR_IDENTIFY, 0x05, 0xde, 0x64};
uint8_t read_check_cc[] = { 0x80 | ICLASS_CMD_READCHECK, 0x02 };
/**
* @brief Retrieves PACS data from PICOPASS card using SAM.
*
* This function is called by appmain.c
* It sends a request to the SAM to get the PACS data from the PICOPASS card.
* The PACS data is then returned to the PM3 client.
*
* @return Status code indicating success or failure of the operation.
*/
int sam_picopass_get_pacs(PacketCommandNG *c) {
const uint8_t flags = c->data.asBytes[0];
const bool disconnectAfter = !!(flags & BITMASK(0));
const bool skipDetect = !!(flags & BITMASK(1));
const bool breakOnNrMac = !!(flags & BITMASK(2));
const bool preventEpurseUpdate = !!(flags & BITMASK(3));
const bool shallow_mod = !!(flags & BITMASK(4));
picopass_hdr_t hdr = {0};
// Bit 4: K.If this bit equals to one, the READCHECK will use the Credit Key (Kc); if equals to zero, Debit Key (Kd) will be used
// bit 7: parity.
// if (use_credit_key)
// read_check_cc[0] = 0x10 | ICLASS_CMD_READCHECK;
uint8_t *cmd = c->data.asBytes + 1;
uint16_t cmd_len = c->length - 1;
BigBuf_free_keep_EM();
int res = PM3_EFAILED;
clear_trace();
I2C_Reset_EnterMainProgram();
StopTicks();
uint8_t *resp = BigBuf_calloc(ISO7816_MAX_FRAME);
set_tracing(true);
StartTicks();
bool shallow_mod = false;
uint16_t resp_len = 0;
int res;
// step 1: ping SAM
sam_get_version();
if (!skipDetect) {
// step 2: get card information
picopass_hdr_t card_a_info;
uint32_t eof_time = 0;
// wakeup
// implicit StartSspClk() happens here
Iso15693InitReader();
if(!select_iclass_tag(&card_a_info, false, &eof_time, shallow_mod)){
goto err;
}
uint32_t start_time = GetCountSspClk();
iclass_send_as_reader(act_all, 1, &start_time, &eof_time, shallow_mod);
switch_clock_to_ticks();
res = GetIso15693AnswerFromTag(resp, ISO7816_MAX_FRAME, ICLASS_READER_TIMEOUT_ACTALL, &eof_time, false, true, &resp_len);
// step 3: SamCommand CardDetected
sam_set_card_detected_picopass(&card_a_info);
}
// step 3: SamCommand RequestPACS, relay NFC communication
uint8_t sam_response[ISO7816_MAX_FRAME] = { 0x00 };
uint8_t sam_response_len = 0;
res = sam_send_request_iso15(cmd, cmd_len, sam_response, &sam_response_len, shallow_mod, breakOnNrMac, preventEpurseUpdate);
if (res != PM3_SUCCESS) {
res = PM3_ECARDEXCHANGE;
goto err;
}
if (g_dbglevel >= DBG_INFO)
print_result("Response data", sam_response, sam_response_len);
goto out;
}
// send Identify
start_time = eof_time + DELAY_ICLASS_VICC_TO_VCD_READER;
iclass_send_as_reader(identify, 1, &start_time, &eof_time, shallow_mod);
// expect a 10-byte response here, 8 byte anticollision-CSN and 2 byte CRC
res = GetIso15693AnswerFromTag(resp, ISO7816_MAX_FRAME, ICLASS_READER_TIMEOUT_OTHERS, &eof_time, false, true, &resp_len);
if (res != PM3_SUCCESS || resp_len != 10) {
res = PM3_ECARDEXCHANGE;
goto out;
}
// copy the Anti-collision CSN to our select-packet
memcpy(&select[1], resp, 8);
// select the card
start_time = eof_time + DELAY_ICLASS_VICC_TO_VCD_READER;
iclass_send_as_reader(select, sizeof(select), &start_time, &eof_time, shallow_mod);
// expect a 10-byte response here, 8 byte CSN and 2 byte CRC
res = GetIso15693AnswerFromTag(resp, ISO7816_MAX_FRAME, ICLASS_READER_TIMEOUT_OTHERS, &eof_time, false, true, &resp_len);
if (res != PM3_SUCCESS || resp_len != 10) {
res = PM3_ECARDEXCHANGE;
goto out;
}
// store CSN
memcpy(hdr.csn, resp, sizeof(hdr.csn));
// card selected, now read config (block1) (only 8 bytes no CRC)
start_time = eof_time + DELAY_ICLASS_VICC_TO_VCD_READER;
iclass_send_as_reader(read_conf, sizeof(read_conf), &start_time, &eof_time, shallow_mod);
// expect a 8-byte response here
res = GetIso15693AnswerFromTag(resp, ISO7816_MAX_FRAME, ICLASS_READER_TIMEOUT_OTHERS, &eof_time, false, true, &resp_len);
if (res != PM3_SUCCESS || resp_len != 10) {
res = PM3_ECARDEXCHANGE;
goto out;
}
// store CONFIG
memcpy((uint8_t *)&hdr.conf, resp, sizeof(hdr.conf));
uint8_t pagemap = get_pagemap(&hdr);
if (pagemap == PICOPASS_NON_SECURE_PAGEMODE) {
res = PM3_EWRONGANSWER;
goto out;
}
// read App Issuer Area block 5
start_time = eof_time + DELAY_ICLASS_VICC_TO_VCD_READER;
iclass_send_as_reader(read_aia, sizeof(read_aia), &start_time, &eof_time, shallow_mod);
// expect a 10-byte response here
res = GetIso15693AnswerFromTag(resp, ISO7816_MAX_FRAME, ICLASS_READER_TIMEOUT_OTHERS, &eof_time, false, true, &resp_len);
if (res != PM3_SUCCESS || resp_len != 10) {
res = PM3_ECARDEXCHANGE;
goto out;
}
// store AIA
memcpy(hdr.app_issuer_area, resp, sizeof(hdr.app_issuer_area));
// card selected, now read e-purse (cc) (block2) (only 8 bytes no CRC)
start_time = eof_time + DELAY_ICLASS_VICC_TO_VCD_READER;
iclass_send_as_reader(read_check_cc, sizeof(read_check_cc), &start_time, &eof_time, shallow_mod);
// expect a 8-byte response here
res = GetIso15693AnswerFromTag(resp, ISO7816_MAX_FRAME, ICLASS_READER_TIMEOUT_OTHERS, &eof_time, false, true, &resp_len);
if (res != PM3_SUCCESS || resp_len != 8) {
res = PM3_ECARDEXCHANGE;
goto out;
}
// store EPURSE
memcpy(hdr.epurse, resp, sizeof(hdr.epurse));
// -----------------------------------------------------------------------------
// SAM comms
// -----------------------------------------------------------------------------
size_t sam_len = 0;
uint8_t *sam_apdu = BigBuf_calloc(ISO7816_MAX_FRAME);
// -----------------------------------------------------------------------------
// first - set detected card (0xAD)
switch_clock_to_ticks();
sam_set_card_detected(&hdr);
// -----------------------------------------------------------------------------
// second - get PACS (0xA1)
// a0 05
// a1 03
// 80 01
// 04
hexstr_to_byte_array("a005a103800104", sam_apdu, &sam_len);
if (sam_send_payload(0x44, 0x0a, 0x44, sam_apdu, (uint16_t *) &sam_len, resp, &resp_len) != PM3_SUCCESS) {
res = PM3_ECARDEXCHANGE;
goto out;
}
print_dbg("-- 2", resp, resp_len);
// TAG response
// -- 0c 05 de64 // read block 5
// Tag|c00a140a000000a110a10e8004 0c05de64 8102 0004 820201f4
// -----------------------------------------------------------------------------
// third AIA block 5 (emulated tag <-> SAM exchange starts here)
// a0da02631c140a00000000bd14a012a010800a ffffff0006fffffff88e 81020000
// picopass legacy is fixed. wants AIA and crc. ff ff ff ff ff ff ff ff ea f5
// picpoasss SE ff ff ff 00 06 ff ff ff f8 8e
hexstr_to_byte_array("a0da02631c140a00000000bd14a012a010800affffff0006fffffff88e81020000", sam_apdu, &sam_len);
memcpy(sam_apdu + 19, hdr.app_issuer_area, sizeof(hdr.app_issuer_area));
AddCrc(sam_apdu + 19, 8);
if (sam_rxtx(sam_apdu, sam_len, resp, &resp_len) == false) {
res = PM3_ECARDEXCHANGE;
goto out;
}
print_dbg("-- 3", resp, resp_len);
// 88 02 -- readcheck (block2 epurse, start of auth)
// Tag|c00a140a000000a10ea10c8002 8802 8102 0004 820201f4 9000
// 61 16 f5 0a140a000000a10ea10c 8002 8802 8102 0004 820201f4 9000
// -----------------------------------------------------------------------------
// forth EPURSE
// a0da02631a140a00000000bd12a010a00e8008 ffffffffedffffff 81020000
hexstr_to_byte_array("a0da02631a140a00000000bd12a010a00e8008ffffffffedffffff81020000", sam_apdu, &sam_len);
memcpy(sam_apdu + 19, hdr.epurse, sizeof(hdr.epurse));
if (sam_rxtx(sam_apdu, sam_len, resp, &resp_len) == false) {
res = PM3_ECARDEXCHANGE;
goto out;
}
print_dbg("-- 4", resp, resp_len);
uint8_t nr_mac[9] = {0};
memcpy(nr_mac, resp + 11, sizeof(nr_mac));
// resp here hold the whole NR/MAC
// 05 9bcd475e965ee20e // CHECK (w key)
print_dbg("NR/MAC", nr_mac, sizeof(nr_mac));
// c00a140a000000a115a1138009 059bcd475e965ee20e 8102 0004 820201f4 9000
// pre calc ourself?
// uint8_t cc_nr[] = {0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0, 0, 0, 0};
uint8_t div_key[8] = {0};
static uint8_t legacy_aa1_key[] = {0xAE, 0xA6, 0x84, 0xA6, 0xDA, 0xB2, 0x32, 0x78};
iclass_calc_div_key(hdr.csn, legacy_aa1_key, div_key, false);
uint8_t mac[4] = {0};
if (g_dbglevel == DBG_DEBUG) {
uint8_t wb[16] = {0};
memcpy(wb, hdr.epurse, sizeof(hdr.epurse));
memcpy(wb + sizeof(hdr.epurse), nr_mac + 1, 4);
print_dbg("cc_nr...", wb, sizeof(wb));
doMAC_N(wb, sizeof(wb), div_key, mac);
print_dbg("Calc MAC...", mac, sizeof(mac));
}
// start ssp clock again...
switch_clock_to_countsspclk();
// NOW we auth against tag
uint8_t cmd_check[9] = { ICLASS_CMD_CHECK };
memcpy(cmd_check + 1, nr_mac + 1, 8);
start_time = GetCountSspClk();
iclass_send_as_reader(cmd_check, sizeof(cmd_check), &start_time, &eof_time, shallow_mod);
// expect a 10-byte response here
res = GetIso15693AnswerFromTag(resp, ISO7816_MAX_FRAME, ICLASS_READER_TIMEOUT_OTHERS, &eof_time, false, true, &resp_len);
if (res != PM3_SUCCESS || resp_len != 4) {
res = PM3_ECARDEXCHANGE;
goto out;
}
// store MAC
memcpy(mac, resp, sizeof(mac));
print_dbg("Got MAC", mac, sizeof(mac));
// -----------------------------------------------------------------------------
// fifth send received MAC
// A0DA026316140A00000000BD0EA00CA00A8004 311E32E9 81020000
hexstr_to_byte_array("A0DA026316140A00000000BD0EA00CA00A8004311E32E981020000", sam_apdu, &sam_len);
memcpy(sam_apdu + 19, mac, sizeof(mac));
switch_clock_to_ticks();
if (sam_rxtx(sam_apdu, sam_len, resp, &resp_len) == false) {
res = PM3_ECARDEXCHANGE;
goto out;
}
print_dbg("-- 5", resp, resp_len);
uint8_t tmp_p1[4] = {0};
uint8_t tmp_p2[4] = {0};
// c161c10000a11aa118800e8702 ffffffff88ffffff 0a914eb981020004820236b09000
memcpy(tmp_p1, resp + 13, sizeof(tmp_p1));
memcpy(tmp_p2, resp + 13 + 4, sizeof(tmp_p2));
// -----------------------------------------------------------------------------
// sixth send fake epurse update
// A0DA02631C140A00000000BD14A012A010800A 88FFFFFFFFFFFFFF9DE1 81020000
hexstr_to_byte_array("A0DA02631C140A00000000BD14A012A010800A88FFFFFFFFFFFFFF9DE181020000", sam_apdu, &sam_len);
memcpy(sam_apdu + 19, tmp_p2, sizeof(tmp_p1));
memcpy(sam_apdu + 19 + 4, tmp_p1, sizeof(tmp_p1));
AddCrc(sam_apdu + 19, 8);
if (sam_rxtx(sam_apdu, sam_len, resp, &resp_len) == false) {
res = PM3_ECARDEXCHANGE;
goto out;
}
print_dbg("-- 6", resp, resp_len);
// c1 61 c1 00 00 a1 10 a1 0e 80 04 0c 06 45 56 81 02 00 04 82 02 01 f4 90 00
// read block 6
switch_clock_to_countsspclk();
start_time = GetCountSspClk();
iclass_send_as_reader(resp + 11, 4, &start_time, &eof_time, shallow_mod);
// expect a 10-byte response here
res = GetIso15693AnswerFromTag(resp, ISO7816_MAX_FRAME, ICLASS_READER_TIMEOUT_OTHERS, &eof_time, false, true, &resp_len);
if (res != PM3_SUCCESS || resp_len != 10) {
res = PM3_ECARDEXCHANGE;
goto out;
}
print_dbg("Block 6 from Picopass", resp, resp_len);
// -----------------------------------------------------------------------------
// eight send block 6 config to SAM
// A0DA02631C140A00000000BD14A012A010800A 030303030003E0174323 81020000
hexstr_to_byte_array("A0DA02631C140A00000000BD14A012A010800A030303030003E017432381020000", sam_apdu, &sam_len);
memcpy(sam_apdu + 19, resp, resp_len);
switch_clock_to_ticks();
if (sam_rxtx(sam_apdu, sam_len, resp, &resp_len) == false) {
res = PM3_ECARDEXCHANGE;
goto out;
}
print_dbg("-- 7", resp, resp_len);
// c161c10000a110a10e8004 0606455681020004820201f49000
// read the credential blocks
switch_clock_to_countsspclk();
start_time = GetCountSspClk();
iclass_send_as_reader(resp + 11, 4, &start_time, &eof_time, shallow_mod);
// expect a 10-byte response here
res = GetIso15693AnswerFromTag(resp, ISO7816_MAX_FRAME, ICLASS_READER_TIMEOUT_OTHERS, &eof_time, false, true, &resp_len);
if (res != PM3_SUCCESS) {
res = PM3_ECARDEXCHANGE;
goto out;
}
print_dbg("Block 6-9 from Picopass", resp, resp_len);
// -----------------------------------------------------------------------------
// nine send credential blocks to SAM
// A0DA026334140A00000000BD2CA02AA0288022 030303030003E017769CB4A198E0DEC82AD4C8211F9968712BE7393CF8E71D7E804C 81020000
hexstr_to_byte_array("A0DA026334140A00000000BD2CA02AA0288022030303030003E017769CB4A198E0DEC82AD4C8211F9968712BE7393CF8E71D7E804C81020000", sam_apdu, &sam_len);
memcpy(sam_apdu + 19, resp, resp_len);
switch_clock_to_ticks();
if (sam_rxtx(sam_apdu, sam_len, resp, &resp_len) == false) {
res = PM3_ECARDEXCHANGE;
goto out;
}
print_dbg("-- 8", resp, resp_len);
// -----------------------------------------------------------------------------
// TEN ask for PACS data
// A0 DA 02 63 0C
// 44 0A 00 00 00 00
// BD 04
// A0 02
// 82 00
// (emulated tag <-> SAM exchange ends here)
hexstr_to_byte_array("A0DA02630C440A00000000BD04A0028200", sam_apdu, &sam_len);
memcpy(sam_apdu + 19, resp, resp_len);
if (sam_rxtx(sam_apdu, sam_len, resp, &resp_len) == false) {
res = PM3_ECARDEXCHANGE;
goto out;
}
print_dbg("-- 9 response", resp, resp_len);
if (memcmp(resp, "\xc1\x64\x00\x00\x00\xbd\x17\x8a\x15", 9) == 0) {
res = PM3_ENOPACS;
goto out;
}
// resp:
// c1 64 00 00 00
// bd 09
// 8a 07
// 03 05 06 95 1f 9a 00 <- decoded PACS data
// 90 00
uint8_t *pacs = BigBuf_calloc(resp[8]);
memcpy(pacs, resp + 9, resp[8]);
print_dbg("-- 10 PACS data", pacs, resp[8]);
reply_ng(CMD_HF_SAM_PICOPASS, PM3_SUCCESS, pacs, resp[8]);
res = PM3_SUCCESS;
goto off;
out:
err:
res = PM3_ENOPACS;
reply_ng(CMD_HF_SAM_PICOPASS, res, NULL, 0);
goto off;
out:
reply_ng(CMD_HF_SAM_PICOPASS, PM3_SUCCESS, sam_response, sam_response_len);
goto off;
off:
if (disconnectAfter) {
switch_off();
}
set_tracing(false);
StopTicks();
BigBuf_free();
return res;
}
// HID SAM <-> MFC
// HID SAM <-> SEOS

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

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

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

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 \

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;
}

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");

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
client/src/hidsio.c Normal file
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
client/src/hidsio.h Normal file
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