mirror of
https://github.com/RfidResearchGroup/proxmark3.git
synced 2026-10-22 12:52:30 +00:00
remove the old SAM implementation
This commit is contained in:
1 parent
4682271488
commit
7ff85b09ec
7 files changed
+741
-858
No files matched your search
+12
-11
@@ -3,17 +3,18 @@ All notable changes to this project will be documented in this file.
|
||||
This project uses the changelog in accordance with [keepchangelog](http://keepachangelog.com/). Please use this to write notable changes, which is not the same as git commit log...
|
||||
|
||||
## [unreleased][unreleased]
|
||||
- Fixed Proxmark5 (PM5/AT32) failing to re-enter the bootloader on the software-reset flash path: the ERTC had no clock source selected, so the battery-domain (BPR) write-protection register write stalled the APB at the full clock and left USB dead until a physical replug; the ERTC is now given a clock before the BPR write (@nemanjan00)
|
||||
- Fixed `hw fpga config` on Proxmark5 (PM5/AT32) needing a prior reader command (e.g. `hf 14a read`) to work: it now powers on the FPGA (24MHz clock) itself at the start of the config, before driving the JTAG upload (@nemanjan00)
|
||||
- Fixed `hf iclass sam` - removed the old implementation (@iceman1001)
|
||||
- Fixed Proxmark5 (PM5/AT32) failing to re-enter the bootloader (@nemanjan00)
|
||||
- Fixed `hw fpga config` on Proxmark5 (PM5/AT32) now powers on the FPGA 24MHz clock (@nemanjan00)
|
||||
- Fixed `hf mfdes chk` to correctly check all provided keys instead of only checking a small portion (@corollary-de).
|
||||
- Fixed `hf iclass unhash` omitting valid hash0 pre-images, which could make the subsequent hashcat DES crack unable to find the master key (@trichimtrich)
|
||||
- Fixed `hf iclass unhash` returning no pre-images at all for some keys, caused by `check()` not being invertible in a single reading (@trichimtrich)
|
||||
- Added `--rgb` option to `hf tune` / `lf tune` (Proxmark5/PM5): mirrors the antenna tuning level on the antenna RGB LED (blue=low, green=mid, red=high, tracking the on-screen bar), for locating tags/implants by feel without watching the screen. Colour is computed client-side; the device gets a new `CMD_PM5_RGB_SET` handled by a dedicated AT32 RGB HAL module (`common_arm/rgb`), so other platforms are unaffected (@nemanjan00)
|
||||
- Fixed `hw version` / `hw status` showing a bogus FPGA image (`fpga_pm3_hf.ncd image 2s30vq100`) on Proxmark5 (PM5/AT32): PM5's Gowin FPGA bitstream is loaded externally and not compiled in, so the built-in Xilinx version info is meaningless there; the `[ FPGA ]` section (and the client FPGA "chip mismatch" check) are now suppressed for PM5 (@nemanjan00)
|
||||
- Added `--rgb` option to `hf tune` / `lf tune` (Proxmark5/PM5): mirrors the antenna tuning level on the antenna RGB LED (@nemanjan00)
|
||||
- Fixed `hw version` / `hw status` showing a bogus FPGA image (`fpga_pm3_hf.ncd image 2s30vq100`) on Proxmark5 (PM5/AT32) (@nemanjan00)
|
||||
- Fixed `hw status` [Model] section reporting "PM3 GENERIC" firmware on Proxmark5 (PM5/AT32); it now reports `PM5` (@nemanjan00)
|
||||
- Added Proxmark5 (PM5/AT32) reporting in `hw version` / `hw status`: correct MCU (AT32F437), flash size and `PM5` target, instead of the AT91-decoded "Unknown / 32 KB / PM3 GENERIC". The device now also reports its on-chip flash size, appended to the `CMD_VERSION` reply in a backward-compatible way (@nemanjan00)
|
||||
- Fixed `hf mf dump` preserving readable sector trailer Key B data instead of overwriting it with values from the supplied key file (@oSPANNERo)
|
||||
- Changed `magic_cards_notes.md` - documented the USCUID-UL helper scripts (`hf_mfu_uscuid` / `hf_mf_uscuid_prog`) and how to set the tag signature, replacing the outdated "No implemented commands" note (@c-barron)
|
||||
- Added `hw version` / `hw status` - now reports Proxmark5 (PM5/AT32) correct MCU (AT32F437) (@nemanjan00)
|
||||
- Fixed `hf mf dump` preserving readable sector trailer Key B data (@oSPANNERo)
|
||||
- Changed `magic_cards_notes.md` - documented the USCUID-UL helper scripts (`hf_mfu_uscuid` / `hf_mf_uscuid_prog`) (@c-barron)
|
||||
- Fixed `hf_mf_uscuid_prog.lua` - corrected the script name shown in its usage text (@c-barron)
|
||||
- Added `hf felica seacauth1` command
|
||||
- Added `lf trovan` commands to support Trovan Animal ID (@iceman1001)
|
||||
@@ -21,8 +22,8 @@ This project uses the changelog in accordance with [keepchangelog](http://keepac
|
||||
- Changed `hf mf gdmparsecfg/gdmsetblk` (@0x6r1an0y)
|
||||
- Renamed `hf mf gdmcfg` (@0x6r1an0y)
|
||||
- Added `hf mfu aesgetuid` for retrieving the real UID when random ID is enabled, using the UIDRetrKey. (@lolbird123)
|
||||
- Fixed `hf iclass view` reading past the end of dump files smaller than a picopass header, causing a heap out-of-bounds read and garbage/leaked heap data being printed as tag content (@munzzyy)
|
||||
- Fixed `wiegand decode` returning facility code 16777339 instead of the encoded value for the IR56 (Inner Range 56-bit) format by masking the header sentinel bit off the facility code (@munzzyy)
|
||||
- Fixed `hf iclass view` reading past the end of dump files smaller than a picopass header (@munzzyy)
|
||||
- Fixed `wiegand decode` returning facility code 16777339 instead of the encoded value for the IR56 (@munzzyy)
|
||||
- Fixed `lf em 4x05 dump` writing byte-swapped (corrupted) block data to the saved dump file (@munzzyy)
|
||||
- Added `hf 14b dump` support for Standard ISO14443-B tags (@thisiscamk)
|
||||
- Added `hf 14b ctrdbl` and `hf 14b ctdump` commands for interacting with ASK CTS tags (@kormax)
|
||||
@@ -36,7 +37,7 @@ This project uses the changelog in accordance with [keepchangelog](http://keepac
|
||||
- Added `hf mfdes getversion` command (@kormax)
|
||||
- Fixed iCLASS emulator writes clearing previously loaded emulator memory after an FPGA reload (@cindersocket)
|
||||
- Added DES transport mode support for iCLASS credential encode/decode helpers. (@cindersocket)
|
||||
- Changed `hf iclass view` to detect iCLASS SE SIO blocks from AIA values that point past block 6. (@cindersocket)
|
||||
- Changed `hf iclass view` - now detect iCLASS SE SIO blocks from AIA values that point past block 6. (@cindersocket)
|
||||
- Changed `doc/magic_cards_notes.md` - updated the ID82xx / Hitag µ clone section with current Proxmark3 support, chip variations, default passwords and detection notes (@mishamyte)
|
||||
- Added two Mifare Classic keys into extensive dictionary which are hardcoded into Mifare Plus SE (@team-orangeBlue)
|
||||
- Fixed `hf mfp rdbl` when using "read multiple" blocks by decrypting the entire buffer instead of one block only (@team-orangeBlue)
|
||||
@@ -61,7 +62,7 @@ This project uses the changelog in accordance with [keepchangelog](http://keepac
|
||||
- Fixed `mfpReadSector` function to decrypt received data, fixing commands like `hf mfp dump` (@team-orangeBlue)
|
||||
- Added `hf mfdes intauth` command (@kormax)
|
||||
- Improved `hf iclass legbrute` throughput: added a 64-wide bitsliced MAC1 sweep that tests 64 key candidates in parallel per cipher tick. Thanks @chick3nman (@antiklesys)
|
||||
- Improved `hf iclass legbrute` throughput further: added wider SIMD backends (128-lane NEON, 256-lane AVX2, 512-lane AVX-512F) with a runtime dispatcher that picks the widest one the CPU supports. Thanks @chick3nman (@antiklesys)
|
||||
- Improved `hf iclass legbrute` throughput further: added wider SIMD backends (128-lane NEON, 256-lane AVX2, 512-lane AVX-512F) Thanks @chick3nman (@antiklesys)
|
||||
- Added `hf mfdes pc` command (@kormax)
|
||||
- Fixed `hf mfu ndefread` NDEF data area size handling (@mjaksn)
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
#include "i2c.h"
|
||||
#include "iso15693.h"
|
||||
#include "protocols.h"
|
||||
#include "crc16.h"
|
||||
|
||||
|
||||
/**
|
||||
@@ -51,6 +52,20 @@ int sam_rxtx(const uint8_t *data, uint16_t n, uint8_t *resp, uint16_t *resplen)
|
||||
*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;
|
||||
@@ -506,6 +521,179 @@ uint16_t sam_copy_payload_nfc2sam(uint8_t *sam_tx, uint8_t *nfc_rx, uint8_t nfc_
|
||||
* @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_rx_buf[1] != 0x61) {
|
||||
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_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);
|
||||
|
||||
// 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_rx_buf[7] == 0x82) {
|
||||
// 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_rx_buf[1] == 0x61) {
|
||||
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) {
|
||||
// SAM resp:
|
||||
// c1 61 c1 00 00
|
||||
|
||||
@@ -73,4 +73,17 @@ 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);
|
||||
|
||||
// NOTE: derived from sam_picopass.c::sam_send_request_iso15's relay loop.
|
||||
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
|
||||
);
|
||||
|
||||
#endif
|
||||
+503
-8
@@ -27,6 +27,12 @@
|
||||
#include "sam_common.h"
|
||||
#include "ticks_apis.h"
|
||||
#include "util.h" // LED_D_ON, LEDsoff
|
||||
#include "iclass.h" // select_iclass_tag, picopass_hdr_t
|
||||
#include "crc16.h" // AddCrc
|
||||
#include "protocols.h" // ICLASS_CMD_UPDATE
|
||||
#include "iso15693.h" // Iso15693InitReader
|
||||
#include "iso14443a.h" // iso14443a_select_card, iso14_apdu
|
||||
#include "fpga_apis.h" // switch_off
|
||||
|
||||
// Tracks whether the SIM module has been initialised since the last reset.
|
||||
// Set true after the first successful sam_sc_handler() invocation; cleared by
|
||||
@@ -39,6 +45,381 @@ void sam_sc_session_invalidate(void) {
|
||||
s_sam_sc_session_active = false;
|
||||
}
|
||||
|
||||
static int sam_sc_snmp_loader_exchange(const uint8_t *snmp, uint16_t snmp_len,
|
||||
uint8_t *response, uint16_t *response_len) {
|
||||
if (snmp == NULL || response == NULL || response_len == NULL ||
|
||||
(uint32_t)snmp_len + 7 > ISO7816_MAX_FRAME)
|
||||
return PM3_EINVARG;
|
||||
|
||||
uint16_t apdu_len = 0;
|
||||
response[apdu_len++] = 0xa0; // PUT DATA, final/only fragment
|
||||
response[apdu_len++] = 0xda;
|
||||
response[apdu_len++] = 0x80; // SnmpLoader extended form
|
||||
response[apdu_len++] = 0x00;
|
||||
response[apdu_len++] = 0x00; // extended Lc marker
|
||||
response[apdu_len++] = (uint8_t)(snmp_len >> 8);
|
||||
response[apdu_len++] = (uint8_t)snmp_len;
|
||||
memcpy(response + apdu_len, snmp, snmp_len);
|
||||
apdu_len += snmp_len;
|
||||
|
||||
if (g_dbglevel >= DBG_INFO) {
|
||||
DbpString("SAM SNMP PUT DATA APDU:");
|
||||
Dbhexdump(apdu_len, response, false);
|
||||
}
|
||||
if (!sam_rxtx(response, apdu_len, response, response_len))
|
||||
return PM3_ECARDEXCHANGE;
|
||||
if (g_dbglevel >= DBG_INFO) {
|
||||
DbpString("SAM SNMP PUT DATA response:");
|
||||
Dbhexdump(*response_len, response, false);
|
||||
}
|
||||
if (*response_len < 2)
|
||||
return PM3_ECARDEXCHANGE;
|
||||
|
||||
const uint16_t put_sw = ((uint16_t)response[*response_len - 2] << 8) | response[*response_len - 1];
|
||||
if (put_sw != 0x9000 && put_sw != 0x6800 && put_sw != 0x6883)
|
||||
return PM3_ECARDEXCHANGE;
|
||||
|
||||
const uint8_t get_data[] = {0xa0, 0xca, 0x80, 0x00, 0x00, 0xff, 0xff};
|
||||
if (g_dbglevel >= DBG_INFO) {
|
||||
DbpString("SAM SNMP GET DATA APDU:");
|
||||
Dbhexdump(sizeof(get_data), get_data, false);
|
||||
}
|
||||
if (!sam_rxtx(get_data, sizeof(get_data), response, response_len))
|
||||
return PM3_ECARDEXCHANGE;
|
||||
if (g_dbglevel >= DBG_INFO) {
|
||||
DbpString("SAM SNMP GET DATA response:");
|
||||
Dbhexdump(*response_len, response, false);
|
||||
}
|
||||
return (*response_len >= 2) ? PM3_SUCCESS : PM3_ECARDEXCHANGE;
|
||||
}
|
||||
|
||||
// Read one definite-length BER TLV. Card API requests in the field
|
||||
// use both normal short lengths and the non-canonical 0x82 00 xx spelling, so
|
||||
// this intentionally accepts all three definite forms used by the SAM.
|
||||
static int sam_sc_read_tlv(const uint8_t *buf, uint16_t buf_len,
|
||||
uint8_t *tag, const uint8_t **value,
|
||||
uint16_t *value_len, uint16_t *consumed) {
|
||||
if (buf == NULL || tag == NULL || value == NULL || value_len == NULL || consumed == NULL || buf_len < 2)
|
||||
return PM3_EINVARG;
|
||||
|
||||
uint16_t off = 0;
|
||||
*tag = buf[off++];
|
||||
uint8_t first_len = buf[off++];
|
||||
uint16_t len = 0;
|
||||
if (first_len < 0x80) {
|
||||
len = first_len;
|
||||
} else if (first_len == 0x81) {
|
||||
if (off >= buf_len) return PM3_EINVARG;
|
||||
len = buf[off++];
|
||||
} else if (first_len == 0x82) {
|
||||
if ((uint32_t)off + 1 >= buf_len) return PM3_EINVARG;
|
||||
len = ((uint16_t)buf[off] << 8) | buf[off + 1];
|
||||
off += 2;
|
||||
} else {
|
||||
return PM3_EINVARG;
|
||||
}
|
||||
if ((uint32_t)off + len > buf_len) return PM3_EINVARG;
|
||||
|
||||
*value = buf + off;
|
||||
*value_len = len;
|
||||
*consumed = (uint16_t)(off + len);
|
||||
return PM3_SUCCESS;
|
||||
}
|
||||
|
||||
// Build/send one continuation to a ProcessCardApi request. Card API
|
||||
// traffic is routed through the external-app endpoint (0x14), not the normal
|
||||
// host endpoint (0x44) used for SC wrapped commands.
|
||||
static int sam_sc_send_card_api_continuation(uint8_t sc_flag,
|
||||
const uint8_t *payload,
|
||||
uint16_t payload_len,
|
||||
uint8_t *response,
|
||||
uint16_t *response_len) {
|
||||
return sam_send_payload_ex(0x14, 0x0a, 0x14, sc_flag,
|
||||
payload, &payload_len, response, response_len);
|
||||
}
|
||||
|
||||
// Card API continuation for an HF transceive result:
|
||||
// BD <L> A0 <L> A0 <L> 80 <L> <APDU response> 81 02 00 00
|
||||
|
||||
static uint8_t sam_sc_ber_len_size(uint16_t len) {
|
||||
return len < 0x80 ? 1 : (len < 0x100 ? 2 : 3);
|
||||
}
|
||||
|
||||
static uint16_t sam_sc_emit_ber_len(uint8_t *out, uint16_t len) {
|
||||
if (len < 0x80) {
|
||||
out[0] = (uint8_t)len;
|
||||
return 1;
|
||||
}
|
||||
if (len < 0x100) {
|
||||
out[0] = 0x81;
|
||||
out[1] = (uint8_t)len;
|
||||
return 2;
|
||||
}
|
||||
out[0] = 0x82;
|
||||
out[1] = (uint8_t)(len >> 8);
|
||||
out[2] = (uint8_t)len;
|
||||
return 3;
|
||||
}
|
||||
|
||||
static uint16_t sam_sc_copy_payload_nfc2sam(uint8_t *out, uint16_t out_size,
|
||||
const uint8_t *nfc, uint16_t nfc_len) {
|
||||
if (out == NULL || nfc == NULL || nfc_len == 0) return 0;
|
||||
|
||||
const uint16_t inner_len = (uint16_t)(1 + sam_sc_ber_len_size(nfc_len) + nfc_len + 4);
|
||||
const uint16_t middle_len = (uint16_t)(1 + sam_sc_ber_len_size(inner_len) + inner_len);
|
||||
const uint16_t outer_len = (uint16_t)(1 + sam_sc_ber_len_size(middle_len) + middle_len);
|
||||
const uint16_t total_len = (uint16_t)(1 + sam_sc_ber_len_size(outer_len) + outer_len);
|
||||
if (total_len > out_size) return 0;
|
||||
|
||||
uint16_t o = 0;
|
||||
out[o++] = 0xbd;
|
||||
o += sam_sc_emit_ber_len(out + o, outer_len);
|
||||
out[o++] = 0xa0;
|
||||
o += sam_sc_emit_ber_len(out + o, middle_len);
|
||||
out[o++] = 0xa0;
|
||||
o += sam_sc_emit_ber_len(out + o, inner_len);
|
||||
out[o++] = 0x80;
|
||||
o += sam_sc_emit_ber_len(out + o, nfc_len);
|
||||
memcpy(out + o, nfc, nfc_len);
|
||||
o += nfc_len;
|
||||
out[o++] = 0x81;
|
||||
out[o++] = 0x02;
|
||||
out[o++] = 0x00;
|
||||
out[o++] = 0x00;
|
||||
return o;
|
||||
}
|
||||
|
||||
static int sam_sc_card_api_loop(uint8_t *response, uint16_t *response_len,
|
||||
bool prevent_epurse_update) {
|
||||
if (response == NULL || response_len == NULL) return PM3_EINVARG;
|
||||
|
||||
uint8_t *continuation = BigBuf_calloc(ISO7816_MAX_FRAME);
|
||||
if (continuation == NULL) return PM3_EMALLOC;
|
||||
|
||||
int res = PM3_SUCCESS;
|
||||
int card_transport = -1;
|
||||
for (uint8_t iteration = 0; iteration < 32; iteration++) {
|
||||
if (*response_len < 6 || response[1] != 0x61) break;
|
||||
|
||||
const uint8_t sc_flag = response[4];
|
||||
const uint8_t *api = response + 5;
|
||||
const uint16_t api_len = (uint16_t)(*response_len - 5);
|
||||
uint8_t root_tag, child_tag, op_tag;
|
||||
const uint8_t *root, *child, *op_value;
|
||||
uint16_t root_len, root_used, child_len, child_used, op_len, op_used;
|
||||
|
||||
if (sam_sc_read_tlv(api, api_len, &root_tag, &root, &root_len, &root_used) != PM3_SUCCESS ||
|
||||
root_tag != 0xa1 ||
|
||||
sam_sc_read_tlv(root, root_len, &child_tag, &child, &child_len, &child_used) != PM3_SUCCESS) {
|
||||
res = PM3_ECARDEXCHANGE;
|
||||
break;
|
||||
}
|
||||
|
||||
uint16_t continuation_len = 0;
|
||||
if (child_tag == 0x82 && child_len == 0) {
|
||||
// samCommandTurnRfFieldOff: acknowledge after dropping the PM3
|
||||
// field. The capture uses the same empty samResponse envelope.
|
||||
switch_off();
|
||||
static const uint8_t rf_off_ack[] = {0xbd, 0x02, 0x8a, 0x00};
|
||||
memcpy(continuation, rf_off_ack, sizeof(rf_off_ack));
|
||||
continuation_len = sizeof(rf_off_ack);
|
||||
} else {
|
||||
if (sam_sc_read_tlv(child, child_len, &op_tag, &op_value, &op_len, &op_used) != PM3_SUCCESS ||
|
||||
op_tag != 0x80) {
|
||||
res = PM3_ECARDEXCHANGE;
|
||||
break;
|
||||
}
|
||||
|
||||
if (child_tag == 0xa0 && op_len == 1 &&
|
||||
(op_value[0] == 0x04 || op_value[0] == 0x02)) {
|
||||
// samCommandScanFieldForCard
|
||||
card_transport = op_value[0];
|
||||
if (op_value[0] == 0x02) {
|
||||
iso14a_card_select_t card_info;
|
||||
memset(&card_info, 0, sizeof(card_info));
|
||||
iso14443a_setup(FPGA_HF_ISO14443A_READER_MOD);
|
||||
bool selected = iso14443a_select_card(NULL, &card_info,
|
||||
NULL, true, 0, false);
|
||||
switch_clock_to_ticks();
|
||||
if (selected == false || card_info.uidlen == 0 || card_info.uidlen > 10) {
|
||||
static const uint8_t no_card[] = {0xbd, 0x02, 0x8a, 0x00};
|
||||
memcpy(continuation, no_card, sizeof(no_card));
|
||||
continuation_len = sizeof(no_card);
|
||||
} else {
|
||||
// SelectedCardInfo / SetDetectedCardInfo.
|
||||
uint16_t o = 0;
|
||||
continuation[o++] = 0xbd;
|
||||
continuation[o++] = 0;
|
||||
continuation[o++] = 0xa0;
|
||||
continuation[o++] = 0;
|
||||
continuation[o++] = 0xa1;
|
||||
continuation[o++] = 0;
|
||||
continuation[o++] = 0x81;
|
||||
continuation[o++] = 0x01;
|
||||
continuation[o++] = 0x14;
|
||||
continuation[o++] = 0xa2;
|
||||
continuation[o++] = 0;
|
||||
continuation[o++] = 0x80;
|
||||
continuation[o++] = 0x02;
|
||||
continuation[o++] = 0x00;
|
||||
continuation[o++] = 0x02;
|
||||
continuation[o++] = 0x81;
|
||||
continuation[o++] = card_info.uidlen;
|
||||
memcpy(continuation + o, card_info.uid, card_info.uidlen);
|
||||
o += card_info.uidlen;
|
||||
continuation[o++] = 0x82;
|
||||
continuation[o++] = 0x02;
|
||||
memcpy(continuation + o, card_info.atqa, 2);
|
||||
o += 2;
|
||||
continuation[o++] = 0x83;
|
||||
continuation[o++] = 0x01;
|
||||
continuation[o++] = card_info.sak;
|
||||
// Back-patch the four nested TLV length bytes now that the
|
||||
// total length `o` is known. Each holds "bytes remaining
|
||||
// after this length byte", i.e. o minus the offset just
|
||||
// past it:
|
||||
// [1] BD outer (payload starts at index 2) -> o-2
|
||||
// [3] A0 wrapper (starts at index 4) -> o-4
|
||||
// [5] A1 samResp (starts at index 6) -> o-6
|
||||
// [10] A2 cardInfo(starts at index 11) -> o-11
|
||||
continuation[1] = (uint8_t)(o - 2);
|
||||
continuation[3] = (uint8_t)(o - 4);
|
||||
continuation[5] = (uint8_t)(o - 6);
|
||||
continuation[10] = (uint8_t)(o - 11);
|
||||
continuation_len = o;
|
||||
}
|
||||
} else {
|
||||
// Picopass SelectedCardInfo continuation.
|
||||
picopass_hdr_t card_info;
|
||||
uint32_t eof_time = 0;
|
||||
memset(&card_info, 0, sizeof(card_info));
|
||||
Iso15693InitReader();
|
||||
bool selected = select_iclass_tag(&card_info, false, &eof_time, false);
|
||||
switch_clock_to_ticks();
|
||||
if (selected == false) {
|
||||
static const uint8_t no_card[] = {0xbd, 0x02, 0x8a, 0x00};
|
||||
memcpy(continuation, no_card, sizeof(no_card));
|
||||
continuation_len = sizeof(no_card);
|
||||
} else {
|
||||
static const uint8_t prefix[] = {
|
||||
0xbd, 0x17, 0xa0, 0x15, 0xa1, 0x13,
|
||||
0x81, 0x01, 0x14, 0xa2, 0x0e,
|
||||
0x80, 0x02, 0x00, 0x04, 0x81, 0x08
|
||||
};
|
||||
memcpy(continuation, prefix, sizeof(prefix));
|
||||
memcpy(continuation + sizeof(prefix), card_info.csn, sizeof(card_info.csn));
|
||||
continuation_len = sizeof(prefix) + sizeof(card_info.csn);
|
||||
}
|
||||
}
|
||||
} else if (child_tag == 0xa1 && op_len > 0) {
|
||||
uint8_t nfc_rx[ISO7816_MAX_FRAME] = {0};
|
||||
uint16_t nfc_rx_len = 0;
|
||||
bool iso14a;
|
||||
if (card_transport == 0x02) {
|
||||
iso14a = true;
|
||||
} else if (card_transport == 0x04) {
|
||||
iso14a = false;
|
||||
} else {
|
||||
iso14a = false;
|
||||
uint16_t child_off = op_used;
|
||||
while (child_off < child_len) {
|
||||
uint8_t arg_tag;
|
||||
const uint8_t *arg_value;
|
||||
uint16_t arg_len, arg_used;
|
||||
if (sam_sc_read_tlv(child + child_off, (uint16_t)(child_len - child_off),
|
||||
&arg_tag, &arg_value, &arg_len, &arg_used) != PM3_SUCCESS)
|
||||
break;
|
||||
if (arg_tag == 0x81 && arg_len == 2 &&
|
||||
arg_value[0] == 0x02 && arg_value[1] == 0x02) {
|
||||
iso14a = true;
|
||||
break;
|
||||
}
|
||||
child_off += arg_used;
|
||||
}
|
||||
}
|
||||
if (iso14a) {
|
||||
switch_clock_to_countsspclk();
|
||||
int apdu_len = iso14_apdu((uint8_t *)op_value, op_len, false,
|
||||
nfc_rx, sizeof(nfc_rx), NULL);
|
||||
switch_clock_to_ticks();
|
||||
if (apdu_len >= 2) {
|
||||
nfc_rx_len = (uint16_t)(apdu_len - 2);
|
||||
res = PM3_SUCCESS;
|
||||
} else {
|
||||
res = PM3_ECARDEXCHANGE;
|
||||
}
|
||||
} else {
|
||||
uint32_t start_time = GetCountSspClk();
|
||||
uint32_t eof_time = start_time + DELAY_ICLASS_VICC_TO_VCD_READER;
|
||||
bool is_update = ((op_value[0] & 0x0f) == ICLASS_CMD_UPDATE);
|
||||
bool fake_epurse = prevent_epurse_update && is_update && op_len >= 10 &&
|
||||
op_value[0] == 0x87 && op_value[1] == 0x02;
|
||||
|
||||
if (fake_epurse) {
|
||||
// The SAM expects the anti-tear e-purse response with the
|
||||
// two 32-bit halves exchanged.
|
||||
memcpy(nfc_rx, op_value + 6, 4);
|
||||
memcpy(nfc_rx + 4, op_value, 4);
|
||||
AddCrc(nfc_rx, 8);
|
||||
nfc_rx_len = 10;
|
||||
} else {
|
||||
switch_clock_to_countsspclk();
|
||||
// Match the established SAM relay tolerance for the
|
||||
// short authentication frames. A CHECK response is only
|
||||
// four bytes and can be missed by a single 380-us wait;
|
||||
// retrying a read/auth command is safe. Never retransmit
|
||||
// an actual UPDATE, however: it may already have modified
|
||||
// the e-purse even when its response was lost.
|
||||
uint8_t tries = is_update ? 1 : 3;
|
||||
uint16_t timeout = is_update ? ICLASS_READER_TIMEOUT_UPDATE : ICLASS_READER_TIMEOUT_ACTALL;
|
||||
nfc_rx_len = 0;
|
||||
do {
|
||||
iclass_send_as_reader((uint8_t *)op_value, op_len, &start_time, &eof_time, false);
|
||||
res = GetIso15693AnswerFromTag(nfc_rx, sizeof(nfc_rx), 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);
|
||||
} while (--tries > 0);
|
||||
switch_clock_to_ticks();
|
||||
}
|
||||
}
|
||||
|
||||
if (res == PM3_SUCCESS && nfc_rx_len > 0) {
|
||||
continuation_len = sam_sc_copy_payload_nfc2sam(continuation, ISO7816_MAX_FRAME,
|
||||
nfc_rx, nfc_rx_len);
|
||||
if (continuation_len == 0)
|
||||
res = PM3_ECARDEXCHANGE;
|
||||
} else {
|
||||
static const uint8_t transceive_failed[] = {
|
||||
0xbd, 0x0a, 0xa0, 0x08, 0x8a, 0x00,
|
||||
0x68, 0x00, 0x81, 0x02, 0x64, 0x01
|
||||
};
|
||||
memcpy(continuation, transceive_failed, sizeof(transceive_failed));
|
||||
continuation_len = sizeof(transceive_failed);
|
||||
res = PM3_SUCCESS; // failure was reported to the SAM
|
||||
}
|
||||
} else {
|
||||
res = PM3_ECARDEXCHANGE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (res != PM3_SUCCESS) break;
|
||||
res = sam_sc_send_card_api_continuation(sc_flag, continuation, continuation_len,
|
||||
response, response_len);
|
||||
if (res != PM3_SUCCESS) break;
|
||||
}
|
||||
|
||||
// A Card API request that remains outstanding after the bounded loop would
|
||||
// otherwise be returned to the host as though it were a final SC response.
|
||||
if (res == PM3_SUCCESS && *response_len >= 2 && response[1] == 0x61)
|
||||
res = PM3_ECARDEXCHANGE;
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
void sam_sc_handler(const PacketCommandNG *c) {
|
||||
|
||||
if (c == NULL || c->length < SAM_SC_HEADER_LEN) {
|
||||
@@ -52,16 +433,25 @@ void sam_sc_handler(const PacketCommandNG *c) {
|
||||
const uint8_t addr_dest = body[SAM_SC_OFF_ADDR_DEST];
|
||||
const uint8_t addr_reply = body[SAM_SC_OFF_ADDR_REPLY];
|
||||
const uint8_t scFlag = body[SAM_SC_OFF_SCFLAG];
|
||||
const bool snmp_loader = addr_src == SAM_SC_ADDR_SNMP_LOADER &&
|
||||
addr_dest == SAM_SC_ADDR_SNMP_LOADER &&
|
||||
addr_reply == SAM_SC_ADDR_SNMP_LOADER;
|
||||
|
||||
const bool force_reset = !!(flags & SAM_SC_FLAG_FORCE_RESET);
|
||||
const bool release = !!(flags & SAM_SC_FLAG_RELEASE);
|
||||
const bool no_payload = !!(flags & SAM_SC_FLAG_NO_PAYLOAD);
|
||||
const bool hf_relay = !!(flags & SAM_SC_FLAG_HF_RELAY);
|
||||
const bool hf_select = !!(flags & SAM_SC_FLAG_HF_SELECT);
|
||||
const bool hf_off = !!(flags & SAM_SC_FLAG_HF_OFF);
|
||||
const bool card_api = !!(flags & SAM_SC_FLAG_CARD_API);
|
||||
const bool prevent_epurse_update = !!(flags & SAM_SC_FLAG_PREVENT_EPURSE_UPDATE);
|
||||
// HF_SELECT / HF_OFF are management ops that carry no SAM payload.
|
||||
const bool no_payload = !!(flags & SAM_SC_FLAG_NO_PAYLOAD) || hf_select || hf_off;
|
||||
|
||||
const uint8_t *payload = body + SAM_SC_HEADER_LEN;
|
||||
uint16_t payload_len = (uint16_t)(c->length - SAM_SC_HEADER_LEN);
|
||||
|
||||
if (no_payload) {
|
||||
// Caller is just managing session state (open/close); no SAM traffic.
|
||||
// Caller is just managing session state (open/close/arm/disarm)
|
||||
payload_len = 0;
|
||||
} else if (payload_len == 0) {
|
||||
reply_ng(CMD_HF_SAM_SC, PM3_EINVARG, NULL, 0);
|
||||
@@ -82,6 +472,10 @@ void sam_sc_handler(const PacketCommandNG *c) {
|
||||
// --info` runs through). Without this warmup, the FIRST sam_send_payload_ex
|
||||
// after I2C_Reset can time out - the 8051<->SAM UART link needs a
|
||||
// sacrificial round-trip to settle. The version response is discarded.
|
||||
//
|
||||
// NOTE: HF_SELECT / HF_OFF must NOT trigger a reset - they run inside an
|
||||
// already-open SC session and a reset would wipe the SAM-side session-flag
|
||||
// binding. They pass neither force_reset nor a cleared session flag.
|
||||
if (force_reset || s_sam_sc_session_active == false) {
|
||||
I2C_Reset_EnterMainProgram();
|
||||
StartTicks();
|
||||
@@ -91,7 +485,85 @@ void sam_sc_handler(const PacketCommandNG *c) {
|
||||
|
||||
int res = PM3_SUCCESS;
|
||||
|
||||
// ---- HF_OFF: disarm the field (no SAM traffic) ----
|
||||
if (hf_off) {
|
||||
switch_off();
|
||||
reply_ng(CMD_HF_SAM_SC, PM3_SUCCESS, NULL, 0);
|
||||
goto done;
|
||||
}
|
||||
|
||||
// ---- HF_SELECT: energize field + select an iCLASS tag, return CSN/AIA ----
|
||||
// Leaves the field ON so subsequent relay-bearing scsend calls can drive
|
||||
// the same card. Reply layout matches the normal path: [scFlag][payload],
|
||||
// here [0x00][CSN(8)][AIA(8)].
|
||||
if (hf_select) {
|
||||
picopass_hdr_t card_info;
|
||||
memset(&card_info, 0, sizeof(card_info));
|
||||
uint32_t eof_time = 0;
|
||||
|
||||
// implicit StartSspClk() happens inside select_iclass_tag
|
||||
Iso15693InitReader();
|
||||
if (select_iclass_tag(&card_info, false, &eof_time, false) == false) {
|
||||
switch_clock_to_ticks();
|
||||
reply_ng(CMD_HF_SAM_SC, PM3_ECARDEXCHANGE, NULL, 0);
|
||||
goto done;
|
||||
}
|
||||
uint8_t aia[8];
|
||||
bool got_aia = false;
|
||||
static const uint8_t read_aia[] = {0x0c, 0x05, 0xde, 0x64};
|
||||
uint8_t aia_rx[10];
|
||||
uint16_t aia_rx_len = 0;
|
||||
uint32_t start_time = GetCountSspClk();
|
||||
uint8_t tries = 3;
|
||||
do {
|
||||
iclass_send_as_reader((uint8_t *)read_aia, sizeof(read_aia),
|
||||
&start_time, &eof_time, false);
|
||||
res = GetIso15693AnswerFromTag(aia_rx, sizeof(aia_rx),
|
||||
ICLASS_READER_TIMEOUT_ACTALL,
|
||||
&eof_time, false, true, &aia_rx_len);
|
||||
if (res == PM3_SUCCESS && aia_rx_len == sizeof(aia_rx)) {
|
||||
memcpy(aia, aia_rx, sizeof(aia));
|
||||
got_aia = true;
|
||||
break;
|
||||
}
|
||||
start_time = eof_time + ((DELAY_ICLASS_VICC_TO_VCD_READER +
|
||||
DELAY_ISO15693_VCD_TO_VICC_READER +
|
||||
(8 * 8 * 8 * 16)) * 2);
|
||||
} while (--tries > 0);
|
||||
|
||||
// Return to ticks so the next SAM I2C exchange has the right clock.
|
||||
switch_clock_to_ticks();
|
||||
|
||||
uint8_t out[17];
|
||||
out[0] = 0x00; // scFlag placeholder (no SAM traffic on this op)
|
||||
memcpy(out + 1, card_info.csn, 8);
|
||||
if (got_aia) memcpy(out + 9, aia, sizeof(aia));
|
||||
reply_ng(CMD_HF_SAM_SC, PM3_SUCCESS, out, got_aia ? sizeof(out) : 9);
|
||||
goto done;
|
||||
}
|
||||
|
||||
if (no_payload == false) {
|
||||
if (hf_relay && card_api == false) {
|
||||
picopass_hdr_t hdr;
|
||||
memset(&hdr, 0, sizeof(hdr));
|
||||
uint32_t eof_time = 0;
|
||||
Iso15693InitReader();
|
||||
if (select_iclass_tag(&hdr, false, &eof_time, false) == false) {
|
||||
if (g_dbglevel >= DBG_INFO) {
|
||||
DbpString("SC relay: re-select FAILED (no card in field)");
|
||||
}
|
||||
switch_clock_to_ticks();
|
||||
reply_ng(CMD_HF_SAM_SC, PM3_ECARDEXCHANGE, NULL, 0);
|
||||
goto done;
|
||||
}
|
||||
if (g_dbglevel >= DBG_INFO) {
|
||||
DbpString("SC relay: re-selected card CSN: ");
|
||||
Dbhexdump(8, hdr.csn, false);
|
||||
}
|
||||
// Back to ticks for the SAM I2C exchange; the relay loop switches to
|
||||
// countsspclk itself when it starts driving HF.
|
||||
switch_clock_to_ticks();
|
||||
}
|
||||
|
||||
uint8_t *response = BigBuf_calloc(ISO7816_MAX_FRAME);
|
||||
if (response == NULL) {
|
||||
@@ -100,11 +572,32 @@ void sam_sc_handler(const PacketCommandNG *c) {
|
||||
}
|
||||
uint16_t response_len = ISO7816_MAX_FRAME;
|
||||
|
||||
res = sam_send_payload_ex(
|
||||
addr_src, addr_dest, addr_reply, scFlag,
|
||||
payload, &payload_len,
|
||||
response, &response_len
|
||||
);
|
||||
if (snmp_loader) {
|
||||
res = sam_sc_snmp_loader_exchange(payload, payload_len, response, &response_len);
|
||||
} else {
|
||||
res = sam_send_payload_ex(
|
||||
addr_src, addr_dest, addr_reply, scFlag,
|
||||
payload, &payload_len,
|
||||
response, &response_len
|
||||
);
|
||||
}
|
||||
|
||||
if (res == PM3_SUCCESS && card_api) {
|
||||
res = sam_sc_card_api_loop(response, &response_len, prevent_epurse_update);
|
||||
} else if (res == PM3_SUCCESS && hf_relay && response_len >= 2 && response[1] == 0x61) {
|
||||
uint8_t *sam_tx = BigBuf_calloc(ISO7816_MAX_FRAME);
|
||||
if (sam_tx == NULL) {
|
||||
res = PM3_EMALLOC;
|
||||
} else {
|
||||
res = sam_relay_iso15_loop(
|
||||
sam_tx, response, &response_len,
|
||||
/* shallow_mod */ false,
|
||||
/* break_on_nr_mac */ false,
|
||||
/* prevent_epurse_update*/ false,
|
||||
NULL, NULL, NULL
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (res != PM3_SUCCESS) {
|
||||
// Whatever happened on the wire, the session may be in an
|
||||
@@ -120,11 +613,13 @@ void sam_sc_handler(const PacketCommandNG *c) {
|
||||
s_sam_sc_session_active = false;
|
||||
}
|
||||
|
||||
if (snmp_loader && res == PM3_SUCCESS) {
|
||||
reply_ng(CMD_HF_SAM_SC, PM3_SUCCESS, response, response_len);
|
||||
// Reformat the buffer for the host: prepend the SAM-assigned scFlag
|
||||
// (firmware-side index 4 of the routing tail), then the SAM payload
|
||||
// (firmware-side index 5 onward). See sam_sc.h for the wire layout.
|
||||
// memmove is safe across the overlapping ranges (dst < src by 4).
|
||||
if (res == PM3_SUCCESS && response_len >= 6) {
|
||||
} else if (res == PM3_SUCCESS && response_len >= 6) {
|
||||
uint8_t sc_flag = response[4];
|
||||
uint16_t sam_payload_len = (uint16_t)(response_len - 5);
|
||||
memmove(response + 1, response + 5, sam_payload_len);
|
||||
|
||||
@@ -76,6 +76,11 @@
|
||||
#define SAM_SC_FLAG_FORCE_RESET (1 << 0)
|
||||
#define SAM_SC_FLAG_RELEASE (1 << 1)
|
||||
#define SAM_SC_FLAG_NO_PAYLOAD (1 << 2)
|
||||
#define SAM_SC_FLAG_HF_RELAY (1 << 3)
|
||||
#define SAM_SC_FLAG_HF_SELECT (1 << 4)
|
||||
#define SAM_SC_FLAG_HF_OFF (1 << 5)
|
||||
#define SAM_SC_FLAG_CARD_API (1 << 6)
|
||||
#define SAM_SC_FLAG_PREVENT_EPURSE_UPDATE (1 << 7)
|
||||
|
||||
// Wire-layout offsets within the CMD_HF_SAM_SC packet body.
|
||||
#define SAM_SC_OFF_FLAGS 0
|
||||
@@ -85,6 +90,8 @@
|
||||
#define SAM_SC_OFF_SCFLAG 4
|
||||
#define SAM_SC_HEADER_LEN 5
|
||||
|
||||
#define SAM_SC_ADDR_SNMP_LOADER 0xff
|
||||
|
||||
void sam_sc_handler(const PacketCommandNG *c);
|
||||
|
||||
// Forces the next sam_sc_handler() call to perform an I2C reset before sending
|
||||
|
||||
+17
-839
@@ -42,6 +42,7 @@
|
||||
#include "cardhelper.h"
|
||||
#include "wiegand_formats.h"
|
||||
#include "wiegand_formatutils.h"
|
||||
#include "util.h" // binstr_2_bytes
|
||||
#include "cmdsmartcard.h" // smart select fct
|
||||
#include "proxendian.h"
|
||||
#include "iclass_cmd.h"
|
||||
@@ -8108,6 +8109,21 @@ static int CmdHFiClassSAMExtract(const char *Cmd) {
|
||||
// 07
|
||||
} else if (d[0] == 0xbd && d[2] == 0xb3 && d[4] == 0xa0) {
|
||||
const uint8_t *pacs = d + 6;
|
||||
// The a0 content element normally starts with 80 <len> <PACS bits>.
|
||||
// Some SAMs / cards return a status-only element with no access-bits
|
||||
// field, e.g. a0 03 82 01 03 - here the first inner tag is 82, not 80.
|
||||
// Don't parse the status byte as PACS (that yields a bogus
|
||||
// "Invalid PACS value"); report that the card has no readable PACS.
|
||||
if (pacs[0] != 0x80) {
|
||||
PrintAndLogEx(WARNING, "No PACS/SIO access-bits returned by the SAM");
|
||||
if (pacs[0] == 0x82 && pacs[1] == 0x01) {
|
||||
PrintAndLogEx(INFO, "SAM content status: " _YELLOW_("0x%02X") " (no physicalAccessBits field)", pacs[2]);
|
||||
}
|
||||
if (verbose) {
|
||||
print_hex(d, resp.length);
|
||||
}
|
||||
return PM3_ENOPACS;
|
||||
}
|
||||
const uint8_t pacs_length = pacs[1];
|
||||
const uint8_t *pacs_data = pacs + 2;
|
||||
int res = HIDDumpPACSBits(pacs_data, pacs_length, verbose);
|
||||
@@ -8153,844 +8169,6 @@ static int CmdHFiClassSAMExtract(const char *Cmd) {
|
||||
return PM3_SUCCESS;
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// HID Artemis secure-channel session - persistent across CLI calls
|
||||
// ===========================================================================
|
||||
//
|
||||
// Sits on top of the new CMD_HF_SAM_SC firmware dispatcher (armsrc/sam_sc.c).
|
||||
// The firmware is just a transport pipe - host owns the SCP02 / Grace crypto
|
||||
// state (sEnc / sMAC1 / sMAC2 / rolling C-MAC + R-MAC) and the SAM-assigned
|
||||
// scFlag. State persists across CLI invocations in the static s_sam_sc.
|
||||
//
|
||||
// All bit patterns mirror UTILITIES_TOOLS/cp1000_client/secure_channel.py.
|
||||
|
||||
// Mirror of armsrc/sam_sc.h - duplicated locally so the client doesn't have
|
||||
// to pull armsrc/ headers. Keep in sync if those flags ever change.
|
||||
#define SAM_SC_FLAG_FORCE_RESET (1 << 0)
|
||||
#define SAM_SC_FLAG_RELEASE (1 << 1)
|
||||
#define SAM_SC_FLAG_NO_PAYLOAD (1 << 2)
|
||||
|
||||
// Mirror of armsrc/i2c.h::ISO7816_MAX_FRAME (270). Not exported to the
|
||||
// client; used as an upper bound for SC plaintext / wrap buffers.
|
||||
#define SAM_SC_MAX_FRAME 270
|
||||
|
||||
// All-zero placeholder master key. Real master keys depend on the target
|
||||
// SAM's provisioning state and must be supplied with --key. Slot 0x85 is the
|
||||
// canonical HidUserAdmin slot index on CP1000-class encoders.
|
||||
static const uint8_t SAM_SC_DEFAULT_MASTER_KEY[16] = {
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
|
||||
};
|
||||
static const uint8_t SAM_SC_DEFAULT_KREF = 0x85;
|
||||
|
||||
typedef struct {
|
||||
bool open;
|
||||
uint8_t kref;
|
||||
uint8_t master_key[16];
|
||||
uint8_t suid[8];
|
||||
uint8_t rnd_a[8];
|
||||
uint8_t rnd_b[8];
|
||||
uint8_t scbk[16];
|
||||
uint8_t s_enc[16];
|
||||
uint8_t s_mac1[16];
|
||||
uint8_t s_mac2[16];
|
||||
uint8_t c_mac[16]; // rolling client MAC (advanced by Wrap)
|
||||
uint8_t r_mac[16]; // rolling server MAC (advanced by Unwrap)
|
||||
uint8_t sc_flag; // SAM-assigned secure-channel routing byte
|
||||
} sam_sc_session_t;
|
||||
|
||||
static sam_sc_session_t s_sam_sc = {0};
|
||||
|
||||
// HID's custom CBC-MAC variant. Zero-pad msg to a 16B multiple; AES-ECB
|
||||
// the first N-1 blocks with sMAC1, the last with sMAC2. Initial mac = iv.
|
||||
// Output is 16 bytes (no truncation). AES-ECB single block done via
|
||||
// aes_encode() with a zero CBC-IV.
|
||||
static void sam_sc_hid_mac(const uint8_t s_mac1[16], const uint8_t s_mac2[16],
|
||||
const uint8_t iv[16],
|
||||
const uint8_t *msg, size_t msg_len,
|
||||
uint8_t out[16]) {
|
||||
uint8_t mac[16];
|
||||
memcpy(mac, iv, 16);
|
||||
|
||||
if (msg_len == 0) msg_len = 16;
|
||||
size_t pad = (16 - (msg_len % 16)) % 16;
|
||||
size_t total = msg_len + pad;
|
||||
size_t blocks = total / 16;
|
||||
|
||||
for (size_t i = 0; i < blocks; i++) {
|
||||
uint8_t block[16];
|
||||
size_t off = i * 16;
|
||||
size_t copy = (off + 16 <= msg_len) ? 16 : (msg_len > off ? msg_len - off : 0);
|
||||
if (copy > 0) memcpy(block, msg + off, copy);
|
||||
if (copy < 16) memset(block + copy, 0x00, 16 - copy);
|
||||
|
||||
for (size_t j = 0; j < 16; j++) block[j] ^= mac[j];
|
||||
|
||||
uint8_t zero_iv[16] = {0}, key_copy[16];
|
||||
memcpy(key_copy, (i + 1 == blocks) ? s_mac2 : s_mac1, 16);
|
||||
aes_encode(zero_iv, key_copy, block, mac, 16);
|
||||
}
|
||||
memcpy(out, mac, 16);
|
||||
}
|
||||
|
||||
// Send a payload via CMD_HF_SAM_SC. Returns SAM-assigned scFlag and the SAM
|
||||
// response bytes (BD/BE-prefixed) per the reply layout in armsrc/sam_sc.h.
|
||||
static int sam_sc_dispatch(uint8_t flags, uint8_t scflag_in,
|
||||
const uint8_t *payload, uint16_t payload_len,
|
||||
uint8_t *sc_flag_out, uint8_t *sam_resp,
|
||||
uint16_t *sam_resp_len) {
|
||||
if (sc_flag_out == NULL || sam_resp == NULL || sam_resp_len == NULL)
|
||||
return PM3_EINVARG;
|
||||
if ((size_t)payload_len + 5 > PM3_CMD_DATA_SIZE)
|
||||
return PM3_EINVARG;
|
||||
|
||||
uint8_t pkt[PM3_CMD_DATA_SIZE] = {0};
|
||||
pkt[0] = flags;
|
||||
pkt[1] = 0x44; // ipcNodeIdExternalApplicationA (us)
|
||||
pkt[2] = 0x0A; // ipcNodeIdPrimarySam
|
||||
pkt[3] = 0x44; // reply-to = us
|
||||
pkt[4] = scflag_in;
|
||||
if (payload_len > 0)
|
||||
memcpy(pkt + 5, payload, payload_len);
|
||||
|
||||
clearCommandBuffer();
|
||||
SendCommandNG(CMD_HF_SAM_SC, pkt, payload_len + 5);
|
||||
|
||||
PacketResponseNG resp;
|
||||
if (WaitForResponse(CMD_HF_SAM_SC, &resp) == false)
|
||||
return PM3_ETIMEOUT;
|
||||
if (resp.status != PM3_SUCCESS)
|
||||
return resp.status;
|
||||
if (resp.length < 1)
|
||||
return PM3_ESOFT;
|
||||
|
||||
*sc_flag_out = resp.data.asBytes[0];
|
||||
uint16_t body_len = (uint16_t)(resp.length - 1);
|
||||
if (body_len > *sam_resp_len) body_len = *sam_resp_len;
|
||||
if (body_len > 0) memcpy(sam_resp, resp.data.asBytes + 1, body_len);
|
||||
*sam_resp_len = body_len;
|
||||
return PM3_SUCCESS;
|
||||
}
|
||||
|
||||
// Build the InitAuth SAM payload (23 bytes):
|
||||
// A0 15 AF 13 80 01 <ver> 81 01 <kref> 82 08 <rnd_a> 83 01 <tca>
|
||||
static uint16_t sam_sc_build_init_auth(uint8_t kref, const uint8_t rnd_a[8],
|
||||
uint8_t out[23]) {
|
||||
out[0] = 0xA0;
|
||||
out[1] = 0x15;
|
||||
out[2] = 0xAF;
|
||||
out[3] = 0x13;
|
||||
out[4] = 0x80;
|
||||
out[5] = 0x01;
|
||||
out[6] = 0x00; // version=0
|
||||
out[7] = 0x81;
|
||||
out[8] = 0x01;
|
||||
out[9] = kref;
|
||||
out[10] = 0x82;
|
||||
out[11] = 0x08;
|
||||
memcpy(out + 12, rnd_a, 8);
|
||||
out[20] = 0x83;
|
||||
out[21] = 0x01;
|
||||
out[22] = 0x00; // tca=0
|
||||
return 23;
|
||||
}
|
||||
|
||||
// Build the ContinueAuth SAM payload (40 bytes):
|
||||
// A0 26 B0 24 80 10 <clientCryptogram> 81 10 <clientCmac>
|
||||
static uint16_t sam_sc_build_continue_auth(const uint8_t client_crypto[16],
|
||||
const uint8_t client_cmac[16],
|
||||
uint8_t out[40]) {
|
||||
out[0] = 0xA0;
|
||||
out[1] = 0x26;
|
||||
out[2] = 0xB0;
|
||||
out[3] = 0x24;
|
||||
out[4] = 0x80;
|
||||
out[5] = 0x10;
|
||||
memcpy(out + 6, client_crypto, 16);
|
||||
out[22] = 0x81;
|
||||
out[23] = 0x10;
|
||||
memcpy(out + 24, client_cmac, 16);
|
||||
return 40;
|
||||
}
|
||||
|
||||
// out = AES-CBC(scbk, IV=0, prefix(2) || rnd_b[0..2] || zeros[12]) (1 block).
|
||||
static void sam_sc_kdf_derive(const uint8_t scbk[16], const uint8_t prefix[2],
|
||||
const uint8_t rnd_b[8], uint8_t out[16]) {
|
||||
uint8_t in[16];
|
||||
in[0] = prefix[0];
|
||||
in[1] = prefix[1];
|
||||
in[2] = rnd_b[0];
|
||||
in[3] = rnd_b[1];
|
||||
memset(in + 4, 0, 12);
|
||||
uint8_t iv[16] = {0}, key_copy[16];
|
||||
memcpy(key_copy, scbk, 16);
|
||||
aes_encode(iv, key_copy, in, out, 16);
|
||||
}
|
||||
|
||||
// Wrap plaintext for transmission on the open SC. Returns wrapped length.
|
||||
// padded = plaintext || 0x80 || zeros (to next 16B)
|
||||
// iv_enc = ~r_mac
|
||||
// ciphertext = AES-CBC(s_enc, iv_enc, padded)
|
||||
// new_cmac = HID-MAC(s_mac1, s_mac2, IV=r_mac, ciphertext)
|
||||
// c_mac <- new_cmac (R-MAC unchanged; only Unwrap advances it)
|
||||
// return ciphertext || new_cmac
|
||||
static uint16_t sam_sc_wrap(const uint8_t *plaintext, uint16_t plaintext_len,
|
||||
uint8_t *out, uint16_t out_cap) {
|
||||
if (s_sam_sc.open == false) return 0;
|
||||
uint16_t padded_len = plaintext_len + 1;
|
||||
if ((padded_len % 16) != 0) padded_len += 16 - (padded_len % 16);
|
||||
if ((uint32_t)padded_len + 16 > out_cap) return 0;
|
||||
|
||||
uint8_t padded[SAM_SC_MAX_FRAME];
|
||||
if (padded_len > sizeof(padded)) return 0;
|
||||
if (plaintext_len > 0) memcpy(padded, plaintext, plaintext_len);
|
||||
padded[plaintext_len] = 0x80;
|
||||
if (padded_len > plaintext_len + 1)
|
||||
memset(padded + plaintext_len + 1, 0x00, padded_len - plaintext_len - 1);
|
||||
|
||||
uint8_t iv_enc[16], key_copy[16];
|
||||
for (int i = 0; i < 16; i++) iv_enc[i] = (uint8_t)(s_sam_sc.r_mac[i] ^ 0xFF);
|
||||
memcpy(key_copy, s_sam_sc.s_enc, 16);
|
||||
aes_encode(iv_enc, key_copy, padded, out, padded_len);
|
||||
|
||||
uint8_t new_cmac[16];
|
||||
sam_sc_hid_mac(s_sam_sc.s_mac1, s_sam_sc.s_mac2, s_sam_sc.r_mac,
|
||||
out, padded_len, new_cmac);
|
||||
|
||||
memcpy(out + padded_len, new_cmac, 16);
|
||||
memcpy(s_sam_sc.c_mac, new_cmac, 16);
|
||||
return (uint16_t)(padded_len + 16);
|
||||
}
|
||||
|
||||
// Unwrap ciphertext+MAC. Verifies MAC under c_mac, decrypts under sEnc with
|
||||
// iv=~c_mac, strips 0x80+zeros padding, advances r_mac. Returns plaintext
|
||||
// length on success, -1 on MAC mismatch or padding error.
|
||||
static int sam_sc_unwrap(const uint8_t *data, uint16_t data_len,
|
||||
uint8_t *plaintext_out, uint16_t out_cap) {
|
||||
if (s_sam_sc.open == false) return -1;
|
||||
if (data_len < 16) return -1;
|
||||
uint16_t ct_len = data_len - 16;
|
||||
const uint8_t *ciphertext = data;
|
||||
const uint8_t *received_mac = data + ct_len;
|
||||
|
||||
uint8_t expected_mac[16];
|
||||
sam_sc_hid_mac(s_sam_sc.s_mac1, s_sam_sc.s_mac2, s_sam_sc.c_mac,
|
||||
ciphertext, ct_len, expected_mac);
|
||||
if (memcmp(expected_mac, received_mac, 16) != 0) return -1;
|
||||
|
||||
if (ct_len == 0) {
|
||||
memcpy(s_sam_sc.r_mac, received_mac, 16);
|
||||
return 0;
|
||||
}
|
||||
if (ct_len > out_cap) return -1;
|
||||
|
||||
uint8_t iv_dec[16], key_copy[16];
|
||||
for (int i = 0; i < 16; i++) iv_dec[i] = (uint8_t)(s_sam_sc.c_mac[i] ^ 0xFF);
|
||||
memcpy(key_copy, s_sam_sc.s_enc, 16);
|
||||
aes_decode(iv_dec, key_copy, (uint8_t *)ciphertext, plaintext_out, ct_len);
|
||||
|
||||
int i = ct_len - 1;
|
||||
while (i >= 0 && plaintext_out[i] == 0x00) i--;
|
||||
if (i < 0 || plaintext_out[i] != 0x80) return -1;
|
||||
int plaintext_len = i;
|
||||
|
||||
memcpy(s_sam_sc.r_mac, received_mac, 16);
|
||||
return plaintext_len;
|
||||
}
|
||||
|
||||
// Peel BD <len> 8A <len> or BD <len> B3 <len> or BE <len> envelopes.
|
||||
// Returns pointer to inner ciphertext+MAC bytes + its length. *path is set
|
||||
// to 'A', 'B', or 'C'. Returns 0 on success, -1 on shape error. Supports
|
||||
// short-form and long-form (0x81/0x82) BER lengths.
|
||||
static int sam_sc_peel_envelope(const uint8_t *resp, uint16_t resp_len,
|
||||
const uint8_t **inner, uint16_t *inner_len,
|
||||
char *path) {
|
||||
if (resp_len < 2) return -1;
|
||||
|
||||
#define SAM_SC_READ_BER_LEN(buf, buflen, off, out_len) \
|
||||
do { \
|
||||
if ((off) >= (buflen)) return -1; \
|
||||
uint8_t b0 = (buf)[(off)++]; \
|
||||
if (b0 < 0x80) { (out_len) = b0; } \
|
||||
else if (b0 == 0x81) { \
|
||||
if ((off) >= (buflen)) return -1; \
|
||||
(out_len) = (buf)[(off)++]; \
|
||||
} else if (b0 == 0x82) { \
|
||||
if ((off) + 1 >= (buflen)) return -1; \
|
||||
(out_len) = ((uint16_t)(buf)[(off)] << 8) | (buf)[(off)+1]; \
|
||||
(off) += 2; \
|
||||
} else return -1; \
|
||||
} while (0)
|
||||
|
||||
uint16_t off = 0;
|
||||
uint8_t outer_tag = resp[off++];
|
||||
uint16_t outer_len;
|
||||
SAM_SC_READ_BER_LEN(resp, resp_len, off, outer_len);
|
||||
if ((uint32_t)off + outer_len > resp_len) return -1;
|
||||
|
||||
if (outer_tag == 0xBE) {
|
||||
*path = 'C';
|
||||
*inner = resp + off;
|
||||
*inner_len = outer_len;
|
||||
return 0;
|
||||
}
|
||||
if (outer_tag != 0xBD) return -1;
|
||||
|
||||
if (off >= resp_len) return -1;
|
||||
uint8_t inner_tag = resp[off++];
|
||||
uint16_t in_len;
|
||||
SAM_SC_READ_BER_LEN(resp, resp_len, off, in_len);
|
||||
if ((uint32_t)off + in_len > resp_len) return -1;
|
||||
|
||||
if (inner_tag == 0x8A) *path = 'A';
|
||||
else if (inner_tag == 0xB3) *path = 'B';
|
||||
else return -1;
|
||||
|
||||
*inner = resp + off;
|
||||
*inner_len = in_len;
|
||||
return 0;
|
||||
|
||||
#undef SAM_SC_READ_BER_LEN
|
||||
}
|
||||
|
||||
// Encode a BER length (short form < 128, otherwise 0x81/0x82).
|
||||
static uint8_t sam_sc_emit_ber_len(uint8_t *out, uint16_t len) {
|
||||
if (len < 0x80) { out[0] = (uint8_t)len; return 1; }
|
||||
if (len < 0x100) { out[0] = 0x81; out[1] = (uint8_t)len; return 2; }
|
||||
out[0] = 0x82;
|
||||
out[1] = (uint8_t)(len >> 8);
|
||||
out[2] = (uint8_t)(len & 0xFF);
|
||||
return 3;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// hf iclass sam scopen
|
||||
// ---------------------------------------------------------------------------
|
||||
static int CmdHFiClassSAMSCOpen(const char *Cmd) {
|
||||
CLIParserContext *ctx;
|
||||
CLIParserInit(&ctx, "hf iclass sam scopen",
|
||||
"Open a HID Artemis secure channel: InitAuth + ContinueAuth.\n"
|
||||
"Persists the session state across CLI commands so subsequent\n"
|
||||
"scsend / scclose operations can wrap/unwrap APDUs against\n"
|
||||
"the same SAM-side session.\n"
|
||||
"Defaults: --key 00000000000000000000000000000000 --kref 0x85",
|
||||
"hf iclass sam scopen --key 00000000000000000000000000000000\n");
|
||||
|
||||
void *argtable[] = {
|
||||
arg_param_begin,
|
||||
arg_str0(NULL, "key", "<hex>", "16-byte AES master key (default: all zeros - override with real key)"),
|
||||
arg_int0(NULL, "kref", "<dec>", "key reference slot 0..255 (default: 0x85)"),
|
||||
arg_lit0("v", "verbose", "verbose output"),
|
||||
arg_param_end
|
||||
};
|
||||
CLIExecWithReturn(ctx, Cmd, argtable, true);
|
||||
|
||||
uint8_t master_key[16];
|
||||
int key_len = 0;
|
||||
if (CLIParamHexToBuf(arg_get_str(ctx, 1), master_key, sizeof(master_key), &key_len) != PM3_SUCCESS) {
|
||||
CLIParserFree(ctx);
|
||||
return PM3_EINVARG;
|
||||
}
|
||||
if (key_len == 0) {
|
||||
memcpy(master_key, SAM_SC_DEFAULT_MASTER_KEY, 16);
|
||||
} else if (key_len != 16) {
|
||||
PrintAndLogEx(FAILED, "--key must be exactly 16 bytes (32 hex chars), got %d", key_len);
|
||||
CLIParserFree(ctx);
|
||||
return PM3_EINVARG;
|
||||
}
|
||||
int kref = arg_get_int_def(ctx, 2, SAM_SC_DEFAULT_KREF);
|
||||
bool verbose = arg_get_lit(ctx, 3);
|
||||
CLIParserFree(ctx);
|
||||
|
||||
if (kref < 0 || kref > 0xFF) {
|
||||
PrintAndLogEx(FAILED, "--kref must be 0..255");
|
||||
return PM3_EINVARG;
|
||||
}
|
||||
|
||||
memset(&s_sam_sc, 0, sizeof(s_sam_sc));
|
||||
memcpy(s_sam_sc.master_key, master_key, 16);
|
||||
s_sam_sc.kref = (uint8_t)kref;
|
||||
|
||||
PrintAndLogEx(INFO, "KREF : 0x%02X", s_sam_sc.kref);
|
||||
PrintAndLogEx(INFO, "Master key : %s", sprint_hex_inrow(master_key, 16));
|
||||
|
||||
// ---------------- Initialize the SAM ----------------
|
||||
// Run the equivalent of `hf iclass sam --info` first. This:
|
||||
// - Resets the SAM via I2C (CMD_HF_SAM_PICOPASS dispatcher)
|
||||
// - Sends sam_get_version + sam_get_serial_number as a warmup
|
||||
// - Prints the version + serial to the user
|
||||
// The InitAuth that follows then reaches a SAM in a known clean state.
|
||||
PrintAndLogEx(INFO, "");
|
||||
PrintAndLogEx(INFO, "--- Initializing SAM ('hf iclass sam --info') ---");
|
||||
int init_rc = CmdHFiClassSAMExtract("--info");
|
||||
if (init_rc != PM3_SUCCESS) {
|
||||
PrintAndLogEx(WARNING, "SAM init returned rc=%d; continuing with InitAuth anyway", init_rc);
|
||||
}
|
||||
PrintAndLogEx(INFO, "--- Opening secure channel ---");
|
||||
PrintAndLogEx(INFO, "");
|
||||
|
||||
// ---------------- InitAuth ----------------
|
||||
uint8_t init_auth[23];
|
||||
sam_sc_build_init_auth(s_sam_sc.kref, s_sam_sc.rnd_a, init_auth);
|
||||
|
||||
uint8_t resp[PM3_CMD_DATA_SIZE];
|
||||
uint16_t resp_len = sizeof(resp);
|
||||
uint8_t sam_flag = 0;
|
||||
int rc = sam_sc_dispatch(SAM_SC_FLAG_FORCE_RESET, 0x00,
|
||||
init_auth, sizeof(init_auth),
|
||||
&sam_flag, resp, &resp_len);
|
||||
if (rc != PM3_SUCCESS) {
|
||||
PrintAndLogEx(FAILED, "InitAuth dispatch failed (rc=%d)", rc);
|
||||
return rc;
|
||||
}
|
||||
if (verbose)
|
||||
PrintAndLogEx(DEBUG, "InitAuth scFlag=0x%02X resp(%u): %s",
|
||||
sam_flag, resp_len, sprint_hex_inrow(resp, resp_len));
|
||||
|
||||
if (resp_len < 36) {
|
||||
PrintAndLogEx(FAILED, "InitAuth response too short (%u bytes)", resp_len);
|
||||
if (verbose) PrintAndLogEx(DEBUG, "raw: %s", sprint_hex_inrow(resp, resp_len));
|
||||
return PM3_ESOFT;
|
||||
}
|
||||
if (resp[0] != 0xBD || resp[2] != 0x8A || resp[3] != 0x20) {
|
||||
PrintAndLogEx(FAILED, "InitAuth response shape unexpected: %02X %02X %02X %02X ...",
|
||||
resp[0], resp[1], resp[2], resp[3]);
|
||||
if (verbose) PrintAndLogEx(DEBUG, "raw: %s", sprint_hex_inrow(resp, resp_len));
|
||||
return PM3_ESOFT;
|
||||
}
|
||||
memcpy(s_sam_sc.suid, resp + 4, 8);
|
||||
memcpy(s_sam_sc.rnd_b, resp + 12, 8);
|
||||
uint8_t server_cryptogram[16];
|
||||
memcpy(server_cryptogram, resp + 20, 16);
|
||||
s_sam_sc.sc_flag = sam_flag;
|
||||
|
||||
PrintAndLogEx(INFO, "scFlag : 0x%02X (SAM-assigned)", s_sam_sc.sc_flag);
|
||||
PrintAndLogEx(INFO, "Server UID : %s", sprint_hex_inrow(s_sam_sc.suid, 8));
|
||||
PrintAndLogEx(INFO, "Server RNDB: %s", sprint_hex_inrow(s_sam_sc.rnd_b, 8));
|
||||
PrintAndLogEx(INFO, "ServerCrypt: %s", sprint_hex_inrow(server_cryptogram, 16));
|
||||
|
||||
// ---------------- Derive SCBK + session keys ----------------
|
||||
{
|
||||
uint8_t suid_data[16];
|
||||
memcpy(suid_data, s_sam_sc.suid, 8);
|
||||
for (int i = 0; i < 8; i++) suid_data[8 + i] = (uint8_t)(s_sam_sc.suid[i] ^ 0xFF);
|
||||
uint8_t iv[16] = {0}, key_copy[16];
|
||||
memcpy(key_copy, master_key, 16);
|
||||
aes_encode(iv, key_copy, suid_data, s_sam_sc.scbk, 16);
|
||||
}
|
||||
static const uint8_t SC_PFX_MAC1[2] = {0x01, 0x01};
|
||||
static const uint8_t SC_PFX_MAC2[2] = {0x01, 0x02};
|
||||
static const uint8_t SC_PFX_ENC[2] = {0x01, 0x82};
|
||||
sam_sc_kdf_derive(s_sam_sc.scbk, SC_PFX_MAC1, s_sam_sc.rnd_b, s_sam_sc.s_mac1);
|
||||
sam_sc_kdf_derive(s_sam_sc.scbk, SC_PFX_MAC2, s_sam_sc.rnd_b, s_sam_sc.s_mac2);
|
||||
sam_sc_kdf_derive(s_sam_sc.scbk, SC_PFX_ENC, s_sam_sc.rnd_b, s_sam_sc.s_enc);
|
||||
|
||||
if (verbose) {
|
||||
PrintAndLogEx(DEBUG, "SCBK : %s", sprint_hex_inrow(s_sam_sc.scbk, 16));
|
||||
PrintAndLogEx(DEBUG, "sEnc : %s", sprint_hex_inrow(s_sam_sc.s_enc, 16));
|
||||
PrintAndLogEx(DEBUG, "sMAC1: %s", sprint_hex_inrow(s_sam_sc.s_mac1, 16));
|
||||
PrintAndLogEx(DEBUG, "sMAC2: %s", sprint_hex_inrow(s_sam_sc.s_mac2, 16));
|
||||
}
|
||||
|
||||
uint8_t expected_srv[16];
|
||||
{
|
||||
uint8_t in[16], iv[16] = {0}, key_copy[16];
|
||||
memcpy(in, s_sam_sc.rnd_a, 8);
|
||||
memcpy(in + 8, s_sam_sc.rnd_b, 8);
|
||||
memcpy(key_copy, s_sam_sc.s_enc, 16);
|
||||
aes_encode(iv, key_copy, in, expected_srv, 16);
|
||||
}
|
||||
if (memcmp(expected_srv, server_cryptogram, 16) != 0) {
|
||||
PrintAndLogEx(FAILED, _RED_("Server cryptogram mismatch") " - master key wrong for KREF 0x%02X",
|
||||
s_sam_sc.kref);
|
||||
if (verbose) {
|
||||
PrintAndLogEx(DEBUG, "expected: %s", sprint_hex_inrow(expected_srv, 16));
|
||||
PrintAndLogEx(DEBUG, "got : %s", sprint_hex_inrow(server_cryptogram, 16));
|
||||
}
|
||||
memset(&s_sam_sc, 0, sizeof(s_sam_sc));
|
||||
return PM3_ESOFT;
|
||||
}
|
||||
PrintAndLogEx(SUCCESS, _GREEN_("InitAuth OK") " - server cryptogram verified");
|
||||
|
||||
// ---------------- ContinueAuth ----------------
|
||||
uint8_t client_crypto[16];
|
||||
{
|
||||
uint8_t in[16], iv[16] = {0}, key_copy[16];
|
||||
memcpy(in, s_sam_sc.rnd_b, 8);
|
||||
memcpy(in + 8, s_sam_sc.rnd_a, 8);
|
||||
memcpy(key_copy, s_sam_sc.s_enc, 16);
|
||||
aes_encode(iv, key_copy, in, client_crypto, 16);
|
||||
}
|
||||
uint8_t client_cmac[16];
|
||||
{
|
||||
uint8_t iv0[16] = {0};
|
||||
sam_sc_hid_mac(s_sam_sc.s_mac1, s_sam_sc.s_mac2, iv0, client_crypto, 16, client_cmac);
|
||||
}
|
||||
if (verbose) {
|
||||
PrintAndLogEx(DEBUG, "client_crypto: %s", sprint_hex_inrow(client_crypto, 16));
|
||||
PrintAndLogEx(DEBUG, "client_cmac : %s", sprint_hex_inrow(client_cmac, 16));
|
||||
}
|
||||
|
||||
uint8_t cont_auth[40];
|
||||
sam_sc_build_continue_auth(client_crypto, client_cmac, cont_auth);
|
||||
|
||||
resp_len = sizeof(resp);
|
||||
rc = sam_sc_dispatch(0, s_sam_sc.sc_flag, cont_auth, sizeof(cont_auth),
|
||||
&sam_flag, resp, &resp_len);
|
||||
if (rc != PM3_SUCCESS) {
|
||||
PrintAndLogEx(FAILED, "ContinueAuth dispatch failed (rc=%d)", rc);
|
||||
memset(&s_sam_sc, 0, sizeof(s_sam_sc));
|
||||
return rc;
|
||||
}
|
||||
if (verbose) {
|
||||
PrintAndLogEx(DEBUG, "ContinueAuth scFlag=0x%02X resp(%u): %s",
|
||||
sam_flag, resp_len, sprint_hex_inrow(resp, resp_len));
|
||||
}
|
||||
if (resp_len < 20) {
|
||||
PrintAndLogEx(FAILED, "ContinueAuth response too short (%u bytes)", resp_len);
|
||||
if (verbose) PrintAndLogEx(DEBUG, "raw: %s", sprint_hex_inrow(resp, resp_len));
|
||||
memset(&s_sam_sc, 0, sizeof(s_sam_sc));
|
||||
return PM3_ESOFT;
|
||||
}
|
||||
if (resp[0] != 0xBD || resp[2] != 0x8A || resp[3] != 0x10) {
|
||||
PrintAndLogEx(FAILED, "ContinueAuth response shape unexpected: %02X %02X %02X %02X ...",
|
||||
resp[0], resp[1], resp[2], resp[3]);
|
||||
if (verbose) PrintAndLogEx(DEBUG, "raw: %s", sprint_hex_inrow(resp, resp_len));
|
||||
memset(&s_sam_sc, 0, sizeof(s_sam_sc));
|
||||
return PM3_ESOFT;
|
||||
}
|
||||
uint8_t server_rmac[16];
|
||||
memcpy(server_rmac, resp + 4, 16);
|
||||
|
||||
uint8_t expected_rmac[16];
|
||||
{
|
||||
uint8_t pad_block[16] = {0};
|
||||
pad_block[0] = 0x80;
|
||||
sam_sc_hid_mac(s_sam_sc.s_mac1, s_sam_sc.s_mac2, client_cmac,
|
||||
pad_block, 16, expected_rmac);
|
||||
}
|
||||
if (memcmp(expected_rmac, server_rmac, 16) != 0) {
|
||||
PrintAndLogEx(FAILED, _RED_("ContinueAuth R-MAC mismatch") " - SAM did not authenticate");
|
||||
if (verbose) {
|
||||
PrintAndLogEx(DEBUG, "expected: %s", sprint_hex_inrow(expected_rmac, 16));
|
||||
PrintAndLogEx(DEBUG, "got : %s", sprint_hex_inrow(server_rmac, 16));
|
||||
}
|
||||
memset(&s_sam_sc, 0, sizeof(s_sam_sc));
|
||||
return PM3_ESOFT;
|
||||
}
|
||||
|
||||
memcpy(s_sam_sc.c_mac, client_cmac, 16);
|
||||
memcpy(s_sam_sc.r_mac, server_rmac, 16);
|
||||
s_sam_sc.sc_flag = sam_flag;
|
||||
s_sam_sc.open = true;
|
||||
|
||||
PrintAndLogEx(SUCCESS, _GREEN_("Secure channel OPEN") " (scFlag=0x%02X, KREF=0x%02X)",
|
||||
s_sam_sc.sc_flag, s_sam_sc.kref);
|
||||
PrintAndLogEx(INFO, "Use 'hf iclass sam scsend' to send wrapped APDUs;");
|
||||
PrintAndLogEx(INFO, "use 'hf iclass sam scclose' to terminate.");
|
||||
return PM3_SUCCESS;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// hf iclass sam scsend --payload <hex>
|
||||
// ---------------------------------------------------------------------------
|
||||
static int CmdHFiClassSAMSCSend(const char *Cmd) {
|
||||
CLIParserContext *ctx;
|
||||
CLIParserInit(&ctx, "hf iclass sam scsend",
|
||||
"Send a plaintext SAMCommand body through the open secure\n"
|
||||
"channel. The client wraps it (encrypt + MAC + chain RMAC),\n"
|
||||
"sends via CMD_HF_SAM_SC, unwraps the response, and prints\n"
|
||||
"the plaintext. Requires 'scopen' to have been called first.\n"
|
||||
"Test payload: --payload 8200 (samCommandGetSamVersion)",
|
||||
"hf iclass sam scsend --payload 8200\n"
|
||||
"hf iclass sam scsend --payload b9028a00 -v\n"
|
||||
"hf iclass sam scsend --payload b903850100");
|
||||
|
||||
void *argtable[] = {
|
||||
arg_param_begin,
|
||||
arg_str1(NULL, "payload", "<hex>", "plaintext SAMCommand body (the bytes inside A0 <len>)"),
|
||||
arg_lit0("v", "verbose", "verbose output"),
|
||||
arg_param_end
|
||||
};
|
||||
CLIExecWithReturn(ctx, Cmd, argtable, false);
|
||||
|
||||
if (s_sam_sc.open == false) {
|
||||
PrintAndLogEx(FAILED, "no open secure channel - run 'scopen' first");
|
||||
CLIParserFree(ctx);
|
||||
return PM3_ESOFT;
|
||||
}
|
||||
|
||||
uint8_t plaintext[SAM_SC_MAX_FRAME];
|
||||
int plaintext_len = 0;
|
||||
if (CLIParamHexToBuf(arg_get_str(ctx, 1), plaintext, sizeof(plaintext), &plaintext_len) != PM3_SUCCESS) {
|
||||
CLIParserFree(ctx);
|
||||
return PM3_EINVARG;
|
||||
}
|
||||
if (plaintext_len < 1) {
|
||||
PrintAndLogEx(FAILED, "--payload must be at least 1 byte");
|
||||
CLIParserFree(ctx);
|
||||
return PM3_EINVARG;
|
||||
}
|
||||
bool verbose = arg_get_lit(ctx, 2);
|
||||
CLIParserFree(ctx);
|
||||
|
||||
PrintAndLogEx(INFO, "Plaintext : %s", sprint_hex_inrow(plaintext, plaintext_len));
|
||||
|
||||
uint8_t wrapped[SAM_SC_MAX_FRAME];
|
||||
uint16_t wrapped_len = sam_sc_wrap(plaintext, (uint16_t)plaintext_len,
|
||||
wrapped, sizeof(wrapped));
|
||||
if (wrapped_len == 0) {
|
||||
PrintAndLogEx(FAILED, "wrap() failed");
|
||||
return PM3_ESOFT;
|
||||
}
|
||||
if (verbose)
|
||||
PrintAndLogEx(DEBUG, "wrapped (%u): %s", wrapped_len, sprint_hex_inrow(wrapped, wrapped_len));
|
||||
|
||||
uint8_t sam_payload[SAM_SC_MAX_FRAME + 4];
|
||||
uint16_t off = 0;
|
||||
sam_payload[off++] = 0xA0;
|
||||
off += sam_sc_emit_ber_len(sam_payload + off, wrapped_len);
|
||||
if ((uint32_t)off + wrapped_len > sizeof(sam_payload)) {
|
||||
PrintAndLogEx(FAILED, "wrapped payload too large for buffer");
|
||||
return PM3_ESOFT;
|
||||
}
|
||||
memcpy(sam_payload + off, wrapped, wrapped_len);
|
||||
off += wrapped_len;
|
||||
|
||||
uint8_t resp[PM3_CMD_DATA_SIZE];
|
||||
uint16_t resp_len = sizeof(resp);
|
||||
uint8_t sam_flag = 0;
|
||||
int rc = sam_sc_dispatch(0, s_sam_sc.sc_flag,
|
||||
sam_payload, off, &sam_flag, resp, &resp_len);
|
||||
if (rc != PM3_SUCCESS) {
|
||||
PrintAndLogEx(FAILED, "dispatch failed (rc=%d)", rc);
|
||||
return rc;
|
||||
}
|
||||
if (verbose) {
|
||||
PrintAndLogEx(DEBUG, "scFlag=0x%02X resp(%u): %s",
|
||||
sam_flag, resp_len, sprint_hex_inrow(resp, resp_len));
|
||||
}
|
||||
if (sam_flag != s_sam_sc.sc_flag && verbose) {
|
||||
PrintAndLogEx(WARNING, "SAM scFlag changed: was 0x%02X, now 0x%02X",
|
||||
s_sam_sc.sc_flag, sam_flag);
|
||||
}
|
||||
|
||||
const uint8_t *inner = NULL;
|
||||
uint16_t inner_len = 0;
|
||||
char path = '?';
|
||||
if (sam_sc_peel_envelope(resp, resp_len, &inner, &inner_len, &path) != 0) {
|
||||
PrintAndLogEx(FAILED, "could not peel SAM response envelope");
|
||||
if (verbose) PrintAndLogEx(DEBUG, "raw: %s", sprint_hex_inrow(resp, resp_len));
|
||||
return PM3_ESOFT;
|
||||
}
|
||||
if (verbose)
|
||||
PrintAndLogEx(DEBUG, "envelope=Path %c, inner ciphertext+MAC (%u): %s",
|
||||
path, inner_len, sprint_hex_inrow(inner, inner_len));
|
||||
|
||||
uint8_t plain_resp[SAM_SC_MAX_FRAME];
|
||||
int plain_len = sam_sc_unwrap(inner, inner_len, plain_resp, sizeof(plain_resp));
|
||||
if (plain_len < 0) {
|
||||
PrintAndLogEx(FAILED, _RED_("unwrap failed") " - MAC mismatch or padding error");
|
||||
if (verbose) PrintAndLogEx(DEBUG, "raw response: %s", sprint_hex_inrow(resp, resp_len));
|
||||
return PM3_ESOFT;
|
||||
}
|
||||
|
||||
if (path == 'C') {
|
||||
PrintAndLogEx(WARNING, _YELLOW_("Path C errorResponse") " - SAM returned an error");
|
||||
} else {
|
||||
PrintAndLogEx(SUCCESS, _GREEN_("Decrypt OK") " (Path %c)", path);
|
||||
}
|
||||
PrintAndLogEx(INFO, "Plaintext response (%d): %s",
|
||||
plain_len, sprint_hex_inrow(plain_resp, plain_len));
|
||||
return PM3_SUCCESS;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// hf iclass sam scclose
|
||||
// ---------------------------------------------------------------------------
|
||||
static int CmdHFiClassSAMSCClose(const char *Cmd) {
|
||||
CLIParserContext *ctx;
|
||||
CLIParserInit(&ctx, "hf iclass sam scclose",
|
||||
"Send Terminate (91 00) over the open SC and release the\n"
|
||||
"firmware-side SAM session.",
|
||||
"hf iclass sam scclose");
|
||||
void *argtable[] = {
|
||||
arg_param_begin,
|
||||
arg_lit0("v", "verbose", "verbose output"),
|
||||
arg_param_end
|
||||
};
|
||||
CLIExecWithReturn(ctx, Cmd, argtable, true);
|
||||
bool verbose = arg_get_lit(ctx, 1);
|
||||
CLIParserFree(ctx);
|
||||
|
||||
if (s_sam_sc.open == false) {
|
||||
PrintAndLogEx(WARNING, "no open secure channel - nothing to close");
|
||||
return PM3_SUCCESS;
|
||||
}
|
||||
|
||||
uint8_t terminate[2] = {0x91, 0x00};
|
||||
uint8_t wrapped[64];
|
||||
uint16_t wrapped_len = sam_sc_wrap(terminate, sizeof(terminate),
|
||||
wrapped, sizeof(wrapped));
|
||||
if (wrapped_len == 0) {
|
||||
PrintAndLogEx(FAILED, "wrap() of Terminate failed - forcing local close");
|
||||
memset(&s_sam_sc, 0, sizeof(s_sam_sc));
|
||||
return PM3_ESOFT;
|
||||
}
|
||||
|
||||
uint8_t sam_payload[80];
|
||||
uint16_t off = 0;
|
||||
sam_payload[off++] = 0xA0;
|
||||
off += sam_sc_emit_ber_len(sam_payload + off, wrapped_len);
|
||||
memcpy(sam_payload + off, wrapped, wrapped_len);
|
||||
off += wrapped_len;
|
||||
|
||||
uint8_t resp[PM3_CMD_DATA_SIZE];
|
||||
uint16_t resp_len = sizeof(resp);
|
||||
uint8_t sam_flag = 0;
|
||||
int rc = sam_sc_dispatch(SAM_SC_FLAG_RELEASE, s_sam_sc.sc_flag,
|
||||
sam_payload, off, &sam_flag, resp, &resp_len);
|
||||
if (verbose) {
|
||||
PrintAndLogEx(DEBUG, "Terminate scFlag=0x%02X resp(%u): %s",
|
||||
sam_flag, resp_len, sprint_hex_inrow(resp, resp_len));
|
||||
}
|
||||
|
||||
memset(&s_sam_sc, 0, sizeof(s_sam_sc));
|
||||
|
||||
if (rc != PM3_SUCCESS) {
|
||||
PrintAndLogEx(WARNING, "Terminate dispatch returned rc=%d - host state cleared anyway", rc);
|
||||
return rc;
|
||||
}
|
||||
PrintAndLogEx(SUCCESS, _GREEN_("Secure channel closed"));
|
||||
return PM3_SUCCESS;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// hf iclass sam dispatcher
|
||||
// ---------------------------------------------------------------------------
|
||||
//
|
||||
// Backwards-compat router: when invoked without a subcommand keyword (or
|
||||
// with a flag-style argument like `--info` / `-d ...` / `-f ...`), defers
|
||||
// to the legacy PACS-extraction implementation (CmdHFiClassSAMExtract).
|
||||
// When the first word matches a known SC subcommand, dispatches to the
|
||||
// scopen / scsend / scclose / help handlers.
|
||||
//
|
||||
// So all of these continue to work:
|
||||
// hf iclass sam (legacy default)
|
||||
// hf iclass sam --info (legacy SAM info)
|
||||
// hf iclass sam -p -d <hex> (legacy with flags)
|
||||
// hf iclass sam -f hf-iclass-dump.bin (legacy emulate-from-file)
|
||||
// And these are new:
|
||||
// hf iclass sam scopen
|
||||
// hf iclass sam scsend --payload <hex>
|
||||
// hf iclass sam scclose
|
||||
// hf iclass sam help (lists the SC subcommands)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int CmdHFiClassSAMHelp(const char *Cmd);
|
||||
|
||||
// SC subcommand dispatch table. Note: no `help` entry here on purpose -
|
||||
// use `hf iclass sam --help` (or `-h`) for the SAM-level help. The bare-word
|
||||
// `help` keyword is still accepted as a deprecated alias (see sam_cmd_is_help_request).
|
||||
static command_t SAMSubCommandTable[] = {
|
||||
{"scopen", CmdHFiClassSAMSCOpen, IfPm3Smartcard, "Open Artemis SC (InitAuth + ContinueAuth); persists session"},
|
||||
{"scsend", CmdHFiClassSAMSCSend, IfPm3Smartcard, "Send wrapped SAMCommand body through the open SC"},
|
||||
{"scclose", CmdHFiClassSAMSCClose, IfPm3Smartcard, "Send Terminate; release the SC session"},
|
||||
{NULL, NULL, NULL, NULL}
|
||||
};
|
||||
|
||||
static int CmdHFiClassSAMHelp(const char *Cmd) {
|
||||
(void)Cmd;
|
||||
PrintAndLogEx(NORMAL, "");
|
||||
PrintAndLogEx(NORMAL, _YELLOW_("hf iclass sam") " - HID SAM operations");
|
||||
PrintAndLogEx(NORMAL, "");
|
||||
PrintAndLogEx(NORMAL, "Secure-channel subcommands:");
|
||||
CmdsHelp(SAMSubCommandTable);
|
||||
PrintAndLogEx(NORMAL, "");
|
||||
PrintAndLogEx(NORMAL, "Legacy PACS-extraction flags (no subcommand, applied to " _YELLOW_("hf iclass sam") " directly):");
|
||||
PrintAndLogEx(NORMAL, " --info get SAM version + serial number (also warms up the SAM)");
|
||||
PrintAndLogEx(NORMAL, " -d, --data <hex> DER-encoded SAMCommand to send (raw, no SC)");
|
||||
PrintAndLogEx(NORMAL, " -s, --snmp --data is in SNMP format without the A0/94 headers");
|
||||
PrintAndLogEx(NORMAL, " -p, --prevent fake the e-purse update during PACS extraction");
|
||||
PrintAndLogEx(NORMAL, " --break stop tag interaction at nr-mac (for SIO extract)");
|
||||
PrintAndLogEx(NORMAL, " -f, --file <fn> emulate from a dump file instead of a real card");
|
||||
PrintAndLogEx(NORMAL, " -n, --nodetect skip card detect + SetDetectedCardInfo");
|
||||
PrintAndLogEx(NORMAL, " -k, --keep keep the field active after the command");
|
||||
PrintAndLogEx(NORMAL, " -t, --tlv decode the response as TLV");
|
||||
PrintAndLogEx(NORMAL, " --shallow shallow modulation");
|
||||
PrintAndLogEx(NORMAL, " -v, --verbose verbose output");
|
||||
PrintAndLogEx(NORMAL, "");
|
||||
PrintAndLogEx(NORMAL, "Examples:");
|
||||
PrintAndLogEx(NORMAL, " " _YELLOW_("hf iclass sam") " extract PACS via SAM (defaults)");
|
||||
PrintAndLogEx(NORMAL, " " _YELLOW_("hf iclass sam --info") " get SAM version + serial (warmup ping)");
|
||||
PrintAndLogEx(NORMAL, " " _YELLOW_("hf iclass sam scopen") " open Artemis secure channel");
|
||||
PrintAndLogEx(NORMAL, " " _YELLOW_("hf iclass sam scsend --payload 8200") " send wrapped SAMCommand");
|
||||
PrintAndLogEx(NORMAL, " " _YELLOW_("hf iclass sam scclose") " terminate the SC session");
|
||||
PrintAndLogEx(NORMAL, "");
|
||||
return PM3_SUCCESS;
|
||||
}
|
||||
|
||||
// Returns true if Cmd consists solely of "-h", "--help", or the bare-word
|
||||
// "help" (with optional surrounding whitespace). Used to intercept help
|
||||
// requests before the dispatcher would otherwise forward them to the legacy
|
||||
// CLIParser. The bare-word "help" is kept as a deprecated alias - it's no
|
||||
// longer advertised in the subcommand table, but existing scripts/muscle
|
||||
// memory still resolve correctly.
|
||||
static bool sam_cmd_is_help_request(const char *Cmd) {
|
||||
while (*Cmd == ' ' || *Cmd == '\t') Cmd++;
|
||||
size_t len = 0;
|
||||
while (Cmd[len] && Cmd[len] != ' ' && Cmd[len] != '\t') len++;
|
||||
if (len == 0) return false;
|
||||
// ensure standalone (no trailing args)
|
||||
const char *tail = Cmd + len;
|
||||
while (*tail == ' ' || *tail == '\t') tail++;
|
||||
if (*tail != '\0') return false;
|
||||
if (len == 2 && memcmp(Cmd, "-h", 2) == 0) return true;
|
||||
if (len == 6 && memcmp(Cmd, "--help", 6) == 0) return true;
|
||||
if (len == 4 && memcmp(Cmd, "help", 4) == 0) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
static int CmdHFiClassSAM(const char *Cmd) {
|
||||
// Skip leading whitespace.
|
||||
while (*Cmd == ' ' || *Cmd == '\t') Cmd++;
|
||||
|
||||
// `--help` / `-h` (standalone) -> SC-aware help instead of the legacy
|
||||
// CLIParser's auto-help (which would hide the SC subcommands). The
|
||||
// bare-word `help` is also accepted as a deprecated alias (no longer
|
||||
// listed in the subcommand table but still resolves here).
|
||||
if (sam_cmd_is_help_request(Cmd)) {
|
||||
return CmdHFiClassSAMHelp(Cmd);
|
||||
}
|
||||
|
||||
// No args or a flag-style invocation -> legacy PACS-extract implementation.
|
||||
// Preserves `hf iclass sam`, `hf iclass sam --info`, `hf iclass sam -d ...`,
|
||||
// `hf iclass sam -f ...` etc.
|
||||
if (*Cmd == '\0' || *Cmd == '-') {
|
||||
return CmdHFiClassSAMExtract(Cmd);
|
||||
}
|
||||
|
||||
// Match the first word against known SC subcommands. The CmdsParse
|
||||
// dispatcher would print help on no match, but we want unknown first
|
||||
// words to fall through to the legacy implementation, so we check first.
|
||||
size_t word_len = 0;
|
||||
while (Cmd[word_len] && Cmd[word_len] != ' ' && Cmd[word_len] != '\t')
|
||||
word_len++;
|
||||
|
||||
for (size_t i = 0; SAMSubCommandTable[i].Name != NULL; i++) {
|
||||
const char *name = SAMSubCommandTable[i].Name;
|
||||
size_t nl = strlen(name);
|
||||
if (nl == word_len && memcmp(name, Cmd, word_len) == 0) {
|
||||
clearCommandBuffer();
|
||||
return CmdsParse(SAMSubCommandTable, Cmd);
|
||||
}
|
||||
}
|
||||
|
||||
// Unknown first word - defer to legacy (which will either parse it as a
|
||||
// free-form arg or error out cleanly with its own help).
|
||||
return CmdHFiClassSAMExtract(Cmd);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// hf iclass liberate — detect and liberate MKF / iCopy-X cloned cards
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -9300,7 +8478,7 @@ static command_t CommandTable[] = {
|
||||
{"managekeys", CmdHFiClassManageKeys, AlwaysAvailable, "Manage keys to use with iclass commands"},
|
||||
{"permutekey", CmdHFiClassPermuteKey, AlwaysAvailable, "Permute function from 'heart of darkness' paper"},
|
||||
{"-----------", CmdHelp, IfPm3Smartcard, "----------------------- " _CYAN_("SAM") " -----------------------"},
|
||||
{"sam", CmdHFiClassSAM, IfPm3Smartcard, "SAM ops: PACS extract + secure channel (scopen/scsend/scclose)"},
|
||||
{"sam", CmdHFiClassSAMExtract, IfPm3Smartcard, "Extract PACS from a HID SAM"},
|
||||
{NULL, NULL, NULL, NULL}
|
||||
};
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
// SEOS commands
|
||||
//-----------------------------------------------------------------------------
|
||||
#include "cmdhfseos.h"
|
||||
#include "cmdhficlass.h" // shared Artemis SCP02 SEOS read implementation
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
Reference in new issue
Block a user