From 0908ff212688cc0cc184ddf52349245216e6c77b Mon Sep 17 00:00:00 2001 From: Lucifer Voeltner Date: Sat, 15 Mar 2025 09:02:17 +0700 Subject: [PATCH] `hf_mfu_uscuid.py` - A helper script for interacting with USCUID-UL --- client/pyscripts/hf_mfu_uscuid.py | 198 ++++++++++++++++++++++++++++++ 1 file changed, 198 insertions(+) create mode 100644 client/pyscripts/hf_mfu_uscuid.py diff --git a/client/pyscripts/hf_mfu_uscuid.py b/client/pyscripts/hf_mfu_uscuid.py new file mode 100644 index 000000000..a477c1b16 --- /dev/null +++ b/client/pyscripts/hf_mfu_uscuid.py @@ -0,0 +1,198 @@ +### Crappy helper script for USCUID-UL, v0.2.4.2 +## Written and tested by Eltrick +# It is recommended that you are able to backdoor read main blocks +# in case changing from one type to another messes up keys/pwd +# unless you know what you're doing. + +## For the uninitiated, the keys are stored in the following locations +## per the corresponding datasheets +# UL11 - PWD - page 18d +# UL21 - PWD - page 39d +# UL-C - KEY - pages 44d to 47d +# NTAG 213 - PWD - page 43d +# NTAG 215 - PWD - page 133d +# NTAG 216 - PWD - page 229d + +import argparse +import pm3 + +try: + # pip install ansicolors + from colors import color +except ModuleNotFoundError: + def color(s, fg=None): + _ = fg + return str(s) + +HEX_DIGITS = "0123456789ABCDEF" +MEMORY_CONFIG = { "C3": "UL11", "3C": "UL21", "00": "UL-C", "A5": "NTAG 213", "5A": "NTAG 215", "AA": "NTAG 216", "55": "Unknown IC with 238 pages" } +KNOWN_CONFIGS = ["C30004030101000B03", "3C0004030101000E03", "000000000000000000", "A50004040201000F03", "5A0004040201001103", "AA0004040201001303"] + +parser = argparse.ArgumentParser(description='A script to help with raw USCUID-UL commands. Out of everything until -s, only one functionality can be used at a time, prioritised in order listed below.') +parser.add_argument('-r', '--read', action='store_true', help='Read and parse config from card') +parser.add_argument('-t', '--type', help='Type to change to: 1-UL11; 2-UL21; 3-UL-C; 4-NTAG213; 5-NTAG215; 6-NTAG216') +parser.add_argument('-c', '--cfg', help='Config to write') +parser.add_argument('-p', '--parse', help='Config to parse') +parser.add_argument('-b', '--bdr', help='Page num to read with backdoor') +parser.add_argument('-w', '--wbd', help='First page num to write with backdoor') +parser.add_argument('-u', '--uid', help='New UID to write') +parser.add_argument('-d', '--data', help='Page data to write if using -w, multiple of 4 bytes') +parser.add_argument('-s', '--sig', help='Signature to write with backdoor') +parser.add_argument('--gen1a', action='store_true', help='Use gen1a (40/43) magic wakeup') +parser.add_argument('--gdm', action='store_true', help='Use gdm alt (20/23) magic wakeup') + +args = parser.parse_args() +card_config = args.read +ul_type = args.type +config = args.cfg +parse = args.parse +backdoor_block = args.bdr +write_backdoor = args.wbd +data = args.data +signature = args.sig +gen1a = args.gen1a +alt = args.gdm +uid = args.uid + +field_on = False +p = pm3.pm3() + +ERROR = "[" + color("-", "red") + "] " +SUCCESS = "[" + color("+", "green") + "] " + +def verify_config(config: str) -> bool: + if len(config) != 32: + print(ERROR + "Configuration data must be 16 bytes.") + return False + if set(config) > set(HEX_DIGITS): + print(ERROR + "Configuration data must be in hex.") + return False + return True + +def parse_config(config: str): + print(SUCCESS + "" + config) + cfg_magic_wup = config[0:4] + cfg_wup_style = config[4:6] + cfg_regular_available = config[6:8] + cfg_auth_type = config[8:10] + cfg_cuid = config[12:14] + cfg_memory_config = config[14:16] + + log_magic_wup = "Magic wakeup " + ("en" if cfg_magic_wup != "8500" else "dis") + "abled" + (" with config access" if cfg_magic_wup == "7AFF" else "") + log_wup_style = "Magic wakeup style " + ("Gen1a 40(7)/43" if cfg_wup_style == "00" else ("GDM 20(7)/23" if cfg_wup_style == "85" else "unknown")) + log_regular_available = "Config " + ("" if cfg_regular_available == "A0" else "un") + "available in regular mode" + log_auth_type = "Auth type " + ("1B - PWD" if cfg_auth_type == "00" else "1A - 3DES") + log_cuid = "CUID " + ("dis" if cfg_cuid == "A0" else "en") + "abled" + log_memory_config = "Maximum memory configuration: " + (MEMORY_CONFIG[cfg_memory_config] if cfg_memory_config in MEMORY_CONFIG.keys() else "unknown") + + print(SUCCESS + "^^^^............................ " + log_magic_wup) + print(SUCCESS + "....^^.......................... " + log_wup_style) + print(SUCCESS + "......^^........................ " + log_regular_available) + print(SUCCESS + "........^^...................... " + log_auth_type) + print(SUCCESS + "..........^^.................... unknown") + print(SUCCESS + "............^^.................. " + log_cuid) + print(SUCCESS + "..............^^................ " + log_memory_config) + print(SUCCESS + "................^^^^^^^^^^^^^^^^ version info") + +def try_auth_magic(enforced = False): + if enforced and not (gen1a | alt): + print(ERROR + "Magic wakeup required. Please select one.") + exit() + if gen1a ^ alt: + p.console("hf 14a raw -akb 7 " + ("40" if gen1a else "20")) + p.console("hf 14a raw -k " + ("43" if gen1a else "23")) + +def write_config(config: str): + try_auth_magic() + for i in range(4): + p.console("hf 14a raw -" + ("s" if (i == 0 and not (gen1a or alt)) else "") + ("k" if i != 3 else "") + "c" + f" E2{i:02x}" + config[8*i:8*i+8], False, False) + +def grab_config() -> str: + try_auth_magic() + p.console("hf 14a raw -c" + ("s" if not (gen1a or alt) else "") + " E050") + return p.grabbed_output.split("\n")[-2][4:-9].replace(" ", "") + +if gen1a and alt: + print(ERROR + "Please only choose one magic wakeup type.") + exit() + +if card_config: + config_grab = grab_config() + if not verify_config(config_grab): + print(ERROR + "Failed to grab config data from card.") + exit() + parse_config(config_grab) + +elif ul_type != None: + ul_type_num = int(ul_type) - 1 + if ul_type_num < 0 or ul_type_num >= len(KNOWN_CONFIGS): + print(ERROR + "Type specified is non-existent.") + exit() + old_config = grab_config() + new_config = old_config[0:8] + ("0A" if ul_type_num == 2 else "00") + old_config[10:14] + KNOWN_CONFIGS[ul_type_num] + write_config(new_config) + +elif config != None: + config = config.upper() + if not verify_config(config): + exit() + write_config(config) + +elif parse != None: + parse = parse.upper() + if not verify_config(parse): + exit() + parse_config(parse) + +elif backdoor_block != None: + block = int(backdoor_block) + try_auth_magic(True) + p.console(f"hf 14a raw -c 30{block:02x}") + print(p.grabbed_output.split("\n")[-2][4:-9].replace(" ", "")) + +elif write_backdoor != None: + write_backdoor_num = int(write_backdoor) + if data == None: + print(ERROR + "Specify data to write to the block.") + exit() + if len(data) % 8 != 0: + print(ERROR + "Data must be a multiple of 4 bytes.") + exit() + + try_auth_magic(True) + for i in range(len(data) // 8): + p.console("hf 14a raw -" + ("k" if i != (len(data) // 8 - 1) else "") + f"c A2{(write_backdoor_num + i):02x}{data[8*i:8*i+8]}", False, False) + +elif uid != None: + if len(uid) != 14: + print(ERROR + "UID must be 7 bytes.") + exit() + try_auth_magic() + p.console(f"hf 14a raw -kc" + ("s" if not (gen1a or alt) else "") + " 3002") + block_2 = p.grabbed_output.split("\n")[-2][4:-9].replace(" ", "")[:8] + uid_bytes = [int(uid[2*x:2*x+2], 16) for x in range(7)] + + bcc_0 = 0x88 ^ uid_bytes[0] ^ uid_bytes[1] ^ uid_bytes[2] + new_block_0 = "" + for i in range(3): + new_block_0 += f"{uid_bytes[i]:02x}" + new_block_0 += f"{bcc_0:02x}" + + bcc_1 = uid_bytes[3] ^ uid_bytes[4] ^ uid_bytes[5] ^ uid_bytes[6] + new_block_1 = uid[6:] + new_block_2 = f"{bcc_1:02x}" + block_2[2:] + p.console("hf 14a raw -kc A200" + new_block_0, False, False) + p.console("hf 14a raw -kc A201" + new_block_1, False, False) + p.console("hf 14a raw -c A202" + new_block_2, False, False) + +elif signature != None: + if len(signature) != 64: + print(ERROR + "Signature must be 32 bytes.") + exit() + try_auth_magic(True) + signature_pages = [signature[8*x:8*x+8] for x in range(8)] + for i in range(8, 16): + p.console("hf 14a raw -c" + ("k" if i != 15 else "") + f" A2F{i:01x}{signature_pages[i - 8]}", False, False) + +# Always try to HALT +p.console("hf 14a raw -c 5000")