Skip to content
Digital Sentinel
All walkthroughs
LetsDefendHardFull solution

LetsDefend: Compromised ICS Device — a PLC, a water pump and Modbus

A water treatment plant with a compromised PLC. Learning Modbus from scratch, tracing the brute force, and the one-space filter mistake that cost me an hour.

5 min read
Watch the video walkthrough

A water treatment plant. The PLC running the pumps and filtration is behaving strangely, and we've been called in to work out why.

I'll start with an admission: I did not know how a PLC works. Not really. So before touching the challenge I googled it — what is a PLC, what does it do, how does it work. Got a high-level picture, then started.

That is not a detour. That is the job.

Open the log file first

The challenge gives you a .pcap and a log.txt.

I didn't open the text file until the very end. I should have opened it first. It would have made the whole thing dramatically easier — the initial access pattern is sitting at the top of it.

Learn from me. Open every file you're given before you start.

The alternative route, in Wireshark:

frame contains "TableID"

The entry point: IP and port

I didn't know what service was running, so I filtered on what I did know:

frame contains "PLC"

That surfaced requests to get_monitor_update and something called MB_board / MBS 502. Open one, go to the HTTP section, and the request URL gives you the public IP and the port.

Note that down — it comes back later.

Finding the attacker

There were several login attempts, so I went with the obvious:

frame contains "login"

Which gives you the source IP doing the trying. You could equally have gone via http.request.method == "POST" and got there the same way.

The credentials that finally worked

Same response, same packet. The successful login has the username and password in it — the account is the PLC operator account, and the password is a Breaking Bad reference.

(I've never watched the show. Apparently it's good.)

The brute-force user agent

Think about what a brute force looks like in a capture before you go hunting. Lots of requests, from one source, in quick succession, hammering the same endpoint.

I scrolled a long way to find it. I could have saved myself the time with:

frame contains "login" && http.request.method == "POST"

Open one, and the User-Agent is a header in the request like any other.

Which protocol controls the pumps?

I didn't know. So — Google.

We'd already seen MB_board and 502 in the logs. Searching for that told me it's Modbus, one of the standard industrial control protocols, and that it runs on port 502.

When you don't know: google it. Always, always, always.

The URL used to manipulate the program

frame contains "monitor_update"

Open a request, read the full URL from the bottom of the packet detail. The logs are full of repeated get_monitor_update calls — which, again, I'd have seen immediately if I'd opened the text file first.

The pump state, and the mistake that cost me

Two questions here: when was the pump first turned on, and what state was it left in.

The second question tells you the answer to the first is a state change to "on", so what I'm really hunting is the pump switch value and its timestamp.

The log file has a pump switch entry with a Boolean type — so it's true or false, one or zero. I spent a long time trying to work out how that value got set. I went through something like 30–50 of the 700-odd frames looking for how it was coded, and eventually found a snippet showing the boolean set to false.

Which meant what I actually wanted was the opposite:

http.response.code == 200 && frame contains "true"

Open that up and the timestamp is right there.

One more thing: .RS files

Going through the code I kept seeing uploads with an .RS extension and wanted to know what it was. Turns out it's a language used in PLC systems.

I did a lot of research on this one that wasn't strictly required to answer any question. I'd do it again — it's the difference between answering a question and understanding a system.

What did we learn?

  1. Open every file you're given, first. The log file had answers I found the hard way.
  2. Google the domain before you start. Knowing what a PLC is made everything after it faster.
  3. Modbus runs on port 502. Now you know too.
  4. Brute force looks like volume from one source against one endpoint.
  5. Filter strings are exact. A space in water pump broke a filter I was sure of.
  6. Booleans in logs mean you can search for the opposite value to find the change.
  7. .RS is a PLC language.

To solve any challenge you don't understand, do what a good researcher does and google it relentlessly. You don't have to be a developer. You have to be curious about how the thing works.

And when your brain really hurts — that's how you know you're getting better.

I'm quite proud of this one!

TagsLetsDefendICSModbusWiresharkblue team