个人中心
奖励计划
指纹插件奖励计划
指纹插件提交机制
插件编写机制
  • lua指纹脚本
  • python指纹脚本

指纹插件奖励计划

  •  
  • 用户提交的指纹在验证通过后,将获得丰厚的现金奖励,具体的奖励计划如下:
  •  
  • 1)工控设备指纹奖励:¥3000~¥5000;
  • 2)视频监控设备指纹奖励:¥800;
  • 3)网络设备指纹奖励:¥800;
  • 4)安全防护设备指纹奖励:¥1000;
  • 5)工控系统信息设备指纹奖励:¥1000;
  • 6)特别指纹贡献,面议。
  •  
  • 注意:根据设备指纹收集的难易度,奖励金额有所不同,设备指纹的分类详见工控设备资产分类表;我们收集的指纹,排除搜索专题中已经存在的协议。
工控设备资产分类表
资产大类 资产中类 资产小类
可编程逻辑控制器(PLC)(包括但不限于以下主流设备:西门子、施耐德、罗克韦尔、三菱、GE、台达、Beckoff、Bachmann、Koyo、Omron)
工控设备 工控设备
远程终端控制系统(RTU)(包括但不限于以下主流设备:安控、凯山、金时、庆仪、航天三院)
集散控制系统(DCS)(包括但不限于以下主流设备:艾默生、霍尼韦尔、横河、西屋、FOXBORO、ABB、浙江中控、和利时、国电智深)
数据采集模块(包括但不限于以下主流设备:研华、泓格)
继电保护装置(包括但不限于以下主流设备:南瑞、四方、南自、许继、西门子、施耐德、ABB)
无线传输模块DTU(包括但不限于以下主流设备:宏电、四信、桑荣、才茂、蓝斯 、驿唐)
变频器(包括但不限于以下主流设备:LG、ABB、富士、松下、台达)
视频监控设备 网络摄像机(IP Camera)(包括但不限于以下主流设备:海康威视、大华、宇视、天地伟业、FOSCAM、亚安、三星、佳能、安讯士、松下、景阳、汉邦高科、D-Link、AVTECH、VIVOTEK)
网络视频录像机(NVR)(包括但不限于以下主流设备:海康威视、大华、宇视、天地伟业、FOSCAM、亚安、三星、佳能、安讯士、松下、景阳、汉邦高科、D-Link、AVTECH、VIVOTEK)
数字录像机(DVR)(包括但不限于以下主流设备:海康威视、大华、宇视、天地伟业、FOSCAM、亚安、三星、佳能、安讯士、松下、景阳、汉邦高科、D-Link、AVTECH、VIVOTEK)
视频管理服务器(VMS)(包括但不限于以下主流设备:海康威视、大华、宇视、天地伟业、FOSCAM、亚安、三星、佳能、安讯士、松下、景阳、汉邦高科、D-Link、AVTECH、VIVOTEK)
工控网络设备及安全设备 网络通信设备 路由器(包括但不限于以下主流设备:H3C、思科、华为等路由器通用系列)
交换机 (包括但不限于以下主流设备:H3C、思科、华为等交换机通用系列) 工业交换机(包括但不限于以下主流设备:摩莎、赫斯曼、东土工业等)
网关
安全防护设备 入侵防御 系统(IPS)
入侵检测系统(IDS)
网闸
防火墙(包括但不限于天融信、网神、网御星云通用系列)
工业防火墙
加密认证设备
工控系统信息设备 服务器 WEB服务器(SCADA为主)
OPC服务器
数据服务器
安全防护服务器
工程站 工程师站(组态软件为主)
操作员站(组态软件为主)
现场操作屏
网络打印机

指纹插件提交机制

  •  
  • 插件发送邮箱:finger@winicssec.com;
  • 邮件名称格式:【插件提交】【XXX协议】【XXX设备】指纹插件提交;
  • 邮件中需提供联系方式、支付方式、指纹插件和验证视频,方便我们同您联系,并在指纹插件验证通过后支付奖励。
  •  
  • 支付方式包括
  • 1. 银行卡信息:银行卡号、银行名称、开户姓名、开户支行。
  • 2. 支付宝信息:支付宝用户名、支付宝帐号。
  •  
  • 联系方式:手机号、微信帐号。
  • 指纹插件:请以附件的方式提交指纹插件程序。
  • 指纹插件验证视频:请以屏幕录屏的方式提供指纹插件验证通过的视频。

插件编写机制

  •  
  • 支持lua、python两种语言的指纹插件。
  • 请在指纹插件程序中,以备注的方式提供如下信息:
  • 1. 传输层协议、应用层协议名称、默认端口号;
  • 2. 设备类型(参考工控设备资产分类表)、厂商、型号、固件/软件版本号、Banner的格式。
  •  
  •  
  • lua指纹脚本:
  •  
  • 用lua语言编写的扫描指纹插件,编程范例参考:
  • https://github.com/digitalbond/Redpoint
  •  
  • lua编写的西门子s7协议指纹脚本范例:
  •  
					

local bin = require "bin"
local nmap = require "nmap"
local shortport = require "shortport"
local stdnse = require "stdnse"
local string = require "string"
local table = require "table"


description = [[
Enumerates Siemens S7 PLC Devices and collects their device information. This
script is based off PLCScan that was developed by Positive Research and
Scadastrangelove (https://code.google.com/p/plcscan/). This script is meant to
provide the same functionality as PLCScan inside of Nmap. Some of the
information that is collected by PLCScan was not ported over; this
information can be parsed out of the packets that are received.

Thanks to Positive Research, and Dmitry Efanov for creating PLCScan
]]

author = "Stephen Hilt (Digital Bond)"
license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
categories = {"discovery", "intrusive"}

---
-- @usage
-- nmap --script s7-info.nse -p 102 
--
-- @output
--102/tcp open  Siemens S7 PLC
--| s7-info:
--|   Basic Hardware: 6ES7 315-2AG10-0AB0
--|   System Name: SIMATIC 300(1)
--|   Copyright: Original Siemens Equipment
--|   Version: 2.6.9
--|   Module Type: CPU 315-2 DP
--|   Module: 6ES7 315-2AG10-0AB0
--|_  Serial Number: S C-X4U421302009
--
--
-- @xmloutput
--6ES7 315-2AG10-0AB0
--SIMATIC 300(1)
--Original Siemens Equipment
--2.6.9
--SimpleServer
--CPU 315-2 DP
--6ES7 315-2AG10-0AB0
--S C-X4U421302009
--


-- port rule for devices running on TCP/102
portrule = shortport.port_or_service(102, "iso-tsap", "tcp")

---
-- Function to send and receive the S7COMM Packet
--
-- First argument is the socket that was created inside of the main Action
-- this will be utilized to send and receive the packets from the host.
-- the second argument is the query to be sent, this is passed in and is created
-- inside of the main action.
-- @param socket the socket that was created in Action.
-- @param query the specific query that you want to send/receive on.
local function send_receive(socket, query)
  local sendstatus, senderr = socket:send(query)
  if(sendstatus == false) then
    return "Error Sending S7COMM"
  end
  -- receive response
  local rcvstatus, response = socket:receive()
  if(rcvstatus == false) then
    return "Error Reading S7COMM"
  end
  return response
end

---
-- Function to parse the first SZL Request response that was received from the S7 PLCC
--
-- First argument is the socket that was created inside of the main Action
-- this will be utilized to send and receive the packets from the host.
-- the second argument is the query to be sent, this is passed in and is created
-- inside of the main action.
-- @param response Packet response that was received from S7 host.
-- @param host The host hat was passed in via Nmap, this is to change output of host/port
-- @param port The port that was passed in via Nmap, this is to change output of host/port
-- @param output Table used for output for return to Nmap
local function parse_response(response, host, port, output)
  -- unpack the protocol ID
  local pos, value = bin.unpack("C", response, 8)
  -- unpack the second byte of the SZL-ID
  local pos, szl_id = bin.unpack("C", response, 31)
  -- set the offset to 0
  local offset = 0
  -- if the protocol ID is 0x32
  if (value == 0x32) then
    local pos
    -- unpack the module information
    pos, output["Module"] = bin.unpack("z", response, 44)
    -- unpack the basic hardware information
    pos, output["Basic Hardware"] = bin.unpack("z", response, 72)
    -- set version number to 0
    local version = 0
    -- parse version number
    local pos, char1, char2, char3 = bin.unpack("CCC", response, 123)
    -- concatenate string, or if string is nil make version number 0.0
    output["Version"] = table.concat({char1 or "0.0", char2, char3}, ".")
    -- return the output table
    return output
  else
    return nil
  end
end

---
-- Function to parse the second SZL Request response that was received from the S7 PLC
--
-- First argument is the socket that was created inside of the main Action
-- this will be utilized to send and receive the packets from the host.
-- the second argument is the query to be sent, this is passed in and is created
-- inside of the main action.
-- @param response Packet response that was received from S7 host.
-- @param output Table used for output for return to Nmap
local function second_parse_response(response, output)
  local offset = 0
  -- unpack the protocol ID
  local pos, value = bin.unpack("C", response, 8)
  -- unpack the second byte of the SZL-ID
  local pos, szl_id = bin.unpack("C", response, 31)
  -- if the protocol ID is 0x32
  if (value == 0x32) then
    -- if the szl-ID is not 0x1c
    if( szl_id ~= 0x1c ) then
      -- change offset to 4, this is where most of valid PLCs will fall
      offset = 4
    end
    -- parse system name
    pos, output["System Name"] = bin.unpack("z", response, 40 + offset)
    -- parse module type
    pos, output["Module Type"] = bin.unpack("z", response, 74 + offset)
    -- parse serial number
    pos, output["Serial Number"] = bin.unpack("z", response, 176 + offset)
    -- parse plant identification
    pos, output["Plant Identification"] = bin.unpack("z", response, 108 + offset)
    -- parse copyright
    pos, output["Copyright"] = bin.unpack("z", response, 142 + offset)

    -- for each element in the table, if it is nil, then remove the information from the table
    for key, value in pairs(output) do
      if(string.len(output[key]) == 0) then
        output[key] = nil
      end
    end
    -- return output
    return output
  else
    return nil
  end
end
---
--  Function to set the nmap output for the host, if a valid S7COMM packet
--  is received then the output will show that the port is open
--  and change the output to reflect an S7 PLC
--
-- @param host Host that was passed in via nmap
-- @param port port that S7COMM is running on
local function set_nmap(host, port)
  --set port Open
  port.state = "open"
  -- set that detected an Siemens S7
  port.version.name = "iso-tsap"
  port.version.devicetype = "specialized"
  port.version.product = "Siemens S7 PLC"
  nmap.set_port_version(host, port)
  nmap.set_port_state(host, port, "open")

end
---
--  Action Function that is used to run the NSE. This function will send the initial query to the
--  host and port that were passed in via nmap. The initial response is parsed to determine if host
--  is a S7COMM device. If it is then more actions are taken to gather extra information.
--
-- @param host Host that was scanned via nmap
-- @param port port that was scanned via nmap
action = function(host, port)
  -- COTP packet with a dst of 102
local COTP = bin.pack("H", "0300001611e00000001400c1020100c2020" .. "102" .. "c0010a")
  -- COTP packet with a dst of 200
  local alt_COTP = bin.pack("H", "0300001611e00000000500c1020100c2020" .. "200" .. "c0010a")
  -- setup the ROSCTR Packet
  local ROSCTR_Setup = bin.pack("H", "0300001902f08032010000000000080000f0000001000101e0")
  -- setup the Read SZL information packet
  local Read_SZL = bin.pack("H", "0300002102f080320700000000000800080001120411440100ff09000400110001")
  -- setup the first SZL request (gather the basic hardware and version number)
  local first_SZL_Request = bin.pack("H", "0300002102f080320700000000000800080001120411440100ff09000400110001")
  -- setup the second SZL request
  local second_SZL_Request = bin.pack("H", "0300002102f080320700000000000800080001120411440100ff090004001c0001")
  -- response is used to collect the packet responses
  local response
  -- output table for Nmap
  local output = stdnse.output_table()
  -- create socket for communications
  local sock = nmap.new_socket()
  -- connect to host
  local constatus, conerr = sock:connect(host, port)
  if not constatus then
    stdnse.debug1('Error establishing connection for %s - %s', host, conerr)
    return nil
  end
  -- send and receive the COTP Packet
  response  = send_receive(sock, COTP)
  -- unpack the PDU Type
  local pos, CC_connect_confirm = bin.unpack("C", response, 6)
  -- if PDU type is not 0xd0, then not a successful COTP connection
  if ( CC_connect_confirm ~= 0xd0) then
    sock:close()
    -- create socket for communications
    stdnse.debug1('S7INFO:: CREATING NEW SOCKET')
    sock = nmap.new_socket()
    -- connect to host
    local constatus, conerr = sock:connect(host, port)
    if not constatus then
      stdnse.debug1('Error establishing connection for %s - %s', host, conerr)
      return nil
    end
    response = send_receive(sock, alt_COTP)
    local pos, CC_connect_confirm = bin.unpack("C", response, 6)
    if ( CC_connect_confirm ~= 0xd0) then
      stdnse.debug1('S7 INFO:: Could not negotiate COTP')
      return nil
    end
  end
  -- send and receive the ROSCTR Setup Packet
  response  = send_receive(sock, ROSCTR_Setup)
  -- unpack the protocol ID
  local pos, protocol_id = bin.unpack("C", response, 8)
  -- if protocol ID is not 0x32 then return nil
  if ( protocol_id ~= 0x32) then
    return nil
  end
  -- send and receive the READ_SZL packet
  response  = send_receive(sock, Read_SZL)
  local pos, protocol_id = bin.unpack("C", response, 8)
  -- if protocol ID is not 0x32 then return nil
  if ( protocol_id ~= 0x32) then
    return nil
  end
  -- send and receive the first SZL Request packet
  response  = send_receive(sock, first_SZL_Request)
  -- parse the response for basic hardware information
  output = parse_response(response, host, port, output)
  -- send and receive the second SZL Request packet
  response = send_receive(sock, second_SZL_Request)
  -- parse the response for more information
  output = second_parse_response(response, output)
  -- close the socket
  sock:close()

  -- If we parsed anything, then set the version info for Nmap
  if #output > 0 then
    set_nmap(host, port)
  end
  -- return output to Nmap
  return output

end

				
					

插件编写机制

  •  
  • 支持lua、python两种语言的指纹插件。
  • 请在指纹插件程序中,以备注的方式提供如下信息:;
  • 1. 传输层协议、应用层协议名称、默认端口号;
  • 2. 设备类型(参考工控设备资产分类表)、厂商、型号、固件/软件版本号、Banner的格式。
  •  
  •  
  • python指纹脚本:
  •  
  • 用python语言编写的扫描指纹插件,编程范例参考:
  • https://code.google.com/p/plcscan/
  •  
  • python编写的西门子s7协议指纹脚本范例:
  •  
						

"""
File: s7.py
Desc: Partial implementation of s7comm protocol
Version: 0.1
"""

from struct import *
from random import randint
from optparse import OptionGroup

import struct
import socket
import string

__FILTER = "".join([' '] + [' ' if chr(x) not in string.printable or chr(x) in string.whitespace else chr(x) for x in range(1,256)])
def StripUnprintable(msg):
    return msg.translate(__FILTER)

class TPKTPacket:
    """ TPKT packet. RFC 1006
    """
    def __init__(self, data=''):
        self.data = str(data)
    def pack(self):
        return pack('!BBH',
            3,                  # version
            0,                  # reserved
            len(self.data)+4    # packet size
        ) + str(self.data)
    def unpack(self,packet):
        try:
            header = unpack('!BBH', packet[:4])
        except struct.error as e:
            raise S7ProtocolError("Unknown TPKT format")

        self.data = packet[4:4+header[2]]
        return self

class COTPConnectionPacket:
    """ COTP Connection Request or Connection Confirm packet (ISO on TCP). RFC 1006
    """
    def __init__(self, dst_ref=0, src_ref=0, dst_tsap=0, src_tsap=0, tpdu_size=0):
        self.dst_ref    = dst_ref
        self.src_ref    = src_ref
        self.dst_tsap   = dst_tsap
        self.src_tsap   = src_tsap
        self.tpdu_size  = tpdu_size

    def pack(self):
        """ make Connection Request Packet
        """
        return pack('!BBHHBBBHBBHBBB',
            17,             # size
            0xe0,           # pdu type: CR
            self.dst_ref,
            self.src_ref,
            0,              # flag
            0xc1, 2, self.src_tsap,
            0xc2, 2, self.dst_tsap,
            0xc0, 1, self.tpdu_size )
    def __str__(self):
        return self.pack()

    def unpack(self, packet):
        """ parse Connection Confirm Packet (header only)
        """
        try:
            size, pdu_type, self.dst_ref, self.src_ref, flags = unpack('!BBHHB', packet[:7])
        except struct.error as e:
            raise S7ProtocolError("Wrong CC packet format")
        if len(packet) != size + 1:
            raise S7ProtocolError("Wrong CC packet size")
        if pdu_type != 0xd0:
            raise S7ProtocolError("Not a CC packet")

        return self

class COTPDataPacket:
    """ COTP Data packet (ISO on TCP). RFC 1006
    """
    def __init__(self, data=''):
        self.data = data
    def pack(self):
        return pack('!BBB',
            2,                      # header len
            0xf0,                   # data packet
            0x80) + str(self.data)
    def unpack(self, packet):
        self.data = packet[ord(packet[0])+1:]
        return self
    def __str__(self):
        return self.pack()

class S7Packet:
    """ S7 packet
    """
    def __init__(self, type=1, req_id=0, parameters='', data=''):
        self.type       = type
        self.req_id     = req_id
        self.parameters = parameters
        self.data       = data
        self.error      = 0

    def pack(self):
        if self.type not in [1,7]:
            raise S7ProtocolError("Unknown pdu type")
        return ( pack('!BBHHHH',
            0x32,                   # protocol s7 magic
            self.type,              # pdu-type
            0,                      # reserved
            self.req_id,            # request id
            len(self.parameters),   # parameters length
            len(self.data)) +       # data length
                 self.parameters +
                 self.data )

    def unpack(self, packet):
        try:
            if ord(packet[1]) in [3,2]:   # pdu-type = response
                header_size = 12
                magic0x32, self.type, reserved, self.req_id, parameters_length, data_length, self.error = unpack('!BBHHHHH', packet[:header_size])
                if self.error:
                    raise S7Error(self.error)
            elif ord(packet[1]) in [1,7]:
                header_size = 10
                magic0x32, self.type, reserved, self.req_id, parameters_length, data_length = unpack('!BBHHHH', packet[:header_size])
            else:
                raise S7ProtocolError("Unknown pdu type (%d)" % ord(packet[1]))
        except struct.error as e:
            raise S7ProtocolError("Wrong S7 packet format")

        self.parameters = packet[header_size:header_size+parameters_length]
        self.data = packet[header_size+parameters_length:header_size+parameters_length+data_length]
        return self

    def __str__(self):
        return self.pack()


class S7ProtocolError(Exception):
    def __init__(self, message, packet=''):
        self.message = message
        self.packet = packet
    def __str__(self):
        return "[ERROR][S7Protocol] %s" % self.message

class S7Error( Exception ):
    _errors = {
        # s7 data errors
        0x05: 'Address Error',
        0x0a: 'Item not available',
        # s7 header errors
        0x8104: 'Context not supported',
        0x8500: 'Wrong PDU size'
    }
    def __init__(self, code):
        self.code = code
    def __str__(self):
        if S7Error._errors.has_key(self.code):
            message = S7Error._errors[self.code]
        else:
            message = 'Unknown error'
        return "[ERROR][S7][0x%x] %s" % (self.code, message)


def Split(ar,size):
    """ split sequence into blocks of given size
    """
    return [ar[i:i+size] for i in range(0, len(ar), size)]

class s7:
    def __init__(self, ip, port, src_tsap=0x200, dst_tsap=0x201, timeout=8):
        self.ip       = ip
        self.port     = port
        self.req_id   = 0
        self.s        = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

        self.dst_ref  = 0
        self.src_ref  = 0x04
        self.dst_tsap = dst_tsap
        self.src_tsap = src_tsap
        self.timeout  = timeout

    def Connect(self):
        """ Establish ISO on TCP connection and negotiate PDU
        """
        #sleep(1)
        self.src_ref = randint(1, 20)
        self.s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.s.settimeout(self.timeout)
        self.s.connect((self.ip, self.port))
        self.s.send(TPKTPacket(COTPConnectionPacket(self.dst_ref, self.src_ref, self.dst_tsap, self.src_tsap, 0x0a)).pack())
        reply = self.s.recv(1024)
        response = COTPConnectionPacket().unpack(TPKTPacket().unpack(reply).data)

        self.NegotiatePDU()

    def Request(self, type, parameters='', data=''):
        """ Send s7 request and receive response
        """
        packet = TPKTPacket(COTPDataPacket(S7Packet(type, self.req_id, parameters, data))).pack()
        self.s.send(packet)
        reply = self.s.recv(1024)
        response = S7Packet().unpack(COTPDataPacket().unpack(TPKTPacket().unpack(reply).data).data)
        if self.req_id != response.req_id:
            raise S7ProtocolError('Sequence ID not correct')
        return response


    def NegotiatePDU(self, pdu=480):
        """ Send negotiate pdu request and receive response. Reply no matter
        """
        response = self.Request(0x01, pack('!BBHHH',
            0xf0,       # function NegotiatePDU
            0x00,       # unknown
            0x01,       # max number of parallel jobs
            0x01,       # max number of parallel jobs
            pdu))      # pdu length

        func, unknown, pj1, pj2, pdu = unpack('!BBHHH', response.parameters)
        return pdu

    def Function(self, type, group, function, data=''):
        parameters = pack('!LBBBB',
            0x00011200 +            # parameter head (magic)
            0x04,                   # parameter length
            0x11,                   # unknown
            type*0x10+group,        # type, function group
            function,               # function
            0x00 )                  # sequence

        data = pack('!BBH', 0xFF, 0x09, len(data)) + data
        response = self.Request(0x07, parameters, data)

        code, transport_size, data_len = unpack('!BBH', response.data[:4])
        if code != 0xFF:
            raise S7Error(code)
        return response.data[4:]

    def ReadSZL(self, szl_id):
        szl_data = self.Function(
            0x04,                   # request
            0x04,                   # szl-functions
            0x01,                   # read szl
            pack('!HH',
                szl_id,             # szl id
                1))                 # szl index

        szl_id, szl_index, element_size, element_count = unpack('!HHHH', szl_data[:8])

        return Split(szl_data[8:], element_size)

def BruteTsap(ip, port, src_tsaps=(0x100, 0x200), dst_tsaps=(0x102, 0x200, 0x201) ):
    for src_tsap in src_tsaps:
        for dst_tsap in dst_tsaps:
            try:
                con = s7(ip, port)
                con.src_tsap = src_tsap
                con.dst_tsap = dst_tsap
                con.Connect()
                return src_tsap, dst_tsap

            except S7ProtocolError as e:
                pass

    return None

def GetIdentity(ip, port, src_tsap, dst_tsap):
    res = []

    szl_dict = {
        0x11:
                { 'title': 'Module Identification',
                  'indexes': {
                      1:'Module',
                      6:'Basic Hardware',
                      7:'Basic Firmware'
                  },
                  'packer': {
                      (1, 6): lambda(packet): "{0:s} v.{2:d}.{3:d}".format(*unpack('!20sHBBH', packet)),
                      (7,): lambda(packet): "{0:s} v.{3:d}.{4:d}.{5:d}".format(*unpack('!20sHBBBB', packet))
                  }
                },
        0x1c:
                { 'title': 'Component Identification',
                  'indexes': {
                      1: 'Name of the PLC',
                      2: 'Name of the module',
                      3: 'Plant identification',
                      4: 'Copyright',
                      5: 'Serial number of module',
                      6: 'Reserved for operating system',
                      7: 'Module type name',
                      8: 'Serial number of memory card',
                      9: 'Manufacturer and profile of a CPU module',
                      10:'OEM ID of a module',
                      11:'Location designation of a module'
                  },
                  'packer': {
                      (1, 2, 5): lambda(packet): "%s" % packet[:24],
                      (3, 7, 8): lambda(packet): "%s" % packet[:32],
                      (4,): lambda(packet): "%s" % packet[:26]
                  }
                }
    }

    con = s7(ip, port, src_tsap, dst_tsap)
    con.Connect()

    for szl_id in szl_dict.keys():
        try:
            entities = con.ReadSZL(szl_id)
        except S7Error:
            continue

        indexes = szl_dict[szl_id]['indexes']
        packers = szl_dict[szl_id]['packer']
        for item in entities:
            if len(item)>2:
                n, = unpack('!H', item[:2])
                item = item[2:]
                title = indexes[n] if indexes.has_key(n) else "Unknown (%d)" % n

                try:
                    packers_keys = [ i for i in packers.keys() if n in i ]
                    formated_item = packers[packers_keys[0]](item).strip('\x00')
                except (struct.error, IndexError) :
                    formated_item = StripUnprintable(item).strip('\x00')

                res.append("%s: %s\t(%s)" % (title.ljust(25), formated_item.ljust(30), item.encode('hex')))

    return res

def Scan(ip, port, options):
    src_tsaps = [ int(n.strip(), 0) for n in options.src_tsap.split(',') ] if options.src_tsap else [0x100, 0x200]
    dst_tsaps = [ int(n.strip(), 0) for n in options.dst_tsap.split(',') ] if options.dst_tsap else [0x102, 0x200, 0x201]

    res = ()
    try:
        res = BruteTsap(ip, port, src_tsaps, dst_tsaps)
    except socket.error as e:
        print "%s:%d %s" % (ip, port, e)

    if not res:
        return False

    print "%s:%d S7comm (src_tsap=0x%x, dst_tsap=0x%x)" % (ip, port, res[0], res[1])

    # sometimes unexpected exceptions occures, so try to get identity several time
    identities = []
    for attempt in [0, 1]:
        try:
            identities = GetIdentity(ip, port, res[0], res[1])
            break
        except (S7ProtocolError, socket.error) as e:
            print "  %s" % e

    for line in identities:
        print "  %s" % line

    return True

def AddOptions(parser):
    group = OptionGroup(parser, "S7 scanner options")
    group.add_option("--src-tsap", help="Try this src-tsap (list) (default: 0x100,0x200)", type="string", metavar="LIST")
    group.add_option("--dst-tsap", help="Try this dst-tsap (list) (default: 0x102,0x200,0x201)", type="string", metavar="LIST")
    parser.add_option_group(group)