From 7ff85b09ec818f892ee449940f52528e5c6dccdb Mon Sep 17 00:00:00 2001 From: iceman1001 Date: Thu, 20 Aug 2026 18:34:57 +0200 Subject: [PATCH] remove the old SAM implementation --- CHANGELOG.md | 23 +- armsrc/sam_common.c | 188 +++++++++ armsrc/sam_common.h | 13 + armsrc/sam_sc.c | 511 ++++++++++++++++++++++- armsrc/sam_sc.h | 7 + client/src/cmdhficlass.c | 856 +-------------------------------------- client/src/cmdhfseos.c | 1 + 7 files changed, 741 insertions(+), 858 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5b86a6320..a80c7790f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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) diff --git a/armsrc/sam_common.c b/armsrc/sam_common.c index 843d6c409..2007f6d0c 100644 --- a/armsrc/sam_common.c +++ b/armsrc/sam_common.c @@ -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 diff --git a/armsrc/sam_common.h b/armsrc/sam_common.h index a353516ac..4c5efa121 100644 --- a/armsrc/sam_common.h +++ b/armsrc/sam_common.h @@ -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 diff --git a/armsrc/sam_sc.c b/armsrc/sam_sc.c index 47945eb2d..021821676 100644 --- a/armsrc/sam_sc.c +++ b/armsrc/sam_sc.c @@ -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 A0 A0 80 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); diff --git a/armsrc/sam_sc.h b/armsrc/sam_sc.h index 204b6242f..da45c89e3 100644 --- a/armsrc/sam_sc.h +++ b/armsrc/sam_sc.h @@ -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 diff --git a/client/src/cmdhficlass.c b/client/src/cmdhficlass.c index c073be5ee..9daeffd60 100644 --- a/client/src/cmdhficlass.c +++ b/client/src/cmdhficlass.c @@ -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 . + // 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 81 01 82 08 83 01 -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 81 10 -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 8A or BD B3 or BE 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", "", "16-byte AES master key (default: all zeros - override with real key)"), - arg_int0(NULL, "kref", "", "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 -// --------------------------------------------------------------------------- -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", "", "plaintext SAMCommand body (the bytes inside A0 )"), - 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 (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 -// 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 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 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} }; diff --git a/client/src/cmdhfseos.c b/client/src/cmdhfseos.c index c386e871a..8458819bd 100644 --- a/client/src/cmdhfseos.c +++ b/client/src/cmdhfseos.c @@ -16,6 +16,7 @@ // SEOS commands //----------------------------------------------------------------------------- #include "cmdhfseos.h" +#include "cmdhficlass.h" // shared Artemis SCP02 SEOS read implementation #include #include #include