Scapy

BLE-Pakete analysieren und eigene Pakete konstruieren

Lernziel

Nach dieser Seite kannst du Scapy installieren, PCAP-Dateien aus BLE-Captures einlesen und Manufacturer-Specific-Data-Felder aus Advertisement-Paketen manuell dekodieren. Du verstehst, wie Scapy Pakete in Schichten zerlegt und wie du eigene Pakete für Tests konstruierst.

Was ist Scapy?

Scapy ist eine Python-Bibliothek zur Paketmanipulation. Sie erlaubt das Lesen, Analysieren, Modifizieren und Erstellen von Netzwerk- und Funkpaketen vollständig in Python — ohne auf Wireshark oder andere GUI-Tools angewiesen zu sein. Scapy unterstützt neben Ethernet und IP auch Bluetooth-Layer, darunter BLE-Advertisement-Strukturen.

Das macht Scapy besonders nützlich, wenn du:

  • PCAP-Dateien automatisiert verarbeiten willst (z. B. alle Manufacturer-Specific-Data-Felder aus hundert Paketen extrahieren)
  • eigene BLE-Pakete für Testzwecke konstruieren willst
  • statistische Auswertungen über aufgezeichneten Traffic durchführen willst

Scapy als Analyse-Ergänzung

Scapy ersetzt Wireshark nicht — es ergänzt es. Während Wireshark für visuelle Protokollanalyse und interaktives Filtern ideal ist, glänzt Scapy bei skriptbasierter Massenverarbeitung und automatisierten Auswertungen.

Installation

pip install scapy

Für BLE-spezifische Features und die vollständige Bluetooth-Layer-Unterstützung empfiehlt sich die aktuelle Version:

pip install scapy>=2.5.0

Auf Linux benötigt Scapy für das direkte Senden und Empfangen von Paketen Root-Rechte. Für das reine Einlesen von PCAP-Dateien sind keine besonderen Berechtigungen notwendig:

# PCAP-Analyse: keine Root-Rechte nötig
python3 analyse.py

# Direktes Senden/Empfangen: Root erforderlich
sudo python3 send_packet.py

BLE-Beispiel: Körperwaage-PCAP analysieren

Die Lebenlang-Körperwaage sendet Messdaten in BLE-Advertisements als Manufacturer Specific Data mit Company-ID 0x05C0. Das folgende Beispiel liest eine aufgezeichnete PCAP-Datei ein und dekodiert die Nutzdaten.

PCAP einlesen und filtern

from scapy.all import rdpcap
from scapy.layers.bluetooth4LE import BTLE_ADV

def load_ble_advertisements(pcap_path: str) -> list:
    """
    PCAP-Datei laden und alle BLE-Advertisement-Pakete herausfiltern.
    Gibt eine Liste von Scapy-Paket-Objekten zurück.
    """
    packets = rdpcap(pcap_path)
    advertisements = [p for p in packets if p.haslayer(BTLE_ADV)]
    return advertisements

# Verwendung
advs = load_ble_advertisements("koerperwaage_capture.pcap")
print(f"Gefundene Advertisements: {len(advs)}")

Manufacturer-Specific-Data dekodieren

Manufacturer Specific Data hat immer die Typ-Kennung 0xFF im AD-Struktur. Die ersten zwei Bytes des Payloads sind die Company-ID (Little-Endian). Für die Lebenlang-Waage ist das 0x05C0 — also die Bytes C0 05 im Paket.

def extract_manufacturer_data(packet) -> bytes | None:
    """
    Manufacturer Specific Data (Typ 0xFF) aus einem BLE-Advertisement extrahieren.
    Gibt den rohen Payload (ohne Typ-Byte) zurück, oder None wenn nicht vorhanden.
    """
    if not packet.haslayer(BTLE_ADV):
        return None

    # Rohe Advertising-Daten aus dem Paket lesen
    raw_data = bytes(packet[BTLE_ADV].payload)

    i = 0
    while i < len(raw_data):
        if i + 1 >= len(raw_data):
            break
        length = raw_data[i]
        if length == 0:
            break
        ad_type = raw_data[i + 1]
        ad_value = raw_data[i + 2 : i + 1 + length]

        if ad_type == 0xFF:  # Manufacturer Specific Data
            return ad_value
        i += 1 + length

    return None

def decode_scale_payload(mfr_data: bytes) -> dict:
    """
    Lebenlang-Waage: Manufacturer-Data-Payload dekodieren.
    Company-ID: 0x05C0 (Bytes [0:2] = C0 05, Little-Endian)
    Offset 2–3: Gewicht in 0,01-kg-Einheiten (Big-Endian)
    Offset 4–5: Körperimpedanz in 0,1-Ohm-Einheiten (Big-Endian)
    """
    if len(mfr_data) < 6:
        return {}

    company_id = (mfr_data[1] << 8) | mfr_data[0]  # Little-Endian
    if company_id != 0x05C0:
        return {}

    weight_raw    = (mfr_data[2] << 8) | mfr_data[3]
    impedance_raw = (mfr_data[4] << 8) | mfr_data[5]

    return {
        "company_id":    hex(company_id),
        "weight_kg":     weight_raw / 100.0,
        "impedance_ohm": impedance_raw / 10.0,
    }

# Alle Waagen-Pakete auswerten
advs = load_ble_advertisements("koerperwaage_capture.pcap")
for pkt in advs:
    mfr_data = extract_manufacturer_data(pkt)
    if mfr_data:
        result = decode_scale_payload(mfr_data)
        if result:
            print(f"Gewicht: {result['weight_kg']:.2f} kg | Impedanz: {result['impedance_ohm']:.1f} Ohm")

Byte-Reihenfolge beachten

BLE-Protokolle verwenden häufig Little-Endian für die Company-ID, aber Big-Endian für Messwerte — überprüfe die Byte-Reihenfolge immer anhand der Wireshark-Analyse oder der App-Quellcode-Befunde aus Phase 1.

Features im Überblick

FeatureBeschreibung
Paket-DissectionAutomatische Zerlegung in Protokollschichten (BTLE → ADV → AD-Struktur)
PCAP lesen/schreibenrdpcap() zum Einlesen, wrpcap() zum Speichern
Schichten-ZugriffDirekter Zugriff auf einzelne Schichten via pkt[LayerName]
Paket-CraftingEigene Pakete aus Schichten zusammensetzen (/-Operator)
Hex-Ausgabepkt.hexdump() und bytes(pkt).hex() für Byte-Inspektion
Feldinspektionpkt.show() zeigt alle Felder mit Werten

Eigene BLE-Pakete konstruieren

Scapy erlaubt das Zusammensetzen von Paketen aus einzelnen Schichten mit dem /-Operator. Das ist nützlich, wenn du Test-Advertisements oder eigene Protokollframes erstellen willst:

from scapy.layers.bluetooth4LE import BTLE, BTLE_ADV, BTLE_ADV_IND

# Manufacturer Specific Data: Company-ID 0x05C0, Testdaten
mfr_payload = bytes([
    0xC0, 0x05,  # Company-ID 0x05C0 (Little-Endian)
    0x08, 0xFC,  # Gewicht: 0x08FC = 2300 → 23.00 kg (Testwert)
    0x01, 0x2C,  # Impedanz: 0x012C = 300 → 30.0 Ohm (Testwert)
])

# AD-Struktur: [length, type=0xFF, payload]
ad_structure = bytes([len(mfr_payload) + 1, 0xFF]) + mfr_payload

print(f"AD-Struktur: {ad_structure.hex(' ')}")
# Ausgabe: 07 ff c0 05 08 fc 01 2c

Fortgeschrittene Anwendungen

PCAP-Statistiken: Wiederholte Payloads erkennen

Bei Geräten ohne Replay-Schutz lässt sich mit Scapy schnell feststellen, ob identische Payloads mehrfach gesendet werden — ein Indiz für fehlende Nonce-Verwendung:

from collections import Counter
from scapy.all import rdpcap
from scapy.layers.bluetooth4LE import BTLE_ADV

def find_repeated_payloads(pcap_path: str) -> dict:
    """Identische Advertisement-Payloads zählen."""
    packets = rdpcap(pcap_path)
    payloads = []

    for pkt in packets:
        if pkt.haslayer(BTLE_ADV):
            raw = bytes(pkt[BTLE_ADV].payload)
            payloads.append(raw.hex())

    counts = Counter(payloads)
    return {payload: count for payload, count in counts.items() if count > 1}

repeats = find_repeated_payloads("capture.pcap")
for payload, count in sorted(repeats.items(), key=lambda x: -x[1]):
    print(f"  {count}x  {payload}")

Integration mit blatann

Scapy und blatann können kombiniert werden: blatann für die Live-Erfassung, Scapy für die Offline-Auswertung der gespeicherten PCAP-Dateien.

# Workflow: blatann aufzeichnet → Wireshark speichert als PCAP → Scapy wertet aus
# Kein direktes API-Bindeglied nötig — das PCAP-Format ist der gemeinsame Standard.

from scapy.all import rdpcap, wrpcap

def filter_by_company_id(pcap_in: str, pcap_out: str, company_id: int) -> int:
    """
    Nur Pakete eines bestimmten Herstellers in neue PCAP-Datei exportieren.
    Gibt die Anzahl gefundener Pakete zurück.
    """
    packets = rdpcap(pcap_in)
    matching = []

    for pkt in packets:
        mfr_data = extract_manufacturer_data(pkt)
        if mfr_data and len(mfr_data) >= 2:
            found_id = (mfr_data[1] << 8) | mfr_data[0]
            if found_id == company_id:
                matching.append(pkt)

    if matching:
        wrpcap(pcap_out, matching)
    return len(matching)

count = filter_by_company_id("full_capture.pcap", "scale_only.pcap", 0x05C0)
print(f"{count} Waagen-Pakete exportiert")

Häufige Probleme

ProblemUrsacheLösung
PermissionError: [Errno 1]Root-Rechte fehlen beim Live-Sendensudo python3 skript.py oder sudo setcap cap_net_raw+ep $(which python3)
AttributeError: BTLE_ADVBLE-Layers nicht importiertfrom scapy.layers.bluetooth4LE import BTLE_ADV explizit importieren
Leere PaketlistePCAP-Format nicht kompatibelWireshark: File → Export Specified Packets → pcap (nicht pcapng)
Layer BTLE not foundÄltere Scapy-Versionpip install --upgrade scapy auf Version 2.5.0+
Falsche Byte-ReihenfolgeLittle-/Big-Endian vertauschtWert mit Wireshark-Rohansicht (I in Wireshark) gegenprüfen

PCAP-Format vs. PCAPng

Wireshark speichert standardmäßig im neueren PCAPng-Format (.pcapng). Scapy liest grundsätzlich beide, aber manche BLE-Layer-Dissectoren funktionieren zuverlässiger mit dem klassischen PCAP-Format. Beim Export in Wireshark: File → Export Specified Packets → Format: pcap wählen.

Zusammenfassung

  • Scapy liest PCAP-Dateien mit rdpcap() und gibt eine Liste von Paket-Objekten zurück
  • BLE-Advertisement-Pakete sind über BTLE_ADV zugänglich, Schichten via pkt[LayerName]
  • Manufacturer Specific Data hat Typ-Byte 0xFF und beginnt mit der Company-ID in Little-Endian
  • Für die Körperwaage (0x05C0) stecken Gewicht (Offset 2–3) und Impedanz (Offset 4–5) im Payload
  • Eigene Pakete baut man mit dem /-Operator aus einzelnen Scapy-Schichten zusammen
  • Root-Rechte sind nur beim Senden nötig, nicht beim Lesen von PCAP-Dateien