Documentation
¶
Overview ¶
Package defense provides passive RF / BLE detection helpers used to surface adversarial activity nearby — the blue-team complement to PromptZero's offensive capability set.
The first capability is Wall-of-Flippers-style detection: classifying BLE advertisements that match the patterns produced by Flipper Zero devices, ESP32-Marauder-class boards running BLE-spam scripts, or any other Apple-Continuity-spam tooling. The detector is heuristic — false positives are documented per signature so an operator can adjust thresholds.
Architecture ¶
The package splits into:
- classifier.go pure-Go advertisement parser + signature matcher. No I/O, easy to unit-test against fixture payloads.
- scanner.go !darwin build — bridges tinygo.org/x/bluetooth BLE adapter to the classifier. Used on Linux (incl. when paired BLE access is available; WSL2 is excluded — see ble.go in internal/flipper/transport for the same rationale).
- scanner_darwin.go darwin stub returning a friendly error so cross-build CI compiles cleanly. Real macOS BLE requires CGO_ENABLED=1 + native build.
Why heuristics, not signatures ¶
There is no "Flipper Zero" identifier in BLE advertisements during a spam attack — the Flipper deliberately rotates MAC, randomises payload fields, and impersonates Apple/Microsoft/Samsung devices. Detection works by spotting *protocol violations* in the impersonated payloads: the Flipper's BLE-spam library emits Apple Action types and lengths outside the spec's normal range, malformed Microsoft Swift Pair payloads with truncated UUIDs, and so on. Each SignatureID documents the exact violation it matches and cites the upstream report.
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func IsKnownAttackOUI ¶ added in v0.22.0
IsKnownAttackOUI is the boolean form of LookupOUI for callers that only need the yes/no signal.
func LookupOUI ¶ added in v0.22.0
LookupOUI returns the descriptive label for a captured MAC address, or "" when the prefix isn't in the curated attack-hardware list. MAC may be in colon, dash, or unseparated form, lower or upper case. Anything malformed (too short, non-hex chars) returns "".
Used by the defensive classifier to enrich a Match struct's description with attribution evidence — operators see "BLE spam from Espressif (ESP32 …)" instead of just "BLE spam from AC:BC:DE:01:02:03".
Types ¶
type Advertisement ¶
type Advertisement struct {
// Address is the peer MAC in canonical uppercase colon notation.
// Empty is allowed — some platforms strip MACs from privacy-mode
// advertisements. Tests pass empty strings when checking the
// signature-only path.
Address string
// LocalName is the GAP local name (complete or shortened). Empty
// when not present in the advertisement.
LocalName string
// ServiceUUIDs is the list of advertised service UUIDs in 128-bit
// canonical lowercase form. Stack-emitted UUID strings.
ServiceUUIDs []string
// ManufacturerData maps the 16-bit manufacturer ID to the raw
// payload bytes. Apple is 0x004C; Microsoft 0x0006; Samsung
// 0x0075; Google Fast Pair uses service-data 0xFE2C, not
// manufacturer-data, so it goes through ServiceData below.
ManufacturerData map[uint16][]byte
// ServiceData maps a 16-bit service UUID to its raw bytes.
ServiceData map[uint16][]byte
// CapturedAt is when the scanner observed this packet.
CapturedAt time.Time
}
Advertisement is the minimal view of a BLE advertisement the classifier needs. The package's scanner (scanner.go) builds this from the platform adapter; tests construct it directly from fixture bytes.
type Match ¶
type Match struct {
Signature SignatureID
Description string
// SourceMAC is the captured peer MAC address in canonical
// uppercase colon notation (AA:BB:CC:DD:EE:FF). Empty when the
// caller's transport didn't surface the address.
SourceMAC string
// FirstSeen is when this advertiser was first observed, or the
// timestamp of this match if no tracker is involved.
FirstSeen time.Time
}
Match is one classification verdict against a captured advertisement.
func Classify ¶
func Classify(ad Advertisement) []Match
Classify runs every stateless signature against ad and returns the matched signatures. Call this from a scanner's per-advertisement callback. Order is deterministic (signatures are evaluated in the order declared above) so tests can rely on indexing.
Stateless signatures that need cross-advertisement context (MAC rotation, frequency thresholds) live on Tracker.Classify instead.
type SignatureID ¶
type SignatureID string
SignatureID identifies one BLE-advertisement classification rule.
const ( // SigAppleContinuitySpam matches an Apple Continuity payload whose // Action Type byte falls outside the documented set 0x05, 0x07, 0x09, // 0x0B, 0x0C, 0x0D, 0x0F, 0x10. The Flipper's spam library iterates // through arbitrary 0x00-0xFF action types and the Marauder/Bruce // equivalents do the same, producing payloads that real Apple devices // never emit. Reference: // https://github.com/k3yomi/Wall-of-Flippers — Apple-Continuity rule. SigAppleContinuitySpam SignatureID = "apple_continuity_spam" // SigSwiftPairMalformed matches a Microsoft Swift Pair payload whose // length is < 6 bytes or whose flags byte is reserved (≥0x05). The // genuine Swift Pair protocol uses ≥6-byte payloads and flags // 0x00-0x04. Flipper spam frequently emits 4-5 byte payloads. SigSwiftPairMalformed SignatureID = "swift_pair_malformed" // SigSamsungWatchSpam matches Samsung Wear/Watch advertisements whose // model-id bytes fall outside the published Samsung lookup ranges. // Reference: Wall-of-Flippers' samsung_models.json table. SigSamsungWatchSpam SignatureID = "samsung_watch_spam" // SigGoogleFastPairSpam matches Google Fast Pair advertisements with // the spam library's signature 3-byte model-id pattern (a single // repeating byte; genuine model IDs are 24-bit unique values). SigGoogleFastPairSpam SignatureID = "google_fast_pair_spam" // SigFlipperServiceUUID matches the Flipper Zero's own BLE serial // service UUID 0xFE60 advertised when a Flipper is in normal (not // spam) operation. False positives: any genuine Flipper Zero in // range. The detector logs but does not raise alerts unless the // operator opted in via DetectFlipperPresence=true. SigFlipperServiceUUID SignatureID = "flipper_service_uuid" // SigHighFrequencyMACRotation marks an advertiser whose source MAC // changed > the configured threshold within the rolling window. The // classifier needs caller-supplied state (RotationTracker) for this // check; raised by [Tracker.Classify], not the stateless [Classify]. SigHighFrequencyMACRotation SignatureID = "high_freq_mac_rotation" )
type Tracker ¶
type Tracker struct {
// RotationWindow is the rolling-window length used by the
// high-frequency-MAC-rotation detector. Zero defaults to 60s.
RotationWindow time.Duration
// RotationThreshold is the number of distinct MACs from the same
// approximate position (within RotationWindow) that triggers the
// rotation match. Zero defaults to 8.
RotationThreshold int
// contains filtered or unexported fields
}
Tracker accumulates per-MAC advertisement history so signatures that span multiple packets (high-frequency MAC rotation, payload churn) can be evaluated. Safe for concurrent use.
Operators wire one Tracker per scan session, feed every observed advertisement through Classify, and read accumulated matches with Snapshot at the end (or on-the-fly via the per-call return value).