CC2540 sniffer passively capturing BLE packets between two devices

When developing Bluetooth software or hardware, developers often need to see the raw data communication between an application and a device. Microsoft provides a Bluetooth Test Platform for Windows, but it offers very limited features and requires expensive hardware. A CC2540 USB dongle is an accessible and affordable alternative. It is cheap and widely available.

A BLE sniffer, also known as a Bluetooth protocol analyzer, is a passive listening device that captures BLE packets sent over the air from nearby devices. It is useful when debugging communication between devices without interfering with the link, and when analyzing a device with an unknown protocol alongside a mobile application.

While the CC2540 dongle ships with its own packet capture software, deeper tasks such as custom analysis, data logging, and protocol decoding usually require BLE sniffing to be integrated into the application itself. The Bluetooth Framework provides this capability. It supports the CC2540 dongle in BLE sniffing mode and exposes a class that captures all Bluetooth LE communication between devices.


How it works

The first thing needed is a CC2540-based USB Bluetooth dongle. The recommended option is the CC2540EMK-USB dongle from Texas Instruments, which ships with the required firmware pre-installed. Any CC2540-based clone from other suppliers also works. The required firmware is available on the Texas Instruments website.


Driver installation

The dongle uses a proprietary Cebal driver by default, and it must be replaced with WinUSB. The replacement is done with the Zadig tool.

  1. Download Zadig.
  2. Start the Zadig application.
  3. Make sure the List All Devices menu item is checked in the Options menu.
  4. Select CC2540 USB Dongle in the drop-down list.
  5. Make sure the source driver is Cebal.
  6. Make sure the target driver is WinUSB.
  7. Click the Replace Driver button and wait for the installation to complete.

Once the driver is replaced, the CC2540 USB dongle is ready to use with the Bluetooth Framework for capturing Bluetooth LE communication. To restore the original Cebal driver:

  1. Open the Device Manager.
  2. Find the CC2540 USB Dongle device under the USB Devices node.
  3. Delete the device.
  4. Refresh devices. Windows re-finds the device and installs the original Cebal driver.

Sample application

The Bluetooth Framework includes the BleSniffer sample application, which can be used as a starting point for developing a BLE sniffing application and for testing the framework's sniffer implementation.

A BLE sniffer captures packets in two modes:

  • Advertising mode — the dongle captures advertising packets, mainly on the primary advertising channels (37, 38, 39). In this mode, a target BLE device can be located and selected for capturing its communication.
  • Connection mode — captures raw packet data exchanged between two Bluetooth LE devices during a connection, on the remaining 37 channels (0 through 36).

After starting the BleSniffer sample, the advertising channel number must be provided: 37, 38, or 39. Once the capture begins, the application reports all received BLE packets. When a connection request is captured, the wclBleSniffer class automatically follows the channel changes to capture control and data frames.

The framework's sniffer can decode the following advertising PDUs:

  • ADV_IND
  • ADV_DIRECT_IND
  • ADV_NONCONN_IND
  • SCAN_REQ
  • SCAN_RSP
  • CONNECT_IND
  • ADV_SCAN_IND

Data PDUs and any other unknown packets are reported as raw byte streams and can be decoded by the application. If additional PDU decoders are needed, contact us and we will do our best to add them.

Note: BLE packet capturing produces a high-volume data stream. Process received frames as quickly as possible. For better performance, copy a captured frame and analyze it in a separate thread. Avoid asynchronous operations inside the capture event handlers.


Using the wclBleSniffer

Starting and stopping the sniffer

The wclBleSniffer class must be created, its events hooked, and then started with a chosen advertising channel. The following minimal example shows the initialization, start, and stop logic for each language.

procedure TfmMain.btStartClick(Sender: TObject);
var
  Res: Integer;
begin
  Res := wclBleSniffer.Start(cbChannel.ItemIndex + 37);
  if Res <> WCL_E_SUCCESS then
    ShowMessage('Start failed: 0x' + IntToHex(Res, 8));
end;

procedure TfmMain.btStopClick(Sender: TObject);
var
  Res: Integer;
begin
  Res := wclBleSniffer.Stop;
  if Res <> WCL_E_SUCCESS then
    ShowMessage('Stop failed: 0x' + IntToHex(Res, 8));
end;

procedure TfmMain.FormDestroy(Sender: TObject);
begin
  wclBleSniffer.Stop;
end;
void __fastcall TfmMain::btStartClick(TObject *Sender)
{
    int Res = wclBleSniffer->Start(cbChannel->ItemIndex + 37);
    if (Res != WCL_E_SUCCESS)
        ShowMessage("Start failed: 0x" + IntToHex(Res, 8));
}

void __fastcall TfmMain::btStopClick(TObject *Sender)
{
    int Res = wclBleSniffer->Stop();
    if (Res != WCL_E_SUCCESS)
        ShowMessage("Stop failed: 0x" + IntToHex(Res, 8));
}

void __fastcall TfmMain::FormDestroy(TObject *Sender)
{
    wclBleSniffer->Stop();
}
// In form's Load event:
FSniffer = new wclBleSniffer();
FSniffer.OnAdvIndReceived += FSniffer_OnAdvIndReceived;
// ... hook other events similarly ...

private void btStart_Click(Object sender, EventArgs e)
{
    Int32 Res = FSniffer.Start(
        (Byte)(cbChannel.SelectedIndex + 37));
    if (Res != wclErrors.WCL_E_SUCCESS)
        MessageBox.Show("Start failed: 0x" + Res.ToString("X8"));
}

private void btStop_Click(Object sender, EventArgs e)
{
    Int32 Res = FSniffer.Stop();
    if (Res != wclErrors.WCL_E_SUCCESS)
        MessageBox.Show("Stop failed: 0x" + Res.ToString("X8"));
}
' In form's Load event:
FSniffer = New wclBleSniffer()
' Hook events using Handles clauses on the method
' declarations.

Private Sub btStart_Click(sender As Object, e As EventArgs)
        Handles btStart.Click
    Dim Res As Int32 =
        FSniffer.Start(cbChannel.SelectedIndex + 37)
    If Res <> wclErrors.WCL_E_SUCCESS Then
        MessageBox.Show("Start failed: 0x" + Res.ToString("X8"))
    End If
End Sub

Private Sub btStop_Click(sender As Object, e As EventArgs)
        Handles btStop.Click
    Dim Res As Int32 = FSniffer.Stop()
    If Res <> wclErrors.WCL_E_SUCCESS Then
        MessageBox.Show("Stop failed: 0x" + Res.ToString("X8"))
    End If
End Sub
// In OnInitDialog:
__hook(&CwclBleSniffer::OnAdvIndReceived, &FSniffer,
    &CBleSnifferDlg::AdvIndReceived);
// ... hook other events ...

void CBleSnifferDlg::OnBnClickedButtonStart()
{
    int Res = FSniffer.Start((const unsigned char)
        (cbChannel.GetCurSel() + 37));
    if (Res != WCL_E_SUCCESS)
        AfxMessageBox(_T("Start failed: 0x") + IntToHex(Res));
}

void CBleSnifferDlg::OnBnClickedButtonStop()
{
    int Res = FSniffer.Stop();
    if (Res != WCL_E_SUCCESS)
        AfxMessageBox(_T("Stop failed: 0x") + IntToHex(Res));
}

Handling captured packets

Each decoded PDU type has its own event handler. The example below shows how to handle the ADV_IND packet, one of the most common advertising PDUs. The same pattern applies to the other six PDU types.

procedure TfmMain.wclBleSnifferAdvIndReceived(Sender: TObject;
  const PduHeader: TwclBluetoothLeAdvertisingPduHeader;
  const AdvA: Int64; const AdvData: Pointer;
  const AdvDataLen: Byte);
begin
  lbPackets.Items.Add('ADV_IND RECEIVED');
  lbPackets.Items.Add('  AdvA: ' + IntToHex(AdvA, 12));
  DumpPduHeader(PduHeader);
  DumpPayload(AdvData, AdvDataLen);
end;
void __fastcall TfmMain::wclBleSnifferAdvIndReceived(TObject *Sender,
    const TwclBluetoothLeAdvertisingPduHeader &PduHeader,
    const __int64 AdvA, const Pointer AdvData,
    const BYTE AdvDataLen)
{
    lbPackets->Items->Add("ADV_IND RECEIVED");
    lbPackets->Items->Add("  AdvA: " + IntToHex(AdvA, 12));
    DumpPduHeader(PduHeader);
    DumpPayload(AdvData, AdvDataLen);
}
void FSniffer_OnAdvIndReceived(Object Sender,
    wclBluetoothLeAdvertisingPduHeader PduHeader,
    Int64 AdvA, Byte[] AdvData)
{
    lbPackets.Items.Add("ADV_IND RECEIVED");
    lbPackets.Items.Add("  AdvA: " + AdvA.ToString("X12"));
    DumpPduHeader(PduHeader);
    DumpPayload(AdvData);
}
Private Sub FSniffer_OnAdvIndReceived(Sender As Object,
        PduHeader As wclBluetoothLeAdvertisingPduHeader,
        AdvA As Long, AdvData As Byte())
        Handles FSniffer.OnAdvIndReceived

    lbPackets.Items.Add("ADV_IND RECEIVED")
    lbPackets.Items.Add("  AdvA: " + AdvA.ToString("X12"))
    DumpPduHeader(PduHeader)
    DumpPayload(AdvData)
End Sub
void CBleSnifferDlg::AdvIndReceived(void* Sender,
    const wclBluetoothLeAdvertisingPduHeader& PduHeader,
    const __int64 AdvA, const unsigned char* const AdvData,
    const unsigned char AdvDataLen)
{
    UNREFERENCED_PARAMETER(Sender);

    lbPackets.AddString(_T("ADV_IND RECEIVED"));
    lbPackets.AddString(_T("  AdvA: ") + IntToHex(AdvA));
    DumpPduHeader(PduHeader);
    DumpPayload(AdvData, AdvDataLen);
}

Frequently asked questions

What is a BLE sniffer?
A BLE sniffer, also known as a Bluetooth protocol analyzer, is a passive listening device that captures BLE packets sent over the air from nearby devices. It is useful for debugging and protocol reverse engineering.
How do I set up the CC2540 dongle for BLE sniffing?
Replace the default Cebal driver with WinUSB using the Zadig tool. The Bluetooth Framework can then use the dongle to capture BLE packets.
What capture modes does the BLE sniffer support?
Advertising mode captures packets on the primary advertising channels (37, 38, 39). Connection mode captures raw data exchanged during a connection on the remaining 37 channels.