defense

package
v0.167.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 11, 2026 License: AGPL-3.0 Imports: 5 Imported by: 0

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

func IsKnownAttackOUI(mac string) bool

IsKnownAttackOUI is the boolean form of LookupOUI for callers that only need the yes/no signal.

func LookupOUI added in v0.22.0

func LookupOUI(mac string) string

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 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).

func (*Tracker) Classify

func (t *Tracker) Classify(ad Advertisement) []Match

Classify runs the stateless Classify plus the stateful rotation detector. Returns every match raised by either.

func (*Tracker) Reset

func (t *Tracker) Reset()

Reset clears all accumulated state.

func (*Tracker) Snapshot

func (t *Tracker) Snapshot() map[string][]Match

Snapshot returns every match observed since this Tracker was created, keyed by source MAC. The returned map is a copy — callers may mutate it freely.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL