Esempio n. 1
0
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)
Esempio n. 2
0
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)
Esempio n. 3
0
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)
Esempio n. 4
0
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)
Esempio n. 5
0
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")
Esempio n. 6
0
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")
Esempio n. 7
0
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))
Esempio n. 8
0
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!")
Esempio n. 9
0
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!")
Esempio n. 10
0
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)
Esempio n. 11
0
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()
Esempio n. 12
0
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()
Esempio n. 13
0
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()
Esempio n. 14
0
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))
Esempio n. 15
0
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
Esempio n. 16
0
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")
Esempio n. 17
0
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)
Esempio n. 18
0
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
Esempio n. 19
0
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
Esempio n. 20
0
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")
Esempio n. 21
0
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)
Esempio n. 22
0
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")
Esempio n. 23
0
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")
Esempio n. 24
0
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")
Esempio n. 25
0
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)
Esempio n. 26
0
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)