Skip to content

Instantly share code, notes, and snippets.

@Mygod
Last active October 25, 2025 18:10
Show Gist options
  • Select an option

  • Save Mygod/f390aabf53cf1406fc71166a47236ebf to your computer and use it in GitHub Desktop.

Select an option

Save Mygod/f390aabf53cf1406fc71166a47236ebf to your computer and use it in GitHub Desktop.

Revisions

  1. Mygod revised this gist Aug 28, 2022. 1 changed file with 2 additions and 1 deletion.
    3 changes: 2 additions & 1 deletion export-ble-infos.py
    Original file line number Diff line number Diff line change
    @@ -153,7 +153,8 @@ def read_reg_actual(key, expected_type):
    # KeyLength ignored for now
    config['LongTermKey']['Rand'] = read_reg('ERand', 'qword')
    config['LongTermKey']['EDiv'] = read_reg('EDIV', 'dword')
    config['IdentityResolvingKey']['Key'] = read_reg('IRK', 'hex16')
    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])
  2. Mygod revised this gist Apr 15, 2021. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion export-ble-infos.py
    Original file line number Diff line number Diff line change
    @@ -154,7 +154,7 @@ def read_reg_actual(key, expected_type):
    config['LongTermKey']['Rand'] = read_reg('ERand', 'qword')
    config['LongTermKey']['EDiv'] = read_reg('EDIV', 'dword')
    config['IdentityResolvingKey']['Key'] = read_reg('IRK', 'hex16')
    if 'CSRK' in dump[section]:
    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)
  3. Mygod revised this gist Feb 25, 2021. 1 changed file with 16 additions and 1 deletion.
    17 changes: 16 additions & 1 deletion export-ble-infos.py
    Original file line number Diff line number Diff line change
    @@ -1,6 +1,21 @@
    #!/usr/bin/python3
    """
    Export your Windows Bluetooth LE keys into Linux!
    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
  4. Mygod revised this gist May 29, 2020. 1 changed file with 3 additions and 1 deletion.
    4 changes: 3 additions & 1 deletion export-ble-infos.py
    Original file line number Diff line number Diff line change
    @@ -2,7 +2,9 @@
    """
    Export your Windows Bluetooth LE keys into Linux!
    Thanks to: http://console.systems/2014/09/how-to-pair-low-energy-le-bluetooth.html
    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:
  5. Mygod revised this gist May 29, 2020. 1 changed file with 11 additions and 3 deletions.
    14 changes: 11 additions & 3 deletions export-ble-infos.py
    Original file line number Diff line number Diff line change
    @@ -63,7 +63,7 @@ def main():
    parser = OptionParser()
    parser.add_option("-v", "--verbose", action='store_true', dest='verbose')
    parser.add_option("-s", "--system", dest="system", metavar="FILE",
    default="/media/mygod/Windows/Windows/System32/config/system",
    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",
    @@ -102,7 +102,14 @@ def main():
    print("Dumping {}/{}...".format(path[1], path[2]))
    config = ConfigParser()
    config.optionxform = str
    config.read_string(template)

    # 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):
    @@ -130,7 +137,8 @@ def read_reg_actual(key, expected_type):
    config['LongTermKey']['Rand'] = read_reg('ERand', 'qword')
    config['LongTermKey']['EDiv'] = read_reg('EDIV', 'dword')
    config['IdentityResolvingKey']['Key'] = read_reg('IRK', 'hex16')
    config['LocalSignatureKey']['Key'] = read_reg('CSRK', '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:
  6. Mygod revised this gist Nov 17, 2017. 1 changed file with 2 additions and 2 deletions.
    4 changes: 2 additions & 2 deletions export-ble-infos.py
    Original file line number Diff line number Diff line change
    @@ -7,8 +7,8 @@
    Usage:
    $ ./export-ble-infos.py <args>
    # cp -r ./bluetooth /var/lib
    # service bluetooth force-reload
    $ sudo bash -c 'cp -r ./bluetooth /var/lib && service bluetooth force-reload'
    $ rm -r bluetooth
    """

    import os
  7. Mygod revised this gist Sep 30, 2017. 1 changed file with 0 additions and 1 deletion.
    1 change: 0 additions & 1 deletion export-ble-infos.py
    Original file line number Diff line number Diff line change
    @@ -7,7 +7,6 @@
    Usage:
    $ ./export-ble-infos.py <args>
    # service bluetooth stop
    # cp -r ./bluetooth /var/lib
    # service bluetooth force-reload
    """
  8. Mygod revised this gist Sep 26, 2017. 1 changed file with 2 additions and 2 deletions.
    4 changes: 2 additions & 2 deletions export-ble-infos.py
    Original file line number Diff line number Diff line change
    @@ -115,9 +115,9 @@ def read_reg_actual(key, expected_type):
    return ''.join(content).upper()
    if expected_type == 'qword':
    assert actual_type == 'hex(b)'
    content = content.split(',')[::-1]
    content = content.split(',')
    assert len(content) == 8
    return str(int(''.join(content), 16))
    return str(int(''.join(content[::-1]), 16))
    if expected_type == 'dword':
    assert actual_type == expected_type
    return str(int(content, 16))
  9. Mygod revised this gist Sep 26, 2017. 1 changed file with 1 addition and 2 deletions.
    3 changes: 1 addition & 2 deletions export-ble-infos.py
    Original file line number Diff line number Diff line change
    @@ -13,15 +13,14 @@
    """

    import os
    import shutil
    import subprocess
    import sys
    import tempfile

    from configparser import ConfigParser
    from optparse import OptionParser

    import shutil

    default_template = """
    [General]
    Name=Designer Mouse
  10. Mygod revised this gist Sep 26, 2017. 1 changed file with 32 additions and 17 deletions.
    49 changes: 32 additions & 17 deletions export-ble-infos.py
    Original file line number Diff line number Diff line change
    @@ -8,7 +8,7 @@
    $ ./export-ble-infos.py <args>
    # service bluetooth stop
    # cp -r ./bluetooth /var/lib/bluetooth
    # cp -r ./bluetooth /var/lib
    # service bluetooth force-reload
    """

    @@ -20,6 +20,8 @@
    from configparser import ConfigParser
    from optparse import OptionParser

    import shutil

    default_template = """
    [General]
    Name=Designer Mouse
    @@ -61,6 +63,7 @@

    def main():
    parser = OptionParser()
    parser.add_option("-v", "--verbose", action='store_true', dest='verbose')
    parser.add_option("-s", "--system", dest="system", metavar="FILE",
    default="/media/mygod/Windows/Windows/System32/config/system",
    help="SYSTEM file in Windows. Usually at /Windows/System32/config/system.")
    @@ -70,6 +73,7 @@ def main():
    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:
    @@ -85,12 +89,15 @@ def main():
    return reged.returncode
    dump = ConfigParser()
    with open(out) as file:
    dump.read_string(file.read().split('\n', 1)[1])
    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])
    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()
    @@ -100,20 +107,26 @@ def main():
    config.read_string(template)

    def read_reg(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(',')[::-1]
    assert(len(content) == 8)
    return str(int(''.join(content), 16))
    if expected_type == 'dword':
    assert(actual_type == expected_type)
    return str(int(content, 16))
    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(',')[::-1]
    assert len(content) == 8
    return str(int(''.join(content), 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')
    @@ -124,6 +137,8 @@ def read_reg(key, expected_type):
    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__":
  11. Mygod revised this gist Sep 26, 2017. 1 changed file with 4 additions and 1 deletion.
    5 changes: 4 additions & 1 deletion export-ble-infos.py
    Original file line number Diff line number Diff line change
    @@ -79,7 +79,10 @@ def main():
    template = default_template

    out = tempfile.mktemp(".reg")
    subprocess.Popen(["reged", "-x", options.system, '\\', options.key, out], stdout=sys.stderr).wait()
    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:
    dump.read_string(file.read().split('\n', 1)[1])
  12. Mygod revised this gist Sep 26, 2017. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion export-ble-infos.py
    Original file line number Diff line number Diff line change
    @@ -4,7 +4,7 @@
    Thanks to: http://console.systems/2014/09/how-to-pair-low-energy-le-bluetooth.html
    Usage: Edit the template below and fire up a terminal.
    Usage:
    $ ./export-ble-infos.py <args>
    # service bluetooth stop
  13. Mygod revised this gist Sep 26, 2017. 1 changed file with 2 additions and 0 deletions.
    2 changes: 2 additions & 0 deletions export-ble-infos.py
    Original file line number Diff line number Diff line change
    @@ -2,6 +2,8 @@
    """
    Export your Windows Bluetooth LE keys into Linux!
    Thanks to: http://console.systems/2014/09/how-to-pair-low-energy-le-bluetooth.html
    Usage: Edit the template below and fire up a terminal.
    $ ./export-ble-infos.py <args>
  14. Mygod created this gist Sep 26, 2017.
    125 changes: 125 additions & 0 deletions export-ble-infos.py
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,125 @@
    #!/usr/bin/python3
    """
    Export your Windows Bluetooth LE keys into Linux!
    Usage: Edit the template below and fire up a terminal.
    $ ./export-ble-infos.py <args>
    # service bluetooth stop
    # cp -r ./bluetooth /var/lib/bluetooth
    # service bluetooth force-reload
    """

    import os
    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("-s", "--system", dest="system", metavar="FILE",
    default="/media/mygod/Windows/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.")
    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")
    subprocess.Popen(["reged", "-x", options.system, '\\', options.key, out], stdout=sys.stderr).wait()
    dump = ConfigParser()
    with open(out) as file:
    dump.read_string(file.read().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
    config.read_string(template)

    def read_reg(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(',')[::-1]
    assert(len(content) == 8)
    return str(int(''.join(content), 16))
    if expected_type == 'dword':
    assert(actual_type == expected_type)
    return str(int(content, 16))
    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')
    config['IdentityResolvingKey']['Key'] = read_reg('IRK', 'hex16')
    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 __name__ == "__main__":
    sys.exit(main())