环境搭建
项目地址:
ble_ctf · github
首先你需要淘宝购买一块ESP32的板子,随便买一块就行了
然后在kali虚拟机里输入如下命令(注意wsl不行,因为串口无法连接到wsl里面,还是虚拟机好一些)
pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple
pip install esptool
首先得安装 esptool 用来烧录 esp32
git clone https://github.com/hackgnar/ble_ctf
cd ble_ctf
esptool.py -p /dev/ttyUSB0 -b 460800 --before default_reset --after hard_reset --chip esp32 write_flash --flash_mode dio --flash_size 2MB --flash_freq 40m 0x1000 build/bootloader/bootloader.bin 0x8000 build/partition_table/partition-table.bin 0x10000 build/ble_ctf.bin
查看蓝牙设备:hciconfig
激活:hciconfig hci0 up
查看蓝牙信息:sudo hciconfig hci0 lestates
给BLECTF上电,然后输入hcitool lescan,就可以扫描周围蓝牙设备了

由此可以得知蓝牙设备的MAC地址了
bug解决
Can’t init device hci0: Function not implemented (38)

这种情况是kali没有蓝牙适配器,淘宝买一个即可,我买的是CSR8510,对linux的支持比较好
connect to xx:xx:xx:xx:xx:xx: Connection refused (111)

装上蓝牙适配器之后,如果出现Connect refused,那么重启一下蓝牙服务即可
service bluetooth start
再不行,点击kali右上角的蓝牙图标,手动关闭打开一下


做题
gattool命令简介
GATT commands
--primary 发现GATT服务
--characteristics 发现设备上所有的characteristics
--char-read 读某个characteristics,需要指定一个handle(句柄)
--char-write 写某个characteristics,需要指定一个handle,使用Write Without Response的方式
--char-write-req 写某个characteristics,需要指定一个handle,使用Write Request的方式
--char-desc 发现所有的Characteristics Descriptor
--listen 监听Characteristics的notification或者indication
Primary Services/Characteristics arguments
-s, --start=0x0001 起始handle
-e, --end=0xffff 结束handle
-u, --uuid=0x1801 16比特或者128比特的UUID
Characteristics Value/Descriptor Read/Write arguments
-a, --handle=0x0001 通过handle来读写characteristic,后面接handle值
-n, --value=0x0001 写characteristic时候的参数,后面接具体的值
Application Options:
-i, --adapter=hciX 后面接设备描述, 如hci0等
-b, --device=MAC 远端设备的蓝牙地址
-t, --addr-type=[public | random] 远端设备蓝牙地址的类型,默认为public
-m, --mtu=MTU att协议的MTU大小
-p, --psm=PSM 制定gatt的PSM,默认值为0
-l, --sec-level=[low | medium | high] 安全等级,默认为low
-I, --interactive 交互式模式
使用gatttool -b F4:65:0B:E7:6E:6A –char-read -a 0x002a|awk -F’:’ ‘{print $2}’|tr -d ‘ ‘|xxd -r -p;printf ‘\n’查看当前分数

关卡1
gatttool -b F4:65:0B:E7:6E:6A --char-write-req -a 0x002c -n $(echo -n "12345678901234567890"|xxd -ps)

熟悉一下怎么交flag
关卡2
请读取句柄(handle)0x002e 的 ASCII 值,并将其提交到用于 flag 提交的句柄 0x002c

gatttool -b F4:65:0B:E7:6E:6A --char-write-req -a 0x002c -n $(echo -n "6432303533303365303939636566663434383335")

关卡3
读取 BLE 设备上 GATT 句柄(handle)0x0030 的 ASCII 值,按照其中的指示操作,并将最终得到的 flag 提交到句柄 0x002c

这里的提示是获得设备名的md5值
那么自然就是BLECTF进行md5了

然后github主页说了md5只取前20字符

gatttool -b F4:65:0B:E7:6E:6A --char-write-req -a 0x002c -n $(echo -n "5cd56d74049ae40f442e"|xxd -ps)
关卡4
蓝牙 GATT 服务提供了一些额外的设备属性。尝试找出 “Generic Access” 服务中的 “Device Name” 值。
使用primary参数发现设备的服务

Generic Access(UUID:1800)和Generic Attribute(UUID:1801)

所以我们接下来要查一下,有关Generic Access服务的相关信息(UUID=1800)
gatttool -b F4:65:0B:E7:6E:6A --characteristics --start=0x0014 --end=0x001c

可以看到handle16对应的是Device Name(UUID=2a00)
这里区分一下handle和value handle,value handle才是我们真正想要的那个
| 名称 | 全称 | 作用 | 是否包含实际数据? |
|---|---|---|---|
| Handle | Attribute Handle | 每个 GATT 属性(Attribute) 的唯一编号 | ❌ 不一定 |
| Value Handle | Characteristic Value Handle | 特征值(Characteristic Value) 的 handle | ✅ 是! |
gatttool -b F4:65:0B:E7:6E:6A --char-write-req -a 0x002c -n $(echo -n "2b00042f7481c7b056c4"|xxd -ps)

关卡5
读取句柄 0032 并按照其指示操作。注意,它并没有让你像之前那样写入到 flag 句柄。当你找到 flag 后,请将其写入你过去用于提交 flag 的那个句柄中。

让我们在这里随便写点东西
gatttool -b F4:65:0B:E7:6E:6A --char-write-req -a 0x0032 -n $(echo 123)

再次read就是flag了
gatttool -b F4:65:0B:E7:6E:6A --char-write-req -a 0x002c -n $(echo -n "3873c0270763568cf7aa"|xxd -ps)
关卡6
按照从读取句柄 0x0034 中获取的指令进行操作。请注意,某些工具仅支持写入十六进制(hex)值,而其他工具则同时提供写入十六进制或 ASCII 文本的方法。

提示写ascii码的“yo”
gatttool -b F4:65:0B:E7:6E:6A --char-read -a 0x0034|awk -F':' '{print $2}'|tr -d ' '|xxd -r -p;printf '\n'

gatttool -b F4:65:0B:E7:6E:6A --char-write-req -a 0x002c -n $(echo -n "c55c6314b3db0a6128af"|xxd -ps)
关卡7
请遵循从读取句柄 0x0036 中获取的指令。请注意,某些工具仅支持写入十六进制值,而其他工具则同时提供写入十六进制或 ASCII 文本的方法。

要我们把0x7写在这里
gatttool -b F4:65:0B:E7:6E:6A --char-write-req -a 0x0036 -n 07
gatttool -b F4:65:0B:E7:6E:6A --char-write-req -a 0x002c -n $(echo -n "1179080b29f8da16ad66"|xxd -ps)
关卡8
请遵循从读取句柄 0x0038 中获取的指令。注意此处的句柄。请记住,句柄可以用整数或十六进制表示,大多数工具(如 gatttool 和 bleah)都支持这两种方式指定句柄。

提示我们把C9写到句柄58里面
gatttool -b F4:65:0B:E7:6E:6A --char-write-req -a 58 -n C9

gatttool -b F4:65:0B:E7:6E:6A --char-write-req -a 0x002c -n $(echo -n "f8b136d937fad6a2be9f"|xxd -ps)
关卡9
查看句柄 0x003c 并按照其指示操作。你应该为此编写一个脚本来实现自动化。同时请注意,某些工具的写入速度比其他工具更快。
让我们爆破它的值

#!/bin/bash
# 配置参数
DEVICE="F4:65:0B:E7:6E:6A"
HANDLE="0x003c"
DELAY=0.1 # 写入后等待时间(秒),可调
echo "开始爆破句柄 $HANDLE 的值 (00 -> ff)"
for i in $(seq 0 255); do
# 格式化为两位十六进制
hex_val=$(printf "%02x" $i)
echo "尝试写入: $hex_val"
# 写入值
gatttool -b $DEVICE --char-write-req -a $HANDLE -n $hex_val > /dev/null 2>&1
# 等待片刻,避免过快请求
sleep $DELAY
# 读取当前值
read_value=$(gatttool -b $DEVICE --char-read -a $HANDLE | awk -F'[: ]' '{print $2}' | tr -d ' ')
# 输出结果
echo "写入 $hex_val -> 读回: $read_value"
# 可选:检测是否匹配,例如如果读回等于写入,则标记
if [ "$read_value" == "$hex_val" ]; then
echo "✅ 匹配: $hex_val"
fi
# 可选:如果发现特殊行为,暂停或保存
# 例如:如果返回值异常,可以 break 或 log
done
echo "爆破完成。"

gatttool -b F4:65:0B:E7:6E:6A --char-write-req -a 0x002c -n $(echo -n "933c1fcfa8ed52d2ec05"|xxd -ps)
关卡10
查看句柄 0x003e 并按照其指示操作。请注意,某些工具在执行读写操作时的连接速度优于其他工具,这取决于该工具提供的功能,或其如何利用主机操作系统上的蓝牙连接缓存。建议尝试使用不同的工具来获取此 flag。一旦找到速度最快的工具,就编写一个脚本或单行 Bash 命令来完成任务。提示:如果操作正确,该任务在运行后大约需要 90 秒完成。

提示要读取该句柄1000次,每次都用gatttool建立连接很明显太慢了,这里我们用gatttool的交互模式
gatttool -b F4:65:0B:E7:6E:6A -I << EOF
connect
$(for i in {1..10000}; do echo "char-read-hnd 0x003e"; done)
quit
EOF
之所以是10000不是1000是因为这里没有设置时延,所以干脆多读几次得了

gatttool -b F4:65:0B:E7:6E:6A --char-write-req -a 0x002c -n $(echo -n "6ffcd214ffebdc0d069e"|xxd -ps)
关卡11
查看句柄 0x0040,并在 Google 上搜索 “gatt notify”。一些工具(例如 gatttool)具备订阅 GATT 通知的功能。

随便发个值,开启监听模式即可

gatttool -b F4:65:0B:E7:6E:6A --char-write-req -a 0x002c -n $(echo -n "3565633337373262636430306366303664386562")

关卡12
查看句柄 0x0042,并在 Google 上搜索 “gatt indicate”。对于像本次挑战这样的单次响应指示(indicate)消息,gatttool 等工具完全可以胜任。

这里其实就想告诉我们indicate和notify不一样,前者需要回应,后者不需要回应,但是在这里实用的命令是一样的

gatttool -b F4:65:0B:E7:6E:6A --char-write-req -a 0x002c -n $(echo -n "6337623836646431323138343863373763313133")
关卡13
查看句柄 0x0046,并按照它的提示操作。请注意,本次通知(notification)挑战要求你接收多条响应才能完成。

这次要接受多次notification

可以看到第一次是假的,第二次之后就是真的了
gatttool -b F4:65:0B:E7:6E:6A --char-write-req -a 0x002c -n $(echo -n "c9457de5fd8cafe349fd"|xxd -ps)
关卡14
查看句柄 0x0048,并在 Google 上搜索 “gatt indicate”。请注意,本次挑战要求你解析多条指示(indicate)响应才能完成。

和刚才一样的指令

gatttool -b F4:65:0B:E7:6E:6A --char-write-req -a 0x002c -n $(echo -n "b6f3a47f207d38e16ffa"|xxd -ps)
关卡15
查看句柄 0x004c,并按照它的提示操作。和以太网或 Wi-Fi 设备类似,你也可以更改蓝牙设备的 MAC 地址。

提示让我们修改我们的MAC地址为11:22:33:44:55:66
BT MAC:Bluetooth MAC Address 的缩写,即 蓝牙设备的 MAC 地址。
用 bdaddr 去修改 MAC 地址,出现如下错误

sudo apt-get install libbluetooth-dev
装一下依赖库即可


gatttool -b F4:65:0B:E7:6E:6A --char-write-req -a 0x002c -n $(echo -n "aca16920583e42bdcf5f"|xxd -ps)
关卡16
阅读句柄 0x004e 并照其指示操作。设置 MTU 可能是一件棘手的事情。虽然有些工具可能提供 MTU 标志,但它们似乎无法真正触发服务器上的 MTU 协商。建议使用 gatttool 的交互模式来完成此任务。默认情况下,BLECTF 服务器设置为强制 MTU 大小为 20。服务器会监听 MTU 协商并进行查看,但实际上我们在代码中并未更改 MTU。我们只是在你使用句柄 0x004e 中指定的值触发 MTU 事件时,触发相应的标志代码。祝你好运!
最大传输单元MTU(Maximum Transmission Unit,MTU),是指网络能够传输的最大数据包大小,以字节为单位。MTU的大小决定了发送端一次能够发送报文的最大字节数。如果MTU超过了接收端所能够承受的最大值,或者是超过了发送路径上途经的某台设备所能够承受的最大值,就会造成报文分片甚至丢弃,加重网络传输的负担。如果太小,那实际传送的数据量就会过小,影响传输效率。

题目让我们把MTU设置成444
这里我们需要用交互模式,因为-m选项不会主动发起 MTU 协商请求


gatttool -b F4:65:0B:E7:6E:6A --char-write-req -a 0x002c -n $(echo -n "b1e409e5a4eaf9fe5158"|xxd -ps)
关卡17
请查看句柄(handle)0x0050,并按照它的指示操作。
本题与其他“写入类”挑战不同:你所使用的写入工具必须正确实现“写入响应”(Write Response ACK)机制。
此外,该 flag 的获取方式也比较 tricky(巧妙/棘手)——flag 会以“通知”(notification)的形式返回数据,尽管该特征(characteristic)并没有设置 “NOTIFY” 属性。

让我们使用–char-write-req方式写入hello
gatttool -b F4:65:0B:E7:6E:6A --char-write-req -a 0x0050 -n $(echo -n "hello"|xxd -ps)

gatttool -b F4:65:0B:E7:6E:6A --char-write-req -a 0x002c -n $(echo -n "d41d8cd98f00b204e980"|xxd -ps)
关卡18
查看句柄 0x0052。请注意它并没有“通知”(notify)属性。但你仍然要在这里执行写入操作,并且无论如何都要监听通知!事情往往并不像表面看起来那样!

虽然这么说,但还是可以监听的

随便写点东西采用监听模式就可以了

gatttool -b F4:65:0B:E7:6E:6A --char-write-req -a 0x002c -n $(echo -n "fc920c68b6006169477b"|xxd -ps)
关卡19
仔细检查句柄 0x0054 的所有属性!对它们进行各种尝试(读、写、通知等),找出构成 flag 的碎片


这里的句柄属性是9b,对应权限如下
10011011 0x9b
10000000 0x80 Extended Properties
00010000 0x10 Notify
00001000 0x08 write
00000010 0x02 read
00000001 0x01 Broadcast


随便写点东西,得到一段flag

通过监听,还可以获得一段flag

gatttool -b F4:65:0B:E7:6E:6A --char-write-req -a 0x002c -n $(echo -n "fbb966958f07e4a0cc48"|xxd -ps)
关卡20

gatttool -b F4:65:0B:E7:6E:6A --char-write-req -a 0x002c -n $(echo -n "d953bfb9846acc2e15ee"|xxd -ps)