Chapter 09
BLE ① 仕組みと状況把握 — 見えない電波を「見る」
この章のゴール.
BLE(Bluetooth Low Energy)を外からテストするために、まず受動的に状況を把握する—— どんなアドバタイズを出しているか、GATT にどんなサービスがあるか、接続がどう成立するかを、 スキャナと簡単な Python で観測できるようになること。攻撃・ファジングは次章に回す。
この章で使う既出の用語(定義は各リンク先). オラクル(01 章 8 節)、ブラックボックステスト(01 章 1 節)、仕様書(01 章 3 節)、規格(01 章 3 節)、状態遷移(02 章 1 節)、有無(04 章 5 節)
1. なぜ無線は特別に難しいか
03 章〜08 章は配線で信号に触れた。無線は線がない。電波は空間を飛び、他の機器と混ざり、再現しづらい。 だから無線のブラックボックステストは、まず「今、何が出ているのか」を可視化するところから始める。 本章で扱う BLE は、組み込み機器でいちばん多く出会う無線であり、道具(スキャナ、bleak)が整っているので学びやすい。
BLE(Bluetooth Low Energy) とは、低消費電力を狙った近距離無線の規格である。ボタン電池で年単位動くセンサやウェアラブルが典型で、生成 AI に書かせた組み込みファームでも「スマホと BLE でつなぐ」構成は非常に多い。
2. BLE の二層構造 — GAP と GATT
BLE を理解する鍵は、役割の違う二つの層に分けて考えることである。
- GAP(Generic Access Profile): 「見つける・つなぐ」を司る層。誰が誰に見えて、どう接続するか。アドバタイズ(自分の存在を周期的に叫ぶ)とスキャン(それを聞く)、そして接続がここ。
- GATT(Generic Attribute Profile): 接続したあとの「何を読み書きするか」を司る層。データはサービス(機能のまとまり)→キャラクタリスティック(個々の値)という木構造で並ぶ。各値には UUID という識別子が付く。
UUID(Universally Unique Identifier) とは、128 ビットの世界的にかぶらない識別番号のこと。標準サービス(心拍計など)は 16 ビットの短縮 UUID を持ち、独自機能は 128 ビットのフル UUID を使う。
3. まず聞く — アドバタイズとスキャン
機器は接続していない間、アドバタイズパケットを周期的に(例: 100 ms ごと)ブロードキャストしている。中には機器名、提供サービスの UUID、送信電力、独自の製造者データなどが入る。これをスキャンで拾えば、接続せずに多くのことが分かる。
- デバイス名 / MAC アドレス: 何がそこにいるか。アドレスがランダム化されているか(プライバシー)
- アドバタイズ間隔: 省電力設計か、状態で変わるか(例: ペアリング待ちだけ高速アドバタイズ)
- 製造者データ / サービスデータ: 温度やボタン状態をアドバタイズだけで流す設計(Beacon 的)だと、接続不要で値が漏れていないか
- RSSI(受信信号強度): 距離の目安。近づけると上がる
道具は、スマホアプリ(nRF Connect、LightBlue)、PC の bleak(Python、クロスプラットフォーム)、より低層まで見るなら Sniffer(nRF52 の BLE sniffer + Wireshark)。まずは bleak のスキャンから。
import asyncio
from bleak import BleakScanner
async def main():
devices = await BleakScanner.discover(timeout=5.0, return_adv=True)
for addr, (dev, adv) in devices.items():
print(addr, dev.name, adv.rssi, "dBm")
print(" services:", adv.service_uuids)
print(" mfr:", adv.manufacturer_data) # 独自データがそのまま見える
asyncio.run(main())4. つないで並べる — GATT の列挙
接続すると、機器が持つサービスとキャラクタリスティックの一覧(GATT テーブル)を読み出せる。これは機器の「外から見た API 仕様書」であり、テスト対象の面(attack surface / test surface)そのものである。
- どんなキャラクタリスティックがあるか。Read / Write / Notify のどれが許されているか
- 認証・暗号化なしで読める / 書けるものがないか(本来守るべき値が素通しでないか、17 章)
- Notify(機器から自発通知)で状態がどう流れるか——これは絶好の観測点(オラクル)になる
from bleak import BleakClient
async def enum(addr):
async with BleakClient(addr) as c:
for s in c.services:
print("SERVICE", s.uuid)
for ch in s.characteristics:
print(" CHAR", ch.uuid, ch.properties) # ['read','write','notify']5. 状況把握でわかるテスト観点
接続せず・接続してすぐの受動観測だけで、こんな不具合が見える。
- アドバタイズに秘密が乗っている(シリアル番号、生の測定値)——設計ミス
- MAC アドレスが固定でプライバシー機能が効いていない——追跡可能
- 暗号化なしで重要値が読める / 書ける——認証設計の欠落
- 状態遷移がアドバタイズ間隔や名前に漏れている(デバッグ用の名前が製品に残存)
- Notify が止まらない / 来ない——電力とデータ供給のバグ
6. 何をオラクルにするか
- 期待するサービス/キャラクタリスティックがあり、権限(R/W/N)が仕様どおり
- 秘密がアドバタイズに出ていない、重要値が無認証で読み書きできない
- Notify の値・周期・単位が期待どおり(03 章〜07 章の観測と突き合わせる)
- RSSI が距離に対して妥当(送信電力設計)
7. 手を動かす
BLEアドバタイズとGATTを読む
この章のポイント
- 無線テストは「今、何が出ているか」の可視化から。BLE は道具が整っていて学びやすい
- GAP(見つける・つなぐ)と GATT(読み書き)の二層で考える。データはサービス→キャラクタリスティックの木
- 接続せずにアドバタイズをスキャンするだけで、名前・UUID・製造者データ・RSSI から多くが分かる
- 接続後の GATT 列挙は「外から見た API 仕様書」。R/W/Notify 権限と暗号化の有無がテスト観点
- 道具は
bleak(Python)、nRF Connect、BLE sniffer + Wireshark