Instantly share code, notes, and snippets.
Last active
February 7, 2025 06:53
-
Star
3
(3)
You must be signed in to star a gist -
Fork
0
(0)
You must be signed in to fork a gist
-
Save mavaddat/d94ad7c4999de516c101134eea0e0703 to your computer and use it in GitHub Desktop.
Get cookies from Google Chrome in PowerShell. Answer on https://stackoverflow.com/questions/53906168/using-chrome-cookies-for-invoke-webrequest-in-powershell/66272141
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
# One time setup | |
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 | |
} | |
# Download the MySQLite repository (for PowerShell <5 without PowerShellGet) | |
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 the zip | |
Unblock-File 'MySQLite.zip' | |
# Extract the MySQLite folder to a module path (e.g. $env:USERPROFILE\Documents\WindowsPowerShell\Modules\) | |
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)) { | |
#Simple alternative, if you have PowerShell ≥5, or the PowerShellGet module: | |
Install-Module MySQLite -Repository PSGallery -Scope CurrentUser | |
} | |
# Import the MySQLite module | |
Import-Module MySQLite #Alternatively, Import-Module \\Path\To\MySQLite | |
# Import Bouncy Castle Classes | |
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 } | |
# Specify for which domain you want to retrieve cookies | |
$domain = 'mavaddat.ca' | |
$cookiesPath = "$env:LOCALAPPDATA\Google\Chrome Beta\User Data\Default\Network\Cookies" # "$env:LOCALAPPDATA\Microsoft\Edge\User Data\Default\Network\Cookies" # "$env:APPDATA\Opera Software\Opera Stable\Cookies" | |
# Investigate the db structure | |
Get-MySQLiteTable -Path $cookiesPath -Detail | |
# Based on the schema of table `cookies`, form the query | |
$query = "SELECT name,encrypted_value,path,host_key FROM `"main`".`"cookies`" WHERE `"host_key`" LIKE '%$domain%' ESCAPE '\' LIMIT 0, 49999;" | |
# Or, get all cookies for all domains | |
$query = "SELECT name,encrypted_value,path,host_key FROM `"main`".`"cookies`" LIMIT 0, 49999;" | |
# Read the cookies from the SQLite | |
$cookies = Invoke-MySQLiteQuery -Path $cookiesPath -Query $query | |
# Get Chromium cookie master key | |
$localStatePath = "$env:LOCALAPPDATA\Google\Chrome Beta\User Data\Local State" # "$env:LOCALAPPDATA\Microsoft\Edge\User Data\Local State" # "$env:APPDATA\Opera Software\Opera Stable\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) | |
# Create a web session object for the IWR work | |
$session = New-Object Microsoft.PowerShell.Commands.WebRequestSession | |
# Prep the cipher elements | |
$cipher = [Org.BouncyCastle.Crypto.Modes.GcmBlockCipher]::new([Org.BouncyCastle.Crypto.Engines.AesEngine]::new()) | |
# Stuff the cookies into the session | |
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) | |
<# | |
# We don't need to keep the non-secret payload; however, this is how to retrieve it (spoiler alert, it's 'v10') | |
$nonSecretPayload = $cipherReader.ReadBytes(([System.Text.Encoding]::ASCII.GetBytes('v10').Count)) # | |
# if you want to read this, just use | |
[System.Text.Encoding]::Default.GetString($nonSecretPayload) | Out-Host | |
#> | |
# Alternatively, if you don't care about 'v10', move the stream pointer past it | |
$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 inner exception [Org.BouncyCastle.Crypto.InvalidCipherTextException] | |
if ($_.Exception.InnerException -is [Org.BouncyCastle.Crypto.InvalidCipherTextException]) { | |
Write-Error 'Invalid Cipher Text' | |
} | |
else { | |
Write-Error $_ # Echo the error unless you have a better way to handle | |
} | |
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 $_ # Echo the error unless you have a better way to handle | |
} | |
} | |
} | |
} | |
finally { | |
$cipherStream.Dispose() | |
$cipherReader.Dispose() | |
} | |
} | |
# Remove sensitive objects | |
$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 | |
# Do IWR Work | |
Invoke-WebRequest -Uri $domain -WebSession $session |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Yes, you can import the following Clixml as drop-in replacements for each respective field of
System.Security.Cryptography.AesGcm
:[System.Security.Cryptography.AesGcm]::NonceByteSizes
:[System.Security.Cryptography.AesGcm]::TagByteSizes
:Finally, I believe if you install PowerShell 7, you will be able to access the assembly above.