From 631055fa3987d4cd99c1b50b2ad530a629e6edf8 Mon Sep 17 00:00:00 2001 From: Drew Hintz Date: Fri, 10 Nov 2017 22:35:04 -0800 Subject: [PATCH 1/3] Given an interesting CSV file of CAN messages and a list of background CAN messages, print which bits in the interesting file have never appeared in the background files. --- examples/can_unique.py | 80 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 examples/can_unique.py diff --git a/examples/can_unique.py b/examples/can_unique.py new file mode 100644 index 00000000000000..8b836cc400f4aa --- /dev/null +++ b/examples/can_unique.py @@ -0,0 +1,80 @@ +#!/usr/bin/env python + +# Given an interesting CSV file of CAN messages and a list of background CAN +# messages, print which bits in the interesting file have never appeared +# in the background files. + +# Expects the CSV file to be in the format from can_logger.py +# Bus,MessageID,Message +# 0,344,c000c00000000000 + +import binascii +import csv +import sys +from panda import Panda + +class Message(): + """Details about a specific message ID.""" + def __init__(self, message_id): + self.message_id = message_id + self.data = {} # keyed by hex string encoded message data + self.ones = [0] * 8 # bit set if 1 is seen + self.zeros = [0] * 8 # bit set if 0 has been seen + + def printBitDiff(self, other): + """Prints bits that are set or cleared compared to other background.""" + for i in xrange(len(self.ones)): + new_ones = ((~other.ones[i]) & 0xff) & self.ones[i] + if new_ones: + print 'id %s new one at byte %d bitmask %d' % ( + self.message_id, i, new_ones) + new_zeros = ((~other.zeros[i]) & 0xff) & self.zeros[i] + if new_zeros: + print 'id %s new zero at byte %d bitmask %d' % ( + self.message_id, i, new_zeros) + + +class Info(): + """A collection of Messages.""" + + def __init__(self): + self.messages = {} # keyed by MessageID + + def load(self, filename): + """Given a CSV file, adds information about message IDs and their values.""" + with open(filename, 'rb') as input: + reader = csv.reader(input) + next(reader, None) # skip the CSV header + for row in reader: + message_id = row[1] + data = row[2] + if message_id not in self.messages: + self.messages[message_id] = Message(message_id) + message = self.messages[message_id] + if data not in self.messages[message_id].data: + message.data[data] = True + bytes = bytearray.fromhex(data) + for i in xrange(len(bytes)): + message.ones[i] = message.ones[i] | int(bytes[i]) + # Inverts the data and masks it to a byte to get the zeros as ones. + message.zeros[i] = message.zeros[i] | ( (~int(bytes[i])) & 0xff) + +def PrintUnique(interesting_file, background_files): + background = Info() + for background_file in background_files: + background.load(background_file) + interesting = Info() + interesting.load(interesting_file) + for message_id in interesting.messages: + if message_id not in background.messages: + print 'New message_id: %s' % message_id + else: + interesting.messages[message_id].printBitDiff( + background.messages[message_id]) + + +if __name__ == "__main__": + if len(sys.argv) < 3: + print 'Usage:\n%s interesting.csv background*.csv' % sys.argv[0] + sys.exit(0) + PrintUnique(sys.argv[1], sys.argv[2:]) From 67d2e01fa8044b8bd1402799682b5a769b8c39f5 Mon Sep 17 00:00:00 2001 From: Drew Hintz Date: Fri, 10 Nov 2017 23:28:44 -0800 Subject: [PATCH 2/3] guide to using can_unique.py --- examples/can_unique.md | 103 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 examples/can_unique.md diff --git a/examples/can_unique.md b/examples/can_unique.md new file mode 100644 index 00000000000000..bf316940d3adb9 --- /dev/null +++ b/examples/can_unique.md @@ -0,0 +1,103 @@ +# How to use can_unique.py to reverse engineer a single bit field + +Let's say our goal is to find the CAN message indicating that the driver's door is either open or closed. +The following process is great for simple single-bit messages. +However for frequently changing values, such as RPM or speed, Cabana's graphical plots are probably better to use. + + +First record a few minutes of background CAN messages with all the doors closed and save it in background.csv: +``` +./can_logger.py +mv output.csv background.csv +``` +Then run can_logger.py for a few seconds while performing the action you're interested, such as opening and then closing the +front-left door and save it as door-fl-1.csv +Repeat the process and save it as door-f1-2.csv to have an easy way to confirm any suspicions. + +Now we'll use can_unique.py to look for unique bits: +``` +$ ./can_unique.py door-fl-1.csv background* +id 820 new one at byte 2 bitmask 2 +id 520 new one at byte 3 bitmask 7 +id 520 new zero at byte 3 bitmask 8 +id 520 new one at byte 5 bitmask 6 +id 520 new zero at byte 5 bitmask 9 +id 559 new zero at byte 6 bitmask 4 +id 804 new one at byte 5 bitmask 2 +id 804 new zero at byte 5 bitmask 1 + +$ ./can_unique.py door-fl-2.csv background* +id 672 new one at byte 3 bitmask 3 +id 820 new one at byte 2 bitmask 2 +id 520 new one at byte 3 bitmask 7 +id 520 new zero at byte 3 bitmask 8 +id 520 new one at byte 5 bitmask 6 +id 520 new zero at byte 5 bitmask 9 +id 559 new zero at byte 6 bitmask 4 +``` + +One of these bits hopefully indicates that the driver's door is open. +Let's go through each message ID to figure out which one is correct. +We expect any correct bits to have changed in both runs. +We can rule out 804 because it only occurred in the first run. +We can rule out 672 because it only occurred in the second run. +That leaves us with these message IDs: 820, 520, 559. Let's take a closer look at each one. + +``` +$ fgrep ,559, door-fl-1.csv |head +0,559,00ff0000000024f0 +0,559,00ff000000004464 +0,559,00ff0000000054a9 +0,559,00ff0000000064e3 +0,559,00ff00000000742e +0,559,00ff000000008451 +0,559,00ff00000000949c +0,559,00ff00000000a4d6 +0,559,00ff00000000b41b +0,559,00ff00000000c442 +``` +Message ID 559 looks like an incrementing value, so it's not what we're looking for. + +``` +$ fgrep ,520, door-fl-2.csv +0,520,26ff00f8a1890000 +0,520,26ff00f8a2890000 +0,520,26ff00f8a2890000 +0,520,26ff00f8a1890000 +0,520,26ff00f8a2890000 +0,520,26ff00f8a1890000 +0,520,26ff00f8a2890000 +0,520,26ff00f8a1890000 +0,520,26ff00f8a2890000 +0,520,26ff00f8a1890000 +0,520,26ff00f8a2890000 +0,520,26ff00f8a1890000 +``` +Message ID 520 oscillates between two values. However I only opened and closed the door once, so this is probably not it. + +``` +$ fgrep ,820, door-fl-1.csv +0,820,44000100a500c802 +0,820,44000100a500c803 +0,820,44000300a500c803 +0,820,44000300a500c802 +0,820,44000300a500c802 +0,820,44000300a500c802 +0,820,44000100a500c802 +0,820,44000100a500c802 +0,820,44000100a500c802 +``` +Message ID 820 looks promising! It starts off at 44000100a500c802 when the door is closed. +When the door is open it goes to 44000300a500c802. +Then when the door is closed again, it goes back to 44000100a500c802. +Let's confirm by looking at the data from our other run: +``` +$ fgrep ,820, door-fl-2.csv +0,820,44000100a500c802 +0,820,44000300a500c802 +0,820,44000100a500c802 +``` +Perfect! We now know that message id 820 at byte 2 bitmask 2 is set if the driver's door is open. +If we repeat the process with the front passenger's door, +then we'll find that message id 820 at byte 2 bitmask 4 is set if the front-right door is open. +This confirms our finding because it's common for similar signals to be near each other. From a5f99b53460aaeb1e4997dd403f2ce6c6df3a6d7 Mon Sep 17 00:00:00 2001 From: Drew Hintz Date: Mon, 20 Nov 2017 22:27:25 -0800 Subject: [PATCH 3/3] update can_unique.py for new can_logger.py format --- examples/can_logger.py | 14 +++++++------- examples/can_unique.py | 21 ++++++++++++++++----- 2 files changed, 23 insertions(+), 12 deletions(-) mode change 100644 => 100755 examples/can_unique.py diff --git a/examples/can_logger.py b/examples/can_logger.py index 6c1edfe48c66e2..05c28a26df1228 100755 --- a/examples/can_logger.py +++ b/examples/can_logger.py @@ -6,14 +6,14 @@ from panda import Panda def can_logger(): - + try: print("Trying to connect to Panda over USB...") p = Panda() - + except AssertionError: print("USB connection failed. Trying WiFi...") - + try: p = Panda("WIFI") except: @@ -26,24 +26,24 @@ def can_logger(): #Write Header csvwriter.writerow(['Bus', 'MessageID', 'Message', 'MessageLength']) print("Writing csv file output.csv. Press Ctrl-C to exit...\n") - + bus0_msg_cnt = 0 bus1_msg_cnt = 0 bus2_msg_cnt = 0 - + while True: can_recv = p.can_recv() for address, _, dat, src in can_recv: csvwriter.writerow([str(src), str(hex(address)), "0x" + binascii.hexlify(dat), len(dat)]) - + if src == 0: bus0_msg_cnt += 1 elif src == 1: bus1_msg_cnt += 1 elif src == 2: bus2_msg_cnt += 1 - + print("Message Counts... Bus 0: " + str(bus0_msg_cnt) + " Bus 1: " + str(bus1_msg_cnt) + " Bus 2: " + str(bus2_msg_cnt), end='\r') except KeyboardInterrupt: diff --git a/examples/can_unique.py b/examples/can_unique.py old mode 100644 new mode 100755 index 8b836cc400f4aa..42488c64bb24fd --- a/examples/can_unique.py +++ b/examples/can_unique.py @@ -5,9 +5,14 @@ # in the background files. # Expects the CSV file to be in the format from can_logger.py +# Bus,MessageID,Message,MessageLength +# 0,0x292,0x040000001068,6 + +# The old can_logger.py format is also supported: # Bus,MessageID,Message # 0,344,c000c00000000000 + import binascii import csv import sys @@ -32,7 +37,7 @@ def printBitDiff(self, other): if new_zeros: print 'id %s new zero at byte %d bitmask %d' % ( self.message_id, i, new_zeros) - + class Info(): """A collection of Messages.""" @@ -46,8 +51,14 @@ def load(self, filename): reader = csv.reader(input) next(reader, None) # skip the CSV header for row in reader: - message_id = row[1] - data = row[2] + if row[1].startswith('0x'): + message_id = row[1][2:] # remove leading '0x' + else: + message_id = hex(int(row[1]))[2:] # old message IDs are in decimal + if row[1].startswith('0x'): + data = row[2][2:] # remove leading '0x' + else: + data = row[2] if message_id not in self.messages: self.messages[message_id] = Message(message_id) message = self.messages[message_id] @@ -71,8 +82,8 @@ def PrintUnique(interesting_file, background_files): else: interesting.messages[message_id].printBitDiff( background.messages[message_id]) - - + + if __name__ == "__main__": if len(sys.argv) < 3: print 'Usage:\n%s interesting.csv background*.csv' % sys.argv[0]