diff --git a/software/script/chameleon_cli_main.py b/software/script/chameleon_cli_main.py index 1fd68ea..734bd30 100644 --- a/software/script/chameleon_cli_main.py +++ b/software/script/chameleon_cli_main.py @@ -55,9 +55,10 @@ class ChameleonCLI: 'help': "Device mode get/set" }, 'slot': { - 'set': new_uint(chameleon_cli_unit.HWSlotSet, "Set emulation tag slot activated."), + 'change': new_uint(chameleon_cli_unit.HWSlotSet, "Set emulation tag slot activated."), 'type': new_uint(chameleon_cli_unit.HWSlotTagType, "Set emulation tag type"), 'init': new_uint(chameleon_cli_unit.HWSlotDataDefault, "Set emulation tag data to default"), + 'enable': new_uint(chameleon_cli_unit.HWSlotEnableSet, "Set emulation tag slot enable or disable"), 'help': "Emulation tag slot.", }, 'help': "hardware controller", @@ -73,6 +74,12 @@ class ChameleonCLI: 'darkside': new_uint(chameleon_cli_unit.HFMFDarkside, "Mifare Classic darkside recover key"), 'rdbl': new_uint(chameleon_cli_unit.HFMFRDBL, "MiFARE Classic read one block"), 'wrbl': new_uint(chameleon_cli_unit.HFMFWRBL, "MiFARE Classic write one block"), + 'detection': { + 'enable': new_uint(chameleon_cli_unit.HFMFDetectionEnable, "Detection enable"), + 'count': new_uint(chameleon_cli_unit.HFMFDetectionLogCount, "Detection log count"), + 'decrypt': new_uint(chameleon_cli_unit.HFMFDetectionDecrypt, "Download log and decrypt keys"), + 'help': "Mifare Classic detection log" + }, 'help': "Mifare Classic mini/1/2/4, attack/read/write" }, 'help': "high frequency tag/reader", @@ -81,7 +88,8 @@ class ChameleonCLI: 'em': { 'read': new_uint(chameleon_cli_unit.LFEMRead, "Scan em410x tag and print id"), 'write': new_uint(chameleon_cli_unit.LFEMWriteT55xx, "Write em410x id to t55xx"), - 'help': "EM410x read/write", + 'sim': new_uint(chameleon_cli_unit.LFEMSim, "Simulation a em410x id card."), + 'help': "EM410x read/write/emulator", }, 'help': "low frequency tag/reader", } diff --git a/software/script/chameleon_cli_unit.py b/software/script/chameleon_cli_unit.py index de67274..e895076 100644 --- a/software/script/chameleon_cli_unit.py +++ b/software/script/chameleon_cli_unit.py @@ -498,6 +498,112 @@ class HFMFWRBL(BaseMF1AuthOpera): print(f" - {colorama.Fore.RED}Write fail.{colorama.Style.RESET_ALL}") +class HFMFDetectionEnable(DeviceRequiredUnit): + + def args_parser(self) -> ArgumentParserNoExit or None: + parser = ArgumentParserNoExit() + parser.add_argument('-e', '--enable', type=int, required=True, choices=[1, 0], + help="1 = enable, 0 = disable") + return parser + + # hf mf detection enable -e 1 + def on_exec(self, args: argparse.Namespace): + enable = True if args.enable == 1 else False + self.cmd_positive.set_mf1_detection_enable(enable) + print(f" - Set mf1 detection { 'enable' if enable else 'disable'}.") + + +class HFMFDetectionLogCount(DeviceRequiredUnit): + + def args_parser(self) -> ArgumentParserNoExit or None: + return None + + # hf mf detection count + def on_exec(self, args: argparse.Namespace): + data_bytes = self.cmd_standard.get_mf1_detection_count().data + count = int.from_bytes(data_bytes, "little", signed=False) + print(f" - MF1 detection log count = {count}") + + +class HFMFDetectionDecrypt(DeviceRequiredUnit): + + detection_log_size = 18 + + def args_parser(self) -> ArgumentParserNoExit or None: + return None + + def decrypt_by_list(self, rs: list): + """ + 从侦测日志列表中解密秘钥 + :param rs: + :return: + """ + keys = [] + for i in range(len(rs)): + item0 = rs[i] + for j in range(i + 1, len(rs)): + item1 = rs[j] + cmd_base = f"{item0['uid']} {item0['nt']} {item0['nr']} {item0['ar']}" + cmd_base += f" {item1['nt']} {item1['nr']} {item1['ar']}" + cmd_recover = f"mfkey32v2.exe {cmd_base}" + # print(cmd_recover) + # Found Key: [e899c526c5cd] + # subprocess.run(cmd_final, cwd=os.path.abspath("../bin/"), shell=True) + process = self.sub_process(cmd_recover) + # wait end + process.wait_process() + # get output + output_str = process.get_output_sync() + # print(output_str) + sea_obj = re.search(r"([a-fA-F0-9]{12})", output_str, flags=re.MULTILINE) + if sea_obj is not None: + keys.append(sea_obj[1]) + + return keys + + # hf mf detection decrypt + def on_exec(self, args: argparse.Namespace): + buffer = bytearray() + index = 0 + count = int.from_bytes(self.cmd_standard.get_mf1_detection_count().data, "little", signed=False) + if count == 0: + print(" - No detection log to download") + return + print(f" - MF1 detection log count = {count}, start download", end="") + while index < count: + tmp = self.cmd_positive.get_mf1_detection_log(index).data + recv_count = int(len(tmp) / HFMFDetectionDecrypt.detection_log_size) + index += recv_count + buffer.extend(tmp) + print(f".", end="") + print() + print(f" - Download done ({len(buffer)}bytes), start parse and decrypt") + + result_maps = chameleon_cstruct.parse_mf1_detection_result(buffer) + for uid in result_maps.keys(): + print(f" - Detection log for uid [{uid.upper()}]") + result_maps_for_uid = result_maps[uid] + for block in result_maps_for_uid: + print(f" > Block {block} detect log decrypting...") + if 'A' in result_maps_for_uid[block]: + # print(f" - A record: { result_maps[block]['A'] }") + records = result_maps_for_uid[block]['A'] + if len(records) > 1: + result_maps[uid][block]['A'] = self.decrypt_by_list(records) + if 'B' in result_maps_for_uid[block]: + # print(f" - B record: { result_maps[block]['B'] }") + records = result_maps_for_uid[block]['B'] + if len(records) > 1: + result_maps[uid][block]['B'] = self.decrypt_by_list(records) + print(" > Result ---------------------------") + for block in result_maps_for_uid.keys(): + if 'A' in result_maps_for_uid[block]: + print(f" > Block {block}, A key result: {result_maps_for_uid[block]['A']}") + if 'B' in result_maps_for_uid[block]: + print(f" > Block {block}, B key result: {result_maps_for_uid[block]['B']}") + return + + class LFEMRead(ReaderRequiredUint): def args_parser(self) -> ArgumentParserNoExit or None: @@ -509,32 +615,69 @@ class LFEMRead(ReaderRequiredUint): print(f" - EM410x ID(10H): {colorama.Fore.GREEN}{id_hex}{colorama.Style.RESET_ALL}") -class LFEMWriteT55xx(ReaderRequiredUint): - def args_parser(self) -> ArgumentParserNoExit or None: - parser = ArgumentParserNoExit() +class LFEMCardRequiredUint(DeviceRequiredUnit): + + @staticmethod + def add_card_arg(parser: ArgumentParserNoExit): parser.add_argument("--id", type=str, required=True, help="EM410x tag id", metavar="hex") return parser + def before_exec(self, args: argparse.Namespace): + if super(LFEMCardRequiredUint, self).before_exec(args): + if not re.match(r"^[a-fA-F0-9]{10}$", args.id): + raise ArgsParserError("ID must include 10 HEX symbols") + return True + return False + + def args_parser(self) -> ArgumentParserNoExit or None: + raise NotImplementedError("Please implement this") + + def on_exec(self, args: argparse.Namespace): + raise NotImplementedError("Please implement this") + + +class LFEMWriteT55xx(LFEMCardRequiredUint, ReaderRequiredUint): + + def args_parser(self) -> ArgumentParserNoExit or None: + parser = ArgumentParserNoExit() + return self.add_card_arg(parser) + + def before_exec(self, args: argparse.Namespace): + b1 = super(LFEMCardRequiredUint, self).before_exec(args) + b2 = super(ReaderRequiredUint, self).before_exec(args) + return b1 and b2 + # lf em write --id 4400999559 def on_exec(self, args: argparse.Namespace): id_hex = args.id - if not re.match(r"^[a-fA-F0-9]{10}$", id_hex): - raise ArgsParserError("ID must include 10 HEX symbols") id_bytes = bytearray.fromhex(id_hex) self.cmd_positive.write_em_410x_to_t55xx(id_bytes) print(f" - EM410x ID(10H): {id_hex} write done.") -class HWSlotSet(DeviceRequiredUnit): +class SlotIndexRequireUint(DeviceRequiredUnit): def args_parser(self) -> ArgumentParserNoExit or None: - parser = ArgumentParserNoExit() + raise NotImplementedError() + + def on_exec(self, args: argparse.Namespace): + raise NotImplementedError() + + @staticmethod + def add_slot_args(parser: ArgumentParserNoExit): slot_choices = [1, 2, 3, 4, 5, 6, 7, 8] parser.add_argument('-s', "--slot", type=int, required=True, help="Slot index", metavar="number", choices=slot_choices) return parser - # hw slot set -s 1 + +class HWSlotSet(SlotIndexRequireUint): + + def args_parser(self) -> ArgumentParserNoExit or None: + parser = ArgumentParserNoExit() + return self.add_slot_args(parser) + + # hw slot change -s 1 def on_exec(self, args: argparse.Namespace): slot_index = args.slot self.cmd_positive.set_slot_activated(slot_index) @@ -543,8 +686,8 @@ class HWSlotSet(DeviceRequiredUnit): class TagTypeRequiredUint(DeviceRequiredUnit): - def args_parser(self) -> ArgumentParserNoExit or None: - parser = ArgumentParserNoExit() + @staticmethod + def add_type_args(parser: ArgumentParserNoExit): type_choices = chameleon_cmd.TagSpecificType.list() help_str = "" for name, value in chameleon_cmd.TagSpecificType.__members__.items(): @@ -555,11 +698,20 @@ class TagTypeRequiredUint(DeviceRequiredUnit): metavar="number", choices=type_choices) return parser + def args_parser(self) -> ArgumentParserNoExit or None: + raise NotImplementedError() + def on_exec(self, args: argparse.Namespace): raise NotImplementedError() -class HWSlotTagType(TagTypeRequiredUint): +class HWSlotTagType(TagTypeRequiredUint, SlotIndexRequireUint): + + def args_parser(self) -> ArgumentParserNoExit or None: + parser = ArgumentParserNoExit() + self.add_type_args(parser) + self.add_slot_args(parser) + return parser # hw slot tagtype -t 2 def on_exec(self, args: argparse.Namespace): @@ -568,13 +720,12 @@ class HWSlotTagType(TagTypeRequiredUint): print(f' - Set slot tag type success.') -class HWSlotDataDefault(TagTypeRequiredUint): +class HWSlotDataDefault(TagTypeRequiredUint, SlotIndexRequireUint): def args_parser(self) -> ArgumentParserNoExit or None: - parser = super(HWSlotDataDefault, self).args_parser() - slot_choices = [1, 2, 3, 4, 5, 6, 7, 8] - parser.add_argument('-s', "--slot", type=int, required=True, - help="Slot index", metavar="number", choices=slot_choices) + parser = ArgumentParserNoExit() + self.add_type_args(parser) + self.add_slot_args(parser) return parser # hw slot init -s 1 -t 2 @@ -583,3 +734,32 @@ class HWSlotDataDefault(TagTypeRequiredUint): slot_num = args.slot self.cmd_positive.set_slot_data_default(slot_num, tag_type) print(f' - Set slot tag data init success.') + + +class HWSlotEnableSet(SlotIndexRequireUint): + def args_parser(self) -> ArgumentParserNoExit or None: + parser = ArgumentParserNoExit() + self.add_slot_args(parser) + parser.add_argument('-e', '--enable', type=int, required=True, help="1 is Enable or 0 Disable", choices=[0, 1]) + return parser + + # hw slot enable -s 1 -e 0 + def on_exec(self, args: argparse.Namespace): + slot_num = args.slot + enable = args.enable + self.cmd_positive.set_slot_enable(slot_num, enable) + print(f' - Set slot {slot_num} {"enable" if enable else "disable"} success.') + + +class LFEMSim(LFEMCardRequiredUint): + + def args_parser(self) -> ArgumentParserNoExit or None: + parser = ArgumentParserNoExit() + return self.add_card_arg(parser) + + # lf em sim --id 4545454545 + def on_exec(self, args: argparse.Namespace): + id_hex = args.id + id_bytes = bytearray.fromhex(id_hex) + self.cmd_positive.set_em140x_sim_id(id_bytes) + print(f' - Set em410x tag id success.') diff --git a/software/script/chameleon_cmd.py b/software/script/chameleon_cmd.py index 1257588..6777812 100644 --- a/software/script/chameleon_cmd.py +++ b/software/script/chameleon_cmd.py @@ -8,6 +8,7 @@ DATA_CMD_GET_DEVICE_MODE = 1002 DATA_CMD_SET_SLOT_ACTIVATED = 1003 DATA_CMD_SET_SLOT_TAG_TYPE = 1004 DATA_CMD_SET_SLOT_DATA_DEFAULT = 1005 +DATA_CMD_SET_SLOT_ENABLE = 1006 DATA_CMD_SCAN_14A_TAG = 2000 DATA_CMD_MF1_SUPPORT_DETECT = 2001 @@ -23,6 +24,11 @@ DATA_CMD_MF1_WRITE_ONE_BLOCK = 2009 DATA_CMD_SCAN_EM410X_TAG = 3000 DATA_CMD_WRITE_EM410X_TO_T5577 = 3001 +DATA_CMD_SET_EM410X_EMU_ID = 5000 +DATA_CMD_SET_MF1_DETECTION_ENABLE = 5003 +DATA_CMD_GET_MF1_DETECTION_COUNT = 5004 +DATA_CMD_GET_MF1_DETECTION_RESULT = 5005 + @enum.unique class TagSenseType(enum.IntEnum): @@ -238,20 +244,24 @@ class BaseChameleonCMD: data.append(slot_index - 1) return self.device.send_cmd_sync(DATA_CMD_SET_SLOT_ACTIVATED, 0x00, data) - def set_slot_tag_type(self, tag_type: TagSpecificType): + def set_slot_tag_type(self, slot_index: int, tag_type: TagSpecificType): """ 设置当前卡槽的模拟卡的标签类型 注意:此操作并不会更改flash中的数据,flash中的数据的变动仅在下次保存时更新 + :param slot_index: 卡槽号码 :param tag_type: 标签类型 :return: """ + if slot_index < 1 or slot_index > 8: + raise ValueError("The slot index range error(1-8)") data = bytearray() + data.append(slot_index - 1) data.append(tag_type) return self.device.send_cmd_sync(DATA_CMD_SET_SLOT_TAG_TYPE, 0x00, data) def set_slot_data_default(self, slot_index: int, tag_type: TagSpecificType): """ - 设置当前卡槽的模拟卡的数据为缺省数据 + 设置指定卡槽的模拟卡的数据为缺省数据 注意:此API会将flash中的数据一并进行设置 :param slot_index: 卡槽号码 :param tag_type: 要设置的缺省标签类型 @@ -264,6 +274,57 @@ class BaseChameleonCMD: data.append(tag_type) return self.device.send_cmd_sync(DATA_CMD_SET_SLOT_DATA_DEFAULT, 0x00, data) + def set_slot_enable(self, slot_index: int, enable: bool): + """ + 设置指定的卡槽是否使能 + :param slot_index: 卡槽号码 + :param enable: 是否使能 + :return: + """ + if slot_index < 1 or slot_index > 8: + raise ValueError("The slot index range error(1-8)") + data = bytearray() + data.append(slot_index - 1) + data.append(0x01 if enable else 0x00) + return self.device.send_cmd_sync(DATA_CMD_SET_SLOT_ENABLE, 0X00, data) + + def set_em140x_sim_id(self, id_bytes: bytearray): + """ + 设置EM410x模拟的卡号 + :param id_bytes: 卡号的字节 + :return: + """ + if len(id_bytes) != 5: + raise ValueError("The id bytes length must equal 5") + return self.device.send_cmd_sync(DATA_CMD_SET_EM410X_EMU_ID, 0x00, id_bytes) + + def set_mf1_detection_enable(self, enable: bool): + """ + 设置是否使能当前卡槽的侦测 + :param enable: 是否使能 + :return: + """ + data = bytearray() + data.append(0x01 if enable else 0x00) + return self.device.send_cmd_sync(DATA_CMD_SET_MF1_DETECTION_ENABLE, 0x00, data) + + def get_mf1_detection_count(self): + """ + 获取当前侦测记录的统计个数 + :return: + """ + return self.device.send_cmd_sync(DATA_CMD_GET_MF1_DETECTION_COUNT, 0x00, None) + + def get_mf1_detection_log(self, index: int): + """ + 从指定的index位置开始获取侦测日志 + :param index: 开始索引 + :return: + """ + data = bytearray() + data.extend(index.to_bytes(4, "big", signed=False)) + return self.device.send_cmd_sync(DATA_CMD_GET_MF1_DETECTION_RESULT, 0x00, data) + class NegativeResponseError(Exception): """ @@ -348,8 +409,8 @@ class PositiveChameleonCMD(BaseChameleonCMD): self.check_status(ret.status, chameleon_status.Device.STATUS_DEVICE_SUCCESS) return ret - def set_slot_tag_type(self, tag_type: TagSpecificType): - ret = super(PositiveChameleonCMD, self).set_slot_tag_type(tag_type) + def set_slot_tag_type(self, slot_index: int, tag_type: TagSpecificType): + ret = super(PositiveChameleonCMD, self).set_slot_tag_type(slot_index, tag_type) self.check_status(ret.status, chameleon_status.Device.STATUS_DEVICE_SUCCESS) return ret @@ -357,3 +418,27 @@ class PositiveChameleonCMD(BaseChameleonCMD): ret = super(PositiveChameleonCMD, self).set_slot_data_default(slot_index, tag_type) self.check_status(ret.status, chameleon_status.Device.STATUS_DEVICE_SUCCESS) return ret + + def set_slot_enable(self, slot_index: int, enable: bool): + ret = super(PositiveChameleonCMD, self).set_slot_enable(slot_index, enable) + self.check_status(ret.status, chameleon_status.Device.STATUS_DEVICE_SUCCESS) + return ret + + def set_em140x_sim_id(self, id_bytes: bytearray): + ret = super(PositiveChameleonCMD, self).set_em140x_sim_id(id_bytes) + self.check_status(ret.status, chameleon_status.Device.STATUS_DEVICE_SUCCESS) + return ret + + def set_mf1_detection_enable(self, enable: bool): + ret = super(PositiveChameleonCMD, self).set_mf1_detection_enable(enable) + self.check_status(ret.status, chameleon_status.Device.STATUS_DEVICE_SUCCESS) + return ret + + def get_mf1_detection_log(self, index: int): + ret = super(PositiveChameleonCMD, self).get_mf1_detection_log(index) + self.check_status(ret.status, chameleon_status.Device.STATUS_DEVICE_SUCCESS) + return ret + + + + diff --git a/software/script/chameleon_cstruct.py b/software/script/chameleon_cstruct.py index ac086f5..eb826ed 100644 --- a/software/script/chameleon_cstruct.py +++ b/software/script/chameleon_cstruct.py @@ -69,3 +69,63 @@ def parse_darkside_acquire_result(data: bytearray): 'nr': bytes_to_u32(data[24: 28]), 'ar': bytes_to_u32(data[28: 32]), } + + +""" +// 验证的基础信息 + struct { + uint8_t block; + uint8_t is_keyb: 1; + uint8_t is_nested: 1; + // 空域,占位置用的 + uint8_t : 6; + } cmd; + // mfkey32必要参数 + uint8_t uid[4]; + uint8_t nt[4]; + uint8_t nr[4]; + uint8_t ar[4]; +""" + + +def parse_mf1_detection_result(data: bytearray): + """ + From bytes parse detection param + :param data: data + :return: + """ + # 转换 + result_list = [] + pos = 0 + while pos < len(data): + result_list.append({ + 'block': data[0 + pos], + 'type': 0x60 + (data[1 + pos] & 0x01), + 'is_nested': True if data[1 + pos] >> 1 & 0x01 == 0x01 else False, + 'uid': data[2 + pos: 6 + pos].hex(), + 'nt': data[6 + pos: 10 + pos].hex(), + 'nr': data[10 + pos: 14 + pos].hex(), + 'ar': data[14 + pos: 18 + pos].hex(), + }) + pos += 18 + + # 归类 + result_map = {} + for item in result_list: + uid = item['uid'] + if uid not in result_map: + result_map[uid] = {} + + block = item['block'] + if block not in result_map[uid]: + result_map[uid][block] = {} + + type_chr = 'A' if item['type'] == 0x60 else 'B' + if type_chr not in result_map[uid][block]: + result_map[uid][block][type_chr] = [] + + result_map[uid][block][type_chr].append(item) + + return result_map + +