
Critical Infrastructure Security Showdown (CISS) 是一個年度首屈一指且獨一無二的網路演練,提供專門的營運技術 (OT) CTF 競賽,該競賽在 Singapore University of Technology and Design (SUTD) 舉行,並由 iTrust 研究中心 組織。CISS 提供了一個絕佳的機會,匯集了全球 CTF 專家來應對實際的 OT 網路安全挑戰。今年的 CISS 2025 包含兩個階段:
-
Stage 1 – Qualifying Round:為期 48 小時的線上 CTF 競賽。(已於 7 月 31 日結束)
-
Stage 2 – Finals:在 SUTD-iTrust 的真實測試平台上進行 4 小時的現場演練。
CISS-2025-Stage 1 上個月結束,共有 61 支國際紅隊 參與,其中 前 10 名隊伍 將晉級決賽。如需更多詳細資訊,請造訪 CISS 2025 Website。
在這篇撰寫文章中,我將逐步介紹我為 CISS2025 第一階段提交的一個 IT-OT 混合挑戰:“Pass the Door of Durin”。這是競賽的第一個挑戰,旨在作為參與者的起點。它具有一個 Siemens S7-1200 PLC 模擬器,該模擬器運行 S7Comm service—允許使用 Siemens S7 OT 協定修改記憶體資料區塊—以及一個 Web 介面,該介面可視化 PLC 記憶體狀態,同時嵌入多個輕量級的 IT 導向謎題。
# 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
每年,CISS CTF 都會推出一個創意故事情節,以激發挑戰設計。2025 年的版本採用了主題 “The Return to Control”,其靈感來自 The Lord of the Rings: The Return of the King(請參閱下面的 CISS2024 海報)。

“Pass the Door of Durin” 挑戰是每個紅隊在 CTF 開始時都應該克服的 first gateway。從佛羅多和山姆前往末日火山摧毀魔戒的危險旅程中汲取靈感,該挑戰為參與者開始他們自己的任務奠定了基礎—解鎖隱藏的知識並進入 IT-OT 網路安全謎題的世界。
該挑戰的核心是為每個紅隊提供一個模擬 Siemens S7-1200 PLC 的實作環境。參與者需要:
-
發現 PLC 的可存取 IP 和連接埠。
-
分析 S7Comm 記憶體資料區塊並推斷 PLC 執行的邏輯。
-
將正確的資料類型 (int, bool, float) 插入到正確的記憶體區塊位址中。
-
控制 PLC 以完成給定的任務並發現旗標。
該挑戰分為 four main tasks,每個任務都包含一對 IT 導向問題和一個 OT 導向問題—總共六個 IT-OT 混合謎題。這種設計確保參與者必須將傳統的 CTF 技能(Web 漏洞利用、掃描、腳本編寫)與 OT 特定的技術(S7Comm 協定處理、記憶體區塊操作)相結合。
整體環境設計如下圖所示:

為了應對挑戰,紅隊需要先 SSH 連線到分配給他們的閘道和跳板主機,然後存取他們專用的 Kali Linux Transaction VM(攻擊者機器)。從那裡,他們需要:
-
識別 PLC IP 位址和可用的服務。
-
將 challenge task and verification web interface 轉發到他們的本機。
-
在 Kali VM 上開發自訂攻擊腳本或安裝工具(具有完整的
sudo權限)。 -
與 PLC’s S7Comm data handling server (Port 102) 和 Flask-based web server (Port 5001) 通訊,以操作 PLC 記憶體並驗證結果。
底層 PLC 模擬基於我的開源專案 Py_PLC_Honey_Pot 的一部分(如下所示),該專案模擬 Siemens S7 邏輯,並為 IT-OT 網路安全演練提供了一個靈活的平台。

Honeypot 介紹連結:https://www.linkedin.com/pulse/python-plc-honeypot-project-yuancheng-liu-vks8c
Challenge Background Story
挑戰背景故事如下所示:

佛羅多和山姆正在前往末日火山摧毀魔戒的路上。時間很短,他們沒有足夠的時間穿過岡達巴山脈。山姆有一張破損的 Dunland 地圖,上面有幾個碎片,它顯示了通過地下隧道繞過山脈的捷徑。在他們進入隧道之前,他們需要找到並穿過「杜林之門」。他們沒有鑰匙,但有一些方法可以解鎖這扇門。
在他們進入隧道之前,佛羅多需要向亞拉岡發送一條訊息,以引起烈焰之眼的注意來幫助他們。
CTF Challenge Question Overview
Pass the Door of Durin 挑戰分為 four main tasks,總共包含 six CTF questions。每個問題都根據其難度級別帶有積分值 (1–10),並且某些問題提供可選提示。使用提示會將該問題的可用分數降低 60%。所有問題都部署在具有定義的最大嘗試次數的 CTF-D platform 上,如下所示。
問題詳細資訊如下所示:
| Question | Description | Points | Max Attempts | Hint |
|---|---|---|---|---|
| Q1: Find the Way | 佛羅多和山姆必須確定通往杜林之門的正確路徑。一旦揭示,旗標將會出現。 | 3 | 5 | 嘗試組合兩個地圖碎片。 |
| Q2: Identify the Protocol | 這扇門由 PLC 控制。識別它使用的通訊協定。 | 1 | 3 | 在網頁原始碼中尋找線索。 |
| Q3: Crack the Door Lock | 佛羅多沒有鑰匙—幫助他繞過鎖。解鎖這扇門會顯示旗標。 | 6 | 5 | 嘗試插入整數值;透過 Web UI 檢查記憶體位址。 |
| Q4: Locate the PLC Memory Block | 山姆必須識別正確的 PLC 記憶體位址才能發送訊息。 | 2 | 3 | 不適用 |
| Q5: Send the Message | 幫助佛羅多向亞拉岡傳遞訊息。發送正確的訊息會顯示旗標。 | 5 | 5 | 嘗試在記憶體中設定布林值。 |
| Q6: Decode the Response | 亞拉岡以隱藏的訊號回覆。調整無線電頻率並解碼回應以提取旗標。 | 3 | 3 | 嘗試在記憶體中設定浮點值。 |
Steps to Solve the Challenge
每個參與者都將被分配一個具有 sudo 權限的 Kali-Linux Transaction VM,允許他們運行不同的紅隊工具或建立攻擊腳本。登入 Transaction VM,然後按照以下步驟逐一完成任務。
Step 1: Access the PLC Web Interface
首先掃描 PLC VM 以識別 OT 協定和 HTTP 服務(連接埠 5002)使用的開放連接埠,使用 nmap 命令,如下所示:
nmap -sS -Pn -T5 -p- 172.23.155.209
Nmap 連接埠掃描結果:

從掃描中,您會發現連接埠 5002(HTTP 服務)和 102 (iso-tsapl) 已開啟。
由於 Transaction VM 運行的是 Ubuntu Server(沒有桌面環境),因此您需要將 PLC 的 Web 介面連接埠轉發到您的本機。使用以下 ssh 命令:
ssh -L 127.0.0.1:5000:172.23.155.209:5002 -J @ @172.23.144.1
建立通道後,在您的本機瀏覽器中開啟 URL http://127.0.0.1:5000 以存取 PLC Web 介面。您將被導向到故事和挑戰介紹頁面,如下所示:

在檢閱背景故事後,透過存取 Web 索引頁面 http://127.0.0.1:5000/Index 繼續挑戰,如下所示以取得 4 個任務介紹清單:

此頁面將引導您到 Question 1,真正的任務從這裡開始。
Step 2: Solve IT-Question 1 – Reconstruct the Map
-
IT puzzle:組合圖片的像素矩陣以取得 PLC Web 登入憑證。
Question 1 是 Task 1 的一部分,該任務挑戰參與者幫助山姆和佛羅多重建路徑。根據任務描述:
山姆和佛羅多從咕嚕那裡獲得了兩張地圖碎片。嘗試還原地圖以找出開始旅程的路線和方向。
為了應對這項挑戰,參與者必須下載並組合兩個地圖碎片圖片。這些圖片標記為「山姆的地圖碎片」和「佛羅多的地圖碎片」,如下所示:

但是,右鍵點擊圖片以儲存它將不起作用,因為瀏覽器內容功能表已停用(檢查頁面原始碼):

為了解決這個問題,您可以:
-
使用瀏覽器的開發人員工具從
標籤中移除反白顯示的部分 onContextMenu="return false;"。這將重新啟用右鍵點擊內容功能表,以便您可以正常儲存圖片。

或者,您可以在新的瀏覽器標籤中開啟圖片 URL 以直接存取它:

下載這兩張圖片後,提示建議我們需要「組合」圖片以顯示隱藏的地圖。為了實現這一點,我們可以編寫一個 Python 程式,將兩張圖片的像素 (R, G, B) 值讀取到矩陣中,然後嘗試不同的組合方法,如下所示:

Result (R3, G3, B3) = ((R1+?R2), (G2+?G2), (B3+?B3)
-
新增兩個矩陣的像素值以產生新的圖片。
-
將第二個圖片矩陣旋轉 90° / 180° / 270°,然後組合像素值。
-
在組合之前反轉第二個圖片矩陣。
-
在組合之前鏡像第二個圖片矩陣。
如果產生的像素值在任何 RGB 通道中超過 255,則該方法無效。正確的解決方案是從上到下鏡像第二個圖片,然後將其矩陣覆蓋到第一個圖片上以重建完整的地圖。一個簡單的 Python 像素 RGB 矩陣組合程式碼如下所示:
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")
或者,使用提供的腳本:pictureCombine.py 嘗試所有像素組合函數以取得正確的函數,這將自動產生組合結果,如下所示:

從合併的圖片中,參與者可以提取:
-
Route String :
FollowSilvertineDarkCliffsUnderstartLight -
Direction String :
WestSideNeArTw0AncientH011yTrEes
填寫 Route 和 Direction 欄位,然後按下按鈕 Go to the "Door of Durin" 以提交答案:

如果路線和方向正確,Question 1 旗標將顯示在頁面底部:

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:識別並確認 PLC 通訊 OT 協定
從先前的 Nmap 連接埠掃描中,我們發現連接埠 102 已開啟:

此連接埠與 ISO-TSAP 協定相關聯。但是,「iso-tsap」不是 正確的旗標。為了識別 PLC 使用的實際 OT 協定,請嘗試在 Google 中搜尋以下查詢:
“port 102 operational technology protocol TSAP type used by PLC”
這將導致兩個常見的結果:
-
Siemens S7 和 S7 Plus
-
The Inter-Control Center Communication Protocol (ICCP)
點擊「Start the Journey」按鈕後返回故事頁面。檢查頁面原始碼以找到名為 protocol.png 的隱藏圖片(1 像素寬的圖片):

為了顯示完整圖片,請使用瀏覽器的開發人員工具移除或修改圖片的樣式,然後顯示的圖片確認 PLC 正在使用 S7Comm 協定,如下所示:

您可以透過運行一個簡單的 S7Comm 客戶端 腳本,如本範例所示
安裝 python snap7 函式庫 https://pypi.org/project/python-snap7/ 並使用 S7Comm 通訊協定處理函式庫:https://github.com/LiuYuancheng/PLC_and_RTU_Simulator/blob/main/S7Comm_RTU_Simulator/src/snap7Comm.py 來建立您的 PLC 連線和記憶體區塊讀寫腳本,如下所示:
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))
執行腳本後,將會印出成功的連線訊息,以確認已建立連線:

Question 2 Flag : S7COMM
Step 4: Solve OT-Question 3 – Crack the Lock
-
OT puzzle: 找出 PLC 內部邏輯,並在有限時間內填入正確記憶體位址區塊的整數值。
Question 3 是 Task 2 的一部分,完成 Task 1 後,Task 2 標籤將自動顯示在導覽列中:

導覽至 Task 2 下的 “Crack the Lock” 挑戰。參與者將看到以下挑戰故事:
Frodo 發現鎖是由 PLC 控制的。他可以使用他的 pin(PLC 通訊客戶端程式)在 PLC 記憶體中設定一些值,以將鎖銷推到正確的位置。
任務頁面如下所示:

參與者必須與 PLC 互動,以設定所有鑰匙切口,將鎖銷推到正確的位置,模擬機械鎖的解鎖。主要挑戰元素包括:
-
鎖定機制由 12 個鎖銷挑戰組成(每行都有綠色、藍色、黃色、灰色矩形)。
-
淺藍色方塊必須與位於 column index = 12 的鑰匙槽 剪切線對齊
-
鑰匙切口由設定特定 PLC 記憶體位址中的值來控制,顯示在 "add"(位址)欄中:
['1[0]', '1[2]', '1[4]', '1[6]', '2[0]', '2[2]', '2[4]', '2[6]', '3[0]', '3[2]', '3[4]', '3[6]'],格式為。[ ] -
鎖銷長度(綠色底部的鎖銷)每 60 秒更新一次,並且在任務頁面上顯示倒數計時器。
-
一旦計時器重置,所有先前設定的值(包括鑰匙切口值)都會被清除。參與者必須在新鎖銷產生後的 60 秒內解決挑戰。
當使用者設定一些鑰匙切口值以推動鑰匙銷時,詳細的元件對應如下所示:

當參與者將整數值輸入到 PLC 中時,他們可以控制鎖銷移動的距離。每個鎖銷的正確值計算如下:Key Cut Value = 12 - Bottom Pin Tumbler Length
例如,如果目前的鎖銷長度為:
[7, 8, 4, 7, 9, 7, 3, 5, 6, 3, 10, 7]
那麼參與者需要為每個記憶體位元組位置設定的相應 鑰匙切口值 將為 [5, 4, 8, 5, 3, 5, 9, 7, 6, 9, 2, 5],如下表所示:
| 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 |
參與者可以使用 Siemens S7Comm 客戶端(如 Question 2 中發現的)將這些值寫入正確的 PLC 記憶體位址。講師可以提供或演示 doorLockSolver.py 腳本的使用,該腳本會自動將正確的值寫入 PLC。攻擊腳本執行日誌如下所示:

將值寫入記憶體後,UI 將更新並顯示鎖銷如何移動,如下所示。一旦所有綠色矩形(鎖銷)與剪切線(第 12 欄)對齊,鎖就可以打開:

Question 3 Flag : CISS_2025_P1Ns_@nd_TUm61ers_@T_C0RR3CT_P0S
Step 5: Solve IT-Question 4 – Find the PLC Memory Address
-
IT puzzle: 找到隱藏檔案並從矩陣中取得 PLC 記憶體區塊位址資料。
-
OT puzzle: 將正確的 Bool 資料注入到記憶體中,以對應字元的摩斯密碼。
一旦 Question 3 解決,Task 4 標籤將被解鎖並顯示在導覽列中:

從線索中,有一些關鍵點可以解決挑戰:
-
Matrix Processing Requirement : Sam 需要處理一個矩陣以提取一些有意義的資料。
-
Message Transmission : 帶有問號的訊息發送者由 PLC 控制,如果我們檢查起始頁面,我們將知道訊息發送機器是電報發射器,這表明訊息是使用摩斯密碼傳輸的。
-
Hidden Morse Code Hint:
"Hi Sam, got problem to find the address of the PLC memory? Check this -- .- - .-. .. -..-"解碼摩斯密碼:-- .- - .-. .. -..- → MATRIX這證實了矩陣檔案對於解決此挑戰至關重要。

螢幕上每個訊息字元下方都有一個 坐標 (x, y):

點擊矩陣摩斯密碼 -- .- - .-. .. -..- 以下載位址矩陣檔案,如下所示:

為了解決這個挑戰,參與者必須下載矩陣檔案並使用給定的坐標來查找矩陣中的值,以確定記憶體位址序列。或者,他們可以使用攻擊腳本 matrixSeacher.py 來檢索所有位址。
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: 將正確的布林值注入到正確的記憶體位址區塊。
解決 Question 4 後,您將獲得對應於訊息 WEDIDPASS 的 PLC 記憶體位址序列。每個字元對應於一個特定的 PLC 記憶體位址,如下所示:
W - 14
E - 33
D - 47
I - 68
D - 105
P - 117
A - 155
S - 198
S - 2025
當您檢查網頁上的訊息部分時,您會發現有助於您解決挑戰的關鍵線索,如圖所示。

-
有一個 隱藏提示(僅在使用滑鼠突出顯示時可見),顯示:
Hint: '.' = True, '-' = False -
每個字元必須轉換為摩斯密碼,並且相應的布林值(點為
True,破折號為False)必須寫入 PLC 記憶體中的 4 個連續位元組(例如,位元組 0、位元組 2、位元組 4、位元組 6)。 -
綠色指示燈表示位元組值正確,紅色表示不正確。
使用提示和每個字元的摩斯密碼表示,我們可以建立下表:
| 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 |
要設定值,請使用以下範例 Python 程式碼:
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)
或者,使用提供的腳本 morseCodeSolver.py 自動化所有字元的整個過程:

當 所有指示燈都變為綠色 時,點擊 “Send the Message” 按鈕。如果值正確,則 Question 5 的旗標 將出現在訊息區域下方的卡片中。
Question 5 : CISS_2025_--_---_.-._..._.C0DE_MSG_S3NT!
Step 7 : Solve IT-Question 6 – Extract Message From Audio
-
IT puzzle: 強力破解 zip 檔案密碼,從音訊檔案中取得資訊。
-
OT puzzle: 將正確的浮點值注入到正確的記憶體位址區塊。
一旦 Question 5 解決,下一個任務(Question 6)將自動顯示在與下方相同的頁面底部:

它顯示訊息,讓參與者調整電報接收器的頻率以接收 Aragorn 的回應訊息:
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.
我們需要將一個浮點數寫入 PLC 記憶體區塊,該區塊控制電報接收器的頻率。
訊息顯示 XOR 所有發送訊息區塊位址索引,在 question 4 中我們得到了旗標 CISS_2025_14_33_47_68_105_117_155_198_2025,然後執行整數 XOR 以獲得正確的記憶體區塊索引:
14 ^ 33 ^ 47 ^ 68 ^ 105 ^ 117 ^ 155 ^ 198 ^ 2025 = 2028
頻率是 CISS 的起始 MM.DD,如下所示為 07.29:

因此,我們需要使用以下程式碼將 7.29 插入到記憶體區塊 2028 中:
client.setAddressVal(2028, 0, 7.29, dataType=REAL_TYPE)
client.setAddressVal(2028, 4, 7.29, dataType=REAL_TYPE) # optinoal
一旦您在 PLC 記憶體中設定了值,請重新整理訊息發送頁面,您現在就可以收到 Aragorn 的訊息了。頁面將變更為以下狀態以顯示任務 4:

點擊連結以下載 .wav 音訊檔案。但是,此音訊檔案受密碼保護在 ZIP 封存檔中。要存取 .wav 檔案,您必須使用常見的密碼清單來暴力破解 ZIP 密碼。您可以使用諸如 fcrackzip 或 [zip2john + john] 之類的工具來執行此操作。例如,使用 fcrackzip 和前 1000 個密碼清單(例如,來自 SecLists 的 Pwdb_top-1000.txt):
fcrackzip -v -D -p Pwdb_top-1000.txt protected_audio.zip

破解後,從 ZIP 封存檔中提取 .wav 檔案。
.wav 檔案包含使用音訊隱寫術的隱藏訊息。收聽音訊檔案,音訊中的聲音似乎不是來自 "Aragorn",連續播放影片,在最後 7 秒,您會聽到一些 "摩斯密碼聲音",剪切音訊檔案並僅留下摩斯密碼聲音。將檔案另存為新的音訊檔案 morse.wmv
使用一些線上摩斯密碼來源解碼器,例如 https://morsecode.world/international/decoder/audio-decoder-adaptive.html 來分析檔案:

然後取得摩斯密碼:--. .. - .... ..- -... .... .. -.. -.. . -. .-- .- ...- .
轉換回文字資料:

我們得到一些資訊:GITSUBHIDDENWAVE 然後我們在 google 和 GitHub 中搜尋關鍵字 "HiddenWave",我們會找到一些工具 https://github.com/techchipnet/HiddenWave。
現在我們回到提取原始音訊,使用開源工具 HiddenWave,特別是 ExWave.py 腳本,可在此處獲得:
複製儲存庫:
git clone https://github.com/techchipnet/HiddenWave.git
cd HiddenWave
執行腳本:
python ExWave.py -f /path/to/your/audio.wav從 .wav 檔案中提取的隱藏訊息將顯示在終端機中 – 這就是您的 Question 6 flag。如下所示:

Question 6 Flag : CISS_2025_H1_Bro_wE11_DON3_FR0Do_@nd_S@m!
恭喜!您已通過都靈之門,並完成了此 CTF 挑戰的所有任務!
如果您對其他 OT PLC CTF 挑戰感興趣,您也可以查看 CISS2024 中的這個 Modbus-PLC 挑戰:https://www.linkedin.com/pulse/hacking-ics-step-by-step-guide-solve-critical-it-ot-ctf-yuancheng-liu-ohjwc
上次編輯者:LiuYuancheng([email protected]) 於 2025/07/15,如果您有任何問題,請傳訊息給我。
No comment for this article.