ICS-OT-PLC CTF Challenge Write-Up: Solving the CISS2025 Stage-1 “Pass the Door of Durin”

English 简体中文 繁体中文 ภาษาไทย Tiếng Việt
Summary

The "Pass the Door of Durin" challenge from CISS2025 Stage 1 presented an IT-OT hybrid CTF centered on a Siemens S7-1200 PLC simulator and an accompanying web interface. Solving it demanded a combination of traditional cybersecurity skills, such as web exploitation and scripting, with operational technology techniques like S7Comm protocol analysis and direct PLC memory manipulation. Key tasks involved reconstructing images to derive credentials, identifying the S7Comm protocol, and dynamically writing integer values to PLC memory to bypass a timed lock. Subsequent puzzles required extracting memory addresses from a matrix, transmitting Morse code through Boolean memory writes, and decoding hidden audio messages after setting a float frequency in the PLC. This integrated approach highlighted the necessity of bridging IT and OT security expertise to tackle complex industrial control system vulnerabilities.

关键基础设施安全对决 (CISS)”是一年一度的首屈一指的网络安全演习,提供专门的运营技术 (OT) CTF 竞赛,该竞赛在新加坡科技设计大学 (SUTD)举行,并由iTrust 研究中心组织。CISS 提供了一个绝佳的机会,汇集了全球 CTF 专家,共同应对现实的 OT 网络安全挑战。今年的 CISS 2025 包含两个阶段:

  • 第一阶段 – 资格赛:为期 48 小时的在线 CTF 竞赛。(已于 7 月 31 日结束)

  • 第二阶段 – 决赛:在 SUTD-iTrust 的真实测试平台上进行 4 小时的现场演习。

CISS-2025 第一阶段已于上个月结束,共有 61 支国际红队参加,其中 前 10 名队伍将晋级决赛。有关更多详细信息,请访问 CISS 2025 网站

在这篇撰写文章中,我将介绍我为 CISS2025 第一阶段提交的一个 IT-OT 混合挑战:“通过都灵之门”。这是比赛的第一个挑战,旨在作为参与者的入口点。它具有一个 Siemens S7-1200 PLC 模拟器,该模拟器运行 S7Comm 服务——允许使用 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

挑战介绍:通过都灵之门

每年,CISS CTF 都会有一个创造性的故事情节来激发挑战设计。2025 年的版本采用了“重掌控制权”的主题,灵感来自《指环王:王者归来》(参见下面的 CISS2024 海报)。

通过都灵之门”挑战是每个红队在 CTF 开始时应该克服的第一个关口。该挑战从 FrodoSam 前往末日火山摧毁魔戒的危险旅程中汲取灵感,为参与者开始他们自己的任务奠定了基础——解锁隐藏的知识并进入 IT-OT 网络安全谜题的世界。

该挑战的核心是为每个红队提供一个模拟 Siemens S7-1200 PLC 的实践环境。参与者需要:

  • 发现 PLC 的可访问 IP 和端口。

  • 分析 S7Comm 内存数据块并推断 PLC 执行的逻辑。

  • 将正确类型的数据(int、bool、float)插入到正确的内存块地址中。

  • 控制 PLC 以完成给定的任务并发现 flag

该挑战分为 四个主要任务,每个任务都包含一对以 IT 为中心的问题和一个以 OT 为中心的问题——总共六个 IT-OT 混合谜题。这种设计确保参与者必须将经典的 CTF 技能(Web 漏洞利用、扫描、脚本编写)与 OT 特定技术(S7Comm 协议处理、内存块操作)相结合。

整体环境设计如下图所示:

要解决此挑战,红队需要首先 SSH 进入分配给他们的网关和跳板机,然后访问他们专用的 Kali Linux Transaction VM(攻击者机器)。从那里,他们需要:

  • 识别 PLC IP 地址和可用服务。

  • 挑战任务和验证 Web 界面转发到他们的本地机器。

  • Kali VM 上开发自定义攻击脚本或安装工具(具有完整的 sudo 权限)。

  • PLCS7Comm 数据处理服务器(端口 102基于 FlaskWeb 服务器(端口 5001通信,以操作 PLC 内存并验证结果。

底层 PLC 模拟基于我的开源项目 Py_PLC_Honey_Pot 的一部分(如下所示),该项目模拟 Siemens S7 逻辑,并为 IT-OT 网络安全演习提供了一个灵活的平台。

Honeypot 介绍链接:https://www.linkedin.com/pulse/python-plc-honeypot-project-yuancheng-liu-vks8c

挑战背景故事

挑战背景故事如下所示:

FrodoSam 正在前往末日火山摧毁魔戒的路上。时间很短,他们没有足够的时间穿过冈达巴山脉。Sam 有一张破损的登兰德地图,上面有几个碎片,显示了通过地下隧道绕过山脉的捷径。在他们进入隧道之前,他们需要找到并穿过“都灵之门”。他们没有钥匙,但有一些方法可以打开这扇门。

在他们进入隧道之前,Frodo 需要向 Aragorn 发送一条消息,以引起火焰之眼的注意来帮助他们。

CTF 挑战问题概述

通过都灵之门”挑战分为 四个主要任务,包含总共 六个 CTF 问题。每个问题根据其难度级别都有一个分值 (1–10),并且某些问题提供可选提示。使用提示会使该问题的可用分数减少 60%。所有问题都部署在具有定义的最大尝试次数的 CTF-D 平台上,如下所示。

问题详情如下所示:

问题 描述 分数 最大尝试次数 提示
Q1:找到道路 FrodoSam 必须确定通往都灵之门的正确路径。一旦揭示,flag 将出现。 3 5 尝试组合两个地图碎片。
Q2:识别协议 这扇门由 PLC 控制。识别它使用的通信协议。 1 3 在网页源代码中查找线索。
Q3:破解门锁 Frodo 没有钥匙——帮助他绕过锁。解锁门会显示 flag 6 5 尝试插入整数值;通过 Web UI 检查内存地址。
Q4:定位 PLC 内存块 Sam 必须识别正确的 PLC 内存地址才能发送消息。 2 3 不适用
Q5:发送消息 帮助 FrodoAragorn 传递消息。发送正确的消息会显示 flag 5 5 尝试在内存中设置布尔值。
Q6:解码响应 Aragorn 以隐藏信号回复。调整无线电频率并解码响应以提取 flag 3 3 尝试在内存中设置浮点值。

 


解决挑战的步骤

每个参与者都将被分配一个具有 sudo 权限的 Kali-Linux Transaction VM,允许他们运行不同的红队工具或创建攻击脚本。登录到 Transaction VM,然后按照以下步骤逐个完成任务。

步骤 1:访问 PLC Web 界面

首先扫描 PLC VM 以识别 OT 协议和 HTTP 服务(端口 5002)使用的开放端口,使用 nmap 命令,如下所示:

nmap -sS -Pn -T5 -p- 172.23.155.209

Nmap 端口扫描结果:

从扫描中,您会发现端口 5002HTTP 服务)和 102 (iso-tsapl) 已打开。

由于 Transaction VM 运行的是 Ubuntu Server(没有桌面环境),因此您需要将 PLCWeb 界面端口转发到您的本地机器。使用以下 ssh 命令:

ssh -L 127.0.0.1:5000:172.23.155.209:5002 -J @ @172.23.144.1

建立隧道后,在您的本地浏览器中打开 http://127.0.0.1:5000 URL 以访问 PLC Web 界面。您将被定向到故事和挑战介绍页面,如下所示:

查看背景故事后,通过访问 Web 索引页面 http://127.0.0.1:5000/Index 继续挑战,如下所示,以获取 4 个任务介绍列表:

此页面将引导您到 问题 1,真正的任务从这里开始。


步骤 2:解决 IT 问题 1 – 重建地图

  • IT 谜题:组合图片的像素矩阵以获取 PLC Web 登录凭据。

问题 1 是任务 1 的一部分,该任务挑战参与者帮助 SamFrodo 重建路径。根据任务描述:

SamFrodoGollum 那里获得了两张地图碎片。尝试恢复地图以找出开始旅程的路线和方向。

要解决此挑战,参与者必须下载并组合两个地图碎片图像。这些图像被标记为“Sam 的地图碎片”和“Frodo 的地图碎片”,如下所示:

但是,右键单击图像以保存它将不起作用,因为浏览器上下文菜单已禁用(检查页面源代码):

要解决此问题,您可以:

  • 使用浏览器的开发者工具从 标签中删除突出显示的部分 onContextMenu="return false;"。这将重新启用右键单击上下文菜单,以便您可以正常保存图像。

或者,您可以在新的浏览器选项卡中打开图像 URL 以直接访问它:

下载两个图像后,提示表明我们需要“组合”图片以显示隐藏的地图。为了实现这一点,我们可以编写一个 Python 程序,将两个图像的像素 (R, G, B) 值读入矩阵,然后尝试不同的组合方法,如下所示:

结果 (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 尝试所有像素组合函数以获得正确的函数,这将自动生成组合结果,如下所示:

从合并的图像中,参与者可以提取:

  • 路线字符串:FollowSilvertineDarkCliffsUnderstartLight

  • 方向字符串:WestSideNeArTw0AncientH011yTrEes

填写路线和方向字段,然后按按钮 Go to the "Door of Durin" 以提交答案:

如果路线和方向正确,问题 1 flag 将显示在页面底部:

问题 1 FlagCISS_2025_@T_THE_FR0NT_0F_Th3_Ho11in_G@t3


步骤 3:解决 OT 问题 2 – 识别 PLC OT 协议

  • OT 谜题:识别并确认 PLC 通信 OT 协议

从之前的 Nmap 端口扫描中,我们发现端口 102 已打开:

此端口与 ISO-TSAP 协议相关联。但是,“iso-tsap不是正确的 flag。要识别 PLC 使用的实际 OT 协议,请尝试在 Google 中搜索以下查询:

“port 102 operational technology protocol TSAP type used by PLC”

这将导致两个常见结果:

  • Siemens S7S7 Plus

  • 控制中心间通信协议 (ICCP)

单击“开始旅程”按钮后返回到故事页面。检查页面源代码以找到名为 protocol.png 的隐藏图像(1 像素宽的图像):

要显示完整图像,请使用浏览器的开发者工具删除或修改图像的样式,然后显示的图像确认 PLC 正在使用 S7Comm 协议,如下所示:

您可以通过运行一个简单的

S7Comm 客户端 脚本,如本例所示

https://github.com/LiuYuancheng/PLC_and_RTU_Simulator/blob/main/S7Comm_RTU_Simulator/testcase/rtuClientTest.py

安装 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))
 

执行脚本后,将打印一条成功的连接消息,以确认已建立连接:

问题 2 标志S7COMM


步骤 4:解决 OT 问题 3 – 破解锁

  • OT 谜题:弄清楚 PLC 内部逻辑,并在有限的时间内将 int 值填充到正确的内存地址块中。

问题 3 是任务 2 的一部分,完成任务 1 后,任务 2 选项卡将自动显示在导航栏中:

导航到任务 2 下的“破解锁”挑战。参与者将看到以下挑战故事:

Frodo 发现锁由 PLC 控制。他可以使用他的 pin(PLC 通信客户端程序)在 PLC 内存中设置一些值,以将锁销推到正确的位置。

任务页面如下所示:

参与者必须与 PLC 交互,以设置所有钥匙槽口,将锁销推到正确的位置,模拟机械锁的解锁。主要挑战元素包括:

  • 锁机构由 12 个锁销挑战组成(每行都有绿色、蓝色、黄色、灰色矩形)。

  • 浅蓝色方块必须与位于列索引 = 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],如下表所示:

地址索引 字节 Idx=0 字节 Idx=2 字节 Idx=4 字节 Idx=6
1 5 4 8 5
2 3 5 7 9
3 6 9 2 5

参与者可以使用Siemens S7Comm 客户端(如问题 2 中发现的)将这些值写入正确的 PLC 内存地址。指导员可以提供或演示doorLockSolver.py脚本的使用,该脚本会自动将正确的值写入 PLC。攻击脚本执行日志如下所示:

将值写入内存后,UI 将更新并显示锁销如何移动,如下所示。一旦所有绿色矩形(锁销)与剪切线(第 12 列)对齐,就可以打开锁:

问题 3 标志CISS_2025_P1Ns_@nd_TUm61ers_@T_C0RR3CT_P0S


步骤 5:解决 IT 问题 4 – 查找 PLC 内存地址

  • IT 谜题:找到隐藏文件并从矩阵中获取 PLC 内存块地址数据。

  • OT 谜题:将正确的 Bool 数据注入到内存中,以映射字符的莫尔斯电码。

一旦问题 3 得到解决,任务 4 选项卡将在导航栏中解锁并显示:

从线索中,有一些关键点可以解决挑战:

  • 矩阵处理要求:Sam 需要处理一个矩阵来提取一些有意义的数据。

  • 消息传输:带有问号的消息发送者由 PLC 控制,如果我们检查起始页面,我们将知道消息发送机器是一台电报发射机,这表明消息是使用莫尔斯电码传输的。

  • 隐藏的莫尔斯电码提示"Hi Sam, got problem to find the address of the PLC memory? Check this -- .- - .-. .. -..-" 解码莫尔斯电码:-- .- - .-. .. -..- → MATRIX 这证实了矩阵文件是解决此挑战的关键。

屏幕上每个字符的下方都有一个坐标 (x, y)

单击矩阵中最多的代码-- .- - .-. .. -..-以下载地址矩阵文件,如下所示:

要解决此挑战,参与者必须下载矩阵文件并使用给定的坐标来查找矩阵中的值,以确定内存地址序列。或者,他们可以使用攻击脚本matrixSeacher.py来检索所有地址。

问题 4 标志CISS_2025_14_33_47_68_105_117_155_198_2025

步骤 6:解决 OT 问题 5 – 通过莫尔斯电码发送消息

  • OT 谜题:将正确的布尔值注入到正确的内存地址块。

解决问题 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)。

  • 指示器绿色表示字节值正确,红色表示不正确。

使用提示和每个字符的莫尔斯电码表示,我们可以构建下表:

字符 内存地址 莫尔斯电码 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来自动执行所有字符的整个过程:

所有指示器都变为绿色时,单击“发送消息”按钮。如果值正确,则问题 5 的标志将出现在消息区域下方的卡片中。

问题 5CISS_2025_--_---_.-._..._.C0DE_MSG_S3NT!


步骤 7:解决 IT 问题 6 – 从音频中提取消息

  • IT 谜题:暴力破解 zip 文件密码,从音频文件中获取信息。

  • OT 谜题:将正确的浮点值注入到正确的内存地址块。

一旦问题 5 得到解决,下一个任务(问题 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 所有发送消息块地址索引,在问题 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 文件中提取的隐藏消息将显示在终端中——这是你的第 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

 


Last edit by LiuYuancheng([email protected]) at 15/07/2025, if you have any problem, please send me a message.

  RELATED

No related programming articles found. Browse all programming tutorials and articles.

  COMMENTS

0

No comment for this article.