Exemplo n.º 1
0
def tester_present(args):
    send_arb_id = args.src
    delay = args.delay
    suppress_positive_response = args.spr

    testerpresent_service_id = 0x3E

    if suppress_positive_response:
        sub_function = 0x80
    else:
        sub_function = 0x00

    message_data = [
        0x02, testerpresent_service_id, sub_function, 0x00, 0x00, 0x00, 0x00,
        0x00
    ]
    print("Sending TesterPresent to arbitration ID {0} (0x{0:02x})".format(
        send_arb_id))
    print("\nPress Ctrl+C to stop\n")
    with CanActions(arb_id=send_arb_id) as can_wrap:
        counter = 1
        while True:
            can_wrap.send(data=message_data)
            print("\rCounter:", counter, end="")
            stdout.flush()
            time.sleep(delay)
            counter += 1
Exemplo n.º 2
0
    def scan_arbitration_ids_to_blacklist(scan_duration):
        print("Scanning for arbitration IDs to blacklist (-autoblacklist)")
        ids_to_blacklist = set()

        def response_handler(msg):
            """
            Blacklists the arbitration ID of a message if it could be misinterpreted as valid diagnostic response

            :param msg: can.Message instance to check
            """
            if len(msg.data) > 1 and 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{0:> 3} 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)
Exemplo n.º 3
0
def initiate_dump(handler, whitelist, separator_seconds, candump_format):
    """
    Runs the 'handler' function on all incoming CAN messages.
    Filtering is controlled by the list 'args.whitelist'
    A separator is printed between messages if no messages have been handled in float 'args.separator_seconds'
    :param handler: function to call on all incoming messages
    :param whitelist: list of allowed arbitration IDs, or None to allow all
    :param separator_seconds: float seconds before printing a separator between messages, or None to never do this
    :param candump_format: bool indicating whether messages should be passed to 'handler' in candump str format
    """

    if candump_format:
        format_func = msg_to_candump_format
    else:
        format_func = str
    separator_enabled = separator_seconds is not None
    last_message_timestamp = datetime.datetime.min
    messages_since_last_separator = 0

    print("Dumping CAN traffic (press Ctrl+C to exit)".format(whitelist))
    with CanActions(notifier_enabled=False) as can_wrap:
        for msg in can_wrap.bus:
            # Separator handling
            if separator_enabled and messages_since_last_separator > 0:
                if (datetime.datetime.now() - last_message_timestamp
                    ).total_seconds() > separator_seconds:
                    # Print separator
                    handler(
                        "--- Count: {0}".format(messages_since_last_separator))
                    messages_since_last_separator = 0
            # Message handling
            if len(whitelist) == 0 or msg.arbitration_id in whitelist:
                handler(format_func(msg))
                last_message_timestamp = datetime.datetime.now()
                messages_since_last_separator += 1
Exemplo n.º 4
0
def start_listener(falling_sort):
    """
    Counts messages per arbitration ID. Prints a list of IDs afterwards, sorted by number of hits.

    :param falling_sort: bool indicating whether results should be sorted in falling order
    """
    found_arb_ids = Counter()
    try:
        # Listen for messages
        print("Running listener (press Ctrl+C to exit)")
        with CanActions(notifier_enabled=False) as can_wrap:
            for msg in can_wrap.bus:
                if msg.arbitration_id not in found_arb_ids:
                    print(
                        "\rLast ID: 0x{0:08x} ({1} unique arbitration IDs found)"
                        .format(msg.arbitration_id,
                                len(found_arb_ids) + 1),
                        end=" ")
                    stdout.flush()
                found_arb_ids[msg.arbitration_id] += 1
    except KeyboardInterrupt:
        # Print results
        if len(found_arb_ids) > 0:
            print("\n\nDetected arbitration IDs:")
            for (arb_id, hits) in sorted(found_arb_ids.items(),
                                         key=lambda x: x[1],
                                         reverse=falling_sort):
                print("Arb id 0x{0:08x} {1} hits".format(arb_id, hits))
        else:
            print("\nNo arbitration IDs were detected.")
Exemplo n.º 5
0
def replay_fuzz(directives, show_requests, show_responses):
    """
    Replay cansend directives from 'filename'

    :param directives: list of (int arb_id, list data) tuples
    :param show_requests: bool indicating whether requests should be printed to stdout
    :param show_responses: bool indicating whether responses should be printed to stdout
    """

    # Define a callback function which will handle incoming messages
    def response_handler(msg):
        if msg.arbitration_id != arb_id or list(msg.data) != data:
            if not show_requests:
                # Print last sent request
                print("Sent: {0}".format(directive))
            print("  Received: {0}".format(
                directive_str(msg.arbitration_id, msg.data)))

    arb_id = None
    data = None
    count = 0

    with CanActions() as can_wrap:
        if show_responses:
            # Enable callback handler for incoming messages
            can_wrap.add_listener(response_handler)
        for arb_id, data in directives:
            count += 1
            directive = directive_str(arb_id, data)
            can_wrap.send(data=data, arb_id=arb_id)
            if show_requests:
                print("Sending ({0}) {1}".format(count, directive))
            sleep(DELAY_BETWEEN_MESSAGES)
    print("Replay finished")
Exemplo n.º 6
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 = args.src
    rcv_arb_id = 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 = insert_message_length([0x00], pad=True)
        # 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))
Exemplo n.º 7
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!")
Exemplo n.º 8
0
def send_messages(messages, loop):
    """
    Sends a list of messages separated by a given delay.

    :param loop: bool indicating whether the message sequence should be looped (re-sent over and over)
    :param messages: List of messages, where a message has the format (arb_id, [data_byte])
    """
    with CanActions(notifier_enabled=False) as can_wrap:
        loop_counter = 0
        while True:
            for i in range(len(messages)):
                msg = messages[i]
                if i != 0 or loop_counter != 0:
                    sleep(msg.delay)
                print("  Arb_id: 0x{0:08x}, data: {1}".format(msg.arb_id, ["{0:02x}".format(a) for a in msg.data]))
                can_wrap.send(msg.data, msg.arb_id, msg.is_extended, msg.is_error, msg.is_remote)
            if not loop:
                break
            loop_counter += 1
Exemplo n.º 9
0
def do_stuff(my_arbitration_id):
    """
    Performs some example operations, such as sending and receiving CAN messages.

    :param my_arbitration_id: The default arbitration id to use when sending messages
    :type my_arbitration_id: int
    """
    # The notifier should only be enabled when handling incoming traffic using callbacks
    use_notifier = False
    # Setup CanActions wrapper to use for receiving and sending messages
    with CanActions(arb_id=my_arbitration_id,
                    notifier_enabled=use_notifier) as can_wrap:
        # Define message contents
        my_message = [0x11, 0x22, 0x33, 0x44]
        # Send message using the default arbitration ID for can_wrap
        can_wrap.send(data=my_message)

        # Send the same message again, but on a custom arbitration ID this time
        my_custom_arbitration_id = 0x123ABC
        can_wrap.send(data=my_message, arb_id=my_custom_arbitration_id)

        # Listen for incoming traffic for a while
        duration_seconds = 1.0
        start_time = time.time()
        end_time = start_time + duration_seconds
        while time.time() < end_time:
            # Check if a message is available
            msg = can_wrap.bus.recv(0)
            if msg is None:
                # No message was available right now - continue listening loop
                continue
            # If we reach here, a message was received. Let's print it!
            print("Received a message on channel", msg.channel)
            print("  Arb ID: 0x{0:x} ({0})".format(msg.arbitration_id))
            data_string = list_to_hex_str(msg.data, ".")
            print("  Data:  ", data_string)
            # Module logic for message handling goes here
            if msg.arbitration_id < 0x10:
                print("  That was a low arbitration ID!")

    # When we reach here, can_wrap has been closed
    print("\nDone!")
Exemplo n.º 10
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), end="")
            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)
Exemplo n.º 11
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
Exemplo n.º 12
0
def start_listener(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)
    """
    try:
        print("Starting listener (press Ctrl+C to exit)")
        with CanActions() as can_wrap:
            can_wrap.add_listener(handler)
            while True:
                pass
    finally:
        if len(found_arb_ids) > 0:
            print("\n\nDetected arbitration IDs:")
            for (arb_id, hits) in sorted(found_arb_ids.items(),
                                         key=lambda x: x[1],
                                         reverse=args.reverse):
                print("Arb id 0x{0:08x} {1} hits".format(arb_id, hits))
        else:
            print("No arbitration IDs were detected.")
Exemplo n.º 13
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 = args.min
    max_id = args.max
    blacklist = set(args.blacklist)
    blacklist_duration = args.autoblacklist
    hit_counter = 0

    def is_valid_response(msg):
        """
        Returns a bool indicating whether 'data' is a valid XCP response

        :param data: list of message data bytes
        :return: True if data is a valid XCP response,
                 False otherwise
        """
        data = msg.data
        return (len(data) > 1 and data[0] == 0xff and any(data[1:])) or \
               (len(data) > 0 and data[0] == 0xfe)

    if blacklist_duration > 0:
        # Perform automatic blacklist scanning
        with CanActions(notifier_enabled=False) as blacklist_wrap:
            blacklist |= auto_blacklist(blacklist_wrap.bus, blacklist_duration,
                                        is_valid_response, True)

    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), end="")
            stdout.flush()

            def response_analyser(msg):
                global hit_counter
                # Ignore blacklisted arbitration IDs
                if msg.arbitration_id in blacklist:
                    return
                # 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)
Exemplo n.º 14
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)
    blacklist = [int_from_str_base(b) for b in args.blacklist]
    hit_counter = 0

    def is_valid_response(data):
        """
        Returns a bool indicating whether 'data' is a valid XCP response

        :param data: list of message data bytes
        :return: True if data is a valid XCP response,
                 False otherwise
        """
        return (len(data) > 1 and data[0] == 0xff and any(data[1:])) or \
               (len(data) > 0 and data[0] == 0xfe)

    def scan_arbitration_ids_to_blacklist(scan_duration):
        print("Scanning for arbitration IDs to blacklist (-autoblacklist)")
        ids_to_blacklist = set()

        def response_handler(msg):
            """
            Blacklists the arbitration ID of a message if it could be misinterpreted as valid diagnostic response

            :param msg: can.Message instance to check
            """
            if is_valid_response(msg.data):
                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{0:> 3} 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)

    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), end="")
            stdout.flush()

            def response_analyser(msg):
                global hit_counter
                # Ignore blacklisted arbitration IDs
                if msg.arbitration_id in blacklist:
                    return
                # 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)
Exemplo n.º 15
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 = args.src
    rcv_arb_id = 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, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00])
            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:
            msg = insert_message_length([0x04], pad=True)
            can_wrap.send(msg)
            print("Cleared DTCs and reset MIL")
        else:
            print("Fetching Diagnostic Trouble Codes")
            msg = insert_message_length([0x03], pad=True)
            can_wrap.send_single_message_with_callback(msg, decode_dtc_pkt)
            time.sleep(0.5)
            print("Fetching Pending Diagnostic Trouble Codes")
            msg = insert_message_length([0x07], pad=True)
            can_wrap.send_single_message_with_callback(msg, decode_dtc_pkt)
            time.sleep(1)
Exemplo n.º 16
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 = args.src
    rcv_arb_id = args.dst
    service_id = 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, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00])
                    else:
                        # Fine, but I don't want the remaining data
                        can_wrap.send(
                            [0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00])
                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], pad=True)
            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")
Exemplo n.º 17
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")
Exemplo n.º 18
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),
                          end="")
                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),
                          end="")
                    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", end="")
            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", end=" ")
            # 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")
Exemplo n.º 19
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 = args.min
    max_id = args.max
    no_stop = args.nostop
    blacklist = 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):
            """
            Blacklists the arbitration ID of a message if it could be misinterpreted as valid diagnostic response

            :param msg: can.Message instance to check
            """
            if len(msg.data) > 1 and 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{0:> 3} 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], pad=True)
        can_wrap.bruteforce_arbitration_id(message,
                                           response_analyser_wrapper,
                                           min_id=min_id,
                                           max_id=max_id,
                                           callback_end=discovery_finished)
Exemplo n.º 20
0
def identify_fuzz(all_composites, show_responses):
    """
    Replays a list of composites causing an effect, prompting for input to help isolate the message causing the effect

    :param all_composites: list of composites
    :param show_responses: bool indicating whether responses should be printed to stdout
    :return: str directive if message is found,
             None otherwise
    """
    def response_handler(msg):
        if msg.arbitration_id != arb_id or list(msg.data) != data:
            response_directive = directive_str(msg.arbitration_id, msg.data)
            print("  Received {0}".format(response_directive))

    arb_id = None
    data = None
    composites = None
    directive = None
    repeat = False

    with CanActions() as can_wrap:
        try:
            # Send all messages in first round
            gen = split_lists(all_composites, 1)
            while True:
                print()
                if repeat:
                    # Keep previous list of messages to send
                    repeat = False
                else:
                    # Get next list of messages to send
                    composites = next(gen)
                if show_responses:
                    # Enable callback handler for incoming messages
                    can_wrap.add_listener(response_handler)
                # Send messages
                for index in range(len(composites)):
                    composite = composites[index]
                    arb_id = composite[0]
                    data = composite[1]
                    directive = directive_str(arb_id, data)
                    can_wrap.send(data=data, arb_id=arb_id)
                    print("Sending ({0}/{1}) {2}".format(
                        index + 1, len(composites), directive))
                    sleep(DELAY_BETWEEN_MESSAGES)
                # Disable callback handler for incoming messages
                can_wrap.clear_listeners()

                # Get user input
                print("\nWas the desired effect observed?")
                valid_response = False
                while not valid_response:
                    valid_response = True

                    response = input(
                        "(y)es | (n)o | (r)eplay | (q)uit: ").lower()

                    if response == "y":
                        if len(composites) == 1:
                            # Single message found
                            print("\nMatch found! Message causing effect: {0}".
                                  format(directive))
                            return directive
                        else:
                            # Split into even smaller lists of messages
                            gen = split_lists(composites,
                                              REPLAY_NUMBER_OF_SUB_LISTS)
                    elif response == "n":
                        # Try next list of messages
                        pass
                    elif response == "r":
                        # Repeat batch
                        repeat = True
                    elif response == "q":
                        # Quit
                        return None
                    else:
                        # Invalid choice - ask again
                        print("Invalid choice")
                        valid_response = False
        except StopIteration:
            # No more messages to try - give up
            print("\nNo match was found.")
            return None
Exemplo n.º 21
0
def mutate_fuzz(initial_arb_id,
                initial_data,
                arb_id_bitmap,
                data_bitmap,
                filename=None,
                show_status=True,
                show_responses=False,
                seed=None):
    """
    Performs mutation based fuzzing of selected nibbles of a given arbitration ID and data.
    Nibble selection is controlled by bool lists 'arb_id_bitmap' and 'data_bitmap'.

    :param initial_arb_id: list of nibbles (ints in interval 0x0-0xF, inclusive)
    :param initial_data: list of nibbles (ints in interval 0x0-0xF, inclusive)
    :param arb_id_bitmap: list of bool values, representing which nibbles of 'initial_arb_id' to bruteforce
    :param data_bitmap: list of bool values, representing which nibbles of 'initial_data' to bruteforce
    :param filename: file to write cansend directives to
    :param show_status: bool indicating whether current message and counter should be printed to stdout
    :param show_responses: bool indicating whether responses should be printed to stdout
    :param seed: use given seed instead of random seed
    """
    # Seed handling
    set_seed(seed)

    def response_handler(msg):
        # Callback handler for printing incoming messages
        if msg.arbitration_id != arb_id or list(msg.data) != data:
            response_directive = directive_str(msg.arbitration_id, msg.data)
            print("  Received {0}".format(response_directive))

    number_of_nibbles_to_fuzz_arb_id = sum(arb_id_bitmap)
    number_of_nibbles_to_fuzz_data = sum(data_bitmap)

    file_logging_enabled = filename is not None
    output_file = None

    # Set initial values - needed in case they are static
    data = None
    arb_id = None
    if number_of_nibbles_to_fuzz_data == 0:
        data = apply_fuzzed_data(initial_data, [], data_bitmap)
    if number_of_nibbles_to_fuzz_arb_id == 0:
        arb_id = int_from_byte_list(
            apply_fuzzed_data(initial_arb_id, [], arb_id_bitmap))

    try:
        if file_logging_enabled:
            output_file = open(filename, "a")
        with CanActions() as can_wrap:
            if show_responses:
                can_wrap.add_listener(response_handler)
            message_count = 0
            while True:
                if number_of_nibbles_to_fuzz_arb_id > 0:
                    # Mutate arbitration ID
                    fuzzed_nibbles_arb_id = [
                        random.randint(0, 0xF)
                        for _ in range(number_of_nibbles_to_fuzz_arb_id)
                    ]
                    arb_id_bytes = apply_fuzzed_data(initial_arb_id,
                                                     fuzzed_nibbles_arb_id,
                                                     arb_id_bitmap)
                    arb_id = int_from_byte_list(arb_id_bytes)

                if number_of_nibbles_to_fuzz_data > 0:
                    # Mutate data
                    fuzzed_nibbles_data = [
                        random.randint(0, 0xF)
                        for _ in range(number_of_nibbles_to_fuzz_data)
                    ]
                    data = apply_fuzzed_data(initial_data, fuzzed_nibbles_data,
                                             data_bitmap)

                if show_status:
                    print("\rSending {0:04x} # {1} ({2})".format(
                        arb_id, list_to_hex_str(data, " "), message_count),
                          end="")
                    stdout.flush()

                can_wrap.send(data, arb_id)
                message_count += 1

                # Log to file
                if file_logging_enabled:
                    write_directive_to_file_handle(output_file, arb_id, data)
                sleep(DELAY_BETWEEN_MESSAGES)
    except KeyboardInterrupt:
        if show_status:
            print()
    finally:
        if output_file is not None:
            output_file.close()
Exemplo n.º 22
0
def bruteforce_fuzz(arb_id,
                    initial_data,
                    data_bitmap,
                    filename=None,
                    start_index=0,
                    show_progress=True,
                    show_responses=True):
    """
    Performs a brute force of selected data nibbles for a given arbitration ID.
    Nibble selection is controlled by bool list 'data_bitmap'.

    Example:
    bruteforce_fuzz(0x123, [0x1, 0x2, 0xA, 0xB], [True, False, False, True])
    will cause the following messages to be sent:

    0x123#02A0
    0x123#02A1
    0x123#02A2
    (...)
    0x123#02AF
    0x123#12A0
    0x123#12A1
    (...)
    0x123#F2AF

    :param arb_id: int arbitration ID
    :param initial_data: list of nibbles (ints in interval 0x0-0xF, inclusive)
    :param data_bitmap: list of bool values, representing which nibbles of 'initial_data' to bruteforce
    :param filename: file to write cansend directives to
    :param start_index: int index to start at (can be used to resume interrupted session)
    :param show_progress: bool indicating whether progress should be printed to stdout
    :param show_responses: bool indicating whether responses should be printed to stdout
    """
    # Sanity checks
    if not 2 <= len(initial_data) <= 16:
        raise ValueError(
            "Invalid initial data: must be between 2 and 16 nibbles")
    if not len(initial_data) % 2 == 0:
        raise ValueError("Invalid initial data: must have an even length")
    if not len(initial_data) == len(data_bitmap):
        raise ValueError(
            "Initial data ({0}) and data bitmap ({1}) must have the same length"
            .format(len(initial_data), len(data_bitmap)))

    number_of_nibbles_to_bruteforce = sum(data_bitmap)
    end_index = 16**number_of_nibbles_to_bruteforce

    if not 0 <= start_index <= end_index:
        raise ValueError(
            "Invalid start index '{0}', current range is [0-{1}]".format(
                start_index, end_index))

    def response_handler(msg):
        # Callback handler for printing incoming messages
        if msg.arbitration_id != arb_id or list(msg.data) != output_data:
            response_directive = directive_str(msg.arbitration_id, msg.data)
            print("  Received {0}".format(response_directive))

    # Initialize fuzzed nibble generator
    nibble_values = range(0xF + 1)
    fuzz_data = product(nibble_values, repeat=number_of_nibbles_to_bruteforce)

    file_logging_enabled = filename is not None
    output_file = None
    output_data = []
    try:
        if file_logging_enabled:
            output_file = open(filename, "a")
        with CanActions(arb_id=arb_id) as can_wrap:
            if show_progress:
                print("Starting at index {0} of {1}\n".format(
                    start_index, end_index))
            if show_responses:
                can_wrap.add_listener(response_handler)
            message_count = 0
            # Traverse all outputs from fuzz generator
            for current_fuzzed_nibbles in fuzz_data:
                # Skip handling until start_index is met
                if message_count < start_index:
                    message_count += 1
                    continue
                # Apply fuzzed data
                output_data = apply_fuzzed_data(initial_data,
                                                current_fuzzed_nibbles,
                                                data_bitmap)
                # Send message
                can_wrap.send(output_data)
                message_count += 1
                if show_progress:
                    print("\rCurrent: {0} Index: {1}".format(
                        list_to_hex_str(output_data, " "), message_count),
                          end="")
                    stdout.flush()
                # Log to file
                if file_logging_enabled:
                    write_directive_to_file_handle(output_file, arb_id,
                                                   output_data)
                sleep(DELAY_BETWEEN_MESSAGES)
            if show_progress:
                print()
    finally:
        if output_file is not None:
            output_file.close()
    if show_progress:
        print("Brute force finished")
Exemplo n.º 23
0
def random_fuzz(static_arb_id=None,
                static_data=None,
                filename=None,
                min_id=ARBITRATION_ID_MIN,
                max_id=ARBITRATION_ID_MAX,
                min_data_length=MIN_DATA_LENGTH,
                max_data_length=MAX_DATA_LENGTH,
                show_status=True,
                seed=None):
    """
    A simple random fuzzer algorithm, which sends random or static data to random or static arbitration IDs

    :param static_arb_id: int representing static arbitration ID
    :param static_data: list of bytes representing static data
    :param filename: file to write cansend directives to
    :param min_id: minimum allowed arbitration ID
    :param max_id: maximum allowed arbitration ID
    :param min_data_length: minimum allowed data length
    :param max_data_length: maximum allowed data length
    :param show_status: bool indicating whether current message and counter should be printed to stdout
    :param seed: use given seed instead of random seed
    """
    # Sanity checks
    if static_arb_id is not None and static_data is not None:
        raise ValueError(
            "Both static arbitration ID and static data cannot be set at the same time"
        )
    if not 0 <= min_id < max_id <= ARBITRATION_ID_MAX:
        raise ValueError("Invalid value for min_id and/or max_id")
    if not MIN_DATA_LENGTH <= min_data_length <= max_data_length <= MAX_DATA_LENGTH:
        raise ValueError(
            "Invalid value for min_data_length ({0}) and/or max_data_length ({1})"
            .format(min_data_length, max_data_length))
    if static_data is not None and len(static_data) > MAX_DATA_LENGTH:
        raise ValueError(
            "static_data ({0} bytes) must not be more than {1} bytes long".
            format(len(static_data), MAX_DATA_LENGTH))

    # Seed handling
    set_seed(seed)

    # Define a callback function which will handle incoming messages
    def response_handler(msg):
        if msg.arbitration_id != arb_id or list(msg.data) != data:
            directive = directive_str(arb_id, data)
            print("\rDirective: {0} (message {1})".format(
                directive, message_count))
            print("  Received message: {0}".format(msg))

    arb_id = None
    data = None
    file_logging_enabled = filename is not None
    output_file = None
    try:
        if file_logging_enabled:
            output_file = open(filename, "a")
        with CanActions() as can_wrap:
            # Register callback handler for incoming messages
            can_wrap.add_listener(response_handler)
            message_count = 0
            # Fuzzing logic
            while True:
                # Set arbitration ID
                if static_arb_id is None:
                    # Use a random arbitration ID
                    arb_id = get_random_arbitration_id(min_id, max_id)
                else:
                    # Use the static arbitration ID
                    arb_id = static_arb_id

                # Set data
                if static_data is None:
                    data = get_random_data(min_data_length, max_data_length)
                else:
                    data = static_data

                if show_status:
                    print("\rMessages sent: {0}".format(message_count), end="")
                    stdout.flush()

                # Send message
                can_wrap.send(data=data, arb_id=arb_id)
                message_count += 1

                # Log to file
                if file_logging_enabled:
                    write_directive_to_file_handle(output_file, arb_id, data)
                sleep(DELAY_BETWEEN_MESSAGES)
    except IOError as e:
        print("ERROR: {0}".format(e))
    finally:
        if output_file is not None:
            output_file.close()