├── README └── modFuzzer.py /README: -------------------------------------------------------------------------------- 1 | Modbus Fuzzer v0.5 Alpha 2 | It is basic Modbus Fuzzer. 3 | In this version for faster operation we target, diagnosis, write coil and write registers function code seprately as user input. 4 | 5 | It is still under development, Me and Myfriend are trying to fix the problem of Generation Algorithm. 6 | 7 | 8 | 9 | Update log: v0.5: Added fuzzing feature for specific function code, Apr 30, 2014 v0.5 and logging feature 10 | 11 | Update log: v0.2 Modified and added scanning function, Dec 14, 2013 v0.2 12 | 13 | More info about it at : 14 | http://sigint.ir/blog/?p=14 15 | -------------------------------------------------------------------------------- /modFuzzer.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | ''' 3 | Created on Apr 16, 2013 v0.1 4 | Modified and added scanning function, Dec 14, 2013 v0.2 5 | Added fuzzing feature for specific function code, Apr 30, 2014 v0.5 6 | 7 | @author: Ali, TJ 8 | ''' 9 | import socket 10 | import sys 11 | from types import * 12 | import struct 13 | import time 14 | import logging 15 | 16 | HOST = '127.0.0.1' # The remote host 17 | dest_port = 502 # The same port as used by the server 18 | TANGO_DOWN = '' 19 | sock = None 20 | dumbflagset = 0; 21 | logging.basicConfig(filename='./fuzzer.log', filemode='a', level=logging.DEBUG, format='[%(asctime)s][%(levelname)s] %(message)s') 22 | 23 | def create_connection(dest_ip, port): 24 | try: 25 | sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 26 | except socket.error, msg: 27 | sys.stderr.write("[ERROR] %s\n" % msg[1]) 28 | sys.exit(1) 29 | 30 | HOST = dest_ip 31 | try: 32 | sock.settimeout(0.5) 33 | sock.connect((HOST, dest_port)) 34 | except socket.error, msg: 35 | logging.exception("Connection Failed!") 36 | else: 37 | logging.info("Connected to Server: %s" % dest_ip) 38 | 39 | return sock 40 | 41 | 42 | def hexstr(s): 43 | return '-'.join('%02x' % ord(c) for c in s) 44 | 45 | 46 | def dumb_fuzzing(dest_ip): 47 | sock = create_connection(dest_ip, dest_port) 48 | unitID = 0 49 | protoID = 0 50 | transID = 0 51 | lengthOfFunctionData = 1 52 | prevField = "" 53 | for functionCode in range(0,255): 54 | for functionData6 in range(0, 255): 55 | for functionData5 in range(0, 255): 56 | for functionData4 in range(0, 255): 57 | for functionData3 in range(0, 255): 58 | for functionData2 in range(0, 255): 59 | for functionData1 in range(0, 255): 60 | functionDataField = prevField + struct.pack(">B", functionData1) 61 | #print"%s" % hexstr(functionDataField) 62 | length = 2 + lengthOfFunctionData 63 | ModbusPacket = struct.pack(">H", transID) + \ 64 | struct.pack(">H", protoID) + \ 65 | struct.pack(">H", length) + \ 66 | struct.pack(">B", unitID) + \ 67 | struct.pack(">B", functionCode) + \ 68 | functionDataField 69 | logging.debug("%s" % hexstr(ModbusPacket)) 70 | #print"%s" % hexstr(ModbusPacket) 71 | try: 72 | sock.send(ModbusPacket) 73 | except socket.timeout: 74 | logging.exception("Sending Timed Out!") 75 | except socket.error: 76 | logging.exception("Sending Failed!") 77 | sock.close() 78 | sock = create_connection(dest_ip, dest_port) 79 | logging.info("Try to Reconnect...") 80 | else: 81 | logging.debug("Sent Packet: %s" % hexstr(ModbusPacket)) 82 | print "Sent: %s" % hexstr(ModbusPacket) 83 | ''' try: 84 | data = sock.recv(1024) 85 | print 'Received %s:' % repr(data) 86 | except socket.timeout: 87 | print '' 88 | except socket.error: 89 | sock.close() 90 | sock = create_connection(dest_ip, dest_port) 91 | 92 | sock.close() 93 | ''' 94 | def smart_fuzzing_with_user_input(dest_ip, msg): 95 | sock = create_connection(dest_ip, dest_port) 96 | strInput = msg 97 | dataSend = "" 98 | shortInput = "" 99 | cnt = 1 100 | for chInput in strInput: 101 | shortInput += chInput 102 | if cnt%2 == 0: 103 | intInput = int(shortInput,16) 104 | dataSend += struct.pack(">B", intInput) 105 | shortInput = "" 106 | cnt += 1 107 | try: 108 | sock.send(dataSend) 109 | print 'sent: %s' % hexstr(dataSend) 110 | except socket.error: 111 | sock.close() 112 | print 'trying to create connection again' 113 | sock = create_connection(dest_ip, dest_port) 114 | try: 115 | dataRecv = sock.recv(1024) 116 | print >>sys.stderr, 'received: %s' % hexstr(dataRecv) 117 | except socket.timeout: 118 | print 'recv timed out!' 119 | except socket.error: 120 | sock.close() 121 | sock = create_connection(dest_ip, dest_port) 122 | sock.close() 123 | 124 | def simulator(dest_ip): 125 | value1 = 0 126 | value2 = 100 127 | transID = 0 128 | while True: 129 | strTransID = "%0.4x" % transID 130 | strHex1 = "%0.4x" % value1 131 | msg1 = strTransID + "0000000B01060000" + strHex1 132 | smart_fuzzing_with_user_input(dest_ip, msg1) 133 | 134 | transID += 1 135 | strTransID = "%0.4x" % transID 136 | strHex2 = "%0.4x" % value2 137 | msg2 = strTransID + "0000000B01060001" + strHex2 138 | smart_fuzzing_with_user_input(dest_ip, msg2) 139 | 140 | value1 += 1 141 | value2 -= 1 142 | transID += 1 143 | if (value1 > 100): 144 | value1 = 0 145 | if (value2 < 0): 146 | value2 = 100 147 | time.sleep(0.3) 148 | 149 | def smart_fuzzing_for_func08h(dest_ip): 150 | sock = create_connection(dest_ip, dest_port) 151 | transID = 0 152 | protocolID = 0 153 | length = 6 154 | unitID = 0 155 | funcCode = 8 # Diagnostic 156 | subFunction = 13 # sub function code start from 0x0000 157 | dataField = 0 158 | 159 | while True: 160 | packet = struct.pack(">H", transID) + struct.pack(">H", protocolID) + struct.pack(">H", length) + \ 161 | struct.pack(">B", unitID) + struct.pack(">B", funcCode) + struct.pack(">H", subFunction) + \ 162 | struct.pack(">H", dataField) 163 | try: 164 | sock.send(packet) 165 | except socket.error: 166 | sock.close() 167 | sock = create_connection(dest_ip, dest_port) 168 | try: 169 | dataRecv = sock.recv(1024) 170 | except socket.timeout: 171 | sys.stdout.write('1.time out\n') 172 | except socket.error: 173 | sock.close() 174 | sock = create_connection(dest_ip, dest_port) 175 | 176 | if len(dataRecv) > 0: 177 | print "Sent: %s" % hexstr(packet) 178 | print "Recv: %s" % hexstr(dataRecv) 179 | 180 | # if len(dataRecv) < 1: 181 | # sock.close() 182 | # sock = create_connection(dest_ip, dest_port) 183 | # try: 184 | # sock.send(packet) 185 | # print "Sent2: %s" % hexstr(packet) 186 | # except socket.error: 187 | # print 'FAILED TO SEND2' 188 | # try: 189 | # dataRecv = sock.recv(1024) 190 | # print "Recv2 : %s" % hexstr(dataRecv) 191 | # except socket.timeout: 192 | # sys.stdout.write('2.time out\n') 193 | # except socket.error: 194 | # print 'FAILED TO RECV2' 195 | 196 | transID = transID + 1 197 | # subFunction = subFunction + 1 198 | dataField = dataField + 1 199 | 200 | def smart_fuzzing_for_func0Fh(dest_ip): 201 | sock = create_connection(dest_ip, dest_port) 202 | transID = 0 203 | protocolID = 0 204 | length = 8 205 | unitID = 0 206 | funcCode = 15 # Write Multiple coils 207 | startAddr = 0 # start from 0x0000 208 | # startAddr = 122 # start from 0xFFFF 209 | quantityOutputs = 8 210 | if (quantityOutputs % 8 == 0): 211 | byteCount = quantityOutputs / 8 212 | else: 213 | byteCount = quantityOutputs / 8 + 1 214 | value = 255 215 | 216 | loopCounter = 0 217 | while True: 218 | packet = struct.pack(">H", transID) + struct.pack(">H", protocolID) + struct.pack(">H", length) + \ 219 | struct.pack(">B", unitID) + struct.pack(">B", funcCode) + struct.pack(">H", startAddr) + \ 220 | struct.pack(">H", quantityOutputs) + struct.pack(">B", byteCount) + struct.pack(">B", value) + \ 221 | struct.pack(">B", 255)*loopCounter 222 | try: 223 | sock.send(packet) 224 | print "Sent: %s" % hexstr(packet) 225 | except socket.error: 226 | sock.close() 227 | sock = create_connection(dest_ip, dest_port) 228 | try: 229 | dataRecv = sock.recv(1024) 230 | print "Recv : %s" % hexstr(dataRecv) 231 | except socket.timeout: 232 | sys.stdout.write('1.time out\n') 233 | except socket.error: 234 | sock.close() 235 | sock = create_connection(dest_ip, dest_port) 236 | 237 | if len(dataRecv) < 1: 238 | sock.close() 239 | sock = create_connection(dest_ip, dest_port) 240 | try: 241 | sock.send(packet) 242 | print "Sent2: %s" % hexstr(packet) 243 | except socket.error: 244 | print 'FAILED TO SEND2' 245 | try: 246 | dataRecv = sock.recv(1024) 247 | print "Recv2 : %s" % hexstr(dataRecv) 248 | except socket.timeout: 249 | sys.stdout.write('2.time out\n') 250 | except socket.error: 251 | print 'FAILED TO RECV2' 252 | 253 | transID = transID + 1 254 | loopCounter = loopCounter + 1 255 | 256 | def smart_fuzzing_for_func10h(dest_ip): 257 | sock = create_connection(dest_ip, dest_port) 258 | transID = 0 259 | protocolID = 0 260 | length = 9 261 | unitID = 0 262 | funcCode = 16 # Write Multiple registers 263 | # startAddr = 0 # start from 0x0000 264 | startAddr = 122 # start from 0xFFFF 265 | quantityReg = 1 266 | byteCount = 2*quantityReg 267 | value = 65535 268 | 269 | loopCounter = 0 270 | while True: 271 | packet = struct.pack(">H", transID) + struct.pack(">H", protocolID) + struct.pack(">H", length) + \ 272 | struct.pack(">B", unitID) + struct.pack(">B", funcCode) + struct.pack(">H", startAddr) + \ 273 | struct.pack(">H", quantityReg) + struct.pack(">B", byteCount) + struct.pack(">H", value) + \ 274 | struct.pack(">B", 255)*loopCounter 275 | try: 276 | sock.send(packet) 277 | print "Sent: %s" % hexstr(packet) 278 | except socket.error: 279 | sock.close() 280 | sock = create_connection(dest_ip, dest_port) 281 | try: 282 | dataRecv = sock.recv(1024) 283 | print "Recv : %s" % hexstr(dataRecv) 284 | except socket.timeout: 285 | sys.stdout.write('1.time out\n') 286 | except socket.error: 287 | sock.close() 288 | sock = create_connection(dest_ip, dest_port) 289 | 290 | if len(dataRecv) < 1: 291 | sock.close() 292 | sock = create_connection(dest_ip, dest_port) 293 | try: 294 | sock.send(packet) 295 | print "Sent2: %s" % hexstr(packet) 296 | except socket.error: 297 | print 'FAILED TO SEND2' 298 | try: 299 | dataRecv = sock.recv(1024) 300 | print "Recv2 : %s" % hexstr(dataRecv) 301 | except socket.timeout: 302 | sys.stdout.write('2.time out\n') 303 | except socket.error: 304 | print 'FAILED TO RECV2' 305 | 306 | transID = transID + 1 307 | loopCounter = loopCounter + 1 308 | # sock.close() 309 | 310 | 311 | 312 | def atod(a): # ascii_to_decimal 313 | return struct.unpack("!L",socket.inet_aton(a))[0] 314 | 315 | def dtoa(d): # decimal_to_ascii 316 | return socket.inet_ntoa(struct.pack("!L", d)) 317 | 318 | 319 | def scan_device(ip_range): 320 | net,_,mask = ip_range.partition('/') 321 | mask = int(mask) 322 | net = atod(net) 323 | for dest_ip in (dtoa(net+n) for n in range(0, 1<<32-mask)): 324 | try: 325 | sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM) 326 | except socket.error, msg: 327 | sock.close() 328 | 329 | try: 330 | sock.settimeout(0.2) 331 | sock.connect((dest_ip, dest_port)) 332 | except socket.error, msg: 333 | print "connection error at %s" % dest_ip 334 | continue 335 | except socket.timeout: 336 | print 'ip %s timeout error' % dest_ip 337 | continue 338 | 339 | unitID = 0 340 | dataRecv = '' 341 | while True: 342 | dataSend = struct.pack(">H", 0) \ 343 | + struct.pack(">H", 0) \ 344 | + struct.pack(">H", 6) \ 345 | + struct.pack(">B", unitID) \ 346 | + struct.pack(">B", 3) \ 347 | + struct.pack(">H", 0) \ 348 | + struct.pack(">H", 1) 349 | try: 350 | sock.send(dataSend) 351 | print "Sent: %s to %s" % (repr(dataSend), dest_ip) 352 | except socket.error: 353 | print 'FAILED TO SEND' 354 | #sock.close() 355 | #continue 356 | 357 | try: 358 | dataRecv = sock.recv(1024) 359 | print "Recv : %s" % repr(dataRecv) 360 | except socket.timeout: 361 | sys.stdout.write('.') 362 | 363 | if len(dataRecv) < 1: 364 | sys.stdout.write('.') 365 | #print "." 366 | unitID += 1 367 | else: 368 | print '\nunit ID %d found at IP %s' % (unitID, dest_ip) 369 | if dumbflagset == 1 : 370 | print 'now starting dumb fuzzing' 371 | dumb_fuzzing(dest_ip) 372 | break 373 | sock.close() 374 | 375 | 376 | # main starts here 377 | 378 | if len(sys.argv) < 3: 379 | print "modbus fuzzer v0.5" 380 | print "" 381 | print "Usage: python modFuzzer.py [-D] [destination_IP]" 382 | print " [-I] [destination_IP] [packet]" 383 | print " [-S] [IP_range]" 384 | print " [-SD] [IP_range]" 385 | print " [-S08] [destination_IP]" 386 | print " [-S0F] [destination_IP]" 387 | print " [-S10] [destination_IP]" 388 | print " [-SIM] [destination_IP]" 389 | print " " 390 | print "Commands:" 391 | print "Either long or short options are allowed." 392 | print " --dumb -D Fuzzing in dumb way" 393 | print " --input -I Fuzzing with given modbus packet" 394 | print " --scan -S Scan the modbus device(s) in given IP range" 395 | print " --sc_dumb -SD Scan the device(s) and doing dumb fuzzing" 396 | print " --f08 -F08 Fuzzing using function code 0x08" 397 | print " --f0f -F0F Fuzzing using function code 0x0F" 398 | print " --f10 -F10 Fuzzing using function code 0x10" 399 | print " --sim -SIM Working in simulator mode" 400 | # print " " 401 | # print "Option:" 402 | # print " --port -p Port number" 403 | print " " 404 | print "Example:" 405 | print "python modFuzzer.py -D 192.168.0.123" 406 | # print "python modFuzzer.py -D 192.168.0.123 -p 8888" 407 | print "python modFuzzer.py -I 192.168.0.23 0000000000060103000A0001" 408 | print "python modFuzzer.py -S 192.168.0.0/24" 409 | print "python modFuzzer.py -F10 192.168.0.0" 410 | print "" 411 | exit(1) 412 | 413 | argv1 = sys.argv[1] 414 | argv2 = sys.argv[2] 415 | argv3 = '' 416 | if len(sys.argv) > 3: 417 | argv3 = sys.argv[3] 418 | 419 | if (argv1=='-D') or (argv1=='--dumb'): # dumb fuzzing 420 | dumb_fuzzing(argv2) 421 | sys.exit(1) 422 | 423 | elif (argv1=='-I') or (argv1=='--input'): # smart user input 424 | smart_fuzzing_with_user_input(argv2, argv3) 425 | 426 | elif (argv1=='-S') or (argv1=='--scan') or (argv1=='-SD'): # scan device 427 | if argv1 =='-SD' : 428 | dumbflagset = 1 429 | scan_device(argv2) 430 | 431 | elif (argv1=='-F08') or (argv1=='--f08'): # smart fuzzing for function code 0x08 432 | smart_fuzzing_for_func08h(argv2) 433 | 434 | elif (argv1=='-S0F') or (argv1=='--f0f'): # smart fuzzing for function code 0x0F 435 | smart_fuzzing_for_func0Fh(argv2) 436 | 437 | elif (argv1=='-S10') or (argv1=='--f10'): # smart fuzzing for function code 0x10 438 | smart_fuzzing_for_func10h(argv2) 439 | 440 | elif (argv1=='--sim') or (argv1=='--sim'): 441 | simulator(argv2) 442 | 443 | sys.exit(0) 444 | --------------------------------------------------------------------------------