4 import csv, time, argparse;
7 from random import randrange
9 from GoodFETMCPCAN import GoodFETMCPCAN;
10 from GoodFETMCPCANCommunication import GoodFETMCPCANCommunication
11 from intelhex import IntelHex;
18 class experiments(GoodFETMCPCANCommunication):
20 This class provides methods for reverse-engineering the protocols on the CAN bus network
21 via the GOODTHOPTER10 board, U{http://goodfet.sourceforge.net/hardware/goodthopter10/}
25 def __init__(self, data_location):
28 @type datalocation: string
29 @param datalocation: path to the folder where data will be stored
31 GoodFETMCPCANCommunication.__init__(self, data_location)
32 #super(experiments,self).__init(self)
36 def filterStdSweep(self, freq, low, high, time = 5):
38 This method will sweep through the range of standard ids given from low to high.
39 This will actively filter for 6 ids at a time and sniff for the given amount of
40 time in seconds. If at least one message is read in then it will go individually
41 through the 6 ids and sniff only for that id for the given amount of time. All the
42 data gathered will be saved. This does not save any sniffed packets.
45 @param freq: The frequency at which the bus is communicating
47 @param low: The low end of the id sweep
49 @param high: The high end of the id sweep
51 @param time: Sniff time for each trial. Default is 5 seconds
53 @rtype: list of numbers
54 @return: A list of all IDs found during the sweep.
58 self.client.MCPsetup()
59 for i in range(low, high+1, 6):
60 print "sniffing id: %d, %d, %d, %d, %d, %d" % (i,i+1,i+2,i+3,i+4,i+5)
61 comment= "sweepFilter: "
62 #comment = "sweepFilter_%d_%d_%d_%d_%d_%d" % (i,i+1,i+2,i+3,i+4,i+5)
63 description = "Running a sweep filer for all the possible standard IDs. This run filters for: %d, %d, %d, %d, %d, %d" % (i,i+1,i+2,i+3,i+4,i+5)
64 count = self.sniff(freq=freq, duration = time, description = description,comment = comment, standardid = [i, i+1, i+2, i+3, i+4, i+5])
66 for j in range(i,i+5):
67 comment = "sweepFilter: "
68 #comment = "sweepFilter: %d" % (j)
69 description = "Running a sweep filer for all the possible standard IDs. This run filters for: %d " % j
70 count = self.sniff(freq=freq, duration = time, description = description,comment = comment, standardid = [j, j, j, j])
76 def sweepRandom(self, freq, number = 5, time = 5):
78 This method will choose random values to listen out of all the possible standard ids up to
79 the given number. It will sniff for the given amount of time on each set of ids on the given
80 frequency. Sniffs in groups of 6 but when at least one message is read in it will go through all
81 six individually before continuing. This does not save any sniffed packets.
84 @param freq: The frequency at which the bus is communicating
86 @param number: High end of the possible ids. This will define a range from 0 to number that the ids will be chosen from
88 @param time: Sniff time for each trial. Default is 5 seconds
90 @rtype: list of numbers, list of numbers
91 @return: A list of all IDs found during the sweep and a list of all the IDs that were listened for throughout the test
93 msgIDs = [] #standard IDs that we have observed during run
94 ids = [] #standard IDs that have been tried
96 self.client.MCPsetup()
97 for i in range(0,number+1,6):
99 comment = "sweepFilter: "
100 for j in range(0,6,1):
102 #comment += "_%d" % id
106 description = "Running a sweep filer for all the possible standard IDs. This runs the following : " + comment
107 count = self.sniff(freq=freq, duration=time, description=description, comment = comment, standardid = idsTemp)
109 for element in idsTemp:
110 #comment = "sweepFilter: %d" % (element)
111 comment="sweepFilter: "
112 description = "Running a sweep filer for all the possible standard IDs. This run filters for: %d " % element
113 count = self.sniff(freq=freq, duration = time, description = description,comment = comment, standardid = [element, element, element])
119 def rtrSweep(self,freq,lowID,highID, attempts = 1,duration = 1, verbose = True):
121 This method will sweep through the range of ids given by lowID to highID and
122 send a remote transmissions request (RTR) to each id and then listen for a response.
123 The RTR will be repeated in the given number of attempts and will sniff for the given duration
124 continuing to the next id.
126 Any messages that are sniffed will be saved to a csv file. The filename will be stored in the DATA_LOCATION folder
127 with a filename that is the date (YYYYMMDD)_rtr.csv. If the file already exists it will append to the end of the file
128 The format will follow that of L{GoodFETMCPCANCommunication.sniff} in that the columns will be as follows:
129 1. timestamp: as floating point number
130 2. error boolean: 1 if there was an error detected of packet formatting (not exhaustive check). 0 otherwise
131 3. comment tag: comment about experiments as String
132 4. duration: Length of overall sniff
133 5. filtering: 1 if there was filtering. 0 otherwise
140 @param freq: The frequency at which the bus is communicating
142 @param lowID: The low end of the id sweep
143 @type highID: integer
144 @param highID: The high end of the id sweep
145 @type attempts: integer
146 @param attempts: The number of times a RTR will be repeated for a given standard id
147 @type duration: integer
148 @param duration: The length of time that it will listen to the bus after sending an RTR
149 @type verbose: boolean
150 @param verbose: When true, messages will be printed out to the terminal
153 @return: Does not return anything
155 #set up file for writing
156 now = datetime.datetime.now()
157 datestr = now.strftime("%Y%m%d")
158 path = self.DATA_LOCATION+datestr+"_rtr.csv"
160 outfile = open(filename,'a');
161 dataWriter = csv.writer(outfile,delimiter=',');
162 dataWriter.writerow(['# Time Error Bytes 1-13']);
163 dataWriter.writerow(['#' + "rtr sweep from %d to %d"%(lowID,highID)])
166 #self.client.serInit()
167 #self.spitSetup(freq)
170 for i in range(lowID,highID+1, 1):
171 self.client.serInit()
172 self.spitSetup(freq) #reset the chip to try and avoid serial timeouts
174 standardid = [i, i, i, i]
175 self.addFilter(standardid, verbose = True)
177 #### split SID into different areas
178 SIDlow = (standardid[0] & 0x07) << 5; # get SID bits 2:0, rotate them to bits 7:5
179 SIDhigh = (standardid[0] >> 3) & 0xFF; # get SID bits 10:3, rotate them to bits 7:0
181 packet = [SIDhigh, SIDlow, 0x00,0x00,0x40]
182 dataWriter.writerow(["#requested id %d"%i])
183 #self.client.poke8(0x2C,0x00); #clear the CANINTF register; we care about bits 0 and 1 (RXnIF flags) which indicate a message is being held
185 packet1 = self.client.rxpacket();
186 packet2 = self.client.rxpacket();
188 self.client.txpacket(packet)
189 ## listen for 2 packets. one should be the rtr we requested the other should be
190 ## a new packet response
191 starttime = tT.time()
192 while ((time.time() - starttime) < duration): #listen for the given duration time period
193 packet = self.client.rxpacket()
196 # we have sniffed a packet, save it
198 row.append("%f"%time.time()) #timestamp
199 row.append(0) #error flag (not checkign)
200 row.append("rtrRequest_%d"%i) #comment
201 row.append(duration) #sniff time
202 row.append(1) # filtering boolean
204 row.append("%02x"%ord(byte));
205 dataWriter.writerow(row)
206 print self.client.packet2parsedstr(packet)
208 # for each trial repeat
209 while( trial <= attempts):
210 print "trial: ", trial
211 self.client.MCPrts(TXB0=True);
212 starttime = time.time()
213 # this time we will sniff for the given amount of time to see if there is a
214 # time till the packets come in
215 while( (time.time()-starttime) < duration):
216 packet=self.client.rxpacket();
220 row.append("%f"%time.time()) #timestamp
221 row.append(0) #error flag (not checking)
222 row.append("rtrRequest_%d"%i) #comment
223 row.append(duration) #sniff time
224 row.append(1) # filtering boolean
226 row.append("%02x"%ord(byte));
227 dataWriter.writerow(row)
228 print self.client.packet2parsedstr(packet)
230 print "sweep complete"
233 # This method will do generation based fuzzing on the id given in standard id
234 # dbLimits is a dictionary of the databytes
235 # dbLimits['db0'] = [low, High]
237 # dbLimits['db7'] = [low, High]
238 # where low is the low end of values for the fuzz, high is the high end value
239 # period is the time between sending packets in milliseconds, writesPerFuzz is the times the
240 # same fuzzed packet will be injecetez. Fuzzes is the number of different packets to be injected
241 def generationFuzzer(self,freq, standardIDs, dbLimits, period, writesPerFuzz, Fuzzes):
243 This method will perform generation based fuzzing on the bus. The method will inject
244 properly formatted, randomly generated messages at a given period for a I{writesPerFuzz}
245 number of times. The packets that are injected into the bus will all be saved in the following path
246 DATALOCATION/InjectedData/(today's date (YYYYMMDD))_GenerationFuzzedPackets.csv. An example filename would be 20130222_GenerationFuzzedPackets.csv
247 Where DATALOCATION is provided when the class is initiated. The data will be saved as integers.
248 Each row will be formatted in the following form::
249 row = [time of injection, standardID, 8, db0, db1, db2, db3, db4, db5, db6, db7]
252 @param freq: The frequency at which the bus is communicating
253 @type standardIDs: list of integers
254 @param standardIDs: List of standard IDs the user wishes to fuzz on. An ID will randomly be chosen
255 with every new random packet generated. If only 1 ID is input in the list then it will
256 only fuzz on that one ID.
257 @type dbLimits: dictionary
258 @param dbLimits: This is a dictionary that holds the limits of each bytes values. Each value in the dictionary will be a list
259 containing the lowest possible value for the byte and the highest possible value. The form is shown below::
261 dbLimits['db0'] = [low, high]
262 dbLimits['db1'] = [low, high]
264 dbLimits['db7'] = [low, high]
267 @param period: The time gap between packet inejctions given in milliseconds
268 @type writesPerFuzz: integer
269 @param writesPerFuzz: This will be the number of times that each randomly generated packet will be injected onto the bus
270 before a new packet is generated
271 @type Fuzzes: integer
272 @param Fuzzes: The number of packets to be generated and injected onto bus
275 @return: This method does not return anything
278 #print "Fuzzing on standard ID: %d" %standardId
279 self.client.serInit()
281 packet = [0,0,0x00,0x00,0x08,0,0,0,0,0,0,0,0] #empty template
284 # #### split SID into different regs
285 # SIDlow = (standardIds[0] & 0x07) << 5; # get SID bits 2:0, rotate them to bits 7:5
286 # SIDhigh = (standardIds[0] >> 3) & 0xFF; # get SID bits 10:3, rotate them to bits 7:0
288 # packet = [SIDhigh, SIDlow, 0x00,0x00, # pad out EID regs
289 # 0x08, # bit 6 must be set to 0 for data frame (1 for RTR)
290 # # lower nibble is DLC
291 # packetTemp[0],packetTemp[1],packetTemp[2],packetTemp[3],packetTemp[4],packetTemp[5],packetTemp[6],packetTemp[7]]
294 #get folder information (based on today's date)
295 now = datetime.datetime.now()
296 datestr = now.strftime("%Y%m%d")
297 path = self.DATA_LOCATION+"InjectedData/"+datestr+"_GenerationFuzzedPackets.csv"
299 outfile = open(filename,'a');
300 dataWriter = csv.writer(outfile,delimiter=',');
301 #dataWriter.writerow(['# Time Error Bytes 1-13']);
302 #dataWriter.writerow(['#' + description])
304 numIds = len(standardIDs)
305 fuzzNumber = 0; #: counts the number of packets we have generated
306 while( fuzzNumber < Fuzzes):
307 id_new = standardIDs[random.randint(0,numIds-1)]
309 #### split SID into different regs
310 SIDhigh = (id_new >> 3) & 0xFF; # get SID bits 10:3, rotate them to bits 7:0
311 SIDlow = (id_new & 0x07) << 5; # get SID bits 2:0, rotate them to bits 7:5
315 #generate a fuzzed packet
316 for i in range(0,8): # for each data byte, fuzz it
318 limits = dbLimits[idx]
319 value = random.randint(limits[0],limits[1]) #generate pseudo-random integer value
322 #put a rough time stamp on the data and get all the data bytes
323 row = [time.tT(), id_new,8] # could make this 8 a variable
325 for i in range(5,13):
326 row.append(packet[i])
327 msg += " %d"%packet[i]
329 dataWriter.writerow(row)
330 self.client.txpacket(packet)
331 tT.sleep(period/1000)
333 #inject the packet the given number of times.
334 for i in range(1,writesPerFuzz):
335 self.client.MCPrts(TXB0=True)
336 tT.sleep(period/1000)
338 print "Fuzzing Complete"
341 def generalFuzz(self,freq, Fuzzes, period, writesPerFuzz):
343 The method will inject properly formatted, randomly generated messages at a given period for a I{writesPerFuzz}
344 number of times. A new random standard id will be chosen with each newly generated packet. IDs will be chosen from the full
345 range of potential ids ranging from 0 to 4095. The packets that are injected into the bus will all be saved in the following path
346 DATALOCATION/InjectedData/(today's date (YYYYMMDD))_GenerationFuzzedPackets.csv. An example filename would be 20130222_GenerationFuzzedPackets.csv
347 Where DATALOCATION is provided when the class is initiated. The data will be saved as integers.
348 Each row will be formatted in the following form::
349 row = [time of injection, standardID, 8, db0, db1, db2, db3, db4, db5, db6, db7]
352 @param freq: The frequency at which the bus is communicating
354 @param period: The time gap between packet inejctions given in milliseconds
355 @type writesPerFuzz: integer
356 @param writesPerFuzz: This will be the number of times that each randomly generated packet will be injected onto the bus
357 before a new packet is generated
358 @type Fuzzes: integer
359 @param Fuzzes: The number of packets to be generated and injected onto bus
362 @return: This method does not return anything
365 #print "Fuzzing on standard ID: %d" %standardId
366 self.client.serInit()
368 packet = [0,0,0x00,0x00,0x08,0,0,0,0,0,0,0,0] #empty template
370 #get folder information (based on today's date)
371 now = datetime.datetime.now()
372 datestr = now.strftime("%Y%m%d")
373 path = self.DATA_LOCATION+"InjectedData/"+datestr+"_GenerationFuzzedPackets.csv"
375 outfile = open(filename,'a');
376 dataWriter = csv.writer(outfile,delimiter=',');
377 #dataWriter.writerow(['# Time Error Bytes 1-13']);
378 #dataWriter.writerow(['#' + description])
380 fuzzNumber = 0; #: counts the number of packets we have generated
381 while( fuzzNumber < Fuzzes):
382 #generate new random standard id in the full range of possible values
383 id_new = random.randint(0,4095)
385 #### split SID into different regs
386 SIDhigh = (id_new >> 3) & 0xFF; # get SID bits 10:3, rotate them to bits 7:0
387 SIDlow = (id_new & 0x07) << 5; # get SID bits 2:0, rotate them to bits 7:5
391 #generate a fuzzed packet
392 for i in range(0,8): # for each data byte, fuzz it
395 value = random.randint(0, 255) #generate pseudo-random integer value
398 #put a rough time stamp on the data and get all the data bytes
399 row = [time.time(), id_new,8]
400 """@todo: allow for varied packet lengths"""
402 for i in range(5,13):
403 row.append(packet[i])
404 msg += " %d"%packet[i]
406 dataWriter.writerow(row)
407 self.client.txpacket(packet)
408 time.sleep(period/1000)
410 #inject the packet the given number of times.
411 for i in range(1,writesPerFuzz):
412 self.client.MCPrts(TXB0=True)
413 time.sleep(period/1000)
415 print "Fuzzing Complete"
418 # assumes 8 byte packets
419 def packetRespond(self,freq, time, repeats, period, responseID, respondPacket,listenID, listenPacket = None):
421 This method will allow the user to listen for a specific packet and then respond with a given message.
422 If no listening packet is included then the method will only listen for the id and respond with the specified
423 packet when it receives a message from that id. This process will continue for the given amount of time (in seconds).
424 and with each message received that matches the listenPacket and ID the transmit message will be sent the I{repeats} number
425 of times at the specified I{period}. This message assumes a packet length of 8 for both messages, although the listenPacket can be None
428 @param freq: Frequency of the CAN bus
430 @param time: Length of time to perform the packet listen/response in seconds.
431 @type repeats: Integer
432 @param repeats: The number of times the response packet will be injected onto the bus after the listening
433 criteria has been met.
435 @param period: The time interval between messages being injected onto the CAN bus. This will be specified in milliseconds
436 @type responseID: Integer
437 @param responseID: The standard ID of the message that we want to inject
438 @type respondPacket: List of integers
439 @param respondPacket: The data we wish to inject into the bus. In the format where respondPacket[0] = databyte 0 ... respondPacket[7] = databyte 7
440 This assumes a packet length of 8.
441 @type listenID: Integer
442 @param listenID: The standard ID of the messages that we are listening for. When we read the correct message from this ID off of the bus, the method will
443 begin re-injecting the responsePacket on the responseID
444 @type listenPacket: List of Integers
445 @param listenPacket: The data we wish to listen for before we inject packets. This will be a list of the databytes, stored as integers such that
446 listenPacket[0] = data byte 0, ..., listenPacket[7] = databyte 7. This assumes a packet length of 8. This input can be None and this
447 will lead to the program only listening for the standardID and injecting the response as soon as any message from that ID is given
451 self.client.serInit()
454 #formulate response packet
455 SIDhigh = (responseID >> 3) & 0xFF; # get SID bits 10:3, rotate them to bits 7:0
456 SIDlow = (responseID & 0x07) << 5; # get SID bits 2:0, rotate them to bits 7:5
457 #resPacket[0] = SIDhigh
458 #resPacket[1] = SIDlow
459 resPacket = [SIDhigh, SIDlow, 0x00,0x00, # pad out EID regs
460 0x08, # bit 6 must be set to 0 for data frame (1 for RTR)
461 # lower nibble is DLC
462 respondPacket[0],respondPacket[1],respondPacket[2],respondPacket[3],respondPacket[4],respondPacket[5],respondPacket[6],respondPacket[7]]
463 #load packet/send once
464 """@todo: make this only load the data onto the chip and not send """
465 self.client.txpacket(resPacket)
466 self.addFilter([listenID,listenID,listenID,listenID, listenID, listenID]) #listen only for this packet
467 startTime = tT.time()
469 while( (tT.time() - startTime) < time):
470 packet = self.client.rxpacket()
472 print "packet read in, responding now"
473 # assume the ids already match since we are filtering for the id
475 #compare packet received to desired packet
476 if( listenPacket == None): # no packets given, just want the id
477 for i in range(0,repeats):
478 self.client.MCPrts(TXB0=True)
479 tT.sleep(period/1000)
480 else: #compare packets
481 sid = ord(packet[0])<<3 | ord(packet[1])>>5
482 print "standard id of packet recieved: ", sid #standard ID
486 byteIn = ord(packet[idx])
488 compareIn = listenPacket[i]
489 print byteIn, compareIn
490 if( byteIn != compareIn):
492 print "packet did not match"
495 if( packet != None ):
496 self.client.MCPrts(TXB0=True)
497 tT.sleep(period/1000)
498 print "Response Listening Terminated."
501 # def generationFuzzRandomID(self, freq, standardIDs, dbLimits, period, writesPerFuzz, Fuzzes):
502 # print "Fuzzing on standard ID: %d" %standardId
503 # self.client.serInit()
504 # self.spitSetup(freq)
505 # packetTemp = [0,0,0,0,0,0,0,0]
506 # #form a basic packet
508 # #### split SID into different regs
509 # SIDlow = (standardId & 0x07) << 5; # get SID bits 2:0, rotate them to bits 7:5
510 # SIDhigh = (standardId >> 3) & 0xFF; # get SID bits 10:3, rotate them to bits 7:0
512 # packet = [SIDhigh, SIDlow, 0x00,0x00, # pad out EID regs
513 # 0x08, # bit 6 must be set to 0 for data frame (1 for RTR)
514 # # lower nibble is DLC
515 # packetTemp[0],packetTemp[1],packetTemp[2],packetTemp[3],packetTemp[4],packetTemp[5],packetTemp[6],packetTemp[7]]
518 # #get folder information (based on today's date)
519 # now = datetime.datetime.now()
520 # datestr = now.strftime("%Y%m%d")
521 # path = self.DATA_LOCATION+"InjectedData/"+datestr+"_GenerationFuzzedPackets.csv"
523 # outfile = open(filename,'a');
524 # dataWriter = csv.writer(outfile,delimiter=',');
525 # #dataWriter.writerow(['# Time Error Bytes 1-13']);
526 # #dataWriter.writerow(['#' + description])
528 # numIds = len(standardIDs)
530 # while( fuzzNumber < Fuzzes):
531 # id_new = standsardIDs[random.randint(0,numIds-1)]
532 # #### split SID into different regs
533 # SIDlow = (id_new & 0x07) << 5; # get SID bits 2:0, rotate them to bits 7:5
534 # SIDhigh = (id_new >> 3) & 0xFF; # get SID bits 10:3, rotate them to bits 7:0
535 # packet[0] = SIDhigh
538 # #generate a fuzzed packet
539 # for i in range(0,8): # for each databyte, fuzz it
541 # limits = dbLimits[idx]
542 # value = random.randint(limits[0],limits[1]) #generate pseudo-random integer value
543 # packet[i+5] = value
545 # #put a rough time stamp on the data and get all the data bytes
546 # row = [time.time(), standardId,8]
547 # msg = "Injecting: "
548 # for i in range(5,13):
549 # row.append(packet[i])
550 # msg += " %d"%packet[i]
552 # dataWriter.writerow(row)
553 # self.client.txpacket(packet)
554 # #inject the packet repeatily
555 # for i in range(1,writesPerFuzz):
556 # self.client.MCPrts(TXB0=True)
557 # time.sleep(period/1000)
559 # print "Fuzzing Complete"