├── LICENSE
├── README.md
├── agent
└── simple-agent.py
├── dbus
└── de.syss.btkbdservice.conf
├── doc
└── paper
│ ├── Rikki_Dont_Lose_That_Bluetooth_Device.pdf
│ └── Security_of_Modern_Bluetooth_Keyboards.pdf
├── images
└── bluetooth_keyboard_emulator.png
├── keyboard.conf.example
├── keyboard
└── keyboard_client.py
├── server
├── keyboard_server.py
└── sdp_record.xml
├── setup.sh
└── start.sh
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 SySS GmbH
4 | Copyright (c) 2017 quangthanh010290
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | SOFTWARE.
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Bluetooth Keyboard Emulator
2 |
3 | The SySS Bluetooth Keyboard Emulator is a simple proof-of-concept software
4 | tool for emulating Bluetooth BR/EDR (a.k.a. Bluetooth Classic) keyboards based
5 | on the [BlueZ 5 Bluetooth Keyboard Emulator for Raspberry Pi (YAPTB Bluetooth keyboard emulator)](https://github.com/0xmemphre/BL_keyboard_RPI)
6 | by Thanh Le.
7 |
8 | It was developed for modern Linux operating systems like [Arch Linux](https://www.archlinux.org/) with a
9 | modern BlueZ 5 Bluetooth protocol stack and utilities.
10 |
11 | So far, the keyboard emulator has only been tested on Arch Linux systems.
12 |
13 | **_Caution: The software tool is still work in progress._**
14 |
15 | ## Requirements
16 |
17 | - Modern Linux operating system with BlueZ 5 protocol stack (e. g. [Arch Linux](https://www.archlinux.org/))
18 | - BlueZ 5 utilities [bluez-utils](https://www.archlinux.org/packages/extra/x86_64/bluez-utils/)
19 | - Bluetooth adapter, e.g. Bluetooth dongle with supported chipset (so far only tested CSR8510 and BCM20702A0)
20 | - Python 3
21 | - [https://pypi.org/project/dbus-python/](https://pypi.org/project/dbus-python/)
22 | - [pynput](https://pypi.org/project/pynput/)
23 |
24 |
25 | ## Setup
26 |
27 | For setting up the Bluetooth Keyboard Emulator simply run the provided shell
28 | script **setup.sh** with root privileges.
29 |
30 | ```
31 | sudo ./setup.sh
32 | ```
33 |
34 | Or manually copy the file **dbus/de.syss.btkbdservice.conf** to **/etc/dbus-1/system.d/**.
35 |
36 | ```
37 | sudo cp dbus/de.syss.btkbdservice.conf /etc/dbus-1/system.d/
38 | ```
39 |
40 | ## Configuration
41 |
42 | Rename or copy the provided sample configuration file **keyboard.conf.example**
43 | to **keyboard.conf** and edit it with your favorite text editor.
44 |
45 | ## Usage
46 |
47 | In order to start the Bluetooth Keyboard Emulator, run the provided shell script
48 | **start.sh** in a terminal with root privileges.
49 |
50 | ```
51 | sudo ./start.sh
52 | ```
53 |
54 | Afterwards, you can attach to the created tmux session named **kbdemu**.
55 |
56 | ```
57 | sudo tmux attach -t kbdemu
58 | ```
59 |
60 | **_Caution: The client component of the running Bluetooth Keyboard Emulator will register and process all keypresses!_**
61 |
62 | For stopping the keyboard emulation, you can simply kill the tmux session.
63 |
64 | ```
65 | sudo tmux kill-session -t kbdemu
66 | ```
67 |
68 | The following screenshot illustrates the keyboard emulator usage.
69 |
70 | 
71 |
72 | # Disclaimer
73 |
74 | Use at your own risk. Do not use without full consent of everyone involved. For educational purposes only.
75 |
--------------------------------------------------------------------------------
/agent/simple-agent.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | """
5 | Bluetooth Keyboard Emulator D-Bus Agent
6 |
7 | Based on the BlueZ simple-agent
8 |
9 | http://www.bluez.org/
10 | """
11 |
12 | import dbus
13 | import dbus.service
14 | import dbus.mainloop.glib
15 |
16 | from gi.repository import GObject
17 | from optparse import OptionParser
18 |
19 | BUS_NAME = 'org.bluez'
20 | AGENT_INTERFACE = 'org.bluez.Agent1'
21 | AGENT_PATH = "/test/agent"
22 |
23 | bus = None
24 | device_obj = None
25 | dev_path = None
26 |
27 |
28 | def ask(prompt):
29 | try:
30 | return raw_input(prompt)
31 | except:
32 | return input(prompt)
33 |
34 |
35 | def set_trusted(path):
36 | props = dbus.Interface(bus.get_object("org.bluez", path),
37 | "org.freedesktop.DBus.Properties")
38 | props.Set("org.bluez.Device1", "Trusted", True)
39 |
40 |
41 | def dev_connect(path):
42 | dev = dbus.Interface(bus.get_object("org.bluez", path),
43 | "org.bluez.Device1")
44 | dev.Connect()
45 |
46 |
47 | class Rejected(dbus.DBusException):
48 | _dbus_error_name = "org.bluez.Error.Rejected"
49 |
50 |
51 | class Agent(dbus.service.Object):
52 | exit_on_release = True
53 |
54 | def set_exit_on_release(self, exit_on_release):
55 | self.exit_on_release = exit_on_release
56 |
57 | @dbus.service.method(AGENT_INTERFACE, in_signature="", out_signature="")
58 | def Release(self):
59 | print("Release")
60 | if self.exit_on_release:
61 | mainloop.quit()
62 |
63 | @dbus.service.method(AGENT_INTERFACE, in_signature="os", out_signature="")
64 | def AuthorizeService(self, device, uuid):
65 | print("AuthorizeService ({}, {})".format(device, uuid))
66 | authorize = ask("Authorize connection (yes/no): ")
67 | if (authorize == "yes"):
68 | return
69 | raise Rejected("Connection rejected by user")
70 |
71 | @dbus.service.method(AGENT_INTERFACE, in_signature="o", out_signature="s")
72 | def RequestPinCode(self, device):
73 | print("RequestPinCode ({})".format(device))
74 | set_trusted(device)
75 | return ask("Enter PIN Code: ")
76 |
77 | @dbus.service.method(AGENT_INTERFACE, in_signature="o", out_signature="u")
78 | def RequestPasskey(self, device):
79 | print("RequestPasskey ({})".format(device))
80 | set_trusted(device)
81 | passkey = ask("Enter passkey: ")
82 | return dbus.UInt32(passkey)
83 |
84 | @dbus.service.method(AGENT_INTERFACE, in_signature="ouq", out_signature="")
85 | def DisplayPasskey(self, device, passkey, entered):
86 | print("DisplayPasskey ({}, {:06u} entered {:u})".
87 | format(device, passkey, entered))
88 |
89 | @dbus.service.method(AGENT_INTERFACE, in_signature="os", out_signature="")
90 | def DisplayPinCode(self, device, pincode):
91 | print("DisplayPinCode ({}, {})".format(device, pincode))
92 |
93 | @dbus.service.method(AGENT_INTERFACE, in_signature="ou", out_signature="")
94 | def RequestConfirmation(self, device, passkey):
95 | print("RequestConfirmation ({}, {:06d}".format(device, passkey))
96 | confirm = ask("Confirm passkey (yes/no): ")
97 | if (confirm == "yes"):
98 | set_trusted(device)
99 | return
100 | raise Rejected("Passkey doesn't match")
101 |
102 | @dbus.service.method(AGENT_INTERFACE, in_signature="o", out_signature="")
103 | def RequestAuthorization(self, device):
104 | print("RequestAuthorization ({})".format(device))
105 | auth = ask("Authorize? (yes/no): ")
106 | if (auth == "yes"):
107 | return
108 | raise Rejected("Pairing rejected")
109 |
110 | @dbus.service.method(AGENT_INTERFACE, in_signature="", out_signature="")
111 | def Cancel(self):
112 | print("Cancel")
113 |
114 |
115 | def pair_reply():
116 | print("Device paired")
117 | set_trusted(dev_path)
118 | dev_connect(dev_path)
119 | mainloop.quit()
120 |
121 |
122 | def pair_error(error):
123 | err_name = error.get_dbus_name()
124 | if err_name == "org.freedesktop.DBus.Error.NoReply" and device_obj:
125 | print("Timed out. Cancelling pairing")
126 | device_obj.CancelPairing()
127 | else:
128 | print("Creating device failed: {}".format(error))
129 |
130 | mainloop.quit()
131 |
132 |
133 | if __name__ == '__main__':
134 | dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
135 |
136 | bus = dbus.SystemBus()
137 |
138 | capability = "KeyboardDisplay"
139 |
140 | path = "/test/agent"
141 | agent = Agent(bus, path)
142 |
143 | mainloop = GObject.MainLoop()
144 |
145 | obj = bus.get_object(BUS_NAME, "/org/bluez")
146 | manager = dbus.Interface(obj, "org.bluez.AgentManager1")
147 | manager.RegisterAgent(path, capability)
148 |
149 | print("[*] Agent registered")
150 | manager.RequestDefaultAgent(path)
151 | mainloop.run()
152 |
--------------------------------------------------------------------------------
/dbus/de.syss.btkbdservice.conf:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/doc/paper/Rikki_Dont_Lose_That_Bluetooth_Device.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SySS-Research/bluetooth-keyboard-emulator/da242cf01d527530f8b936323d7d8f457dc2d552/doc/paper/Rikki_Dont_Lose_That_Bluetooth_Device.pdf
--------------------------------------------------------------------------------
/doc/paper/Security_of_Modern_Bluetooth_Keyboards.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SySS-Research/bluetooth-keyboard-emulator/da242cf01d527530f8b936323d7d8f457dc2d552/doc/paper/Security_of_Modern_Bluetooth_Keyboards.pdf
--------------------------------------------------------------------------------
/images/bluetooth_keyboard_emulator.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SySS-Research/bluetooth-keyboard-emulator/da242cf01d527530f8b936323d7d8f457dc2d552/images/bluetooth_keyboard_emulator.png
--------------------------------------------------------------------------------
/keyboard.conf.example:
--------------------------------------------------------------------------------
1 | [default]
2 | BluetoothAddress = 00:01:02:03:04:05
3 | DeviceName = SySS_Keyboard
4 | DeviceShortName = SySSKbd
5 |
6 | # device interface
7 | Interface = hci0
8 |
9 | # different Bluetooth chipsets require different spoofing methods for changing
10 | # the MAC address, e.g. CSR8510 requires bdaddr, or BCM20702A0 requires btmgmt
11 | # valid spoofing options are: none, bdddr, btmgmt
12 | SpoofingMethod = btmgmt
13 |
14 | [auto_connect]
15 | AutoConnect = false
16 | Target= 00:00:00:00:00:00
17 |
--------------------------------------------------------------------------------
/keyboard/keyboard_client.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # -*- coding: utf-8 -*-
3 |
4 | """
5 | Bluetooth Keyboard Emulator D-Bus Client
6 |
7 | by Matthias Deeg , SySS GmbH
8 |
9 | based on BlueZ 5 Bluetooth Keyboard Emulator for Raspberry Pi
10 | (YAPTB Bluetooth keyboard emulator) by Thanh Le
11 | Source code and information of this project can be found via
12 | https://github.com/0xmemphre/BL_keyboard_RPI,
13 | http://www.mlabviet.com/2017/09/make-raspberry-pi3-as-emulator.html
14 |
15 | MIT License
16 |
17 | Copyright (c) 2018 SySS GmbH
18 | Copyright (c) 2017 quangthanh010290
19 |
20 | Permission is hereby granted, free of charge, to any person obtaining a copy
21 | of this software and associated documentation files (the "Software"), to deal
22 | in the Software without restriction, including without limitation the rights
23 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
24 | copies of the Software, and to permit persons to whom the Software is
25 | furnished to do so, subject to the following conditions:
26 |
27 | The above copyright notice and this permission notice shall be included in
28 | all copies or substantial portions of the Software.
29 |
30 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
31 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
32 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
33 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
34 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
35 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
36 | SOFTWARE.
37 | """
38 |
39 | __version__ = '0.8'
40 | __author__ = 'Matthias Deeg'
41 |
42 |
43 | import dbus
44 | import dbus.service
45 |
46 | from pynput import keyboard
47 |
48 | # operating modes
49 | NON_INTERACTIVE_MODE = 0
50 | INTERACTIVE_MODE = 1
51 |
52 | # USB HID keyboard modifier
53 | MODIFIER_NONE = 0
54 | MODIFIER_CONTROL_LEFT = 1 << 0
55 | MODIFIER_SHIFT_LEFT = 1 << 1
56 | MODIFIER_ALT_LEFT = 1 << 2
57 | MODIFIER_GUI_LEFT = 1 << 3
58 | MODIFIER_CONTROL_RIGHT = 1 << 4
59 | MODIFIER_SHIFT_RIGHT = 1 << 5
60 | MODIFIER_ALT_RIGHT = 1 << 6
61 | MODIFIER_GUI_RIGHT = 1 << 7
62 |
63 | # USB HID key codes
64 | KEY_NONE = 0x00
65 | KEY_A = 0x04
66 | KEY_B = 0x05
67 | KEY_C = 0x06
68 | KEY_D = 0x07
69 | KEY_E = 0x08
70 | KEY_F = 0x09
71 | KEY_G = 0x0A
72 | KEY_H = 0x0B
73 | KEY_I = 0x0C
74 | KEY_J = 0x0D
75 | KEY_K = 0x0E
76 | KEY_L = 0x0F
77 | KEY_M = 0x10
78 | KEY_N = 0x11
79 | KEY_O = 0x12
80 | KEY_P = 0x13
81 | KEY_Q = 0x14
82 | KEY_R = 0x15
83 | KEY_S = 0x16
84 | KEY_T = 0x17
85 | KEY_U = 0x18
86 | KEY_V = 0x19
87 | KEY_W = 0x1A
88 | KEY_X = 0x1B
89 | KEY_Y = 0x1C
90 | KEY_Z = 0x1D
91 | KEY_1 = 0x1E
92 | KEY_2 = 0x1F
93 | KEY_3 = 0x20
94 | KEY_4 = 0x21
95 | KEY_5 = 0x22
96 | KEY_6 = 0x23
97 | KEY_7 = 0x24
98 | KEY_8 = 0x25
99 | KEY_9 = 0x26
100 | KEY_0 = 0x27
101 | KEY_RETURN = 0x28
102 | KEY_ESCAPE = 0x29
103 | KEY_BACKSPACE = 0x2A
104 | KEY_TAB = 0x2B
105 | KEY_SPACE = 0x2C
106 | KEY_MINUS = 0x2D
107 | KEY_EQUAL = 0x2E
108 | KEY_BRACKET_LEFT = 0x2F
109 | KEY_BRACKET_RIGHT = 0x30
110 | KEY_BACKSLASH = 0x31
111 | KEY_EUROPE_1 = 0x32
112 | KEY_SEMICOLON = 0x33
113 | KEY_APOSTROPHE = 0x34
114 | KEY_GRAVE = 0x35
115 | KEY_COMMA = 0x36
116 | KEY_PERIOD = 0x37
117 | KEY_SLASH = 0x38
118 | KEY_CAPS_LOCK = 0x39
119 | KEY_F1 = 0x3A
120 | KEY_F2 = 0x3B
121 | KEY_F3 = 0x3C
122 | KEY_F4 = 0x3D
123 | KEY_F5 = 0x3E
124 | KEY_F6 = 0x3F
125 | KEY_F7 = 0x40
126 | KEY_F8 = 0x41
127 | KEY_F9 = 0x42
128 | KEY_F10 = 0x43
129 | KEY_F11 = 0x44
130 | KEY_F12 = 0x45
131 | KEY_PRINT_SCREEN = 0x46
132 | KEY_SCROLL_LOCK = 0x47
133 | KEY_PAUSE = 0x48
134 | KEY_INSERT = 0x49
135 | KEY_HOME = 0x4A
136 | KEY_PAGE_UP = 0x4B
137 | KEY_DELETE = 0x4C
138 | KEY_END = 0x4D
139 | KEY_PAGE_DOWN = 0x4E
140 | KEY_ARROW_RIGHT = 0x4F
141 | KEY_ARROW_LEFT = 0x50
142 | KEY_ARROW_DOWN = 0x51
143 | KEY_ARROW_UP = 0x52
144 | KEY_NUM_LOCK = 0x53
145 | KEY_KEYPAD_DIVIDE = 0x54
146 | KEY_KEYPAD_MULTIPLY = 0x55
147 | KEY_KEYPAD_SUBTRACT = 0x56
148 | KEY_KEYPAD_ADD = 0x57
149 | KEY_KEYPAD_ENTER = 0x58
150 | KEY_KEYPAD_1 = 0x59
151 | KEY_KEYPAD_2 = 0x5A
152 | KEY_KEYPAD_3 = 0x5B
153 | KEY_KEYPAD_4 = 0x5C
154 | KEY_KEYPAD_5 = 0x5D
155 | KEY_KEYPAD_6 = 0x5E
156 | KEY_KEYPAD_7 = 0x5F
157 | KEY_KEYPAD_8 = 0x60
158 | KEY_KEYPAD_9 = 0x61
159 | KEY_KEYPAD_0 = 0x62
160 | KEY_KEYPAD_DECIMAL = 0x63
161 | KEY_EUROPE_2 = 0x64
162 | KEY_APPLICATION = 0x65
163 | KEY_POWER = 0x66
164 | KEY_KEYPAD_EQUAL = 0x67
165 | KEY_F13 = 0x68
166 | KEY_F14 = 0x69
167 | KEY_F15 = 0x6A
168 | KEY_CONTROL_LEFT = 0xE0
169 | KEY_SHIFT_LEFT = 0xE1
170 | KEY_ALT_LEFT = 0xE2
171 | KEY_GUI_LEFT = 0xE3
172 | KEY_CONTROL_RIGHT = 0xE4
173 | KEY_SHIFT_RIGHT = 0xE5
174 | KEY_ALT_RIGHT = 0xE6
175 | KEY_GUI_RIGHT = 0xE7
176 |
177 | # mapping for special characters (pynput enum values)
178 | KEYMAP_SPECIAL_CHARS = {
179 | keyboard.Key.alt : (MODIFIER_ALT_LEFT, KEY_NONE),
180 | keyboard.Key.alt_l : (MODIFIER_ALT_LEFT, KEY_NONE),
181 | keyboard.Key.alt_r : (MODIFIER_ALT_RIGHT, KEY_NONE),
182 | keyboard.Key.alt_gr : (MODIFIER_ALT_RIGHT, KEY_NONE),
183 | keyboard.Key.backspace : (MODIFIER_NONE, KEY_BACKSPACE),
184 | keyboard.Key.caps_lock : (MODIFIER_NONE, KEY_CAPS_LOCK),
185 | keyboard.Key.cmd : (MODIFIER_GUI_LEFT, KEY_NONE),
186 | keyboard.Key.cmd_l : (MODIFIER_GUI_LEFT, KEY_NONE),
187 | keyboard.Key.cmd_r : (MODIFIER_GUI_RIGHT, KEY_NONE),
188 | keyboard.Key.ctrl : (MODIFIER_CONTROL_LEFT, KEY_NONE),
189 | keyboard.Key.ctrl_l : (MODIFIER_CONTROL_LEFT, KEY_NONE),
190 | keyboard.Key.ctrl_r : (MODIFIER_CONTROL_RIGHT, KEY_NONE),
191 | keyboard.Key.delete : (MODIFIER_NONE, KEY_DELETE),
192 | keyboard.Key.down : (MODIFIER_NONE, KEY_ARROW_DOWN),
193 | keyboard.Key.end : (MODIFIER_NONE, KEY_END),
194 | keyboard.Key.enter : (MODIFIER_NONE, KEY_RETURN),
195 | keyboard.Key.esc : (MODIFIER_NONE, KEY_ESCAPE),
196 | keyboard.Key.f1 : (MODIFIER_NONE, KEY_F1),
197 | keyboard.Key.f2 : (MODIFIER_NONE, KEY_F2),
198 | keyboard.Key.f3 : (MODIFIER_NONE, KEY_F3),
199 | keyboard.Key.f4 : (MODIFIER_NONE, KEY_F4),
200 | keyboard.Key.f5 : (MODIFIER_NONE, KEY_F5),
201 | keyboard.Key.f6 : (MODIFIER_NONE, KEY_F6),
202 | keyboard.Key.f7 : (MODIFIER_NONE, KEY_F7),
203 | keyboard.Key.f8 : (MODIFIER_NONE, KEY_F8),
204 | keyboard.Key.f9 : (MODIFIER_NONE, KEY_F9),
205 | keyboard.Key.f10 : (MODIFIER_NONE, KEY_F10),
206 | keyboard.Key.f11 : (MODIFIER_NONE, KEY_F11),
207 | keyboard.Key.f12 : (MODIFIER_NONE, KEY_F12),
208 | keyboard.Key.f13 : (MODIFIER_NONE, KEY_F13),
209 | keyboard.Key.f14 : (MODIFIER_NONE, KEY_F14),
210 | keyboard.Key.f15 : (MODIFIER_NONE, KEY_F15),
211 | keyboard.Key.home : (MODIFIER_NONE, KEY_HOME),
212 | keyboard.Key.left : (MODIFIER_NONE, KEY_ARROW_LEFT),
213 | keyboard.Key.page_down : (MODIFIER_NONE, KEY_PAGE_DOWN),
214 | keyboard.Key.page_up : (MODIFIER_NONE, KEY_PAGE_UP),
215 | keyboard.Key.right : (MODIFIER_NONE, KEY_ARROW_RIGHT),
216 | keyboard.Key.shift : (MODIFIER_SHIFT_LEFT, KEY_NONE),
217 | keyboard.Key.shift_l : (MODIFIER_SHIFT_LEFT, KEY_NONE),
218 | keyboard.Key.shift_r : (MODIFIER_SHIFT_RIGHT, KEY_NONE),
219 | keyboard.Key.space : (MODIFIER_NONE, KEY_SPACE),
220 | keyboard.Key.tab : (MODIFIER_NONE, KEY_TAB),
221 | keyboard.Key.up : (MODIFIER_NONE, KEY_ARROW_UP),
222 | keyboard.Key.insert : (MODIFIER_NONE, KEY_INSERT),
223 | keyboard.Key.menu : (MODIFIER_GUI_RIGHT, KEY_NONE),
224 | keyboard.Key.num_lock : (MODIFIER_NONE, KEY_NUM_LOCK),
225 | keyboard.Key.pause : (MODIFIER_NONE, KEY_PAUSE),
226 | keyboard.Key.print_screen : (MODIFIER_NONE, KEY_PRINT_SCREEN),
227 | keyboard.Key.scroll_lock : (MODIFIER_NONE, KEY_SCROLL_LOCK)
228 | }
229 |
230 | # key mapping for printable characters of default German keyboard layout
231 | KEYMAP_GERMAN = {
232 | ' ' : (MODIFIER_NONE, KEY_SPACE),
233 | '!' : (MODIFIER_SHIFT_LEFT, KEY_1),
234 | '"' : (MODIFIER_SHIFT_LEFT, KEY_2),
235 | '#' : (MODIFIER_NONE, KEY_EUROPE_1),
236 | '$' : (MODIFIER_SHIFT_LEFT, KEY_4),
237 | '%' : (MODIFIER_SHIFT_LEFT, KEY_5),
238 | '&' : (MODIFIER_SHIFT_LEFT, KEY_6),
239 | '(' : (MODIFIER_SHIFT_LEFT, KEY_8),
240 | ')' : (MODIFIER_SHIFT_LEFT, KEY_9),
241 | '*' : (MODIFIER_NONE, KEY_KEYPAD_MULTIPLY),
242 | '+' : (MODIFIER_NONE, KEY_KEYPAD_ADD),
243 | ',' : (MODIFIER_NONE, KEY_COMMA),
244 | '-' : (MODIFIER_NONE, KEY_KEYPAD_SUBTRACT),
245 | '.' : (MODIFIER_NONE, KEY_PERIOD),
246 | '/' : (MODIFIER_SHIFT_LEFT, KEY_7),
247 | '0' : (MODIFIER_NONE, KEY_0),
248 | '1' : (MODIFIER_NONE, KEY_1),
249 | '2' : (MODIFIER_NONE, KEY_2),
250 | '3' : (MODIFIER_NONE, KEY_3),
251 | '4' : (MODIFIER_NONE, KEY_4),
252 | '5' : (MODIFIER_NONE, KEY_5),
253 | '6' : (MODIFIER_NONE, KEY_6),
254 | '7' : (MODIFIER_NONE, KEY_7),
255 | '8' : (MODIFIER_NONE, KEY_8),
256 | '9' : (MODIFIER_NONE, KEY_9),
257 | ':' : (MODIFIER_SHIFT_LEFT, KEY_PERIOD),
258 | ';' : (MODIFIER_SHIFT_LEFT, KEY_COMMA),
259 | '<' : (MODIFIER_NONE, KEY_EUROPE_2),
260 | '=' : (MODIFIER_SHIFT_LEFT, KEY_0),
261 | '>' : (MODIFIER_SHIFT_LEFT, KEY_EUROPE_2),
262 | '?' : (MODIFIER_SHIFT_LEFT, KEY_MINUS),
263 | '@' : (MODIFIER_ALT_RIGHT, KEY_Q),
264 | 'A' : (MODIFIER_SHIFT_LEFT, KEY_A),
265 | 'B' : (MODIFIER_SHIFT_LEFT, KEY_B),
266 | 'C' : (MODIFIER_SHIFT_LEFT, KEY_C),
267 | 'D' : (MODIFIER_SHIFT_LEFT, KEY_D),
268 | 'E' : (MODIFIER_SHIFT_LEFT, KEY_E),
269 | 'F' : (MODIFIER_SHIFT_LEFT, KEY_F),
270 | 'G' : (MODIFIER_SHIFT_LEFT, KEY_G),
271 | 'H' : (MODIFIER_SHIFT_LEFT, KEY_H),
272 | 'I' : (MODIFIER_SHIFT_LEFT, KEY_I),
273 | 'J' : (MODIFIER_SHIFT_LEFT, KEY_J),
274 | 'K' : (MODIFIER_SHIFT_LEFT, KEY_K),
275 | 'L' : (MODIFIER_SHIFT_LEFT, KEY_L),
276 | 'M' : (MODIFIER_SHIFT_LEFT, KEY_M),
277 | 'N' : (MODIFIER_SHIFT_LEFT, KEY_N),
278 | 'O' : (MODIFIER_SHIFT_LEFT, KEY_O),
279 | 'P' : (MODIFIER_SHIFT_LEFT, KEY_P),
280 | 'Q' : (MODIFIER_SHIFT_LEFT, KEY_Q),
281 | 'R' : (MODIFIER_SHIFT_LEFT, KEY_R),
282 | 'S' : (MODIFIER_SHIFT_LEFT, KEY_S),
283 | 'T' : (MODIFIER_SHIFT_LEFT, KEY_T),
284 | 'U' : (MODIFIER_SHIFT_LEFT, KEY_U),
285 | 'V' : (MODIFIER_SHIFT_LEFT, KEY_V),
286 | 'W' : (MODIFIER_SHIFT_LEFT, KEY_W),
287 | 'X' : (MODIFIER_SHIFT_LEFT, KEY_X),
288 | 'Y' : (MODIFIER_SHIFT_LEFT, KEY_Z),
289 | 'Z' : (MODIFIER_SHIFT_LEFT, KEY_Y),
290 | '[' : (MODIFIER_ALT_RIGHT, KEY_8),
291 | '\\' : (MODIFIER_ALT_RIGHT, KEY_MINUS),
292 | ']' : (MODIFIER_ALT_RIGHT, KEY_9),
293 | '^' : (MODIFIER_NONE, KEY_GRAVE),
294 | '_' : (MODIFIER_SHIFT_LEFT, KEY_SLASH),
295 | '`' : (MODIFIER_SHIFT_LEFT, KEY_EQUAL),
296 | 'a' : (MODIFIER_NONE, KEY_A),
297 | 'b' : (MODIFIER_NONE, KEY_B),
298 | 'c' : (MODIFIER_NONE, KEY_C),
299 | 'd' : (MODIFIER_NONE, KEY_D),
300 | 'e' : (MODIFIER_NONE, KEY_E),
301 | 'f' : (MODIFIER_NONE, KEY_F),
302 | 'g' : (MODIFIER_NONE, KEY_G),
303 | 'h' : (MODIFIER_NONE, KEY_H),
304 | 'i' : (MODIFIER_NONE, KEY_I),
305 | 'j' : (MODIFIER_NONE, KEY_J),
306 | 'k' : (MODIFIER_NONE, KEY_K),
307 | 'l' : (MODIFIER_NONE, KEY_L),
308 | 'm' : (MODIFIER_NONE, KEY_M),
309 | 'n' : (MODIFIER_NONE, KEY_N),
310 | 'o' : (MODIFIER_NONE, KEY_O),
311 | 'p' : (MODIFIER_NONE, KEY_P),
312 | 'q' : (MODIFIER_NONE, KEY_Q),
313 | 'r' : (MODIFIER_NONE, KEY_R),
314 | 's' : (MODIFIER_NONE, KEY_S),
315 | 't' : (MODIFIER_NONE, KEY_T),
316 | 'u' : (MODIFIER_NONE, KEY_U),
317 | 'v' : (MODIFIER_NONE, KEY_V),
318 | 'w' : (MODIFIER_NONE, KEY_W),
319 | 'x' : (MODIFIER_NONE, KEY_X),
320 | 'y' : (MODIFIER_NONE, KEY_Z),
321 | 'z' : (MODIFIER_NONE, KEY_Y),
322 | '{' : (MODIFIER_ALT_RIGHT, KEY_7),
323 | '|' : (MODIFIER_ALT_RIGHT, KEY_EUROPE_2),
324 | '}' : (MODIFIER_ALT_RIGHT, KEY_0),
325 | '~' : (MODIFIER_ALT_RIGHT, KEY_BRACKET_RIGHT),
326 | u'\'' : (MODIFIER_SHIFT_LEFT, KEY_EUROPE_1),
327 | u'Ä' : (MODIFIER_SHIFT_LEFT, KEY_APOSTROPHE),
328 | u'Ö' : (MODIFIER_SHIFT_LEFT, KEY_SEMICOLON),
329 | u'Ü' : (MODIFIER_SHIFT_LEFT, KEY_BRACKET_LEFT),
330 | u'ä' : (MODIFIER_NONE, KEY_APOSTROPHE),
331 | u'ö' : (MODIFIER_NONE, KEY_SEMICOLON),
332 | u'ü' : (MODIFIER_NONE, KEY_BRACKET_LEFT),
333 | u'ß' : (MODIFIER_NONE, KEY_MINUS),
334 | u'€' : (MODIFIER_ALT_RIGHT, KEY_E)
335 | }
336 |
337 |
338 | class Keyboard():
339 | """Emulated keyboard"""
340 |
341 | def __init__(self):
342 | # state structure of the emulated Bluetooth keyboard
343 | self.state = [
344 | 0xA1, # input report
345 | 0x01, # usage report : keyboard
346 | 0x00, # modifier byte
347 | 0x00, # Vendor reserved
348 | 0x00, # 6 bytes for key codes
349 | 0x00,
350 | 0x00,
351 | 0x00,
352 | 0x00,
353 | 0x00]
354 |
355 | self.pressed_key_count = 0 # initialize keypress counter
356 | self.keymap = KEYMAP_GERMAN # set keymap
357 | self.mode = INTERACTIVE_MODE # interactive mode is default
358 |
359 | # initialize D-Bus client
360 | print("[*] Initialize D-Bus keyboard client")
361 | self.bus = dbus.SystemBus()
362 | self.btkservice = self.bus.get_object("de.syss.btkbdservice",
363 | "/de/syss/btkbdservice")
364 | self.iface = dbus.Interface(self.btkservice, "de.syss.btkbdservice")
365 |
366 | def change_state(self, keydata, keypress=True):
367 | """Change keyboard state"""
368 |
369 | print("key count {}".format(self.pressed_key_count))
370 |
371 | if keypress:
372 | if keydata[0] != MODIFIER_NONE and keydata[1] != KEY_NONE:
373 | if keydata[1] not in self.state[4:]:
374 | # increase key count
375 | self.pressed_key_count += 1
376 |
377 | # find free key slot
378 | i = self.state[4:].index(0)
379 |
380 | # set key
381 | self.state[4 + i] = keydata[1]
382 | print("Key press {}".format(keydata[1]))
383 |
384 | self.state[2] = keydata[0]
385 |
386 |
387 | elif keydata[1] != KEY_NONE:
388 | if keydata[1] not in self.state[4:]:
389 | # increase key count
390 | self.pressed_key_count += 1
391 |
392 | # find free key slot
393 | i = self.state[4:].index(0)
394 |
395 | # set key
396 | self.state[4 + i] = keydata[1]
397 | print("Key press {}".format(keydata[1]))
398 |
399 | elif keydata[0] != MODIFIER_NONE and keydata[1] == KEY_NONE:
400 | # process modifier keys
401 | print("{} pressed".format(keydata[0]))
402 |
403 | self.state[2] |= keydata[0]
404 | print("Modify modifier byte {}".format(self.state[2]))
405 |
406 | else:
407 | if keydata[1] != KEY_NONE:
408 | # decrease keypress count
409 | self.pressed_key_count -= 1
410 | print("Key release {}".format(keydata[1]))
411 |
412 | # update state
413 | i = self.state[4:].index(keydata[1])
414 | self.state[4 + i] = 0
415 |
416 | if keydata[0] != MODIFIER_NONE:
417 | print("{} released".format(keydata[0]))
418 | self.state[2] &= ~keydata[0]
419 |
420 | print(self)
421 | self.send_input()
422 |
423 | def send_input(self):
424 | """Forward keyboard events to the D-Bus service"""
425 |
426 | modifier_byte = self.state[2]
427 | self.iface.send_keys(modifier_byte, self.state[4:10])
428 |
429 | def on_press(self, key):
430 | """Change keyboard state on key presses"""
431 | if self.pressed_key_count < 6:
432 | # set state of newly pressed key but consider limit of max. 6 keys
433 | # pressed at the same time
434 | try:
435 | # process printable characters
436 | k = self.keymap[key.char]
437 |
438 | # change keyboard state
439 | self.change_state(k)
440 | except AttributeError:
441 | # process special keys
442 | k = KEYMAP_SPECIAL_CHARS[key]
443 |
444 | # change keyboard state
445 | self.change_state(k)
446 | except KeyError:
447 | print("KeyError: {}".format(key))
448 |
449 | def on_release(self, key):
450 | """Change keyboard state on key releases"""
451 |
452 | try:
453 | # process printable characters
454 | k = self.keymap[key.char]
455 |
456 | # change keyboard state
457 | self.change_state(k, False)
458 | except AttributeError:
459 | # process special keys
460 | k = KEYMAP_SPECIAL_CHARS[key]
461 |
462 | # change keyboard state
463 | self.change_state(k, False)
464 | except KeyError:
465 | print(key)
466 |
467 | # if key == keyboard.Key.esc:
468 | # # Stop listener
469 | # return False
470 |
471 | def event_loop(self):
472 | """Collect events until released"""
473 |
474 | with keyboard.Listener(
475 | on_press=self.on_press,
476 | on_release=self.on_release) as listener:
477 | listener.join()
478 |
479 | def __str__(self):
480 | """Keyboard state as string"""
481 |
482 | return repr(self.state)
483 |
484 | # main
485 | if __name__ == "__main__":
486 | print("[*] Intialize keyboard")
487 | kbd = Keyboard()
488 |
489 | print("[*] Start event loop ...")
490 | kbd.event_loop()
491 |
--------------------------------------------------------------------------------
/server/keyboard_server.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # -*- coding: utf-8 -*-
3 |
4 | """
5 | Bluetooth Keyboard Emulator D-Bus Server
6 |
7 | by Matthias Deeg , SySS GmbH
8 |
9 | based on BlueZ 5 Bluetooth Keyboard Emulator for Raspberry Pi
10 | (YAPTB Bluetooth keyboard emulator) by Thanh Le
11 | Source code and information of this project can be found via
12 | https://github.com/0xmemphre/BL_keyboard_RPI,
13 | http://www.mlabviet.com/2017/09/make-raspberry-pi3-as-emulator.html
14 |
15 | MIT License
16 |
17 | Copyright (c) 2018 SySS GmbH
18 | Copyright (c) 2017 quangthanh010290
19 |
20 | Permission is hereby granted, free of charge, to any person obtaining a copy
21 | of this software and associated documentation files (the "Software"), to deal
22 | in the Software without restriction, including without limitation the rights
23 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
24 | copies of the Software, and to permit persons to whom the Software is
25 | furnished to do so, subject to the following conditions:
26 |
27 | The above copyright notice and this permission notice shall be included in
28 | all copies or substantial portions of the Software.
29 |
30 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
31 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
32 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
33 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
34 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
35 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
36 | SOFTWARE.
37 | """
38 |
39 | __version__ = '1.0'
40 | __author__ = 'Matthias Deeg'
41 |
42 |
43 | import configparser
44 | import dbus
45 | import dbus.service
46 | import dbus.mainloop
47 | import dbus.mainloop.glib
48 | import gi
49 | import os
50 | import subprocess
51 | import sys
52 | import time
53 |
54 | from bluetooth import BluetoothSocket, L2CAP
55 | from dbus.mainloop.glib import DBusGMainLoop
56 | from struct import pack
57 | gi.require_version('Gtk', '3.0')
58 | from gi.repository import Gtk
59 |
60 | # sleep time after Bluetooth command line tools
61 | OS_CMD_SLEEP = 1.5
62 |
63 |
64 | class BTKbdBluezProfile(dbus.service.Object):
65 | """Bluez 5 profile for emulated Bluetooth Keyboard"""
66 | fd = -1
67 |
68 | def __init__(self, bus, path):
69 | dbus.service.Object.__init__(self, bus, path)
70 |
71 | @dbus.service.method("org.bluez.Profile1", in_signature="",
72 | out_signature="")
73 | def Release(self):
74 | print("[*] Release")
75 | dbus.mainloop.quit()
76 |
77 | @dbus.service.method("org.bluez.Profile1",
78 | in_signature="", out_signature="")
79 | def Cancel(self):
80 | print("[*] Cancel")
81 |
82 | @dbus.service.method("org.bluez.Profile1", in_signature="oha{sv}",
83 | out_signature="")
84 | def NewConnection(self, path, fd, properties):
85 | self.fd = fd.take()
86 | print("[*] NewConnection({}, {:d})".format(path, self.fd))
87 | for key in properties.keys():
88 | if key == "Version" or key == "Features":
89 | print(" {} = 0x{:04x}".format(key, properties[key]))
90 | else:
91 | print(" {} = {}".format(key, properties[key]))
92 |
93 | @dbus.service.method("org.bluez.Profile1", in_signature="o",
94 | out_signature="")
95 | def RequestDisconnection(self, path):
96 | print("[*] RequestDisconnection({})".format(path))
97 |
98 | if (self.fd > 0):
99 | os.close(self.fd)
100 | self.fd = -1
101 |
102 |
103 | class BTKbDevice():
104 | """Bluetooth HID keyboard device"""
105 |
106 | # control and interrupt service ports
107 | P_CTRL = 17 # Service port (control) from SDP record
108 | P_INTR = 19 # Service port (interrupt) from SDP record
109 |
110 | # D-Bus path of the BlueZ profile
111 | PROFILE_DBUS_PATH = "/bluez/syss/btkbd_profile"
112 |
113 | # file path of the SDP record
114 | SDP_RECORD_PATH = "{}{}".format(sys.path[0], "/sdp_record.xml")
115 |
116 | # device UUID
117 | UUID = "00001124-0000-1000-8000-00805f9b34fb"
118 |
119 | def __init__(self):
120 | """Initialize Bluetooth keyboard device"""
121 |
122 | # read config file with address and device name
123 | print("[*] Read configuration file")
124 | config = configparser.ConfigParser()
125 | config.read("../keyboard.conf")
126 |
127 | try:
128 | self.bdaddr = config['default']['BluetoothAddress']
129 | self.device_name = config['default']['DeviceName']
130 | self.device_short_name = config['default']['DeviceShortName']
131 | self.interface = config['default']['Interface']
132 | self.spoofing_method = config['default']['SpoofingMethod']
133 | self.auto_connect = config['auto_connect']['AutoConnect']
134 | self.connect_target = config['auto_connect']['Target']
135 | except KeyError:
136 | sys.exit("[-] Could not read all required configuration values")
137 |
138 | print("[*] Initialize Bluetooth device")
139 | self.configure_device()
140 | self.register_bluez_profile()
141 |
142 | def configure_device(self):
143 | """Configure bluetooth hardware device"""
144 |
145 | print("[*] Configuring emulated Bluetooth keyboard")
146 |
147 | # power on Bluetooth device
148 | p = subprocess.run(['btmgmt', '--index', self.interface, 'power',
149 | 'off'], stdout=subprocess.PIPE)
150 | time.sleep(OS_CMD_SLEEP)
151 |
152 | # spoof device address if configured
153 | if self.spoofing_method == 'bdaddr':
154 | print("[+] Spoof device {} address {} via btmgmt".
155 | format(self.interface, self.bdaddr))
156 |
157 | # power on Bluetooth device
158 | p = subprocess.run(['btmgmt', '--index', self.interface, 'power',
159 | 'on'], stdout=subprocess.PIPE)
160 | time.sleep(OS_CMD_SLEEP)
161 |
162 | # set Bluetooth address using bdaddr software tool with manual
163 | # reset, so that we have to power on the device
164 | p = subprocess.run(['bdaddr', '-i', self.interface, '-r',
165 | self.bdaddr], stdout=subprocess.PIPE)
166 | time.sleep(OS_CMD_SLEEP)
167 |
168 | # power on Bluetooth device
169 | p = subprocess.run(['btmgmt', '--index', self.interface, 'power',
170 | 'on'], stdout=subprocess.PIPE)
171 | time.sleep(OS_CMD_SLEEP)
172 |
173 | # set device class
174 | print("[+] Set device class")
175 | p = subprocess.run(['btmgmt', '--index', self.interface, 'class',
176 | '5', '64'], stdout=subprocess.PIPE)
177 | time.sleep(OS_CMD_SLEEP)
178 |
179 | # set device name and short name
180 | print("[+] Set device name: {} ({})".
181 | format(self.device_name, self.device_short_name))
182 | p = subprocess.run(['btmgmt', '--index', self.interface, 'name',
183 | self.device_name, self.device_short_name],
184 | stdout=subprocess.PIPE)
185 |
186 | # set device to connectable
187 | p = subprocess.run(['btmgmt', '--index', self.interface,
188 | 'connectable', 'on'], stdout=subprocess.PIPE)
189 | time.sleep(OS_CMD_SLEEP)
190 |
191 | # power on Bluetooth device
192 | p = subprocess.run(['btmgmt', '--index', self.interface, 'power',
193 | 'on'], stdout=subprocess.PIPE)
194 | time.sleep(OS_CMD_SLEEP)
195 |
196 | elif self.spoofing_method == 'btmgmt':
197 | print("[+] Spoof device {} address {} via btmgmt".
198 | format(self.interface, self.bdaddr))
199 |
200 | # set Bluetooth address
201 | print("[+] Set Bluetooth address: {}".format(self.bdaddr))
202 | p = subprocess.run(['btmgmt', '--index', self.interface,
203 | 'public-addr', self.bdaddr],
204 | stdout=subprocess.PIPE)
205 |
206 | print(p.stdout)
207 | if "fail" in str(p.stdout, "utf-8"):
208 | print("[-] Error setting Bluetooth address")
209 | sys.exit(1)
210 |
211 | # power on Bluetooth device using btmgmt software tool
212 | p = subprocess.run(['btmgmt', '--index', self.interface, 'power',
213 | 'on'], stdout=subprocess.PIPE)
214 | # print(p.stdout)
215 | time.sleep(OS_CMD_SLEEP)
216 |
217 | # set device class
218 | print("[+] Set device class")
219 | p = subprocess.run(['btmgmt', '--index', self.interface, 'class',
220 | '5', '64'], stdout=subprocess.PIPE)
221 | # print(p.stdout)
222 | time.sleep(OS_CMD_SLEEP)
223 |
224 | # set device name and short name
225 | print("[+] Set device name: {} ({})".
226 | format(self.device_name, self.device_short_name))
227 |
228 | p = subprocess.run(['btmgmt', '--index', self.interface, 'name',
229 | self.device_name, self.device_short_name],
230 | stdout=subprocess.PIPE)
231 | # print(p.stdout)
232 | time.sleep(OS_CMD_SLEEP)
233 |
234 | # set device to connectable
235 | p = subprocess.run(['btmgmt', '--index', self.interface,
236 | 'connectable', 'on'], stdout=subprocess.PIPE)
237 | # print(p.stdout)
238 |
239 | time.sleep(OS_CMD_SLEEP)
240 |
241 | # turn on discoverable mode
242 | print("[+] Turn on discoverable mode")
243 | p = subprocess.run(['bluetoothctl', 'discoverable', 'on'],
244 | stdout=subprocess.PIPE)
245 | # print(p.stdout)
246 |
247 | def register_bluez_profile(self):
248 | """Setup and register BlueZ profile"""
249 |
250 | print("Configuring Bluez Profile")
251 |
252 | # setup profile options
253 | service_record = self.read_sdp_service_record()
254 |
255 | opts = {
256 | "ServiceRecord": service_record,
257 | "Role": "server",
258 | "RequireAuthentication": False,
259 | "RequireAuthorization": False
260 | }
261 |
262 | # retrieve a proxy for the bluez profile interface
263 | bus = dbus.SystemBus()
264 | manager = dbus.Interface(bus.get_object("org.bluez", "/org/bluez"),
265 | "org.bluez.ProfileManager1")
266 |
267 | profile = BTKbdBluezProfile(bus, BTKbDevice.PROFILE_DBUS_PATH)
268 |
269 | manager.RegisterProfile(BTKbDevice.PROFILE_DBUS_PATH, BTKbDevice.UUID,
270 | opts)
271 |
272 | print("[*] Profile registered")
273 |
274 | def read_sdp_service_record(self):
275 | """Read SDP service record"""
276 |
277 | print("[*] Reading service record")
278 | try:
279 | fh = open(BTKbDevice.SDP_RECORD_PATH, "r")
280 | except Exception:
281 | sys.exit("[*] Could not open the SDP record. Exiting ...")
282 |
283 | return fh.read()
284 |
285 | def listen(self):
286 | """Listen for incoming client connections"""
287 |
288 | print("[*] Waiting for connections")
289 | self.scontrol = BluetoothSocket(L2CAP)
290 | self.sinterrupt = BluetoothSocket(L2CAP)
291 |
292 | # bind these sockets to a port - port zero to select next available
293 | self.scontrol.bind((self.bdaddr, self.P_CTRL))
294 | self.sinterrupt.bind((self.bdaddr, self.P_INTR))
295 |
296 | # start listening on the server sockets (only allow 1 connection)
297 | self.scontrol.listen(1)
298 | self.sinterrupt.listen(1)
299 |
300 | self.ccontrol, cinfo = self.scontrol.accept()
301 | print("[*] Connection on the control channel from {}"
302 | .format(cinfo[0]))
303 |
304 | self.cinterrupt, cinfo = self.sinterrupt.accept()
305 | print("[*] Connection on the interrupt channel from {}"
306 | .format(cinfo[0]))
307 |
308 | def connect(self, target):
309 | """Connect to target MAC (the keyboard must already be known to the
310 | target)"""
311 |
312 | print("[*] Connecting to {}".format(target))
313 | self.scontrol = BluetoothSocket(L2CAP)
314 | self.sinterrupt = BluetoothSocket(L2CAP)
315 |
316 | self.scontrol.connect((target, self.P_CTRL))
317 | self.sinterrupt.connect((target, self.P_INTR))
318 |
319 | self.ccontrol = self.scontrol
320 | self.cinterrupt = self.sinterrupt
321 |
322 | def send_string(self, message):
323 | """Send a string to the host machine"""
324 |
325 | self.cinterrupt.send(message)
326 |
327 |
328 | class BTKbdService(dbus.service.Object):
329 | """D-Bus service for emulated Bluetooth keyboard"""
330 |
331 | def __init__(self):
332 | print("[*] Inititalize D-Bus Bluetooth keyboard service")
333 |
334 | # set up as a D-Bus service
335 | bus_name = dbus.service.BusName("de.syss.btkbdservice",
336 | bus=dbus.SystemBus())
337 | dbus.service.Object.__init__(self, bus_name, "/de/syss/btkbdservice")
338 |
339 | # create and setup our device
340 | self.device = BTKbDevice()
341 |
342 | if self.device.auto_connect == "true":
343 | # Switch into paring mode or connect to already known target?
344 | # files = os.listdir('/var/lib/bluetooth/{}'.format(self.device.bdaddr))
345 | # mac_regex = re.compile(r'([0-9A-F]{2}:){5}([0-9A-F]{2})')
346 | # files = list(filter(mac_regex.match, files))
347 | # if files:
348 | # connect to configured target
349 | self.device.connect(self.device.connect_target)
350 | else:
351 | # start listening for new connections
352 | self.device.listen()
353 |
354 | @dbus.service.method('de.syss.btkbdservice', in_signature='yay')
355 | def send_keys(self, modifiers, keys):
356 | """Send keys"""
357 |
358 | # create 10 byte data structure
359 | byte_list = [0xA1, 0x01, modifiers, 0x00]
360 | for key_code in keys:
361 | byte_list.append(key_code)
362 |
363 | # add some padding bytes to have a 10 byte packet
364 | if len(byte_list) < 10:
365 | padding = len(byte_list) - 10
366 | for i in range(padding):
367 | byte_list.append(0)
368 |
369 | data = pack("10B", *byte_list)
370 |
371 | self.device.send_string(data)
372 |
373 |
374 | # main routine
375 | if __name__ == "__main__":
376 | # check for required root privileges
377 | if not os.geteuid() == 0:
378 | sys.exit("[-] Please run the keyboard server as root")
379 |
380 | # start D-Bus Bluetooth keyboard emulator service
381 | DBusGMainLoop(set_as_default=True)
382 | btkbdservice = BTKbdService()
383 | Gtk.main()
384 |
--------------------------------------------------------------------------------
/server/sdp_record.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
--------------------------------------------------------------------------------
/setup.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | sudo cp dbus/de.syss.btkbdservice.conf /etc/dbus-1/system.d/
4 |
--------------------------------------------------------------------------------
/start.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # stop the Bluetooth background process
4 | sudo systemctl stop bluetooth
5 |
6 | # get current Path
7 | export C_PATH=$(pwd)
8 |
9 | # create tmux session
10 | tmux has-session -t kbdemu
11 | if [ $? != 0 ]; then
12 | tmux new-session -s kbdemu -n os -d
13 | tmux split-window -h -t kbdemu
14 | tmux split-window -v -t kbdemu:os.0
15 | tmux split-window -v -t kbdemu:os.1
16 | tmux split-window -v -t kbdemu:os.3
17 | tmux send-keys -t kbdemu:os.0 'cd $C_PATH && sudo /usr/lib/bluetooth/bluetoothd --nodetach --debug -p time ' C-m
18 | tmux send-keys -t kbdemu:os.1 'cd $C_PATH/server && sudo python3 keyboard_server.py ' C-m
19 | tmux send-keys -t kbdemu:os.2 'cd $C_PATH && sudo /usr/bin/bluetoothctl' C-m
20 | tmux send-keys -t kbdemu:os.3 'cd $C_PATH/keyboard/ && sleep 5 && sudo python3 keyboard_client.py' C-m
21 | tmux send-keys -t kbdemu:os.4 'cd $C_PATH/agent/ && sleep 5 && sudo python3 simple-agent.py' C-m
22 | fi
23 |
--------------------------------------------------------------------------------