Example #1
0
def __security_seed_wrapper(args):
    """Wrapper used to initiate security seed dump"""
    arb_id_request = args.src
    arb_id_response = args.dst
    reset_type = args.reset
    session_type = args.sess_type
    level = args.sec_level
    num_seeds = args.num
    reset_delay = args.delay

    seed_list = []
    try:
        print("Security seed dump started. Press Ctrl+C to stop.\n")
        while num_seeds > len(seed_list) or num_seeds == 0:
            # Extended diagnostics
            response = extended_session(arb_id_request, arb_id_response,
                                        session_type)
            if not Iso14229_1.is_positive_response(response):
                print("Unable to enter extended session. Retrying...\n")
                continue

            # Request seed
            response = request_seed(arb_id_request, arb_id_response, level,
                                    None, None)
            if response is None:
                print("\nInvalid response")
            elif Iso14229_1.is_positive_response(response):
                seed_list.append(list_to_hex_str(response[2:]))
                print("Seed received: {}\t(Total captured: {})".format(
                    list_to_hex_str(response[2:]), len(seed_list)),
                      end="\r")
                stdout.flush()
            else:
                print_negative_response(response)
                break
            if reset_type:
                ecu_reset(arb_id_request, arb_id_response, reset_type, None)
                time.sleep(reset_delay)
    except KeyboardInterrupt:
        print("Interrupted by user.")
    except ValueError as e:
        print(e)
        return

    if len(seed_list) > 0:
        print("\n")
        print("Security Access Seeds captured:")
        for seed in seed_list:
            print(seed)
Example #2
0
def __ecu_reset_wrapper(args):
    """Wrapper used to initiate ECU Reset"""
    arb_id_request = args.src
    arb_id_response = args.dst
    reset_type = args.reset_type
    timeout = args.timeout

    print(
        "Sending ECU reset, type 0x{0:02x} to arbitration ID {1} (0x{1:02x})".
        format(reset_type, arb_id_request))
    try:
        response = ecu_reset(arb_id_request, arb_id_response, reset_type,
                             timeout)
    except ValueError as e:
        print("ValueError: {0}".format(e))
        return

    # Decode response
    if response is None:
        print("No response was received")
    else:
        response_length = len(response)
        if response_length == 0:
            # Empty response
            print("Received empty response")
        elif response_length == 1:
            # Invalid response length
            print(
                "Received response [{0:02x}] (1 byte), expected at least 2 bytes"
                .format(response[0], len(response)))
        elif Iso14229_1.is_positive_response(response):
            # Positive response handling
            response_service_id = response[0]
            subfunction = response[1]
            expected_response_id = Iso14229_1.get_service_response_id(
                Services.EcuReset.service_id)
            if response_service_id == expected_response_id and subfunction == reset_type:
                # Positive response
                print("Received positive response")
                if response_length > 2:
                    # Additional data can be seconds left to reset (powerDownTime) or manufacturer specific
                    additional_data = list_to_hex_str(response[2:], ",")
                    print("Response contains additional data: [{0}]".format(
                        additional_data))
            else:
                # Service and/or subfunction mismatch
                print(
                    "Response service ID 0x{0:02x} and subfunction 0x{1:02x} do not match expected values "
                    "0x{2:02x} and 0x{3:02x}".format(response_service_id,
                                                     subfunction,
                                                     expected_response_id,
                                                     reset_type))
        else:
            # Negative response handling
            nrc = response[1]
            nrc_description = NRC_NAMES.get(nrc, "Unknown NRC value")
            print(
                "Received negative response code (NRC) 0x{0:02x}: {1}".format(
                    nrc, nrc_description))
Example #3
0
def directive_str(arb_id, data):
    """
    Converts a directive to its string representation

    :param arb_id: message arbitration ID
    :param data: message data bytes
    :return: str representing directive
    """
    data = list_to_hex_str(data, "")
    directive = "{0:03X}#{1}".format(arb_id, data)
    return directive
Example #4
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(list_to_hex_str(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)
Example #5
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, list_to_hex_str(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
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!")
Example #7
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()
Example #8
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")
Example #9
0
 def print_msg_as_text(msg):
     print(list_to_hex_str(msg.data[1:], ""))
Example #10
0
 def __str__(self):
     return "[{0}]".format(list_to_hex_str(self.message_data, ", "))