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.
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?
- Open every file you're given, first. The log file had answers I found the hard way.
- Google the domain before you start. Knowing what a PLC is made everything after it faster.
- Modbus runs on port 502. Now you know too.
- Brute force looks like volume from one source against one endpoint.
- Filter strings are exact. A space in
water pumpbroke a filter I was sure of. - Booleans in logs mean you can search for the opposite value to find the change.
.RSis 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!