wireshark
Traffic revealed normal Modbus traffic plus "extra" communications—all protocol-compliant, evading firewall alerts. Attacker knows the protocol.
译:流量显示正常的Modbus流量加上“额外”的通信——所有协议都符合,可以避开防火墙警报。攻击者知道协议。
首先拿到一个流量包,一共有四个流(1-4),发现是工控流量,全是modbus
192.168.100.102返回的Read Holding Registers响应中包含了一个假的flag
检查其他Modbus响应,发现192.168.100.101的响应中包含特殊字符串:
S7COMM01
查询资料得到:这是DES密钥(8字节长度,符合DES密钥特征)
#!/usr/bin/env python3
import struct
import socket
from typing import List, Dict, Optional
from Crypto.Cipher import DES
PCAP_FILE = "challenge.pcapng"
def parse_pcap(path: str) -> List[Dict]:
"""Parse PCAP file and extract Modbus/TCP packets."""
with open(path, "rb") as f:
data = f.read()
# Detect endianness from magic number
endian = {
b"\xd4\xc3\xb2\xa1": "<",
b"\xa1\xb2\xc3\xd4": ">"
}.get(data[:4])
if endian is None:
raise ValueError("Unsupported pcap format")
offset = 24 # Skip global header
packets = []
pkt_index = 0
while offset + 16 <= len(data):
# Parse packet header
ts_sec, ts_usec, incl_len, orig_len = struct.unpack(
f"{endian}IIII", data[offset:offset + 16]
)
offset += 16
# Extract packet data
raw = data[offset:offset + incl_len]
offset += incl_len
pkt_index += 1
# Skip non-IPv4 packets
if len(raw) < 14 or struct.unpack("!H", raw[12:14])[0] != 0x0800:
continue
# Parse IP header
ip = raw[14:]
if len(ip) < 20 or ip[9] != 6: # Skip non-TCP packets
continue
src_ip = socket.inet_ntoa(ip[12:16])
dst_ip = socket.inet_ntoa(ip[16:20])
# Parse TCP header
tcp = ip[(ip[0] & 0x0F) * 4:]
if len(tcp) < 20:
continue
src_port, dst_port = struct.unpack("!HH", tcp[:4])
if 502 not in (src_port, dst_port): # Skip non-Modbus packets
continue
payload = tcp[(tcp[12] >> 4) * 4:]
if len(payload) < 8: # Skip incomplete Modbus headers
continue
# Parse Modbus/TCP MBAP
tid, pid, length = struct.unpack("!HHH", payload[:6])
unit_id = payload[6]
func_code = payload[7]
pdu = payload[8:]
packets.append({
"index": pkt_index,
"ts": ts_sec + ts_usec / 1_000_000,
"src": src_ip,
"dst": dst_ip,
"sport": src_port,
"dport": dst_port,
"tid": tid,
"unit": unit_id,
"func": func_code,
"pdu": pdu,
"raw": payload,
})
return packets
def printable(bs: bytes) -> str:
"""Convert bytes to printable ASCII with dots for non-printable chars."""
return "".join(chr(b) if 32 <= b < 127 else "." for b in bs)
def des_ecb_decrypt(ciphertext: bytes, key: bytes) -> bytes:
"""Decrypt DES-ECB using pycryptodome."""
cipher = DES.new(key, DES.MODE_ECB)
return cipher.decrypt(ciphertext)
def find_fake_flag(packets: List[Dict]) -> Optional[str]:
"""Find fake flag in Modbus Read Holding Registers responses."""
for pkt in packets:
if (pkt["func"] == 3 and pkt["src"] == "192.168.100.102"
and len(pkt["pdu"]) >= 1):
text = printable(pkt["pdu"][1:])
if "flag{" in text:
return text[text.index("flag{"):]
return None
def find_des_key(packets: List[Dict]) -> Optional[Dict]:
"""Find DES key in Modbus Read Holding Registers responses."""
for pkt in packets:
if (pkt["func"] == 3 and pkt["src"] == "192.168.100.101"
and len(pkt["pdu"]) >= 1 and b"S7COMM01" in pkt["pdu"][1:]):
return {
"key": b"S7COMM01",
"packet_index": pkt["index"]
}
return None
def extract_cipher_blocks(packets: List[Dict], after_index: int) -> List[bytes]:
"""Extract cipher blocks from Diagnostics/Return Query Data packets."""
blocks = []
for pkt in packets:
if pkt["index"] <= after_index:
continue
if not (pkt["src"] == "192.168.100.10"
and pkt["dst"] == "192.168.100.101"
and pkt["func"] == 8
and len(pkt["pdu"]) >= 10):
continue
subfunc = struct.unpack("!H", pkt["pdu"][:2])[0]
data = pkt["pdu"][2:]
# Filter out obvious test messages and collect valid blocks
if subfunc == 0x0000 and len(data) == 8 and data not in (b"LINK", b"PING", b"TEST", b"ECHO"):
blocks.append(data)
if len(blocks) == 6:
break
return blocks
def main():
packets = parse_pcap(PCAP_FILE)
# Step 1: Find fake flag and DES key
fake_flag = find_fake_flag(packets)
if fake_flag:
print("[+] Fake flag found:", fake_flag)
key_info = find_des_key(packets)
if not key_info:
raise RuntimeError("DES key not found")
des_key = key_info["key"]
key_packet_index = key_info["packet_index"]
print(f"[+] DES key found in packet #{key_packet_index}: {des_key!r}")
# Step 2: Extract cipher blocks after key packet
blocks = extract_cipher_blocks(packets, key_packet_index)
if len(blocks) != 6:
raise RuntimeError(f"Expected 6 blocks, got {len(blocks)}")
ciphertext = b"".join(blocks)
print("[+] Ciphertext:", ciphertext.hex())
# Step 3: Decrypt and extract flag
plaintext = des_ecb_decrypt(ciphertext, des_key)
print("[+] Decrypted raw:", plaintext)
# Remove PKCS#7 padding
pad = plaintext[-1]
if 1 <= pad <= 8 and plaintext.endswith(bytes([pad]) * pad):
plaintext = plaintext[:-pad]
print("[+] Real flag:", plaintext.decode())
if __name__ == "__main__":
main()
输出:
[+] Fake flag found: flag{this_is_fake_try_harder!}
[+] DES key found in packet #30612: b'S7COMM01'
[+] Ciphertext: ded7825ede4fd19c9f37371c37c6fa2d54e6fe2801f0df1d763175a586db1c629efa82d0f8eacb417b4419392b4a6aa8
[+] Decrypted raw: b'flag{a3f8e2d1-7c19-4a6b-b5e8-9d2f0c4a7e31}\x06\x06\x06\x06\x06\x06'
[+] Real flag: flag{a3f8e2d1-7c19-4a6b-b5e8-9d2f0c4a7e31}
Real sign in
The picture for pixls ,Please find the secret and send it to the WeChat official account background to get the flag!!
译:图片为pixls,请找到秘密并将其发送到微信公众号后台以获取旗帜
拿到的压缩包经过加密,同时发现压缩包加密算法为:ZipCrypto,压缩算法需为:Store
并且里面有PNG文件,想到进行明文爆破
C:\Users\q1388\Desktop\工具\bkcrack-1.8.1-win64>bkcrack.exe -C 1.zip -c challenge.png -p png_header
bkcrack 1.8.1 - 2025-10-25
[15:26:41] Z reduction using 9 bytes of known plaintext
100.0 % (9 / 9)
[15:26:41] Attack on 712831 Z values at index 6
Keys: 5eb34ede c49019bf 815834b9
42.5 % (302894 / 712831)
Found a solution. Stopping.
You may resume the attack with the option: --continue-attack 302894
[15:28:07] Keys
5eb34ede c49019bf 815834b9
C:\Users\q1388\Desktop\工具\bkcrack-1.8.1-win64>bkcrack -C 1.zip -c challenge.png -k 5eb34ede c49019bf 815834b9 -U out.zip 123
bkcrack 1.8.1 - 2025-10-25
[15:33:00] Writing unlocked archive out.zip with password "123"
100.0 % (1 / 1)
Wrote unlocked archive.
打开png:

接下来是:
-------------------------------------------------------------------------------------------------------------------
Zigzag变换(Zigzag Transformation)
知识点:
Zigzag变换通过之字形路径遍历矩阵元素,将二维数据展平为一维序列。这种遍历方式确保了相邻的二维数据在一维序列中也保持相邻。
原始矩阵: Zigzag序列:
0 1 5 6 0 → 1 → 2 → 3 → 4 → 5 → 6 → 7 → 8
2 4 7 8
3 8 9 10
实际遍历路径:
→ ↘ ↓ ↘
↓ ↗ → ↘
→ ↘ ↓ ↘
**主要应用***
1. JPEG图像压缩(最著名)
对DCT(离散余弦变换)系数矩阵进行Zigzag扫描
将低频系数(重要信息)排在前面,高频系数(通常为零)排在后面
便于后续的行程编码和霍夫曼编码
2. 视频编码(H.264/HEVC等)
用于变换系数的熵编码优化
3. 数据压缩与加密
改变数据排列顺序,增强压缩效率或安全性
算法实现要点:
def zigzag(matrix):
rows, cols = len(matrix), len(matrix[0])
result = []
for s in range(rows + cols - 1):
if s % 2 == 0: # 偶数对角线:向上遍历
for i in range(max(0, s - cols + 1), min(s + 1, rows)):
j = s - i
result.append(matrix[i][j])
else: # 奇数对角线:向下遍历
for i in range(min(s, rows - 1), max(-1, s - cols), -1):
j = s - i
result.append(matrix[i][j])
return result
优点:
| 特性 | 说明 |
|---|---|
| 保持局部性 | 相邻像素/系数在序列中仍相邻 |
| 能量集中 | 将重要数据前置,利于压缩 |
| 简单高效 | 计算复杂度低,实现简单 |
--------------------------------------------------------------------------------------------------------------------
from PIL import Image
def zigzag_decrypt_pixels(input_image, output_image, rows):
"""
PNG 图片像素 ZigZag 解密
Args:
input_image: 输入的加密 PNG 图片路径
output_image: 输出的解密 PNG 图片路径
rows: ZigZag 行数
"""
# 打开图片
img = Image.open(input_image)
width, height = img.size
pixels = list(img.getdata())
total_pixels = len(pixels)
if rows <= 1 or rows >= total_pixels:
img.save(output_image)
print("行数无效,直接保存原图")
return
# 计算每行应该有多少像素
fence_lengths = [0] * rows
row = 0
direction = 1
for i in range(total_pixels):
fence_lengths[row] += 1
if row == 0:
direction = 1
elif row == rows - 1:
direction = -1
row += direction
# 分割像素到各行
fence = []
index = 0
for length in fence_lengths:
fence.append(list(pixels[index:index + length]))
index += length
# 按 ZigZag 路径读取并重组
result_pixels = []
row = 0
direction = 1
row_indices = [0] * rows
for i in range(total_pixels):
result_pixels.append(fence[row][row_indices[row]])
row_indices[row] += 1
if row == 0:
direction = 1
elif row == rows - 1:
direction = -1
row += direction
# 创建新图片
decrypted_img = Image.new(img.mode, (width, height))
decrypted_img.putdata(result_pixels)
decrypted_img.save(output_image)
print(f"解密完成!输出:{output_image}")
# 使用示例
if __name__ == "__main__":
# 修改为你的文件路径和行数
input_path = "encrypted.png" # 输入的加密图片
output_path = "decrypted.png" # 输出的解密图片
num_rows = 3 # ZigZag 行数 (根据题目调整)
zigzag_decrypt_pixels(input_path, output_path, num_rows)
执行后得到新的图片,是个二维码:

扫描后得到:
I_love_DesCTF
关注公众号,发送信息后得到flag;
DesCTF{Have fun and enjoy the challenges ahead!!!}

Comments NOTHING