题目信息

题面提示:

人們都說,愛情建立在溝通之上。
很明顯,Anna 和 Jason 正在同一個頻道上。

Flag 格式:

1
PUCTF26{[a-zA-Z0-9_]+_[a-fA-F0-9]{32}}

附件给了两个流量包:

  • greenhat-anna.pcap
  • greenhat-jason.pcap

这题的核心不是单纯搜明文 flag,而是:

  1. 先从 HTTP 明文流量里提取 配置 / 图片 / 响应头 等线索;
  2. 还原 agent_message对称密钥
  3. 解密 C2 通信;
  4. 从被窃取的文件内容里拿到 flag。

1. 初步观察

先全局搜关键字,能看到几个非常醒目的明文资源:

  • /.well-known/service-config
  • /photos/selfie.jpg
  • /downloads/photo-viewer
  • /private/note.txt
  • POST //api/v1.4/agent_message

其中 agent_message 最可疑,因为题面说 Anna 和 Jason 在同一“频道”上,而这个接口明显像是 Agent/C2 的消息通道。

在流量里还能直接看到 service-config 的响应内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
{
"service": "media-sync",
"version": "3.2.1",
"auth": {
"method": "composite",
"components": ["alpha", "beta", "gamma"],
"algorithm": "aes-256-cbc",
"verify": false
},
"nodes": [
{
"id": "node-0",
"host": "10.20.0.20",
"port": 80,
"path": "/api/v1.4/agent_message",
"secret": "3UurTD6IUvd9jQ=="
}
]
}

这里已经把最关键的情报告诉我们了:

  • 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
2
3
Base64(
UUID(36 bytes ASCII) || ???binary payload???
)

继续看长度和分块后可以确认,后半段可以拆成:

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 Hotel
  • Artist = Jason & Michelle
  • DateTimeOriginal = 2026:02:15 22:45:03

其中真正有密码学价值的是这条:

1
IACTNJIOUGTU4m4=

4. 三段材料的长度非常“巧”

现在我们手里有三段 Base64:

  1. EXIF UserComment

    1
    IACTNJIOUGTU4m4=
  2. service-config 响应头里的 X-Correlation-ID

    1
    bwoRWmUWL6d9XCw=
  3. node-0secret

    1
    3UurTD6IUvd9jQ==

把它们分别 Base64 解码:

1
2
3
4
5
6
7
8
>>> base64.b64decode("IACTNJIOUGTU4m4=").hex()
'20009334920e5064d4e26e'

>>> base64.b64decode("bwoRWmUWL6d9XCw=").hex()
'6f0a115a65162fa77d5c2c'

>>> base64.b64decode("3UurTD6IUvd9jQ==").hex()
'dd4bab4c3e8852f77d8d'

它们的长度分别是:

  • 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 以后,解密步骤就很直接了:

  1. Base64 解码 body
  2. 取前 36 字节作为 UUID
  3. 取接下来的 16 字节作为 IV
  4. 取最后 32 字节作为 HMAC
  5. 中间部分作为 AES-CBC 密文
  6. HMAC-SHA256(key, IV || ct) 做完整性校验
  7. 校验通过后用 AES-256-CBC 解密并去 PKCS#7 padding

第一条请求解出来是一个典型的 Agent checkin:

1
2
3
4
5
6
7
{
"action":"checkin",
"architecture":"amd64",
"host":"anna-pc",
"ips":["10.20.0.10"],
...
}

服务端返回:

1
2
3
4
5
{
"id":"4285f4d5-1a45-4125-8b94-2e1ea181b4a1",
"status":"success",
"action":"checkin"
}

之后就是连续的 get_tasking 轮询,以及服务端下发的命令。


7. 解密后看到的攻击行为

把整条 C2 通信解开以后,可以看到服务端在对 Anna 的机器做枚举和取证,典型任务包括:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
whoami
id
uname -a
ls -la /home/
ls -la /home/anna/
ls -la /home/anna/Documents/
ip addr show
cat /etc/resolv.conf
find /home/anna -name '*.txt' 2>/dev/null | head -20
cat /home/anna/.bash_history 2>/dev/null || echo 'no history'
netstat -tlnp 2>/dev/null || ss -tlnp
ps aux
download /home/anna/Documents/diary.txt
download /home/anna/Documents/draft-message.txt
cat /home/anna/Documents/draft-message.txt

这里最重要的是两次文件下载:

  • /home/anna/Documents/diary.txt
  • /home/anna/Documents/draft-message.txt

8. 从下载块里恢复 flag

download 类型的回包不是直接明文文件,而是 JSON 里嵌一个 chunk_data 字段,内容再次 Base64 编码。

例如 draft-message.txt 的这一段回传里有:

1
2
3
4
5
{
"download": {
"chunk_data": "aGV5IHNhcmFoLCBhcmUgeW91IHRoZXJlPwpjYW4geW91IHRlbGwga2V2aW4gaSBjYW50IG1ha2UgaXQgdG9tb3Jyb3cK..."
}
}

把这个 chunk_data Base64 解码以后,就能得到完整文本。关键片段如下:

1
2
3
oh btw here is the secret code for our shared album
PUCTF26{1_4m_w4tch_1ng_y0u_ch34t_a3b7c9d1e5f20846f1d9b3a7c5e80264}
dont share it with anyone especially not jason

因此最终 flag 为:

1
PUCTF26{1_4m_w4tch_1ng_y0u_ch34t_a3b7c9d1e5f20846f1d9b3a7c5e80264}

9. 最终脚本思路

完整脚本做了这些事:

  1. dpkt 解析 pcap,按 TCP 四元组重组流;
  2. 解析 HTTP 请求/响应;
  3. 自动提取:
    • service-config 的 JSON
    • X-Correlation-ID
    • node-0 secret
    • selfie.jpg 的 EXIF UserComment
  4. 枚举三段 key material 的排列,找到能通过 HMAC 校验的 32 字节 key;
  5. 解密全部 agent_message
  6. 遍历 JSON 里的 chunk_data 并 Base64 解码;
  7. 正则提取 flag。

脚本运行结果:

1
2
3
4
5
6
[+] user_comment      = IACTNJIOUGTU4m4=
[+] x-correlation-id = bwoRWmUWL6d9XCw=
[+] node0 secret = 3UurTD6IUvd9jQ==
[+] key order = usercomment || x-correlation-id || node0-secret
[+] aes key (hex) = 20009334920e5064d4e26e6f0a115a65162fa77d5c2cdd4bab4c3e8852f77d8d
[+] FLAG = PUCTF26{1_4m_w4tch_1ng_y0u_ch34t_a3b7c9d1e5f20846f1d9b3a7c5e80264}

复盘

这题的设计点挺漂亮:

  • 表面是双人聊天 / 流量取证题;
  • 中间用博客、配置、图片 EXIF 把 key material 分散藏起来;
  • 真正的核心是把 C2 的 AES-CBC + HMAC 封装还原出来;
  • flag 不在明文流量里,而是在 被窃取文件 的内容里。
1
明文资源取线索 -> 组合密钥 -> 解密 C2 -> 恢复 exfil 文件 -> 拿 flag