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 ¶
- Firmware source: https://github.com/pr3y/Bruce
- Wiki: https://github.com/pr3y/Bruce/wiki
- Supported boards: https://github.com/pr3y/Bruce/wiki/Supported-Boards
Index ¶
- Variables
- type AP
- type Capabilities
- type Capture
- type Client
- func (c *Client) BadUSBRun(ctx context.Context, ducky string) error
- func (c *Client) Capabilities() Capabilities
- func (c *Client) Close() error
- func (c *Client) Deauth(ctx context.Context, bssid string, channel int) error
- func (c *Client) EvilTwin(ctx context.Context, ssid, bssid string) error
- func (c *Client) IRReceive(ctx context.Context) (Capture, error)
- func (c *Client) IRSend(ctx context.Context, protocol, code string) error
- func (c *Client) LoRaScan(ctx context.Context, freq float64) error
- func (c *Client) NFCRead(ctx context.Context) (NFCCard, error)
- func (c *Client) RawCommand(ctx context.Context, cmd string) (string, error)
- func (c *Client) Scan5GHz(ctx context.Context) ([]AP, error)
- func (c *Client) ScanWiFi(ctx context.Context) ([]AP, error)
- func (c *Client) SetCapabilities(caps Capabilities)
- func (c *Client) ZigbeeScan(ctx context.Context) ([]ZigbeePeer, error)
- type MockPort
- type NFCCard
- type Port
- type ZigbeePeer
Constants ¶
This section is empty.
Variables ¶
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.
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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) Deauth ¶
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 ¶
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 ¶
IRReceive opens the IR receiver and waits for a signal. Returns a Capture or ErrCapabilityNotAvailable when HasIR is false.
func (*Client) IRSend ¶
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 ¶
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 ¶
NFCRead reads an NFC card/tag via the attached PN532 module. Returns ErrCapabilityNotAvailable when HasNFC is false.
func (*Client) RawCommand ¶
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 ¶
Scan5GHz triggers a 5 GHz Wi-Fi AP scan. Returns ErrCapabilityNotAvailable when the board does not have HasFiveGHz set.
func (*Client) ScanWiFi ¶
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.
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 ¶
ParseNFCCard parses Bruce NFC read output into an NFCCard struct.
type Port ¶
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.