这是一个使用
mySQLite和
Bouncy Castle(
Gist)的PowerShell脚本解决方案。
if (-not (Get-Package 'Portable.BouncyCastle' -ErrorAction Ignore)) {
if (-not (Get-PackageSource -Name NuGet -ErrorAction Ignore)) {
Register-PackageSource -Name NuGet -Location https://api.nuget.org/v3/index.json -ProviderName NuGet | Set-PackageSource -Trusted
}
Install-Package -Name 'Portable.BouncyCastle' -Source NuGet -Scope CurrentUser -SkipDependencies
}
if (-not(Get-Module -Name MySQLite -ErrorAction Ignore) -and ($PSVersionTable.PSVersion.Major -lt 5)) {
$RepositoryZipUrl = 'https://api.github.com/repos/jdhitsolutions/MySQLite/zipball/master'
Invoke-RestMethod -Uri $RepositoryZipUrl -OutFile 'MySQLite.zip'
Unblock-File 'MySQLite.zip'
Expand-Archive -Path 'MySQLite.zip' -DestinationPath $($env:PSModulePath -split [System.IO.Path]::PathSeparator | Where-Object { (Test-Path -Path $_ -PathType Container -ErrorAction SilentlyContinue) -and $(try { $tmp = New-Item -Path $_ -Name ([System.IO.Path]::GetRandomFileName()) -ItemType File -Value (Get-Random) -ErrorAction SilentlyContinue; Remove-Item -Path $tmp; $true | Write-Output } catch { $false | Write-Output } ) } | Select-Object -First 1) -Force -Confirm
}
elseif (-not(Get-Module -Name MySQLite -ErrorAction Ignore) -and ($PSVersionTable.PSVersion.Major -ge 5)) {
Install-Module MySQLite -Repository PSGallery -Scope CurrentUser
}
Import-Module MySQLite
Get-Package 'Portable.BouncyCastle' | ForEach-Object { Add-Type -LiteralPath ($_.Source | Split-Path | Get-ChildItem -Filter 'netstandard*' -Recurse -Directory | Get-ChildItem -Filter *.dll -Recurse -File ).FullName }
$domain = 'mavaddat.ca'
$cookiesPath = "$env:LOCALAPPDATA\Google\Chrome Beta\User Data\Default\Network\Cookies"
Get-MySQLiteTable -Path $cookiesPath -Detail
$query = "SELECT name,encrypted_value,path,host_key FROM `"main`".`"cookies`" WHERE `"host_key`" LIKE '%$domain%' ESCAPE '\' LIMIT 0, 49999;"
$query = "SELECT name,encrypted_value,path,host_key FROM `"main`".`"cookies`" LIMIT 0, 49999;"
$cookies = Invoke-MySQLiteQuery -Path $cookiesPath -Query $query
$localStatePath = "$env:LOCALAPPDATA\Google\Chrome Beta\User Data\Local State"
$cookiesKeyEncBaseSixtyFour = (Get-Content -Path $localStatePath | ConvertFrom-Json).'os_crypt'.'encrypted_key'
$cookiesKeyEnc = [System.Convert]::FromBase64String($cookiesKeyEncBaseSixtyFour) | Select-Object -Skip ([System.Text.Encoding]::UTF8.GetBytes('DPAPI').Count)
$cookiesKey = [System.Security.Cryptography.ProtectedData]::Unprotect($cookiesKeyEnc, $null, [System.Security.Cryptography.DataProtectionScope]::LocalMachine)
$session = New-Object Microsoft.PowerShell.Commands.WebRequestSession
$cipher = [Org.BouncyCastle.Crypto.Modes.GcmBlockCipher]::new([Org.BouncyCastle.Crypto.Engines.AesEngine]::new())
foreach ($cookie in $cookies) {
$path = [string]::IsNullOrEmpty($cookie.path) ? '/' : $cookie.path
try {
$cipherStream = [System.IO.MemoryStream]::new($cookie.encrypted_value)
$cipherReader = [System.IO.BinaryReader]::new($cipherStream)
$cipherReader.BaseStream.Position = [System.Text.Encoding]::ASCII.GetBytes('v10').Count
$nonce = $cipherReader.ReadBytes([System.Security.Cryptography.AesGcm]::NonceByteSizes.MinSize)
$parameters = [Org.BouncyCastle.Crypto.Parameters.AeadParameters]::new( ([Org.BouncyCastle.Crypto.Parameters.KeyParameter]::new($cookiesKey)), ([System.Security.Cryptography.AesGcm]::TagByteSizes.MaxSize * [byte]::MaxValue.GetShortestBitLength()), $nonce)
$cipher.Init($false, $parameters)
$cipherText = $cipherReader.ReadBytes($cookie.encrypted_value.Length)
$plainText = [byte[]]::new($cipher.GetOutputSize($cipherText.Length))
if (-not [string]::IsNullOrEmpty($plainText)) {
try {
$len = $cipher.ProcessBytes($cipherText, 0, $cipherText.Length, $plainText, 0)
$bytesDeciphered = $cipher.DoFinal($plainText, $len)
Write-Verbose "Deciphered $bytesDeciphered bytes"
}
catch [System.Management.Automation.MethodInvocationException] {
if ($_.Exception.InnerException -is [Org.BouncyCastle.Crypto.InvalidCipherTextException]) {
Write-Error 'Invalid Cipher Text'
}
else {
Write-Error $_
}
continue
}
finally {
$cipher.Reset()
}
try {
$session.Cookies.Add([System.Net.Cookie]::new(($cookie.name), [System.Text.Encoding]::Default.GetString($plainText), $path, ($cookie.host_key -replace '^\.')))
}
catch [System.Management.Automation.MethodInvocationException] {
if ($_.Exception.InnerException -is [System.Net.CookieException]) {
$session.Cookies.Add([System.Net.Cookie]::new(($cookie.name), [System.Web.HttpUtility]::UrlEncode([System.Text.Encoding]::Default.GetString($plainText)), $path, ($cookie.host_key -replace '^\.')))
}
else {
Write-Error $_
}
}
}
}
finally {
$cipherStream.Dispose()
$cipherReader.Dispose()
}
}
$cipherReader = $null
$cipherStream = $null
$cookiesKey = $null
$cookiesKeyEnc = $null
$cookiesKeyEncBaseSixtyFour = $null
$nonce = $null
$cipher = $null
$cipherText = $null
$plainText = $null
Remove-Variable cipher, cipherReader, cipherStream, cookiesKey, cookiesKeyEnc, cookiesKeyEncBaseSixtyFour, nonce, cipherText, plainText
Invoke-WebRequest -Uri $domain -WebSession $session
我如何实现了上述内容
有两个需要查询的Chromium文件:
User Data
local state
- SQLite中的Cookies
User Data
是一个以JSON格式保存了Cookies加密密钥的文件。对于我来说(使用Chrome Beta 107.0.5304.18在Windows 11上),它位于 %LOCALAPPDATA%\Google\Chrome Beta\User Data\Local State
。
基于Chromium的浏览器 使用存储在二进制SQLite数据库文件中的Cookies。请参考Superuser上关于如何找到Chrome的这个数据库的问题,"有没有一种方法可以实时查看Cookies及其值?"。
对我来说,这个文件被命名为
Cookies
,并且位于
%LOCALAPPDATA%\Google\Chrome Beta\User Data\Default\Network\Cookies
。使用
MySQLite,我可以看到数据库有两个表,其模式如下:
Cookies
表
名称 |
类型 |
模式 |
creation_utc |
整数 |
"creation_utc" 非空整数 |
host_key |
文本 |
"host_key" 非空文本 |
name |
文本 |
"name" 非空文本 |
value |
文本 |
"value" 非空文本 |
path |
文本 |
"path" 非空文本 |
expires_utc |
整数 |
"expires_utc" 非空整数 |
is_secure |
整数 |
"is_secure" 非空整数 |
is_httponly |
整数 |
"is_httponly" 非空整数 |
last_access_utc |
整数 |
"last_access_utc" 非空整数 |
has_expires |
整数 |
"has_expires" 非空整数 默认为1 |
is_persistent |
整数 |
"is_persistent" 非空整数 默认为1 |
priority |
整数 |
"priority" 非空整数 默认为1 |
encrypted_value |
BLOB |
"encrypted_value" BLOB 默认为空字符串 |
samesite |
整数 |
"samesite" 非空整数 默认为-1 |
source_scheme |
整数 |
"source_scheme" 非空整数 默认为0 |
source_port |
整数 |
"source_port" 非空整数 默认为-1 |
is_same_party |
整数 |
"is_same_party" 非空整数 默认为0 |
Meta
表
名称 |
类型 |
模式 |
键 |
LONGVARCHAR |
"key" LONGVARCHAR NOT NULL UNIQUE |
值 |
LONGVARCHAR |
"value" LONGVARCHAR |
查看 stackoverflow 问题 "如何在 C# (.NET Core) 中读取 Brave 浏览器 cookie 数据库的加密值?" 此外,用户 @michael-fromberger 在这里详细介绍了基于 Chromium 的 cookie 结构:Google Chrome 加密的 Cookies — 这些详细说明了上述代码中许多看似无关紧要的字符串 (DPAPI
、v10
) 的来源。我还按照 PowerShell cookie 方法使用了 this gist 作为模板,作者是 @lawrencegripper。