diff --git a/.github/workflows/build_client.yml b/.github/workflows/build_client.yml index 31b01082..20348202 100644 --- a/.github/workflows/build_client.yml +++ b/.github/workflows/build_client.yml @@ -38,10 +38,15 @@ jobs: uses: actions/checkout@v3 with: ref: ${{ inputs.checkout-sha == null && github.sha || inputs.checkout-sha }} + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.12' - name: Install PyInstaller and client dependencies run: | - pip3 install pyinstaller - pip3 install -r software/script/requirements.txt + python -m pip install --upgrade pip + pip install pyinstaller + pip install -r software/script/requirements.txt - name: Run OS specific setup run: ${{ matrix.pre_command }} - name: Compile native code diff --git a/CHANGELOG.md b/CHANGELOG.md index 2adb0587..398b0b31 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,8 +30,7 @@ This project uses the changelog in accordance with [keepchangelog](http://keepac - Added support for timestamped comments in CLI via `rem`, `;`, `%` or `#` (@doegox) - Fixed watchdog trigger during `hw factory_reset` (@doegox) - Added PyInstaller support for CLI client (@augustozanellato) - - Added TechSecurityTools to Autorized Distributors (@yanis333) - + - Added proper Mifare Ultralight (original, C, EV1) / NTAG (213, 215, 216) emulation (@turbocooler). ## [v2.0.0][2023-09-26] - Added `hw slot nick delete` and DELETE_SLOT_TAG_NICK (@doegox) diff --git a/README.md b/README.md index ffe6a783..a2023e32 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,8 @@ Europe: [Lab401](https://lab401.com/) United States: [Hackerwarehouse](https://hackerwarehouse.com/) +MTools Tec: [MTools Tec](https://shop.mtoolstec.com/) + Anywhere else: [Sneaktechnology](https://sneaktechnology.com) / [Aliexpress by RRG](https://proxgrind.aliexpress.com/store/1101312023) / [TechSecurityTools](https://techsecuritytools.com/product/chameleon-ultra/) # What is it and how to use ? @@ -39,6 +41,7 @@ Table for future functionality progress # Compatible applications * [ChameleonUltraGUI](https://github.com/GameTec-live/ChameleonUltraGUI) +* [MTools BLE](docs/mtoolsble.md) # Videos @@ -47,6 +50,7 @@ Table for future functionality progress * [Downloading and compiling the official CLI](https://www.youtube.com/watch?v=VGpAeitNXH0) * [Downloading ChameleonUltraGUI](https://www.youtube.com/watch?v=rHH7iqbX3nY) * [ChameleonGUI features overview](https://www.youtube.com/watch?v=YqE8wyVSse4) +* [MTools BLE - How to clone a card with ChameleonUltra](https://youtu.be/IvH-xtdW1Wk?si=4exqgAAeJ-kxU3aN) # Official channels diff --git a/docs/development.md b/docs/development.md index 409dbd56..33c295b8 100644 --- a/docs/development.md +++ b/docs/development.md @@ -334,6 +334,31 @@ in a second terminal: JLinkRTTClient ``` +For Windows add these lines to the `tasks.json`: +```json + { + "label": "logs", + "type": "shell", + "command": "C:\\WINDOWS\\System32\\cmd.exe", + "args": ["/K", "C:\\Program Files\\SEGGER\\JLink\\JLinkRTTClient.exe"], + "options": { + "cwd": "C:\\Program Files\\SEGGER\\JLink" + }, + "problemMatcher": [] + }, + { + "label": "logger server", + "type": "shell", + "command": "C:\\WINDOWS\\System32\\cmd.exe", + "args": ["/K", "C:\\Program Files\\SEGGER\\JLink\\JLink.Exe", "-if", "SWD", "-device", "nrf52", "-speed", "4000", "-autoconnect", "1"], + "options": { + "cwd": "C:\\Program Files\\SEGGER\\JLink" + }, + "problemMatcher": [] + }, +``` + + ## Using SWO pin as UART to monitor NRF_LOG One can set `NRF_LOG_UART_ON_SWO_ENABLED := 1` in `Makefile.defs` to activate this functionality. diff --git a/docs/firmware.md b/docs/firmware.md index c056e825..9bf5162d 100644 --- a/docs/firmware.md +++ b/docs/firmware.md @@ -71,7 +71,7 @@ On a new Chameleon (or after a factory reset), 3 slots are defined, slot 1 holdi - slot 1 LF: a EM4100 with UID `DEADBEEF88` - slot 1 HF: a MIFARE Classic 1k with UID `DEADBEEF` -- slot 2 HF: a MIFARE Classic 1k with UID `DEADBEEF` +- slot 2 HF: a MIFARE Classic Ultralight with UID `04689571FA5C64` - slot 3 LF: a EM4100 with UID `DEADBEEF88` When a slot is selected, the LED shows what type of card is loaded with the following color code: diff --git a/docs/mtoolsble.md b/docs/mtoolsble.md new file mode 100644 index 00000000..6586f9de --- /dev/null +++ b/docs/mtoolsble.md @@ -0,0 +1,50 @@ +# MTools BLE Introduction +MTools BLE supports managing the ChamleonUltra, ChameleonLite and DevKits via BLE connections. +## Downlaod Link +- [MTools BLE on iOS](https://apps.apple.com/app/mtools-ble-rfid-reader/id1531345398) +- [MTools BLE on Google Play](https://play.google.com/store/apps/details?id=com.mtoolstec.mtoolsLite) + +## How to connect with Bluetooth in MTools BLE +1. Click **A** or **B** button to power on. +2. Click **Bluetooth List** icon in App to search devices. +3. Click **Connect** button on the right to connect. + +#### Notice for Bluetooth Connection +1. Grant the Bluetooth permission of App on iOS. +2. Allow Location permission to scan Bluetooth devices on Android. + +## Features for ChameleonUltra +### Slot Manager +1. Fetch all slot status. +2. Enable or disable Slots. +3. Change LF and HF Slot name. +4. Set LF and HF Tag Type. +5. Delete and reset all slots. + +### Reader +1. Fast read LF and HF Tag. +2. Simulate Mifare Classic Tag with UID, SAK, ATQA and empty dump. +3. Simulate Mifare Ultralight Tag with UID, SAK, ATQA and empty dump. +4. Simulate EM410X LF tag or manually set the ID then simulate. + +### Mifare Classic Dump +1. eRead full dump from current active slot to App. +2. Upload full dump to current active slot and simulate. +3. Read Mifare Mini, 1K, 2K, 4K dump from tag with known keys. +4. Write Gen1A, Gen2, Gen3, Gen4 dump to tag with known keys. +5. Format common and magic Mifare Classic tags. +6. Modify block data and save to new dump file. + +### Mifare Ultralight Dump +1. eRead full dump from current active slot to App. +2. Upload full dump to current active slot and simulate. +3. Read Mifare Ultralight dump from tag. +4. Write Mifare Ultralight dump to tag. + +### Settings +1. Set the Animation of LEDs. +2. Set press and long press button of A and B. +3. Set the **Mifare Classic Emulation** of current slot. +4. Set the **Mifare Ultralight Emulation** of current slot. +5. DFU Tool for updating firmware. +6. Reset Chameleon Device. \ No newline at end of file diff --git a/docs/mtoolslite.md b/docs/mtoolslite.md deleted file mode 100644 index 813069c2..00000000 --- a/docs/mtoolslite.md +++ /dev/null @@ -1,42 +0,0 @@ -# MTools Lite Introduction -MTools Lite supports managing the ChamleonUltra, ChameleonLite and DevKits via BLE connections. -## Downlaod Link -- [MTools Lite on iOS](https://apps.apple.com/app/mtools-ble-rfid-reader/id1531345398) -- [MTools Lite on Google Play](https://play.google.com/store/apps/details?id=com.mtoolstec.mtoolsLite) - -## How to connect with Bluetooth in MTools Lite -1. Click **A** or **B** button to power on. -2. Click **Bluetooth List** icon in App to search devices. -3. Click **Connect** button on the right to connect. - -#### Notice for Bluetooth Connection -1. Grant the Bluetooth permission of App on iOS. -2. Allow Location permission to scan Bluetooth devices on Android. - -## Functions for ChameleonUltra in MTools Lite -### Slot Manager -1. Fetch all slot status. -2. Enable or disable Slots. -3. Change LF and HF Slot name. -4. Set LF and HF Tag Type. -5. Delete and reset all slots. - -### Reader -1. Fast read LF and HF Tag. -2. Simulate Mifare Classic Tag with UID, SAK, ATQA and empty dump. -3. Simulate EM410X LF tag or manually set the ID then simulate. - -### Dumps -1. Mifare Keys Manage. -2. Get dump of Mifare Classic Mini, 1K, 2K and 4K with known keys. -3. Modify block data and save to new dump file. -4. Do quick simulation to current active slot. -5. Upload full dump to current active slot. -6. Eread full dump from current active slot to App. - -### Settings -1. Set the Animation of LEDs. -2. Set press and long press button of A and B. -3. Set the **Mifare Classic Emulation** of current slot. -4. Enter DFU Mode. -5. Reset Chameleon Device. \ No newline at end of file diff --git a/docs/protocol.md b/docs/protocol.md index 2eef2a63..29941737 100644 --- a/docs/protocol.md +++ b/docs/protocol.md @@ -388,6 +388,54 @@ Notes: * Command: no data * Response: no data or N bytes: `uidlen|uid[uidlen]|atqa[2]|sak|atslen|ats[atslen]`. UID, ATQA, SAK and ATS as bytes. * CLI: cf `hw slot list`/`hf mf econfig`/`hf mfu econfig` +### 4019: MF0_NTAG_GET_UID_MAGIC_MODE +* Command: no data +* Response: 1 byte where a non-zero value indicates that UID magic mode is enabled for the current slot. +* CLI: cf `hf mfu econfig` +### 4020: MF0_NTAG_SET_UID_MAGIC_MODE +* Command: 1 byte where a non-zero value indicates that UID magic mode should be enabled for the current slot, otherwise disabled. +* Response: no data +* CLI: cf `hf mfu econfig --enable-uid-magic`/`hf mfu econfig --disable-uid-magic` +### 4021: MF0_NTAG_READ_EMU_PAGE_DATA +* Command: 2 bytes: one for first page index, one for count of pages to be read. +* Response: `4 * n` bytes where `n` is the number if pages to be read +* CLI: cf `hf mfu eview` +### 4022: MF0_NTAG_WRITE_EMU_PAGE_DATA +* Command: 2 + `n * 4` bytes: one for first page index, one for count of pages to be read, `n * 4` for `n` pages data. +* Response: no data +* CLI: unused +### 4023: MF0_NTAG_GET_VERSION_DATA +* Command: no data +* Response: 8 version data bytes. +* CLI: cf `hf mfu econfig` +### 4024: MF0_NTAG_SET_VERSION_DATA +* Command: 8 version data bytes. +* Response: no data +* CLI: cf `hf mfu econfig --set-version ` +### 4025: MF0_NTAG_GET_SIGNATURE_DATA +* Command: no data +* Response: 32 signature data bytes. +* CLI: cf `hf mfu econfig` +### 4026: MF0_NTAG_SET_SIGNATURE_DATA +* Command: 32 signature data bytes. +* Response: no data +* CLI: cf `hf mfu econfig --set-signature ` +### 4027: MF0_NTAG_GET_COUNTER_DATA +* Command: 1 byte for the counter index +* Response: 3 bytes for the counter value (big-endian) + 1 byte for tearing where `0xBD` means tearing flag is not set. +* CLI: cf `hf mfu ercnt` +### 4028: MF0_NTAG_SET_COUNTER_DATA +* Command: 1 byte where the lower 7 bits are the counter index and the top bit indicates whether tearing event flag should be reset + 3 bytes of the counter value (big-endian). +* Response: no data +* CLI: cf `hf mfu ewcnt` +### 4029: MF0_NTAG_RESET_AUTH_CNT +* Command: no data +* Response: 1 byte for the old value of the unsuccessful auth counter. +* CLI: cf `hf mfu econfig --reset-auth-cnt` +### 4030: MF0_NTAG_GET_PAGE_COUNT +* Command: no data +* Response: 1 byte is the number of pages available in the current card slot +* CLI: unused ### 5000: EM410X_SET_EMU_ID * Command: 5 bytes. `id[5]`. ID as 5 bytes. * Response: no data diff --git a/docs/quickstart.md b/docs/quickstart.md index cd82e9eb..6e93a389 100644 --- a/docs/quickstart.md +++ b/docs/quickstart.md @@ -9,8 +9,8 @@ Quickly get up and running with your Chameleon and no technical skill - [ChameleonUltraGUI on Windows](https://nightly.link/GameTec-live/ChameleonUltraGUI/workflows/build-app/main/windows-installer.zip) ([or without installer](https://nightly.link/GameTec-live/ChameleonUltraGUI/workflows/build-app/main/windows.zip)) - [ChameleonUltraGUI on Linux](https://nightly.link/GameTec-live/ChameleonUltraGUI/workflows/build-app/main/linux.zip) - [ChameleonUltraGUI on macOS](https://apps.apple.com/app/chameleon-ultra-gui/id6462919364) - - [MTools Lite on iOS](https://apps.apple.com/app/mtools-ble-rfid-reader/id1531345398) - - [MTools Lite on Google Play](https://play.google.com/store/apps/details?id=com.mtoolstec.mtoolsLite) + - [MTools BLE on App Store](https://apps.apple.com/app/mtools-ble-rfid-reader/id1531345398) + - [MTools BLE on Google Play](https://play.google.com/store/apps/details?id=com.mtoolstec.mtoolsLite) 2. Connect your Chameleon via USB or BLE diff --git a/firmware/application/Makefile b/firmware/application/Makefile index 632b3e28..b6596afd 100644 --- a/firmware/application/Makefile +++ b/firmware/application/Makefile @@ -28,7 +28,7 @@ SRC_FILES += \ $(PROJ_DIR)/rfid/nfctag/hf/crypto1_helper.c \ $(PROJ_DIR)/rfid/nfctag/hf/nfc_14a.c \ $(PROJ_DIR)/rfid/nfctag/hf/nfc_mf1.c \ - $(PROJ_DIR)/rfid/nfctag/hf/nfc_ntag.c \ + $(PROJ_DIR)/rfid/nfctag/hf/nfc_mf0_ntag.c \ $(PROJ_DIR)/rfid/nfctag/lf/lf_tag_em.c \ $(PROJ_DIR)/utils/dataframe.c \ $(PROJ_DIR)/utils/delayed_reset.c \ diff --git a/firmware/application/src/app_cmd.c b/firmware/application/src/app_cmd.c index 8a7e9ab1..cff94f1f 100644 --- a/firmware/application/src/app_cmd.c +++ b/firmware/application/src/app_cmd.c @@ -688,13 +688,44 @@ static data_frame_tx_t *cmd_processor_em410x_get_emu_id(uint16_t cmd, uint16_t s return data_frame_make(cmd, STATUS_SUCCESS, LF_EM410X_TAG_ID_SIZE, responseData); } -static data_frame_tx_t *cmd_processor_hf14a_get_anti_coll_data(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { +static nfc_tag_14a_coll_res_reference_t *get_coll_res_data(bool write) { + nfc_tag_14a_coll_res_reference_t *info; tag_slot_specific_type_t tag_types; + tag_emulation_get_specific_types_by_slot(tag_emulation_get_slot(), &tag_types); - if (tag_types.tag_hf == TAG_TYPE_UNDEFINED) { - return data_frame_make(cmd, STATUS_SUCCESS, 0, NULL); // no data in slot, don't send garbage - } - nfc_tag_14a_coll_res_reference_t *info = get_saved_mifare_coll_res(); + + switch (tag_types.tag_hf) { + case TAG_TYPE_MIFARE_1024: + case TAG_TYPE_MIFARE_2048: + case TAG_TYPE_MIFARE_4096: + case TAG_TYPE_MIFARE_Mini: + info = write ? get_mifare_coll_res() : get_saved_mifare_coll_res(); + break; + case TAG_TYPE_MF0ICU1: + case TAG_TYPE_MF0ICU2: + case TAG_TYPE_MF0UL11: + case TAG_TYPE_MF0UL21: + case TAG_TYPE_NTAG_210: + case TAG_TYPE_NTAG_212: + case TAG_TYPE_NTAG_213: + case TAG_TYPE_NTAG_215: + case TAG_TYPE_NTAG_216: + info = nfc_tag_mf0_ntag_get_coll_res(); + break; + default: + // no collision resolution data for slot + info = NULL; + break; + } + + return info; +} + +static data_frame_tx_t *cmd_processor_hf14a_get_anti_coll_data(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + nfc_tag_14a_coll_res_reference_t *info = get_coll_res_data(false); + + if (info == NULL) return data_frame_make(cmd, STATUS_SUCCESS, 0, NULL); + // uidlen[1]|uid[uidlen]|atqa[2]|sak[1]|atslen[1]|ats[atslen] // dynamic length, so no struct uint8_t payload[1 + *info->size + 2 + 1 + 1 + 254]; @@ -790,6 +821,166 @@ static data_frame_tx_t *cmd_processor_mf1_read_emu_block_data(uint16_t cmd, uint return data_frame_make(cmd, STATUS_SUCCESS, result_length, result_buffer); } +static data_frame_tx_t *cmd_processor_mf0_ntag_write_emu_page_data(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + uint8_t byte; + uint8_t active_slot = tag_emulation_get_slot(); + + tag_slot_specific_type_t active_slot_tag_types; + tag_emulation_get_specific_types_by_slot(active_slot, &active_slot_tag_types); + + int nr_pages = nfc_tag_mf0_ntag_get_nr_pages_by_tag_type(active_slot_tag_types.tag_hf); + // This means wrong slot type. + if (nr_pages <= 0) return data_frame_make(cmd, STATUS_INVALID_SLOT_TYPE, 0, data); + + if (length < 2) { + byte = nr_pages; + return data_frame_make(cmd, STATUS_PAR_ERR, 1, &byte); + } + + int page_index = data[0]; + int pages_count = data[1]; + int byte_length = (int)pages_count * NFC_TAG_MF0_NTAG_DATA_SIZE; + + if (pages_count == 0) return data_frame_make(cmd, STATUS_SUCCESS, 0, NULL); + else if ( + (page_index >= ((int)nr_pages)) + || (pages_count > (((int)nr_pages) - page_index)) + || (((int)length - 2) < byte_length) + ) { + byte = nr_pages; + return data_frame_make(cmd, STATUS_PAR_ERR, 1, &byte); + } + + tag_data_buffer_t *buffer = get_buffer_by_tag_type(active_slot_tag_types.tag_hf); + nfc_tag_mf0_ntag_information_t *info = (nfc_tag_mf0_ntag_information_t *)buffer->buffer; + + memcpy(&info->memory[page_index][0], &data[2], byte_length); + + return data_frame_make(cmd, STATUS_SUCCESS, 0, NULL); +} + +static data_frame_tx_t *cmd_processor_mf0_ntag_get_emu_page_count(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + uint8_t byte; + uint8_t active_slot = tag_emulation_get_slot(); + + tag_slot_specific_type_t active_slot_tag_types; + tag_emulation_get_specific_types_by_slot(active_slot, &active_slot_tag_types); + + int nr_pages = nfc_tag_mf0_ntag_get_nr_pages_by_tag_type(active_slot_tag_types.tag_hf); + // This means wrong slot type. + if (nr_pages <= 0) return data_frame_make(cmd, STATUS_INVALID_SLOT_TYPE, 0, data); + + // Convert the int value to u8 value if it's valid. + byte = nr_pages; + return data_frame_make(cmd, STATUS_SUCCESS, 1, &byte); +} + +static data_frame_tx_t *cmd_processor_mf0_ntag_read_emu_page_data(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + uint8_t byte; + uint8_t active_slot = tag_emulation_get_slot(); + + tag_slot_specific_type_t active_slot_tag_types; + tag_emulation_get_specific_types_by_slot(active_slot, &active_slot_tag_types); + + int nr_pages = nfc_tag_mf0_ntag_get_nr_pages_by_tag_type(active_slot_tag_types.tag_hf); + // This means wrong slot type. + if (nr_pages <= 0) return data_frame_make(cmd, STATUS_INVALID_SLOT_TYPE, 0, data); + + if (length < 2) { + byte = nr_pages; + return data_frame_make(cmd, STATUS_PAR_ERR, 1, &byte); + } + + int page_index = data[0]; + int pages_count = data[1]; + + if (pages_count == 0) return data_frame_make(cmd, STATUS_SUCCESS, 0, NULL); + + tag_data_buffer_t *buffer = get_buffer_by_tag_type(active_slot_tag_types.tag_hf); + nfc_tag_mf0_ntag_information_t *info = (nfc_tag_mf0_ntag_information_t *)buffer->buffer; + + return data_frame_make(cmd, STATUS_SUCCESS, pages_count * NFC_TAG_MF0_NTAG_DATA_SIZE, &info->memory[page_index][0]); +} + +static data_frame_tx_t *cmd_processor_mf0_ntag_get_version_data(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + uint8_t *version_data = nfc_tag_mf0_ntag_get_version_data(); + if (version_data == NULL) return data_frame_make(cmd, STATUS_INVALID_SLOT_TYPE, 0, NULL); + + return data_frame_make(cmd, STATUS_SUCCESS, NFC_TAG_MF0_NTAG_VER_SIZE, version_data); +} + +static data_frame_tx_t *cmd_processor_mf0_ntag_set_version_data(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + if (length != 8) return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); + + uint8_t *version_data = nfc_tag_mf0_ntag_get_version_data(); + if (version_data == NULL) return data_frame_make(cmd, STATUS_INVALID_SLOT_TYPE, 0, NULL); + memcpy(version_data, data, NFC_TAG_MF0_NTAG_VER_SIZE); + + return data_frame_make(cmd, STATUS_SUCCESS, 0, NULL); +} + +static data_frame_tx_t *cmd_processor_mf0_ntag_get_signature_data(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + uint8_t *signature_data = nfc_tag_mf0_ntag_get_signature_data(); + if (signature_data == NULL) return data_frame_make(cmd, STATUS_INVALID_SLOT_TYPE, 0, NULL); + + return data_frame_make(cmd, STATUS_SUCCESS, NFC_TAG_MF0_NTAG_SIG_SIZE, signature_data); +} + +static data_frame_tx_t *cmd_processor_mf0_ntag_set_signature_data(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + if (length != NFC_TAG_MF0_NTAG_SIG_SIZE) return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); + + uint8_t *signature_data = nfc_tag_mf0_ntag_get_signature_data(); + if (signature_data == NULL) return data_frame_make(cmd, STATUS_INVALID_SLOT_TYPE, 0, NULL); + memcpy(signature_data, data, NFC_TAG_MF0_NTAG_SIG_SIZE); + + return data_frame_make(cmd, STATUS_SUCCESS, 0, NULL); +} + +static data_frame_tx_t *cmd_processor_mf0_ntag_get_counter_data(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + if (length != 1) return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); + + uint8_t index = data[0] & 0x7F; + uint8_t *counter_data = nfc_tag_mf0_ntag_get_counter_data_by_index(index); + if (counter_data == NULL) return data_frame_make(cmd, STATUS_INVALID_SLOT_TYPE, 0, NULL); + + bool tearing = (counter_data[MF0_NTAG_AUTHLIM_OFF_IN_CTR] & MF0_NTAG_AUTHLIM_MASK_IN_CTR) != 0; + + uint8_t response[4]; + memcpy(response, counter_data, 3); + response[3] = tearing ? 0x00 : 0xBD; + + return data_frame_make(cmd, STATUS_SUCCESS, 4, response); +} + +static data_frame_tx_t *cmd_processor_mf0_ntag_set_counter_data(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + if (length != 4) return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); + + uint8_t index = data[0] & 0x7F; + uint8_t *counter_data = nfc_tag_mf0_ntag_get_counter_data_by_index(index); + if (counter_data == NULL) return data_frame_make(cmd, STATUS_INVALID_SLOT_TYPE, 0, NULL); + + // clear tearing event flag + if ((data[0] & 0x80) == 0x80) { + counter_data[MF0_NTAG_AUTHLIM_OFF_IN_CTR] &= ~MF0_NTAG_TEARING_MASK_IN_AUTHLIM; + } + + // copy the actual counter value + memcpy(counter_data, &data[1], 3); + + return data_frame_make(cmd, STATUS_SUCCESS, 0, NULL); +} + +static data_frame_tx_t *cmd_processor_mf0_ntag_reset_auth_cnt(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + // all tags with counters support auth + uint8_t *counter_data = nfc_tag_mf0_ntag_get_counter_data_by_index(0); + if (counter_data == NULL) return data_frame_make(cmd, STATUS_INVALID_SLOT_TYPE, 0, NULL); + + uint8_t old_value = counter_data[MF0_NTAG_AUTHLIM_OFF_IN_CTR] & MF0_NTAG_AUTHLIM_MASK_IN_CTR; + counter_data[MF0_NTAG_AUTHLIM_OFF_IN_CTR] &= ~MF0_NTAG_AUTHLIM_MASK_IN_CTR; + + return data_frame_make(cmd, STATUS_SUCCESS, 1, &old_value); +} + static data_frame_tx_t *cmd_processor_hf14a_set_anti_coll_data(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { // uidlen[1]|uid[uidlen]|atqa[2]|sak[1]|atslen[1]|ats[atslen] // dynamic length, so no struct @@ -799,7 +990,8 @@ static data_frame_tx_t *cmd_processor_hf14a_set_anti_coll_data(uint16_t cmd, uin (length < 1 + data[0] + 2 + 1 + 1 + data[1 + data[0] + 2 + 1])) { return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); } - nfc_tag_14a_coll_res_reference_t *info = get_mifare_coll_res(); + nfc_tag_14a_coll_res_reference_t *info = get_coll_res_data(true); + uint16_t offset = 0; *(info->size) = (nfc_tag_14a_uid_size)data[offset]; offset++; @@ -1026,6 +1218,24 @@ static data_frame_tx_t *after_hf_reader_run(uint16_t cmd, uint16_t status, uint1 // fct will be defined after m_data_cmd_map because we need to know its size data_frame_tx_t *cmd_processor_get_device_capabilities(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data); +static data_frame_tx_t *cmd_processor_mf0_ntag_get_uid_mode(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + int rc = nfc_tag_mf0_ntag_get_uid_mode(); + + if (rc < 0) return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); + else { + uint8_t res = rc; + return data_frame_make(cmd, STATUS_SUCCESS, 1, &res); + } +} + +static data_frame_tx_t *cmd_processor_mf0_ntag_set_uid_mode(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + if (length != 1 || !nfc_tag_mf0_ntag_set_uid_mode(data[0] != 0)) { + return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); + } + + return data_frame_make(cmd, STATUS_SUCCESS, 0, NULL); +} + /** * (cmd -> processor) function map, the map struct is: * cmd code before process cmd processor after process @@ -1109,6 +1319,18 @@ static cmd_data_map_t m_data_cmd_map[] = { { DATA_CMD_MF1_GET_WRITE_MODE, NULL, cmd_processor_mf1_get_write_mode, NULL }, { DATA_CMD_MF1_SET_WRITE_MODE, NULL, cmd_processor_mf1_set_write_mode, NULL }, { DATA_CMD_HF14A_GET_ANTI_COLL_DATA, NULL, cmd_processor_hf14a_get_anti_coll_data, NULL }, + { DATA_CMD_MF0_NTAG_GET_UID_MAGIC_MODE, NULL, cmd_processor_mf0_ntag_get_uid_mode, NULL }, + { DATA_CMD_MF0_NTAG_SET_UID_MAGIC_MODE, NULL, cmd_processor_mf0_ntag_set_uid_mode, NULL }, + { DATA_CMD_MF0_NTAG_READ_EMU_PAGE_DATA, NULL, cmd_processor_mf0_ntag_read_emu_page_data, NULL }, + { DATA_CMD_MF0_NTAG_WRITE_EMU_PAGE_DATA, NULL, cmd_processor_mf0_ntag_write_emu_page_data, NULL }, + { DATA_CMD_MF0_NTAG_GET_VERSION_DATA, NULL, cmd_processor_mf0_ntag_get_version_data, NULL }, + { DATA_CMD_MF0_NTAG_SET_VERSION_DATA, NULL, cmd_processor_mf0_ntag_set_version_data, NULL }, + { DATA_CMD_MF0_NTAG_GET_SIGNATURE_DATA, NULL, cmd_processor_mf0_ntag_get_signature_data, NULL }, + { DATA_CMD_MF0_NTAG_SET_SIGNATURE_DATA, NULL, cmd_processor_mf0_ntag_set_signature_data, NULL }, + { DATA_CMD_MF0_NTAG_GET_COUNTER_DATA, NULL, cmd_processor_mf0_ntag_get_counter_data, NULL }, + { DATA_CMD_MF0_NTAG_SET_COUNTER_DATA, NULL, cmd_processor_mf0_ntag_set_counter_data, NULL }, + { DATA_CMD_MF0_NTAG_RESET_AUTH_CNT, NULL, cmd_processor_mf0_ntag_reset_auth_cnt, NULL }, + { DATA_CMD_MF0_NTAG_GET_PAGE_COUNT, NULL, cmd_processor_mf0_ntag_get_emu_page_count, NULL }, { DATA_CMD_EM410X_SET_EMU_ID, NULL, cmd_processor_em410x_set_emu_id, NULL }, { DATA_CMD_EM410X_GET_EMU_ID, NULL, cmd_processor_em410x_get_emu_id, NULL }, diff --git a/firmware/application/src/app_main.c b/firmware/application/src/app_main.c index 011f4ba7..28213510 100644 --- a/firmware/application/src/app_main.c +++ b/firmware/application/src/app_main.c @@ -651,10 +651,16 @@ static void btn_fn_copy_ic_uid(void) { break; } + case TAG_TYPE_NTAG_210: + case TAG_TYPE_NTAG_212: case TAG_TYPE_NTAG_213: case TAG_TYPE_NTAG_215: - case TAG_TYPE_NTAG_216: { - nfc_tag_ntag_information_t *p_info = (nfc_tag_ntag_information_t *)buffer->buffer; + case TAG_TYPE_NTAG_216: + case TAG_TYPE_MF0ICU1: + case TAG_TYPE_MF0ICU2: + case TAG_TYPE_MF0UL11: + case TAG_TYPE_MF0UL21: { + nfc_tag_mf0_ntag_information_t *p_info = (nfc_tag_mf0_ntag_information_t *)buffer->buffer; antres = &(p_info->res_coll); break; } diff --git a/firmware/application/src/app_status.h b/firmware/application/src/app_status.h index b0c05f64..0716dbf5 100644 --- a/firmware/application/src/app_status.h +++ b/firmware/application/src/app_status.h @@ -32,4 +32,5 @@ #define STATUS_NOT_IMPLEMENTED (0x69) // Calling some unrealized operations, which belongs to the missed error of the developer #define STATUS_FLASH_WRITE_FAIL (0x70) // Flash writing failed #define STATUS_FLASH_READ_FAIL (0x71) // Flash read failed +#define STATUS_INVALID_SLOT_TYPE (0x72) // Invalid slot type #endif diff --git a/firmware/application/src/data_cmd.h b/firmware/application/src/data_cmd.h index 10313b16..6b0ff588 100644 --- a/firmware/application/src/data_cmd.h +++ b/firmware/application/src/data_cmd.h @@ -106,6 +106,18 @@ #define DATA_CMD_MF1_GET_WRITE_MODE (4016) #define DATA_CMD_MF1_SET_WRITE_MODE (4017) #define DATA_CMD_HF14A_GET_ANTI_COLL_DATA (4018) +#define DATA_CMD_MF0_NTAG_GET_UID_MAGIC_MODE (4019) +#define DATA_CMD_MF0_NTAG_SET_UID_MAGIC_MODE (4020) +#define DATA_CMD_MF0_NTAG_READ_EMU_PAGE_DATA (4021) +#define DATA_CMD_MF0_NTAG_WRITE_EMU_PAGE_DATA (4022) +#define DATA_CMD_MF0_NTAG_GET_VERSION_DATA (4023) +#define DATA_CMD_MF0_NTAG_SET_VERSION_DATA (4024) +#define DATA_CMD_MF0_NTAG_GET_SIGNATURE_DATA (4025) +#define DATA_CMD_MF0_NTAG_SET_SIGNATURE_DATA (4026) +#define DATA_CMD_MF0_NTAG_GET_COUNTER_DATA (4027) +#define DATA_CMD_MF0_NTAG_SET_COUNTER_DATA (4028) +#define DATA_CMD_MF0_NTAG_RESET_AUTH_CNT (4029) +#define DATA_CMD_MF0_NTAG_GET_PAGE_COUNT (4030) // // ****************************************************************** diff --git a/firmware/application/src/rfid/nfctag/hf/nfc_14a.c b/firmware/application/src/rfid/nfctag/hf/nfc_14a.c index 352efc8a..d1cd700f 100644 --- a/firmware/application/src/rfid/nfctag/hf/nfc_14a.c +++ b/firmware/application/src/rfid/nfctag/hf/nfc_14a.c @@ -295,6 +295,7 @@ uint8_t nfc_tag_14a_unwrap_frame(const uint8_t *pbtFrame, const size_t szFrameBi * @param[in] appendCrc Whether to send the byte flow, automatically send the CRC16 verification automatically */ void nfc_tag_14a_tx_bytes(uint8_t *data, uint32_t bytes, bool appendCrc) { + ASSERT(bytes <= MAX_NFC_TX_BUFFER_SIZE); NFC_14A_TX_BYTE_CORE(data, bytes, appendCrc, NRF_NFCT_FRAME_DELAY_MODE_WINDOWGRID); } diff --git a/firmware/application/src/rfid/nfctag/hf/nfc_14a.h b/firmware/application/src/rfid/nfctag/hf/nfc_14a.h index f70fc1fd..c103c02f 100644 --- a/firmware/application/src/rfid/nfctag/hf/nfc_14a.h +++ b/firmware/application/src/rfid/nfctag/hf/nfc_14a.h @@ -3,7 +3,7 @@ #include "tag_emulation.h" -#define MAX_NFC_RX_BUFFER_SIZE 64 +#define MAX_NFC_RX_BUFFER_SIZE 257 #define MAX_NFC_TX_BUFFER_SIZE 64 #define NFC_TAG_14A_CRC_LENGTH 2 diff --git a/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c b/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c new file mode 100644 index 00000000..125d9e19 --- /dev/null +++ b/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c @@ -0,0 +1,1247 @@ +#include + +#include "nfc_mf0_ntag.h" +#include "nfc_14a.h" +#include "fds_util.h" +#include "tag_persistence.h" + +#define NRF_LOG_MODULE_NAME tag_mf0_ntag +#include "nrf_log.h" +#include "nrf_log_ctrl.h" +#include "nrf_log_default_backends.h" +NRF_LOG_MODULE_REGISTER(); + + +#define VERSION_FIXED_HEADER 0x00 +#define VERSION_VENDOR_ID 0x04 +#define MF0ULx1_VERSION_PRODUCT_TYPE 0x03 +#define NTAG_VERSION_PRODUCT_TYPE 0x04 +#define VERSION_PRODUCT_SUBTYPE_17pF 0x01 +#define VERSION_PRODUCT_SUBTYPE_50pF 0x02 +#define VERSION_MAJOR_PRODUCT 0x01 +#define VERSION_MINOR_PRODUCT 0x00 +#define MF0UL11_VERSION_STORAGE_SIZE 0x0B +#define MF0UL21_VERSION_STORAGE_SIZE 0x0E +#define NTAG210_VERSION_STORAGE_SIZE 0x0B +#define NTAG212_VERSION_STORAGE_SIZE 0x0E +#define NTAG213_VERSION_STORAGE_SIZE 0x0F +#define NTAG215_VERSION_STORAGE_SIZE 0x11 +#define NTAG216_VERSION_STORAGE_SIZE 0x13 +#define VERSION_PROTOCOL_TYPE 0x03 + +// MF0 and NTAG COMMANDS +#define CMD_GET_VERSION 0x60 +#define CMD_READ 0x30 +#define CMD_FAST_READ 0x3A +#define CMD_WRITE 0xA2 +#define CMD_COMPAT_WRITE 0xA0 +#define CMD_READ_CNT 0x39 +#define CMD_INCR_CNT 0xA5 +#define CMD_PWD_AUTH 0x1B +#define CMD_READ_SIG 0x3C +#define CMD_CHECK_TEARING_EVENT 0x3E +#define CMD_VCSL 0x4B + +// MEMORY LAYOUT STUFF, addresses and sizes in bytes +// UID stuff +#define UID_CL1_ADDRESS 0x00 +#define UID_CL1_SIZE 3 +#define UID_BCC1_ADDRESS 0x03 +#define UID_CL2_ADDRESS 0x04 +#define UID_CL2_SIZE 4 +#define UID_BCC2_ADDRESS 0x08 +// LockBytes stuff +#define STATIC_LOCKBYTE_0_ADDRESS 0x0A +#define STATIC_LOCKBYTE_1_ADDRESS 0x0B +// CONFIG stuff +#define MF0ICU2_USER_MEMORY_END 0x28 +#define MF0ICU2_CNT_PAGE 0x29 +#define MF0ICU2_FIRST_KEY_PAGE 0x2C +#define MF0UL11_FIRST_CFG_PAGE 0x10 +#define MF0UL11_USER_MEMORY_END (MF0UL11_FIRST_CFG_PAGE) +#define MF0UL21_FIRST_CFG_PAGE 0x25 +#define MF0UL21_USER_MEMORY_END 0x24 +#define NTAG210_FIRST_CFG_PAGE 0x10 +#define NTAG210_USER_MEMORY_END (NTAG210_FIRST_CFG_PAGE) +#define NTAG212_FIRST_CFG_PAGE 0x25 +#define NTAG212_USER_MEMORY_END 0x24 +#define NTAG213_FIRST_CFG_PAGE 0x29 +#define NTAG213_USER_MEMORY_END 0x28 +#define NTAG215_FIRST_CFG_PAGE 0x83 +#define NTAG215_USER_MEMORY_END 0x82 +#define NTAG216_FIRST_CFG_PAGE 0xE3 +#define NTAG216_USER_MEMORY_END 0xE2 +#define CONFIG_AREA_SIZE 8 + +// CONFIG offsets, relative to config start address +#define CONF_MIRROR_BYTE 0 +#define CONF_MIRROR_PAGE_BYTE 2 +#define CONF_ACCESS_PAGE_OFFSET 1 +#define CONF_ACCESS_BYTE 0 +#define CONF_AUTH0_BYTE 0x03 +#define CONF_PWD_PAGE_OFFSET 2 +#define CONF_PACK_PAGE_OFFSET 3 +#define CONF_VCTID_PAGE_OFFSET 1 +#define CONF_VCTID_PAGE_BYTE 1 + +#define MIRROR_BYTE_BYTE_MASK 0x30 +#define MIRROR_BYTE_BYTE_SHIFT 4 +#define MIRROR_BYTE_CONF_MASK 0xC0 +#define MIRROR_BYTE_CONF_SHIFT 6 + +// WRITE STUFF +#define BYTES_PER_WRITE 4 +#define PAGE_WRITE_MIN 0x02 + +// CONFIG masks to check individual needed bits +#define CONF_ACCESS_AUTHLIM_MASK 0x07 +#define CONF_ACCESS_NFC_CNT_EN 0x10 +#define CONF_ACCESS_NFC_CNT_PWD_PROT 0x04 +#define CONF_ACCESS_CFGLCK 0x40 +#define CONF_ACCESS_PROT 0x80 + +#define VERSION_INFO_LENGTH 8 //8 bytes info length + crc + +#define BYTES_PER_READ 16 + +// SIGNATURE Length +#define SIGNATURE_LENGTH 32 +#define PAGES_PER_VERSION (NFC_TAG_MF0_NTAG_VER_SIZE / NFC_TAG_MF0_NTAG_DATA_SIZE) + +// Values for MIRROR_CONF +#define MIRROR_CONF_DISABLED 0 +#define MIRROR_CONF_UID 1 +#define MIRROR_CONF_CNT 2 +#define MIRROR_CONF_UID_CNT 3 + +#define MIRROR_UID_SIZE 14 +#define MIRROR_CNT_SIZE 6 +#define MIRROR_UID_CNT_SIZE 21 + +// NTAG215_Version[7] mean: +// 0x0F ntag213 +// 0x11 ntag215 +// 0x13 ntag216 +const uint8_t ntagVersion[8] = {0x00, 0x04, 0x04, 0x02, 0x01, 0x00, 0x11, 0x03}; + +// Data structure pointer to the label information +static nfc_tag_mf0_ntag_information_t *m_tag_information = NULL; +// Define and use shadow anti -collision resources +static nfc_tag_14a_coll_res_reference_t m_shadow_coll_res; +//Define and use MF0/NTAG special communication buffer +static nfc_tag_mf0_ntag_tx_buffer_t m_tag_tx_buffer; +// Save the specific type of MF0/NTAG currently being simulated +static tag_specific_type_t m_tag_type; +static bool m_tag_authenticated = false; +static bool m_did_first_read = false; + +int nfc_tag_mf0_ntag_get_nr_pages_by_tag_type(tag_specific_type_t tag_type) { + int nr_pages = -1; + + switch (tag_type) { + case TAG_TYPE_MF0ICU1: + nr_pages = MF0ICU1_PAGES; + break; + case TAG_TYPE_MF0ICU2: + nr_pages = MF0ICU2_PAGES; + break; + case TAG_TYPE_MF0UL11: + nr_pages = MF0UL11_PAGES; + break; + case TAG_TYPE_MF0UL21: + nr_pages = MF0UL21_PAGES; + break; + case TAG_TYPE_NTAG_210: + nr_pages = NTAG210_PAGES; + break; + case TAG_TYPE_NTAG_212: + nr_pages = NTAG212_PAGES; + break; + case TAG_TYPE_NTAG_213: + nr_pages = NTAG213_PAGES; + break; + case TAG_TYPE_NTAG_215: + nr_pages = NTAG215_PAGES; + break; + case TAG_TYPE_NTAG_216: + nr_pages = NTAG216_PAGES; + break; + default: + nr_pages = -1; + break; + } + + return nr_pages; +} + +static int get_nr_pages_by_tag_type(tag_specific_type_t tag_type) { + int nr_pages = nfc_tag_mf0_ntag_get_nr_pages_by_tag_type(tag_type); + ASSERT(nr_pages > 0); + return nr_pages; +} + +static int get_total_pages_by_tag_type(tag_specific_type_t tag_type) { + int nr_pages = 0; + + switch (tag_type) { + case TAG_TYPE_MF0ICU1: + nr_pages = MF0ICU1_PAGES; + break; + case TAG_TYPE_MF0ICU2: + nr_pages = MF0ICU2_PAGES; + break; + case TAG_TYPE_MF0UL11: + nr_pages = MF0UL11_TOTAL_PAGES; + break; + case TAG_TYPE_MF0UL21: + nr_pages = MF0UL21_TOTAL_PAGES; + break; + case TAG_TYPE_NTAG_210: + nr_pages = NTAG210_TOTAL_PAGES; + break; + case TAG_TYPE_NTAG_212: + nr_pages = NTAG212_TOTAL_PAGES; + break; + case TAG_TYPE_NTAG_213: + nr_pages = NTAG213_TOTAL_PAGES; + break; + case TAG_TYPE_NTAG_215: + nr_pages = NTAG215_TOTAL_PAGES; + break; + case TAG_TYPE_NTAG_216: + nr_pages = NTAG216_TOTAL_PAGES; + break; + default: + ASSERT(false); + break; + } + + return nr_pages; +} + +static int get_first_cfg_page_by_tag_type(tag_specific_type_t tag_type) { + int page; + + switch (tag_type) { + case TAG_TYPE_MF0UL11: + page = MF0UL11_FIRST_CFG_PAGE; + break; + case TAG_TYPE_MF0UL21: + page = MF0UL21_FIRST_CFG_PAGE; + break; + case TAG_TYPE_NTAG_210: + page = NTAG210_FIRST_CFG_PAGE; + break; + case TAG_TYPE_NTAG_212: + page = NTAG212_FIRST_CFG_PAGE; + break; + case TAG_TYPE_NTAG_213: + page = NTAG213_FIRST_CFG_PAGE; + break; + case TAG_TYPE_NTAG_215: + page = NTAG215_FIRST_CFG_PAGE; + break; + case TAG_TYPE_NTAG_216: + page = NTAG216_FIRST_CFG_PAGE; + break; + default: + page = 0; + break; + } + + return page; +} + +static int get_block_max_by_tag_type(tag_specific_type_t tag_type, bool read) { + int max_pages = get_nr_pages_by_tag_type(tag_type); + int first_cfg_page = get_first_cfg_page_by_tag_type(tag_type); + + if (first_cfg_page == 0 || m_tag_authenticated || m_tag_information->config.mode_uid_magic) return max_pages; + + uint8_t auth0 = m_tag_information->memory[first_cfg_page][CONF_AUTH0_BYTE]; + uint8_t access = m_tag_information->memory[first_cfg_page + 1][0]; + + NRF_LOG_INFO("auth0 %02x access %02x max_pages %02x first_cfg_page %02x authenticated %i", auth0, access, max_pages, first_cfg_page, m_tag_authenticated); + + if (!read || ((access & CONF_ACCESS_PROT) != 0)) return (max_pages > auth0) ? auth0 : max_pages; + else return max_pages; +} + +static bool is_ntag() { + switch (m_tag_type) { + case TAG_TYPE_NTAG_210: + case TAG_TYPE_NTAG_212: + case TAG_TYPE_NTAG_213: + case TAG_TYPE_NTAG_215: + case TAG_TYPE_NTAG_216: + return true; + default: + return false; + } +} + +int get_version_page_by_tag_type(tag_specific_type_t tag_type) { + int version_page_off; + + switch (tag_type) { + case TAG_TYPE_MF0UL11: + version_page_off = MF0UL11_PAGES + MF0ULx1_NUM_CTRS; + break; + case TAG_TYPE_MF0UL21: + version_page_off = MF0UL21_PAGES + MF0ULx1_NUM_CTRS; + break; + // NTAG 210 and 212 don't have a counter, but we still allocate space for one to + // record unsuccessful auth attempts + case TAG_TYPE_NTAG_210: + version_page_off = NTAG210_PAGES + NTAG_NUM_CTRS; + break; + case TAG_TYPE_NTAG_212: + version_page_off = NTAG212_PAGES + NTAG_NUM_CTRS; + break; + case TAG_TYPE_NTAG_213: + version_page_off = NTAG213_PAGES + NTAG_NUM_CTRS; + break; + case TAG_TYPE_NTAG_215: + version_page_off = NTAG215_PAGES + NTAG_NUM_CTRS; + break; + case TAG_TYPE_NTAG_216: + version_page_off = NTAG216_PAGES + NTAG_NUM_CTRS; + break; + default: + version_page_off = -1; + break; + } + + return version_page_off; +} + +int get_signature_page_by_tag_type(tag_specific_type_t tag_type) { + int version_page_off; + + switch (m_tag_type) { + case TAG_TYPE_MF0UL11: + version_page_off = MF0UL11_PAGES + MF0ULx1_NUM_CTRS + PAGES_PER_VERSION; + break; + case TAG_TYPE_MF0UL21: + version_page_off = MF0UL21_PAGES + MF0ULx1_NUM_CTRS + PAGES_PER_VERSION; + break; + case TAG_TYPE_NTAG_210: + version_page_off = NTAG210_PAGES + NTAG_NUM_CTRS + PAGES_PER_VERSION; + break; + case TAG_TYPE_NTAG_212: + version_page_off = NTAG212_PAGES + NTAG_NUM_CTRS + PAGES_PER_VERSION; + break; + case TAG_TYPE_NTAG_213: + version_page_off = NTAG213_PAGES + NTAG_NUM_CTRS + PAGES_PER_VERSION; + break; + case TAG_TYPE_NTAG_215: + version_page_off = NTAG215_PAGES + NTAG_NUM_CTRS + PAGES_PER_VERSION; + break; + case TAG_TYPE_NTAG_216: + version_page_off = NTAG216_PAGES + NTAG_NUM_CTRS + PAGES_PER_VERSION; + break; + default: + version_page_off = -1; + break; + } + + return version_page_off; +} + +uint8_t *nfc_tag_mf0_ntag_get_version_data() { + int version_page = get_version_page_by_tag_type(m_tag_type); + + if (version_page > 0) return &m_tag_information->memory[version_page][0]; + else return NULL; +} + +uint8_t *nfc_tag_mf0_ntag_get_signature_data() { + int signature_page = get_signature_page_by_tag_type(m_tag_type); + + if (signature_page > 0) return &m_tag_information->memory[signature_page][0]; + else return NULL; +} + +static void handle_get_version_command() { + int version_page = get_version_page_by_tag_type(m_tag_type); + + if (version_page > 0) { + memcpy(m_tag_tx_buffer.tx_buffer, &m_tag_information->memory[version_page][0], NFC_TAG_MF0_NTAG_VER_SIZE); + nfc_tag_14a_tx_bytes(m_tag_tx_buffer.tx_buffer, NFC_TAG_MF0_NTAG_VER_SIZE, true); + } else { + NRF_LOG_WARNING("current card type does not support GET_VERSION"); + // MF0ICU1 and MF0ICU2 do not support GET_VERSION + if (is_ntag()) nfc_tag_14a_tx_nbit(NAK_INVALID_OPERATION_TBV, 4); + } +} + +static void handle_read_sig_command() { + int signature_page = get_signature_page_by_tag_type(m_tag_type); + + if (signature_page > 0) { + memcpy(m_tag_tx_buffer.tx_buffer, &m_tag_information->memory[signature_page][0], NFC_TAG_MF0_NTAG_SIG_SIZE); + nfc_tag_14a_tx_bytes(m_tag_tx_buffer.tx_buffer, NFC_TAG_MF0_NTAG_SIG_SIZE, true); + } else { + NRF_LOG_WARNING("current card type does not support READ_SIG"); + // MF0ICU1 and MF0ICU2 do not support READ_SIG + if (is_ntag()) nfc_tag_14a_tx_nbit(NAK_INVALID_OPERATION_TBV, 4); + } +} + +static int mirror_size_for_mode(uint8_t mirror_mode) { + switch (mirror_mode) { + case MIRROR_CONF_UID: + return MIRROR_UID_SIZE; + case MIRROR_CONF_CNT: + return MIRROR_CNT_SIZE; + case MIRROR_CONF_UID_CNT: + return MIRROR_UID_CNT_SIZE; + default: + ASSERT(false); + return 0; + } +} + +static int get_user_data_end_by_tag_type(tag_specific_type_t type) { + int nr_pages = 0; + + switch (type) { + case TAG_TYPE_MF0ICU1: + nr_pages = MF0ICU1_PAGES; + break; + case TAG_TYPE_MF0ICU2: + nr_pages = MF0ICU2_USER_MEMORY_END; + break; + case TAG_TYPE_MF0UL11: + nr_pages = MF0UL11_USER_MEMORY_END; + break; + case TAG_TYPE_MF0UL21: + nr_pages = MF0UL21_USER_MEMORY_END; + break; + case TAG_TYPE_NTAG_210: + nr_pages = NTAG210_USER_MEMORY_END; + break; + case TAG_TYPE_NTAG_212: + nr_pages = NTAG212_USER_MEMORY_END; + break; + case TAG_TYPE_NTAG_213: + nr_pages = NTAG213_USER_MEMORY_END; + break; + case TAG_TYPE_NTAG_215: + nr_pages = NTAG215_USER_MEMORY_END; + break; + case TAG_TYPE_NTAG_216: + nr_pages = NTAG216_USER_MEMORY_END; + break; + default: + ASSERT(false); + break; + } + + return nr_pages; +} + +static uint8_t *get_counter_data_by_index(uint8_t index, bool external) { + uint8_t ctr_page_off; + uint8_t ctr_page_end; + uint8_t first_index = 0; // NTAG cards have one counter that is at address 2 so we have to adjust logic + + switch (m_tag_type) { + case TAG_TYPE_MF0UL11: + ctr_page_off = MF0UL11_PAGES; + ctr_page_end = ctr_page_off + MF0ULx1_NUM_CTRS; + break; + case TAG_TYPE_MF0UL21: + ctr_page_off = MF0UL21_PAGES; + ctr_page_end = ctr_page_off + MF0ULx1_NUM_CTRS; + break; + case TAG_TYPE_NTAG_210: + if (external) return NULL; // NTAG 210 tags don't really have a counter + ctr_page_off = NTAG210_PAGES; + ctr_page_end = ctr_page_off + NTAG_NUM_CTRS; + break; + case TAG_TYPE_NTAG_212: + if (external) return NULL; // NTAG 212 tags don't really have a counter + ctr_page_off = NTAG212_PAGES; + ctr_page_end = ctr_page_off + NTAG_NUM_CTRS; + break; + case TAG_TYPE_NTAG_213: + ctr_page_off = NTAG213_PAGES; + ctr_page_end = ctr_page_off + NTAG_NUM_CTRS; + first_index = 2; + break; + case TAG_TYPE_NTAG_215: + ctr_page_off = NTAG215_PAGES; + ctr_page_end = ctr_page_off + NTAG_NUM_CTRS; + first_index = 2; + break; + case TAG_TYPE_NTAG_216: + ctr_page_off = NTAG216_PAGES; + ctr_page_end = ctr_page_off + NTAG_NUM_CTRS; + first_index = 2; + break; + default: + return NULL; + } + + if (!external) first_index = 0; + + // check that counter index is in bounds + if ((index < first_index) || ((index - first_index) >= (ctr_page_end - ctr_page_off))) return NULL; + + return m_tag_information->memory[ctr_page_off + index - first_index]; +} + +uint8_t *nfc_tag_mf0_ntag_get_counter_data_by_index(uint8_t index) { + // from the point of chameleon this is an internal access since only NFC accesses are considered external accesses + return get_counter_data_by_index(index, false); +} + +static char hex_digit(int n) { + if (n < 10) return '0' + n; + else return 'A' + n - 10; +} + +static void bytes2hex(const uint8_t *bytes, char *hex, size_t len) { + for (size_t i = 0; i < len; i++) { + *hex++ = hex_digit(bytes[i] >> 4); + *hex++ = hex_digit(bytes[i] & 0x0F); + } +} + +static void handle_any_read(uint8_t block_num, uint8_t block_cnt, uint8_t block_max) { + ASSERT(block_cnt <= block_max); + ASSERT((block_max - block_cnt) >= block_num); + + uint8_t first_cfg_page = get_first_cfg_page_by_tag_type(m_tag_type); + + // password pages are present on all tags that have config pages + uint8_t pwd_page = 0; + if (first_cfg_page != 0) pwd_page = first_cfg_page + CONF_PWD_PAGE_OFFSET; + + // extract mirroring config + int mirror_page_off = 0; + int mirror_page_end = 0; + int mirror_byte_off = 0; + int mirror_mode = 0; + int mirror_size = 0; + uint8_t mirror_buf[MIRROR_UID_CNT_SIZE]; + if (is_ntag()) { + uint8_t mirror = m_tag_information->memory[first_cfg_page][CONF_MIRROR_BYTE]; + mirror_page_off = m_tag_information->memory[first_cfg_page][CONF_MIRROR_PAGE_BYTE]; + mirror_mode = (mirror & MIRROR_BYTE_CONF_MASK) >> MIRROR_BYTE_CONF_SHIFT; + mirror_byte_off = (mirror & MIRROR_BYTE_BYTE_MASK) >> MIRROR_BYTE_BYTE_SHIFT; + + // NTAG 210/212 don't have a counter thus no mirror mode + switch (m_tag_type) { + case TAG_TYPE_NTAG_210: + case TAG_TYPE_NTAG_212: + mirror_mode = MIRROR_CONF_UID; + break; + default: + break; + } + + if ((mirror_page_off > 3) && (mirror_mode != MIRROR_CONF_DISABLED)) { + mirror_size = mirror_size_for_mode(mirror_mode); + int user_data_end = get_user_data_end_by_tag_type(m_tag_type); + int pages_needed = + (mirror_byte_off + mirror_size + (NFC_TAG_MF0_NTAG_DATA_SIZE - 1)) / NFC_TAG_MF0_NTAG_DATA_SIZE; + + if ((pages_needed >= user_data_end) || ((user_data_end - pages_needed) < mirror_page_off)) { + NRF_LOG_ERROR("invalid mirror config %02x %02x %02x", mirror_page_off, mirror_byte_off, mirror_mode); + mirror_page_off = 0; + } else { + mirror_page_end = mirror_page_off + pages_needed; + + switch (mirror_mode) { + case MIRROR_CONF_UID: + bytes2hex(m_tag_information->res_coll.uid, (char *)mirror_buf, 7); + break; + case MIRROR_CONF_CNT: + bytes2hex(get_counter_data_by_index(0, false), (char *)mirror_buf, 3); + break; + case MIRROR_CONF_UID_CNT: + bytes2hex(m_tag_information->res_coll.uid, (char *)mirror_buf, 7); + mirror_buf[7] = 'x'; + bytes2hex(get_counter_data_by_index(0, false), (char *)&mirror_buf[8], 3); + break; + } + } + } + } + + for (uint8_t block = 0; block < block_cnt; block++) { + uint8_t block_to_read = (block_num + block) % block_max; + uint8_t *tx_buf_ptr = m_tag_tx_buffer.tx_buffer + block * NFC_TAG_MF0_NTAG_DATA_SIZE; + + // In case PWD or PACK pages are read we need to write zero to the output buffer. In UID magic mode we don't care. + if (m_tag_information->config.mode_uid_magic || (pwd_page == 0) || (block_to_read < pwd_page) || (block_to_read > (pwd_page + 1))) { + memcpy(tx_buf_ptr, m_tag_information->memory[block_to_read], NFC_TAG_MF0_NTAG_DATA_SIZE); + } else { + memset(tx_buf_ptr, 0, NFC_TAG_MF0_NTAG_DATA_SIZE); + } + + // apply mirroring if needed + if ((mirror_page_off > 0) && (mirror_size > 0) && (block_to_read >= mirror_page_off) && (block_to_read < mirror_page_end)) { + // When accessing the first page that includes mirrored data the offset into the mirror buffer is + // definitely zero. Later pages need to account for the offset in the first page. Offset in the + // destination page chunk will be zero however. + int mirror_buf_off = (block_to_read - mirror_page_off) * NFC_TAG_MF0_NTAG_DATA_SIZE; + int offset_in_cur_block = mirror_byte_off; + if (mirror_buf_off != 0) { + mirror_buf_off -= mirror_byte_off; + offset_in_cur_block = 0; + } + + int mirror_copy_size = mirror_size - mirror_buf_off; + if (mirror_copy_size > NFC_TAG_MF0_NTAG_DATA_SIZE) mirror_copy_size = NFC_TAG_MF0_NTAG_DATA_SIZE; + + // Ensure we don't corrupt memory here. + ASSERT(offset_in_cur_block < NFC_TAG_MF0_NTAG_DATA_SIZE); + ASSERT(mirror_buf_off <= sizeof(mirror_buf)); + ASSERT(mirror_copy_size <= (sizeof(mirror_buf) - mirror_buf_off)); + + memcpy(&tx_buf_ptr[offset_in_cur_block], &mirror_buf[mirror_buf_off], mirror_copy_size); + } + } + + NRF_LOG_DEBUG("READ handled %02x %02x %02x", block_num, block_cnt, block_max); + + // update counter for NTAG cards if needed + switch (m_tag_type) { + case TAG_TYPE_NTAG_213: + case TAG_TYPE_NTAG_215: + case TAG_TYPE_NTAG_216: if (!m_did_first_read) { + m_did_first_read = true; + + int first_cfg_page = get_first_cfg_page_by_tag_type(m_tag_type); + int access = m_tag_information->memory[first_cfg_page + CONF_ACCESS_PAGE_OFFSET][CONF_ACCESS_BYTE]; + + if ((access & CONF_ACCESS_NFC_CNT_EN) != 0) { + uint8_t *ctr = get_counter_data_by_index(0, false); + uint32_t counter = (((uint32_t)ctr[0]) << 16) | (((uint32_t)ctr[1]) << 8) | ((uint32_t)ctr[2]); + if (counter < 0xFFFFFF) counter += 1; + ctr[0] = (uint8_t)(counter >> 16); + ctr[1] = (uint8_t)(counter >> 8); + ctr[2] = (uint8_t)(counter); + } + break; + } + default: + break; + } + + nfc_tag_14a_tx_bytes(m_tag_tx_buffer.tx_buffer, ((int)block_cnt) * NFC_TAG_MF0_NTAG_DATA_SIZE, true); +} + +static void handle_read_command(uint8_t block_num) { + int block_max = get_block_max_by_tag_type(m_tag_type, true); + + NRF_LOG_DEBUG("handling READ %02x %02x", block_num, block_max); + + if (block_num >= block_max) { + NRF_LOG_WARNING("too large block num %02x >= %02x", block_num, block_max); + + nfc_tag_14a_tx_nbit(NAK_INVALID_OPERATION_TBV, 4); + return; + } + + handle_any_read(block_num, 4, block_max); +} + +static void handle_fast_read_command(uint8_t block_num, uint8_t end_block_num) { + switch (m_tag_type) + { + case TAG_TYPE_MF0UL11: + case TAG_TYPE_MF0UL21: + case TAG_TYPE_NTAG_210: + case TAG_TYPE_NTAG_212: + case TAG_TYPE_NTAG_213: + case TAG_TYPE_NTAG_215: + case TAG_TYPE_NTAG_216: + // command is supported + break; + default: + if (is_ntag()) nfc_tag_14a_tx_nbit(NAK_INVALID_OPERATION_TBV, 4); + return; + } + + int block_max = get_block_max_by_tag_type(m_tag_type, true); + + if (block_num >= end_block_num || end_block_num >= block_max) { + nfc_tag_14a_tx_nbit(NAK_INVALID_OPERATION_TBV, 4); + return; + } + + NRF_LOG_INFO("HANDLING FAST READ %02x %02x", block_num, end_block_num); + + handle_any_read(block_num, end_block_num - block_num, block_max); +} + +static bool check_ro_lock_on_page(int block_num) { + if (block_num < 3) return true; + else if (block_num == 3) return (m_tag_information->memory[2][2] & 9) != 0; // bits 0 and 3 + else if (block_num <= MF0ICU1_PAGES) { + bool locked = false; + + // check block locking bits + if (block_num <= 9) locked |= (m_tag_information->memory[2][2] & 2) == 2; + else locked |= (m_tag_information->memory[2][2] & 4) == 4; + + locked |= (((*(uint16_t *)&m_tag_information->memory[2][2]) >> block_num) & 1) == 1; + + return locked; + } else { + uint8_t *p_lock_bytes = NULL; + int user_memory_end = 0; + int dyn_lock_bit_page_cnt = 0; + int index = block_num - MF0ICU1_PAGES; + + switch (m_tag_type) { + case TAG_TYPE_MF0ICU1: + return true; + case TAG_TYPE_MF0ICU2: { + p_lock_bytes = m_tag_information->memory[MF0ICU2_USER_MEMORY_END]; + + if (block_num < MF0ICU2_USER_MEMORY_END) { + uint8_t byte2 = p_lock_bytes[0]; + + // Account for block locking bits first. + bool locked = (byte2 & (0x10 * (block_num >= 28))) != 0; + locked |= (byte2 >> (1 + (index / 4) + (block_num >= 28))); + return locked; + } else if (block_num == MF0ICU2_USER_MEMORY_END) { + return false; + } else if (block_num < MF0ICU2_FIRST_KEY_PAGE) { + uint8_t byte3 = p_lock_bytes[1]; + return ((byte3 >> (block_num - MF0ICU2_CNT_PAGE)) & 1) != 0; + } else { + uint8_t byte3 = p_lock_bytes[1]; + return (byte3 & 0x80) != 0; + } + } + // for the next two we reuse the check for CFGLCK bit used for NTAG + case TAG_TYPE_MF0UL11: + ASSERT(block_num >= MF0UL11_USER_MEMORY_END); + user_memory_end = MF0UL11_USER_MEMORY_END; + break; + case TAG_TYPE_MF0UL21: { + user_memory_end = MF0UL11_USER_MEMORY_END; + if (block_num < user_memory_end) { + p_lock_bytes = m_tag_information->memory[MF0UL21_USER_MEMORY_END]; + uint16_t lock_word = (((uint16_t)p_lock_bytes[1]) << 8) | (uint16_t)p_lock_bytes[0]; + bool locked = ((lock_word >> (index / 2)) & 1) != 0; + locked |= ((p_lock_bytes[2] >> (index / 4)) & 1) != 0; + return locked; + } + break; + } + case TAG_TYPE_NTAG_210: + user_memory_end = NTAG210_USER_MEMORY_END; + dyn_lock_bit_page_cnt = 0; // NTAG 210 doesn't have dynamic lock bits + break; + case TAG_TYPE_NTAG_212: + user_memory_end = NTAG212_USER_MEMORY_END; + dyn_lock_bit_page_cnt = 2; + break; + case TAG_TYPE_NTAG_213: + user_memory_end = NTAG213_USER_MEMORY_END; + dyn_lock_bit_page_cnt = 2; + break; + case TAG_TYPE_NTAG_215: + user_memory_end = NTAG215_USER_MEMORY_END; + dyn_lock_bit_page_cnt = 16; + break; + case TAG_TYPE_NTAG_216: + user_memory_end = NTAG216_USER_MEMORY_END; + dyn_lock_bit_page_cnt = 16; + break; + default: + ASSERT(false); + break; + } + + if (block_num < user_memory_end) { + ASSERT(dyn_lock_bit_page_cnt > 0); + + p_lock_bytes = m_tag_information->memory[user_memory_end]; + uint16_t lock_word = (((uint16_t)p_lock_bytes[1]) << 8) | (uint16_t)p_lock_bytes[0]; + + bool locked_small_range = ((lock_word >> (index / dyn_lock_bit_page_cnt)) & 1) != 0; + bool locked_large_range = ((p_lock_bytes[2] >> (index / dyn_lock_bit_page_cnt / 2)) & 1) != 0; + + return locked_small_range | locked_large_range; + } else { + // check CFGLCK bit + int first_cfg_page = get_first_cfg_page_by_tag_type(m_tag_type); + uint8_t access = m_tag_information->memory[first_cfg_page + CONF_ACCESS_PAGE_OFFSET][CONF_ACCESS_BYTE]; + if ((access & CONF_ACCESS_CFGLCK) != 0) + return (block_num >= first_cfg_page) && ((block_num - first_cfg_page) <= 1); + else + return false; + } + } +} + +static int handle_write_command(uint8_t block_num, uint8_t *p_data) { + int block_max = get_block_max_by_tag_type(m_tag_type, false); + + if (block_num >= block_max) { + NRF_LOG_ERROR("Write failed: block_num %08x >= block_max %08x", block_num, block_max); + + return NAK_INVALID_OPERATION_TBV; + } + + if (m_tag_information->config.mode_uid_magic) { + // anything can be written in this mode + memcpy(m_tag_information->memory[block_num], p_data, NFC_TAG_MF0_NTAG_DATA_SIZE); + return ACK_VALUE; + } + + switch (block_num) { + case 0: + case 1: + return NAK_INVALID_OPERATION_TBV; + case 2: + // Page 2 contains lock bytes for pages 3-15. These are OR'ed when not in the UID + // magic mode. First two bytes are ignored. + m_tag_information->memory[2][2] |= p_data[2]; + m_tag_information->memory[2][3] |= p_data[3]; + break; + case 3: + // Page 3 contains what's called OTP bits for Ultralight tags and CC bits for NTAG + // cards, these work in the same way. + if (!check_ro_lock_on_page(block_num)) { + // lock bit for OTP page is not set + for (int i = 0; i < NFC_TAG_MF0_NTAG_DATA_SIZE; i++) { + m_tag_information->memory[3][i] |= p_data[i]; + } + } else return NAK_INVALID_OPERATION_TBV; + break; + default: + if (!check_ro_lock_on_page(block_num)) { + memcpy(m_tag_information->memory[block_num], p_data, NFC_TAG_MF0_NTAG_DATA_SIZE); + } else return NAK_INVALID_OPERATION_TBV; + break; + } + + return ACK_VALUE; +} + +static void handle_read_cnt_command(uint8_t index) { + // first check if the counter even exists for external commands + uint8_t *cnt_data = get_counter_data_by_index(index, true); + if (cnt_data == NULL) { + nfc_tag_14a_tx_nbit(NAK_INVALID_OPERATION_TBV, 4); + return; + } + + // deny counter reading when counter password protection is enabled and reader is not authenticated + if (is_ntag() && !m_tag_information->config.mode_uid_magic) { + int first_cfg_page = get_first_cfg_page_by_tag_type(m_tag_type); + int access = m_tag_information->memory[first_cfg_page + CONF_ACCESS_PAGE_OFFSET][CONF_ACCESS_BYTE]; + + if ((access & CONF_ACCESS_NFC_CNT_PWD_PROT) != 0 && !m_tag_authenticated) { + nfc_tag_14a_tx_nbit(NAK_INVALID_OPERATION_TBV, 4); + return; + } + } + + memcpy(m_tag_tx_buffer.tx_buffer, cnt_data, 3); + nfc_tag_14a_tx_bytes(m_tag_tx_buffer.tx_buffer, 3, true); +} + +static void handle_incr_cnt_command(uint8_t block_num, uint8_t *p_data) { + uint8_t ctr_page_off; + uint8_t ctr_page_end; + switch (m_tag_type) { + case TAG_TYPE_MF0UL11: + ctr_page_off = MF0UL11_PAGES; + ctr_page_end = ctr_page_off + MF0ULx1_NUM_CTRS; + break; + case TAG_TYPE_MF0UL21: + ctr_page_off = MF0UL21_PAGES; + ctr_page_end = ctr_page_off + MF0ULx1_NUM_CTRS; + break; + default: + if (is_ntag()) nfc_tag_14a_tx_nbit(NAK_INVALID_OPERATION_TBV, 4); + return; + } + + // check that counter index is in bounds + if (block_num >= (ctr_page_end - ctr_page_off)) { + nfc_tag_14a_tx_nbit(NAK_INVALID_OPERATION_TBV, 4); + return; + } + + uint8_t *cnt_data = m_tag_information->memory[ctr_page_off + block_num]; + uint32_t incr_value = ((uint32_t)p_data[0] << 16) | ((uint32_t)p_data[1] << 8) | ((uint32_t)p_data[2]); + uint32_t cnt = ((uint32_t)cnt_data[0] << 16) | ((uint32_t)cnt_data[1] << 8) | ((uint32_t)cnt_data[2]); + + if ((0xFFFFFF - cnt) < incr_value) { + // set tearing event flag + cnt_data[MF0_NTAG_AUTHLIM_OFF_IN_CTR] |= MF0_NTAG_TEARING_MASK_IN_AUTHLIM; + + nfc_tag_14a_tx_nbit(NAK_INVALID_OPERATION_TBIV, 4); + } else { + cnt += incr_value; + + cnt_data[0] = (uint8_t)(cnt >> 16); + cnt_data[1] = (uint8_t)(cnt >> 8); + cnt_data[2] = (uint8_t)(cnt & 0xff); + + nfc_tag_14a_tx_nbit(ACK_VALUE, 4); + } +} + +static void handle_pwd_auth_command(uint8_t *p_data) { + int first_cfg_page = get_first_cfg_page_by_tag_type(m_tag_type); + uint8_t *cnt_data = get_counter_data_by_index(0, false); + if (first_cfg_page == 0 || cnt_data == NULL) { + if (is_ntag()) nfc_tag_14a_tx_nbit(NAK_INVALID_OPERATION_TBV, 4); + return; + } + + // check AUTHLIM counter + uint8_t auth_cnt = cnt_data[MF0_NTAG_AUTHLIM_OFF_IN_CTR] & MF0_NTAG_AUTHLIM_MASK_IN_CTR; + uint8_t auth_lim = m_tag_information->memory[first_cfg_page + 1][0] & CONF_ACCESS_AUTHLIM_MASK; + if ((auth_lim > 0) && (auth_lim <= auth_cnt)) { + nfc_tag_14a_tx_nbit(NAK_INVALID_OPERATION_TBIV, 4); + return; + } + + uint32_t pwd = *(uint32_t *)m_tag_information->memory[first_cfg_page + CONF_PWD_PAGE_OFFSET]; + uint32_t supplied_pwd = *(uint32_t *)&p_data[1]; + if (pwd != supplied_pwd) { + if (auth_lim) { + cnt_data[MF0_NTAG_AUTHLIM_OFF_IN_CTR] &= ~MF0_NTAG_AUTHLIM_MASK_IN_CTR; + cnt_data[MF0_NTAG_AUTHLIM_OFF_IN_CTR] |= (auth_cnt + 1) & MF0_NTAG_AUTHLIM_MASK_IN_CTR; + } + nfc_tag_14a_tx_nbit(NAK_INVALID_OPERATION_TBIV, 4); + return; + } + + // reset authentication attempts counter and authenticate user + cnt_data[MF0_NTAG_AUTHLIM_OFF_IN_CTR] &= ~MF0_NTAG_AUTHLIM_MASK_IN_CTR; + m_tag_authenticated = true; // TODO: this should be possible to reset somehow + + // Send the PACK value back + nfc_tag_14a_tx_bytes(m_tag_information->memory[first_cfg_page + CONF_PACK_PAGE_OFFSET], 2, true); +} + +static void handle_check_tearing_event(int index) { + switch (m_tag_type) { + case TAG_TYPE_MF0UL11: + case TAG_TYPE_MF0UL21: { + uint8_t *ctr_data = get_counter_data_by_index(index, true); + + if (ctr_data) { + m_tag_tx_buffer.tx_buffer[0] = (ctr_data[MF0_NTAG_AUTHLIM_OFF_IN_CTR] & MF0_NTAG_TEARING_MASK_IN_AUTHLIM) == 0 ? 0xBD : 0x00; + nfc_tag_14a_tx_bytes(m_tag_tx_buffer.tx_buffer, 1, true); + } else { + nfc_tag_14a_tx_nbit(NAK_INVALID_OPERATION_TBV, 4); + } + + break; + } + default: + if (is_ntag()) nfc_tag_14a_tx_nbit(NAK_INVALID_OPERATION_TBV, 4); + break; + } +} + +static void handle_vcsl_command(uint16_t szDataBits) { + switch (m_tag_type) { + case TAG_TYPE_MF0UL11: + case TAG_TYPE_MF0UL21: + if (szDataBits < 168) { + nfc_tag_14a_tx_nbit(NAK_INVALID_OPERATION_TBV, 4); + break; + } + break; + default: + if (is_ntag()) nfc_tag_14a_tx_nbit(NAK_INVALID_OPERATION_TBV, 4); + break; + } + + int first_cfg_page = get_first_cfg_page_by_tag_type(m_tag_type); + m_tag_tx_buffer.tx_buffer[0] = m_tag_information->memory[first_cfg_page + CONF_VCTID_PAGE_OFFSET][CONF_VCTID_PAGE_BYTE]; + + nfc_tag_14a_tx_bytes(m_tag_tx_buffer.tx_buffer, 1, true); +} + +static void nfc_tag_mf0_ntag_state_handler(uint8_t *p_data, uint16_t szDataBits) { + uint8_t command = p_data[0]; + uint8_t block_num = p_data[1]; + + if (szDataBits < 16) return; + + NRF_LOG_INFO("received mfu command %x of size %u bits", command, szDataBits); + + switch (command) { + case CMD_GET_VERSION: + handle_get_version_command(); + break; + case CMD_READ: { + handle_read_command(block_num); + break; + } + case CMD_FAST_READ: { + uint8_t end_block_num = p_data[2]; + // TODO: support ultralight + handle_fast_read_command(block_num, end_block_num); + break; + } + case CMD_WRITE: + case CMD_COMPAT_WRITE: { + int resp = handle_write_command(block_num, &p_data[2]); + nfc_tag_14a_tx_nbit(resp, 4); + break; + } + case CMD_PWD_AUTH: { + handle_pwd_auth_command(p_data); + break; + } + case CMD_READ_SIG: + handle_read_sig_command(); + break; + case CMD_READ_CNT: + handle_read_cnt_command(block_num); + break; + case CMD_INCR_CNT: + handle_incr_cnt_command(block_num, &p_data[2]); + break; + case CMD_CHECK_TEARING_EVENT: + handle_check_tearing_event(block_num); + break; + case CMD_VCSL: { + handle_vcsl_command(szDataBits); + break; + } + default: + if (is_ntag()) nfc_tag_14a_tx_nbit(NAK_INVALID_OPERATION_TBV, 4); + break; + } + return; +} + +nfc_tag_14a_coll_res_reference_t *nfc_tag_mf0_ntag_get_coll_res() { + // Use a separate anti -conflict information instead of using the information in the sector + m_shadow_coll_res.sak = m_tag_information->res_coll.sak; + m_shadow_coll_res.atqa = m_tag_information->res_coll.atqa; + m_shadow_coll_res.uid = m_tag_information->res_coll.uid; + m_shadow_coll_res.size = &(m_tag_information->res_coll.size); + m_shadow_coll_res.ats = &(m_tag_information->res_coll.ats); + // Finally, a shadow data structure pointer with only reference, no physical shadow, + return &m_shadow_coll_res; +} + +static void nfc_tag_mf0_ntag_reset_handler() { + m_tag_authenticated = false; + m_did_first_read = false; +} + +static int get_information_size_by_tag_type(tag_specific_type_t type) { + return sizeof(nfc_tag_14a_coll_res_entity_t) + sizeof(nfc_tag_mf0_ntag_configure_t) + (get_total_pages_by_tag_type(type) * NFC_TAG_MF0_NTAG_DATA_SIZE); +} + +/** @brief MF0/NTAG callback before saving data + * @param type detailed label type + * @param buffer data buffer + * @return to be saved, the length of the data that needs to be saved, it means not saved when 0 + */ +int nfc_tag_mf0_ntag_data_savecb(tag_specific_type_t type, tag_data_buffer_t *buffer) { + if (m_tag_type != TAG_TYPE_UNDEFINED && m_tag_information != NULL) { + // Save the corresponding size data according to the current label type + return get_information_size_by_tag_type(type); + } else { + ASSERT(false); + return 0; + } +} + +int nfc_tag_mf0_ntag_data_loadcb(tag_specific_type_t type, tag_data_buffer_t *buffer) { + int info_size = get_information_size_by_tag_type(type); + if (buffer->length >= info_size) { + // Convert the data buffer to MF0/NTAG structure type + m_tag_information = (nfc_tag_mf0_ntag_information_t *)buffer->buffer; + // The specific type of MF0/NTAG tag that is simulated by the cache + m_tag_type = type; + // Register 14A communication management interface + nfc_tag_14a_handler_t handler_for_14a = { + .get_coll_res = nfc_tag_mf0_ntag_get_coll_res, + .cb_state = nfc_tag_mf0_ntag_state_handler, + .cb_reset = nfc_tag_mf0_ntag_reset_handler, + }; + nfc_tag_14a_set_handler(&handler_for_14a); + NRF_LOG_INFO("HF ntag data load finish."); + } else { + ASSERT(buffer->length == info_size); + NRF_LOG_ERROR("nfc_tag_mf0_ntag_information_t too big."); + } + return info_size; +} + +typedef struct __attribute__((aligned(4))) { + nfc_tag_14a_coll_res_entity_t res_coll; + nfc_tag_mf0_ntag_configure_t config; + uint8_t memory[NFC_TAG_NTAG_BLOCK_MAX][NFC_TAG_MF0_NTAG_DATA_SIZE]; +} +nfc_tag_mf0_ntag_information_max_t; + +// Initialized NTAG factory data +bool nfc_tag_mf0_ntag_data_factory(uint8_t slot, tag_specific_type_t tag_type) { + // default ntag data + uint8_t default_p0[] = { 0x04, 0x68, 0x95, 0x71 }; + uint8_t default_p1[] = { 0xFA, 0x5C, 0x64, 0x80 }; + uint8_t default_p2[] = { 0x42, 0x48, 0x0F, 0xE0 }; + + if (!is_ntag()) { + default_p2[2] = 0; + default_p2[3] = 0; + } + + // default ntag info + nfc_tag_mf0_ntag_information_max_t ntag_tmp_information; + nfc_tag_mf0_ntag_information_t *p_ntag_information; + p_ntag_information = (nfc_tag_mf0_ntag_information_t *)&ntag_tmp_information; + + memset(p_ntag_information, 0, sizeof(nfc_tag_mf0_ntag_information_max_t)); + + int block_max = get_nr_pages_by_tag_type(tag_type); + for (int block = 0; block < block_max; block++) { + switch (block) { + case 0: + memcpy(p_ntag_information->memory[block], default_p0, NFC_TAG_MF0_NTAG_DATA_SIZE); + break; + case 1: + memcpy(p_ntag_information->memory[block], default_p1, NFC_TAG_MF0_NTAG_DATA_SIZE); + break; + case 2: + memcpy(p_ntag_information->memory[block], default_p2, NFC_TAG_MF0_NTAG_DATA_SIZE); + break; + default: + memset(p_ntag_information->memory[block], 0, NFC_TAG_MF0_NTAG_DATA_SIZE); + break; + } + } + + int first_cfg_page = get_first_cfg_page_by_tag_type(tag_type); + if (first_cfg_page != 0) { + p_ntag_information->memory[first_cfg_page][CONF_AUTH0_BYTE] = 0xFF; // set AUTH to 0xFF + *(uint32_t *)p_ntag_information->memory[first_cfg_page + CONF_PWD_PAGE_OFFSET] = 0xFFFFFFFF; // set PWD to FFFFFFFF + + switch (tag_type) { + case TAG_TYPE_MF0UL11: + case TAG_TYPE_MF0UL21: + p_ntag_information->memory[first_cfg_page + 1][1] = 0x05; // set VCTID to 0x05 + break; + case TAG_TYPE_NTAG_213: + case TAG_TYPE_NTAG_215: + case TAG_TYPE_NTAG_216: + p_ntag_information->memory[first_cfg_page][0] = 0x04; // set MIRROR to 0x04 (STRG_MOD_EN to 1) + break; + default: + ASSERT(false); + break; + } + } + + int version_page = get_version_page_by_tag_type(tag_type); + if (version_page > 0) { + uint8_t *version_data = &p_ntag_information->memory[version_page][0]; + + switch (m_tag_type) { + case TAG_TYPE_MF0UL11: + version_data[6] = MF0UL11_VERSION_STORAGE_SIZE; + version_data[2] = MF0ULx1_VERSION_PRODUCT_TYPE; + break; + case TAG_TYPE_MF0UL21: + version_data[6] = MF0UL21_VERSION_STORAGE_SIZE; + version_data[2] = MF0ULx1_VERSION_PRODUCT_TYPE; + break; + case TAG_TYPE_NTAG_210: + version_data[6] = NTAG210_VERSION_STORAGE_SIZE; + version_data[2] = NTAG_VERSION_PRODUCT_TYPE; + version_data[3] = VERSION_PRODUCT_SUBTYPE_17pF; + break; + case TAG_TYPE_NTAG_212: + version_data[6] = NTAG212_VERSION_STORAGE_SIZE; + version_data[2] = NTAG_VERSION_PRODUCT_TYPE; + version_data[3] = VERSION_PRODUCT_SUBTYPE_17pF; + break; + case TAG_TYPE_NTAG_213: + version_data[6] = NTAG213_VERSION_STORAGE_SIZE; + version_data[2] = NTAG_VERSION_PRODUCT_TYPE; + break; + case TAG_TYPE_NTAG_215: + version_data[6] = NTAG215_VERSION_STORAGE_SIZE; + version_data[2] = NTAG_VERSION_PRODUCT_TYPE; + break; + case TAG_TYPE_NTAG_216: + version_data[6] = NTAG216_VERSION_STORAGE_SIZE; + version_data[2] = NTAG_VERSION_PRODUCT_TYPE; + break; + default: + ASSERT(false); + break; + } + + version_data[0] = VERSION_FIXED_HEADER; + version_data[1] = VERSION_VENDOR_ID; + if (version_data[3] == 0) version_data[3] = VERSION_PRODUCT_SUBTYPE_50pF; // TODO: make configurable for MF0ULx1 + version_data[4] = VERSION_MAJOR_PRODUCT; + version_data[5] = VERSION_MINOR_PRODUCT; + version_data[7] = VERSION_PROTOCOL_TYPE; + } + + int signature_page = get_signature_page_by_tag_type(tag_type); + if (signature_page > 0) { + memset(&p_ntag_information->memory[signature_page][0], 0, NFC_TAG_MF0_NTAG_SIG_SIZE); + } + + // default ntag auto ant-collision res + p_ntag_information->res_coll.atqa[0] = 0x44; + p_ntag_information->res_coll.atqa[1] = 0x00; + p_ntag_information->res_coll.sak[0] = 0x00; + p_ntag_information->res_coll.uid[0] = 0x04; + p_ntag_information->res_coll.uid[1] = 0x68; + p_ntag_information->res_coll.uid[2] = 0x95; + p_ntag_information->res_coll.uid[3] = 0x71; + p_ntag_information->res_coll.uid[4] = 0xFA; + p_ntag_information->res_coll.uid[5] = 0x5C; + p_ntag_information->res_coll.uid[6] = 0x64; + p_ntag_information->res_coll.size = NFC_TAG_14A_UID_DOUBLE_SIZE; + p_ntag_information->res_coll.ats.length = 0; + + // default ntag config + p_ntag_information->config.mode_uid_magic = false; + + // save data to flash + tag_sense_type_t sense_type = get_sense_type_from_tag_type(tag_type); + fds_slot_record_map_t map_info; + get_fds_map_by_slot_sense_type_for_dump(slot, sense_type, &map_info); + int info_size = get_information_size_by_tag_type(tag_type); + NRF_LOG_INFO("MF0/NTAG info size: %d", info_size); + bool ret = fds_write_sync(map_info.id, map_info.key, info_size, p_ntag_information); + if (ret) { + NRF_LOG_INFO("Factory slot data success."); + } else { + NRF_LOG_ERROR("Factory slot data error."); + } + return ret; +} + +int nfc_tag_mf0_ntag_get_uid_mode() { + if (m_tag_type == TAG_TYPE_UNDEFINED || m_tag_information == NULL) return -1; + + return (int)m_tag_information->config.mode_uid_magic; +} + +bool nfc_tag_mf0_ntag_set_uid_mode(bool enabled) { + if (m_tag_type == TAG_TYPE_UNDEFINED || m_tag_information == NULL) return false; + + m_tag_information->config.mode_uid_magic = enabled; + return true; +} \ No newline at end of file diff --git a/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.h b/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.h new file mode 100644 index 00000000..b65a01e1 --- /dev/null +++ b/firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.h @@ -0,0 +1,83 @@ +#ifndef NFC_NTAG_H +#define NFC_NTAG_H + +#include "nfc_14a.h" + +#define NFC_TAG_MF0_NTAG_DATA_SIZE 4 +#define NFC_TAG_MF0_NTAG_SIG_SIZE 32 +#define NFC_TAG_MF0_NTAG_VER_SIZE 8 +#define NFC_TAG_MF0_NTAG_SIG_PAGES (NFC_TAG_MF0_NTAG_SIG_SIZE / NFC_TAG_MF0_NTAG_DATA_SIZE) +#define NFC_TAG_MF0_NTAG_VER_PAGES (NFC_TAG_MF0_NTAG_VER_SIZE / NFC_TAG_MF0_NTAG_DATA_SIZE) + +#define NFC_TAG_MF0_FRAME_SIZE (16 + NFC_TAG_14A_CRC_LENGTH) +#define NFC_TAG_MF0_BLOCK_MAX 41 + +#define MF0ULx1_NUM_CTRS 3 // number of Ultralight EV1 one-way counters +#define NTAG_NUM_CTRS 1 // number of NTAG one-way counters + +#define MF0ULx1_EXTRA_PAGES (MF0ULx1_NUM_CTRS + NFC_TAG_MF0_NTAG_VER_PAGES + NFC_TAG_MF0_NTAG_SIG_PAGES) +#define NTAG_EXTRA_PAGES (NTAG_NUM_CTRS + NFC_TAG_MF0_NTAG_VER_PAGES + NFC_TAG_MF0_NTAG_SIG_PAGES) + +#define NTAG210_PAGES 20 //20 pages total for ntag210, from 0 to 44 +#define NTAG210_TOTAL_PAGES (NTAG210_PAGES + NTAG_EXTRA_PAGES) // 1 more page for the counter +#define NTAG212_PAGES 41 //41 pages total for ntag212, from 0 to 44 +#define NTAG212_TOTAL_PAGES (NTAG212_PAGES + NTAG_EXTRA_PAGES) // 1 more page for the counter +#define NTAG213_PAGES 45 //45 pages total for ntag213, from 0 to 44 +#define NTAG213_TOTAL_PAGES (NTAG213_PAGES + NTAG_EXTRA_PAGES) // 1 more page for the counter +#define NTAG215_PAGES 135 //135 pages total for ntag215, from 0 to 134 +#define NTAG215_TOTAL_PAGES (NTAG215_PAGES + NTAG_EXTRA_PAGES) // 1 more page for the counter +#define NTAG216_PAGES 231 //231 pages total for ntag216, from 0 to 230 +#define NTAG216_TOTAL_PAGES (NTAG216_PAGES + NTAG_EXTRA_PAGES) // 1 more page for the counter + +#define MF0ICU1_PAGES 16 //16 pages total for MF0ICU1 (the original UL), from 0 to 15 +#define MF0ICU2_PAGES 36 //16 pages total for MF0ICU2 (UL C), from 0 to 35 +#define MF0UL11_PAGES 20 //20 pages total for MF0UL11 (UL EV1), from 0 to 19 +#define MF0UL11_TOTAL_PAGES (MF0UL11_PAGES + MF0ULx1_EXTRA_PAGES) // 3 more pages for 3 one way counters +#define MF0UL21_PAGES 41 //231 pages total for MF0UL21 (UL EV1), from 0 to 40 +#define MF0UL21_TOTAL_PAGES (MF0UL21_PAGES + MF0ULx1_EXTRA_PAGES) // 3 more pages for 3 one way counters + +#define NFC_TAG_NTAG_FRAME_SIZE 64 +#define NFC_TAG_NTAG_BLOCK_MAX NTAG216_TOTAL_PAGES + +// Since all counters are 24-bit and each currently supported tag that supports counters +// has password authentication we store the auth attempts counter in the last bit of the +// first counter. AUTHLIM is only 3 bits though so we reserve 4 bits just to be sure and +// use the top bit for tearing event flag. +#define MF0_NTAG_AUTHLIM_OFF_IN_CTR 3 +#define MF0_NTAG_AUTHLIM_MASK_IN_CTR 0xF +#define MF0_NTAG_TEARING_MASK_IN_AUTHLIM 0x80 + +typedef struct { + uint8_t mode_uid_magic: 1; + // reserve + uint8_t reserved1: 7; + uint8_t reserved2; + uint8_t reserved3; +} nfc_tag_mf0_ntag_configure_t; + +typedef struct __attribute__((aligned(4))) { + nfc_tag_14a_coll_res_entity_t res_coll; + nfc_tag_mf0_ntag_configure_t config; + uint8_t memory[][NFC_TAG_MF0_NTAG_DATA_SIZE]; +} +nfc_tag_mf0_ntag_information_t; + +typedef struct { + // TX buffer must fit the largest possible frame size. + // TODO: This size should be decreased as the maximum allowed frame size is 257 (see 6.14.13.36 in datasheet). + uint8_t tx_buffer[NFC_TAG_NTAG_BLOCK_MAX * NFC_TAG_MF0_NTAG_DATA_SIZE]; +} nfc_tag_mf0_ntag_tx_buffer_t; + +int nfc_tag_mf0_ntag_data_loadcb(tag_specific_type_t type, tag_data_buffer_t *buffer); +int nfc_tag_mf0_ntag_data_savecb(tag_specific_type_t type, tag_data_buffer_t *buffer); +bool nfc_tag_mf0_ntag_data_factory(uint8_t slot, tag_specific_type_t tag_type); +int nfc_tag_mf0_ntag_get_nr_pages_by_tag_type(tag_specific_type_t tag_type); +uint8_t *nfc_tag_mf0_ntag_get_counter_data_by_index(uint8_t index); +uint8_t *nfc_tag_mf0_ntag_get_version_data(void); +uint8_t *nfc_tag_mf0_ntag_get_signature_data(void); +nfc_tag_14a_coll_res_reference_t *nfc_tag_mf0_ntag_get_coll_res(void); + +int nfc_tag_mf0_ntag_get_uid_mode(void); +bool nfc_tag_mf0_ntag_set_uid_mode(bool enabled); + +#endif diff --git a/firmware/application/src/rfid/nfctag/hf/nfc_ntag.c b/firmware/application/src/rfid/nfctag/hf/nfc_ntag.c deleted file mode 100644 index a90f3c7b..00000000 --- a/firmware/application/src/rfid/nfctag/hf/nfc_ntag.c +++ /dev/null @@ -1,301 +0,0 @@ -#include - -#include "nfc_ntag.h" -#include "nfc_14a.h" -#include "fds_util.h" -#include "tag_persistence.h" - -#define NRF_LOG_MODULE_NAME tag_ntag -#include "nrf_log.h" -#include "nrf_log_ctrl.h" -#include "nrf_log_default_backends.h" -NRF_LOG_MODULE_REGISTER(); - -#define NTAG213_VERSION 0x0F -#define NTAG215_VERSION 0x11 -#define NTAG216_VERSION 0x13 - -// NTAG COMMANDS -#define CMD_GET_VERSION 0x60 -#define CMD_READ 0x30 -#define CMD_FAST_READ 0x3A -#define CMD_WRITE 0xA2 -#define CMD_COMPAT_WRITE 0xA0 -#define CMD_READ_CNT 0x39 -#define CMD_PWD_AUTH 0x1B -#define CMD_READ_SIG 0x3C - -// MEMORY LAYOUT STUFF, addresses and sizes in bytes -// UID stuff -#define UID_CL1_ADDRESS 0x00 -#define UID_CL1_SIZE 3 -#define UID_BCC1_ADDRESS 0x03 -#define UID_CL2_ADDRESS 0x04 -#define UID_CL2_SIZE 4 -#define UID_BCC2_ADDRESS 0x08 -// LockBytes stuff -#define STATIC_LOCKBYTE_0_ADDRESS 0x0A -#define STATIC_LOCKBYTE_1_ADDRESS 0x0B -// CONFIG stuff -#define NTAG213_CONFIG_AREA_START_ADDRESS 0xA4 // 4 * 0x29 -#define NTAG215_CONFIG_AREA_START_ADDRESS 0x20C // 4 * 0x83 -#define NTAG216_CONFIG_AREA_START_ADDRESS 0x38C // 4 * 0xE3 -#define CONFIG_AREA_SIZE 8 -// CONFIG offsets, relative to config start address -#define CONF_AUTH0_OFFSET 0x03 -#define CONF_ACCESS_OFFSET 0x04 -#define CONF_PASSWORD_OFFSET 0x08 -#define CONF_PACK_OFFSET 0x0C - -// WRITE STUFF -#define BYTES_PER_WRITE 4 -#define PAGE_WRITE_MIN 0x02 - -// CONFIG masks to check individual needed bits -#define CONF_ACCESS_PROT 0x80 - -#define VERSION_INFO_LENGTH 8 //8 bytes info length + crc - -#define BYTES_PER_READ 16 - -// SIGNATURE Length -#define SIGNATURE_LENGTH 32 - -// NTAG215_Version[7] mean: -// 0x0F ntag213 -// 0x11 ntag215 -// 0x13 ntag216 -const uint8_t ntagVersion[8] = {0x00, 0x04, 0x04, 0x02, 0x01, 0x00, 0x11, 0x03}; -/* pwd auth for amiibo */ -uint8_t ntagPwdOK[2] = {0x80, 0x80}; - -// Data structure pointer to the label information -static nfc_tag_ntag_information_t *m_tag_information = NULL; -// Define and use shadow anti -collision resources -static nfc_tag_14a_coll_res_reference_t m_shadow_coll_res; -//Define and use NTAG special communication buffer -static nfc_tag_ntag_tx_buffer_t m_tag_tx_buffer; -// Save the specific type of NTAG currently being simulated -static tag_specific_type_t m_tag_type; - -static int get_block_max_by_tag_type(tag_specific_type_t tag_type) { - int block_max; - switch (tag_type) { - case TAG_TYPE_NTAG_213: - block_max = NTAG213_PAGES; - break; - default: - case TAG_TYPE_NTAG_215: - block_max = NTAG215_PAGES; - break; - case TAG_TYPE_NTAG_216: - block_max = NTAG216_PAGES; - break; - } - return block_max; -} - -static int get_block_cfg_by_tag_type(tag_specific_type_t tag_type) { - int block_max; - switch (tag_type) { - case TAG_TYPE_NTAG_213: - block_max = NTAG213_CONFIG_AREA_START_ADDRESS; - break; - default: - case TAG_TYPE_NTAG_215: - block_max = NTAG215_CONFIG_AREA_START_ADDRESS; - break; - case TAG_TYPE_NTAG_216: - block_max = NTAG216_CONFIG_AREA_START_ADDRESS; - break; - } - return block_max; -} - -void nfc_tag_ntag_state_handler(uint8_t *p_data, uint16_t szDataBits) { - uint8_t command = p_data[0]; - uint8_t block_num = p_data[1]; - - switch (command) { - case CMD_GET_VERSION: - memcpy(m_tag_tx_buffer.tx_buffer, ntagVersion, 8); - switch (m_tag_type) { - case TAG_TYPE_NTAG_213: - m_tag_tx_buffer.tx_buffer[6] = NTAG213_VERSION; - break; - default: - case TAG_TYPE_NTAG_215: - m_tag_tx_buffer.tx_buffer[6] = NTAG215_VERSION; - break; - case TAG_TYPE_NTAG_216: - m_tag_tx_buffer.tx_buffer[6] = NTAG216_VERSION; - break; - } - nfc_tag_14a_tx_bytes(m_tag_tx_buffer.tx_buffer, 8, true); - break; - case CMD_READ: - if (block_num < get_block_max_by_tag_type(m_tag_type)) { - for (int block = 0; block < 4; block++) { - memcpy(m_tag_tx_buffer.tx_buffer + block * 4, m_tag_information->memory[block_num + block], NFC_TAG_NTAG_DATA_SIZE); - } - nfc_tag_14a_tx_bytes(m_tag_tx_buffer.tx_buffer, BYTES_PER_READ, true); - } else { - nfc_tag_14a_tx_nbit(NAK_INVALID_OPERATION_TBIV, 4); - } - break; - case CMD_FAST_READ: { - uint8_t end_block_num = p_data[2]; - if ((block_num > end_block_num) || (block_num >= get_block_max_by_tag_type(m_tag_type)) || (end_block_num >= get_block_max_by_tag_type(m_tag_type))) { - nfc_tag_14a_tx_nbit(NAK_INVALID_OPERATION_TBV, 4); - break; - } - for (int block = block_num; block <= end_block_num; block++) { - memcpy(m_tag_tx_buffer.tx_buffer + (block - block_num) * 4, m_tag_information->memory[block], NFC_TAG_NTAG_DATA_SIZE); - } - nfc_tag_14a_tx_bytes(m_tag_tx_buffer.tx_buffer, (end_block_num - block_num + 1) * NFC_TAG_NTAG_DATA_SIZE, true); - break; - } - case CMD_WRITE: - // TODO - nfc_tag_14a_tx_nbit(ACK_VALUE, 4); - break; - case CMD_COMPAT_WRITE: - // TODO - break; - case CMD_PWD_AUTH: { - /* TODO: IMPLEMENT COUNTER AUTHLIM */ - uint8_t Password[4]; - memcpy(Password, m_tag_information->memory[get_block_cfg_by_tag_type(m_tag_type) + CONF_PASSWORD_OFFSET], 4); - if (Password[0] != p_data[1] || Password[1] != p_data[2] || Password[2] != p_data[3] || Password[3] != p_data[4]) { - nfc_tag_14a_tx_nbit(NAK_INVALID_OPERATION_TBIV, 4); - break; - } - /* Authenticate the user */ - //RESET AUTHLIM COUNTER, CURRENTLY NOT IMPLEMENTED - // TODO - /* Send the PACK value back */ - if (m_tag_information->config.mode_uid_magic) { - nfc_tag_14a_tx_bytes(ntagPwdOK, 2, true); - } else { - nfc_tag_14a_tx_bytes(m_tag_information->memory[get_block_cfg_by_tag_type(m_tag_type) + CONF_PASSWORD_OFFSET], 2, true); - } - break; - } - case CMD_READ_SIG: - memset(m_tag_tx_buffer.tx_buffer, 0xCA, SIGNATURE_LENGTH); - nfc_tag_14a_tx_bytes(m_tag_tx_buffer.tx_buffer, SIGNATURE_LENGTH, true); - break; - } - return; -} - -nfc_tag_14a_coll_res_reference_t *get_ntag_coll_res() { - // Use a separate anti -conflict information instead of using the information in the sector - m_shadow_coll_res.sak = m_tag_information->res_coll.sak; - m_shadow_coll_res.atqa = m_tag_information->res_coll.atqa; - m_shadow_coll_res.uid = m_tag_information->res_coll.uid; - m_shadow_coll_res.size = &(m_tag_information->res_coll.size); - m_shadow_coll_res.ats = &(m_tag_information->res_coll.ats); - // Finally, a shadow data structure pointer with only reference, no physical shadow, - return &m_shadow_coll_res; -} - -void nfc_tag_ntag_reset_handler() { - // TODO -} - -static int get_information_size_by_tag_type(tag_specific_type_t type) { - return sizeof(nfc_tag_14a_coll_res_entity_t) + sizeof(nfc_tag_ntag_configure_t) + (get_block_max_by_tag_type(type) * NFC_TAG_NTAG_DATA_SIZE); -} - -/** @brief ntag's callback before saving data - * @param type detailed label type - * @param buffer data buffer - * @return to be saved, the length of the data that needs to be saved, it means not saved when 0 - */ -int nfc_tag_ntag_data_savecb(tag_specific_type_t type, tag_data_buffer_t *buffer) { - if (m_tag_type != TAG_TYPE_UNDEFINED) { - // Save the corresponding size data according to the current label type - return get_information_size_by_tag_type(type); - } else { - return 0; - } -} - -int nfc_tag_ntag_data_loadcb(tag_specific_type_t type, tag_data_buffer_t *buffer) { - int info_size = get_information_size_by_tag_type(type); - if (buffer->length >= info_size) { - // Convert the data buffer to NTAG structure type - m_tag_information = (nfc_tag_ntag_information_t *)buffer->buffer; - // The specific type of NTAG that is simulated by the cache - m_tag_type = type; - // Register 14A communication management interface - nfc_tag_14a_handler_t handler_for_14a = { - .get_coll_res = get_ntag_coll_res, - .cb_state = nfc_tag_ntag_state_handler, - .cb_reset = nfc_tag_ntag_reset_handler, - }; - nfc_tag_14a_set_handler(&handler_for_14a); - NRF_LOG_INFO("HF ntag data load finish."); - } else { - NRF_LOG_ERROR("nfc_tag_ntag_information_t too big."); - } - return info_size; -} - -// Initialized NTAG factory data -bool nfc_tag_ntag_data_factory(uint8_t slot, tag_specific_type_t tag_type) { - // default ntag data - uint8_t default_p0[] = { 0x04, 0x68, 0x95, 0x71 }; - uint8_t default_p1[] = { 0xFA, 0x5C, 0x64, 0x80 }; - uint8_t default_p2[] = { 0x42, 0x48, 0x0F, 0xE0 }; - - // default ntag info - nfc_tag_ntag_information_t ntag_tmp_information; - nfc_tag_ntag_information_t *p_ntag_information; - p_ntag_information = &ntag_tmp_information; - int block_max = get_block_max_by_tag_type(tag_type); - for (int block = 0; block < block_max; block++) { - if (block == 0) { - memcpy(p_ntag_information->memory[block], default_p0, NFC_TAG_NTAG_DATA_SIZE); - } - if (block == 1) { - memcpy(p_ntag_information->memory[block], default_p1, NFC_TAG_NTAG_DATA_SIZE); - } - if (block == 2) { - memcpy(p_ntag_information->memory[block], default_p2, NFC_TAG_NTAG_DATA_SIZE); - } - } - - // default ntag auto ant-collision res - p_ntag_information->res_coll.atqa[0] = 0x44; - p_ntag_information->res_coll.atqa[1] = 0x00; - p_ntag_information->res_coll.sak[0] = 0x00; - p_ntag_information->res_coll.uid[0] = 0x04; - p_ntag_information->res_coll.uid[1] = 0x68; - p_ntag_information->res_coll.uid[2] = 0x95; - p_ntag_information->res_coll.uid[3] = 0x71; - p_ntag_information->res_coll.uid[4] = 0xFA; - p_ntag_information->res_coll.uid[5] = 0x5C; - p_ntag_information->res_coll.uid[6] = 0x64; - p_ntag_information->res_coll.size = NFC_TAG_14A_UID_DOUBLE_SIZE; - p_ntag_information->res_coll.ats.length = 0; - - // default ntag config - p_ntag_information->config.mode_uid_magic = true; - p_ntag_information->config.detection_enable = false; - - // save data to flash - tag_sense_type_t sense_type = get_sense_type_from_tag_type(tag_type); - fds_slot_record_map_t map_info; - get_fds_map_by_slot_sense_type_for_dump(slot, sense_type, &map_info); - int info_size = get_information_size_by_tag_type(tag_type); - NRF_LOG_INFO("NTAG info size: %d", info_size); - bool ret = fds_write_sync(map_info.id, map_info.key, info_size, p_ntag_information); - if (ret) { - NRF_LOG_INFO("Factory slot data success."); - } else { - NRF_LOG_ERROR("Factory slot data error."); - } - return ret; -} diff --git a/firmware/application/src/rfid/nfctag/hf/nfc_ntag.h b/firmware/application/src/rfid/nfctag/hf/nfc_ntag.h deleted file mode 100644 index fcbd4306..00000000 --- a/firmware/application/src/rfid/nfctag/hf/nfc_ntag.h +++ /dev/null @@ -1,39 +0,0 @@ -#ifndef NFC_NTAG_H -#define NFC_NTAG_H - -#include "nfc_14a.h" - -#define NFC_TAG_NTAG_DATA_SIZE 4 -#define NFC_TAG_NTAG_FRAME_SIZE 64 -#define NFC_TAG_NTAG_BLOCK_MAX 231 - -#define NTAG213_PAGES 45 //45 pages total for ntag213, from 0 to 44 -#define NTAG215_PAGES 135 //135 pages total for ntag215, from 0 to 134 -#define NTAG216_PAGES 231 //231 pages total for ntag216, from 0 to 230 - - -typedef struct { - uint8_t mode_uid_magic: 1; - uint8_t detection_enable: 1; - // reserve - uint8_t reserved1: 5; - uint8_t reserved2; - uint8_t reserved3; -} nfc_tag_ntag_configure_t; - -typedef struct __attribute__((aligned(4))) { - nfc_tag_14a_coll_res_entity_t res_coll; - nfc_tag_ntag_configure_t config; - uint8_t memory[NFC_TAG_NTAG_BLOCK_MAX][NFC_TAG_NTAG_DATA_SIZE]; -} -nfc_tag_ntag_information_t; - -typedef struct { - uint8_t tx_buffer[NFC_TAG_NTAG_FRAME_SIZE]; -} nfc_tag_ntag_tx_buffer_t; - -int nfc_tag_ntag_data_loadcb(tag_specific_type_t type, tag_data_buffer_t *buffer); -int nfc_tag_ntag_data_savecb(tag_specific_type_t type, tag_data_buffer_t *buffer); -bool nfc_tag_ntag_data_factory(uint8_t slot, tag_specific_type_t tag_type); - -#endif diff --git a/firmware/application/src/rfid/nfctag/tag_base_type.h b/firmware/application/src/rfid/nfctag/tag_base_type.h index 9f6b2027..5e833199 100644 --- a/firmware/application/src/rfid/nfctag/tag_base_type.h +++ b/firmware/application/src/rfid/nfctag/tag_base_type.h @@ -75,6 +75,12 @@ typedef enum { TAG_TYPE_NTAG_213 = 1100, TAG_TYPE_NTAG_215, TAG_TYPE_NTAG_216, + TAG_TYPE_MF0ICU1, + TAG_TYPE_MF0ICU2, + TAG_TYPE_MF0UL11, + TAG_TYPE_MF0UL21, + TAG_TYPE_NTAG_210, + TAG_TYPE_NTAG_212, // MIFARE Plus series 1200 // DESFire series 1300 @@ -106,7 +112,13 @@ typedef enum { TAG_TYPE_MIFARE_4096,\ TAG_TYPE_NTAG_213,\ TAG_TYPE_NTAG_215,\ - TAG_TYPE_NTAG_216 + TAG_TYPE_NTAG_216,\ + TAG_TYPE_MF0ICU1,\ + TAG_TYPE_MF0ICU2,\ + TAG_TYPE_MF0UL11,\ + TAG_TYPE_MF0UL21,\ + TAG_TYPE_NTAG_210,\ + TAG_TYPE_NTAG_212 typedef struct { tag_specific_type_t tag_hf; diff --git a/firmware/application/src/rfid/nfctag/tag_emulation.c b/firmware/application/src/rfid/nfctag/tag_emulation.c index 65a05e45..9f8d1671 100644 --- a/firmware/application/src/rfid/nfctag/tag_emulation.c +++ b/firmware/application/src/rfid/nfctag/tag_emulation.c @@ -2,7 +2,7 @@ #include "nfc_14a.h" #include "lf_tag_em.h" #include "nfc_mf1.h" -#include "nfc_ntag.h" +#include "nfc_mf0_ntag.h" #include "fds_ids.h" #include "fds_util.h" #include "tag_emulation.h" @@ -71,7 +71,7 @@ static tag_slot_config_t slotConfig ALIGN_U32 = { // See tag_emulation_factory_init for actual tag content .slots = { { .enabled_hf = true, .enabled_lf = true, .tag_hf = TAG_TYPE_MIFARE_1024, .tag_lf = TAG_TYPE_EM410X, }, // 1 - { .enabled_hf = true, .enabled_lf = false, .tag_hf = TAG_TYPE_MIFARE_1024, .tag_lf = TAG_TYPE_UNDEFINED, }, // 2 + { .enabled_hf = true, .enabled_lf = false, .tag_hf = TAG_TYPE_MF0ICU1, .tag_lf = TAG_TYPE_UNDEFINED, }, // 2 { .enabled_hf = false, .enabled_lf = true, .tag_hf = TAG_TYPE_UNDEFINED, .tag_lf = TAG_TYPE_EM410X, }, // 3 { .enabled_hf = false, .enabled_lf = false, .tag_hf = TAG_TYPE_UNDEFINED, .tag_lf = TAG_TYPE_UNDEFINED, }, // 4 { .enabled_hf = false, .enabled_lf = false, .tag_hf = TAG_TYPE_UNDEFINED, .tag_lf = TAG_TYPE_UNDEFINED, }, // 5 @@ -100,9 +100,16 @@ static tag_base_handler_map_t tag_base_map[] = { { TAG_SENSE_HF, TAG_TYPE_MIFARE_2048, nfc_tag_mf1_data_loadcb, nfc_tag_mf1_data_savecb, nfc_tag_mf1_data_factory, &m_tag_data_hf }, { TAG_SENSE_HF, TAG_TYPE_MIFARE_4096, nfc_tag_mf1_data_loadcb, nfc_tag_mf1_data_savecb, nfc_tag_mf1_data_factory, &m_tag_data_hf }, // NTAG tag simulation - { TAG_SENSE_HF, TAG_TYPE_NTAG_213, nfc_tag_ntag_data_loadcb, nfc_tag_ntag_data_savecb, nfc_tag_ntag_data_factory, &m_tag_data_hf }, - { TAG_SENSE_HF, TAG_TYPE_NTAG_215, nfc_tag_ntag_data_loadcb, nfc_tag_ntag_data_savecb, nfc_tag_ntag_data_factory, &m_tag_data_hf }, - { TAG_SENSE_HF, TAG_TYPE_NTAG_216, nfc_tag_ntag_data_loadcb, nfc_tag_ntag_data_savecb, nfc_tag_ntag_data_factory, &m_tag_data_hf }, + { TAG_SENSE_HF, TAG_TYPE_NTAG_210, nfc_tag_mf0_ntag_data_loadcb, nfc_tag_mf0_ntag_data_savecb, nfc_tag_mf0_ntag_data_factory, &m_tag_data_hf }, + { TAG_SENSE_HF, TAG_TYPE_NTAG_212, nfc_tag_mf0_ntag_data_loadcb, nfc_tag_mf0_ntag_data_savecb, nfc_tag_mf0_ntag_data_factory, &m_tag_data_hf }, + { TAG_SENSE_HF, TAG_TYPE_NTAG_213, nfc_tag_mf0_ntag_data_loadcb, nfc_tag_mf0_ntag_data_savecb, nfc_tag_mf0_ntag_data_factory, &m_tag_data_hf }, + { TAG_SENSE_HF, TAG_TYPE_NTAG_215, nfc_tag_mf0_ntag_data_loadcb, nfc_tag_mf0_ntag_data_savecb, nfc_tag_mf0_ntag_data_factory, &m_tag_data_hf }, + { TAG_SENSE_HF, TAG_TYPE_NTAG_216, nfc_tag_mf0_ntag_data_loadcb, nfc_tag_mf0_ntag_data_savecb, nfc_tag_mf0_ntag_data_factory, &m_tag_data_hf }, + // MF0 tag simulation + { TAG_SENSE_HF, TAG_TYPE_MF0ICU1, nfc_tag_mf0_ntag_data_loadcb, nfc_tag_mf0_ntag_data_savecb, nfc_tag_mf0_ntag_data_factory, &m_tag_data_hf }, + { TAG_SENSE_HF, TAG_TYPE_MF0ICU2, nfc_tag_mf0_ntag_data_loadcb, nfc_tag_mf0_ntag_data_savecb, nfc_tag_mf0_ntag_data_factory, &m_tag_data_hf }, + { TAG_SENSE_HF, TAG_TYPE_MF0UL11, nfc_tag_mf0_ntag_data_loadcb, nfc_tag_mf0_ntag_data_savecb, nfc_tag_mf0_ntag_data_factory, &m_tag_data_hf }, + { TAG_SENSE_HF, TAG_TYPE_MF0UL21, nfc_tag_mf0_ntag_data_loadcb, nfc_tag_mf0_ntag_data_savecb, nfc_tag_mf0_ntag_data_factory, &m_tag_data_hf }, }; @@ -701,7 +708,7 @@ void tag_emulation_factory_init(void) { } } - if (slotConfig.slots[1].enabled_hf && slotConfig.slots[1].tag_hf == TAG_TYPE_MIFARE_1024) { + if (slotConfig.slots[1].enabled_hf && slotConfig.slots[1].tag_hf == TAG_TYPE_MF0ICU1) { // Initialize a high -frequency M1 card in the card slot 2, if it does not exist. get_fds_map_by_slot_sense_type_for_dump(1, TAG_SENSE_HF, &map_info); if (!fds_is_exists(map_info.id, map_info.key)) { diff --git a/firmware/application/src/rfid_main.h b/firmware/application/src/rfid_main.h index fd6279b3..dadf6a34 100644 --- a/firmware/application/src/rfid_main.h +++ b/firmware/application/src/rfid_main.h @@ -8,7 +8,7 @@ #include "hw_connect.h" #include "nfc_14a.h" #include "nfc_mf1.h" -#include "nfc_ntag.h" +#include "nfc_mf0_ntag.h" #include "lf_tag_em.h" #include "tag_emulation.h" diff --git a/software/script/chameleon_cli_unit.py b/software/script/chameleon_cli_unit.py index bcd22395..6bcf9e9b 100644 --- a/software/script/chameleon_cli_unit.py +++ b/software/script/chameleon_cli_unit.py @@ -21,6 +21,7 @@ from chameleon_utils import ArgumentParserNoExit, ArgsParserError, UnexpectedResponseError from chameleon_utils import CLITree from chameleon_utils import CR, CG, CB, CC, CY, C0 +from chameleon_utils import print_mem_dump from chameleon_enum import Command, Status, SlotNumber, TagSenseType, TagSpecificType from chameleon_enum import MifareClassicWriteMode, MifareClassicPrngType, MifareClassicDarksideStatus, MfcKeyType from chameleon_enum import AnimationMode, ButtonPressFunction, ButtonType, MfcValueBlockOperator @@ -327,16 +328,42 @@ def update_hf14a_anticoll(self, args, uid, atqa, sak, ats): class MFUAuthArgsUnit(ReaderRequiredUnit): def args_parser(self) -> ArgumentParserNoExit: parser = ArgumentParserNoExit() - # TODO: - # -k, --key Authentication key (UL-C 16 bytes, EV1/NTAG 4 bytes) - # -l Swap entered key's endianness + + def key_parser(key: str) -> bytes: + try: + key = bytes.fromhex(key) + except: + raise ValueError("Key should be a hex string") + + if len(key) not in [4, 16]: + raise ValueError("Key should either be 4 or 16 bytes long") + elif len(key) == 16: + raise ValueError("Ultralight-C authentication isn't supported yet") + + return key + + parser.add_argument( + '-k', '--key', type=key_parser, metavar="", help="Authentication key (EV1/NTAG 4 bytes)." + ) + parser.add_argument('-l', action='store_true', dest='swap_endian', help="Swap endianness of the key.") + return parser def get_param(self, args): + key = args.key + + if key is not None and args.swap_endian: + key = bytearray(key) + for i in range(len(key)): + tmp = key[i] + key[i] = key[len(key) - 1 - i] + key = bytes(key) + class Param: - def __init__(self): - pass - return Param() + def __init__(self, key): + self.key = key + + return Param(key) def on_exec(self, args: argparse.Namespace): raise NotImplementedError("Please implement this") @@ -704,7 +731,7 @@ def recover_a_key(self, block_known, type_known, key_known, block_target, type_t if nt_level == 0: # It's a staticnested tag? nt_uid_obj = self.cmd.mf1_static_nested_acquire( block_known, type_known, key_known, block_target, type_target) - cmd_param = f"{nt_uid_obj['uid']} {str(type_target)}" + cmd_param = f"{nt_uid_obj['uid']} {int(type_target)}" for nt_item in nt_uid_obj['nts']: cmd_param += f" {nt_item['nt']} {nt_item['nt_enc']}" tool_name = "staticnested" @@ -912,21 +939,12 @@ def on_exec(self, args: argparse.Namespace): # read keys from key format file if args.import_key is not None: - buf = args.import_key.read() - if len(buf) % 6 != 0: - print(f' - {CR}Failed to parse keys from {args.import_key.name} (as .key format){C0}') + if not load_key_file(args.import_key, keys): return - for i in range(0, len(buf), 6): - keys.add(bytes(buf[i:i+6])) if args.import_dic is not None: - text = re.sub(r'#.*$', '', args.import_dic.read(), flags=re.MULTILINE) - buf = bytearray.fromhex(text) - if len(buf) % 6 != 0: - print(f' - {CR}Failed to parse keys from {args.import_dic.name} (as .dic format){C0}') + if not load_dic_file(args.import_dic, keys): return - for i in range(0, len(buf), 6): - keys.add(bytes(buf[i:i+6])) if len(keys) == 0: print(f' - {CR}No keys{C0}') @@ -1011,6 +1029,52 @@ def on_exec(self, args: argparse.Namespace): else: print(f" - {CR}Write fail.{C0}") +@hf_mf.command('view') +class HFMFView(MF1AuthArgsUnit): + def args_parser(self) -> ArgumentParserNoExit: + parser = ArgumentParserNoExit() + parser.description = 'Display content from tag memory or dump file' + mifare_type_group = parser.add_mutually_exclusive_group() + mifare_type_group.add_argument('--mini', help='MIFARE Classic Mini / S20', action='store_const', dest='maxSectors', const=5) + mifare_type_group.add_argument('--1k', help='MIFARE Classic 1k / S50 (default)', action='store_const', dest='maxSectors', const=16) + mifare_type_group.add_argument('--2k', help='MIFARE Classic/Plus 2k', action='store_const', dest='maxSectors', const=32) + mifare_type_group.add_argument('--4k', help='MIFARE Classic 4k / S70', action='store_const', dest='maxSectors', const=40) + parser.add_argument('-d', '--dump-file', required=False, type=argparse.FileType("rb"), help="Dump file to read") + parser.add_argument('-k', '--key-file', required=False, type=argparse.FileType("r"), help="File containing keys of tag to write (exported with fchk --export)") + parser.set_defaults(maxSectors=16) + return parser + + def on_exec(self, args: argparse.Namespace): + data = bytearray(0) + if args.dump_file is not None: + print("Reading dump file") + data = args.dump_file.read() + elif args.key_file is not None: + print("Reading tag memory") + # read keys from file + keys = list() + for line in args.key_file.readlines(): + a, b = [bytes.fromhex(h) for h in line[:-1].split(":")] + keys.append((a, b)) + if len(keys) != args.maxSectors: + raise ArgsParserError(f"Invalid key file. Found {len(keys)}, expected {args.maxSectors}") + # iterate over blocks + for blk in range(0, args.maxSectors * 4): + resp = None + try: + # first try with key B + resp = self.cmd.mf1_read_one_block(blk, MfcKeyType.B, keys[blk//4][1]) + except UnexpectedResponseError: + # ignore read errors at this stage as we want to try key A + pass + if not resp: + # try with key A if B was unsuccessful + # this will raise an exception if key A fails too + resp = self.cmd.mf1_read_one_block(blk, MfcKeyType.A, keys[blk//4][0]) + data.extend(resp) + else: + raise ArgsParserError("Missing args. Specify --dump-file (-d) or --key-file (-k)") + print_mem_dump(data,16) @hf_mf.command('value') class HFMFVALUE(ReaderRequiredUnit): @@ -1420,6 +1484,39 @@ def on_exec(self, args: argparse.Namespace): fd.write(data) print("\n - Read success") +@hf_mf.command('eview') +class HFMFEView(SlotIndexArgsAndGoUnit, DeviceRequiredUnit): + def args_parser(self) -> ArgumentParserNoExit: + parser = ArgumentParserNoExit() + parser.description = 'View data from emulator memory' + self.add_slot_args(parser) + return parser + + def on_exec(self, args: argparse.Namespace): + selected_slot = self.cmd.get_active_slot() + slot_info = self.cmd.get_slot_info() + tag_type = TagSpecificType(slot_info[selected_slot]['hf']) + + if tag_type == TagSpecificType.MIFARE_Mini: + block_count = 20 + elif tag_type == TagSpecificType.MIFARE_1024: + block_count = 64 + elif tag_type == TagSpecificType.MIFARE_2048: + block_count = 128 + elif tag_type == TagSpecificType.MIFARE_4096: + block_count = 256 + else: + raise Exception("Card in current slot is not Mifare Classic/Plus in SL1 mode") + index = 0 + data = bytearray(0) + max_blocks = self.device_com.data_max_length // 16 + while block_count > 0: + # read all the blocks + chunk_count = min(block_count, max_blocks) + data.extend(self.cmd.mf1_read_emu_block_data(index, chunk_count)) + index += chunk_count + block_count -= chunk_count + print_mem_dump(data,16) @hf_mf.command('econfig') class HFMFEConfig(SlotIndexArgsAndGoUnit, HF14AAntiCollArgsUnit, DeviceRequiredUnit): @@ -1573,21 +1670,335 @@ def on_exec(self, args: argparse.Namespace): f'- {"Log (mfkey32) mode:":40}{f"{CG}enabled{C0}" if detection else f"{CR}disabled{C0}"}') +@hf_mfu.command('ercnt') +class HFMFUVERSION(DeviceRequiredUnit): + def args_parser(self) -> ArgumentParserNoExit: + parser = ArgumentParserNoExit() + parser.description = 'Read MIFARE Ultralight / NTAG counter value.' + parser.add_argument('-c', '--counter', type=int, required=True, help="Counter index.") + return parser + + def on_exec(self, args: argparse.Namespace): + value, no_tearing = self.cmd.mfu_read_emu_counter_data(args.counter) + print(f" - Value: {value:06x}") + if no_tearing: + print(f" - Tearing: {CG}not set{C0}") + else: + print(f" - Tearing: {CR}set{C0}") + + +@hf_mfu.command('ewcnt') +class HFMFUVERSION(DeviceRequiredUnit): + def args_parser(self) -> ArgumentParserNoExit: + parser = ArgumentParserNoExit() + parser.description = 'Read MIFARE Ultralight / NTAG counter value.' + parser.add_argument('-c', '--counter', type=int, required=True, help="Counter index.") + parser.add_argument('-v', '--value', type=int, required=True, help="Counter value (24-bit).") + parser.add_argument('-t', '--reset-tearing', action='store_true', help="Reset tearing event flag.") + return parser + + def on_exec(self, args: argparse.Namespace): + if args.value > 0xFFFFFF: + print(f"{CR}Counter value {args.value:#x} is too large.{C0}") + return + + self.cmd.mfu_write_emu_counter_data(args.counter, args.value, args.reset_tearing) + + print('- Ok') + + @hf_mfu.command('rdpg') class HFMFURDPG(MFUAuthArgsUnit): def args_parser(self) -> ArgumentParserNoExit: parser = super().args_parser() - parser.description = 'MIFARE Ultralight read one page' + parser.description = 'MIFARE Ultralight / NTAG read one page' parser.add_argument('-p', '--page', type=int, required=True, metavar="", help="The page where the key will be used against") return parser + def on_exec(self, args: argparse.Namespace): + param = self.get_param(args) + + options = { + 'activate_rf_field': 0, + 'wait_response': 1, + 'append_crc': 1, + 'auto_select': 1, + 'keep_rf_field': 0, + 'check_response_crc': 1, + } + + if param.key is not None: + options['keep_rf_field'] = 1 + try: + resp = self.cmd.hf14a_raw(options=options, resp_timeout_ms=200, data=struct.pack('!B', 0x1B)+param.key) + + failed_auth = len(resp) < 2 + if not failed_auth: + print(f" - PACK: {resp[:2].hex()}") + except Exception as e: + # failed auth may cause tags to be lost + failed_auth = True + + options['keep_rf_field'] = 0 + options['auto_select'] = 0 + else: + failed_auth = False + + if not failed_auth: + resp = self.cmd.hf14a_raw(options=options, resp_timeout_ms=200, data=struct.pack('!BB', 0x30, args.page)) + print(f" - Data: {resp[:4].hex()}") + else: + try: + self.cmd.hf14a_raw(options=options, resp_timeout_ms=200, data=struct.pack('!BB', 0x30, args.page)) + except: + # we may lose the tag again here + pass + print(f" {CR}- Auth failed{C0}") + + +@hf_mfu.command('wrpg') +class HFMFUWRPG(MFUAuthArgsUnit): + def args_parser(self) -> ArgumentParserNoExit: + parser = super().args_parser() + parser.description = 'MIFARE Ultralight / NTAG write one page' + parser.add_argument('-p', '--page', type=int, required=True, metavar="", + help="The index of the page to write to.") + parser.add_argument('-d', '--data', type=bytes.fromhex, required=True, metavar="", + help="Your page data, as a 4 byte (8 character) hex string.") + return parser + + def on_exec(self, args: argparse.Namespace): + param = self.get_param(args) + + data = args.data + if len(data) != 4: + print(f"{CR}Page data should be a 4 byte (8 character) hex string{C0}") + return + + options = { + 'activate_rf_field': 0, + 'wait_response': 1, + 'append_crc': 1, + 'auto_select': 1, + 'keep_rf_field': 0, + 'check_response_crc': 0, + } + + if param.key is not None: + options['keep_rf_field'] = 1 + options['check_response_crc'] = 1 + try: + resp = self.cmd.hf14a_raw(options=options, resp_timeout_ms=200, data=struct.pack('!B', 0x1B)+param.key) + + failed_auth = len(resp) < 2 + if not failed_auth: + print(f" - PACK: {resp[:2].hex()}") + except Exception as e: + # failed auth may cause tags to be lost + failed_auth = True + + options['keep_rf_field'] = 0 + options['auto_select'] = 0 + options['check_response_crc'] = 0 + else: + failed_auth = False + + if not failed_auth: + resp = self.cmd.hf14a_raw(options=options, resp_timeout_ms=200, data=struct.pack('!BB', 0xA2, args.page)+data) + + if resp[0] == 0x0A: + print(f" - Ok") + else: + print(f"{CR}Write failed ({resp[0]:#04x}).{C0}") + else: + # send a command just to disable the field. use read to avoid corrupting the data + try: + self.cmd.hf14a_raw(options=options, resp_timeout_ms=200, data=struct.pack('!BB', 0x30, args.page)) + except: + # we may lose the tag again here + pass + print(f" {CR}- Auth failed{C0}") + + +@hf_mfu.command('eview') +class HFMFUEVIEW(DeviceRequiredUnit): + def args_parser(self) -> ArgumentParserNoExit: + parser = ArgumentParserNoExit() + parser.description = 'MIFARE Ultralight / NTAG view emulator data' + return parser + + def get_param(self, args): + class Param: + def __init__(self): + pass + + return Param() + + def on_exec(self, args: argparse.Namespace): + param = self.get_param(args) + + nr_pages = self.cmd.mfu_get_emu_pages_count() + page = 0 + while page < nr_pages: + count = min(nr_pages - page, 16) + data = self.cmd.mfu_read_emu_page_data(page, count) + for i in range(0, len(data), 4): + print(f"#{page+(i>>2):02x}: {data[i:i+4].hex()}") + page += count + + +@hf_mfu.command('eload') +class HFMFUELOAD(DeviceRequiredUnit): + def args_parser(self) -> ArgumentParserNoExit: + parser = ArgumentParserNoExit() + parser.description = 'MIFARE Ultralight / NTAG load emulator data' + parser.add_argument( + '-f', '--file', required=True, type=str, help="File to load data from." + ) + parser.add_argument( + '-t', '--type', type=str, required=False, help="Force writing as either raw binary or hex.", choices=['bin', 'hex'] + ) + return parser + + def get_param(self, args): + class Param: + def __init__(self): + pass + + return Param() + + def on_exec(self, args: argparse.Namespace): + file_type = args.type + if file_type is None: + if args.file.endswith('.eml') or args.file.endswith('.txt'): + file_type = 'hex' + else: + file_type = 'bin' + + if file_type == 'hex': + with open(args.file, 'r') as f: + data = f.read() + data = re.sub('#.*$', '', data, flags=re.MULTILINE) + data = bytes.fromhex(data) + else: + with open(args.file, 'rb') as f: + data = f.read() + + # this will throw an exception on incorrect slot type + nr_pages = self.cmd.mfu_get_emu_pages_count() + size = nr_pages * 4 + if len(data) > size: + print(f"{CR}Dump file is too large for the current slot (expected {size} bytes).{C0}") + return + elif (len(data) % 4) > 0: + print(f"{CR}Dump file's length is not a multiple of 4 bytes.{C0}") + return + elif len(data) < size: + print(f"{CY}Dump file is smaller than the current slot's memory ({len(data)} < {size}).{C0}") + + nr_pages = len(data) >> 2 + page = 0 + while page < nr_pages: + offset = page * 4 + cur_count = min(16, nr_pages - page) + + if offset >= len(data): + page_data = bytes.fromhex("00000000") * cur_count + else: + page_data = data[offset:offset + 4 * cur_count] + + self.cmd.mfu_write_emu_page_data(page, page_data) + page += cur_count + + print(f" - Ok") + + +@hf_mfu.command('esave') +class HFMFUESAVE(DeviceRequiredUnit): + def args_parser(self) -> ArgumentParserNoExit: + parser = ArgumentParserNoExit() + parser.description = 'MIFARE Ultralight / NTAG save emulator data' + parser.add_argument( + '-f', '--file', required=True, type=str, help='File to save data to.' + ) + parser.add_argument( + '-t', '--type', type=str, required=False, help="Force writing as either raw binary or hex.", choices=['bin', 'hex'] + ) + return parser + def get_param(self, args): class Param: def __init__(self): - self.page = args.page + pass + return Param() + def on_exec(self, args: argparse.Namespace): + file_type = args.type + fd = None + save_as_eml = False + + if file_type is None: + if args.file.endswith('.eml') or args.file.endswith('.txt'): + file_type = 'hex' + else: + file_type = 'bin' + + if file_type == 'hex': + fd = open(args.file, 'w+') + save_as_eml = True + else: + fd = open(args.file, 'wb+') + + with fd: + # this will throw an exception on incorrect slot type + nr_pages = self.cmd.mfu_get_emu_pages_count() + + fd.truncate(0) + + # write version and signature as comments if saving as .eml + if save_as_eml: + try: + version = self.cmd.mf0_ntag_get_version_data() + + fd.write(f"# Version: {version.hex()}\n") + except: + pass # slot does not have version data + + try: + signature = self.cmd.mf0_ntag_get_signature_data() + + if signature != b"\x00" * 32: + fd.write(f"# Signature: {signature.hex()}\n") + except: + pass # slot does not have signature data + + page = 0 + while page < nr_pages: + cur_count = min(32, nr_pages - page) + + data = self.cmd.mfu_read_emu_page_data(page, cur_count) + if save_as_eml: + for i in range(0, len(data), 4): + fd.write(data[i:i+4].hex() + "\n") + else: + fd.write(data) + + page += cur_count + + print(f" - Ok") + + +@hf_mfu.command('rcnt') +class HFMFURCNT(MFUAuthArgsUnit): + def args_parser(self) -> ArgumentParserNoExit: + parser = super().args_parser() + parser.description = 'MIFARE Ultralight / NTAG read counter' + parser.add_argument('-c', '--counter', type=int, required=True, metavar="", + help="Index of the counter to read (always 0 for NTAG, 0-2 for Ultralight EV1).") + return parser + def on_exec(self, args: argparse.Namespace): param = self.get_param(args) @@ -1599,9 +2010,34 @@ def on_exec(self, args: argparse.Namespace): 'keep_rf_field': 0, 'check_response_crc': 1, } - # TODO: auth first if a key is given - resp = self.cmd.hf14a_raw(options=options, resp_timeout_ms=200, data=struct.pack('!BB', 0x30, param.page)) - print(f" - Data: {resp[:4].hex()}") + + if param.key is not None: + options['keep_rf_field'] = 1 + try: + resp = self.cmd.hf14a_raw(options=options, resp_timeout_ms=200, data=struct.pack('!B', 0x1B)+param.key) + + failed_auth = len(resp) < 2 + if not failed_auth: + print(f" - PACK: {resp[:2].hex()}") + except Exception as e: + # failed auth may cause tags to be lost + failed_auth = True + + options['keep_rf_field'] = 0 + options['auto_select'] = 0 + else: + failed_auth = False + + if not failed_auth: + resp = self.cmd.hf14a_raw(options=options, resp_timeout_ms=200, data=struct.pack('!BB', 0x39, args.counter)) + print(f" - Data: {resp[:3].hex()}") + else: + try: + self.cmd.hf14a_raw(options=options, resp_timeout_ms=200, data=struct.pack('!BB', 0x39, args.counter)) + except: + # we may lose the tag again here + pass + print(f" {CR}- Auth failed{C0}") @hf_mfu.command('dump') @@ -1611,41 +2047,159 @@ def args_parser(self) -> ArgumentParserNoExit: parser.description = 'MIFARE Ultralight dump pages' parser.add_argument('-p', '--page', type=int, required=False, metavar="", default=0, help="Manually set number of pages to dump") - parser.add_argument('-q', '--qty', type=int, required=False, metavar="", default=16, + parser.add_argument('-q', '--qty', type=int, required=False, metavar="", help="Manually set number of pages to dump") parser.add_argument('-f', '--file', type=str, required=False, default="", help="Specify a filename for dump file") + parser.add_argument('-t', '--type', type=str, required=False, choices=['bin', 'hex'], + help="Force writing as either raw binary or hex.") return parser - - def get_param(self, args): - class Param: - def __init__(self): - self.start_page = args.page - self.stop_page = args.page + args.qty - self.output_file = args.file - return Param() - - def on_exec(self, args: argparse.Namespace): - param = self.get_param(args) - fd = None - save_as_eml = False - if param.output_file != "": - if param.output_file.endswith('.eml'): - fd = open(param.output_file, 'w+') - save_as_eml = True - else: - fd = open(param.output_file, 'wb+') - # TODO: auth first if a key is given + + def do_dump(self, args: argparse.Namespace, param, fd, save_as_eml): + if args.qty is not None: + stop_page = min(args.page + args.qty, 256) + else: + stop_page = None + + tags = self.cmd.hf14a_scan() + if len(tags) > 1: + print(f'- {CR}Collision detected, leave only one tag.{C0}') + return + elif len(tags) == 0: + print(f'- {CR}No tag detected.{C0}') + return + elif tags[0]['atqa'] != b'\x44\x00' or tags[0]['sak'] != b'\x00': + print(f'- {CR}Tag is not Mifare Ultralight compatible (ATQA {tags[0]["atqa"].hex()} SAK {tags[0]["sak"].hex()}).{C0}') + return + options = { 'activate_rf_field': 0, 'wait_response': 1, 'append_crc': 1, 'auto_select': 1, - 'keep_rf_field': 0, + 'keep_rf_field': 1, 'check_response_crc': 1, } - for i in range(param.start_page, param.stop_page): - resp = self.cmd.hf14a_raw(options=options, resp_timeout_ms=200, data=struct.pack('!BB', 0x30, i)) + + # if stop page isn't set manually, try autodetection + if stop_page is None: + tag_name = None + + # first try sending the GET_VERSION command + try: + version = self.cmd.hf14a_raw(options=options, resp_timeout_ms=100, data=struct.pack('!B', 0x60)) + if len(version) == 0: + version = None + except: + version = None + + # try sending AUTHENTICATE command and observe the result + try: + supports_auth = len(self.cmd.hf14a_raw(options=options, resp_timeout_ms=100, data=struct.pack('!B', 0x1A))) != 0 + except: + supports_auth = False + + if version is not None and not supports_auth: + # either ULEV1 or NTAG + assert len(version) == 8 + + is_mikron_ulev1 = version[1] == 0x34 and version[2] == 0x21 + if (version[2] == 3 or is_mikron_ulev1) and version[4] == 1 and version[5] == 0: + # Ultralight EV1 V0 + size_map = { + 0x0B: ('Mifare Ultralight EV1 48b', 20), + 0x0E: ('Mifare Ultralight EV1 128b', 41), + } + elif version[2] == 4 and version[4] == 1 and version[5] == 0: + # NTAG 210/212/213/215/216 V0 + size_map = { + 0x0B: ('NTAG 210', 20), + 0x0E: ('NTAG 212', 41), + 0x0F: ('NTAG 213', 45), + 0x11: ('NTAG 215', 135), + 0x13: ('NTAG 216', 231), + } + else: + size_map = {} + + if version[6] in size_map: + tag_name, stop_page = size_map[version[6]] + elif version is None and supports_auth: + # Ultralight C + tag_name = 'Mifare Ultralight C' + stop_page = 48 + elif version is None and not supports_auth: + try: + # Invalid command returning a NAK means that's some old type of NTAG. + self.cmd.hf14a_raw(options=options, resp_timeout_ms=100, data=struct.pack('!B', 0xFF)) + + print(f' - {CY}Tag is likely NTAG 20x, reading until first error.{C0}') + stop_page = 256 + except: + # Regular Ultralight + tag_name = 'Mifare Ultralight' + stop_page = 16 + else: + # This is probably Ultralight AES, but we don't support this one yet. + pass + + if tag_name is not None: + print(f' - Detected tag type as {tag_name}.') + + if stop_page is None: + print(f' - {CY}Couldn\'t autodetect the expected card size, reading until first error.{C0}') + stop_page = 256 + + needs_stop = False + + if param.key is not None: + try: + resp = self.cmd.hf14a_raw(options=options, resp_timeout_ms=200, data=struct.pack('!B', 0x1B)+param.key) + + needs_stop = len(resp) < 2 + if not needs_stop: + print(f" - PACK: {resp[:2].hex()}") + except Exception as e: + # failed auth may cause tags to be lost + needs_stop = True + + options['auto_select'] = 0 + + # this handles auth failure + if needs_stop: + print(f" - {CR}Auth failed{C0}") + if fd is not None: + fd.close() + fd = None + + for i in range(args.page, stop_page): + # this could be done once in theory but the command would need to be optimized properly + if param.key is not None and not needs_stop: + resp = self.cmd.hf14a_raw(options=options, resp_timeout_ms=200, data=struct.pack('!B', 0x1B)+param.key) + options['auto_select'] = 0 # prevent resets + pack = resp[:2].hex() + + # disable the rf field after the last command + if i == (stop_page - 1) or needs_stop: + options['keep_rf_field'] = 0 + + try: + resp = self.cmd.hf14a_raw(options=options, resp_timeout_ms=200, data=struct.pack('!BB', 0x30, i)) + except: + # probably lost tag, but we still need to disable rf field + resp = None + + if needs_stop: + # break if this command was sent just to disable RF field + break + elif resp is None or len(resp) == 0: + # we need to disable RF field if we reached the last valid page so send one more read command + needs_stop = True + continue + + # after the read we are sure we no longer need to select again + options['auto_select'] = 0 + # TODO: can be optimized as we get 4 pages at once but beware of wrapping # in case of end of memory or LOCK on ULC and no key provided data = resp[:4] @@ -1655,21 +2209,136 @@ def on_exec(self, args: argparse.Namespace): fd.write(data.hex()+'\n') else: fd.write(data) + + if needs_stop and stop_page != 256: + print(f' - {CY}Dump is shorter than expected.{C0}') + if args.file != '': + print(f" - {CG}Dump written in {args.file}.{C0}") + + def on_exec(self, args: argparse.Namespace): + param = self.get_param(args) + + file_type = args.type + fd = None + save_as_eml = False + + if args.file != '': + if file_type is None: + if args.file.endswith('.eml') or args.file.endswith('.txt'): + file_type = 'hex' + else: + file_type = 'bin' + + if file_type == 'hex': + fd = open(args.file, 'w+') + save_as_eml = True + else: + fd = open(args.file, 'wb+') + if fd is not None: - print(f" - {CG}Dump written in {param.output_file}.{C0}") - fd.close() + with fd: + fd.truncate(0) + self.do_dump(args, param, fd, save_as_eml) + else: + self.do_dump(args, param, fd, save_as_eml) + + +@hf_mfu.command('version') +class HFMFUVERSION(ReaderRequiredUnit): + def args_parser(self) -> ArgumentParserNoExit: + parser = ArgumentParserNoExit() + parser.description = 'Request MIFARE Ultralight / NTAG version data.' + return parser + + def on_exec(self, args: argparse.Namespace): + options = { + 'activate_rf_field': 0, + 'wait_response': 1, + 'append_crc': 1, + 'auto_select': 1, + 'keep_rf_field': 0, + 'check_response_crc': 1, + } + + resp = self.cmd.hf14a_raw(options=options, resp_timeout_ms=200, data=struct.pack('!B', 0x60)) + print(f" - Data: {resp[:8].hex()}") + + +@hf_mfu.command('signature') +class HFMFUSIGNATURE(ReaderRequiredUnit): + def args_parser(self) -> ArgumentParserNoExit: + parser = ArgumentParserNoExit() + parser.description = 'Request MIFARE Ultralight / NTAG ECC signature data.' + return parser + + def on_exec(self, args: argparse.Namespace): + options = { + 'activate_rf_field': 0, + 'wait_response': 1, + 'append_crc': 1, + 'auto_select': 1, + 'keep_rf_field': 0, + 'check_response_crc': 1, + } + + resp = self.cmd.hf14a_raw(options=options, resp_timeout_ms=200, data=struct.pack('!BB', 0x3C, 0x00)) + print(f" - Data: {resp[:32].hex()}") @hf_mfu.command('econfig') class HFMFUEConfig(SlotIndexArgsAndGoUnit, HF14AAntiCollArgsUnit, DeviceRequiredUnit): def args_parser(self) -> ArgumentParserNoExit: parser = ArgumentParserNoExit() - parser.description = 'Settings of Mifare Classic emulator' + parser.description = 'Settings of Mifare Ultralight / NTAG emulator' self.add_slot_args(parser) self.add_hf14a_anticoll_args(parser) + uid_magic_group = parser.add_mutually_exclusive_group() + uid_magic_group.add_argument('--enable-uid-magic', action='store_true', help="Enable UID magic mode") + uid_magic_group.add_argument('--disable-uid-magic', action='store_true', help="Disable UID magic mode") + parser.add_argument('--set-version', type=bytes.fromhex, help="Set data to be returned by the GET_VERSION command.") + parser.add_argument('--set-signature', type=bytes.fromhex, help="Set data to be returned by the READ_SIG command.") + parser.add_argument('--reset-auth-cnt', action='store_true', help="Resets the counter of unsuccessful authentication attempts.") return parser def on_exec(self, args: argparse.Namespace): + aux_data_changed = False + aux_data_change_requested = False + + if args.set_version is not None: + aux_data_change_requested = True + aux_data_changed = True + + if len(args.set_version) != 8: + print(f"{CR}Version data should be 8 bytes long.{C0}") + return + + try: + self.cmd.mf0_ntag_set_version_data(args.set_version) + except: + print(f"{CR}Tag type does not support GET_VERSION command.{C0}") + return + + if args.set_signature is not None: + aux_data_change_requested = True + aux_data_changed = True + + if len(args.set_signature) != 32: + print(f"{CR}Signature data should be 32 bytes long.{C0}") + return + + try: + self.cmd.mf0_ntag_set_signature_data(args.set_signature) + except: + print(f"{CR}Tag type does not support READ_SIG command.{C0}") + return + + if args.reset_auth_cnt: + aux_data_change_requested = True + old_value = self.cmd.mfu_reset_auth_cnt() + if old_value != 0: + aux_data_changed = True + print(f"- Unsuccessful auth counter has been reset from {old_value} to 0.") + # collect current settings anti_coll_data = self.cmd.hf14a_get_anti_coll_data() if len(anti_coll_data) == 0: @@ -1683,6 +2352,12 @@ def on_exec(self, args: argparse.Namespace): fwslot = SlotNumber.to_fw(self.slot_num) hf_tag_type = TagSpecificType(slotinfo[fwslot]['hf']) if hf_tag_type not in [ + TagSpecificType.MF0ICU1, + TagSpecificType.MF0ICU2, + TagSpecificType.MF0UL11, + TagSpecificType.MF0UL21, + TagSpecificType.NTAG_210, + TagSpecificType.NTAG_212, TagSpecificType.NTAG_213, TagSpecificType.NTAG_215, TagSpecificType.NTAG_216, @@ -1690,9 +2365,21 @@ def on_exec(self, args: argparse.Namespace): print(f"{CR}Slot {self.slot_num} not configured as MIFARE Ultralight / NTAG{C0}") return change_requested, change_done, uid, atqa, sak, ats = self.update_hf14a_anticoll(args, uid, atqa, sak, ats) - if change_done: + + if args.enable_uid_magic: + change_requested = True + self.cmd.mf0_ntag_set_uid_magic_mode(True) + magic_mode = True + elif args.disable_uid_magic: + change_requested = True + self.cmd.mf0_ntag_set_uid_magic_mode(False) + magic_mode = False + else: + magic_mode = self.cmd.mf0_ntag_get_uid_magic_mode() + + if change_done or aux_data_changed: print(' - MFU/NTAG Emulator settings updated') - if not change_requested: + if not (change_requested or aux_data_change_requested): print(f'- {"Type:":40}{CY}{hf_tag_type}{C0}') print(f'- {"UID:":40}{CY}{uid.hex().upper()}{C0}') print(f'- {"ATQA:":40}{CY}{atqa.hex().upper()} ' @@ -1700,6 +2387,22 @@ def on_exec(self, args: argparse.Namespace): print(f'- {"SAK:":40}{CY}{sak.hex().upper()}{C0}') if len(ats) > 0: print(f'- {"ATS:":40}{CY}{ats.hex().upper()}{C0}') + if magic_mode: + print(f'- {"UID Magic:":40}{CY}enabled{C0}') + else: + print(f'- {"UID Magic:":40}{CY}disabled{C0}') + + try: + version = self.cmd.mf0_ntag_get_version_data() + print(f'- {"Version:":40}{CY}{version.hex().upper()}{C0}') + except: + pass + + try: + signature = self.cmd.mf0_ntag_get_signature_data() + print(f'- {"Signature:":40}{CY}{signature.hex().upper()}{C0}') + except: + pass @lf_em_410x.command('read') diff --git a/software/script/chameleon_cmd.py b/software/script/chameleon_cmd.py index 2ef363dd..9c9f2c22 100644 --- a/software/script/chameleon_cmd.py +++ b/software/script/chameleon_cmd.py @@ -573,6 +573,69 @@ def mf1_read_emu_block_data(self, block_start: int, block_count: int): resp.parsed = resp.data return resp + @expect_response(Status.SUCCESS) + def mfu_get_emu_pages_count(self): + """ + Gets the number of pages available in the current MF0 / NTAG slot + """ + resp = self.device.send_cmd_sync(Command.MF0_NTAG_GET_PAGE_COUNT) + resp.parsed = resp.data[0] + return resp + + @expect_response(Status.SUCCESS) + def mfu_read_emu_page_data(self, page_start: int, page_count: int): + """ + Gets data for selected block range + """ + data = struct.pack('!BB', page_start, page_count) + resp = self.device.send_cmd_sync(Command.MF0_NTAG_READ_EMU_PAGE_DATA, data) + resp.parsed = resp.data + return resp + + @expect_response(Status.SUCCESS) + def mfu_write_emu_page_data(self, page_start: int, data: bytes): + """ + Gets data for selected block range + """ + count = len(data) >> 2 + + assert (len(data) % 4) == 0 + assert (page_start >= 0) and (count + page_start) <= 256 + + data = struct.pack('!BB', page_start, count) + data + resp = self.device.send_cmd_sync(Command.MF0_NTAG_WRITE_EMU_PAGE_DATA, data) + return resp + + @expect_response(Status.SUCCESS) + def mfu_read_emu_counter_data(self, index: int) -> (int, bool): + """ + Gets data for selected counter + """ + data = struct.pack('!B', index) + resp = self.device.send_cmd_sync(Command.MF0_NTAG_GET_COUNTER_DATA, data) + if resp.status == Status.SUCCESS: + resp.parsed = (((resp.data[0] << 16) | (resp.data[1] << 8) | resp.data[2]), resp.data[3] == 0xBD) + return resp + + @expect_response(Status.SUCCESS) + def mfu_write_emu_counter_data(self, index: int, value: int, reset_tearing: bool): + """ + Sets data for selected counter + """ + data = struct.pack('!BBBB', index | (int(reset_tearing) << 7), (value >> 16) & 0xFF, (value >> 8) & 0xFF, value & 0xFF) + resp = self.device.send_cmd_sync(Command.MF0_NTAG_SET_COUNTER_DATA, data) + return resp + + @expect_response(Status.SUCCESS) + def mfu_reset_auth_cnt(self): + """ + Resets authentication counter + """ + resp = self.device.send_cmd_sync(Command.MF0_NTAG_RESET_AUTH_CNT, bytes()) + if resp.status == Status.SUCCESS: + resp.parsed = resp.data[0] + return resp + @expect_response(Status.SUCCESS) def hf14a_set_anti_coll_data(self, uid: bytes, atqa: bytes, sak: bytes, ats: bytes = b''): """ @@ -919,6 +982,41 @@ def hf14a_get_anti_coll_data(self): resp.parsed = {'uid': uid, 'atqa': atqa, 'sak': sak, 'ats': ats} return resp + @expect_response(Status.SUCCESS) + def mf0_ntag_get_uid_magic_mode(self): + resp = self.device.send_cmd_sync(Command.MF0_NTAG_GET_UID_MAGIC_MODE) + if resp.status == Status.SUCCESS: + resp.parsed, = struct.unpack('!?', resp.data) + return resp + + @expect_response(Status.SUCCESS) + def mf0_ntag_set_uid_magic_mode(self, enabled: bool): + return self.device.send_cmd_sync(Command.MF0_NTAG_SET_UID_MAGIC_MODE, struct.pack('?', enabled)) + + @expect_response(Status.SUCCESS) + def mf0_ntag_get_version_data(self): + resp = self.device.send_cmd_sync(Command.MF0_NTAG_GET_VERSION_DATA) + if resp.status == Status.SUCCESS: + resp.parsed = resp.data[:8] + return resp + + @expect_response(Status.SUCCESS) + def mf0_ntag_set_version_data(self, data: bytes): + assert len(data) == 8 + return self.device.send_cmd_sync(Command.MF0_NTAG_SET_VERSION_DATA, data) + + @expect_response(Status.SUCCESS) + def mf0_ntag_get_signature_data(self): + resp = self.device.send_cmd_sync(Command.MF0_NTAG_GET_SIGNATURE_DATA) + if resp.status == Status.SUCCESS: + resp.parsed = resp.data[:32] + return resp + + @expect_response(Status.SUCCESS) + def mf0_ntag_set_signature_data(self, data: bytes): + assert len(data) == 32 + return self.device.send_cmd_sync(Command.MF0_NTAG_SET_SIGNATURE_DATA, data) + @expect_response(Status.SUCCESS) def get_ble_pairing_enable(self): """ diff --git a/software/script/chameleon_enum.py b/software/script/chameleon_enum.py index 2c13b8fe..3bb9868e 100644 --- a/software/script/chameleon_enum.py +++ b/software/script/chameleon_enum.py @@ -95,6 +95,18 @@ class Command(enum.IntEnum): MF1_GET_WRITE_MODE = 4016 MF1_SET_WRITE_MODE = 4017 HF14A_GET_ANTI_COLL_DATA = 4018 + MF0_NTAG_GET_UID_MAGIC_MODE = 4019 + MF0_NTAG_SET_UID_MAGIC_MODE = 4020 + MF0_NTAG_READ_EMU_PAGE_DATA = 4021 + MF0_NTAG_WRITE_EMU_PAGE_DATA = 4022 + MF0_NTAG_GET_VERSION_DATA = 4023 + MF0_NTAG_SET_VERSION_DATA = 4024 + MF0_NTAG_GET_SIGNATURE_DATA = 4025 + MF0_NTAG_SET_SIGNATURE_DATA = 4026 + MF0_NTAG_GET_COUNTER_DATA = 4027 + MF0_NTAG_SET_COUNTER_DATA = 4028 + MF0_NTAG_RESET_AUTH_CNT = 4029 + MF0_NTAG_GET_PAGE_COUNT = 4030 EM410X_SET_EMU_ID = 5000 EM410X_GET_EMU_ID = 5001 @@ -127,6 +139,7 @@ class Status(enum.IntEnum): NOT_IMPLEMENTED = 0x69 FLASH_WRITE_FAIL = 0x70 FLASH_READ_FAIL = 0x71 + INVALID_SLOT_TYPE = 0x72 def __str__(self): if self == Status.HF_TAG_OK: @@ -165,6 +178,8 @@ def __str__(self): return "Flash write failed" elif self == Status.FLASH_READ_FAIL: return "Flash read failed" + elif self == Status.INVALID_SLOT_TYPE: + return "Invalid card type in slot" return "Invalid status" @@ -260,6 +275,12 @@ class TagSpecificType(enum.IntEnum): NTAG_213 = 1100 NTAG_215 = 1101 NTAG_216 = 1102 + MF0ICU1 = 1103 + MF0ICU2 = 1104 + MF0UL11 = 1105 + MF0UL21 = 1106 + NTAG_210 = 1107 + NTAG_212 = 1108 # MIFARE Plus series 1200 # DESFire series 1300 @@ -303,6 +324,18 @@ def __str__(self): return "NTAG 215" elif self == TagSpecificType.NTAG_216: return "NTAG 216" + elif self == TagSpecificType.MF0ICU1: + return "Mifare Ultralight" + elif self == TagSpecificType.MF0ICU2: + return "Mifare Ultralight C" + elif self == TagSpecificType.MF0UL11: + return "Mifare Ultralight EV1 (640 bit)" + elif self == TagSpecificType.MF0UL21: + return "Mifare Ultralight EV1 (1312 bit)" + elif self == TagSpecificType.NTAG_210: + return "NTAG 210" + elif self == TagSpecificType.NTAG_212: + return "NTAG 212" elif self < TagSpecificType.OLD_TAG_TYPES_END: return "Old tag type, must be migrated! Upgrade fw!" return "Invalid" @@ -438,4 +471,4 @@ def __str__(self): class MfcValueBlockOperator(enum.IntEnum): DECREMENT = 0xC0 INCREMENT = 0xC1 - RESTORE = 0xC2 \ No newline at end of file + RESTORE = 0xC2 diff --git a/software/script/chameleon_utils.py b/software/script/chameleon_utils.py index 3ace362b..b758b4ac 100644 --- a/software/script/chameleon_utils.py +++ b/software/script/chameleon_utils.py @@ -102,6 +102,21 @@ def print_help(self): print('') self.help_requested = True +def print_mem_dump(bindata, blocksize): + + hexadecimal_len = blocksize*3+1 + ascii_len = blocksize+1 + print(f"[=] ----+{hexadecimal_len*'-'}+{ascii_len*'-'}") + print(f"[=] blk | data{(hexadecimal_len-5)*' '}| ascii") + print(f"[=] ----+{hexadecimal_len*'-'}+{ascii_len*'-'}") + + blocks = [bindata[i:i+blocksize] for i in range(0, len(bindata), blocksize)] + blk_index = 1 + for b in blocks: + hexstr = ' '.join(b.hex()[i:i+2] for i in range(0, len(b.hex()), 2)) + asciistr = ''.join([chr(b[i]) if (b[i] > 31 and b[i] < 127) else '.' for i in range(0,len(b),1)]) + print(f"[=] {blk_index:3} | {hexstr.upper()} | {asciistr} ") + blk_index += 1 def expect_response(accepted_responses: Union[int, list[int]]) -> Callable[..., Any]: """