bruce

package
v0.331.0 Latest Latest
Warning

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

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

Documentation

Overview

Package bruce interfaces with the Bruce pentesting firmware for ESP32-based boards over a USB-serial connection.

Bruce firmware (https://github.com/pr3y/Bruce, 5.4k★) is an open-source offensive-security firmware for ESP32, M5Stack Cardputer, M5StickC, T-Display-S3, ESP32-C5 (5 GHz), and Cheap Yellow Display boards. It supports Wi-Fi (2.4 GHz and 5 GHz on C5), BLE scanning/spam, RF analysis, IR replay, NFC via PN532, BadUSB via the USB HID stack, Zigbee/IEEE 802.15.4 passive scanning, and LoRa — all navigated through a text-mode serial menu.

Protocol

Bruce uses USB CDC-ACM at 115 200 baud, 8N1. The interface is a text-mode interactive menu: pressing Enter (sending "\n") selects the highlighted item or advances to the next prompt. Commands can also be sent as full strings followed by "\n". Bruce echoes each command line back and then produces line-buffered output. There is no fixed end-of-response sentinel: callers use a configurable idle/line-count heuristic (or a known termination token per command family).

Capability detection

Bruce prints a boot banner over serial that identifies the board model and firmware version, for example:

"Bruce 1.0.4 M5StackCardputer"
"Bruce 1.2 ESP32-C5 5G"

The ParseBanner function extracts Capabilities from that banner. HasFiveGHz is set when the banner contains "ESP32-C5" or "5G/5g". HasZigbee is true for banner strings that include "Zigbee". BoardType is the normalized lowercase board identifier.

References

Index

Constants

This section is empty.

Variables

View Source
var ErrCapabilityNotAvailable = fmt.Errorf("bruce: capability not available on this board")

ErrCapabilityNotAvailable is returned by capability-gated methods (e.g. Scan5GHz) when the connected board does not support that feature.

View Source
var ErrNotConnected = fmt.Errorf("bruce: not connected")

ErrNotConnected is returned when a Client method is called before Connect.

Functions

This section is empty.

Types

type AP

type AP struct {
	SSID    string `json:"ssid,omitempty"`
	BSSID   string `json:"bssid,omitempty"`
	RSSI    int    `json:"rssi,omitempty"`
	Channel int    `json:"channel,omitempty"`
	Band    string `json:"band,omitempty"` // "2.4GHz" or "5GHz"
	RawLine string `json:"raw,omitempty"`
}

AP is a discovered Wi-Fi access point.

func ParseAPList

func ParseAPList(raw, band string) []AP

ParseAPList parses Bruce AP-scan output into a slice of AP structs. band is annotated on each AP ("2.4GHz" or "5GHz"). Lines that cannot be parsed as APs are silently skipped; callers that need the raw text can use the AP.RawLine field for traceability.

type Capabilities

type Capabilities struct {
	// HasFiveGHz is true when the board supports 5 GHz Wi-Fi (ESP32-C5).
	HasFiveGHz bool `json:"has_5ghz"`

	// HasZigbee is true when the banner indicates Zigbee/IEEE 802.15.4 support.
	HasZigbee bool `json:"has_zigbee"`

	// HasLoRa is true when the banner or board type indicates LoRa support.
	HasLoRa bool `json:"has_lora"`

	// HasNFC is true when the board has a PN532 NFC module.
	HasNFC bool `json:"has_nfc"`

	// HasIR is true when the board has an IR blaster/receiver.
	HasIR bool `json:"has_ir"`

	// BoardType is the normalized lowercase board identifier, e.g.
	// "cardputer", "m5stickc", "t-display-s3", "cyd", "esp32-c5".
	BoardType string `json:"board_type,omitempty"`

	// FirmwareVersion is the semver string extracted from the boot banner.
	FirmwareVersion string `json:"firmware_version,omitempty"`
}

Capabilities is the feature set detected from the Bruce boot banner. All fields are derived from banner parsing — no runtime probing.

func ParseBanner

func ParseBanner(banner string) Capabilities

ParseBanner extracts Capabilities from the Bruce boot banner string.

Bruce banners observed in the wild (source: https://github.com/pr3y/Bruce/wiki/Supported-Boards):

"Bruce 1.0.4 M5StackCardputer"
"Bruce 1.2 ESP32-C5 5G"
"Bruce 1.1 M5StickCPlus2"
"Bruce 1.3 T-Display-S3"
"Bruce 1.0 CYD"      (Cheap Yellow Display)

Capability rules:

  • HasFiveGHz — banner contains "ESP32-C5" or "5G" (case-insensitive)
  • HasZigbee — banner contains "Zigbee" (case-insensitive)
  • HasLoRa — banner contains "LoRa" (case-insensitive)
  • HasNFC — banner contains "NFC" or "PN532" (case-insensitive)
  • HasIR — banner contains "IR" or any known IR-capable board name (Cardputer, M5Stick, T-Display have IR by default)
  • BoardType — normalized lowercase token derived from the board identifier
  • FirmwareVersion — semver string from the banner

type Capture

type Capture struct {
	Protocol string `json:"protocol,omitempty"`
	Code     string `json:"code,omitempty"`
	RawData  string `json:"raw_data,omitempty"`
}

Capture is the result of an IR receive operation.

func ParseCapture

func ParseCapture(raw string) Capture

ParseCapture parses Bruce IR receive output into a Capture struct.

type Client

type Client struct {
	// contains filtered or unexported fields
}

Client manages communication with Bruce firmware over a serial port. Construct with Connect (production) or NewWithPort (testing).

func Connect

func Connect(ctx context.Context, portName string, baudRate int) (*Client, error)

Connect opens portName at baudRate (default 115 200), drains any pending bytes, sends a newline to surface the Bruce menu banner, and reads back the version/board line to populate Capabilities.

ctx controls the initial banner-read deadline.

func NewWithPort

func NewWithPort(p Port) *Client

NewWithPort wires a Client around a caller-supplied Port. Used by tests that inject a fake serial backend; production code should call Connect.

func (*Client) BadUSBRun

func (c *Client) BadUSBRun(ctx context.Context, ducky string) error

BadUSBRun executes a Ducky Script payload from Bruce's SD card. ducky is the filename (without leading path) on the Bruce SD card. AUTHORIZED PENTEST / LAB USE ONLY.

Validates that the filename is non-empty and doesn't try to traverse the SD card root — the firmware accepts only a flat filename, but a model passing "../etc/payload.txt" or "/foo/x.txt" would silently fail to find the file at runtime.

func (*Client) Capabilities

func (c *Client) Capabilities() Capabilities

Capabilities returns the capability set populated during Connect. When the Client was constructed with NewWithPort the caller may set capabilities via [SetCapabilities] before calling any capability-gated method.

func (*Client) Close

func (c *Client) Close() error

Close releases the underlying serial port.

func (*Client) Deauth

func (c *Client) Deauth(ctx context.Context, bssid string, channel int) error

Deauth sends a deauthentication attack against the specified BSSID on the given channel. AUTHORIZED PENTEST / LAB USE ONLY.

Validates BSSID format and channel range before transport. The tool layer (internal/tools/bruce.go) already catches empty bssid / zero channel; this is defense-in-depth for direct callers and catches malformed MACs / out-of-range channels that the tool layer doesn't.

Capability gate (v0.198): 5 GHz channels (36-165) require HasFiveGHz. Boards without it can't tune the 5 GHz radio at all, so the firmware silently fails or emits an opaque error. Return ErrCapabilityNotAvailable up front instead so the operator gets the same diagnostic shape Scan5GHz emits.

func (*Client) EvilTwin

func (c *Client) EvilTwin(ctx context.Context, ssid, bssid string) error

EvilTwin starts a rogue access point cloning ssid/bssid. The fake AP uses the same SSID to lure clients. AUTHORIZED PENTEST / LAB USE ONLY.

Validates BSSID format and rejects empty SSID before transport.

func (*Client) IRReceive

func (c *Client) IRReceive(ctx context.Context) (Capture, error)

IRReceive opens the IR receiver and waits for a signal. Returns a Capture or ErrCapabilityNotAvailable when HasIR is false.

func (*Client) IRSend

func (c *Client) IRSend(ctx context.Context, protocol, code string) error

IRSend transmits an IR signal using the specified protocol and code string. Returns ErrCapabilityNotAvailable when HasIR is false.

Validates protocol + code non-empty before transport (defense in depth — the tool spec layer catches these too).

func (*Client) LoRaScan

func (c *Client) LoRaScan(ctx context.Context, freq float64) error

LoRaScan passively listens on freq (MHz) for LoRa packets. Returns ErrCapabilityNotAvailable when HasLoRa is false.

Validates freq against a coarse plausibility window (100-1000 MHz) that covers the four major LoRa bands (433.92 EU/AS, 868.1 EU, 915.0 US, plus 169 / 433 niche bands). Tighter regional gating is left to the firmware — we only catch obvious LLM mistakes like freq=0 or freq=2400 (mixing LoRa with WiFi).

func (*Client) NFCRead

func (c *Client) NFCRead(ctx context.Context) (NFCCard, error)

NFCRead reads an NFC card/tag via the attached PN532 module. Returns ErrCapabilityNotAvailable when HasNFC is false.

func (*Client) RawCommand

func (c *Client) RawCommand(ctx context.Context, cmd string) (string, error)

RawCommand sends cmd followed by '\n', reads until the port goes idle for one poll cycle or ctx expires, and returns the response as a trimmed string. This is the escape hatch for any Bruce command not yet wrapped by a typed method.

func (*Client) Scan5GHz

func (c *Client) Scan5GHz(ctx context.Context) ([]AP, error)

Scan5GHz triggers a 5 GHz Wi-Fi AP scan. Returns ErrCapabilityNotAvailable when the board does not have HasFiveGHz set.

func (*Client) ScanWiFi

func (c *Client) ScanWiFi(ctx context.Context) ([]AP, error)

ScanWiFi triggers a 2.4 GHz Wi-Fi AP scan and returns the parsed results. The scan runs for approximately scanDuration (or the firmware's built-in dwell time if shorter). Pass 0 to use defaultCmdTimeout.

func (*Client) SetCapabilities

func (c *Client) SetCapabilities(caps Capabilities)

SetCapabilities overwrites the stored capability set. Used by tests and by callers that want to hint capabilities discovered out-of-band.

func (*Client) ZigbeeScan

func (c *Client) ZigbeeScan(ctx context.Context) ([]ZigbeePeer, error)

ZigbeeScan performs a passive IEEE 802.15.4 scan and returns any overheard PAN beacons. Returns ErrCapabilityNotAvailable when HasZigbee is false.

type MockPort

type MockPort struct {
	// contains filtered or unexported fields
}

MockPort is an in-memory implementation of Port for unit tests.

Writes feed into a command dispatcher: each complete "\n"-terminated line is matched against a scripted-response table and the response bytes are made available for subsequent Read calls. Unscripted commands receive an empty response so callers don't block.

func NewMockPort

func NewMockPort() *MockPort

NewMockPort returns an initialised MockPort.

func (*MockPort) Close

func (m *MockPort) Close() error

func (*MockPort) LinesSeen

func (m *MockPort) LinesSeen() []string

LinesSeen returns a copy of every command line received so far.

func (*MockPort) Read

func (m *MockPort) Read(p []byte) (int, error)

func (*MockPort) Respond

func (m *MockPort) Respond(cmd, body string)

Respond registers a canned response body for cmd. The body is returned verbatim (with a trailing "\n" appended if absent) when the MockPort receives that command.

func (*MockPort) SetReadTimeout

func (m *MockPort) SetReadTimeout(d time.Duration) error

func (*MockPort) Write

func (m *MockPort) Write(p []byte) (int, error)

type NFCCard

type NFCCard struct {
	UID      string   `json:"uid,omitempty"`
	ATQ      string   `json:"atq,omitempty"`
	SAK      string   `json:"sak,omitempty"`
	RawLines []string `json:"raw_lines,omitempty"`
}

NFCCard holds the data read from an NFC tag via PN532.

func ParseNFCCard

func ParseNFCCard(raw string) NFCCard

ParseNFCCard parses Bruce NFC read output into an NFCCard struct.

type Port

type Port interface {
	io.Reader
	io.Writer
	io.Closer
	SetReadTimeout(time.Duration) error
}

Port is the subset of go.bug.st/serial.Port the package actually uses. Exported so tests can inject a fake backend via NewWithPort without opening a real device.

type ZigbeePeer

type ZigbeePeer struct {
	PANID     string `json:"pan_id,omitempty"`
	ShortAddr string `json:"short_addr,omitempty"`
	Channel   int    `json:"channel,omitempty"`
	RawLine   string `json:"raw,omitempty"`
}

ZigbeePeer is a device observed during an IEEE 802.15.4 passive scan.

func ParseZigbeeList

func ParseZigbeeList(raw string) []ZigbeePeer

ParseZigbeeList parses Bruce Zigbee/IEEE 802.15.4 scan output into a slice of ZigbeePeer structs.

Jump to

Keyboard shortcuts

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