Bose QC Ultra 2

April 13, 2026 · View on GitHub

TL;DR

We can control the headphones from Linux without the Bose app, Bose account, or cloud.

The AudioModes function block (block 31) accepts the START operator without authentication. This lets us switch ANC modes, read battery, read all device info, and more — all over a direct Bluetooth RFCOMM connection.

Bose locked down SET/SETGET operators behind cloud-mediated ECDH authentication, but left the START operator on AudioModes wide open. This is the same operator the app uses to change modes in real time.

Device Info

  • Product: Bose QC Ultra 2 HP (codename "wolverine")
  • Platform: OTG-QCC-384 (Qualcomm QCC chipset)
  • Firmware: 8.2.20+g34cf029
  • BT MAC: 68:F2:1F:XX:XX:XX
  • Serial: 085958TXXXXXXXXXX
  • Custom name: "Fargo"
  • Product ID: 0x4082, Variant: 0x01

BMAP Protocol

  • Version: 1.1.0
  • Transport: Bluetooth SPP over RFCOMM channel 2
  • Packet format: [fblock_id, function_id, flags, payload_length, ...payload]
  • Flags byte: (device_id << 6) | (port_num << 4) | (operator & 0x0F)
  • Operators: SET=0, GET=1, SET_GET=2, STATUS=3, ERROR=4, START=5, RESULT=6, PROCESSING=7

What Works Without Authentication

Reading (GET operator — all blocks)

Everything can be read without auth:

  • Battery level, firmware version, serial number, product name
  • Current ANC mode, EQ settings, connected devices
  • All device info across all function blocks

Writing — AudioModes (Block 31, START operator)

This is the breakthrough. The START operator on block 31 is unauthenticated.

Changing ANC/Audio Mode

Packet: [31, 3, 0x05, 2, MODE_INDEX, VOICE_PROMPT]
  - Block 31 (AudioModes), Function 3 (CurrentMode), Operator START (5)
  - MODE_INDEX: which mode to activate (see table below)
  - VOICE_PROMPT: 0=silent, 1=play voice prompt
  - Response: RESULT with the new mode index on success

Available Audio Modes

IndexNameDescriptionConfig byte
0QuietFull active noise cancellation0x01
1AwareTransparency / passthrough0x02
2ImmersionSpatial audio immersive0x22
3CinemaSpatial audio cinema0x24
4HomeCustom home profile0x0a
5NoneEmpty/custom slot0x00
6NoneEmpty/custom slot0x00
7NoneEmpty/custom slot0x00

Other Unauthenticated START Commands (Block 31)

FunctionNameNotes
[31.1]GetAllReturns PROCESSING — triggers full state dump
[31.3]CurrentModeMode switching — confirmed working
[31.6]ModeConfigReturns all mode configs as STATUS messages
[31.9]ResetAccepts START (InvalidData with empty payload)

Unauthenticated START in Other Blocks

FunctionNameStatusNotes
[7.1]Control.GetAllPROCESSINGTriggers control state dump
[7.4]Control.PowerRESULT0=power off, 1=power on
[4.1]DevMgmt.ConnectInvalidDataNeeds device MAC — may initiate BT connection
[4.8]DevMgmt.PairingModeRESULT0x01=enable, 0x00=disable
[4.12]DevMgmt.RoutingRESULTSwitch active multipoint device (see below)
[5.1]AudioMgmt.SourceGET onlyQuery active audio source (see below)
[5.3]AudioMgmt.ControlInvalidDataNeeds payload — play/pause/skip (see AudioControlValue)
[18.19]ValidatedDeviceIdentityKeypairPROCESSINGAccepts public key for auth flow

AudioModes SETGET — Full Config Control (No Auth!)

BREAKTHROUGH #2: SETGET on AudioModes block 31 is completely unauthenticated. Preset modes (0-4) are firmware-locked (return Runtime error 8), but custom mode slots (5-10) accept full configuration changes via SETGET. Combined with CurrentMode START to switch to the custom mode, this gives us complete control over CNC level, spatial audio, wind block, and ANC — all without any auth.

FunctionOperatorStatusNotes
[31.6] ModeConfigSETGETWORKS on modes 5-10Full config: CNC, spatial, wind, ANC
[31.6] ModeConfigSETError 5 (auth)SET is auth-gated, but SETGET isn't
[31.8] FavoritesSETGETWORKSSet favorite mode indices
[31.8] FavoritesSETError 5 (auth)
[31.4] DefaultModeSETGETTimeout (may work)Set power-on mode
[31.4] DefaultModeSETError 5 (auth)
[31.3] CurrentModeSETGETError 5 (auth)But START works for switching
ModeConfig SETGET Payload Format (40 bytes)

Built by FBlockAudioModesKt.createAudioModesConfigSetGetPayload():

Offset  Size  Field              Values
0       1     modeIndex          5-10 (custom slots only)
1-2     2     voicePrompt        (byte1, byte2) — see AudioModesPrompt enum
3-34    32    modeName           UTF-8, null-padded to 32 bytes
35      1     cncLevel           0-10 (noise cancellation intensity)
36      1     autoCNCEnabled     0=off, 1=on
37      1     spatialAudioType   0=off, 1=fixedToRoom, 2=fixedToHead
38      1     windBlockEnabled   0=off, 1=on
39      1     ancToggleEnabled   0=off, 1=on
ModeConfig STATUS Response Format (48 bytes)

The firmware adds 3 flag bytes and extra config fields:

Offset  Size  Field              Notes
0       1     modeIndex
1-2     2     voicePrompt
3-5     3     flags              [3]=isUserEditable, [4]=isConfigured, [5]=?
6-37    32    modeName
38-39   2     ?                  Always 0 for custom modes
40-41   2     ?                  Mode-type specific (0x1d for custom modes)
42      1     cncLevel           0-10
43      1     autoCNCEnabled
44      1     spatialAudioType   0=off, 1=room, 2=head
45      1     windBlockEnabled
46      1     ?
47      1     ancToggleEnabled

Preset modes have flags[3]=0x00 (locked); custom/user modes have flags[3]=0x01 (writable).

ModeNameEditableConfiguredNotes
0QuietNoNoFirmware preset
1AwareNoNoFirmware preset
2ImmersionNoNoFirmware preset
3CinemaNoNoFirmware preset
4HomeYesYesUser-created in app
5-10None/CustomYesNoEmpty slots, fully configurable

Mode 4 (Home) is editable because it was created by the user via the app. Modes 5-10 are empty slots that accept full configuration. The cloud auth the app uses is likely for syncing profiles across devices, not for writing to the headphone firmware — SETGET bypasses it entirely.

The real CNC path: [31.10] AudioModesSettingsConfig

The ModeConfig-slot approach works for configuring stored profiles, but for live CNC/spatial/wind/ANC control, use [31.10] AudioModesSettingsConfig SETGET directly. This is the same register the Bose app writes to, fully unauthenticated, and applies immediately without mode switching.

# [31.10] SETGET, 5-byte payload: [cnc, autoCNC, spatial, wind, anc]
# cnc: 0-10 (INVERTED: 0=max ANC, 10=most ambient)
# autoCNC: 0 only (1 is rejected with Runtime error 8)
# spatial: 0=off, 1=room, 2=head
# wind: 0=off, 1=on
# anc: 0=off, 1=on
send(bmap_packet(31, 10, OP_SETGET, [5, 0, 0, 0, 1]))
# ↑ CNC level 5, autoCNC off, spatial off, wind block off, ANC on

Critical audibility interaction: the CNC level only produces an audible difference when anc=on AND wind=off. Wind Block masks the CNC DSP path (probably to prevent wind compression from fighting ANC). With wind on, CNC 0 and CNC 10 sound identical.

Also: autoCNCEnabled=1 causes Runtime error 8 — firmware rejects it. Only manual CNC (auto_cnc=0) is allowed.

Mode Config Details (raw data)

Mode 0 (Quiet):     000001000001 "Quiet"     ...00000001
Mode 1 (Aware):     0100020000014 "Aware"    ...020a0000000001
Mode 2 (Immersion): 020022000001 "Immersion" ...0002000001
Mode 3 (Cinema):    0300240000004 "Cinema"   ...0001000001
Mode 4 (Home):      04000a010100 "Home"      ...1d000000010001
Mode 5 (None):      0500000100004 "None"     ...1d0a0000010001
Mode 6 (None):      0600000100004 "None"     ...1d0a0000010001
Mode 7 (None):      0700000100004 "None"     ...1d0a0000010001

Writing — Settings Block SETGET (Also Unauthenticated!)

BREAKTHROUGH #3: The SETGET operator on the Settings block [1.x] is ALSO unauthenticated! SET and START are auth-gated (error 5), but SETGET bypasses auth.

Settings SETGET Summary

All of these use [1, FUNC, 0x02, LEN, ...PAYLOAD] (block 1, operator SETGET):

FuncNamePayload (SETGET)Notes
[1.2]ProductNameUTF-8 string (no flag byte)Free-form device name
[1.3]VoicePrompts1 byte: (enabled<<5)|langSee language table below
[1.5]CNCAUTH REQUIRED (use [31.6] instead)
[1.7]EQ/Range2 bytes: [value, bandId]value=-10..+10, band=0/1/2
[1.9]Buttons3 bytes: [btnId, evt, mode]See button tables below
[1.10]Multipoint1 byte: 0=off, 1=on
[1.11]Sidetone2 bytes: [persist, mode]mode: 0=off,1=high,2=med,3=low
[1.24]AutoPlayPause1 byte: 0=off, 1=onPause on ear removal
[1.27]AutoAnswer1 byte: 0=off, 1=onAuto-answer calls

EQ Details [1.7]

SETGET payload: [VALUE, BAND_ID]
  - VALUE: -10 to +10 as signed byte (0xf6 to 0x0a)
  - BAND_ID: 0=Bass, 1=Mid, 2=Treble

GET response: 3× 4-byte groups [min, max, current, bandId]
  - e.g. f60a0500 f60a0001 f60af702 = bass=+5, mid=0, treble=-9

Voice Prompt Languages [1.3]

IDLanguageIDLanguage
0UK English12Hebrew
1US English13Turkish
2French14Dutch
3Italian15Japanese
4German16Cantonese
5EU Spanish17Arabic
6MX Spanish18Swedish
7BR Portuguese19Danish
8Mandarin20Norwegian
9Korean21Finnish
10Russian22Hindi
11Polish

Button Remapping [1.9]

GET Response Format (7+ bytes)
[0]   buttonId          — which physical button
[1]   buttonEventType   — what gesture triggers it
[2]   configuredAction  — current assigned action
[3:7] supportedMask     — bitmask of supported ActionButtonMode values
[7:]  unavailableMask   — bitmask of unavailable modes (optional)
SETGET Payload (3 bytes)
[buttonId, buttonEventType, newActionMode]
Button IDs (ConfigurableButtonId)
IDName
0x00DistalCnc (CNC button)
0x01Reserved
0x02VPA (voice assistant)
0x03RightShortcut
0x04LeftShortcut
0x80Shortcut (QC Ultra 2 programmable button)
Button Event Types
IDGesture
0Reserved
1Rising edge
2Falling edge
3Short press
4Single press
5Press and hold
6Double press
7Double press and hold
8Triple press
9Long press
10Very long press
11Very very long press
12Very very very long press
Action Button Modes
IDActionIDAction
0NotConfigured11TrackBack
1VPA (voice assistant)12FetchNotifications
2ANC cycle13WindMode
3BatteryLevel14Disabled
4PlayPause15ClientInteraction
5IncreaseCNC16SpotifyGo
6DecreaseCNC17ModesCarousel
7ToggleWakeWord19SpatialAudioMode
8SwitchDevice20LineInSwitch
9ConversationMode21Linking
10TrackForward
Current Config (QC Ultra 2)
Button 0x80 (Shortcut), long_press → Disabled
Supported actions: SwitchDevice, TrackBack, unknown(22), unknown(25)
Raw: 80090e00094002

Note: Button remapping via SETGET is confirmed to accept payloads (tested echo-back) but has not been tested with actual mode changes yet. The supported action bitmask indicates only a few actions are available for this button/event combo. To remap: bose raw "01 09 02 03 80 09 08" (Shortcut, long_press → SwitchDevice)

What Requires Authentication (Blocked)

SET and START operators on most blocks return error 5 (OpNotSupp) without completing cloud-mediated ECDH authentication. However, SETGET is often unauthenticated — Bose only gated SET and START.

Confirmed auth-blocked (SET/START only):

  • Settings.ProductName [1.2] — device name (SET blocked, SETGET untested)
  • Settings.Multipoint [1.10] — multipoint toggle
  • Settings.StandbyTimer [1.4] — auto-off timer
  • AudioManagement.Control [5.3] — play/pause/skip (SET/START blocked)

Confirmed SETGET works without auth:

  • Settings.EQ [1.7] — full equalizer control (-10 to +10, 3 bands)
  • AudioModes.ModeConfig [31.6] — CNC, spatial, wind, ANC on custom modes
  • AudioModes.Favorites [31.8] — favorite mode selection

RFCOMM Channels

ChannelPurpose
1SPP (connection refused)
2BMAP control — primary protocol channel
8Refused
14Status beacon (sends ff5502... periodically)
22Diagnostic/log stream (not BMAP, dumps data regardless of input)
24Silent (purpose unknown)

USB Interface

The headphones expose two USB HID interfaces when connected via USB-C:

InterfaceClassDevicePurpose
0Vendor HIDhidraw9BMAP control channel (bidirectional)
1Consumer HIDhidraw10Media keys (play/pause/volume)
2Audio ControlUSB audio control
3Audio StreamingUSB audio data (mic, isochronous)

USB HID Report IDs (Interface 0, Vendor Specific 0xFF00)

Report IDDirectionSizeLikely Purpose
0x09IN126 bytesSmall BMAP responses
0x0aFEATURE62 bytesFeature report (config?)
0x0cOUT1022 bytesBMAP commands (main)
0x0dIN259 bytesBMAP responses (main)
0x0eOUT512 bytesBMAP commands (alt)
0x0fIN512 bytesBMAP responses (alt)
0x10OUT675 bytesBMAP commands (large)
0x11IN675 bytesBMAP responses (large)

USB Status

Sending BMAP packets in report 0x0c gets a response on 0x0d, but always the same 3 bytes: 04 01 05. This appears to be a "not initialized" error — the USB BMAP channel likely requires a handshake sequence before accepting commands.

Over Bluetooth RFCOMM, BMAP is immediately active. The USB HID channel appears to be firmware update only, not general BMAP control:

  • The Bose Updater (Windows, Qt/C++ app) uses a modified HIDAPI library to communicate over USB HID, downloads firmware from AWS S3, and pushes it through HID reports
  • The 04 01 05 response to all our BMAP attempts is likely "not in DFU mode"
  • The updater probably sends a special command to enter firmware update mode
  • No desktop Bose app exists for general headphone control — only the updater

Conclusion: USB is for firmware updates. Use Bluetooth RFCOMM for control.

USB Device Info

Vendor:  0x05a7 (Bose Corporation)
Product: 0x4082 (Bose QC Ultra 2 HP)
Speed:   Full Speed (12 Mbps)
Serial:  T5333020XXXXXXXXXXXXX

Function Block Map

BlockNameFunctions found
0ProductInfo0,1,2,3,5,6,7,12,15,17,23
1Settings0,2,3,5,7,9,10,11,12,24,27
2Status0,2,5,16,21
3FirmwareUpdate0,1,4,6,7,15,16
4DeviceManagement0,1,4,8,9,14,18
5AudioManagement0,1,3,4,5,7,13,17
6CallManagement0
7Control0,1,4
8Debug7,8
9Notification0,2
18Authentication0,1,9,11,12,13,24
31AudioModes0,2,3,4,8,10,11

Settings Functions (Block 1)

FuncNameRead valueNotes
0FblockInfo"1.1.0"
2ProductName"Fargo"
3VoicePrompts41000081020000
5CNC0b0003[numSteps=11, step=0, flags=3]
7RangeControl/EQf60a0000f60a0001f60a00023-band EQ, all at 0
9Buttons80090e00094002
10Multipoint07
11Sidetone01020f
12SetupComplete01
24AutoPlayPause01
27AutoAnswer01

Status Functions (Block 2)

FuncNameRead valueDecoded
2BatteryLevel50ffff000x50 = 80% battery

CNC (Noise Cancellation) Details

  • Read: GET [1.5] returns [numSteps, currentStep, flags]
    • numSteps: total steps (11 on this device = 0-10)
    • currentStep: current level (0=min, 10=max)
    • flags: bit0=isEnabled, bit1=!userEnableDisable
  • Write: SetGet [1.5] with payload [step, enabled?1:0]
    • Requires authentication — returns OpNotSupp error 5 without auth

EQ Details

  • 3-band equalizer stored in [1.7]
  • Format: 3x 4-byte groups [f60a, VALUE, BAND_INDEX]
  • Values are signed bytes (f7=-9, 00=center, 0a=+10)
  • Band 0=Bass, 1=Mid, 2=Treble

Authentication System (Block 18)

Overview

Cloud-mediated ECDH P-384 challenge-response. The headphones require a signature from Bose's cloud servers (nadc.data.api.bose.io) before granting SET/SETGET privileges. The app acts as a proxy between headphones and cloud.

Auth Capabilities (from [18.1] bitmask 0339083e07)

Supported: FblockInfo, GetAll, CondensedChallenge, OtpKeyType, ProductName, PlatformName, ValidatedDeviceIdentityKeypair, PropagateProductIrk, NoTokenChallenge, ProductToCloudChallenge, ProductToCloudChallengeVerifyResponse, CloudToProductChallenge, GoogleFeatureKeys, GoogleFeatureKeyData

Auth Device Info

  • Device ECDH public key at [18.9]: P-384 PEM format
  • Product name [18.12]: "wolverine"
  • Platform [18.13]: "OTG-QCC-384"
  • OTP key type [18.11]: 3
  • Product IRK [18.24]: 3713b952XXXXXXXXXXXXXXXXXXXXXXXX

Hypothesized Auth Flow

  1. App generates ephemeral ECDH keypair
  2. [18.19] START → App sends public key → headphones return PROCESSING
  3. [18.27] START → Headphones generate challenge → app forwards to Bose cloud
  4. Bose cloud signs the challenge
  5. [18.28] → App sends cloud response back to headphones
  6. [18.29] → Cloud-to-product verification
  7. Headphones grant SET/SETGET privileges for this session

Cloud API

  • Primary API: nadc.data.api.bose.io (uses QUIC/HTTP3, falls back to HTTPS)
  • Identity: id.api.bose.io/id-idp-mgr-core/
  • Services: services.api.bose.io (Apigee gateway)
  • Config: nadc-config.data.api.bose.io
  • Firmware: ota.cdn.bose.io, updates-framingham-prod.smartproducts.bose.io
  • API key system called "Galapagos" — key fetched from remote config
  • App has certificate pinning — rejects user-installed CA certs

Why Auth Bypass Works

Bose protected SET (operator 0) and SET_GET (operator 2) behind cloud auth. But START (operator 5) on the AudioModes block was left unprotected. The app uses START to change modes in real time (it's the "instant switch" path). SET_GET is used for persistent config changes. This distinction means we can control the headphones in real time but can't change saved configuration.

Audio Source & Device Routing

AudioManagement Source [5.1] — Query Active Source

GET-only function. Response payload:

[0-1]  Supported sources (bitset, 2 bytes)
[2]    Active source type: 0=NONE, 1=BLUETOOTH, 2=AUXILIARY
[3+]   Source-specific data (BLUETOOTH: 6 bytes MAC address)

Source types from AudioControlSourceType.java:

  • NONE (0x00) — no active source
  • BLUETOOTH (0x01) — BT A2DP, 6 bytes additional data (MAC)
  • AUXILIARY (0x02) — 3.5mm line-in, no additional data

No SET/START on [5.1] — source switching happens implicitly (plug in aux, or route a BT device via [4.12]).

DeviceManagement Routing [4.12] — Switch Active BT Device

START operator to route audio to a specific paired BT device (multipoint switch).

Payload (7 bytes):

[0]    Flags: 0x82 (bit7=UP routing direction, bit1=device slot)
[1-6]  Target device MAC address (6 bytes)

Response handling (from DeviceManagementBmapPacketParser.java):

  • RESULT: bytes [1-6] = MAC of now-active device (success)
  • ERROR: bytes [0-1] = 16-bit error code
  • STATUS: bytes [2-7] = MAC of newly routed device

ROUTING_TYPE enum: UP=1 (value << 7 = 0x80), DOWN=0.

AudioManagement Control [5.3] — Transport Controls

START operator with single-byte payload. Values from AudioControlValue.java:

0x00  STOP
0x01  PLAY
0x02  PAUSE
0x03  TRACK_FORWARD
0x04  TRACK_BACK
0x05  FAST_FORWARD_PRESS
0x06  FAST_FORWARD_RELEASE
0x07  REWIND_PRESS
0x08  REWIND_RELEASE

DeviceManagement Functions (Block 4) — Full Map

From DeviceManagementPackets.java:

FuncNameNotes
0FblockInfo
1ConnectNeeds device MAC
2Disconnect
3RemoveDevice
4ListDevicesReturns paired device list
5InfoDevice info query
7ClearDeviceList
8PairingMode0x01=enable, 0x00=disable
9LocalMacAddress
10PrepareP2P
11P2PMode
12RoutingSwitch active multipoint device

BMAP Error Codes

CodeNameDescription
0UnknownUnknown error
1LengthInvalid length
2ChksumInvalid checksum
3FblockNotSuppFunction block not supported
4FuncNotSuppFunction not supported
5OpNotSuppOperator not supported (needs auth)
6InvalidDataData values incorrect
7DataUnavailableRequested data not available
8RuntimeTemporary read/write failure
9TimeoutTimeout
10InvalidStateNot applicable to current state
20InsecureTransportPacket on insecure transport

BMAP Protocol Class References

  • com.bose.bmap.messages.enums.spec.BmapFunctionBlock — block IDs
  • com.bose.bmap.messages.enums.spec.BmapFunction — function IDs
  • com.bose.bmap.messages.enums.spec.BmapOperator — operator IDs
  • com.bose.bmap.messages.packets.AudioModesCurrentModeStartPacket — mode switch
  • com.bose.bmap.messages.packets.SettingsCncSetGetPacket — CNC control
  • com.bose.bmap.service.SppConnectionManager — SPP UUID: 00001101-...
  • com.bose.bmap.messages.models.settings.CncLevel — CNC response parser
  • com.bose.bmap.messages.responses.SettingsCncResponse — CNC payload format
  • com.bose.bmap.utils.encryption.ECDH — secp256r1 key generation
  • com.bose.bmap.model.factories.AuthenticationPackets — auth error codes & capabilities bitmask

Tools

  • bmap-capture.py — Interactive setting change capture tool
  • captures/ — Captured setting toggle data (8 captures)
  • snoop/ — Network captures and bugreports

Quick Reference: Control Headphones from Linux

import socket

BOSE_MAC = "68:F2:1F:XX:XX:XX"
sock = socket.socket(socket.AF_BLUETOOTH, socket.SOCK_STREAM, socket.BTPROTO_RFCOMM)
sock.settimeout(2)
sock.connect((BOSE_MAC, 2))

# Switch to Quiet (full ANC): mode=0
sock.send(bytes([31, 3, 0x05, 2, 0, 0]))

# Switch to Aware (transparency): mode=1
sock.send(bytes([31, 3, 0x05, 2, 1, 0]))

# Switch to Immersion: mode=2
sock.send(bytes([31, 3, 0x05, 2, 2, 0]))

# Read battery level
sock.send(bytes([2, 2, 0x01, 0x00]))
resp = sock.recv(4096)
battery_pct = resp[4]  # hex value, e.g. 0x50 = 80%

# Read current mode
sock.send(bytes([31, 3, 0x01, 0x00]))
resp = sock.recv(4096)
current_mode = resp[4]  # 0=Quiet, 1=Aware, 2=Immersion, etc.

QC35 NoiseCancellation Block (Block 3) — Firmware 4.8.1

Block 3 is a separate NoiseCancellation fblock on the QC35, distinct from the Settings-level ANR control at [1.6]. Investigation results:

Functions

FuncGETSET/SETGETSTARTNotes
3.101 or 02Auth-gated (error 5)Auth-gatedBinary NC system state
3.2Auth-gatedAuth-gated14-byte payload acceptedNC state transition
3.3Auth-gatedUnknown
3.401000000020000000000 (10 bytes)Auth-gatedNC config/capabilities
3.5Auth-gatedUnknown
3.6Empty STATUSUnknown
3.7Auth-gatedUnknown
3.8+FuncNotSuppNot implemented

[3.2] START Payload Format (14 bytes)

Offset  Size  Field
0       1     currentState  — must match [3.1] value
1-3     3     reserved (zeros)
4       1     targetState   — must be a valid transition target
5-13    9     reserved (zeros)

Accepts exactly 14-17 bytes (Length error outside). Only valid transition found: 01 → 02 (RESULT returned, [3.1] changes). All others return error 15 or InvalidData.

[3.1] State Values

  • 01 = default state (after ANR changes, after power cycle)
  • 02 = alternate state (reached via [3.2] START transition)

Key Findings

  • [3.1] does not correlate with ANR mode — changing ANR via [1.6] does not affect [3.1]
  • [3.4] config is static (01 00 00 00 02 00 00 00 00 00) regardless of ANR mode
  • The [3.2] START is a binary state toggle, not continuous NC level control
  • This is likely the low-level NC hardware enable/disable — not useful for user-facing control
  • ANR at [1.6] remains the correct interface for NC mode control on QC35 firmware 4.8.1
  • The continuous CNC slider (0-10) seen on older firmware (1.x) is not recoverable via block 3

Conclusion

Block 3 on QC35 firmware 4.8.1 is a low-level NC system control that was likely exposed on older firmware but is now mostly auth-gated. The only unauthenticated path ([3.2] START) is a binary state transition with no practical NC level control. The discrete ANR modes (off/high/wind/low) via [1.6] SETGET are the full extent of unauthenticated NC control on this firmware version.

Future Work

  • Crack ModeConfig SETGET payload format DONE — full CNC/spatial/wind/ANC control
  • Crack Settings SETGET DONE — EQ, name, sidetone, multipoint, all work
  • Implement button remapping [1.9] DONE — SETGET works, verified on QC35 and QC Ultra 2
  • Try USB-C connection PARTIALLY DONE — USB HID interface found, needs init handshake
  • Crack USB BMAP initialization — capture USB traffic from app to find handshake sequence
  • Explore [31.9] AudioModes Reset — factory reset individual modes?
  • Reverse audio source/device routing DONE — [5.1] source query, [4.12] routing START, [5.3] transport controls
  • Implement [5.3] AudioManagement Control — play/pause/skip (payload format now known)
  • Build a system tray widget / PipeWire integration
  • Investigate firmware downgrade via bose-dfu over USB
  • Map the unknown bytes [40-41] in ModeConfig STATUS (mode-type specific config?)