题目信息
题面提示:
人們都說,愛情建立在溝通之上。
很明顯,Anna 和 Jason 正在同一個頻道上。
Flag 格式:
1 | PUCTF26{[a-zA-Z0-9_]+_[a-fA-F0-9]{32}} |
附件给了两个流量包:
greenhat-anna.pcapgreenhat-jason.pcap
这题的核心不是单纯搜明文 flag,而是:
- 先从 HTTP 明文流量里提取 配置 / 图片 / 响应头 等线索;
- 还原
agent_message的 对称密钥; - 解密 C2 通信;
- 从被窃取的文件内容里拿到 flag。
1. 初步观察
先全局搜关键字,能看到几个非常醒目的明文资源:
/.well-known/service-config/photos/selfie.jpg/downloads/photo-viewer/private/note.txtPOST //api/v1.4/agent_message
其中 agent_message 最可疑,因为题面说 Anna 和 Jason 在同一“频道”上,而这个接口明显像是 Agent/C2 的消息通道。
在流量里还能直接看到 service-config 的响应内容:
1 | { |
这里已经把最关键的情报告诉我们了:
agent_message用的是 AES-256-CBC- 密钥不是单个值,而是 composite(由多个 component 拼起来)
node-0的 secret 是:
1 | 3UurTD6IUvd9jQ== |
同一个 HTTP 响应头里还有一个很关键的字段:
1 | X-Correlation-ID: bwoRWmUWL6d9XCw= |
这通常看起来像普通追踪头,但在这种题里很可能也是 key material。
2. 观察 agent_message 的外层格式
POST //api/v1.4/agent_message 的 body 长这样:
1 | NDI4NWY0ZDUtMWE0NS00MTI1LThiOTQtMmUxZWExODFiNGEx... |
一眼看上去就是 Base64。
把它 Base64 解码后,可以看到前 36 字节是一个 UUID,例如:
1 | 4285f4d5-1a45-4125-8b94-2e1ea181b4a1 |
也就是说消息格式不是普通 JSON,而是:
1 | Base64( |
继续看长度和分块后可以确认,后半段可以拆成:
1 | UUID(36) || IV(16) || CIPHERTEXT(n*16) || HMAC-SHA256(32) |
我拿第一条消息验证后,结构是:
- UUID:
37bc765d-c15a-45b6-9d1b-f246ee7c0ccc - IV 长度:16 字节
- 密文长度:608 字节
- 末尾校验:32 字节
这和 AES-CBC + HMAC-SHA256 的常见封装完全一致。
3. 从图片里拿到第三段 key material
接下来要解决的是:alpha / beta / gamma 到底分别是什么。
流量中有一张明文下载的图片:
1 | GET /photos/selfie.jpg HTTP/1.1 |
把这张 JPEG 提出来看 EXIF,可以直接看到 UserComment:
1 | IACTNJIOUGTU4m4= |
另外 EXIF 里还带了时间和描述:
ImageDescription = MK Love HotelArtist = Jason & MichelleDateTimeOriginal = 2026:02:15 22:45:03
其中真正有密码学价值的是这条:
1 | IACTNJIOUGTU4m4= |
4. 三段材料的长度非常“巧”
现在我们手里有三段 Base64:
EXIF
UserComment1
IACTNJIOUGTU4m4=
service-config响应头里的X-Correlation-ID1
bwoRWmUWL6d9XCw=
node-0的secret1
3UurTD6IUvd9jQ==
把它们分别 Base64 解码:
1 | base64.b64decode("IACTNJIOUGTU4m4=").hex() |
它们的长度分别是:
- 11 字节
- 11 字节
- 10 字节
合计正好:
1 | 11 + 11 + 10 = 32 bytes |
而 AES-256 的 key 恰好就是 32 字节。
这基本已经明示了:composite 的三段 component 就是这三段 Base64 解码后的结果。
5. 确定拼接顺序
虽然三段材料已经找齐,但顺序还没完全确定。
最稳的方法不是猜,而是直接枚举 3! = 6 种排列,并用第一条 agent_message 验证:
- 先按
UUID(36) || IV(16) || CT || HMAC(32)切分; - 对候选 key 计算:
1 | HMAC-SHA256(key, IV || ciphertext) |
- 哪个排列的 HMAC 与包尾的 32 字节完全一致,哪个就是正确 key。
实际只有一种排列能通过校验:
1 | usercomment || x-correlation-id || node0-secret |
最终 AES key 十六进制是:
1 | 20009334920e5064d4e26e6f0a115a65162fa77d5c2cdd4bab4c3e8852f77d8d |
6. 解密 agent_message
确定 key 以后,解密步骤就很直接了:
- Base64 解码 body
- 取前 36 字节作为 UUID
- 取接下来的 16 字节作为 IV
- 取最后 32 字节作为 HMAC
- 中间部分作为 AES-CBC 密文
- 用
HMAC-SHA256(key, IV || ct)做完整性校验 - 校验通过后用 AES-256-CBC 解密并去 PKCS#7 padding
第一条请求解出来是一个典型的 Agent checkin:
1 | { |
服务端返回:
1 | { |
之后就是连续的 get_tasking 轮询,以及服务端下发的命令。
7. 解密后看到的攻击行为
把整条 C2 通信解开以后,可以看到服务端在对 Anna 的机器做枚举和取证,典型任务包括:
1 | whoami |
这里最重要的是两次文件下载:
/home/anna/Documents/diary.txt/home/anna/Documents/draft-message.txt
8. 从下载块里恢复 flag
download 类型的回包不是直接明文文件,而是 JSON 里嵌一个 chunk_data 字段,内容再次 Base64 编码。
例如 draft-message.txt 的这一段回传里有:
1 | { |
把这个 chunk_data Base64 解码以后,就能得到完整文本。关键片段如下:
1 | oh btw here is the secret code for our shared album |
因此最终 flag 为:
1 | PUCTF26{1_4m_w4tch_1ng_y0u_ch34t_a3b7c9d1e5f20846f1d9b3a7c5e80264} |
9. 最终脚本思路
完整脚本做了这些事:
- 用
dpkt解析 pcap,按 TCP 四元组重组流; - 解析 HTTP 请求/响应;
- 自动提取:
service-config的 JSONX-Correlation-IDnode-0 secretselfie.jpg的 EXIFUserComment
- 枚举三段 key material 的排列,找到能通过 HMAC 校验的 32 字节 key;
- 解密全部
agent_message; - 遍历 JSON 里的
chunk_data并 Base64 解码; - 正则提取 flag。
脚本运行结果:
1 | [+] user_comment = IACTNJIOUGTU4m4= |
复盘
这题的设计点挺漂亮:
- 表面是双人聊天 / 流量取证题;
- 中间用博客、配置、图片 EXIF 把 key material 分散藏起来;
- 真正的核心是把 C2 的 AES-CBC + HMAC 封装还原出来;
- flag 不在明文流量里,而是在 被窃取文件 的内容里。
1 | 明文资源取线索 -> 组合密钥 -> 解密 C2 -> 恢复 exfil 文件 -> 拿 flag |