def xcp_arbitration_id_discovery(args): """Scans for XCP support by brute forcing XCP connect messages against different arbitration IDs.""" global hit_counter min_id = int_from_str_base(args.min) max_id = int_from_str_base(args.max) hit_counter = 0 with CanActions() as can_wrap: print("Starting XCP discovery") def response_analyser_wrapper(arb_id): print "\rSending XCP connect to 0x{0:04x}".format(arb_id), stdout.flush() def response_analyser(msg): global hit_counter # Handle positive response if msg.data[0] == 0xff and any(msg.data[1:]): hit_counter += 1 decode_connect_response(msg) print("Found XCP at arb ID 0x{0:04x}, reply at 0x{1:04x}".format(arb_id, msg.arbitration_id)) print("#" * 20) print("\n") # Handle negative response elif msg.data[0] == 0xfe: print("\nFound XCP (with a bad reply) at arbitration ID 0x{0:03x}, reply at 0x{1:04x}".format( arb_id, msg.arbitration_id)) decode_xcp_error(msg) return response_analyser def discovery_end(s): print("\r{0}: Found {1} possible matches.".format(s, hit_counter)) can_wrap.bruteforce_arbitration_id([0xff], response_analyser_wrapper, min_id=min_id, max_id=max_id, callback_end=discovery_end)
def dcm_dtc(args): """ Fetches and prints the Diagnostic Trouble Codes from a supported service (Mode $03) :param args: A namespace containing src, dst and clear """ send_arb_id = int_from_str_base(args.src) rcv_arb_id = int_from_str_base(args.dst) clear = args.clear def decode_dtc(msg): if msg.arbitration_id != rcv_arb_id: return if msg.data[1] != 0x43: return def dtc_type(x): return {0: "P", 1: "B", 2: "C", 3: "U"}.get(x, "?") dtc_type_char = dtc_type(msg.data[3] & 0xF0 >> 4) print ("DTC: {0}{1:01x}{2:02x}\n".format(dtc_type_char, msg.data[3] & 0x0F, msg.data[4])) with CanActions(arb_id=send_arb_id) as can_wrap: if clear: can_wrap.send([0x01, 0x04]) print ("Cleared DTCs and reset MIL") else: print ("Fetching Diagnostic Trouble Codes") can_wrap.send_single_message_with_callback([0x01, 0x03], decode_dtc) time.sleep(1)
def dcm_discovery(args): """ Scans for diagnostics support by sending session control against different arbitration IDs. :param: args: A namespace containing min and max """ min_id = int_from_str_base(args.min) max_id = int_from_str_base(args.max) with CanActions() as can_wrap: print("Starting diagnostics service discovery") def response_analyser_wrapper(arb_id): print "\rSending diagnostics Tester Present to 0x{0:04x}".format(arb_id), stdout.flush() def response_analyser(msg): # Catch both ok and negative response if msg.data[1] in [0x50, 0x7F]: print("\nFound diagnostics at arbitration ID 0x{0:04x}, " "reply at 0x{1:04x}".format(arb_id, msg.arbitration_id)) can_wrap.bruteforce_stop() return response_analyser def none_found(s): print("\nDiagnostics service could not be found: {0}".format(s)) # Message to bruteforce - [length, session control, default session] message = insert_message_length([0x10, 0x01]) can_wrap.bruteforce_arbitration_id(message, response_analyser_wrapper, min_id=min_id, max_id=max_id, callback_end=none_found)
def xcp_get_basic_information(args): send_arb_id = int_from_str_base(args.src) rcv_arb_id = int_from_str_base(args.dst) def callback_wrapper(callback): """ Adds handling of uninteresting or error messages to a callback function. :param callback: The callback function to run on successful messages :return: A callback function with extended message handling """ def c(msg): if msg.arbitration_id != rcv_arb_id: return if msg.data[0] == 0xfe: return if msg.data[0] == 0xff: callback(msg) else: print("Unexpected reply:\n{0}\n".format(msg)) return c class ProbeMessage(): """Wrapper class for probe messages""" def __init__(self, message_data, callback): self.message_data = message_data self.callback = callback_wrapper(callback) def __str__(self): return "{0}".format(["{0:02x}".format(a) for a in self.message_data]) # Callback handler for GetId messages def print_msg_as_text(msg): print("".join([chr(x) for x in msg.data[1:]])) def handle_get_id_reply(msg): can_wrap.send_single_message_with_callback([0xf5, msg.data[4]], callback_wrapper(print_msg_as_text)) # Define probe messages probe_msgs = [ProbeMessage([0xff], decode_connect_response), # Connect ProbeMessage([0xfb], decode_get_comm_mode_info_response), # GetCommMode ProbeMessage([0xfd], decode_get_status_response), # GetStatus ProbeMessage([0xfa, 0x00], handle_get_id_reply), # GetId ASCII text ProbeMessage([0xfa, 0x01], handle_get_id_reply), # GetId ASAM-MC2 filename w/o path/ext ProbeMessage([0xfa, 0x02], handle_get_id_reply), # GetId ASAM-MC2 filename with path/ext ProbeMessage([0xfa, 0x03], handle_get_id_reply), # GetId ASAM-MC2 URL ProbeMessage([0xfa, 0x04], handle_get_id_reply)] # GetId ASAM-MC2 fileToUpload # Initiate probing with CanActions(arb_id=send_arb_id) as can_wrap: print("Probing for XCP info") for probe in probe_msgs: print("Sending probe message: {0}".format(probe)) can_wrap.send_single_message_with_callback(probe.message_data, probe.callback) time.sleep(2) print("Probing finished")
def xcp_get_basic_information(args): send_arb_id = int_from_str_base(args.src) rcv_arb_id = int_from_str_base(args.dst) def callback_wrapper(callback): """ Adds handling of uninteresting or error messages to a callback function. :param callback: The callback function to run on successful messages :return: A callback function with extended message handling """ def c(msg): if msg.arbitration_id != rcv_arb_id: return if msg.data[0] == 0xfe: return if msg.data[0] == 0xff: callback(msg) else: print("Unexpected reply:\n{0}\n".format(msg)) return c class ProbeMessage(): """Wrapper class for probe messages""" def __init__(self, message_data, callback): self.message_data = message_data self.callback = callback_wrapper(callback) def __str__(self): return "{0}: {1}".format(self.name, ["{0:02x}".format(a) for a in self.message_data]) # Callback handler for GetId messages def print_msg_as_text(msg): print("".join([chr(x) for x in msg.data[1:]])) def handle_get_id_reply(msg): can_wrap.send_single_message_with_callback([0xf5, msg.data[4]], callback_wrapper(print_msg_as_text)) # Define probe messages probe_msgs = [ProbeMessage([0xff], decode_connect_response), # Connect ProbeMessage([0xfb], decode_get_comm_mode_info_response), # GetCommMode ProbeMessage([0xfd], decode_get_status_response), # GetStatus ProbeMessage([0xfa, 0x00], handle_get_id_reply), # GetId ASCII text ProbeMessage([0xfa, 0x01], handle_get_id_reply), # GetId ASAM-MC2 filename w/o path/ext ProbeMessage([0xfa, 0x02], handle_get_id_reply), # GetId ASAM-MC2 filename with path/ext ProbeMessage([0xfa, 0x03], handle_get_id_reply), # GetId ASAM-MC2 URL ProbeMessage([0xfa, 0x04], handle_get_id_reply)] # GetId ASAM-MC2 fileToUpload # Initiate probing with CanActions(arb_id=send_arb_id) as can_wrap: print("Probing for XCP info") for probe in probe_msgs: print("Sending probe message: {0}".format(probe)) can_wrap.send_single_message_with_callback(probe.message_data, probe.callback) time.sleep(2) print("Probing finished")
def service_discovery(args): """ Scans for supported DCM services. Prints a list of all supported services afterwards. :param args: A namespace containing src and dst """ send_arb_id = int_from_str_base(args.src) rcv_arb_id = int_from_str_base(args.dst) with CanActions(arb_id=send_arb_id) as can_wrap: print("Starting DCM service discovery") supported_services = [] def response_analyser_wrapper(service_id): print("\rProbing service 0x{0:02x} ({1} found)".format( service_id, len(supported_services)), end="") stdout.flush() def response_analyser(m): # Skip incoming messages with wrong arbitration ID if m.arbitration_id != rcv_arb_id: return # Skip replies where service is not supported if m.data[3] == 0x11: return # Service supported - add to list supported_services.append(m.data[2]) return response_analyser def done(): print("\nDone!") # Message to bruteforce - [length, service id] msg = [0x01, 0x00] # Index of service id byte in message service_index = 1 try: # Initiate bruteforce can_wrap.bruteforce_data(msg, service_index, response_analyser_wrapper, callback_end=done) finally: # Clear listeners can_wrap.notifier.listeners = [] print("") # Print id and name of all found services for service in supported_services: service_name = DCM_SERVICE_NAMES.get(service, "Unknown service") print("Supported service 0x{0:02x}: {1}".format( service, service_name))
def xcp_command_discovery(args): """Attempts to call all XCP commands and lists which ones are supported.""" global connect_reply, command_reply send_arb_id = int_from_str_base(args.src) rcv_arb_id = int_from_str_base(args.dst) connect_message = [0xFF, 0, 0, 0, 0, 0, 0, 0] def connect_callback_handler(msg): global connect_reply if msg.arbitration_id == rcv_arb_id: connect_reply = True print("XCP command discovery\n") print("COMMAND{0}SUPPORTED".format(" " * 17)) print("-" * 33) with CanActions(send_arb_id) as can_wrap: # Bruteforce against list of commands (excluding connect) for cmd_code, cmd_desc in XCP_COMMAND_CODES[1:]: # Connect connect_reply = False can_wrap.send_single_message_with_callback( connect_message, connect_callback_handler) connect_timestamp = datetime.now() while not connect_reply and datetime.now( ) - connect_timestamp < timedelta(seconds=3): pass if not connect_reply: print("ERROR: Connect timeout") exit() # Build message for current command cmd_msg = [cmd_code, 0, 0, 0, 0, 0, 0, 0] # Callback handler for current command def callback_handler(msg): global command_reply if msg.arbitration_id == rcv_arb_id: print("{0:<23} {1}".format(cmd_desc, msg.data[0] != 0xFE)) command_reply = True command_reply = False # Send, wait for reply, clear listeners and move on can_wrap.send_single_message_with_callback( cmd_msg, callback=callback_handler) command_timestamp = datetime.now() while not command_reply and datetime.now( ) - command_timestamp < timedelta(seconds=3): pass if not command_reply: print("ERROR: Command timeout") exit() can_wrap.clear_listeners() print("\nDone!")
def xcp_command_discovery(args): """Attempts to call all XCP commands and lists which ones are supported.""" global connect_reply, command_reply send_arb_id = int_from_str_base(args.src) rcv_arb_id = int_from_str_base(args.dst) connect_message = [0xFF, 0, 0, 0, 0, 0, 0, 0] def connect_callback_handler(msg): global connect_reply if msg.arbitration_id == rcv_arb_id: connect_reply = True print ("XCP command discovery\n") print ("COMMAND{0}SUPPORTED".format(" " * 17)) print ("-" * 33) with CanActions(send_arb_id) as can_wrap: # Bruteforce against list of commands (excluding connect) for cmd_code, cmd_desc in XCP_COMMAND_CODES[1:]: # Connect connect_reply = False can_wrap.send_single_message_with_callback(connect_message, connect_callback_handler) connect_timestamp = datetime.now() while not connect_reply and datetime.now() - connect_timestamp < timedelta(seconds=3): pass if not connect_reply: print ("ERROR: Connect timeout") exit() # Build message for current command cmd_msg = [cmd_code, 0, 0, 0, 0, 0, 0, 0] # Callback handler for current command def callback_handler(msg): global command_reply if msg.arbitration_id == rcv_arb_id: print ("{0:<23} {1}".format(cmd_desc, msg.data[0] != 0xFE)) command_reply = True command_reply = False # Send, wait for reply, clear listeners and move on can_wrap.send_single_message_with_callback(cmd_msg, callback=callback_handler) command_timestamp = datetime.now() while not command_reply and datetime.now() - command_timestamp < timedelta(seconds=3): pass if not command_reply: print ("ERROR: Command timeout") exit() can_wrap.clear_listeners() print ("\nDone!")
def dcm_discovery(args): """ Scans for diagnostics support by sending session control against different arbitration IDs. :param: args: A namespace containing min and max """ min_id = int_from_str_base(args.min) max_id = int_from_str_base(args.max) no_stop = args.nostop class Diagnostics: found = False with CanActions() as can_wrap: print("Starting diagnostics service discovery") def response_analyser_wrapper(arb_id): print "\rSending Diagnostic Session Control to 0x{0:04x}".format( arb_id), stdout.flush() def response_analyser(msg): # Catch both ok and negative response if len(msg.data) >= 2 and msg.data[1] in [0x50, 0x7F]: Diagnostics.found = True print( "\nFound diagnostics at arbitration ID 0x{0:04x}, " "reply at 0x{1:04x}".format(arb_id, msg.arbitration_id)) if no_stop == False: can_wrap.bruteforce_stop() return response_analyser def discovery_finished(s): if Diagnostics.found: print("\n{0}".format(s)) else: print( "\nDiagnostics service could not be found: {0}".format(s)) # Message to bruteforce - [length, session control, default session] message = insert_message_length([0x10, 0x01]) can_wrap.bruteforce_arbitration_id(message, response_analyser_wrapper, min_id=min_id, max_id=max_id, callback_end=discovery_finished)
def parse_messages(msgs): """ Parses a list of message strings and returns them in a [(arb_id, [data_byte])] format. :param msgs: List of message strings :return: List of (arb_id, [data_byte]) tuples """ message_list = [] msg = None try: for msg in msgs: msg_parts = msg.split("#", 1) arb_id = int_from_str_base(msg_parts[0]) if arb_id is None: raise ValueError("Invalid arbitration ID: '{0}'".format(msg_parts[0])) msg_data = [] # Check data length byte_list = msg_parts[1].split(".") if not 0 < len(byte_list) <= 8: raise ValueError("Invalid data length: {0}".format(len(byte_list))) # Validate data bytes for byte in byte_list: byte_int = int(byte, 16) if not 0x00 <= byte_int <= 0xff: raise ValueError("Invalid byte value: '{0}'".format(byte)) msg_data.append(byte_int) fixed_msg = arb_id, msg_data message_list.append(fixed_msg) return message_list except ValueError as e: print("Invalid message at position {0}: '{1}'\nFailure reason: {2}".format(len(message_list), msg, e)) exit()
def parse_messages(msgs): """ Parses a list of message strings and returns them in a [(arb_id, [data_byte])] format. :param msgs: List of message strings :return: List of (arb_id, [data_byte]) tuples """ message_list = [] try: for msg in msgs: msg_parts = msg.split("#", 1) arb_id = int_from_str_base(msg_parts[0]) if arb_id is None: raise ValueError("Invalid arbitration ID: '{0}'".format(msg_parts[0])) msg_data = [] # Check data length byte_list = msg_parts[1].split(".") if not 0 < len(byte_list) <= 8: raise ValueError("Invalid data length: {0}".format(len(byte_list))) # Validate data bytes for byte in byte_list: byte_int = int(byte, 16) if not 0x00 <= byte_int <= 0xff: raise ValueError("Invalid byte value: '{0}'".format(byte)) msg_data.append(byte_int) fixed_msg = arb_id, msg_data message_list.append(fixed_msg) return message_list except ValueError as e: print("Invalid message at position {0}: '{1}'\nFailure reason: {2}".format(len(message_list), msg, e)) exit()
def parse_messages(msgs, delay): """ Parses a list of message strings. :param delay: Delay between each message :param msgs: list of message strings :return: list of CanMessage instances """ message_list = [] msg = None try: for msg in msgs: msg_parts = msg.split("#", 1) arb_id = int_from_str_base(msg_parts[0]) if arb_id is None: raise ValueError("Invalid arbitration ID: '{0}'".format(msg_parts[0])) msg_data = [] # Check data length byte_list = msg_parts[1].split(".") if not 0 < len(byte_list) <= 8: raise ValueError("Invalid data length: {0}".format(len(byte_list))) # Validate data bytes for byte in byte_list: byte_int = int(byte, 16) if not 0x00 <= byte_int <= 0xff: raise ValueError("Invalid byte value: '{0}'".format(byte)) msg_data.append(byte_int) fixed_msg = CanMessage(arb_id, msg_data, delay) message_list.append(fixed_msg) # No delay before sending first message return message_list except ValueError as e: print("Invalid message at position {0}: '{1}'\nFailure reason: {2}".format(len(message_list), msg, e)) exit()
def service_discovery(args): """ Scans for supported DCM services. Prints a list of all supported services afterwards. :param args: A namespace containing src and dst """ send_arb_id = int_from_str_base(args.src) rcv_arb_id = int_from_str_base(args.dst) with CanActions(arb_id=send_arb_id) as can_wrap: print ("Starting DCM service discovery") supported_services = [] def response_analyser_wrapper(service_id): print "\rProbing service 0x{0:02x} ({1} found)".format(service_id, len(supported_services)), stdout.flush() def response_analyser(m): # Skip incoming messages with wrong arbitration ID if m.arbitration_id != rcv_arb_id: return # Skip replies where service is not supported if m.data[3] == 0x11: return # Service supported - add to list supported_services.append(m.data[2]) return response_analyser def done(): print ("\nDone!") # Message to bruteforce - [length, service id] msg = [0x01, 0x00] # Index of service id byte in message service_index = 1 try: # Initiate bruteforce can_wrap.bruteforce_data(msg, service_index, response_analyser_wrapper, callback_end=done) finally: # Clear listeners can_wrap.notifier.listeners = [] print ("") # Print id and name of all found services for service in supported_services: service_name = DCM_SERVICE_NAMES.get(service, "Unknown service") print ("Supported service 0x{0:02x}: {1}".format(service, service_name))
def list_int_from_str_base(line): """ Converts a given string to its list int representation. Uses CaringCaribou's int_from_str_base implementation. :param line: A given string that follows the format of (for example): "0xFF 0xFF 0xFF 0xFF". :return: Returns a list of ints representing the values in the string. For example: [0xFF, 0xFF, 0xFF, 0xFF] (with 0xFF in its int representation). """ temp = line.split() for i in range(len(temp)): temp[i] = int_from_str_base(temp[i]) return temp
def module_main(arg_list): """ Module main wrapper. This is the entry point of the module when called by cc.py :param arg_list: Module argument list passed by cc.py """ try: # Parse arguments args = parse_args(arg_list) # Parse arbitration ID from the arguments (this function resolves both base 10 and hex values) arbitration_id = int_from_str_base(args.arbId) # Time to actually do stuff foo(arbitration_id) except KeyboardInterrupt: print("\n\nTerminated by user")
def directive_send(arb_id, payload, response_handler): """ Sends a cansend directive. :param arb_id: The destination arbitration id. :param payload: The payload to be sent. :param response_handler: The callback handler that needs to be called when a response message is received. """ arb_id = "0x" + arb_id send_msg = payload_to_str_base(payload) with CanActions(int_from_str_base(arb_id)) as can_wrap: # Send the message on the CAN bus and register a callback # handler for incoming messages can_wrap.send_single_message_with_callback(list_int_from_str_base(send_msg), response_handler) # Letting callback handler be active for CALLBACK_HANDLER_DURATION seconds sleep(CALLBACK_HANDLER_DURATION)
def initiate_dump(handler, args): """ Adds a CAN message handler which should add found data to found_arb_ids. Prints all of these afterwards, sorted by the number of hits. :param handler: Message handler function :param args: Argument namespace (reversed sorting applied if args.reverse) """ whitelist = [int_from_str_base(x) for x in args.whitelist] if args.candump_format: format_func = msg_to_candump_format else: format_func = str def whitelist_handling(msg): if len(whitelist) == 0 or msg.arbitration_id in whitelist: handler(format_func(msg)) print("Dumping CAN traffic (press Ctrl+C to exit)".format(whitelist)) with CanActions() as can_wrap: can_wrap.add_listener(whitelist_handling) while True: pass
def subfunc_discovery(args): """ Scans for subfunctions of a given service. :param args: A namespace containing src, dst, service, show and i """ send_arb_id = int_from_str_base(args.src) rcv_arb_id = int_from_str_base(args.dst) service_id = int_from_str_base(args.service) show_data = args.show bruteforce_indices = args.i with CanActions(arb_id=send_arb_id) as can_wrap: found_sub_functions = [] print("Starting DCM sub-function discovery") def response_analyser_wrapper(data): print "\rProbing sub-function 0x{0:02x} data {1} (found: {2})".format( service_id, data, len(found_sub_functions)), stdout.flush() def response_analyser(msg): if msg.arbitration_id != rcv_arb_id: return # Response queued - do not handle if msg.data[:4] == [0x03, 0x7f, service_id, 0x78]: can_wrap.current_delay = 1.0 return # Catch ok status elif msg.data[1]-0x40 == service_id or\ (msg.data[1] == 0x7F and msg.data[3] not in [0x11, 0x12, 0x31, 0x78]): # TODO - more? found_sub_functions.append((data, [msg])) elif msg.data[0] == 0x10: # If response takes up multiple frames can_wrap.current_delay = 1.0 found_sub_functions.append((data, [msg])) if show_data: # Cool, give me the rest can_wrap.send([0x30]) else: # Fine, but I don't want the remaining data can_wrap.send([0x32]) elif show_data and msg.data[0] & 0xF0 == 0x20: # Parts following a 0x30 in multiple frame response (keep waiting) can_wrap.current_delay = 1.0 found_sub_functions[-1][1].append(msg) else: # We've got an answer - no reason to keep waiting can_wrap.current_delay = 0.0 return response_analyser def finished(s): print("\nDone: {0}".format(s)) try: # Message to bruteforce - [length, session control, default session] message = insert_message_length([service_id, 0x00, 0x00]) can_wrap.bruteforce_data_new(message, bruteforce_indices=bruteforce_indices, callback=response_analyser_wrapper, callback_end=finished) can_wrap.notifier.listeners = [] finally: # Print found functions if len(found_sub_functions) > 0: print("\n\nFound sub-functions for service 0x{0:02x} ({1}):\n".format( service_id, DCM_SERVICE_NAMES.get(service_id, "Unknown service"))) for (sub_function, msgs) in found_sub_functions: print("Sub-function {0}".format(" ".join(sub_function))) if show_data: for msg in msgs: print(" {0}".format(msg)) else: print("\n\nNo sub-functions were found")
def dcm_discovery(args): """ Scans for diagnostics support by sending session control against different arbitration IDs. :param: args: A namespace containing min and max """ min_id = int_from_str_base(args.min) max_id = int_from_str_base(args.max) no_stop = args.nostop blacklist = [int_from_str_base(b) for b in args.blacklist] valid_responses = [0x50, 0x7F] def scan_arbitration_ids_to_blacklist(scan_duration): print("Scanning for arbitration IDs to blacklist (-autoblacklist)") ids_to_blacklist = set() def response_handler(msg): if msg.data[1] in valid_responses: ids_to_blacklist.add(msg.arbitration_id) with CanActions() as can_actions: # Listen for matches can_actions.add_listener(response_handler) for i in range(scan_duration, 0, -1): print("\r Scanning... {0} seconds left, {1} found".format( i - 1, len(ids_to_blacklist)), end="") stdout.flush() time.sleep(1) print("") can_actions.clear_listeners() # Add found matches to blacklist for arb_id in ids_to_blacklist: blacklist.append(arb_id) # Perform automatic blacklist scanning if args.autoblacklist > 0: scan_arbitration_ids_to_blacklist(args.autoblacklist) class Diagnostics: found = False with CanActions() as can_wrap: print("Starting diagnostics service discovery") def response_analyser_wrapper(arb_id): print("\rSending Diagnostic Session Control to 0x{0:04x}".format( arb_id), end="") stdout.flush() def response_analyser(msg): # Ignore blacklisted arbitration IDs if msg.arbitration_id in blacklist: return # Catch both ok and negative response if len(msg.data) >= 2 and msg.data[1] in valid_responses: Diagnostics.found = True print("\nFound diagnostics at arbitration ID 0x{0:04x}, " "reply at 0x{1:04x}".format(arb_id, msg.arbitration_id)) if not no_stop: can_wrap.bruteforce_stop() return response_analyser def discovery_finished(s): if Diagnostics.found: print("\n{0}".format(s)) else: print( "\nDiagnostics service could not be found: {0}".format(s)) # Message to bruteforce - [length, session control, default session] message = insert_message_length([0x10, 0x01]) can_wrap.bruteforce_arbitration_id(message, response_analyser_wrapper, min_id=min_id, max_id=max_id, callback_end=discovery_finished)
def xcp_memory_dump(args): """ Performs a memory dump to file or stdout via XCP. :param args: A namespace containing src, dst, start, length and f """ send_arb_id = int_from_str_base(args.src) rcv_arb_id = int_from_str_base(args.dst) start_address = int_from_str_base(args.start) length = int_from_str_base(args.length) dump_file = args.f # TODO Implement support for larger segments against ECUs which support this (e.g. 0xfc for test board) max_segment_size = 0x7 global byte_counter, bytes_left, dump_complete, segment_counter, timeout_start # Timeout timer dump_complete = False # Counters for data length byte_counter = 0 segment_counter = 0 def handle_upload_reply(msg): global byte_counter, bytes_left, dump_complete, timeout_start, segment_counter if msg.arbitration_id != rcv_arb_id: return if msg.data[0] == 0xfe: decode_xcp_error(msg) return if msg.data[0] == 0xff: # Reset timeout timer timeout_start = datetime.now() # Calculate end index of data to handle end_index = min(8, bytes_left + 1) if dump_file: with open(dump_file, "ab") as outfile: outfile.write(bytearray(msg.data[1:end_index])) else: print(" ".join( ["{0:02x}".format(j) for j in msg.data[1:end_index]])) # Update counters byte_counter += max_segment_size bytes_left -= max_segment_size if bytes_left < 1: if dump_file: print "\rDumping segment {0} ({1} b, 0 b left)".format( segment_counter, length) print("Dump complete!") dump_complete = True elif byte_counter > max_segment_size - 1: # Dump another segment segment_counter += 1 if dump_file: # Print progress print "\rDumping segment {0} ({1} b, {2} b left)".format( segment_counter, ((segment_counter + 1) * max_segment_size + byte_counter), bytes_left), stdout.flush() byte_counter = 0 can_wrap.send_single_message_with_callback( [0xf5, min(max_segment_size, bytes_left)], handle_upload_reply) def handle_set_mta_reply(msg): if msg.arbitration_id != rcv_arb_id: return if msg.data[0] == 0xfe: decode_xcp_error(msg) return if msg.data[0] == 0xff: print("Set MTA acked") print("Dumping data:") # Initiate dumping if dump_file: print "\rDumping segment 0", can_wrap.send_single_message_with_callback( [0xf5, min(max_segment_size, bytes_left)], handle_upload_reply) else: print("Unexpected reply: {0}\n".format(msg)) def handle_connect_reply(msg): if msg.arbitration_id != rcv_arb_id: return if msg.data[0] == 0xfe: decode_xcp_error(msg) return if msg.data[0] == 0xff: print "Connected: Using", # Check connect reply to see whether to reverse byte order for MTA msb_format = msg.data[2] & 1 if msb_format: print("Motorola format (MSB lower)") else: print("Intel format (LSB lower)") r.reverse() can_wrap.send_single_message_with_callback( [0xf6, 0x00, 0x00, 0x00, r[0], r[1], r[2], r[3]], handle_set_mta_reply) else: print("Unexpected connect reply: {0}\n".format(msg)) # Calculate address bytes (4 bytes, least significant first) r = [] n = start_address bytes_left = length # Calculate start address (r is automatically reversed after connect if needed) n &= 0xFFFFFFFF for i in range(4): r.append(n & 0xFF) n >>= 8 # Make sure dump_file can be opened if specified (clearing it if it already exists) if dump_file: try: with open(dump_file, "w") as _: pass except IOError as e: print("Error when opening dump file:\n\n{0}".format(e)) return # Initialize with CanActions(arb_id=send_arb_id) as can_wrap: print("Attempting XCP memory dump") # Connect and prepare for dump can_wrap.send_single_message_with_callback([0xff], handle_connect_reply) # Idle timeout handling timeout_start = datetime.now() while not dump_complete and datetime.now() - timeout_start < timedelta( seconds=3): pass if not dump_complete: print("\nERROR: Dump ended due to idle timeout")
def xcp_memory_dump(args): """ Performs a memory dump to file or stdout via XCP. :param args: A namespace containing src, dst, start, length and f """ send_arb_id = int_from_str_base(args.src) rcv_arb_id = int_from_str_base(args.dst) start_address = int_from_str_base(args.start) length = int_from_str_base(args.length) dump_file = args.f # FIXME max size is 0xfc for test board max_segment_size = 0x7 global byte_counter, bytes_left, dump_complete, segment_counter, timeout_start # Timeout timer dump_complete = False # Counters for data length byte_counter = 0 segment_counter = 0 def handle_upload_reply(msg): global byte_counter, bytes_left, dump_complete, timeout_start, segment_counter if msg.arbitration_id != rcv_arb_id: return if msg.data[0] == 0xFE: decode_xcp_error(msg) return if msg.data[0] == 0xFF: # Reset timeout timer timeout_start = datetime.now() # Calculate end index of data to handle end_index = min(8, bytes_left + 1) if dump_file: with open(dump_file, "ab") as outfile: outfile.write(bytearray(msg.data[1:end_index])) else: print (" ".join(["{0:02x}".format(i) for i in msg.data[1:end_index]])) # Update counters byte_counter += 7 bytes_left -= 7 # FIXME Hmm if bytes_left < 1: if dump_file: print "\rDumping segment {0} ({1} b, 0 b left)".format(segment_counter, length) print ("Dump complete!") dump_complete = True elif byte_counter > max_segment_size - 1: # Dump another segment segment_counter += 1 if dump_file: # Print progress print "\rDumping segment {0} ({1} b, {2} b left)".format( segment_counter, ((segment_counter + 1) * max_segment_size + byte_counter), bytes_left ), stdout.flush() byte_counter = 0 can_wrap.send_single_message_with_callback( [0xF5, min(max_segment_size, bytes_left)], handle_upload_reply ) def handle_set_mta_reply(msg): if msg.arbitration_id != rcv_arb_id: return if msg.data[0] == 0xFE: decode_xcp_error(msg) return if msg.data[0] == 0xFF: print ("Set MTA acked") print ("Dumping data:") # Initiate dumping if dump_file: print "\rDumping segment 0", can_wrap.send_single_message_with_callback([0xF5, min(max_segment_size, bytes_left)], handle_upload_reply) else: print ("Unexpected reply: {0}\n".format(msg)) def handle_connect_reply(msg): if msg.arbitration_id != rcv_arb_id: return if msg.data[0] == 0xFE: decode_xcp_error(msg) return if msg.data[0] == 0xFF: print "Connected: Using", # Check connect reply to see whether to reverse byte order for MTA msb_format = msg.data[2] & 1 if msb_format: print ("Motorola format (MSB lower)") else: print ("Intel format (LSB lower)") r.reverse() can_wrap.send_single_message_with_callback( [0xF6, 0x00, 0x00, 0x00, r[0], r[1], r[2], r[3]], handle_set_mta_reply ) else: print ("Unexpected connect reply: {0}\n".format(msg)) # Calculate address bytes (4 bytes, least significant first) r = [] n = start_address bytes_left = length # Calculate start address (r is automatically reversed after connect if needed) n &= 0xFFFFFFFF for i in range(4): r.append(n & 0xFF) n >>= 8 # Make sure dump_file can be opened if specified (clearing it if it already exists) if dump_file: try: with open(dump_file, "w") as tmp: pass except IOError as e: print ("Error when opening dump file:\n\n{0}".format(e)) return # Initialize with CanActions(arb_id=send_arb_id) as can_wrap: print ("Attempting XCP memory dump") # Connect and prepare for dump can_wrap.send_single_message_with_callback([0xFF], handle_connect_reply) # Idle timeout handling timeout_start = datetime.now() while not dump_complete and datetime.now() - timeout_start < timedelta(seconds=3): pass if not dump_complete: print ("\nERROR: Dump ended due to idle timeout")
def subfunc_discovery(args): """ Scans for subfunctions of a given service. :param args: A namespace containing src, dst, service, show and i """ send_arb_id = int_from_str_base(args.src) rcv_arb_id = int_from_str_base(args.dst) service_id = int_from_str_base(args.service) show_data = args.show bruteforce_indices = args.i # Sanity checks all_valid = True for i in bruteforce_indices: if not 2 <= i <= 7: print( "Invalid bruteforce index '{0}' - must be in range 2-7".format( i)) all_valid = False if not all_valid: return with CanActions(arb_id=send_arb_id) as can_wrap: found_sub_functions = [] print("Starting DCM sub-function discovery") def response_analyser_wrapper(data): print("\rProbing sub-function 0x{0:02x} data {1} (found: {2})". format(service_id, data, len(found_sub_functions)), end="") stdout.flush() def response_analyser(msg): if msg.arbitration_id != rcv_arb_id: return # Response queued - do not handle if msg.data[:4] == [0x03, 0x7f, service_id, 0x78]: can_wrap.current_delay = 1.0 return # Catch ok status elif msg.data[1] - 0x40 == service_id or \ (msg.data[1] == 0x7F and msg.data[3] not in [0x11, 0x12, 0x31, 0x78]): found_sub_functions.append((data, [msg])) elif msg.data[0] == 0x10: # If response takes up multiple frames can_wrap.current_delay = 1.0 found_sub_functions.append((data, [msg])) if show_data: # Cool, give me the rest can_wrap.send([0x30]) else: # Fine, but I don't want the remaining data can_wrap.send([0x32]) elif show_data and msg.data[0] & 0xF0 == 0x20: # Parts following a 0x30 in multiple frame response (keep waiting) can_wrap.current_delay = 1.0 found_sub_functions[-1][1].append(msg) else: # We got an answer - no reason to keep waiting can_wrap.current_delay = 0.0 return response_analyser def finished(s): print("\nDone: {0}".format(s)) try: # Message to bruteforce - [length, session control, default session] message = insert_message_length([service_id, 0x00, 0x00]) can_wrap.bruteforce_data_new(message, bruteforce_indices=bruteforce_indices, callback=response_analyser_wrapper, callback_done=finished) can_wrap.notifier.listeners = [] finally: # Print found functions if len(found_sub_functions) > 0: print("\n\nFound sub-functions for service 0x{0:02x} ({1}):\n". format( service_id, DCM_SERVICE_NAMES.get(service_id, "Unknown service"))) for (sub_function, msgs) in found_sub_functions: print("Sub-function {0}".format(" ".join(sub_function))) if show_data: for message in msgs: print(" {0}".format(message)) else: print("\n\nNo sub-functions were found")
def dcm_dtc(args): """ Fetches and prints the Diagnostic Trouble Codes from a supported service (Mode $03) :param args: A namespace containing src, dst and clear """ send_arb_id = int_from_str_base(args.src) rcv_arb_id = int_from_str_base(args.dst) clear = args.clear big_data = [] big_data_size = 0 def dtc_type(x): return { 0: "P", 1: "C", 2: "B", 3: "U", }.get(x, "?") def decode_dtc(data): # Expects 2 bytes if len(data) != 2: return return dtc_type((data[0] & 0xC0) >> 6) + format( (data[0] & 0x30) >> 4) + format(data[0] & 0x0F, "01x") + format( data[1], "02x") def decode_dtc_pkt(msg): if msg.arbitration_id != rcv_arb_id: return return_packet = False # TODO: Are we sure that data byte 0 is 0x10, or should the check be against data[0] & 0x10 instead? if msg.data[0] == 0x10 and (msg.data[2] == 0x43 or msg.data[2] == 0x47): return_packet = True if (msg.data[0] & 0xF0) == 0x20: # We should probably set a state for this return_packet = True if msg.data[1] == 0x43: return_packet = True if msg.data[2] == 0x47: return_packet = True if not return_packet: return global big_data global big_data_size if big_data_size == 0 and (msg.data[1] == 0x43 or msg.data[1] == 0x47): # Single frame print("There are {0} DTCs".format(msg.data[2])) if msg.data[2] == 0: return if msg.data[0] > 2: print("DTC: {0}".format(decode_dtc(msg.data[3:5]))) if msg.data[0] > 4: print("DTC: {0}".format(decode_dtc(msg.data[5:6]))) if msg.data[0] > 6: print("DTC: {0}".format(decode_dtc(msg.data[7:9]))) if msg.data[0] == 0x10: # Multi Frame (First Frame) full_dlc = (msg.data[0] & 0x0F) + msg.data[1] print("There are {0} DTCs".format(msg.data[3])) print("DTC: {0}".format(decode_dtc(msg.data[4:6]))) print("DTC: {0}".format(decode_dtc(msg.data[6:8]))) can_wrap.send([0x30, 0x0, 0x0]) big_data_size = full_dlc - 6 if (msg.data[0] & 0xF0) == 0x20: # Consecutive if big_data_size > 8: big_data.extend(msg.data[1:]) big_data_size -= 7 else: big_data.extend(msg.data[1:big_data_size + 1]) big_data_size = 0 if big_data_size == 0: for i in range(0, len(big_data), 2): print("DTC: {0}".format(decode_dtc(big_data[i:i + 2]))) with CanActions(arb_id=send_arb_id) as can_wrap: if clear: can_wrap.send([0x01, 0x04]) print("Cleared DTCs and reset MIL") else: print("Fetching Diagnostic Trouble Codes") can_wrap.send_single_message_with_callback([0x01, 0x03], decode_dtc_pkt) time.sleep(0.5) print("Fetching Pending Diagnostic Trouble Codes") can_wrap.send_single_message_with_callback([0x01, 0x07], decode_dtc_pkt) time.sleep(1)
def dcm_dtc(args): """ Fetches and prints the Diagnostic Trouble Codes from a supported service (Mode $03) :param args: A namespace containing src, dst and clear """ send_arb_id = int_from_str_base(args.src) rcv_arb_id = int_from_str_base(args.dst) clear = args.clear big_data = [] big_data_size = 0 def dtc_type(x): return { 0: 'P', 1: 'C', 2: 'B', 3: 'U', }.get(x, "?") def decode_dtc(data): # Expects 2 bytes if len(data) != 2: return return dtc_type((data[0] & 0xC0) >> 6) + format((data[0] & 0x30) >> 4) + format(data[0] & 0x0F, '01x') + format(data[1], '02x') def decode_dtc_pkt(msg): if msg.arbitration_id != rcv_arb_id: return return_packet = False # TODO: Are we sure that data byte 0 is 0x10, or should the check be against data[0] & 0x10 instead? if msg.data[0] == 0x10 and (msg.data[2] == 0x43 or msg.data[2] == 0x47): return_packet = True if (msg.data[0] & 0xF0) == 0x20: # We should probably set a state for this return_packet = True if msg.data[1] == 0x43: return_packet = True if msg.data[2] == 0x47: return_packet = True if not return_packet: return global big_data global big_data_size if big_data_size == 0 and (msg.data[1] == 0x43 or msg.data[1] == 0x47): # Single frame print("There are {0} DTCs".format(msg.data[2])) if msg.data[2] == 0: return if msg.data[0] > 2: print("DTC: {0}".format(decode_dtc(msg.data[3:5]))) if msg.data[0] > 4: print("DTC: {0}".format(decode_dtc(msg.data[5:6]))) if msg.data[0] > 6: print("DTC: {0}".format(decode_dtc(msg.data[7:9]))) if msg.data[0] == 0x10: # Multi Frame (First Frame) full_dlc = (msg.data[0] & 0x0F) + msg.data[1] print("There are {0} DTCs".format(msg.data[3])) print("DTC: {0}".format(decode_dtc(msg.data[4:6]))) print("DTC: {0}".format(decode_dtc(msg.data[6:8]))) can_wrap.send([0x30,0x0,0x0]) big_data_size = full_dlc - 6 if (msg.data[0] & 0xF0) == 0x20: # Consecutive index = msg.data[0] & 0xF if big_data_size > 8: # big_data += msg.data[1:] big_data.extend(msg.data[1:]) big_data_size -= 7 else: # big_data += msg.data[1:big_data_size+1] big_data.extend(msg.data[1:big_data_size+1]) big_data_size = 0 if big_data_size == 0: for i in range(0,len(big_data), 2): print("DTC: {0}".format(decode_dtc(big_data[i:i+2]))) with CanActions(arb_id=send_arb_id) as can_wrap: if clear: can_wrap.send([0x01, 0x04]) print("Cleared DTCs and reset MIL") else: print("Fetching Diagnostic Trouble Codes") can_wrap.send_single_message_with_callback([0x01, 0x03], decode_dtc_pkt) time.sleep(0.5) print("Fetching Pending Diagnostic Trouble Codes") can_wrap.send_single_message_with_callback([0x01, 0x07], decode_dtc_pkt) time.sleep(1)