Esempio n. 1
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 = 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))
Esempio n. 2
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. 3
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. 4
0
def tester_present(args):
    send_arb_id = int_from_str_base(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
Esempio n. 5
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)
Esempio n. 6
0
def parse_args(args):
    """
    Argument parser for the dump module.

    :param args: List of arguments
    :return: Argument namespace
    """
    parser = argparse.ArgumentParser(
        prog="cc.py dump",
        formatter_class=argparse.RawDescriptionHelpFormatter,
        description="CAN traffic dump module for CaringCaribou",
        epilog="""Example usage:
  cc.py dump
  cc.py dump -s 1.0
  cc.py dump -f output.txt
  cc.py dump -c -f output.txt 0x733 0x734""")
    parser.add_argument("-f",
                        "--file",
                        metavar="F",
                        help="Write output to file F (default: stdout)")
    parser.add_argument("whitelist",
                        metavar="W",
                        nargs="*",
                        help="Arbitration ID to whitelist")
    parser.add_argument("-c",
                        action="store_true",
                        dest="candump_format",
                        help="Output on candump format")
    parser.add_argument("-s",
                        type=float,
                        metavar="SEC",
                        dest="separator_seconds",
                        help="Print separating line after SEC silent seconds")
    args = parser.parse_args(args)
    # Parse whitelist
    whitelist = []
    for item in args.whitelist:
        item_int = int_from_str_base(item)
        if item_int is None:
            raise ValueError(
                "Invalid value passed in whitelist argument: '{0}'".format(
                    item))
        whitelist.append(item_int)
    args.whitelist = whitelist
    return args
Esempio n. 7
0
def parse_messages(msgs, delay, pad):
    """
    Parses a list of message strings.

    :param delay: Delay between each message
    :param msgs: list of message strings
    :param pad: bool indicating whether messages should be padded to 8 bytes
    :return: list of CanMessage instances
    """
    message_list = []
    msg = None
    try:
        for msg in msgs:
            msg_parts = msg.split("#", 1)
            # Check arbitration ID
            arb_id = int_from_str_base(msg_parts[0])
            if arb_id is None:
                raise ValueError("Invalid arbitration ID: '{0}'".format(msg_parts[0]))
            if arb_id > ARBITRATION_ID_MAX_EXTENDED:
                raise ValueError("Arbitration ID too large (max is 0x{0:x})".format(ARBITRATION_ID_MAX_EXTENDED))
            # 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
            msg_data = []
            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)
            if pad:
                # Pad to 8 bytes
                msg_data.extend([PADDING_BYTE] * (8 - len(msg_data)))
            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. 8
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. 9
0
def module_main(args):
    """
    Dump module main wrapper.

    :param args: List of module arguments
    """
    args = parse_args(args)
    whitelist = [int_from_str_base(x) for x in args.whitelist]
    separator_seconds = args.separator_seconds
    candump_format = args.candump_format

    # Print to stdout
    if args.file is None:
        initiate_dump(print, whitelist, separator_seconds, candump_format)
    # Print to file
    else:
        try:
            with open(args.file, "w") as output_file:
                global count
                count = 0

                # Write file header
                header = file_header()
                output_file.write(header)

                def write_line_to_file(line):
                    global count
                    count += 1
                    print("\rMessages printed to file: {0}".format(count),
                          end="")
                    output_file.write("{0}\n".format(line))
                    stdout.flush()

                initiate_dump(write_line_to_file, whitelist, separator_seconds,
                              candump_format)
        except IOError as e:
            print("IOError: {0}".format(e))
Esempio n. 10
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, 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)
Esempio n. 11
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, 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")
Esempio n. 12
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):
            """
            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)
Esempio n. 13
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")
Esempio n. 14
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. 15
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)