
The Critical Infrastructure Security Showdown (CISS) is an annually premier and one-of-its-kind cyber exercise provides a dedicated operational technology (OT) CTF competition, which held at the Singapore University of Technology and Design (SUTD) and organized by the iTrust research center. CISS provides the great opportunity brings together global CTF experts to tackle realistic OT cybersecurity challenges. This year CISS 2025 consists of two stages:
-
Stage 1 – Qualifying Round: a 48-hour online CTF competition.(Finished on 31th July)
-
Stage 2 – Finals: a 4-hour live exercise on real testbeds at SUTD-iTrust.
The CISS-2025-Stage 1 concluded last month with 61 international Red Teams participating and the top 10 teams will advance to the finals. For further details, please visit the CISS 2025 Website.
In this write-up article, I will walk through one IT-OT hybrid challenges I submitted for Stage 1 of CISS2025: “Pass the Door of Durin”. This was the very first challenge of the competition, designed as an entry point for participants. It features a Siemens S7-1200 PLC simulator that runs an S7Comm service—allowing memory data block modification using the Siemens S7 OT protocol—and a web interface that visualizes the PLC memory state while embedding several lightweight IT-focused puzzles.
# Author: Yuancheng Liu
# Created: 2025/06/28
# Version: v_0.3.1
# Copyright: Copyright (c) National Cybersecurity R&D Laboratory (NCL)
# License: MIT License
Challenge Introduction : Pass the Door of Durin
Each year, the CISS CTF features a creative storyline to inspire challenge design. The 2025 edition adopts the theme “The Return to Control”, inspired by The Lord of the Rings: The Return of the King (see the CISS2024 poster below).

The “Pass the Door of Durin” challenge serves as the first gateway that every Red Team should overcome at the start of the CTF. Drawing inspiration from Frodo and Sam’s perilous their journey to Mount Doom to destroy the ring, the challenge sets the stage for participants to begin their own quest—unlocking hidden knowledge and gaining entry into the world of IT-OT cybersecurity puzzles.
At its core, the challenge provides a hands-on environment simulating a Siemens S7-1200 PLC for each red team. The participants need to :
-
Discover the PLC’s accessible IP and ports.
-
Analyze the S7Comm memory data blocks and infer the logic executed by the PLC.
-
Insert the correct type of data (int, bool, float) into the correct memory block addresses .
-
Control the PLC to complete the given tasks and uncover the flags.
The challenge is structured into four main tasks, each consisting of a pair of IT-focused question and an OT-focused question—a total of six IT-OT hybrid puzzles. This design ensures participants must combine classical CTF skills (web exploitation, scanning, scripting) with OT-specific techniques (S7Comm protocol handling, memory block manipulation).
The overall environment design is illustrated below:

To solve the challenge, Red Teams need to first SSH into the gateway and jumphost assigned to them, then access their dedicated Kali Linux Transaction VM (The attacker machine). From there, they need to:
-
Identify the PLC IP address and available services.
-
Forward the challenge task and verification web interface to their local machine.
-
Develop custom attack scripts or install tools (with full
sudoprivileges) on the Kali VM. -
Communicate with both the PLC’s S7Comm data handling server (Port 102) and the Flask-based web server (Port 5001) to manipulate the PLC memory and validate results.
The underlying PLC simulation is based on part of my open-source project Py_PLC_Honey_Pot,(as shown below) which emulates Siemens S7 logic and provides a flexible platform for IT-OT cybersecurity exercises.

Honeypot introduction link: https://www.linkedin.com/pulse/python-plc-honeypot-project-yuancheng-liu-vks8c
Challenge Background Story
The challenge background story is shown below:

Frodo and Sam are on their way to Mount Doom to destroy the ring. The time is short, they don't have enough time to walk through the mountains of Gundabad. Sam has a broken map of Dunland with several fragments, it shows the shortcut to bypass the mountain through an underground tunnel. Before they enter the tunnel, they need to find and pass through the "Door of Durin". They don't have the key, but there is some way to unlock the door.
Before they can enter the tunnel, Frodo needs to send a message to Aragorn to attract the attention of the Fiery Eye to help them.
CTF Challenge Question Overview
The Pass the Door of Durin challenge is divided into four main tasks, containing a total of six CTF questions. Each question carries a point value (1–10) according to its difficulty level, and some questions provide optional hints. Using a hint reduces the available score by 60% for that question. All questions are deployed on the CTF-D platform with defined maximum attempts, as summarized below.
The question detail is shown below:
| Question | Description | Points | Max Attempts | Hint |
|---|---|---|---|---|
| Q1: Find the Way | Frodo and Sam must determine the correct path to the Door of Durin. Once revealed, the flag will appear. | 3 | 5 | Try combining the two map fragments. |
| Q2: Identify the Protocol | The door is controlled by a PLC. Identify the communication protocol it uses. | 1 | 3 | Look for clues in the web page source. |
| Q3: Crack the Door Lock | Frodo doesn’t have a key—help him bypass the lock. Unlocking the door reveals the flag. | 6 | 5 | Try inserting integer values; check the memory address via the web UI. |
| Q4: Locate the PLC Memory Block | Sam must identify the correct PLC memory address to send a message. | 2 | 3 | N/A |
| Q5: Send the Message | Help Frodo deliver a message to Aragorn. Sending the correct message reveals the flag. | 5 | 5 | Try setting the Boolean value in memory. |
| Q6: Decode the Response | Aragorn replies in a hidden signal. Adjust the radio frequency and decode the response to extract the flag. | 3 | 3 | Try setting the float value in memory. |
Steps to Solve the Challenge
Each participants will be assigned a Kali-Linux Transaction VM with sudo permission, allowing them to run different red teaming tools or create attack scripts. Log in to the Transaction VM, then follow the steps below to complete the tasks one by one.
Step 1: Access the PLC Web Interface
Start by scanning the PLC VM to identify open ports used by OT protocols and the HTTP service (port 5002), using the nmap command as shown below:
nmap -sS -Pn -T5 -p- 172.23.155.209
Nmap ports scanning result :

From the scan, you’ll find that ports 5002 (HTTP service) and 102 (iso-tsapl) are open.
Since the Transaction VM runs Ubuntu Server (without a desktop environment), you’ll need to port-forward the PLC’s web interface to your local machine. Use the following ssh command:
ssh -L 127.0.0.1:5000:172.23.155.209:5002 -J <your jusphost ID>@<gateway IP> <team_name>@172.23.144.1
Once the tunnel is established, open the URL http://127.0.0.1:5000 in your local browser to access the PLC web interface. You’ll be directed to the story and challenge introduction page as shown below:

After reviewing the background story, proceed to the challenge by accessing the web index page http://127.0.0.1:5000/Index , as shown below to get the 4 tasks introduction list :

This page will lead you to Question 1, where the real mission begins.
Step 2: Solve IT-Question 1 – Reconstruct the Map
-
IT puzzle: Combine the pictures' pixels matrix to get the PLC web login credentials.
Question 1 is part of Task 1, which challenges participants to help Sam and Frodo reconstruct a path. According to the task description:
Sam and Frodo get two pieces of map fragments from the Gollum. Try to restore the map to figure out the route and direction to start the journey.
To solve this challenge, participants must download and combine two map fragment images. These are labeled "Sam’s map fragment" and "Frodo’s map fragment" as shown below :

However, right-clicking the image to save it won’t work as the browser context menu is disabled (check the page source) :

To resolve this, you can:
-
Remove the highlighted part onContextMenu="return false;" from the <img> tag using your browser's developer tools. This will re-enable the right-click context menu so you can save the image normally.

Alternatively, you can open the image URL in a new browser tab to access it directly:

Once both images are downloaded, the hint suggests that we need to “combine” the pictures to reveal the hidden map. To achieve this, we can write a Python program that reads the pixel (R, G, B) values of both images into matrices and then experiment with different methods of combining them, as shown below:

Result (R3, G3, B3) = ((R1+?R2), (G2+?G2), (B3+?B3)
-
Add the pixel values of the two matrices to generate a new image.
-
Rotate the second image matrix by 90° / 180° / 270° and then combine pixel values.
-
Reverse the second image matrix before combining.
-
Mirror the second image matrix before combining.
If the resulting pixel values exceed 255 in any RGB channel, that approach is invalid. The correct solution is to top-down mirroring the second image and then overlay its matrix onto the first image to reconstruct the full map. A simple python pixel RGB matrix combine code is shown below:
image_path1 = "img1.jpg"
img1 = Image.open(image_path1)
width1, height1 = img1.size
pixel_data1 = list(img1.getdata()) # Get pixel values
pixel_2d_lis1 = [pixel_data1[i * width1:(i + 1) * width1] for i in range(height1)]
# Read picture 2 in a matrix and do top-down mirroring
image_path2 = "img2.jpg"
img2 = Image.open(image_path2)
width2, height2 = img2.size
pixel_data2 = list(img2.getdata()) # Get pixel values
pixel_2d_list2 = [pixel_data2[i * width2:(i + 1) * width2] for i in range(height2)]
topdownMirror = pixel_2d_list2[::-1]
# combine the pixels to the new image
img3List = []
for i, item in enumerate(pixel_2d_lis1):
newItem = []
for j, pixel in enumerate(item):
img2Pixel = topdownMirror[i][j]
img3R = pixel[0] + img2Pixel[0]
img3G = pixel[1] + img2Pixel[1]
img3B = pixel[2] + img2Pixel[2]
newItem.append((img3R, img3G, img3B))
img3List.append(newItem)
img3Data = np.array(img3List, dtype=np.uint8)
img3 = Image.fromarray(img3Data, 'RGB')
img3.save("result.jpg")
Alternatively, use the provided script: pictureCombine.py to try all the pixel combine function to get the correct one, This will automatically generate the combined result, as shown below:

From the merged image, participants can extract:
-
Route String :
FollowSilvertineDarkCliffsUnderstartLight -
Direction String :
WestSideNeArTw0AncientH011yTrEes
Fill in the Route and Direction fields and press the button Go to the "Door of Durin" to submit the answer:

If the route and direction is correct, Question 1 flag will be displayed at the bottom of the page:

Question 1 Flag : CISS_2025_@T_THE_FR0NT_0F_Th3_Ho11in_G@t3
Step 3: Solve OT-Question 2 – Identify the PLC OT Protocol
-
OT puzzle: Identify and confirm the PLC communication OT protocol
From the earlier Nmap port scan, we discovered that port 102 is open:

This port is associated with the ISO-TSAP protocol. However, "iso-tsap" is not the correct flag. To identify the actual OT protocol used by the PLC, try searching the following query in Google:
“port 102 operational technology protocol TSAP type used by PLC”
This will lead to two common results:
-
Siemens S7 and S7 Plus
-
The Inter-Control Center Communication Protocol (ICCP)
Return to the story page after clicking the “Start the Journey” button. Inspect the page source to locate the hidden image named protocol.png (1 pixel wide image ) :

To reveal the full image, remove or modify the image's style using the browser’s developer tools, then the revealed image confirms that the PLC is using the S7Comm protocol as shown below:

You can verify the protocol by running a simple S7Comm client script as shown in this example
Install the python snap7 library https://pypi.org/project/python-snap7/ and use the S7Comm communication protocol handling lib: https://github.com/LiuYuancheng/PLC_and_RTU_Simulator/blob/main/S7Comm_RTU_Simulator/src/snap7Comm.py to build your PLC connection and memory block read and write script as shown below:
PLC_IP= '172.23.155.209'
client = snap7Comm.s7CommClient(PLC_IP, rtuPort=102, snapLibPath=libpath)
connection = client.checkConn()
print("S7Comm client connection: %s" % str(connection))
After executing the script, a successful connection message will be printed to confirm that a connection has been established:

Question 2 Flag :
S7COMM
Step 4: Solve OT-Question 3 – Crack the Lock
-
OT puzzle: Figure out the PLC internal logic and fill in the int values to correct memory address block in limited time.
Question 3 is part of Task 2, after completing Task 1, Task 2 tab will be displayed automatically in the navigation bar:

Navigate to “Crack the Lock” challenge under Task 2. Participants will see the following challenge story:
Frodo finds out that the lock is controlled by a PLC. He can use his pin (PLC communication client program) to set some value in the PLC memory to push the lock pins to the right position.
The task page is shown below:

Participants must interact with the PLC to set all the key cuts to push the pin tumblers to the correct position, simulating the unlocking of a mechanical lock. Key challenge elements include:
-
The lock mechanism consists of 12 pin tumblers challenge ( each row with green, blue, yellow, gray rectangles).
-
The light blue squares must be aligned with the keyway shear line located at column index = 12
-
The key cut is controlled by setting values in specific PLC memory addresses, shown in the "add" (address) column:
['1[0]', '1[2]', '1[4]', '1[6]', '2[0]', '2[2]', '2[4]', '2[6]', '3[0]', '3[2]', '3[4]', '3[6]'], with the format<address_Index>[<byte_Index>]. -
The pin tumbler lengths (bottom pin tumblers in green color) updates every 60 seconds, and a countdown timer is displayed on the task page.
-
Once the timer resets, all previously set values are cleared including the key cut values. Participants must solve the challenge within 60 seconds after the new tumblers are generated.
The detailed components mapping is shown below when the user set some key cut value to push the key pin timbers:

When participants input integer values into the PLC, they control how far the tumblers move. The correct value for each tumbler is calculated as: Key Cut Value = 12 - Bottom Pin Tumbler Length
For example, if the current tumbler lengths are:
[7, 8, 4, 7, 9, 7, 3, 5, 6, 3, 10, 7]
Then the corresponding key cut values the participants need to set for each memory bytes position will be [5, 4, 8, 5, 3, 5, 9, 7, 6, 9, 2, 5] as list in the below table:
| Address Index | Byte Idx=0 | Byte Idx=2 | Byte Idx=4 | Byte Idx=6 |
|---|---|---|---|---|
| 1 | 5 | 4 | 8 | 5 |
| 2 | 3 | 5 | 7 | 9 |
| 3 | 6 | 9 | 2 | 5 |
Participants can use a Siemens S7Comm client (as discovered in Question 2) to write these values to the correct PLC memory addresses. Instructors can provide or demo the use of the doorLockSolver.py script, which automatically writes the correct values to the PLC. The attack script execution log is shown below:

After values are written to memory, the UI will update and display how the tumblers shift as shown below. Once all the green rectangles (pin tumblers) align with the shear line (column 12), the lock can be open:

Question 3 Flag :
CISS_2025_P1Ns_@nd_TUm61ers_@T_C0RR3CT_P0S
Step 5: Solve IT-Question 4 – Find the PLC Memory Address
-
IT puzzle: Find the hidden file and get the PLC memory block address data from matrix.
-
OT puzzle: Inject correct Bool data to the memory to map the characters' morse code.
Once Question 3 is solved, the Task 4 tab will be unlocked and displayed in the navigation bar:

From the clue, there as some key points to solve the challenge:
-
Matrix Processing Requirement : Sam is required to process a matrix to extract some meaningful data.
-
Message Transmission : The message sender with question mark is controlled by PLC, if we check the start page, we will know the message sending machine is a telegraph transmitter, which suggests that the message is transmitted using Morse code.
-
Hidden Morse Code Hint:
"Hi Sam, got problem to find the address of the PLC memory? Check this -- .- - .-. .. -..-"Decoding the Morse code:-- .- - .-. .. -..- → MATRIXThis confirms that the matrix file is central to solving this challenge.

Each character of the message on the screen has a coordinate (x, y) below it:

Click the matrix most code -- .- - .-. .. -..- to download the address matrix file as shown below:

To solve this challenge, participants must download the matrix file and use the given coordinates to look up values within the matrix in order to determine the memory address sequence. Alternatively, they may use the attack script matrixSeacher.py to retrieve all the addresses.
Question 4 Flag :
CISS_2025_14_33_47_68_105_117_155_198_2025
Step 6: Solve OT-Question 5 – Send the Message via Morse Code
-
OT puzzle: Inject the correct bool values to correct memory address block.
After solving Question 4, you will obtained the sequence of PLC memory addresses corresponding to the message WEDIDPASS. Each character maps to a specific PLC memory address, as shown below:
W - 14
E - 33
D - 47
I - 68
D - 105
P - 117
A - 155
S - 198
S - 2025
When you check the message section on the web page, you will find key clues to help you solve the challenge, as illustrated in the picture.

-
There is a hidden hint (visible only when highlighted with a mouse) that shows:
Hint: '.' = True, '-' = False -
Each character must be converted into Morse code, and the corresponding Boolean values (
Truefor dot,Falsefor dash) must be written into 4 consecutive bytes in the PLC memory (e.g., Byte 0, Byte 2, Byte 4, Byte 6). -
The indicator green means that the byte value is correct and red means it is incorrect.
Using the hint and the Morse code representations for each character, we can build the following table:
| Character | Memory Address | Mors code | Byte_0 | Byte_2 | Byte_4 | Byte_6 |
|---|---|---|---|---|---|---|
| W | 14 | .-- |
True |
False |
False |
|
| E | 33 | . |
True |
|||
| D | 47 | -.. |
False |
True |
True |
|
| I | 68 | .. |
True |
True |
||
| D | 105 | -.. |
False |
True |
True |
|
| P | 117 | .--. |
True |
False |
False |
True |
| A | 155 | .- |
True |
False |
||
| S | 198 | ... |
True |
True |
True |
|
| S | 2025 | ... |
True |
True |
True |
To set the values, use the following sample Python code:
memoryIdx = 2025
byteIndex = (0,2, 4)
valueList = (True, True, True)
for i in range(len(byteIndex)):
client.setAddressVal(memoryIdx, byteIndex[i], valueList[i], dataType=BOOL_TYPE)
time.sleep(0.1)
time.sleep(0.5)
Alternatively, use the provided script morseCodeSolver.py to automate the entire process for all characters:

When all indicators have turned green, click the “Send the Message” button. If the values are correct, the flag for Question 5 will appear in the card beneath the message area.
Question 5 :
CISS_2025_--_---_.-._..._.C0DE_MSG_S3NT!
Step 7 : Solve IT-Question 6 – Extract Message From Audio
-
IT puzzle: Brute force break the zip file password, get information from the audio file.
-
OT puzzle: Inject the correct float values to correct memory address block.
Once Question 5 is solved, the next task (Question 6) will be displayed automatically at the bottom of the same page as shown below:

It shows message to let the participant to adjust the telegraph receiver's frequency to receive Aragorn's response message:
Hi Frodo, you need to change your telegraph receiver to the correct frequency (HZ) same as Aragorn's transmitter! The frequency is the start MM.DD of CISS 2025 and the memory block is the XOR of all the send message address block index.
We need to write a float number to the PLC memory block which control the telegraph receiver's frequency.
The message shows XOR all the send message block address index, in question 4 we got the flag CISS_2025_14_33_47_68_105_117_155_198_2025, then do the integer XOR to get the correct memory block index:
14 ^ 33 ^ 47 ^ 68 ^ 105 ^ 117 ^ 155 ^ 198 ^ 2025 = 2028
The frequency is the start MM.DD of CISS which is 07.29 as shown below:

So we need to insert 7.29 to the memory block 2028 with below code:
client.setAddressVal(2028, 0, 7.29, dataType=REAL_TYPE)
client.setAddressVal(2028, 4, 7.29, dataType=REAL_TYPE) # optinoal
Once you set the value in the PLC memory, refresh the message sending page and you can receive the Aragorn's message now. The page will change to below state to show the task 4 :

Click the link to download the .wav audio file. However, this audio file is password-protected inside a ZIP archive. To access the .wav file, you must brute-force the ZIP password using a common password list. You may use tools such as fcrackzip or [zip2john + john] to do this. For example, using fcrackzip and a top 1000 password list (e.g., Pwdb_top-1000.txt from SecLists):
fcrackzip -v -D -p Pwdb_top-1000.txt protected_audio.zip

Once cracked, extract the .wav file from the ZIP archive.
The .wav file contains a hidden message using audio steganography. Listen the audio file, the voice in the audio seems not from "Aragorn", continuous playing the video, in the last 7 sec, you will hear some "morse code sound", cut the audio file and only left the morse code sound. Save the file as as a new audio file morse.wmv
Use some online morse code source decoder such as https://morsecode.world/international/decoder/audio-decoder-adaptive.html to analyze the file:

Then get the morse code: --. .. - .... ..- -... .... .. -.. -.. . -. .-- .- ...- .
Covert back to text data:

We get some information : GITSUBHIDDENWAVE Then we search the key word "HiddenWave" in google and GitHub, we will find some tool https://github.com/techchipnet/HiddenWave.
Now we back to extract the original audio, use the open-source tool HiddenWave, specifically the ExWave.py script, available here:
Clone the repository:
git clone https://github.com/techchipnet/HiddenWave.git
cd HiddenWave
Run the script:
python ExWave.py -f /path/to/your/audio.wav
The hidden message extracted from the .wav file will be displayed in the terminal – this is your Question 6 flag. as shown below:

Question 6 Flag :
CISS_2025_H1_Bro_wE11_DON3_FR0Do_@nd_S@m!
Congratulation ! Now you have passed the Door of Durin and finished all the task of this CTF challenge !
If you are interest about other OT PLC CTF challenge, you can also check this Modbus-PLC challenge in CISS2024: https://www.linkedin.com/pulse/hacking-ics-step-by-step-guide-solve-critical-it-ot-ctf-yuancheng-liu-ohjwc
Last edit by LiuYuancheng([email protected]) at 15/07/2025, if you have any problem, please send me a message.
No comment for this article.