
An PHDays 9 veranstalteten wir einen Wettbewerb zum Hacken einer Gaspumpanlage - den
Industrial Ninja- Wettbewerb. Auf dem Gelände befanden sich drei Stände mit unterschiedlichen Sicherheitsparametern (Keine Sicherheit, Niedrige Sicherheit, Hohe Sicherheit), die denselben industriellen Prozess nachahmten: Luft wurde unter Druck in den Ballon gepumpt (und dann abgesenkt).
Trotz verschiedener Sicherheitsparameter war die Hardware der Stände dieselbe: Siemens Simatic S7-300 PLC; Notabblasknopf und Druckmessgerät (angeschlossen an digitale SPS-Eingänge (DI)); Ventile zum Pumpen und Entlüften (angeschlossen an die digitalen Ausgänge der SPS (DO)) - siehe Abbildung unten.

Die SPS entschied sich abhängig von den Druckwerten und gemäß ihrem Programm, die Kugel zu blasen oder aufzublasen (öffnete und schloss die entsprechenden Ventile). An allen Ständen war jedoch ein manueller Steuermodus vorgesehen, der es ermöglichte, die Ventilzustände ohne Einschränkungen zu steuern.
Die Stände zeichneten sich durch die Komplexität der Aktivierung dieses Modus aus: Auf einem ungeschützten Stand war dies am einfachsten, auf dem Hochsicherheitsstand war es dementsprechend schwieriger.
In zwei Tagen waren fünf der sechs Probleme gelöst; Der Gewinner des ersten Platzes erhielt 233 Punkte (er verbrachte eine Woche damit, sich auf den Wettbewerb vorzubereiten). Drei Gewinner: Ich platziere - a1exdandy, II - Rubikoid, III - Ze.
Während der PHDays konnte jedoch keiner der Teilnehmer alle drei Stände überwinden. Daher entschieden wir uns für einen Online-Wettbewerb und veröffentlichten Anfang Juni die schwierigste Aufgabe. Die Teilnehmer mussten die Aufgabe in einem Monat erledigen, die Flagge finden, die Lösung detailliert und interessant beschreiben.
Im Rahmen der Kürzung veröffentlichen wir eine Analyse der besten Lösung für den im Laufe des Monats gesendeten Auftrag. Sie wurde von Alexey Kovrizhnykh (a1exdandy) von der Digital Security Company gefunden, die während der PHDays den 1. Platz im Wettbewerb belegte. Nachfolgend geben wir den Text mit unseren Kommentaren an.
Erste Analyse
In der Aufgabe gab es also ein Archiv mit Dateien:
- block_upload_traffic.pcapng
- DB100.bin
- hints.txt
Die Datei hints.txt enthält die erforderlichen Informationen und Tipps zur Lösung der Aufgabe. Hier sind die Inhalte:
- Petrovich sagte mir gestern, dass Sie von PlcSim Blöcke in Schritt 7 herunterladen können.
- Am Stand wurden SLCs der Siemens Simatic S7-300-Serie eingesetzt.
- PlcSim ist ein SPS-Emulator, mit dem Sie Programme für Siemens S7-SPS ausführen und debuggen können.
Die Datei DB100.bin enthält anscheinend einen DB100-SPS-Datenblock:
00000000: 0100 0102 6e02 0401 0206 0100 0101 0102 .... n ...........
00000010: 1002 0501 0202 2002 0501 0206 0100 0102 ...... .........
00000020: 0102 7702 0401 0206 0100 0103 0102 0a02 ..w .............
00000030: 0501 0202 1602 0501 0206 0100 0104 0102 ................
00000040: 7502 0401 0206 0100 0105 0102 0a02 0501 u ...............
00000050: 0202 1602 0501 0206 0100 0106 0102 3402 .............. 4.
00000060: 0401 0206 0100 0107 0102 2602 0501 0202 .......... & .....
00000070: 4c02 0501 0206 0100 0108 0102 3302 0401 L ........... 3 ...
00000080: 0206 0100 0109 0102 0a02 0501 0202 1602 ................
00000090: 0501 0206 0100 010a 0102 3702 0401 0206 .......... 7 .....
000000a0: 0100 010b 0102 2202 0501 0202 4602 0501 ...... "..... F ...
000000b0: 0206 0100 010c 0102 3302 0401 0206 0100 ........ 3 .......
000000c0: 010d 0102 0a02 0501 0202 1602 0501 0206 ................
000000d0: 0100 010e 0102 6d02 0401 0206 0100 010f ...... m .........
000000e0: 0102 1102 0501 0202 2302 0501 0206 0100 ........ # .......
000000f0: 0110 0102 3502 0401 0206 0100 0111 0102 .... 5 ...........
00000100: 1202 0501 0202 2502 0501 0206 0100 0112 ......% .........
00000110: 0102 3302 0401 0206 0100 0113 0102 2602 ..3 ........... &.
00000120: 0501 0202 4c02 0501 0206 0100 .... L .......
Dem Namen nach zu urteilen, enthält die Datei block_upload_traffic.pcapng einen Speicherauszug des Blockladeverkehrs zur SPS.
Es ist erwähnenswert, dass dieser Verkehrsstau auf dem Wettbewerbsgelände während der Konferenz etwas schwieriger zu bekommen war. Dazu musste das Skript aus der Projektdatei für TeslaSCADA2 verstanden werden. Daraus konnte man erkennen, wo sich der mit RC4 verschlüsselte Dump befindet und welcher Schlüssel zum Entschlüsseln verwendet werden sollte. Dumps von Datenblöcken am Standort können mit dem S7-Protokollclient abgerufen werden. Ich habe dafür den Demo-Client aus dem Snap7-Paket verwendet.
Extrahieren von Signalverarbeitungseinheiten aus einem Verkehrsdump
Wenn Sie sich den Inhalt des Speicherauszugs ansehen, können Sie verstehen, dass die Signalverarbeitungsblöcke OB1, FC1, FC2 und FC3 darin übertragen werden:

Diese Blöcke müssen extrahiert werden. Dies kann beispielsweise mit dem folgenden Skript erfolgen, nachdem der Datenverkehr vom pcapng-Format in pcap konvertiert wurde:
Nachdem Sie die empfangenen Blöcke untersucht haben, können Sie feststellen, dass sie immer mit den Bytes 70 70 (pp) beginnen. Jetzt müssen Sie lernen, wie Sie sie analysieren. Ein Hinweis auf die Aufgabe legt nahe, dass Sie hierfür PlcSim verwenden müssen.
Vom Menschen lesbare Anweisungen aus Blöcken erhalten
Versuchen wir zunächst, S7-PlcSim zu programmieren, indem wir mehrere Blöcke mit sich wiederholenden Anweisungen (= Q 0.0) mit der Simatic Manager-Software laden und das Ergebnis im SPS-Emulator in der Datei example.plc speichern. Durch Betrachten des Inhalts der Datei können Sie den Beginn der geladenen Blöcke leicht anhand der Signatur 70 70 bestimmen, die wir zuvor entdeckt haben. Vor den Blöcken wird die Blockgröße anscheinend in Form eines 4-Byte-Little-Endian-Werts geschrieben.

Nachdem wir Informationen über die Struktur von SPS-Dateien erhalten hatten, wurde der folgende Aktionsplan zum Lesen von SPS-S7-Programmen angezeigt:
- Mit dem Simatic Manager erstellen wir in S7-PlcSim eine Blockstruktur ähnlich der, die wir aus dem Dump erhalten haben. Die Blockgrößen müssen mit ihren Bezeichnern (OB1, FC1, FC2, FC3) übereinstimmen (erreicht durch Füllen der Blöcke mit der erforderlichen Anzahl von Anweisungen).
- Speichern Sie die SPS in einer Datei.
- Wir ersetzen den Inhalt der Blöcke in der empfangenen Datei durch die Blöcke aus dem Verkehrsdump. Der Beginn der Blöcke wird durch die Signatur bestimmt.
- Die resultierende Datei wird auf S7-PlcSim hochgeladen und wir sehen uns den Inhalt der Blöcke in Simatic Manager an.
Blöcke können beispielsweise durch folgenden Code ersetzt werden:
with open('original.plc', 'rb') as f: plc = f.read() blocks = [] for fname in ['OB1.bin', 'FC1.bin', 'FC2.bin', 'FC3.bin']: with open(fname, 'rb') as f: blocks.append(f.read()) i = plc.find(b'pp') for block in blocks: plc = plc[:i] + block + plc[i+len(block):] i = plc.find(b'pp', i + 1) with open('target.plc', 'wb') as f: f.write(plc)
Aleksey ging einen möglicherweise komplizierteren, aber immer noch richtigen Weg. Wir gingen davon aus, dass die Teilnehmer das NetToPlcSim-Programm verwenden würden, damit PlcSim über das Netzwerk kommunizieren, Blöcke über Snap7 in PlcSim laden und diese Blöcke dann als Projekt von PlcSim unter Verwendung der Entwicklungsumgebung herunterladen könnte.
Durch Öffnen der resultierenden Datei in S7-PlcSim können Sie überschriebene Blöcke mit Simatic Manager lesen. Die wichtigsten Geräteverwaltungsfunktionen sind in Block FC1 aufgezeichnet. Die Variable # TEMP0 zieht besondere Aufmerksamkeit auf sich, wenn sie eingeschaltet ist, scheint es, dass die SPS-Steuerung basierend auf den Werten des Bitspeichers M2.2 und M2.3 in den manuellen Modus geschaltet wird. # TEMP0 wird von FC3 eingestellt.

Um das Problem zu lösen, muss die FC3-Funktion analysiert und verstanden werden, was zu tun ist, damit eine logische Einheit zurückgegeben wird.
Die SPS-Signalverarbeitungsblöcke am Stand für niedrige Sicherheit am Wettbewerbsstandort wurden auf die gleiche Weise angeordnet, aber um den Wert der Variablen # TEMP0 festzulegen, reichte es aus, die Zeile my ninja way in den DB1-Block zu schreiben. Die Überprüfung des Werts im Block war klar angeordnet und erforderte keine gründlichen Kenntnisse der Blockprogrammiersprache. Auf der Hochsicherheitsstufe wird es natürlich viel schwieriger sein, eine manuelle Steuerung zu erreichen, und es ist notwendig, die Feinheiten der STL-Sprache zu verstehen (eine der Möglichkeiten, die S7-SPS zu programmieren).
Reverse Block FC3
Der Inhalt des FC3-Blocks in der STL-Darstellung: Der Code ist ziemlich umfangreich und für eine Person, die mit STL nicht vertraut ist, kann er kompliziert erscheinen. Es ist nicht sinnvoll, jede Anweisung im Rahmen dieses Artikels zu zerlegen. Detaillierte Anweisungen und STL-
Sprachfunktionen finden Sie im entsprechenden Handbuch:
Anweisungsliste (STL) für die S7-300- und S7-400-Programmierung . Hier werde ich nach der Verarbeitung denselben Code angeben - Bezeichnungen und Variablen umbenennen und Kommentare hinzufügen, die den Arbeitsalgorithmus und einige Konstruktionen der STL-Sprache beschreiben. Ich stelle sofort fest, dass in dem betrachteten Block eine virtuelle Maschine implementiert ist, die einen im DB100-Block befindlichen Bytecode ausführt, dessen Inhalt wir kennen. Anweisungen für virtuelle Maschinen bestehen aus 1 Byte Betriebscode und Bytes mit Argumenten, einem Byte für jedes Argument. Alle überprüften Anweisungen haben zwei Argumente. Ich habe ihre Werte in den Kommentaren als X und Y bezeichnet.
Nachdem wir eine Vorstellung von den Anweisungen der virtuellen Maschine haben, schreiben wir einen kleinen Disassembler zum Parsen des Bytecodes im DB100-Block:
import string alph = string.ascii_letters + string.digits with open('DB100.bin', 'rb') as f: m = f.read() pc = 0 while pc < len(m): op = m[pc] if op == 1: print('R{} = DB101[{}]'.format(m[pc + 2], m[pc + 1])) pc += 3 elif op == 2: c = chr(m[pc + 1]) c = c if c in alph else '?' print('R{} = {:02x} ({})'.format(m[pc + 2], m[pc + 1], c)) pc += 3 elif op == 4: print('R0 = 0; R{} = (R{} == R{})'.format( m[pc + 1], m[pc + 1], m[pc + 2])) pc += 3 elif op == 5: print('R0 = 0; R{} = R{} - R{}'.format( m[pc + 1], m[pc + 1], m[pc + 2])) pc += 3 elif op == 6: print('CHECK (R{} == R{})\n'.format( m[pc + 1], m[pc + 2])) pc += 3 else: print('unk opcode {}'.format(op)) break
Als Ergebnis erhalten wir den folgenden Code der virtuellen Maschine:
Code der virtuellen Maschine R1 = DB101[0] R2 = 6e (n) R0 = 0; R1 = (R1 == R2) CHECK (R1 == R0) R1 = DB101[1] R2 = 10 (?) R0 = 0; R1 = R1 - R2 R2 = 20 (?) R0 = 0; R1 = R1 - R2 CHECK (R1 == R0) R1 = DB101[2] R2 = 77 (w) R0 = 0; R1 = (R1 == R2) CHECK (R1 == R0) R1 = DB101[3] R2 = 0a (?) R0 = 0; R1 = R1 - R2 R2 = 16 (?) R0 = 0; R1 = R1 - R2 CHECK (R1 == R0) R1 = DB101[4] R2 = 75 (u) R0 = 0; R1 = (R1 == R2) CHECK (R1 == R0) R1 = DB101[5] R2 = 0a (?) R0 = 0; R1 = R1 - R2 R2 = 16 (?) R0 = 0; R1 = R1 - R2 CHECK (R1 == R0) R1 = DB101[6] R2 = 34 (4) R0 = 0; R1 = (R1 == R2) CHECK (R1 == R0) R1 = DB101[7] R2 = 26 (?) R0 = 0; R1 = R1 - R2 R2 = 4c (L) R0 = 0; R1 = R1 - R2 CHECK (R1 == R0) R1 = DB101[8] R2 = 33 (3) R0 = 0; R1 = (R1 == R2) CHECK (R1 == R0) R1 = DB101[9] R2 = 0a (?) R0 = 0; R1 = R1 - R2 R2 = 16 (?) R0 = 0; R1 = R1 - R2 CHECK (R1 == R0) R1 = DB101[10] R2 = 37 (7) R0 = 0; R1 = (R1 == R2) CHECK (R1 == R0) R1 = DB101[11] R2 = 22 (?) R0 = 0; R1 = R1 - R2 R2 = 46 (F) R0 = 0; R1 = R1 - R2 CHECK (R1 == R0) R1 = DB101[12] R2 = 33 (3) R0 = 0; R1 = (R1 == R2) CHECK (R1 == R0) R1 = DB101[13] R2 = 0a (?) R0 = 0; R1 = R1 - R2 R2 = 16 (?) R0 = 0; R1 = R1 - R2 CHECK (R1 == R0) R1 = DB101[14] R2 = 6d (m) R0 = 0; R1 = (R1 == R2) CHECK (R1 == R0) R1 = DB101[15] R2 = 11 (?) R0 = 0; R1 = R1 - R2 R2 = 23 (?) R0 = 0; R1 = R1 - R2 CHECK (R1 == R0) R1 = DB101[16] R2 = 35 (5) R0 = 0; R1 = (R1 == R2) CHECK (R1 == R0) R1 = DB101[17] R2 = 12 (?) R0 = 0; R1 = R1 - R2 R2 = 25 (?) R0 = 0; R1 = R1 - R2 CHECK (R1 == R0) R1 = DB101[18] R2 = 33 (3) R0 = 0; R1 = (R1 == R2) CHECK (R1 == R0) R1 = DB101[19] R2 = 26 (?) R0 = 0; R1 = R1 - R2 R2 = 4c (L) R0 = 0; R1 = R1 - R2 CHECK (R1 == R0)
Wie Sie sehen können, überprüft dieses Programm einfach jedes Symbol aus DB101 auf Gleichheit mit einem bestimmten Wert. Die letzte Zeile zum Bestehen aller Prüfungen: n0w u 4r3 7h3 m4573r. Wenn diese Leitung im DB101-Block platziert ist, wird die manuelle Steuerung der SPS aktiviert und es ist möglich, den Ballon zu sprengen oder zu entleeren.
Das ist alles! Alexey hat ein hohes Maß an Wissen bewiesen, das eines industriellen Ninja würdig ist :) Wir haben dem Gewinner unvergessliche Preise geschickt. Vielen Dank an alle Teilnehmer!