Lucene search

K
openvasCopyright (C) 2016 Greenbone AGOPENVAS:1361412562310106099
HistoryJun 17, 2016 - 12:00 a.m.

Siemens SIMATIC S7 Device Detection (COTP)

2016-06-1700:00:00
Copyright (C) 2016 Greenbone AG
plugins.openvas.org
1235

COTP (Connection-Oriented Transport Protocol) based detection
of Siemens SIMATIC S7 devices.

# SPDX-FileCopyrightText: 2016 Greenbone AG
# Some text descriptions might be excerpted from (a) referenced
# source(s), and are Copyright (C) by the respective right holder(s).
#
# SPDX-License-Identifier: GPL-2.0-only

if(description)
{
  script_oid("1.3.6.1.4.1.25623.1.0.106099");
  script_version("2023-10-13T05:06:10+0000");
  script_tag(name:"last_modification", value:"2023-10-13 05:06:10 +0000 (Fri, 13 Oct 2023)");
  script_tag(name:"creation_date", value:"2016-06-17 17:08:52 +0700 (Fri, 17 Jun 2016)");
  script_tag(name:"cvss_base", value:"0.0");
  script_tag(name:"cvss_base_vector", value:"AV:N/AC:L/Au:N/C:N/I:N/A:N");

  script_tag(name:"qod_type", value:"remote_banner");

  script_name("Siemens SIMATIC S7 Device Detection (COTP)");

  script_category(ACT_GATHER_INFO);

  script_copyright("Copyright (C) 2016 Greenbone AG");
  script_family("Product detection");
  script_dependencies("find_service.nasl");
  script_require_ports(102);

  script_tag(name:"summary", value:"COTP (Connection-Oriented Transport Protocol) based detection
  of Siemens SIMATIC S7 devices.");

  exit(0);
}

include("byte_func.inc");
include("dump.inc");
include("host_details.inc");
include("http_func.inc");
include("misc_func.inc");
include("port_service_func.inc");

function cotp_send_recv(req, soc) {
  local_var req, soc;

  send(socket: soc, data:req);
  recv = recv(socket: soc, length: 6, min: 6);

  if (strlen(recv) < 6)
    return;

  len = (getword(blob: recv, pos: 2) - 6);

  if (len < 1 || len > 65535)
    return;

  recv += recv(socket: soc, length: len);

  if (strlen(recv) != (len + 6))
    return;

  return recv;
}

function cotp_extract_packet(data) {
  local_var data;

  cotpPacket = substr(data, 7);
  if (hexstr(cotpPacket[1]) == "01" || hexstr(cotpPacket[1]) == "07")
    header_length = 10;
  else
    header_length = 12;

  param_length = getword(blob: cotpPacket, pos: 6);
  return substr(cotpPacket, header_length + param_length);
}

port = 102;

if (!get_port_state(port))
  exit(0);

soc = open_sock_tcp(port);
if (!soc)
  exit(0);

# COTP connection request
connectionReq = raw_string(0x03, 0x00,         # TPKT (version 3) "Emulate" ISO transport services COTP on top of TCP
                           0x00, 0x16,         #   length
                           0x11,               # COTP: length
                           0xe0,               #   PDU Type (CR Connect Request)
                           0x00, 0x00,         #   Destination reference
                           0x00, 0x02,         #   Source reference (can be set by the client)
                           0x00,               #   Class/extended format/flow control
                           0xc1,               #   Parameter code (src-tsap)
                           0x02,               #   Parameter length
                           0x01, 0x00,         #   Source TSAP
                           0xc2,               #   Parameter code (dst-tsap)
                           0x02,               #   Parameter length
                           0x01, 0x02,         #   Destination TSAP
                           0xc0,               #   Parameter code (tpdu-size)
                           0x01,               #   Parameter length
                           0x0a);              #   TPDU size

recv = cotp_send_recv(req: connectionReq, soc: soc);

if (!recv || hexstr(recv[5]) != "d0") {
  # we have to open a new socket
  close(soc);

  soc = open_sock_tcp(port);
  if (!soc)
    exit(0);

  # nb: Try an alternative request
  connectionReq = raw_string(0x03, 0x00, 0x00, 0x16, 0x11, 0xe0, 0x00, 0x00,
                             0x00, 0x05, 0x00, 0xc1, 0x02, 0x01, 0x00, 0xc2,
                             0x02, 0x02, 0x00, 0xc0, 0x01, 0x0a);
  recv = cotp_send_recv(req: connectionReq, soc: soc);

  if (!recv || hexstr(recv[5]) != "d0") {
    close(soc);
    exit(0);
  }
}

negotiatePdu = raw_string(0x03, 0x00, 0x00, 0x19,       # TPKT header
                          0x02,                         # COTP: length
                          0xf0,                         #   PDU Type (DT Data)
                          0x80,                         #   Flags
                          0x32,                         # S7 Communication: Protocol ID
                          0x01,                         #   Header: ROSCTR: Job
                          0x00, 0x00,                   #     Redundancy Identification (Reserved)
                          0x00, 0x00,                   #     Protocol Data Unit Reference
                          0x00, 0x08,                   #     Parameter length
                          0x00, 0x00,                   #     Data length
                          0xf0,                         #   Parameter: Setup communictation
                          0x00,                         #     Reserved
                          0x00, 0x01,                   #     Max AmQ calling
                          0x00, 0x01,                   #     Max AmQ called
                          0x01, 0xe0);                  #     PDU length

recv = cotp_send_recv(req: negotiatePdu, soc: soc);

# nb: S7 Comm response ACK
if (!recv || hexstr(recv[8] != "03")) {
  close(soc);
  exit(0);
}

readModuleID = raw_string(0x03, 0x00, 0x00, 0x21,       # TPKT header
                          0x02, 0xf0, 0x80,             # COTP
                          0x32,                         # S7 Communication
                          0x07,                         #   Header: ROSCTR: Userdata
                          0x00, 0x00,                   #     Redundancy ID
                          0x00, 0x00,                   #     PDU Ref
                          0x00, 0x08,                   #     Param length
                          0x00, 0x08,                   #     Data Length
                          0x00, 0x01, 0x12,             #   Parameter: Parameter header
                          0x04,                         #     length
                          0x11,
                          0x44,                         #     Type: Request / Function Group: CPU functions
                          0x01,                         #     Subfunction: Read SZL
                          0x00,                         #     Sequence number
                          0xff,                         #   Data: Return code: Success
                          0x09,                         #     Transport size
                          0x00, 0x04,                   #     length
                          0x00, 0x11,                   #     SZL-ID  (Module identification)
                          0x00, 0x01);                  #     SZL-Index

recv = cotp_send_recv(req: readModuleID, soc: soc);

if (!recv) {
  close(soc);
  exit(0);
}

dataPacket = cotp_extract_packet(data: recv);

# Return code must be "Success"
if (hexstr(dataPacket[0]) != "ff") {
  close(soc);
  exit(0);
}

version = "unknown";

if (strlen(dataPacket) >= 96) {
  # Version
  ver = hexstr(substr(dataPacket, 93, 95));

  v1 = ver[0] + ver[1];
  v2 = ver[2] + ver[3];
  v3 = ver[4] + ver[5];
  version = hex2dec(xvalue: v1) + "." + hex2dec(xvalue: v2) + "." + hex2dec(xvalue: v3);

  # Module
  module = substr(dataPacket, 14, 32);
  set_kb_item(name: "siemens/simatic_s7/cotp/module", value: module);
}

log_message(port: port, data: "A Siemens SIMATIC S7 service answering to COTP requests seems to be running on this port.");
# nb: Register the service since we can be quite sure that this talks COTP
service_register(port: port, proto: "cotp", ipproto: "tcp", message: "A Siemens SIMATIC S7 service answering to COTP requests seems to be running on this port.");

# Read the component identifications to extract the model
readComponentID = raw_string(0x03, 0x00, 0x00, 0x21, 0x02, 0xf0, 0x80, 0x32,
                             0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00,
                             0x08, 0x00, 0x01, 0x12, 0x04, 0x11, 0x44, 0x01,
                             0x00, 0xff, 0x09, 0x00, 0x04, 0x00, 0x1c, 0x00,
                             0x01);

recv = cotp_send_recv(req: readComponentID, soc: soc);
close(soc);

model = "unknown";

if (recv) {
  dataPacket = cotp_extract_packet(data: recv);

  if (hexstr(dataPacket[0]) == "ff") {
    dataPacket = substr(dataPacket, 4);

    element_size = getword(blob: dataPacket, pos: 4);
    dataPacket = substr(dataPacket, 8);

    for (i=0; i<strlen(dataPacket); i=i+element_size) {
      element = substr(dataPacket, i, i+element_size);
      if (hexstr(element[1]) == "01") {
        plcName = substr(element, 2);
        mod = eregmatch(pattern: "simatic([ ,]+)?(.*)", string: plcName, icase: TRUE);
        if (mod[2]) {
          model = mod[2];
        }
      }
      else
        if (hexstr(element[1]) == "02") {
          moduleName = substr(element, 2);
          mod = eregmatch(pattern: "((CPU )||(S7-))(.*)", string: moduleName, icase: TRUE);
          if (mod[4]) {
            model = mod[4];
          }
       }
       else if (hexstr(element[1]) == "07") {
         moduleType = substr(element, 2);
         model = moduleType;
         set_kb_item(name: "siemens/simatic_s7/cotp/modtype", value: moduleType);
       }
    }
  }
}

if (version != "unknown") {
  set_kb_item(name: "siemens/simatic_s7/detected", value: TRUE);
  if (model != "unknown") {
    # The extracted model string might included some non-printable characters so normalize it first
    model = bin2string(ddata: model, noprint_replacement: "");
    if (egrep(string: model, pattern: "^(CPU )?3.."))
      model = 300;
    set_kb_item(name: "siemens/simatic_s7/cotp/model", value: model);
  }

  if (version != "unknown")
    set_kb_item(name: "siemens/simatic_s7/cotp/" + port + "/version", value: version);

  set_kb_item(name: "siemens/simatic_s7/cotp/port", value: port);
}

exit(0);