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 scapyFür BLE-spezifische Features und die vollständige Bluetooth-Layer-Unterstützung empfiehlt sich die aktuelle Version:
pip install scapy>=2.5.0Auf 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.pyBLE-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
| Feature | Beschreibung |
|---|---|
| Paket-Dissection | Automatische Zerlegung in Protokollschichten (BTLE → ADV → AD-Struktur) |
| PCAP lesen/schreiben | rdpcap() zum Einlesen, wrpcap() zum Speichern |
| Schichten-Zugriff | Direkter Zugriff auf einzelne Schichten via pkt[LayerName] |
| Paket-Crafting | Eigene Pakete aus Schichten zusammensetzen (/-Operator) |
| Hex-Ausgabe | pkt.hexdump() und bytes(pkt).hex() für Byte-Inspektion |
| Feldinspektion | pkt.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 2cFortgeschrittene 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
| Problem | Ursache | Lösung |
|---|---|---|
PermissionError: [Errno 1] | Root-Rechte fehlen beim Live-Senden | sudo python3 skript.py oder sudo setcap cap_net_raw+ep $(which python3) |
AttributeError: BTLE_ADV | BLE-Layers nicht importiert | from scapy.layers.bluetooth4LE import BTLE_ADV explizit importieren |
| Leere Paketliste | PCAP-Format nicht kompatibel | Wireshark: File → Export Specified Packets → pcap (nicht pcapng) |
Layer BTLE not found | Ältere Scapy-Version | pip install --upgrade scapy auf Version 2.5.0+ |
| Falsche Byte-Reihenfolge | Little-/Big-Endian vertauscht | Wert 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_ADVzugänglich, Schichten viapkt[LayerName] - Manufacturer Specific Data hat Typ-Byte
0xFFund 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