工控系统蜜罐建设与协议仿真技术分享

0x1、简介

随着Eripp、Shodan、Zoomeye类似的网络空间搜索引擎先后的出现,网络扫描技术的发展和探测的增多,工控设备、物联网设备、基础设施等作为互联网的一部分,已逐渐被攻击者所重视。从2010年的震网到如今的Havex,工控网络作为一个相对封闭的网络则出现了越来越多更具有针对性的攻击事件和恶意程序。有针对的模拟工控系统某些特征的蜜罐出现也变得必要,蜜罐作为一种相对主动的安全检测手段,相信在未来使用的会越来越广泛。

0x2、工控蜜罐日志分析

Link:工控蜜罐日志收集项目链接地址

博主于6月低在香港和大陆外网节点搭建了设备协议仿真程序,用于发现主动扫描和来源IP的操作行为,根据积累的日志得出了如下结论:
A、目前针对特定端口(tcp/102、tcp/502)的扫描和识别的源IP均来自于国外。
s7comm_sim1
B、对协议的识别使用的是已知公开的技术。(由ScadaStrangeLove发布的plcscan和Digitalbond移植的基于nmap的nse识别枚举脚本,这一点可以根据目前接收到的交互报文断定,如读取西门子PLC设备时使用的是SZL请求,识别Modbus设备型号信息使用了43号功能码)。
s7comm_modbus_sim1
C、协议仿真程序并未收到恶意攻击和写入指令(例如西门子PLC CPU的stop指令和数据写入指令等,modbus的05、06等具有写入功能的功能码,详请参考日志)。

0x3、快速实现协议仿真

案例一

Modbus协议介绍

Modbus协议是工控网络中最常见的一种协议之一,该协议由莫迪康于1979年,为使用可编程逻辑控制器(PLC)而发表,因为简单易用至今使用已经相当广泛,如RTU、PLC、传感器等,协议可以适用于以太网、串口。同样因为使用广泛,公网暴露数量较多(Shodan查询连接)。

Modbus协议服务端实例脚本
Link:Modbus协议资料
#!/usr/bin/python  
# version 2.7
# Source Code form https://github.com/tecpal/PyModbus
# change Z-0ne

import os
import sys
import socket,thread
from array import array
from time import sleep, ctime
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(('',502))
s.listen(10)
F = open('c:\modbus.log','a',0)
sys.stdout = F
def TCP(conn,addr,F):
  buffer = array('B',[0]*300)
  while 1:
    try:
      conn.recv_into(buffer)
      ID = buffer[6]
      FC = buffer[7]
      mADR = buffer[8]
      lADR = buffer[9]
      ADR = mADR*256+lADR
      LEN = buffer[10]*256+buffer[11]
      BYT = LEN*2
      print "Received = ",buffer[0:13+buffer[12]]
      if (FC < 5 and FC > 0):   #Read Inputs or Registers
        DAT = array('B')
        if FC < 3: 
          BYT = (lambda x: x/8 if (x%8==0) else x/8+1)(LEN)     #Round off the no. of bytes
          v = 85          #send 85,86.. for bytes.
          for i in range(BYT): 
            DAT.append(v)
            v = (lambda x: x+1 if (x<255) else 85)(v)
        else:
          for i in range(LEN):  #Sends back the address as data
            DAT.append(mADR)
            DAT.append(lADR)
            if (lADR == 255):
              lADR = 0
              mADR = mADR + 1
            else: lADR = lADR + 1
        print "ID= %d,  Fun.Code= %d,  Address= %d,  Length= %d" %(ID, FC, ADR, LEN)
        conn.send(array('B', [0,0,0,0,0, BYT+3, ID, FC, BYT]) + DAT )
      elif (FC == 15 or FC == 16 or FC == 6 or FC == 43 or FC == 17):    #Write Registers
        BYT = buffer[12]
        conn.send(array('B', [0,0,0,0,0, 6, ID, FC, mADR, lADR, buffer[10], buffer[11] ] ) )
        buf = buffer[13:(13+BYT)]
        message = ': ADR:'+str(ADR)+' '
        if FC == 15:
          print "ID= %d,  Fun.Code= %d,  Address= %d,  Length= %d,  Bytes= %d" %(ID, FC, ADR, LEN, BYT)
          for j in range(BYT):  message = message+('Byte:'+str(j)+'='+str(buf[j])+', ')
        elif FC == 16:
          print "ID= %d,  Fun.Code= %d,  Address= %d,  Length= %d,  Bytes= %d" %(ID, FC, ADR, LEN, BYT)
          for j in range(BYT/2): message = message+('Reg:'+str(j)+'='+str((buf[j*2]<<8)+(buf[j*2+1]))+', ')
        elif FC == 6:
          print "ID= %d,  Fun.Code= %d,  Address= %d, Bytes= %d" %(ID, FC, ADR, LEN)
          message = message+('Reg:'+str(LEN))
        elif FC == 43:
          print "ID= %d,  Fun.Code= %d,  Address= %d, Bytes= %d" %(ID, FC, ADR, LEN)
          message = message+('Reg:'+str(LEN))
          conn.send(bytes(bytearray([0x00, 0x00, 
										0x00, 0x00, 
										0x00, 0x32, 
										0x00, 
										0x2b, #43 FC
										0x0e, 0x01, 0x81, 0x00, 0x00, 0x03, 
										0x00, 0x14, 0x53, 0x63, 0x68, 0x6e, #Schneider Electric
										0x65, 0x69, 0x64, 0x65, 0x72, 0x20, 
										0x45, 0x6c, 0x65, 0x63, 0x74, 0x72, 
										0x69, 0x63, 0x20, 0x20, 0x01, 0x0c, 
										0x42, 0x4d, 0x58, 0x20, 0x50, 0x33, #BMX P34 20 20
										0x34, 0x20, 0x32, 0x30, 0x32, 0x30, 
										0x02, 0x04, 0x76, 0x32, 0x2e, 0x32	#V2.2
										])))
        elif FC == 17:
          print "ID= %d,  Fun.Code= %d,  Address= %d, Bytes= %d" %(ID, FC, ADR, LEN)
          message = message+('Reg:'+str(LEN))
          conn.send(array('B', [0,0,0,0,0,3,FC,171,1] ) )#Illegal function
        F.write(ctime() + message + "\n")
      else:
        conn.send(array('B', [0,0,0,0,0,3,FC,171,1])) #Illegal function
        print "Funtion Code %d Not Supported" %FC
        F.write(ctime() + message + "\n")
        exit()
      sleep(1)
    except Exception, e:
      print e, "\nConnection with Client terminated"
      F.write(ctime() + "\n")
      exit()
while 1:
  conn, addr = s.accept()
  print "Connected by", addr[0]
  thread.start_new_thread(TCP,(conn,addr,F))

modbus_sim1

案例二

西门子S7协议介绍

西门子S7系列或CP模块使用以太网通信时主要基于ISO TCP (RFC1006)和西门子自有S7协议实现,并且协议详细实现由厂商自行持有,官方并未公开,外部应用与西门子S7系列PLC使用以太网通讯时可以使用西门子官方组件或者非官方实现的的开源通讯库实现,目前第三方开源的通讯库同样也实现了较多功能,如libnodave,snap7等均已解码了西门子PLC的较多操作功能。

西门子S7协议服务端介绍

西门子S7协议实现较为较为复杂,并且功能较多,实现高交互需要利用西门子的开源协议栈实现,以SNAP7项目为例,该项目中提供了PLC服务端的部分功能仿真,使用时只需对固定特征进行修改即可实现PLC服务端的功能。

0x4、注意事项

1、 使用Conpot类似开源工控蜜罐系统需要注意本地化,使用默认配置是不可取的。如下图
conpot_s7comm_sim1

About Z-0ne

Leave a Reply

Your email address will not be published. Required fields are marked *