5 月 21 日刷题记录

整理当天刷题过程里的关键步骤、脚本片段和切入点。

今日靶场

循镜

公交车司机 (Misc/工控流量分析)

靶场题目

1. 题目背景与分析

  • 题目名称:公交车司机
  • 题目描述:有一名黑客入侵了公交车交通系统,我们需要分析流量中的蛛丝马迹,找到 FLAG。
  • 文件名称modbus.pcapng

通过 Wireshark 的统计 (Statistics) -> 协议分级 (Protocol Hierarchy)功能观察,发现该流量包中 100%100\% 均为 Modbus/TCP 协议流量,总共包含 150 个数据包。

结合题目公交车交通系统的背景,判定这是一道典型的工控安全流量分析题。黑客通过 Modbus 协议向 PLC(可编程逻辑控制器)发送了指令,Flag 极有可能隐藏在黑客写入的数据中。

2. 解题步骤

步骤一:流量宏观观察

使用 Wireshark 打开流量包,在主界面过滤器中输入 modbus 进行筛选。

观察发现,所有的 Query 请求功能码均为 Func: 16: Write Multiple Registers(写多个保持寄存器),且每一条报文的长度均为固定的 69 字节。这表明黑客正在连续、有规律地向工控设备写入特定数据。

步骤二:数据提取

由于数据包较多(150个),逐个手动查看效率较低。我们利用 Wireshark 的导出功能将流量包转换为结构化的 JSON 文件:

  • 点击 文件 (File) -> 导出分组解析结果 (Export Packet Dissections) -> 作为 JSON... (As JSON...)

随后编写 Python 脚本,批量提取每个 Modbus 报文中的寄存器写入值(modbus.regval_uint16 字段):

Python

text
import json

# 读取导出的 JSON 流量数据
with open('modbus.json', 'r', encoding='utf-8') as f:
    packets = json.load(f)

extracted_values = []
for p in packets:
    layers = p.get('_source', {}).get('layers', {})
    mb = layers.get('modbus', {})
    
    # 提取寄存器写入值
    for k, v in mb.items():
        if isinstance(v, dict) and 'modbus.regval_uint16' in v:
            extracted_values.append(int(v['modbus.regval_uint16']))
            break

步骤三:数据分析与解码

对提取出的寄存器数值进行分析发现,前 66 个数据包的值较小,属于干扰或探测流量。

第 67 个数据包开始,写入的数值对应的 ASCII 码呈现明显的十六进制字符特征(如 54 对应字符 '6'99 对应字符 'c')。

将第 67 包之后的所有数值转换成对应的字符并拼接,得到如下十六进制字符串:

Plaintext

text
666c61677b31343234303135392d376633362d346333362d623436612d3734613661366465643834617d

使用 Python 或 CyberChef 将该 Hex 字符串转换为明文字符串:

Python

text
hex_str = "666c61677b31343234303135392d376633362d346333362d623436612d3734613661366465643834617d"
flag = bytes.fromhex(hex_str).decode('utf-8')
print(flag)

3. 最终 FLAG

Plaintext

text
flag{14240159-7f36-4c36-b46a-74a6a6ded84a}

老乡鸡sign

靶场题目

解题过程

拿到图片后,先查看文件结构。PNG 正常结束于 IEND,但文件尾部仍然存在多余数据,说明图片后面可能拼接了其他文件。

手动解析 PNG chunk,发现 IEND 之后还有 358 字节数据:

python
import struct

  

path = "老乡鸡.png"

data = open(path, "rb").read()

  

offset = 8

while offset < len(data):

    length = struct.unpack(">I", data[offset:offset + 4])[0]

    chunk_type = data[offset + 4:offset + 8]

    offset += 12 + length

    if chunk_type == b"IEND":

        break

  

print("IEND end:", offset)

print("trailing bytes:", len(data) - offset)

查看尾部数据时可以看到明显的 ZIP 目录结构特征,例如 504B0102504B0506,并且还出现了 .txt 文件名特征。但是文件头缺失了 504B0304,所以普通解压工具无法直接解压。

尾部数据开头类似:

text
33 00 01 00 63 00 ...

...

50 4B 01 02 ...

...

50 4B 05 06 ...

因此将 IEND 后面的数据取出,并在前面补上 ZIP 本地文件头 PK\x03\x04,也就是十六进制的 504B0304

python
import struct

src = "老乡鸡.png"
data = open(src, "rb").read()

offset = 8
while offset < len(data):
    length = struct.unpack(">I", data[offset:offset + 4])[0]
    chunk_type = data[offset + 4:offset + 8]
    offset += 12 + length
    if chunk_type == b"IEND":
        break

tail = data[offset:]

open("recovered_tail_fixed.zip", "wb").write(b"PK\x03\x04" + tail)

修复后可以识别为 ZIP,里面有一个文件。文件名使用 GBK 解码后为:

老乡鸡秘诀.txt

继续查看 ZIP 信息,发现压缩方式为 99,这是 WinZip AES 加密。

python
import zipfile

with zipfile.ZipFile("recovered_tail_fixed.zip") as z:
    for info in z.infolist():
        print(info.filename)
        print(info.compress_type)
        print(info.flag_bits)

图片元数据中存在异常时间戳提示:

bash
exiftool 老乡鸡.png

可以看到时间相关字段异常,对应时间戳:

text
1065054630

用该时间戳作为密码解压 AES ZIP。

python
import pyzipper

password = b"1065054630"

with pyzipper.AESZipFile("recovered_tail_fixed.zip") as z:

    name = z.namelist()[0]
    z.setpassword(password)
    data = z.read(name)
    print(data.decode("utf-8"))

得到内容:

text
hocctf {老乡鸡_中式快餐_老母鸡汤_香飘全国}

干净卫生,比家里还放心

Flag

text
hocctf {老乡鸡_中式快餐_老母鸡汤_香飘全国}

评论区

评论加载中...