├── .github └── FUNDING.yml ├── Bitlocker Key Finder v3.1 ├── Bitlocker Key Finder v3.2.py ├── Bitlocker_Key_Finder v3.0 ├── Bitlocker_Key_Finder.py ├── Bitlocker_Key_FinderGUI.py ├── Bitlocker_Key_Finderv3.3.py ├── LICENSE └── README.md /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: northloopforensics 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 12 | polar: # Replace with a single Polar username 13 | buy_me_a_coffee: # Replace with a single Buy Me a Coffee username 14 | thanks_dev: # Replace with a single thanks.dev username 15 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 16 | -------------------------------------------------------------------------------- /Bitlocker Key Finder v3.1: -------------------------------------------------------------------------------- 1 | #Python3 2 | #Bitlocker_Key_FinderGUI v3 3 | import re 4 | import os 5 | import fnmatch 6 | import shutil 7 | import PySimpleGUI as sg 8 | import subprocess 9 | import string 10 | import ctypes, os 11 | import datetime 12 | 13 | pattern = re.compile(r"\d{6}-\d{6}-\d{6}-\d{6}-\d{6}-\d{6}-\d{6}-\d{6}") 14 | Bit_Keys = [] 15 | txt_Files = [] 16 | now = datetime.datetime.now() 17 | 18 | def isAdmin(): #Checks admin status of the program 19 | try: 20 | is_admin = (os.getuid() == 0) 21 | except AttributeError: 22 | is_admin = ctypes.windll.shell32.IsUserAnAdmin() != 0 23 | return is_admin 24 | 25 | def walk(): #Walks the directory tree to find txt files 26 | for root, dirs, file in folder: 27 | for filename in file: 28 | if filename.endswith(('.txt', '.TXT', '.bek', '.BEK')): #filters to txt and bek files 29 | txt_Files.append(os.path.join(root, filename)) #creates list of txt and bek files 30 | 31 | def name_search(): #Finds txt and bek files and adds them to the bit_keys list 32 | 33 | for ele in txt_Files: 34 | if fnmatch.fnmatch(ele, "*BitLocker Recovery Key*"): 35 | print(ele + '\n') 36 | Bit_Keys.append(ele) 37 | if fnmatch.fnmatch(ele, "*.BEK"): 38 | print(ele + '\n') 39 | Bit_Keys.append(ele) 40 | 41 | def string_search(): #Regex for key values 42 | for ele in txt_Files: 43 | try: 44 | too_big = os.path.getsize(ele) 45 | if too_big >= 1048576: #Check file size is less than 10mb 46 | pass 47 | else: 48 | with open(ele, 'r', encoding="utf-16-le") as text: #this encoding is the default used by Microsoft in creating the txt files 49 | text = text.read() 50 | k = re.findall(pattern, text) 51 | for key in k: 52 | Bit_Keys.append(ele) 53 | print(ele + " - " + key + '\n') 54 | except UnicodeDecodeError: 55 | pass 56 | except PermissionError: 57 | pass 58 | 59 | def copy_key_files(): #COPIES THE KEYS FROM BIT_KEYS LIST TO USER SELECTED DIRECTORY 60 | destination = values['OUTPUT'] 61 | print("*** COPYING KEY RELATED FILES... ***") 62 | copy_list = [] 63 | for i in Bit_Keys: 64 | if i not in copy_list: 65 | copy_list.append(i) 66 | try: 67 | for key_File in copy_list: 68 | shutil.copy(key_File, destination) 69 | 70 | except Exception: 71 | print(key_File +' - Error Copying- check for user access to file or name collision in destination') 72 | pass 73 | print("\n*** FILES COPIED TO DESTINATION DIRECTORY. ***\n") 74 | 75 | def get_active_keys(): #Searches for key values for mounted volumes 76 | print("\n*** TRIAGING KEYS ON CURRENT SYSTEM ***\n") 77 | if isAdmin(): #checks admin status before proceeding 78 | print("\nRunning as Admin...") 79 | Drive_letters = ['%s:' % d for d in string.ascii_uppercase if os.path.exists('%s:' % d)] #Produces list of volumes on target system 80 | comp_name = os.environ['COMPUTERNAME'] #gets target computer name for report title 81 | comp_name = comp_name.strip('\\') 82 | key_report = os.path.join(values['OUTPUT'], comp_name + '-BitlockerReport.txt') 83 | with open(key_report, 'w') as report: 84 | report.write("Bitlocker Key Finder v3.0 \n") #writing the header for the report 1) Version 2) Date 3)User of System 85 | report.write(now) 86 | report.write("\nUser Account Used: ") 87 | report.write(os.getlogin()) 88 | report.write("\n\n") 89 | for drive in Drive_letters: 90 | try: 91 | mng_bde = subprocess.check_output(["manage-bde", "-protectors", drive, "-get"]) #runs the manage-bde query for each drive letter in drive letter list 92 | mng_bde = (mng_bde.splitlines(True)) 93 | print(drive, " - Key protectors found!\n") 94 | for drive_state in mng_bde: 95 | drive_state = drive_state.decode("utf-8") 96 | print(drive_state) 97 | report.write(drive_state) #writes query output to file 98 | print('\n') 99 | except: 100 | subprocess.CalledProcessError 101 | print(drive, ' - No key protectors found. Not Bitlocked. ') 102 | else: 103 | print("Not running as Admin! Restart as Admin to continue.") 104 | 105 | working_Dir = os.getcwd() #Used for default output folder 106 | 107 | sg.theme('LightGrey2') 108 | 109 | layout = [ [sg.Text('Bitlocker Key Finder', size=(18, 1), font=('Impact', 25, 'bold italic'))], 110 | [sg.Text('')], 111 | [sg.Text('Find Saved Bitlocker .TXT and .BEK Files', font=('Arial', 12,'bold'))], 112 | [sg.Text('Select Volume or Directory to Search:'),sg.Input(key='SOURCE',), sg.FolderBrowse(key='SOURCE')], 113 | [sg.Checkbox('File Name Search', key="FILENAME"), sg.Checkbox('String Pattern Search', key="REG")], 114 | #[sg.Text('_'*82)], 115 | [sg.Checkbox('Copy responsive files to Output Directory', enable_events=True ,key="COPYSWITCH")], 116 | [sg.Text('')], 117 | [sg.Text('Recover Keys from Current Machine', font=('Arial', 12,'bold'))], 118 | [sg.Checkbox('Save keys for mounted volumes to Output Directory - MUST BE RUN AS ADMIN', key="MGBDE")], 119 | [sg.Text('')], 120 | [sg.Text(" Output Directory: ", font=('Arial', 11, 'bold')), sg.Input(key='OUTPUT', default_text=working_Dir, disabled=False), sg.FolderBrowse(key='OUTPUT1', enable_events=True, disabled=False)], 121 | # [sg.Output(size=(85,10),)], 122 | [sg.Text('')], 123 | [sg.Button('Find Keys', key='Ok'), sg.Text(' '*125), sg.Button('?', key='HELP')]] 124 | 125 | # Create the Window 126 | window = sg.Window('North Loop Consulting', layout, no_titlebar=False, alpha_channel=1, grab_anywhere=False) 127 | # Event Loop to process "events" and get the "values" of the inputs 128 | 129 | while True: 130 | event, values = window.read() 131 | if event == sg.WIN_CLOSED or event == 'Exit': # if user closes window or clicks cancel 132 | break 133 | if event == 'HELP': 134 | sg.Popup("FOR BEST RESULTS RUN AS ADMIN \n\nThis tool seeks to find Bitlocker Recovery Keys with a focus for on-scene triage. On opening the tool you will see both an interface window to input selections and a console window which will display search results. Closing one window will close the other. \n\nThe tool uses three methods to recover keys: \n\nThe first method searches for file names consistent with Recovery Key files including .TXT and .BEK file extensions. This method returns the file's path. \n\nThe second method performs pattern searches for key values in text files encoded in UTF 16 LE. This method returns the file's path and the string hit.\n\nIf you are running both methods, expect duplicate file hits. \n\nFiles meeting your search criteria can be saved to an output folder of your choice.\n\nThe third method makes use of the manage-bde interface to collect keys for mounted volumes on an active system and save those keys to a text file. \n\nNo warranty or guarantee is offered with this tool. Use at your own risk. \n\nCopyright 2021 North Loop Consulting\n") 135 | window.refresh() 136 | elif event == 'Ok': 137 | folder = os.walk(values["SOURCE"]) 138 | print("*** SEARCHING FOR KEY FILES... ***") 139 | walk() 140 | if values["FILENAME"] == True: 141 | print("Searching for file names in " + values['SOURCE']) 142 | name_search() 143 | if values["REG"] == True: 144 | print("Conducting string search in " + values['SOURCE']) 145 | string_search() 146 | if values['COPYSWITCH'] == True: 147 | copy_key_files() 148 | if values['MGBDE'] == True: 149 | get_active_keys() 150 | print("\n****** COMPLETE ******") 151 | window.refresh() 152 | window.close() 153 | -------------------------------------------------------------------------------- /Bitlocker Key Finder v3.2.py: -------------------------------------------------------------------------------- 1 | #Bitlocker Key Finder v3.2 2 | import re 3 | import os 4 | import fnmatch 5 | import shutil 6 | import subprocess 7 | import string 8 | import ctypes 9 | import chardet 10 | import datetime 11 | import tkinter as tk 12 | from tkinter import filedialog, messagebox, ttk 13 | import threading 14 | 15 | pattern = re.compile(r"\d{6}-\d{6}-\d{6}-\d{6}-\d{6}-\d{6}-\d{6}-\d{6}") 16 | Bit_Keys = [] 17 | txt_Files = [] 18 | Collected_Keys = [] 19 | now = datetime.datetime.now() 20 | 21 | # STARTUPINFO to hide the command windows 22 | startupinfo = subprocess.STARTUPINFO() 23 | startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW 24 | startupinfo.wShowWindow = subprocess.SW_HIDE 25 | 26 | def isAdmin(): 27 | try: 28 | is_admin = (os.getuid() == 0) 29 | except AttributeError: 30 | is_admin = ctypes.windll.shell32.IsUserAnAdmin() != 0 31 | return is_admin 32 | 33 | def walk(folder): 34 | for root, dirs, files in os.walk(folder): 35 | for filename in files: 36 | if filename.endswith(('.txt', '.TXT', '.bek', '.BEK')): 37 | txt_Files.append(os.path.join(root, filename)) 38 | 39 | def name_search(gui): 40 | for ele in txt_Files: 41 | if fnmatch.fnmatch(ele, "*BitLocker Recovery Key*"): 42 | Bit_Keys.append(ele) 43 | gui.log_message(f"Found BitLocker Recovery Key file: {ele}", "success") 44 | if fnmatch.fnmatch(ele, "*.BEK"): 45 | Bit_Keys.append(ele) 46 | gui.log_message(f"Found BEK file: {ele}", "success") 47 | 48 | def exhaustive_string_search(gui): 49 | for ele in txt_Files: 50 | try: 51 | if not os.path.exists(ele): 52 | gui.log_message(f"File not found: {ele}", "warning") 53 | continue 54 | 55 | too_big = os.path.getsize(ele) 56 | if too_big >= 1048576: # Skip files larger than 1MB 57 | print(f"File too large (>1MB), skipping: {ele}", "warning") 58 | continue 59 | 60 | # Read the raw contents of the file 61 | with open(ele, 'rb') as raw_file: 62 | raw_content = raw_file.read() 63 | 64 | # Detect the encoding 65 | detected = chardet.detect(raw_content) 66 | encoding = detected['encoding'] 67 | 68 | try: 69 | decoded_content = raw_content.decode(encoding) 70 | k = re.findall(pattern, decoded_content) 71 | for key in k: 72 | Bit_Keys.append(ele) 73 | gui.log_message(f"Found BitLocker key in file: {ele}", "success") 74 | gui.log_message(f"Key: {key}", "info") 75 | Collected_Keys.append(key) 76 | except UnicodeDecodeError: 77 | gui.log_message(f"Unable to decode file as {encoding}: {ele}", "warning") 78 | 79 | except FileNotFoundError: 80 | gui.log_message(f"File not found: {ele}", "warning") 81 | except PermissionError: 82 | gui.log_message(f"Permission denied: {ele}", "warning") 83 | except Exception as e: 84 | print(f"Error processing file {ele}: {str(e)}", "error") 85 | 86 | def UTF16LE_string_search(gui): 87 | for ele in txt_Files: 88 | try: 89 | if not os.path.exists(ele): 90 | gui.log_message(f"File not found: {ele}", "warning") 91 | continue 92 | 93 | too_big = os.path.getsize(ele) 94 | if too_big >= 1048576: # Skip files larger than 1MB 95 | print(f"File too large (>1MB), skipping: {ele}", "warning") 96 | continue 97 | 98 | # Read the raw contents of the file 99 | with open(ele, 'rb') as raw_file: 100 | raw_content = raw_file.read() 101 | 102 | try: 103 | decoded_content = raw_content.decode('utf-16-le') 104 | k = re.findall(pattern, decoded_content) 105 | for key in k: 106 | Bit_Keys.append(ele) 107 | gui.log_message(f"Found BitLocker key in file: {ele}", "success") 108 | gui.log_message(f"Key: {key}", "info") 109 | Collected_Keys.append(key) 110 | except UnicodeDecodeError: 111 | # gui.log_message(f"Unable to decode file as UTF-16LE: {ele}", "warning") 112 | pass 113 | else: 114 | print(f"File is not UTF-16LE encoded, skipping: {ele}", "info") 115 | 116 | except FileNotFoundError: 117 | gui.log_message(f"File not found: {ele}", "warning") 118 | except PermissionError: 119 | gui.log_message(f"Permission denied: {ele}", "warning") 120 | except Exception as e: 121 | gui.log_message(f"Error processing file {ele}: {str(e)}", "error") 122 | 123 | class BitlockerKeyFinderGUI: 124 | def __init__(self, master): 125 | self.master = master 126 | master.title("North Loop Consulting - Bitlocker Key Finder v3.2") 127 | master.geometry("800x600") 128 | master.configure(bg="#f0f0f0") 129 | 130 | self.create_widgets() 131 | self.master.after(100, self.periodic_refresh) # Start periodic refresh 132 | 133 | def create_widgets(self): 134 | 135 | # Find Saved Bitlocker .TXT and .BEK Files 136 | tk.Label(self.master, text="Find Saved Bitlocker .TXT and .BEK Files", font=("Arial", 12, "bold"), bg="#f0f0f0").pack(pady=5) 137 | 138 | # Source Directory 139 | source_frame = tk.Frame(self.master, bg="#f0f0f0") 140 | source_frame.pack(fill="x", padx=10, pady=5) 141 | tk.Label(source_frame, text="Select Volume or Directory to Search:", bg="#f0f0f0").pack(side="left") 142 | self.source_entry = tk.Entry(source_frame, width=50) 143 | self.source_entry.pack(side="left", expand=True, fill="x", padx=5) 144 | tk.Button(source_frame, text="Browse", command=self.browse_source, bg="#4CAF50", fg="white", relief=tk.RAISED).pack(side="left") 145 | 146 | # Checkboxes for search options 147 | checkbox_frame = tk.Frame(self.master, bg="#f0f0f0") 148 | checkbox_frame.pack(pady=5) 149 | self.filename_var = tk.BooleanVar() 150 | tk.Checkbutton(checkbox_frame, text="File Name Search (Fast)", variable=self.filename_var, bg="#f0f0f0").pack(side="left") 151 | 152 | self.utf16le_search_var = tk.BooleanVar() 153 | tk.Checkbutton(checkbox_frame, text="UTF-16LE String Search", variable=self.utf16le_search_var, bg="#f0f0f0").pack(side="left") 154 | 155 | self.exhaustive_search_var = tk.BooleanVar() 156 | tk.Checkbutton(checkbox_frame, text="Exhaustive String Search (Slow)", variable=self.exhaustive_search_var, bg="#f0f0f0").pack(side="left") 157 | 158 | # Copy Files Option 159 | self.copy_var = tk.BooleanVar() 160 | tk.Checkbutton(self.master, text="Copy responsive files to Output Directory", variable=self.copy_var, bg="#f0f0f0").pack(pady=5) 161 | 162 | # Recover Keys from Current Machine 163 | tk.Label(self.master, text="Recover Keys from Current Machine", font=("Arial", 12, "bold"), bg="#f0f0f0").pack(pady=5) 164 | self.mgbde_var = tk.BooleanVar() 165 | tk.Checkbutton(self.master, text="ADMIN ONLY - Save keys for mounted volumes to Output Directory", variable=self.mgbde_var, bg="#f0f0f0").pack() 166 | 167 | # Output Directory 168 | output_frame = tk.Frame(self.master, bg="#f0f0f0") 169 | output_frame.pack(fill="x", padx=10, pady=5) 170 | tk.Label(output_frame, text="Output Directory:", font=("Arial", 11, "bold"), bg="#f0f0f0").pack(side="left") 171 | self.output_entry = tk.Entry(output_frame, width=50) 172 | self.output_entry.pack(side="left", expand=True, fill="x", padx=5) 173 | self.output_entry.insert(0, os.getcwd()) 174 | tk.Button(output_frame, text="Browse", command=self.browse_output, bg="#4CAF50", fg="white", relief=tk.RAISED).pack(side="left") 175 | 176 | # Buttons 177 | button_frame = tk.Frame(self.master, bg="#f0f0f0") 178 | button_frame.pack(pady=10) 179 | self.find_keys_button = tk.Button(button_frame, text="Find Keys", command=self.start_find_keys_thread, bg="#2196F3", fg="white", relief=tk.RAISED) 180 | self.find_keys_button.pack(side="left", padx=5) 181 | 182 | tk.Button(button_frame, text="Help", command=self.show_help, bg="orange", fg="black", relief=tk.RAISED).pack(side="left", padx=5) 183 | 184 | # Console 185 | console_frame = tk.Frame(self.master, bg="#333333") 186 | console_frame.pack(fill="both", expand=True, padx=10, pady=5) 187 | # tk.Label(console_frame, text="Console Output", font=("Arial", 12, "bold"), bg="#333333", fg="white").pack() 188 | self.console = tk.Text(console_frame, wrap="word", height=15, bg="#ffffff", fg="white") 189 | self.console.pack(fill="both", expand=True) 190 | self.console.tag_configure("info", foreground="blue") 191 | self.console.tag_configure("warning", foreground="orange") 192 | self.console.tag_configure("error", foreground="red") 193 | self.console.tag_configure("success", foreground="black") 194 | self.console.tag_configure("bold", font=("Arial", 10,)) 195 | 196 | clear_frame = tk.Frame(self.master, bg="#f0f0f0") 197 | clear_frame.pack(fill="x", padx=10, pady=(0, 10)) # Add padding at the bottom 198 | tk.Button(clear_frame, text="Clear Window", command=self.clear_console, bg="#FF5722", fg="white", relief=tk.RAISED).pack() 199 | 200 | 201 | def browse_source(self): 202 | folder_path = filedialog.askdirectory() 203 | if folder_path: 204 | self.source_entry.delete(0, tk.END) 205 | self.source_entry.insert(0, folder_path) 206 | 207 | def browse_output(self): 208 | folder_path = filedialog.askdirectory() 209 | if folder_path: 210 | self.output_entry.delete(0, tk.END) 211 | self.output_entry.insert(0, folder_path) 212 | 213 | def show_help(self): 214 | help_message = ( 215 | "Copyright 2024 North Loop Consulting\n" 216 | "Bitlocker Key Finder\n\n" 217 | "1. Select the directory to search for Bitlocker Recovery Keys or BEK files.\n" 218 | "2. Choose search options:\n" 219 | " - File Name Search: A quick search for file names consistent with key files.\n" 220 | " - UTF-16LE String Search: Searches for Bitlocker keys in UTF-16LE encoded files.\n" 221 | " - Exhaustive String Search: Performs a search of all .txt files smaller than 1MB for keys.\n" 222 | "3. Optionally, enable the Copy Files option to copy found files to the output directory.\n" 223 | "4. Optionally, enable the recovery of keys from the current machine (ADMIN ONLY).\n" 224 | "5. Choose the output directory to save results.\n" 225 | "6. Click 'Find Keys' to start the search." 226 | ) 227 | messagebox.showinfo("Help", help_message) 228 | 229 | def start_find_keys_thread(self): 230 | # Disable the Find Keys button 231 | self.find_keys_button.config(state=tk.DISABLED) 232 | # Start the find keys process in a separate thread 233 | thread = threading.Thread(target=self.find_keys) 234 | thread.start() 235 | 236 | def find_keys(self): 237 | global Bit_Keys, txt_Files 238 | Bit_Keys = [] 239 | txt_Files = [] 240 | folder_path = self.source_entry.get() 241 | 242 | # Traverse the directory and find .txt and .BEK files 243 | walk(folder_path) 244 | 245 | if self.filename_var.get(): 246 | self.log_message(f"Searching for file names in {folder_path}", "info") 247 | name_search(self) 248 | 249 | if self.exhaustive_search_var.get(): 250 | self.log_message(f"Conducting exhaustive string search in {folder_path}", "info") 251 | exhaustive_string_search(self) 252 | 253 | if self.utf16le_search_var.get(): 254 | self.log_message(f"Conducting UTF-16LE string search in {folder_path}", "info") 255 | UTF16LE_string_search(self) 256 | 257 | self.log_message(f"Total BitLocker keys/files found: {len(Bit_Keys)}", "success") 258 | 259 | if self.copy_var.get(): 260 | self.copy_key_files() 261 | 262 | if self.mgbde_var.get(): 263 | self.get_active_keys() 264 | 265 | self.log_message("SEARCH COMPLETE", "success") 266 | 267 | # Re-enable the Find Keys button 268 | self.find_keys_button.config(state=tk.NORMAL) 269 | 270 | def copy_key_files(self): 271 | output_folder = self.output_entry.get() 272 | if not os.path.isdir(output_folder): 273 | self.log_message("Invalid output directory. Please select a valid directory.", "warning") 274 | return 275 | for file in Bit_Keys: 276 | try: 277 | shutil.copy(file, output_folder) 278 | self.log_message(f"Copied file: {file}", "success") 279 | except Exception as e: 280 | self.log_message(f"Error copying file {file}: {str(e)}", "error") 281 | 282 | def get_active_keys(self): 283 | if not isAdmin(): 284 | self.log_message("Admin rights are required to retrieve BitLocker keys.", "warning") 285 | return 286 | output_folder = self.output_entry.get() 287 | comp_name = os.environ['COMPUTERNAME'] #gets target computer name for report title 288 | comp_name = comp_name.strip('\\') 289 | key_report = os.path.join(output_folder, comp_name + '-BitlockerReport.txt') 290 | Drive_letters = ['%s:' % d for d in string.ascii_uppercase if os.path.exists('%s:' % d)] #Produces list of volumes on target system 291 | 292 | 293 | if not os.path.isdir(output_folder): 294 | self.log_message("Invalid output directory. Please select a valid directory.", "warning") 295 | return 296 | with open(key_report, 'w') as report: 297 | report.write("Bitlocker Key Finder v3.0 \n") #writing the header for the report 1) Version 2) Date 3)User of System 298 | report.write(now.strftime("%Y-%m-%d, %H:%M:%S")) 299 | report.write("\nUser Account Used: ") 300 | report.write(os.getlogin()) 301 | report.write("\n\n") 302 | try: 303 | volumes = subprocess.check_output(["manage-bde", "-status"], startupinfo=startupinfo).decode("utf-8") 304 | self.log_message(volumes, "info") 305 | volume_lines = volumes.splitlines() 306 | with open(key_report, "a") as key_file: 307 | for line in volume_lines: 308 | 309 | if "Volume " in line: 310 | volume = line.split()[1] 311 | print(volume) 312 | try: 313 | recovery_keys = subprocess.check_output(["manage-bde", "-protectors", "-get", volume], startupinfo=startupinfo).decode("utf-8") 314 | key_file.write(f"Bitlocker key found for {volume}!\n\n") 315 | key_file.write(recovery_keys) 316 | self.log_message(f"BitLocker key for volume {volume} written to report at {key_report}", "success") 317 | # self.log_message(f"{recovery_keys}", "info") 318 | except subprocess.CalledProcessError: 319 | # self.log_message(f"No BitLocker credentials found for {volume}", "warning") 320 | key_file.write(f"No BitLocker credentials found for {volume}\n\n") 321 | except Exception as e: 322 | self.log_message(f"Error retrieving BitLocker keys: {str(e)}", "error") 323 | 324 | 325 | def log_message(self, message, level="info"): 326 | timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") 327 | formatted_message = f"[{timestamp}] {message}\n" 328 | self.console.insert(tk.END, formatted_message, (level, "bold")) 329 | self.console.see(tk.END) 330 | self.master.after(100, lambda: self.master.update_idletasks()) # Regular GUI update 331 | 332 | def clear_console(self): 333 | self.console.delete(1.0, tk.END) 334 | 335 | def periodic_refresh(self): 336 | self.master.update_idletasks() 337 | self.master.after(100, self.periodic_refresh) 338 | 339 | # Create and run the Tkinter application 340 | root = tk.Tk() 341 | app = BitlockerKeyFinderGUI(root) 342 | root.mainloop() 343 | -------------------------------------------------------------------------------- /Bitlocker_Key_Finder v3.0: -------------------------------------------------------------------------------- 1 | #Python3 2 | #Bitlocker_Key_FinderGUI v3 3 | import re 4 | import os 5 | import fnmatch 6 | import shutil 7 | import PySimpleGUI as sg 8 | import subprocess 9 | import string 10 | import ctypes, os 11 | import datetime 12 | 13 | pattern = re.compile(r"\d{6}-\d{6}-\d{6}-\d{6}-\d{6}-\d{6}-\d{6}-\d{6}") 14 | Bit_Keys = [] 15 | txt_Files = [] 16 | now = datetime.datetime.now() 17 | 18 | def isAdmin(): #Checks admin status of the program 19 | try: 20 | is_admin = (os.getuid() == 0) 21 | except AttributeError: 22 | is_admin = ctypes.windll.shell32.IsUserAnAdmin() != 0 23 | return is_admin 24 | 25 | def walk(): #Walks the directory tree to find txt files 26 | for root, dirs, file in folder: 27 | for filename in file: 28 | if filename.endswith(('.txt', '.TXT', '.bek', '.BEK')): #filters to txt and bek files 29 | txt_Files.append(os.path.join(root, filename)) #creates list of txt and bek files 30 | 31 | def name_search(): #Finds txt and bek files and adds them to the bit_keys list 32 | 33 | for ele in txt_Files: 34 | if fnmatch.fnmatch(ele, "*BitLocker Recovery Key*"): 35 | print(ele + '\n') 36 | Bit_Keys.append(ele) 37 | if fnmatch.fnmatch(ele, "*.BEK"): 38 | print(ele + '\n') 39 | Bit_Keys.append(ele) 40 | 41 | def string_search(): #Regex for key values 42 | for ele in txt_Files: 43 | try: 44 | with open(ele, 'r', encoding="utf-16-le") as text: #this encoding is the default used by Microsoft in creating the txt files 45 | text = text.read() 46 | k = re.findall(pattern, text) 47 | for key in k: 48 | Bit_Keys.append(ele) 49 | print(ele + " - " + key + '\n') 50 | except UnicodeDecodeError: 51 | pass 52 | except PermissionError: 53 | pass 54 | 55 | def copy_key_files(): #COPIES THE KEYS FROM BIT_KEYS LIST TO USER SELECTED DIRECTORY 56 | destination = values['OUTPUT'] 57 | print("*** COPYING KEY RELATED FILES... ***") 58 | copy_list = [] 59 | for i in Bit_Keys: 60 | if i not in copy_list: 61 | copy_list.append(i) 62 | try: 63 | for key_File in copy_list: 64 | shutil.copy(key_File, destination) 65 | 66 | except Exception: 67 | print(key_File +' - Error Copying- check for user access to file or name collision in destination') 68 | pass 69 | print("\n*** FILES COPIED TO DESTINATION DIRECTORY. ***\n") 70 | 71 | def get_active_keys(): #Searches for key values for mounted volumes 72 | print("\n*** TRIAGING KEYS ON CURRENT SYSTEM ***\n") 73 | if isAdmin(): #checks admin status before proceeding 74 | print("\nRunning as Admin...") 75 | Drive_letters = ['%s:' % d for d in string.ascii_uppercase if os.path.exists('%s:' % d)] #Produces list of volumes on target system 76 | comp_name = os.environ['COMPUTERNAME'] #gets target computer name for report title 77 | comp_name = comp_name.strip('\\') 78 | key_report = os.path.join(values['OUTPUT'], comp_name + '-BitlockerReport.txt') 79 | with open(key_report, 'w') as report: 80 | report.write("Bitlocker Key Finder v3.0 \n") #writing the header for the report 1) Version 2) Date 3)User of System 81 | report.write(now) 82 | report.write("\nUser Account Used: ") 83 | report.write(os.getlogin()) 84 | report.write("\n\n") 85 | for drive in Drive_letters: 86 | try: 87 | mng_bde = subprocess.check_output(["manage-bde", "-protectors", drive, "-get"]) #runs the manage-bde query for each drive letter in drive letter list 88 | mng_bde = (mng_bde.splitlines(True)) 89 | print(drive, " - Key protectors found!\n") 90 | for drive_state in mng_bde: 91 | drive_state = drive_state.decode("utf-8") 92 | print(drive_state) 93 | report.write(drive_state) #writes query output to file 94 | print('\n') 95 | except: 96 | subprocess.CalledProcessError 97 | print(drive, ' - No key protectors found. Not Bitlocked. ') 98 | else: 99 | print("Not running as Admin! Restart as Admin to continue.") 100 | 101 | working_Dir = os.getcwd() #Used for default output folder 102 | 103 | sg.theme('LightGrey2') 104 | 105 | layout = [ [sg.Text('Bitlocker Key Finder', size=(18, 1), font=('Impact', 25, 'bold italic'))], 106 | [sg.Text('')], 107 | [sg.Text('Find Saved Bitlocker .TXT and .BEK Files', font=('Arial', 12,'bold'))], 108 | [sg.Text('Select Volume or Directory to Search:'),sg.Input(key='SOURCE',), sg.FolderBrowse(key='SOURCE')], 109 | [sg.Checkbox('File Name Search', key="FILENAME"), sg.Checkbox('String Pattern Search', key="REG")], 110 | #[sg.Text('_'*82)], 111 | [sg.Checkbox('Copy responsive files to Output Directory', enable_events=True ,key="COPYSWITCH")], 112 | [sg.Text('')], 113 | [sg.Text('Recover Keys from Current Machine', font=('Arial', 12,'bold'))], 114 | [sg.Checkbox('Save keys for mounted volumes to Output Directory - MUST BE RUN AS ADMIN', key="MGBDE")], 115 | [sg.Text('')], 116 | [sg.Text(" Output Directory: ", font=('Arial', 11, 'bold')), sg.Input(key='OUTPUT', default_text=working_Dir, disabled=False), sg.FolderBrowse(key='OUTPUT1', enable_events=True, disabled=False)], 117 | # [sg.Output(size=(85,10),)], 118 | [sg.Text('')], 119 | [sg.Button('Find Keys', key='Ok'), sg.Text(' '*125), sg.Button('?', key='HELP')]] 120 | 121 | # Create the Window 122 | window = sg.Window('North Loop Consulting', layout, no_titlebar=False, alpha_channel=1, grab_anywhere=False) 123 | # Event Loop to process "events" and get the "values" of the inputs 124 | 125 | while True: 126 | event, values = window.read() 127 | if event == sg.WIN_CLOSED or event == 'Exit': # if user closes window or clicks cancel 128 | break 129 | if event == 'HELP': 130 | sg.Popup("FOR BEST RESULTS RUN AS ADMIN \n\nThis tool seeks to find Bitlocker Recovery Keys with a focus for on-scene triage. On opening the tool you will see both an interface window to input selections and a console window which will display search results. Closing one window will close the other. \n\nThe tool uses three methods to recover keys: \n\nThe first method searches for file names consistent with Recovery Key files including .TXT and .BEK file extensions. This method returns the file's path. \n\nThe second method performs pattern searches for key values in text files encoded in UTF 16 LE. This method returns the file's path and the string hit.\n\nIf you are running both methods, expect duplicate file hits. \n\nFiles meeting your search criteria can be saved to an output folder of your choice.\n\nThe third method makes use of the manage-bde interface to collect keys for mounted volumes on an active system and save those keys to a text file. \n\nNo warranty or guarantee is offered with this tool. Use at your own risk. \n\nCopyright 2021 North Loop Consulting\n") 131 | window.refresh() 132 | elif event == 'Ok': 133 | folder = os.walk(values["SOURCE"]) 134 | print("*** SEARCHING FOR KEY FILES... ***") 135 | walk() 136 | if values["FILENAME"] == True: 137 | print("Searching for file names in " + values['SOURCE']) 138 | name_search() 139 | if values["REG"] == True: 140 | print("Conducting string search in " + values['SOURCE']) 141 | string_search() 142 | if values['COPYSWITCH'] == True: 143 | copy_key_files() 144 | if values['MGBDE'] == True: 145 | get_active_keys() 146 | print("\n****** COMPLETE ******") 147 | window.refresh() 148 | window.close() 149 | -------------------------------------------------------------------------------- /Bitlocker_Key_Finder.py: -------------------------------------------------------------------------------- 1 | #Python3 2 | 3 | import re 4 | import os 5 | import fnmatch 6 | import argparse 7 | 8 | _author_ = ['Copyright 2021 North Loop Consulting'] 9 | _copy_ = ['(C) 2021'] 10 | _description_ = ("---Bitlocker_Key_Finder v1.1---" 11 | " A tool to locate and retrieve Bitlocker Recovery files." 12 | " Searches file names and file content for recovery keys." 13 | ) 14 | 15 | parser = argparse.ArgumentParser( 16 | description=_description_, 17 | epilog="{}".format( 18 | ", ".join(_author_), _copy_)) 19 | 20 | parser.add_argument("INPUT_VOLUME", help="Input volume letter - ex. 'C:\\\\' or Absolute path - ex. 'E:\\Evidence\\MountedImage\\C'") 21 | args = parser.parse_args() 22 | 23 | In_Vol = args.INPUT_VOLUME 24 | 25 | txt_Files = [] 26 | for root, dirs, file in os.walk(In_Vol): 27 | for filename in file: 28 | if filename.endswith(('.txt', '.TXT', '.bek', '.BEK')): #filters to txt and bek files 29 | txt_Files.append(os.path.join(root, filename)) #creates list of txt and bek files 30 | pattern = re.compile(r"\d{6}-\d{6}-\d{6}-\d{6}-\d{6}-\d{6}-\d{6}-\d{6}") 31 | Bit_Keys = [] 32 | for ele in txt_Files: 33 | if fnmatch.fnmatch(ele, "*BitLocker Recovery Key*"): 34 | print(ele) 35 | Bit_Keys.append(ele) 36 | if fnmatch.fnmatch(ele, "*.BEK"): 37 | print(ele + '\n') 38 | Bit_Keys.append(ele) 39 | 40 | if len(Bit_Keys) == 0: 41 | print("""*************************************************************************** 42 | \nNo Bitlocker Recovery text files were found. 43 | \nWould you like to perform a string search on all text files (slow process)? 44 | \n***************************************************************************""") 45 | choice = input("'Yes' or 'No': ") 46 | if choice == 'Yes' or "Yes " or "yes" or "yes ": 47 | for ele in txt_Files: 48 | try: 49 | with open(ele, 'r', encoding="utf-16-le") as text: 50 | text = text.read() 51 | k = re.findall(pattern, text) 52 | for key in k: 53 | print(ele + " - " + key) 54 | except UnicodeDecodeError: 55 | pass 56 | except PermissionError: 57 | pass 58 | 59 | if len(Bit_Keys) >= 1: 60 | print("""++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 61 | \nGREAT JOB!!! YOU FOUND SOME! 62 | \nWould you like to continue and search the contents of all text files (slower process)? 63 | \n++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++""") 64 | choice = input("'Yes' or 'No': ") 65 | if choice == 'Yes' or "Yes " or "yes" or "yes ": 66 | for ele in txt_Files: 67 | try: 68 | with open(ele, 'r', encoding="utf-16-le") as text: 69 | text = text.read() 70 | k = re.findall(pattern, text) 71 | for key in k: 72 | print(ele + " - " + key) 73 | except UnicodeDecodeError: 74 | pass 75 | except PermissionError: 76 | pass 77 | -------------------------------------------------------------------------------- /Bitlocker_Key_FinderGUI.py: -------------------------------------------------------------------------------- 1 | #Python3 2 | #Bitlocker_Key_FinderGUI 3 | import re 4 | import os 5 | import fnmatch 6 | import shutil 7 | import PySimpleGUI as sg 8 | 9 | pattern = re.compile(r"\d{6}-\d{6}-\d{6}-\d{6}-\d{6}-\d{6}-\d{6}-\d{6}") 10 | Bit_Keys = [] 11 | txt_Files = [] 12 | 13 | def walk(): #Walks the directory tree to find txt files 14 | for root, dirs, file in folder: 15 | for filename in file: 16 | if filename.endswith(('.txt', '.TXT', '.bek', '.BEK')): #filters to txt and bek files 17 | txt_Files.append(os.path.join(root, filename)) #creates list of txt and bek files 18 | 19 | def name_search(): #Finds txt and bek files and adds them to the bit_keys list 20 | 21 | for ele in txt_Files: 22 | if fnmatch.fnmatch(ele, "*BitLocker Recovery Key*"): 23 | print(ele + '\n') 24 | Bit_Keys.append(ele) 25 | if fnmatch.fnmatch(ele, "*.BEK"): 26 | print(ele + '\n') 27 | Bit_Keys.append(ele) 28 | 29 | def string_search(): #Regex for key values 30 | for ele in txt_Files: 31 | try: 32 | with open(ele, 'r', encoding="utf-16-le") as text: #this encoding is the default used by Microsoft in creating the txt files 33 | text = text.read() 34 | k = re.findall(pattern, text) 35 | for key in k: 36 | Bit_Keys.append(ele) 37 | print(ele + " - " + key + '\n') 38 | except UnicodeDecodeError: 39 | pass 40 | except PermissionError: 41 | pass 42 | 43 | def copy_key_files(): #COPIES THE KEYS FROM BIT_KEYS LIST TO USER SELECTED DIRECTORY 44 | destination = values['OUTPUT'] 45 | print("*** COPYING KEY RELATED FILES... ***") 46 | copy_list = [] 47 | for i in Bit_Keys: #create a unique list of paths to avoid copy errors in monitor window 48 | if i not in copy_list: 49 | copy_list.append(i) 50 | try: 51 | for key_File in copy_list: 52 | shutil.copy(key_File, destination) 53 | 54 | except Exception: 55 | print(key_File +' - Error Copying- check for user access to file or name collision in destination') 56 | pass 57 | print("\n*** FILES COPIED TO DESTINATION DIRECTORY. ***\n") 58 | 59 | sg.theme('Reddit') 60 | 61 | layout = [ [sg.Text('Bitlocker Key Finder', size=(18, 1), font=('Impact', 20, 'bold italic'))], 62 | [sg.Text('Select Volume or Directory to search:')], 63 | [sg.Text('Source:'),sg.Input(key='SOURCE',), sg.FolderBrowse(key='SOURCE')], 64 | [sg.Checkbox('File Name Search', key="FILENAME"), sg.Checkbox('String Pattern Search', key="REG")], 65 | #[sg.Text('_'*82)], 66 | [sg.Checkbox('Copy responsive files to directory: ', enable_events=True ,key="COPYSWITCH")], 67 | [sg.Input(key='OUTPUT', disabled=True), sg.FolderBrowse(key='OUTPUT1', enable_events=True, disabled=True)], 68 | [sg.Output(size=(80,8),)], 69 | [sg.Button('Ok'), sg.Button('Exit'), sg.Text(' '*118), sg.Button('?', key='HELP')]] 70 | 71 | # Create the Window 72 | window = sg.Window('North Loop Consulting', layout, no_titlebar=False, alpha_channel=1, grab_anywhere=False) 73 | # Event Loop to process "events" and get the "values" of the inputs 74 | 75 | while True: 76 | event, values = window.read() 77 | if event == sg.WIN_CLOSED or event == 'Exit': # if user closes window or clicks cancel 78 | break 79 | if values['COPYSWITCH'] == True: #Enable/Disable file output directory 80 | window['OUTPUT'].update(disabled=False) 81 | window['OUTPUT1'].update(disabled=False) 82 | if values['COPYSWITCH'] == False: 83 | window['OUTPUT'].update(disabled=True) 84 | window['OUTPUT1'].update(disabled=True) 85 | if event == 'HELP': 86 | sg.Popup("The tool seeks to find Bitlocker Recovery Keys using two methods. \n\nThe first method searches for filenames consistent with Recovery Key files including .TXT and .BEK file extensions. This method returns the file's path. \n\nThe second method performs pattern searches for key values in text files encoded in UTF 16 LE. This method returns the file's path and the string hit.\n\nIf you are running both methods, expect duplicate file hits. \n\nFiles meeting your search criteria can be saved to an output folder of your choice. \n\nCopyright 2021 North Loop Consulting") 87 | window.refresh() 88 | elif event == 'Ok': 89 | folder = os.walk(values["SOURCE"]) 90 | print("*** SEARCHING FOR KEY FILES... ***") 91 | walk() 92 | if values["FILENAME"] == True: 93 | print("Searching for file names in " + values['SOURCE']) 94 | name_search() 95 | if values["REG"] == True: 96 | print("Conducting string search in " + values['SOURCE']) 97 | string_search() 98 | if values['COPYSWITCH'] == True: 99 | copy_key_files() 100 | print("\n****** COMPLETE ******") 101 | window.refresh() 102 | window.close() 103 | -------------------------------------------------------------------------------- /Bitlocker_Key_Finderv3.3.py: -------------------------------------------------------------------------------- 1 | #Bitlocker Key Finder v3.2 2 | import re 3 | import os 4 | import fnmatch 5 | import shutil 6 | import subprocess 7 | import string 8 | import ctypes 9 | import chardet 10 | import datetime 11 | import tkinter as tk 12 | from tkinter import filedialog, messagebox, ttk 13 | import threading 14 | import docx 15 | import pandas as pd 16 | import csv 17 | from striprtf.striprtf import rtf_to_text 18 | 19 | pattern = re.compile(r"\d{6}-\d{6}-\d{6}-\d{6}-\d{6}-\d{6}-\d{6}-\d{6}") 20 | key_pattern = re.compile(r"\d{6}-\d{6}-\d{6}-\d{6}-\d{6}-\d{6}-\d{6}-\d{6}") 21 | id_pattern = re.compile(r"[a-zA-Z0-9]{8}-[a-zA-Z0-9]{4}-[a-zA-Z0-9]{4}-[a-zA-Z0-9]{4}-[a-zA-Z0-9]{12}") 22 | data = [] 23 | Bit_Keys = [] 24 | txt_Files = [] 25 | Collected_Keys = [] 26 | now = datetime.datetime.now() 27 | 28 | # STARTUPINFO to hide the command windows 29 | # startupinfo = subprocess.STARTUPINFO() 30 | # startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW 31 | # startupinfo.wShowWindow = subprocess.SW_HIDE 32 | 33 | def isAdmin(): 34 | try: 35 | is_admin = (os.getuid() == 0) 36 | except AttributeError: 37 | is_admin = ctypes.windll.shell32.IsUserAnAdmin() != 0 38 | return is_admin 39 | def parse_docx(gui,file): 40 | try: 41 | doc = docx.Document(file) 42 | for para in doc.paragraphs: 43 | print(para.text) 44 | key_match = key_pattern.findall(para.text) 45 | id_match = id_pattern.findall(para.text) 46 | if key_match: 47 | for key in key_match: 48 | recovery_id = id_match[0] if id_match else "Unknown" 49 | data.append({"File Path": file, "Recovery Key ID": recovery_id, "BitLocker Key": key}) 50 | gui.log_message(f"File {file} Key ID {recovery_id} Bitlocker Key {key}", "info") 51 | except Exception as e: 52 | print(f"Error parsing {file}: {e}") 53 | return 54 | try: 55 | for table in doc.tables: 56 | for row in table.rows: 57 | for cell in row.cells: 58 | key_match = key_pattern.findall(cell.text) 59 | id_match = id_pattern.findall(cell.text) 60 | if key_match: 61 | for key in key_match: 62 | recovery_id = id_match[0] if id_match else "Unknown" 63 | data.append({"File Path": file, "Recovery Key ID": recovery_id, "BitLocker Key": key}) 64 | except Exception as e: 65 | print(f"Error parsing {file}: {e}") 66 | return 67 | 68 | def parse_xlsx(file): 69 | try: 70 | doc = pd.read_excel(file) 71 | for index, row in doc.iterrows(): 72 | # Convert the row to a string and write to the text file 73 | row_text = ' | '.join([str(item) for item in row.values]) # Join row values with a delimiter 74 | key_match = key_pattern.findall(row_text) 75 | id_match = id_pattern.findall(row_text) 76 | if key_match: 77 | for key in key_match: 78 | recovery_id = id_match[0] if id_match else "Unknown" 79 | data.append({"File Path": file, "Recovery Key ID": recovery_id, "BitLocker Key": key}) 80 | except Exception as e: 81 | print(f"Error parsing {file}: {e}") 82 | return 83 | 84 | def parse_rtf(file): 85 | try: 86 | with open(file, 'rb') as f: # Open in binary mode 87 | rtf_content = f.read() 88 | text = rtf_to_text(rtf_content.decode('utf-8')) # Decode to proper encoding 89 | key_match = key_pattern.findall(text) 90 | id_match = id_pattern.findall(text) 91 | if key_match: 92 | for key in key_match: 93 | recovery_id = id_match[0] if id_match else "Unknown" 94 | data.append({"File Path": file, "Recovery Key ID": recovery_id, "BitLocker Key": key}) 95 | except Exception as e: 96 | print(f"Error parsing {file}: {e}") 97 | 98 | def walk(folder): 99 | for root, dirs, files in os.walk(folder): 100 | for file in files: 101 | if file.endswith(('.txt', '.TXT', '.bek', '.BEK')): 102 | txt_Files.append(os.path.join(root, file)) 103 | 104 | def exhaustive_walk(gui, folder): 105 | for root, dirs, files in os.walk(folder): 106 | for file in files: 107 | # if file.endswith(('.txt', '.TXT', '.bek', '.BEK')): 108 | # txt_Files.append(os.path.join(root, file)) 109 | if file.endswith(".docx"): 110 | parse_docx(gui, os.path.join(root, file)) 111 | elif file.endswith(".xlsx"): 112 | parse_xlsx(gui, os.path.join(root, file)) 113 | elif file.endswith(".rtf"): 114 | parse_rtf(gui, os.path.join(root, file)) 115 | 116 | 117 | def name_search(gui): 118 | for ele in txt_Files: 119 | if fnmatch.fnmatch(ele, "*BitLocker Recovery Key*"): 120 | Bit_Keys.append(ele) 121 | gui.log_message(f"Found BitLocker Recovery Key file: {ele}", "success") 122 | if fnmatch.fnmatch(ele, "*.BEK"): 123 | Bit_Keys.append(ele) 124 | gui.log_message(f"Found BEK file: {ele}", "success") 125 | 126 | def exhaustive_search(gui): 127 | for ele in txt_Files: 128 | try: 129 | if not os.path.exists(ele): 130 | gui.log_message(f"File not found: {ele}", "warning") 131 | continue 132 | 133 | too_big = os.path.getsize(ele) 134 | if too_big >= 1048576: # Skip files larger than 1MB 135 | print(f"File too large (>1MB), skipping: {ele}", "warning") 136 | continue 137 | 138 | # Read the raw contents of the file 139 | with open(ele, 'rb') as raw_file: 140 | raw_content = raw_file.read() 141 | 142 | # Detect the encoding 143 | detected = chardet.detect(raw_content) 144 | encoding = detected['encoding'] 145 | 146 | try: 147 | decoded_content = raw_content.decode(encoding) 148 | # k = re.findall(pattern, decoded_content) 149 | key_match = key_pattern.findall(decoded_content) 150 | id_match = id_pattern.findall(decoded_content) 151 | if key_match: 152 | for key in key_match: 153 | recovery_id = id_match[0] if id_match else "Unknown" 154 | data.append({"File Path": ele, "Recovery Key ID": recovery_id, "BitLocker Key": key}) 155 | for key in key_match: 156 | Bit_Keys.append(ele) 157 | gui.log_message(f"Found BitLocker key in file: {ele}", "success") 158 | gui.log_message(f"Key: {key}", "info") 159 | Collected_Keys.append(key) 160 | except UnicodeDecodeError: 161 | gui.log_message(f"Unable to decode file as {encoding}: {ele}", "warning") 162 | 163 | except FileNotFoundError: 164 | gui.log_message(f"File not found: {ele}", "warning") 165 | except PermissionError: 166 | gui.log_message(f"Permission denied: {ele}", "warning") 167 | except Exception as e: 168 | print(f"Error processing file {ele}: {str(e)}", "error") 169 | 170 | def UTF16LE_string_search(gui): 171 | for ele in txt_Files: 172 | try: 173 | if not os.path.exists(ele): 174 | gui.log_message(f"File not found: {ele}", "warning") 175 | continue 176 | 177 | too_big = os.path.getsize(ele) 178 | if too_big >= 1048576: # Skip files larger than 1MB 179 | print(f"File too large (>1MB), skipping: {ele}", "warning") 180 | continue 181 | 182 | # Read the raw contents of the file 183 | with open(ele, 'rb') as raw_file: 184 | raw_content = raw_file.read() 185 | 186 | try: 187 | decoded_content = raw_content.decode('utf-16-le') 188 | # k = re.findall(pattern, decoded_content) 189 | key_match = key_pattern.findall(decoded_content) 190 | id_match = id_pattern.findall(decoded_content) 191 | if key_match: 192 | for key in key_match: 193 | recovery_id = id_match[0] if id_match else "Unknown" 194 | data.append({"File Path": ele, "Recovery Key ID": recovery_id, "BitLocker Key": key}) 195 | 196 | Bit_Keys.append(ele) 197 | gui.log_message(f"Found BitLocker key in file: {ele}", "success") 198 | gui.log_message(f"Key: {key}", "info") 199 | Collected_Keys.append(key) 200 | except UnicodeDecodeError: 201 | # gui.log_message(f"Unable to decode file as UTF-16LE: {ele}", "warning") 202 | pass 203 | else: 204 | print(f"File is not UTF-16LE encoded, skipping: {ele}", "info") 205 | 206 | except FileNotFoundError: 207 | gui.log_message(f"File not found: {ele}", "warning") 208 | except PermissionError: 209 | gui.log_message(f"Permission denied: {ele}", "warning") 210 | except Exception as e: 211 | gui.log_message(f"Error processing file {ele}: {str(e)}", "error") 212 | 213 | def make_key_report(report_folder): 214 | output_folder = report_folder 215 | csv_file = os.path.join(output_folder, "BitlockerKeyReport" + now.strftime("%Y%m%d%H%M%S") + ".csv") 216 | with open(csv_file, mode='w', newline='', encoding='utf-8') as file: 217 | writer = csv.DictWriter(file, fieldnames=["File Path", "Recovery Key ID", "BitLocker Key"]) 218 | writer.writeheader() 219 | writer.writerows(data) 220 | # print(data) 221 | 222 | class BitlockerKeyFinderGUI: 223 | def __init__(self, master): 224 | self.master = master 225 | master.title("North Loop Consulting - Bitlocker Key Finder v3.2") 226 | master.geometry("800x600") 227 | master.configure(bg="#f0f0f0") 228 | 229 | self.create_widgets() 230 | self.master.after(100, self.periodic_refresh) # Start periodic refresh 231 | 232 | def create_widgets(self): 233 | 234 | # Find Saved Bitlocker .TXT and .BEK Files 235 | tk.Label(self.master, text="Find Saved Bitlocker .TXT and .BEK Files", font=("Arial", 12, "bold"), bg="#f0f0f0").pack(pady=5) 236 | 237 | # Source Directory 238 | source_frame = tk.Frame(self.master, bg="#f0f0f0") 239 | source_frame.pack(fill="x", padx=10, pady=5) 240 | tk.Label(source_frame, text="Select Volume or Directory to Search:", bg="#f0f0f0").pack(side="left") 241 | self.source_entry = tk.Entry(source_frame, width=50) 242 | self.source_entry.pack(side="left", expand=True, fill="x", padx=5) 243 | tk.Button(source_frame, text="Browse", command=self.browse_source, bg="#4CAF50", fg="white", relief=tk.RAISED).pack(side="left") 244 | 245 | # Checkboxes for search options 246 | checkbox_frame = tk.Frame(self.master, bg="#f0f0f0") 247 | checkbox_frame.pack(pady=5) 248 | self.filename_var = tk.BooleanVar() 249 | tk.Checkbutton(checkbox_frame, text="File Name Search (Fast)", variable=self.filename_var, bg="#f0f0f0").pack(side="left") 250 | 251 | self.utf16le_search_var = tk.BooleanVar() 252 | tk.Checkbutton(checkbox_frame, text="UTF-16LE String Search", variable=self.utf16le_search_var, bg="#f0f0f0").pack(side="left") 253 | 254 | self.exhaustive_search_var = tk.BooleanVar() 255 | tk.Checkbutton(checkbox_frame, text="Exhaustive Search - All txt, docx, xlsx, & rtf files (Slow)", variable=self.exhaustive_search_var, bg="#f0f0f0").pack(side="left") 256 | 257 | # Copy Files Option 258 | self.copy_var = tk.BooleanVar() 259 | tk.Checkbutton(self.master, text="Copy responsive files to Output Directory", variable=self.copy_var, bg="#f0f0f0").pack(pady=5) 260 | 261 | # Recover Keys from Current Machine 262 | tk.Label(self.master, text="Recover Keys from Current Machine", font=("Arial", 12, "bold"), bg="#f0f0f0").pack(pady=5) 263 | self.mgbde_var = tk.BooleanVar() 264 | tk.Checkbutton(self.master, text="ADMIN ONLY - Save keys for mounted volumes to Output Directory", variable=self.mgbde_var, bg="#f0f0f0").pack() 265 | 266 | # Output Directory 267 | output_frame = tk.Frame(self.master, bg="#f0f0f0") 268 | output_frame.pack(fill="x", padx=10, pady=5) 269 | tk.Label(output_frame, text="Output Directory:", font=("Arial", 11, "bold"), bg="#f0f0f0").pack(side="left") 270 | self.output_entry = tk.Entry(output_frame, width=50) 271 | self.output_entry.pack(side="left", expand=True, fill="x", padx=5) 272 | self.output_entry.insert(0, os.getcwd()) 273 | tk.Button(output_frame, text="Browse", command=self.browse_output, bg="#4CAF50", fg="white", relief=tk.RAISED).pack(side="left") 274 | 275 | # Buttons 276 | button_frame = tk.Frame(self.master, bg="#f0f0f0") 277 | button_frame.pack(pady=10) 278 | self.find_keys_button = tk.Button(button_frame, text="Find Keys", command=self.start_find_keys_thread, bg="#2196F3", fg="white", relief=tk.RAISED) 279 | self.find_keys_button.pack(side="left", padx=5) 280 | 281 | tk.Button(button_frame, text="Help", command=self.show_help, bg="orange", fg="black", relief=tk.RAISED).pack(side="left", padx=5) 282 | 283 | # Console 284 | console_frame = tk.Frame(self.master, bg="#333333") 285 | console_frame.pack(fill="both", expand=True, padx=10, pady=5) 286 | # tk.Label(console_frame, text="Console Output", font=("Arial", 12, "bold"), bg="#333333", fg="white").pack() 287 | self.console = tk.Text(console_frame, wrap="word", height=15, bg="#ffffff", fg="white") 288 | self.console.pack(fill="both", expand=True) 289 | self.console.tag_configure("info", foreground="blue") 290 | self.console.tag_configure("warning", foreground="orange") 291 | self.console.tag_configure("error", foreground="red") 292 | self.console.tag_configure("success", foreground="black") 293 | self.console.tag_configure("bold", font=("Arial", 10,)) 294 | self.progress = ttk.Progressbar(self.master, orient="horizontal", length=800, mode="determinate") 295 | self.progress.pack(pady=0) 296 | clear_frame = tk.Frame(self.master, bg="#f0f0f0") 297 | clear_frame.pack(fill="x", padx=10, pady=(0, 10)) # Add padding at the bottom 298 | tk.Button(clear_frame, text="Clear Window", command=self.clear_console, bg="#FF5722", fg="white", relief=tk.RAISED).pack() 299 | 300 | 301 | def browse_source(self): 302 | folder_path = filedialog.askdirectory() 303 | if folder_path: 304 | self.source_entry.delete(0, tk.END) 305 | self.source_entry.insert(0, folder_path) 306 | 307 | def browse_output(self): 308 | folder_path = filedialog.askdirectory() 309 | if folder_path: 310 | self.output_entry.delete(0, tk.END) 311 | self.output_entry.insert(0, folder_path) 312 | 313 | def show_help(self): 314 | help_message = ( 315 | "Copyright 2024 North Loop Consulting\n" 316 | "Bitlocker Key Finder\n\n" 317 | "1. Select the directory to search for Bitlocker Recovery Keys or BEK files.\n" 318 | "2. Choose search options:\n" 319 | " - File Name Search: A quick search for file names consistent with key files.\n" 320 | " - UTF-16LE String Search: Searches for Bitlocker keys in UTF-16LE encoded files.\n" 321 | " - Exhaustive String Search: Performs a search of all .txt files smaller than 1MB for keys.\n" 322 | "3. Optionally, enable the Copy Files option to copy found files to the output directory.\n" 323 | "4. Optionally, enable the recovery of keys from the current machine (ADMIN ONLY).\n" 324 | "5. Choose the output directory to save results.\n" 325 | "6. Click 'Find Keys' to start the search." 326 | ) 327 | messagebox.showinfo("Help", help_message) 328 | 329 | def start_find_keys_thread(self): 330 | # Disable the Find Keys button 331 | self.find_keys_button.config(state=tk.DISABLED) 332 | # Start the find keys process in a separate thread 333 | thread = threading.Thread(target=self.find_keys) 334 | thread.start() 335 | 336 | 337 | 338 | def find_keys(self): 339 | global Bit_Keys, txt_Files 340 | Bit_Keys = [] 341 | txt_Files = [] 342 | folder_path = self.source_entry.get() 343 | 344 | # Traverse the directory and find .txt and .BEK files 345 | walk(folder_path) 346 | total_files = len(txt_Files) 347 | self.progress['value'] = 0 348 | self.progress['maximum'] = total_files 349 | 350 | if self.filename_var.get(): 351 | self.log_message(f"Searching for file names in {folder_path}", "info") 352 | name_search(self) 353 | 354 | if self.exhaustive_search_var.get(): 355 | self.log_message(f"Conducting exhaustive string search in {folder_path}", "info") 356 | exhaustive_walk(self, folder_path) 357 | exhaustive_search(self) 358 | make_key_report(self.output_entry.get()) 359 | 360 | 361 | 362 | if self.utf16le_search_var.get(): 363 | self.log_message(f"Conducting UTF-16LE string search in {folder_path}", "info") 364 | UTF16LE_string_search(self) 365 | 366 | self.log_message(f"Total BitLocker keys/files found: {len(Bit_Keys)}", "success") 367 | 368 | if self.copy_var.get(): 369 | self.copy_key_files() 370 | 371 | if self.mgbde_var.get(): 372 | self.get_active_keys() 373 | 374 | self.log_message("SEARCH COMPLETE", "success") 375 | 376 | # Re-enable the Find Keys button 377 | self.find_keys_button.config(state=tk.NORMAL) 378 | 379 | 380 | 381 | 382 | def copy_key_files(self): 383 | output_folder = self.output_entry.get() 384 | if not os.path.isdir(output_folder): 385 | self.log_message("Invalid output directory. Please select a valid directory.", "warning") 386 | return 387 | for file in Bit_Keys: 388 | try: 389 | shutil.copy(file, output_folder) 390 | self.log_message(f"Copied file: {file}", "success") 391 | except Exception as e: 392 | self.log_message(f"Error copying file {file}: {str(e)}", "error") 393 | 394 | def get_active_keys(self): 395 | if not isAdmin(): 396 | self.log_message("Admin rights are required to retrieve BitLocker keys.", "warning") 397 | return 398 | output_folder = self.output_entry.get() 399 | comp_name = os.environ['COMPUTERNAME'] #gets target computer name for report title 400 | comp_name = comp_name.strip('\\') 401 | key_report = os.path.join(output_folder, comp_name + '-BitlockerReport.txt') 402 | Drive_letters = ['%s:' % d for d in string.ascii_uppercase if os.path.exists('%s:' % d)] #Produces list of volumes on target system 403 | 404 | 405 | if not os.path.isdir(output_folder): 406 | self.log_message("Invalid output directory. Please select a valid directory.", "warning") 407 | return 408 | with open(key_report, 'w') as report: 409 | report.write("Bitlocker Key Finder v3.0 \n") #writing the header for the report 1) Version 2) Date 3)User of System 410 | report.write(now.strftime("%Y-%m-%d, %H:%M:%S")) 411 | report.write("\nUser Account Used: ") 412 | report.write(os.getlogin()) 413 | report.write("\n\n") 414 | try: 415 | # volumes = subprocess.check_output(["manage-bde", "-status"], startupinfo=startupinfo).decode("utf-8") 416 | volumes = subprocess.check_output(["manage-bde", "-status"]).decode("utf-8") 417 | 418 | self.log_message(volumes, "info") 419 | volume_lines = volumes.splitlines() 420 | with open(key_report, "a") as key_file: 421 | for line in volume_lines: 422 | 423 | if "Volume " in line: 424 | volume = line.split()[1] 425 | print(volume) 426 | try: 427 | recovery_keys = subprocess.check_output(["manage-bde", "-protectors", "-get", volume], startupinfo=startupinfo).decode("utf-8") 428 | key_file.write(f"Bitlocker key found for {volume}!\n\n") 429 | key_file.write(recovery_keys) 430 | self.log_message(f"BitLocker key for volume {volume} written to report at {key_report}", "success") 431 | # self.log_message(f"{recovery_keys}", "info") 432 | except subprocess.CalledProcessError: 433 | # self.log_message(f"No BitLocker credentials found for {volume}", "warning") 434 | key_file.write(f"No BitLocker credentials found for {volume}\n\n") 435 | except Exception as e: 436 | self.log_message(f"Error retrieving BitLocker keys: {str(e)}", "error") 437 | 438 | 439 | def log_message(self, message, level="info"): 440 | timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") 441 | formatted_message = f"[{timestamp}] {message}\n" 442 | self.console.insert(tk.END, formatted_message, (level, "bold")) 443 | self.console.see(tk.END) 444 | self.master.after(100, lambda: self.master.update_idletasks()) # Regular GUI update 445 | 446 | def clear_console(self): 447 | self.console.delete(1.0, tk.END) 448 | 449 | def periodic_refresh(self): 450 | self.master.update_idletasks() 451 | self.master.after(100, self.periodic_refresh) 452 | 453 | # Create and run the Tkinter application 454 | root = tk.Tk() 455 | app = BitlockerKeyFinderGUI(root) 456 | root.mainloop() 457 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Bitlocker Key Finder License 2 | Attribution-NonCommercial 4.0 International 3 | 4 | Section 1 -- Definitions. 5 | 6 | a. Adapted Material means material subject to Copyright and Similar 7 | Rights that is derived from or based upon the Licensed Material 8 | and in which the Licensed Material is translated, altered, 9 | arranged, transformed, or otherwise modified in a manner requiring 10 | permission under the Copyright and Similar Rights held by the 11 | Licensor. For purposes of this Public License, where the Licensed 12 | Material is a musical work, performance, or sound recording, 13 | Adapted Material is always produced where the Licensed Material is 14 | synched in timed relation with a moving image. 15 | 16 | b. Adapter's License means the license You apply to Your Copyright 17 | and Similar Rights in Your contributions to Adapted Material in 18 | accordance with the terms and conditions of this Public License. 19 | 20 | c. Copyright and Similar Rights means copyright and/or similar rights 21 | closely related to copyright including, without limitation, 22 | performance, broadcast, sound recording, and Sui Generis Database 23 | Rights, without regard to how the rights are labeled or 24 | categorized. For purposes of this Public License, the rights 25 | specified in Section 2(b)(1)-(2) are not Copyright and Similar 26 | Rights. 27 | d. Effective Technological Measures means those measures that, in the 28 | absence of proper authority, may not be circumvented under laws 29 | fulfilling obligations under Article 11 of the WIPO Copyright 30 | Treaty adopted on December 20, 1996, and/or similar international 31 | agreements. 32 | 33 | e. Exceptions and Limitations means fair use, fair dealing, and/or 34 | any other exception or limitation to Copyright and Similar Rights 35 | that applies to Your use of the Licensed Material. 36 | 37 | f. Licensed Material means the artistic or literary work, database, 38 | or other material to which the Licensor applied this Public 39 | License. 40 | 41 | g. Licensed Rights means the rights granted to You subject to the 42 | terms and conditions of this Public License, which are limited to 43 | all Copyright and Similar Rights that apply to Your use of the 44 | Licensed Material and that the Licensor has authority to license. 45 | 46 | h. Licensor means the individual(s) or entity(ies) granting rights 47 | under this Public License. 48 | 49 | i. NonCommercial means not primarily intended for or directed towards 50 | commercial advantage or monetary compensation. For purposes of 51 | this Public License, the exchange of the Licensed Material for 52 | other material subject to Copyright and Similar Rights by digital 53 | file-sharing or similar means is NonCommercial provided there is 54 | no payment of monetary compensation in connection with the 55 | exchange. 56 | 57 | j. Share means to provide material to the public by any means or 58 | process that requires permission under the Licensed Rights, such 59 | as reproduction, public display, public performance, distribution, 60 | dissemination, communication, or importation, and to make material 61 | available to the public including in ways that members of the 62 | public may access the material from a place and at a time 63 | individually chosen by them. 64 | 65 | k. Sui Generis Database Rights means rights other than copyright 66 | resulting from Directive 96/9/EC of the European Parliament and of 67 | the Council of 11 March 1996 on the legal protection of databases, 68 | as amended and/or succeeded, as well as other essentially 69 | equivalent rights anywhere in the world. 70 | 71 | l. You means the individual or entity exercising the Licensed Rights 72 | under this Public License. Your has a corresponding meaning. 73 | 74 | 75 | Section 2 -- Scope. 76 | 77 | a. License grant. 78 | 79 | 1. Subject to the terms and conditions of this Public License, 80 | the Licensor hereby grants You a worldwide, royalty-free, 81 | non-sublicensable, non-exclusive, irrevocable license to 82 | exercise the Licensed Rights in the Licensed Material to: 83 | 84 | a. reproduce and Share the Licensed Material, in whole or 85 | in part, for NonCommercial purposes only; and 86 | 87 | b. produce, reproduce, and Share Adapted Material for 88 | NonCommercial purposes only. 89 | 90 | 2. Exceptions and Limitations. For the avoidance of doubt, where 91 | Exceptions and Limitations apply to Your use, this Public 92 | License does not apply, and You do not need to comply with 93 | its terms and conditions. 94 | 95 | 3. Term. The term of this Public License is specified in Section 96 | 6(a). 97 | 98 | 4. Media and formats; technical modifications allowed. The 99 | Licensor authorizes You to exercise the Licensed Rights in 100 | all media and formats whether now known or hereafter created, 101 | and to make technical modifications necessary to do so. The 102 | Licensor waives and/or agrees not to assert any right or 103 | authority to forbid You from making technical modifications 104 | necessary to exercise the Licensed Rights, including 105 | technical modifications necessary to circumvent Effective 106 | Technological Measures. For purposes of this Public License, 107 | simply making modifications authorized by this Section 2(a) 108 | (4) never produces Adapted Material. 109 | 110 | 5. Downstream recipients. 111 | 112 | a. Offer from the Licensor -- Licensed Material. Every 113 | recipient of the Licensed Material automatically 114 | receives an offer from the Licensor to exercise the 115 | Licensed Rights under the terms and conditions of this 116 | Public License. 117 | 118 | b. No downstream restrictions. You may not offer or impose 119 | any additional or different terms or conditions on, or 120 | apply any Effective Technological Measures to, the 121 | Licensed Material if doing so restricts exercise of the 122 | Licensed Rights by any recipient of the Licensed 123 | Material. 124 | 125 | 6. No endorsement. Nothing in this Public License constitutes or 126 | may be construed as permission to assert or imply that You 127 | are, or that Your use of the Licensed Material is, connected 128 | with, or sponsored, endorsed, or granted official status by, 129 | the Licensor or others designated to receive attribution as 130 | provided in Section 3(a)(1)(A)(i). 131 | 132 | b. Other rights. 133 | 134 | 1. Moral rights, such as the right of integrity, are not 135 | licensed under this Public License, nor are publicity, 136 | privacy, and/or other similar personality rights; however, to 137 | the extent possible, the Licensor waives and/or agrees not to 138 | assert any such rights held by the Licensor to the limited 139 | extent necessary to allow You to exercise the Licensed 140 | Rights, but not otherwise. 141 | 142 | 2. Patent and trademark rights are not licensed under this 143 | Public License. 144 | 145 | 3. To the extent possible, the Licensor waives any right to 146 | collect royalties from You for the exercise of the Licensed 147 | Rights, whether directly or through a collecting society 148 | under any voluntary or waivable statutory or compulsory 149 | licensing scheme. In all other cases the Licensor expressly 150 | reserves any right to collect such royalties, including when 151 | the Licensed Material is used other than for NonCommercial 152 | purposes. 153 | 154 | 155 | Section 3 -- License Conditions. 156 | 157 | Your exercise of the Licensed Rights is expressly made subject to the 158 | following conditions. 159 | 160 | a. Attribution. 161 | 162 | 1. If You Share the Licensed Material (including in modified 163 | form), You must: 164 | 165 | a. retain the following if it is supplied by the Licensor 166 | with the Licensed Material: 167 | 168 | i. identification of the creator(s) of the Licensed 169 | Material and any others designated to receive 170 | attribution, in any reasonable manner requested by 171 | the Licensor (including by pseudonym if 172 | designated); 173 | 174 | ii. a copyright notice; 175 | 176 | iii. a notice that refers to this Public License; 177 | 178 | iv. a notice that refers to the disclaimer of 179 | warranties; 180 | 181 | v. a URI or hyperlink to the Licensed Material to the 182 | extent reasonably practicable; 183 | 184 | b. indicate if You modified the Licensed Material and 185 | retain an indication of any previous modifications; and 186 | 187 | c. indicate the Licensed Material is licensed under this 188 | Public License, and include the text of, or the URI or 189 | hyperlink to, this Public License. 190 | 191 | 2. You may satisfy the conditions in Section 3(a)(1) in any 192 | reasonable manner based on the medium, means, and context in 193 | which You Share the Licensed Material. For example, it may be 194 | reasonable to satisfy the conditions by providing a URI or 195 | hyperlink to a resource that includes the required 196 | information. 197 | 198 | 3. If requested by the Licensor, You must remove any of the 199 | information required by Section 3(a)(1)(A) to the extent 200 | reasonably practicable. 201 | 202 | 4. If You Share Adapted Material You produce, the Adapter's 203 | License You apply must not prevent recipients of the Adapted 204 | Material from complying with this Public License. 205 | 206 | 207 | Section 4 -- Sui Generis Database Rights. 208 | 209 | Where the Licensed Rights include Sui Generis Database Rights that 210 | apply to Your use of the Licensed Material: 211 | 212 | a. for the avoidance of doubt, Section 2(a)(1) grants You the right 213 | to extract, reuse, reproduce, and Share all or a substantial 214 | portion of the contents of the database for NonCommercial purposes 215 | only; 216 | 217 | b. if You include all or a substantial portion of the database 218 | contents in a database in which You have Sui Generis Database 219 | Rights, then the database in which You have Sui Generis Database 220 | Rights (but not its individual contents) is Adapted Material; and 221 | 222 | c. You must comply with the conditions in Section 3(a) if You Share 223 | all or a substantial portion of the contents of the database. 224 | 225 | For the avoidance of doubt, this Section 4 supplements and does not 226 | replace Your obligations under this Public License where the Licensed 227 | Rights include other Copyright and Similar Rights. 228 | 229 | 230 | Section 5 -- Disclaimer of Warranties and Limitation of Liability. 231 | 232 | a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE 233 | EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS 234 | AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF 235 | ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS, 236 | IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION, 237 | WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR 238 | PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS, 239 | ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT 240 | KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT 241 | ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU. 242 | 243 | b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE 244 | TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION, 245 | NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT, 246 | INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES, 247 | COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR 248 | USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN 249 | ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR 250 | DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR 251 | IN PART, THIS LIMITATION MAY NOT APPLY TO YOU. 252 | 253 | c. The disclaimer of warranties and limitation of liability provided 254 | above shall be interpreted in a manner that, to the extent 255 | possible, most closely approximates an absolute disclaimer and 256 | waiver of all liability. 257 | 258 | 259 | Section 6 -- Term and Termination. 260 | 261 | a. This Public License applies for the term of the Copyright and 262 | Similar Rights licensed here. However, if You fail to comply with 263 | this Public License, then Your rights under this Public License 264 | terminate automatically. 265 | 266 | b. Where Your right to use the Licensed Material has terminated under 267 | Section 6(a), it reinstates: 268 | 269 | 1. automatically as of the date the violation is cured, provided 270 | it is cured within 30 days of Your discovery of the 271 | violation; or 272 | 273 | 2. upon express reinstatement by the Licensor. 274 | 275 | For the avoidance of doubt, this Section 6(b) does not affect any 276 | right the Licensor may have to seek remedies for Your violations 277 | of this Public License. 278 | 279 | c. For the avoidance of doubt, the Licensor may also offer the 280 | Licensed Material under separate terms or conditions or stop 281 | distributing the Licensed Material at any time; however, doing so 282 | will not terminate this Public License. 283 | 284 | d. Sections 1, 5, 6, 7, and 8 survive termination of this Public 285 | License. 286 | 287 | 288 | Section 7 -- Other Terms and Conditions. 289 | 290 | a. The Licensor shall not be bound by any additional or different 291 | terms or conditions communicated by You unless expressly agreed. 292 | 293 | b. Any arrangements, understandings, or agreements regarding the 294 | Licensed Material not stated herein are separate from and 295 | independent of the terms and conditions of this Public License. 296 | 297 | 298 | Section 8 -- Interpretation. 299 | 300 | a. For the avoidance of doubt, this Public License does not, and 301 | shall not be interpreted to, reduce, limit, restrict, or impose 302 | conditions on any use of the Licensed Material that could lawfully 303 | be made without permission under this Public License. 304 | 305 | b. To the extent possible, if any provision of this Public License is 306 | deemed unenforceable, it shall be automatically reformed to the 307 | minimum extent necessary to make it enforceable. If the provision 308 | cannot be reformed, it shall be severed from this Public License 309 | without affecting the enforceability of the remaining terms and 310 | conditions. 311 | 312 | c. No term or condition of this Public License will be waived and no 313 | failure to comply consented to unless expressly agreed to by the 314 | Licensor. 315 | 316 | d. Nothing in this Public License constitutes or may be interpreted 317 | as a limitation upon, or waiver of, any privileges and immunities 318 | that apply to the Licensor or You, including from the legal 319 | processes of any jurisdiction or authority.Bitlocker_Key_Finder Use License 320 | 321 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Bitlocker_Key_Finder 2 | 3 | *As mentioned in Thisweekin4n6. How cool is that!?! 4 | 5 | A digital forensic solution for addressing Bitlocker credentials. 6 | 7 | This tool automates the search for TXT and BEK files containing Bitlocker Recovery Keys. It can search based on file name or patterns within relevant text files. 8 | The intended use case is for forensic work necessitating such a search. 9 | 10 | v1.0 is a Python 3 script to locate Bitlocker Recovery Key text files. 11 | 12 | The only input required is the volume or directory to be searched. Provide a volume or absolute path to a directory. 13 | After searching for relevant file names, a string search can be performed on text file contents in the event a Recovery Key document has been renamed. 14 | 15 | Usage: 16 | python Bitlocker_Key_Finder.py 'volume or directory' 17 | python Bitlocker_Key_Finder.py C:\\ 18 | python Bitlocker_Key_Finder.py "C:\Users\user\Documents" 19 | 20 | The script returns all findings to the command prompt. 21 | 22 | The GUI can be found in v2.1 and higher and includes additional functionality. 23 | 24 | ![alt text](https://github.com/user-attachments/assets/adc3a22c-545b-4fa2-9bfe-fea5899bd4fa) 25 | 26 | Like the script, the GUI seeks saved Recovery Key files in both TXT and BEK formats. It also recovers keys for mounted volumes on active systems. 27 | 28 | As of v3.0, opening the tool presents the user interface window (see above) and an output console window. 29 | 30 | Recovering keys for mounted volumes requires admin permissions. If you forget and attempt to use a function needing admin access, the tool will prompt you to restart it with elevated permissions. 31 | 32 | Identified TXT, CSV, DOCX, XLSX, RTF, and BEK files can be copied to a location chosen by the user. This same output location will be used to store a report for key information related to mounted volumes on the target system. 33 | 34 | Copied BEK files are visible. 35 | 36 | ![alt text](https://user-images.githubusercontent.com/73806121/149680779-97783cc9-9edc-4ff7-907d-48ed21961dfd.png) 37 | 38 | Reporting is intended to describe the tool used, date/time of use, user account utilized to collect key data on mounted and accessible volumes. 39 | 40 | Please let me know if you run into any issues or have suggestions for the tool. 41 | --------------------------------------------------------------------------------