AD Is Full of Bitmasks, Decipher Them with Powershell

Active Directory uses bit masks, or bit fields, or bit flags or bit maps or whatever you want to call them.  Anyway, AD uses them, a lot.  Let's say you're digging deep into Active Directory internals, and you want to see all the attributes in your schema that are eligible for being made confidential.  We know that something called "base schema attributes" cannot be made confidential, and that we know whether an attribute is a base schema attribute or not based on a certain bit that is set in that attribute's systemFlags attribute.

Get-ADObject -LDAPFilter "(objectClass=attributeSchema)" `
             -SearchBase (Get-ADRootDSE).schemaNamingContext `
             -Properties SystemFlags

That'll give us all attributes (regardless to what type of object they apply) in the schema, including their systemFlags property.  Problem is, the systemFlags property is all condensed into an integer, and unless you're Rain Man and have no problems doing it all in your head, you might like it if that systemFlags property were translated into something more meaningful.

This is where the magic of enumerations comes in.

Add-Type -TypeDefinition @'
    [System.Flags]
    public enum SystemFlagsAttr : uint
    {
        FLAG_ATTR_NOT_REPLICATED         = 0x00000001,
        FLAG_ATTR_REQ_PARTIAL_SET_MEMBER = 0x00000002,
        FLAG_ATTR_IS_CONSTRUCTED         = 0x00000004,
        FLAG_ATTR_IS_OPERATIONAL         = 0x00000008,
        FLAG_SCHEMA_BASE_OBJECT          = 0x00000010,
        FLAG_ATTR_IS_RDN                 = 0x00000020,
        FLAG_DISALLOW_MOVE_ON_DELETE     = 0x02000000,
        FLAG_DOMAIN_DISALLOW_MOVE        = 0x04000000,
        FLAG_DOMAIN_DISALLOW_RENAME      = 0x08000000,
        FLAG_CONFIG_ALLOW_LIMITED_MOVE   = 0x10000000,
        FLAG_CONFIG_ALLOW_MOVE           = 0x20000000,
        FLAG_CONFIG_ALLOW_RENAME         = 0x40000000,
        FLAG_DISALLOW_DELETE             = 0x80000000
    }
'@

So a smart reader already knows that since we are looking for "base schema objects," which in the enum above equates to 0x10 hex, which is 16 decimal which is the fourth bit... then it becomes pretty easy to spot all the attributes that have a SystemFlags of 16 in our above Powershell command. But what about when bit 4 and bit 2 and bit 26 are all turned on?  This is why enums are our friends.  Let's retry our Powershell command using the enum now:

Get-ADObject -LDAPFilter "(objectClass=attributeSchema)" `
             -SearchBase (Get-ADRootDSE).schemaNamingContext `
             -Properties SystemFlags | `
             Select Name, `
             @{n='SystemFlags'; e={[Enum]::Parse('SystemFlagsAttr', $_.SystemFlags)}}

Now it's a lot more readable:

...
ms-DS-Service-AuthN-Policy-BL    FLAG_ATTR_NOT_REPLICATED, FLAG_SCHEMA_BASE_OBJECT
ms-DS-Assigned-AuthN-Policy      FLAG_SCHEMA_BASE_OBJECT
ms-DS-Assigned-AuthN-Policy-BL   FLAG_ATTR_NOT_REPLICATED, FLAG_SCHEMA_BASE_OBJECT
...

Pretty easy to filter those results and see which attributes are base schema attributes, and which are not.

Lastly, as an exercise I will leave to the reader, you can also use the SearchFlags (not SystemFlags) property of an attribute to determine whether the attribute is already set as confidential or not.

    [System.Flags]
    public enum SearchFlags
    {
        fATTINDEX              = 0x0001,
        fPDNTATTINDEX          = 0x0002,
        fANR                   = 0x0004,
        fPRESERVEONDELETE      = 0x0008,
        fCOPY                  = 0x0010,
        fTUPLEINDEX            = 0x0020,
        fSUBTREEATTINDEX       = 0x0040,
        fCONFIDENTIAL          = 0x0080,
        fNEVERVALUEAUDIT       = 0x0100,
        fRODCFilteredAttribute = 0x0200,
        fEXTENDEDLINKTRACKING  = 0x0400,
        fBASEONLY              = 0x0800,
        fPARTITIONSECRET       = 0x1000
    }

Watch-PerfCounter

Just a little Powershell I wrote when I had nothing better to do.  It's kinda' reminiscent of procdump's ability to wait until a certain threshold is crossed before it takes action.  That's what this Cmdlet does.  It just watches a performance counter (any performance counter, also works against remote computers,) and when the specified threshold is crossed, it executes whatever file you specified in the "Action" parameter.  I wrote it because it helps solve performance-related cases where a repro is hard to catch.

#
<#
.SYNOPSIS
Watches a Windows performance counter, and executes the specified file
once the specified counter threshold is crossed.
.DESCRIPTION
Watches a Windows performance counter, and executes the specified file
once the specified counter threshold is crossed. You must know the 
name and path of the performance counter you're after. Use Get-Counter
if you want to explore the syntax of performance counter paths. You
may monitor performance counters on a remote machine as well. The
specified Action can be any executable file, an exe, a vbs, a bat file,
etc. You can also specify the PollFrequency, in seconds. Default is 5.
Use the Verbose switch for extra detail.
.PARAMETER CounterName
This is the full path of the performance counter that you want to monitor.
It may be from the local machine or from a remote machine.
.PARAMETER Threshold
When the performance counter crosses this threshold, the specified action will be triggered. 
It could be an absolute value or it could be a percentage, depending on the perf counter.
.PARAMETER Action
This can be any file that exists and is accessible - it will be executed when the performance
counter threshold is crossed.
.PARAMETER PollFrequency
The frequency, in seconds, that the performance counter will be polled. Default is 5 seconds.
.EXAMPLE
C:\PS> Watch-PerfCounter -CounterName '\processor(_total)\% processor time' -Threshold 90 -Action 'C:\MyFile.bat' -PollFrequency 10 -Verbose
Polls the processer time performance counter every 10 seconds, and executes MyFile.bat once the CPU is over 90%. Outputs Verbose information.
.EXAMPLE
C:\PS> Watch-PerfCounter -CounterName '\\host02\memory\% committed bytes in use' -Threshold 25 -Action 'C:\Compress.exe'
Polls the memory perf counter on remote computer HOST02, and executes a program once it crosses 25%.
.NOTES
Powershell written by Joseph Ryan Ries, but it was Justin Turner's idea.
#>
Function Watch-PerfCounter
{
    #Version: 01.00 - September 17 2015 - Initial release.
    #Version: 01.01 - September 17 2015 - Added the word "seconds" to the sentence "Polling ever x seconds."
    [CmdletBinding()]
    Param([Parameter(Mandatory=$True)]
            [String]$CounterName,
          [Parameter(Mandatory=$True)]
            [Decimal]$Threshold,
          [Parameter(Mandatory=$True)]
          [ValidateScript({Test-Path $_ -PathType Leaf})]
            [String]$Action,
          [Parameter(Mandatory=$False)]
          [ValidateRange(1,360)]
            [Int]$PollFrequency = 5)
    BEGIN
    {
        Set-StrictMode -Version Latest
        [Diagnostics.Stopwatch]$Stopwatch = [Diagnostics.Stopwatch]::StartNew()

        Write-Verbose "$($PSCmdlet.CommandRuntime) beginning on $(Get-Date)."

        Try
        {
            Get-Counter $CounterName -ErrorAction Stop | Out-Null
            Write-Verbose "Performance counter $CounterName was found."
        }
        Catch
        {
            Write-Error "Perfermance counter was not found or could not be read! `n`n $($_.Exception.Message)"
            Return
        }

        Write-Verbose "Polling every $PollFrequency seconds."
        Write-Verbose "Use Ctrl+C to abort."


        [Bool]$ThresholdCrossed = $False

        While (-Not($ThresholdCrossed))
        {
            Try
            {
                $CookedValue = (Get-Counter $CounterName -ErrorAction Stop).CounterSamples.CookedValue

                Write-Verbose "$CounterName = $CookedValue"

                If ($CookedValue -GT $Threshold)
                {
                    Write-Verbose "Performance counter $CounterName has crossed the threshold of $Threshold!"
                    $ThresholdCrossed = $True
                }
            }
            Catch
            {
                Write-Error "Error reading performance counter. This error may be transient. `n`n $($_.Exception.Message)"
            }

            If (-Not($ThresholdCrossed))
            {
                Start-Sleep -Seconds $PollFrequency
            }
        }

        Invoke-Expression $Action
    }
    PROCESS
    {

    }
    END
    {
        # Reminder: This code block still executes even if we return prematurely from the BEGIN block.
        $Stopwatch.Stop()
        Write-Verbose "$($PSCmdlet.CommandRuntime) completed in $([Math]::Round($Stopwatch.Elapsed.TotalSeconds, 2)) seconds."
    }
}

PSNTDS

NTDS.dit (Microsoft Active Directory) forensics module for Powershell.

Find it on Github here.

Current bug: Unencrypted NT hashes are incorrect!

This Powershell module is intended to aid in the process of examining and extracting information from the Active Directory database known as NTDS.dit.

Special thanks to Csaba Barta (csaba.barta@gmail.com, www.ntdsxtract.com) without whom this module would not be possible.

Also thanks to @moyix, moyix.blogspot.com, brendan@cs.columbia.edu

Also thanks to Brendan Dolan-Gavitt, author of creddump

Q: So why did I reinvent the wheel?

A: Because:

  1. Python shouldn't have all the fun. I wanted to port the ideas into idiomatic Powershell.
  2. I wanted to gain in-depth knowledge of the subject for myself, not just run someone else's scripts.
  3. I have further plans with this data and it will be very handy to have it in Powershell format.

Currently exported Cmdlets:

Export-NTDSFromNTDSUtilSnapshot
Export-SystemRegistryHive
Get-BootKeyFromSystemRegistryHive
Get-DecryptedHash
Get-DecryptedPEK
Get-RC4EncryptedData
Import-NTDSDatabaseFromFile

A couple of screenshots:

alt tag

alt tag

alt tag



Send-OutlookMailMessage with Voting Options

I needed a way to programmatically send emails through Outlook that contained "voting options" ... the little buttons that let the recipients vote yes or no or whatever on a given topic.  So I whipped up a reusable cmdlet right quick.

#
Function Send-OutlookMailMessage
{
    [CmdletBinding()]
    Param([Parameter(Mandatory=$True)][String[]]$To,
          [Parameter(Mandatory=$False)][String]$Subject,
          [Parameter(Mandatory=$False)][String]$Body,
          [Parameter(Mandatory=$False)][String[]]$Attachments,
          [Parameter(Mandatory=$False)][String[]]$VotingOptions,
          [Parameter(Mandatory=$False)][ValidateSet('High','Low','Normal')][String]$Priority = 'Normal',
          [Parameter(Mandatory=$False)][Switch]$BodyAsHTML = $False)

    BEGIN
    {
        Set-StrictMode -Version Latest
        [Bool]$ShouldProcess = $True
        [Bool]$OutlookWasAlreadyRunning = $True

        Try
        {
            Get-Process Outlook -ErrorAction Stop | Out-Null
        }
        Catch
        {
            $OutlookWasAlreadyRunning = $False
        }

        Try
        {
            $Outlook = New-Object -ComObject Outlook.Application -ErrorAction Stop
            If ($Outlook -EQ $Null)
            {
                Throw 'COM Object was null.'
            }
            Write-Verbose -Message 'Outlook COM object loaded.'
        }
        Catch
        {
            Write-Error "Failed to load Outlook! ($($_.Exception.Message))"        
            $ShouldProcess = $False
        }
    }
    PROCESS
    {
        If ($ShouldProcess)
        {
            Try
            {
                $Mail = $Outlook.CreateItem(0)
                If ($Mail -EQ $Null)
                {
                    Throw
                }
                Write-Verbose -Message 'Mail item created.'
            }
            Catch
            {
                Write-Error "Failed to create mail item! ($($_.Exception.Message))"
                $ShouldProcess = $False
                Return
            }

            Foreach ($Recipient In $To)
            {
                Try
                {
                    $Mail.Recipients.Add($Recipient) | Out-Null
                }
                Catch
                {
                    Write-Error "Failed to add recipient $Recipient"
                    $ShouldProcess = $False
                    Return
                }
                Write-Verbose -Message "Added recipient $Recipient"
            }

            If ($Subject.Length -GT 0)
            {
                $Mail.Subject = $Subject
            }
            Else
            {
                Write-Warning 'No email subject was supplied.'
            }
            If ($Body.Length -GT 0)
            {
                If ($BodyAsHTML)
                {
                    $Mail.HTMLBody = $Body
                }
                Else
                {
                    $Mail.Body = $Body
                }
            }
            Else
            {
                Write-Warning 'No email body was supplied.'
            }

            Foreach ($File In $Attachments)
            {
                Try
                {
                    # TODO: Suppress warning dialog box when attempting to send an 'unsafe' attachment?
                    $Mail.Attachments.Add($File) | Out-Null
                }
                Catch
                {
                    Write-Error "Error adding $File as an attachment!"
                    $ShouldProcess = $False
                    Return
                }
            }

            If ($VotingOptions.Count -GT 0)
            {
                $Mail.VotingOptions = $VotingOptions -Join ';'
            }

            Switch ($Priority)
            {
                'High'
                { 
                    $Mail.Importance = 2
                }
                'Low' 
                { 
                    $Mail.Importance = 0
                }
                'Normal' 
                { 
                    $Mail.Importance = 1
                }
                Default 
                { 
                    Write-Error "Debug priority!"
                    $ShouldProcess = $False
                    Return
                }
            }

            $Mail.Send()
        }
    }
    END
    {
        If ($Outlook)
        {
            If (-Not($OutlookWasAlreadyRunning))
            {
                $Outlook.Quit()
            }
            [System.Runtime.Interopservices.Marshal]::ReleaseComObject($Outlook) | Out-Null
        }
    }
}

Send-OutlookMailMessage -To 'ryan@myotherpcisacloud.com' -Subject 'Can you create an Outlook poll via Powershell?' -Body "

Well, can you?

" -VotingOptions 'Yes','No','IDK' -BodyAsHTML

Poking Around DNS Scavenging Settings with Powershell

I've been working toward getting DNS scavenging turned on in a domain.  DNS scavenging, as you may know, takes a good deal of patience and forethought.  It's not something you want to just blindly enable without doing any reconnaissance first.  First off, since I'm new to this environment, let me scan all the domain controllers (which are also the DNS servers in this case) and see what the scavenging and aging settings currently look like:
 
#
$Servers = @()
ForEach ($DC In Get-ADDomainController -Filter *)
{
  $Server = New-Object PSObject -Property @{ 
     Hostname   = $DC.HostName
     Scavenging = $((Get-DnsServerScavenging `
                        -ComputerName $DC.HostName).ScavengingState)
     Aging      = $((Get-DnsServerZoneAging `
                        -Name 'acme.com' `
                        -ComputerName $DC.HostName).AgingEnabled)
    }
  $Servers += $Server
}

$Servers | FT -AutoSize
#
Hostname Scavenging Aging
-------- ---------- -----
DC01     False      True
DC02     False      True
DC03     False      True
DC04     False      True
DC05     False      True

So record aging is already turned on for the zone.  All that's left to do is enable scavenging on one of the DNS servers. (I don't like having all of the domain controllers scavenging - just one.)  But before I do that, I want to wait a while (like, a couple weeks) and see what hosts are updating their DNS records and which ones aren't. Lucky us - DNS server has a WMI provider.

#
$Records = Get-WmiObject 
              -Namespace 'Root\MicrosoftDNS' 
              -Query 'SELECT * FROM MicrosoftDNS_ResourceRecord WHERE Timestamp != 0'

$Records | Select TextRepresentation, `
           @{n='Timestamp'; e={([DateTime]'1/1/1601').AddHours($_.Timestamp)}} `
         | Where Timestamp -LT (Get-Date).AddDays(-30) | FT -AutoSize

The only tough bit is that the record's timestamp comes as a 32-bit integer that represents the number of hours elapsed since January 1st, 1601.  So you'd want to convert that into a meaningful date.  Now we can see which resource records in DNS aren't refreshing themselves on a regular basis.  After checking that list for sanity and correcting any problems, we can turn on scavenging.