Skip to content

Instantly share code, notes, and snippets.

@Mygod
Last active November 26, 2024 21:16
Show Gist options
  • Save Mygod/f390aabf53cf1406fc71166a47236ebf to your computer and use it in GitHub Desktop.
Save Mygod/f390aabf53cf1406fc71166a47236ebf to your computer and use it in GitHub Desktop.
Export your Windows Bluetooth LE keys into Linux!
#!/usr/bin/python3
"""
Copyright 2021 Mygod
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
What is this: Export your Windows Bluetooth LE keys into Linux!
Thanks to:
* http://console.systems/2014/09/how-to-pair-low-energy-le-bluetooth.html
* https://gist.github.com/corecoding/eac76d3da20c7e427a1848b8aed8e334/revisions#diff-6eeb0d27c24cc10680e8574f75648585
Usage:
$ ./export-ble-infos.py <args>
$ sudo bash -c 'cp -r ./bluetooth /var/lib && service bluetooth force-reload'
$ rm -r bluetooth
"""
import os
import shutil
import subprocess
import sys
import tempfile
from configparser import ConfigParser
from optparse import OptionParser
default_template = """
[General]
Name=Designer Mouse
Appearance=0x03c2
AddressType=static
SupportedTechnologies=LE;
Trusted=true
Blocked=false
Services=00001800-0000-1000-8000-00805f9b34fb;00001801-0000-1000-8000-00805f9b34fb;0000180a-0000-1000-8000-00805f9b34fb;0000180f-0000-1000-8000-00805f9b34fb;00001812-0000-1000-8000-00805f9b34fb;
[IdentityResolvingKey]
Key=
[LocalSignatureKey]
Key=
Counter=0
Authenticated=false
[LongTermKey]
Key=
Authenticated=0
EncSize=16
EDiv=
Rand=
[DeviceID]
Source=2
Vendor=1118
Product=2053
Version=272
[ConnectionParameters]
MinInterval=6
MaxInterval=6
Latency=60
Timeout=300
"""
def main():
parser = OptionParser()
parser.add_option("-v", "--verbose", action='store_true', dest='verbose')
parser.add_option("-s", "--system", dest="system", metavar="FILE",
default="/mnt/Windows/System32/config/SYSTEM",
help="SYSTEM file in Windows. Usually at /Windows/System32/config/system.")
parser.add_option("-k", "--key", dest="key", metavar="KEY",
default=r"ControlSet001\Services\BTHPORT\Parameters\Keys",
help="Registry key for BT. [default: %default]")
parser.add_option("-o", "--output", dest="output", metavar="DIR", default="bluetooth",
help="Output directory. [default: %default]")
parser.add_option("-t", "--template", dest="template", metavar="FILE", help="Template file.")
parser.add_option("-a", "--attributes", dest='attributes', help="Additional attributes file to be copied.")
options, args = parser.parse_args()
if options.template:
with open(options.template) as file:
template = file.read()
else:
template = default_template
out = tempfile.mktemp(".reg")
reged = subprocess.Popen(["reged", "-x", options.system, '\\', options.key, out], stdout=sys.stderr)
reged.wait()
if reged.returncode:
return reged.returncode
dump = ConfigParser()
with open(out) as file:
reged_out = file.read()
if options.verbose:
print(reged_out)
dump.read_string(reged_out.split('\n', 1)[1])
os.unlink(out)
for section in dump:
path = section[len(options.key) + 2:].split('\\')
assert not path[0]
if len(path) == 3:
path[1] = ':'.join([path[1][i:i + 2] for i in range(0, len(path[1]), 2)]).upper()
path[2] = ':'.join([path[2][i:i + 2] for i in range(0, len(path[2]), 2)]).upper()
print("Dumping {}/{}...".format(path[1], path[2]))
config = ConfigParser()
config.optionxform = str
# See if device has been paired in Linux before
existing_template = '/var/lib/bluetooth/{}/{}/info'.format(path[1], path[2])
if (os.path.exists(existing_template)):
with open(existing_template) as file:
config.read_string(file.read())
else:
config.read_string(template)
def read_reg(key, expected_type):
def read_reg_actual(key, expected_type):
actual_type, content = dump[section]['"{}"'.format(key)].split(':', 1)
if expected_type == 'hex16':
assert actual_type == 'hex'
content = content.split(',')
assert len(content) == 16
return ''.join(content).upper()
if expected_type == 'qword':
assert actual_type == 'hex(b)'
content = content.split(',')
assert len(content) == 8
return str(int(''.join(content[::-1]), 16))
if expected_type == 'dword':
assert actual_type == expected_type
return str(int(content, 16))
assert False
result = read_reg_actual(key, expected_type)
if options.verbose:
print("{} of type {}: {}".format(key, expected_type, result))
return result
config['LongTermKey']['Key'] = read_reg('LTK', 'hex16')
# KeyLength ignored for now
config['LongTermKey']['Rand'] = read_reg('ERand', 'qword')
config['LongTermKey']['EDiv'] = read_reg('EDIV', 'dword')
if '"IRK"' in dump[section]:
config['IdentityResolvingKey']['Key'] = read_reg('IRK', 'hex16')
if '"CSRK"' in dump[section]:
config['LocalSignatureKey']['Key'] = read_reg('CSRK', 'hex16')
output_dir = os.path.join(options.output, path[1], path[2])
os.makedirs(output_dir, exist_ok=True)
with open(os.path.join(output_dir, 'info'), 'w') as file:
config.write(file, False)
if options.attributes:
shutil.copyfile(options.attributes, os.path.join(output_dir, 'attributes'))
if __name__ == "__main__":
sys.exit(main())
@Konfekt
Copy link

Konfekt commented May 16, 2023

I'm assuming that the path I see in the script isn't working.. i mout windows and linux but maybe it's not enough. I can see my windows file through: **/media/$user/OS ... and so on ..

The MS Windows partition must be mounted to /mnt.
Maybe this could be either made configurable or mentioned in the usage section?

@Konfekt
Copy link

Konfekt commented May 16, 2023

Reading

parser.add_option("-s", "--system", dest="system", metavar="FILE",
                      default="/mnt/Windows/System32/config/SYSTEM",
                      help="SYSTEM file in Windows. Usually at /Windows/System32/config/system.")

it actually is by the -s option but the help entry suggests that the mount point is not.

@pramodhrachuri
Copy link

Any guide on how to use this script?

@Konfekt
Copy link

Konfekt commented May 19, 2023

Download it as export-ble-infos.py, and then run lsblk to find out your Microsoft Windows partition, say /dev/sdb1, mount it by sudo mount /dev/sdb1 /mnt and then run

$ chmod a+x ./export-ble-infos.py
$ ./export-ble-infos.py <args>
$ sudo bash -c 'cp -r ./bluetooth /var/lib && service bluetooth force-reload'
$ rm -r bluetooth

to import all Bluetooth Low Energy devices, such as Microsoft Designer Mouse and Keyboard, from Microsoft Windows >= 10 to Linux.

@escape0707
Copy link

escape0707 commented May 19, 2023

@pramodhrachuri I mean it's literally written in the first screen you'll see when opening this page. If you can read others reply, you can read it too. https://gist.github.com/Mygod/f390aabf53cf1406fc71166a47236ebf#file-export-ble-infos-py-L24

And also python ./export-ble-infos.py --help...

@st4r1ight
Copy link

st4r1ight commented Jun 23, 2023

I was having a similar problem to SpeakingOfBrad in this comment, but with the added bonus issue that my keyboard would change its MAC address every time you connected it to a new device, so getting the script to use its Bluetooth file as a template required some extra work. Here is the full set of steps that I had to take for it to work.

  1. Pair the keyboard in Linux so that a template file is generated for the script to work with
  2. Reboot to Windows and pair the device in Windows. If your keyboard supports more than one device (like some keyboards with 3 connection modes), make sure that you use the same connection every time.
  3. Obtain the device's current MAC address on Windows. I was able to guess what it was because I only have one Bluetooth LE device (so it was the only one that showed up in the export). Otherwise this article may be of use.
  4. Reboot to Linux, and, if you haven't already, install chntpw and mount your Windows partition.
  5. List the currently paired Bluetooth devices with the bluetoothctl devices command. Write down the MAC address (six pairs of hexadecimal digits) of the device that is paired with Linux.
  6. Navigate to /var/lib/bluetooth/. Inside there will be a folder named for your computer's Bluetooth MAC address. cd into it and you will see folders named for Bluetooth MAC address. Rename the one that corresponds to your Linux MAC (that you obtained from bluetoothctl devices) to the device's Windows MAC address. You will probably want to be in a root shell for this (sudo -i) since /var/lib/bluetooth is not readable by the user.
  7. Run the export-ble-infos.py script WITH SUDO so that it can see your current Bluetooth devices.
  8. Copy the bluetooth folder that the script creates into /var/lib (sudo cp -r ./bluetooth/ /var/lib). cp will ask if you want to replace info files, say yes.
  9. Restart Bluetooth (systemctl restart bluetooth)
  10. Backup your Bluetooth folder so you never ever have to do that again.

@sir-farfan
Copy link

Is there a script like this one for non-ble devices?

@Konfekt
Copy link

Konfekt commented Jul 17, 2023

You could try bt-dualboot

@sir-farfan
Copy link

Thanks o:

@stevemphillips
Copy link

I'm trying this and no output directory is created. Any idea what is wrong?

This is the command and console output:

steve@aragorn:~$ sudo python3 ./export-ble-infos.py -s /media/steve/BOOTCAMP/Windows/System32/config/SYSTEM
reged version 0.1 140201, (c) Petter N Hagen
Exporting to file '/tmp/tmpfkmkggfk.reg'...
Exporting key 'Keys' with 1 subkeys and 0 values...
Exporting key '5cf370809d45' with 0 subkeys and 3 values...

Should the output directory be in my current directory or somewhere else?

@StfnJ
Copy link

StfnJ commented Nov 26, 2024

This used to work for me, but after upgrading to Fedora 37 (not sure if its related), the following fields appears on my info file:

  • [RemoteSignatureKey]
  • [PeripheralLongTermKey]
  • [SlaveLongTermKey]

Both [PeripheralLongTermKey] and [SlaveLongTermKey] with their own EDiv and ERand, but there's no corresponding Key on the Windows registry. Have you guys ever had this happen?

In case someone ends up here as I did - I think PeripheralLongTermKey / SlaveLongTermKey can be left as is at least in some cases. That seemed to be the case for me (Keychron V6 Max keyboard, dualboot Windows 10 + Ubuntu 24), where switching the keys for LTK (along with Rand and EDiv) and IRK sufficed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment