Last active
August 24, 2021 03:01
-
-
Save hsheth2/3fefdf620da6a2c567208079d305698e to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import pathlib | |
import time | |
import re | |
import dataclasses | |
from typing import List | |
from pwnlib.tubes.process import process | |
from configparser import ConfigParser | |
# Config defaults. | |
WINDOWS_SYSTEM_PATH = pathlib.Path("Windows/System32/config") | |
WINDOWS_KEY_PATHS_TO_TRY = [ | |
"CurrentControlSet\Services\BTHPORT\Parameters\Keys", | |
"CurrentControlSet\services\BTHPORT\Parameters\Keys", | |
"ControlSet001\Services\BTHPORT\Parameters\Keys", | |
] | |
LINUX_BLUETOOTH_INFO_PATH = "/var/lib/bluetooth/{card}/{device}/info" | |
DEFAULT_WAIT_TIME = 0.1 | |
ENCODING_TYPE = 'utf-8' | |
@dataclasses.dataclass | |
class LinkKey: | |
bt_card_mac: str | |
bt_device_mac: str | |
link_key: str | |
def clean_mac_address(mac_raw: str) -> str: | |
# Via https://stackoverflow.com/a/11006780/5004662. | |
assert len(mac_raw) == 12 | |
mac_with_colons = ':'.join(mac_raw[i:i+2] for i in range(0,12,2)) | |
return mac_with_colons.upper() | |
def clean_link_key(link_key_raw: str) -> str: | |
link_key = link_key_raw.replace(' ', '') | |
assert len(link_key) == 32 | |
return link_key.upper() | |
def read_keys_from_windows(windows_c_drive: pathlib.Path) -> List[LinkKey]: | |
# Use chntpw to browse the Windows registry. | |
print('Reading from Windows SYSTEM registry') | |
registry = process( | |
["chntpw", "-e", "SYSTEM"], | |
cwd=(windows_c_drive / WINDOWS_SYSTEM_PATH), | |
) | |
registry.recvuntil(b'? for help') | |
registry.clean_and_log() | |
def run_command(command: str) -> str: | |
registry.sendline(command.encode(ENCODING_TYPE)) | |
time.sleep(DEFAULT_WAIT_TIME) | |
output = registry.recvS() | |
return output | |
# Just try cd into each path, and one of them will probably succeed. | |
for key_path in WINDOWS_KEY_PATHS_TO_TRY: | |
run_command(f"cd {key_path}") | |
# List MAC addresses of the Bluetooth cards. | |
bluetooth_macs_output = run_command('ls') | |
bluetooth_card_macs: List[str] = re.findall(r'<([a-z0-9]+)>', bluetooth_macs_output) | |
print('Found bluetooth cards:', bluetooth_card_macs) | |
# For each Bluetooth card, get the connected devices and their link keys. | |
keys: List[LinkKey] = [] | |
for bt_card_mac in bluetooth_card_macs: | |
print(f'Examining bluetooth card: {bt_card_mac}') | |
bluetooth_devices_output = run_command(f'ls {bt_card_mac}') | |
bluetooth_devices_macs: List[str] = re.findall(r'<([a-z0-9]+)>', bluetooth_devices_output) | |
print(' Found paired bluetooth devices:', bluetooth_card_macs) | |
for bt_device_mac in bluetooth_devices_macs: | |
print(' Examining bluetooth device:', bt_device_mac) | |
link_key_output = run_command(f'hex {bt_card_mac}\{bt_device_mac}') | |
link_key = re.search(r'^:00000\s+(([A-Z0-9]{2} ){16})', link_key_output, re.MULTILINE).group(1) | |
print(' Found link key:', link_key) | |
keys.append(LinkKey( | |
bt_card_mac=clean_mac_address(bt_card_mac), | |
bt_device_mac=clean_mac_address(bt_device_mac), | |
link_key=clean_link_key(link_key), | |
)) | |
# Cleanup the subprocess. | |
registry.sendline(b'q') | |
registry.shutdown('send') | |
time.sleep(DEFAULT_WAIT_TIME) | |
registry.clean_and_log() | |
retcode = registry.poll(block=True) | |
assert retcode == 0 | |
return keys | |
def update_bluetooth_config(key: LinkKey) -> None: | |
config_file = pathlib.Path(LINUX_BLUETOOTH_INFO_PATH.format(card=key.bt_card_mac, device=key.bt_device_mac)) | |
if not config_file.exists(): | |
print(f'Skipping {config_file} - not already paired') | |
return | |
with config_file.open() as f: | |
# Ensure config parser preserves case and comments. | |
config_info = ConfigParser(allow_no_value=True) | |
config_info.optionxform = str | |
config_info.read_file(f) | |
modified = False | |
current_key = config_info['LinkKey']['Key'] | |
if current_key != key.link_key: | |
config_info['LinkKey']['Key'] = key.link_key | |
config_info['LinkKey']['; (previous) Key'] = current_key | |
modified = True | |
if modified: | |
print(f'Updating {config_file}') | |
with config_file.open('w') as f: | |
config_info.write(f, False) | |
else: | |
print(f'Already up to date - {config_file}') | |
if __name__ == '__main__': | |
# TODO check if chntpw is installed already; sudo apt install chntpw | |
# TODO add a dry-run option | |
# TODO use reged instead of chntpw; comes from the same package but won't require the | |
# pwntools hacks for interactive use. should be something along these lines: | |
# reged -x SYSTEM '\' 'CurrentControlSet\Services\BTHPORT\Parameters\Keys' /tmp/foo.reg | |
# TODO this does not work for bluetooth LE devices | |
# TODO this does not yet reset or restart the bluetooth process; should be something like | |
# sudo service bluetooth force-reload | |
# (or on arch) sudo systemctl restart bluetooth.service | |
WINDOWS_MOUNT_DIRECTORY = pathlib.Path("/media/hsheth/78D6DFB8D6DF74BA") | |
keys = read_keys_from_windows(WINDOWS_MOUNT_DIRECTORY) | |
print() | |
for key in keys: | |
update_bluetooth_config(key) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment