# Malicious BLE Peripheral Specification

The attacker needs a programmable BLE peripheral (e.g. nRF52 DK with Zephyr,
or a Linux box running BlueZ + `bluetoothctl`/custom GATT server).

## GATT Layout

- **1 primary service**, UUID `12345678-0000-1000-8000-00805f9b34fb`
- **N characteristics** under that service, where **N >= 2** (the loop in
  `OnGetCharacteristics()` must have at least one iteration remaining after
  the failing one). Larger N (e.g. 16–32) widens the heap-spray target:
  the freed `std::vector` backing array is `N * sizeof(ComPtr) = N * 8` bytes.
- Each characteristic should have at least 1 descriptor so that
  `GetDescriptorsAsync()` is meaningful.

## Timing Behaviour (the hard part)

`PostAsyncResults()` fails iff `IAsyncOperation::put_Completed()` returns a
failure HRESULT. Under normal WinRT semantics this succeeds even if the
operation has already completed/errored. Known failure modes:

- `RO_E_CLOSED` — the async operation object was closed before the handler
  was attached.
- `RPC_E_DISCONNECTED` / `0x800706BA` — the proxy to the Windows Bluetooth
  service process was torn down (e.g. service crash or COM marshaling
  failure) between `GetDescriptorsAsync()` returning and `put_Completed()`
  running.
- `E_OUTOFMEMORY` — under memory pressure.

The peripheral therefore should:

1. Respond normally to *Primary Service Discovery* and *Characteristic
   Discovery* so the loop at `bluetooth_gatt_discoverer_winrt.cc:386` is
   entered with N entries.
2. When the central issues the **Find Information Request** for the K-th
   characteristic's descriptors (K < N), immediately send an **LL_TERMINATE_IND**
   (link-layer disconnect) instead of replying. Repeat across many connect
   cycles with K varied and small jitter, hoping to land the disconnect such
   that the WinRT `IAsyncOperation<GattDescriptorsResult*>` enters a closed
   state before Chrome calls `put_Completed()` on it.

This is **opportunistic** — the peripheral cannot deterministically force the
HRESULT failure. The web page in `poc.html` retries `gatt.connect()` in a loop
to maximise attempts.
