Blog

jabo

SNMP und Simatic S7-1500

Das SNMP (Simple Network Management Protocol) wird häufig für das Managen von Netzwerken verwendet. Es lässt sich aber auch verwenden, um industrielle Steueranlagen zu klassifizieren. Im folgen möchte ich darauf eingehen, wie wir vorgegangen sind, um bei einer Siemens Simatic S7-1500 Informationen rauszukitzeln.

Zunächst nutzten wir gängige Linux-tools, um unter anderem zu ermitteln, welche Felder sich auslesen lassen. Danach schauten wir uns eine solche Abfrage genauer an, um sie implementieren zu können.

Mit dem Protokoll verfügbare Felder ermitteln

SNMP ist ein auf UDP basierendes Protokoll, das in ASN.1 codiert ist und das für Abfragen von Informationen über Netzwerkgeräte und zum Konfigurieren derer verwendet wird. Diese Informationen umfassen im Allgemeinen Gerätenamen, -orte, -besitzer und Ähnliches. Gespeichert werden sie in sogenannten Management Information Bases (MIBs), die sich in einer Plattform unabhängigen Sprache auf dem per SNMP angesprochenem Gerät befinden. In einem MIB können sich mehrere MIB-Variablen befinden. Angesprochen werden diese über einen Object Identifier (OID). Beispielhafte OIDs werden wir im Folgenden noch kennenlernen.

In der Standardeinstellung spricht die S7-1500 Version 1 des SNMP, weshalb sich die weiteren Ausführungen auf diese beschränken. Diese Version hat keine Sicherheitsmechanismen, um die Kommunikation zu schützen oder den Zugriff auf eine kleine Gruppe von Nutzern zu beschränken. Die einzige Möglichkeit, die zum Sichern der Kommunikation existiert, ist der sog. Community String. Ein Passwort, das im Klartext übertragen wird. Es gibt einen Community String, um Variablen zu lesen und einen zum Lesen und Schreiben. Hierbei ist aber anzumerken, dass nicht alle Variablen, die lesbar sind, auch beschreibbar sind. Die Standardeinstellung von Siemens ist public für den Community String, der das Lesen ermöglicht, und privat für den Community String, der das Lesen und Schreiben ermöglicht.

Zum Auslesen von Werten gibt es im Protokoll für uns zwei grundlegend entscheidende Befehle:

  • getRequest
  • getNextRequest

Beide haben eine ähnliche Funktionalität mit dem Unterschied, dass getRequest die MIB-Variable oder eine Liste von mehreren abfragt, getNextRequest hingegen eine iterative Abfrage stellt, d.h. es wird neben dem abgefragten Wert auch die OID des lexikographisch nächsten MIBs in der Antwort erwartet. getNextRequest wird also eher verwendet, um nach einzelnen MIB-Variablen zu suchen, während getRequest eher verwendet wird, wenn die OID und die zugehörige Variable(n) schon bekannt sind.

Um mögliche von der PLC verfügbare Felder zu erhalten, stellen wir zuerst eine getNextRequest-Anfrage mittels snmpwalk: $ snmpwalk -c public -v1 192.168.0.1

wobei die PLC die Adresse 192.168.0.1 besitzt. Die Anfrage, die snmpwalk als erstes stellt, ist die nach der OID 1.3.6.1.2.1. Hinter dieser OID befindet sich u.a. das Feld sysDesrc.0 mit der OID 1.3.6.1.2.1.1.1.0. In diesem Feld sind allgemeine Systeminformationen abgelegt. Den Inhalt dieses Feldes liefert die PLC in der Antwort zusammen mit der lexikographisch nächsten OID, die von snmpwalk als nächstes abgefragt wird. Dieser Vorgang wiederholt sich, bis die PLC keine "neue", also noch nicht angefragte, OID in ihrer Antwort schickt. Hierbei ist anzumerken, dass es noch weitere OIDs mit MIBs dahinter geben kann, die wir so nicht erhalten, weil die PLC uns die entsprechenden OIDs nicht gibt.

Ausschnitte aus der Ausgabe von snmpwalk sehen folgendermaßen aus:

SNMPv2-MIB::sysDescr.0 = STRING: Siemens, SIMATIC S7, CPU1511-1 PN, 6ES7 511-1AK00-0AB0, HW: 5, FW: V1.7.0, ...
SNMPv2-MIB::sysObjectID.0 = OID: SNMPv2-SMI::zeroDotZero
DISMAN-EVENT-MIB::sysUpTimeInstance = Timeticks: (1268180) 3:31:21.80
SNMPv2-MIB::sysContact.0 = STRING:
SNMPv2-MIB::sysName.0 = STRING:
SNMPv2-MIB::sysLocation.0 = STRING:
SNMPv2-MIB::sysServices.0 = INTEGER: 78
...
IF-MIB::ifIndex.1 = INTEGER: 1
IF-MIB::ifIndex.2 = INTEGER: 2
...
IF-MIB::ifDescr.1 = STRING: Siemens, SIMATIC S7, internal, X1
IF-MIB::ifDescr.2 = STRING: Siemens, SIMATIC S7, Ethernet Port, X1 P1R
...
End of MIB

Setzen eines Wertes

Nun wollen wir einzelne Felder manuell abfragen können. Dafür setzen wir erst den Namen des Gerätes auf "alte Klapperkiste". Hierfür verwenden wir snmpset: $ snmpset -c private -v 1 192.168.0.1 sysName.0 s "alte klapperkiste"

Testweise verwenden wir snmpget zur getRequest-Abfrage $ snmpget -c public -v1 192.168.0.1 sysName.0

Die Ausgabe liefert: SNMPv2-MIB::sysName.0 = STRING: alte klapperkiste

Wir konnten den Wert erfolgreich beschreiben. Das ist einer der wenigen Werte, die in dieser PLC beschreibbar sind, denn wir können in dieser Konfiguration nur auf Werte zugreifen, die in vielen SNMP-fähigen Geräten verfügbar sind (Siehe Liste der verfügbaren Felder am Ende des Artikels). Beschreibbar sind daher nur sysContact, sysName und sysLocation (siehe Siemens-SNMP-Dokumentation) Äquivalent zu den Anfragen oben kann anstatt sysName.0 auch 1.3.6.1.2.1.1.5.0 abgefragt werden, da sysName.0 von den Tools in die entsprechende OID aufgelöst wird (siehe auch).

Die Anfrage in Bytes

Um nun die Abfrage des Namens in einem eigenen Programm verwenden zu können, betrachten wir die getRequest-Anfrage genauer. Da SNMP auf UDP aufsetzt, reicht es den UDP-Payload zu analysieren. Dieser sieht beispielsweise so aus:

{width=50%}

Zur Codierung der Nachricht wird, wie bereits angesprochen, ASN.1 verwendet. Um einen Überblick über die Syntax zu bekommen, empfehlen wir diese Anleitung

Für unsere Nachricht sind folgende Felder entscheidend:

Der rote Bereich gibt die Version des SNMP-Protokolles an, in diesem Fall Version 1. Der grüne Bereich gibt den Community String (hier: public) an. Der cyanfarbene Bereich gibt die angefragte OID an. 2b steht für die ersten beiden Teile unserer OID 1.3 .6.1.2.1.1.5.0. Die nachfolgenden Bytes stehen je für einen Teil der OID. Sollten hierbei OIDs abgefragt werden, die Werte größer als 127 beinhalten (z.B. 1.3.6.1.4.1. 4329 .6.3.2.2.0.1), empfehlen wir diese Quelle. Hier wird detailliert erklärt, wie die OIDs codiert werden. So wird aus 1.3.6.1.4.1.4329.6.3.2.2.0.1 2b 06 01 04 01 a1 69 06 03 02 02 00 01

Nun können wir die Anfragen leicht anpassen (hierbei müssen eventuell die Längenfelder angepasst werden - Siehe dafür obigen Link zur Codierung von ASN.1) und manuell durch ein selbst geschriebenes Programm, wie einen Portscanner, stellen.

Um einen Überblick über die verfügbaren Felder dieser Anlage zu liefern, folgt hier eine fast vollständige Auflistung der durch den snmpwalk ermittelten Felder. Allein aus diesen lassen sich nämlich teils auch schon Informationen ablesen. Beispielsweise ist hier erkenntlich, dass zur Zeit der Abfrage der Port 443 geöffnet war, weil ein https-Server aktiv war.

SNMPv2-MIB::sysDescr.0 = STRING:
SNMPv2-MIB::sysObjectID.0 = OID: SNMPv2-SMI::zeroDotZero
DISMAN-EVENT-MIB::sysUpTimeInstance = Timeticks:
SNMPv2-MIB::sysContact.0 = STRING:
SNMPv2-MIB::sysName.0 = STRING:
SNMPv2-MIB::sysLocation.0 = STRING:
SNMPv2-MIB::sysServices.0 = INTEGER:
IF-MIB::ifNumber.0 = INTEGER:
IF-MIB::ifIndex.1 = INTEGER:
IF-MIB::ifDescr.1 = STRING:
IF-MIB::ifType.1 = INTEGER:
IF-MIB::ifMtu.1 = INTEGER:
IF-MIB::ifSpeed.1 = Gauge32:
IF-MIB::ifPhysAddress.1 = STRING:
IF-MIB::ifAdminStatus.1 = INTEGER:
IF-MIB::ifOperStatus.1 = INTEGER:
IF-MIB::ifLastChange.1 = Timeticks:
IF-MIB::ifInOctets.1 = Counter32:
IF-MIB::ifInUcastPkts.1 = Counter32:
IF-MIB::ifInNUcastPkts.1 = Counter32:
IF-MIB::ifInDiscards.1 = Counter32:
IF-MIB::ifInErrors.1 = Counter32:
IF-MIB::ifInUnknownProtos.1 = Counter32:
IF-MIB::ifOutOctets.1 = Counter32:
IF-MIB::ifOutUcastPkts.1 = Counter32:
IF-MIB::ifOutNUcastPkts.1 = Counter32:
IF-MIB::ifOutDiscards.1 = Counter32:
IF-MIB::ifOutErrors.1 = Counter32:
IF-MIB::ifOutQLen.1 = Gauge32:
IF-MIB::ifSpecific.1 = OID: SNMPv2-SMI::zeroDotZero
...
IP-MIB::ipForwarding.0 = INTEGER:
IP-MIB::ipDefaultTTL.0 = INTEGER:
IP-MIB::ipInReceives.0 = Counter32:
IP-MIB::ipInHdrErrors.0 = Counter32:
IP-MIB::ipInAddrErrors.0 = Counter32:
IP-MIB::ipForwDatagrams.0 = Counter32:
IP-MIB::ipInUnknownProtos.0 = Counter32:
IP-MIB::ipInDiscards.0 = Counter32:
IP-MIB::ipInDelivers.0 = Counter32:
IP-MIB::ipOutRequests.0 = Counter32:
IP-MIB::ipOutDiscards.0 = Counter32:
IP-MIB::ipOutNoRoutes.0 = Counter32:
IP-MIB::ipReasmTimeout.0 = INTEGER:
IP-MIB::ipReasmReqds.0 = Counter32:
IP-MIB::ipReasmOKs.0 = Counter32:
IP-MIB::ipReasmFails.0 = Counter32:
IP-MIB::ipFragOKs.0 = Counter32:
IP-MIB::ipFragFails.0 = Counter32:
IP-MIB::ipFragCreates.0 = Counter32:
IP-MIB::ipAdEntAddr.192.168.0.1 = IpAddress:
IP-MIB::ipAdEntIfIndex.192.168.0.1 = INTEGER:
IP-MIB::ipAdEntNetMask.192.168.0.1 = IpAddress:
IP-MIB::ipAdEntBcastAddr.192.168.0.1 = INTEGER:
IP-MIB::ipAdEntReasmMaxSize.192.168.0.1 = INTEGER:
IP-MIB::ip.21.1.1.192.168.0.0 = IpAddress
...
IP-MIB::ipNetToMediaIfIndex.1.192.168.0.2 = INTEGER:
IP-MIB::ipNetToMediaPhysAddress.1.192.168.0.2 = STRING:
IP-MIB::ipNetToMediaNetAddress.1.192.168.0.2 = IpAddress:
IP-MIB::ipNetToMediaType.1.192.168.0.2 = INTEGER:
IP-MIB::ipRoutingDiscards.0 = Counter32:
IP-MIB::icmpInMsgs.0 = Counter32:
IP-MIB::icmpInErrors.0 = Counter32:
IP-MIB::icmpInDestUnreachs.0 = Counter32:
IP-MIB::icmpInTimeExcds.0 = Counter32:
IP-MIB::icmpInParmProbs.0 = Counter32:
IP-MIB::icmpInSrcQuenchs.0 = Counter32:
IP-MIB::icmpInRedirects.0 = Counter32:
IP-MIB::icmpInEchos.0 = Counter32:
IP-MIB::icmpInEchoReps.0 = Counter32:
IP-MIB::icmpInTimestamps.0 = Counter32:
IP-MIB::icmpInTimestampReps.0 = Counter32:
IP-MIB::icmpInAddrMasks.0 = Counter32:
IP-MIB::icmpInAddrMaskReps.0 = Counter32:
IP-MIB::icmpOutMsgs.0 = Counter32:
IP-MIB::icmpOutErrors.0 = Counter32:
IP-MIB::icmpOutDestUnreachs.0 = Counter32:
IP-MIB::icmpOutTimeExcds.0 = Counter32:
IP-MIB::icmpOutParmProbs.0 = Counter32:
IP-MIB::icmpOutSrcQuenchs.0 = Counter32:
IP-MIB::icmpOutRedirects.0 = Counter32:
IP-MIB::icmpOutEchos.0 = Counter32:
IP-MIB::icmpOutEchoReps.0 = Counter32:
IP-MIB::icmpOutTimestamps.0 = Counter32:
IP-MIB::icmpOutTimestampReps.0 = Counter32:
IP-MIB::icmpOutAddrMasks.0 = Counter32:
IP-MIB::icmpOutAddrMaskReps.0 = Counter32:
TCP-MIB::tcpRtoAlgorithm.0 = INTEGER:
TCP-MIB::tcpRtoMin.0 = INTEGER:
TCP-MIB::tcpRtoMax.0 = INTEGER:
TCP-MIB::tcpMaxConn.0 = INTEGER:
TCP-MIB::tcpActiveOpens.0 = Counter32:
TCP-MIB::tcpPassiveOpens.0 = Counter32:
TCP-MIB::tcpAttemptFails.0 = Counter32:
TCP-MIB::tcpEstabResets.0 = Counter32:
TCP-MIB::tcpCurrEstab.0 = Gauge32:
TCP-MIB::tcpInSegs.0 = Counter32:
TCP-MIB::tcpOutSegs.0 = Counter32:
TCP-MIB::tcpRetransSegs.0 = Counter32:
TCP-MIB::tcpConnState.0.0.0.0.102.0.0.0.0.0 = INTEGER:
TCP-MIB::tcpConnState.192.168.0.1.443.0.0.0.0.0 = INTEGER:
TCP-MIB::tcpConnLocalAddress.0.0.0.0.102.0.0.0.0.0 = IpAddress: 0.0.0.0
TCP-MIB::tcpConnLocalAddress.192.168.0.1.443.0.0.0.0.0 = IpAddress: 192.168.0.1
TCP-MIB::tcpConnLocalPort.0.0.0.0.102.0.0.0.0.0 = INTEGER: 102
TCP-MIB::tcpConnLocalPort.192.168.0.1.443.0.0.0.0.0 = INTEGER: 443
TCP-MIB::tcpConnRemAddress.0.0.0.0.102.0.0.0.0.0 = IpAddress: 0.0.0.0
TCP-MIB::tcpConnRemAddress.192.168.0.1.443.0.0.0.0.0 = IpAddress: 0.0.0.0
TCP-MIB::tcpConnRemPort.0.0.0.0.102.0.0.0.0.0 = INTEGER:
TCP-MIB::tcpConnRemPort.192.168.0.1.443.0.0.0.0.0 = INTEGER:
TCP-MIB::tcpInErrs.0 = Counter32:
TCP-MIB::tcpOutRsts.0 = Counter32:
UDP-MIB::udpInDatagrams.0 = Counter32:
UDP-MIB::udpNoPorts.0 = Counter32:
UDP-MIB::udpInErrors.0 = Counter32:
UDP-MIB::udpOutDatagrams.0 = Counter32:
UDP-MIB::udpLocalAddress.0.0.0.0.161 = IpAddress:
...
UDP-MIB::udpLocalPort.0.0.0.0.161 = INTEGER:
...
SNMPv2-MIB::snmpInPkts.0 = Counter32:
SNMPv2-MIB::snmpOutPkts.0 = Counter32:
SNMPv2-MIB::snmpInBadVersions.0 = Counter32:
SNMPv2-MIB::snmpInBadCommunityNames.0 = Counter32:
SNMPv2-MIB::snmpInBadCommunityUses.0 = Counter32:
SNMPv2-MIB::snmpInASNParseErrs.0 = Counter32:
SNMPv2-MIB::snmpInTooBigs.0 = Counter32:
SNMPv2-MIB::snmpInNoSuchNames.0 = Counter32:
SNMPv2-MIB::snmpInBadValues.0 = Counter32:
SNMPv2-MIB::snmpInReadOnlys.0 = Counter32:
SNMPv2-MIB::snmpInGenErrs.0 = Counter32:
SNMPv2-MIB::snmpInTotalReqVars.0 = Counter32:
SNMPv2-MIB::snmpInTotalSetVars.0 = Counter32:
SNMPv2-MIB::snmpInGetRequests.0 = Counter32:
SNMPv2-MIB::snmpInGetNexts.0 = Counter32:
SNMPv2-MIB::snmpInSetRequests.0 = Counter32:
SNMPv2-MIB::snmpInGetResponses.0 = Counter32:
SNMPv2-MIB::snmpInTraps.0 = Counter32:
SNMPv2-MIB::snmpOutTooBigs.0 = Counter32:
SNMPv2-MIB::snmpOutNoSuchNames.0 = Counter32:
SNMPv2-MIB::snmpOutBadValues.0 = Counter32:
SNMPv2-MIB::snmpOutGenErrs.0 = Counter32:
SNMPv2-MIB::snmpOutGetRequests.0 = Counter32:
SNMPv2-MIB::snmpOutGetNexts.0 = Counter32:
SNMPv2-MIB::snmpOutSetRequests.0 = Counter32:
SNMPv2-MIB::snmpOutGetResponses.0 = Counter32:
SNMPv2-MIB::snmpOutTraps.0 = Counter32:
SNMPv2-MIB::snmpEnableAuthenTraps.0 = INTEGER:

Hilfreiche Links: