diff --git a/PowerShell/BloodHound.ps1 b/PowerShell/BloodHound.ps1
index 172e31c68..51ab191b5 100644
--- a/PowerShell/BloodHound.ps1
+++ b/PowerShell/BloodHound.ps1
@@ -2,7 +2,7 @@
<#
- PowerSploit File: PowerView.ps1
+ File: BloodHound.ps1
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: None
@@ -765,50 +765,6 @@ filter Get-IniContent {
}
}
-filter Export-PowerViewCSV {
-<#
- .SYNOPSIS
-
- This helper exports an -InputObject to a .csv in a thread-safe manner
- using a mutex. This is so the various multi-threaded functions in
- PowerView has a thread-safe way to export output to the same file.
-
- Based partially on Dmitry Sotnikov's Export-CSV code
- at http://poshcode.org/1590
-
- .LINK
-
- http://poshcode.org/1590
- http://dmitrysotnikov.wordpress.com/2010/01/19/Export-Csv-append/
-#>
- Param(
- [Parameter(Mandatory=$True, ValueFromPipeline=$True, ValueFromPipelineByPropertyName=$True)]
- [System.Management.Automation.PSObject[]]
- $InputObject,
-
- [Parameter(Mandatory=$True, Position=0)]
- [String]
- [ValidateNotNullOrEmpty()]
- $OutFile
- )
-
- $ObjectCSV = $InputObject | ConvertTo-Csv -NoTypeInformation
-
- # mutex so threaded code doesn't stomp on the output file
- $Mutex = New-Object System.Threading.Mutex $False,'CSVMutex';
- $Null = $Mutex.WaitOne()
-
- if (Test-Path -Path $OutFile) {
- # hack to skip the first line of output if the file already exists
- $ObjectCSV | ForEach-Object { $Start=$True }{ if ($Start) {$Start=$False} else {$_} } | Out-File -Encoding 'ASCII' -Append -FilePath $OutFile
- }
- else {
- $ObjectCSV | Out-File -Encoding 'ASCII' -Append -FilePath $OutFile
- }
-
- $Mutex.ReleaseMutex()
-}
-
filter Get-IPAddress {
<#
@@ -1092,7 +1048,6 @@ filter Convert-ADName {
}
else {
Write-Warning "Can not identify InType for $ObjectName"
- return $ObjectName
}
}
elseif($InputType -eq 'NT4') {
@@ -1129,7 +1084,7 @@ filter Convert-ADName {
Invoke-Method $Translate "Init" (1, $Domain)
}
catch [System.Management.Automation.MethodInvocationException] {
- Write-Verbose "Error with translate init in Convert-ADName: $_"
+ # Write-Verbose "Error with translate init in Convert-ADName: $_"
}
Set-Property $Translate "ChaseReferral" (0x60)
@@ -1139,359 +1094,7 @@ filter Convert-ADName {
(Invoke-Method $Translate "Get" ($NameTypes[$OutputType]))
}
catch [System.Management.Automation.MethodInvocationException] {
- Write-Verbose "Error with translate Set/Get in Convert-ADName: $_"
- }
-}
-
-
-function ConvertFrom-UACValue {
-<#
- .SYNOPSIS
-
- Converts a UAC int value to human readable form.
-
- .PARAMETER Value
-
- The int UAC value to convert.
-
- .PARAMETER ShowAll
-
- Show all UAC values, with a + indicating the value is currently set.
-
- .EXAMPLE
-
- PS C:\> ConvertFrom-UACValue -Value 66176
-
- Convert the UAC value 66176 to human readable format.
-
- .EXAMPLE
-
- PS C:\> Get-NetUser jason | select useraccountcontrol | ConvertFrom-UACValue
-
- Convert the UAC value for 'jason' to human readable format.
-
- .EXAMPLE
-
- PS C:\> Get-NetUser jason | select useraccountcontrol | ConvertFrom-UACValue -ShowAll
-
- Convert the UAC value for 'jason' to human readable format, showing all
- possible UAC values.
-#>
-
- [CmdletBinding()]
- param(
- [Parameter(Mandatory=$True, ValueFromPipeline=$True)]
- $Value,
-
- [Switch]
- $ShowAll
- )
-
- begin {
- # values from https://support.microsoft.com/en-us/kb/305144
- $UACValues = New-Object System.Collections.Specialized.OrderedDictionary
- $UACValues.Add("SCRIPT", 1)
- $UACValues.Add("ACCOUNTDISABLE", 2)
- $UACValues.Add("HOMEDIR_REQUIRED", 8)
- $UACValues.Add("LOCKOUT", 16)
- $UACValues.Add("PASSWD_NOTREQD", 32)
- $UACValues.Add("PASSWD_CANT_CHANGE", 64)
- $UACValues.Add("ENCRYPTED_TEXT_PWD_ALLOWED", 128)
- $UACValues.Add("TEMP_DUPLICATE_ACCOUNT", 256)
- $UACValues.Add("NORMAL_ACCOUNT", 512)
- $UACValues.Add("INTERDOMAIN_TRUST_ACCOUNT", 2048)
- $UACValues.Add("WORKSTATION_TRUST_ACCOUNT", 4096)
- $UACValues.Add("SERVER_TRUST_ACCOUNT", 8192)
- $UACValues.Add("DONT_EXPIRE_PASSWORD", 65536)
- $UACValues.Add("MNS_LOGON_ACCOUNT", 131072)
- $UACValues.Add("SMARTCARD_REQUIRED", 262144)
- $UACValues.Add("TRUSTED_FOR_DELEGATION", 524288)
- $UACValues.Add("NOT_DELEGATED", 1048576)
- $UACValues.Add("USE_DES_KEY_ONLY", 2097152)
- $UACValues.Add("DONT_REQ_PREAUTH", 4194304)
- $UACValues.Add("PASSWORD_EXPIRED", 8388608)
- $UACValues.Add("TRUSTED_TO_AUTH_FOR_DELEGATION", 16777216)
- $UACValues.Add("PARTIAL_SECRETS_ACCOUNT", 67108864)
- }
-
- process {
-
- $ResultUACValues = New-Object System.Collections.Specialized.OrderedDictionary
-
- if($Value -is [Int]) {
- $IntValue = $Value
- }
- elseif ($Value -is [PSCustomObject]) {
- if($Value.useraccountcontrol) {
- $IntValue = $Value.useraccountcontrol
- }
- }
- else {
- Write-Warning "Invalid object input for -Value : $Value"
- return $Null
- }
-
- if($ShowAll) {
- foreach ($UACValue in $UACValues.GetEnumerator()) {
- if( ($IntValue -band $UACValue.Value) -eq $UACValue.Value) {
- $ResultUACValues.Add($UACValue.Name, "$($UACValue.Value)+")
- }
- else {
- $ResultUACValues.Add($UACValue.Name, "$($UACValue.Value)")
- }
- }
- }
- else {
- foreach ($UACValue in $UACValues.GetEnumerator()) {
- if( ($IntValue -band $UACValue.Value) -eq $UACValue.Value) {
- $ResultUACValues.Add($UACValue.Name, "$($UACValue.Value)")
- }
- }
- }
- $ResultUACValues
- }
-}
-
-
-filter Get-Proxy {
-<#
- .SYNOPSIS
-
- Enumerates the proxy server and WPAD conents for the current user.
-
- .PARAMETER ComputerName
-
- The computername to enumerate proxy settings on, defaults to local host.
-
- .EXAMPLE
-
- PS C:\> Get-Proxy
-
- Returns the current proxy settings.
-#>
- param(
- [Parameter(ValueFromPipeline=$True)]
- [ValidateNotNullOrEmpty()]
- [String]
- $ComputerName = $ENV:COMPUTERNAME
- )
-
- try {
- $Reg = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey('CurrentUser', $ComputerName)
- $RegKey = $Reg.OpenSubkey("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Internet Settings")
- $ProxyServer = $RegKey.GetValue('ProxyServer')
- $AutoConfigURL = $RegKey.GetValue('AutoConfigURL')
-
- $Wpad = ""
- if($AutoConfigURL -and ($AutoConfigURL -ne "")) {
- try {
- $Wpad = (New-Object Net.Webclient).DownloadString($AutoConfigURL)
- }
- catch {
- Write-Warning "Error connecting to AutoConfigURL : $AutoConfigURL"
- }
- }
-
- if($ProxyServer -or $AutoConfigUrl) {
-
- $Properties = @{
- 'ProxyServer' = $ProxyServer
- 'AutoConfigURL' = $AutoConfigURL
- 'Wpad' = $Wpad
- }
-
- New-Object -TypeName PSObject -Property $Properties
- }
- else {
- Write-Warning "No proxy settings found for $ComputerName"
- }
- }
- catch {
- Write-Warning "Error enumerating proxy settings for $ComputerName : $_"
- }
-}
-
-
-function Request-SPNTicket {
-<#
- .SYNOPSIS
-
- Request the kerberos ticket for a specified service principal name (SPN).
-
- .PARAMETER SPN
-
- The service principal name to request the ticket for. Required.
-
- .EXAMPLE
-
- PS C:\> Request-SPNTicket -SPN "HTTP/web.testlab.local"
-
- Request a kerberos service ticket for the specified SPN.
-
- .EXAMPLE
-
- PS C:\> "HTTP/web1.testlab.local","HTTP/web2.testlab.local" | Request-SPNTicket
-
- Request kerberos service tickets for all SPNs passed on the pipeline.
-
- .EXAMPLE
-
- PS C:\> Get-NetUser -SPN | Request-SPNTicket
-
- Request kerberos service tickets for all users with non-null SPNs.
-#>
-
- [CmdletBinding()]
- Param (
- [Parameter(Mandatory=$True, ValueFromPipelineByPropertyName = $True)]
- [Alias('ServicePrincipalName')]
- [String[]]
- $SPN
- )
-
- begin {
- Add-Type -AssemblyName System.IdentityModel
- }
-
- process {
- Write-Verbose "Requesting ticket for: $SPN"
- New-Object System.IdentityModel.Tokens.KerberosRequestorSecurityToken -ArgumentList $SPN
- }
-}
-
-
-function Get-PathAcl {
-<#
- .SYNOPSIS
-
- Enumerates the ACL for a given file path.
-
- .PARAMETER Path
-
- The local/remote path to enumerate the ACLs for.
-
- .PARAMETER Recurse
-
- If any ACL results are groups, recurse and retrieve user membership.
-
- .EXAMPLE
-
- PS C:\> Get-PathAcl "\\SERVER\Share\"
-
- Returns ACLs for the given UNC share.
-#>
- [CmdletBinding()]
- param(
- [Parameter(Mandatory=$True, ValueFromPipeline=$True)]
- [String]
- $Path,
-
- [Switch]
- $Recurse
- )
-
- begin {
-
- function Convert-FileRight {
-
- # From http://stackoverflow.com/questions/28029872/retrieving-security-descriptor-and-getting-number-for-filesystemrights
-
- [CmdletBinding()]
- param(
- [Int]
- $FSR
- )
-
- $AccessMask = @{
- [uint32]'0x80000000' = 'GenericRead'
- [uint32]'0x40000000' = 'GenericWrite'
- [uint32]'0x20000000' = 'GenericExecute'
- [uint32]'0x10000000' = 'GenericAll'
- [uint32]'0x02000000' = 'MaximumAllowed'
- [uint32]'0x01000000' = 'AccessSystemSecurity'
- [uint32]'0x00100000' = 'Synchronize'
- [uint32]'0x00080000' = 'WriteOwner'
- [uint32]'0x00040000' = 'WriteDAC'
- [uint32]'0x00020000' = 'ReadControl'
- [uint32]'0x00010000' = 'Delete'
- [uint32]'0x00000100' = 'WriteAttributes'
- [uint32]'0x00000080' = 'ReadAttributes'
- [uint32]'0x00000040' = 'DeleteChild'
- [uint32]'0x00000020' = 'Execute/Traverse'
- [uint32]'0x00000010' = 'WriteExtendedAttributes'
- [uint32]'0x00000008' = 'ReadExtendedAttributes'
- [uint32]'0x00000004' = 'AppendData/AddSubdirectory'
- [uint32]'0x00000002' = 'WriteData/AddFile'
- [uint32]'0x00000001' = 'ReadData/ListDirectory'
- }
-
- $SimplePermissions = @{
- [uint32]'0x1f01ff' = 'FullControl'
- [uint32]'0x0301bf' = 'Modify'
- [uint32]'0x0200a9' = 'ReadAndExecute'
- [uint32]'0x02019f' = 'ReadAndWrite'
- [uint32]'0x020089' = 'Read'
- [uint32]'0x000116' = 'Write'
- }
-
- $Permissions = @()
-
- # get simple permission
- $Permissions += $SimplePermissions.Keys | % {
- if (($FSR -band $_) -eq $_) {
- $SimplePermissions[$_]
- $FSR = $FSR -band (-not $_)
- }
- }
-
- # get remaining extended permissions
- $Permissions += $AccessMask.Keys |
- ? { $FSR -band $_ } |
- % { $AccessMask[$_] }
-
- ($Permissions | ?{$_}) -join ","
- }
- }
-
- process {
-
- try {
- $ACL = Get-Acl -Path $Path
-
- $ACL.GetAccessRules($true,$true,[System.Security.Principal.SecurityIdentifier]) | ForEach-Object {
-
- $Names = @()
- if ($_.IdentityReference -match '^S-1-5-21-[0-9]+-[0-9]+-[0-9]+-[0-9]+') {
- $Object = Get-ADObject -SID $_.IdentityReference
- $Names = @()
- $SIDs = @($Object.objectsid)
-
- if ($Recurse -and (@('268435456','268435457','536870912','536870913') -contains $Object.samAccountType)) {
- $SIDs += Get-NetGroupMember -SID $Object.objectsid | Select-Object -ExpandProperty MemberSid
- }
-
- $SIDs | ForEach-Object {
- $Names += ,@($_, (Convert-SidToName $_))
- }
- }
- else {
- $Names += ,@($_.IdentityReference.Value, (Convert-SidToName $_.IdentityReference.Value))
- }
-
- ForEach($Name in $Names) {
- $Out = New-Object PSObject
- $Out | Add-Member Noteproperty 'Path' $Path
- $Out | Add-Member Noteproperty 'FileSystemRights' (Convert-FileRight -FSR $_.FileSystemRights.value__)
- $Out | Add-Member Noteproperty 'IdentityReference' $Name[1]
- $Out | Add-Member Noteproperty 'IdentitySID' $Name[0]
- $Out | Add-Member Noteproperty 'AccessControlType' $_.AccessControlType
- $Out
- }
- }
- }
- catch {
- Write-Warning $_
- }
+ # Write-Verbose "Error with translate Set/Get in Convert-ADName: $_"
}
}
@@ -1781,408 +1384,54 @@ filter Get-DomainSearcher {
}
-filter Convert-DNSRecord {
+filter Get-NetDomain {
<#
.SYNOPSIS
- Decodes a binary DNS record.
-
- Adapted/ported from Michael B. Smith's code at https://raw.githubusercontent.com/mmessano/PowerShell/master/dns-dump.ps1
-
- .PARAMETER DNSRecord
+ Returns a given domain object.
- The domain to query for zones, defaults to the current domain.
+ .PARAMETER Domain
- .LINK
+ The domain name to query for, defaults to the current domain.
- https://raw.githubusercontent.com/mmessano/PowerShell/master/dns-dump.ps1
-#>
- param(
- [Parameter(Position=0, ValueFromPipelineByPropertyName=$True, Mandatory=$True)]
- [Byte[]]
- $DNSRecord
- )
+ .PARAMETER Credential
- function Get-Name {
- # modified decodeName from https://raw.githubusercontent.com/mmessano/PowerShell/master/dns-dump.ps1
- [CmdletBinding()]
- param(
- [Byte[]]
- $Raw
- )
+ A [Management.Automation.PSCredential] object of alternate credentials
+ for connection to the target domain.
- [Int]$Length = $Raw[0]
- [Int]$Segments = $Raw[1]
- [Int]$Index = 2
- [String]$Name = ""
+ .EXAMPLE
- while ($Segments-- -gt 0)
- {
- [Int]$SegmentLength = $Raw[$Index++]
- while ($SegmentLength-- -gt 0) {
- $Name += [Char]$Raw[$Index++]
- }
- $Name += "."
- }
- $Name
- }
+ PS C:\> Get-NetDomain -Domain testlab.local
- $RDataLen = [BitConverter]::ToUInt16($DNSRecord, 0)
- $RDataType = [BitConverter]::ToUInt16($DNSRecord, 2)
- $UpdatedAtSerial = [BitConverter]::ToUInt32($DNSRecord, 8)
+ .EXAMPLE
- $TTLRaw = $DNSRecord[12..15]
- # reverse for big endian
- $Null = [array]::Reverse($TTLRaw)
- $TTL = [BitConverter]::ToUInt32($TTLRaw, 0)
+ PS C:\> "testlab.local" | Get-NetDomain
- $Age = [BitConverter]::ToUInt32($DNSRecord, 20)
- if($Age -ne 0) {
- $TimeStamp = ((Get-Date -Year 1601 -Month 1 -Day 1 -Hour 0 -Minute 0 -Second 0).AddHours($age)).ToString()
- }
- else {
- $TimeStamp = "[static]"
- }
+ .LINK
- $DNSRecordObject = New-Object PSObject
+ http://social.technet.microsoft.com/Forums/scriptcenter/en-US/0c5b3f83-e528-4d49-92a4-dee31f4b481c/finding-the-dn-of-the-the-domain-without-admodule-in-powershell?forum=ITCG
+#>
- if($RDataType -eq 1) {
- $IP = "{0}.{1}.{2}.{3}" -f $DNSRecord[24], $DNSRecord[25], $DNSRecord[26], $DNSRecord[27]
- $Data = $IP
- $DNSRecordObject | Add-Member Noteproperty 'RecordType' 'A'
- }
+ param(
+ [Parameter(ValueFromPipeline=$True)]
+ [String]
+ $Domain,
- elseif($RDataType -eq 2) {
- $NSName = Get-Name $DNSRecord[24..$DNSRecord.length]
- $Data = $NSName
- $DNSRecordObject | Add-Member Noteproperty 'RecordType' 'NS'
- }
+ [Management.Automation.PSCredential]
+ $Credential
+ )
- elseif($RDataType -eq 5) {
- $Alias = Get-Name $DNSRecord[24..$DNSRecord.length]
- $Data = $Alias
- $DNSRecordObject | Add-Member Noteproperty 'RecordType' 'CNAME'
- }
+ if($Credential) {
- elseif($RDataType -eq 6) {
- # TODO: how to implement properly? nested object?
- $Data = $([System.Convert]::ToBase64String($DNSRecord[24..$DNSRecord.length]))
- $DNSRecordObject | Add-Member Noteproperty 'RecordType' 'SOA'
- }
+ Write-Verbose "Using alternate credentials for Get-NetDomain"
- elseif($RDataType -eq 12) {
- $Ptr = Get-Name $DNSRecord[24..$DNSRecord.length]
- $Data = $Ptr
- $DNSRecordObject | Add-Member Noteproperty 'RecordType' 'PTR'
- }
+ if(!$Domain) {
+ # if no domain is supplied, extract the logon domain from the PSCredential passed
+ $Domain = $Credential.GetNetworkCredential().Domain
+ Write-Verbose "Extracted domain '$Domain' from -Credential"
+ }
- elseif($RDataType -eq 13) {
- # TODO: how to implement properly? nested object?
- $Data = $([System.Convert]::ToBase64String($DNSRecord[24..$DNSRecord.length]))
- $DNSRecordObject | Add-Member Noteproperty 'RecordType' 'HINFO'
- }
-
- elseif($RDataType -eq 15) {
- # TODO: how to implement properly? nested object?
- $Data = $([System.Convert]::ToBase64String($DNSRecord[24..$DNSRecord.length]))
- $DNSRecordObject | Add-Member Noteproperty 'RecordType' 'MX'
- }
-
- elseif($RDataType -eq 16) {
-
- [string]$TXT = ""
- [int]$SegmentLength = $DNSRecord[24]
- $Index = 25
- while ($SegmentLength-- -gt 0) {
- $TXT += [char]$DNSRecord[$index++]
- }
-
- $Data = $TXT
- $DNSRecordObject | Add-Member Noteproperty 'RecordType' 'TXT'
- }
-
- elseif($RDataType -eq 28) {
- # TODO: how to implement properly? nested object?
- $Data = $([System.Convert]::ToBase64String($DNSRecord[24..$DNSRecord.length]))
- $DNSRecordObject | Add-Member Noteproperty 'RecordType' 'AAAA'
- }
-
- elseif($RDataType -eq 33) {
- # TODO: how to implement properly? nested object?
- $Data = $([System.Convert]::ToBase64String($DNSRecord[24..$DNSRecord.length]))
- $DNSRecordObject | Add-Member Noteproperty 'RecordType' 'SRV'
- }
-
- else {
- $Data = $([System.Convert]::ToBase64String($DNSRecord[24..$DNSRecord.length]))
- $DNSRecordObject | Add-Member Noteproperty 'RecordType' 'UNKNOWN'
- }
-
- $DNSRecordObject | Add-Member Noteproperty 'UpdatedAtSerial' $UpdatedAtSerial
- $DNSRecordObject | Add-Member Noteproperty 'TTL' $TTL
- $DNSRecordObject | Add-Member Noteproperty 'Age' $Age
- $DNSRecordObject | Add-Member Noteproperty 'TimeStamp' $TimeStamp
- $DNSRecordObject | Add-Member Noteproperty 'Data' $Data
- $DNSRecordObject
-}
-
-
-filter Get-DNSZone {
-<#
- .SYNOPSIS
-
- Enumerates the Active Directory DNS zones for a given domain.
-
- .PARAMETER Domain
-
- The domain to query for zones, defaults to the current domain.
-
- .PARAMETER DomainController
-
- Domain controller to reflect LDAP queries through.
-
- .PARAMETER PageSize
-
- The PageSize to set for the LDAP searcher object.
-
- .PARAMETER Credential
-
- A [Management.Automation.PSCredential] object of alternate credentials
- for connection to the target domain.
-
- .PARAMETER FullData
-
- Switch. Return full computer objects instead of just system names (the default).
-
- .EXAMPLE
-
- PS C:\> Get-DNSZone
-
- Retrieves the DNS zones for the current domain.
-
- .EXAMPLE
-
- PS C:\> Get-DNSZone -Domain dev.testlab.local -DomainController primary.testlab.local
-
- Retrieves the DNS zones for the dev.testlab.local domain, reflecting the LDAP queries
- through the primary.testlab.local domain controller.
-#>
-
- param(
- [Parameter(Position=0, ValueFromPipeline=$True)]
- [String]
- $Domain,
-
- [String]
- $DomainController,
-
- [ValidateRange(1,10000)]
- [Int]
- $PageSize = 200,
-
- [Management.Automation.PSCredential]
- $Credential,
-
- [Switch]
- $FullData
- )
-
- $DNSSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -PageSize $PageSize -Credential $Credential
- $DNSSearcher.filter="(objectClass=dnsZone)"
-
- if($DNSSearcher) {
- $Results = $DNSSearcher.FindAll()
- $Results | Where-Object {$_} | ForEach-Object {
- # convert/process the LDAP fields for each result
- $Properties = Convert-LDAPProperty -Properties $_.Properties
- $Properties | Add-Member NoteProperty 'ZoneName' $Properties.name
-
- if ($FullData) {
- $Properties
- }
- else {
- $Properties | Select-Object ZoneName,distinguishedname,whencreated,whenchanged
- }
- }
- $Results.dispose()
- $DNSSearcher.dispose()
- }
-
- $DNSSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -PageSize $PageSize -Credential $Credential -ADSprefix "CN=MicrosoftDNS,DC=DomainDnsZones"
- $DNSSearcher.filter="(objectClass=dnsZone)"
-
- if($DNSSearcher) {
- $Results = $DNSSearcher.FindAll()
- $Results | Where-Object {$_} | ForEach-Object {
- # convert/process the LDAP fields for each result
- $Properties = Convert-LDAPProperty -Properties $_.Properties
- $Properties | Add-Member NoteProperty 'ZoneName' $Properties.name
-
- if ($FullData) {
- $Properties
- }
- else {
- $Properties | Select-Object ZoneName,distinguishedname,whencreated,whenchanged
- }
- }
- $Results.dispose()
- $DNSSearcher.dispose()
- }
-}
-
-
-filter Get-DNSRecord {
-<#
- .SYNOPSIS
-
- Enumerates the Active Directory DNS records for a given zone.
-
- .PARAMETER ZoneName
-
- The zone to query for records (which can be enumearted with Get-DNSZone). Required.
-
- .PARAMETER Domain
-
- The domain to query for zones, defaults to the current domain.
-
- .PARAMETER DomainController
-
- Domain controller to reflect LDAP queries through.
-
- .PARAMETER PageSize
-
- The PageSize to set for the LDAP searcher object.
-
- .PARAMETER Credential
-
- A [Management.Automation.PSCredential] object of alternate credentials
- for connection to the target domain.
-
- .EXAMPLE
-
- PS C:\> Get-DNSRecord -ZoneName testlab.local
-
- Retrieve all records for the testlab.local zone.
-
- .EXAMPLE
-
- PS C:\> Get-DNSZone | Get-DNSRecord
-
- Retrieve all records for all zones in the current domain.
-
- .EXAMPLE
-
- PS C:\> Get-DNSZone -Domain dev.testlab.local | Get-DNSRecord -Domain dev.testlab.local
-
- Retrieve all records for all zones in the dev.testlab.local domain.
-#>
-
- param(
- [Parameter(Position=0, ValueFromPipelineByPropertyName=$True, Mandatory=$True)]
- [String]
- $ZoneName,
-
- [String]
- $Domain,
-
- [String]
- $DomainController,
-
- [ValidateRange(1,10000)]
- [Int]
- $PageSize = 200,
-
- [Management.Automation.PSCredential]
- $Credential
- )
-
- $DNSSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -PageSize $PageSize -Credential $Credential -ADSprefix "DC=$($ZoneName),CN=MicrosoftDNS,DC=DomainDnsZones"
- $DNSSearcher.filter="(objectClass=dnsNode)"
-
- if($DNSSearcher) {
- $Results = $DNSSearcher.FindAll()
- $Results | Where-Object {$_} | ForEach-Object {
- try {
- # convert/process the LDAP fields for each result
- $Properties = Convert-LDAPProperty -Properties $_.Properties | Select-Object name,distinguishedname,dnsrecord,whencreated,whenchanged
- $Properties | Add-Member NoteProperty 'ZoneName' $ZoneName
-
- # convert the record and extract the properties
- if ($Properties.dnsrecord -is [System.DirectoryServices.ResultPropertyValueCollection]) {
- # TODO: handle multiple nested records properly?
- $Record = Convert-DNSRecord -DNSRecord $Properties.dnsrecord[0]
- }
- else {
- $Record = Convert-DNSRecord -DNSRecord $Properties.dnsrecord
- }
-
- if($Record) {
- $Record.psobject.properties | ForEach-Object {
- $Properties | Add-Member NoteProperty $_.Name $_.Value
- }
- }
-
- $Properties
- }
- catch {
- Write-Warning "ERROR: $_"
- $Properties
- }
- }
- $Results.dispose()
- $DNSSearcher.dispose()
- }
-}
-
-
-filter Get-NetDomain {
-<#
- .SYNOPSIS
-
- Returns a given domain object.
-
- .PARAMETER Domain
-
- The domain name to query for, defaults to the current domain.
-
- .PARAMETER Credential
-
- A [Management.Automation.PSCredential] object of alternate credentials
- for connection to the target domain.
-
- .EXAMPLE
-
- PS C:\> Get-NetDomain -Domain testlab.local
-
- .EXAMPLE
-
- PS C:\> "testlab.local" | Get-NetDomain
-
- .LINK
-
- http://social.technet.microsoft.com/Forums/scriptcenter/en-US/0c5b3f83-e528-4d49-92a4-dee31f4b481c/finding-the-dn-of-the-the-domain-without-admodule-in-powershell?forum=ITCG
-#>
-
- param(
- [Parameter(ValueFromPipeline=$True)]
- [String]
- $Domain,
-
- [Management.Automation.PSCredential]
- $Credential
- )
-
- if($Credential) {
-
- Write-Verbose "Using alternate credentials for Get-NetDomain"
-
- if(!$Domain) {
- # if no domain is supplied, extract the logon domain from the PSCredential passed
- $Domain = $Credential.GetNetworkCredential().Domain
- Write-Verbose "Extracted domain '$Domain' from -Credential"
- }
-
- $DomainContext = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext('Domain', $Domain, $Credential.UserName, $Credential.GetNetworkCredential().Password)
+ $DomainContext = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext('Domain', $Domain, $Credential.UserName, $Credential.GetNetworkCredential().Password)
try {
[System.DirectoryServices.ActiveDirectory.Domain]::GetDomain($DomainContext)
@@ -2328,43 +1577,6 @@ filter Get-NetForestDomain {
}
-filter Get-NetForestCatalog {
-<#
- .SYNOPSIS
-
- Return all global catalogs for a given forest.
-
- .PARAMETER Forest
-
- The forest name to query domain for.
-
- .PARAMETER Credential
-
- A [Management.Automation.PSCredential] object of alternate credentials
- for connection to the target domain.
-
- .EXAMPLE
-
- PS C:\> Get-NetForestCatalog
-#>
-
- param(
- [Parameter(ValueFromPipeline=$True)]
- [String]
- $Forest,
-
- [Management.Automation.PSCredential]
- $Credential
- )
-
- $ForestObject = Get-NetForest -Forest $Forest -Credential $Credential
-
- if($ForestObject) {
- $ForestObject.FindAllGlobalCatalogs()
- }
-}
-
-
filter Get-NetDomainController {
<#
.SYNOPSIS
@@ -2442,51 +1654,67 @@ filter Get-NetDomainController {
#
########################################################
-function Get-NetUser {
+
+function Get-NetComputer {
<#
.SYNOPSIS
- Query information for a given user or users in the domain
- using ADSI and LDAP. Another -Domain can be specified to
- query for users across a trust.
- Replacement for "net users /domain"
+ This function utilizes adsisearcher to query the current AD context
+ for current computer objects. Based off of Carlos Perez's Audit.psm1
+ script in Posh-SecMod (link below).
- .PARAMETER UserName
+ .PARAMETER ComputerName
- Username filter string, wildcards accepted.
+ Return computers with a specific name, wildcards accepted.
- .PARAMETER Domain
+ .PARAMETER SPN
- The domain to query for users, defaults to the current domain.
+ Return computers with a specific service principal name, wildcards accepted.
- .PARAMETER DomainController
+ .PARAMETER OperatingSystem
- Domain controller to reflect LDAP queries through.
+ Return computers with a specific operating system, wildcards accepted.
- .PARAMETER ADSpath
+ .PARAMETER ServicePack
- The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
- Useful for OU queries.
+ Return computers with a specific service pack, wildcards accepted.
.PARAMETER Filter
A customized ldap filter string to use, e.g. "(description=*admin*)"
- .PARAMETER AdminCount
+ .PARAMETER Printers
- Switch. Return users with adminCount=1.
+ Switch. Return only printers.
- .PARAMETER SPN
+ .PARAMETER Ping
+
+ Switch. Ping each host to ensure it's up before enumerating.
- Switch. Only return user objects with non-null service principal names.
+ .PARAMETER FullData
- .PARAMETER Unconstrained
+ Switch. Return full computer objects instead of just system names (the default).
+
+ .PARAMETER Domain
+
+ The domain to query for computers, defaults to the current domain.
+
+ .PARAMETER DomainController
+
+ Domain controller to reflect LDAP queries through.
- Switch. Return users that have unconstrained delegation.
+ .PARAMETER ADSpath
+
+ The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
+ Useful for OU queries.
+
+ .PARAMETER SiteName
+
+ The AD Site name to search for computers.
- .PARAMETER AllowDelegation
+ .PARAMETER Unconstrained
- Switch. Return user accounts that are not marked as 'sensitive and not allowed for delegation'
+ Switch. Return computer objects that have unconstrained delegation.
.PARAMETER PageSize
@@ -2499,41 +1727,75 @@ function Get-NetUser {
.EXAMPLE
- PS C:\> Get-NetUser -Domain testing
+ PS C:\> Get-NetComputer
+
+ Returns the current computers in current domain.
+
+ .EXAMPLE
+
+ PS C:\> Get-NetComputer -SPN mssql*
+
+ Returns all MS SQL servers on the domain.
+
+ .EXAMPLE
+
+ PS C:\> Get-NetComputer -Domain testing
+
+ Returns the current computers in 'testing' domain.
.EXAMPLE
- PS C:\> Get-NetUser -ADSpath "LDAP://OU=secret,DC=testlab,DC=local"
+ PS C:\> Get-NetComputer -Domain testing -FullData
+
+ Returns full computer objects in the 'testing' domain.
+
+ .LINK
+
+ https://github.com/darkoperator/Posh-SecMod/blob/master/Audit/Audit.psm1
#>
- param(
- [Parameter(Position=0, ValueFromPipeline=$True)]
+ [CmdletBinding()]
+ Param (
+ [Parameter(ValueFromPipeline=$True)]
+ [Alias('HostName')]
[String]
- $UserName,
+ $ComputerName = '*',
[String]
- $Domain,
+ $SPN,
[String]
- $DomainController,
+ $OperatingSystem,
[String]
- $ADSpath,
+ $ServicePack,
[String]
$Filter,
[Switch]
- $SPN,
+ $Printers,
[Switch]
- $AdminCount,
+ $Ping,
[Switch]
- $Unconstrained,
+ $FullData,
+
+ [String]
+ $Domain,
+
+ [String]
+ $DomainController,
+
+ [String]
+ $ADSpath,
+
+ [String]
+ $SiteName,
[Switch]
- $AllowDelegation,
+ $Unconstrained,
[ValidateRange(1,10000)]
[Int]
@@ -2544,377 +1806,394 @@ function Get-NetUser {
)
begin {
- if($Domain) {
- $TargetDomain = $Domain
- }
- else {
- $TargetDomain = (Get-NetDomain).name
- }
-
- # so this isn't repeated if users are passed on the pipeline
- $UserSearcher = Get-DomainSearcher -Domain $TargetDomain -ADSpath $ADSpath -DomainController $DomainController -PageSize $PageSize -Credential $Credential
-
- $PrimaryGroups = @{}
+ # so this isn't repeated if multiple computer names are passed on the pipeline
+ $CompSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -ADSpath $ADSpath -PageSize $PageSize -Credential $Credential
}
process {
- if($UserSearcher) {
+
+ if ($CompSearcher) {
# if we're checking for unconstrained delegation
if($Unconstrained) {
- Write-Verbose "Checking for unconstrained delegation"
+ Write-Verbose "Searching for computers with for unconstrained delegation"
$Filter += "(userAccountControl:1.2.840.113556.1.4.803:=524288)"
}
- if($AllowDelegation) {
- Write-Verbose "Checking for users who can be delegated"
- # negation of "Accounts that are sensitive and not trusted for delegation"
- $Filter += "(!(userAccountControl:1.2.840.113556.1.4.803:=1048574))"
+ # set the filters for the seracher if it exists
+ if($Printers) {
+ Write-Verbose "Searching for printers"
+ # $CompSearcher.filter="(&(objectCategory=printQueue)$Filter)"
+ $Filter += "(objectCategory=printQueue)"
+ }
+ if($SPN) {
+ Write-Verbose "Searching for computers with SPN: $SPN"
+ $Filter += "(servicePrincipalName=$SPN)"
}
- if($AdminCount) {
- Write-Verbose "Checking for adminCount=1"
- $Filter += "(admincount=1)"
+ if($OperatingSystem) {
+ $Filter += "(operatingsystem=$OperatingSystem)"
}
-
- # check if we're using a username filter or not
- if($UserName) {
- # samAccountType=805306368 indicates user objects
- $UserSearcher.filter="(&(samAccountType=805306368)(samAccountName=$UserName)$Filter)"
+ if($ServicePack) {
+ $Filter += "(operatingsystemservicepack=$ServicePack)"
}
- elseif($SPN) {
- $UserSearcher.filter="(&(samAccountType=805306368)(servicePrincipalName=*)$Filter)"
+ if($SiteName) {
+ $Filter += "(serverreferencebl=$SiteName)"
}
- else {
- # filter is something like "(samAccountName=*blah*)" if specified
- $UserSearcher.filter="(&(samAccountType=805306368)$Filter)"
+
+ $CompFilter = "(&(sAMAccountType=805306369)(dnshostname=$ComputerName)$Filter)"
+ Write-Verbose "Get-NetComputer filter : $CompFilter"
+ $CompSearcher.filter = $CompFilter
+ if(-not $FullData) {
+ $Null = $CompSearcher.PropertiesToLoad.Add('dnshostname')
}
try {
- $Results = $UserSearcher.FindAll()
- $Results | Where-Object {$_} | ForEach-Object {
- # convert/process the LDAP fields for each result
- $User = Convert-LDAPProperty -Properties $_.Properties
-
- $User | Add-Member NoteProperty 'Domain' $TargetDomain
-
- $DomainSID = $User.objectsid.Substring(0, $User.objectsid.LastIndexOf('-'))
- $PrimaryGroupSID = "$DomainSID-$($User.primarygroupid)"
-
- $User | Add-Member NoteProperty 'PrimaryGroupSID' $PrimaryGroupSID
-
- if($PrimaryGroups[$PrimaryGroupSID]) {
- $PrimaryGroupName = $PrimaryGroups[$PrimaryGroupSID]
- }
- else {
- $PrimaryGroupName = Get-ADObject -Domain $Domain -SID $PrimaryGroupSID | Select-Object -ExpandProperty samaccountname
- $PrimaryGroups[$PrimaryGroupSID] = $PrimaryGroupName
+ ForEach($ComputerResult in $CompSearcher.FindAll()) {
+ if($ComputerResult) {
+ $Up = $True
+ if($Ping) {
+ $Up = Test-Connection -Count 1 -Quiet -ComputerName $ComputerResult.properties.dnshostname
+ }
+ if($Up) {
+ # return full data objects
+ if ($FullData) {
+ # convert/process the LDAP fields for each result
+ $Computer = Convert-LDAPProperty -Properties $ComputerResult.Properties
+ $Computer.PSObject.TypeNames.Add('PowerView.Computer')
+ $Computer
+ }
+ else {
+ # otherwise we're just returning the DNS host name
+ $ComputerResult.properties.dnshostname
+ }
+ }
}
-
- $User | Add-Member NoteProperty 'PrimaryGroupName' $PrimaryGroupName
-
- $User.PSObject.TypeNames.Add('PowerView.User')
- $User
}
- $Results.dispose()
+
+ $CompSearcher.dispose()
}
catch {
- Write-Verbose "Error building the UserSearcher searcher object!"
+ Write-Warning "Error: $_"
}
- $UserSearcher.dispose()
}
}
}
-function Add-NetUser {
+function Get-ADObject {
<#
.SYNOPSIS
- Adds a domain user or a local user to the current (or remote) machine,
- if permissions allow, utilizing the WinNT service provider and
- DirectoryServices.AccountManagement, respectively.
+ Takes a domain SID and returns the user, group, or computer object
+ associated with it.
- The default behavior is to add a user to the local machine.
- An optional group name to add the user to can be specified.
+ .PARAMETER SID
- .PARAMETER UserName
+ The SID of the domain object you're querying for.
- The username to add. If not given, it defaults to 'backdoor'
+ .PARAMETER Name
- .PARAMETER Password
+ The Name of the domain object you're querying for.
- The password to set for the added user. If not given, it defaults to 'Password123!'
+ .PARAMETER SamAccountName
- .PARAMETER GroupName
+ The SamAccountName of the domain object you're querying for.
- Group to optionally add the user to.
+ .PARAMETER Domain
- .PARAMETER ComputerName
+ The domain to query for objects, defaults to the current domain.
- Hostname to add the local user to, defaults to 'localhost'
+ .PARAMETER DomainController
- .PARAMETER Domain
+ Domain controller to reflect LDAP queries through.
- Specified domain to add the user to.
+ .PARAMETER ADSpath
- .EXAMPLE
+ The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
+ Useful for OU queries.
- PS C:\> Add-NetUser -UserName john -Password 'Password123!'
+ .PARAMETER Filter
- Adds a localuser 'john' to the local machine with password of 'Password123!'
+ Additional LDAP filter string for the query.
- .EXAMPLE
+ .PARAMETER ReturnRaw
- PS C:\> Add-NetUser -UserName john -Password 'Password123!' -ComputerName server.testlab.local
+ Switch. Return the raw object instead of translating its properties.
+ Used by Set-ADObject to modify object properties.
- Adds a localuser 'john' with password of 'Password123!' to server.testlab.local's local Administrators group.
+ .PARAMETER PageSize
- .EXAMPLE
+ The PageSize to set for the LDAP searcher object.
- PS C:\> Add-NetUser -UserName john -Password password -GroupName "Domain Admins" -Domain ''
+ .PARAMETER Credential
- Adds the user "john" with password "password" to the current domain and adds
- the user to the domain group "Domain Admins"
+ A [Management.Automation.PSCredential] object of alternate credentials
+ for connection to the target domain.
.EXAMPLE
- PS C:\> Add-NetUser -UserName john -Password password -GroupName "Domain Admins" -Domain 'testing'
+ PS C:\> Get-ADObject -SID "S-1-5-21-2620891829-2411261497-1773853088-1110"
+
+ Get the domain object associated with the specified SID.
- Adds the user "john" with password "password" to the 'testing' domain and adds
- the user to the domain group "Domain Admins"
+ .EXAMPLE
- .Link
+ PS C:\> Get-ADObject -ADSpath "CN=AdminSDHolder,CN=System,DC=testlab,DC=local"
- http://blogs.technet.com/b/heyscriptingguy/archive/2010/11/23/use-powershell-to-create-local-user-accounts.aspx
+ Get the AdminSDHolder object for the testlab.local domain.
#>
[CmdletBinding()]
Param (
- [ValidateNotNullOrEmpty()]
+ [Parameter(ValueFromPipeline=$True)]
[String]
- $UserName = 'backdoor',
+ $SID,
- [ValidateNotNullOrEmpty()]
[String]
- $Password = 'Password123!',
+ $Name,
- [ValidateNotNullOrEmpty()]
[String]
- $GroupName,
+ $SamAccountName,
- [ValidateNotNullOrEmpty()]
- [Alias('HostName')]
[String]
- $ComputerName = 'localhost',
+ $Domain,
- [ValidateNotNullOrEmpty()]
[String]
- $Domain
- )
+ $DomainController,
- if ($Domain) {
+ [String]
+ $ADSpath,
- $DomainObject = Get-NetDomain -Domain $Domain
- if(-not $DomainObject) {
- Write-Warning "Error in grabbing $Domain object"
- return $Null
- }
+ [String]
+ $Filter,
- # add the assembly we need
- Add-Type -AssemblyName System.DirectoryServices.AccountManagement
+ [Switch]
+ $ReturnRaw,
- # http://richardspowershellblog.wordpress.com/2008/05/25/system-directoryservices-accountmanagement/
- # get the domain context
- $Context = New-Object -TypeName System.DirectoryServices.AccountManagement.PrincipalContext -ArgumentList ([System.DirectoryServices.AccountManagement.ContextType]::Domain), $DomainObject
+ [ValidateRange(1,10000)]
+ [Int]
+ $PageSize = 200,
- # create the user object
- $User = New-Object -TypeName System.DirectoryServices.AccountManagement.UserPrincipal -ArgumentList $Context
+ [Management.Automation.PSCredential]
+ $Credential
+ )
+ process {
+ if($SID -and (-not $Domain)) {
+ # if a SID is passed, try to resolve it to a reachable domain name for the searcher
+ try {
+ $Name = Convert-SidToName $SID
+ if($Name) {
+ $Canonical = Convert-ADName -ObjectName $Name -InputType NT4 -OutputType Canonical
+ if($Canonical) {
+ $Domain = $Canonical.split("/")[0]
+ }
+ else {
+ Write-Verbose "Error resolving SID '$SID'"
+ return $Null
+ }
+ }
+ }
+ catch {
+ Write-Verbose "Error resolving SID '$SID' : $_"
+ return $Null
+ }
+ }
- # set user properties
- $User.Name = $UserName
- $User.SamAccountName = $UserName
- $User.PasswordNotRequired = $False
- $User.SetPassword($Password)
- $User.Enabled = $True
+ $ObjectSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $ADSpath -PageSize $PageSize
- Write-Verbose "Creating user $UserName to with password '$Password' in domain $Domain"
+ if($ObjectSearcher) {
+ if($SID) {
+ $ObjectSearcher.filter = "(&(objectsid=$SID)$Filter)"
+ }
+ elseif($Name) {
+ $ObjectSearcher.filter = "(&(name=$Name)$Filter)"
+ }
+ elseif($SamAccountName) {
+ $ObjectSearcher.filter = "(&(samAccountName=$SamAccountName)$Filter)"
+ }
- try {
- # commit the user
- $User.Save()
- "[*] User $UserName successfully created in domain $Domain"
- }
- catch {
- Write-Warning '[!] User already exists!'
- return
+ try {
+ $Results = $ObjectSearcher.FindAll()
+ $Results | Where-Object {$_} | ForEach-Object {
+ if($ReturnRaw) {
+ $_
+ }
+ else {
+ # convert/process the LDAP fields for each result
+ Convert-LDAPProperty -Properties $_.Properties
+ }
+ }
+ $Results.dispose()
+ }
+ catch {
+ Write-Verbose "Error building the searcher object!"
+ }
+ $ObjectSearcher.dispose()
}
}
- else {
+}
- Write-Verbose "Creating user $UserName to with password '$Password' on $ComputerName"
- # if it's not a domain add, it's a local machine add
- $ObjOu = [ADSI]"WinNT://$ComputerName"
- $ObjUser = $ObjOu.Create('User', $UserName)
- $ObjUser.SetPassword($Password)
+function Get-NetOU {
+<#
+ .SYNOPSIS
- # commit the changes to the local machine
- try {
- $Null = $ObjUser.SetInfo()
- "[*] User $UserName successfully created on host $ComputerName"
- }
- catch {
- Write-Warning '[!] Account already exists!'
- return
- }
- }
+ Gets a list of all current OUs in a domain.
- # if a group is specified, invoke Add-NetGroupUser and return its value
- if ($GroupName) {
- # if we're adding the user to a domain
- if ($Domain) {
- Add-NetGroupUser -UserName $UserName -GroupName $GroupName -Domain $Domain
- "[*] User $UserName successfully added to group $GroupName in domain $Domain"
- }
- # otherwise, we're adding to a local group
- else {
- Add-NetGroupUser -UserName $UserName -GroupName $GroupName -ComputerName $ComputerName
- "[*] User $UserName successfully added to group $GroupName on host $ComputerName"
- }
- }
-}
+ .PARAMETER OUName
+ The OU name to query for, wildcards accepted.
-function Add-NetGroupUser {
-<#
- .SYNOPSIS
+ .PARAMETER GUID
- Adds a user to a domain group or a local group on the current (or remote) machine,
- if permissions allow, utilizing the WinNT service provider and
- DirectoryServices.AccountManagement, respectively.
+ Only return OUs with the specified GUID in their gplink property.
- .PARAMETER UserName
+ .PARAMETER Domain
- The domain username to query for.
+ The domain to query for OUs, defaults to the current domain.
- .PARAMETER GroupName
+ .PARAMETER DomainController
- Group to add the user to.
+ Domain controller to reflect LDAP queries through.
- .PARAMETER ComputerName
+ .PARAMETER ADSpath
- Hostname to add the user to, defaults to localhost.
+ The LDAP source to search through.
- .PARAMETER Domain
+ .PARAMETER FullData
+
+ Switch. Return full OU objects instead of just object names (the default).
+
+ .PARAMETER PageSize
+
+ The PageSize to set for the LDAP searcher object.
+
+ .PARAMETER Credential
- Domain to add the user to.
+ A [Management.Automation.PSCredential] object of alternate credentials
+ for connection to the target domain.
.EXAMPLE
- PS C:\> Add-NetGroupUser -UserName john -GroupName Administrators
+ PS C:\> Get-NetOU
- Adds a localuser "john" to the local group "Administrators"
+ Returns the current OUs in the domain.
.EXAMPLE
- PS C:\> Add-NetGroupUser -UserName john -GroupName "Domain Admins" -Domain dev.local
+ PS C:\> Get-NetOU -OUName *admin* -Domain testlab.local
+
+ Returns all OUs with "admin" in their name in the testlab.local domain.
+
+ .EXAMPLE
+
+ PS C:\> Get-NetOU -GUID 123-...
+
+ Returns all OUs with linked to the specified group policy object.
+
+ .EXAMPLE
+
+ PS C:\> "*admin*","*server*" | Get-NetOU
- Adds the existing user "john" to the domain group "Domain Admins" in "dev.local"
+ Get the full OU names for the given search terms piped on the pipeline.
#>
[CmdletBinding()]
- param(
- [Parameter(Mandatory = $True)]
- [ValidateNotNullOrEmpty()]
+ Param (
+ [Parameter(ValueFromPipeline=$True)]
[String]
- $UserName,
+ $OUName = '*',
- [Parameter(Mandatory = $True)]
- [ValidateNotNullOrEmpty()]
[String]
- $GroupName,
+ $GUID,
- [ValidateNotNullOrEmpty()]
- [Alias('HostName')]
[String]
- $ComputerName,
+ $Domain,
[String]
- $Domain
- )
+ $DomainController,
- # add the assembly if we need it
- Add-Type -AssemblyName System.DirectoryServices.AccountManagement
+ [String]
+ $ADSpath,
- # if we're adding to a remote host's local group, use the WinNT provider
- if($ComputerName -and ($ComputerName -ne "localhost")) {
- try {
- Write-Verbose "Adding user $UserName to $GroupName on host $ComputerName"
- ([ADSI]"WinNT://$ComputerName/$GroupName,group").add("WinNT://$ComputerName/$UserName,user")
- "[*] User $UserName successfully added to group $GroupName on $ComputerName"
- }
- catch {
- Write-Warning "[!] Error adding user $UserName to group $GroupName on $ComputerName"
- return
- }
- }
+ [Switch]
+ $FullData,
- # otherwise it's a local machine or domain add
- else {
- try {
- if ($Domain) {
- Write-Verbose "Adding user $UserName to $GroupName on domain $Domain"
- $CT = [System.DirectoryServices.AccountManagement.ContextType]::Domain
- $DomainObject = Get-NetDomain -Domain $Domain
- if(-not $DomainObject) {
- return $Null
- }
- # get the full principal context
- $Context = New-Object -TypeName System.DirectoryServices.AccountManagement.PrincipalContext -ArgumentList $CT, $DomainObject
+ [ValidateRange(1,10000)]
+ [Int]
+ $PageSize = 200,
+
+ [Management.Automation.PSCredential]
+ $Credential
+ )
+
+ begin {
+ $OUSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $ADSpath -PageSize $PageSize
+ }
+ process {
+ if ($OUSearcher) {
+ if ($GUID) {
+ # if we're filtering for a GUID in .gplink
+ $OUSearcher.filter="(&(objectCategory=organizationalUnit)(name=$OUName)(gplink=*$GUID*))"
}
else {
- # otherwise, get the local machine context
- Write-Verbose "Adding user $UserName to $GroupName on localhost"
- $Context = New-Object System.DirectoryServices.AccountManagement.PrincipalContext([System.DirectoryServices.AccountManagement.ContextType]::Machine, $Env:ComputerName)
+ $OUSearcher.filter="(&(objectCategory=organizationalUnit)(name=$OUName))"
}
- # find the particular group
- $Group = [System.DirectoryServices.AccountManagement.GroupPrincipal]::FindByIdentity($Context,$GroupName)
-
- # add the particular user to the group
- $Group.Members.add($Context, [System.DirectoryServices.AccountManagement.IdentityType]::SamAccountName, $UserName)
-
- # commit the changes
- $Group.Save()
- }
- catch {
- Write-Warning "Error adding $UserName to $GroupName : $_"
+ try {
+ $Results = $OUSearcher.FindAll()
+ $Results | Where-Object {$_} | ForEach-Object {
+ if ($FullData) {
+ # convert/process the LDAP fields for each result
+ $OU = Convert-LDAPProperty -Properties $_.Properties
+ $OU.PSObject.TypeNames.Add('PowerView.OU')
+ $OU
+ }
+ else {
+ # otherwise just returning the ADS paths of the OUs
+ $_.properties.adspath
+ }
+ }
+ $Results.dispose()
+ $OUSearcher.dispose()
+ }
+ catch {
+ Write-Warning $_
+ }
}
}
}
-function Get-UserProperty {
+function Get-NetSite {
<#
.SYNOPSIS
- Returns a list of all user object properties. If a property
- name is specified, it returns all [user:property] values.
-
- Taken directly from @obscuresec's post:
- http://obscuresecurity.blogspot.com/2014/04/ADSISearcher.html
+ Gets a list of all current sites in a domain.
- .PARAMETER Properties
+ .PARAMETER SiteName
- Property names to extract for users.
+ Site filter string, wildcards accepted.
.PARAMETER Domain
- The domain to query for user properties, defaults to the current domain.
+ The domain to query for sites, defaults to the current domain.
.PARAMETER DomainController
Domain controller to reflect LDAP queries through.
+ .PARAMETER ADSpath
+
+ The LDAP source to search through.
+
+ .PARAMETER GUID
+
+ Only return site with the specified GUID in their gplink property.
+
+ .PARAMETER FullData
+
+ Switch. Return full site objects instead of just object names (the default).
+
.PARAMETER PageSize
The PageSize to set for the LDAP searcher object.
@@ -2926,26 +2205,16 @@ function Get-UserProperty {
.EXAMPLE
- PS C:\> Get-UserProperty -Domain testing
-
- Returns all user properties for users in the 'testing' domain.
-
- .EXAMPLE
-
- PS C:\> Get-UserProperty -Properties ssn,lastlogon,location
-
- Returns all an array of user/ssn/lastlogin/location combinations
- for users in the current domain.
-
- .LINK
+ PS C:\> Get-NetSite -Domain testlab.local -FullData
- http://obscuresecurity.blogspot.com/2014/04/ADSISearcher.html
+ Returns the full data objects for all sites in testlab.local
#>
[CmdletBinding()]
- param(
- [String[]]
- $Properties,
+ Param (
+ [Parameter(ValueFromPipeline=$True)]
+ [String]
+ $SiteName = "*",
[String]
$Domain,
@@ -2953,6 +2222,15 @@ function Get-UserProperty {
[String]
$DomainController,
+ [String]
+ $ADSpath,
+
+ [String]
+ $GUID,
+
+ [Switch]
+ $FullData,
+
[ValidateRange(1,10000)]
[Int]
$PageSize = 200,
@@ -2961,44 +2239,100 @@ function Get-UserProperty {
$Credential
)
- if($Properties) {
- # extract out the set of all properties for each object
- $Properties = ,"name" + $Properties
- Get-NetUser -Domain $Domain -DomainController $DomainController -PageSize $PageSize -Credential $Credential | Select-Object -Property $Properties
+ begin {
+ $SiteSearcher = Get-DomainSearcher -ADSpath $ADSpath -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSprefix "CN=Sites,CN=Configuration" -PageSize $PageSize
}
- else {
- # extract out just the property names
- Get-NetUser -Domain $Domain -DomainController $DomainController -PageSize $PageSize -Credential $Credential | Select-Object -First 1 | Get-Member -MemberType *Property | Select-Object -Property 'Name'
+ process {
+ if($SiteSearcher) {
+
+ if ($GUID) {
+ # if we're filtering for a GUID in .gplink
+ $SiteSearcher.filter="(&(objectCategory=site)(name=$SiteName)(gplink=*$GUID*))"
+ }
+ else {
+ $SiteSearcher.filter="(&(objectCategory=site)(name=$SiteName))"
+ }
+
+ try {
+ $Results = $SiteSearcher.FindAll()
+ $Results | Where-Object {$_} | ForEach-Object {
+ if ($FullData) {
+ # convert/process the LDAP fields for each result
+ $Site = Convert-LDAPProperty -Properties $_.Properties
+ $Site.PSObject.TypeNames.Add('PowerView.Site')
+ $Site
+ }
+ else {
+ # otherwise just return the site name
+ $_.properties.name
+ }
+ }
+ $Results.dispose()
+ $SiteSearcher.dispose()
+ }
+ catch {
+ Write-Verbose $_
+ }
+ }
}
}
-filter Find-UserField {
+function Get-DomainSID {
<#
.SYNOPSIS
- Searches user object fields for a given word (default *pass*). Default
- field being searched is 'description'.
+ Gets the SID for the domain.
- Taken directly from @obscuresec's post:
- http://obscuresecurity.blogspot.com/2014/04/ADSISearcher.html
+ .PARAMETER Domain
- .PARAMETER SearchTerm
+ The domain to query, defaults to the current domain.
- Term to search for, default of "pass".
+ .PARAMETER DomainController
- .PARAMETER SearchField
+ Domain controller to reflect LDAP queries through.
- User field to search, default of "description".
+ .EXAMPLE
- .PARAMETER ADSpath
+ C:\> Get-DomainSID -Domain TEST
- The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
- Useful for OU queries.
+ Returns SID for the domain 'TEST'
+#>
+
+ param(
+ [String]
+ $Domain,
+
+ [String]
+ $DomainController
+ )
+
+ $ComputerSearcher = Get-DomainSearcher -Domain $TargetDomain -DomainController $DomainController
+ $ComputerSearcher.Filter = '(sAMAccountType=805306369)'
+ $Null = $ComputerSearcher.PropertiesToLoad.Add('objectsid')
+ $Result = $ComputerSearcher.FindOne()
+
+ if(-not $Result) {
+ Write-Verbose "Get-DomainSID: no results retrieved"
+ }
+ else {
+ $DCObject = Convert-LDAPProperty -Properties $Result.Properties
+ $DCSID = $DCObject.objectsid
+ $DCSID.Substring(0, $DCSID.LastIndexOf('-'))
+ }
+}
+
+
+function Get-NetFileServer {
+<#
+ .SYNOPSIS
+
+ Returns a list of all file servers extracted from user
+ homedirectory, scriptpath, and profilepath fields.
.PARAMETER Domain
- Domain to search computer fields for, defaults to the current domain.
+ The domain to query for user file servers, defaults to the current domain.
.PARAMETER DomainController
@@ -3015,23 +2349,19 @@ filter Find-UserField {
.EXAMPLE
- PS C:\> Find-UserField -SearchField info -SearchTerm backup
+ PS C:\> Get-NetFileServer
- Find user accounts with "backup" in the "info" field.
-#>
+ Returns active file servers.
- [CmdletBinding()]
- param(
- [Parameter(Position=0,ValueFromPipeline=$True)]
- [String]
- $SearchTerm = 'pass',
+ .EXAMPLE
- [String]
- $SearchField = 'description',
+ PS C:\> Get-NetFileServer -Domain testing
- [String]
- $ADSpath,
+ Returns active file servers for the 'testing' domain.
+#>
+ [CmdletBinding()]
+ param(
[String]
$Domain,
@@ -3046,655 +2376,659 @@ filter Find-UserField {
$Credential
)
- Get-NetUser -ADSpath $ADSpath -Domain $Domain -DomainController $DomainController -Credential $Credential -Filter "($SearchField=*$SearchTerm*)" -PageSize $PageSize | Select-Object samaccountname,$SearchField
-}
-
+ function Split-Path {
+ # short internal helper to split UNC server paths
+ param([String]$Path)
-filter Get-UserEvent {
-<#
- .SYNOPSIS
-
- Dump and parse security events relating to an account logon (ID 4624)
- or a TGT request event (ID 4768). Intended to be used and tested on
- Windows 2008 Domain Controllers.
- Admin Reqd? YES
-
- Author: @sixdub
-
- .PARAMETER ComputerName
-
- The computer to get events from. Default: Localhost
-
- .PARAMETER EventType
-
- Either 'logon', 'tgt', or 'all'. Defaults: 'logon'
-
- .PARAMETER DateStart
-
- Filter out all events before this date. Default: 5 days
-
- .PARAMETER Credential
-
- A [Management.Automation.PSCredential] object of alternate credentials
- for connection to the target domain.
-
- .EXAMPLE
-
- PS C:\> Get-UserEvent -ComputerName DomainController.testlab.local
-
- .LINK
-
- http://www.sixdub.net/2014/11/07/offensive-event-parsing-bringing-home-trophies/
-#>
-
- Param(
- [Parameter(ValueFromPipeline=$True)]
- [String]
- $ComputerName = $Env:ComputerName,
-
- [String]
- [ValidateSet("logon","tgt","all")]
- $EventType = "logon",
-
- [DateTime]
- $DateStart = [DateTime]::Today.AddDays(-5),
-
- [Management.Automation.PSCredential]
- $Credential
- )
-
- if($EventType.ToLower() -like "logon") {
- [Int32[]]$ID = @(4624)
- }
- elseif($EventType.ToLower() -like "tgt") {
- [Int32[]]$ID = @(4768)
- }
- else {
- [Int32[]]$ID = @(4624, 4768)
- }
-
- if($Credential) {
- Write-Verbose "Using alternative credentials"
- $Arguments = @{
- 'ComputerName' = $ComputerName;
- 'Credential' = $Credential;
- 'FilterHashTable' = @{ LogName = 'Security'; ID=$ID; StartTime=$DateStart};
- 'ErrorAction' = 'SilentlyContinue';
- }
- }
- else {
- $Arguments = @{
- 'ComputerName' = $ComputerName;
- 'FilterHashTable' = @{ LogName = 'Security'; ID=$ID; StartTime=$DateStart};
- 'ErrorAction' = 'SilentlyContinue';
+ if ($Path -and ($Path.split("\\").Count -ge 3)) {
+ $Temp = $Path.split("\\")[2]
+ if($Temp -and ($Temp -ne '')) {
+ $Temp
+ }
}
}
- # grab all events matching our filter for the specified host
- Get-WinEvent @Arguments | ForEach-Object {
-
- if($ID -contains 4624) {
- # first parse and check the logon event type. This could be later adapted and tested for RDP logons (type 10)
- if($_.message -match '(?s)(?<=Logon Type:).*?(?=(Impersonation Level:|New Logon:))') {
- if($Matches) {
- $LogonType = $Matches[0].trim()
- $Matches = $Null
- }
- }
- else {
- $LogonType = ""
- }
-
- # interactive logons or domain logons
- if (($LogonType -eq 2) -or ($LogonType -eq 3)) {
- try {
- # parse and store the account used and the address they came from
- if($_.message -match '(?s)(?<=New Logon:).*?(?=Process Information:)') {
- if($Matches) {
- $UserName = $Matches[0].split("`n")[2].split(":")[1].trim()
- $Domain = $Matches[0].split("`n")[3].split(":")[1].trim()
- $Matches = $Null
- }
- }
- if($_.message -match '(?s)(?<=Network Information:).*?(?=Source Port:)') {
- if($Matches) {
- $Address = $Matches[0].split("`n")[2].split(":")[1].trim()
- $Matches = $Null
- }
- }
-
- # only add if there was account information not for a machine or anonymous logon
- if ($UserName -and (-not $UserName.endsWith('$')) -and ($UserName -ne 'ANONYMOUS LOGON')) {
- $LogonEventProperties = @{
- 'Domain' = $Domain
- 'ComputerName' = $ComputerName
- 'Username' = $UserName
- 'Address' = $Address
- 'ID' = '4624'
- 'LogonType' = $LogonType
- 'Time' = $_.TimeCreated
- }
- New-Object -TypeName PSObject -Property $LogonEventProperties
- }
- }
- catch {
- Write-Verbose "Error parsing event logs: $_"
- }
- }
- }
- if($ID -contains 4768) {
- # the TGT event type
- try {
- if($_.message -match '(?s)(?<=Account Information:).*?(?=Service Information:)') {
- if($Matches) {
- $Username = $Matches[0].split("`n")[1].split(":")[1].trim()
- $Domain = $Matches[0].split("`n")[2].split(":")[1].trim()
- $Matches = $Null
- }
- }
+ $UserSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -Credential $Credential -PageSize $PageSize
- if($_.message -match '(?s)(?<=Network Information:).*?(?=Additional Information:)') {
- if($Matches) {
- $Address = $Matches[0].split("`n")[1].split(":")[-1].trim()
- $Matches = $Null
- }
- }
+ # only search for user objects that have one of the fields we're interested in set
+ $UserSearcher.filter = "(&(samAccountType=805306368)(|(homedirectory=*)(scriptpath=*)(profilepath=*)))"
- $LogonEventProperties = @{
- 'Domain' = $Domain
- 'ComputerName' = $ComputerName
- 'Username' = $UserName
- 'Address' = $Address
- 'ID' = '4768'
- 'LogonType' = ''
- 'Time' = $_.TimeCreated
- }
+ # only return the fields we're interested in
+ $UserSearcher.PropertiesToLoad.AddRange(('homedirectory', 'scriptpath', 'profilepath'))
- New-Object -TypeName PSObject -Property $LogonEventProperties
- }
- catch {
- Write-Verbose "Error parsing event logs: $_"
- }
- }
- }
+ # get all results w/o the pipeline and uniquify them (I know it's not pretty)
+ Sort-Object -Unique -InputObject $(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'])}})
}
-function Get-ObjectAcl {
+function Get-DFSshare {
<#
.SYNOPSIS
- Returns the ACLs associated with a specific active directory object.
-
- Thanks Sean Metcalf (@pyrotek3) for the idea and guidance.
- .PARAMETER SamAccountName
-
- Object name to filter for.
-
- .PARAMETER Name
-
- Object name to filter for.
+ Returns a list of all fault-tolerant distributed file
+ systems for a given domain.
- .PARAMETER DistinguishedName
+ .PARAMETER Version
- Object distinguished name to filter for.
+ The version of DFS to query for servers.
+ 1/v1, 2/v2, or all
- .PARAMETER ResolveGUIDs
+ .PARAMETER Domain
- Switch. Resolve GUIDs to their display names.
+ The domain to query for user DFS shares, defaults to the current domain.
- .PARAMETER Filter
+ .PARAMETER DomainController
- A customized ldap filter string to use, e.g. "(description=*admin*)"
+ Domain controller to reflect LDAP queries through.
.PARAMETER ADSpath
The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
Useful for OU queries.
- .PARAMETER ADSprefix
-
- Prefix to set for the searcher (like "CN=Sites,CN=Configuration")
-
- .PARAMETER RightsFilter
-
- Only return results with the associated rights, "All", "ResetPassword","WriteMembers"
-
- .PARAMETER Domain
-
- The domain to use for the query, defaults to the current domain.
-
- .PARAMETER DomainController
-
- Domain controller to reflect LDAP queries through.
-
.PARAMETER PageSize
The PageSize to set for the LDAP searcher object.
- .EXAMPLE
-
- PS C:\> Get-ObjectAcl -SamAccountName matt.admin -domain testlab.local
+ .PARAMETER Credential
- Get the ACLs for the matt.admin user in the testlab.local domain
+ A [Management.Automation.PSCredential] object of alternate credentials
+ for connection to the target domain.
.EXAMPLE
- PS C:\> Get-ObjectAcl -SamAccountName matt.admin -domain testlab.local -ResolveGUIDs
+ PS C:\> Get-DFSshare
- Get the ACLs for the matt.admin user in the testlab.local domain and
- resolve relevant GUIDs to their display names.
+ Returns all distributed file system shares for the current domain.
.EXAMPLE
- PS C:\> Get-NetOU -FullData | Get-ObjectAcl -ResolveGUIDs
+ PS C:\> Get-DFSshare -Domain test
- Enumerate the ACL permissions for all OUs in the domain.
+ Returns all distributed file system shares for the 'test' domain.
#>
[CmdletBinding()]
- Param (
- [Parameter(ValueFromPipelineByPropertyName=$True)]
- [String]
- $SamAccountName,
-
- [Parameter(ValueFromPipelineByPropertyName=$True)]
+ param(
[String]
- $Name = "*",
+ [ValidateSet("All","V1","1","V2","2")]
+ $Version = "All",
- [Parameter(ValueFromPipelineByPropertyName=$True)]
[String]
- $DistinguishedName = "*",
-
- [Switch]
- $ResolveGUIDs,
+ $Domain,
[String]
- $Filter,
+ $DomainController,
[String]
$ADSpath,
- [String]
- $ADSprefix,
-
- [String]
- [ValidateSet("All","ResetPassword","WriteMembers")]
- $RightsFilter,
-
- [String]
- $Domain,
-
- [String]
- $DomainController,
-
[ValidateRange(1,10000)]
[Int]
- $PageSize = 200
+ $PageSize = 200,
+
+ [Management.Automation.PSCredential]
+ $Credential
)
- begin {
- $Searcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -ADSpath $ADSpath -ADSprefix $ADSprefix -PageSize $PageSize
+ function Parse-Pkt {
+ [CmdletBinding()]
+ param(
+ [byte[]]
+ $Pkt
+ )
- # get a GUID -> name mapping
- if($ResolveGUIDs) {
- $GUIDs = Get-GUIDMap -Domain $Domain -DomainController $DomainController -PageSize $PageSize
- }
- }
+ $bin = $Pkt
+ $blob_version = [bitconverter]::ToUInt32($bin[0..3],0)
+ $blob_element_count = [bitconverter]::ToUInt32($bin[4..7],0)
+ $offset = 8
+ #https://msdn.microsoft.com/en-us/library/cc227147.aspx
+ $object_list = @()
+ for($i=1; $i -le $blob_element_count; $i++){
+ $blob_name_size_start = $offset
+ $blob_name_size_end = $offset + 1
+ $blob_name_size = [bitconverter]::ToUInt16($bin[$blob_name_size_start..$blob_name_size_end],0)
- process {
+ $blob_name_start = $blob_name_size_end + 1
+ $blob_name_end = $blob_name_start + $blob_name_size - 1
+ $blob_name = [System.Text.Encoding]::Unicode.GetString($bin[$blob_name_start..$blob_name_end])
- if ($Searcher) {
+ $blob_data_size_start = $blob_name_end + 1
+ $blob_data_size_end = $blob_data_size_start + 3
+ $blob_data_size = [bitconverter]::ToUInt32($bin[$blob_data_size_start..$blob_data_size_end],0)
- if($SamAccountName) {
- $Searcher.filter="(&(samaccountname=$SamAccountName)(name=$Name)(distinguishedname=$DistinguishedName)$Filter)"
- }
- else {
- $Searcher.filter="(&(name=$Name)(distinguishedname=$DistinguishedName)$Filter)"
- }
+ $blob_data_start = $blob_data_size_end + 1
+ $blob_data_end = $blob_data_start + $blob_data_size - 1
+ $blob_data = $bin[$blob_data_start..$blob_data_end]
+ switch -wildcard ($blob_name) {
+ "\siteroot" { }
+ "\domainroot*" {
+ # Parse DFSNamespaceRootOrLinkBlob object. Starts with variable length DFSRootOrLinkIDBlob which we parse first...
+ # DFSRootOrLinkIDBlob
+ $root_or_link_guid_start = 0
+ $root_or_link_guid_end = 15
+ $root_or_link_guid = [byte[]]$blob_data[$root_or_link_guid_start..$root_or_link_guid_end]
+ $guid = New-Object Guid(,$root_or_link_guid) # should match $guid_str
+ $prefix_size_start = $root_or_link_guid_end + 1
+ $prefix_size_end = $prefix_size_start + 1
+ $prefix_size = [bitconverter]::ToUInt16($blob_data[$prefix_size_start..$prefix_size_end],0)
+ $prefix_start = $prefix_size_end + 1
+ $prefix_end = $prefix_start + $prefix_size - 1
+ $prefix = [System.Text.Encoding]::Unicode.GetString($blob_data[$prefix_start..$prefix_end])
- try {
- $Results = $Searcher.FindAll()
- $Results | Where-Object {$_} | ForEach-Object {
- $Object = [adsi]($_.path)
+ $short_prefix_size_start = $prefix_end + 1
+ $short_prefix_size_end = $short_prefix_size_start + 1
+ $short_prefix_size = [bitconverter]::ToUInt16($blob_data[$short_prefix_size_start..$short_prefix_size_end],0)
+ $short_prefix_start = $short_prefix_size_end + 1
+ $short_prefix_end = $short_prefix_start + $short_prefix_size - 1
+ $short_prefix = [System.Text.Encoding]::Unicode.GetString($blob_data[$short_prefix_start..$short_prefix_end])
- if($Object.distinguishedname) {
- $Access = $Object.PsBase.ObjectSecurity.access
- $Access | ForEach-Object {
- $_ | Add-Member NoteProperty 'ObjectDN' $Object.distinguishedname[0]
+ $type_start = $short_prefix_end + 1
+ $type_end = $type_start + 3
+ $type = [bitconverter]::ToUInt32($blob_data[$type_start..$type_end],0)
- if($Object.objectsid[0]){
- $S = (New-Object System.Security.Principal.SecurityIdentifier($Object.objectsid[0],0)).Value
- }
- else {
- $S = $Null
- }
+ $state_start = $type_end + 1
+ $state_end = $state_start + 3
+ $state = [bitconverter]::ToUInt32($blob_data[$state_start..$state_end],0)
- $_ | Add-Member NoteProperty 'ObjectSID' $S
- $_
- }
- }
- } | ForEach-Object {
- if($RightsFilter) {
- $GuidFilter = Switch ($RightsFilter) {
- "ResetPassword" { "00299570-246d-11d0-a768-00aa006e0529" }
- "WriteMembers" { "bf9679c0-0de6-11d0-a285-00aa003049e2" }
- Default { "00000000-0000-0000-0000-000000000000"}
- }
- if($_.ObjectType -eq $GuidFilter) { $_ }
- }
- else {
- $_
- }
- } | ForEach-Object {
- if($GUIDs) {
- # if we're resolving GUIDs, map them them to the resolved hash table
- $AclProperties = @{}
- $_.psobject.properties | ForEach-Object {
- if( ($_.Name -eq 'ObjectType') -or ($_.Name -eq 'InheritedObjectType') ) {
- try {
- $AclProperties[$_.Name] = $GUIDS[$_.Value.toString()]
- }
- catch {
- $AclProperties[$_.Name] = $_.Value
- }
- }
- else {
- $AclProperties[$_.Name] = $_.Value
- }
- }
- New-Object -TypeName PSObject -Property $AclProperties
+ $comment_size_start = $state_end + 1
+ $comment_size_end = $comment_size_start + 1
+ $comment_size = [bitconverter]::ToUInt16($blob_data[$comment_size_start..$comment_size_end],0)
+ $comment_start = $comment_size_end + 1
+ $comment_end = $comment_start + $comment_size - 1
+ if ($comment_size -gt 0) {
+ $comment = [System.Text.Encoding]::Unicode.GetString($blob_data[$comment_start..$comment_end])
}
- else { $_ }
- }
- $Results.dispose()
- $Searcher.dispose()
- }
- catch {
- Write-Warning $_
- }
- }
- }
-}
-
+ $prefix_timestamp_start = $comment_end + 1
+ $prefix_timestamp_end = $prefix_timestamp_start + 7
+ # https://msdn.microsoft.com/en-us/library/cc230324.aspx FILETIME
+ $prefix_timestamp = $blob_data[$prefix_timestamp_start..$prefix_timestamp_end] #dword lowDateTime #dword highdatetime
+ $state_timestamp_start = $prefix_timestamp_end + 1
+ $state_timestamp_end = $state_timestamp_start + 7
+ $state_timestamp = $blob_data[$state_timestamp_start..$state_timestamp_end]
+ $comment_timestamp_start = $state_timestamp_end + 1
+ $comment_timestamp_end = $comment_timestamp_start + 7
+ $comment_timestamp = $blob_data[$comment_timestamp_start..$comment_timestamp_end]
+ $version_start = $comment_timestamp_end + 1
+ $version_end = $version_start + 3
+ $version = [bitconverter]::ToUInt32($blob_data[$version_start..$version_end],0)
-function Add-ObjectAcl {
-<#
- .SYNOPSIS
+ # Parse rest of DFSNamespaceRootOrLinkBlob here
+ $dfs_targetlist_blob_size_start = $version_end + 1
+ $dfs_targetlist_blob_size_end = $dfs_targetlist_blob_size_start + 3
+ $dfs_targetlist_blob_size = [bitconverter]::ToUInt32($blob_data[$dfs_targetlist_blob_size_start..$dfs_targetlist_blob_size_end],0)
- Adds an ACL for a specific active directory object.
+ $dfs_targetlist_blob_start = $dfs_targetlist_blob_size_end + 1
+ $dfs_targetlist_blob_end = $dfs_targetlist_blob_start + $dfs_targetlist_blob_size - 1
+ $dfs_targetlist_blob = $blob_data[$dfs_targetlist_blob_start..$dfs_targetlist_blob_end]
+ $reserved_blob_size_start = $dfs_targetlist_blob_end + 1
+ $reserved_blob_size_end = $reserved_blob_size_start + 3
+ $reserved_blob_size = [bitconverter]::ToUInt32($blob_data[$reserved_blob_size_start..$reserved_blob_size_end],0)
- AdminSDHolder ACL approach from Sean Metcalf (@pyrotek3)
- https://adsecurity.org/?p=1906
+ $reserved_blob_start = $reserved_blob_size_end + 1
+ $reserved_blob_end = $reserved_blob_start + $reserved_blob_size - 1
+ $reserved_blob = $blob_data[$reserved_blob_start..$reserved_blob_end]
+ $referral_ttl_start = $reserved_blob_end + 1
+ $referral_ttl_end = $referral_ttl_start + 3
+ $referral_ttl = [bitconverter]::ToUInt32($blob_data[$referral_ttl_start..$referral_ttl_end],0)
- ACE setting method adapted from https://social.technet.microsoft.com/Forums/windowsserver/en-US/df3bfd33-c070-4a9c-be98-c4da6e591a0a/forum-faq-using-powershell-to-assign-permissions-on-active-directory-objects.
+ #Parse DFSTargetListBlob
+ $target_count_start = 0
+ $target_count_end = $target_count_start + 3
+ $target_count = [bitconverter]::ToUInt32($dfs_targetlist_blob[$target_count_start..$target_count_end],0)
+ $t_offset = $target_count_end + 1
- 'ResetPassword' doesn't need to know the user's current password
- 'WriteMembers' allows for the modification of group membership
+ for($j=1; $j -le $target_count; $j++){
+ $target_entry_size_start = $t_offset
+ $target_entry_size_end = $target_entry_size_start + 3
+ $target_entry_size = [bitconverter]::ToUInt32($dfs_targetlist_blob[$target_entry_size_start..$target_entry_size_end],0)
+ $target_time_stamp_start = $target_entry_size_end + 1
+ $target_time_stamp_end = $target_time_stamp_start + 7
+ # FILETIME again or special if priority rank and priority class 0
+ $target_time_stamp = $dfs_targetlist_blob[$target_time_stamp_start..$target_time_stamp_end]
+ $target_state_start = $target_time_stamp_end + 1
+ $target_state_end = $target_state_start + 3
+ $target_state = [bitconverter]::ToUInt32($dfs_targetlist_blob[$target_state_start..$target_state_end],0)
- .PARAMETER TargetSamAccountName
+ $target_type_start = $target_state_end + 1
+ $target_type_end = $target_type_start + 3
+ $target_type = [bitconverter]::ToUInt32($dfs_targetlist_blob[$target_type_start..$target_type_end],0)
- Target object name to filter for.
+ $server_name_size_start = $target_type_end + 1
+ $server_name_size_end = $server_name_size_start + 1
+ $server_name_size = [bitconverter]::ToUInt16($dfs_targetlist_blob[$server_name_size_start..$server_name_size_end],0)
- .PARAMETER TargetName
+ $server_name_start = $server_name_size_end + 1
+ $server_name_end = $server_name_start + $server_name_size - 1
+ $server_name = [System.Text.Encoding]::Unicode.GetString($dfs_targetlist_blob[$server_name_start..$server_name_end])
- Target object name to filter for.
+ $share_name_size_start = $server_name_end + 1
+ $share_name_size_end = $share_name_size_start + 1
+ $share_name_size = [bitconverter]::ToUInt16($dfs_targetlist_blob[$share_name_size_start..$share_name_size_end],0)
+ $share_name_start = $share_name_size_end + 1
+ $share_name_end = $share_name_start + $share_name_size - 1
+ $share_name = [System.Text.Encoding]::Unicode.GetString($dfs_targetlist_blob[$share_name_start..$share_name_end])
- .PARAMETER TargetDistinguishedName
+ $target_list += "\\$server_name\$share_name"
+ $t_offset = $share_name_end + 1
+ }
+ }
+ }
+ $offset = $blob_data_end + 1
+ $dfs_pkt_properties = @{
+ 'Name' = $blob_name
+ 'Prefix' = $prefix
+ 'TargetList' = $target_list
+ }
+ $object_list += New-Object -TypeName PSObject -Property $dfs_pkt_properties
+ $prefix = $null
+ $blob_name = $null
+ $target_list = $null
+ }
- Target object distinguished name to filter for.
+ $servers = @()
+ $object_list | ForEach-Object {
+ if ($_.TargetList) {
+ $_.TargetList | ForEach-Object {
+ $servers += $_.split("\")[2]
+ }
+ }
+ }
- .PARAMETER TargetFilter
+ $servers
+ }
- A customized ldap filter string to use to find a target, e.g. "(description=*admin*)"
+ function Get-DFSshareV1 {
+ [CmdletBinding()]
+ param(
+ [String]
+ $Domain,
- .PARAMETER TargetADSpath
+ [String]
+ $DomainController,
- The LDAP source for the target, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
+ [String]
+ $ADSpath,
- .PARAMETER TargetADSprefix
+ [ValidateRange(1,10000)]
+ [Int]
+ $PageSize = 200,
- Prefix to set for the target searcher (like "CN=Sites,CN=Configuration")
+ [Management.Automation.PSCredential]
+ $Credential
+ )
- .PARAMETER PrincipalSID
+ $DFSsearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $ADSpath -PageSize $PageSize
- The SID of the principal object to add for access.
+ if($DFSsearcher) {
+ $DFSshares = @()
+ $DFSsearcher.filter = "(&(objectClass=fTDfs))"
- .PARAMETER PrincipalName
+ try {
+ $Results = $DFSSearcher.FindAll()
+ $Results | Where-Object {$_} | ForEach-Object {
+ $Properties = $_.Properties
+ $RemoteNames = $Properties.remoteservername
+ $Pkt = $Properties.pkt
- The name of the principal object to add for access.
+ $DFSshares += $RemoteNames | ForEach-Object {
+ try {
+ if ( $_.Contains('\') ) {
+ New-Object -TypeName PSObject -Property @{'Name'=$Properties.name[0];'RemoteServerName'=$_.split("\")[2]}
+ }
+ }
+ catch {
+ Write-Verbose "Error in parsing DFS share : $_"
+ }
+ }
+ }
+ $Results.dispose()
+ $DFSSearcher.dispose()
- .PARAMETER PrincipalSamAccountName
+ if($pkt -and $pkt[0]) {
+ Parse-Pkt $pkt[0] | ForEach-Object {
+ # If a folder doesn't have a redirection it will
+ # have a target like
+ # \\null\TestNameSpace\folder\.DFSFolderLink so we
+ # do actually want to match on "null" rather than
+ # $null
+ if ($_ -ne "null") {
+ New-Object -TypeName PSObject -Property @{'Name'=$Properties.name[0];'RemoteServerName'=$_}
+ }
+ }
+ }
+ }
+ catch {
+ Write-Warning "Get-DFSshareV1 error : $_"
+ }
+ $DFSshares | Sort-Object -Property "RemoteServerName"
+ }
+ }
- The samAccountName of the principal object to add for access.
+ function Get-DFSshareV2 {
+ [CmdletBinding()]
+ param(
+ [String]
+ $Domain,
- .PARAMETER Rights
+ [String]
+ $DomainController,
- Rights to add for the principal, "All","ResetPassword","WriteMembers","DCSync"
+ [String]
+ $ADSpath,
- .PARAMETER Domain
+ [ValidateRange(1,10000)]
+ [Int]
+ $PageSize = 200,
- The domain to use for the target query, defaults to the current domain.
+ [Management.Automation.PSCredential]
+ $Credential
+ )
- .PARAMETER DomainController
+ $DFSsearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $ADSpath -PageSize $PageSize
- Domain controller to reflect LDAP queries through.
+ if($DFSsearcher) {
+ $DFSshares = @()
+ $DFSsearcher.filter = "(&(objectClass=msDFS-Linkv2))"
+ $DFSSearcher.PropertiesToLoad.AddRange(('msdfs-linkpathv2','msDFS-TargetListv2'))
- .PARAMETER PageSize
+ try {
+ $Results = $DFSSearcher.FindAll()
+ $Results | Where-Object {$_} | ForEach-Object {
+ $Properties = $_.Properties
+ $target_list = $Properties.'msdfs-targetlistv2'[0]
+ $xml = [xml][System.Text.Encoding]::Unicode.GetString($target_list[2..($target_list.Length-1)])
+ $DFSshares += $xml.targets.ChildNodes | ForEach-Object {
+ try {
+ $Target = $_.InnerText
+ if ( $Target.Contains('\') ) {
+ $DFSroot = $Target.split("\")[3]
+ $ShareName = $Properties.'msdfs-linkpathv2'[0]
+ New-Object -TypeName PSObject -Property @{'Name'="$DFSroot$ShareName";'RemoteServerName'=$Target.split("\")[2]}
+ }
+ }
+ catch {
+ Write-Verbose "Error in parsing target : $_"
+ }
+ }
+ }
+ $Results.dispose()
+ $DFSSearcher.dispose()
+ }
+ catch {
+ Write-Warning "Get-DFSshareV2 error : $_"
+ }
+ $DFSshares | Sort-Object -Unique -Property "RemoteServerName"
+ }
+ }
- The PageSize to set for the LDAP searcher object.
+ $DFSshares = @()
- .EXAMPLE
+ if ( ($Version -eq "all") -or ($Version.endsWith("1")) ) {
+ $DFSshares += Get-DFSshareV1 -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $ADSpath -PageSize $PageSize
+ }
+ if ( ($Version -eq "all") -or ($Version.endsWith("2")) ) {
+ $DFSshares += Get-DFSshareV2 -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $ADSpath -PageSize $PageSize
+ }
- Add-ObjectAcl -TargetSamAccountName matt -PrincipalSamAccountName john
+ $DFSshares | Sort-Object -Property ("RemoteServerName","Name") -Unique
+}
- Grants 'john' all full access rights to the 'matt' account.
- .EXAMPLE
+########################################################
+#
+# GPO related functions.
+#
+########################################################
- Add-ObjectAcl -TargetSamAccountName matt -PrincipalSamAccountName john -Rights ResetPassword
+function Get-GptTmpl {
+<#
+ .SYNOPSIS
- Grants 'john' the right to reset the password for the 'matt' account.
+ Helper to parse a GptTmpl.inf policy file path into a custom object.
- .LINK
+ .PARAMETER GptTmplPath
- https://adsecurity.org/?p=1906
+ The GptTmpl.inf file path name to parse.
- https://social.technet.microsoft.com/Forums/windowsserver/en-US/df3bfd33-c070-4a9c-be98-c4da6e591a0a/forum-faq-using-powershell-to-assign-permissions-on-active-directory-objects?forum=winserverpowershell
-#>
+ .PARAMETER UsePSDrive
- [CmdletBinding()]
- Param (
- [String]
- $TargetSamAccountName,
+ Switch. Mount the target GptTmpl folder path as a temporary PSDrive.
- [String]
- $TargetName = "*",
+ .EXAMPLE
- [Alias('DN')]
- [String]
- $TargetDistinguishedName = "*",
+ PS C:\> Get-GptTmpl -GptTmplPath "\\dev.testlab.local\sysvol\dev.testlab.local\Policies\{31B2F340-016D-11D2-945F-00C04FB984F9}\MACHINE\Microsoft\Windows NT\SecEdit\GptTmpl.inf"
- [String]
- $TargetFilter,
+ Parse the default domain policy .inf for dev.testlab.local
+#>
+ [CmdletBinding()]
+ Param (
+ [Parameter(Mandatory=$True, ValueFromPipeline=$True)]
[String]
- $TargetADSpath,
+ $GptTmplPath,
- [String]
- $TargetADSprefix,
+ [Switch]
+ $UsePSDrive
+ )
- [String]
- [ValidatePattern('^S-1-5-21-[0-9]+-[0-9]+-[0-9]+-[0-9]+')]
- $PrincipalSID,
+ begin {
+ if($UsePSDrive) {
+ # if we're PSDrives, create a temporary mount point
+ $Parts = $GptTmplPath.split('\')
+ $FolderPath = $Parts[0..($Parts.length-2)] -join '\'
+ $FilePath = $Parts[-1]
+ $RandDrive = ("abcdefghijklmnopqrstuvwxyz".ToCharArray() | Get-Random -Count 7) -join ''
- [String]
- $PrincipalName,
+ Write-Verbose "Mounting path $GptTmplPath using a temp PSDrive at $RandDrive"
- [String]
- $PrincipalSamAccountName,
+ try {
+ $Null = New-PSDrive -Name $RandDrive -PSProvider FileSystem -Root $FolderPath -ErrorAction Stop
+ }
+ catch {
+ Write-Verbose "Error mounting path $GptTmplPath : $_"
+ return $Null
+ }
- [String]
- [ValidateSet("All","ResetPassword","WriteMembers","DCSync")]
- $Rights = "All",
+ # so we can cd/dir the new drive
+ $TargetGptTmplPath = $RandDrive + ":\" + $FilePath
+ }
+ else {
+ $TargetGptTmplPath = $GptTmplPath
+ }
+ }
- [String]
- $RightsGUID,
+ process {
+ try {
+ Write-Verbose "Attempting to parse GptTmpl: $TargetGptTmplPath"
+ $TargetGptTmplPath | Get-IniContent -ErrorAction SilentlyContinue
+ }
+ catch {
+ # Write-Verbose "Error parsing $TargetGptTmplPath : $_"
+ }
+ }
- [String]
- $Domain,
+ end {
+ if($UsePSDrive -and $RandDrive) {
+ Write-Verbose "Removing temp PSDrive $RandDrive"
+ Get-PSDrive -Name $RandDrive -ErrorAction SilentlyContinue | Remove-PSDrive -Force
+ }
+ }
+}
+
+
+function Get-GroupsXML {
+<#
+ .SYNOPSIS
+
+ Helper to parse a groups.xml file path into a custom object.
+
+ .PARAMETER GroupsXMLpath
+
+ The groups.xml file path name to parse.
+
+ .PARAMETER UsePSDrive
+
+ Switch. Mount the target groups.xml folder path as a temporary PSDrive.
+#>
+ [CmdletBinding()]
+ Param (
+ [Parameter(Mandatory=$True, ValueFromPipeline=$True)]
[String]
- $DomainController,
+ $GroupsXMLPath,
- [ValidateRange(1,10000)]
- [Int]
- $PageSize = 200
+ [Switch]
+ $UsePSDrive
)
begin {
- $Searcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -ADSpath $TargetADSpath -ADSprefix $TargetADSprefix -PageSize $PageSize
+ if($UsePSDrive) {
+ # if we're PSDrives, create a temporary mount point
+ $Parts = $GroupsXMLPath.split('\')
+ $FolderPath = $Parts[0..($Parts.length-2)] -join '\'
+ $FilePath = $Parts[-1]
+ $RandDrive = ("abcdefghijklmnopqrstuvwxyz".ToCharArray() | Get-Random -Count 7) -join ''
- if($PrincipalSID) {
- $ResolvedPrincipalSID = $PrincipalSID
- }
- else {
- $Principal = Get-ADObject -Domain $Domain -DomainController $DomainController -Name $PrincipalName -SamAccountName $PrincipalSamAccountName -PageSize $PageSize
+ Write-Verbose "Mounting path $GroupsXMLPath using a temp PSDrive at $RandDrive"
- if(!$Principal) {
- throw "Error resolving principal"
+ try {
+ $Null = New-PSDrive -Name $RandDrive -PSProvider FileSystem -Root $FolderPath -ErrorAction Stop
+ }
+ catch {
+ Write-Verbose "Error mounting path $GroupsXMLPath : $_"
+ return $Null
}
- $ResolvedPrincipalSID = $Principal.objectsid
+
+ # so we can cd/dir the new drive
+ $TargetGroupsXMLPath = $RandDrive + ":\" + $FilePath
}
- if(!$ResolvedPrincipalSID) {
- throw "Error resolving principal"
+ else {
+ $TargetGroupsXMLPath = $GroupsXMLPath
}
}
process {
- if ($Searcher) {
-
- if($TargetSamAccountName) {
- $Searcher.filter="(&(samaccountname=$TargetSamAccountName)(name=$TargetName)(distinguishedname=$TargetDistinguishedName)$TargetFilter)"
- }
- else {
- $Searcher.filter="(&(name=$TargetName)(distinguishedname=$TargetDistinguishedName)$TargetFilter)"
- }
-
- try {
- $Results = $Searcher.FindAll()
- $Results | Where-Object {$_} | ForEach-Object {
-
- # adapted from https://social.technet.microsoft.com/Forums/windowsserver/en-US/df3bfd33-c070-4a9c-be98-c4da6e591a0a/forum-faq-using-powershell-to-assign-permissions-on-active-directory-objects
+ try {
+ Write-Verbose "Attempting to parse Groups.xml: $TargetGroupsXMLPath"
+ [XML]$GroupsXMLcontent = Get-Content $TargetGroupsXMLPath -ErrorAction Stop
- $TargetDN = $_.Properties.distinguishedname
+ # process all group properties in the XML
+ $GroupsXMLcontent | Select-Xml "//Groups" | Select-Object -ExpandProperty node | ForEach-Object {
- $Identity = [System.Security.Principal.IdentityReference] ([System.Security.Principal.SecurityIdentifier]$ResolvedPrincipalSID)
- $InheritanceType = [System.DirectoryServices.ActiveDirectorySecurityInheritance] "None"
- $ControlType = [System.Security.AccessControl.AccessControlType] "Allow"
- $ACEs = @()
+ $Groupname = $_.Group.Properties.groupName
- if($RightsGUID) {
- $GUIDs = @($RightsGUID)
+ # extract the localgroup sid for memberof
+ $GroupSID = $_.Group.Properties.GroupSid
+ if(-not $LocalSid) {
+ if($Groupname -match 'Administrators') {
+ $GroupSID = 'S-1-5-32-544'
}
- else {
- $GUIDs = Switch ($Rights) {
- # ResetPassword doesn't need to know the user's current password
- "ResetPassword" { "00299570-246d-11d0-a768-00aa006e0529" }
- # allows for the modification of group membership
- "WriteMembers" { "bf9679c0-0de6-11d0-a285-00aa003049e2" }
- # 'DS-Replication-Get-Changes' = 1131f6aa-9c07-11d1-f79f-00c04fc2dcd2
- # 'DS-Replication-Get-Changes-All' = 1131f6ad-9c07-11d1-f79f-00c04fc2dcd2
- # 'DS-Replication-Get-Changes-In-Filtered-Set' = 89e95b76-444d-4c62-991a-0facbeda640c
- # when applied to a domain's ACL, allows for the use of DCSync
- "DCSync" { "1131f6aa-9c07-11d1-f79f-00c04fc2dcd2", "1131f6ad-9c07-11d1-f79f-00c04fc2dcd2", "89e95b76-444d-4c62-991a-0facbeda640c"}
- }
+ elseif($Groupname -match 'Remote Desktop') {
+ $GroupSID = 'S-1-5-32-555'
}
-
- if($GUIDs) {
- foreach($GUID in $GUIDs) {
- $NewGUID = New-Object Guid $GUID
- $ADRights = [System.DirectoryServices.ActiveDirectoryRights] "ExtendedRight"
- $ACEs += New-Object System.DirectoryServices.ActiveDirectoryAccessRule $Identity,$ADRights,$ControlType,$NewGUID,$InheritanceType
- }
+ elseif($Groupname -match 'Guests') {
+ $GroupSID = 'S-1-5-32-546'
}
else {
- # deault to GenericAll rights
- $ADRights = [System.DirectoryServices.ActiveDirectoryRights] "GenericAll"
- $ACEs += New-Object System.DirectoryServices.ActiveDirectoryAccessRule $Identity,$ADRights,$ControlType,$InheritanceType
+ $GroupSID = Convert-NameToSid -ObjectName $Groupname | Select-Object -ExpandProperty SID
}
+ }
+
+ # extract out members added to this group
+ $Members = $_.Group.Properties.members | Select-Object -ExpandProperty Member | Where-Object { $_.action -match 'ADD' } | ForEach-Object {
+ if($_.sid) { $_.sid }
+ else { $_.name }
+ }
- Write-Verbose "Granting principal $ResolvedPrincipalSID '$Rights' on $($_.Properties.distinguishedname)"
+ if ($Members) {
- try {
- # add all the new ACEs to the specified object
- ForEach ($ACE in $ACEs) {
- Write-Verbose "Granting principal $ResolvedPrincipalSID '$($ACE.ObjectType)' rights on $($_.Properties.distinguishedname)"
- $Object = [adsi]($_.path)
- $Object.PsBase.ObjectSecurity.AddAccessRule($ACE)
- $Object.PsBase.commitchanges()
+ # extract out any/all filters...I hate you GPP
+ if($_.Group.filters) {
+ $Filters = $_.Group.filters.GetEnumerator() | ForEach-Object {
+ New-Object -TypeName PSObject -Property @{'Type' = $_.LocalName;'Value' = $_.name}
}
}
- catch {
- Write-Warning "Error granting principal $ResolvedPrincipalSID '$Rights' on $TargetDN : $_"
+ else {
+ $Filters = $Null
}
+
+ if($Members -isnot [System.Array]) { $Members = @($Members) }
+
+ $GPOGroup = New-Object PSObject
+ $GPOGroup | Add-Member Noteproperty 'GPOPath' $TargetGroupsXMLPath
+ $GPOGroup | Add-Member Noteproperty 'Filters' $Filters
+ $GPOGroup | Add-Member Noteproperty 'GroupName' $GroupName
+ $GPOGroup | Add-Member Noteproperty 'GroupSID' $GroupSID
+ $GPOGroup | Add-Member Noteproperty 'GroupMemberOf' $Null
+ $GPOGroup | Add-Member Noteproperty 'GroupMembers' $Members
+ $GPOGroup
}
- $Results.dispose()
- $Searcher.dispose()
- }
- catch {
- Write-Warning "Error: $_"
}
}
+ catch {
+ # Write-Verbose "Error parsing $TargetGroupsXMLPath : $_"
+ }
+ }
+
+ end {
+ if($UsePSDrive -and $RandDrive) {
+ Write-Verbose "Removing temp PSDrive $RandDrive"
+ Get-PSDrive -Name $RandDrive -ErrorAction SilentlyContinue | Remove-PSDrive -Force
+ }
}
}
-function Invoke-ACLScanner {
+function Get-NetGPOGroup {
<#
.SYNOPSIS
- Searches for ACLs for specifable AD objects (default to all domain objects)
- with a domain sid of > -1000, and have modifiable rights.
-
- Thanks Sean Metcalf (@pyrotek3) for the idea and guidance.
- .PARAMETER SamAccountName
+ Returns all GPOs in a domain that set "Restricted Groups" or use groups.xml on on target machines.
- Object name to filter for.
+ Author: @harmj0y
+ License: BSD 3-Clause
+ Required Dependencies: Get-NetGPO, Get-GptTmpl, Get-GroupsXML, Convert-NameToSid, Convert-SidToName
+ Optional Dependencies: None
- .PARAMETER Name
+ .DESCRIPTION
- Object name to filter for.
+ First enumerates all GPOs in the current/target domain using Get-NetGPO with passed
+ arguments, and for each GPO checks if 'Restricted Groups' are set with GptTmpl.inf or
+ group membership is set through Group Policy Preferences groups.xml files. For any
+ GptTmpl.inf files found, the file is parsed with Get-GptTmpl and any 'Group Membership'
+ section data is processed if present. Any found Groups.xml files are parsed with
+ Get-GroupsXML and those memberships are returned as well.
- .PARAMETER DistinguishedName
+ .PARAMETER GPOname
- Object distinguished name to filter for.
+ The GPO name to query for, wildcards accepted.
- .PARAMETER Filter
+ .PARAMETER DisplayName
- A customized ldap filter string to use, e.g. "(description=*admin*)"
+ The GPO display name to query for, wildcards accepted.
- .PARAMETER ADSpath
+ .PARAMETER Domain
- The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
- Useful for OU queries.
+ The domain to query for GPOs, defaults to the current domain.
- .PARAMETER ADSprefix
+ .PARAMETER DomainController
- Prefix to set for the searcher (like "CN=Sites,CN=Configuration")
+ Domain controller to reflect LDAP queries through.
- .PARAMETER Domain
+ .PARAMETER ADSpath
- The domain to use for the query, defaults to the current domain.
+ The LDAP source to search through for GPOs.
+ e.g. "LDAP://cn={8FF59D28-15D7-422A-BCB7-2AE45724125A},cn=policies,cn=system,DC=dev,DC=testlab,DC=local"
- .PARAMETER DomainController
+ .PARAMETER ResolveMemberSIDs
- Domain controller to reflect LDAP queries through.
+ Switch. Try to resolve the SIDs of all found group members.
- .PARAMETER ResolveGUIDs
+ .PARAMETER UsePSDrive
- Switch. Resolve GUIDs to their display names.
+ Switch. Mount any found policy files with temporary PSDrives.
.PARAMETER PageSize
@@ -3702,275 +3036,272 @@ function Invoke-ACLScanner {
.EXAMPLE
- PS C:\> Invoke-ACLScanner -ResolveGUIDs | Export-CSV -NoTypeInformation acls.csv
+ PS C:\> Get-NetGPOGroup
+
+ Returns all local groups set by GPO along with their members and memberof.
+
+ .LINK
- Enumerate all modifable ACLs in the current domain, resolving GUIDs to display
- names, and export everything to a .csv
+ https://morgansimonsenblog.azurewebsites.net/tag/groups/
#>
[CmdletBinding()]
Param (
- [Parameter(ValueFromPipeline=$True)]
[String]
- $SamAccountName,
+ $GPOname = '*',
[String]
- $Name = "*",
+ $DisplayName,
- [Alias('DN')]
[String]
- $DistinguishedName = "*",
+ $Domain,
[String]
- $Filter,
+ $DomainController,
[String]
$ADSpath,
- [String]
- $ADSprefix,
-
- [String]
- $Domain,
-
- [String]
- $DomainController,
+ [Switch]
+ $ResolveMemberSIDs,
[Switch]
- $ResolveGUIDs,
+ $UsePSDrive,
[ValidateRange(1,10000)]
[Int]
$PageSize = 200
)
- # Get all domain ACLs with the appropriate parameters
- Get-ObjectACL @PSBoundParameters | ForEach-Object {
- # add in the translated SID for the object identity
- $_ | Add-Member Noteproperty 'IdentitySID' ($_.IdentityReference.Translate([System.Security.Principal.SecurityIdentifier]).Value)
- $_
- } | Where-Object {
- # check for any ACLs with SIDs > -1000
- try {
- # TODO: change this to a regex for speedup?
- [int]($_.IdentitySid.split("-")[-1]) -ge 1000
- }
- catch {}
- } | Where-Object {
- # filter for modifiable rights
- ($_.ActiveDirectoryRights -eq "GenericAll") -or ($_.ActiveDirectoryRights -match "Write") -or ($_.ActiveDirectoryRights -match "Create") -or ($_.ActiveDirectoryRights -match "Delete") -or (($_.ActiveDirectoryRights -match "ExtendedRight") -and ($_.AccessControlType -eq "Allow"))
- }
-}
-
+ $Option = [System.StringSplitOptions]::RemoveEmptyEntries
-filter Get-GUIDMap {
-<#
- .SYNOPSIS
+ $GPOSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $ADSpath -PageSize $PageSize
+ $GPOSearcher.filter="(&(objectCategory=groupPolicyContainer)(name=*)(gpcfilesyspath=*))"
+ $GPOSearcher.PropertiesToLoad.AddRange(('displayname', 'name', 'gpcfilesyspath'))
- Helper to build a hash table of [GUID] -> resolved names
+ ForEach($GPOResult in $GPOSearcher.FindAll()) {
- Heavily adapted from http://blogs.technet.com/b/ashleymcglone/archive/2013/03/25/active-directory-ou-permissions-report-free-powershell-script-download.aspx
+ $GPOdisplayName = $GPOResult.Properties['displayname']
+ $GPOname = $GPOResult.Properties['name']
+ $GPOPath = $GPOResult.Properties['gpcfilesyspath']
+ Write-Verbose "Get-NetGPOGroup: enumerating $GPOPath"
- .PARAMETER Domain
+ $ParseArgs = @{
+ 'GptTmplPath' = "$GPOPath\MACHINE\Microsoft\Windows NT\SecEdit\GptTmpl.inf"
+ 'UsePSDrive' = $UsePSDrive
+ }
- The domain to use for the query, defaults to the current domain.
+ # parse the GptTmpl.inf 'Restricted Groups' file if it exists
+ $Inf = Get-GptTmpl @ParseArgs
- .PARAMETER DomainController
+ if($Inf -and ($Inf.psbase.Keys -contains 'Group Membership')) {
- Domain controller to reflect LDAP queries through.
+ $Memberships = @{}
- .PARAMETER PageSize
+ # group the members/memberof fields for each entry
+ ForEach ($Membership in $Inf.'Group Membership'.GetEnumerator()) {
+ $Group, $Relation = $Membership.Key.Split('__', $Option) | ForEach-Object {$_.Trim()}
- The PageSize to set for the LDAP searcher object.
+ # extract out ALL members
+ $MembershipValue = $Membership.Value | Where-Object {$_} | ForEach-Object { $_.Trim('*') } | Where-Object {$_}
- .LINK
+ if($ResolveMemberSIDs) {
+ # if the resulting member is username and not a SID, attempt to resolve it
+ $GroupMembers = @()
+ ForEach($Member in $MembershipValue) {
+ if($Member -and ($Member.Trim() -ne '')) {
+ if($Member -notmatch '^S-1-.*') {
+ $MemberSID = Convert-NameToSid -Domain $Domain -ObjectName $Member | Select-Object -ExpandProperty SID
+ if($MemberSID) {
+ $GroupMembers += $MemberSID
+ }
+ else {
+ $GroupMembers += $Member
+ }
+ }
+ else {
+ $GroupMembers += $Member
+ }
+ }
+ }
+ $MembershipValue = $GroupMembers
+ }
- http://blogs.technet.com/b/ashleymcglone/archive/2013/03/25/active-directory-ou-permissions-report-free-powershell-script-download.aspx
-#>
+ if(-not $Memberships[$Group]) {
+ $Memberships[$Group] = @{}
+ }
+ if($MembershipValue -isnot [System.Array]) {$MembershipValue = @($MembershipValue)}
+ $Memberships[$Group].Add($Relation, $MembershipValue)
+ }
- [CmdletBinding()]
- Param (
- [Parameter(ValueFromPipeline=$True)]
- [String]
- $Domain,
-
- [String]
- $DomainController,
-
- [ValidateRange(1,10000)]
- [Int]
- $PageSize = 200
- )
-
- $GUIDs = @{'00000000-0000-0000-0000-000000000000' = 'All'}
+ ForEach ($Membership in $Memberships.GetEnumerator()) {
+ if($Membership -and $Membership.Key -and ($Membership.Key -match '^\*')) {
+ # if the SID is already resolved (i.e. begins with *) try to resolve SID to a name
+ $GroupSID = $Membership.Key.Trim('*')
+ if($GroupSID -and ($GroupSID.Trim() -ne '')) {
+ $GroupName = Convert-SidToName -SID $GroupSID
+ }
+ else {
+ $GroupName = $False
+ }
+ }
+ else {
+ $GroupName = $Membership.Key
- $SchemaPath = (Get-NetForest).schema.name
+ if($GroupName -and ($GroupName.Trim() -ne '')) {
+ if($Groupname -match 'Administrators') {
+ $GroupSID = 'S-1-5-32-544'
+ }
+ elseif($Groupname -match 'Remote Desktop') {
+ $GroupSID = 'S-1-5-32-555'
+ }
+ elseif($Groupname -match 'Guests') {
+ $GroupSID = 'S-1-5-32-546'
+ }
+ elseif($GroupName.Trim() -ne '') {
+ $GroupSID = Convert-NameToSid -Domain $Domain -ObjectName $Groupname | Select-Object -ExpandProperty SID
+ }
+ else {
+ $GroupSID = $Null
+ }
+ }
+ }
- $SchemaSearcher = Get-DomainSearcher -ADSpath $SchemaPath -DomainController $DomainController -PageSize $PageSize
- if($SchemaSearcher) {
- $SchemaSearcher.filter = "(schemaIDGUID=*)"
- try {
- $Results = $SchemaSearcher.FindAll()
- $Results | Where-Object {$_} | ForEach-Object {
- # convert the GUID
- $GUIDs[(New-Object Guid (,$_.properties.schemaidguid[0])).Guid] = $_.properties.name[0]
+ $GPOGroup = New-Object PSObject
+ $GPOGroup | Add-Member Noteproperty 'GPODisplayName' $GPODisplayName
+ $GPOGroup | Add-Member Noteproperty 'GPOName' $GPOName
+ $GPOGroup | Add-Member Noteproperty 'GPOPath' $GPOPath
+ $GPOGroup | Add-Member Noteproperty 'GPOType' 'RestrictedGroups'
+ $GPOGroup | Add-Member Noteproperty 'Filters' $Null
+ $GPOGroup | Add-Member Noteproperty 'GroupName' $GroupName
+ $GPOGroup | Add-Member Noteproperty 'GroupSID' $GroupSID
+ $GPOGroup | Add-Member Noteproperty 'GroupMemberOf' $Membership.Value.Memberof
+ $GPOGroup | Add-Member Noteproperty 'GroupMembers' $Membership.Value.Members
+ $GPOGroup
}
- $Results.dispose()
- $SchemaSearcher.dispose()
}
- catch {
- Write-Verbose "Error in building GUID map: $_"
+
+ $ParseArgs = @{
+ 'GroupsXMLpath' = "$GPOPath\MACHINE\Preferences\Groups\Groups.xml"
+ 'UsePSDrive' = $UsePSDrive
}
- }
- $RightsSearcher = Get-DomainSearcher -ADSpath $SchemaPath.replace("Schema","Extended-Rights") -DomainController $DomainController -PageSize $PageSize -Credential $Credential
- if ($RightsSearcher) {
- $RightsSearcher.filter = "(objectClass=controlAccessRight)"
- try {
- $Results = $RightsSearcher.FindAll()
- $Results | Where-Object {$_} | ForEach-Object {
- # convert the GUID
- $GUIDs[$_.properties.rightsguid[0].toString()] = $_.properties.name[0]
+ Get-GroupsXML @ParseArgs | ForEach-Object {
+ if($ResolveMemberSIDs) {
+ $GroupMembers = @()
+ ForEach($Member in $_.GroupMembers) {
+ if($Member -and ($Member.Trim() -ne '')) {
+ if($Member -notmatch '^S-1-.*') {
+ # if the resulting member is username and not a SID, attempt to resolve it
+ $MemberSID = Convert-NameToSid -Domain $Domain -ObjectName $Member | Select-Object -ExpandProperty SID
+ if($MemberSID) {
+ $GroupMembers += $MemberSID
+ }
+ else {
+ $GroupMembers += $Member
+ }
+ }
+ else {
+ $GroupMembers += $Member
+ }
+ }
+ }
+ $_.GroupMembers = $GroupMembers
}
- $Results.dispose()
- $RightsSearcher.dispose()
- }
- catch {
- Write-Verbose "Error in building GUID map: $_"
+
+ $_ | Add-Member Noteproperty 'GPODisplayName' $GPODisplayName
+ $_ | Add-Member Noteproperty 'GPOName' $GPOName
+ $_ | Add-Member Noteproperty 'GPOType' 'GroupPolicyPreferences'
+ $_
}
}
-
- $GUIDs
}
-function Get-NetComputer {
+function Find-GPOLocation {
<#
.SYNOPSIS
- This function utilizes adsisearcher to query the current AD context
- for current computer objects. Based off of Carlos Perez's Audit.psm1
- script in Posh-SecMod (link below).
-
- .PARAMETER ComputerName
-
- Return computers with a specific name, wildcards accepted.
-
- .PARAMETER SPN
-
- Return computers with a specific service principal name, wildcards accepted.
-
- .PARAMETER OperatingSystem
-
- Return computers with a specific operating system, wildcards accepted.
-
- .PARAMETER ServicePack
-
- Return computers with a specific service pack, wildcards accepted.
-
- .PARAMETER Filter
-
- A customized ldap filter string to use, e.g. "(description=*admin*)"
-
- .PARAMETER Printers
+ Enumerates the machines where a specific user/group is a member of a specific
+ local group, all through GPO correlation.
- Switch. Return only printers.
+ Author: @harmj0y
+ License: BSD 3-Clause
+ Required Dependencies: Get-NetGPOGroup, Get-NetOU, Get-NetComputer, Get-ADObject, Get-NetSite
+ Optional Dependencies: None
- .PARAMETER Ping
+ .DESCRIPTION
- Switch. Ping each host to ensure it's up before enumerating.
+ Takes a user/group name and optional domain, and determines the computers in the domain
+ the user/group has local admin (or RDP) rights to.
- .PARAMETER FullData
+ It does this by:
+ 1. resolving the user/group to its proper SID
+ 2. enumerating all groups the user/group is a current part of
+ and extracting all target SIDs to build a target SID list
+ 3. pulling all GPOs that set 'Restricted Groups' or Groups.xml by calling
+ Get-NetGPOGroup
+ 4. matching the target SID list to the queried GPO SID list
+ to enumerate all GPO the user is effectively applied with
+ 5. enumerating all OUs and sites and applicable GPO GUIs are
+ applied to through gplink enumerating
+ 6. querying for all computers under the given OUs or sites
- Switch. Return full computer objects instead of just system names (the default).
+ If no user/group is specified, all user/group -> machine mappings discovered through
+ GPO relationships are returned.
.PARAMETER Domain
- The domain to query for computers, defaults to the current domain.
+ Optional domain the user exists in for querying, defaults to the current domain.
.PARAMETER DomainController
Domain controller to reflect LDAP queries through.
- .PARAMETER ADSpath
-
- The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
- Useful for OU queries.
-
- .PARAMETER SiteName
+ .PARAMETER LocalGroup
- The AD Site name to search for computers.
+ The local group to check access against.
+ Can be "Administrators" (S-1-5-32-544), "RDP/Remote Desktop Users" (S-1-5-32-555),
+ or a custom local SID. Defaults to local 'Administrators'.
- .PARAMETER Unconstrained
+ .PARAMETER UsePSDrive
- Switch. Return computer objects that have unconstrained delegation.
+ Switch. Mount any found policy files with temporary PSDrives.
.PARAMETER PageSize
The PageSize to set for the LDAP searcher object.
- .PARAMETER Credential
-
- A [Management.Automation.PSCredential] object of alternate credentials
- for connection to the target domain.
-
.EXAMPLE
- PS C:\> Get-NetComputer
+ PS C:\> Find-GPOLocation
- Returns the current computers in current domain.
+ Find all user/group -> machine relationships where the user/group is a member
+ of the local administrators group on target machines.
.EXAMPLE
- PS C:\> Get-NetComputer -SPN mssql*
+ PS C:\> Find-GPOLocation -UserName dfm
- Returns all MS SQL servers on the domain.
+ Find all computers that dfm user has local administrator rights to in
+ the current domain.
.EXAMPLE
- PS C:\> Get-NetComputer -Domain testing
+ PS C:\> Find-GPOLocation -UserName dfm -Domain dev.testlab.local
- Returns the current computers in 'testing' domain.
+ Find all computers that dfm user has local administrator rights to in
+ the dev.testlab.local domain.
.EXAMPLE
- PS C:\> Get-NetComputer -Domain testing -FullData
-
- Returns full computer objects in the 'testing' domain.
-
- .LINK
+ PS C:\> Find-GPOLocation -UserName jason -LocalGroup RDP
- https://github.com/darkoperator/Posh-SecMod/blob/master/Audit/Audit.psm1
+ Find all computers that jason has local RDP access rights to in the domain.
#>
[CmdletBinding()]
Param (
- [Parameter(ValueFromPipeline=$True)]
- [Alias('HostName')]
- [String]
- $ComputerName = '*',
-
- [String]
- $SPN,
-
- [String]
- $OperatingSystem,
-
- [String]
- $ServicePack,
-
- [String]
- $Filter,
-
- [Switch]
- $Printers,
-
- [Switch]
- $Ping,
-
- [Switch]
- $FullData,
-
[String]
$Domain,
@@ -3978,9372 +3309,785 @@ function Get-NetComputer {
$DomainController,
[String]
- $ADSpath,
-
- [String]
- $SiteName,
+ $LocalGroup = 'Administrators',
[Switch]
- $Unconstrained,
+ $UsePSDrive,
[ValidateRange(1,10000)]
[Int]
- $PageSize = 200,
-
- [Management.Automation.PSCredential]
- $Credential
+ $PageSize = 200
)
- begin {
- # so this isn't repeated if multiple computer names are passed on the pipeline
- $CompSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -ADSpath $ADSpath -PageSize $PageSize -Credential $Credential
+ $TargetSIDs = @('*')
+
+ # figure out what the SID is of the target local group we're checking for membership in
+ if($LocalGroup -like "*Admin*") {
+ $TargetLocalSID = 'S-1-5-32-544'
+ }
+ elseif ( ($LocalGroup -like "*RDP*") -or ($LocalGroup -like "*Remote*") ) {
+ $TargetLocalSID = 'S-1-5-32-555'
+ }
+ elseif ($LocalGroup -like "S-1-5-*") {
+ $TargetLocalSID = $LocalGroup
+ }
+ else {
+ throw "LocalGroup must be 'Administrators', 'RDP', or a 'S-1-5-X' SID format."
}
- process {
+ if(-not $TargetSIDs) {
+ throw "No effective target SIDs!"
+ }
- if ($CompSearcher) {
+ Write-Verbose "TargetLocalSID: $TargetLocalSID"
+ Write-Verbose "Effective target SIDs: $TargetSIDs"
- # if we're checking for unconstrained delegation
- if($Unconstrained) {
- Write-Verbose "Searching for computers with for unconstrained delegation"
- $Filter += "(userAccountControl:1.2.840.113556.1.4.803:=524288)"
- }
- # set the filters for the seracher if it exists
- if($Printers) {
- Write-Verbose "Searching for printers"
- # $CompSearcher.filter="(&(objectCategory=printQueue)$Filter)"
- $Filter += "(objectCategory=printQueue)"
- }
- if($SPN) {
- Write-Verbose "Searching for computers with SPN: $SPN"
- $Filter += "(servicePrincipalName=$SPN)"
+ $GPOGroupArgs = @{
+ 'Domain' = $Domain
+ 'DomainController' = $DomainController
+ 'UsePSDrive' = $UsePSDrive
+ 'ResolveMemberSIDs' = $True
+ 'PageSize' = $PageSize
+ }
+
+ # enumerate all GPO group mappings for the target domain that involve our target SID set
+ Sort-Object -Property GPOName -Unique -InputObject $(ForEach($GPOGroup in (Get-NetGPOGroup @GPOGroupArgs)) {
+ # if the locally set group is what we're looking for, check the GroupMembers ('members')
+ # for our target SID
+ if($GPOgroup.GroupSID -match $TargetLocalSID) {
+ ForEach($GPOgroupMember in $GPOgroup.GroupMembers) {
+ if($GPOgroupMember) {
+ if ( ($TargetSIDs[0] -eq '*') -or ($TargetSIDs -Contains $GPOgroupMember) ) {
+ $GPOgroup
+ }
+ }
}
- if($OperatingSystem) {
- $Filter += "(operatingsystem=$OperatingSystem)"
+ }
+ # if the group is a 'memberof' the group we're looking for, check GroupSID against the targt SIDs
+ if( ($GPOgroup.GroupMemberOf -contains $TargetLocalSID) ) {
+ if( ($TargetSIDs[0] -eq '*') -or ($TargetSIDs -Contains $GPOgroup.GroupSID) ) {
+ $GPOgroup
}
- if($ServicePack) {
- $Filter += "(operatingsystemservicepack=$ServicePack)"
+ }
+ }) | ForEach-Object {
+
+ $GPOname = $_.GPODisplayName
+ write-verbose "GPOname: $GPOname"
+ $GPOguid = $_.GPOName
+ $GPOPath = $_.GPOPath
+ $GPOType = $_.GPOType
+ if($_.GroupMembers) {
+ $GPOMembers = $_.GroupMembers
+ }
+ else {
+ $GPOMembers = $_.GroupSID
+ }
+
+ $Filters = $_.Filters
+
+ if(-not $TargetObject) {
+ # if the * wildcard was used, set the ObjectDistName as the GPO member SID set
+ # so all relationship mappings are output
+ $TargetObjectSIDs = $GPOMembers
+ }
+ else {
+ $TargetObjectSIDs = $TargetObject
+ }
+
+ # find any OUs that have this GUID applied and then retrieve any computers from the OU
+ Get-NetOU -Domain $Domain -DomainController $DomainController -GUID $GPOguid -FullData -PageSize $PageSize | ForEach-Object {
+ if($Filters) {
+ # filter for computer name/org unit if a filter is specified
+ # TODO: handle other filters (i.e. OU filters?) again, I hate you GPP...
+ $FilterValue = $Filters.Value
+ $OUComputers = ForEach($OUComputer in (Get-NetComputer -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $_.ADSpath -PageSize $PageSize)) {
+ if($OUComputer.ToLower() -match $Filters.Value) {
+ $OUComputer
+ }
+ }
}
- if($SiteName) {
- $Filter += "(serverreferencebl=$SiteName)"
+ else {
+ $OUComputers = Get-NetComputer -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $_.ADSpath -PageSize $PageSize
}
- $CompFilter = "(&(sAMAccountType=805306369)(dnshostname=$ComputerName)$Filter)"
- Write-Verbose "Get-NetComputer filter : '$CompFilter'"
- $CompSearcher.filter = $CompFilter
-
- try {
- $Results = $CompSearcher.FindAll()
- $Results | Where-Object {$_} | ForEach-Object {
- $Up = $True
- if($Ping) {
- # TODO: how can these results be piped to ping for a speedup?
- $Up = Test-Connection -Count 1 -Quiet -ComputerName $_.properties.dnshostname
+ if($OUComputers) {
+ if($OUComputers -isnot [System.Array]) {$OUComputers = @($OUComputers)}
+ ForEach ($TargetSid in $TargetObjectSIDs) {
+ $Object = Get-ADObject -SID $TargetSid
+ if (-not $Object) {
+ $Object = Get-ADObject -SID $TargetSid -Domain $Domain -DomainController $DomainController -Credential $Credential -PageSize $PageSize
}
- if($Up) {
- # return full data objects
- if ($FullData) {
- # convert/process the LDAP fields for each result
- $Computer = Convert-LDAPProperty -Properties $_.Properties
- $Computer.PSObject.TypeNames.Add('PowerView.Computer')
- $Computer
- }
- else {
- # otherwise we're just returning the DNS host name
- $_.properties.dnshostname
- }
+ if($Object) {
+ $MemberDN = $Object.distinguishedName
+ $ObjectDomain = $MemberDN.subString($MemberDN.IndexOf("DC=")) -replace 'DC=','' -replace ',','.'
+ $IsGroup = @('268435456','268435457','536870912','536870913') -contains $Object.samaccounttype
+
+ $GPOLocation = New-Object PSObject
+ $GPOLocation | Add-Member Noteproperty 'ObjectDomain' $ObjectDomain
+ $GPOLocation | Add-Member Noteproperty 'ObjectName' $Object.samaccountname
+ $GPOLocation | Add-Member Noteproperty 'ObjectDN' $Object.distinguishedname
+ $GPOLocation | Add-Member Noteproperty 'ObjectSID' $Object.objectsid
+ $GPOLocation | Add-Member Noteproperty 'IsGroup' $IsGroup
+ $GPOLocation | Add-Member Noteproperty 'GPODomain' $Domain
+ $GPOLocation | Add-Member Noteproperty 'GPODisplayName' $GPOname
+ $GPOLocation | Add-Member Noteproperty 'GPOGuid' $GPOGuid
+ $GPOLocation | Add-Member Noteproperty 'GPOPath' $GPOPath
+ $GPOLocation | Add-Member Noteproperty 'GPOType' $GPOType
+ $GPOLocation | Add-Member Noteproperty 'ContainerName' $_.distinguishedname
+ $GPOLocation | Add-Member Noteproperty 'ComputerName' $OUComputers
+ $GPOLocation.PSObject.TypeNames.Add('PowerView.GPOLocalGroup')
+ $GPOLocation
}
}
- $Results.dispose()
- $CompSearcher.dispose()
}
- catch {
- Write-Warning "Error: $_"
+ }
+
+ # find any sites that have this GUID applied
+ Get-NetSite -Domain $Domain -DomainController $DomainController -GUID $GPOguid -PageSize $PageSize -FullData | ForEach-Object {
+
+ ForEach ($TargetSid in $TargetObjectSIDs) {
+ # $Object = Get-ADObject -SID $TargetSid -Domain $Domain -DomainController $DomainController -Credential $Credential -PageSize $PageSize
+ $Object = Get-ADObject -SID $TargetSid
+ if (-not $Object) {
+ $Object = Get-ADObject -SID $TargetSid -Domain $Domain -DomainController $DomainController -Credential $Credential -PageSize $PageSize
+ }
+ if($Object) {
+ $MemberDN = $Object.distinguishedName
+ $ObjectDomain = $MemberDN.subString($MemberDN.IndexOf("DC=")) -replace 'DC=','' -replace ',','.'
+ $IsGroup = @('268435456','268435457','536870912','536870913') -contains $Object.samaccounttype
+
+ $AppliedSite = New-Object PSObject
+ $GPOLocation | Add-Member Noteproperty 'ObjectDomain' $ObjectDomain
+ $AppliedSite | Add-Member Noteproperty 'ObjectName' $Object.samaccountname
+ $AppliedSite | Add-Member Noteproperty 'ObjectDN' $Object.distinguishedname
+ $AppliedSite | Add-Member Noteproperty 'ObjectSID' $Object.objectsid
+ $AppliedSite | Add-Member Noteproperty 'IsGroup' $IsGroup
+ $AppliedSite | Add-Member Noteproperty 'GPODomain' $Domain
+ $AppliedSite | Add-Member Noteproperty 'GPODisplayName' $GPOname
+ $AppliedSite | Add-Member Noteproperty 'GPOGuid' $GPOGuid
+ $AppliedSite | Add-Member Noteproperty 'GPOPath' $GPOPath
+ $AppliedSite | Add-Member Noteproperty 'GPOType' $GPOType
+ $AppliedSite | Add-Member Noteproperty 'ContainerName' $_.distinguishedname
+ $AppliedSite | Add-Member Noteproperty 'ComputerName' $_.siteobjectbl
+ $AppliedSite.PSObject.TypeNames.Add('PowerView.GPOLocalGroup')
+ $AppliedSite
+ }
}
}
}
}
-function Get-ADObject {
+########################################################
+#
+# Functions that enumerate a single host, either through
+# WinNT, WMI, remote registry, or API calls
+# (with PSReflect).
+#
+########################################################
+
+function Get-NetLocalGroup {
<#
.SYNOPSIS
- Takes a domain SID and returns the user, group, or computer object
- associated with it.
-
- .PARAMETER SID
-
- The SID of the domain object you're querying for.
-
- .PARAMETER Name
-
- The Name of the domain object you're querying for.
-
- .PARAMETER SamAccountName
-
- The SamAccountName of the domain object you're querying for.
+ Gets a list of all current users in a specified local group,
+ or returns the names of all local groups with -ListGroups.
- .PARAMETER Domain
+ .PARAMETER ComputerName
- The domain to query for objects, defaults to the current domain.
+ The hostname or IP to query for local group users.
- .PARAMETER DomainController
+ .PARAMETER ComputerFile
- Domain controller to reflect LDAP queries through.
+ File of hostnames/IPs to query for local group users.
- .PARAMETER ADSpath
+ .PARAMETER GroupName
- The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
- Useful for OU queries.
+ The local group name to query for users. If not given, it defaults to "Administrators"
- .PARAMETER Filter
+ .PARAMETER Recurse
- Additional LDAP filter string for the query.
+ Switch. If the local member member is a domain group, recursively try to resolve its members to get a list of domain users who can access this machine.
- .PARAMETER ReturnRaw
+ .PARAMETER API
- Switch. Return the raw object instead of translating its properties.
- Used by Set-ADObject to modify object properties.
+ Switch. Use API calls instead of the WinNT service provider. Less information,
+ but the results are faster.
- .PARAMETER PageSize
+ .PARAMETER IsDomain
- The PageSize to set for the LDAP searcher object.
+ Switch. Only return results that are domain accounts.
- .PARAMETER Credential
+ .PARAMETER DomainSID
- A [Management.Automation.PSCredential] object of alternate credentials
- for connection to the target domain.
+ The SID of the enumerated machine's domain, used to identify if results are domain
+ or local when using the -API flag.
.EXAMPLE
- PS C:\> Get-ADObject -SID "S-1-5-21-2620891829-2411261497-1773853088-1110"
+ PS C:\> Get-NetLocalGroup
- Get the domain object associated with the specified SID.
+ Returns the usernames that of members of localgroup "Administrators" on the local host.
.EXAMPLE
- PS C:\> Get-ADObject -ADSpath "CN=AdminSDHolder,CN=System,DC=testlab,DC=local"
-
- Get the AdminSDHolder object for the testlab.local domain.
-#>
-
- [CmdletBinding()]
- Param (
- [Parameter(ValueFromPipeline=$True)]
- [String]
- $SID,
-
- [String]
- $Name,
-
- [String]
- $SamAccountName,
-
- [String]
- $Domain,
-
- [String]
- $DomainController,
+ PS C:\> Get-NetLocalGroup -ComputerName WINDOWSXP
- [String]
- $ADSpath,
+ Returns all the local administrator accounts for WINDOWSXP
- [String]
- $Filter,
+ .EXAMPLE
- [Switch]
- $ReturnRaw,
+ PS C:\> Get-NetLocalGroup -ComputerName WINDOWS7 -Recurse
- [ValidateRange(1,10000)]
- [Int]
- $PageSize = 200,
+ Returns all effective local/domain users/groups that can access WINDOWS7 with
+ local administrative privileges.
- [Management.Automation.PSCredential]
- $Credential
- )
- process {
- if($SID -and (-not $Domain)) {
- # if a SID is passed, try to resolve it to a reachable domain name for the searcher
- try {
- $Name = Convert-SidToName $SID
- if($Name) {
- $Canonical = Convert-ADName -ObjectName $Name -InputType NT4 -OutputType Canonical
- if($Canonical) {
- $Domain = $Canonical.split("/")[0]
- }
- else {
- Write-Verbose "Error resolving SID '$SID'"
- return $Null
- }
- }
- }
- catch {
- Write-Verbose "Error resolving SID '$SID' : $_"
- return $Null
- }
- }
+ .EXAMPLE
- $ObjectSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $ADSpath -PageSize $PageSize
+ PS C:\> "WINDOWS7", "WINDOWSSP" | Get-NetLocalGroup -API
- if($ObjectSearcher) {
- if($SID) {
- $ObjectSearcher.filter = "(&(objectsid=$SID)$Filter)"
- }
- elseif($Name) {
- $ObjectSearcher.filter = "(&(name=$Name)$Filter)"
- }
- elseif($SamAccountName) {
- $ObjectSearcher.filter = "(&(samAccountName=$SamAccountName)$Filter)"
- }
+ Returns all local groups on the the passed hosts using API calls instead of the
+ WinNT service provider.
- try {
- $Results = $ObjectSearcher.FindAll()
- $Results | Where-Object {$_} | ForEach-Object {
- if($ReturnRaw) {
- $_
- }
- else {
- # convert/process the LDAP fields for each result
- Convert-LDAPProperty -Properties $_.Properties
- }
- }
- $Results.dispose()
- }
- catch {
- Write-Verbose "Error building the searcher object!"
- }
- $ObjectSearcher.dispose()
- }
- }
-}
+ .LINK
+ http://stackoverflow.com/questions/21288220/get-all-local-members-and-groups-displayed-together
+ http://msdn.microsoft.com/en-us/library/aa772211(VS.85).aspx
+#>
-function Set-ADObject {
-<#
- .SYNOPSIS
+ [CmdletBinding(DefaultParameterSetName = 'WinNT')]
+ param(
+ [Parameter(ParameterSetName = 'API', Position=0, ValueFromPipeline=$True)]
+ [Parameter(ParameterSetName = 'WinNT', Position=0, ValueFromPipeline=$True)]
+ [Alias('HostName')]
+ [String[]]
+ $ComputerName = $Env:ComputerName,
- Takes a SID, name, or SamAccountName to query for a specified
- domain object, and then sets a specified 'PropertyName' to a
- specified 'PropertyValue'.
-
- .PARAMETER SID
-
- The SID of the domain object you're querying for.
-
- .PARAMETER Name
-
- The Name of the domain object you're querying for.
-
- .PARAMETER SamAccountName
-
- The SamAccountName of the domain object you're querying for.
-
- .PARAMETER Domain
-
- The domain to query for objects, defaults to the current domain.
-
- .PARAMETER DomainController
-
- Domain controller to reflect LDAP queries through.
-
- .PARAMETER Filter
-
- Additional LDAP filter string for the query.
-
- .PARAMETER PropertyName
-
- The property name to set.
-
- .PARAMETER PropertyValue
-
- The value to set for PropertyName
-
- .PARAMETER PropertyXorValue
-
- Integer value to binary xor (-bxor) with the current int value.
-
- .PARAMETER ClearValue
-
- Switch. Clear the value of PropertyName
-
- .PARAMETER PageSize
-
- The PageSize to set for the LDAP searcher object.
-
- .PARAMETER Credential
-
- A [Management.Automation.PSCredential] object of alternate credentials
- for connection to the target domain.
-
- .EXAMPLE
-
- PS C:\> Set-ADObject -SamAccountName matt.admin -PropertyName countrycode -PropertyValue 0
-
- Set the countrycode for matt.admin to 0
-
- .EXAMPLE
-
- PS C:\> Set-ADObject -SamAccountName matt.admin -PropertyName useraccountcontrol -PropertyXorValue 65536
-
- Set the password not to expire on matt.admin
-#>
-
- [CmdletBinding()]
- Param (
- [String]
- $SID,
-
- [String]
- $Name,
-
- [String]
- $SamAccountName,
-
- [String]
- $Domain,
-
- [String]
- $DomainController,
-
- [String]
- $Filter,
-
- [Parameter(Mandatory = $True)]
- [String]
- $PropertyName,
-
- $PropertyValue,
-
- [Int]
- $PropertyXorValue,
-
- [Switch]
- $ClearValue,
-
- [ValidateRange(1,10000)]
- [Int]
- $PageSize = 200,
-
- [Management.Automation.PSCredential]
- $Credential
- )
-
- $Arguments = @{
- 'SID' = $SID
- 'Name' = $Name
- 'SamAccountName' = $SamAccountName
- 'Domain' = $Domain
- 'DomainController' = $DomainController
- 'Filter' = $Filter
- 'PageSize' = $PageSize
- 'Credential' = $Credential
- }
- # splat the appropriate arguments to Get-ADObject
- $RawObject = Get-ADObject -ReturnRaw @Arguments
-
- try {
- # get the modifiable object for this search result
- $Entry = $RawObject.GetDirectoryEntry()
-
- if($ClearValue) {
- Write-Verbose "Clearing value"
- $Entry.$PropertyName.clear()
- $Entry.commitchanges()
- }
-
- elseif($PropertyXorValue) {
- $TypeName = $Entry.$PropertyName[0].GetType().name
-
- # UAC value references- https://support.microsoft.com/en-us/kb/305144
- $PropertyValue = $($Entry.$PropertyName) -bxor $PropertyXorValue
- $Entry.$PropertyName = $PropertyValue -as $TypeName
- $Entry.commitchanges()
- }
-
- else {
- $Entry.put($PropertyName, $PropertyValue)
- $Entry.setinfo()
- }
- }
- catch {
- Write-Warning "Error setting property $PropertyName to value '$PropertyValue' for object $($RawObject.Properties.samaccountname) : $_"
- }
-}
-
-
-function Invoke-DowngradeAccount {
-<#
- .SYNOPSIS
-
- Set reversible encryption on a given account and then force the password
- to be set on next user login. To repair use "-Repair".
-
- .PARAMETER SamAccountName
-
- The SamAccountName of the domain object you're querying for.
-
- .PARAMETER Name
-
- The Name of the domain object you're querying for.
-
- .PARAMETER Domain
-
- The domain to query for objects, defaults to the current domain.
-
- .PARAMETER DomainController
-
- Domain controller to reflect LDAP queries through.
-
- .PARAMETER Filter
-
- Additional LDAP filter string for the query.
-
- .PARAMETER Repair
-
- Switch. Unset the reversible encryption flag and force password reset flag.
-
- .PARAMETER Credential
-
- A [Management.Automation.PSCredential] object of alternate credentials
- for connection to the target domain.
-
- .EXAMPLE
-
- PS> Invoke-DowngradeAccount -SamAccountName jason
-
- Set reversible encryption on the 'jason' account and force the password to be changed.
-
- .EXAMPLE
-
- PS> Invoke-DowngradeAccount -SamAccountName jason -Repair
-
- Unset reversible encryption on the 'jason' account and remove the forced password change.
-#>
-
- [CmdletBinding()]
- Param (
- [Parameter(ParameterSetName = 'SamAccountName', Position=0, ValueFromPipeline=$True)]
- [String]
- $SamAccountName,
-
- [Parameter(ParameterSetName = 'Name')]
- [String]
- $Name,
-
- [String]
- $Domain,
-
- [String]
- $DomainController,
-
- [String]
- $Filter,
-
- [Switch]
- $Repair,
-
- [Management.Automation.PSCredential]
- $Credential
- )
-
- process {
- $Arguments = @{
- 'SamAccountName' = $SamAccountName
- 'Name' = $Name
- 'Domain' = $Domain
- 'DomainController' = $DomainController
- 'Filter' = $Filter
- 'Credential' = $Credential
- }
-
- # splat the appropriate arguments to Get-ADObject
- $UACValues = Get-ADObject @Arguments | select useraccountcontrol | ConvertFrom-UACValue
-
- if($Repair) {
-
- if($UACValues.Keys -contains "ENCRYPTED_TEXT_PWD_ALLOWED") {
- # if reversible encryption is set, unset it
- Set-ADObject @Arguments -PropertyName useraccountcontrol -PropertyXorValue 128
- }
-
- # unset the forced password change
- Set-ADObject @Arguments -PropertyName pwdlastset -PropertyValue -1
- }
-
- else {
-
- if($UACValues.Keys -contains "DONT_EXPIRE_PASSWORD") {
- # if the password is set to never expire, unset
- Set-ADObject @Arguments -PropertyName useraccountcontrol -PropertyXorValue 65536
- }
-
- if($UACValues.Keys -notcontains "ENCRYPTED_TEXT_PWD_ALLOWED") {
- # if reversible encryption is not set, set it
- Set-ADObject @Arguments -PropertyName useraccountcontrol -PropertyXorValue 128
- }
-
- # force the password to be changed on next login
- Set-ADObject @Arguments -PropertyName pwdlastset -PropertyValue 0
- }
- }
-}
-
-
-function Get-ComputerProperty {
-<#
- .SYNOPSIS
-
- Returns a list of all computer object properties. If a property
- name is specified, it returns all [computer:property] values.
-
- Taken directly from @obscuresec's post:
- http://obscuresecurity.blogspot.com/2014/04/ADSISearcher.html
-
- .PARAMETER Properties
-
- Return property names for computers.
-
- .PARAMETER Domain
-
- The domain to query for computer properties, defaults to the current domain.
-
- .PARAMETER DomainController
-
- Domain controller to reflect LDAP queries through.
-
- .PARAMETER PageSize
-
- The PageSize to set for the LDAP searcher object.
-
- .PARAMETER Credential
-
- A [Management.Automation.PSCredential] object of alternate credentials
- for connection to the target domain.
-
- .EXAMPLE
-
- PS C:\> Get-ComputerProperty -Domain testing
-
- Returns all user properties for computers in the 'testing' domain.
-
- .EXAMPLE
-
- PS C:\> Get-ComputerProperty -Properties ssn,lastlogon,location
-
- Returns all an array of computer/ssn/lastlogin/location combinations
- for computers in the current domain.
-
- .LINK
-
- http://obscuresecurity.blogspot.com/2014/04/ADSISearcher.html
-#>
-
- [CmdletBinding()]
- param(
- [String[]]
- $Properties,
-
- [String]
- $Domain,
-
- [String]
- $DomainController,
-
- [ValidateRange(1,10000)]
- [Int]
- $PageSize = 200,
-
- [Management.Automation.PSCredential]
- $Credential
- )
-
- if($Properties) {
- # extract out the set of all properties for each object
- $Properties = ,"name" + $Properties | Sort-Object -Unique
- Get-NetComputer -Domain $Domain -DomainController $DomainController -Credential $Credential -FullData -PageSize $PageSize | Select-Object -Property $Properties
- }
- else {
- # extract out just the property names
- Get-NetComputer -Domain $Domain -DomainController $DomainController -Credential $Credential -FullData -PageSize $PageSize | Select-Object -first 1 | Get-Member -MemberType *Property | Select-Object -Property "Name"
- }
-}
-
-
-function Find-ComputerField {
-<#
- .SYNOPSIS
-
- Searches computer object fields for a given word (default *pass*). Default
- field being searched is 'description'.
-
- Taken directly from @obscuresec's post:
- http://obscuresecurity.blogspot.com/2014/04/ADSISearcher.html
-
- .PARAMETER SearchTerm
-
- Term to search for, default of "pass".
-
- .PARAMETER SearchField
-
- User field to search in, default of "description".
-
- .PARAMETER ADSpath
-
- The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
- Useful for OU queries.
-
- .PARAMETER Domain
-
- Domain to search computer fields for, defaults to the current domain.
-
- .PARAMETER DomainController
-
- Domain controller to reflect LDAP queries through.
-
- .PARAMETER PageSize
-
- The PageSize to set for the LDAP searcher object.
-
- .PARAMETER Credential
-
- A [Management.Automation.PSCredential] object of alternate credentials
- for connection to the target domain.
-
- .EXAMPLE
-
- PS C:\> Find-ComputerField -SearchTerm backup -SearchField info
-
- Find computer accounts with "backup" in the "info" field.
-#>
-
- [CmdletBinding()]
- param(
- [Parameter(Position=0,ValueFromPipeline=$True)]
- [Alias('Term')]
- [String]
- $SearchTerm = 'pass',
-
- [Alias('Field')]
- [String]
- $SearchField = 'description',
-
- [String]
- $ADSpath,
-
- [String]
- $Domain,
-
- [String]
- $DomainController,
-
- [ValidateRange(1,10000)]
- [Int]
- $PageSize = 200,
-
- [Management.Automation.PSCredential]
- $Credential
- )
-
- process {
- Get-NetComputer -ADSpath $ADSpath -Domain $Domain -DomainController $DomainController -Credential $Credential -FullData -Filter "($SearchField=*$SearchTerm*)" -PageSize $PageSize | Select-Object samaccountname,$SearchField
- }
-}
-
-
-function Get-NetOU {
-<#
- .SYNOPSIS
-
- Gets a list of all current OUs in a domain.
-
- .PARAMETER OUName
-
- The OU name to query for, wildcards accepted.
-
- .PARAMETER GUID
-
- Only return OUs with the specified GUID in their gplink property.
-
- .PARAMETER Domain
-
- The domain to query for OUs, defaults to the current domain.
-
- .PARAMETER DomainController
-
- Domain controller to reflect LDAP queries through.
-
- .PARAMETER ADSpath
-
- The LDAP source to search through.
-
- .PARAMETER FullData
-
- Switch. Return full OU objects instead of just object names (the default).
-
- .PARAMETER PageSize
-
- The PageSize to set for the LDAP searcher object.
-
- .PARAMETER Credential
-
- A [Management.Automation.PSCredential] object of alternate credentials
- for connection to the target domain.
-
- .EXAMPLE
-
- PS C:\> Get-NetOU
-
- Returns the current OUs in the domain.
-
- .EXAMPLE
-
- PS C:\> Get-NetOU -OUName *admin* -Domain testlab.local
-
- Returns all OUs with "admin" in their name in the testlab.local domain.
-
- .EXAMPLE
-
- PS C:\> Get-NetOU -GUID 123-...
-
- Returns all OUs with linked to the specified group policy object.
-
- .EXAMPLE
-
- PS C:\> "*admin*","*server*" | Get-NetOU
-
- Get the full OU names for the given search terms piped on the pipeline.
-#>
-
- [CmdletBinding()]
- Param (
- [Parameter(ValueFromPipeline=$True)]
- [String]
- $OUName = '*',
-
- [String]
- $GUID,
-
- [String]
- $Domain,
-
- [String]
- $DomainController,
-
- [String]
- $ADSpath,
-
- [Switch]
- $FullData,
-
- [ValidateRange(1,10000)]
- [Int]
- $PageSize = 200,
-
- [Management.Automation.PSCredential]
- $Credential
- )
-
- begin {
- $OUSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $ADSpath -PageSize $PageSize
- }
- process {
- if ($OUSearcher) {
- if ($GUID) {
- # if we're filtering for a GUID in .gplink
- $OUSearcher.filter="(&(objectCategory=organizationalUnit)(name=$OUName)(gplink=*$GUID*))"
- }
- else {
- $OUSearcher.filter="(&(objectCategory=organizationalUnit)(name=$OUName))"
- }
-
- try {
- $Results = $OUSearcher.FindAll()
- $Results | Where-Object {$_} | ForEach-Object {
- if ($FullData) {
- # convert/process the LDAP fields for each result
- $OU = Convert-LDAPProperty -Properties $_.Properties
- $OU.PSObject.TypeNames.Add('PowerView.OU')
- $OU
- }
- else {
- # otherwise just returning the ADS paths of the OUs
- $_.properties.adspath
- }
- }
- $Results.dispose()
- $OUSearcher.dispose()
- }
- catch {
- Write-Warning $_
- }
- }
- }
-}
-
-
-function Get-NetSite {
-<#
- .SYNOPSIS
-
- Gets a list of all current sites in a domain.
-
- .PARAMETER SiteName
-
- Site filter string, wildcards accepted.
-
- .PARAMETER Domain
-
- The domain to query for sites, defaults to the current domain.
-
- .PARAMETER DomainController
-
- Domain controller to reflect LDAP queries through.
-
- .PARAMETER ADSpath
-
- The LDAP source to search through.
-
- .PARAMETER GUID
-
- Only return site with the specified GUID in their gplink property.
-
- .PARAMETER FullData
-
- Switch. Return full site objects instead of just object names (the default).
-
- .PARAMETER PageSize
-
- The PageSize to set for the LDAP searcher object.
-
- .PARAMETER Credential
-
- A [Management.Automation.PSCredential] object of alternate credentials
- for connection to the target domain.
-
- .EXAMPLE
-
- PS C:\> Get-NetSite -Domain testlab.local -FullData
-
- Returns the full data objects for all sites in testlab.local
-#>
-
- [CmdletBinding()]
- Param (
- [Parameter(ValueFromPipeline=$True)]
- [String]
- $SiteName = "*",
-
- [String]
- $Domain,
-
- [String]
- $DomainController,
-
- [String]
- $ADSpath,
-
- [String]
- $GUID,
-
- [Switch]
- $FullData,
-
- [ValidateRange(1,10000)]
- [Int]
- $PageSize = 200,
-
- [Management.Automation.PSCredential]
- $Credential
- )
-
- begin {
- $SiteSearcher = Get-DomainSearcher -ADSpath $ADSpath -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSprefix "CN=Sites,CN=Configuration" -PageSize $PageSize
- }
- process {
- if($SiteSearcher) {
-
- if ($GUID) {
- # if we're filtering for a GUID in .gplink
- $SiteSearcher.filter="(&(objectCategory=site)(name=$SiteName)(gplink=*$GUID*))"
- }
- else {
- $SiteSearcher.filter="(&(objectCategory=site)(name=$SiteName))"
- }
-
- try {
- $Results = $SiteSearcher.FindAll()
- $Results | Where-Object {$_} | ForEach-Object {
- if ($FullData) {
- # convert/process the LDAP fields for each result
- $Site = Convert-LDAPProperty -Properties $_.Properties
- $Site.PSObject.TypeNames.Add('PowerView.Site')
- $Site
- }
- else {
- # otherwise just return the site name
- $_.properties.name
- }
- }
- $Results.dispose()
- $SiteSearcher.dispose()
- }
- catch {
- Write-Verbose $_
- }
- }
- }
-}
-
-
-function Get-NetSubnet {
-<#
- .SYNOPSIS
-
- Gets a list of all current subnets in a domain.
-
- .PARAMETER SiteName
-
- Only return subnets from the specified SiteName.
-
- .PARAMETER Domain
-
- The domain to query for subnets, defaults to the current domain.
-
- .PARAMETER DomainController
-
- Domain controller to reflect LDAP queries through.
-
- .PARAMETER ADSpath
-
- The LDAP source to search through.
-
- .PARAMETER FullData
-
- Switch. Return full subnet objects instead of just object names (the default).
-
- .PARAMETER PageSize
-
- The PageSize to set for the LDAP searcher object.
-
- .PARAMETER Credential
-
- A [Management.Automation.PSCredential] object of alternate credentials
- for connection to the target domain.
-
- .EXAMPLE
-
- PS C:\> Get-NetSubnet
-
- Returns all subnet names in the current domain.
-
- .EXAMPLE
-
- PS C:\> Get-NetSubnet -Domain testlab.local -FullData
-
- Returns the full data objects for all subnets in testlab.local
-#>
-
- [CmdletBinding()]
- Param (
- [Parameter(ValueFromPipeline=$True)]
- [String]
- $SiteName = "*",
-
- [String]
- $Domain,
-
- [String]
- $ADSpath,
-
- [String]
- $DomainController,
-
- [Switch]
- $FullData,
-
- [ValidateRange(1,10000)]
- [Int]
- $PageSize = 200,
-
- [Management.Automation.PSCredential]
- $Credential
- )
-
- begin {
- $SubnetSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $ADSpath -ADSprefix "CN=Subnets,CN=Sites,CN=Configuration" -PageSize $PageSize
- }
-
- process {
- if($SubnetSearcher) {
-
- $SubnetSearcher.filter="(&(objectCategory=subnet))"
-
- try {
- $Results = $SubnetSearcher.FindAll()
- $Results | Where-Object {$_} | ForEach-Object {
- if ($FullData) {
- # convert/process the LDAP fields for each result
- Convert-LDAPProperty -Properties $_.Properties | Where-Object { $_.siteobject -match "CN=$SiteName" }
- }
- else {
- # otherwise just return the subnet name and site name
- if ( ($SiteName -and ($_.properties.siteobject -match "CN=$SiteName,")) -or ($SiteName -eq '*')) {
-
- $SubnetProperties = @{
- 'Subnet' = $_.properties.name[0]
- }
- try {
- $SubnetProperties['Site'] = ($_.properties.siteobject[0]).split(",")[0]
- }
- catch {
- $SubnetProperties['Site'] = 'Error'
- }
-
- New-Object -TypeName PSObject -Property $SubnetProperties
- }
- }
- }
- $Results.dispose()
- $SubnetSearcher.dispose()
- }
- catch {
- Write-Warning $_
- }
- }
- }
-}
-
-
-function Get-DomainSID {
-<#
- .SYNOPSIS
-
- Gets the SID for the domain.
-
- .PARAMETER Domain
-
- The domain to query, defaults to the current domain.
-
- .PARAMETER DomainController
-
- Domain controller to reflect LDAP queries through.
-
- .EXAMPLE
-
- C:\> Get-DomainSID -Domain TEST
-
- Returns SID for the domain 'TEST'
-#>
-
- param(
- [String]
- $Domain,
-
- [String]
- $DomainController
- )
-
- $DCSID = Get-NetComputer -Domain $Domain -DomainController $DomainController -FullData -Filter '(userAccountControl:1.2.840.113556.1.4.803:=8192)' | Select-Object -First 1 -ExpandProperty objectsid
- if($DCSID) {
- $DCSID.Substring(0, $DCSID.LastIndexOf('-'))
- }
- else {
- Write-Verbose "Error extracting domain SID for $Domain"
- }
-}
-
-
-function Get-NetGroup {
-<#
- .SYNOPSIS
-
- Gets a list of all current groups in a domain, or all
- the groups a given user/group object belongs to.
-
- .PARAMETER GroupName
-
- The group name to query for, wildcards accepted.
-
- .PARAMETER SID
-
- The group SID to query for.
-
- .PARAMETER UserName
-
- The user name (or group name) to query for all effective
- groups of.
-
- .PARAMETER Filter
-
- A customized ldap filter string to use, e.g. "(description=*admin*)"
-
- .PARAMETER Domain
-
- The domain to query for groups, defaults to the current domain.
-
- .PARAMETER DomainController
-
- Domain controller to reflect LDAP queries through.
-
- .PARAMETER ADSpath
-
- The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
- Useful for OU queries.
-
- .PARAMETER AdminCount
-
- Switch. Return group with adminCount=1.
-
- .PARAMETER FullData
-
- Switch. Return full group objects instead of just object names (the default).
-
- .PARAMETER RawSids
-
- Switch. Return raw SIDs when using "Get-NetGroup -UserName X"
-
- .PARAMETER PageSize
-
- The PageSize to set for the LDAP searcher object.
-
- .PARAMETER Credential
-
- A [Management.Automation.PSCredential] object of alternate credentials
- for connection to the target domain.
-
- .EXAMPLE
-
- PS C:\> Get-NetGroup
-
- Returns the current groups in the domain.
-
- .EXAMPLE
-
- PS C:\> Get-NetGroup -GroupName *admin*
-
- Returns all groups with "admin" in their group name.
-
- .EXAMPLE
-
- PS C:\> Get-NetGroup -Domain testing -FullData
-
- Returns full group data objects in the 'testing' domain
-#>
-
- [CmdletBinding()]
- param(
- [Parameter(ValueFromPipeline=$True)]
- [String]
- $GroupName = '*',
-
- [String]
- $SID,
-
- [String]
- $UserName,
-
- [String]
- $Filter,
-
- [String]
- $Domain,
-
- [String]
- $DomainController,
-
- [String]
- $ADSpath,
-
- [Switch]
- $AdminCount,
-
- [Switch]
- $FullData,
-
- [Switch]
- $RawSids,
-
- [ValidateRange(1,10000)]
- [Int]
- $PageSize = 200,
-
- [Management.Automation.PSCredential]
- $Credential
- )
-
- begin {
- $GroupSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $ADSpath -PageSize $PageSize
- }
-
- process {
- if($GroupSearcher) {
-
- if($AdminCount) {
- Write-Verbose "Checking for adminCount=1"
- $Filter += "(admincount=1)"
- }
-
- if ($UserName) {
- # get the raw user object
- $User = Get-ADObject -SamAccountName $UserName -Domain $Domain -DomainController $DomainController -Credential $Credential -ReturnRaw -PageSize $PageSize | Select-Object -First 1
-
- if($User) {
- # convert the user to a directory entry
- $UserDirectoryEntry = $User.GetDirectoryEntry()
-
- # cause the cache to calculate the token groups for the user
- $UserDirectoryEntry.RefreshCache("tokenGroups")
-
- $UserDirectoryEntry.TokenGroups | ForEach-Object {
- # convert the token group sid
- $GroupSid = (New-Object System.Security.Principal.SecurityIdentifier($_,0)).Value
-
- # ignore the built in groups
- if($GroupSid -notmatch '^S-1-5-32-.*') {
- if($FullData) {
- $Group = Get-ADObject -SID $GroupSid -PageSize $PageSize -Domain $Domain -DomainController $DomainController -Credential $Credential
- $Group.PSObject.TypeNames.Add('PowerView.Group')
- $Group
- }
- else {
- if($RawSids) {
- $GroupSid
- }
- else {
- Convert-SidToName -SID $GroupSid
- }
- }
- }
- }
- }
- else {
- Write-Warning "UserName '$UserName' failed to resolve."
- }
- }
- else {
- if ($SID) {
- $GroupSearcher.filter = "(&(objectCategory=group)(objectSID=$SID)$Filter)"
- }
- else {
- $GroupSearcher.filter = "(&(objectCategory=group)(name=$GroupName)$Filter)"
- }
-
- $Results = $GroupSearcher.FindAll()
- $Results | Where-Object {$_} | ForEach-Object {
- # if we're returning full data objects
- if ($FullData) {
- # convert/process the LDAP fields for each result
- $Group = Convert-LDAPProperty -Properties $_.Properties
- $Group.PSObject.TypeNames.Add('PowerView.Group')
- $Group
- }
- else {
- # otherwise we're just returning the group name
- $_.properties.samaccountname
- }
- }
- $Results.dispose()
- $GroupSearcher.dispose()
- }
- }
- }
-}
-
-
-function Get-NetGroupMember {
-<#
- .SYNOPSIS
-
- This function users [ADSI] and LDAP to query the current AD context
- or trusted domain for users in a specified group. If no GroupName is
- specified, it defaults to querying the "Domain Admins" group.
- This is a replacement for "net group 'name' /domain"
-
- .PARAMETER GroupName
-
- The group name to query for users.
-
- .PARAMETER SID
-
- The Group SID to query for users. If not given, it defaults to 512 "Domain Admins"
-
- .PARAMETER Filter
-
- A customized ldap filter string to use, e.g. "(description=*admin*)"
-
- .PARAMETER Domain
-
- The domain to query for group users, defaults to the current domain.
-
- .PARAMETER DomainController
-
- Domain controller to reflect LDAP queries through.
-
- .PARAMETER ADSpath
-
- The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
- Useful for OU queries.
-
- .PARAMETER FullData
-
- Switch. Returns full data objects instead of just group/users.
-
- .PARAMETER Recurse
-
- Switch. If the group member is a group, recursively try to query its members as well.
-
- .PARAMETER UseMatchingRule
-
- Switch. Use LDAP_MATCHING_RULE_IN_CHAIN in the LDAP search query when -Recurse is specified.
- Much faster than manual recursion, but doesn't reveal cross-domain groups.
-
- .PARAMETER PageSize
-
- The PageSize to set for the LDAP searcher object.
-
- .PARAMETER Credential
-
- A [Management.Automation.PSCredential] object of alternate credentials
- for connection to the target domain.
-
- .EXAMPLE
-
- PS C:\> Get-NetGroupMember
-
- Returns the usernames that of members of the "Domain Admins" domain group.
-
- .EXAMPLE
-
- PS C:\> Get-NetGroupMember -Domain testing -GroupName "Power Users"
-
- Returns the usernames that of members of the "Power Users" group in the 'testing' domain.
-
- .LINK
-
- http://www.powershellmagazine.com/2013/05/23/pstip-retrieve-group-membership-of-an-active-directory-group-recursively/
-#>
-
- [CmdletBinding()]
- param(
- [Parameter(ValueFromPipeline=$True)]
- [String]
- $GroupName,
-
- [String]
- $SID,
-
- [String]
- $Domain,
-
- [String]
- $DomainController,
-
- [String]
- $ADSpath,
-
- [Switch]
- $FullData,
-
- [Switch]
- $Recurse,
-
- [Switch]
- $UseMatchingRule,
-
- [ValidateRange(1,10000)]
- [Int]
- $PageSize = 200,
-
- [Management.Automation.PSCredential]
- $Credential
- )
-
- begin {
- if($DomainController) {
- $TargetDomainController = $DomainController
- }
- else {
- $TargetDomainController = ((Get-NetDomain -Credential $Credential).PdcRoleOwner).Name
- }
-
- if($Domain) {
- $TargetDomain = $Domain
- }
- else {
- $TargetDomain = Get-NetDomain -Credential $Credential | Select-Object -ExpandProperty name
- }
-
- # so this isn't repeated if users are passed on the pipeline
- $GroupSearcher = Get-DomainSearcher -Domain $TargetDomain -DomainController $TargetDomainController -Credential $Credential -ADSpath $ADSpath -PageSize $PageSize
- }
-
- process {
- if ($GroupSearcher) {
- if ($Recurse -and $UseMatchingRule) {
- # resolve the group to a distinguishedname
- if ($GroupName) {
- $Group = Get-NetGroup -GroupName $GroupName -Domain $TargetDomain -DomainController $TargetDomainController -Credential $Credential -FullData -PageSize $PageSize
- }
- elseif ($SID) {
- $Group = Get-NetGroup -SID $SID -Domain $TargetDomain -DomainController $TargetDomainController -Credential $Credential -FullData -PageSize $PageSize
- }
- else {
- # default to domain admins
- $SID = (Get-DomainSID -Domain $TargetDomain -DomainController $TargetDomainController) + "-512"
- $Group = Get-NetGroup -SID $SID -Domain $TargetDomain -DomainController $TargetDomainController -Credential $Credential -FullData -PageSize $PageSize
- }
- $GroupDN = $Group.distinguishedname
- $GroupFoundName = $Group.name
-
- if ($GroupDN) {
- $GroupSearcher.filter = "(&(samAccountType=805306368)(memberof:1.2.840.113556.1.4.1941:=$GroupDN)$Filter)"
- $GroupSearcher.PropertiesToLoad.AddRange(('distinguishedName','samaccounttype','lastlogon','lastlogontimestamp','dscorepropagationdata','objectsid','whencreated','badpasswordtime','accountexpires','iscriticalsystemobject','name','usnchanged','objectcategory','description','codepage','instancetype','countrycode','distinguishedname','cn','admincount','logonhours','objectclass','logoncount','usncreated','useraccountcontrol','objectguid','primarygroupid','lastlogoff','samaccountname','badpwdcount','whenchanged','memberof','pwdlastset','adspath'))
-
- $Members = $GroupSearcher.FindAll()
- $GroupFoundName = $GroupName
- }
- else {
- Write-Error "Unable to find Group"
- }
- }
- else {
- if ($GroupName) {
- $GroupSearcher.filter = "(&(objectCategory=group)(name=$GroupName)$Filter)"
- }
- elseif ($SID) {
- $GroupSearcher.filter = "(&(objectCategory=group)(objectSID=$SID)$Filter)"
- }
- else {
- # default to domain admins
- $SID = (Get-DomainSID -Domain $TargetDomain -DomainController $TargetDomainController) + "-512"
- $GroupSearcher.filter = "(&(objectCategory=group)(objectSID=$SID)$Filter)"
- }
-
- $Members = @()
- $GroupSearcher.PropertiesToLoad.Clear()
- $Result = $False
- try {
- $Result = $GroupSearcher.FindOne()
- }
- catch {
- Write-Verbose "Error retrieving group searcher results: $_"
- }
-
- $GroupFoundName = ''
-
- if ($Result -and $Result.properties) {
- try {
- $Members = $Result.properties.item("member")
- }
- catch {
- Write-Verbose "Error retrieving members property."
- }
-
- if($Members.count -eq 0) {
-
- $Finished = $False
- $Bottom = 0
- $Top = 0
-
- while(!$Finished) {
- $Top = $Bottom + 1499
- $MemberRange="member;range=$Bottom-$Top"
- $Bottom += 1500
-
- $GroupSearcher.PropertiesToLoad.Clear()
- [void]$GroupSearcher.PropertiesToLoad.Add("$MemberRange")
- [void]$GroupSearcher.PropertiesToLoad.Add("name")
- try {
- $Result = $GroupSearcher.FindOne()
- $RangedProperty = $Result.Properties.PropertyNames -like "member;range=*"
- $Members += $Result.Properties.item($RangedProperty)
- $GroupFoundName = $Result.properties.item("name")[0]
-
- if ($Members.count -eq 0) {
- $Finished = $True
- }
- }
- catch [System.Management.Automation.MethodInvocationException] {
- $Finished = $True
- }
- }
- }
- else {
- $GroupFoundName = $Result.properties.item("name")[0]
- $Members += $Result.Properties.item($RangedProperty)
- }
- }
- $GroupSearcher.dispose()
- }
-
- $Members | Where-Object {$_} | ForEach-Object {
- # if we're doing the LDAP_MATCHING_RULE_IN_CHAIN recursion
- if ($Recurse -and $UseMatchingRule) {
- $Properties = $_.Properties
- }
- else {
- if($TargetDomainController) {
- $Result = [adsi]"LDAP://$TargetDomainController/$_"
- }
- else {
- $Result = [adsi]"LDAP://$_"
- }
- if($Result){
- $Properties = $Result.Properties
- }
- }
-
- if($Properties) {
-
- $IsGroup = @('268435456','268435457','536870912','536870913') -contains $Properties.samaccounttype
-
- if ($FullData) {
- $GroupMember = Convert-LDAPProperty -Properties $Properties
- }
- else {
- $GroupMember = New-Object PSObject
- }
-
- $GroupMember | Add-Member Noteproperty 'GroupDomain' $TargetDomain
- $GroupMember | Add-Member Noteproperty 'GroupName' $GroupFoundName
-
- if($Properties.objectSid) {
- $MemberSID = ((New-Object System.Security.Principal.SecurityIdentifier $Properties.objectSid[0],0).Value)
- }
- else {
- $MemberSID = $Null
- }
-
- try {
- $MemberDN = $Properties.distinguishedname[0]
-
- if (($MemberDN -match 'ForeignSecurityPrincipals') -and ($MemberDN -match 'S-1-5-21')) {
- try {
- if(-not $MemberSID) {
- $MemberSID = $Properties.cn[0]
- }
- $MemberSimpleName = Convert-SidToName -SID $MemberSID | Convert-ADName -InputType 'NT4' -OutputType 'Canonical'
- if($MemberSimpleName) {
- $MemberDomain = $MemberSimpleName.Split('/')[0]
- }
- else {
- Write-Warning "Error converting $MemberDN"
- $MemberDomain = $Null
- }
- }
- catch {
- Write-Warning "Error converting $MemberDN"
- $MemberDomain = $Null
- }
- }
- else {
- # extract the FQDN from the Distinguished Name
- $MemberDomain = $MemberDN.subString($MemberDN.IndexOf("DC=")) -replace 'DC=','' -replace ',','.'
- }
- }
- catch {
- $MemberDN = $Null
- $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 = Convert-SidToName $Properties.cn[0]
- }
- catch {
- # if there's a problem contacting the domain to resolve the SID
- $MemberName = $Properties.cn
- }
- }
-
- $GroupMember | Add-Member Noteproperty 'MemberDomain' $MemberDomain
- $GroupMember | Add-Member Noteproperty 'MemberName' $MemberName
- $GroupMember | Add-Member Noteproperty 'MemberSID' $MemberSID
- $GroupMember | Add-Member Noteproperty 'IsGroup' $IsGroup
- $GroupMember | Add-Member Noteproperty 'MemberDN' $MemberDN
- $GroupMember | Add-Member Noteproperty 'ObjectClass' $Properties.objectclass -Force
- $GroupMember | Add-Member Noteproperty 'DNSHostName' $Properties.dnshostname -Force
- $GroupMember.PSObject.TypeNames.Add('PowerView.GroupMember')
- $GroupMember
-
- # if we're doing manual recursion
- if ($Recurse -and !$UseMatchingRule -and $IsGroup -and $MemberName) {
- if($FullData) {
- Get-NetGroupMember -FullData -Domain $MemberDomain -DomainController $TargetDomainController -Credential $Credential -GroupName $MemberName -Recurse -PageSize $PageSize
- }
- else {
- Get-NetGroupMember -Domain $MemberDomain -DomainController $TargetDomainController -Credential $Credential -GroupName $MemberName -Recurse -PageSize $PageSize
- }
- }
- }
- }
- }
- }
-}
-
-
-function Get-NetFileServer {
-<#
- .SYNOPSIS
-
- Returns a list of all file servers extracted from user
- homedirectory, scriptpath, and profilepath fields.
-
- .PARAMETER Domain
-
- The domain to query for user file servers, defaults to the current domain.
-
- .PARAMETER DomainController
-
- Domain controller to reflect LDAP queries through.
-
- .PARAMETER TargetUsers
-
- An array of users to query for file servers.
-
- .PARAMETER PageSize
-
- The PageSize to set for the LDAP searcher object.
-
- .PARAMETER Credential
-
- A [Management.Automation.PSCredential] object of alternate credentials
- for connection to the target domain.
-
- .EXAMPLE
-
- PS C:\> Get-NetFileServer
-
- Returns active file servers.
-
- .EXAMPLE
-
- PS C:\> Get-NetFileServer -Domain testing
-
- Returns active file servers for the 'testing' domain.
-#>
-
- [CmdletBinding()]
- param(
- [String]
- $Domain,
-
- [String]
- $DomainController,
-
- [String[]]
- $TargetUsers,
-
- [ValidateRange(1,10000)]
- [Int]
- $PageSize = 200,
-
- [Management.Automation.PSCredential]
- $Credential
- )
-
- function SplitPath {
- # 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
- }
- }
- }
-
- Get-NetUser -Domain $Domain -DomainController $DomainController -Credential $Credential -PageSize $PageSize | Where-Object {$_} | Where-Object {
- # filter for any target users
- if($TargetUsers) {
- $TargetUsers -Match $_.samAccountName
- }
- else { $True }
- } | ForEach-Object {
- # split out every potential file server path
- if($_.homedirectory) {
- SplitPath($_.homedirectory)
- }
- if($_.scriptpath) {
- SplitPath($_.scriptpath)
- }
- if($_.profilepath) {
- SplitPath($_.profilepath)
- }
-
- } | Where-Object {$_} | Sort-Object -Unique
-}
-
-
-function Get-DFSshare {
-<#
- .SYNOPSIS
-
- Returns a list of all fault-tolerant distributed file
- systems for a given domain.
-
- .PARAMETER Version
-
- The version of DFS to query for servers.
- 1/v1, 2/v2, or all
-
- .PARAMETER Domain
-
- The domain to query for user DFS shares, defaults to the current domain.
-
- .PARAMETER DomainController
-
- Domain controller to reflect LDAP queries through.
-
- .PARAMETER ADSpath
-
- The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
- Useful for OU queries.
-
- .PARAMETER PageSize
-
- The PageSize to set for the LDAP searcher object.
-
- .PARAMETER Credential
-
- A [Management.Automation.PSCredential] object of alternate credentials
- for connection to the target domain.
-
- .EXAMPLE
-
- PS C:\> Get-DFSshare
-
- Returns all distributed file system shares for the current domain.
-
- .EXAMPLE
-
- PS C:\> Get-DFSshare -Domain test
-
- Returns all distributed file system shares for the 'test' domain.
-#>
-
- [CmdletBinding()]
- param(
- [String]
- [ValidateSet("All","V1","1","V2","2")]
- $Version = "All",
-
- [String]
- $Domain,
-
- [String]
- $DomainController,
-
- [String]
- $ADSpath,
-
- [ValidateRange(1,10000)]
- [Int]
- $PageSize = 200,
-
- [Management.Automation.PSCredential]
- $Credential
- )
-
- function Parse-Pkt {
- [CmdletBinding()]
- param(
- [byte[]]
- $Pkt
- )
-
- $bin = $Pkt
- $blob_version = [bitconverter]::ToUInt32($bin[0..3],0)
- $blob_element_count = [bitconverter]::ToUInt32($bin[4..7],0)
- $offset = 8
- #https://msdn.microsoft.com/en-us/library/cc227147.aspx
- $object_list = @()
- for($i=1; $i -le $blob_element_count; $i++){
- $blob_name_size_start = $offset
- $blob_name_size_end = $offset + 1
- $blob_name_size = [bitconverter]::ToUInt16($bin[$blob_name_size_start..$blob_name_size_end],0)
-
- $blob_name_start = $blob_name_size_end + 1
- $blob_name_end = $blob_name_start + $blob_name_size - 1
- $blob_name = [System.Text.Encoding]::Unicode.GetString($bin[$blob_name_start..$blob_name_end])
-
- $blob_data_size_start = $blob_name_end + 1
- $blob_data_size_end = $blob_data_size_start + 3
- $blob_data_size = [bitconverter]::ToUInt32($bin[$blob_data_size_start..$blob_data_size_end],0)
-
- $blob_data_start = $blob_data_size_end + 1
- $blob_data_end = $blob_data_start + $blob_data_size - 1
- $blob_data = $bin[$blob_data_start..$blob_data_end]
- switch -wildcard ($blob_name) {
- "\siteroot" { }
- "\domainroot*" {
- # Parse DFSNamespaceRootOrLinkBlob object. Starts with variable length DFSRootOrLinkIDBlob which we parse first...
- # DFSRootOrLinkIDBlob
- $root_or_link_guid_start = 0
- $root_or_link_guid_end = 15
- $root_or_link_guid = [byte[]]$blob_data[$root_or_link_guid_start..$root_or_link_guid_end]
- $guid = New-Object Guid(,$root_or_link_guid) # should match $guid_str
- $prefix_size_start = $root_or_link_guid_end + 1
- $prefix_size_end = $prefix_size_start + 1
- $prefix_size = [bitconverter]::ToUInt16($blob_data[$prefix_size_start..$prefix_size_end],0)
- $prefix_start = $prefix_size_end + 1
- $prefix_end = $prefix_start + $prefix_size - 1
- $prefix = [System.Text.Encoding]::Unicode.GetString($blob_data[$prefix_start..$prefix_end])
-
- $short_prefix_size_start = $prefix_end + 1
- $short_prefix_size_end = $short_prefix_size_start + 1
- $short_prefix_size = [bitconverter]::ToUInt16($blob_data[$short_prefix_size_start..$short_prefix_size_end],0)
- $short_prefix_start = $short_prefix_size_end + 1
- $short_prefix_end = $short_prefix_start + $short_prefix_size - 1
- $short_prefix = [System.Text.Encoding]::Unicode.GetString($blob_data[$short_prefix_start..$short_prefix_end])
-
- $type_start = $short_prefix_end + 1
- $type_end = $type_start + 3
- $type = [bitconverter]::ToUInt32($blob_data[$type_start..$type_end],0)
-
- $state_start = $type_end + 1
- $state_end = $state_start + 3
- $state = [bitconverter]::ToUInt32($blob_data[$state_start..$state_end],0)
-
- $comment_size_start = $state_end + 1
- $comment_size_end = $comment_size_start + 1
- $comment_size = [bitconverter]::ToUInt16($blob_data[$comment_size_start..$comment_size_end],0)
- $comment_start = $comment_size_end + 1
- $comment_end = $comment_start + $comment_size - 1
- if ($comment_size -gt 0) {
- $comment = [System.Text.Encoding]::Unicode.GetString($blob_data[$comment_start..$comment_end])
- }
- $prefix_timestamp_start = $comment_end + 1
- $prefix_timestamp_end = $prefix_timestamp_start + 7
- # https://msdn.microsoft.com/en-us/library/cc230324.aspx FILETIME
- $prefix_timestamp = $blob_data[$prefix_timestamp_start..$prefix_timestamp_end] #dword lowDateTime #dword highdatetime
- $state_timestamp_start = $prefix_timestamp_end + 1
- $state_timestamp_end = $state_timestamp_start + 7
- $state_timestamp = $blob_data[$state_timestamp_start..$state_timestamp_end]
- $comment_timestamp_start = $state_timestamp_end + 1
- $comment_timestamp_end = $comment_timestamp_start + 7
- $comment_timestamp = $blob_data[$comment_timestamp_start..$comment_timestamp_end]
- $version_start = $comment_timestamp_end + 1
- $version_end = $version_start + 3
- $version = [bitconverter]::ToUInt32($blob_data[$version_start..$version_end],0)
-
- # Parse rest of DFSNamespaceRootOrLinkBlob here
- $dfs_targetlist_blob_size_start = $version_end + 1
- $dfs_targetlist_blob_size_end = $dfs_targetlist_blob_size_start + 3
- $dfs_targetlist_blob_size = [bitconverter]::ToUInt32($blob_data[$dfs_targetlist_blob_size_start..$dfs_targetlist_blob_size_end],0)
-
- $dfs_targetlist_blob_start = $dfs_targetlist_blob_size_end + 1
- $dfs_targetlist_blob_end = $dfs_targetlist_blob_start + $dfs_targetlist_blob_size - 1
- $dfs_targetlist_blob = $blob_data[$dfs_targetlist_blob_start..$dfs_targetlist_blob_end]
- $reserved_blob_size_start = $dfs_targetlist_blob_end + 1
- $reserved_blob_size_end = $reserved_blob_size_start + 3
- $reserved_blob_size = [bitconverter]::ToUInt32($blob_data[$reserved_blob_size_start..$reserved_blob_size_end],0)
-
- $reserved_blob_start = $reserved_blob_size_end + 1
- $reserved_blob_end = $reserved_blob_start + $reserved_blob_size - 1
- $reserved_blob = $blob_data[$reserved_blob_start..$reserved_blob_end]
- $referral_ttl_start = $reserved_blob_end + 1
- $referral_ttl_end = $referral_ttl_start + 3
- $referral_ttl = [bitconverter]::ToUInt32($blob_data[$referral_ttl_start..$referral_ttl_end],0)
-
- #Parse DFSTargetListBlob
- $target_count_start = 0
- $target_count_end = $target_count_start + 3
- $target_count = [bitconverter]::ToUInt32($dfs_targetlist_blob[$target_count_start..$target_count_end],0)
- $t_offset = $target_count_end + 1
-
- for($j=1; $j -le $target_count; $j++){
- $target_entry_size_start = $t_offset
- $target_entry_size_end = $target_entry_size_start + 3
- $target_entry_size = [bitconverter]::ToUInt32($dfs_targetlist_blob[$target_entry_size_start..$target_entry_size_end],0)
- $target_time_stamp_start = $target_entry_size_end + 1
- $target_time_stamp_end = $target_time_stamp_start + 7
- # FILETIME again or special if priority rank and priority class 0
- $target_time_stamp = $dfs_targetlist_blob[$target_time_stamp_start..$target_time_stamp_end]
- $target_state_start = $target_time_stamp_end + 1
- $target_state_end = $target_state_start + 3
- $target_state = [bitconverter]::ToUInt32($dfs_targetlist_blob[$target_state_start..$target_state_end],0)
-
- $target_type_start = $target_state_end + 1
- $target_type_end = $target_type_start + 3
- $target_type = [bitconverter]::ToUInt32($dfs_targetlist_blob[$target_type_start..$target_type_end],0)
-
- $server_name_size_start = $target_type_end + 1
- $server_name_size_end = $server_name_size_start + 1
- $server_name_size = [bitconverter]::ToUInt16($dfs_targetlist_blob[$server_name_size_start..$server_name_size_end],0)
-
- $server_name_start = $server_name_size_end + 1
- $server_name_end = $server_name_start + $server_name_size - 1
- $server_name = [System.Text.Encoding]::Unicode.GetString($dfs_targetlist_blob[$server_name_start..$server_name_end])
-
- $share_name_size_start = $server_name_end + 1
- $share_name_size_end = $share_name_size_start + 1
- $share_name_size = [bitconverter]::ToUInt16($dfs_targetlist_blob[$share_name_size_start..$share_name_size_end],0)
- $share_name_start = $share_name_size_end + 1
- $share_name_end = $share_name_start + $share_name_size - 1
- $share_name = [System.Text.Encoding]::Unicode.GetString($dfs_targetlist_blob[$share_name_start..$share_name_end])
-
- $target_list += "\\$server_name\$share_name"
- $t_offset = $share_name_end + 1
- }
- }
- }
- $offset = $blob_data_end + 1
- $dfs_pkt_properties = @{
- 'Name' = $blob_name
- 'Prefix' = $prefix
- 'TargetList' = $target_list
- }
- $object_list += New-Object -TypeName PSObject -Property $dfs_pkt_properties
- $prefix = $null
- $blob_name = $null
- $target_list = $null
- }
-
- $servers = @()
- $object_list | ForEach-Object {
- if ($_.TargetList) {
- $_.TargetList | ForEach-Object {
- $servers += $_.split("\")[2]
- }
- }
- }
-
- $servers
- }
-
- function Get-DFSshareV1 {
- [CmdletBinding()]
- param(
- [String]
- $Domain,
-
- [String]
- $DomainController,
-
- [String]
- $ADSpath,
-
- [ValidateRange(1,10000)]
- [Int]
- $PageSize = 200,
-
- [Management.Automation.PSCredential]
- $Credential
- )
-
- $DFSsearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $ADSpath -PageSize $PageSize
-
- if($DFSsearcher) {
- $DFSshares = @()
- $DFSsearcher.filter = "(&(objectClass=fTDfs))"
-
- try {
- $Results = $DFSSearcher.FindAll()
- $Results | Where-Object {$_} | ForEach-Object {
- $Properties = $_.Properties
- $RemoteNames = $Properties.remoteservername
- $Pkt = $Properties.pkt
-
- $DFSshares += $RemoteNames | ForEach-Object {
- try {
- if ( $_.Contains('\') ) {
- New-Object -TypeName PSObject -Property @{'Name'=$Properties.name[0];'RemoteServerName'=$_.split("\")[2]}
- }
- }
- catch {
- Write-Verbose "Error in parsing DFS share : $_"
- }
- }
- }
- $Results.dispose()
- $DFSSearcher.dispose()
-
- if($pkt -and $pkt[0]) {
- Parse-Pkt $pkt[0] | ForEach-Object {
- # If a folder doesn't have a redirection it will
- # have a target like
- # \\null\TestNameSpace\folder\.DFSFolderLink so we
- # do actually want to match on "null" rather than
- # $null
- if ($_ -ne "null") {
- New-Object -TypeName PSObject -Property @{'Name'=$Properties.name[0];'RemoteServerName'=$_}
- }
- }
- }
- }
- catch {
- Write-Warning "Get-DFSshareV1 error : $_"
- }
- $DFSshares | Sort-Object -Property "RemoteServerName"
- }
- }
-
- function Get-DFSshareV2 {
- [CmdletBinding()]
- param(
- [String]
- $Domain,
-
- [String]
- $DomainController,
-
- [String]
- $ADSpath,
-
- [ValidateRange(1,10000)]
- [Int]
- $PageSize = 200,
-
- [Management.Automation.PSCredential]
- $Credential
- )
-
- $DFSsearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $ADSpath -PageSize $PageSize
-
- if($DFSsearcher) {
- $DFSshares = @()
- $DFSsearcher.filter = "(&(objectClass=msDFS-Linkv2))"
- $DFSSearcher.PropertiesToLoad.AddRange(('msdfs-linkpathv2','msDFS-TargetListv2'))
-
- try {
- $Results = $DFSSearcher.FindAll()
- $Results | Where-Object {$_} | ForEach-Object {
- $Properties = $_.Properties
- $target_list = $Properties.'msdfs-targetlistv2'[0]
- $xml = [xml][System.Text.Encoding]::Unicode.GetString($target_list[2..($target_list.Length-1)])
- $DFSshares += $xml.targets.ChildNodes | ForEach-Object {
- try {
- $Target = $_.InnerText
- if ( $Target.Contains('\') ) {
- $DFSroot = $Target.split("\")[3]
- $ShareName = $Properties.'msdfs-linkpathv2'[0]
- New-Object -TypeName PSObject -Property @{'Name'="$DFSroot$ShareName";'RemoteServerName'=$Target.split("\")[2]}
- }
- }
- catch {
- Write-Verbose "Error in parsing target : $_"
- }
- }
- }
- $Results.dispose()
- $DFSSearcher.dispose()
- }
- catch {
- Write-Warning "Get-DFSshareV2 error : $_"
- }
- $DFSshares | Sort-Object -Unique -Property "RemoteServerName"
- }
- }
-
- $DFSshares = @()
-
- if ( ($Version -eq "all") -or ($Version.endsWith("1")) ) {
- $DFSshares += Get-DFSshareV1 -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $ADSpath -PageSize $PageSize
- }
- if ( ($Version -eq "all") -or ($Version.endsWith("2")) ) {
- $DFSshares += Get-DFSshareV2 -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $ADSpath -PageSize $PageSize
- }
-
- $DFSshares | Sort-Object -Property ("RemoteServerName","Name") -Unique
-}
-
-
-########################################################
-#
-# GPO related functions.
-#
-########################################################
-
-function Get-GptTmpl {
-<#
- .SYNOPSIS
-
- Helper to parse a GptTmpl.inf policy file path into a custom object.
-
- .PARAMETER GptTmplPath
-
- The GptTmpl.inf file path name to parse.
-
- .PARAMETER UsePSDrive
-
- Switch. Mount the target GptTmpl folder path as a temporary PSDrive.
-
- .EXAMPLE
-
- PS C:\> Get-GptTmpl -GptTmplPath "\\dev.testlab.local\sysvol\dev.testlab.local\Policies\{31B2F340-016D-11D2-945F-00C04FB984F9}\MACHINE\Microsoft\Windows NT\SecEdit\GptTmpl.inf"
-
- Parse the default domain policy .inf for dev.testlab.local
-#>
-
- [CmdletBinding()]
- Param (
- [Parameter(Mandatory=$True, ValueFromPipeline=$True)]
- [String]
- $GptTmplPath,
-
- [Switch]
- $UsePSDrive
- )
-
- begin {
- if($UsePSDrive) {
- # if we're PSDrives, create a temporary mount point
- $Parts = $GptTmplPath.split('\')
- $FolderPath = $Parts[0..($Parts.length-2)] -join '\'
- $FilePath = $Parts[-1]
- $RandDrive = ("abcdefghijklmnopqrstuvwxyz".ToCharArray() | Get-Random -Count 7) -join ''
-
- Write-Verbose "Mounting path $GptTmplPath using a temp PSDrive at $RandDrive"
-
- try {
- $Null = New-PSDrive -Name $RandDrive -PSProvider FileSystem -Root $FolderPath -ErrorAction Stop
- }
- catch {
- Write-Verbose "Error mounting path $GptTmplPath : $_"
- return $Null
- }
-
- # so we can cd/dir the new drive
- $TargetGptTmplPath = $RandDrive + ":\" + $FilePath
- }
- else {
- $TargetGptTmplPath = $GptTmplPath
- }
- Write-Verbose "GptTmplPath: $GptTmplPath"
- }
-
- process {
- try {
- Write-Verbose "Parsing $TargetGptTmplPath"
- $TargetGptTmplPath | Get-IniContent -ErrorAction SilentlyContinue
- }
- catch {
- Write-Verbose "Error parsing $TargetGptTmplPath : $_"
- }
- }
-
- end {
- if($UsePSDrive -and $RandDrive) {
- Write-Verbose "Removing temp PSDrive $RandDrive"
- Get-PSDrive -Name $RandDrive -ErrorAction SilentlyContinue | Remove-PSDrive -Force
- }
- }
-}
-
-
-function Get-GroupsXML {
-<#
- .SYNOPSIS
-
- Helper to parse a groups.xml file path into a custom object.
-
- .PARAMETER GroupsXMLpath
-
- The groups.xml file path name to parse.
-
- .PARAMETER UsePSDrive
-
- Switch. Mount the target groups.xml folder path as a temporary PSDrive.
-#>
-
- [CmdletBinding()]
- Param (
- [Parameter(Mandatory=$True, ValueFromPipeline=$True)]
- [String]
- $GroupsXMLPath,
-
- [Switch]
- $UsePSDrive
- )
-
- begin {
- if($UsePSDrive) {
- # if we're PSDrives, create a temporary mount point
- $Parts = $GroupsXMLPath.split('\')
- $FolderPath = $Parts[0..($Parts.length-2)] -join '\'
- $FilePath = $Parts[-1]
- $RandDrive = ("abcdefghijklmnopqrstuvwxyz".ToCharArray() | Get-Random -Count 7) -join ''
-
- Write-Verbose "Mounting path $GroupsXMLPath using a temp PSDrive at $RandDrive"
-
- try {
- $Null = New-PSDrive -Name $RandDrive -PSProvider FileSystem -Root $FolderPath -ErrorAction Stop
- }
- catch {
- Write-Verbose "Error mounting path $GroupsXMLPath : $_"
- return $Null
- }
-
- # so we can cd/dir the new drive
- $TargetGroupsXMLPath = $RandDrive + ":\" + $FilePath
- }
- else {
- $TargetGroupsXMLPath = $GroupsXMLPath
- }
- }
-
- process {
-
- try {
- [XML]$GroupsXMLcontent = Get-Content $TargetGroupsXMLPath -ErrorAction Stop
-
- # process all group properties in the XML
- $GroupsXMLcontent | Select-Xml "//Groups" | Select-Object -ExpandProperty node | ForEach-Object {
-
- $Groupname = $_.Group.Properties.groupName
-
- # extract the localgroup sid for memberof
- $GroupSID = $_.Group.Properties.GroupSid
- if(-not $LocalSid) {
- if($Groupname -match 'Administrators') {
- $GroupSID = 'S-1-5-32-544'
- }
- elseif($Groupname -match 'Remote Desktop') {
- $GroupSID = 'S-1-5-32-555'
- }
- elseif($Groupname -match 'Guests') {
- $GroupSID = 'S-1-5-32-546'
- }
- else {
- $GroupSID = Convert-NameToSid -ObjectName $Groupname | Select-Object -ExpandProperty SID
- }
- }
-
- # extract out members added to this group
- $Members = $_.Group.Properties.members | Select-Object -ExpandProperty Member | Where-Object { $_.action -match 'ADD' } | ForEach-Object {
- if($_.sid) { $_.sid }
- else { $_.name }
- }
-
- if ($Members) {
-
- # extract out any/all filters...I hate you GPP
- if($_.Group.filters) {
- $Filters = $_.Group.filters.GetEnumerator() | ForEach-Object {
- New-Object -TypeName PSObject -Property @{'Type' = $_.LocalName;'Value' = $_.name}
- }
- }
- else {
- $Filters = $Null
- }
-
- if($Members -isnot [System.Array]) { $Members = @($Members) }
-
- $GPOGroup = New-Object PSObject
- $GPOGroup | Add-Member Noteproperty 'GPOPath' $TargetGroupsXMLPath
- $GPOGroup | Add-Member Noteproperty 'Filters' $Filters
- $GPOGroup | Add-Member Noteproperty 'GroupName' $GroupName
- $GPOGroup | Add-Member Noteproperty 'GroupSID' $GroupSID
- $GPOGroup | Add-Member Noteproperty 'GroupMemberOf' $Null
- $GPOGroup | Add-Member Noteproperty 'GroupMembers' $Members
- $GPOGroup
- }
- }
- }
- catch {
- Write-Verbose "Error parsing $TargetGroupsXMLPath : $_"
- }
- }
-
- end {
- if($UsePSDrive -and $RandDrive) {
- Write-Verbose "Removing temp PSDrive $RandDrive"
- Get-PSDrive -Name $RandDrive -ErrorAction SilentlyContinue | Remove-PSDrive -Force
- }
- }
-}
-
-
-function Get-NetGPO {
-<#
- .SYNOPSIS
-
- Gets a list of all current GPOs in a domain.
-
- .PARAMETER GPOname
-
- The GPO name to query for, wildcards accepted.
-
- .PARAMETER DisplayName
-
- The GPO display name to query for, wildcards accepted.
-
- .PARAMETER ComputerName
-
- Return all GPO objects applied to a given computer (FQDN).
-
- .PARAMETER Domain
-
- The domain to query for GPOs, defaults to the current domain.
-
- .PARAMETER DomainController
-
- Domain controller to reflect LDAP queries through.
-
- .PARAMETER ADSpath
-
- The LDAP source to search through
- e.g. "LDAP://cn={8FF59D28-15D7-422A-BCB7-2AE45724125A},cn=policies,cn=system,DC=dev,DC=testlab,DC=local"
-
- .PARAMETER PageSize
-
- The PageSize to set for the LDAP searcher object.
-
- .PARAMETER Credential
-
- A [Management.Automation.PSCredential] object of alternate credentials
- for connection to the target domain.
-
- .EXAMPLE
-
- PS C:\> Get-NetGPO -Domain testlab.local
-
- Returns the GPOs in the 'testlab.local' domain.
-#>
- [CmdletBinding()]
- Param (
- [Parameter(ValueFromPipeline=$True)]
- [String]
- $GPOname = '*',
-
- [String]
- $DisplayName,
-
- [String]
- $ComputerName,
-
- [String]
- $Domain,
-
- [String]
- $DomainController,
-
- [String]
- $ADSpath,
-
- [ValidateRange(1,10000)]
- [Int]
- $PageSize = 200,
-
- [Management.Automation.PSCredential]
- $Credential
- )
-
- begin {
- $GPOSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $ADSpath -PageSize $PageSize
- }
-
- process {
- if ($GPOSearcher) {
-
- if($ComputerName) {
- $GPONames = @()
- $Computers = Get-NetComputer -ComputerName $ComputerName -Domain $Domain -DomainController $DomainController -FullData -PageSize $PageSize
-
- if(!$Computers) {
- throw "Computer $ComputerName in domain '$Domain' not found! Try a fully qualified host name"
- }
-
- # get the given computer's OU
- $ComputerOUs = @()
- ForEach($Computer in $Computers) {
- # extract all OUs a computer is a part of
- $DN = $Computer.distinguishedname
-
- $ComputerOUs += $DN.split(",") | ForEach-Object {
- if($_.startswith("OU=")) {
- $DN.substring($DN.indexof($_))
- }
- }
- }
-
- Write-Verbose "ComputerOUs: $ComputerOUs"
-
- # find all the GPOs linked to the computer's OU
- ForEach($ComputerOU in $ComputerOUs) {
- $GPONames += Get-NetOU -Domain $Domain -DomainController $DomainController -ADSpath $ComputerOU -FullData -PageSize $PageSize | ForEach-Object {
- # get any GPO links
- write-verbose "blah: $($_.name)"
- $_.gplink.split("][") | ForEach-Object {
- if ($_.startswith("LDAP")) {
- $_.split(";")[0]
- }
- }
- }
- }
-
- Write-Verbose "GPONames: $GPONames"
-
- # find any GPOs linked to the site for the given computer
- $ComputerSite = (Get-SiteName -ComputerName $ComputerName).SiteName
- if($ComputerSite -and ($ComputerSite -notlike 'Error*')) {
- $GPONames += Get-NetSite -SiteName $ComputerSite -FullData | ForEach-Object {
- if($_.gplink) {
- $_.gplink.split("][") | ForEach-Object {
- if ($_.startswith("LDAP")) {
- $_.split(";")[0]
- }
- }
- }
- }
- }
-
- $GPONames | Where-Object{$_ -and ($_ -ne '')} | ForEach-Object {
-
- # use the gplink as an ADS path to enumerate all GPOs for the computer
- $GPOSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $_ -PageSize $PageSize
- $GPOSearcher.filter="(&(objectCategory=groupPolicyContainer)(name=$GPOname))"
-
- try {
- $Results = $GPOSearcher.FindAll()
- $Results | Where-Object {$_} | ForEach-Object {
- $Out = Convert-LDAPProperty -Properties $_.Properties
- $Out | Add-Member Noteproperty 'ComputerName' $ComputerName
- $Out
- }
- $Results.dispose()
- $GPOSearcher.dispose()
- }
- catch {
- Write-Warning $_
- }
- }
- }
-
- else {
- if($DisplayName) {
- $GPOSearcher.filter="(&(objectCategory=groupPolicyContainer)(displayname=$DisplayName))"
- }
- else {
- $GPOSearcher.filter="(&(objectCategory=groupPolicyContainer)(name=$GPOname))"
- }
-
- try {
- $Results = $GPOSearcher.FindAll()
- $Results | Where-Object {$_} | ForEach-Object {
- if($ADSPath -and ($ADSpath -Match '^GC://')) {
- $Properties = Convert-LDAPProperty -Properties $_.Properties
- try {
- $GPODN = $Properties.distinguishedname
- $GPODomain = $GPODN.subString($GPODN.IndexOf("DC=")) -replace 'DC=','' -replace ',','.'
- $gpcfilesyspath = "\\$GPODomain\SysVol\$GPODomain\Policies\$($Properties.cn)"
- $Properties | Add-Member Noteproperty 'gpcfilesyspath' $gpcfilesyspath
- $Properties
- }
- catch {
- $Properties
- }
- }
- else {
- # convert/process the LDAP fields for each result
- Convert-LDAPProperty -Properties $_.Properties
- }
- }
- $Results.dispose()
- $GPOSearcher.dispose()
- }
- catch {
- Write-Warning $_
- }
- }
- }
- }
-}
-
-
-function New-GPOImmediateTask {
-<#
- .SYNOPSIS
-
- Builds an 'Immediate' schtask to push out through a specified GPO.
-
- .PARAMETER TaskName
-
- Name for the schtask to recreate. Required.
-
- .PARAMETER Command
-
- The command to execute with the task, defaults to 'powershell'
-
- .PARAMETER CommandArguments
-
- The arguments to supply to the -Command being launched.
-
- .PARAMETER TaskDescription
-
- An optional description for the task.
-
- .PARAMETER TaskAuthor
-
- The displayed author of the task, defaults to ''NT AUTHORITY\System'
-
- .PARAMETER TaskModifiedDate
-
- The displayed modified date for the task, defaults to 30 days ago.
-
- .PARAMETER GPOname
-
- The GPO name to build the task for.
-
- .PARAMETER GPODisplayName
-
- The GPO display name to build the task for.
-
- .PARAMETER Domain
-
- The domain to query for the GPOs, defaults to the current domain.
-
- .PARAMETER DomainController
-
- Domain controller to reflect LDAP queries through.
-
- .PARAMETER ADSpath
-
- The LDAP source to search through
- e.g. "LDAP://cn={8FF59D28-15D7-422A-BCB7-2AE45724125A},cn=policies,cn=system,DC=dev,DC=testlab,DC=local"
-
- .PARAMETER Credential
-
- A [Management.Automation.PSCredential] object of alternate credentials
- for connection to the target.
-
- .EXAMPLE
-
- PS> New-GPOImmediateTask -TaskName Debugging -GPODisplayName SecurePolicy -CommandArguments '-c "123 | Out-File C:\Temp\debug.txt"' -Force
-
- Create an immediate schtask that executes the specified PowerShell arguments and
- push it out to the 'SecurePolicy' GPO, skipping the confirmation prompt.
-
- .EXAMPLE
-
- PS> New-GPOImmediateTask -GPODisplayName SecurePolicy -Remove -Force
-
- Remove all schtasks from the 'SecurePolicy' GPO, skipping the confirmation prompt.
-#>
- [CmdletBinding(DefaultParameterSetName = 'Create')]
- Param (
- [Parameter(ParameterSetName = 'Create', Mandatory = $True)]
- [String]
- [ValidateNotNullOrEmpty()]
- $TaskName,
-
- [Parameter(ParameterSetName = 'Create')]
- [String]
- [ValidateNotNullOrEmpty()]
- $Command = 'powershell',
-
- [Parameter(ParameterSetName = 'Create')]
- [String]
- [ValidateNotNullOrEmpty()]
- $CommandArguments,
-
- [Parameter(ParameterSetName = 'Create')]
- [String]
- [ValidateNotNullOrEmpty()]
- $TaskDescription = '',
-
- [Parameter(ParameterSetName = 'Create')]
- [String]
- [ValidateNotNullOrEmpty()]
- $TaskAuthor = 'NT AUTHORITY\System',
-
- [Parameter(ParameterSetName = 'Create')]
- [String]
- [ValidateNotNullOrEmpty()]
- $TaskModifiedDate = (Get-Date (Get-Date).AddDays(-30) -Format u).trim("Z"),
-
- [Parameter(ParameterSetName = 'Create')]
- [Parameter(ParameterSetName = 'Remove')]
- [String]
- $GPOname,
-
- [Parameter(ParameterSetName = 'Create')]
- [Parameter(ParameterSetName = 'Remove')]
- [String]
- $GPODisplayName,
-
- [Parameter(ParameterSetName = 'Create')]
- [Parameter(ParameterSetName = 'Remove')]
- [String]
- $Domain,
-
- [Parameter(ParameterSetName = 'Create')]
- [Parameter(ParameterSetName = 'Remove')]
- [String]
- $DomainController,
-
- [Parameter(ParameterSetName = 'Create')]
- [Parameter(ParameterSetName = 'Remove')]
- [String]
- $ADSpath,
-
- [Parameter(ParameterSetName = 'Create')]
- [Parameter(ParameterSetName = 'Remove')]
- [Switch]
- $Force,
-
- [Parameter(ParameterSetName = 'Remove')]
- [Switch]
- $Remove,
-
- [Parameter(ParameterSetName = 'Create')]
- [Parameter(ParameterSetName = 'Remove')]
- [Management.Automation.PSCredential]
- $Credential
- )
-
- # build the XML spec for our 'immediate' scheduled task
- $TaskXML = ''+$TaskAuthor+''+$TaskDescription+'NT AUTHORITY\SystemHighestAvailableS4UPT10MPT1HtruefalseIgnoreNewfalsetruefalsetruefalsetruetruePT0S7PT0SPT15M3'+$Command+''+$CommandArguments+'%LocalTimeXmlEx%%LocalTimeXmlEx%true'
-
- if (!$PSBoundParameters['GPOname'] -and !$PSBoundParameters['GPODisplayName']) {
- Write-Warning 'Either -GPOName or -GPODisplayName must be specified'
- return
- }
-
- # eunmerate the specified GPO(s)
- $GPOs = Get-NetGPO -GPOname $GPOname -DisplayName $GPODisplayName -Domain $Domain -DomainController $DomainController -ADSpath $ADSpath -Credential $Credential
-
- if(!$GPOs) {
- Write-Warning 'No GPO found.'
- return
- }
-
- $GPOs | ForEach-Object {
- $ProcessedGPOName = $_.Name
- try {
- Write-Verbose "Trying to weaponize GPO: $ProcessedGPOName"
-
- # map a network drive as New-PSDrive/New-Item/etc. don't accept -Credential properly :(
- if($Credential) {
- Write-Verbose "Mapping '$($_.gpcfilesyspath)' to network drive N:\"
- $Path = $_.gpcfilesyspath.TrimEnd('\')
- $Net = New-Object -ComObject WScript.Network
- $Net.MapNetworkDrive("N:", $Path, $False, $Credential.UserName, $Credential.GetNetworkCredential().Password)
- $TaskPath = "N:\Machine\Preferences\ScheduledTasks\"
- }
- else {
- $TaskPath = $_.gpcfilesyspath + "\Machine\Preferences\ScheduledTasks\"
- }
-
- if($Remove) {
- if(!(Test-Path "$TaskPath\ScheduledTasks.xml")) {
- Throw "Scheduled task doesn't exist at $TaskPath\ScheduledTasks.xml"
- }
-
- if (!$Force -and !$psCmdlet.ShouldContinue('Do you want to continue?',"Removing schtask at $TaskPath\ScheduledTasks.xml")) {
- return
- }
-
- Remove-Item -Path "$TaskPath\ScheduledTasks.xml" -Force
- }
- else {
- if (!$Force -and !$psCmdlet.ShouldContinue('Do you want to continue?',"Creating schtask at $TaskPath\ScheduledTasks.xml")) {
- return
- }
-
- # create the folder if it doesn't exist
- $Null = New-Item -ItemType Directory -Force -Path $TaskPath
-
- if(Test-Path "$TaskPath\ScheduledTasks.xml") {
- Throw "Scheduled task already exists at $TaskPath\ScheduledTasks.xml !"
- }
-
- $TaskXML | Set-Content -Encoding ASCII -Path "$TaskPath\ScheduledTasks.xml"
- }
-
- if($Credential) {
- Write-Verbose "Removing mounted drive at N:\"
- $Net = New-Object -ComObject WScript.Network
- $Net.RemoveNetworkDrive("N:")
- }
- }
- catch {
- Write-Warning "Error for GPO $ProcessedGPOName : $_"
- if($Credential) {
- Write-Verbose "Removing mounted drive at N:\"
- $Net = New-Object -ComObject WScript.Network
- $Net.RemoveNetworkDrive("N:")
- }
- }
- }
-}
-
-
-function Get-NetGPOGroup {
-<#
- .SYNOPSIS
-
- Returns all GPOs in a domain that set "Restricted Groups" or use groups.xml on on target machines.
-
- Author: @harmj0y
- License: BSD 3-Clause
- Required Dependencies: Get-NetGPO, Get-GptTmpl, Get-GroupsXML, Convert-NameToSid, Convert-SidToName
- Optional Dependencies: None
-
- .DESCRIPTION
-
- First enumerates all GPOs in the current/target domain using Get-NetGPO with passed
- arguments, and for each GPO checks if 'Restricted Groups' are set with GptTmpl.inf or
- group membership is set through Group Policy Preferences groups.xml files. For any
- GptTmpl.inf files found, the file is parsed with Get-GptTmpl and any 'Group Membership'
- section data is processed if present. Any found Groups.xml files are parsed with
- Get-GroupsXML and those memberships are returned as well.
-
- .PARAMETER GPOname
-
- The GPO name to query for, wildcards accepted.
-
- .PARAMETER DisplayName
-
- The GPO display name to query for, wildcards accepted.
-
- .PARAMETER Domain
-
- The domain to query for GPOs, defaults to the current domain.
-
- .PARAMETER DomainController
-
- Domain controller to reflect LDAP queries through.
-
- .PARAMETER ADSpath
-
- The LDAP source to search through for GPOs.
- e.g. "LDAP://cn={8FF59D28-15D7-422A-BCB7-2AE45724125A},cn=policies,cn=system,DC=dev,DC=testlab,DC=local"
-
- .PARAMETER ResolveMemberSIDs
-
- Switch. Try to resolve the SIDs of all found group members.
-
- .PARAMETER UsePSDrive
-
- Switch. Mount any found policy files with temporary PSDrives.
-
- .PARAMETER PageSize
-
- The PageSize to set for the LDAP searcher object.
-
- .EXAMPLE
-
- PS C:\> Get-NetGPOGroup
-
- Returns all local groups set by GPO along with their members and memberof.
-
- .LINK
-
- https://morgansimonsenblog.azurewebsites.net/tag/groups/
-#>
-
- [CmdletBinding()]
- Param (
- [String]
- $GPOname = '*',
-
- [String]
- $DisplayName,
-
- [String]
- $Domain,
-
- [String]
- $DomainController,
-
- [String]
- $ADSpath,
-
- [Switch]
- $ResolveMemberSIDs,
-
- [Switch]
- $UsePSDrive,
-
- [ValidateRange(1,10000)]
- [Int]
- $PageSize = 200
- )
-
- $Option = [System.StringSplitOptions]::RemoveEmptyEntries
-
- # get every GPO from the specified domain with restricted groups set
- Get-NetGPO -GPOName $GPOname -DisplayName $GPOname -Domain $Domain -DomainController $DomainController -ADSpath $ADSpath -PageSize $PageSize | ForEach-Object {
-
- $GPOdisplayName = $_.displayname
- $GPOname = $_.name
- $GPOPath = $_.gpcfilesyspath
-
- $ParseArgs = @{
- 'GptTmplPath' = "$GPOPath\MACHINE\Microsoft\Windows NT\SecEdit\GptTmpl.inf"
- 'UsePSDrive' = $UsePSDrive
- }
-
- # parse the GptTmpl.inf 'Restricted Groups' file if it exists
- $Inf = Get-GptTmpl @ParseArgs
-
- if($Inf -and ($Inf.psbase.Keys -contains 'Group Membership')) {
-
- $Memberships = @{}
-
- # group the members/memberof fields for each entry
- ForEach ($Membership in $Inf.'Group Membership'.GetEnumerator()) {
- $Group, $Relation = $Membership.Key.Split('__', $Option) | ForEach-Object {$_.Trim()}
-
- # extract out ALL members
- $MembershipValue = $Membership.Value | Where-Object {$_} | ForEach-Object { $_.Trim('*') } | Where-Object {$_}
-
- if($ResolveMemberSIDs) {
- # if the resulting member is username and not a SID, attempt to resolve it
- $GroupMembers = @()
- ForEach($Member in $MembershipValue) {
- if($Member -and ($Member.Trim() -ne '')) {
- if($Member -notmatch '^S-1-.*') {
- $MemberSID = Convert-NameToSid -Domain $Domain -ObjectName $Member | Select-Object -ExpandProperty SID
- if($MemberSID) {
- $GroupMembers += $MemberSID
- }
- else {
- $GroupMembers += $Member
- }
- }
- else {
- $GroupMembers += $Member
- }
- }
- }
- $MembershipValue = $GroupMembers
- }
-
- if(-not $Memberships[$Group]) {
- $Memberships[$Group] = @{}
- }
- if($MembershipValue -isnot [System.Array]) {$MembershipValue = @($MembershipValue)}
- $Memberships[$Group].Add($Relation, $MembershipValue)
- }
-
- ForEach ($Membership in $Memberships.GetEnumerator()) {
- if($Membership -and $Membership.Key -and ($Membership.Key -match '^\*')) {
- # if the SID is already resolved (i.e. begins with *) try to resolve SID to a name
- $GroupSID = $Membership.Key.Trim('*')
- if($GroupSID -and ($GroupSID.Trim() -ne '')) {
- $GroupName = Convert-SidToName -SID $GroupSID
- }
- else {
- $GroupName = $False
- }
- }
- else {
- $GroupName = $Membership.Key
-
- if($GroupName -and ($GroupName.Trim() -ne '')) {
- if($Groupname -match 'Administrators') {
- $GroupSID = 'S-1-5-32-544'
- }
- elseif($Groupname -match 'Remote Desktop') {
- $GroupSID = 'S-1-5-32-555'
- }
- elseif($Groupname -match 'Guests') {
- $GroupSID = 'S-1-5-32-546'
- }
- elseif($GroupName.Trim() -ne '') {
- $GroupSID = Convert-NameToSid -Domain $Domain -ObjectName $Groupname | Select-Object -ExpandProperty SID
- }
- else {
- $GroupSID = $Null
- }
- }
- }
-
- $GPOGroup = New-Object PSObject
- $GPOGroup | Add-Member Noteproperty 'GPODisplayName' $GPODisplayName
- $GPOGroup | Add-Member Noteproperty 'GPOName' $GPOName
- $GPOGroup | Add-Member Noteproperty 'GPOPath' $GPOPath
- $GPOGroup | Add-Member Noteproperty 'GPOType' 'RestrictedGroups'
- $GPOGroup | Add-Member Noteproperty 'Filters' $Null
- $GPOGroup | Add-Member Noteproperty 'GroupName' $GroupName
- $GPOGroup | Add-Member Noteproperty 'GroupSID' $GroupSID
- $GPOGroup | Add-Member Noteproperty 'GroupMemberOf' $Membership.Value.Memberof
- $GPOGroup | Add-Member Noteproperty 'GroupMembers' $Membership.Value.Members
- $GPOGroup
- }
- }
-
- $ParseArgs = @{
- 'GroupsXMLpath' = "$GPOPath\MACHINE\Preferences\Groups\Groups.xml"
- 'UsePSDrive' = $UsePSDrive
- }
-
- Get-GroupsXML @ParseArgs | ForEach-Object {
- if($ResolveMemberSIDs) {
- $GroupMembers = @()
- ForEach($Member in $_.GroupMembers) {
- if($Member -and ($Member.Trim() -ne '')) {
- if($Member -notmatch '^S-1-.*') {
- # if the resulting member is username and not a SID, attempt to resolve it
- $MemberSID = Convert-NameToSid -Domain $Domain -ObjectName $Member | Select-Object -ExpandProperty SID
- if($MemberSID) {
- $GroupMembers += $MemberSID
- }
- else {
- $GroupMembers += $Member
- }
- }
- else {
- $GroupMembers += $Member
- }
- }
- }
- $_.GroupMembers = $GroupMembers
- }
-
- $_ | Add-Member Noteproperty 'GPODisplayName' $GPODisplayName
- $_ | Add-Member Noteproperty 'GPOName' $GPOName
- $_ | Add-Member Noteproperty 'GPOType' 'GroupPolicyPreferences'
- $_
- }
- }
-}
-
-
-function Find-GPOLocation {
-<#
- .SYNOPSIS
-
- Enumerates the machines where a specific user/group is a member of a specific
- local group, all through GPO correlation.
-
- Author: @harmj0y
- License: BSD 3-Clause
- Required Dependencies: Get-NetUser, Get-NetGroup, Get-NetGPOGroup, Get-NetOU, Get-NetComputer, Get-ADObject, Get-NetSite
- Optional Dependencies: None
-
- .DESCRIPTION
-
- Takes a user/group name and optional domain, and determines the computers in the domain
- the user/group has local admin (or RDP) rights to.
-
- It does this by:
- 1. resolving the user/group to its proper SID
- 2. enumerating all groups the user/group is a current part of
- and extracting all target SIDs to build a target SID list
- 3. pulling all GPOs that set 'Restricted Groups' or Groups.xml by calling
- Get-NetGPOGroup
- 4. matching the target SID list to the queried GPO SID list
- to enumerate all GPO the user is effectively applied with
- 5. enumerating all OUs and sites and applicable GPO GUIs are
- applied to through gplink enumerating
- 6. querying for all computers under the given OUs or sites
-
- If no user/group is specified, all user/group -> machine mappings discovered through
- GPO relationships are returned.
-
- .PARAMETER UserName
-
- A (single) user name name to query for access.
-
- .PARAMETER GroupName
-
- A (single) group name name to query for access.
-
- .PARAMETER Domain
-
- Optional domain the user exists in for querying, defaults to the current domain.
-
- .PARAMETER DomainController
-
- Domain controller to reflect LDAP queries through.
-
- .PARAMETER LocalGroup
-
- The local group to check access against.
- Can be "Administrators" (S-1-5-32-544), "RDP/Remote Desktop Users" (S-1-5-32-555),
- or a custom local SID. Defaults to local 'Administrators'.
-
- .PARAMETER UsePSDrive
-
- Switch. Mount any found policy files with temporary PSDrives.
-
- .PARAMETER PageSize
-
- The PageSize to set for the LDAP searcher object.
-
- .EXAMPLE
-
- PS C:\> Find-GPOLocation
-
- Find all user/group -> machine relationships where the user/group is a member
- of the local administrators group on target machines.
-
- .EXAMPLE
-
- PS C:\> Find-GPOLocation -UserName dfm
-
- Find all computers that dfm user has local administrator rights to in
- the current domain.
-
- .EXAMPLE
-
- PS C:\> Find-GPOLocation -UserName dfm -Domain dev.testlab.local
-
- Find all computers that dfm user has local administrator rights to in
- the dev.testlab.local domain.
-
- .EXAMPLE
-
- PS C:\> Find-GPOLocation -UserName jason -LocalGroup RDP
-
- Find all computers that jason has local RDP access rights to in the domain.
-#>
-
- [CmdletBinding()]
- Param (
- [String]
- $UserName,
-
- [String]
- $GroupName,
-
- [String]
- $Domain,
-
- [String]
- $DomainController,
-
- [String]
- $LocalGroup = 'Administrators',
-
- [Switch]
- $UsePSDrive,
-
- [ValidateRange(1,10000)]
- [Int]
- $PageSize = 200
- )
-
- if($UserName) {
- # if a group name is specified, get that user object so we can extract the target SID
- $User = Get-NetUser -UserName $UserName -Domain $Domain -DomainController $DomainController -PageSize $PageSize | Select-Object -First 1
- $UserSid = $User.objectsid
-
- if(-not $UserSid) {
- Throw "User '$UserName' not found!"
- }
-
- $TargetSIDs = @($UserSid)
- $ObjectSamAccountName = $User.samaccountname
- $TargetObject = $UserSid
- }
- elseif($GroupName) {
- # if a group name is specified, get that group object so we can extract the target SID
- $Group = Get-NetGroup -GroupName $GroupName -Domain $Domain -DomainController $DomainController -FullData -PageSize $PageSize | Select-Object -First 1
- $GroupSid = $Group.objectsid
-
- if(-not $GroupSid) {
- Throw "Group '$GroupName' not found!"
- }
-
- $TargetSIDs = @($GroupSid)
- $ObjectSamAccountName = $Group.samaccountname
- $TargetObject = $GroupSid
- }
- else {
- $TargetSIDs = @('*')
- }
-
- # figure out what the SID is of the target local group we're checking for membership in
- if($LocalGroup -like "*Admin*") {
- $TargetLocalSID = 'S-1-5-32-544'
- }
- elseif ( ($LocalGroup -like "*RDP*") -or ($LocalGroup -like "*Remote*") ) {
- $TargetLocalSID = 'S-1-5-32-555'
- }
- elseif ($LocalGroup -like "S-1-5-*") {
- $TargetLocalSID = $LocalGroup
- }
- else {
- throw "LocalGroup must be 'Administrators', 'RDP', or a 'S-1-5-X' SID format."
- }
-
- # if we're not listing all relationships, use the tokenGroups approach from Get-NetGroup to
- # get all effective security SIDs this object is a part of
- if($TargetSIDs[0] -and ($TargetSIDs[0] -ne '*')) {
- $TargetSIDs += Get-NetGroup -Domain $Domain -DomainController $DomainController -PageSize $PageSize -UserName $ObjectSamAccountName -RawSids
- }
-
- if(-not $TargetSIDs) {
- throw "No effective target SIDs!"
- }
-
- Write-Verbose "TargetLocalSID: $TargetLocalSID"
- Write-Verbose "Effective target SIDs: $TargetSIDs"
-
- $GPOGroupArgs = @{
- 'Domain' = $Domain
- 'DomainController' = $DomainController
- 'UsePSDrive' = $UsePSDrive
- 'ResolveMemberSIDs' = $True
- 'PageSize' = $PageSize
- }
-
- # enumerate all GPO group mappings for the target domain that involve our target SID set
- $GPOgroups = Get-NetGPOGroup @GPOGroupArgs | ForEach-Object {
-
- $GPOgroup = $_
-
- # if the locally set group is what we're looking for, check the GroupMembers ('members')
- # for our target SID
- if($GPOgroup.GroupSID -match $TargetLocalSID) {
- $GPOgroup.GroupMembers | Where-Object {$_} | ForEach-Object {
- if ( ($TargetSIDs[0] -eq '*') -or ($TargetSIDs -Contains $_) ) {
- $GPOgroup
- }
- }
- }
- # if the group is a 'memberof' the group we're looking for, check GroupSID against the targt SIDs
- if( ($GPOgroup.GroupMemberOf -contains $TargetLocalSID) ) {
- if( ($TargetSIDs[0] -eq '*') -or ($TargetSIDs -Contains $GPOgroup.GroupSID) ) {
- $GPOgroup
- }
- }
- } | Sort-Object -Property GPOName -Unique
-
- $GPOgroups | ForEach-Object {
-
- $GPOname = $_.GPODisplayName
- $GPOguid = $_.GPOName
- $GPOPath = $_.GPOPath
- $GPOType = $_.GPOType
- if($_.GroupMembers) {
- $GPOMembers = $_.GroupMembers
- }
- else {
- $GPOMembers = $_.GroupSID
- }
-
- $Filters = $_.Filters
-
- if(-not $TargetObject) {
- # if the * wildcard was used, set the ObjectDistName as the GPO member SID set
- # so all relationship mappings are output
- $TargetObjectSIDs = $GPOMembers
- }
- else {
- $TargetObjectSIDs = $TargetObject
- }
-
- # find any OUs that have this GUID applied and then retrieve any computers from the OU
- Get-NetOU -Domain $Domain -DomainController $DomainController -GUID $GPOguid -FullData -PageSize $PageSize | ForEach-Object {
- if($Filters) {
- # filter for computer name/org unit if a filter is specified
- # TODO: handle other filters (i.e. OU filters?) again, I hate you GPP...
- $OUComputers = Get-NetComputer -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $_.ADSpath -FullData -PageSize $PageSize | Where-Object {
- $_.adspath -match ($Filters.Value)
- } | ForEach-Object { $_.dnshostname }
- }
- else {
- $OUComputers = Get-NetComputer -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $_.ADSpath -PageSize $PageSize
- }
-
- if($OUComputers) {
- if($OUComputers -isnot [System.Array]) {$OUComputers = @($OUComputers)}
-
- ForEach ($TargetSid in $TargetObjectSIDs) {
- $Object = Get-ADObject -SID $TargetSid -Domain $Domain -DomainController $DomainController -Credential $Credential -PageSize $PageSize
-
- $IsGroup = @('268435456','268435457','536870912','536870913') -contains $Object.samaccounttype
-
- $GPOLocation = New-Object PSObject
- $GPOLocation | Add-Member Noteproperty 'ObjectName' $Object.samaccountname
- $GPOLocation | Add-Member Noteproperty 'ObjectDN' $Object.distinguishedname
- $GPOLocation | Add-Member Noteproperty 'ObjectSID' $Object.objectsid
- $GPOLocation | Add-Member Noteproperty 'Domain' $Domain
- $GPOLocation | Add-Member Noteproperty 'IsGroup' $IsGroup
- $GPOLocation | Add-Member Noteproperty 'GPODisplayName' $GPOname
- $GPOLocation | Add-Member Noteproperty 'GPOGuid' $GPOGuid
- $GPOLocation | Add-Member Noteproperty 'GPOPath' $GPOPath
- $GPOLocation | Add-Member Noteproperty 'GPOType' $GPOType
- $GPOLocation | Add-Member Noteproperty 'ContainerName' $_.distinguishedname
- $GPOLocation | Add-Member Noteproperty 'ComputerName' $OUComputers
- $GPOLocation.PSObject.TypeNames.Add('PowerView.GPOLocalGroup')
- $GPOLocation
- }
- }
- }
-
- # find any sites that have this GUID applied
- Get-NetSite -Domain $Domain -DomainController $DomainController -GUID $GPOguid -PageSize $PageSize -FullData | ForEach-Object {
-
- ForEach ($TargetSid in $TargetObjectSIDs) {
- $Object = Get-ADObject -SID $TargetSid -Domain $Domain -DomainController $DomainController -Credential $Credential -PageSize $PageSize
-
- $IsGroup = @('268435456','268435457','536870912','536870913') -contains $Object.samaccounttype
-
- $AppliedSite = New-Object PSObject
- $AppliedSite | Add-Member Noteproperty 'ObjectName' $Object.samaccountname
- $AppliedSite | Add-Member Noteproperty 'ObjectDN' $Object.distinguishedname
- $AppliedSite | Add-Member Noteproperty 'ObjectSID' $Object.objectsid
- $AppliedSite | Add-Member Noteproperty 'IsGroup' $IsGroup
- $AppliedSite | Add-Member Noteproperty 'Domain' $Domain
- $AppliedSite | Add-Member Noteproperty 'GPODisplayName' $GPOname
- $AppliedSite | Add-Member Noteproperty 'GPOGuid' $GPOGuid
- $AppliedSite | Add-Member Noteproperty 'GPOPath' $GPOPath
- $AppliedSite | Add-Member Noteproperty 'GPOType' $GPOType
- $AppliedSite | Add-Member Noteproperty 'ContainerName' $_.distinguishedname
- $AppliedSite | Add-Member Noteproperty 'ComputerName' $_.siteobjectbl
- $AppliedSite.PSObject.TypeNames.Add('PowerView.GPOLocalGroup')
- $AppliedSite
- }
- }
- }
-}
-
-
-function Find-GPOComputerAdmin {
-<#
- .SYNOPSIS
-
- Takes a computer (or GPO) object and determines what users/groups are in the specified
- local group for the machine.
-
- Author: @harmj0y
- License: BSD 3-Clause
- Required Dependencies: Get-NetComputer, Get-SiteName, Get-NetSite, Get-NetGPOGroup, Get-ADObject, Get-NetGroupMember, Convert-SidToName
- Optional Dependencies: None
-
- .DESCRIPTION
-
- If a -ComputerName is specified, retrieve the complete computer object, attempt to
- determine the OU the computer is a part of. Then resolve the computer's site name with
- Get-SiteName and retrieve all sites object Get-NetSite. For those results, attempt to
- enumerate all linked GPOs and associated local group settings with Get-NetGPOGroup. For
- each resulting GPO group, resolve the resulting user/group name to a full AD object and
- return the results. This will return the domain objects that are members of the specified
- -LocalGroup for the given computer.
-
- Inverse of Find-GPOLocation.
-
- .PARAMETER ComputerName
-
- The computer to determine local administrative access to.
-
- .PARAMETER OUName
-
- OU name to determine who has local adminisrtative acess to computers
- within it.
-
- .PARAMETER Domain
-
- Optional domain the computer/OU exists in, defaults to the current domain.
-
- .PARAMETER DomainController
-
- Domain controller to reflect LDAP queries through.
-
- .PARAMETER Recurse
-
- Switch. If a returned member is a group, recurse and get all members.
-
- .PARAMETER LocalGroup
-
- The local group to check access against.
- Can be "Administrators" (S-1-5-32-544), "RDP/Remote Desktop Users" (S-1-5-32-555),
- or a custom local SID.
- Defaults to local 'Administrators'.
-
- .PARAMETER UsePSDrive
-
- Switch. Mount any found policy files with temporary PSDrives.
-
- .PARAMETER PageSize
-
- The PageSize to set for the LDAP searcher object.
-
- .EXAMPLE
-
- PS C:\> Find-GPOComputerAdmin -ComputerName WINDOWS3.dev.testlab.local
-
- Finds users who have local admin rights over WINDOWS3 through GPO correlation.
-
- .EXAMPLE
-
- PS C:\> Find-GPOComputerAdmin -ComputerName WINDOWS3.dev.testlab.local -LocalGroup RDP
-
- Finds users who have RDP rights over WINDOWS3 through GPO correlation.
-#>
-
- [CmdletBinding()]
- Param (
- [Parameter(ValueFromPipeline=$True)]
- [String]
- $ComputerName,
-
- [String]
- $OUName,
-
- [String]
- $Domain,
-
- [String]
- $DomainController,
-
- [Switch]
- $Recurse,
-
- [String]
- $LocalGroup = 'Administrators',
-
- [Switch]
- $UsePSDrive,
-
- [ValidateRange(1,10000)]
- [Int]
- $PageSize = 200
- )
-
- process {
-
- if(!$ComputerName -and !$OUName) {
- Throw "-ComputerName or -OUName must be provided"
- }
-
- $GPOGroups = @()
-
- if($ComputerName) {
- $Computers = Get-NetComputer -ComputerName $ComputerName -Domain $Domain -DomainController $DomainController -FullData -PageSize $PageSize
-
- if(!$Computers) {
- throw "Computer $ComputerName in domain '$Domain' not found! Try a fully qualified host name"
- }
-
- $TargetOUs = @()
- ForEach($Computer in $Computers) {
- # extract all OUs a computer is a part of
- $DN = $Computer.distinguishedname
-
- $TargetOUs += $DN.split(",") | ForEach-Object {
- if($_.startswith("OU=")) {
- $DN.substring($DN.indexof($_))
- }
- }
- }
-
- # enumerate any linked GPOs for the computer's site
- $ComputerSite = (Get-SiteName -ComputerName $ComputerName).SiteName
- if($ComputerSite -and ($ComputerSite -notlike 'Error*')) {
- $GPOGroups += Get-NetSite -SiteName $ComputerSite -FullData | ForEach-Object {
- if($_.gplink) {
- $_.gplink.split("][") | ForEach-Object {
- if ($_.startswith("LDAP")) {
- $_.split(";")[0]
- }
- }
- }
- } | ForEach-Object {
- $GPOGroupArgs = @{
- 'Domain' = $Domain
- 'DomainController' = $DomainController
- 'ResolveMemberSIDs' = $True
- 'UsePSDrive' = $UsePSDrive
- 'PageSize' = $PageSize
- }
-
- # for each GPO link, get any locally set user/group SIDs
- Get-NetGPOGroup @GPOGroupArgs
- }
- }
- }
- else {
- $TargetOUs = @($OUName)
- }
-
- Write-Verbose "Target OUs: $TargetOUs"
-
- $TargetOUs | Where-Object {$_} | ForEach-Object {
-
- $GPOLinks = Get-NetOU -Domain $Domain -DomainController $DomainController -ADSpath $_ -FullData -PageSize $PageSize | ForEach-Object {
- # and then get any GPO links
- if($_.gplink) {
- $_.gplink.split("][") | ForEach-Object {
- if ($_.startswith("LDAP")) {
- $_.split(";")[0]
- }
- }
- }
- }
-
- $GPOGroupArgs = @{
- 'Domain' = $Domain
- 'DomainController' = $DomainController
- 'UsePSDrive' = $UsePSDrive
- 'ResolveMemberSIDs' = $True
- 'PageSize' = $PageSize
- }
-
- # extract GPO groups that are set through any gPlink for this OU
- $GPOGroups += Get-NetGPOGroup @GPOGroupArgs | ForEach-Object {
- ForEach($GPOLink in $GPOLinks) {
- $Name = $_.GPOName
- if($GPOLink -like "*$Name*") {
- $_
- }
- }
- }
- }
-
- # for each found GPO group, resolve the SIDs of the members
- $GPOgroups | Sort-Object -Property GPOName -Unique | ForEach-Object {
- $GPOGroup = $_
-
- if($GPOGroup.GroupMembers) {
- $GPOMembers = $GPOGroup.GroupMembers
- }
- else {
- $GPOMembers = $GPOGroup.GroupSID
- }
-
- $GPOMembers | ForEach-Object {
- # resolve this SID to a domain object
- $Object = Get-ADObject -Domain $Domain -DomainController $DomainController -PageSize $PageSize -SID $_
-
- $IsGroup = @('268435456','268435457','536870912','536870913') -contains $Object.samaccounttype
-
- $GPOComputerAdmin = New-Object PSObject
- $GPOComputerAdmin | Add-Member Noteproperty 'ComputerName' $ComputerName
- $GPOComputerAdmin | Add-Member Noteproperty 'ObjectName' $Object.samaccountname
- $GPOComputerAdmin | Add-Member Noteproperty 'ObjectDN' $Object.distinguishedname
- $GPOComputerAdmin | Add-Member Noteproperty 'ObjectSID' $_
- $GPOComputerAdmin | Add-Member Noteproperty 'IsGroup' $IsGroup
- $GPOComputerAdmin | Add-Member Noteproperty 'GPODisplayName' $GPOGroup.GPODisplayName
- $GPOComputerAdmin | Add-Member Noteproperty 'GPOGuid' $GPOGroup.GPOName
- $GPOComputerAdmin | Add-Member Noteproperty 'GPOPath' $GPOGroup.GPOPath
- $GPOComputerAdmin | Add-Member Noteproperty 'GPOType' $GPOGroup.GPOType
- $GPOComputerAdmin
-
- # if we're recursing and the current result object is a group
- if($Recurse -and $GPOComputerAdmin.isGroup) {
-
- Get-NetGroupMember -Domain $Domain -DomainController $DomainController -SID $_ -FullData -Recurse -PageSize $PageSize | ForEach-Object {
-
- $MemberDN = $_.distinguishedName
-
- # extract the FQDN from the Distinguished Name
- $MemberDomain = $MemberDN.subString($MemberDN.IndexOf("DC=")) -replace 'DC=','' -replace ',','.'
-
- $MemberIsGroup = @('268435456','268435457','536870912','536870913') -contains $_.samaccounttype
-
- if ($_.samAccountName) {
- # forest users have the samAccountName set
- $MemberName = $_.samAccountName
- }
- else {
- # external trust users have a SID, so convert it
- try {
- $MemberName = Convert-SidToName $_.cn
- }
- catch {
- # if there's a problem contacting the domain to resolve the SID
- $MemberName = $_.cn
- }
- }
-
- $GPOComputerAdmin = New-Object PSObject
- $GPOComputerAdmin | Add-Member Noteproperty 'ComputerName' $ComputerName
- $GPOComputerAdmin | Add-Member Noteproperty 'ObjectName' $MemberName
- $GPOComputerAdmin | Add-Member Noteproperty 'ObjectDN' $MemberDN
- $GPOComputerAdmin | Add-Member Noteproperty 'ObjectSID' $_.objectsid
- $GPOComputerAdmin | Add-Member Noteproperty 'IsGroup' $MemberIsGrou
- $GPOComputerAdmin | Add-Member Noteproperty 'GPODisplayName' $GPOGroup.GPODisplayName
- $GPOComputerAdmin | Add-Member Noteproperty 'GPOGuid' $GPOGroup.GPOName
- $GPOComputerAdmin | Add-Member Noteproperty 'GPOPath' $GPOGroup.GPOPath
- $GPOComputerAdmin | Add-Member Noteproperty 'GPOType' $GPOTypep
- $GPOComputerAdmin
- }
- }
- }
- }
- }
-}
-
-
-function Get-DomainPolicy {
-<#
- .SYNOPSIS
-
- Returns the default domain or DC policy for a given
- domain or domain controller.
-
- Thanks Sean Metacalf (@pyrotek3) for the idea and guidance.
-
- .PARAMETER Source
-
- Extract Domain or DC (domain controller) policies.
-
- .PARAMETER Domain
-
- The domain to query for default policies, defaults to the current domain.
-
- .PARAMETER DomainController
-
- Domain controller to reflect LDAP queries through.
-
- .PARAMETER ResolveSids
-
- Switch. Resolve Sids from a DC policy to object names.
-
- .PARAMETER UsePSDrive
-
- Switch. Mount any found policy files with temporary PSDrives.
-
- .EXAMPLE
-
- PS C:\> Get-DomainPolicy
-
- Returns the domain policy for the current domain.
-
- .EXAMPLE
-
- PS C:\> Get-DomainPolicy -Source DC -DomainController MASTER.testlab.local
-
- Returns the policy for the MASTER.testlab.local domain controller.
-#>
-
- [CmdletBinding()]
- Param (
- [String]
- [ValidateSet("Domain","DC")]
- $Source ="Domain",
-
- [String]
- $Domain,
-
- [String]
- $DomainController,
-
- [Switch]
- $ResolveSids,
-
- [Switch]
- $UsePSDrive
- )
-
- if($Source -eq "Domain") {
- # query the given domain for the default domain policy object
- $GPO = Get-NetGPO -Domain $Domain -DomainController $DomainController -GPOname "{31B2F340-016D-11D2-945F-00C04FB984F9}"
-
- if($GPO) {
- # grab the GptTmpl.inf file and parse it
- $GptTmplPath = $GPO.gpcfilesyspath + "\MACHINE\Microsoft\Windows NT\SecEdit\GptTmpl.inf"
-
- $ParseArgs = @{
- 'GptTmplPath' = $GptTmplPath
- 'UsePSDrive' = $UsePSDrive
- }
-
- # parse the GptTmpl.inf
- Get-GptTmpl @ParseArgs
- }
-
- }
- elseif($Source -eq "DC") {
- # query the given domain/dc for the default domain controller policy object
- $GPO = Get-NetGPO -Domain $Domain -DomainController $DomainController -GPOname "{6AC1786C-016F-11D2-945F-00C04FB984F9}"
-
- if($GPO) {
- # grab the GptTmpl.inf file and parse it
- $GptTmplPath = $GPO.gpcfilesyspath + "\MACHINE\Microsoft\Windows NT\SecEdit\GptTmpl.inf"
-
- $ParseArgs = @{
- 'GptTmplPath' = $GptTmplPath
- 'UsePSDrive' = $UsePSDrive
- }
-
- # parse the GptTmpl.inf
- Get-GptTmpl @ParseArgs | ForEach-Object {
- if($ResolveSids) {
- # if we're resolving sids in PrivilegeRights to names
- $Policy = New-Object PSObject
- $_.psobject.properties | ForEach-Object {
- if( $_.Name -eq 'PrivilegeRights') {
-
- $PrivilegeRights = New-Object PSObject
- # for every nested SID member of PrivilegeRights, try to unpack everything and resolve the SIDs as appropriate
- $_.Value.psobject.properties | ForEach-Object {
-
- $Sids = $_.Value | ForEach-Object {
- try {
- if($_ -isnot [System.Array]) {
- Convert-SidToName $_
- }
- else {
- $_ | ForEach-Object { Convert-SidToName $_ }
- }
- }
- catch {
- Write-Verbose "Error resolving SID : $_"
- }
- }
-
- $PrivilegeRights | Add-Member Noteproperty $_.Name $Sids
- }
-
- $Policy | Add-Member Noteproperty 'PrivilegeRights' $PrivilegeRights
- }
- else {
- $Policy | Add-Member Noteproperty $_.Name $_.Value
- }
- }
- $Policy
- }
- else { $_ }
- }
- }
- }
-}
-
-
-
-########################################################
-#
-# Functions that enumerate a single host, either through
-# WinNT, WMI, remote registry, or API calls
-# (with PSReflect).
-#
-########################################################
-
-function Get-NetLocalGroup {
-<#
- .SYNOPSIS
-
- Gets a list of all current users in a specified local group,
- or returns the names of all local groups with -ListGroups.
-
- .PARAMETER ComputerName
-
- The hostname or IP to query for local group users.
-
- .PARAMETER ComputerFile
-
- File of hostnames/IPs to query for local group users.
-
- .PARAMETER GroupName
-
- The local group name to query for users. If not given, it defaults to "Administrators"
-
- .PARAMETER ListGroups
-
- Switch. List all the local groups instead of their members.
- Old Get-NetLocalGroups functionality.
-
- .PARAMETER Recurse
-
- Switch. If the local member member is a domain group, recursively try to resolve its members to get a list of domain users who can access this machine.
-
- .PARAMETER API
-
- Switch. Use API calls instead of the WinNT service provider. Less information,
- but the results are faster.
-
- .EXAMPLE
-
- PS C:\> Get-NetLocalGroup
-
- Returns the usernames that of members of localgroup "Administrators" on the local host.
-
- .EXAMPLE
-
- PS C:\> Get-NetLocalGroup -ComputerName WINDOWSXP
-
- Returns all the local administrator accounts for WINDOWSXP
-
- .EXAMPLE
-
- PS C:\> Get-NetLocalGroup -ComputerName WINDOWS7 -Recurse
-
- Returns all effective local/domain users/groups that can access WINDOWS7 with
- local administrative privileges.
-
- .EXAMPLE
-
- PS C:\> Get-NetLocalGroup -ComputerName WINDOWS7 -ListGroups
-
- Returns all local groups on the WINDOWS7 host.
-
- .EXAMPLE
-
- PS C:\> "WINDOWS7", "WINDOWSSP" | Get-NetLocalGroup -API
-
- Returns all local groups on the the passed hosts using API calls instead of the
- WinNT service provider.
-
- .LINK
-
- http://stackoverflow.com/questions/21288220/get-all-local-members-and-groups-displayed-together
- http://msdn.microsoft.com/en-us/library/aa772211(VS.85).aspx
-#>
-
- [CmdletBinding(DefaultParameterSetName = 'WinNT')]
- param(
- [Parameter(ParameterSetName = 'API', Position=0, ValueFromPipeline=$True)]
- [Parameter(ParameterSetName = 'WinNT', Position=0, ValueFromPipeline=$True)]
- [Alias('HostName')]
- [String[]]
- $ComputerName = $Env:ComputerName,
-
- [Parameter(ParameterSetName = 'WinNT')]
- [Parameter(ParameterSetName = 'API')]
- [ValidateScript({Test-Path -Path $_ })]
- [Alias('HostList')]
- [String]
- $ComputerFile,
-
- [Parameter(ParameterSetName = 'WinNT')]
- [Parameter(ParameterSetName = 'API')]
- [String]
- $GroupName = 'Administrators',
-
- [Parameter(ParameterSetName = 'WinNT')]
- [Switch]
- $ListGroups,
-
- [Parameter(ParameterSetName = 'WinNT')]
- [Switch]
- $Recurse,
-
- [Parameter(ParameterSetName = 'API')]
- [Switch]
- $API
- )
-
- process {
-
- $Servers = @()
-
- # if we have a host list passed, grab it
- if($ComputerFile) {
- $Servers = Get-Content -Path $ComputerFile
- }
- else {
- # otherwise assume a single host name
- $Servers += $ComputerName | Get-NameField
- }
-
- # query the specified group using the WINNT provider, and
- # extract fields as appropriate from the results
- ForEach($Server in $Servers) {
-
- if($API) {
- # if we're using the Netapi32 NetLocalGroupGetMembers API call to get the local group information
-
- # arguments for NetLocalGroupGetMembers
- $QueryLevel = 2
- $PtrInfo = [IntPtr]::Zero
- $EntriesRead = 0
- $TotalRead = 0
- $ResumeHandle = 0
-
- # get the local user information
- $Result = $Netapi32::NetLocalGroupGetMembers($Server, $GroupName, $QueryLevel, [ref]$PtrInfo, -1, [ref]$EntriesRead, [ref]$TotalRead, [ref]$ResumeHandle)
-
- # Locate the offset of the initial intPtr
- $Offset = $PtrInfo.ToInt64()
-
- $LocalUsers = @()
-
- # 0 = success
- if (($Result -eq 0) -and ($Offset -gt 0)) {
-
- # Work out how mutch to increment the pointer by finding out the size of the structure
- $Increment = $LOCALGROUP_MEMBERS_INFO_2::GetSize()
-
- # parse all the result structures
- for ($i = 0; ($i -lt $EntriesRead); $i++) {
- # create a new int ptr at the given offset and cast the pointer as our result structure
- $NewIntPtr = New-Object System.Intptr -ArgumentList $Offset
- $Info = $NewIntPtr -as $LOCALGROUP_MEMBERS_INFO_2
-
- $Offset = $NewIntPtr.ToInt64()
- $Offset += $Increment
-
- $SidString = ""
- $Result2 = $Advapi32::ConvertSidToStringSid($Info.lgrmi2_sid, [ref]$SidString);$LastError = [Runtime.InteropServices.Marshal]::GetLastWin32Error()
-
- if($Result2 -eq 0) {
- Write-Verbose "Error: $(([ComponentModel.Win32Exception] $LastError).Message)"
- }
- else {
- $LocalUser = New-Object PSObject
- $LocalUser | Add-Member Noteproperty 'ComputerName' $Server
- $LocalUser | Add-Member Noteproperty 'AccountName' $Info.lgrmi2_domainandname
- $LocalUser | Add-Member Noteproperty 'SID' $SidString
-
- $IsGroup = $($Info.lgrmi2_sidusage -ne 'SidTypeUser')
- $LocalUser | Add-Member Noteproperty 'IsGroup' $IsGroup
-
- $LocalUser.PSObject.TypeNames.Add('PowerView.LocalUserAPI')
-
- $LocalUsers += $LocalUser
- }
- }
-
- # free up the result buffer
- $Null = $Netapi32::NetApiBufferFree($PtrInfo)
-
- # try to extract out the machine SID by using the -500 account as a reference
- $MachineSid = $LocalUsers | Where-Object {$_.SID -like '*-500'}
- try {
- $Parts = $MachineSid.SID.Split('-')
- $MachineSid = $Parts[0..($Parts.Length -2)] -join '-'
-
- $LocalUsers | ForEach-Object {
- if($_.SID -match $MachineSid) {
- $_ | Add-Member Noteproperty 'IsDomain' $False
- }
- else {
- $_ | Add-Member Noteproperty 'IsDomain' $True
- }
- }
- $LocalUsers
- }
- catch {
- Write-Verbose "Error retrieving machine SID for $Server"
- }
- }
- else {
- Write-Verbose "Error: $(([ComponentModel.Win32Exception] $Result).Message)"
- }
- }
-
- else {
- # otherwise we're using the WinNT service provider
- try {
- if($ListGroups) {
- # if we're listing the group names on a remote server
- $Computer = [ADSI]"WinNT://$Server,computer"
-
- $Computer.psbase.children | Where-Object { $_.psbase.schemaClassName -eq 'group' } | ForEach-Object {
- $Group = New-Object PSObject
- $Group | Add-Member Noteproperty 'Server' $Server
- $Group | Add-Member Noteproperty 'Group' ($_.name[0])
- $Group | Add-Member Noteproperty 'SID' ((New-Object System.Security.Principal.SecurityIdentifier $_.objectsid[0],0).Value)
- $Group | Add-Member Noteproperty 'Description' ($_.Description[0])
- $Group.PSObject.TypeNames.Add('PowerView.LocalGroup')
- $Group
- }
- }
- else {
- # otherwise we're listing the group members
- $Members = @($([ADSI]"WinNT://$Server/$GroupName,group").psbase.Invoke('Members'))
-
- $Members | ForEach-Object {
-
- $Member = New-Object PSObject
- $Member | Add-Member Noteproperty 'ComputerName' $Server
-
- $AdsPath = ($_.GetType().InvokeMember('Adspath', 'GetProperty', $Null, $_, $Null)).Replace('WinNT://', '')
-
- # try to translate the NT4 domain to a FQDN if possible
- $Name = Convert-ADName -ObjectName $AdsPath -InputType 'NT4' -OutputType 'Canonical'
-
- if($Name) {
- $FQDN = $Name.split("/")[0]
- $ObjName = $AdsPath.split("/")[-1]
- $Name = "$FQDN/$ObjName"
- $IsDomain = $True
- }
- else {
- $Name = $AdsPath
- $IsDomain = $False
- }
-
- $Member | Add-Member Noteproperty 'AccountName' $Name
-
- if($IsDomain) {
- # translate the binary sid to a string
- $Member | Add-Member Noteproperty 'SID' ((New-Object System.Security.Principal.SecurityIdentifier($_.GetType().InvokeMember('ObjectSID', 'GetProperty', $Null, $_, $Null),0)).Value)
-
- $Member | Add-Member Noteproperty 'Description' ""
- $Member | Add-Member Noteproperty 'Disabled' $False
-
- # check if the member is a group
- $IsGroup = ($_.GetType().InvokeMember('Class', 'GetProperty', $Null, $_, $Null) -eq 'group')
- $Member | Add-Member Noteproperty 'IsGroup' $IsGroup
- $Member | Add-Member Noteproperty 'IsDomain' $IsDomain
-
- if($IsGroup) {
- $Member | Add-Member Noteproperty 'LastLogin' $Null
- }
- else {
- try {
- $Member | Add-Member Noteproperty 'LastLogin' ( $_.GetType().InvokeMember('LastLogin', 'GetProperty', $Null, $_, $Null))
- }
- catch {
- $Member | Add-Member Noteproperty 'LastLogin' $Null
- }
- }
- $Member | Add-Member Noteproperty 'PwdLastSet' ""
- $Member | Add-Member Noteproperty 'PwdExpired' ""
- $Member | Add-Member Noteproperty 'UserFlags' ""
- }
- else {
- # repull this user object so we can ensure correct information
- $LocalUser = $([ADSI] "WinNT://$AdsPath")
-
- # translate the binary sid to a string
- $Member | Add-Member Noteproperty 'SID' ((New-Object System.Security.Principal.SecurityIdentifier($LocalUser.objectSid.value,0)).Value)
-
- $Member | Add-Member Noteproperty 'Description' ($LocalUser.Description[0])
-
- # UAC flags of 0x2 mean the account is disabled
- $Member | Add-Member Noteproperty 'Disabled' $(($LocalUser.userFlags.value -band 2) -eq 2)
-
- # check if the member is a group
- $Member | Add-Member Noteproperty 'IsGroup' ($LocalUser.SchemaClassName -like 'group')
- $Member | Add-Member Noteproperty 'IsDomain' $IsDomain
-
- if($IsGroup) {
- $Member | Add-Member Noteproperty 'LastLogin' ""
- }
- else {
- try {
- $Member | Add-Member Noteproperty 'LastLogin' ( $LocalUser.LastLogin[0])
- }
- catch {
- $Member | Add-Member Noteproperty 'LastLogin' ""
- }
- }
-
- $Member | Add-Member Noteproperty 'PwdLastSet' ( (Get-Date).AddSeconds(-$LocalUser.PasswordAge[0]))
- $Member | Add-Member Noteproperty 'PwdExpired' ( $LocalUser.PasswordExpired[0] -eq '1')
- $Member | Add-Member Noteproperty 'UserFlags' ( $LocalUser.UserFlags[0] )
- }
- $Member.PSObject.TypeNames.Add('PowerView.LocalUser')
- $Member
-
- # if the result is a group domain object and we're recursing, try to resolve all the group member results
- if($Recurse -and $IsDomain -and $IsGroup) {
-
- $FQDN = $Name.split("/")[0]
- $GroupName = $Name.split("/")[1].trim()
-
- Get-NetGroupMember -GroupName $GroupName -Domain $FQDN -FullData -Recurse | ForEach-Object {
-
- $Member = New-Object PSObject
- $Member | Add-Member Noteproperty 'ComputerName' "$FQDN/$($_.GroupName)"
-
- $MemberDN = $_.distinguishedName
- # extract the FQDN from the Distinguished Name
- $MemberDomain = $MemberDN.subString($MemberDN.IndexOf("DC=")) -replace 'DC=','' -replace ',','.'
-
- $MemberIsGroup = @('268435456','268435457','536870912','536870913') -contains $_.samaccounttype
-
- if ($_.samAccountName) {
- # forest users have the samAccountName set
- $MemberName = $_.samAccountName
- }
- else {
- try {
- # external trust users have a SID, so convert it
- try {
- $MemberName = Convert-SidToName $_.cn
- }
- catch {
- # if there's a problem contacting the domain to resolve the SID
- $MemberName = $_.cn
- }
- }
- catch {
- Write-Verbose "Error resolving SID : $_"
- }
- }
-
- $Member | Add-Member Noteproperty 'AccountName' "$MemberDomain/$MemberName"
- $Member | Add-Member Noteproperty 'SID' $_.objectsid
- $Member | Add-Member Noteproperty 'Description' $_.description
- $Member | Add-Member Noteproperty 'Disabled' $False
- $Member | Add-Member Noteproperty 'IsGroup' $MemberIsGroup
- $Member | Add-Member Noteproperty 'IsDomain' $True
- $Member | Add-Member Noteproperty 'LastLogin' ''
- $Member | Add-Member Noteproperty 'PwdLastSet' $_.pwdLastSet
- $Member | Add-Member Noteproperty 'PwdExpired' ''
- $Member | Add-Member Noteproperty 'UserFlags' $_.userAccountControl
- $Member.PSObject.TypeNames.Add('PowerView.LocalUser')
- $Member
- }
- }
- }
- }
- }
- catch {
- Write-Warning "[!] Error: $_"
- }
- }
- }
- }
-}
-
-
-filter Get-NetShare {
-<#
- .SYNOPSIS
-
- This function will execute the NetShareEnum Win32API call to query
- a given host for open shares. This is a replacement for
- "net share \\hostname"
-
- .PARAMETER ComputerName
-
- The hostname to query for shares. Also accepts IP addresses.
-
- .OUTPUTS
-
- SHARE_INFO_1 structure. A representation of the SHARE_INFO_1
- result structure which includes the name and note for each share,
- with the ComputerName added.
-
- .EXAMPLE
-
- PS C:\> Get-NetShare
-
- Returns active shares on the local host.
-
- .EXAMPLE
-
- PS C:\> Get-NetShare -ComputerName sqlserver
-
- Returns active shares on the 'sqlserver' host
-
- .EXAMPLE
-
- PS C:\> Get-NetComputer | Get-NetShare
-
- Returns all shares for all computers in the domain.
-
- .LINK
-
- http://www.powershellmagazine.com/2014/09/25/easily-defining-enums-structs-and-win32-functions-in-memory/
-#>
-
- [CmdletBinding()]
- param(
- [Parameter(ValueFromPipeline=$True)]
- [Alias('HostName')]
- [Object[]]
- [ValidateNotNullOrEmpty()]
- $ComputerName = 'localhost'
- )
-
- # extract the computer name from whatever object was passed on the pipeline
- $Computer = $ComputerName | Get-NameField
-
- # arguments for NetShareEnum
- $QueryLevel = 1
- $PtrInfo = [IntPtr]::Zero
- $EntriesRead = 0
- $TotalRead = 0
- $ResumeHandle = 0
-
- # get the share information
- $Result = $Netapi32::NetShareEnum($Computer, $QueryLevel, [ref]$PtrInfo, -1, [ref]$EntriesRead, [ref]$TotalRead, [ref]$ResumeHandle)
-
- # Locate the offset of the initial intPtr
- $Offset = $PtrInfo.ToInt64()
-
- # 0 = success
- if (($Result -eq 0) -and ($Offset -gt 0)) {
-
- # Work out how mutch to increment the pointer by finding out the size of the structure
- $Increment = $SHARE_INFO_1::GetSize()
-
- # parse all the result structures
- for ($i = 0; ($i -lt $EntriesRead); $i++) {
- # create a new int ptr at the given offset and cast the pointer as our result structure
- $NewIntPtr = New-Object System.Intptr -ArgumentList $Offset
- $Info = $NewIntPtr -as $SHARE_INFO_1
-
- # return all the sections of the structure
- $Shares = $Info | Select-Object *
- $Shares | Add-Member Noteproperty 'ComputerName' $Computer
- $Offset = $NewIntPtr.ToInt64()
- $Offset += $Increment
- $Shares
- }
-
- # free up the result buffer
- $Null = $Netapi32::NetApiBufferFree($PtrInfo)
- }
- else {
- Write-Verbose "Error: $(([ComponentModel.Win32Exception] $Result).Message)"
- }
-}
-
-
-filter Get-NetLoggedon {
-<#
- .SYNOPSIS
-
- This function will execute the NetWkstaUserEnum Win32API call to query
- a given host for actively logged on users.
-
- .PARAMETER ComputerName
-
- The hostname to query for logged on users.
-
- .OUTPUTS
-
- WKSTA_USER_INFO_1 structure. A representation of the WKSTA_USER_INFO_1
- result structure which includes the username and domain of logged on users,
- with the ComputerName added.
-
- .EXAMPLE
-
- PS C:\> Get-NetLoggedon
-
- Returns users actively logged onto the local host.
-
- .EXAMPLE
-
- PS C:\> Get-NetLoggedon -ComputerName sqlserver
-
- Returns users actively logged onto the 'sqlserver' host.
-
- .EXAMPLE
-
- PS C:\> Get-NetComputer | Get-NetLoggedon
-
- Returns all logged on userse for all computers in the domain.
-
- .LINK
-
- http://www.powershellmagazine.com/2014/09/25/easily-defining-enums-structs-and-win32-functions-in-memory/
-#>
-
- [CmdletBinding()]
- param(
- [Parameter(ValueFromPipeline=$True)]
- [Alias('HostName')]
- [Object[]]
- [ValidateNotNullOrEmpty()]
- $ComputerName = 'localhost'
- )
-
- # extract the computer name from whatever object was passed on the pipeline
- $Computer = $ComputerName | Get-NameField
-
- # Declare the reference variables
- $QueryLevel = 1
- $PtrInfo = [IntPtr]::Zero
- $EntriesRead = 0
- $TotalRead = 0
- $ResumeHandle = 0
-
- # get logged on user information
- $Result = $Netapi32::NetWkstaUserEnum($Computer, $QueryLevel, [ref]$PtrInfo, -1, [ref]$EntriesRead, [ref]$TotalRead, [ref]$ResumeHandle)
-
- # Locate the offset of the initial intPtr
- $Offset = $PtrInfo.ToInt64()
-
- # 0 = success
- if (($Result -eq 0) -and ($Offset -gt 0)) {
-
- # Work out how mutch to increment the pointer by finding out the size of the structure
- $Increment = $WKSTA_USER_INFO_1::GetSize()
-
- # parse all the result structures
- for ($i = 0; ($i -lt $EntriesRead); $i++) {
- # create a new int ptr at the given offset and cast the pointer as our result structure
- $NewIntPtr = New-Object System.Intptr -ArgumentList $Offset
- $Info = $NewIntPtr -as $WKSTA_USER_INFO_1
-
- # return all the sections of the structure
- $LoggedOn = $Info | Select-Object *
- $LoggedOn | Add-Member Noteproperty 'ComputerName' $Computer
- $Offset = $NewIntPtr.ToInt64()
- $Offset += $Increment
- $LoggedOn
- }
-
- # free up the result buffer
- $Null = $Netapi32::NetApiBufferFree($PtrInfo)
- }
- else {
- Write-Verbose "Error: $(([ComponentModel.Win32Exception] $Result).Message)"
- }
-}
-
-
-filter Get-NetSession {
-<#
- .SYNOPSIS
-
- This function will execute the NetSessionEnum Win32API call to query
- a given host for active sessions on the host.
- Heavily adapted from dunedinite's post on stackoverflow (see LINK below)
-
- .PARAMETER ComputerName
-
- The ComputerName to query for active sessions.
-
- .PARAMETER UserName
-
- The user name to filter for active sessions.
-
- .OUTPUTS
-
- SESSION_INFO_10 structure. A representation of the SESSION_INFO_10
- result structure which includes the host and username associated
- with active sessions, with the ComputerName added.
-
- .EXAMPLE
-
- PS C:\> Get-NetSession
-
- Returns active sessions on the local host.
-
- .EXAMPLE
-
- PS C:\> Get-NetSession -ComputerName sqlserver
-
- Returns active sessions on the 'sqlserver' host.
-
- .EXAMPLE
-
- PS C:\> Get-NetDomainController | Get-NetSession
-
- Returns active sessions on all domain controllers.
-
- .LINK
-
- http://www.powershellmagazine.com/2014/09/25/easily-defining-enums-structs-and-win32-functions-in-memory/
-#>
-
- [CmdletBinding()]
- param(
- [Parameter(ValueFromPipeline=$True)]
- [Alias('HostName')]
- [Object[]]
- [ValidateNotNullOrEmpty()]
- $ComputerName = 'localhost',
-
- [String]
- $UserName = ''
- )
-
- # extract the computer name from whatever object was passed on the pipeline
- $Computer = $ComputerName | Get-NameField
-
- # arguments for NetSessionEnum
- $QueryLevel = 10
- $PtrInfo = [IntPtr]::Zero
- $EntriesRead = 0
- $TotalRead = 0
- $ResumeHandle = 0
-
- # get session information
- $Result = $Netapi32::NetSessionEnum($Computer, '', $UserName, $QueryLevel, [ref]$PtrInfo, -1, [ref]$EntriesRead, [ref]$TotalRead, [ref]$ResumeHandle)
-
- # Locate the offset of the initial intPtr
- $Offset = $PtrInfo.ToInt64()
-
- # 0 = success
- if (($Result -eq 0) -and ($Offset -gt 0)) {
-
- # Work out how mutch to increment the pointer by finding out the size of the structure
- $Increment = $SESSION_INFO_10::GetSize()
-
- # parse all the result structures
- for ($i = 0; ($i -lt $EntriesRead); $i++) {
- # create a new int ptr at the given offset and cast the pointer as our result structure
- $NewIntPtr = New-Object System.Intptr -ArgumentList $Offset
- $Info = $NewIntPtr -as $SESSION_INFO_10
-
- # return all the sections of the structure
- $Sessions = $Info | Select-Object *
- $Sessions | Add-Member Noteproperty 'ComputerName' $Computer
- $Offset = $NewIntPtr.ToInt64()
- $Offset += $Increment
- $Sessions
- }
- # free up the result buffer
- $Null = $Netapi32::NetApiBufferFree($PtrInfo)
- }
- else {
- Write-Verbose "Error: $(([ComponentModel.Win32Exception] $Result).Message)"
- }
-}
-
-
-filter Get-LoggedOnLocal {
-<#
- .SYNOPSIS
-
- This function will query the HKU registry values to retrieve the local
- logged on users SID and then attempt and reverse it.
- Adapted technique from Sysinternal's PSLoggedOn script. Benefit over
- using the NetWkstaUserEnum API (Get-NetLoggedon) of less user privileges
- required (NetWkstaUserEnum requires remote admin access).
-
- Note: This function requires only domain user rights on the
- machine you're enumerating, but remote registry must be enabled.
-
- Function: Get-LoggedOnLocal
- Author: Matt Kelly, @BreakersAll
-
- .PARAMETER ComputerName
-
- The ComputerName to query for active sessions.
-
- .EXAMPLE
-
- PS C:\> Get-LoggedOnLocal
-
- Returns active sessions on the local host.
-
- .EXAMPLE
-
- PS C:\> Get-LoggedOnLocal -ComputerName sqlserver
-
- Returns active sessions on the 'sqlserver' host.
-
-#>
-
- [CmdletBinding()]
- param(
- [Parameter(ValueFromPipeline=$True)]
- [Alias('HostName')]
- [Object[]]
- [ValidateNotNullOrEmpty()]
- $ComputerName = 'localhost'
- )
-
- # process multiple host object types from the pipeline
- $ComputerName = Get-NameField -Object $ComputerName
-
- try {
- # retrieve HKU remote registry values
- $Reg = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey('Users', "$ComputerName")
-
- # sort out bogus sid's like _class
- $Reg.GetSubKeyNames() | Where-Object { $_ -match 'S-1-5-21-[0-9]+-[0-9]+-[0-9]+-[0-9]+$' } | ForEach-Object {
- $UserName = Convert-SidToName $_
-
- $Parts = $UserName.Split('\')
- $UserDomain = $Null
- $UserName = $Parts[-1]
- if ($Parts.Length -eq 2) {
- $UserDomain = $Parts[0]
- }
-
- $LocalLoggedOnUser = New-Object PSObject
- $LocalLoggedOnUser | Add-Member Noteproperty 'ComputerName' "$ComputerName"
- $LocalLoggedOnUser | Add-Member Noteproperty 'UserDomain' $UserDomain
- $LocalLoggedOnUser | Add-Member Noteproperty 'UserName' $UserName
- $LocalLoggedOnUser | Add-Member Noteproperty 'UserSID' $_
- $LocalLoggedOnUser
- }
- }
- catch {
- Write-Verbose "Error opening remote registry on '$ComputerName'"
- }
-}
-
-
-filter Get-NetRDPSession {
-<#
- .SYNOPSIS
-
- This function will execute the WTSEnumerateSessionsEx and
- WTSQuerySessionInformation Win32API calls to query a given
- RDP remote service for active sessions and originating IPs.
- This is a replacement for qwinsta.
-
- Note: only members of the Administrators or Account Operators local group
- can successfully execute this functionality on a remote target.
-
- .PARAMETER ComputerName
-
- The hostname to query for active RDP sessions.
-
- .EXAMPLE
-
- PS C:\> Get-NetRDPSession
-
- Returns active RDP/terminal sessions on the local host.
-
- .EXAMPLE
-
- PS C:\> Get-NetRDPSession -ComputerName "sqlserver"
-
- Returns active RDP/terminal sessions on the 'sqlserver' host.
-
- .EXAMPLE
-
- PS C:\> Get-NetDomainController | Get-NetRDPSession
-
- Returns active RDP/terminal sessions on all domain controllers.
-#>
-
- [CmdletBinding()]
- param(
- [Parameter(ValueFromPipeline=$True)]
- [Alias('HostName')]
- [Object[]]
- [ValidateNotNullOrEmpty()]
- $ComputerName = 'localhost'
- )
-
- # extract the computer name from whatever object was passed on the pipeline
- $Computer = $ComputerName | Get-NameField
-
- # open up a handle to the Remote Desktop Session host
- $Handle = $Wtsapi32::WTSOpenServerEx($Computer)
-
- # if we get a non-zero handle back, everything was successful
- if ($Handle -ne 0) {
-
- # arguments for WTSEnumerateSessionsEx
- $ppSessionInfo = [IntPtr]::Zero
- $pCount = 0
-
- # get information on all current sessions
- $Result = $Wtsapi32::WTSEnumerateSessionsEx($Handle, [ref]1, 0, [ref]$ppSessionInfo, [ref]$pCount);$LastError = [Runtime.InteropServices.Marshal]::GetLastWin32Error()
-
- # Locate the offset of the initial intPtr
- $Offset = $ppSessionInfo.ToInt64()
-
- if (($Result -ne 0) -and ($Offset -gt 0)) {
-
- # Work out how mutch to increment the pointer by finding out the size of the structure
- $Increment = $WTS_SESSION_INFO_1::GetSize()
-
- # parse all the result structures
- for ($i = 0; ($i -lt $pCount); $i++) {
-
- # create a new int ptr at the given offset and cast the pointer as our result structure
- $NewIntPtr = New-Object System.Intptr -ArgumentList $Offset
- $Info = $NewIntPtr -as $WTS_SESSION_INFO_1
-
- $RDPSession = New-Object PSObject
-
- if ($Info.pHostName) {
- $RDPSession | Add-Member Noteproperty 'ComputerName' $Info.pHostName
- }
- else {
- # if no hostname returned, use the specified hostname
- $RDPSession | Add-Member Noteproperty 'ComputerName' $Computer
- }
-
- $RDPSession | Add-Member Noteproperty 'SessionName' $Info.pSessionName
-
- if ($(-not $Info.pDomainName) -or ($Info.pDomainName -eq '')) {
- # if a domain isn't returned just use the username
- $RDPSession | Add-Member Noteproperty 'UserName' "$($Info.pUserName)"
- }
- else {
- $RDPSession | Add-Member Noteproperty 'UserName' "$($Info.pDomainName)\$($Info.pUserName)"
- }
-
- $RDPSession | Add-Member Noteproperty 'ID' $Info.SessionID
- $RDPSession | Add-Member Noteproperty 'State' $Info.State
-
- $ppBuffer = [IntPtr]::Zero
- $pBytesReturned = 0
-
- # query for the source client IP with WTSQuerySessionInformation
- # https://msdn.microsoft.com/en-us/library/aa383861(v=vs.85).aspx
- $Result2 = $Wtsapi32::WTSQuerySessionInformation($Handle, $Info.SessionID, 14, [ref]$ppBuffer, [ref]$pBytesReturned);$LastError2 = [Runtime.InteropServices.Marshal]::GetLastWin32Error()
-
- if($Result -eq 0) {
- Write-Verbose "Error: $(([ComponentModel.Win32Exception] $LastError2).Message)"
- }
- else {
- $Offset2 = $ppBuffer.ToInt64()
- $NewIntPtr2 = New-Object System.Intptr -ArgumentList $Offset2
- $Info2 = $NewIntPtr2 -as $WTS_CLIENT_ADDRESS
-
- $SourceIP = $Info2.Address
- if($SourceIP[2] -ne 0) {
- $SourceIP = [String]$SourceIP[2]+"."+[String]$SourceIP[3]+"."+[String]$SourceIP[4]+"."+[String]$SourceIP[5]
- }
- else {
- $SourceIP = $Null
- }
-
- $RDPSession | Add-Member Noteproperty 'SourceIP' $SourceIP
- $RDPSession
-
- # free up the memory buffer
- $Null = $Wtsapi32::WTSFreeMemory($ppBuffer)
-
- $Offset += $Increment
- }
- }
- # free up the memory result buffer
- $Null = $Wtsapi32::WTSFreeMemoryEx(2, $ppSessionInfo, $pCount)
- }
- else {
- Write-Verbose "Error: $(([ComponentModel.Win32Exception] $LastError).Message)"
- }
- # Close off the service handle
- $Null = $Wtsapi32::WTSCloseServer($Handle)
- }
- else {
- Write-Verbose "Error opening the Remote Desktop Session Host (RD Session Host) server for: $ComputerName"
- }
-}
-
-
-filter Invoke-CheckLocalAdminAccess {
-<#
- .SYNOPSIS
-
- This function will use the OpenSCManagerW Win32API call to establish
- a handle to the remote host. If this succeeds, the current user context
- has local administrator acess to the target.
-
- Idea stolen from the local_admin_search_enum post module in Metasploit written by:
- 'Brandon McCann "zeknox" '
- 'Thomas McCarthy "smilingraccoon" '
- 'Royce Davis "r3dy" '
-
- .PARAMETER ComputerName
-
- The hostname to query for active sessions.
-
- .OUTPUTS
-
- $True if the current user has local admin access to the hostname, $False otherwise
-
- .EXAMPLE
-
- PS C:\> Invoke-CheckLocalAdminAccess -ComputerName sqlserver
-
- Returns active sessions on the local host.
-
- .EXAMPLE
-
- PS C:\> Get-NetComputer | Invoke-CheckLocalAdminAccess
-
- Sees what machines in the domain the current user has access to.
-
- .LINK
-
- https://github.com/rapid7/metasploit-framework/blob/master/modules/post/windows/gather/local_admin_search_enum.rb
- http://www.powershellmagazine.com/2014/09/25/easily-defining-enums-structs-and-win32-functions-in-memory/
-#>
-
- [CmdletBinding()]
- param(
- [Parameter(ValueFromPipeline=$True)]
- [Alias('HostName')]
- [Object[]]
- [ValidateNotNullOrEmpty()]
- $ComputerName = 'localhost'
- )
-
- # extract the computer name from whatever object was passed on the pipeline
- $Computer = $ComputerName | Get-NameField
-
- # 0xF003F - SC_MANAGER_ALL_ACCESS
- # http://msdn.microsoft.com/en-us/library/windows/desktop/ms685981(v=vs.85).aspx
- $Handle = $Advapi32::OpenSCManagerW("\\$Computer", 'ServicesActive', 0xF003F);$LastError = [Runtime.InteropServices.Marshal]::GetLastWin32Error()
-
- Write-Verbose "Invoke-CheckLocalAdminAccess handle: $Handle"
-
- $IsAdmin = New-Object PSObject
- $IsAdmin | Add-Member Noteproperty 'ComputerName' $Computer
-
- # if we get a non-zero handle back, everything was successful
- if ($Handle -ne 0) {
- $Null = $Advapi32::CloseServiceHandle($Handle)
- $IsAdmin | Add-Member Noteproperty 'IsAdmin' $True
- }
- else {
- Write-Verbose "Error: $(([ComponentModel.Win32Exception] $LastError).Message)"
- $IsAdmin | Add-Member Noteproperty 'IsAdmin' $False
- }
-
- $IsAdmin
-}
-
-
-filter Get-SiteName {
-<#
- .SYNOPSIS
-
- This function will use the DsGetSiteName Win32API call to look up the
- name of the site where a specified computer resides.
-
- .PARAMETER ComputerName
-
- The hostname to look the site up for, default to localhost.
-
- .EXAMPLE
-
- PS C:\> Get-SiteName -ComputerName WINDOWS1
-
- Returns the site for WINDOWS1.testlab.local.
-
- .EXAMPLE
-
- PS C:\> Get-NetComputer | Invoke-CheckLocalAdminAccess
-
- Returns the sites for every machine in AD.
-#>
- [CmdletBinding()]
- param(
- [Parameter(ValueFromPipeline=$True)]
- [Alias('HostName')]
- [Object[]]
- [ValidateNotNullOrEmpty()]
- $ComputerName = $Env:ComputerName
- )
-
- # extract the computer name from whatever object was passed on the pipeline
- $Computer = $ComputerName | Get-NameField
-
- # if we get an IP address, try to resolve the IP to a hostname
- if($Computer -match '^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$') {
- $IPAddress = $Computer
- $Computer = [System.Net.Dns]::GetHostByAddress($Computer)
- }
- else {
- $IPAddress = @(Get-IPAddress -ComputerName $Computer)[0].IPAddress
- }
-
- $PtrInfo = [IntPtr]::Zero
-
- $Result = $Netapi32::DsGetSiteName($Computer, [ref]$PtrInfo)
-
- $ComputerSite = New-Object PSObject
- $ComputerSite | Add-Member Noteproperty 'ComputerName' $Computer
- $ComputerSite | Add-Member Noteproperty 'IPAddress' $IPAddress
-
- if ($Result -eq 0) {
- $Sitename = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($PtrInfo)
- $ComputerSite | Add-Member Noteproperty 'SiteName' $Sitename
- }
- else {
- $ErrorMessage = "Error: $(([ComponentModel.Win32Exception] $Result).Message)"
- $ComputerSite | Add-Member Noteproperty 'SiteName' $ErrorMessage
- }
-
- $Null = $Netapi32::NetApiBufferFree($PtrInfo)
-
- $ComputerSite
-}
-
-
-filter Get-LastLoggedOn {
-<#
- .SYNOPSIS
-
- This function uses remote registry functionality to return
- the last user logged onto a target machine.
-
- Note: This function requires administrative rights on the
- machine you're enumerating.
-
- .PARAMETER ComputerName
-
- The hostname to query for the last logged on user.
- Defaults to the localhost.
-
- .PARAMETER Credential
-
- A [Management.Automation.PSCredential] object for the remote connection.
-
- .EXAMPLE
-
- PS C:\> Get-LastLoggedOn
-
- Returns the last user logged onto the local machine.
-
- .EXAMPLE
-
- PS C:\> Get-LastLoggedOn -ComputerName WINDOWS1
-
- Returns the last user logged onto WINDOWS1
-
- .EXAMPLE
-
- PS C:\> Get-NetComputer | Get-LastLoggedOn
-
- Returns the last user logged onto all machines in the domain.
-#>
-
- [CmdletBinding()]
- param(
- [Parameter(ValueFromPipeline=$True)]
- [Alias('HostName')]
- [Object[]]
- [ValidateNotNullOrEmpty()]
- $ComputerName = 'localhost',
-
- [Management.Automation.PSCredential]
- $Credential
- )
-
- # extract the computer name from whatever object was passed on the pipeline
- $Computer = $ComputerName | Get-NameField
-
- # HKEY_LOCAL_MACHINE
- $HKLM = 2147483650
-
- # try to open up the remote registry key to grab the last logged on user
- try {
-
- if($Credential) {
- $Reg = Get-WmiObject -List 'StdRegProv' -Namespace root\default -Computername $Computer -Credential $Credential -ErrorAction SilentlyContinue
- }
- else {
- $Reg = Get-WmiObject -List 'StdRegProv' -Namespace root\default -Computername $Computer -ErrorAction SilentlyContinue
- }
-
- $Key = "SOFTWARE\Microsoft\Windows\CurrentVersion\Authentication\LogonUI"
- $Value = "LastLoggedOnUser"
- $LastUser = $Reg.GetStringValue($HKLM, $Key, $Value).sValue
-
- $LastLoggedOn = New-Object PSObject
- $LastLoggedOn | Add-Member Noteproperty 'ComputerName' $Computer
- $LastLoggedOn | Add-Member Noteproperty 'LastLoggedOn' $LastUser
- $LastLoggedOn
- }
- catch {
- Write-Warning "[!] Error opening remote registry on $Computer. Remote registry likely not enabled."
- }
-}
-
-
-filter Get-CachedRDPConnection {
-<#
- .SYNOPSIS
-
- Uses remote registry functionality to query all entries for the
- "Windows Remote Desktop Connection Client" on a machine, separated by
- user and target server.
-
- Note: This function requires administrative rights on the
- machine you're enumerating.
-
- .PARAMETER ComputerName
-
- The hostname to query for RDP client information.
- Defaults to localhost.
-
- .PARAMETER Credential
-
- A [Management.Automation.PSCredential] object for the remote connection.
-
- .EXAMPLE
-
- PS C:\> Get-CachedRDPConnection
-
- Returns the RDP connection client information for the local machine.
-
- .EXAMPLE
-
- PS C:\> Get-CachedRDPConnection -ComputerName WINDOWS2.testlab.local
-
- Returns the RDP connection client information for the WINDOWS2.testlab.local machine
-
- .EXAMPLE
-
- PS C:\> Get-CachedRDPConnection -ComputerName WINDOWS2.testlab.local -Credential $Cred
-
- Returns the RDP connection client information for the WINDOWS2.testlab.local machine using alternate credentials.
-
- .EXAMPLE
-
- PS C:\> Get-NetComputer | Get-CachedRDPConnection
-
- Get cached RDP information for all machines in the domain.
-#>
-
- [CmdletBinding()]
- param(
- [Parameter(ValueFromPipeline=$True)]
- [Alias('HostName')]
- [Object[]]
- [ValidateNotNullOrEmpty()]
- $ComputerName = 'localhost',
-
- [Management.Automation.PSCredential]
- $Credential
- )
-
- # extract the computer name from whatever object was passed on the pipeline
- $Computer = $ComputerName | Get-NameField
-
- # HKEY_USERS
- $HKU = 2147483651
-
- try {
- if($Credential) {
- $Reg = Get-WmiObject -List 'StdRegProv' -Namespace root\default -Computername $Computer -Credential $Credential -ErrorAction SilentlyContinue
- }
- else {
- $Reg = Get-WmiObject -List 'StdRegProv' -Namespace root\default -Computername $Computer -ErrorAction SilentlyContinue
- }
-
- # extract out the SIDs of domain users in this hive
- $UserSIDs = ($Reg.EnumKey($HKU, "")).sNames | ? { $_ -match 'S-1-5-21-[0-9]+-[0-9]+-[0-9]+-[0-9]+$' }
-
- foreach ($UserSID in $UserSIDs) {
-
- try {
- $UserName = Convert-SidToName $UserSID
-
- # pull out all the cached RDP connections
- $ConnectionKeys = $Reg.EnumValues($HKU,"$UserSID\Software\Microsoft\Terminal Server Client\Default").sNames
-
- foreach ($Connection in $ConnectionKeys) {
- # make sure this key is a cached connection
- if($Connection -match 'MRU.*') {
- $TargetServer = $Reg.GetStringValue($HKU, "$UserSID\Software\Microsoft\Terminal Server Client\Default", $Connection).sValue
-
- $FoundConnection = New-Object PSObject
- $FoundConnection | Add-Member Noteproperty 'ComputerName' $Computer
- $FoundConnection | Add-Member Noteproperty 'UserName' $UserName
- $FoundConnection | Add-Member Noteproperty 'UserSID' $UserSID
- $FoundConnection | Add-Member Noteproperty 'TargetServer' $TargetServer
- $FoundConnection | Add-Member Noteproperty 'UsernameHint' $Null
- $FoundConnection
- }
- }
-
- # pull out all the cached server info with username hints
- $ServerKeys = $Reg.EnumKey($HKU,"$UserSID\Software\Microsoft\Terminal Server Client\Servers").sNames
-
- foreach ($Server in $ServerKeys) {
-
- $UsernameHint = $Reg.GetStringValue($HKU, "$UserSID\Software\Microsoft\Terminal Server Client\Servers\$Server", 'UsernameHint').sValue
-
- $FoundConnection = New-Object PSObject
- $FoundConnection | Add-Member Noteproperty 'ComputerName' $Computer
- $FoundConnection | Add-Member Noteproperty 'UserName' $UserName
- $FoundConnection | Add-Member Noteproperty 'UserSID' $UserSID
- $FoundConnection | Add-Member Noteproperty 'TargetServer' $Server
- $FoundConnection | Add-Member Noteproperty 'UsernameHint' $UsernameHint
- $FoundConnection
- }
-
- }
- catch {
- Write-Verbose "Error: $_"
- }
- }
-
- }
- catch {
- Write-Warning "Error accessing $Computer, likely insufficient permissions or firewall rules on host: $_"
- }
-}
-
-
-filter Get-RegistryMountedDrive {
-<#
- .SYNOPSIS
-
- Uses remote registry functionality to query all entries for the
- the saved network mounted drive on a machine, separated by
- user and target server.
-
- Note: This function requires administrative rights on the
- machine you're enumerating.
-
- .PARAMETER ComputerName
-
- The hostname to query for RDP client information.
- Defaults to localhost.
-
- .PARAMETER Credential
-
- A [Management.Automation.PSCredential] object for the remote connection.
-
- .EXAMPLE
-
- PS C:\> Get-RegistryMountedDrive
-
- Returns the saved network mounted drives for the local machine.
-
- .EXAMPLE
-
- PS C:\> Get-RegistryMountedDrive -ComputerName WINDOWS2.testlab.local
-
- Returns the saved network mounted drives for the WINDOWS2.testlab.local machine
-
- .EXAMPLE
-
- PS C:\> Get-RegistryMountedDrive -ComputerName WINDOWS2.testlab.local -Credential $Cred
-
- Returns the saved network mounted drives for the WINDOWS2.testlab.local machine using alternate credentials.
-
- .EXAMPLE
-
- PS C:\> Get-NetComputer | Get-RegistryMountedDrive
-
- Get the saved network mounted drives for all machines in the domain.
-#>
-
- [CmdletBinding()]
- param(
- [Parameter(ValueFromPipeline=$True)]
- [Alias('HostName')]
- [Object[]]
- [ValidateNotNullOrEmpty()]
- $ComputerName = 'localhost',
-
- [Management.Automation.PSCredential]
- $Credential
- )
-
- # extract the computer name from whatever object was passed on the pipeline
- $Computer = $ComputerName | Get-NameField
-
- # HKEY_USERS
- $HKU = 2147483651
-
- try {
- if($Credential) {
- $Reg = Get-WmiObject -List 'StdRegProv' -Namespace root\default -Computername $Computer -Credential $Credential -ErrorAction SilentlyContinue
- }
- else {
- $Reg = Get-WmiObject -List 'StdRegProv' -Namespace root\default -Computername $Computer -ErrorAction SilentlyContinue
- }
-
- # extract out the SIDs of domain users in this hive
- $UserSIDs = ($Reg.EnumKey($HKU, "")).sNames | ? { $_ -match 'S-1-5-21-[0-9]+-[0-9]+-[0-9]+-[0-9]+$' }
-
- foreach ($UserSID in $UserSIDs) {
-
- try {
- $UserName = Convert-SidToName $UserSID
-
- $DriveLetters = ($Reg.EnumKey($HKU, "$UserSID\Network")).sNames
-
- ForEach($DriveLetter in $DriveLetters) {
- $ProviderName = $Reg.GetStringValue($HKU, "$UserSID\Network\$DriveLetter", 'ProviderName').sValue
- $RemotePath = $Reg.GetStringValue($HKU, "$UserSID\Network\$DriveLetter", 'RemotePath').sValue
- $DriveUserName = $Reg.GetStringValue($HKU, "$UserSID\Network\$DriveLetter", 'UserName').sValue
- if(-not $UserName) { $UserName = '' }
-
- if($RemotePath -and ($RemotePath -ne '')) {
- $MountedDrive = New-Object PSObject
- $MountedDrive | Add-Member Noteproperty 'ComputerName' $Computer
- $MountedDrive | Add-Member Noteproperty 'UserName' $UserName
- $MountedDrive | Add-Member Noteproperty 'UserSID' $UserSID
- $MountedDrive | Add-Member Noteproperty 'DriveLetter' $DriveLetter
- $MountedDrive | Add-Member Noteproperty 'ProviderName' $ProviderName
- $MountedDrive | Add-Member Noteproperty 'RemotePath' $RemotePath
- $MountedDrive | Add-Member Noteproperty 'DriveUserName' $DriveUserName
- $MountedDrive
- }
- }
- }
- catch {
- Write-Verbose "Error: $_"
- }
- }
- }
- catch {
- Write-Warning "Error accessing $Computer, likely insufficient permissions or firewall rules on host: $_"
- }
-}
-
-
-filter Get-NetProcess {
-<#
- .SYNOPSIS
-
- Gets a list of processes/owners on a remote machine.
-
- .PARAMETER ComputerName
-
- The hostname to query processes. Defaults to the local host name.
-
- .PARAMETER Credential
-
- A [Management.Automation.PSCredential] object for the remote connection.
-
- .EXAMPLE
-
- PS C:\> Get-NetProcess -ComputerName WINDOWS1
-
- Returns the current processes for WINDOWS1
-#>
-
- [CmdletBinding()]
- param(
- [Parameter(ValueFromPipeline=$True)]
- [Alias('HostName')]
- [Object[]]
- [ValidateNotNullOrEmpty()]
- $ComputerName = [System.Net.Dns]::GetHostName(),
-
- [Management.Automation.PSCredential]
- $Credential
- )
-
- # extract the computer name from whatever object was passed on the pipeline
- $Computer = $ComputerName | Get-NameField
-
- try {
- if($Credential) {
- $Processes = Get-WMIobject -Class Win32_process -ComputerName $ComputerName -Credential $Credential
- }
- else {
- $Processes = Get-WMIobject -Class Win32_process -ComputerName $ComputerName
- }
-
- $Processes | ForEach-Object {
- $Owner = $_.getowner();
- $Process = New-Object PSObject
- $Process | Add-Member Noteproperty 'ComputerName' $Computer
- $Process | Add-Member Noteproperty 'ProcessName' $_.ProcessName
- $Process | Add-Member Noteproperty 'ProcessID' $_.ProcessID
- $Process | Add-Member Noteproperty 'Domain' $Owner.Domain
- $Process | Add-Member Noteproperty 'User' $Owner.User
- $Process
- }
- }
- catch {
- Write-Verbose "[!] Error enumerating remote processes on $Computer, access likely denied: $_"
- }
-}
-
-
-function Find-InterestingFile {
-<#
- .SYNOPSIS
-
- This function recursively searches a given UNC path for files with
- specific keywords in the name (default of pass, sensitive, secret, admin,
- login and unattend*.xml). The output can be piped out to a csv with the
- -OutFile flag. By default, hidden files/folders are included in search results.
-
- .PARAMETER Path
-
- UNC/local path to recursively search.
-
- .PARAMETER Terms
-
- Terms to search for.
-
- .PARAMETER OfficeDocs
-
- Switch. Search for office documents (*.doc*, *.xls*, *.ppt*)
-
- .PARAMETER FreshEXEs
-
- Switch. Find .EXEs accessed within the last week.
-
- .PARAMETER LastAccessTime
-
- Only return files with a LastAccessTime greater 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 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 OutFile
-
- Output results to a specified csv output file.
-
- .PARAMETER UsePSDrive
-
- Switch. Mount target remote path with temporary PSDrives.
-
- .OUTPUTS
-
- The full path, owner, lastaccess time, lastwrite time, and size for each found file.
-
- .EXAMPLE
-
- PS C:\> 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
-
- PS C:\> Find-InterestingFile -Path \\WINDOWS7\Users\ -Terms salaries,email -OutFile out.csv
-
- Returns any files on the remote path \\WINDOWS7\Users\ that have 'salaries'
- or 'email' in the title, and writes the results out to a csv file
- named 'out.csv'
-
- .EXAMPLE
-
- PS C:\> 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.
-
- .LINK
-
- http://www.harmj0y.net/blog/redteaming/file-server-triage-on-red-team-engagements/
-#>
-
- param(
- [Parameter(ValueFromPipeline=$True)]
- [String]
- $Path = '.\',
-
- [Alias('Terms')]
- [String[]]
- $SearchTerms = @('pass', 'sensitive', 'admin', 'login', 'secret', 'unattend*.xml', '.vmdk', 'creds', 'credential', '.config'),
-
- [Switch]
- $OfficeDocs,
-
- [Switch]
- $FreshEXEs,
-
- [String]
- $LastAccessTime,
-
- [String]
- $LastWriteTime,
-
- [String]
- $CreationTime,
-
- [Switch]
- $ExcludeFolders,
-
- [Switch]
- $ExcludeHidden,
-
- [Switch]
- $CheckWriteAccess,
-
- [String]
- $OutFile,
-
- [Switch]
- $UsePSDrive
- )
-
- begin {
-
- $Path += if(!$Path.EndsWith('\')) {"\"}
-
- if ($Credential) {
- $UsePSDrive = $True
- }
-
- # append wildcards to the front and back of all search terms
- $SearchTerms = $SearchTerms | ForEach-Object { if($_ -notmatch '^\*.*\*$') {"*$($_)*"} else{$_} }
-
- # search just for office documents if specified
- if ($OfficeDocs) {
- $SearchTerms = @('*.doc', '*.docx', '*.xls', '*.xlsx', '*.ppt', '*.pptx')
- }
-
- # find .exe's accessed within the last 7 days
- if($FreshEXEs) {
- # get an access time limit of 7 days ago
- $LastAccessTime = (Get-Date).AddDays(-7).ToString('MM/dd/yyyy')
- $SearchTerms = '*.exe'
- }
-
- if($UsePSDrive) {
- # if we're PSDrives, create a temporary mount point
-
- $Parts = $Path.split('\')
- $FolderPath = $Parts[0..($Parts.length-2)] -join '\'
- $FilePath = $Parts[-1]
-
- $RandDrive = ("abcdefghijklmnopqrstuvwxyz".ToCharArray() | Get-Random -Count 7) -join ''
-
- Write-Verbose "Mounting path '$Path' using a temp PSDrive at $RandDrive"
-
- try {
- $Null = New-PSDrive -Name $RandDrive -PSProvider FileSystem -Root $FolderPath -ErrorAction Stop
- }
- catch {
- Write-Verbose "Error mounting path '$Path' : $_"
- return $Null
- }
-
- # so we can cd/dir the new drive
- $Path = "${RandDrive}:\${FilePath}"
- }
- }
-
- process {
-
- Write-Verbose "[*] Search path $Path"
-
- function Invoke-CheckWrite {
- # 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 {
- Write-Verbose -Message $Error[0]
- $False
- }
- }
-
- $SearchArgs = @{
- 'Path' = $Path
- 'Recurse' = $True
- 'Force' = $(-not $ExcludeHidden)
- 'Include' = $SearchTerms
- 'ErrorAction' = 'SilentlyContinue'
- }
-
- Get-ChildItem @SearchArgs | ForEach-Object {
- Write-Verbose $_
- # check if we're excluding folders
- if(!$ExcludeFolders -or !$_.PSIsContainer) {$_}
- } | ForEach-Object {
- if($LastAccessTime -or $LastWriteTime -or $CreationTime) {
- if($LastAccessTime -and ($_.LastAccessTime -gt $LastAccessTime)) {$_}
- elseif($LastWriteTime -and ($_.LastWriteTime -gt $LastWriteTime)) {$_}
- elseif($CreationTime -and ($_.CreationTime -gt $CreationTime)) {$_}
- }
- else {$_}
- } | ForEach-Object {
- # filter for write access (if applicable)
- if((-not $CheckWriteAccess) -or (Invoke-CheckWrite -Path $_.FullName)) {$_}
- } | Select-Object FullName,@{Name='Owner';Expression={(Get-Acl $_.FullName).Owner}},LastAccessTime,LastWriteTime,CreationTime,Length | ForEach-Object {
- # check if we're outputting to the pipeline or an output file
- if($OutFile) {Export-PowerViewCSV -InputObject $_ -OutFile $OutFile}
- else {$_}
- }
- }
-
- end {
- if($UsePSDrive -and $RandDrive) {
- Write-Verbose "Removing temp PSDrive $RandDrive"
- Get-PSDrive -Name $RandDrive -ErrorAction SilentlyContinue | Remove-PSDrive -Force
- }
- }
-}
-
-
-########################################################
-#
-# 'Meta'-functions start below
-#
-########################################################
-
-function Invoke-ThreadedFunction {
- # Helper used by any threaded host enumeration functions
- [CmdletBinding()]
- param(
- [Parameter(Position=0,Mandatory=$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 {
-
- if ($PSBoundParameters['Debug']) {
- $DebugPreference = 'Continue'
- }
-
- Write-Verbose "[*] Total number of hosts: $($ComputerName.count)"
-
- # 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()
-
- # import the current session state's variables and functions so the chained PowerView
- # functionality can be used by the threaded blocks
- if(!$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()
-
- $Jobs = @()
- $PS = @()
- $Wait = @()
-
- $Counter = 0
- }
-
- process {
-
- ForEach ($Computer in $ComputerName) {
-
- # make sure we get a server name
- if ($Computer -ne '') {
- # Write-Verbose "[*] Enumerating server $Computer ($($Counter+1) of $($ComputerName.count))"
-
- While ($($Pool.GetAvailableRunspaces()) -le 0) {
- Start-Sleep -MilliSeconds 500
- }
-
- # create a "powershell pipeline runner"
- $PS += [powershell]::create()
-
- $PS[$Counter].runspacepool = $Pool
-
- # add the script block + arguments
- $Null = $PS[$Counter].AddScript($ScriptBlock).AddParameter('ComputerName', $Computer)
- if($ScriptParameters) {
- ForEach ($Param in $ScriptParameters.GetEnumerator()) {
- $Null = $PS[$Counter].AddParameter($Param.Name, $Param.Value)
- }
- }
-
- # start job
- $Jobs += $PS[$Counter].BeginInvoke();
-
- # store wait handles for WaitForAll call
- $Wait += $Jobs[$Counter].AsyncWaitHandle
- }
- $Counter = $Counter + 1
- }
- }
-
- end {
-
- Write-Verbose "Waiting for scanning threads to finish..."
-
- $WaitTimeout = Get-Date
-
- # set a 60 second timeout for the scanning threads
- while ($($Jobs | Where-Object {$_.IsCompleted -eq $False}).count -gt 0 -and $($($(Get-Date) - $WaitTimeout).totalSeconds) -lt 60) {
- Start-Sleep -MilliSeconds 500
- }
-
- # end async call
- for ($y = 0; $y -lt $Counter; $y++) {
-
- try {
- # complete async job
- $PS[$y].EndInvoke($Jobs[$y])
-
- } catch {
- Write-Warning "error: $_"
- }
- finally {
- $PS[$y].Dispose()
- }
- }
-
- $Pool.Dispose()
- Write-Verbose "All threads completed!"
- }
-}
-
-
-function Invoke-UserHunter {
-<#
- .SYNOPSIS
-
- Finds which machines users of a specified group are logged into.
-
- Author: @harmj0y
- License: BSD 3-Clause
-
- .DESCRIPTION
-
- This function finds the local domain name for a host using Get-NetDomain,
- queries the domain for users of a specified group (default "domain admins")
- with Get-NetGroupMember or reads in a target user list, queries the domain for all
- active machines with Get-NetComputer or reads in a pre-populated host list,
- randomly shuffles the target list, then for each server it gets a list of
- active users with Get-NetSession/Get-NetLoggedon. The found user list is compared
- against the target list, and a status message is displayed for any hits.
- The flag -CheckAccess will check each positive host to see if the current
- user has local admin access to the machine.
-
- .PARAMETER ComputerName
-
- Host array to enumerate, passable on the pipeline.
-
- .PARAMETER ComputerFile
-
- File of hostnames/IPs to search.
-
- .PARAMETER ComputerFilter
-
- Host filter name to query AD for, wildcards accepted.
-
- .PARAMETER ComputerADSpath
-
- The LDAP source to search through for hosts, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
- Useful for OU queries.
-
- .PARAMETER Unconstrained
-
- Switch. Only enumerate computers that have unconstrained delegation.
-
- .PARAMETER GroupName
-
- Group name to query for target users.
-
- .PARAMETER TargetServer
-
- Hunt for users who are effective local admins on a target server.
-
- .PARAMETER UserName
-
- Specific username to search for.
-
- .PARAMETER UserFilter
-
- A customized ldap filter string to use for user enumeration, e.g. "(description=*admin*)"
-
- .PARAMETER UserADSpath
-
- The LDAP source to search through for users, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
- Useful for OU queries.
-
- .PARAMETER UserFile
-
- File of usernames to search for.
-
- .PARAMETER AdminCount
-
- Switch. Hunt for users with adminCount=1.
-
- .PARAMETER AllowDelegation
-
- Switch. Return user accounts that are not marked as 'sensitive and not allowed for delegation'
-
- .PARAMETER StopOnSuccess
-
- Switch. Stop hunting after finding after finding a target user.
-
- .PARAMETER NoPing
-
- Don't ping each host to ensure it's up before enumerating.
-
- .PARAMETER CheckAccess
-
- Switch. Check if the current user has local admin access to found machines.
-
- .PARAMETER Delay
-
- Delay between enumerating hosts, defaults to 0
-
- .PARAMETER Jitter
-
- Jitter for the host delay, defaults to +/- 0.3
-
- .PARAMETER Domain
-
- Domain for query for machines, defaults to the current domain.
-
- .PARAMETER DomainController
-
- Domain controller to reflect LDAP queries through.
-
- .PARAMETER ShowAll
-
- Switch. Return all user location results, i.e. Invoke-UserView functionality.
-
- .PARAMETER SearchForest
-
- Switch. Search all domains in the forest for target users instead of just
- a single domain.
-
- .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'
-
- .PARAMETER ForeignUsers
-
- Switch. Only return results that are not part of searched domain.
-
- .PARAMETER Threads
-
- The maximum concurrent threads to execute.
-
- .EXAMPLE
-
- PS C:\> Invoke-UserHunter -CheckAccess
-
- Finds machines on the local domain where domain admins are logged into
- and checks if the current user has local administrator access.
-
- .EXAMPLE
-
- PS C:\> Invoke-UserHunter -Domain 'testing'
-
- Finds machines on the 'testing' domain where domain admins are logged into.
-
- .EXAMPLE
-
- PS C:\> Invoke-UserHunter -Threads 20
-
- Multi-threaded user hunting, replaces Invoke-UserHunterThreaded.
-
- .EXAMPLE
-
- PS C:\> Invoke-UserHunter -UserFile users.txt -ComputerFile hosts.txt
-
- Finds machines in hosts.txt where any members of users.txt are logged in
- or have sessions.
-
- .EXAMPLE
-
- PS C:\> Invoke-UserHunter -GroupName "Power Users" -Delay 60
-
- Find machines on the domain where members of the "Power Users" groups are
- logged into with a 60 second (+/- *.3) randomized delay between
- touching each host.
-
- .EXAMPLE
-
- PS C:\> Invoke-UserHunter -TargetServer FILESERVER
-
- Query FILESERVER for useres who are effective local administrators using
- Get-NetLocalGroup -Recurse, and hunt for that user set on the network.
-
- .EXAMPLE
-
- PS C:\> Invoke-UserHunter -SearchForest
-
- Find all machines in the current forest where domain admins are logged in.
-
- .EXAMPLE
-
- PS C:\> Invoke-UserHunter -Stealth
-
- Executes old Invoke-StealthUserHunter functionality, enumerating commonly
- used servers and checking just sessions for each.
-
- .LINK
- http://blog.harmj0y.net
-#>
-
- [CmdletBinding()]
- param(
- [Parameter(Position=0,ValueFromPipeline=$True)]
- [Alias('Hosts')]
- [String[]]
- $ComputerName,
-
- [ValidateScript({Test-Path -Path $_ })]
- [Alias('HostList')]
- [String]
- $ComputerFile,
-
- [String]
- $ComputerFilter,
-
- [String]
- $ComputerADSpath,
-
- [Switch]
- $Unconstrained,
-
- [String]
- $GroupName = 'Domain Admins',
-
- [String]
- $TargetServer,
-
- [String]
- $UserName,
-
- [String]
- $UserFilter,
-
- [String]
- $UserADSpath,
-
- [ValidateScript({Test-Path -Path $_ })]
- [String]
- $UserFile,
-
- [Switch]
- $AdminCount,
-
- [Switch]
- $AllowDelegation,
-
- [Switch]
- $CheckAccess,
-
- [Switch]
- $StopOnSuccess,
-
- [Switch]
- $NoPing,
-
- [UInt32]
- $Delay = 0,
-
- [Double]
- $Jitter = .3,
-
- [String]
- $Domain,
-
- [String]
- $DomainController,
-
- [Switch]
- $ShowAll,
-
- [Switch]
- $SearchForest,
-
- [Switch]
- $Stealth,
-
- [String]
- [ValidateSet("DFS","DC","File","All")]
- $StealthSource ="All",
-
- [Switch]
- $ForeignUsers,
-
- [Int]
- [ValidateRange(1,100)]
- $Threads
- )
-
- begin {
-
- if ($PSBoundParameters['Debug']) {
- $DebugPreference = 'Continue'
- }
-
- # random object for delay
- $RandNo = New-Object System.Random
-
- Write-Verbose "[*] Running Invoke-UserHunter with delay of $Delay"
-
- #####################################################
- #
- # First we build the host target set
- #
- #####################################################
-
- if($ComputerFile) {
- # if we're using a host list, read the targets in and add them to the target list
- $ComputerName = Get-Content -Path $ComputerFile
- }
-
- if(!$ComputerName) {
- [Array]$ComputerName = @()
-
- if($Domain) {
- $TargetDomains = @($Domain)
- }
- elseif($SearchForest) {
- # get ALL the domains in the forest to search
- $TargetDomains = Get-NetForestDomain | ForEach-Object { $_.Name }
- }
- else {
- # use the local domain
- $TargetDomains = @( (Get-NetDomain).name )
- }
-
- if($Stealth) {
- Write-Verbose "Stealth mode! Enumerating commonly used servers"
- Write-Verbose "Stealth source: $StealthSource"
-
- ForEach ($Domain in $TargetDomains) {
- if (($StealthSource -eq "File") -or ($StealthSource -eq "All")) {
- Write-Verbose "[*] Querying domain $Domain for File Servers..."
- $ComputerName += Get-NetFileServer -Domain $Domain -DomainController $DomainController
- }
- if (($StealthSource -eq "DFS") -or ($StealthSource -eq "All")) {
- Write-Verbose "[*] Querying domain $Domain for DFS Servers..."
- $ComputerName += Get-DFSshare -Domain $Domain -DomainController $DomainController | ForEach-Object {$_.RemoteServerName}
- }
- if (($StealthSource -eq "DC") -or ($StealthSource -eq "All")) {
- Write-Verbose "[*] Querying domain $Domain for Domain Controllers..."
- $ComputerName += Get-NetDomainController -LDAP -Domain $Domain -DomainController $DomainController | ForEach-Object { $_.dnshostname}
- }
- }
- }
- else {
- ForEach ($Domain in $TargetDomains) {
- Write-Verbose "[*] Querying domain $Domain for hosts"
-
- $Arguments = @{
- 'Domain' = $Domain
- 'DomainController' = $DomainController
- 'ADSpath' = $ADSpath
- 'Filter' = $ComputerFilter
- 'Unconstrained' = $Unconstrained
- }
-
- $ComputerName += Get-NetComputer @Arguments
- }
- }
-
- # remove any null target hosts, uniquify the list and shuffle it
- $ComputerName = $ComputerName | Where-Object { $_ } | Sort-Object -Unique | Sort-Object { Get-Random }
- if($($ComputerName.Count) -eq 0) {
- throw "No hosts found!"
- }
- }
-
- #####################################################
- #
- # Now we build the user target set
- #
- #####################################################
-
- # users we're going to be searching for
- $TargetUsers = @()
-
- # get the current user so we can ignore it in the results
- $CurrentUser = ([Environment]::UserName).toLower()
-
- # if we're showing all results, skip username enumeration
- if($ShowAll -or $ForeignUsers) {
- $User = New-Object PSObject
- $User | Add-Member Noteproperty 'MemberDomain' $Null
- $User | Add-Member Noteproperty 'MemberName' '*'
- $TargetUsers = @($User)
-
- if($ForeignUsers) {
- # if we're searching for user results not in the primary domain
- $krbtgtName = Convert-ADName -ObjectName "krbtgt@$($Domain)" -InputType Simple -OutputType NT4
- $DomainShortName = $krbtgtName.split("\")[0]
- }
- }
- # if we want to hunt for the effective domain users who can access a target server
- elseif($TargetServer) {
- Write-Verbose "Querying target server '$TargetServer' for local users"
- $TargetUsers = Get-NetLocalGroup $TargetServer -Recurse | Where-Object {(-not $_.IsGroup) -and $_.IsDomain } | ForEach-Object {
- $User = New-Object PSObject
- $User | Add-Member Noteproperty 'MemberDomain' ($_.AccountName).split("/")[0].toLower()
- $User | Add-Member Noteproperty 'MemberName' ($_.AccountName).split("/")[1].toLower()
- $User
- } | Where-Object {$_}
- }
- # if we get a specific username, only use that
- elseif($UserName) {
- Write-Verbose "[*] Using target user '$UserName'..."
- $User = New-Object PSObject
- if($TargetDomains) {
- $User | Add-Member Noteproperty 'MemberDomain' $TargetDomains[0]
- }
- else {
- $User | Add-Member Noteproperty 'MemberDomain' $Null
- }
- $User | Add-Member Noteproperty 'MemberName' $UserName.ToLower()
- $TargetUsers = @($User)
- }
- # read in a target user list if we have one
- elseif($UserFile) {
- $TargetUsers = Get-Content -Path $UserFile | ForEach-Object {
- $User = New-Object PSObject
- if($TargetDomains) {
- $User | Add-Member Noteproperty 'MemberDomain' $TargetDomains[0]
- }
- else {
- $User | Add-Member Noteproperty 'MemberDomain' $Null
- }
- $User | Add-Member Noteproperty 'MemberName' $_
- $User
- } | Where-Object {$_}
- }
- elseif($UserADSpath -or $UserFilter -or $AdminCount) {
- ForEach ($Domain in $TargetDomains) {
-
- $Arguments = @{
- 'Domain' = $Domain
- 'DomainController' = $DomainController
- 'ADSpath' = $UserADSpath
- 'Filter' = $UserFilter
- 'AdminCount' = $AdminCount
- 'AllowDelegation' = $AllowDelegation
- }
-
- Write-Verbose "[*] Querying domain $Domain for users"
- $TargetUsers += Get-NetUser @Arguments | ForEach-Object {
- $User = New-Object PSObject
- $User | Add-Member Noteproperty 'MemberDomain' $Domain
- $User | Add-Member Noteproperty 'MemberName' $_.samaccountname
- $User
- } | Where-Object {$_}
-
- }
- }
- else {
- ForEach ($Domain in $TargetDomains) {
- Write-Verbose "[*] Querying domain $Domain for users of group '$GroupName'"
- $TargetUsers += Get-NetGroupMember -GroupName $GroupName -Domain $Domain -DomainController $DomainController
- }
- }
-
- if (( (-not $ShowAll) -and (-not $ForeignUsers) ) -and ((!$TargetUsers) -or ($TargetUsers.Count -eq 0))) {
- throw "[!] No users found to search for!"
- }
-
- # script block that enumerates a server
- $HostEnumBlock = {
- param($ComputerName, $Ping, $TargetUsers, $CurrentUser, $Stealth, $DomainShortName)
-
- # optionally check if the server is up first
- $Up = $True
- if($Ping) {
- $Up = Test-Connection -Count 1 -Quiet -ComputerName $ComputerName
- }
- if($Up) {
- if(!$DomainShortName) {
- # if we're not searching for foreign users, check session information
- $Sessions = Get-NetSession -ComputerName $ComputerName
- ForEach ($Session in $Sessions) {
- $UserName = $Session.sesi10_username
- $CName = $Session.sesi10_cname
-
- if($CName -and $CName.StartsWith("\\")) {
- $CName = $CName.TrimStart("\")
- }
-
- # make sure we have a result
- if (($UserName) -and ($UserName.trim() -ne '') -and (!($UserName -match $CurrentUser))) {
-
- $TargetUsers | Where-Object {$UserName -like $_.MemberName} | ForEach-Object {
-
- $IPAddress = @(Get-IPAddress -ComputerName $ComputerName)[0].IPAddress
- $FoundUser = New-Object PSObject
- $FoundUser | Add-Member Noteproperty 'UserDomain' $_.MemberDomain
- $FoundUser | Add-Member Noteproperty 'UserName' $UserName
- $FoundUser | Add-Member Noteproperty 'ComputerName' $ComputerName
- $FoundUser | Add-Member Noteproperty 'IPAddress' $IPAddress
- $FoundUser | Add-Member Noteproperty 'SessionFrom' $CName
-
- # Try to resolve the DNS hostname of $Cname
- try {
- $CNameDNSName = [System.Net.Dns]::GetHostEntry($CName) | Select-Object -ExpandProperty HostName
- $FoundUser | Add-Member NoteProperty 'SessionFromName' $CnameDNSName
- }
- catch {
- $FoundUser | Add-Member NoteProperty 'SessionFromName' $Null
- }
-
- # see if we're checking to see if we have local admin access on this machine
- if ($CheckAccess) {
- $Admin = Invoke-CheckLocalAdminAccess -ComputerName $CName
- $FoundUser | Add-Member Noteproperty 'LocalAdmin' $Admin
- }
- else {
- $FoundUser | Add-Member Noteproperty 'LocalAdmin' $Null
- }
- $FoundUser.PSObject.TypeNames.Add('PowerView.UserSession')
- $FoundUser
- }
- }
- }
- }
- if(!$Stealth) {
- # if we're not 'stealthy', enumerate loggedon users as well
- $LoggedOn = Get-NetLoggedon -ComputerName $ComputerName
- ForEach ($User in $LoggedOn) {
- $UserName = $User.wkui1_username
- # TODO: translate domain to authoratative name
- # then match domain name ?
- $UserDomain = $User.wkui1_logon_domain
-
- # make sure wet have a result
- if (($UserName) -and ($UserName.trim() -ne '')) {
-
- $TargetUsers | Where-Object {$UserName -like $_.MemberName} | ForEach-Object {
-
- $Proceed = $True
- if($DomainShortName) {
- if ($DomainShortName.ToLower() -ne $UserDomain.ToLower()) {
- $Proceed = $True
- }
- else {
- $Proceed = $False
- }
- }
- if($Proceed) {
- $IPAddress = @(Get-IPAddress -ComputerName $ComputerName)[0].IPAddress
- $FoundUser = New-Object PSObject
- $FoundUser | Add-Member Noteproperty 'UserDomain' $UserDomain
- $FoundUser | Add-Member Noteproperty 'UserName' $UserName
- $FoundUser | Add-Member Noteproperty 'ComputerName' $ComputerName
- $FoundUser | Add-Member Noteproperty 'IPAddress' $IPAddress
- $FoundUser | Add-Member Noteproperty 'SessionFrom' $Null
- $FoundUser | Add-Member Noteproperty 'SessionFromName' $Null
-
- # see if we're checking to see if we have local admin access on this machine
- if ($CheckAccess) {
- $Admin = Invoke-CheckLocalAdminAccess -ComputerName $ComputerName
- $FoundUser | Add-Member Noteproperty 'LocalAdmin' $Admin
- }
- else {
- $FoundUser | Add-Member Noteproperty 'LocalAdmin' $Null
- }
- $FoundUser.PSObject.TypeNames.Add('PowerView.UserSession')
- $FoundUser
- }
- }
- }
- }
- }
- }
- }
-
- }
-
- process {
-
- if($Threads) {
- Write-Verbose "Using threading with threads = $Threads"
-
- # if we're using threading, kick off the script block with Invoke-ThreadedFunction
- $ScriptParams = @{
- 'Ping' = $(-not $NoPing)
- 'TargetUsers' = $TargetUsers
- 'CurrentUser' = $CurrentUser
- 'Stealth' = $Stealth
- 'DomainShortName' = $DomainShortName
- }
-
- # kick off the threaded script block + arguments
- Invoke-ThreadedFunction -ComputerName $ComputerName -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams -Threads $Threads
- }
-
- else {
- if(-not $NoPing -and ($ComputerName.count -ne 1)) {
- # ping all hosts in parallel
- $Ping = {param($ComputerName) if(Test-Connection -ComputerName $ComputerName -Count 1 -Quiet -ErrorAction Stop){$ComputerName}}
- $ComputerName = Invoke-ThreadedFunction -NoImports -ComputerName $ComputerName -ScriptBlock $Ping -Threads 100
- }
-
- Write-Verbose "[*] Total number of active hosts: $($ComputerName.count)"
- $Counter = 0
-
- ForEach ($Computer in $ComputerName) {
-
- $Counter = $Counter + 1
-
- # sleep for our semi-randomized interval
- Start-Sleep -Seconds $RandNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay)
-
- Write-Verbose "[*] Enumerating server $Computer ($Counter of $($ComputerName.count))"
- $Result = Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList $Computer, $False, $TargetUsers, $CurrentUser, $Stealth, $DomainShortName
- $Result
-
- if($Result -and $StopOnSuccess) {
- Write-Verbose "[*] Target user found, returning early"
- return
- }
- }
- }
-
- }
-}
-
-
-function Invoke-StealthUserHunter {
- [CmdletBinding()]
- param(
- [Parameter(Position=0,ValueFromPipeline=$True)]
- [Alias('Hosts')]
- [String[]]
- $ComputerName,
-
- [ValidateScript({Test-Path -Path $_ })]
- [Alias('HostList')]
- [String]
- $ComputerFile,
-
- [String]
- $ComputerFilter,
-
- [String]
- $ComputerADSpath,
-
- [String]
- $GroupName = 'Domain Admins',
-
- [String]
- $TargetServer,
-
- [String]
- $UserName,
-
- [String]
- $UserFilter,
-
- [String]
- $UserADSpath,
-
- [ValidateScript({Test-Path -Path $_ })]
- [String]
- $UserFile,
-
- [Switch]
- $CheckAccess,
-
- [Switch]
- $StopOnSuccess,
-
- [Switch]
- $NoPing,
-
- [UInt32]
- $Delay = 0,
-
- [Double]
- $Jitter = .3,
-
- [String]
- $Domain,
-
- [Switch]
- $ShowAll,
-
- [Switch]
- $SearchForest,
-
- [String]
- [ValidateSet("DFS","DC","File","All")]
- $StealthSource ="All"
- )
- # kick off Invoke-UserHunter with stealth options
- Invoke-UserHunter -Stealth @PSBoundParameters
-}
-
-
-function Invoke-ProcessHunter {
-<#
- .SYNOPSIS
-
- Query the process lists of remote machines, searching for
- processes with a specific name or owned by a specific user.
- Thanks to @paulbrandau for the approach idea.
-
- Author: @harmj0y
- License: BSD 3-Clause
-
- .PARAMETER ComputerName
-
- Host array to enumerate, passable on the pipeline.
-
- .PARAMETER ComputerFile
-
- File of hostnames/IPs to search.
-
- .PARAMETER ComputerFilter
-
- Host filter name to query AD for, wildcards accepted.
-
- .PARAMETER ComputerADSpath
-
- The LDAP source to search through for hosts, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
- Useful for OU queries.
-
- .PARAMETER ProcessName
-
- The name of the process to hunt, or a comma separated list of names.
-
- .PARAMETER GroupName
-
- Group name to query for target users.
-
- .PARAMETER TargetServer
-
- Hunt for users who are effective local admins on a target server.
-
- .PARAMETER UserName
-
- Specific username to search for.
-
- .PARAMETER UserFilter
-
- A customized ldap filter string to use for user enumeration, e.g. "(description=*admin*)"
-
- .PARAMETER UserADSpath
-
- The LDAP source to search through for users, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
- Useful for OU queries.
-
- .PARAMETER UserFile
-
- File of usernames to search for.
-
- .PARAMETER StopOnSuccess
-
- Switch. Stop hunting after finding after finding a target user/process.
-
- .PARAMETER NoPing
-
- Switch. Don't ping each host to ensure it's up before enumerating.
-
- .PARAMETER Delay
-
- Delay between enumerating hosts, defaults to 0
-
- .PARAMETER Jitter
-
- Jitter for the host delay, defaults to +/- 0.3
-
- .PARAMETER Domain
-
- Domain for query for machines, defaults to the current domain.
-
- .PARAMETER DomainController
-
- Domain controller to reflect LDAP queries through.
-
- .PARAMETER ShowAll
-
- Switch. Return all user location results.
-
- .PARAMETER SearchForest
-
- Switch. Search all domains in the forest for target users instead of just
- a single domain.
-
- .PARAMETER Threads
-
- The maximum concurrent threads to execute.
-
- .PARAMETER Credential
-
- A [Management.Automation.PSCredential] object of alternate credentials
- for connection to the target machine/domain.
-
- .EXAMPLE
-
- PS C:\> Invoke-ProcessHunter -Domain 'testing'
-
- Finds machines on the 'testing' domain where domain admins have a
- running process.
-
- .EXAMPLE
-
- PS C:\> Invoke-ProcessHunter -Threads 20
-
- Multi-threaded process hunting, replaces Invoke-ProcessHunterThreaded.
-
- .EXAMPLE
-
- PS C:\> Invoke-ProcessHunter -UserFile users.txt -ComputerFile hosts.txt
-
- Finds machines in hosts.txt where any members of users.txt have running
- processes.
-
- .EXAMPLE
-
- PS C:\> Invoke-ProcessHunter -GroupName "Power Users" -Delay 60
-
- Find machines on the domain where members of the "Power Users" groups have
- running processes with a 60 second (+/- *.3) randomized delay between
- touching each host.
-
- .LINK
-
- http://blog.harmj0y.net
-#>
-
- [CmdletBinding()]
- param(
- [Parameter(Position=0,ValueFromPipeline=$True)]
- [Alias('Hosts')]
- [String[]]
- $ComputerName,
-
- [ValidateScript({Test-Path -Path $_ })]
- [Alias('HostList')]
- [String]
- $ComputerFile,
-
- [String]
- $ComputerFilter,
-
- [String]
- $ComputerADSpath,
-
- [String]
- $ProcessName,
-
- [String]
- $GroupName = 'Domain Admins',
-
- [String]
- $TargetServer,
-
- [String]
- $UserName,
-
- [String]
- $UserFilter,
-
- [String]
- $UserADSpath,
-
- [ValidateScript({Test-Path -Path $_ })]
- [String]
- $UserFile,
-
- [Switch]
- $StopOnSuccess,
-
- [Switch]
- $NoPing,
-
- [UInt32]
- $Delay = 0,
-
- [Double]
- $Jitter = .3,
-
- [String]
- $Domain,
-
- [String]
- $DomainController,
-
- [Switch]
- $ShowAll,
-
- [Switch]
- $SearchForest,
-
- [ValidateRange(1,100)]
- [Int]
- $Threads,
-
- [Management.Automation.PSCredential]
- $Credential
- )
-
- begin {
-
- if ($PSBoundParameters['Debug']) {
- $DebugPreference = 'Continue'
- }
-
- # random object for delay
- $RandNo = New-Object System.Random
-
- Write-Verbose "[*] Running Invoke-ProcessHunter with delay of $Delay"
-
- #####################################################
- #
- # First we build the host target set
- #
- #####################################################
-
- # if we're using a host list, read the targets in and add them to the target list
- if($ComputerFile) {
- $ComputerName = Get-Content -Path $ComputerFile
- }
-
- if(!$ComputerName) {
- [array]$ComputerName = @()
-
- if($Domain) {
- $TargetDomains = @($Domain)
- }
- elseif($SearchForest) {
- # get ALL the domains in the forest to search
- $TargetDomains = Get-NetForestDomain -DomainController $DomainController -Credential $Credential | ForEach-Object { $_.Name }
- }
- else {
- # use the local domain
- $TargetDomains = @( (Get-NetDomain -Domain $Domain -Credential $Credential).name )
- }
-
- ForEach ($Domain in $TargetDomains) {
- Write-Verbose "[*] Querying domain $Domain for hosts"
- $ComputerName += Get-NetComputer -Domain $Domain -DomainController $DomainController -Credential $Credential -Filter $ComputerFilter -ADSpath $ComputerADSpath
- }
-
- # remove any null target hosts, uniquify the list and shuffle it
- $ComputerName = $ComputerName | Where-Object { $_ } | Sort-Object -Unique | Sort-Object { Get-Random }
- if($($ComputerName.Count) -eq 0) {
- throw "No hosts found!"
- }
- }
-
- #####################################################
- #
- # Now we build the user target set
- #
- #####################################################
-
- if(!$ProcessName) {
- Write-Verbose "No process name specified, building a target user set"
-
- # users we're going to be searching for
- $TargetUsers = @()
-
- # if we want to hunt for the effective domain users who can access a target server
- if($TargetServer) {
- Write-Verbose "Querying target server '$TargetServer' for local users"
- $TargetUsers = Get-NetLocalGroup $TargetServer -Recurse | Where-Object {(-not $_.IsGroup) -and $_.IsDomain } | ForEach-Object {
- ($_.AccountName).split("/")[1].toLower()
- } | Where-Object {$_}
- }
- # if we get a specific username, only use that
- elseif($UserName) {
- Write-Verbose "[*] Using target user '$UserName'..."
- $TargetUsers = @( $UserName.ToLower() )
- }
- # read in a target user list if we have one
- elseif($UserFile) {
- $TargetUsers = Get-Content -Path $UserFile | Where-Object {$_}
- }
- elseif($UserADSpath -or $UserFilter) {
- ForEach ($Domain in $TargetDomains) {
- Write-Verbose "[*] Querying domain $Domain for users"
- $TargetUsers += Get-NetUser -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $UserADSpath -Filter $UserFilter | ForEach-Object {
- $_.samaccountname
- } | Where-Object {$_}
- }
- }
- else {
- ForEach ($Domain in $TargetDomains) {
- Write-Verbose "[*] Querying domain $Domain for users of group '$GroupName'"
- $TargetUsers += Get-NetGroupMember -GroupName $GroupName -Domain $Domain -DomainController $DomainController -Credential $Credential| ForEach-Object {
- $_.MemberName
- }
- }
- }
-
- if ((-not $ShowAll) -and ((!$TargetUsers) -or ($TargetUsers.Count -eq 0))) {
- throw "[!] No users found to search for!"
- }
- }
-
- # script block that enumerates a server
- $HostEnumBlock = {
- param($ComputerName, $Ping, $ProcessName, $TargetUsers, $Credential)
-
- # optionally check if the server is up first
- $Up = $True
- if($Ping) {
- $Up = Test-Connection -Count 1 -Quiet -ComputerName $ComputerName
- }
- if($Up) {
- # try to enumerate all active processes on the remote host
- # and search for a specific process name
- $Processes = Get-NetProcess -Credential $Credential -ComputerName $ComputerName -ErrorAction SilentlyContinue
-
- ForEach ($Process in $Processes) {
- # if we're hunting for a process name or comma-separated names
- if($ProcessName) {
- $ProcessName.split(",") | ForEach-Object {
- if ($Process.ProcessName -match $_) {
- $Process
- }
- }
- }
- # if the session user is in the target list, display some output
- elseif ($TargetUsers -contains $Process.User) {
- $Process
- }
- }
- }
- }
-
- }
-
- process {
-
- if($Threads) {
- Write-Verbose "Using threading with threads = $Threads"
-
- # if we're using threading, kick off the script block with Invoke-ThreadedFunction
- $ScriptParams = @{
- 'Ping' = $(-not $NoPing)
- 'ProcessName' = $ProcessName
- 'TargetUsers' = $TargetUsers
- 'Credential' = $Credential
- }
-
- # kick off the threaded script block + arguments
- Invoke-ThreadedFunction -ComputerName $ComputerName -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams -Threads $Threads
- }
-
- else {
- if(-not $NoPing -and ($ComputerName.count -ne 1)) {
- # ping all hosts in parallel
- $Ping = {param($ComputerName) if(Test-Connection -ComputerName $ComputerName -Count 1 -Quiet -ErrorAction Stop){$ComputerName}}
- $ComputerName = Invoke-ThreadedFunction -NoImports -ComputerName $ComputerName -ScriptBlock $Ping -Threads 100
- }
-
- Write-Verbose "[*] Total number of active hosts: $($ComputerName.count)"
- $Counter = 0
-
- ForEach ($Computer in $ComputerName) {
-
- $Counter = $Counter + 1
-
- # sleep for our semi-randomized interval
- Start-Sleep -Seconds $RandNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay)
-
- Write-Verbose "[*] Enumerating server $Computer ($Counter of $($ComputerName.count))"
- $Result = Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList $Computer, $False, $ProcessName, $TargetUsers, $Credential
- $Result
-
- if($Result -and $StopOnSuccess) {
- Write-Verbose "[*] Target user/process found, returning early"
- return
- }
- }
- }
- }
-}
-
-
-function Invoke-EventHunter {
-<#
- .SYNOPSIS
-
- Queries all domain controllers on the network for account
- logon events (ID 4624) and TGT request events (ID 4768),
- searching for target users.
-
- Note: Domain Admin (or equiv) rights are needed to query
- this information from the DCs.
-
- Author: @sixdub, @harmj0y
- License: BSD 3-Clause
-
- .PARAMETER ComputerName
-
- Host array to enumerate, passable on the pipeline.
-
- .PARAMETER ComputerFile
-
- File of hostnames/IPs to search.
-
- .PARAMETER ComputerFilter
-
- Host filter name to query AD for, wildcards accepted.
-
- .PARAMETER ComputerADSpath
-
- The LDAP source to search through for hosts, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
- Useful for OU queries.
-
- .PARAMETER GroupName
-
- Group name to query for target users.
-
- .PARAMETER TargetServer
-
- Hunt for users who are effective local admins on a target server.
-
- .PARAMETER UserName
-
- Specific username to search for.
-
- .PARAMETER UserFilter
-
- A customized ldap filter string to use for user enumeration, e.g. "(description=*admin*)"
-
- .PARAMETER UserADSpath
-
- The LDAP source to search through for users, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
- Useful for OU queries.
-
- .PARAMETER UserFile
-
- File of usernames to search for.
-
- .PARAMETER NoPing
-
- Don't ping each host to ensure it's up before enumerating.
-
- .PARAMETER Domain
-
- Domain for query for machines, defaults to the current domain.
-
- .PARAMETER DomainController
-
- Domain controller to reflect LDAP queries through.
-
- .PARAMETER SearchDays
-
- Number of days back to search logs for. Default 3.
-
- .PARAMETER SearchForest
-
- Switch. Search all domains in the forest for target users instead of just
- a single domain.
-
- .PARAMETER Threads
-
- The maximum concurrent threads to execute.
-
- .PARAMETER Credential
-
- A [Management.Automation.PSCredential] object of alternate credentials
- for connection to the target domain.
-
- .EXAMPLE
-
- PS C:\> Invoke-EventHunter
-
- .LINK
-
- http://blog.harmj0y.net
-#>
-
- [CmdletBinding()]
- param(
- [Parameter(Position=0,ValueFromPipeline=$True)]
- [Alias('Hosts')]
- [String[]]
- $ComputerName,
-
- [ValidateScript({Test-Path -Path $_ })]
- [Alias('HostList')]
- [String]
- $ComputerFile,
-
- [String]
- $ComputerFilter,
-
- [String]
- $ComputerADSpath,
-
- [String]
- $GroupName = 'Domain Admins',
-
- [String]
- $TargetServer,
-
- [String[]]
- $UserName,
-
- [String]
- $UserFilter,
-
- [String]
- $UserADSpath,
-
- [ValidateScript({Test-Path -Path $_ })]
- [String]
- $UserFile,
-
- [String]
- $Domain,
-
- [String]
- $DomainController,
-
- [Int32]
- $SearchDays = 3,
-
- [Switch]
- $SearchForest,
-
- [ValidateRange(1,100)]
- [Int]
- $Threads,
-
- [Management.Automation.PSCredential]
- $Credential
- )
-
- begin {
-
- if ($PSBoundParameters['Debug']) {
- $DebugPreference = 'Continue'
- }
-
- # random object for delay
- $RandNo = New-Object System.Random
-
- Write-Verbose "[*] Running Invoke-EventHunter"
-
- if($Domain) {
- $TargetDomains = @($Domain)
- }
- elseif($SearchForest) {
- # get ALL the domains in the forest to search
- $TargetDomains = Get-NetForestDomain | ForEach-Object { $_.Name }
- }
- else {
- # use the local domain
- $TargetDomains = @( (Get-NetDomain -Credential $Credential).name )
- }
-
- #####################################################
- #
- # First we build the host target set
- #
- #####################################################
-
- if(!$ComputerName) {
- # if we're using a host list, read the targets in and add them to the target list
- if($ComputerFile) {
- $ComputerName = Get-Content -Path $ComputerFile
- }
- elseif($ComputerFilter -or $ComputerADSpath) {
- [array]$ComputerName = @()
- ForEach ($Domain in $TargetDomains) {
- Write-Verbose "[*] Querying domain $Domain for hosts"
- $ComputerName += Get-NetComputer -Domain $Domain -DomainController $DomainController -Credential $Credential -Filter $ComputerFilter -ADSpath $ComputerADSpath
- }
- }
- else {
- # if a computer specifier isn't given, try to enumerate all domain controllers
- [array]$ComputerName = @()
- ForEach ($Domain in $TargetDomains) {
- Write-Verbose "[*] Querying domain $Domain for domain controllers"
- $ComputerName += Get-NetDomainController -LDAP -Domain $Domain -DomainController $DomainController -Credential $Credential | ForEach-Object { $_.dnshostname}
- }
- }
-
- # remove any null target hosts, uniquify the list and shuffle it
- $ComputerName = $ComputerName | Where-Object { $_ } | Sort-Object -Unique | Sort-Object { Get-Random }
- if($($ComputerName.Count) -eq 0) {
- throw "No hosts found!"
- }
- }
-
- #####################################################
- #
- # Now we build the user target set
- #
- #####################################################
-
- # users we're going to be searching for
- $TargetUsers = @()
-
- # if we want to hunt for the effective domain users who can access a target server
- if($TargetServer) {
- Write-Verbose "Querying target server '$TargetServer' for local users"
- $TargetUsers = Get-NetLocalGroup $TargetServer -Recurse | Where-Object {(-not $_.IsGroup) -and $_.IsDomain } | ForEach-Object {
- ($_.AccountName).split("/")[1].toLower()
- } | Where-Object {$_}
- }
- # if we get a specific username, only use that
- elseif($UserName) {
- # Write-Verbose "[*] Using target user '$UserName'..."
- $TargetUsers = $UserName | ForEach-Object {$_.ToLower()}
- if($TargetUsers -isnot [System.Array]) {
- $TargetUsers = @($TargetUsers)
- }
- }
- # read in a target user list if we have one
- elseif($UserFile) {
- $TargetUsers = Get-Content -Path $UserFile | Where-Object {$_}
- }
- elseif($UserADSpath -or $UserFilter) {
- ForEach ($Domain in $TargetDomains) {
- Write-Verbose "[*] Querying domain $Domain for users"
- $TargetUsers += Get-NetUser -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $UserADSpath -Filter $UserFilter | ForEach-Object {
- $_.samaccountname
- } | Where-Object {$_}
- }
- }
- else {
- ForEach ($Domain in $TargetDomains) {
- Write-Verbose "[*] Querying domain $Domain for users of group '$GroupName'"
- $TargetUsers += Get-NetGroupMember -GroupName $GroupName -Domain $Domain -DomainController $DomainController -Credential $Credential | ForEach-Object {
- $_.MemberName
- }
- }
- }
-
- if (((!$TargetUsers) -or ($TargetUsers.Count -eq 0))) {
- throw "[!] No users found to search for!"
- }
-
- # script block that enumerates a server
- $HostEnumBlock = {
- param($ComputerName, $Ping, $TargetUsers, $SearchDays, $Credential)
-
- # optionally check if the server is up first
- $Up = $True
- if($Ping) {
- $Up = Test-Connection -Count 1 -Quiet -ComputerName $ComputerName
- }
- if($Up) {
- # try to enumerate
- if($Credential) {
- Get-UserEvent -ComputerName $ComputerName -Credential $Credential -EventType 'all' -DateStart ([DateTime]::Today.AddDays(-$SearchDays)) | Where-Object {
- # filter for the target user set
- $TargetUsers -contains $_.UserName
- }
- }
- else {
- Get-UserEvent -ComputerName $ComputerName -EventType 'all' -DateStart ([DateTime]::Today.AddDays(-$SearchDays)) | Where-Object {
- # filter for the target user set
- $TargetUsers -contains $_.UserName
- }
- }
- }
- }
-
- }
-
- process {
-
- if($Threads) {
- Write-Verbose "Using threading with threads = $Threads"
-
- # if we're using threading, kick off the script block with Invoke-ThreadedFunction
- $ScriptParams = @{
- 'Ping' = $(-not $NoPing)
- 'TargetUsers' = $TargetUsers
- 'SearchDays' = $SearchDays
- 'Credential' = $Credential
- }
-
- # kick off the threaded script block + arguments
- Invoke-ThreadedFunction -ComputerName $ComputerName -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams -Threads $Threads
- }
-
- else {
- if(-not $NoPing -and ($ComputerName.count -ne 1)) {
- # ping all hosts in parallel
- $Ping = {param($ComputerName) if(Test-Connection -ComputerName $ComputerName -Count 1 -Quiet -ErrorAction Stop){$ComputerName}}
- $ComputerName = Invoke-ThreadedFunction -NoImports -ComputerName $ComputerName -ScriptBlock $Ping -Threads 100
- }
-
- Write-Verbose "[*] Total number of active hosts: $($ComputerName.count)"
- $Counter = 0
-
- ForEach ($Computer in $ComputerName) {
-
- $Counter = $Counter + 1
-
- # sleep for our semi-randomized interval
- Start-Sleep -Seconds $RandNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay)
-
- Write-Verbose "[*] Enumerating server $Computer ($Counter of $($ComputerName.count))"
- Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList $Computer, $(-not $NoPing), $TargetUsers, $SearchDays, $Credential
- }
- }
-
- }
-}
-
-
-function Invoke-ShareFinder {
-<#
- .SYNOPSIS
-
- This function finds the local domain name for a host using Get-NetDomain,
- queries the domain for all active machines with Get-NetComputer, then for
- each server it lists of active shares with Get-NetShare. Non-standard shares
- can be filtered out with -Exclude* flags.
-
- Author: @harmj0y
- License: BSD 3-Clause
-
- .PARAMETER ComputerName
-
- Host array to enumerate, passable on the pipeline.
-
- .PARAMETER ComputerFile
-
- File of hostnames/IPs to search.
-
- .PARAMETER ComputerFilter
-
- Host filter name to query AD for, wildcards accepted.
-
- .PARAMETER ComputerADSpath
-
- The LDAP source to search through for hosts, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
- Useful for OU queries.
-
- .PARAMETER ExcludeStandard
-
- Switch. Exclude standard shares from display (C$, IPC$, print$ etc.)
-
- .PARAMETER ExcludePrint
-
- Switch. Exclude the print$ share.
-
- .PARAMETER ExcludeIPC
-
- Switch. Exclude the IPC$ share.
-
- .PARAMETER CheckShareAccess
-
- Switch. Only display found shares that the local user has access to.
-
- .PARAMETER CheckAdmin
-
- Switch. Only display ADMIN$ shares the local user has access to.
-
- .PARAMETER NoPing
-
- Switch. Don't ping each host to ensure it's up before enumerating.
-
- .PARAMETER Delay
-
- Delay between enumerating hosts, defaults to 0.
-
- .PARAMETER Jitter
-
- Jitter for the host delay, defaults to +/- 0.3.
-
- .PARAMETER Domain
-
- Domain to query for machines, defaults to the current domain.
-
- .PARAMETER DomainController
-
- Domain controller to reflect LDAP queries through.
-
- .PARAMETER SearchForest
-
- Switch. Search all domains in the forest for target users instead of just
- a single domain.
-
- .PARAMETER Threads
-
- The maximum concurrent threads to execute.
-
- .EXAMPLE
-
- PS C:\> Invoke-ShareFinder -ExcludeStandard
-
- Find non-standard shares on the domain.
-
- .EXAMPLE
-
- PS C:\> Invoke-ShareFinder -Threads 20
-
- Multi-threaded share finding, replaces Invoke-ShareFinderThreaded.
-
- .EXAMPLE
-
- PS C:\> Invoke-ShareFinder -Delay 60
-
- Find shares on the domain with a 60 second (+/- *.3)
- randomized delay between touching each host.
-
- .EXAMPLE
-
- PS C:\> Invoke-ShareFinder -ComputerFile hosts.txt
-
- Find shares for machines in the specified hosts file.
-
- .LINK
- http://blog.harmj0y.net
-#>
-
- [CmdletBinding()]
- param(
- [Parameter(Position=0,ValueFromPipeline=$True)]
- [Alias('Hosts')]
- [String[]]
- $ComputerName,
-
- [ValidateScript({Test-Path -Path $_ })]
- [Alias('HostList')]
- [String]
- $ComputerFile,
-
- [String]
- $ComputerFilter,
-
- [String]
- $ComputerADSpath,
-
- [Switch]
- $ExcludeStandard,
-
- [Switch]
- $ExcludePrint,
-
- [Switch]
- $ExcludeIPC,
-
- [Switch]
- $NoPing,
-
- [Switch]
- $CheckShareAccess,
-
- [Switch]
- $CheckAdmin,
-
- [UInt32]
- $Delay = 0,
-
- [Double]
- $Jitter = .3,
-
- [String]
- $Domain,
-
- [String]
- $DomainController,
-
- [Switch]
- $SearchForest,
-
- [ValidateRange(1,100)]
- [Int]
- $Threads
- )
-
- begin {
- if ($PSBoundParameters['Debug']) {
- $DebugPreference = 'Continue'
- }
-
- # random object for delay
- $RandNo = New-Object System.Random
-
- Write-Verbose "[*] Running Invoke-ShareFinder with delay of $Delay"
-
- # figure out the shares we want to ignore
- [String[]] $ExcludedShares = @('')
-
- if ($ExcludePrint) {
- $ExcludedShares = $ExcludedShares + "PRINT$"
- }
- if ($ExcludeIPC) {
- $ExcludedShares = $ExcludedShares + "IPC$"
- }
- if ($ExcludeStandard) {
- $ExcludedShares = @('', "ADMIN$", "IPC$", "C$", "PRINT$")
- }
-
- # if we're using a host file list, read the targets in and add them to the target list
- if($ComputerFile) {
- $ComputerName = Get-Content -Path $ComputerFile
- }
-
- if(!$ComputerName) {
- [array]$ComputerName = @()
-
- if($Domain) {
- $TargetDomains = @($Domain)
- }
- elseif($SearchForest) {
- # get ALL the domains in the forest to search
- $TargetDomains = Get-NetForestDomain | ForEach-Object { $_.Name }
- }
- else {
- # use the local domain
- $TargetDomains = @( (Get-NetDomain).name )
- }
-
- ForEach ($Domain in $TargetDomains) {
- Write-Verbose "[*] Querying domain $Domain for hosts"
- $ComputerName += Get-NetComputer -Domain $Domain -DomainController $DomainController -Filter $ComputerFilter -ADSpath $ComputerADSpath
- }
-
- # remove any null target hosts, uniquify the list and shuffle it
- $ComputerName = $ComputerName | Where-Object { $_ } | Sort-Object -Unique | Sort-Object { Get-Random }
- if($($ComputerName.count) -eq 0) {
- throw "No hosts found!"
- }
- }
-
- # script block that enumerates a server
- $HostEnumBlock = {
- param($ComputerName, $Ping, $CheckShareAccess, $ExcludedShares, $CheckAdmin)
-
- # optionally check if the server is up first
- $Up = $True
- if($Ping) {
- $Up = Test-Connection -Count 1 -Quiet -ComputerName $ComputerName
- }
- if($Up) {
- # get the shares for this host and check what we find
- $Shares = Get-NetShare -ComputerName $ComputerName
- ForEach ($Share in $Shares) {
- Write-Verbose "[*] Server share: $Share"
- $NetName = $Share.shi1_netname
- $Remark = $Share.shi1_remark
- $Path = '\\'+$ComputerName+'\'+$NetName
-
- # make sure we get a real share name back
- if (($NetName) -and ($NetName.trim() -ne '')) {
- # if we're just checking for access to ADMIN$
- if($CheckAdmin) {
- if($NetName.ToUpper() -eq "ADMIN$") {
- try {
- $Null = [IO.Directory]::GetFiles($Path)
- "\\$ComputerName\$NetName `t- $Remark"
- }
- catch {
- Write-Verbose "Error accessing path $Path : $_"
- }
- }
- }
- # skip this share if it's in the exclude list
- elseif ($ExcludedShares -NotContains $NetName.ToUpper()) {
- # see if we want to check access to this share
- if($CheckShareAccess) {
- # check if the user has access to this path
- try {
- $Null = [IO.Directory]::GetFiles($Path)
- "\\$ComputerName\$NetName `t- $Remark"
- }
- catch {
- Write-Verbose "Error accessing path $Path : $_"
- }
- }
- else {
- "\\$ComputerName\$NetName `t- $Remark"
- }
- }
- }
- }
- }
- }
-
- }
-
- process {
-
- if($Threads) {
- Write-Verbose "Using threading with threads = $Threads"
-
- # if we're using threading, kick off the script block with Invoke-ThreadedFunction
- $ScriptParams = @{
- 'Ping' = $(-not $NoPing)
- 'CheckShareAccess' = $CheckShareAccess
- 'ExcludedShares' = $ExcludedShares
- 'CheckAdmin' = $CheckAdmin
- }
-
- # kick off the threaded script block + arguments
- Invoke-ThreadedFunction -ComputerName $ComputerName -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams -Threads $Threads
- }
-
- else {
- if(-not $NoPing -and ($ComputerName.count -ne 1)) {
- # ping all hosts in parallel
- $Ping = {param($ComputerName) if(Test-Connection -ComputerName $ComputerName -Count 1 -Quiet -ErrorAction Stop){$ComputerName}}
- $ComputerName = Invoke-ThreadedFunction -NoImports -ComputerName $ComputerName -ScriptBlock $Ping -Threads 100
- }
-
- Write-Verbose "[*] Total number of active hosts: $($ComputerName.count)"
- $Counter = 0
-
- ForEach ($Computer in $ComputerName) {
-
- $Counter = $Counter + 1
-
- # sleep for our semi-randomized interval
- Start-Sleep -Seconds $RandNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay)
-
- Write-Verbose "[*] Enumerating server $Computer ($Counter of $($ComputerName.count))"
- Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList $Computer, $False, $CheckShareAccess, $ExcludedShares, $CheckAdmin
- }
- }
-
- }
-}
-
-
-function Invoke-FileFinder {
-<#
- .SYNOPSIS
-
- Finds sensitive files on the domain.
-
- Author: @harmj0y
- License: BSD 3-Clause
-
- .DESCRIPTION
-
- This function finds the local domain name for a host using Get-NetDomain,
- queries the domain for all active machines with Get-NetComputer, grabs
- the readable shares for each server, and recursively searches every
- share for files with specific keywords in the name.
- If a share list is passed, EVERY share is enumerated regardless of
- other options.
-
- .PARAMETER ComputerName
-
- Host array to enumerate, passable on the pipeline.
-
- .PARAMETER ComputerFile
-
- File of hostnames/IPs to search.
-
- .PARAMETER ComputerFilter
-
- Host filter name to query AD for, wildcards accepted.
-
- .PARAMETER ComputerADSpath
-
- The LDAP source to search through for hosts, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
- Useful for OU queries.
-
- .PARAMETER ShareList
-
- List if \\HOST\shares to search through.
-
- .PARAMETER Terms
-
- Terms to search for.
-
- .PARAMETER OfficeDocs
-
- Switch. Search for office documents (*.doc*, *.xls*, *.ppt*)
-
- .PARAMETER FreshEXEs
-
- Switch. Find .EXEs accessed within the last week.
-
- .PARAMETER LastAccessTime
-
- Only return files with a LastAccessTime greater than this date value.
-
- .PARAMETER LastWriteTime
-
- Only return files with a LastWriteTime greater than this date value.
-
- .PARAMETER CreationTime
-
- Only return files with a CreationDate greater than this date value.
-
- .PARAMETER IncludeC
-
- Switch. Include any C$ shares in recursive searching (default ignore).
-
- .PARAMETER IncludeAdmin
-
- Switch. Include any ADMIN$ shares in recursive searching (default ignore).
-
- .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 OutFile
-
- Output results to a specified csv output file.
-
- .PARAMETER NoClobber
-
- Switch. Don't overwrite any existing output file.
-
- .PARAMETER NoPing
-
- Switch. Don't ping each host to ensure it's up before enumerating.
-
- .PARAMETER Delay
-
- Delay between enumerating hosts, defaults to 0
-
- .PARAMETER Jitter
-
- Jitter for the host delay, defaults to +/- 0.3
-
- .PARAMETER Domain
-
- Domain to query for machines, defaults to the current domain.
-
- .PARAMETER DomainController
-
- Domain controller to reflect LDAP queries through.
-
- .PARAMETER SearchForest
-
- Search all domains in the forest for target users instead of just
- a single domain.
-
- .PARAMETER SearchSYSVOL
-
- Switch. Search for login scripts on the SYSVOL of the primary DCs for each specified domain.
-
- .PARAMETER Threads
-
- The maximum concurrent threads to execute.
-
- .PARAMETER UsePSDrive
-
- Switch. Mount target remote path with temporary PSDrives.
-
- .EXAMPLE
-
- PS C:\> Invoke-FileFinder
-
- Find readable files on the domain with 'pass', 'sensitive',
- 'secret', 'admin', 'login', or 'unattend*.xml' in the name,
-
- .EXAMPLE
-
- PS C:\> Invoke-FileFinder -Domain testing
-
- Find readable files on the 'testing' domain with 'pass', 'sensitive',
- 'secret', 'admin', 'login', or 'unattend*.xml' in the name,
-
- .EXAMPLE
-
- PS C:\> Invoke-FileFinder -IncludeC
-
- Find readable files on the domain with 'pass', 'sensitive',
- 'secret', 'admin', 'login' or 'unattend*.xml' in the name,
- including C$ shares.
-
- .EXAMPLE
-
- PS C:\> Invoke-FileFinder -ShareList shares.txt -Terms accounts,ssn -OutFile out.csv
-
- Enumerate a specified share list for files with 'accounts' or
- 'ssn' in the name, and write everything to "out.csv"
-
- .LINK
- http://www.harmj0y.net/blog/redteaming/file-server-triage-on-red-team-engagements/
-
-#>
-
- [CmdletBinding()]
- param(
- [Parameter(Position=0,ValueFromPipeline=$True)]
- [Alias('Hosts')]
- [String[]]
- $ComputerName,
-
- [ValidateScript({Test-Path -Path $_ })]
- [Alias('HostList')]
- [String]
- $ComputerFile,
-
- [String]
- $ComputerFilter,
-
- [String]
- $ComputerADSpath,
-
- [ValidateScript({Test-Path -Path $_ })]
- [String]
- $ShareList,
-
- [Switch]
- $OfficeDocs,
-
- [Switch]
- $FreshEXEs,
-
- [Alias('Terms')]
- [String[]]
- $SearchTerms,
-
- [ValidateScript({Test-Path -Path $_ })]
- [String]
- $TermList,
-
- [String]
- $LastAccessTime,
-
- [String]
- $LastWriteTime,
-
- [String]
- $CreationTime,
-
- [Switch]
- $IncludeC,
-
- [Switch]
- $IncludeAdmin,
-
- [Switch]
- $ExcludeFolders,
-
- [Switch]
- $ExcludeHidden,
-
- [Switch]
- $CheckWriteAccess,
-
- [String]
- $OutFile,
-
- [Switch]
- $NoClobber,
-
- [Switch]
- $NoPing,
-
- [UInt32]
- $Delay = 0,
-
- [Double]
- $Jitter = .3,
-
- [String]
- $Domain,
-
- [String]
- $DomainController,
-
- [Switch]
- $SearchForest,
-
- [Switch]
- $SearchSYSVOL,
-
- [ValidateRange(1,100)]
- [Int]
- $Threads,
-
- [Switch]
- $UsePSDrive
- )
-
- begin {
- if ($PSBoundParameters['Debug']) {
- $DebugPreference = 'Continue'
- }
-
- # random object for delay
- $RandNo = New-Object System.Random
-
- Write-Verbose "[*] Running Invoke-FileFinder with delay of $Delay"
-
- $Shares = @()
-
- # figure out the shares we want to ignore
- [String[]] $ExcludedShares = @("C$", "ADMIN$")
-
- # see if we're specifically including any of the normally excluded sets
- if ($IncludeC) {
- if ($IncludeAdmin) {
- $ExcludedShares = @()
- }
- else {
- $ExcludedShares = @("ADMIN$")
- }
- }
-
- if ($IncludeAdmin) {
- if ($IncludeC) {
- $ExcludedShares = @()
- }
- else {
- $ExcludedShares = @("C$")
- }
- }
-
- # delete any existing output file if it already exists
- if(!$NoClobber) {
- if ($OutFile -and (Test-Path -Path $OutFile)) { Remove-Item -Path $OutFile }
- }
-
- # if there's a set of terms specified to search for
- if ($TermList) {
- ForEach ($Term in Get-Content -Path $TermList) {
- if (($Term -ne $Null) -and ($Term.trim() -ne '')) {
- $SearchTerms += $Term
- }
- }
- }
-
- # if we're hard-passed a set of shares
- if($ShareList) {
- ForEach ($Item in Get-Content -Path $ShareList) {
- if (($Item -ne $Null) -and ($Item.trim() -ne '')) {
- # exclude any "[tab]- commants", i.e. the output from Invoke-ShareFinder
- $Share = $Item.Split("`t")[0]
- $Shares += $Share
- }
- }
- }
- else {
- # if we're using a host file list, read the targets in and add them to the target list
- if($ComputerFile) {
- $ComputerName = Get-Content -Path $ComputerFile
- }
-
- if(!$ComputerName) {
-
- if($Domain) {
- $TargetDomains = @($Domain)
- }
- elseif($SearchForest) {
- # get ALL the domains in the forest to search
- $TargetDomains = Get-NetForestDomain | ForEach-Object { $_.Name }
- }
- else {
- # use the local domain
- $TargetDomains = @( (Get-NetDomain).name )
- }
-
- if($SearchSYSVOL) {
- ForEach ($Domain in $TargetDomains) {
- $DCSearchPath = "\\$Domain\SYSVOL\"
- Write-Verbose "[*] Adding share search path $DCSearchPath"
- $Shares += $DCSearchPath
- }
- if(!$SearchTerms) {
- # search for interesting scripts on SYSVOL
- $SearchTerms = @('.vbs', '.bat', '.ps1')
- }
- }
- else {
- [array]$ComputerName = @()
-
- ForEach ($Domain in $TargetDomains) {
- Write-Verbose "[*] Querying domain $Domain for hosts"
- $ComputerName += Get-NetComputer -Filter $ComputerFilter -ADSpath $ComputerADSpath -Domain $Domain -DomainController $DomainController
- }
-
- # remove any null target hosts, uniquify the list and shuffle it
- $ComputerName = $ComputerName | Where-Object { $_ } | Sort-Object -Unique | Sort-Object { Get-Random }
- if($($ComputerName.Count) -eq 0) {
- throw "No hosts found!"
- }
- }
- }
- }
-
- # script block that enumerates shares and files on a server
- $HostEnumBlock = {
- param($ComputerName, $Ping, $ExcludedShares, $SearchTerms, $ExcludeFolders, $OfficeDocs, $ExcludeHidden, $FreshEXEs, $CheckWriteAccess, $OutFile, $UsePSDrive)
-
- Write-Verbose "ComputerName: $ComputerName"
- Write-Verbose "ExcludedShares: $ExcludedShares"
- $SearchShares = @()
-
- if($ComputerName.StartsWith("\\")) {
- # if a share is passed as the server
- $SearchShares += $ComputerName
- }
- else {
- # if we're enumerating the shares on the target server first
- $Up = $True
- if($Ping) {
- $Up = Test-Connection -Count 1 -Quiet -ComputerName $ComputerName
- }
- if($Up) {
- # get the shares for this host and display what we find
- $Shares = Get-NetShare -ComputerName $ComputerName
- ForEach ($Share in $Shares) {
-
- $NetName = $Share.shi1_netname
- $Path = '\\'+$ComputerName+'\'+$NetName
-
- # make sure we get a real share name back
- if (($NetName) -and ($NetName.trim() -ne '')) {
-
- # skip this share if it's in the exclude list
- if ($ExcludedShares -NotContains $NetName.ToUpper()) {
- # check if the user has access to this path
- try {
- $Null = [IO.Directory]::GetFiles($Path)
- $SearchShares += $Path
- }
- catch {
- Write-Verbose "[!] No access to $Path"
- }
- }
- }
- }
- }
- }
-
- ForEach($Share in $SearchShares) {
- $SearchArgs = @{
- 'Path' = $Share
- 'SearchTerms' = $SearchTerms
- 'OfficeDocs' = $OfficeDocs
- 'FreshEXEs' = $FreshEXEs
- 'LastAccessTime' = $LastAccessTime
- 'LastWriteTime' = $LastWriteTime
- 'CreationTime' = $CreationTime
- 'ExcludeFolders' = $ExcludeFolders
- 'ExcludeHidden' = $ExcludeHidden
- 'CheckWriteAccess' = $CheckWriteAccess
- 'OutFile' = $OutFile
- 'UsePSDrive' = $UsePSDrive
- }
-
- Find-InterestingFile @SearchArgs
- }
- }
- }
-
- process {
-
- if($Threads) {
- Write-Verbose "Using threading with threads = $Threads"
-
- # if we're using threading, kick off the script block with Invoke-ThreadedFunction
- $ScriptParams = @{
- 'Ping' = $(-not $NoPing)
- 'ExcludedShares' = $ExcludedShares
- 'SearchTerms' = $SearchTerms
- 'ExcludeFolders' = $ExcludeFolders
- 'OfficeDocs' = $OfficeDocs
- 'ExcludeHidden' = $ExcludeHidden
- 'FreshEXEs' = $FreshEXEs
- 'CheckWriteAccess' = $CheckWriteAccess
- 'OutFile' = $OutFile
- 'UsePSDrive' = $UsePSDrive
- }
-
- # kick off the threaded script block + arguments
- if($Shares) {
- # pass the shares as the hosts so the threaded function code doesn't have to be hacked up
- Invoke-ThreadedFunction -ComputerName $Shares -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams -Threads $Threads
- }
- else {
- Invoke-ThreadedFunction -ComputerName $ComputerName -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams -Threads $Threads
- }
- }
-
- else {
- if($Shares){
- $ComputerName = $Shares
- }
- elseif(-not $NoPing -and ($ComputerName.count -gt 1)) {
- # ping all hosts in parallel
- $Ping = {param($ComputerName) if(Test-Connection -ComputerName $ComputerName -Count 1 -Quiet -ErrorAction Stop){$ComputerName}}
- $ComputerName = Invoke-ThreadedFunction -NoImports -ComputerName $ComputerName -ScriptBlock $Ping -Threads 100
- }
-
- Write-Verbose "[*] Total number of active hosts: $($ComputerName.count)"
- $Counter = 0
-
- $ComputerName | Where-Object {$_} | ForEach-Object {
- Write-Verbose "Computer: $_"
- $Counter = $Counter + 1
-
- # sleep for our semi-randomized interval
- Start-Sleep -Seconds $RandNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay)
-
- Write-Verbose "[*] Enumerating server $_ ($Counter of $($ComputerName.count))"
-
- Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList $_, $False, $ExcludedShares, $SearchTerms, $ExcludeFolders, $OfficeDocs, $ExcludeHidden, $FreshEXEs, $CheckWriteAccess, $OutFile, $UsePSDrive
- }
- }
- }
-}
-
-
-function Find-LocalAdminAccess {
-<#
- .SYNOPSIS
-
- Finds machines on the local domain where the current user has
- local administrator access. Uses multithreading to
- speed up enumeration.
-
- Author: @harmj0y
- License: BSD 3-Clause
-
- .DESCRIPTION
-
- This function finds the local domain name for a host using Get-NetDomain,
- queries the domain for all active machines with Get-NetComputer, then for
- each server it checks if the current user has local administrator
- access using Invoke-CheckLocalAdminAccess.
-
- Idea stolen from the local_admin_search_enum post module in
- Metasploit written by:
- 'Brandon McCann "zeknox" '
- 'Thomas McCarthy "smilingraccoon" '
- 'Royce Davis "r3dy" '
-
- .PARAMETER ComputerName
-
- Host array to enumerate, passable on the pipeline.
-
- .PARAMETER ComputerFile
-
- File of hostnames/IPs to search.
-
- .PARAMETER ComputerFilter
-
- Host filter name to query AD for, wildcards accepted.
-
- .PARAMETER ComputerADSpath
-
- The LDAP source to search through for hosts, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
- Useful for OU queries.
-
- .PARAMETER NoPing
-
- Switch. Don't ping each host to ensure it's up before enumerating.
-
- .PARAMETER Delay
-
- Delay between enumerating hosts, defaults to 0
-
- .PARAMETER Jitter
-
- Jitter for the host delay, defaults to +/- 0.3
-
- .PARAMETER Domain
-
- Domain to query for machines, defaults to the current domain.
-
- .PARAMETER DomainController
-
- Domain controller to reflect LDAP queries through.
-
- .PARAMETER SearchForest
-
- Switch. Search all domains in the forest for target users instead of just
- a single domain.
-
- .PARAMETER Threads
-
- The maximum concurrent threads to execute.
-
- .EXAMPLE
-
- PS C:\> Find-LocalAdminAccess
-
- Find machines on the local domain where the current user has local
- administrator access.
-
- .EXAMPLE
-
- PS C:\> Find-LocalAdminAccess -Threads 10
-
- Multi-threaded access hunting, replaces Find-LocalAdminAccessThreaded.
-
- .EXAMPLE
-
- PS C:\> Find-LocalAdminAccess -Domain testing
-
- Find machines on the 'testing' domain where the current user has
- local administrator access.
-
- .EXAMPLE
-
- PS C:\> Find-LocalAdminAccess -ComputerFile hosts.txt
-
- Find which machines in the host list the current user has local
- administrator access.
-
- .LINK
-
- https://github.com/rapid7/metasploit-framework/blob/master/modules/post/windows/gather/local_admin_search_enum.rb
- http://www.harmj0y.net/blog/penetesting/finding-local-admin-with-the-veil-framework/
-#>
-
- [CmdletBinding()]
- param(
- [Parameter(Position=0,ValueFromPipeline=$True)]
- [Alias('Hosts')]
- [String[]]
- $ComputerName,
-
- [ValidateScript({Test-Path -Path $_ })]
- [Alias('HostList')]
- [String]
- $ComputerFile,
-
- [String]
- $ComputerFilter,
-
- [String]
- $ComputerADSpath,
-
- [Switch]
- $NoPing,
-
- [UInt32]
- $Delay = 0,
-
- [Double]
- $Jitter = .3,
-
- [String]
- $Domain,
-
- [String]
- $DomainController,
-
- [Switch]
- $SearchForest,
-
- [ValidateRange(1,100)]
- [Int]
- $Threads
- )
-
- begin {
- if ($PSBoundParameters['Debug']) {
- $DebugPreference = 'Continue'
- }
-
- # random object for delay
- $RandNo = New-Object System.Random
-
- Write-Verbose "[*] Running Find-LocalAdminAccess with delay of $Delay"
-
- # if we're using a host list, read the targets in and add them to the target list
- if($ComputerFile) {
- $ComputerName = Get-Content -Path $ComputerFile
- }
-
- if(!$ComputerName) {
- [array]$ComputerName = @()
-
- if($Domain) {
- $TargetDomains = @($Domain)
- }
- elseif($SearchForest) {
- # get ALL the domains in the forest to search
- $TargetDomains = Get-NetForestDomain | ForEach-Object { $_.Name }
- }
- else {
- # use the local domain
- $TargetDomains = @( (Get-NetDomain).name )
- }
-
- ForEach ($Domain in $TargetDomains) {
- Write-Verbose "[*] Querying domain $Domain for hosts"
- $ComputerName += Get-NetComputer -Filter $ComputerFilter -ADSpath $ComputerADSpath -Domain $Domain -DomainController $DomainController
- }
-
- # remove any null target hosts, uniquify the list and shuffle it
- $ComputerName = $ComputerName | Where-Object { $_ } | Sort-Object -Unique | Sort-Object { Get-Random }
- if($($ComputerName.Count) -eq 0) {
- throw "No hosts found!"
- }
- }
-
- # script block that enumerates a server
- $HostEnumBlock = {
- param($ComputerName, $Ping)
-
- $Up = $True
- if($Ping) {
- $Up = Test-Connection -Count 1 -Quiet -ComputerName $ComputerName
- }
- if($Up) {
- # check if the current user has local admin access to this server
- $Access = Invoke-CheckLocalAdminAccess -ComputerName $ComputerName
- if ($Access) {
- $ComputerName
- }
- }
- }
-
- }
-
- process {
-
- if($Threads) {
- Write-Verbose "Using threading with threads = $Threads"
-
- # if we're using threading, kick off the script block with Invoke-ThreadedFunction
- $ScriptParams = @{
- 'Ping' = $(-not $NoPing)
- }
-
- # kick off the threaded script block + arguments
- Invoke-ThreadedFunction -ComputerName $ComputerName -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams -Threads $Threads
- }
-
- else {
- if(-not $NoPing -and ($ComputerName.count -ne 1)) {
- # ping all hosts in parallel
- $Ping = {param($ComputerName) if(Test-Connection -ComputerName $ComputerName -Count 1 -Quiet -ErrorAction Stop){$ComputerName}}
- $ComputerName = Invoke-ThreadedFunction -NoImports -ComputerName $ComputerName -ScriptBlock $Ping -Threads 100
- }
-
- Write-Verbose "[*] Total number of active hosts: $($ComputerName.count)"
- $Counter = 0
-
- ForEach ($Computer in $ComputerName) {
-
- $Counter = $Counter + 1
-
- # sleep for our semi-randomized interval
- Start-Sleep -Seconds $RandNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay)
-
- Write-Verbose "[*] Enumerating server $Computer ($Counter of $($ComputerName.count))"
- Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList $Computer, $False
- }
- }
- }
-}
-
-
-function Get-ExploitableSystem {
-<#
- .Synopsis
-
- This module will query Active Directory for the hostname, OS version, and service pack level
- for each computer account. That information is then cross-referenced against a list of common
- Metasploit exploits that can be used during penetration testing.
-
- .DESCRIPTION
-
- This module will query Active Directory for the hostname, OS version, and service pack level
- for each computer account. That information is then cross-referenced against a list of common
- Metasploit exploits that can be used during penetration testing. The script filters out disabled
- domain computers and provides the computer's last logon time to help determine if it's been
- decommissioned. Also, since the script uses data tables to output affected systems the results
- can be easily piped to other commands such as test-connection or a Export-Csv.
-
- .PARAMETER ComputerName
-
- Return computers with a specific name, wildcards accepted.
-
- .PARAMETER SPN
-
- Return computers with a specific service principal name, wildcards accepted.
-
- .PARAMETER OperatingSystem
-
- Return computers with a specific operating system, wildcards accepted.
-
- .PARAMETER ServicePack
-
- Return computers with a specific service pack, wildcards accepted.
-
- .PARAMETER Filter
-
- A customized ldap filter string to use, e.g. "(description=*admin*)"
-
- .PARAMETER Ping
-
- Switch. Ping each host to ensure it's up before enumerating.
-
- .PARAMETER Domain
-
- The domain to query for computers, defaults to the current domain.
-
- .PARAMETER DomainController
-
- Domain controller to reflect LDAP queries through.
-
- .PARAMETER ADSpath
-
- The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
- Useful for OU queries.
-
- .PARAMETER Unconstrained
-
- Switch. Return computer objects that have unconstrained delegation.
-
- .PARAMETER PageSize
-
- The PageSize to set for the LDAP searcher object.
-
- .PARAMETER Credential
-
- A [Management.Automation.PSCredential] object of alternate credentials
- for connection to the target domain.
-
- .EXAMPLE
-
- The example below shows the standard command usage. Disabled system are excluded by default, but
- the "LastLgon" column can be used to determine which systems are live. Usually, if a system hasn't
- logged on for two or more weeks it's been decommissioned.
- PS C:\> Get-ExploitableSystem -DomainController 192.168.1.1 -Credential demo.com\user | Format-Table -AutoSize
- [*] Grabbing computer accounts from Active Directory...
- [*] Loading exploit list for critical missing patches...
- [*] Checking computers for vulnerable OS and SP levels...
- [+] Found 5 potentially vulnerable systems!
- ComputerName OperatingSystem ServicePack LastLogon MsfModule CVE
- ------------ --------------- ----------- --------- --------- ---
- ADS.demo.com Windows Server 2003 Service Pack 2 4/8/2015 5:46:52 PM exploit/windows/dcerpc/ms07_029_msdns_zonename http://www.cvedetails....
- ADS.demo.com Windows Server 2003 Service Pack 2 4/8/2015 5:46:52 PM exploit/windows/smb/ms08_067_netapi http://www.cvedetails....
- ADS.demo.com Windows Server 2003 Service Pack 2 4/8/2015 5:46:52 PM exploit/windows/smb/ms10_061_spoolss http://www.cvedetails....
- LVA.demo.com Windows Server 2003 Service Pack 2 4/8/2015 1:44:46 PM exploit/windows/dcerpc/ms07_029_msdns_zonename http://www.cvedetails....
- LVA.demo.com Windows Server 2003 Service Pack 2 4/8/2015 1:44:46 PM exploit/windows/smb/ms08_067_netapi http://www.cvedetails....
- LVA.demo.com Windows Server 2003 Service Pack 2 4/8/2015 1:44:46 PM exploit/windows/smb/ms10_061_spoolss http://www.cvedetails....
- assess-xppro.demo.com Windows XP Professional Service Pack 3 4/1/2014 11:11:54 AM exploit/windows/smb/ms08_067_netapi http://www.cvedetails....
- assess-xppro.demo.com Windows XP Professional Service Pack 3 4/1/2014 11:11:54 AM exploit/windows/smb/ms10_061_spoolss http://www.cvedetails....
- HVA.demo.com Windows Server 2003 Service Pack 2 11/5/2013 9:16:31 PM exploit/windows/dcerpc/ms07_029_msdns_zonename http://www.cvedetails....
- HVA.demo.com Windows Server 2003 Service Pack 2 11/5/2013 9:16:31 PM exploit/windows/smb/ms08_067_netapi http://www.cvedetails....
- HVA.demo.com Windows Server 2003 Service Pack 2 11/5/2013 9:16:31 PM exploit/windows/smb/ms10_061_spoolss http://www.cvedetails....
- DB1.demo.com Windows Server 2003 Service Pack 2 3/22/2012 5:05:34 PM exploit/windows/dcerpc/ms07_029_msdns_zonename http://www.cvedetails....
- DB1.demo.com Windows Server 2003 Service Pack 2 3/22/2012 5:05:34 PM exploit/windows/smb/ms08_067_netapi http://www.cvedetails....
- DB1.demo.com Windows Server 2003 Service Pack 2 3/22/2012 5:05:34 PM exploit/windows/smb/ms10_061_spoolss http://www.cvedetails....
-
- .EXAMPLE
-
- PS C:\> Get-ExploitableSystem | Export-Csv c:\temp\output.csv -NoTypeInformation
-
- How to write the output to a csv file.
-
- .EXAMPLE
-
- PS C:\> Get-ExploitableSystem -Domain testlab.local -Ping
-
- Return a set of live hosts from the testlab.local domain
-
- .LINK
-
- http://www.netspi.com
- https://github.com/nullbind/Powershellery/blob/master/Stable-ish/ADS/Get-ExploitableSystems.psm1
-
- .NOTES
-
- Author: Scott Sutherland - 2015, NetSPI
- Modifications to integrate into PowerView by @harmj0y
- Version: Get-ExploitableSystem.psm1 v1.1
- Comments: The technique used to query LDAP was based on the "Get-AuditDSComputerAccount"
- function found in Carols Perez's PoshSec-Mod project. The general idea is based off of
- Will Schroeder's "Invoke-FindVulnSystems" function from the PowerView toolkit.
-#>
- [CmdletBinding()]
- Param(
- [Parameter(ValueFromPipeline=$True)]
- [Alias('HostName')]
- [String]
- $ComputerName = '*',
-
- [String]
- $SPN,
-
- [String]
- $OperatingSystem = '*',
-
- [String]
- $ServicePack = '*',
-
- [String]
- $Filter,
-
- [Switch]
- $Ping,
-
- [String]
- $Domain,
-
- [String]
- $DomainController,
-
- [String]
- $ADSpath,
-
- [Switch]
- $Unconstrained,
-
- [ValidateRange(1,10000)]
- [Int]
- $PageSize = 200,
-
- [Management.Automation.PSCredential]
- $Credential
- )
-
- Write-Verbose "[*] Grabbing computer accounts from Active Directory..."
-
- # Create data table for hostnames, os, and service packs from LDAP
- $TableAdsComputers = New-Object System.Data.DataTable
- $Null = $TableAdsComputers.Columns.Add('Hostname')
- $Null = $TableAdsComputers.Columns.Add('OperatingSystem')
- $Null = $TableAdsComputers.Columns.Add('ServicePack')
- $Null = $TableAdsComputers.Columns.Add('LastLogon')
-
- Get-NetComputer -FullData @PSBoundParameters | ForEach-Object {
-
- $CurrentHost = $_.dnshostname
- $CurrentOs = $_.operatingsystem
- $CurrentSp = $_.operatingsystemservicepack
- $CurrentLast = $_.lastlogon
- $CurrentUac = $_.useraccountcontrol
-
- $CurrentUacBin = [convert]::ToString($_.useraccountcontrol,2)
-
- # Check the 2nd to last value to determine if its disabled
- $DisableOffset = $CurrentUacBin.Length - 2
- $CurrentDisabled = $CurrentUacBin.Substring($DisableOffset,1)
-
- # Add computer to list if it's enabled
- if ($CurrentDisabled -eq 0) {
- # Add domain computer to data table
- $Null = $TableAdsComputers.Rows.Add($CurrentHost,$CurrentOS,$CurrentSP,$CurrentLast)
- }
- }
-
- # Status user
- Write-Verbose "[*] Loading exploit list for critical missing patches..."
-
- # ----------------------------------------------------------------
- # Setup data table for list of msf exploits
- # ----------------------------------------------------------------
-
- # Create data table for list of patches levels with a MSF exploit
- $TableExploits = New-Object System.Data.DataTable
- $Null = $TableExploits.Columns.Add('OperatingSystem')
- $Null = $TableExploits.Columns.Add('ServicePack')
- $Null = $TableExploits.Columns.Add('MsfModule')
- $Null = $TableExploits.Columns.Add('CVE')
-
- # Add exploits to data table
- $Null = $TableExploits.Rows.Add("Windows 7","","exploit/windows/smb/ms10_061_spoolss","http://www.cvedetails.com/cve/2010-2729")
- $Null = $TableExploits.Rows.Add("Windows Server 2000","Server Pack 1","exploit/windows/dcerpc/ms03_026_dcom","http://www.cvedetails.com/cve/2003-0352/")
- $Null = $TableExploits.Rows.Add("Windows Server 2000","Server Pack 1","exploit/windows/dcerpc/ms05_017_msmq","http://www.cvedetails.com/cve/2005-0059")
- $Null = $TableExploits.Rows.Add("Windows Server 2000","Server Pack 1","exploit/windows/iis/ms03_007_ntdll_webdav","http://www.cvedetails.com/cve/2003-0109")
- $Null = $TableExploits.Rows.Add("Windows Server 2000","Server Pack 1","exploit/windows/wins/ms04_045_wins","http://www.cvedetails.com/cve/2004-1080/")
- $Null = $TableExploits.Rows.Add("Windows Server 2000","Service Pack 2","exploit/windows/dcerpc/ms03_026_dcom","http://www.cvedetails.com/cve/2003-0352/")
- $Null = $TableExploits.Rows.Add("Windows Server 2000","Service Pack 2","exploit/windows/dcerpc/ms05_017_msmq","http://www.cvedetails.com/cve/2005-0059")
- $Null = $TableExploits.Rows.Add("Windows Server 2000","Service Pack 2","exploit/windows/iis/ms03_007_ntdll_webdav","http://www.cvedetails.com/cve/2003-0109")
- $Null = $TableExploits.Rows.Add("Windows Server 2000","Service Pack 2","exploit/windows/smb/ms04_011_lsass","http://www.cvedetails.com/cve/2003-0533/")
- $Null = $TableExploits.Rows.Add("Windows Server 2000","Service Pack 2","exploit/windows/wins/ms04_045_wins","http://www.cvedetails.com/cve/2004-1080/")
- $Null = $TableExploits.Rows.Add("Windows Server 2000","Service Pack 3","exploit/windows/dcerpc/ms03_026_dcom","http://www.cvedetails.com/cve/2003-0352/")
- $Null = $TableExploits.Rows.Add("Windows Server 2000","Service Pack 3","exploit/windows/dcerpc/ms05_017_msmq","http://www.cvedetails.com/cve/2005-0059")
- $Null = $TableExploits.Rows.Add("Windows Server 2000","Service Pack 3","exploit/windows/iis/ms03_007_ntdll_webdav","http://www.cvedetails.com/cve/2003-0109")
- $Null = $TableExploits.Rows.Add("Windows Server 2000","Service Pack 3","exploit/windows/wins/ms04_045_wins","http://www.cvedetails.com/cve/2004-1080/")
- $Null = $TableExploits.Rows.Add("Windows Server 2000","Service Pack 4","exploit/windows/dcerpc/ms03_026_dcom","http://www.cvedetails.com/cve/2003-0352/")
- $Null = $TableExploits.Rows.Add("Windows Server 2000","Service Pack 4","exploit/windows/dcerpc/ms05_017_msmq","http://www.cvedetails.com/cve/2005-0059")
- $Null = $TableExploits.Rows.Add("Windows Server 2000","Service Pack 4","exploit/windows/dcerpc/ms07_029_msdns_zonename","http://www.cvedetails.com/cve/2007-1748")
- $Null = $TableExploits.Rows.Add("Windows Server 2000","Service Pack 4","exploit/windows/smb/ms04_011_lsass","http://www.cvedetails.com/cve/2003-0533/")
- $Null = $TableExploits.Rows.Add("Windows Server 2000","Service Pack 4","exploit/windows/smb/ms06_040_netapi","http://www.cvedetails.com/cve/2006-3439")
- $Null = $TableExploits.Rows.Add("Windows Server 2000","Service Pack 4","exploit/windows/smb/ms06_066_nwapi","http://www.cvedetails.com/cve/2006-4688")
- $Null = $TableExploits.Rows.Add("Windows Server 2000","Service Pack 4","exploit/windows/smb/ms06_070_wkssvc","http://www.cvedetails.com/cve/2006-4691")
- $Null = $TableExploits.Rows.Add("Windows Server 2000","Service Pack 4","exploit/windows/smb/ms08_067_netapi","http://www.cvedetails.com/cve/2008-4250")
- $Null = $TableExploits.Rows.Add("Windows Server 2000","Service Pack 4","exploit/windows/wins/ms04_045_wins","http://www.cvedetails.com/cve/2004-1080/")
- $Null = $TableExploits.Rows.Add("Windows Server 2000","","exploit/windows/dcerpc/ms03_026_dcom","http://www.cvedetails.com/cve/2003-0352/")
- $Null = $TableExploits.Rows.Add("Windows Server 2000","","exploit/windows/dcerpc/ms05_017_msmq","http://www.cvedetails.com/cve/2005-0059")
- $Null = $TableExploits.Rows.Add("Windows Server 2000","","exploit/windows/iis/ms03_007_ntdll_webdav","http://www.cvedetails.com/cve/2003-0109")
- $Null = $TableExploits.Rows.Add("Windows Server 2000","","exploit/windows/smb/ms05_039_pnp","http://www.cvedetails.com/cve/2005-1983")
- $Null = $TableExploits.Rows.Add("Windows Server 2000","","exploit/windows/wins/ms04_045_wins","http://www.cvedetails.com/cve/2004-1080/")
- $Null = $TableExploits.Rows.Add("Windows Server 2003","Server Pack 1","exploit/windows/dcerpc/ms07_029_msdns_zonename","http://www.cvedetails.com/cve/2007-1748")
- $Null = $TableExploits.Rows.Add("Windows Server 2003","Server Pack 1","exploit/windows/smb/ms06_040_netapi","http://www.cvedetails.com/cve/2006-3439")
- $Null = $TableExploits.Rows.Add("Windows Server 2003","Server Pack 1","exploit/windows/smb/ms06_066_nwapi","http://www.cvedetails.com/cve/2006-4688")
- $Null = $TableExploits.Rows.Add("Windows Server 2003","Server Pack 1","exploit/windows/smb/ms08_067_netapi","http://www.cvedetails.com/cve/2008-4250")
- $Null = $TableExploits.Rows.Add("Windows Server 2003","Server Pack 1","exploit/windows/wins/ms04_045_wins","http://www.cvedetails.com/cve/2004-1080/")
- $Null = $TableExploits.Rows.Add("Windows Server 2003","Service Pack 2","exploit/windows/dcerpc/ms07_029_msdns_zonename","http://www.cvedetails.com/cve/2007-1748")
- $Null = $TableExploits.Rows.Add("Windows Server 2003","Service Pack 2","exploit/windows/smb/ms08_067_netapi","http://www.cvedetails.com/cve/2008-4250")
- $Null = $TableExploits.Rows.Add("Windows Server 2003","Service Pack 2","exploit/windows/smb/ms10_061_spoolss","http://www.cvedetails.com/cve/2010-2729")
- $Null = $TableExploits.Rows.Add("Windows Server 2003","","exploit/windows/dcerpc/ms03_026_dcom","http://www.cvedetails.com/cve/2003-0352/")
- $Null = $TableExploits.Rows.Add("Windows Server 2003","","exploit/windows/smb/ms06_040_netapi","http://www.cvedetails.com/cve/2006-3439")
- $Null = $TableExploits.Rows.Add("Windows Server 2003","","exploit/windows/smb/ms08_067_netapi","http://www.cvedetails.com/cve/2008-4250")
- $Null = $TableExploits.Rows.Add("Windows Server 2003","","exploit/windows/wins/ms04_045_wins","http://www.cvedetails.com/cve/2004-1080/")
- $Null = $TableExploits.Rows.Add("Windows Server 2003 R2","","exploit/windows/dcerpc/ms03_026_dcom","http://www.cvedetails.com/cve/2003-0352/")
- $Null = $TableExploits.Rows.Add("Windows Server 2003 R2","","exploit/windows/smb/ms04_011_lsass","http://www.cvedetails.com/cve/2003-0533/")
- $Null = $TableExploits.Rows.Add("Windows Server 2003 R2","","exploit/windows/smb/ms06_040_netapi","http://www.cvedetails.com/cve/2006-3439")
- $Null = $TableExploits.Rows.Add("Windows Server 2003 R2","","exploit/windows/wins/ms04_045_wins","http://www.cvedetails.com/cve/2004-1080/")
- $Null = $TableExploits.Rows.Add("Windows Server 2008","Service Pack 2","exploit/windows/smb/ms09_050_smb2_negotiate_func_index","http://www.cvedetails.com/cve/2009-3103")
- $Null = $TableExploits.Rows.Add("Windows Server 2008","Service Pack 2","exploit/windows/smb/ms10_061_spoolss","http://www.cvedetails.com/cve/2010-2729")
- $Null = $TableExploits.Rows.Add("Windows Server 2008","","exploit/windows/smb/ms08_067_netapi","http://www.cvedetails.com/cve/2008-4250")
- $Null = $TableExploits.Rows.Add("Windows Server 2008","","exploit/windows/smb/ms09_050_smb2_negotiate_func_index","http://www.cvedetails.com/cve/2009-3103")
- $Null = $TableExploits.Rows.Add("Windows Server 2008","","exploit/windows/smb/ms10_061_spoolss","http://www.cvedetails.com/cve/2010-2729")
- $Null = $TableExploits.Rows.Add("Windows Server 2008 R2","","exploit/windows/smb/ms10_061_spoolss","http://www.cvedetails.com/cve/2010-2729")
- $Null = $TableExploits.Rows.Add("Windows Vista","Server Pack 1","exploit/windows/smb/ms08_067_netapi","http://www.cvedetails.com/cve/2008-4250")
- $Null = $TableExploits.Rows.Add("Windows Vista","Server Pack 1","exploit/windows/smb/ms09_050_smb2_negotiate_func_index","http://www.cvedetails.com/cve/2009-3103")
- $Null = $TableExploits.Rows.Add("Windows Vista","Server Pack 1","exploit/windows/smb/ms10_061_spoolss","http://www.cvedetails.com/cve/2010-2729")
- $Null = $TableExploits.Rows.Add("Windows Vista","Service Pack 2","exploit/windows/smb/ms09_050_smb2_negotiate_func_index","http://www.cvedetails.com/cve/2009-3103")
- $Null = $TableExploits.Rows.Add("Windows Vista","Service Pack 2","exploit/windows/smb/ms10_061_spoolss","http://www.cvedetails.com/cve/2010-2729")
- $Null = $TableExploits.Rows.Add("Windows Vista","","exploit/windows/smb/ms08_067_netapi","http://www.cvedetails.com/cve/2008-4250")
- $Null = $TableExploits.Rows.Add("Windows Vista","","exploit/windows/smb/ms09_050_smb2_negotiate_func_index","http://www.cvedetails.com/cve/2009-3103")
- $Null = $TableExploits.Rows.Add("Windows XP","Server Pack 1","exploit/windows/dcerpc/ms03_026_dcom","http://www.cvedetails.com/cve/2003-0352/")
- $Null = $TableExploits.Rows.Add("Windows XP","Server Pack 1","exploit/windows/dcerpc/ms05_017_msmq","http://www.cvedetails.com/cve/2005-0059")
- $Null = $TableExploits.Rows.Add("Windows XP","Server Pack 1","exploit/windows/smb/ms04_011_lsass","http://www.cvedetails.com/cve/2003-0533/")
- $Null = $TableExploits.Rows.Add("Windows XP","Server Pack 1","exploit/windows/smb/ms05_039_pnp","http://www.cvedetails.com/cve/2005-1983")
- $Null = $TableExploits.Rows.Add("Windows XP","Server Pack 1","exploit/windows/smb/ms06_040_netapi","http://www.cvedetails.com/cve/2006-3439")
- $Null = $TableExploits.Rows.Add("Windows XP","Service Pack 2","exploit/windows/dcerpc/ms05_017_msmq","http://www.cvedetails.com/cve/2005-0059")
- $Null = $TableExploits.Rows.Add("Windows XP","Service Pack 2","exploit/windows/smb/ms06_040_netapi","http://www.cvedetails.com/cve/2006-3439")
- $Null = $TableExploits.Rows.Add("Windows XP","Service Pack 2","exploit/windows/smb/ms06_066_nwapi","http://www.cvedetails.com/cve/2006-4688")
- $Null = $TableExploits.Rows.Add("Windows XP","Service Pack 2","exploit/windows/smb/ms06_070_wkssvc","http://www.cvedetails.com/cve/2006-4691")
- $Null = $TableExploits.Rows.Add("Windows XP","Service Pack 2","exploit/windows/smb/ms08_067_netapi","http://www.cvedetails.com/cve/2008-4250")
- $Null = $TableExploits.Rows.Add("Windows XP","Service Pack 2","exploit/windows/smb/ms10_061_spoolss","http://www.cvedetails.com/cve/2010-2729")
- $Null = $TableExploits.Rows.Add("Windows XP","Service Pack 3","exploit/windows/smb/ms08_067_netapi","http://www.cvedetails.com/cve/2008-4250")
- $Null = $TableExploits.Rows.Add("Windows XP","Service Pack 3","exploit/windows/smb/ms10_061_spoolss","http://www.cvedetails.com/cve/2010-2729")
- $Null = $TableExploits.Rows.Add("Windows XP","","exploit/windows/dcerpc/ms03_026_dcom","http://www.cvedetails.com/cve/2003-0352/")
- $Null = $TableExploits.Rows.Add("Windows XP","","exploit/windows/dcerpc/ms05_017_msmq","http://www.cvedetails.com/cve/2005-0059")
- $Null = $TableExploits.Rows.Add("Windows XP","","exploit/windows/smb/ms06_040_netapi","http://www.cvedetails.com/cve/2006-3439")
- $Null = $TableExploits.Rows.Add("Windows XP","","exploit/windows/smb/ms08_067_netapi","http://www.cvedetails.com/cve/2008-4250")
-
- # Status user
- Write-Verbose "[*] Checking computers for vulnerable OS and SP levels..."
-
- # ----------------------------------------------------------------
- # Setup data table to store vulnerable systems
- # ----------------------------------------------------------------
-
- # Create data table to house vulnerable server list
- $TableVulnComputers = New-Object System.Data.DataTable
- $Null = $TableVulnComputers.Columns.Add('ComputerName')
- $Null = $TableVulnComputers.Columns.Add('OperatingSystem')
- $Null = $TableVulnComputers.Columns.Add('ServicePack')
- $Null = $TableVulnComputers.Columns.Add('LastLogon')
- $Null = $TableVulnComputers.Columns.Add('MsfModule')
- $Null = $TableVulnComputers.Columns.Add('CVE')
-
- # Iterate through each exploit
- $TableExploits | ForEach-Object {
-
- $ExploitOS = $_.OperatingSystem
- $ExploitSP = $_.ServicePack
- $ExploitMsf = $_.MsfModule
- $ExploitCVE = $_.CVE
-
- # Iterate through each ADS computer
- $TableAdsComputers | ForEach-Object {
-
- $AdsHostname = $_.Hostname
- $AdsOS = $_.OperatingSystem
- $AdsSP = $_.ServicePack
- $AdsLast = $_.LastLogon
-
- # Add exploitable systems to vul computers data table
- if ($AdsOS -like "$ExploitOS*" -and $AdsSP -like "$ExploitSP" ) {
- # Add domain computer to data table
- $Null = $TableVulnComputers.Rows.Add($AdsHostname,$AdsOS,$AdsSP,$AdsLast,$ExploitMsf,$ExploitCVE)
- }
- }
- }
-
- # Display results
- $VulnComputer = $TableVulnComputers | Select-Object ComputerName -Unique | Measure-Object
- $VulnComputerCount = $VulnComputer.Count
-
- if ($VulnComputer.Count -gt 0) {
- # Return vulnerable server list order with some hack date casting
- Write-Verbose "[+] Found $VulnComputerCount potentially vulnerable systems!"
- $TableVulnComputers | Sort-Object { $_.lastlogon -as [datetime]} -Descending
- }
- else {
- Write-Verbose "[-] No vulnerable systems were found."
- }
-}
-
-
-function Invoke-EnumerateLocalAdmin {
-<#
- .SYNOPSIS
-
- This function queries the domain for all active machines with
- Get-NetComputer, then for each server it queries the local
- Administrators with Get-NetLocalGroup.
-
- Author: @harmj0y
- License: BSD 3-Clause
-
- .PARAMETER ComputerName
-
- Host array to enumerate, passable on the pipeline.
-
- .PARAMETER ComputerFile
-
- File of hostnames/IPs to search.
-
- .PARAMETER ComputerFilter
-
- Host filter name to query AD for, wildcards accepted.
-
- .PARAMETER ComputerADSpath
-
- The LDAP source to search through for hosts, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
- Useful for OU queries.
-
- .PARAMETER NoPing
-
- Switch. Don't ping each host to ensure it's up before enumerating.
-
- .PARAMETER Delay
-
- Delay between enumerating hosts, defaults to 0
-
- .PARAMETER Jitter
-
- Jitter for the host delay, defaults to +/- 0.3
-
- .PARAMETER OutFile
-
- Output results to a specified csv output file.
-
- .PARAMETER NoClobber
-
- Switch. Don't overwrite any existing output file.
-
- .PARAMETER TrustGroups
-
- Switch. Only return results that are not part of the local machine
- or the machine's domain. Old Invoke-EnumerateLocalTrustGroup
- functionality.
-
- .PARAMETER DomainOnly
-
- Switch. Only return domain (non-local) results
-
- .PARAMETER Domain
-
- Domain to query for machines, defaults to the current domain.
-
- .PARAMETER DomainController
-
- Domain controller to reflect LDAP queries through.
-
- .PARAMETER SearchForest
-
- Switch. Search all domains in the forest for target users instead of just
- a single domain.
-
- .PARAMETER API
-
- Switch. Use API calls instead of the WinNT service provider. Less information,
- but the results are faster.
-
- .PARAMETER Threads
-
- The maximum concurrent threads to execute.
-
- .EXAMPLE
-
- PS C:\> Invoke-EnumerateLocalAdmin
-
- Enumerates the members of local administrators for all machines
- in the current domain.
-
- .EXAMPLE
-
- PS C:\> Invoke-EnumerateLocalAdmin -Threads 10
-
- Threaded local admin enumeration, replaces Invoke-EnumerateLocalAdminThreaded
-
- .LINK
-
- http://blog.harmj0y.net/
-#>
-
- [CmdletBinding()]
- param(
- [Parameter(Position=0,ValueFromPipeline=$True)]
- [Alias('Hosts')]
- [String[]]
- $ComputerName,
-
- [ValidateScript({Test-Path -Path $_ })]
- [Alias('HostList')]
- [String]
- $ComputerFile,
-
- [String]
- $ComputerFilter,
-
- [String]
- $ComputerADSpath,
-
- [Switch]
- $NoPing,
-
- [UInt32]
- $Delay = 0,
-
- [Double]
- $Jitter = .3,
-
- [String]
- $OutFile,
-
- [Switch]
- $NoClobber,
-
- [Switch]
- $TrustGroups,
-
- [Switch]
- $DomainOnly,
-
- [String]
- $Domain,
-
- [String]
- $DomainController,
-
- [Switch]
- $SearchForest,
-
- [ValidateRange(1,100)]
- [Int]
- $Threads,
-
- [Switch]
- $API
- )
-
- begin {
- if ($PSBoundParameters['Debug']) {
- $DebugPreference = 'Continue'
- }
-
- # random object for delay
- $RandNo = New-Object System.Random
-
- Write-Verbose "[*] Running Invoke-EnumerateLocalAdmin with delay of $Delay"
-
- # if we're using a host list, read the targets in and add them to the target list
- if($ComputerFile) {
- $ComputerName = Get-Content -Path $ComputerFile
- }
-
- if(!$ComputerName) {
- [array]$ComputerName = @()
-
- if($Domain) {
- $TargetDomains = @($Domain)
- }
- elseif($SearchForest) {
- # get ALL the domains in the forest to search
- $TargetDomains = Get-NetForestDomain | ForEach-Object { $_.Name }
- }
- else {
- # use the local domain
- $TargetDomains = @( (Get-NetDomain).name )
- }
-
- ForEach ($Domain in $TargetDomains) {
- Write-Verbose "[*] Querying domain $Domain for hosts"
- $ComputerName += Get-NetComputer -Filter $ComputerFilter -ADSpath $ComputerADSpath -Domain $Domain -DomainController $DomainController
- }
-
- # remove any null target hosts, uniquify the list and shuffle it
- $ComputerName = $ComputerName | Where-Object { $_ } | Sort-Object -Unique | Sort-Object { Get-Random }
- if($($ComputerName.Count) -eq 0) {
- throw "No hosts found!"
- }
- }
-
- # delete any existing output file if it already exists
- if(!$NoClobber) {
- if ($OutFile -and (Test-Path -Path $OutFile)) { Remove-Item -Path $OutFile }
- }
-
- if($TrustGroups) {
-
- Write-Verbose "Determining domain trust groups"
-
- # find all group names that have one or more users in another domain
- $TrustGroupNames = Find-ForeignGroup -Domain $Domain -DomainController $DomainController | ForEach-Object { $_.GroupName } | Sort-Object -Unique
-
- $TrustGroupsSIDs = $TrustGroupNames | ForEach-Object {
- # ignore the builtin administrators group for a DC (S-1-5-32-544)
- # TODO: ignore all default built in sids?
- Get-NetGroup -Domain $Domain -DomainController $DomainController -GroupName $_ -FullData | Where-Object { $_.objectsid -notmatch "S-1-5-32-544" } | ForEach-Object { $_.objectsid }
- }
-
- # query for the primary domain controller so we can extract the domain SID for filtering
- $DomainSID = Get-DomainSID -Domain $Domain -DomainController $DomainController
- }
-
- # script block that enumerates a server
- $HostEnumBlock = {
- param($ComputerName, $Ping, $OutFile, $DomainSID, $TrustGroupsSIDs, $API, $DomainOnly)
-
- # optionally check if the server is up first
- $Up = $True
- if($Ping) {
- $Up = Test-Connection -Count 1 -Quiet -ComputerName $ComputerName
- }
- if($Up) {
- # grab the users for the local admins on this server
- if($API) {
- $LocalAdmins = Get-NetLocalGroup -ComputerName $ComputerName -API
- }
- else {
- $LocalAdmins = Get-NetLocalGroup -ComputerName $ComputerName
- }
-
- # if we just want to return cross-trust users
- if($DomainSID) {
- # get the local machine SID
- $LocalSID = ($LocalAdmins | Where-Object { $_.SID -match '.*-500$' }).SID -replace "-500$"
- Write-Verbose "LocalSid for $ComputerName : $LocalSID"
- # filter out accounts that begin with the machine SID and domain SID
- # but preserve any groups that have users across a trust ($TrustGroupSIDS)
- $LocalAdmins = $LocalAdmins | Where-Object { ($TrustGroupsSIDs -contains $_.SID) -or ((-not $_.SID.startsWith($LocalSID)) -and (-not $_.SID.startsWith($DomainSID))) }
- }
-
- if($DomainOnly) {
- $LocalAdmins = $LocalAdmins | Where-Object {$_.IsDomain}
- }
-
- if($LocalAdmins -and ($LocalAdmins.Length -ne 0)) {
- # output the results to a csv if specified
- if($OutFile) {
- $LocalAdmins | Export-PowerViewCSV -OutFile $OutFile
- }
- else {
- # otherwise return the user objects
- $LocalAdmins
- }
- }
- else {
- Write-Verbose "[!] No users returned from $ComputerName"
- }
- }
- }
- }
-
- process {
-
- if($Threads) {
- Write-Verbose "Using threading with threads = $Threads"
-
- # if we're using threading, kick off the script block with Invoke-ThreadedFunction
- $ScriptParams = @{
- 'Ping' = $(-not $NoPing)
- 'OutFile' = $OutFile
- 'DomainSID' = $DomainSID
- 'TrustGroupsSIDs' = $TrustGroupsSIDs
- }
-
- # kick off the threaded script block + arguments
- if($API) {
- $ScriptParams['API'] = $True
- }
-
- if($DomainOnly) {
- $ScriptParams['DomainOnly'] = $True
- }
-
- Invoke-ThreadedFunction -ComputerName $ComputerName -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams -Threads $Threads
- }
-
- else {
- if(-not $NoPing -and ($ComputerName.count -ne 1)) {
- # ping all hosts in parallel
- $Ping = {param($ComputerName) if(Test-Connection -ComputerName $ComputerName -Count 1 -Quiet -ErrorAction Stop){$ComputerName}}
- $ComputerName = Invoke-ThreadedFunction -NoImports -ComputerName $ComputerName -ScriptBlock $Ping -Threads 100
- }
-
- Write-Verbose "[*] Total number of active hosts: $($ComputerName.count)"
- $Counter = 0
-
- ForEach ($Computer in $ComputerName) {
-
- $Counter = $Counter + 1
-
- # sleep for our semi-randomized interval
- Start-Sleep -Seconds $RandNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay)
- Write-Verbose "[*] Enumerating server $Computer ($Counter of $($ComputerName.count))"
-
- $ScriptArgs = @($Computer, $False, $OutFile, $DomainSID, $TrustGroupsSIDs, $API, $DomainOnly)
-
- Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList $ScriptArgs
- }
- }
- }
-}
-
-
-########################################################
-#
-# Domain trust functions below.
-#
-########################################################
-
-function Get-NetDomainTrust {
-<#
- .SYNOPSIS
-
- Return all domain trusts for the current domain or
- a specified domain.
-
- .PARAMETER Domain
-
- The domain whose trusts to enumerate, defaults to the current domain.
-
- .PARAMETER DomainController
-
- Domain controller to reflect LDAP queries through.
-
- .PARAMETER ADSpath
-
- The LDAP source to search through, e.g. "LDAP://DC=testlab,DC=local".
- Useful for global catalog queries ;)
-
- .PARAMETER API
-
- Use an API call (DsEnumerateDomainTrusts) to enumerate the trusts.
-
- .PARAMETER LDAP
-
- Switch. Use LDAP queries to enumerate the trusts instead of direct domain connections.
- More likely to get around network segmentation, but not as accurate.
-
- .PARAMETER PageSize
-
- The PageSize to set for the LDAP searcher object.
-
- .EXAMPLE
-
- PS C:\> Get-NetDomainTrust
-
- Return domain trusts for the current domain using built in .NET methods.
-
- .EXAMPLE
-
- PS C:\> Get-NetDomainTrust -Domain "prod.testlab.local"
-
- Return domain trusts for the "prod.testlab.local" domain using .NET methods
-
- .EXAMPLE
-
- PS C:\> Get-NetDomainTrust -LDAP -Domain "prod.testlab.local" -DomainController "PRIMARY.testlab.local"
-
- Return domain trusts for the "prod.testlab.local" domain enumerated through LDAP
- queries, reflecting queries through the "Primary.testlab.local" domain controller,
- using .NET methods.
-
- .EXAMPLE
-
- PS C:\> Get-NetDomainTrust -API -Domain "prod.testlab.local"
-
- Return domain trusts for the "prod.testlab.local" domain enumerated through API calls.
-
- .EXAMPLE
-
- PS C:\> Get-NetDomainTrust -API -DomainController WINDOWS2.testlab.local
-
- Return domain trusts reachable from the WINDOWS2 machine through API calls.
-#>
-
- [CmdletBinding()]
- param(
- [Parameter(Position=0, ValueFromPipeline=$True)]
- [String]
- $Domain,
-
- [String]
- $DomainController,
-
- [String]
- $ADSpath,
-
- [Switch]
- $API,
-
- [Switch]
- $LDAP,
-
- [ValidateRange(1,10000)]
- [Int]
- $PageSize = 200,
-
- [Management.Automation.PSCredential]
- $Credential
- )
-
- begin {
- $TrustAttributes = @{
- [uint32]'0x00000001' = 'non_transitive'
- [uint32]'0x00000002' = 'uplevel_only'
- [uint32]'0x00000004' = 'quarantined_domain'
- [uint32]'0x00000008' = 'forest_transitive'
- [uint32]'0x00000010' = 'cross_organization'
- [uint32]'0x00000020' = 'within_forest'
- [uint32]'0x00000040' = 'treat_as_external'
- [uint32]'0x00000080' = 'trust_uses_rc4_encryption'
- [uint32]'0x00000100' = 'trust_uses_aes_keys'
- [uint32]'0x00000200' = 'cross_organization_no_tgt_delegation'
- [uint32]'0x00000400' = 'pim_trust'
- }
- }
-
- process {
-
- if(-not $Domain) {
- # if not domain is specified grab the current domain
- $SourceDomain = (Get-NetDomain -Credential $Credential).Name
- }
- else {
- $SourceDomain = $Domain
- }
-
- if($LDAP -or $ADSPath) {
-
- $TrustSearcher = Get-DomainSearcher -Domain $SourceDomain -DomainController $DomainController -Credential $Credential -PageSize $PageSize -ADSpath $ADSpath
-
- $SourceSID = Get-DomainSID -Domain $SourceDomain -DomainController $DomainController
-
- if($TrustSearcher) {
-
- $TrustSearcher.Filter = '(objectClass=trustedDomain)'
-
- $Results = $TrustSearcher.FindAll()
- $Results | Where-Object {$_} | ForEach-Object {
- $Props = $_.Properties
- $DomainTrust = New-Object PSObject
-
- $TrustAttrib = @()
- $TrustAttrib += $TrustAttributes.Keys | Where-Object { $Props.trustattributes[0] -band $_ } | ForEach-Object { $TrustAttributes[$_] }
-
- $Direction = Switch ($Props.trustdirection) {
- 0 { 'Disabled' }
- 1 { 'Inbound' }
- 2 { 'Outbound' }
- 3 { 'Bidirectional' }
- }
- $ObjectGuid = New-Object Guid @(,$Props.objectguid[0])
- $TargetSID = (New-Object System.Security.Principal.SecurityIdentifier($Props.securityidentifier[0],0)).Value
- $DomainTrust | Add-Member Noteproperty 'SourceName' $SourceDomain
- $DomainTrust | Add-Member Noteproperty 'SourceSID' $SourceSID
- $DomainTrust | Add-Member Noteproperty 'TargetName' $Props.name[0]
- $DomainTrust | Add-Member Noteproperty 'TargetSID' $TargetSID
- $DomainTrust | Add-Member Noteproperty 'ObjectGuid' "{$ObjectGuid}"
- $DomainTrust | Add-Member Noteproperty 'TrustType' $($TrustAttrib -join ',')
- $DomainTrust | Add-Member Noteproperty 'TrustDirection' "$Direction"
- $DomainTrust.PSObject.TypeNames.Add('PowerView.DomainTrustLDAP')
- $DomainTrust
- }
- $Results.dispose()
- $TrustSearcher.dispose()
- }
- }
- elseif($API) {
- if(-not $DomainController) {
- $DomainController = Get-NetDomainController -Credential $Credential -Domain $SourceDomain | Select-Object -First 1 | Select-Object -ExpandProperty Name
- }
-
- if($DomainController) {
- # arguments for DsEnumerateDomainTrusts
- $PtrInfo = [IntPtr]::Zero
-
- # 63 = DS_DOMAIN_IN_FOREST + DS_DOMAIN_DIRECT_OUTBOUND + DS_DOMAIN_TREE_ROOT + DS_DOMAIN_PRIMARY + DS_DOMAIN_NATIVE_MODE + DS_DOMAIN_DIRECT_INBOUND
- $Flags = 63
- $DomainCount = 0
-
- # get the trust information from the target server
- $Result = $Netapi32::DsEnumerateDomainTrusts($DomainController, $Flags, [ref]$PtrInfo, [ref]$DomainCount)
-
- # Locate the offset of the initial intPtr
- $Offset = $PtrInfo.ToInt64()
-
- # 0 = success
- if (($Result -eq 0) -and ($Offset -gt 0)) {
-
- # Work out how mutch to increment the pointer by finding out the size of the structure
- $Increment = $DS_DOMAIN_TRUSTS::GetSize()
-
- # parse all the result structures
- for ($i = 0; ($i -lt $DomainCount); $i++) {
- # create a new int ptr at the given offset and cast the pointer as our result structure
- $NewIntPtr = New-Object System.Intptr -ArgumentList $Offset
- $Info = $NewIntPtr -as $DS_DOMAIN_TRUSTS
-
- $Offset = $NewIntPtr.ToInt64()
- $Offset += $Increment
-
- $SidString = ""
- $Result = $Advapi32::ConvertSidToStringSid($Info.DomainSid, [ref]$SidString);$LastError = [Runtime.InteropServices.Marshal]::GetLastWin32Error()
-
- if($Result -eq 0) {
- Write-Verbose "Error: $(([ComponentModel.Win32Exception] $LastError).Message)"
- }
- else {
- $DomainTrust = New-Object PSObject
- $DomainTrust | Add-Member Noteproperty 'SourceDomain' $SourceDomain
- $DomainTrust | Add-Member Noteproperty 'SourceDomainController' $DomainController
- $DomainTrust | Add-Member Noteproperty 'NetbiosDomainName' $Info.NetbiosDomainName
- $DomainTrust | Add-Member Noteproperty 'DnsDomainName' $Info.DnsDomainName
- $DomainTrust | Add-Member Noteproperty 'Flags' $Info.Flags
- $DomainTrust | Add-Member Noteproperty 'ParentIndex' $Info.ParentIndex
- $DomainTrust | Add-Member Noteproperty 'TrustType' $Info.TrustType
- $DomainTrust | Add-Member Noteproperty 'TrustAttributes' $Info.TrustAttributes
- $DomainTrust | Add-Member Noteproperty 'DomainSid' $SidString
- $DomainTrust | Add-Member Noteproperty 'DomainGuid' $Info.DomainGuid
- $DomainTrust.PSObject.TypeNames.Add('PowerView.APIDomainTrust')
- $DomainTrust
- }
- }
- # free up the result buffer
- $Null = $Netapi32::NetApiBufferFree($PtrInfo)
- }
- else {
- Write-Verbose "Error: $(([ComponentModel.Win32Exception] $Result).Message)"
- }
- }
- else {
- Write-Verbose "Could not retrieve domain controller for $Domain"
- }
- }
- else {
- # if we're using direct domain connections through .NET
- $FoundDomain = Get-NetDomain -Domain $Domain -Credential $Credential
- if($FoundDomain) {
- $FoundDomain.GetAllTrustRelationships() | ForEach-Object {
- $_.PSObject.TypeNames.Add('PowerView.DomainTrust')
- $_
- }
- }
- }
- }
-}
-
-
-function Get-NetForestTrust {
-<#
- .SYNOPSIS
-
- Return all trusts for the current forest.
-
- .PARAMETER Forest
-
- Return trusts for the specified forest.
-
- .PARAMETER Credential
-
- A [Management.Automation.PSCredential] object of alternate credentials
- for connection to the target domain.
-
- .EXAMPLE
-
- PS C:\> Get-NetForestTrust
-
- Return current forest trusts.
-
- .EXAMPLE
-
- PS C:\> Get-NetForestTrust -Forest "test"
-
- Return trusts for the "test" forest.
-#>
-
- [CmdletBinding()]
- param(
- [Parameter(Position=0,ValueFromPipeline=$True)]
- [String]
- $Forest,
-
- [Management.Automation.PSCredential]
- $Credential
- )
-
- process {
- $FoundForest = Get-NetForest -Forest $Forest -Credential $Credential
-
- if($FoundForest) {
- $FoundForest.GetAllTrustRelationships() | ForEach-Object {
- $_.PSObject.TypeNames.Add('PowerView.ForestTrust')
- $_
- }
- }
- }
-}
-
-
-function Find-ForeignUser {
-<#
- .SYNOPSIS
-
- Enumerates users who are in groups outside of their
- principal domain. The -Recurse option will try to map all
- transitive domain trust relationships and enumerate all
- users who are in groups outside of their principal domain.
+ [Parameter(ParameterSetName = 'WinNT')]
+ [Parameter(ParameterSetName = 'API')]
+ [ValidateScript({Test-Path -Path $_ })]
+ [Alias('HostList')]
+ [String]
+ $ComputerFile,
- .PARAMETER UserName
+ [Parameter(ParameterSetName = 'WinNT')]
+ [Parameter(ParameterSetName = 'API')]
+ [String]
+ $GroupName = 'Administrators',
- Username to filter results for, wildcards accepted.
+ [Parameter(ParameterSetName = 'API')]
+ [Switch]
+ $API,
- .PARAMETER Domain
+ [Switch]
+ $IsDomain,
- Domain to query for users, defaults to the current domain.
+ [ValidateNotNullOrEmpty()]
+ [String]
+ $DomainSID
+ )
- .PARAMETER DomainController
+ process {
- Domain controller to reflect LDAP queries through.
+ $Servers = @()
- .PARAMETER LDAP
+ # if we have a host list passed, grab it
+ if($ComputerFile) {
+ $Servers = Get-Content -Path $ComputerFile
+ }
+ else {
+ # otherwise assume a single host name
+ $Servers += $ComputerName | Get-NameField
+ }
- Switch. Use LDAP queries to enumerate the trusts instead of direct domain connections.
- More likely to get around network segmentation, but not as accurate.
+ # query the specified group using the WINNT provider, and
+ # extract fields as appropriate from the results
+ ForEach($Server in $Servers) {
- .PARAMETER Recurse
+ if($API) {
+ # if we're using the Netapi32 NetLocalGroupGetMembers API call to get the local group information
- Switch. Enumerate all user trust groups from all reachable domains recursively.
+ # arguments for NetLocalGroupGetMembers
+ $QueryLevel = 2
+ $PtrInfo = [IntPtr]::Zero
+ $EntriesRead = 0
+ $TotalRead = 0
+ $ResumeHandle = 0
- .PARAMETER PageSize
+ # get the local user information
+ $Result = $Netapi32::NetLocalGroupGetMembers($Server, $GroupName, $QueryLevel, [ref]$PtrInfo, -1, [ref]$EntriesRead, [ref]$TotalRead, [ref]$ResumeHandle)
- The PageSize to set for the LDAP searcher object.
+ # Locate the offset of the initial intPtr
+ $Offset = $PtrInfo.ToInt64()
- .LINK
+ $LocalUsers = @()
- http://blog.harmj0y.net/
-#>
+ # 0 = success
+ if (($Result -eq 0) -and ($Offset -gt 0)) {
- [CmdletBinding()]
- param(
- [String]
- $UserName,
+ # Work out how mutch to increment the pointer by finding out the size of the structure
+ $Increment = $LOCALGROUP_MEMBERS_INFO_2::GetSize()
- [String]
- $Domain,
+ # parse all the result structures
+ for ($i = 0; ($i -lt $EntriesRead); $i++) {
+ # create a new int ptr at the given offset and cast the pointer as our result structure
+ $NewIntPtr = New-Object System.Intptr -ArgumentList $Offset
+ $Info = $NewIntPtr -as $LOCALGROUP_MEMBERS_INFO_2
- [String]
- $DomainController,
+ $Offset = $NewIntPtr.ToInt64()
+ $Offset += $Increment
- [Switch]
- $LDAP,
+ $SidString = ''
+ $Result2 = $Advapi32::ConvertSidToStringSid($Info.lgrmi2_sid, [ref]$SidString);$LastError = [Runtime.InteropServices.Marshal]::GetLastWin32Error()
- [Switch]
- $Recurse,
+ if($Result2 -eq 0) {
+ # error?
+ }
+ else {
+ $IsGroup = $($Info.lgrmi2_sidusage -ne 'SidTypeUser')
+ $LocalUsers += @{
+ 'ComputerName' = $Server
+ 'AccountName' = $Info.lgrmi2_domainandname
+ 'SID' = $SidString
+ 'IsGroup' = $IsGroup
+ 'Type' = 'LocalUser'
+ }
+ }
+ }
- [ValidateRange(1,10000)]
- [Int]
- $PageSize = 200
- )
+ # free up the result buffer
+ $Null = $Netapi32::NetApiBufferFree($PtrInfo)
- function Get-ForeignUser {
- # helper used to enumerate users who are in groups outside of their principal domain
- param(
- [String]
- $UserName,
+ $MachineSid = ($LocalUsers | Where-Object {$_['SID'] -like '*-500'})['SID']
+ $MachineSid = $MachineSid.Substring(0, $MachineSid.LastIndexOf('-'))
+ try {
+ ForEach($LocalUser in $LocalUsers) {
+ if($DomainSID -and ($LocalUser['SID'] -match $DomainSID)) {
+ $LocalUser['IsDomain'] = $True
+ }
+ elseif($LocalUser['SID'] -match $MachineSid) {
+ $LocalUser['IsDomain'] = $False
+ }
+ else {
+ $LocalUser['IsDomain'] = $True
+ }
+ if($IsDomain) {
+ if($LocalUser['IsDomain']) {
+ $LocalUser
+ }
+ }
+ else {
+ $LocalUser
+ }
+ }
+ }
+ catch { }
+ }
+ else {
+ # error
+ }
+ }
- [String]
- $Domain,
+ else {
+ # otherwise we're using the WinNT service provider
+ try {
+ $LocalUsers = @()
+ $Members = @($([ADSI]"WinNT://$Server/$GroupName,group").psbase.Invoke('Members'))
- [String]
- $DomainController,
+ $Members | ForEach-Object {
+ $LocalUser = ([ADSI]$_)
- [ValidateRange(1,10000)]
- [Int]
- $PageSize = 200
- )
+ $AdsPath = $LocalUser.InvokeGet('AdsPath').Replace('WinNT://', '')
- if ($Domain) {
- # get the domain name into distinguished form
- $DistinguishedDomainName = "DC=" + $Domain -replace '\.',',DC='
- }
- else {
- $DistinguishedDomainName = [String] ([adsi]'').distinguishedname
- $Domain = $DistinguishedDomainName -replace 'DC=','' -replace ',','.'
- }
+ if(([regex]::Matches($AdsPath, '/')).count -eq 1) {
+ # DOMAIN\user
+ $MemberIsDomain = $True
+ $Name = $AdsPath.Replace('/', '\')
+ }
+ else {
+ # DOMAIN\machine\user
+ $MemberIsDomain = $False
+ $Name = $AdsPath.Substring($AdsPath.IndexOf('/')+1).Replace('/', '\')
+ }
- Get-NetUser -Domain $Domain -DomainController $DomainController -UserName $UserName -PageSize $PageSize -Filter '(memberof=*)' | ForEach-Object {
- ForEach ($Membership in $_.memberof) {
- $Index = $Membership.IndexOf("DC=")
- if($Index) {
-
- $GroupDomain = $($Membership.substring($Index)) -replace 'DC=','' -replace ',','.'
-
- if ($GroupDomain.CompareTo($Domain)) {
- # if the group domain doesn't match the user domain, output
- $GroupName = $Membership.split(",")[0].split("=")[1]
- $ForeignUser = New-Object PSObject
- $ForeignUser | Add-Member Noteproperty 'UserDomain' $Domain
- $ForeignUser | Add-Member Noteproperty 'UserName' $_.samaccountname
- $ForeignUser | Add-Member Noteproperty 'GroupDomain' $GroupDomain
- $ForeignUser | Add-Member Noteproperty 'GroupName' $GroupName
- $ForeignUser | Add-Member Noteproperty 'GroupDN' $Membership
- $ForeignUser
+ $IsGroup = ($LocalUser.SchemaClassName -like 'group')
+ if($IsDomain) {
+ if($MemberIsDomain) {
+ $LocalUsers += @{
+ 'ComputerName' = $Server
+ 'AccountName' = $Name
+ 'SID' = ((New-Object System.Security.Principal.SecurityIdentifier($LocalUser.InvokeGet('ObjectSID'),0)).Value)
+ 'IsGroup' = $IsGroup
+ 'IsDomain' = $MemberIsDomain
+ 'Type' = 'LocalUser'
+ }
+ }
+ }
+ else {
+ $LocalUsers += @{
+ 'ComputerName' = $Server
+ 'AccountName' = $Name
+ 'SID' = ((New-Object System.Security.Principal.SecurityIdentifier($LocalUser.InvokeGet('ObjectSID'),0)).Value)
+ 'IsGroup' = $IsGroup
+ 'IsDomain' = $MemberIsDomain
+ 'Type' = 'LocalUser'
+ }
+ }
}
+ $LocalUsers
+ }
+ catch {
+ Write-Verbose "Get-NetLocalGroup error for $Server : $_"
}
}
}
}
-
- if ($Recurse) {
- # get all rechable domains in the trust mesh and uniquify them
- if($LDAP -or $DomainController) {
- $DomainTrusts = Invoke-MapDomainTrust -LDAP -DomainController $DomainController -PageSize $PageSize | ForEach-Object { $_.SourceDomain } | Sort-Object -Unique
- }
- else {
- $DomainTrusts = Invoke-MapDomainTrust -PageSize $PageSize | ForEach-Object { $_.SourceDomain } | Sort-Object -Unique
- }
-
- ForEach($DomainTrust in $DomainTrusts) {
- # get the trust groups for each domain in the trust mesh
- Write-Verbose "Enumerating trust groups in domain $DomainTrust"
- Get-ForeignUser -Domain $DomainTrust -UserName $UserName -PageSize $PageSize
- }
- }
- else {
- Get-ForeignUser -Domain $Domain -DomainController $DomainController -UserName $UserName -PageSize $PageSize
- }
}
-function Find-ForeignGroup {
+filter Get-NetLoggedon {
<#
.SYNOPSIS
- Enumerates all the members of a given domain's groups
- and finds users that are not in the queried domain.
- The -Recurse flag will perform this enumeration for all
- eachable domain trusts.
+ This function will execute the NetWkstaUserEnum Win32API call to query
+ a given host for actively logged on users.
- .PARAMETER GroupName
+ .PARAMETER ComputerName
- Groupname to filter results for, wildcards accepted.
+ The hostname to query for logged on users.
- .PARAMETER Domain
+ .OUTPUTS
- Domain to query for groups, defaults to the current domain.
+ WKSTA_USER_INFO_1 structure. A representation of the WKSTA_USER_INFO_1
+ result structure which includes the username and domain of logged on users,
+ with the ComputerName added.
- .PARAMETER DomainController
+ .EXAMPLE
- Domain controller to reflect LDAP queries through.
+ PS C:\> Get-NetLoggedon
- .PARAMETER LDAP
+ Returns users actively logged onto the local host.
- Switch. Use LDAP queries to enumerate the trusts instead of direct domain connections.
- More likely to get around network segmentation, but not as accurate.
+ .EXAMPLE
- .PARAMETER Recurse
+ PS C:\> Get-NetLoggedon -ComputerName sqlserver
- Switch. Enumerate all group trust users from all reachable domains recursively.
+ Returns users actively logged onto the 'sqlserver' host.
- .PARAMETER PageSize
+ .EXAMPLE
- The PageSize to set for the LDAP searcher object.
+ PS C:\> Get-NetComputer | Get-NetLoggedon
+
+ Returns all logged on userse for all computers in the domain.
.LINK
- http://blog.harmj0y.net/
+ http://www.powershellmagazine.com/2014/09/25/easily-defining-enums-structs-and-win32-functions-in-memory/
#>
[CmdletBinding()]
param(
- [String]
- $GroupName = '*',
-
- [String]
- $Domain,
-
- [String]
- $DomainController,
+ [Parameter(ValueFromPipeline=$True)]
+ [Alias('HostName')]
+ [Object[]]
+ [ValidateNotNullOrEmpty()]
+ $ComputerName = 'localhost'
+ )
- [Switch]
- $LDAP,
+ # extract the computer name from whatever object was passed on the pipeline
+ $Computer = $ComputerName | Get-NameField
- [Switch]
- $Recurse,
+ # Declare the reference variables
+ $QueryLevel = 1
+ $PtrInfo = [IntPtr]::Zero
+ $EntriesRead = 0
+ $TotalRead = 0
+ $ResumeHandle = 0
- [ValidateRange(1,10000)]
- [Int]
- $PageSize = 200
- )
+ # get logged on user information
+ $Result = $Netapi32::NetWkstaUserEnum($Computer, $QueryLevel, [ref]$PtrInfo, -1, [ref]$EntriesRead, [ref]$TotalRead, [ref]$ResumeHandle)
- function Get-ForeignGroup {
- param(
- [String]
- $GroupName = '*',
+ # Locate the offset of the initial intPtr
+ $Offset = $PtrInfo.ToInt64()
- [String]
- $Domain,
+ # 0 = success
+ if (($Result -eq 0) -and ($Offset -gt 0)) {
- [String]
- $DomainController,
+ # Work out how mutch to increment the pointer by finding out the size of the structure
+ $Increment = $WKSTA_USER_INFO_1::GetSize()
- [ValidateRange(1,10000)]
- [Int]
- $PageSize = 200
- )
+ # parse all the result structures
+ for ($i = 0; ($i -lt $EntriesRead); $i++) {
+ # create a new int ptr at the given offset and cast the pointer as our result structure
+ $NewIntPtr = New-Object System.Intptr -ArgumentList $Offset
+ $Info = $NewIntPtr -as $WKSTA_USER_INFO_1
- if(-not $Domain) {
- $Domain = (Get-NetDomain).Name
+ # return all the sections of the structure
+ $LoggedOn = $Info | Select-Object *
+ $LoggedOn | Add-Member Noteproperty 'ComputerName' $Computer
+ $Offset = $NewIntPtr.ToInt64()
+ $Offset += $Increment
+ $LoggedOn
}
- $DomainDN = "DC=$($Domain.Replace('.', ',DC='))"
- Write-Verbose "DomainDN: $DomainDN"
+ # free up the result buffer
+ $Null = $Netapi32::NetApiBufferFree($PtrInfo)
+ }
+ else {
+ Write-Verbose "Error: $(([ComponentModel.Win32Exception] $Result).Message)"
+ }
+}
- # standard group names to ignore
- $ExcludeGroups = @("Users", "Domain Users", "Guests")
- # get all the groupnames for the given domain
- Get-NetGroup -GroupName $GroupName -Filter '(member=*)' -Domain $Domain -DomainController $DomainController -FullData -PageSize $PageSize | Where-Object {
- # exclude common large groups
- -not ($ExcludeGroups -contains $_.samaccountname) } | ForEach-Object {
+filter Get-NetSession {
+<#
+ .SYNOPSIS
- $GroupName = $_.samAccountName
+ This function will execute the NetSessionEnum Win32API call to query
+ a given host for active sessions on the host.
+ Heavily adapted from dunedinite's post on stackoverflow (see LINK below)
- $_.member | ForEach-Object {
- # filter for foreign SIDs in the cn field for users in another domain,
- # or if the DN doesn't end with the proper DN for the queried domain
- if (($_ -match 'CN=S-1-5-21.*-.*') -or ($DomainDN -ne ($_.substring($_.IndexOf("DC="))))) {
+ .PARAMETER ComputerName
- $UserDomain = $_.subString($_.IndexOf("DC=")) -replace 'DC=','' -replace ',','.'
- $UserName = $_.split(",")[0].split("=")[1]
+ The ComputerName to query for active sessions.
- $ForeignGroupUser = New-Object PSObject
- $ForeignGroupUser | Add-Member Noteproperty 'GroupDomain' $Domain
- $ForeignGroupUser | Add-Member Noteproperty 'GroupName' $GroupName
- $ForeignGroupUser | Add-Member Noteproperty 'UserDomain' $UserDomain
- $ForeignGroupUser | Add-Member Noteproperty 'UserName' $UserName
- $ForeignGroupUser | Add-Member Noteproperty 'UserDN' $_
- $ForeignGroupUser
- }
- }
- }
- }
+ .PARAMETER UserName
- if ($Recurse) {
- # get all rechable domains in the trust mesh and uniquify them
- if($LDAP -or $DomainController) {
- $DomainTrusts = Invoke-MapDomainTrust -LDAP -DomainController $DomainController -PageSize $PageSize | ForEach-Object { $_.SourceDomain } | Sort-Object -Unique
- }
- else {
- $DomainTrusts = Invoke-MapDomainTrust -PageSize $PageSize | ForEach-Object { $_.SourceDomain } | Sort-Object -Unique
- }
+ The user name to filter for active sessions.
- ForEach($DomainTrust in $DomainTrusts) {
- # get the trust groups for each domain in the trust mesh
- Write-Verbose "Enumerating trust groups in domain $DomainTrust"
- Get-ForeignGroup -GroupName $GroupName -Domain $Domain -DomainController $DomainController -PageSize $PageSize
- }
- }
- else {
- Get-ForeignGroup -GroupName $GroupName -Domain $Domain -DomainController $DomainController -PageSize $PageSize
- }
-}
+ .OUTPUTS
+ SESSION_INFO_10 structure. A representation of the SESSION_INFO_10
+ result structure which includes the host and username associated
+ with active sessions, with the ComputerName added.
-function Find-ManagedSecurityGroups {
-<#
- .SYNOPSIS
+ .EXAMPLE
- This function retrieves all security groups in the domain and identifies ones that
- have a manager set. It also determines whether the manager has the ability to add
- or remove members from the group.
+ PS C:\> Get-NetSession
- Author: Stuart Morgan (@ukstufus)
- License: BSD 3-Clause
+ Returns active sessions on the local host.
.EXAMPLE
- PS C:\> Find-ManagedSecurityGroups | Export-PowerViewCSV -NoTypeInformation group-managers.csv
+ PS C:\> Get-NetSession -ComputerName sqlserver
- Store a list of all security groups with managers in group-managers.csv
+ Returns active sessions on the 'sqlserver' host.
- .DESCRIPTION
+ .EXAMPLE
- 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.
+ PS C:\> Get-NetDomainController | Get-NetSession
- 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.
+ Returns active sessions on all domain controllers.
.LINK
- https://github.com/PowerShellEmpire/Empire/pull/119
-
+ http://www.powershellmagazine.com/2014/09/25/easily-defining-enums-structs-and-win32-functions-in-memory/
#>
- # Go through the list of security groups on the domain and identify those who have a manager
- Get-NetGroup -FullData -Filter '(&(managedBy=*)(groupType:1.2.840.113556.1.4.803:=2147483648))' | Select-Object -Unique distinguishedName,managedBy,cn | ForEach-Object {
-
- # Retrieve the object that the managedBy DN refers to
- $group_manager = Get-ADObject -ADSPath $_.managedBy | Select-Object cn,distinguishedname,name,samaccounttype,samaccountname
-
- # Create a results object to store our findings
- $results_object = New-Object -TypeName PSObject -Property @{
- 'GroupCN' = $_.cn
- 'GroupDN' = $_.distinguishedname
- 'ManagerCN' = $group_manager.cn
- 'ManagerDN' = $group_manager.distinguishedName
- 'ManagerSAN' = $group_manager.samaccountname
- 'ManagerType' = ''
- 'CanManagerWrite' = $FALSE
- }
+ [CmdletBinding()]
+ param(
+ [Parameter(ValueFromPipeline=$True)]
+ [Alias('HostName')]
+ [Object[]]
+ [ValidateNotNullOrEmpty()]
+ $ComputerName = 'localhost',
- # Determine whether the manager is a user or a group
- if ($group_manager.samaccounttype -eq 0x10000000) {
- $results_object.ManagerType = 'Group'
- } elseif ($group_manager.samaccounttype -eq 0x30000000) {
- $results_object.ManagerType = 'User'
- }
+ [String]
+ $UserName = ''
+ )
+
+ # extract the computer name from whatever object was passed on the pipeline
+ $Computer = $ComputerName | Get-NameField
- # Find the ACLs that relate to the ability to write to the group
- $xacl = Get-ObjectAcl -ADSPath $_.distinguishedname -Rights WriteMembers
+ # arguments for NetSessionEnum
+ $QueryLevel = 10
+ $PtrInfo = [IntPtr]::Zero
+ $EntriesRead = 0
+ $TotalRead = 0
+ $ResumeHandle = 0
+
+ # get session information
+ $Result = $Netapi32::NetSessionEnum($Computer, '', $UserName, $QueryLevel, [ref]$PtrInfo, -1, [ref]$EntriesRead, [ref]$TotalRead, [ref]$ResumeHandle)
+
+ # Locate the offset of the initial intPtr
+ $Offset = $PtrInfo.ToInt64()
+
+ # 0 = success
+ if (($Result -eq 0) -and ($Offset -gt 0)) {
+
+ # Work out how mutch to increment the pointer by finding out the size of the structure
+ $Increment = $SESSION_INFO_10::GetSize()
+
+ # parse all the result structures
+ for ($i = 0; ($i -lt $EntriesRead); $i++) {
+ # create a new int ptr at the given offset and cast the pointer as our result structure
+ $NewIntPtr = New-Object System.Intptr -ArgumentList $Offset
+ $Info = $NewIntPtr -as $SESSION_INFO_10
- # Double-check that the manager
- if ($xacl.ObjectType -eq 'bf9679c0-0de6-11d0-a285-00aa003049e2' -and $xacl.AccessControlType -eq 'Allow' -and $xacl.IdentityReference.Value.Contains($group_manager.samaccountname)) {
- $results_object.CanManagerWrite = $TRUE
+ # return all the sections of the structure
+ $Sessions = $Info | Select-Object *
+ $Sessions | Add-Member Noteproperty 'ComputerName' $Computer
+ $Offset = $NewIntPtr.ToInt64()
+ $Offset += $Increment
+ $Sessions
}
- $results_object
+ # free up the result buffer
+ $Null = $Netapi32::NetApiBufferFree($PtrInfo)
+ }
+ else {
+ Write-Verbose "Error: $(([ComponentModel.Win32Exception] $Result).Message)"
}
}
-function Invoke-MapDomainTrust {
+filter Get-LoggedOnLocal {
<#
.SYNOPSIS
- This function gets all trusts for the current domain,
- and tries to get all trusts for each domain it finds.
-
- .PARAMETER LDAP
+ This function will query the HKU registry values to retrieve the local
+ logged on users SID and then attempt and reverse it.
+ Adapted technique from Sysinternal's PSLoggedOn script. Benefit over
+ using the NetWkstaUserEnum API (Get-NetLoggedon) of less user privileges
+ required (NetWkstaUserEnum requires remote admin access).
- Switch. Use LDAP queries to enumerate the trusts instead of direct domain connections.
- More likely to get around network segmentation, but not as accurate.
+ Note: This function requires only domain user rights on the
+ machine you're enumerating, but remote registry must be enabled.
- .PARAMETER DomainController
+ Function: Get-LoggedOnLocal
+ Author: Matt Kelly, @BreakersAll
- Domain controller to reflect LDAP queries through.
+ .PARAMETER ComputerName
- .PARAMETER PageSize
+ The ComputerName to query for active sessions.
- The PageSize to set for the LDAP searcher object.
+ .EXAMPLE
- .PARAMETER Credential
+ PS C:\> Get-LoggedOnLocal
- A [Management.Automation.PSCredential] object of alternate credentials
- for connection to the target domain.
+ Returns active sessions on the local host.
.EXAMPLE
- PS C:\> Invoke-MapDomainTrust | Export-CSV -NoTypeInformation trusts.csv
-
- Map all reachable domain trusts and output everything to a .csv file.
+ PS C:\> Get-LoggedOnLocal -ComputerName sqlserver
- .LINK
+ Returns active sessions on the 'sqlserver' host.
- http://blog.harmj0y.net/
#>
+
[CmdletBinding()]
param(
- [Switch]
- $LDAP,
-
- [String]
- $DomainController,
-
- [ValidateRange(1,10000)]
- [Int]
- $PageSize = 200,
-
- [Management.Automation.PSCredential]
- $Credential
+ [Parameter(ValueFromPipeline=$True)]
+ [Alias('HostName')]
+ [Object[]]
+ [ValidateNotNullOrEmpty()]
+ $ComputerName = 'localhost'
)
- # keep track of domains seen so we don't hit infinite recursion
- $SeenDomains = @{}
-
- # our domain status tracker
- $Domains = New-Object System.Collections.Stack
-
- # get the current domain and push it onto the stack
- $CurrentDomain = (Get-NetDomain -Credential $Credential).Name
- $Domains.push($CurrentDomain)
-
- while($Domains.Count -ne 0) {
-
- $Domain = $Domains.Pop()
-
- # if we haven't seen this domain before
- if ($Domain -and ($Domain.Trim() -ne "") -and (-not $SeenDomains.ContainsKey($Domain))) {
-
- Write-Verbose "Enumerating trusts for domain '$Domain'"
-
- # mark it as seen in our list
- $Null = $SeenDomains.add($Domain, "")
-
- try {
- # get all the trusts for this domain
- if($LDAP -or $DomainController) {
- $Trusts = Get-NetDomainTrust -Domain $Domain -LDAP -DomainController $DomainController -PageSize $PageSize -Credential $Credential
- }
- else {
- $Trusts = Get-NetDomainTrust -Domain $Domain -PageSize $PageSize -Credential $Credential
- }
-
- if($Trusts -isnot [System.Array]) {
- $Trusts = @($Trusts)
- }
-
- # get any forest trusts, if they exist
- if(-not ($LDAP -or $DomainController) ) {
- $Trusts += Get-NetForestTrust -Forest $Domain -Credential $Credential
- }
-
- if ($Trusts) {
- if($Trusts -isnot [System.Array]) {
- $Trusts = @($Trusts)
- }
+ # process multiple host object types from the pipeline
+ $ComputerName = Get-NameField -Object $ComputerName
- # enumerate each trust found
- ForEach ($Trust in $Trusts) {
- if($Trust.SourceName -and $Trust.TargetName) {
- $SourceDomain = $Trust.SourceName
- $TargetDomain = $Trust.TargetName
- $TrustType = $Trust.TrustType
- $TrustDirection = $Trust.TrustDirection
- $ObjectType = $Trust.PSObject.TypeNames | Where-Object {$_ -match 'PowerView'} | Select-Object -First 1
+ try {
+ # retrieve HKU remote registry values
+ $Reg = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey('Users', "$ComputerName")
- # make sure we process the target
- $Null = $Domains.Push($TargetDomain)
+ # sort out bogus sid's like _class
+ $Reg.GetSubKeyNames() | Where-Object { $_ -match 'S-1-5-21-[0-9]+-[0-9]+-[0-9]+-[0-9]+$' } | ForEach-Object {
+ $UserName = Convert-SidToName $_
- # build the nicely-parsable custom output object
- $DomainTrust = New-Object PSObject
- $DomainTrust | Add-Member Noteproperty 'SourceDomain' "$SourceDomain"
- $DomainTrust | Add-Member Noteproperty 'SourceSID' $Trust.SourceSID
- $DomainTrust | Add-Member Noteproperty 'TargetDomain' "$TargetDomain"
- $DomainTrust | Add-Member Noteproperty 'TargetSID' $Trust.TargetSID
- $DomainTrust | Add-Member Noteproperty 'TrustType' "$TrustType"
- $DomainTrust | Add-Member Noteproperty 'TrustDirection' "$TrustDirection"
- $DomainTrust.PSObject.TypeNames.Add($ObjectType)
- $DomainTrust
- }
- }
- }
- }
- catch {
- Write-Verbose "[!] Error: $_"
+ $Parts = $UserName.Split('\')
+ $UserDomain = $Null
+ $UserName = $Parts[-1]
+ if ($Parts.Length -eq 2) {
+ $UserDomain = $Parts[0]
}
+
+ $LocalLoggedOnUser = New-Object PSObject
+ $LocalLoggedOnUser | Add-Member Noteproperty 'ComputerName' "$ComputerName"
+ $LocalLoggedOnUser | Add-Member Noteproperty 'UserDomain' $UserDomain
+ $LocalLoggedOnUser | Add-Member Noteproperty 'UserName' $UserName
+ $LocalLoggedOnUser | Add-Member Noteproperty 'UserSID' $_
+ $LocalLoggedOnUser
}
}
+ catch { }
}
########################################################
#
-# BloodHound specific fuctions.
+# Domain trust functions below.
#
########################################################
-function Get-BloodHoundData {
+function Get-NetDomainTrust {
<#
.SYNOPSIS
- This function automates the collection of the data needed for BloodHound.
+ Return all domain trusts for the current domain or
+ a specified domain.
- Author: @harmj0y
- License: BSD 3-Clause
- Required Dependencies: PowerView.ps1
- Optional Dependencies: None
+ .PARAMETER Domain
- .DESCRIPTION
+ The domain whose trusts to enumerate, defaults to the current domain.
- This function collects the information needed to populate the BloodHound graph
- database. It offers a varity of targeting and collection options.
- By default, it will map all domain trusts, enumerate all groups and associated memberships,
- enumerate all computers on the domain and execute session/loggedon/local admin enumeration
- queries against each. Targeting options are modifiable with -CollectionMethod. The
- -SearchForest searches all domains in the forest instead of just the current domain.
+ .PARAMETER DomainController
- .PARAMETER ComputerName
+ Domain controller to reflect LDAP queries through.
- Host array to enumerate, passable on the pipeline.
+ .PARAMETER ADSpath
- .PARAMETER Domain
+ The LDAP source to search through, e.g. "LDAP://DC=testlab,DC=local".
+ Useful for global catalog queries ;)
- Domain to query for machines, defaults to the current domain.
+ .PARAMETER API
- .PARAMETER DomainController
+ Use an API call (DsEnumerateDomainTrusts) to enumerate the trusts.
- Domain controller to reflect LDAP queries through.
+ .PARAMETER LDAP
- .PARAMETER CollectionMethod
+ Switch. Use LDAP queries to enumerate the trusts instead of direct domain connections.
+ More likely to get around network segmentation, but not as accurate.
- The method to collect data. 'Group', 'LocalGroup', 'GPOLocalGroup', 'Sesssion', 'LoggedOn', 'Trusts, 'TrustsLDAP', 'Stealth', or 'Default'.
- 'TrustsLDAP' uses LDAP enumeration for trusts, while 'Trusts' using .NET methods.
- 'Stealth' uses 'Group' collection, stealth user hunting ('Session' on certain servers), 'GPOLocalGroup' enumeration, and LDAP trust enumeration.
- 'Default' uses 'Group' collection, regular user hunting with 'Session'/'LoggedOn', 'LocalGroup' enumeration, and 'Trusts' enumeration.
+ .PARAMETER PageSize
- .PARAMETER SearchForest
+ The PageSize to set for the LDAP searcher object.
- Switch. Search all domains in the forest for target users instead of just
- a single domain.
+ .EXAMPLE
- .PARAMETER Threads
+ PS C:\> Get-NetDomainTrust
- The maximum concurrent threads to execute.
+ Return domain trusts for the current domain using built in .NET methods.
.EXAMPLE
- PS C:\> Get-BloodHoundData | Export-BloodHoundData -URI http://SERVER:7474/ -UserPass "user:pass"
+ PS C:\> Get-NetDomainTrust -Domain "prod.testlab.local"
- Executes default collection options and exports the data to a BloodHound neo4j RESTful API endpoint.
+ Return domain trusts for the "prod.testlab.local" domain using .NET methods
.EXAMPLE
- PS C:\> Get-BloodHoundData | Export-BloodHoundData -URI http://SERVER:7474/ -UserPass "user:pass" -Threads 20
+ PS C:\> Get-NetDomainTrust -LDAP -Domain "prod.testlab.local" -DomainController "PRIMARY.testlab.local"
- Executes default collection options and exports the data to a BloodHound neo4j RESTful API endpoint,
- and use 20 threads for collection operations.
+ Return domain trusts for the "prod.testlab.local" domain enumerated through LDAP
+ queries, reflecting queries through the "Primary.testlab.local" domain controller,
+ using .NET methods.
.EXAMPLE
- PS C:\> Get-BloodHoundData | Export-BloodHoundCSV
+ PS C:\> Get-NetDomainTrust -API -Domain "prod.testlab.local"
- Executes default collection options and exports the data to a CSVs in the current directory.
+ Return domain trusts for the "prod.testlab.local" domain enumerated through API calls.
.EXAMPLE
- PS C:\> Get-BloodHoundData -CollectionMethod 'Stealth' | Export-BloodHoundData -URI http://SERVER:7474/ -UserPass "user:pass"
+ PS C:\> Get-NetDomainTrust -API -DomainController WINDOWS2.testlab.local
- Executes 'stealth' collection options and exports the data to a BloodHound neo4j RESTful API endpoint.
- This includes 'stealth' user hunting and GPO object correlation for local admin membership.
- This is significantly faster but the information is not as complete as the default options.
+ Return domain trusts reachable from the WINDOWS2 machine through API calls.
#>
- [CmdletBinding(DefaultParameterSetName = 'None')]
+ [CmdletBinding()]
param(
[Parameter(Position=0, ValueFromPipeline=$True)]
- [Alias('Hosts')]
- [String[]]
- $ComputerName,
-
[String]
$Domain,
@@ -13351,296 +4095,356 @@ function Get-BloodHoundData {
$DomainController,
[String]
- [ValidateSet('Group', 'LocalGroup', 'GPOLocalGroup', 'Session', 'LoggedOn', 'Stealth', 'Trusts', 'TrustsLDAP', 'Default')]
- $CollectionMethod = 'Default',
+ $ADSpath,
[Switch]
- $SearchForest,
+ $API,
+
+ [Switch]
+ $LDAP,
- [ValidateRange(1,100)]
+ [ValidateRange(1,10000)]
[Int]
- $Threads
+ $PageSize = 200,
+
+ [Management.Automation.PSCredential]
+ $Credential
)
begin {
-
- Switch ($CollectionMethod) {
- 'Group' { $UseGroup = $True; $SkipComputerEnumeration = $True }
- 'LocalGroup' { $UseLocalGroup = $True }
- 'GPOLocalGroup' { $UseGPOGroup = $True; $SkipComputerEnumeration = $True }
- 'Session' { $UseSession = $True }
- 'LoggedOn' { $UseLoggedOn = $True }
- 'TrustsLDAP' { $UseDomainTrustsLDAP = $True; $SkipComputerEnumeration = $True }
- 'Trusts' { $UseDomainTrusts = $True; $SkipComputerEnumeration = $True }
- 'Stealth' {
- $UseGroup = $True
- $UseGPOGroup = $True
- $UseSession = $True
- $UseDomainTrustsLDAP = $True
- }
- 'Default' {
- $UseGroup = $True
- $UseLocalGroup = $True
- $UseSession = $True
- $UseLoggedOn = $True
- $UseDomainTrusts = $True
- }
+ $TrustAttributes = @{
+ [uint32]'0x00000001' = 'non_transitive'
+ [uint32]'0x00000002' = 'uplevel_only'
+ [uint32]'0x00000004' = 'quarantined_domain'
+ [uint32]'0x00000008' = 'forest_transitive'
+ [uint32]'0x00000010' = 'cross_organization'
+ [uint32]'0x00000020' = 'within_forest'
+ [uint32]'0x00000040' = 'treat_as_external'
+ [uint32]'0x00000080' = 'trust_uses_rc4_encryption'
+ [uint32]'0x00000100' = 'trust_uses_aes_keys'
+ [uint32]'0x00000200' = 'cross_organization_no_tgt_delegation'
+ [uint32]'0x00000400' = 'pim_trust'
}
+ }
- if($Domain) {
- $TargetDomains = @($Domain)
- }
- elseif($SearchForest) {
- # get ALL the domains in the forest to search
- $TargetDomains = Get-NetForestDomain | ForEach-Object { $_.Name }
+ process {
+
+ if(-not $Domain) {
+ # if not domain is specified grab the current domain
+ $SourceDomain = (Get-NetDomain -Credential $Credential).Name
}
else {
- # use the local domain
- $TargetDomains = @( (Get-NetDomain).name )
+ $SourceDomain = $Domain
}
- if($UseGroup) {
- ForEach ($TargetDomain in $TargetDomains) {
- # enumerate all groups and all members of each group
- Get-NetGroup -Domain $TargetDomain -DomainController $DomainController | Get-NetGroupMember -Domain $TargetDomain -DomainController $DomainController
+ if($LDAP -or $ADSPath) {
- # enumerate all user objects so we can extract out the primary group for each
- # Get-NetUser -Domain $Domain -DomainController $DomainController
- $DomainSID = Get-DomainSID -Domain $TargetDomain -DomainController $DomainController
- $UserSearcher = Get-DomainSearcher -Domain $TargetDomain -DomainController $DomainController
- $UserSearcher.filter = '(samAccountType=805306368)'
- $UserSearcher.PropertiesToLoad.AddRange(('samaccountname','primarygroupid'))
- $PrimaryGroups = @{}
+ $TrustSearcher = Get-DomainSearcher -Domain $SourceDomain -DomainController $DomainController -Credential $Credential -PageSize $PageSize -ADSpath $ADSpath
- ForEach($UserResult in $UserSearcher.FindAll()) {
- $User = Convert-LDAPProperty -Properties $UserResult.Properties
- $User | Add-Member NoteProperty 'Domain' $TargetDomain
+ $SourceSID = Get-DomainSID -Domain $SourceDomain -DomainController $DomainController
- $PrimaryGroupSID = "$DomainSID-$($User.primarygroupid)"
+ if($TrustSearcher) {
- if($PrimaryGroups[$PrimaryGroupSID]) {
- $PrimaryGroupName = $PrimaryGroups[$PrimaryGroupSID]
- }
- else {
- $PrimaryGroupName = Get-ADObject -Domain $Domain -SID $PrimaryGroupSID | Select-Object -ExpandProperty samaccountname
- $PrimaryGroups[$PrimaryGroupSID] = $PrimaryGroupName
- }
+ $TrustSearcher.Filter = '(objectClass=trustedDomain)'
- $User | Add-Member NoteProperty 'PrimaryGroupName' $PrimaryGroupName
- $User.PSObject.TypeNames.Add('PowerView.User')
- $User
- }
+ $Results = $TrustSearcher.FindAll()
+ $Results | Where-Object {$_} | ForEach-Object {
+ $Props = $_.Properties
+ $DomainTrust = New-Object PSObject
+
+ $TrustAttrib = @()
+ $TrustAttrib += $TrustAttributes.Keys | Where-Object { $Props.trustattributes[0] -band $_ } | ForEach-Object { $TrustAttributes[$_] }
- $UserSearcher.Dispose()
+ $Direction = Switch ($Props.trustdirection) {
+ 0 { 'Disabled' }
+ 1 { 'Inbound' }
+ 2 { 'Outbound' }
+ 3 { 'Bidirectional' }
+ }
+ $ObjectGuid = New-Object Guid @(,$Props.objectguid[0])
+ $TargetSID = (New-Object System.Security.Principal.SecurityIdentifier($Props.securityidentifier[0],0)).Value
+ $DomainTrust | Add-Member Noteproperty 'SourceName' $SourceDomain
+ $DomainTrust | Add-Member Noteproperty 'SourceSID' $SourceSID
+ $DomainTrust | Add-Member Noteproperty 'TargetName' $Props.name[0]
+ $DomainTrust | Add-Member Noteproperty 'TargetSID' $TargetSID
+ $DomainTrust | Add-Member Noteproperty 'ObjectGuid' "{$ObjectGuid}"
+ $DomainTrust | Add-Member Noteproperty 'TrustType' $($TrustAttrib -join ',')
+ $DomainTrust | Add-Member Noteproperty 'TrustDirection' "$Direction"
+ $DomainTrust.PSObject.TypeNames.Add('PowerView.DomainTrustLDAP')
+ $DomainTrust
+ }
+ $Results.dispose()
+ $TrustSearcher.dispose()
}
}
+ elseif($API) {
+ if(-not $DomainController) {
+ $DomainController = Get-NetDomainController -Credential $Credential -Domain $SourceDomain | Select-Object -First 1 | Select-Object -ExpandProperty Name
+ }
- if($UseDomainTrusts) {
- Invoke-MapDomainTrust
- }
+ if($DomainController) {
+ # arguments for DsEnumerateDomainTrusts
+ $PtrInfo = [IntPtr]::Zero
- if($UseDomainTrustsLDAP) {
- Invoke-MapDomainTrust -LDAP -DomainController $DomainController
- }
+ # 63 = DS_DOMAIN_IN_FOREST + DS_DOMAIN_DIRECT_OUTBOUND + DS_DOMAIN_TREE_ROOT + DS_DOMAIN_PRIMARY + DS_DOMAIN_NATIVE_MODE + DS_DOMAIN_DIRECT_INBOUND
+ $Flags = 63
+ $DomainCount = 0
- if (-not $SkipComputerEnumeration) {
- if(-not $ComputerName) {
- [Array]$TargetComputers = @()
+ # get the trust information from the target server
+ $Result = $Netapi32::DsEnumerateDomainTrusts($DomainController, $Flags, [ref]$PtrInfo, [ref]$DomainCount)
- ForEach ($Domain2 in $TargetDomains) {
- if($CollectionMethod -eq 'Stealth') {
- Write-Verbose "Querying domain $Domain2 for File Servers..."
- $TargetComputers += Get-NetFileServer -Domain $Domain2 -DomainController $DomainController
+ # Locate the offset of the initial intPtr
+ $Offset = $PtrInfo.ToInt64()
- Write-Verbose "Querying domain $Domain2 for DFS Servers..."
- $TargetComputers += Get-DFSshare -Domain $Domain2 -DomainController $DomainController | ForEach-Object {$_.RemoteServerName}
+ # 0 = success
+ if (($Result -eq 0) -and ($Offset -gt 0)) {
- Write-Verbose "Querying domain $Domain2 for Domain Controllers..."
- $TargetComputers += Get-NetDomainController -LDAP -Domain $Domain2 -DomainController $DomainController | ForEach-Object { $_.dnshostname}
- }
- else {
- Write-Verbose "Querying domain $Domain2 for hosts"
+ # Work out how mutch to increment the pointer by finding out the size of the structure
+ $Increment = $DS_DOMAIN_TRUSTS::GetSize()
- $TargetComputers += Get-NetComputer -Domain $Domain2 -DomainController $DomainController
- }
+ # parse all the result structures
+ for ($i = 0; ($i -lt $DomainCount); $i++) {
+ # create a new int ptr at the given offset and cast the pointer as our result structure
+ $NewIntPtr = New-Object System.Intptr -ArgumentList $Offset
+ $Info = $NewIntPtr -as $DS_DOMAIN_TRUSTS
+
+ $Offset = $NewIntPtr.ToInt64()
+ $Offset += $Increment
+
+ $SidString = ""
+ $Result = $Advapi32::ConvertSidToStringSid($Info.DomainSid, [ref]$SidString);$LastError = [Runtime.InteropServices.Marshal]::GetLastWin32Error()
- if($UseGPOGroup) {
- Write-Verbose "Enumerating GPO local group memberships for domain $Domain2"
- Find-GPOLocation -Domain $Domain2 -DomainController $DomainController
-
- # add in a local administrator relationship for "Domain Admins" -> every machine
- $DomainSID = Get-DomainSID -Domain $Domain2 -DomainController $DomainController
- $DomainAdminsSid = "$DomainSID-512"
- $Temp = Convert-SidToName -SID $DomainAdminsSid
- $DomainAdminsName = $Temp.Split('\')[1]
-
- Get-NetComputer -Domain $Domain2 -DomainController $DomainController | ForEach-Object {
- $LocalUser = New-Object PSObject
- $LocalUser | Add-Member Noteproperty 'MemberName' $DomainAdminsName
- $LocalUser | Add-Member Noteproperty 'MemberDomain' $Domain2
- $LocalUser | Add-Member Noteproperty 'IsGroup' $True
- $LocalUser | Add-Member Noteproperty 'ComputerName' $_
- $LocalUser.PSObject.TypeNames.Add('PowerView.LocalUserSpecified')
- $LocalUser
+ if($Result -eq 0) {
+ Write-Verbose "Error: $(([ComponentModel.Win32Exception] $LastError).Message)"
+ }
+ else {
+ $DomainTrust = New-Object PSObject
+ $DomainTrust | Add-Member Noteproperty 'SourceDomain' $SourceDomain
+ $DomainTrust | Add-Member Noteproperty 'SourceDomainController' $DomainController
+ $DomainTrust | Add-Member Noteproperty 'NetbiosDomainName' $Info.NetbiosDomainName
+ $DomainTrust | Add-Member Noteproperty 'DnsDomainName' $Info.DnsDomainName
+ $DomainTrust | Add-Member Noteproperty 'Flags' $Info.Flags
+ $DomainTrust | Add-Member Noteproperty 'ParentIndex' $Info.ParentIndex
+ $DomainTrust | Add-Member Noteproperty 'TrustType' $Info.TrustType
+ $DomainTrust | Add-Member Noteproperty 'TrustAttributes' $Info.TrustAttributes
+ $DomainTrust | Add-Member Noteproperty 'DomainSid' $SidString
+ $DomainTrust | Add-Member Noteproperty 'DomainGuid' $Info.DomainGuid
+ $DomainTrust.PSObject.TypeNames.Add('PowerView.APIDomainTrust')
+ $DomainTrust
}
}
+ # free up the result buffer
+ $Null = $Netapi32::NetApiBufferFree($PtrInfo)
}
-
- # remove any null target hosts, uniquify the list and shuffle it
- $TargetComputers = $TargetComputers | Where-Object { $_ } | Sort-Object -Unique | Sort-Object { Get-Random }
- if($($TargetComputers.Count) -eq 0) {
- Write-Warning "No hosts found!"
+ else {
+ Write-Verbose "Error: $(([ComponentModel.Win32Exception] $Result).Message)"
}
}
else {
- $TargetComputers = $ComputerName
+ Write-Verbose "Could not retrieve domain controller for $Domain"
+ }
+ }
+ else {
+ # if we're using direct domain connections through .NET
+ $FoundDomain = Get-NetDomain -Domain $Domain -Credential $Credential
+ if($FoundDomain) {
+ $FoundDomain.GetAllTrustRelationships() | ForEach-Object {
+ $_.PSObject.TypeNames.Add('PowerView.DomainTrust')
+ $_
+ }
+ }
+ }
+ }
+}
+
+
+function Get-NetForestTrust {
+<#
+ .SYNOPSIS
+
+ Return all trusts for the current forest.
+
+ .PARAMETER Forest
+
+ Return trusts for the specified forest.
+
+ .PARAMETER Credential
+
+ A [Management.Automation.PSCredential] object of alternate credentials
+ for connection to the target domain.
+
+ .EXAMPLE
+
+ PS C:\> Get-NetForestTrust
+
+ Return current forest trusts.
+
+ .EXAMPLE
+
+ PS C:\> Get-NetForestTrust -Forest "test"
+
+ Return trusts for the "test" forest.
+#>
+
+ [CmdletBinding()]
+ param(
+ [Parameter(Position=0,ValueFromPipeline=$True)]
+ [String]
+ $Forest,
+
+ [Management.Automation.PSCredential]
+ $Credential
+ )
+
+ process {
+ $FoundForest = Get-NetForest -Forest $Forest -Credential $Credential
+
+ if($FoundForest) {
+ $FoundForest.GetAllTrustRelationships() | ForEach-Object {
+ $_.PSObject.TypeNames.Add('PowerView.ForestTrust')
+ $_
}
}
+ }
+}
+
+
+function Invoke-MapDomainTrust {
+<#
+ .SYNOPSIS
+
+ This function gets all trusts for the current domain,
+ and tries to get all trusts for each domain it finds.
+
+ .PARAMETER LDAP
+
+ Switch. Use LDAP queries to enumerate the trusts instead of direct domain connections.
+ More likely to get around network segmentation, but not as accurate.
+
+ .PARAMETER DomainController
+
+ Domain controller to reflect LDAP queries through.
+
+ .PARAMETER PageSize
+
+ The PageSize to set for the LDAP searcher object.
+
+ .PARAMETER Credential
+
+ A [Management.Automation.PSCredential] object of alternate credentials
+ for connection to the target domain.
+
+ .EXAMPLE
+
+ PS C:\> Invoke-MapDomainTrust | Export-CSV -NoTypeInformation trusts.csv
- # get the current user so we can ignore it in the results
- $CurrentUser = ([Environment]::UserName).toLower()
+ Map all reachable domain trusts and output everything to a .csv file.
- # script block that enumerates a server
- $HostEnumBlock = {
- param($ComputerName, $Ping, $CurrentUser2, $UseLocalGroup2, $UseSession2, $UseLoggedon2)
+ .LINK
- $Up = $True
- if($Ping) {
- $Up = Test-Connection -Count 1 -Quiet -ComputerName $ComputerName
- }
- if($Up) {
+ http://blog.harmj0y.net/
+#>
+ [CmdletBinding()]
+ param(
+ [Switch]
+ $LDAP,
- if($UseLocalGroup2) {
- # grab the users for the local admins on this server
- Get-NetLocalGroup -ComputerName $ComputerName -API | Where-Object {$_.IsDomain}
- }
+ [String]
+ $DomainController,
- $IPAddress = @(Get-IPAddress -ComputerName $ComputerName)[0].IPAddress
+ [ValidateRange(1,10000)]
+ [Int]
+ $PageSize = 200,
- if($UseSession2) {
- $Sessions = Get-NetSession -ComputerName $ComputerName
- ForEach ($Session in $Sessions) {
- $UserName = $Session.sesi10_username
- $CName = $Session.sesi10_cname
+ [Management.Automation.PSCredential]
+ $Credential
+ )
- if($CName -and $CName.StartsWith("\\")) {
- $CName = $CName.TrimStart("\")
- }
+ # keep track of domains seen so we don't hit infinite recursion
+ $SeenDomains = @{}
- # make sure we have a result
- if (($UserName) -and ($UserName.trim() -ne '') -and ($UserName -notmatch '\$') -and ($UserName -notmatch $CurrentUser2)) {
+ # our domain status tracker
+ $Domains = New-Object System.Collections.Stack
- $FoundUser = New-Object PSObject
- $FoundUser | Add-Member Noteproperty 'UserDomain' $Null
- $FoundUser | Add-Member Noteproperty 'UserName' $UserName
- $FoundUser | Add-Member Noteproperty 'ComputerName' $ComputerName
- $FoundUser | Add-Member Noteproperty 'IPAddress' $IPAddress
- $FoundUser | Add-Member Noteproperty 'SessionFrom' $CName
+ # get the current domain and push it onto the stack
+ $CurrentDomain = (Get-NetDomain -Credential $Credential).Name
+ $Domains.push($CurrentDomain)
- # Try to resolve the DNS hostname of $Cname
- try {
- $CNameDNSName = [System.Net.Dns]::GetHostEntry($CName) | Select-Object -ExpandProperty HostName
- }
- catch {
- $CNameDNSName = $CName
- }
- $FoundUser | Add-Member NoteProperty 'SessionFromName' $CNameDNSName
- $FoundUser | Add-Member Noteproperty 'LocalAdmin' $Null
- $FoundUser.PSObject.TypeNames.Add('PowerView.UserSession')
- $FoundUser
- }
- }
- }
+ while($Domains.Count -ne 0) {
- if($UseLoggedon2) {
- $LoggedOn = Get-NetLoggedon -ComputerName $ComputerName
- ForEach ($User in $LoggedOn) {
- $UserName = $User.wkui1_username
- $UserDomain = $User.wkui1_logon_domain
+ $Domain = $Domains.Pop()
- # ignore local account logons
- # TODO: better way to determine if network logon or not
- if($ComputerName -notmatch "^$UserDomain") {
- if (($UserName) -and ($UserName.trim() -ne '') -and ($UserName -notmatch '\$')) {
- $FoundUser = New-Object PSObject
- $FoundUser | Add-Member Noteproperty 'UserDomain' $UserDomain
- $FoundUser | Add-Member Noteproperty 'UserName' $UserName
- $FoundUser | Add-Member Noteproperty 'ComputerName' $ComputerName
- $FoundUser | Add-Member Noteproperty 'IPAddress' $IPAddress
- $FoundUser | Add-Member Noteproperty 'SessionFrom' $Null
- $FoundUser | Add-Member Noteproperty 'SessionFromName' $Null
- $FoundUser | Add-Member Noteproperty 'LocalAdmin' $Null
- $FoundUser.PSObject.TypeNames.Add('PowerView.UserSession')
- $FoundUser
- }
- }
- }
+ # if we haven't seen this domain before
+ if ($Domain -and ($Domain.Trim() -ne "") -and (-not $SeenDomains.ContainsKey($Domain))) {
- $LocalLoggedOn = Get-LoggedOnLocal -ComputerName $ComputerName
- ForEach ($User in $LocalLoggedOn) {
- $UserName = $User.UserName
- $UserDomain = $User.UserDomain
+ Write-Verbose "Enumerating trusts for domain '$Domain'"
- # ignore local account logons ?
- if($ComputerName -notmatch "^$UserDomain") {
- $FoundUser = New-Object PSObject
- $FoundUser | Add-Member Noteproperty 'UserDomain' $UserDomain
- $FoundUser | Add-Member Noteproperty 'UserName' $UserName
- $FoundUser | Add-Member Noteproperty 'ComputerName' $ComputerName
- $FoundUser | Add-Member Noteproperty 'IPAddress' $IPAddress
- $FoundUser | Add-Member Noteproperty 'SessionFrom' $Null
- $FoundUser | Add-Member Noteproperty 'SessionFromName' $Null
- $FoundUser | Add-Member Noteproperty 'LocalAdmin' $Null
- $FoundUser.PSObject.TypeNames.Add('PowerView.UserSession')
- $FoundUser
- }
- }
+ # mark it as seen in our list
+ $Null = $SeenDomains.add($Domain, "")
+
+ try {
+ # get all the trusts for this domain
+ if($LDAP -or $DomainController) {
+ $Trusts = Get-NetDomainTrust -Domain $Domain -LDAP -DomainController $DomainController -PageSize $PageSize -Credential $Credential
+ }
+ else {
+ $Trusts = Get-NetDomainTrust -Domain $Domain -PageSize $PageSize -Credential $Credential
}
- }
- }
- }
- process {
- if (-not $SkipComputerEnumeration) {
- if($Threads) {
- Write-Verbose "Using threading with threads = $Threads"
+ if($Trusts -isnot [System.Array]) {
+ $Trusts = @($Trusts)
+ }
- # if we're using threading, kick off the script block with Invoke-ThreadedFunction
- $ScriptParams = @{
- 'Ping' = $True
- 'CurrentUser2' = $CurrentUser
- 'UseLocalGroup2' = $UseLocalGroup
- 'UseSession2' = $UseSession
- 'UseLoggedon2' = $UseLoggedon
+ # get any forest trusts, if they exist
+ if(-not ($LDAP -or $DomainController) ) {
+ $Trusts += Get-NetForestTrust -Forest $Domain -Credential $Credential
}
- Invoke-ThreadedFunction -ComputerName $TargetComputers -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams -Threads $Threads
- }
+ if ($Trusts) {
+ if($Trusts -isnot [System.Array]) {
+ $Trusts = @($Trusts)
+ }
- else {
- if($TargetComputers.Count -ne 1) {
- # ping all hosts in parallel
- $Ping = {param($ComputerName2) if(Test-Connection -ComputerName $ComputerName2 -Count 1 -Quiet -ErrorAction Stop) { $ComputerName2 }}
- $TargetComputers2 = Invoke-ThreadedFunction -NoImports -ComputerName $TargetComputers -ScriptBlock $Ping -Threads 100
- }
- else {
- $TargetComputers2 = $TargetComputers
- }
+ # enumerate each trust found
+ ForEach ($Trust in $Trusts) {
+ if($Trust.SourceName -and $Trust.TargetName) {
+ $SourceDomain = $Trust.SourceName
+ $TargetDomain = $Trust.TargetName
+ $TrustType = $Trust.TrustType
+ $TrustDirection = $Trust.TrustDirection
+ $ObjectType = $Trust.PSObject.TypeNames | Where-Object {$_ -match 'PowerView'} | Select-Object -First 1
- Write-Verbose "[*] Total number of active hosts: $($TargetComputers2.count)"
- $Counter = 0
+ # make sure we process the target
+ $Null = $Domains.Push($TargetDomain)
- $TargetComputers2 | ForEach-Object {
- $Counter = $Counter + 1
- Write-Verbose "[*] Enumerating server $($_) ($Counter of $($TargetComputers2.count))"
- Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList @($_, $False, $CurrentUser, $UseLocalGroup, $UseSession, $UseLoggedon)
+ # build the nicely-parsable custom output object
+ $DomainTrust = New-Object PSObject
+ $DomainTrust | Add-Member Noteproperty 'SourceDomain' "$SourceDomain"
+ $DomainTrust | Add-Member Noteproperty 'SourceSID' $Trust.SourceSID
+ $DomainTrust | Add-Member Noteproperty 'TargetDomain' "$TargetDomain"
+ $DomainTrust | Add-Member Noteproperty 'TargetSID' $Trust.TargetSID
+ $DomainTrust | Add-Member Noteproperty 'TrustType' "$TrustType"
+ $DomainTrust | Add-Member Noteproperty 'TrustDirection' "$TrustDirection"
+ $DomainTrust.PSObject.TypeNames.Add($ObjectType)
+ $DomainTrust
+ }
+ }
}
}
+ catch {
+ Write-Verbose "[!] Error: $_"
+ }
}
}
}
+########################################################
+#
+# BloodHound specific fuctions.
+#
+########################################################
+
function Get-GlobalCatalogUserMapping {
<#
.SYNOPSIS
@@ -13720,33 +4524,55 @@ function Get-GlobalCatalogUserMapping {
}
-function Export-BloodHoundData {
+function Invoke-BloodHound {
<#
.SYNOPSIS
- Takes custom objects from Get-BloodHound data and exports everything to a BloodHound
- neo4j RESTful API batch ingestion interface.
+ This function automates the collection of the data needed for BloodHound.
Author: @harmj0y
License: BSD 3-Clause
- Required Dependencies: PowerView.ps1
+ Required Dependencies: None
Optional Dependencies: None
.DESCRIPTION
- This function takes custom tagged PowerView objects types from Get-BloodHoundData and packages/ingests
- them into a neo4j RESTful API batch ingestion interface. For user session data without a logon
- domain, by default the global catalog is used to attempt to deconflict what domain the user may be
- located in. If the user exists in more than one domain in the forest, a series of weights is used to
- modify the attack path likelihood.
+ This function collects the information needed to populate the BloodHound graph
+ database. It offers a varity of targeting and collection options.
+ By default, it will map all domain trusts, enumerate all groups and associated memberships,
+ enumerate all computers on the domain and execute session/loggedon/local admin enumeration
+ queries against each. Targeting options are modifiable with -CollectionMethod. The
+ -SearchForest searches all domains in the forest instead of just the current domain.
+ By default, the data is output to CSVs in the current folder location (old Export-BloodHoundCSV functionality).
+ To modify this, use -CSVFolder. To export to a neo4j RESTful API interface, specify a
+ -URI X and -UserPass "...".
+
+ .PARAMETER Domain
- Cypher queries are built for each appropriate relationship to ingest, and the set of queries is 'batched'
- so '-Throttle X' queries are sent at a time in each batch request. All of the Cypher queries are
- jsonified using System.Web.Script.Serialization.javascriptSerializer.
+ Domain to query for machines, defaults to the current domain.
- .PARAMETER Object
+ .PARAMETER DomainController
+
+ Domain controller to bind to for queries.
+
+ .PARAMETER CollectionMethod
+
+ The method to collect data. 'Group', 'LocalGroup', 'GPOLocalGroup', 'Sesssion', 'LoggedOn', 'Trusts, 'Stealth', or 'Default'.
+ 'Stealth' uses 'Group' collection, stealth user hunting ('Session' on certain servers), 'GPOLocalGroup' enumeration, and trust enumeration.
+ 'Default' uses 'Group' collection, regular user hunting with 'Session'/'LoggedOn', 'LocalGroup' enumeration, and 'Trusts' enumeration.
+
+ .PARAMETER SearchForest
+
+ Switch. Search all domains in the forest for target users instead of just
+ a single domain.
+
+ .PARAMETER CSVFolder
- The PowerView PSObject to export to the RESTful API interface.
+ The CSV folder to use for output, defaults to the current folder location.
+
+ .PARAMETER CSVPrefix
+
+ A prefix for all CSV files.
.PARAMETER URI
@@ -13754,936 +4580,1163 @@ function Export-BloodHoundData {
.PARAMETER UserPass
- The "user:password" for the BloodHound neo4j instance.
-
- .PARAMETER SkipGCDeconfliction
+ The "user:password" for the BloodHound neo4j instance
- Switch. Don't resolve user domain memberships for session information using a global catalog.
+ .PARAMETER GlobalCatalog
- .PARAMETER GlobalCatalog
+ The global catalog location to resolve user memberships from, form of GC://global.catalog.
- The global catalog location to resole user memberships from, form of GC://global.catalog.
+ .PARAMETER Threads
- .PARAMETER Credential
+ The maximum concurrent threads to execute, default of 20.
- A [Management.Automation.PSCredential] object that stores a BloodHound username
- and password for the neo4j connection.
+ .EXAMPLE
- .PARAMETER Throttle
+ PS C:\> Invoke-BloodHound
- The number of object insertion queries to run in each batch, defaults to 100.
+ Executes default collection methods and exports the data to a CSVs in the current directory.
.EXAMPLE
- PS C:\> Get-BloodHoundData | Export-BloodHoundData -URI http://SERVER:7474/ -UserPass "user:pass"
+ PS C:\> Invoke-BloodHound -URI http://SERVER:7474/ -UserPass "user:pass"
Executes default collection options and exports the data to a BloodHound neo4j RESTful API endpoint.
.EXAMPLE
- PS C:\> Get-BloodHoundData | Export-BloodHoundData -URI http://SERVER:7474/ -UserPass "user:pass" -SkipGCDeconfliction
+ PS C:\> Invoke-BloodHound -CollectionMethod stealth
- Executes default collection options and exports the data to a BloodHound neo4j RESTful API endpoint,
- and skip the global catalog deconfliction process.
+ Executes stealth collection and exports the data to a CSVs in the current directory.
+ This includes 'stealth' user hunting and GPO object correlation for local admin membership.
+ This is significantly faster but the information is not as complete as the default options.
.LINK
http://neo4j.com/docs/stable/rest-api-batch-ops.html
http://stackoverflow.com/questions/19839469/optimizing-high-volume-batch-inserts-into-neo4j-using-rest
-
#>
- [CmdletBinding(DefaultParameterSetName = 'PlaintextPW')]
+
+ [CmdletBinding(DefaultParameterSetName = 'CSVExport')]
param(
- [Parameter(Position=0, ValueFromPipeline=$True, Mandatory = $True)]
- [PSObject]
- $Object,
+ [String]
+ $Domain,
+
+ [String]
+ $DomainController,
+
+ [String]
+ [ValidateSet('Group', 'LocalGroup', 'GPOLocalGroup', 'Session', 'LoggedOn', 'Stealth', 'Trusts', 'Default')]
+ $CollectionMethod = 'Default',
+
+ [Switch]
+ $SearchForest,
+
+ [Parameter(ParameterSetName = 'CSVExport')]
+ [ValidateScript({ Test-Path -Path $_ })]
+ [String]
+ $CSVFolder = $(Get-Location),
+
+ [Parameter(ParameterSetName = 'CSVExport')]
+ [ValidateNotNullOrEmpty()]
+ [String]
+ $CSVPrefix,
- [Parameter(Position=1, Mandatory = $True)]
+ [Parameter(ParameterSetName = 'RESTAPI', Mandatory = $True)]
[URI]
$URI,
- [Parameter(Position=2, Mandatory = $True, ParameterSetName = 'PlaintextPW')]
+ [Parameter(ParameterSetName = 'RESTAPI', Mandatory = $True)]
[String]
[ValidatePattern('.*:.*')]
$UserPass,
- [Parameter(Position=2, Mandatory = $True, ParameterSetName = 'PSCredential')]
- [Management.Automation.PSCredential]
- $Credential,
-
- [Switch]
- $SkipGCDeconfliction,
-
[ValidatePattern('^GC://')]
[String]
$GlobalCatalog,
+ [ValidateRange(1,50)]
[Int]
- $Throttle = 1000
+ $Threads = 20
)
- begin {
- $WebClient = New-Object System.Net.WebClient
+ BEGIN {
+
+ Switch ($CollectionMethod) {
+ 'Group' { $UseGroup = $True; $SkipComputerEnumeration = $True; $SkipGCDeconfliction = $True }
+ 'LocalGroup' { $UseLocalGroup = $True; $SkipGCDeconfliction = $True }
+ 'GPOLocalGroup' { $UseGPOGroup = $True; $SkipComputerEnumeration = $True; $SkipGCDeconfliction = $True }
+ 'Session' { $UseSession = $True; $SkipGCDeconfliction = $False }
+ 'LoggedOn' { $UseLoggedOn = $True; $SkipGCDeconfliction = $True }
+ 'Trusts' { $UseDomainTrusts = $True; $SkipComputerEnumeration = $True; $SkipGCDeconfliction = $True }
+ 'Stealth' {
+ $UseGroup = $True
+ $UseGPOGroup = $True
+ $UseSession = $True
+ $UseDomainTrusts = $True
+ $SkipGCDeconfliction = $False
+ }
+ 'Default' {
+ $UseGroup = $True
+ $UseLocalGroup = $True
+ $UseSession = $True
+ $UseLoggedOn = $True
+ $UseDomainTrusts = $True
+ $SkipGCDeconfliction = $False
+ }
+ }
+
+ if ($PSCmdlet.ParameterSetName -eq 'CSVExport') {
+ try {
+ $OutputFolder = $CSVFolder | Resolve-Path -ErrorAction Stop | Select-Object -ExpandProperty Path
+ }
+ catch {
+ throw "Error: $_"
+ }
- if($PSBoundParameters['Credential']) {
- $BloodHoundUserName = $Credential.UserName
- $BloodHoundPassword = $Credential.GetNetworkCredential().Password
- $Base64UserPass = [System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($BloodHoundUserName + ':' + $BloodHoundPassword))
+ if($CSVPrefix) {
+ $CSVExportPrefix = "$($CSVPrefix)_"
+ }
+ else {
+ $CSVExportPrefix = ''
+ }
+
+ Write-Output "Writing output to CSVs in: $OutputFolder\$CSVExportPrefix"
+
+ if($UseSession -or $UseLoggedon) {
+ $SessionPath = "$OutputFolder\$($CSVExportPrefix)user_sessions.csv"
+ $Exists = [System.IO.File]::Exists($SessionPath)
+ $SessionFileStream = New-Object IO.FileStream($SessionPath, [System.IO.FileMode]::Append, [System.IO.FileAccess]::Write, [IO.FileShare]::Read)
+ $SessionWriter = New-Object System.IO.StreamWriter($SessionFileStream)
+ $SessionWriter.AutoFlush = $True
+ if (-not $Exists) {
+ # add the header if the file doesn't already exist
+ $SessionWriter.WriteLine('"ComputerName","UserName","Weight"')
+ }
+ }
+
+ if($UseGroup) {
+ $GroupPath = "$OutputFolder\$($CSVExportPrefix)group_memberships.csv"
+ $Exists = [System.IO.File]::Exists($GroupPath)
+ $GroupFileStream = New-Object IO.FileStream($GroupPath, [System.IO.FileMode]::Append, [System.IO.FileAccess]::Write, [IO.FileShare]::Read)
+ $GroupWriter = New-Object System.IO.StreamWriter($GroupFileStream)
+ $GroupWriter.AutoFlush = $True
+ if (-not $Exists) {
+ # add the header if the file doesn't already exist
+ $GroupWriter.WriteLine('"GroupName","AccountName","AccountType"')
+ }
+ }
+
+ if($UseLocalGroup -or $UseGPOGroup) {
+ $LocalAdminPath = "$OutputFolder\$($CSVExportPrefix)local_admins.csv"
+ $Exists = [System.IO.File]::Exists($LocalAdminPath)
+ $LocalAdminFileStream = New-Object IO.FileStream($LocalAdminPath, [System.IO.FileMode]::Append, [System.IO.FileAccess]::Write, [IO.FileShare]::Read)
+ $LocalAdminWriter = New-Object System.IO.StreamWriter($LocalAdminFileStream)
+ $LocalAdminWriter.AutoFlush = $True
+ if (-not $Exists) {
+ # add the header if the file doesn't already exist
+ $LocalAdminWriter.WriteLine('"ComputerName","AccountName","AccountType"')
+ }
+ }
+
+ if($UseDomainTrusts) {
+ $TrustsPath = "$OutputFolder\$($CSVExportPrefix)trusts.csv"
+ $Exists = [System.IO.File]::Exists($TrustsPath)
+ $TrustsFileStream = New-Object IO.FileStream($TrustsPath, [System.IO.FileMode]::Append, [System.IO.FileAccess]::Write, [IO.FileShare]::Read)
+ $TrustWriter = New-Object System.IO.StreamWriter($TrustsFileStream)
+ $TrustWriter.AutoFlush = $True
+ if (-not $Exists) {
+ # add the header if the file doesn't already exist
+ $TrustWriter.WriteLine('"SourceDomain","TargetDomain","TrustDirection","TrustType","Transitive')
+ }
+ }
}
+
else {
+ # otherwise we're doing ingestion straight to the neo4j RESTful API interface
+
+ $Throttle = 1000
+ $WebClient = New-Object System.Net.WebClient
+
$Base64UserPass = [System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($UserPass))
- }
- # add the auth headers
- $WebClient.Headers.Add('Accept','application/json; charset=UTF-8')
- $WebClient.Headers.Add('Authorization',"Basic $Base64UserPass")
+ # add the auth headers
+ $WebClient.Headers.Add('Accept','application/json; charset=UTF-8')
+ $WebClient.Headers.Add('Authorization',"Basic $Base64UserPass")
- # check auth to the BloodHound neo4j server
- try {
- $Null = $WebClient.DownloadString($URI.AbsoluteUri + 'user/neo4j')
- Write-Verbose "Connection established with neo4j ingestion interface at $($URI.AbsoluteUri)"
- $Authorized = $True
- }
- catch {
- $Authorized = $False
- throw "Error connecting to Neo4j rest REST server at '$($URI.AbsoluteUri)'"
- }
+ # check auth to the BloodHound neo4j server
+ try {
+ $Null = $WebClient.DownloadString($URI.AbsoluteUri + 'user/neo4j')
+ Write-Verbose "Connection established with neo4j ingestion interface at $($URI.AbsoluteUri)"
+ $Authorized = $True
+ }
+ catch {
+ $Authorized = $False
+ throw "Error connecting to Neo4j rest REST server at '$($URI.AbsoluteUri)'"
+ }
+
+ Write-Output "Sending output to neo4j RESTful API interface at: $($URI.AbsoluteUri)"
+
+ $Null = [Reflection.Assembly]::LoadWithPartialName("System.Web.Extensions")
+
+ # from http://stackoverflow.com/questions/28077854/powershell-2-0-convertfrom-json-and-convertto-json-implementation
+ function ConvertTo-Json20([object] $Item){
+ $ps_js = New-Object System.Web.Script.Serialization.javascriptSerializer
+ return $ps_js.Serialize($item)
+ }
- Add-Type -Assembly System.Web.Extensions
+ $Authorized = $True
+ $Statements = New-Object System.Collections.ArrayList
- # from http://stackoverflow.com/questions/28077854/powershell-2-0-convertfrom-json-and-convertto-json-implementation
- function ConvertTo-Json20([object] $Item){
- $ps_js = New-Object System.Web.Script.Serialization.javascriptSerializer
- return $ps_js.Serialize($item)
+ # add in the necessary constraints on nodes
+ $Null = $Statements.Add( @{ "statement"="CREATE CONSTRAINT ON (c:User) ASSERT c.UserName IS UNIQUE" } )
+ $Null = $Statements.Add( @{ "statement"="CREATE CONSTRAINT ON (c:Computer) ASSERT c.ComputerName IS UNIQUE"} )
+ $Null = $Statements.Add( @{ "statement"="CREATE CONSTRAINT ON (c:Group) ASSERT c.GroupName IS UNIQUE" } )
+ $Json = @{ "statements"=[System.Collections.Hashtable[]]$Statements }
+ $JsonRequest = ConvertTo-Json20 $Json
+ $Null = $WebClient.UploadString($URI.AbsoluteUri + "db/data/transaction/commit", $JsonRequest)
+ $Statements.Clear()
}
- $Authorized = $True
- $Statements = New-Object System.Collections.ArrayList
-
$UserDomainMappings = @{}
if(-not $SkipGCDeconfliction) {
# if we're doing session enumeration, create a {user : @(domain,..)} from a global catalog
# in order to do user domain deconfliction for sessions
-
- if(-not $PSBoundParameters['GlobalCatalog']) {
- $UserDomainMappings = Get-GlobalCatalogUserMapping
+ if($PSBoundParameters['GlobalCatalog']) {
+ $UserDomainMappings = Get-GlobalCatalogUserMapping -GlobalCatalog $GlobalCatalog
}
else {
- $UserDomainMappings = Get-GlobalCatalogUserMapping -GlobalCatalog $GlobalCatalog
+ $UserDomainMappings = Get-GlobalCatalogUserMapping
}
}
- }
+ $DomainShortnameMappings = @{}
- process {
- if($Authorized) {
+ if($Domain) {
+ $TargetDomains = @($Domain)
+ }
+ elseif($SearchForest) {
+ # get ALL the domains in the forest to search
+ $TargetDomains = Get-NetForestDomain | Select-Object -ExpandProperty Name
+ }
+ else {
+ # use the local domain
+ $TargetDomains = @( (Get-NetDomain).Name )
+ }
- $Queries = @()
+ if($UseGroup -and $TargetDomains) {
+ ForEach ($TargetDomain in $TargetDomains) {
+ # enumerate all groups and all members of each group
- if($Object.PSObject.TypeNames -contains 'PowerView.UserSession') {
- if($Object.SessionFromName) {
- # implying a result from Get-NetSession
- try {
- $UserName = $Object.UserName.ToUpper()
- $SessionFromName = $Object.SessionFromName
- $ComputerDomain = $Object.SessionFromName.SubString($Object.SessionFromName.IndexOf('.')+1).ToUpper()
-
- if($UserDomainMappings) {
- $UserDomain = $Null
- if($UserDomainMappings[$UserName]) {
- if($UserDomainMappings[$UserName].Count -eq 1) {
- $UserDomain = $UserDomainMappings[$UserName]
- $LoggedOnUser = "$UserName@$UserDomain"
-
- $Queries += "MERGE (user:User { name: UPPER('$LoggedOnUser') }) MERGE (computer:Computer { name: UPPER('$SessionFromName') }) MERGE (computer)-[:HasSession {Weight: '1'}]->(user)"
- }
- else {
- $UserDomainMappings[$UserName] | ForEach-Object {
- if($_ -eq $ComputerDomain) {
- $UserDomain = $_
- $LoggedOnUser = "$UserName@$UserDomain"
+ Write-Verbose "Enumerating group memberships for domain $TargetDomain"
- $Queries += "MERGE (user:User { name: UPPER('$LoggedOnUser') }) MERGE (computer:Computer { name: UPPER('$SessionFromName') }) MERGE (computer)-[:HasSession {Weight: '1'}]->(user)"
- }
- else {
- $UserDomain = $_
- $LoggedOnUser = "$UserName@$UserDomain"
+ $GroupSearcher = Get-DomainSearcher -Domain $TargetDomain -DomainController $DomainController
- $Queries += "MERGE (user:User { name: UPPER('$LoggedOnUser') }) MERGE (computer:Computer { name: UPPER('$SessionFromName') }) MERGE (computer)-[:HasSession {Weight: '2'}]->(user)"
- }
+ # only search for security groups for a speedup (we don't care about distribution groups)
+ $GroupSearcher.filter = "(&(groupType:1.2.840.113556.1.4.803:=2147483648)(member=*))"
+ $Null = $GroupSearcher.PropertiesToLoad.AddRange(('samaccountname', 'member'))
+ $PrimaryGroups = @{}
+ $DomainSID = Get-DomainSID -Domain $TargetDomain -DomainController $DomainController
+ $GroupCounter = 0
+
+ ForEach($GroupResult in $GroupSearcher.FindAll()) {
+ ForEach($Member in $GroupResult.properties['member']) {
+ $Properties = ([adsi]"LDAP://$Member").Properties
+ $IsGroup = @('268435456','268435457','536870912','536870913') -contains $Properties.samaccounttype
+
+ if($GroupCounter % 100 -eq 0) {
+ Write-Verbose "Group counter: $GroupCounter"
+ }
+
+ $MemberDN = $Null
+ $MemberDomain = $Null
+
+ try {
+ $MemberDN = $Properties.distinguishedname[0]
+
+ if (($MemberDN -match 'ForeignSecurityPrincipals') -and ($MemberDN -match 'S-1-5-21')) {
+ try {
+ if(-not $MemberSID) {
+ $MemberSID = $Properties.cn[0]
+ }
+ $MemberSimpleName = Convert-SidToName -SID $MemberSID | Convert-ADName -InputType 'NT4' -OutputType 'Canonical'
+ if($MemberSimpleName) {
+ $MemberDomain = $MemberSimpleName.Split('/')[0]
}
+ else {
+ Write-Verbose "Error converting $MemberDN"
+ }
+ }
+ catch {
+ Write-Verbose "Error converting $MemberDN"
}
}
else {
- # no user object in the GC with this username
- $LoggedOnUser = "$UserName@UNKNOWN"
- $Queries += "MERGE (user:User { name: UPPER('$LoggedOnUser') }) MERGE (computer:Computer { name: UPPER('$SessionFromName') }) MERGE (computer)-[:HasSession {Weight: '2'}]->(user)"
+ # extract the FQDN from the Distinguished Name
+ $MemberDomain = $MemberDN.subString($MemberDN.IndexOf("DC=")) -replace 'DC=','' -replace ',','.'
}
}
- else {
- $LoggedOnUser = "$UserName@$ComputerDomain"
- $Queries += "MERGE (user:User { name: UPPER('$LoggedOnUser') }) MERGE (computer:Computer { name: UPPER('$SessionFromName') }) MERGE (computer)-[:HasSession {Weight: '2'}]->(user)"
- }
- }
- catch {
- Write-Warning "Error extracting domain from $($Object.SessionFromName)"
- }
- }
- elseif($Object.SessionFrom) {
- $Queries += "MERGE (user:User { name: UPPER(`"$($Object.UserName)`") }) MERGE (computer:Computer { name: UPPER(`"$($Object.SessionFrom)`") }) MERGE (computer)-[:HasSession {Weight: '2'}]->(user)"
- }
- else {
- # assume Get-NetLoggedOn result
- try {
- $MemberSimpleName = "$($Object.UserDomain)\$($Object.UserName)" | Convert-ADName -InputType 'NT4' -OutputType 'Canonical'
+ catch {}
- if($MemberSimpleName) {
- $MemberDomain = $MemberSimpleName.Split('/')[0]
- $AccountName = "$($Object.UserName)@$MemberDomain"
+ if ($Properties.samaccountname) {
+ # forest users have the samAccountName set
+ $MemberName = $Properties.samaccountname[0]
}
else {
- $AccountName = "$($Object.UserName)@UNKNOWN"
+ # external trust users have a SID, so convert it
+ try {
+ $MemberName = Convert-SidToName $Properties.cn[0]
+ }
+ catch {
+ # if there's a problem contacting the domain to resolve the SID
+ $MemberName = $Properties.cn
+ }
}
- $Queries += "MERGE (user:User { name: UPPER('$AccountName') }) MERGE (computer:Computer { name: UPPER('$($Object.ComputerName)') }) MERGE (computer)-[:HasSession {Weight: '1'}]->(user)"
- }
- catch {
- Write-Verbose "Error converting $($Object.UserDomain)\$($Object.UserName)"
- }
- }
- }
- elseif($Object.PSObject.TypeNames -contains 'PowerView.GroupMember') {
- $AccountName = "$($Object.MemberName)@$($Object.MemberDomain)"
+ $MemberPrimaryGroupName = $Null
+ try {
+ if($MemberDomain -eq $TargetDomain) {
+ # also retrieve the primary group name for this user
+ if($Properties.primaryGroupID -and $Properties.primaryGroupID -ne '') {
+ $PrimaryGroupSID = "$DomainSID-$($Properties.primaryGroupID)"
+ if($PrimaryGroups[$PrimaryGroupSID]) {
+ $PrimaryGroupName = $PrimaryGroups[$PrimaryGroupSID]
+ }
+ else {
+ $PrimaryGroupName = Get-ADObject -Domain $Domain -SID $PrimaryGroupSID | Select-Object -ExpandProperty samaccountname
+ $PrimaryGroups[$PrimaryGroupSID] = $PrimaryGroupName
+ }
+ $MemberPrimaryGroupName = "$PrimaryGroupName@$TargetDomain"
+ }
+ else { }
+ }
+ }
+ catch { }
- if ($Object.MemberName -Match "\\") {
- # if the membername itself contains a backslash, get the trailing section
- # TODO: later preserve this once BloodHound can properly display these characters
- $AccountName = $($Object.Membername).split('\')[1] + '@' + $($Object.MemberDomain)
- }
+ $GroupName = $GroupResult.properties['samaccountname'][0]
+ $GroupDomain = $TargetDomain
+ $GroupName = "$GroupName@$GroupDomain"
- $GroupName = "$($Object.GroupName)@$($Object.GroupDomain)"
+ if ($MemberName -Match "\\") {
+ # if the membername itself contains a backslash, get the trailing section
+ # TODO: later preserve this once BloodHound can properly display these characters
+ $AccountName = $MemberName.split('\')[1] + '@' + $MemberDomain
+ }
+ else {
+ $AccountName = "$MemberName@$MemberDomain"
+ }
- if($Object.IsGroup) {
- $Queries += "MERGE (group1:Group { name: UPPER('$AccountName') }) MERGE (group2:Group { name: UPPER('$GroupName') }) MERGE (group1)-[:MemberOf]->(group2)"
- }
- else {
- if($Object.ObjectClass -and ($Object.ObjectClass -contains 'computer')) {
- $Queries += "MERGE (computer:Computer { name: UPPER('$($Object.dnshostname)') }) MERGE (group:Group { name: UPPER('$GroupName') }) MERGE (computer)-[:MemberOf]->(group)"
- }
- else {
- # otherwise there's no way to determine if this is a computer object or not
- $Queries += "MERGE (user:User { name: UPPER('$AccountName') }) MERGE (group:Group { name: UPPER('$GroupName') }) MERGE (user)-[:MemberOf]->(group)"
+ if ($PSCmdlet.ParameterSetName -eq 'CSVExport') {
+ if($IsGroup) {
+ $GroupWriter.WriteLine("`"$GroupName`",`"$AccountName`",`"group`"")
+ if($MemberPrimaryGroupName) {
+ $GroupWriter.WriteLine("`"$MemberPrimaryGroupName`",`"$AccountName`",`"group`"")
+ }
+ }
+ else {
+ if($Properties.objectclass -contains 'computer') {
+ $AccountName = $Properties.dnshostname
+ $GroupWriter.WriteLine("`"$GroupName`",`"$AccountName`",`"computer`"")
+ if($MemberPrimaryGroupName) {
+ $GroupWriter.WriteLine("`"$MemberPrimaryGroupName`",`"$AccountName`",`"computer`"")
+ }
+ }
+ else {
+ # otherwise there's no way to determine if this is a computer object or not
+ $GroupWriter.WriteLine("`"$GroupName`",`"$AccountName`",`"user`"")
+ if($MemberPrimaryGroupName) {
+ $GroupWriter.WriteLine("`"$MemberPrimaryGroupName`",`"$AccountName`",`"user`"")
+ }
+ }
+ }
+ }
+ else {
+ # otherwise we're exporting to the neo4j RESTful API
+ if($IsGroup) {
+ $Null = $Statements.Add( @{ "statement"="MERGE (group1:Group { name: UPPER('$AccountName') }) MERGE (group2:Group { name: UPPER('$GroupName') }) MERGE (group1)-[:MemberOf]->(group2)" } )
+ if($MemberPrimaryGroupName) {
+ $Null = $Statements.Add( @{ "statement"="MERGE (group1:Group { name: UPPER('$AccountName') }) MERGE (group2:Group { name: UPPER('$MemberPrimaryGroupName') }) MERGE (group1)-[:MemberOf]->(group2)" } )
+ }
+ }
+ else {
+ if($Properties.objectclass -contains 'computer') {
+ $AccountName = $Properties.dnshostname
+ $Null = $Statements.Add( @{ "statement"="MERGE (computer:Computer { name: UPPER('$AccountName') }) MERGE (group:Group { name: UPPER('$GroupName') }) MERGE (computer)-[:MemberOf]->(group)" } )
+ if($MemberPrimaryGroupName) {
+ $Null = $Statements.Add( @{ "statement"="MERGE (computer:Computer { name: UPPER('$AccountName') }) MERGE (group:Group { name: UPPER('$MemberPrimaryGroupName') }) MERGE (computer)-[:MemberOf]->(group)" } )
+ }
+ }
+ else {
+ # otherwise there's no way to determine if this is a computer object or not...
+ $Null = $Statements.Add( @{ "statement"="MERGE (user:User { name: UPPER('$AccountName') }) MERGE (group:Group { name: UPPER('$GroupName') }) MERGE (user)-[:MemberOf]->(group)" } )
+ if($MemberPrimaryGroupName) {
+ $Null = $Statements.Add( @{ "statement"="MERGE (user:User { name: UPPER('$AccountName') }) MERGE (group:Group { name: UPPER('$MemberPrimaryGroupName') }) MERGE (user)-[:MemberOf]->(group)" } )
+ }
+ }
+ }
+ if ($Statements.Count -ge $Throttle) {
+ $Json = @{ "statements"=[System.Collections.Hashtable[]]$Statements }
+ $JsonRequest = ConvertTo-Json20 $Json
+ $Null = $WebClient.UploadString($URI.AbsoluteUri + "db/data/transaction/commit", $JsonRequest)
+ $Statements.Clear()
+ }
+ }
+ $GroupCounter += 1
}
}
- }
- elseif($Object.PSObject.TypeNames -Contains 'PowerView.User') {
- $AccountDomain = $Object.Domain
- $AccountName = "$($Object.SamAccountName)@$AccountDomain"
-
- if($Object.PrimaryGroupName -and ($Object.PrimaryGroupName -ne '')) {
- $PrimaryGroupName = "$($Object.PrimaryGroupName)@$AccountDomain"
- $Queries += "MERGE (user:User { name: UPPER('$AccountName') }) MERGE (group:Group { name: UPPER('$PrimaryGroupName') }) MERGE (user)-[:MemberOf]->(group)"
- }
- # TODO: extract pwdlastset/etc. and ingest
- }
- elseif(($Object.PSObject.TypeNames -contains 'PowerView.LocalUserAPI') -or ($Object.PSObject.TypeNames -contains 'PowerView.LocalUser')) {
- $AccountName = $($Object.AccountName.replace('/', '\')).split('\')[-1]
-
- $MemberSimpleName = Convert-SidToName -SID $Object.SID | Convert-ADName -InputType 'NT4' -OutputType 'Canonical'
-
- if($MemberSimpleName) {
- $MemberDomain = $MemberSimpleName.Split('/')[0]
- }
- else {
- $MemberDomain = "UNKNOWN"
- }
-
- $AccountName = "$AccountName@$MemberDomain"
-
- if($Object.IsGroup) {
- $Queries += "MERGE (group:Group { name: UPPER('$AccountName') }) MERGE (computer:Computer { name: UPPER('$($Object.ComputerName)') }) MERGE (group)-[:AdminTo]->(computer)"
- }
- else {
- $Queries += "MERGE (user:User { name: UPPER('$AccountName') }) MERGE (computer:Computer { name: UPPER('$($Object.ComputerName)') }) MERGE (user)-[:AdminTo]->(computer)"
- }
- }
- elseif($Object.PSObject.TypeNames -contains 'PowerView.LocalUserSpecified') {
- # manually specified localgroup membership where resolution happens by the callee
-
- $AccountName = "$($Object.MemberName)@$($Object.MemberDomain)"
+ $GroupSearcher.Dispose()
- if($Object.IsGroup) {
- $Queries += "MERGE (group:Group { name: UPPER('$AccountName') }) MERGE (computer:Computer { name: UPPER('$($Object.ComputerName)') }) MERGE (group)-[:AdminTo]->(computer)"
- }
- else {
- $Queries += "MERGE (user:User { name: UPPER('$AccountName') }) MERGE (computer:Computer { name: UPPER('$($Object.ComputerName)') }) MERGE (user)-[:AdminTo]->(computer)"
+ if ($PSCmdlet.ParameterSetName -eq 'RESTAPI') {
+ $Json = @{ "statements"=[System.Collections.Hashtable[]]$Statements }
+ $JsonRequest = ConvertTo-Json20 $Json
+ $Null = $WebClient.UploadString($URI.AbsoluteUri + "db/data/transaction/commit", $JsonRequest)
+ $Statements.Clear()
}
+ Write-Verbose "Done with group enumeration for domain $TargetDomain"
}
- elseif($Object.PSObject.TypeNames -contains 'PowerView.GPOLocalGroup') {
- if(![string]::IsNullOrEmpty($Object.SID)){
- $MemberSimpleName = Convert-SidToName -SID $Object.SID | Convert-ADName -InputType 'NT4' -OutputType 'Canonical'
- }
-
- if($MemberSimpleName) {
- $MemberDomain = $MemberSimpleName.Split('/')[0]
- $AccountName = "$($Object.ObjectName)@$MemberDomain"
- }
- else {
- $AccountName = $Object.ObjectName
- }
+ Write-Verbose "Enumerating group enumeration"
+ [GC]::Collect()
+ }
- ForEach($Computer in $Object.ComputerName) {
- if($Object.IsGroup) {
- $Queries += "MERGE (group:Group { name: UPPER('$AccountName') }) MERGE (computer:Computer { name: UPPER('$Computer') }) MERGE (group)-[:AdminTo]->(computer)"
- }
- else {
- $Queries += "MERGE (user:User { name: UPPER('$AccountName') }) MERGE (computer:Computer { name: UPPER('$Computer') }) MERGE (user)-[:AdminTo]->(computer)"
- }
- }
- }
- elseif($Object.PSObject.TypeNames -contains 'PowerView.DomainTrustLDAP') {
- # [uint32]'0x00000001' = 'non_transitive'
- # [uint32]'0x00000002' = 'uplevel_only'
- # [uint32]'0x00000004' = 'quarantined_domain'
- # [uint32]'0x00000008' = 'forest_transitive'
- # [uint32]'0x00000010' = 'cross_organization'
- # [uint32]'0x00000020' = 'within_forest'
- # [uint32]'0x00000040' = 'treat_as_external'
- # [uint32]'0x00000080' = 'trust_uses_rc4_encryption'
- # [uint32]'0x00000100' = 'trust_uses_aes_keys'
- # [uint32]'0x00000200' = 'cross_organization_no_tgt_delegation'
- # [uint32]'0x00000400' = 'pim_trust'
- if($Object.SourceDomain) {
- $SourceDomain = $Object.SourceDomain
+ if($UseDomainTrusts -and $TargetDomains) {
+ Write-Verbose "Mapping domain trusts"
+ Invoke-MapDomainTrust | ForEach-Object {
+ if($_.SourceDomain) {
+ $SourceDomain = $_.SourceDomain
}
else {
- $SourceDomain = $Object.SourceName
+ $SourceDomain = $_.SourceName
}
- if($Object.TargetDomain) {
- $TargetDomain = $Object.TargetDomain
+ if($_.TargetDomain) {
+ $TargetDomain = $_.TargetDomain
}
else {
- $TargetDomain = $Object.TargetName
+ $TargetDomain = $_.TargetName
}
- $Query = "MERGE (SourceDomain:Domain { name: UPPER('$SourceDomain') }) MERGE (TargetDomain:Domain { name: UPPER('$TargetDomain') })"
-
- if($Object.TrustType -match 'cross_organization') {
- $TrustType = 'CrossLink'
- }
- elseif ($Object.TrustType -match 'within_forest') {
- $TrustType = 'ParentChild'
- }
- elseif ($Object.TrustType -match 'forest_transitive') {
- $TrustType = 'Forest'
- }
- elseif ($Object.TrustType -match 'treat_as_external') {
- $TrustType = 'External'
+ if ($PSCmdlet.ParameterSetName -eq 'CSVExport') {
+ $TrustWriter.WriteLine("`"$SourceDomain`",`"$TargetDomain`",`"$($_.TrustDirection)`",`"$($_.TrustType)`",`"$True`"")
}
else {
- Write-Verbose "Trust type unhandled/unknown: $($Object.TrustType)"
- $TrustType = 'Unknown'
- }
+ $Null = $Statements.Add( @{ "statement"="MERGE (SourceDomain:Domain { name: UPPER('$SourceDomain') }) MERGE (TargetDomain:Domain { name: UPPER('$TargetDomain') })" } )
- if ($Object.TrustType -match 'non_transitive') {
- $Transitive = $False
- }
- else {
+ $TrustType = $_.TrustType
$Transitive = $True
- }
-
- Switch ($Object.TrustDirection) {
- 'Inbound' {
- $Query += " MERGE (TargetDomain)-[:TrustedBy{ TrustType: UPPER('$TrustType'), Transitive: UPPER('$Transitive')}]->(SourceDomain)"
- }
- 'Outbound' {
- $Query += " MERGE (SourceDomain)-[:TrustedBy{ TrustType: UPPER('$TrustType'), Transitive: UPPER('$Transitive')}]->(TargetDomain)"
- }
- 'Bidirectional' {
- $Query += " MERGE (TargetDomain)-[:TrustedBy{ TrustType: UPPER('$TrustType'), Transitive: UPPER('$Transitive')}]->(SourceDomain) MERGE (SourceDomain)-[:TrustedBy{ TrustType: UPPER('$TrustType'), Transitive: UPPER('$Transitive')}]->(TargetDomain)"
- }
- }
- $Queries += $Query
- }
- elseif($Object.PSObject.TypeNames -contains 'PowerView.DomainTrust') {
- if($Object.SourceDomain) {
- $SourceDomain = $Object.SourceDomain
- }
- else {
- $SourceDomain = $Object.SourceName
- }
- if($Object.TargetDomain) {
- $TargetDomain = $Object.TargetDomain
- }
- else {
- $TargetDomain = $Object.TargetName
- }
-
- $Query = "MERGE (SourceDomain:Domain { name: UPPER('$SourceDomain') }) MERGE (TargetDomain:Domain { name: UPPER('$TargetDomain') })"
- $TrustType = $Object.TrustType
- $Transitive = $True
-
- Switch ($Object.TrustDirection) {
- 'Inbound' {
- $Query += " MERGE (SourceDomain)-[:TrustedBy{ TrustType: UPPER('$TrustType'), Transitive: UPPER('$Transitive')}]->(TargetDomain)"
- }
- 'Outbound' {
- $Query += " MERGE (TargetDomain)-[:TrustedBy{ TrustType: UPPER('$TrustType'), Transitive: UPPER('$Transitive')}]->(SourceDomain)"
- }
- 'Bidirectional' {
- $Query += " MERGE (TargetDomain)-[:TrustedBy{ TrustType: UPPER('$TrustType'), Transitive: UPPER('$Transitive')}]->(SourceDomain) MERGE (SourceDomain)-[:TrustedBy{ TrustType: UPPER('$TrustType'), Transitive: UPPER('$Transitive')}]->(TargetDomain)"
+ Switch ($_.TrustDirection) {
+ 'Inbound' {
+ $Null = $Statements.Add( @{ "statement"="MERGE (SourceDomain)-[:TrustedBy{ TrustType: UPPER('$TrustType'), Transitive: UPPER('$Transitive')}]->(TargetDomain)" } )
+ }
+ 'Outbound' {
+ $Null = $Statements.Add( @{ "statement"="MERGE (TargetDomain)-[:TrustedBy{ TrustType: UPPER('$TrustType'), Transitive: UPPER('$Transitive')}]->(SourceDomain)" } )
+ }
+ 'Bidirectional' {
+ $Null = $Statements.Add( @{ "statement"="MERGE (TargetDomain)-[:TrustedBy{ TrustType: UPPER('$TrustType'), Transitive: UPPER('$Transitive')}]->(SourceDomain) MERGE (SourceDomain)-[:TrustedBy{ TrustType: UPPER('$TrustType'), Transitive: UPPER('$Transitive')}]->(TargetDomain)" } )
+ }
}
- }
- $Queries += $Query
- }
- elseif($Object.PSObject.TypeNames -contains 'PowerView.ForestTrust') {
- if($Object.SourceDomain) {
- $SourceDomain = $Object.SourceDomain
- }
- else {
- $SourceDomain = $Object.SourceName
- }
- if($Object.TargetDomain) {
- $TargetDomain = $Object.TargetDomain
- }
- else {
- $TargetDomain = $Object.TargetName
- }
- $Query = "MERGE (SourceDomain:Domain { name: UPPER('$SourceDomain') }) MERGE (TargetDomain:Domain { name: UPPER('$TargetDomain') })"
-
- $TrustType = 'Forest'
- $Transitive = $True
-
- Switch ($Object.TrustDirection) {
- 'Inbound' {
- $Query += " MERGE (SourceDomain)-[:TrustedBy{ TrustType: UPPER('FOREST'), Transitive: UPPER('$Transitive')}]->(TargetDomain)"
- }
- 'Outbound' {
- $Query += " MERGE (TargetDomain)-[:TrustedBy{ TrustType: UPPER('FOREST'), Transitive: UPPER('$Transitive')}]->(SourceDomain)"
- }
- 'Bidirectional' {
- $Query += " MERGE (TargetDomain)-[:TrustedBy{ TrustType: UPPER('FOREST'), Transitive: UPPER('$Transitive')}]->(SourceDomain) MERGE (SourceDomain)-[:TrustedBy{ TrustType: UPPER('FOREST'), Transitive: UPPER('$Transitive')}]->(TargetDomain)"
- }
}
- $Queries += $Query
- }
- else {
- Write-Verbose "No matching type name"
- }
-
- ForEach($Query in $Queries) {
- $Null = $Statements.Add( @{ "statement"=$Query } )
}
- if ($Statements.Count -ge $Throttle) {
+ if ($PSCmdlet.ParameterSetName -eq 'RESTAPI') {
$Json = @{ "statements"=[System.Collections.Hashtable[]]$Statements }
$JsonRequest = ConvertTo-Json20 $Json
$Null = $WebClient.UploadString($URI.AbsoluteUri + "db/data/transaction/commit", $JsonRequest)
$Statements.Clear()
}
+ Write-Verbose "Done mapping domain trusts"
}
- else {
- throw 'Not authorized'
- }
- }
- end {
- if($Authorized) {
- $Json = @{ "statements"=[System.Collections.Hashtable[]]$Statements }
- $JsonRequest = ConvertTo-Json20 $Json
- $Null = $WebClient.UploadString($URI.AbsoluteUri + "db/data/transaction/commit", $JsonRequest)
- $Statements.Clear()
- }
- }
-}
-
-
-function Export-BloodHoundCSV {
-<#
- .SYNOPSIS
-
- Takes input from Get-BloodHound data and exports the objects to one custom CSV file
- per object type (sessions, local admin, domain trusts, etc.).
-
- Author: @harmj0y
- License: BSD 3-Clause
- Required Dependencies: PowerView.ps1
- Optional Dependencies: None
-
- .DESCRIPTION
-
- This function takes custom tagged PowerView objects types from Get-BloodHoundData and exports
- the data to one custom CSV file per object type (sessions, local admin, domain trusts, etc.).
- For user session data without a logon domain, by default the global catalog is used to attempt to
- deconflict what domain the user may be located in. If the user exists in more than one domain in
- the forest, a series of weights is used to modify the attack path likelihood.
-
- .PARAMETER Object
-
- The PowerView PSObject to export to csv.
-
- .PARAMETER CSVFolder
- The folder to output all CSVs to, defaults to the current working directory.
+ if($UseGPOGroup -and $TargetDomains) {
+ ForEach ($TargetDomain in $TargetDomains) {
- .PARAMETER CSVPrefix
+ Write-Verbose "Enumerating GPO local group memberships for domain $TargetDomain"
+ Find-GPOLocation -Domain $TargetDomain -DomainController $DomainController | ForEach-Object {
+ $AccountName = "$($_.ObjectName)@$($_.ObjectDomain)"
+ ForEach($Computer in $_.ComputerName) {
+ if($_.IsGroup) {
+ if ($PSCmdlet.ParameterSetName -eq 'CSVExport') {
+ $LocalAdminWriter.WriteLine("`"$Computer`",`"$AccountName`",`"group`"")
+ }
+ else {
+ $Null = $Statements.Add( @{"statement"="MERGE (group:Group { name: UPPER('$AccountName') }) MERGE (computer:Computer { name: UPPER('$Computer') }) MERGE (group)-[:AdminTo]->(computer)" } )
+ }
+ }
+ else {
+ if ($PSCmdlet.ParameterSetName -eq 'CSVExport') {
+ $LocalAdminWriter.WriteLine("`"$Computer`",`"$AccountName`",`"user`"")
+ }
+ else {
+ $Null = $Statements.Add( @{"statement"="MERGE (user:User { name: UPPER('$AccountName') }) MERGE (computer:Computer { name: UPPER('$Computer') }) MERGE (user)-[:AdminTo]->(computer)" } )
+ }
+ }
+ }
+ }
+ Write-Verbose "Done enumerating GPO local group memberships for domain $TargetDomain"
+ }
+ Write-Verbose "Done enumerating GPO local group"
+ # TODO: cypher query to add 'domain admins' to every found machine
+ }
- A prefix to append to each CSV file.
+ # get the current user so we can ignore it in the results
+ $CurrentUser = ([Environment]::UserName).toLower()
- .PARAMETER SkipGCDeconfliction
+ # script block that enumerates a server
+ $HostEnumBlock = {
+ param($ComputerName, $Ping, $CurrentUser2, $UseLocalGroup2, $UseSession2, $UseLoggedon2, $DomainSID2)
+ $Up = $True
+ if($Ping) {
+ $Up = Test-Connection -Count 1 -Quiet -ComputerName $ComputerName
+ }
+ if($Up) {
- Switch. Don't resolve user domain memberships for session information using a global catalog.
+ if($UseLocalGroup2) {
+ # grab the users for the local admins on this server
+ $Results = Get-NetLocalGroup -ComputerName $ComputerName -API -IsDomain -DomainSID $DomainSID2
+ if($Results) {
+ $Results
+ }
+ else {
+ Get-NetLocalGroup -ComputerName $ComputerName -IsDomain -DomainSID $DomainSID2
+ }
+ }
- .PARAMETER GlobalCatalog
+ $IPAddress = @(Get-IPAddress -ComputerName $ComputerName)[0].IPAddress
- The global catalog location to resole user memberships from, form of GC://global.catalog.
+ if($UseSession2) {
+ ForEach ($Session in $(Get-NetSession -ComputerName $ComputerName)) {
+ $UserName = $Session.sesi10_username
+ $CName = $Session.sesi10_cname
- .EXAMPLE
+ if($CName -and $CName.StartsWith("\\")) {
+ $CName = $CName.TrimStart("\")
+ }
- PS C:\> Get-BloodHoundData | Export-BloodHoundCSV
+ # make sure we have a result
+ if (($UserName) -and ($UserName.trim() -ne '') -and ($UserName -notmatch '\$') -and ($UserName -notmatch $CurrentUser2)) {
+ # Try to resolve the DNS hostname of $Cname
+ try {
+ $CNameDNSName = [System.Net.Dns]::GetHostEntry($CName) | Select-Object -ExpandProperty HostName
+ }
+ catch {
+ $CNameDNSName = $CName
+ }
+ @{
+ 'UserDomain' = $Null
+ 'UserName' = $UserName
+ 'ComputerName' = $ComputerName
+ 'IPAddress' = $IPAddress
+ 'SessionFrom' = $CName
+ 'SessionFromName' = $CNameDNSName
+ 'LocalAdmin' = $Null
+ 'Type' = 'UserSession'
+ }
+ }
+ }
+ }
- Executes default collection options and exports the data to user_sessions.csv, group_memberships.csv,
- local_admins.csv, and trusts.csv in the current directory.
+ if($UseLoggedon2) {
+ ForEach ($User in $(Get-NetLoggedon -ComputerName $ComputerName)) {
+ $UserName = $User.wkui1_username
+ $UserDomain = $User.wkui1_logon_domain
- .EXAMPLE
+ # ignore local account logons
+ if($ComputerName -notmatch "^$UserDomain") {
+ if (($UserName) -and ($UserName.trim() -ne '') -and ($UserName -notmatch '\$')) {
+ @{
+ 'UserDomain' = $UserDomain
+ 'UserName' = $UserName
+ 'ComputerName' = $ComputerName
+ 'IPAddress' = $IPAddress
+ 'SessionFrom' = $Null
+ 'SessionFromName' = $Null
+ 'LocalAdmin' = $Null
+ 'Type' = 'UserSession'
+ }
+ }
+ }
+ }
- PS C:\> Get-BloodHoundData | Export-BloodHoundCSV -SkipGCDeconfliction
+ ForEach ($User in $(Get-LoggedOnLocal -ComputerName $ComputerName)) {
+ $UserName = $User.UserName
+ $UserDomain = $User.UserDomain
- Executes default collection options, skips the global catalog deconfliction, and exports the data
- to user_sessions.csv, group_memberships.csv, local_admins.csv, and trusts.csv in the current directory.
+ # ignore local account logons ?
+ if($ComputerName -notmatch "^$UserDomain") {
+ @{
+ 'UserDomain' = $UserDomain
+ 'UserName' = $UserName
+ 'ComputerName' = $ComputerName
+ 'IPAddress' = $IPAddress
+ 'SessionFrom' = $Null
+ 'SessionFromName' = $Null
+ 'LocalAdmin' = $Null
+ 'Type' = 'UserSession'
+ }
+ }
+ }
+ }
+ }
+ }
- .EXAMPLE
+ if ($TargetDomains -and (-not $SkipComputerEnumeration)) {
+ # 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()
- PS C:\> Get-BloodHoundData | Export-BloodHoundCSV -CSVFolder C:\Temp\ -CSVPrefix "domainX"
+ # grab all the current variables for this runspace
+ $MyVars = Get-Variable -Scope 1
- Executes default collection options and exports the data to domainX_user_sessions.csv, domainX_group_memberships.csv,
- domainX_local_admins.csv, and tdomainX_rusts.csv in C:\Temp\.
+ # 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')
- .NOTES
+ # 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))
+ }
+ }
- CSV file types:
+ # 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))
+ }
- PowerView.UserSession -> $($CSVExportPrefix)user_sessions.csv
- UserName,ComputerName,Weight
- "john@domain.local","computer2.domain.local",1
+ # threading adapted from
+ # https://github.com/darkoperator/Posh-SecMod/blob/master/Discovery/Discovery.psm1#L407
+ # Thanks Carlos!
- PowerView.GroupMember/PowerView.User -> $($CSVExportPrefix)group_memberships.csv
- AccountName,AccountType,GroupName
- "john@domain.local","user","GROUP1"
- "computer3.testlab.local","computer","GROUP1"
+ # create a pool of maxThread runspaces
+ Write-Verbose "Creating a runspace with $Threads threads"
+ $Pool = [RunspaceFactory]::CreateRunspacePool(1, $Threads, $SessionState, $Host)
+ $Pool.ThreadOptions = [System.Management.Automation.Runspaces.PSThreadOptions]::UseNewThread
+ $Pool.Open()
- PowerView.LocalUserAPI/PowerView.GPOLocalGroup -> $($CSVExportPrefix)local_admins.csv
- AccountName,AccountType,ComputerName
- "john@domain.local","user","computer2.domain.local"
+ $Jobs = @()
+ $PS = @()
+ $Wait = @()
+ $Counter = 0
+ $MovingWindow = 0
+ }
+ }
- PowerView.DomainTrustLDAP/PowerView.DomainTrust/PowerView.ForestTrust -> $($CSVExportPrefix)trusts.csv
- SourceDomain,TargetDomain,TrustDirection,TrustType,Transitive
- "domain.local","dev.domain.local","Bidirectional","ParentChild","True"
-#>
+ PROCESS {
+ if ($TargetDomains -and (-not $SkipComputerEnumeration)) {
- [CmdletBinding()]
- param(
- [Parameter(Position = 0, ValueFromPipeline = $True, Mandatory = $True)]
- [PSObject]
- $Object,
+ if($Statements) {
+ $Statements.Clear()
+ }
- [Parameter()]
- [ValidateScript({ Test-Path -Path $_ })]
- [String]
- $CSVFolder = $(Get-Location),
+ ForEach ($TargetDomain in $TargetDomains) {
- [Parameter()]
- [ValidateNotNullOrEmpty()]
- [String]
- $CSVPrefix,
+ $DomainSID = Get-DomainSid -Domain $TargetDomain
- [Switch]
- $SkipGCDeconfliction,
+ $ScriptParameters = @{
+ 'Ping' = $True
+ 'CurrentUser2' = $CurrentUser
+ 'UseLocalGroup2' = $UseLocalGroup
+ 'UseSession2' = $UseSession
+ 'UseLoggedon2' = $UseLoggedon
+ 'DomainSID2' = $DomainSID
+ }
- [ValidatePattern('^GC://')]
- [String]
- $GlobalCatalog
- )
+ if($CollectionMethod -eq 'Stealth') {
+ Write-Verbose "Executing stealth computer enumeration of domain $TargetDomain"
- BEGIN {
- try {
- $OutputFolder = $CSVFolder | Resolve-Path -ErrorAction Stop | Select-Object -ExpandProperty Path
- }
- catch {
- throw "Error: $_"
- }
+ [Array]$TargetComputers = @()
+ Write-Verbose "Querying domain $TargetDomain for File Servers"
+ $TargetComputers += Get-NetFileServer -Domain $TargetDomain -DomainController $DomainController
- if($CSVPrefix) {
- $CSVExportPrefix = "$($CSVPrefix)_"
- }
- else {
- $CSVExportPrefix = ''
- }
+ Write-Verbose "Querying domain $TargetDomain for DFS Servers"
+ $TargetComputers += ForEach($DFSServer in $(Get-DFSshare -Domain $TargetDomain -DomainController $DomainController)) {
+ $DFSServer.RemoteServerName
+ }
- $UserDomainMappings = @{}
- if(-not $SkipGCDeconfliction) {
- # if we're doing session enumeration, create a {user : @(domain,..)} from a global catalog
- # in order to do user domain deconfliction for sessions
+ Write-Verbose "Querying domain $TargetDomain for Domain Controllers"
+ $TargetComputers += ForEach($DomainController in $(Get-NetDomainController -LDAP -DomainController $DomainController -Domain $TargetDomain)) {
+ $DomainController.dnshostname
+ }
- if(-not $PSBoundParameters['GlobalCatalog']) {
- $UserDomainMappings = Get-GlobalCatalogUserMapping
- }
- else {
- $UserDomainMappings = Get-GlobalCatalogUserMapping -GlobalCatalog $GlobalCatalog
- }
- }
- }
+ $TargetComputers = $TargetComputers | Where-Object {$_ -and ($_.Trim() -ne '')} | Sort-Object -Unique
- PROCESS {
+ ForEach ($Computer in $TargetComputers) {
+ While ($($Pool.GetAvailableRunspaces()) -le 0) {
+ Start-Sleep -MilliSeconds 500
+ }
- if($Object.PSObject.TypeNames -contains 'PowerView.UserSession') {
+ # create a "powershell pipeline runner"
+ $PS += [PowerShell]::Create()
+ $PS[$Counter].RunspacePool = $Pool
- if($Object.SessionFromName) {
- try {
- $UserName = $Object.UserName.ToUpper()
- $SessionFromName = $Object.SessionFromName
- $ComputerDomain = $Object.SessionFromName.SubString($Object.SessionFromName.IndexOf('.')+1).ToUpper()
+ # add the script block + arguments
+ $Null = $PS[$Counter].AddScript($HostEnumBlock).AddParameter('ComputerName', $Computer)
+ ForEach ($Param in $ScriptParameters.GetEnumerator()) {
+ $Null = $PS[$Counter].AddParameter($Param.Name, $Param.Value)
+ }
- if($UserDomainMappings) {
- $UserDomain = $Null
- if($UserDomainMappings[$UserName]) {
- if($UserDomainMappings[$UserName].Count -eq 1) {
- $UserDomain = $UserDomainMappings[$UserName]
- $LoggedOnUser = "$UserName@$UserDomain"
-
- $Properties = @{
- UserName = $LoggedOnUser
- ComputerName = $SessionFromName
- Weight = 1
- }
- New-Object PSObject -Property $Properties | Export-PowerViewCSV -OutFile "$OutputFolder\$($CSVExportPrefix)user_sessions.csv"
+ # start job
+ $Jobs += $PS[$Counter].BeginInvoke()
+ $Counter += 1
+ }
+ }
+ else {
+ Write-Verbose "Enumerating all machines in domain $TargetDomain"
+ $ComputerSearcher = Get-DomainSearcher -Domain $TargetDomain -DomainController $DomainController
+ $ComputerSearcher.filter = '(sAMAccountType=805306369)'
+ $Null = $ComputerSearcher.PropertiesToLoad.Add('dnshostname')
+
+ ForEach($ComputerResult in $ComputerSearcher.FindAll()) {
+ $Slept = $False
+ if($Counter % 100 -eq 0) {
+ Write-Verbose "Computer counter: $Counter"
+ }
+ elseif($Counter % 1000 -eq 0) {
+ 1..3 | ForEach-Object {
+ $Null = [GC]::Collect()
}
- else {
- $ComputerDomain = $Object.SessionFromName.SubString($Object.SessionFromName.IndexOf('.')+1).ToUpper()
+ }
- $UserDomainMappings[$UserName] | ForEach-Object {
- if($_ -eq $ComputerDomain) {
- $UserDomain = $_
- $LoggedOnUser = "$UserName@$UserDomain"
+ while ($($Pool.GetAvailableRunspaces()) -le 0) {
+ Start-Sleep -MilliSeconds 500
+ $Slept = $True
+ }
- $Properties = @{
- UserName = $LoggedOnUser
- ComputerName = $SessionFromName
- Weight = 1
- }
- New-Object PSObject -Property $Properties | Export-PowerViewCSV -OutFile "$OutputFolder\$($CSVExportPrefix)user_sessions.csv"
- }
- else {
- $UserDomain = $_
- $LoggedOnUser = "$UserName@$UserDomain"
+ # if we slept, meaning all threads were occupised, consume results as they complete
+ # with a 'moving window' that moves 300 threads behind the current point
+ if($Slept -and (($Counter-$Threads-300) -gt 0) ) {
+ for ($y = $MovingWindow; $y -lt $($Counter-$Threads-300); $y++) {
+ if($Jobs[$y].IsCompleted) {
+ try {
+ # complete async job
+ $PS[$y].EndInvoke($Jobs[$y]) | ForEach-Object {
+ if($_['Type'] -eq 'UserSession') {
+ if($_['SessionFromName']) {
+ try {
+ $SessionFromName = $_['SessionFromName']
+ $UserName = $_['UserName'].ToUpper()
+ $ComputerDomain = $_['SessionFromName'].SubString($_['SessionFromName'].IndexOf('.')+1).ToUpper()
+
+ if($UserDomainMappings) {
+ $UserDomain = $Null
+ if($UserDomainMappings[$UserName]) {
+ if($UserDomainMappings[$UserName].Count -eq 1) {
+ $UserDomain = $UserDomainMappings[$UserName]
+ $LoggedOnUser = "$UserName@$UserDomain"
+ if ($PSCmdlet.ParameterSetName -eq 'CSVExport') {
+ $SessionWriter.WriteLine("`"$SessionFromName`",`"$LoggedOnUser`",`"1`"")
+ }
+ else {
+ $Null = $Statements.Add( @{"statement"="MERGE (user:User { name: UPPER('$LoggedOnUser') }) MERGE (computer:Computer { name: UPPER('$SessionFromName') }) MERGE (computer)-[:HasSession {Weight: '1'}]->(user)" } )
+ }
+ }
+ else {
+ $ComputerDomain = $_['SessionFromName'].SubString($_['SessionFromName'].IndexOf('.')+1).ToUpper()
+
+ $UserDomainMappings[$UserName] | ForEach-Object {
+ # for multiple GC results, set a weight of 1 for the same domain as the target computer
+ if($_ -eq $ComputerDomain) {
+ $UserDomain = $_
+ $LoggedOnUser = "$UserName@$UserDomain"
+ if ($PSCmdlet.ParameterSetName -eq 'CSVExport') {
+ $SessionWriter.WriteLine("`"$SessionFromName`",`"$LoggedOnUser`",`"1`"")
+ }
+ else {
+ $Null = $Statements.Add( @{"statement"="MERGE (user:User { name: UPPER('$LoggedOnUser') }) MERGE (computer:Computer { name: UPPER('$SessionFromName') }) MERGE (computer)-[:HasSession {Weight: '1'}]->(user)" } )
+ }
+ }
+ # and set a weight of 2 for all other users in additional domains
+ else {
+ $UserDomain = $_
+ $LoggedOnUser = "$UserName@$UserDomain"
+ if ($PSCmdlet.ParameterSetName -eq 'CSVExport') {
+ $SessionWriter.WriteLine("`"$SessionFromName`",`"$LoggedOnUser`",`"2`"")
+ }
+ else {
+ $Null = $Statements.Add( @{"statement"="MERGE (user:User { name: UPPER('$LoggedOnUser') }) MERGE (computer:Computer { name: UPPER('$SessionFromName') }) MERGE (computer)-[:HasSession {Weight: '2'}]->(user)" } )
+ }
+ }
+ }
+ }
+ }
+ else {
+ # no user object in the GC with this username, so set the domain to "UNKNOWN"
+ $LoggedOnUser = "$UserName@UNKNOWN"
+ if ($PSCmdlet.ParameterSetName -eq 'CSVExport') {
+ $SessionWriter.WriteLine("`"$SessionFromName`",`"$LoggedOnUser`",`"2`"")
+ }
+ else {
+ $Null = $Statements.Add( @{"statement"="MERGE (user:User { name: UPPER('$LoggedOnUser') }) MERGE (computer:Computer { name: UPPER('$SessionFromName') }) MERGE (computer)-[:HasSession {Weight: '2'}]->(user)" } )
+ }
+ }
+ }
+ else {
+ # if not using GC mappings, set the weight to 2
+ $LoggedOnUser = "$UserName@$ComputerDomain"
+ if ($PSCmdlet.ParameterSetName -eq 'CSVExport') {
+ $SessionWriter.WriteLine("`"$SessionFromName`",`"$LoggedOnUser`",`"2`"")
+ }
+ else {
+ $Null = $Statements.Add( @{"statement"="MERGE (user:User { name: UPPER('$LoggedOnUser') }) MERGE (computer:Computer { name: UPPER('$SessionFromName') }) MERGE (computer)-[:HasSession {Weight: '2'}]->(user)"} )
+ }
+ }
+ }
+ catch {
+ Write-Warning "Error extracting domain from $SessionFromName"
+ }
+ }
+ elseif($_['SessionFrom']) {
+ $SessionFromName = $_['SessionFrom']
+ $LoggedOnUser = "$($_['UserName'])@UNKNOWN"
+ if ($PSCmdlet.ParameterSetName -eq 'CSVExport') {
+ $SessionWriter.WriteLine("`"$SessionFromName`",`"$LoggedOnUser`",`"2`"")
+ }
+ else {
+ $Null = $Statements.Add( @{"statement"="MERGE (user:User { name: UPPER(`"$LoggedOnUser`") }) MERGE (computer:Computer { name: UPPER(`"$SessionFromName`") }) MERGE (computer)-[:HasSession {Weight: '2'}]->(user)"} )
+ }
+ }
+ else {
+ # assume Get-NetLoggedOn result
+ $UserDomain = $_['UserDomain']
+ $UserName = $_['UserName']
+ try {
+ if($DomainShortnameMappings[$UserDomain]) {
+ # in case the short name mapping is 'cached'
+ $AccountName = "$UserName@$($DomainShortnameMappings[$UserDomain])"
+ }
+ else {
+ $MemberSimpleName = "$UserDomain\$UserName" | Convert-ADName -InputType 'NT4' -OutputType 'Canonical'
+
+ if($MemberSimpleName) {
+ $MemberDomain = $MemberSimpleName.Split('/')[0]
+ $AccountName = "$UserName@$MemberDomain"
+ $DomainShortnameMappings[$UserDomain] = $MemberDomain
+ }
+ else {
+ $AccountName = "$UserName@UNKNOWN"
+ }
+ }
+
+ $SessionFromName = $_['ComputerName']
+
+ if ($PSCmdlet.ParameterSetName -eq 'CSVExport') {
+ $SessionWriter.WriteLine("`"$SessionFromName`",`"$AccountName`",`"1`"")
+ }
+ else {
+ $Null = $Statements.Add( @{"statement"="MERGE (user:User { name: UPPER('$AccountName') }) MERGE (computer:Computer { name: UPPER('$SessionFromName') }) MERGE (computer)-[:HasSession {Weight: '1'}]->(user)" } )
+ }
+ }
+ catch {
+ Write-Verbose "Error converting $UserDomain\$UserName : $_"
+ }
+ }
+ }
+ elseif($_['Type'] -eq 'LocalUser') {
+ $Parts = $_['AccountName'].split('\')
+ $UserDomain = $Parts[0]
+ $UserName = $Parts[-1]
+
+ if($DomainShortnameMappings[$UserDomain]) {
+ # in case the short name mapping is 'cached'
+ $AccountName = "$UserName@$($DomainShortnameMappings[$UserDomain])"
+ }
+ else {
+ $MemberSimpleName = "$UserDomain\$UserName" | Convert-ADName -InputType 'NT4' -OutputType 'Canonical'
+
+ if($MemberSimpleName) {
+ $MemberDomain = $MemberSimpleName.Split('/')[0]
+ $AccountName = "$UserName@$MemberDomain"
+ $DomainShortnameMappings[$UserDomain] = $MemberDomain
+ }
+ else {
+ $AccountName = "$UserName@UNKNOWN"
+ }
+ }
+
+ $ComputerName = $_['ComputerName']
+ if($_['IsGroup']) {
+ if ($PSCmdlet.ParameterSetName -eq 'CSVExport') {
+ $LocalAdminWriter.WriteLine("`"$ComputerName`",`"$AccountName`",`"group`"")
+ }
+ else {
+ $Null = $Statements.Add( @{ "statement"="MERGE (group:Group { name: UPPER('$AccountName') }) MERGE (computer:Computer { name: UPPER('$ComputerName') }) MERGE (group)-[:AdminTo]->(computer)" } )
+ }
+ }
+ else {
+ if ($PSCmdlet.ParameterSetName -eq 'CSVExport') {
+ $LocalAdminWriter.WriteLine("`"$ComputerName`",`"$AccountName`",`"user`"")
+ }
+ else {
+ $Null = $Statements.Add( @{"statement"="MERGE (user:User { name: UPPER('$AccountName') }) MERGE (computer:Computer { name: UPPER('$ComputerName') }) MERGE (user)-[:AdminTo]->(computer)" } )
+ }
+ }
+ }
- $Properties = @{
- UserName = $LoggedOnUser
- ComputerName = $SessionFromName
- Weight = 2
+ if (($PSCmdlet.ParameterSetName -eq 'RESTAPI') -and ($Statements.Count -ge $Throttle)) {
+ $Json = @{ "statements"=[System.Collections.Hashtable[]]$Statements }
+ $JsonRequest = ConvertTo-Json20 $Json
+ $Null = $WebClient.UploadString($URI.AbsoluteUri + "db/data/transaction/commit", $JsonRequest)
+ $Statements.Clear()
+ }
}
- New-Object PSObject -Property $Properties | Export-PowerViewCSV -OutFile "$OutputFolder\$($CSVExportPrefix)user_sessions.csv"
+ }
+ catch {
+ Write-Verbose "Error ending Invoke-BloodHound thread $y : $_"
+ }
+ finally {
+ $PS[$y].Dispose()
+ $PS[$y] = $Null
+ $Jobs[$y] = $Null
}
}
}
+ $MovingWindow = $Counter-$Threads-200
}
- else {
- # no user object in the GC with this username
- $LoggedOnUser = "$UserName@UNKNOWN"
-
- $Properties = @{
- UserName = $LoggedOnUser
- ComputerName = $SessionFromName
- Weight = 2
- }
- New-Object PSObject -Property $Properties | Export-PowerViewCSV -OutFile "$OutputFolder\$($CSVExportPrefix)user_sessions.csv"
- }
- }
- else {
- $LoggedOnUser = "$UserName@$ComputerDomain"
- $Properties = @{
- UserName = $LoggedOnUser
- ComputerName = $SessionFromName
- Weight = 2
- }
- New-Object PSObject -Property $Properties | Export-PowerViewCSV -OutFile "$OutputFolder\$($CSVExportPrefix)user_sessions.csv"
- }
- }
- catch {
- Write-Warning "Error extracting domain from $($Object.SessionFromName)"
- }
- }
- elseif($Object.SessionFrom) {
- $Properties = @{
- UserName = "$($Object.UserName)@UNKNOWN"
- ComputerName = $Object.SessionFrom
- Weight = 2
- }
- New-Object PSObject -Property $Properties | Export-PowerViewCSV -OutFile "$OutputFolder\$($CSVExportPrefix)user_sessions.csv"
- }
- else {
- # assume Get-NetLoggedOn result
- try {
- $MemberSimpleName = "$($Object.UserDomain)\$($Object.UserName)" | Convert-ADName -InputType 'NT4' -OutputType 'Canonical'
- if($MemberSimpleName) {
- $MemberDomain = $MemberSimpleName.Split('/')[0]
- $AccountName = "$($Object.UserName)@$MemberDomain"
- }
- else {
- $AccountName = "$($Object.UserName)@UNKNOWN"
- }
-
- $Properties = @{
- UserName = $AccountName
- ComputerName = $Object.ComputerName
- Weight = 1
- }
- New-Object PSObject -Property $Properties | Export-PowerViewCSV -OutFile "$OutputFolder\$($CSVExportPrefix)user_sessions.csv"
- }
- catch {
- Write-Verbose "Error converting $($Object.UserDomain)\$($Object.UserName)"
- }
- }
- }
- elseif($Object.PSObject.TypeNames -contains 'PowerView.GroupMember') {
- $AccountName = "$($Object.MemberName)@$($Object.MemberDomain)"
+ # create a "powershell pipeline runner"
+ $PS += [PowerShell]::Create()
+ $PS[$Counter].RunspacePool = $Pool
- if ($Object.MemberName -Match "\\") {
- # if the membername itself contains a backslash, get the trailing section
- # TODO: later preserve this once BloodHound can properly display these characters
- $AccountName = $($Object.Membername).split('\')[1] + '@' + $($Object.MemberDomain)
- }
+ # add the script block + arguments
+ $Null = $PS[$Counter].AddScript($HostEnumBlock).AddParameter('ComputerName', $($ComputerResult.Properties['dnshostname']))
- $GroupName = "$($Object.GroupName)@$($Object.GroupDomain)"
+ ForEach ($Param in $ScriptParameters.GetEnumerator()) {
+ $Null = $PS[$Counter].AddParameter($Param.Name, $Param.Value)
+ }
- if($Object.IsGroup) {
- $Properties = @{
- AccountName = $AccountName
- AccountType = 'group'
- GroupName = $GroupName
- }
- New-Object PSObject -Property $Properties | Export-PowerViewCSV -OutFile "$OutputFolder\$($CSVExportPrefix)group_memberships.csv"
- }
- else {
- # check if -FullData objects are returned, and if so check if the group member is a computer object
- if($Object.ObjectClass -and ($Object.ObjectClass -contains 'computer')) {
- $Properties = @{
- AccountName = $Object.dnshostname
- AccountType = 'computer'
- GroupName = $GroupName
+ # start job
+ $Jobs += $PS[$Counter].BeginInvoke()
+ $Counter += 1
}
- New-Object PSObject -Property $Properties | Export-PowerViewCSV -OutFile "$OutputFolder\$($CSVExportPrefix)group_memberships.csv"
- }
- else {
- # otherwise there's no way to determine if this is a computer object or not
- $Properties = @{
- AccountName = $AccountName
- AccountType = 'user'
- GroupName = $GroupName
- }
- New-Object PSObject -Property $Properties | Export-PowerViewCSV -OutFile "$OutputFolder\$($CSVExportPrefix)group_memberships.csv"
+ $ComputerSearcher.Dispose()
+ [GC]::Collect()
}
}
}
- elseif($Object.PSObject.TypeNames -Contains 'PowerView.User') {
- $AccountDomain = $Object.Domain
- $AccountName = "$($Object.SamAccountName)@$AccountDomain"
-
- if($Object.PrimaryGroupName -and ($Object.PrimaryGroupName -ne '')) {
- $PrimaryGroupName = "$($Object.PrimaryGroupName)@$AccountDomain"
+ }
- $Properties = @{
- AccountName = $AccountName
- AccountType = 'user'
- GroupName = $PrimaryGroupName
- }
- New-Object PSObject -Property $Properties | Export-PowerViewCSV -OutFile "$OutputFolder\$($CSVExportPrefix)group_memberships.csv"
- }
- # TODO: extract pwdlastset/etc. and ingest
- }
- elseif(($Object.PSObject.TypeNames -contains 'PowerView.LocalUserAPI') -or ($Object.PSObject.TypeNames -contains 'PowerView.LocalUser')) {
- $AccountName = $($Object.AccountName.replace('/', '\')).split('\')[-1]
+ END {
- $MemberSimpleName = Convert-SidToName -SID $Object.SID | Convert-ADName -InputType 'NT4' -OutputType 'Canonical'
+ if ($TargetDomains -and (-not $SkipComputerEnumeration)) {
+ Write-Verbose "Waiting for Invoke-BloodHound threads to finish..."
+ Start-Sleep -Seconds 30
- if($MemberSimpleName) {
- $MemberDomain = $MemberSimpleName.Split('/')[0]
- }
- else {
- $MemberDomain = "UNKNOWN"
- }
+ for ($y = 0; $y -lt $Counter; $y++) {
+ if($Jobs[$y] -and ($Jobs[$y].IsCompleted)) {
+ try {
+ # complete async job
+ $PS[$y].EndInvoke($Jobs[$y]) | ForEach-Object {
+ if($_['Type'] -eq 'UserSession') {
+ if($_['SessionFromName']) {
+ try {
+ $SessionFromName = $_['SessionFromName']
+ $UserName = $_['UserName'].ToUpper()
+ $ComputerDomain = $_['SessionFromName'].SubString($_['SessionFromName'].IndexOf('.')+1).ToUpper()
+
+ if($UserDomainMappings) {
+ $UserDomain = $Null
+ if($UserDomainMappings[$UserName]) {
+ if($UserDomainMappings[$UserName].Count -eq 1) {
+ $UserDomain = $UserDomainMappings[$UserName]
+ $LoggedOnUser = "$UserName@$UserDomain"
+ if ($PSCmdlet.ParameterSetName -eq 'CSVExport') {
+ $SessionWriter.WriteLine("`"$SessionFromName`",`"$LoggedOnUser`",`"1`"")
+ }
+ else {
+ $Null = $Statements.Add( @{"statement"="MERGE (user:User { name: UPPER('$LoggedOnUser') }) MERGE (computer:Computer { name: UPPER('$SessionFromName') }) MERGE (computer)-[:HasSession {Weight: '1'}]->(user)" } )
+ }
+ }
+ else {
+ $ComputerDomain = $_['SessionFromName'].SubString($_['SessionFromName'].IndexOf('.')+1).ToUpper()
+
+ $UserDomainMappings[$UserName] | ForEach-Object {
+ # for multiple GC results, set a weight of 1 for the same domain as the target computer
+ if($_ -eq $ComputerDomain) {
+ $UserDomain = $_
+ $LoggedOnUser = "$UserName@$UserDomain"
+ if ($PSCmdlet.ParameterSetName -eq 'CSVExport') {
+ $SessionWriter.WriteLine("`"$SessionFromName`",`"$LoggedOnUser`",`"1`"")
+ }
+ else {
+ $Null = $Statements.Add( @{"statement"="MERGE (user:User { name: UPPER('$LoggedOnUser') }) MERGE (computer:Computer { name: UPPER('$SessionFromName') }) MERGE (computer)-[:HasSession {Weight: '1'}]->(user)" } )
+ }
+ }
+ # and set a weight of 2 for all other users in additional domains
+ else {
+ $UserDomain = $_
+ $LoggedOnUser = "$UserName@$UserDomain"
+ if ($PSCmdlet.ParameterSetName -eq 'CSVExport') {
+ $SessionWriter.WriteLine("`"$SessionFromName`",`"$LoggedOnUser`",`"2`"")
+ }
+ else {
+ $Null = $Statements.Add( @{"statement"="MERGE (user:User { name: UPPER('$LoggedOnUser') }) MERGE (computer:Computer { name: UPPER('$SessionFromName') }) MERGE (computer)-[:HasSession {Weight: '2'}]->(user)" } )
+ }
+ }
+ }
+ }
+ }
+ else {
+ # no user object in the GC with this username, so set the domain to "UNKNOWN"
+ $LoggedOnUser = "$UserName@UNKNOWN"
+ if ($PSCmdlet.ParameterSetName -eq 'CSVExport') {
+ $SessionWriter.WriteLine("`"$SessionFromName`",`"$LoggedOnUser`",`"2`"")
+ }
+ else {
+ $Null = $Statements.Add( @{"statement"="MERGE (user:User { name: UPPER('$LoggedOnUser') }) MERGE (computer:Computer { name: UPPER('$SessionFromName') }) MERGE (computer)-[:HasSession {Weight: '2'}]->(user)" } )
+ }
+ }
+ }
+ else {
+ # if not using GC mappings, set the weight to 2
+ $LoggedOnUser = "$UserName@$ComputerDomain"
+ if ($PSCmdlet.ParameterSetName -eq 'CSVExport') {
+ $SessionWriter.WriteLine("`"$SessionFromName`",`"$LoggedOnUser`",`"2`"")
+ }
+ else {
+ $Null = $Statements.Add( @{"statement"="MERGE (user:User { name: UPPER('$LoggedOnUser') }) MERGE (computer:Computer { name: UPPER('$SessionFromName') }) MERGE (computer)-[:HasSession {Weight: '2'}]->(user)"} )
+ }
+ }
+ }
+ catch {
+ Write-Warning "Error extracting domain from $SessionFromName"
+ }
+ }
+ elseif($_['SessionFrom']) {
+ $SessionFromName = $_['SessionFrom']
+ $LoggedOnUser = "$($_['UserName'])@UNKNOWN"
+ if ($PSCmdlet.ParameterSetName -eq 'CSVExport') {
+ $SessionWriter.WriteLine("`"$SessionFromName`",`"$LoggedOnUser`",`"2`"")
+ }
+ else {
+ $Null = $Statements.Add( @{"statement"="MERGE (user:User { name: UPPER(`"$LoggedOnUser`") }) MERGE (computer:Computer { name: UPPER(`"$SessionFromName`") }) MERGE (computer)-[:HasSession {Weight: '2'}]->(user)"} )
+ }
+ }
+ else {
+ # assume Get-NetLoggedOn result
+ $UserDomain = $_['UserDomain']
+ $UserName = $_['UserName']
+ try {
+ if($DomainShortnameMappings[$UserDomain]) {
+ # in case the short name mapping is 'cached'
+ $AccountName = "$UserName@$($DomainShortnameMappings[$UserDomain])"
+ }
+ else {
+ $MemberSimpleName = "$UserDomain\$UserName" | Convert-ADName -InputType 'NT4' -OutputType 'Canonical'
- $AccountName = "$AccountName@$MemberDomain"
+ if($MemberSimpleName) {
+ $MemberDomain = $MemberSimpleName.Split('/')[0]
+ $AccountName = "$UserName@$MemberDomain"
+ $DomainShortnameMappings[$UserDomain] = $MemberDomain
+ }
+ else {
+ $AccountName = "$UserName@UNKNOWN"
+ }
+ }
- if($Object.IsGroup) {
- $Properties = @{
- AccountName = $AccountName
- AccountType = 'group'
- ComputerName = $Object.ComputerName
- }
- New-Object PSObject -Property $Properties | Export-PowerViewCSV -OutFile "$OutputFolder\$($CSVExportPrefix)local_admins.csv"
- }
- else {
- $Properties = @{
- AccountName = $AccountName
- AccountType = 'user'
- ComputerName = $Object.ComputerName
- }
- New-Object PSObject -Property $Properties | Export-PowerViewCSV -OutFile "$OutputFolder\$($CSVExportPrefix)local_admins.csv"
- }
- }
- elseif($Object.PSObject.TypeNames -contains 'PowerView.LocalUserSpecified') {
- # manually specified localgroup membership where resolution happens by the callee
+ $SessionFromName = $_['ComputerName']
- $AccountName = "$($Object.MemberName)@$($Object.MemberDomain)"
+ if ($PSCmdlet.ParameterSetName -eq 'CSVExport') {
+ $SessionWriter.WriteLine("`"$SessionFromName`",`"$AccountName`",`"1`"")
+ }
+ else {
+ $Null = $Statements.Add( @{"statement"="MERGE (user:User { name: UPPER('$AccountName') }) MERGE (computer:Computer { name: UPPER('$SessionFromName') }) MERGE (computer)-[:HasSession {Weight: '1'}]->(user)" } )
+ }
+ }
+ catch {
+ Write-Verbose "Error converting $UserDomain\$UserName : $_"
+ }
+ }
+ }
+ elseif($_['Type'] -eq 'LocalUser') {
+ $Parts = $_['AccountName'].split('\')
+ $UserDomain = $Parts[0]
+ $UserName = $Parts[-1]
+
+ if($DomainShortnameMappings[$UserDomain]) {
+ # in case the short name mapping is 'cached'
+ $AccountName = "$UserName@$($DomainShortnameMappings[$UserDomain])"
+ }
+ else {
+ $MemberSimpleName = "$UserDomain\$UserName" | Convert-ADName -InputType 'NT4' -OutputType 'Canonical'
- if($Object.IsGroup) {
- $Properties = @{
- AccountName = $AccountName
- AccountType = 'group'
- ComputerName = $Object.ComputerName
- }
- New-Object PSObject -Property $Properties | Export-PowerViewCSV -OutFile "$OutputFolder\$($CSVExportPrefix)local_admins.csv"
- }
- else {
- $Properties = @{
- AccountName = $AccountName
- AccountType = 'user'
- ComputerName = $Object.ComputerName
- }
- New-Object PSObject -Property $Properties | Export-PowerViewCSV -OutFile "$OutputFolder\$($CSVExportPrefix)local_admins.csv"
- }
- }
- elseif($Object.PSObject.TypeNames -contains 'PowerView.GPOLocalGroup') {
- if(![string]::IsNullOrEmpty($Object.SID)){
- $MemberSimpleName = Convert-SidToName -SID $Object.ObjectSID | Convert-ADName -InputType 'NT4' -OutputType 'Canonical'
- }
+ if($MemberSimpleName) {
+ $MemberDomain = $MemberSimpleName.Split('/')[0]
+ $AccountName = "$UserName@$MemberDomain"
+ $DomainShortnameMappings[$UserDomain] = $MemberDomain
+ }
+ else {
+ $AccountName = "$UserName@UNKNOWN"
+ }
+ }
- if($MemberSimpleName) {
- $MemberDomain = $MemberSimpleName.Split('/')[0]
- $AccountName = "$($Object.ObjectName)@$MemberDomain"
- }
- else {
- $AccountName = $Object.ObjectName
- }
+ $ComputerName = $_['ComputerName']
+ if($_['IsGroup']) {
+ if ($PSCmdlet.ParameterSetName -eq 'CSVExport') {
+ $LocalAdminWriter.WriteLine("`"$ComputerName`",`"$AccountName`",`"group`"")
+ }
+ else {
+ $Null = $Statements.Add( @{ "statement"="MERGE (group:Group { name: UPPER('$AccountName') }) MERGE (computer:Computer { name: UPPER('$ComputerName') }) MERGE (group)-[:AdminTo]->(computer)" } )
+ }
+ }
+ else {
+ if ($PSCmdlet.ParameterSetName -eq 'CSVExport') {
+ $LocalAdminWriter.WriteLine("`"$ComputerName`",`"$AccountName`",`"user`"")
+ }
+ else {
+ $Null = $Statements.Add( @{"statement"="MERGE (user:User { name: UPPER('$AccountName') }) MERGE (computer:Computer { name: UPPER('$ComputerName') }) MERGE (user)-[:AdminTo]->(computer)" } )
+ }
+ }
+ }
- ForEach($Computer in $Object.ComputerName) {
- if($Object.IsGroup) {
- $Properties = @{
- AccountName = $AccountName
- AccountType = 'group'
- ComputerName = $Computer
+ if (($PSCmdlet.ParameterSetName -eq 'RESTAPI') -and ($Statements.Count -ge $Throttle)) {
+ $Json = @{ "statements"=[System.Collections.Hashtable[]]$Statements }
+ $JsonRequest = ConvertTo-Json20 $Json
+ $Null = $WebClient.UploadString($URI.AbsoluteUri + "db/data/transaction/commit", $JsonRequest)
+ $Statements.Clear()
+ }
+ }
}
- New-Object PSObject -Property $Properties | Export-PowerViewCSV -OutFile "$OutputFolder\$($CSVExportPrefix)local_admins.csv"
- }
- else {
- $Properties = @{
- AccountName = $AccountName
- AccountType = 'user'
- ComputerName = $Computer
+ catch {
+ Write-Verbose "Error ending Invoke-BloodHound thread $y : $_"
+ }
+ finally {
+ $PS[$y].Dispose()
+ $PS[$y] = $Null
+ $Jobs[$y] = $Null
}
- New-Object PSObject -Property $Properties | Export-PowerViewCSV -OutFile "$OutputFolder\$($CSVExportPrefix)local_admins.csv"
}
}
+ $Pool.Dispose()
+ Write-Verbose "All threads completed!"
}
- elseif($Object.PSObject.TypeNames -contains 'PowerView.DomainTrustLDAP') {
- # [uint32]'0x00000001' = 'non_transitive'
- # [uint32]'0x00000002' = 'uplevel_only'
- # [uint32]'0x00000004' = 'quarantined_domain'
- # [uint32]'0x00000008' = 'forest_transitive'
- # [uint32]'0x00000010' = 'cross_organization'
- # [uint32]'0x00000020' = 'within_forest'
- # [uint32]'0x00000040' = 'treat_as_external'
- # [uint32]'0x00000080' = 'trust_uses_rc4_encryption'
- # [uint32]'0x00000100' = 'trust_uses_aes_keys'
- # [uint32]'0x00000200' = 'cross_organization_no_tgt_delegation'
- # [uint32]'0x00000400' = 'pim_trust'
- if($Object.SourceDomain) {
- $SourceDomain = $Object.SourceDomain
- }
- else {
- $SourceDomain = $Object.SourceName
- }
- if($Object.TargetDomain) {
- $TargetDomain = $Object.TargetDomain
- }
- else {
- $TargetDomain = $Object.TargetName
- }
-
- $TrustType = Switch ($Object.TrustType) {
- 'cross_organization' { 'CrossLink' }
- 'within_forest' { 'ParentChild' }
- 'forest_transitive' { 'Forest' }
- 'treat_as_external' { 'External' }
- 'Default' { 'Unknown' }
- }
-
- if ($Object.TrustType -match 'non_transitive') {
- $Transitive = $False
- }
- else {
- $Transitive = $True
- }
-
- $Properties = @{
- SourceDomain = $SourceDomain
- TargetDomain = $TargetDomain
- TrustDirection = $Object.TrustDirection
- TrustType = $TrustType
- Transitive = "$Transitive"
- }
- New-Object PSObject -Property $Properties | Export-PowerViewCSV -OutFile "$OutputFolder\$($CSVExportPrefix)trusts.csv"
- }
- elseif($Object.PSObject.TypeNames -contains 'PowerView.DomainTrust') {
- if($Object.SourceDomain) {
- $SourceDomain = $Object.SourceDomain
- }
- else {
- $SourceDomain = $Object.SourceName
- }
- if($Object.TargetDomain) {
- $TargetDomain = $Object.TargetDomain
- }
- else {
- $TargetDomain = $Object.TargetName
- }
- $Properties = @{
- SourceDomain = $SourceDomain
- TargetDomain = $TargetDomain
- TrustDirection = $Object.TrustDirection
- TrustType = $Object.TrustType
- Transitive = "$True"
- }
- New-Object PSObject -Property $Properties | Export-PowerViewCSV -OutFile "$OutputFolder\$($CSVExportPrefix)trusts.csv"
- }
- elseif($Object.PSObject.TypeNames -contains 'PowerView.ForestTrust') {
- if($Object.SourceDomain) {
- $SourceDomain = $Object.SourceDomain
+ if ($PSCmdlet.ParameterSetName -eq 'CSVExport') {
+ if($SessionWriter) {
+ $SessionWriter.Dispose()
+ $SessionFileStream.Dispose()
}
- else {
- $SourceDomain = $Object.SourceName
+ if($GroupWriter) {
+ $GroupWriter.Dispose()
+ $GroupFileStream.Dispose()
}
- if($Object.TargetDomain) {
- $TargetDomain = $Object.TargetDomain
+
+ if($LocalAdminWriter) {
+ $LocalAdminWriter.Dispose()
+ $LocalAdminFileStream.Dispose()
}
- else {
- $TargetDomain = $Object.TargetName
+ if($TrustWriter) {
+ $TrustWriter.Dispose()
+ $TrustsFileStream.Dispose()
}
- $Properties = @{
- SourceDomain = $SourceDomain
- TargetDomain = $TargetDomain
- TrustDirection = $Object.TrustDirection
- TrustType = 'Forest'
- Transitive = "$True"
- }
- New-Object PSObject -Property $Properties | Export-PowerViewCSV -OutFile "$OutputFolder\$($CSVExportPrefix)trusts.csv"
+ Write-Output "Done writing output to CSVs in: $OutputFolder\$CSVExportPrefix"
}
else {
- Write-Verbose "No matching type name"
+ $Json = @{ "statements"=[System.Collections.Hashtable[]]$Statements }
+ $JsonRequest = ConvertTo-Json20 $Json
+ $Null = $WebClient.UploadString($URI.AbsoluteUri + "db/data/transaction/commit", $JsonRequest)
+ $Statements.Clear()
+ Write-Output "Done sending output to neo4j RESTful API interface at: $($URI.AbsoluteUri)"
}
+
+ [GC]::Collect()
}
}
@@ -14701,62 +5754,14 @@ $Mod = New-InMemoryModule -ModuleName Win32
# 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 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 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 advapi32 ConvertSidToStringSid ([Int]) @([IntPtr], [String].MakeByRefType()) -SetLastError)
)
-# 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 WTS_SESSION_INFO_1 @{
- 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 SHARE_INFO_1 @{
- shi1_netname = field 0 String -MarshalAs @('LPWStr')
- shi1_type = field 1 UInt32
- shi1_remark = field 2 String -MarshalAs @('LPWStr')
-}
# the NetWkstaUserEnum result structure
$WKSTA_USER_INFO_1 = struct $Mod WKSTA_USER_INFO_1 @{
@@ -14834,4 +5839,5 @@ $DS_DOMAIN_TRUSTS = struct $Mod DS_DOMAIN_TRUSTS @{
$Types = $FunctionDefinitions | Add-Win32Type -Module $Mod -Namespace 'Win32'
$Netapi32 = $Types['netapi32']
$Advapi32 = $Types['advapi32']
-$Wtsapi32 = $Types['wtsapi32']
+
+Set-Alias Get-BloodHoundData Invoke-BloodHound