├── License.txt ├── README.md └── fuzzer.py /License.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Bill Hass {billhass@umich.edu}, Yelizaveta Burakova {ybura@umich.edu} 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pyfuzz_can 2 | 3 | ### Dependencies: 4 | - Python 2.7.X 5 | https://www.python.org/downloads/ 6 | 7 | - Python-CAN 8 | https://bitbucket.org/hardbyte/python-can 9 | 10 | - (Optional to build Python-CAN from source) Mercurial 11 | https://www.mercurial-scm.org/wiki/Download 12 | 13 | ### Setup: 14 | 15 | **1. Install the Python-CAN dependency.** 16 | 17 | * Using PiP: 18 | ``` 19 | $> pip install python-can 20 | ``` 21 | 22 | * From source: 23 | 24 | Clone Python-CAN to your local machine: 25 | 26 | `$> hg clone https://bitbucket.org/hardbyte/python-can` 27 | 28 | Install Python-CAN normally with: 29 | ``` 30 | $> cd python-can 31 | $> python setup.py install 32 | ``` 33 | 34 | **2. Create interface config file for Python-CAN at ~/can.conf:** 35 | ``` 36 | [default] 37 | interface = pcan 38 | channel = PCAN_USBBUS1 39 | ``` 40 | The interface and channel given are for PEAK PCAN-USB. These will change depending on your CAN interface. Refer to the Python-CAN documentation for your device's configuration : http://python-can.readthedocs.io/en/latest/configuration.html. 41 | 42 | **3. Clone pyfuzz_can to your local machine:** 43 | 44 | `$> git clone https://github.com/bhass1/pyfuzz_can.git` 45 | 46 | **4. You are ready to fuzz. Try:** 47 | ``` 48 | $> cd pyfuzz_can 49 | $> python fuzzer.py 50 | ``` 51 | 52 | For help use: 53 | 54 | `$> python fuzzer.py --help` 55 | 56 | ### Common Issues 57 | **1. Cannot open a required shared object file. (e.g. `OSError: libpcanbasic.so: cannot open shared object file: No such file or directory`)** 58 | 59 | Don't forget to install device drivers for your CAN interface device on your platform. For this particular issue on Linux (missing libpcanbasic.so), you need to download and install the peak-linux-driver (http://www.peak-system.com/fileadmin/media/linux/index.htm) and PCAN-Basic API (http://www.peak-system.com/produktcd/Develop/PC%20interfaces/Linux/PCAN-Basic_API_for_Linux/PCAN_Basic_Linux-4.2.0.tar.gz). 60 | 61 | However, there are many other interface devices supported by Python-CAN: http://python-can.readthedocs.io/en/latest/interfaces.html. Ensure your CAN interface drivers are installed properly. 62 | 63 | **2. Import can problem. `ModuleNotFoundError: No module named 'can'`** 64 | 65 | Ensure Python-CAN is installed. Try `pip install python-can` 66 | 67 | **3. Something else?** 68 | 69 | Send me an email: `billhass@umich.edu` or open an issue! 70 | 71 | -------------------------------------------------------------------------------- /fuzzer.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | #### Copyright (c) 2016 Bill Hass {billhass@umich.edu}, Yelizaveta Burakova {ybura@umich.edu} 4 | #### Refer to Liscense.txt 5 | 6 | 7 | import can 8 | import getopt 9 | import sys 10 | import os, random 11 | import time 12 | import string 13 | from datetime import datetime 14 | 15 | valid_baud_rates = [ 16 | 1000000, 17 | 800000, 18 | 500000, 19 | 250000, 20 | 125000, 21 | 100000, 22 | 95000, 23 | 83000, 24 | 50000, 25 | 47000, 26 | 33000, 27 | 20000, 28 | 10000, 29 | 5000 30 | ] 31 | 32 | def usage(): 33 | print("Used to fuzz 29-bit CAN data frames (Particularly suited for J1939).") 34 | print("A typical usage is to try random data bytes for a given PGN and source") 35 | print("address at a given rate.") 36 | print("") 37 | print("fuzzer.py -p PGN -s SA -r RATE") 38 | print("") 39 | print("You may give an entire 29-bit CAN ID and use templated data bytes to ") 40 | print("provide a mix of known data and random data.") 41 | print("") 42 | print("fuzzer.py -r RATE -c 29bit_CANID -d DATA") 43 | print("") 44 | print("FLAGS") 45 | print("-b, --baud Baud rate integer in bits/second. (ex. 250000)") 46 | print("-P, --priority Priority in hex. (ex. 18)") 47 | print("-p, --pgn PGN in hex. (ex. fee1)") 48 | print("-s, --source Source address in hex. (ex. 00)") 49 | print("-d, --data Eight data bytes in hex. A single byte is two hex chars.") 50 | print(" Use x for random nibble. (ex. xx22xxxx556677xx)") 51 | print(" Use # for counter nibble. (ex. 11223344556677##)") 52 | print(" Only 1 counter allowed, and can be whole size of data, but limited to mod 8 for now.") 53 | print(" Use + for checksum nibble. (ex. 11223344556677++)") 54 | print(" Only 1 checksum allowed, and must be in final data byte.") 55 | print("-r, --rate An integer period that packets will be sent in ms in set {1, infinity}. (ex. 100)") 56 | print("-c, --canid Whole 29-bit CAN ID in hex. (ex. 18ef1200)") 57 | print("-O, --offline Used to flag that the CAN hardware is not connected") 58 | 59 | 60 | """ 61 | Fuzzes either id, data, or both 62 | 63 | can_str: string with id to send in hex with 'x' to represent random char 64 | data_str: formatted data string in hex with 'x' to represent random char 65 | rate: integer number of ms to wait between sending 66 | """ 67 | def fuzzID(can_str, data_str, rate): 68 | global theBus, offline 69 | data_lst = [0, 0, 0, 0, 0, 0, 0, 0] 70 | can_id = 0x000000 71 | can_template = can_str 72 | data_template = data_str 73 | hex_chars = "0123456789abcdef" #ignore capitals so we don't have twice as many letters to randomly choose 74 | 75 | checksum = 0 76 | counter = 0 77 | has_counter = False 78 | 79 | while(True): 80 | can_str = can_template 81 | data_str = data_template 82 | while 'x' in can_str: 83 | can_str = can_str.replace('x', random.choice(hex_chars), 1) #Replace x with random hex char 84 | 85 | while 'x' in data_str: 86 | data_str = data_str.replace('x', random.choice(hex_chars), 1)#Replace x with random hex char 87 | 88 | if '#' in data_str: 89 | #This is counter field 90 | has_counter = True 91 | count_count = data_str.count('#') 92 | start_pos = data_str.find('#') 93 | for i in range(count_count - 1): 94 | if data_str[i+start_pos+1] != '#': 95 | print("Poor counter formatting. Only one field of consecutive # is allowed") 96 | sys.exit(2) 97 | cnt_str = "{0:0{1}x}".format(counter, count_count) 98 | for i in range(count_count): 99 | data_str = data_str.replace('#', cnt_str[i], 1) 100 | 101 | #Do checksum calculation last because we need to know all the bits and bytes 102 | if '+' in data_str: 103 | #This is checksum field 104 | count_check = data_str.count('+') 105 | if count_check > 2: 106 | print("Poor checksum formatting. A checksum greater than 1 byte is not allowed") 107 | sys.exit(2) 108 | start_pos = data_str.find('+') 109 | if start_pos < 14: 110 | print("Poor checksum formatting. The checksum must be in the last data byte") 111 | sys.exit(2) 112 | for i in range(count_check - 1): 113 | if data_str[i+start_pos+1] != '+': 114 | print("Poor checksum formatting. Only one field of consecutive + is allowed") 115 | sys.exit(2) 116 | #checksum different depending on PGN 117 | checksum = int(can_str[0:2],16) + int(can_str[2:4],16) + int(can_str[4:6],16) + int(can_str[6:8],16) 118 | for i in range(7): 119 | checksum += int(data_str[i*2:i*2+2],16) 120 | checksum += counter 121 | if can_str[2:6] == "0000": 122 | checksum = (((checksum >> 6) & 0x3) + (checksum >> 0x3) + checksum) & 0x7 123 | elif count_check == 2: 124 | checksum = checksum & 0xff 125 | else: 126 | checksum = ((checksum >> 4) + checksum) & 0xf 127 | 128 | chk_str = "{0:0{1}x}".format(checksum, count_check) 129 | for i in range(count_check): 130 | data_str = data_str.replace('+', chk_str[i], 1) 131 | 132 | #Increment counter after checksum calc 133 | if has_counter: 134 | counter = (counter + 1) % 8 #TODO : Only mod 8 for specific PGNs or user-specified modulus 135 | 136 | try: 137 | can_id = int(can_str, 16) 138 | for i in range(8): 139 | data_lst[i] = int(data_str[i*2]+data_str[i*2+1], 16) 140 | except: 141 | print("can_str {} and data_str {} \ncan_id {} and data {}\nEXCEPTION THROWN".format(can_str, data_str, can_id, data_lst)) 142 | sys.exit(2) 143 | 144 | msg = can.Message(arbitration_id=can_id, data=data_lst, extended_id=True) 145 | if not offline: 146 | try: 147 | theBus.send(msg) 148 | print("{} : {} {} {} {} {} {} {} {} {}".format(datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f'), hex(can_id), 149 | hex(data_lst[0]), hex(data_lst[1]), hex(data_lst[2]), hex(data_lst[3]), hex(data_lst[4]), hex(data_lst[5]), hex(data_lst[6]), hex(data_lst[7]))) 150 | except can.CanError: 151 | print("Message NOT sent") 152 | time.sleep(5000) 153 | else: 154 | print("{} : {} {} {} {} {} {} {} {} {}".format(datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f'), hex(can_id), 155 | hex(data_lst[0]), hex(data_lst[1]), hex(data_lst[2]), hex(data_lst[3]), hex(data_lst[4]), hex(data_lst[5]), hex(data_lst[6]), hex(data_lst[7]))) 156 | sys.stdout.flush() 157 | time.sleep(rate / float(1000)) 158 | 159 | """ 160 | Assumes prio, pgn, and source are properly formatted hex strings 161 | Returns CAN ID string 162 | """ 163 | def makeCanId(prio, pgn, source): 164 | if prio == "" : 165 | prio = "08" 166 | if pgn == "" : 167 | pgn = "xxxx" 168 | if source == "" : 169 | source = "xx" 170 | return prio + pgn + source 171 | 172 | 173 | def main(): 174 | global theBus, offline 175 | 176 | prio = "" 177 | pgn = "" 178 | source = "" 179 | data = "" 180 | rate = 100 181 | canid = "" 182 | baud = 250000 183 | offline = False 184 | 185 | #get args 186 | try: 187 | opts, args = getopt.getopt(sys.argv[1:], 'P:p:s:d:r:c:b:Oh', ['priority=','pgn=','source=','data=','rate=','canid=','baud=','offline','help']) 188 | except getopt.GetoptError: 189 | usage() 190 | sys.exit(2) 191 | for opt, arg in opts: 192 | if opt in ('-h', '--help'): 193 | usage() 194 | sys.exit(2) 195 | elif opt in ('-P', '--priority'): 196 | prio = arg; 197 | elif opt in ('-p', '--pgn'): 198 | pgn = arg; 199 | elif opt in ('-s', '--source'): 200 | source = arg 201 | elif opt in ('-d', '--data'): 202 | data = arg 203 | elif opt in ('-r', '--rate'): 204 | rate = arg 205 | elif opt in ('-c', '--canid'): 206 | canid = arg 207 | elif opt in ('-b', '--baud'): 208 | baud = arg 209 | elif opt in ('-O', '--offline'): 210 | offline = True 211 | else: 212 | usage() 213 | sys.exit(2) 214 | #check args 215 | data_chars = "0123456789abcdefABCDEFxX#+" 216 | can_chars = "0123456789abcdefABCDEFxX" 217 | if data != "" : 218 | if len(data) != 16 : 219 | usage() 220 | print("\nBAD DATA LENGTH {}".format(len(data))) 221 | sys.exit(2) 222 | if not all(elt in data_chars for elt in data) : 223 | usage() 224 | print("\nINVALID DATA CHARACTERS") 225 | sys.exit(2) 226 | if canid != "" and (pgn != "" or prio != "" or source != "") : 227 | usage() 228 | print("\nPlease only provide either -c or [-P, -p, and -s]") 229 | sys.exit(2) 230 | if canid != "" : 231 | if len(canid) != 8 : 232 | usage() 233 | print("\nBAD CAN ID LENGTH") 234 | sys.exit(2) 235 | if not all(elt in can_chars for elt in canid) : 236 | usage() 237 | print("\nINVALID CANID CHARACTERS") 238 | sys.exit(2) 239 | if pgn != "" : 240 | if len(pgn) != 4 : 241 | usage() 242 | print("\nBAD PGN LENGTH") 243 | sys.exit(2) 244 | if not all(elt in can_chars for elt in pgn) : 245 | usage() 246 | print("\nINVALID PGN CHARACTERS") 247 | sys.exit(2) 248 | if prio != "" : 249 | if len(prio) != 2 : 250 | usage() 251 | print("\nBAD PRIORITY LENGTH") 252 | sys.exit(2) 253 | if not all(elt in can_chars for elt in prio) : 254 | usage() 255 | print("\nINVALID PRIORITY CHARACTERS") 256 | sys.exit(2) 257 | if source != "" : 258 | if len(source) != 2 : 259 | usage() 260 | print("\nBAD SOURCE LENGTH") 261 | sys.exit(2) 262 | if not all(elt in can_chars for elt in source) : 263 | usage() 264 | print("\nINVALID SOURCE CHARACTERS") 265 | sys.exit(2) 266 | if rate != 100 : 267 | try: 268 | rate = int(rate) 269 | except: 270 | usage() 271 | sys.exit(2) 272 | if rate < 1 : 273 | usage() 274 | sys.exit(2) 275 | if rate > 1000 : 276 | print("Did you really want > 1s period? Rate set to {}".format(rate)) 277 | if baud != 250000 : 278 | try: 279 | baud = int(baud) 280 | except: 281 | usage() 282 | sys.exit(2) 283 | if baud not in valid_baud_rates : 284 | usage() 285 | sys.exit(2) 286 | 287 | #good to go 288 | if not offline : theBus = can.interface.Bus(bitrate=baud) 289 | 290 | 291 | if canid == "" : 292 | #use prio 293 | canid = makeCanId(prio, pgn, source) 294 | 295 | if canid == "" : 296 | print("Must provide a canid or pgn") 297 | sys.exit(2) 298 | 299 | if data == "" : 300 | data = "xxxxxxxxxxxxxxxx" 301 | 302 | canid = canid.replace('X', 'x') 303 | data = data.replace('X', 'x') 304 | 305 | print("canid: {}".format(canid)) 306 | print("data: {}".format(data)) 307 | print("rate: {}".format(rate)) 308 | try: 309 | fuzzID(canid, data, rate) 310 | except KeyboardInterrupt: #Detect Ctrl-C and exit cleanly 311 | theBus.shutdown() 312 | print("Goodbye!") 313 | 314 | if __name__ == "__main__": 315 | main() 316 | --------------------------------------------------------------------------------