From c51051b30ecf612e68f5f4620b4fceb33fada681 Mon Sep 17 00:00:00 2001 From: taichunmin Date: Mon, 23 Mar 2026 20:13:11 +0800 Subject: [PATCH 1/2] Fix firmware application USB serial number --- CHANGELOG.md | 1 + firmware/application/src/sdk_config.h | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c8792a5..f191b57 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ All notable changes to this project will be documented in this file. This project uses the changelog in accordance with [keepchangelog](http://keepachangelog.com/). Please use this to write notable changes, which is not the same as git commit log... ## [unreleased][unreleased] + - Fix firmware application USB serial number (@taichunmin) - Added ioProx LF protocol support (read, emulate and T55xx clone) - Added commands to dump and clone Mifare tags - Fix bad missing tools warning (@suut) diff --git a/firmware/application/src/sdk_config.h b/firmware/application/src/sdk_config.h index 5bddb15..943af40 100644 --- a/firmware/application/src/sdk_config.h +++ b/firmware/application/src/sdk_config.h @@ -6614,7 +6614,7 @@ #ifndef APP_USBD_STRING_SERIAL_EXTERN -#define APP_USBD_STRING_SERIAL_EXTERN 0 +#define APP_USBD_STRING_SERIAL_EXTERN 1 #endif // APP_USBD_STRING_SERIAL - String descriptor for the serial number. @@ -6622,7 +6622,7 @@ // Note: This value is not editable in Configuration Wizard. // Serial number that is defined the same way like in @ref APP_USBD_STRINGS_MANUFACTURER. #ifndef APP_USBD_STRING_SERIAL -#define APP_USBD_STRING_SERIAL APP_USBD_STRING_DESC("000000000000") +#define APP_USBD_STRING_SERIAL g_extern_serial_number #endif // From 6f4722a96418daf4712a641736b3d7cd7117b586 Mon Sep 17 00:00:00 2001 From: Sam Date: Wed, 25 Mar 2026 11:31:40 +0800 Subject: [PATCH 2/2] feat(cli): integrate hardnested attack into autopwn for HardNested vulnerable cards When autopwn detects a HardNested vulnerable card (nt_level=2) with some known keys, it now automatically attempts to recover remaining keys using the hardnested attack, instead of only printing an advisory message. The implementation: - Iterates over each missing key slot, picking a known key before each attempt (allows newly recovered keys to be reused for subsequent targets) - Invokes hardnested.recover_key() with standard parameters (200 max runs, 3 max attempts) - After each found key, checks if it is reusable for other sectors - Falls back to senested attack if hardnested does not recover all keys This matches the existing behavior for nested and static-encrypted-nested attacks. --- software/script/chameleon_cli_unit.py | 40 +++++++++++++++++++++++++-- 1 file changed, 37 insertions(+), 3 deletions(-) diff --git a/software/script/chameleon_cli_unit.py b/software/script/chameleon_cli_unit.py index c9818a5..846eb98 100644 --- a/software/script/chameleon_cli_unit.py +++ b/software/script/chameleon_cli_unit.py @@ -2440,9 +2440,43 @@ class HFMFAutopwn(ReaderRequiredUnit): print(f" {CG}[+]{C0} Some keys found, recovering remaining..") if nt_level == 2: - print( - f" {CY}[+]{C0} Hardened card — use 'hf mf hardnested' to recover remaining keys" - ) + missing_keys = self.find_missing_keys(current_keys_found, total) + hardnested = HFMFHardNested.__new__(HFMFHardNested) + BaseCLIUnit.__init__(hardnested) + hardnested._device_cmd = self.cmd + for missing_key_num, key_type_target in missing_keys.items(): + if current_keys_found.get(missing_key_num) is not None: + print(f" {CG}[+]{C0} Key {missing_key_num} found by reuse") + continue + block_known, key_known_bytes, type_known = self.choose_random_known_key( + current_keys_found + ) + block_target = (missing_key_num // 2) * 4 + hn_key = hardnested.recover_key( + False, + block_known, + type_known, + key_known_bytes, + block_target, + key_type_target, + False, + 200, + 3, + ) + if hn_key is None: + continue + print(f" {CG}[+]{C0} Found key {missing_key_num}: {hn_key.upper()}") + current_keys_found[missing_key_num] = bytes.fromhex(hn_key) + current_keys_found = dict(sorted(current_keys_found.items())) + _, mask_bytes = self.mask_from_keys(missing_keys) + current_keys_found = self.merge_found_sector_keys( + current_keys_found, + self.try_key(bytes.fromhex(hn_key), self.neg_bytes(mask_bytes)), + ) + if len(current_keys_found) < total: + current_keys_found = self.run_senested( + current_keys_found, max_sectors_num + ) elif nt_level == 0: current_keys_found = self.run_senested(current_keys_found, max_sectors_num) else: