mirror of
https://github.com/RfidResearchGroup/ChameleonUltra.git
synced 2026-03-31 23:25:50 +00:00
220 lines
10 KiB
Python
220 lines
10 KiB
Python
import argparse
|
|
import os
|
|
import platform
|
|
import sys
|
|
import traceback
|
|
import types
|
|
import chameleon_com
|
|
import chameleon_cmd
|
|
import colorama
|
|
import chameleon_cli_unit
|
|
|
|
|
|
ULTRA = r"""
|
|
╦ ╦╦ ╔╦╗╦═╗╔═╗
|
|
███████ ║ ║║ ║ ╠╦╝╠═╣
|
|
╚═╝╩═╝╩ ╩╚═╩ ╩
|
|
"""
|
|
|
|
LITE = r"""
|
|
╦ ╦╔╦╗╔═╗
|
|
███████ ║ ║ ║ ║╣
|
|
╩═╝╩ ╩ ╚═╝
|
|
"""
|
|
|
|
# create by http://patorjk.com/software/taag/#p=display&f=ANSI%20Shadow&t=Chameleon%20Ultra
|
|
BANNER = f"""
|
|
██████╗██╗ ██╗ █████╗ ███╗ ███╗███████╗██╗ ███████╗ ██████╗ ███╗ ██╗
|
|
██╔════╝██║ ██║██╔══██╗████╗ ████║██╔════╝██║ ██╔════╝██╔═══██╗████╗ ██║
|
|
██║ ███████║███████║██╔████╔██║█████╗ ██║ █████╗ ██║ ██║██╔██╗ ██║
|
|
██║ ██╔══██║██╔══██║██║╚██╔╝██║██╔══╝ ██║ ██╔══╝ ██║ ██║██║╚██╗██║
|
|
╚██████╗██║ ██║██║ ██║██║ ╚═╝ ██║███████╗███████╗███████╗╚██████╔╝██║ ╚████║
|
|
╚═════╝╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝╚══════╝╚══════╝ ╚═════╝ ╚═╝ ╚═══╝
|
|
|
|
"""
|
|
|
|
|
|
def new_uint(unit_clz, help_msg):
|
|
"""
|
|
new a uint dict object
|
|
:param unit_clz: unit implement class
|
|
:param help_msg: unit usage
|
|
:return: a dict...
|
|
"""
|
|
return {
|
|
'unit': unit_clz,
|
|
'help': help_msg,
|
|
}
|
|
|
|
|
|
class ChameleonCLI:
|
|
"""
|
|
CLI for chameleon
|
|
"""
|
|
|
|
def __init__(self):
|
|
self.cmd_maps = {
|
|
'hw': {
|
|
'connect': new_uint(chameleon_cli_unit.HWConnect, "Connect to chameleon by serial port"),
|
|
'mode': {
|
|
'set': new_uint(chameleon_cli_unit.HWModeSet, "Change device mode to tag reader or tag emulator"),
|
|
'get': new_uint(chameleon_cli_unit.HWModeGet, "Get current device mode"),
|
|
'help': "Device mode get/set"
|
|
},
|
|
'slot': {
|
|
'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"),
|
|
'nick': {
|
|
'set': new_uint(chameleon_cli_unit.HWSlotNickSet, "Set tag nick name for slot"),
|
|
'get': new_uint(chameleon_cli_unit.HWSlotNickGet, "Get tag nick name for slot"),
|
|
'help': "Get/Set tag nick name for slot",
|
|
},
|
|
'update': new_uint(chameleon_cli_unit.HWSlotUpdate, "Update config & data to device flash"),
|
|
'openall': new_uint(chameleon_cli_unit.HWSlotOpenAll, "Open all slot and set to default data"),
|
|
'help': "Emulation tag slot.",
|
|
},
|
|
'dfu': new_uint(chameleon_cli_unit.HWDFU, "Restart application to bootloader mode(Not yet implement dfu)."),
|
|
'help': "hardware controller",
|
|
},
|
|
'hf': {
|
|
'14a': {
|
|
'scan': new_uint(chameleon_cli_unit.HF14AScan, "Scan 14a tag, and print basic information"),
|
|
'info': new_uint(chameleon_cli_unit.HF14AInfo, "Scan 14a tag, and print detail information"),
|
|
'help': "ISO14443-a tag read/write/info...",
|
|
},
|
|
'mf': {
|
|
'nested': new_uint(chameleon_cli_unit.HFMFNested, "Mifare Classic nested recover key"),
|
|
'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"
|
|
},
|
|
'sim': new_uint(chameleon_cli_unit.HFMFSim, "Simulation a mifare classic card"),
|
|
'eload': new_uint(chameleon_cli_unit.HFMFELoad, "Load data to emulator memory"),
|
|
'help': "Mifare Classic mini/1/2/4, attack/read/write"
|
|
},
|
|
'help': "high frequency tag/reader",
|
|
},
|
|
'lf': {
|
|
'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"),
|
|
'sim': new_uint(chameleon_cli_unit.LFEMSim, "Simulation a em410x id card"),
|
|
'help': "EM410x read/write/emulator",
|
|
},
|
|
'help': "low frequency tag/reader",
|
|
}
|
|
}
|
|
|
|
# new a device communication instance(only communication)
|
|
self.device_com = chameleon_com.ChameleonCom()
|
|
|
|
@staticmethod
|
|
def print_banner():
|
|
"""
|
|
print chameleon ascii banner
|
|
:return:
|
|
"""
|
|
print(colorama.Fore.YELLOW + BANNER)
|
|
|
|
def parse_cli_cmd(self, cmd_str):
|
|
"""
|
|
parse cmd from str
|
|
:param cmd_str:
|
|
:return:
|
|
"""
|
|
cmds = cmd_str.split(" ")
|
|
cmd_maps: dict or types.FunctionType = self.cmd_maps
|
|
cmd_end = ""
|
|
for cmd in cmds:
|
|
if cmd in cmd_maps: # CMD found in map, we can continue find next
|
|
cmd_maps = cmd_maps[cmd]
|
|
cmd_end = cmd
|
|
else: # CMD not found
|
|
break
|
|
cmd_end_position = cmd_str.index(cmd_end) + len(cmd_end) + 1
|
|
return cmd_maps, (cmd_str[:cmd_end_position], cmd_str[cmd_end_position:])
|
|
|
|
def startCLI(self):
|
|
"""
|
|
start listen input.
|
|
:return:
|
|
"""
|
|
self.print_banner()
|
|
while True:
|
|
# wait user input
|
|
status = f"{colorama.Fore.GREEN}USB" if self.device_com.isOpen() else f"{colorama.Fore.RED}Offline"
|
|
print(f"[{status}{colorama.Style.RESET_ALL}] chameleon --> ", end="")
|
|
cmd_str = input().strip()
|
|
|
|
# clear screen
|
|
if cmd_str == "clear":
|
|
if platform.system() == 'Windows':
|
|
os.system("cls")
|
|
elif platform.system() == 'Linux':
|
|
os.system("clear")
|
|
else:
|
|
print("No screen clear implement")
|
|
continue
|
|
|
|
if cmd_str == "exit":
|
|
print("Bye, thank you. ^.^ ")
|
|
sys.exit(996)
|
|
|
|
# parse cmd
|
|
cmd_map, args_str = self.parse_cli_cmd(cmd_str)
|
|
is_exec_map = 'unit' in cmd_map
|
|
if is_exec_map:
|
|
# new a unit instance
|
|
unit_clz = cmd_map['unit']
|
|
if callable(unit_clz):
|
|
unit: chameleon_cli_unit.BaseCLIUnit = unit_clz()
|
|
else:
|
|
raise TypeError("CMD unit is not a 'BaseCLIUnit'")
|
|
# set variables of required
|
|
unit.device_com = self.device_com
|
|
# parse args
|
|
args_parse_result = unit.args_parser()
|
|
if args_parse_result is not None:
|
|
args: argparse.ArgumentParser = args_parse_result
|
|
args.prog = args_str[0]
|
|
try:
|
|
args_parse_result = args.parse_args(args_str[1].split())
|
|
except chameleon_cli_unit.ArgsParserError as e:
|
|
args.print_usage()
|
|
print(str(e).strip(), end="\n\n")
|
|
continue
|
|
except chameleon_cli_unit.ParserExitIntercept:
|
|
# don't exit process.
|
|
continue
|
|
# noinspection PyBroadException
|
|
try:
|
|
# before process cmd, we need to do something...
|
|
if not unit.before_exec(args_parse_result):
|
|
continue
|
|
# start process cmd
|
|
unit.on_exec(args_parse_result)
|
|
except (chameleon_cmd.NegativeResponseError, chameleon_cli_unit.ArgsParserError) as e:
|
|
print(f"{colorama.Fore.RED}{str(e)}{colorama.Style.RESET_ALL}")
|
|
except Exception:
|
|
print(f"CLI exception: {colorama.Fore.RED}{traceback.format_exc()}{colorama.Style.RESET_ALL}")
|
|
elif isinstance(cmd_map, dict):
|
|
print("".ljust(18, "-") + "".ljust(10) + "".ljust(30, "-"))
|
|
for map_key in cmd_map:
|
|
map_item = cmd_map[map_key]
|
|
if 'help' in map_item:
|
|
cmd_title = f"{colorama.Fore.GREEN}{map_key}{colorama.Style.RESET_ALL}"
|
|
help_line = (f" - {cmd_title}".ljust(37)) + f"[ {map_item['help']} ]"
|
|
print(help_line)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
colorama.init(autoreset=True)
|
|
ChameleonCLI().startCLI()
|