The Most Dangerous User Right You (Probably) Have Never Heard Of

I find Windows user rights pretty interesting. Separate from machine/domain object DACLs, user rights govern things like “by what method can specific users log into a particular system” and are managed under User Rights Assignment in Group Policy. Sidenote: I recently integrated privilege enumeration into PowerUp in the Get-ProcessTokenPrivilege function, with -Special returning ‘privileged’ privileges.


One user right I overlooked, until Ben Campbell’s post on constrained delegation, was SeEnableDelegationPrivilege. This right governs whether a user account can “Enable computer and user accounts to be trusted for delegation.” Part of the reason I overlooked it is stated right in the documentation: “There is no reason to assign this user right to anyone on member servers and workstations that belong to a domain because it has no meaning in those contexts; it is only relevant on domain controllers and stand-alone computers.” So this right applies to the domain, not the local domain-joined machine.

Ben explained how SeEnableDelegationPrivilege factors into constrained delegation. This was a missing piece of the whole puzzle for me. We both first thought that this right only governed the modification of the TRUSTED_FOR_DELEGATION and TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION flags- this would have opened up a nifty attack that Ben outlined. Unfortunately for us attackers, it appears that this right also controls the modification of the msDS-AllowedToDelegateTo property, which contains the targets for constrained delegation. If this is unclear, check out the post from last week for more background on constrained delegation.

TL;DR we can’t modify delegation specific user account control settings NOR the msDS-AllowedToDelegateTo field for targets (even if we have full control of the object) if we don’t have the SeEnableDelegationPrivilege right:

Now the question is: how can we determine which users have this right in the domain? Since SeEnableDelegationPrivilege is applicable only on a domain controller itself, we need to check if any group policy object applied to a domain controller modifies the user right assignments for that given DC. In most cases, this will be the “Default Domain Controllers Policy” (GUID = {6AC1786C-016F-11D2-945F-00C04FB984F9}). This is exactly what the Get-DomainPolicy -Source DC PowerView function will do:

So by default only members of BUILTIN\Administrators (i.e. Domain Admins/Enterprise Admins/etc.) have the right to modify these delegation settings. But what happens if we can edit this GPO, or any other GPO applied to the domain controller?

Why Care

There are a million ways to backdoor Active Directory given sufficient rights (make that a million and one : ). Sean Metcalf calls these “Sneaky Active Directory Persistence Tricks“. Some of these involve ACL backdoors, something I’ve covered some in the past. Other approaches might require maliciously editing GPOs. Still others could involve editing user objects. The SeEnableDelegationPrivilege approach is a bit of everything above.

TL;DR: if we control an object that has SeEnableDelegationPrivilege in the domain, AND said object has GenericAll/GenericWrite rights over any other user object in the domain, we can compromise the domain at will, indefinitely.

Given elevated domain rights OR edit rights to the default domain controller GPO (something @_wald0, @cptjesus, and I are currently working on for BloodHound) for just a few minutes, you can make a single modification to the given GPO to implement this backdoor. This GPO is located at \\DOMAIN\sysvol\testlab.local\Policies\{6AC1786C-016F-11D2-945F-00C04fB984F9}\MACHINE\Microsoft\Windows NT\SecEdit\GptTmpl.inf. By adding any user SID or username to the SeEnableDelegationPrivilege line of the [Privilege Rights] section, the setting will take hold whenever the user/machine’s current DC reboots or refreshes its group policy:

If eviluser has full rights over ANY user in the domain, we can modify that user’s msDS-AllowedToDelegateTo value to be whatever target service we want to compromise. We can also modify the TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION UAC flag if needed. In this case, let’s use ldap/DOMAIN_CONTROLLER to facilitate DCSyncing at will:

If eviluser has GenericAll over any target victim, then we don’t even have to know the victim user’s password. We can execute a force password reset using Set-DomainUserPassword to a known value and then execute the asktgt.exe/s4u.exe attack flow.

Obviously, from the defensive side, take note of what users have the SeEnableDelegationPrivilege privilege on your domain controllers, through PowerView or other means. This right effectively gives those users complete control of the domain, making a great ‘subtle’, but easy to detect (if you know what you’re looking for) AD backdoor. There are obviously ways you could subvert this given SYSTEM access on a domain controller, and I will detail methods to detect specific DACL modification in the coming weeks, but auditing these applied GPOs is a great start.


Several weeks ago my workmate Lee Christensen (who helped develop this post and material) and I spent some time diving into Active Directory’s S4U2Self and S4U2Proxy protocol extensions. Then, just recently, Benjamin Delpy and Ben Campbell had an interesting public conversation about the same topic on Twitter. This culminated with Benjamin releasing a modification to Kekeo that allows for easy abuse of S4U misconfigurations. As I was writing this, Ben also published an excellent post on this very topic, which everyone should read before continuing. No, seriously, go read Ben’s post first.

Lee and I wanted to write out our understanding of the technology and how you can go about abusing any misconfigurations while on engagements. Some of this will overlap with Ben’s post, but we have incorporated a few different aspects that we think add at least a bit of value. Ben also covers the Linux exploitation aspect, which we won’t touch on in this post.

At the heart of this matter is the delegation of privileges – allowing one user to pretend to be another in Active Directory. This delegation (currently) comes in two flavors: unconstrained and constrained delegation. If you don’t care about the technical details, skip to the Abusing S4U section.

Unconstrained Delegation

Say you have a server (or service account) that needs to impersonate another user for some reason. One common scenario is when a user authenticates to a web server, using Kerberos or other protocols, and the server wants to nicely integrate with a SQL backend. Active Directory grants two general ways to go about this: constrained and unconstrained delegation.

Unconstrained delegation used to be the only option available in Windows 2000, and the functionality has been kept (presumably for backwards compatibility reasons). We’ll only briefly cover this delegation type as Sean Metcalf has a great post that covers it in depth. In that article Sean states, “When Kerberos Unconstrained Delegation is enabled on the server hosting the service specified in the Service Principal Name referenced in the TGS-REQ (step 3), the Domain Controller the DC places a copy of the user’s TGT into the service ticket. When the user’s service ticket (TGS) is provided to the server for service access, the server opens the TGS and places the user’s TGT into LSASS for later use. The Application Server can now impersonate that user without limitation!“.

Here’s a graphical overview of the protocol from Microsoft:

Tl;dr: The TGT will be stuffed into memory where an attacker can extract and reuse it if:

  1. You are able to compromise a server that has unconstrained delegation set.
  2. You are able to trick a domain user that doesn’t have ‘Account is sensitive and cannot be delegated’ enabled (see Protections below) to connect to any service on the machine. This includes clicking on \\SERVER\Share.

This allows an attacker to impersonate that user to any service/machine on the domain! Obviously bad mmmkay. To contrast, if unconstrained delegation isn’t enabled, just a normal service ticket without a TGT stuffed inside it would be submitted, so the attacker would get no additional lateral spread advantage.

How can you tell which machines have unconstrained delegation set? This is actually pretty easy: search for any machine that has a userAccountControl attribute containing ADS_UF_TRUSTED_FOR_DELEGATION. You can do this with an LDAP filter of ‘(userAccountControl:1.2.840.113556.1.4.803:=524288)’, which is what PowerView’s Get-DomainComputer function does when passed the -Unconstrained flag:

Constrained Delegation

Obviously unconstrained delegation can be quite dangerous in the hands of a careless admin. Microsoft realized this early on and released ‘constrained’ delegation with Windows 2003. This included a set of Kerberos protocol extensions called S4U2Self and S4U2Proxy. These extensions also enable something called protocol transition, which we’ll go over in a bit.

In essence, constrained delegation is a way to limit exactly what services a particular machine/account can access while impersonating other users. Here’s how a service account configured with constrained delegation looks in the Active Directory GUI:

The ‘service’ specified is a service principal name that the account is allowed to access while impersonating other users. This is HOST/PRIMARY.testlab.local in our above example. Before we get into the specifics of how this works, here’s how that target object looks in PowerView:

The field of interest is msds-allowedtodelegateto, but there’s also a modification to the account’s userAccountControl property. Essentially, if a computer/user object has a userAccountControl value containing TRUSTED_TO_AUTH_FOR_DELEGATION then anyone who compromises that account can impersonate any user to the SPNs set in msds-allowedtodelegateto. Ben mentions SeEnableDelegationPrivilege being required to actually modify these parameters, which I’ll go over in more depth next week.

But first, a bit more on how Active Directory implements this whole process. Feel free to skip ahead to the Abusing S4U section if you’re not interested.

S4U2Self, S4U2Proxy, and Protocol Transition

So you have a web service account that needs to impersonate users to only a specific backend service, but you don’t want to allow unconstrained delegation to run wild. Microsoft’s solution to how to architect this is through the service-for-user (S4U) set of Kerberos extensions. There’s extensive documentation on this topic; Lee and I were partial to the Microsoft’s “Kerberos Protocol Extensions: Service for User and Constrained Delegation Protocol” ([MS-SFU]). What follows is our current understanding. If we’ve messed something up, please let us know!

The first extension that implements constrained delegation is the S4U2self extension, which allows a service to request a special forwardable service ticket to itself on behalf of a particular user. This is meant for use in cases where a user authenticates to a service in a way not using Kerberos, i.e. in our web service case. During the first KRB_TGS_REQ to the KDC, the forwardable flag it set, which requests that the TGS returned be marked as forwardable and thus able to be used with the S4U2proxy extension. In unconstrained delegation, a TGT is used to identify the user, but in this case the S4U extension uses the PA-FOR-USER structure as a new type in the “padata”/pre-authentication data field.

Note that the S4U2self process can be executed for any user, and that target user’s password is not required. Also, the S4U2self process is only allowed if the requesting user has the TRUSTED_TO_AUTH_FOR_DELEGATION field set in their userAccountControl.

Now, Lee and I first thought that this may be a way to Kerberoast any user we wanted, but unfortunately for us attackers this isn’t the case. The PAC is signed for the source (not the target) user, in this case the requesting service account, so universal Kerberoasting is out of the picture. But we now have a special service ticket that’s forwardable to the target service configured for constrained delegation in this case.

The second extension is S4U2proxy, which allows the caller, the service account in our case, to use this forwardable ticket to request a service ticket to any SPN specified in msds-allowedtodelegateto, impersonating the user specified in the S4U2self step. The KDC checks if the requested service is listed in the msds-allowedtodelegateto field of the requesting user, and issues the ticket if this check passes. In this way the delegation is ‘constrained’ to specific target services.

Here’s Microsoft’s diagram of S4U2self and S4U2proxy:

This set of extensions allows for what Microsoft calls protocol transition, which starts with the first Kerberos exchange during the S4u2Self component. This means that a service can authenticate a user over a non-Kerberos protocol and ‘transition’ the authentication to Kerberos, allowing for easy interoperability with existing environments.

Abusing S4U

If you’re asking yourself “so what” or skipped ahead to this section, we can think of a few ways that the S4U extensions can come into play on a pentest.

The first is to enumerate all computers and users with a non-null msds-allowedtodelegateto field set. This can be done easily with PowerView’s -TrustedToAuth flag for Get-DomainUser/Get-DomainComputer:

Now, remember that a machine or user account with a SPN set under msds-allowedtodelegateto can pretend to be any user they want to the target service SPN. So if you’re able to compromise one of these accounts, you can spoof elevated access to the target SPN. For the HOST SPN this allows complete remote takeover. For a MSSQLSvc SPN this would allow DBA rights. A CIFS SPN would allow complete remote file access. A HTTP SPN it would likely allow for the takeover of the remote webservice, and LDAP allows for DCSync ; ) HTTP/SQL service accounts, even if they aren’t elevated admin on the target, can also possibly be abused with Rotten Potato to elevate rights to SYSTEM (though I haven’t tested this personally).

Luckily for us, Benjamin recently released a modification to Kekeo to help facilitate these types of lateral spread attacks if we know the plaintext password of the specific accounts. Lee and I envision four different specific scenarios involving S4U that you may want to abuse. We have tested two of the scenarios in a lab reliably, but haven’t been able to get the other two working (notes below). [edit]: @gentilkiwi reached out and let Lee and I know that asktgt.exe accepts a /key:NTLM argument as well as a password. This allows us to execute scenarios 3 and 4 below using account hashes instead of plaintexts!

Scenario 1 : User Account Configured For Constrained Delegation + A Known Plaintext

This is the scenario that Benjamin showed in his tweet. If you are able to compromise the plaintext password for a user account that has constrained delegation enabled, you can use Kekeo to request a TGT, execute the S4U TGS request, and then ultimately access the target service.

Enumerating users with msds-allowedtodelegateto

Requesting a TGT for the user account with constrained delegation enabled

Using s4u.exe to execute S4U2Proxy

Injecting the S4U ticket to utilize access

Again, if you would like to execute this attack from a Linux system, read Ben’s post.

Scenario 2 : Agent on a Computer Configured For Constrained Delegation

If you are able to compromise a computer account that is configured for constrained delegation (instead of a user account) the attack approach is a bit different. As any process running as SYSTEM takes on the privileges of the local machine account, we can skip the Kekeo asktgt.exe step. You can also use an alternative method to execute the S4U2Proxy process, helpfully provided by Microsoft. Lee and I translated the process from C# into PowerShell as follows:

As detailed by Microsoft, when using WindowsIdentity, an “identify-level” token is returned by default for most situations. This allows you to see what groups are associated with the user token, but doesn’t allow you to reuse the access. In order to use the impersonation context to access additional network resources, an impersonation-level token is needed, which is only returned when the requesting account has the “Act as part of the operating system” user right (SeTcbPrivilege). This right is only granted to SYSTEM by default, but since we need to be SYSTEM already to use the privileges of the machine account on the network, we don’t need to worry.

Also, due to some of the powershell.exe peculiarities I mentioned a bit ago, if you are using PowerShell Version 2, you need to launch powershell.exe in single-thread apartment mode (with the “-sta” flag) in order for the token impersonation to work properly:

SYSTEM on a computer with msds-allowedtodelegateto set

S4U2Proxy for a computer account

Scenario 3 : User Account Configured For Constrained Delegation + A Known NTLM Hash

Our next goal was to execute this transition attack from a Window system only given the the target user’s NTLM hash, which we were unfortunately not able to get working properly with the same method as scenario 2. Our gut feeling is that we’re missing some silly detail, but we wanted to detail what we tried and what went wrong in case anyone had a tip for getting it working properly. [Edit] Ben’s pointed out that /key:NTLM works for asktgt.exe as well, which is covered below.

We attempted to use Mimikatz’ PTH command to inject the user’s hash into memory (assuming you are a local admin on the pivot system) instead of Kekro’s asktgt.exe. One issue here (as in scenario 2) is SeTcbPrivilege, but despite explicitly granting our principal user that right we still ran into issues. It appears that the the S4U2Self step worked correctly:

Despite the necessary privileges/rights, it appeared that the S4U2Proxy process fell back to NTLM instead of Kerberos with some NULL auths instead of the proper process:

[Edit] You can execute this scenario with asktgt.exe/s4u.exe nearly identically to scenario 1. Simply substitute /key:NTLM instead of /password:PLAINTEXT:

Scenario 4 : Computer Account Configured For Constrained Delegation + A Known NTLM Hash

If you compromise a computer account hash through some means, and want to execute the attack from another domain machine, we imagined that you would execute an attack flow nearly identical to scenario 3. Unfortunately, we ran into the same problems. Again, if anyone can give us a tip on what we’re doing wrong, we would be greatly appreciative :) [Edit] This can be executed with /user:MACHINE$ and /key:NTLM for asktgt.exe, identical to scenario 3:


Microsoft has a great protection already built into Active Directory that can help mitigate delegation abuse. If an account has “Account is sensitive and cannot be delegated” enabled, then “the security context of the user will not be delegated to a service even if the service account is set as trusted for Kerberos delegation“. You can easily check if an account has this set by again examining the userAccountControl attribute, checking for the NOT_DELEGATED value. PowerView allows you to easily search for accounts with this value set or not set (Get-DomainUser -AllowDelegation/-DisallowDelegation) and you can use the ConvertFrom-UACValue function to examine the values set for a particular account, as shown in previous examples.

Next week I will have a post that overlaps a bit with this topic, and presents additional defensive ideas concerning the rights needed to modify these delegation components for user objects.

Make PowerView Great Again

Yesterday’s commit to the PowerSploit dev branch is the biggest set of changes to PowerView since its inception. I’ve spent the last month or so rewriting PowerView from the ground up, squashing a number of bugs, adding a chunk of features, and standardizing the code base’s behavior. The commit message summarizes the modifications, but I wanted to spend some time detailing the massive set of changes. The previous PowerSploit Dev branch was merged into Master, and we will do a tagged release at some point next week.

Note: this new PowerView code is definitely beta, but should be usable. I guarantee there are new bugs that snuck in that I wasn’t able to catch, so let us know when you find them!

New Function Naming Scheme

PowerView functions now follow a brand new Verb-PrefixNoun naming scheme, partially to somewhat mirror the ‘real’ Active Directory cmdlets, and partially to expose more contextual information to the operator. I’m sure that this will irritate some people, but after some usage I believe the new naming system will make more sense to frequent users. There’s also a large set of aliases that map the old function names to the new, which should ease the transition. I spent serious time considering the new function names, trying to select what makes the most sense (there’s a method to the madness!) but I fully admit that my choices might not be perfect. If anyone makes a reasonably argued case, I’m open to changing specific function names.

These are now the verb selections in PowerView, with the general explanation for each:

  • Get-* : retrieve full raw data sets, such as Get-DomainUser (old Get-NetUser)
  • Find-* : finds specific data entries in a data set or execute threaded computer enumeration, e.g. Find-DomainShare (old Invoke-ShareHunter)
  • Add-* : adds a new object to a collection/destination, e.g. Add-DomainGroupMember which adds a domain user/group to an existing domain group
  • New-* : creates a new object/resource, e.g. New-DomainUser which creates a new domain user
  • Set-* : modifies an object, e.g. Set-DomainObject which sets domain object properties
  • Convert-* : converts object names/types, e.g. Convert-ADName which converts username formats

The Verb-PrefixNoun should now give an indication of the data source being queried. The idea with the new prefixes is to give operators an idea of what type of traffic they’re generating when executing enumeration, solely by function names:

  • Verb-Domain* : indicates that LDAP/.NET querying methods are being executed, e.g. Get-DomainOU which queries for domain organizational units through LDAP
  • Verb-WMI* : indicates that WMI is being used under the hood to execute enumeration, e.g. Get-WMIRegLastLoggedOn which enumerates the last logged on user for a host through WMI
  • Verb-Net* : indicates that Win32 API access is being used under the hood, e.g. Get-NetSession which utilizes the NetSessionEnum() Win32 API call under the hood

Nouns have been renamed to much more descriptive. I tried to think through “what object is this function returning” and naming accordingly. This resulted in one “gotcha” renaming situation that I couldn’t fix with aliases. Get-NetLocalGroup is a commonly used function that previously returned the members of a specified local group, not the groups themselves. Now, Get-NetLocalGroup will return local groups themselves, while Get-NetLocalGroupMember (old Get-NetLocalGroup) will return the members of a local group. This should be the only big gotcha, but hopefully will make more sense going forward.

Parameter/Output Standardization

Another big change is the new standardization of parameter sets across functions, particularly in the Verb-Domain* LDAP functions. This was also done to better match the behavior of the official AD cmdlets. Here are a few of the new parameters, with an explanation as well as any old aliases:

  • -Identity : replaces -UserName/-ComputerName/etc., more on this below
  • -LDAPFilter : old -Filter parameter, allows for the addition of custom LDAP filtering for any function (i.e. Get-DomainUser -LDAPFilter ‘(description=*pass*)’ )
  • -Properties : only return specific properties, more on this below
  • -SearchBase : old -ADSPath, specifies the LDAP source to search through (i.e. Get-DomainUser -SearchBase “LDAP://OU=secret,DC=testlab,DC=local” for OU queries)
  • -Server : specifies the Active Directory server to bind to, replaces the old -DomainController parameter
  • -SearchScope : new, specifies the scope to search under (Base/OneLevel/Subtree)
  • -ServerTimeLimit : new, specifies the maximum amount of time the server spends searching, useful for tuning in specific situations
  • -SecurityMasks : specifies an option for examining security information of a directory object, Dacl/Group/Owner/Sacl. Allows you to retrieve security information easily for any returned object.
  • -FindOne : returns only one result instead of all results. Useful for determining object schemas.
  • -Tombstone : specifies that the searcher should also return deleted/tombstoned objects.
  • -Credential : credential support, more on this later in the post

Also, all functions now return full objects on the pipeline. Previously (due to how PowerView evolved) some functions returned full objects (like Get-NetUser) while some returned partial data with a -FullData option for complete objects (like Get-NetComputer). This started to bug me more and more, so everything should now be standardized. If you want to retrieve specific properties, you can now do something like -Properties samaccountname,lastlogon, using -FindOne to first determine the property names you want to extract.

The great thing about this approach, as opposed to using a | Select-Object pipe at the end, is that this optimizes “towards the left”, meaning the server only returns the data fields you requested. This greatly cuts down the amount of traffic between your client and the target domain controller.


Most LDAP/Get-Domain* functions now have an -Identity X property instead of -ComputerName/-UserName/etc. -Identity accepts not just a samAccountName, but also distinguishedName, GUID, object SID, and dnsHostName in the computer case. These can also be interspersed together, meaning you can do something like this:

This makes functions more flexible, and again mimicking the behavior of the official AD cmdlets.


One of the biggest changes implemented is the support for -Credential for all appropriate functions. The exact behavior differs under the hood depending on function implementation (another motivator for the function renaming). I wanted to explain exactly what’s happening under the hood for each function type that accepts -Credential so you can under the traffic/events you’re producing per function executed.

First up, the Verb-Domain* functions. The functions pass through any -Credential parameter to the Get-DomainSearcher function which abstracts the alternate credential logic away. You can check out the implementation here. Basically, the code binds to the specified domain/searchBase by creating a new DirectoryServices.DirectoryEntry object with alternate credentials, which is then passed to DirectoryServices.DirectorySearcher in order to perform LDAP/AD searches.

Convert-ADName is a weird one, as it utilizes the NameTranslate COM object in the backend. Luckily Pasquale Lantella has a great Script Center code example based on Bill Stewart’s code/article that accepts an alternate credential. By invoking the ‘InitEx’ method on the COM object we can initialize everything correctly.

Verb-WMI* functions use WMI methods on the backend for remote enumeration. Specifically, Get-WmiObject is used, often with the StdRegProv class, in order to gather specific information from remote systems (for example Get-WMIRegLastLoggedOn to return the last user who logged onto a remote machine machine). Because built-in WMI methods are used, we can easily pass a -Credential object through without any issue.

Verb-Net* functions are the most painful. Many of PowerView’s Win32 API functions (like NetSessionEnum()) don’t accept alternate credential specifications. Instead, we have to use token impersonation. The newly minted Invoke-UserImpersonation will execute LogonUser() with the logonType set to LOGON32_LOGON_NEW_CREDENTIALS, which “allows the caller to clone its current token and specify new credentials for outbound connections“. This is the equivalent of executing a “runas /netonly” type of logon. However, instead of spawning a new process, ImpersonateLoggedOnUser() called on the token handle from LogonUser() in order to impersonate the newly logged on user, and then the logon handle is returned. Invoke-RevertToSelf can then revert any token impersonation, and if the logon handle from Invoke-UserImpersonation is passed, that will be closed out as well. This process lets us temporarily impersonate the user from a -Credential, execute the enumeration, and revert transparently. However, keep in mind every time you execute the LogonUser() call in Invoke-UserImpersonation, a logon event occurs.

Now, here’s the rub: Version 2 of powershell.exe starts in a multi-threaded apartment state, while Version 3+ starts in a single-threaded apartment state. Without diving into the specifics, the LogonUser() call works in both situations, but when you try to call ImpersonateLoggedOnUser() to impersonate the loggedon user token in Version 2, the call succeeds but the newly-impersonated token isn’t applied to newly spawned actions and we don’t get out alternate user context. PowerShell v3 and up is fine, and you can force PowerShell Version 2 to launch in a single-threaded apartment state with powershell.exe -sta, but it’s still potentially problematic. I’ve tried to handle this in two ways in PowerView:

  • Invoke-UserImpersonation will check if the current apartment state is STA and display a warning. So at least if you try to run something like Get-NetSession -ComputerName X -Credential $Cred from PowerShell v2, you’ll be alerted that it likely won’t work.
  • For any ‘meta’ functions (i.e. Find-DomainUserLocation/Invoke-UserHunter) that use threading, the abstracted New-ThreadedFunction helper now manually sets the apartment state of the new runspaces to be STA/single-threaded apartment. Invoke-UserImpersonation is executed once during the Begin{} block and the logon handle is passed to the script block being threaded. This prevents multiple logon events from being executed, and allows for the threaded enumeration to run from Version 2+ of PowerShell.

Also, as all of these functions accept a proper [Management.Automation.PSCredential] object for -Credential, it helps to know how to create these credential objects non-interactively (i.e. with Get-Credential). Here’s how you can do so:

Also,  most functions that accept -Credential now have an example (through Get-Help -Detailed/-Examples) that demonstrates this functionality, in case you forget the syntax.

Misc. Changes

The following miscellaneous changes were made:

  • Lots of function cleanup/code rot removal and standardization:
    • Additional options added to Get-DomainSearcher in order to support new param sets
    • Expanded parameter validation
    • XML help format standardized and expanded for every function
    • PSScriptAnalyzer fixups- passes PS script analyzer now!
    • Nearly all functions should tag custom types to output objects
  • -Identity supported by all appropriate functions
  • Transformed all filters to functions
  • Expanded the formats for Convert-ADName
  • Get-SPNTicket returns encrypted ticket part automatically now, and Hashcat output format added
  • Write-Verbose/Write-Warning/Throw messages now have the function name tagged in the message. This will make debugging SIGNIFICANTLY easier.
  • Verb-Domain* functions now all include a -FindOne function to return one result
  • Get-DomainUserEvent now uses -XPathFilter for a massive speedup
  • Lots of bug fixes (and new bug additions :)
  • “Required Dependencies” for each function completed
  • Fixed logic bugs for -ComputerIdentity in Get-DomainGPO, now enumerates domain-linked GPOs as well
  • Added -UserIdentity to Get-DomainGPO to enumerate GPOs applied to a given user identity
  • Now passes PSScriptAnalyzer

The following functions were removed:

  • Get-ComputerProperty, Get-UserProperty, Find-ComputerField, Find-UserField
  • Get-NameField (translated to ValueFromPipelineByPropertyName calls)
  • Invoke-DowngradeAccount – not really used, more PoC
  • Add-NetUser – split into New-DomainUser/others
  • Add-NetGroupUser – split into Add-DomainGroupMember/others
  • New-GPOImmediateTask – inconsistent and better done manually
  • Invoke-StealthUserHunter – combined into Find-DomainUserLocation
  • Get-ExploitableSystem – not really used, difficult to update

The following exported functions were added:

  • Add-RemoteConnection – ‘mounts’ a remote UNC path using WNetAddConnection2W
  • Remove-RemoteConnection – ‘unmounts’ a remote UNC path using WNetCancelConnection2
  • Invoke-UserImpersonation – creates a new “runas /netonly” type logon and impersonates the token in the current thread
  • Invoke-RevertToSelf – reverts any token impersonation
  • Invoke-Kerberoast – automates Kerberoasting
  • Find-DomainObjectPropertyOutlier – finds user/group/computer objects in AD that have ‘outlier’ properties sets
  • New-DomainUser – creates a new domain user
  • New-DomainGroup – creates a new domain group
  • Add-DomainGroupMember – adds a domain user (or group) to an existing domain group
  • Get-NetLocalGroup – now returns local groups themselves
  • Get-NetLocalGroupMember – returns local group members (old Get-NetLocalGroup)

The following functions were renamed. Aliases were made for each to ease the transition:

  • Get-IPAddress -> Resolve-IPAddress
  • Convert-NameToSid -> ConvertTo-SID
  • Convert-SidToName -> ConvertFrom-SID
  • Request-SPNTicket -> Get-DomainSPNTicket
  • Get-DNSZone -> Get-DomainDNSZone
  • Get-DNSRecord -> Get-DomainDNSRecord
  • Get-NetDomain -> Get-Domain
  • Get-NetDomainController -> Get-DomainController
  • Get-NetForest -> Get-Forest
  • Get-NetForestDomain -> Get-ForestDomain
  • Get-NetForestCatalog -> Get-ForestGlobalCatalog
  • Get-NetUser -> Get-DomainUser
  • Get-UserEvent -> Get-DomainUserEvent
  • Get-NetComputer -> Get-DomainComputer
  • Get-ADObject -> Get-DomainObject
  • Set-ADObject -> Set-DomainObject
  • Get-ObjectAcl -> Get-DomainObjectAcl
  • Add-ObjectAcl -> Add-DomainObjectAcl
  • Invoke-ACLScanner -> Find-InterestingDomainAcl
  • Get-GUIDMap -> Get-DomainGUIDMap
  • Get-NetOU -> Get-DomainOU
  • Get-NetSite -> Get-DomainSite
  • Get-NetSubnet -> Get-DomainSubnet
  • Get-NetGroup -> Get-DomainGroup
  • Find-ManagedSecurityGroups -> Get-DomainManagedSecurityGroup
  • Get-NetGroupMember -> Get-DomainGroupMember
  • Get-NetFileServer -> Get-DomainFileServer
  • Get-DFSshare -> Get-DomainDFSShare
  • Get-NetGPO -> Get-DomainGPO
  • Get-NetGPOGroup -> Get-DomainGPOLocalGroup
  • Find-GPOLocation -> Get-DomainGPOUserLocalGroupMapping
  • Find-GPOComputerAdmin -> Get-DomainGPOComputerLocalGroupMapping
  • Get-LoggedOnLocal -> Get-RegLoggedOn
  • Invoke-CheckLocalAdminAccess -> Test-AdminAccess
  • Get-SiteName -> Get-NetComputerSiteName
  • Get-Proxy -> Get-WMIRegProxy
  • Get-LastLoggedOn -> Get-WMIRegLastLoggedOn
  • Get-CachedRDPConnection -> Get-WMIRegCachedRDPConnection
  • Get-RegistryMountedDrive -> Get-WMIRegMountedDrive
  • Get-NetProcess -> Get-WMIProcess
  • Invoke-ThreadedFunction -> New-ThreadedFunction
  • Invoke-UserHunter -> Find-DomainUserLocation
  • Invoke-ProcessHunter -> Find-DomainProcess
  • Invoke-EventHunter -> Find-DomainUserEvent
  • Invoke-ShareFinder -> Find-DomainShare
  • Invoke-FileFinder -> Find-InterestingDomainShareFile
  • Invoke-EnumerateLocalAdmin -> Find-DomainLocalGroupMember
  • Get-NetDomainTrust -> Get-DomainTrust
  • Get-NetForestTrust -> Get-ForestTrust
  • Find-ForeignUser -> Get-DomainForeignUser
  • Find-ForeignGroup -> Get-DomainForeignGroupMember
  • Invoke-MapDomainTrust -> Get-DomainTrustMapping

Docs, Docs, Docs Docs Docs!

Following in @jaredcatkinson‘s documentation efforts for PowerForensics, I also started the generation of documentation for PowerView (and soon all of PowerSploit) by using platyPS. This awesome project can generate markdown docs purely from your PowerShell project’s existing XML help. For now doc files will be in ./docs/ of the dev branch and we will do our best to keep them updated.

With markdown docs generated easily, we also started integration with, an external documentation hoster that integrates nicely with GitHub. The docs and formatting have a ways to go, but you can see a start of how things will look at:


As mentioned, this is by far the biggest overhaul PowerView has ever had, so there are guaranteed to be unintended bugs and other issues. I’m intending on updating the PowerView cheat sheet soon with the new syntax as well. We’re actively field-testing the code now and actively pushing changes to the Dev branch of PowerSploit, so if you have any issues let us know and we will try for a reasonable turnaround. We’re quite excited for all the changes, and hope that everyone else is as well!

Kerberoasting Without Mimikatz

Just about two years ago, Tim Medin presented a new attack technique he christened “Kerberoasting“. While we didn’t realize the full implications of this at the time of release, this attack technique has been a bit of a game changer for us on engagements. More and more attention has been brought to Kerberoasting recently, with @mubix releasing a three part series on the topic, Sean Metcalf covering it several times, and @leonjza doing a detailed writeup as well.

Thanks to an awesome PowerView pull request by @machosec, Kerberoasting is easier than ever using pure PowerShell. I wanted to briefly cover this technique and its background, how we’ve been using it recently, and a few awesome new developments.

Kerberoasting Background

I first heard about Kerberoasting from Tim at SANS HackFest 2014 during his “Attacking Kerberos: Kicking the Guard Dog of Hades” talk (he also released a Kerberoasting toolkit here). I’ll briefly paraphrase some technical detail of the attack, but I highly recommend you read Tim’s slides and/or Sean’s explanation for more detail. There’s also an excellent page of Microsoft documentation titled “Kerberos Technical Supplement for Windows” which finally clarified a few points involved in this process that were fuzzy to me.

Here’s my version of the obligatory “this is how kerberos works” graphic:


As far as how Kerberoasting fits into this process, this is how I understand it (if I am mistaken on some point please let me know!): after a user authenticates to the key distribution center (KDC, which in the case of a Windows domain is the domain controller) they receive a ticket-granting-ticket (TGT) signed with the domain krbtgt account that proves they are who they say they are. The TGT is then used to request service tickets (TGS) for specific resources/services on the domain. Part of the service ticket is encrypted with the NTLM hash of the target service instance. So how does the KDC determine exactly what key to use when encrypting these service tickets?

The Windows implementation of the Kerberos protocol uses service principal names (SPNs) to determine which service account hash to use to encrypt the service ticket. There are two “types” of service principal names in Active Directory: “host-based” SPNs that are linked to a domain computer account and “arbitrary” SPNs that are usually (but not always) linked to a domain user account.

As Microsoft explains, “When a new computer account is created in Active Directory, host-based SPNs are automatically generated for built-in services…In reality, SPNs are only created for the HOST service and all built-in services use the HOST SPN”. Put another way, “The HOST service represents the host computer. The HOST SPN is used to access the host computer account whose long term key is used by the Kerberos protocol when it creates a service ticket”. Here’s an example of a default computer account in my test domain:


You can see the HOST/WINDOWS1 and HOST/WINDOWS1.testlab.local SPNs for the WINDOWS1$ computer account. When a domain user requests access to \\WINDOWS1.testlab.local\C$, the KDC maps this request to the HOST/WINDOWS1.testlab.local SPN, indicating that the WINDOWS1$ machine account NTLM hash (which is stored both on WINDOWS1 locally and the NTDS.dit Active Directory database on the DC/KDC) should be used to encrypt the server part of the service ticket. The signed/encrypted ticket is then presented to WINDOWS1.testlab.local, which is responsible for determining whether the requesting user should be granted access.

From the Kerberoasting perspective, we generally don’t care about host-based SPNs, as a computer’s machine account password is randomized by default and rotates every 30 days. However, remember that arbitrary SPNs can also be registered for domain user accounts as well. One common example is a service account that manages several MSSQL instances; this user account would have a <MSSQLSvc/HOST:PORT> SPN for each MSSQL instance it’s registered for stored in the user’s serviceprincipalname attribute (Sean keeps an updated list of SPNs here). If we have an arbitrary SPN that is registered for a domain user account, then the NTLM hash of that user’s account’s plaintext password is used for the service ticket creation. This is the key to Kerberoasting.

Obligatory “So Why Does This Matter?”

Because of how Kerberos works, any user can request a TGS for any service that has a registered SPN (HOST or arbitrary) in a user or computer account in Active Directory. Remember that just requesting this ticket doesn’t grant access to the requesting user, as it’s up to the server/service to ultimately determine whether the user should be given access. Tim realized that because of this, and because part of a TGS requested for an SPN instance is encrypted with the NTLM hash of a service account’s plaintext password, any user can request these TGS tickets and then crack the service account’s plaintext password offline, without the risk of account lockout!

To reiterate, any domain user account that has a service principal name set can have a TGS for that SPN requested by any user in the domain, allowing for the offline cracking of the service account plaintext password! This is obviously dependent on a crackable service account plaintext, but luckily for us service accounts tend to often have simple passwords that change very infrequently. ¯\_(ツ)_/¯

As an added bonus, Tim mentions on slide 18 of his presentation deck:



“Old School” Kerberoasting

Tim’s outlined approach/toolkit used a combination of toolsets to request tickets, extract them from memory (using Mimikatz), and transform them into a crackable format. In general, the process (up until recently) went as follows:

  • Enumerate the domain accounts with SPNs set- either with Tim’s GetUserSPNS.ps1 script, Sean’s Find-PSServiceAccounts.ps1 script, or PowerView’s “Get-NetUser -SPN“.
  • Request TGSs for these specific SPNs with the builtin Windows tool setspn.exe or the .NET System.IdentityModel.Tokens.KerberosRequestorSecurityToken class in PowerShell.
  • Extract these tickets from memory by invoking the kerberos::list /export Mimikatz command , with the optional base64 export format set first. The tickets were then downloaded, or the base64-encoded versions pulled down to the attacker’s machine and decoded.
  • Begin offline password cracking with Tim’s, or extract a crackable hash format from the raw ticket with John the Ripper’s

xan7r branched Tim’s toolset and added an autokerberoast.ps1 script that automated large components of this process. Also, @tifkin_ wrote a Go version of a TGS cracker that functioned a bit faster than the original Python version.

“New School” Kerberoasting

A few recent(ish) things really simplified our usage of Kerberoasting on engagements. First, Michael Kramer added the KRB5TGS format to John the Ripper in September of 2015. Second, @Fist0urs committed the same algorithm to Hashcat in Febuary 2016, opening the door for GPU-based cracking of these tickets. This was really a watershed for us, as it greatly expanded the range of service account passwords we could crack. And finally, Matan Hart (@machosec)’s pull request to PowerView removed the Mimikatz requirement.

@machosec realized that .NET class KerberosRequestorSecurityToken used in previous approaches also had a GetRequest() method, which returns the raw byte stream of the Kerberos service ticket. With a bit string manipulation, Matan was able to easily extract out the encrypted (i.e. the crackable hash component) of the TGS. We are now no longer dependent on Mimikatz for ticket extraction!

I recently rolled the necessary functions into a single, self-contained script that contains the necessary components from PowerView (this has also been updated in Empire). We are currently in the process of refactoring large components of PowerSploit, and the updated functions will be posted here after the changes are published. This custom-rolled script includes the Invoke-Kerberoast function, which wraps the logic from Get-NetUser -SPN (to enumerate user accounts with a non-null servicePrincipalName) and Get-SPNTicket to request associated TGS tickets and output John and Hashcat crackable strings. For now, here’s what the output of the script looks like:


It also works across domains!


By default, the John format is output, but -OutputFormat Hashcat will output everything Hashcat-ready. Note that the -AdminCount flag only Kerberoasts accounts with AdminCount=1, meaning user accounts that are (or were) ‘protected’ and, therefore, almost always highly privileged:


And here’s how the updated Empire module looks:


Note that for non-Empire weaponizations, as PSObjects are output, you will need to pipe the results to Format-List or ConvertTo-Csv -NoTypeInformation in order to preserve the information you want displayed. You can then crack these tickets as @mubix described in his third post.

Again, the self-contained, PowerShell 2.0-compliant script is on my Gists here. Hopefully this is as much use to you as it has been for us over the past few months!

Empire Fails


Everyone makes mistakes, and we’re certainly no exception. Empire has suffered from a few security issues since its original release at BSides LV in 2015, and for a while, I’ve wanted to give some technical details on the specific mistakes we’ve made along the way for the sake of transparency. Thanks to a recent second disclosure by Spencer McIntyre (@zeroSteiner) several weeks ago, it seemed to be an appropriate time to own up to our transgressions. This post will cover the crypto issue disclosed right after release by Jon Cave (@joncave), as well as the two separate RCE issues disclosed by Spencer.

Crypto is Hard

One of the earlier Empire issues disclosed after the project was released was pull request #3 “Use authenticated encryption” by Jon Cave. As Jon described, “Even though the agent to server communications are encrypted they are still malleable and vulnerable to attack“. So what does this mean, and what’s the fix?

Essentially, because we didn’t originally include any kind of message integrity/validation in our communications, it was possible to modify sniffed Empire ciphertext in a way that modified the type of the packet. In Jon’s example, he modified the first byte of the ciphertext (the IV) in order to change the first part of the plaintext (the packet type). Since there are only 256 packet types, one of them being TYPE_EXIT, with a relatively small number of packets sent to the control server it was possible to force the exit of agents if the sessionID was known, effectively creating a DoS on the control server. Here was Jon’s simple PoC:

Other attacks were theoretically possible, including the chance for encrypted information disclosure (though this was complicated by a lack of padding on Empire’s part).

Jon’s fix was to implement MD5 HMAC into message communications that occur after staging, along with double HMAC verification on the server side in order to prevent timing attacks. I bought Jon a round of beers at 44con, where he explained that he actually found the issue within 1-2 days of Empire being released. He also helped proof the Empire 2.0 protocol redesign and offered several optimizations.

RCE Is Bad Mmmmkay

The next mistake was much, much worse. Can you spot the mistake in lib/common/ ?

A few months after release, Spencer McIntyre sent us a disclosure that allowed for remote compromise of an Empire control server. We worked to get a fix out quickly and Spencer chose not to release the PoC he titled ‘Skywalker’, but he recently released a updated PoC and Metasploit module that is compatible with the v2 version of the bug (described below). Issue #52 shows the patch for the original exploit.

One of the packet types for Empire is TASK_DOWNLOAD, which is the chunked response to a file download. The control server takes the packets comprising a file download and reconstructs the original file in ./downloads/SESSIONID/<original_path>/ where the original path is cloned on the server side to preserve the downloaded file path. What Spencer realized is that we weren’t doing proper path sanitization, leaving the path save mechanism open to a path traversal.

Spencer’s exploit will first ‘fake’ an agent checking into the control server, executing the normal key exchange process. A TASK_DOWNLOAD packet response is then sent to the control server, with the original file name compromising a ../path/traversal and the payload composing a malicious crontab entry. The server receives the packet and dutifully saves the malicious crontab to /etc/crontab, which will provide remote execution if the control server is running as root.

We fixed this with PR #52, which uses os.path.abspath to compare a normalized absolutized version of the save path to the ‘safe’ downloads folder.

Skywalker v2, “Oh no, not again”

About a month ago Spencer contacted us again with another disclosure notice (nothing makes your heart sink like getting a gpg message from Spencer with ‘skywalker.v2’ in the title : ). The second version of this exploit abused a malicious client SESSIONID to execute a similar type of path traversal. Spencer also included a patch that we verified and integrated into both the development and master branches with the 1.6 release.

Spencer graciously waited to release the exploit proof of concept and Metasploit module for a month after the path was integrated. The pull request (#7450) containing the exploit module was submitted this morning and is located here if you want to check it out.

Security is Hard

We wanted to extend another big thank you to both Jon and Spencer for the disclosures. We take it as a big compliment that anyone would look at our code as closely as they have, and we owe a lot to both of these researchers for providing fixes. If anyone else finds additional issues, please let us know, and we’ll buy you some rounds at the next con we all end up at!

The Empire Strikes Back

CA.Empire20.0211.6.QññIn the aftermath of their dual, Darth Vader beckons Luke Skywalker in a scene from " The Empire Strikes Back Special Edition"empire_welcome

We recently made some of the biggest changes to Empire since its release at BSidesLV in 2015. This post will summarize many of the modifications for the Empire 2.0 beta release, but also check out @enigma0x3‘s and my “A Year in the Empire” presentation we gave at Derbycon 6 for more information (slides here). This also marks an expansion of the Empire Development Team, which now includes @enigma0x3, @sixdub, @rvrsh3ll, @xorrior, and @killswitch_gui. The beta code is current in the 2.0_beta branch of the newly-relocated Empire repository – we want to stress again that this code is beta, so use with caution until it’s properly tested and merged to the master branch. We also still need to work out a proper methodology for migrating agents from 1.X to 2.X, which we’re hoping to work out in the next few weeks.

The original motivation for PowerShell Empire started almost as a thought exercise in late 2014. While various PowerShell projects implemented many of the capabilities of a modern RAT (keylogging, screenshots, the amazing Mimikatz, etc.), there wasn’t a pure PowerShell agent that brought everything together.

Nearly a year later, in late 2015/early 2016, we ran into another situation. We were preparing for an OS X-heavy client and realized that the public toolsets available at the time didn’t satisfy our customer’s requirements. The result of a frantic month of coding, EmPyre was built starting from PowerShell Empire’s code base mainly due to the fact that we had < 30 days to develop a fully-functioning capability in order to accomplish our objectives. We would not consider ourselves to be OS X experts, and simply didn’t have the time to develop a ‘native’ OS X rat (and controller) from scratch in that time. We went with the ‘living off the land’ philosophy we pursued with PowerShell Empire, this time opting for a Python 2.7 stdlib compliant agent that would also work for Linux.

For more background on both projects, check out the PowerShell Empire blog series as well as its Python EmPyre brother.

After months of development and the BSides LV 2016 presentation  with @424f424f and @killswitch_gui, many people naturally asked “are these projects going to be integrated?“. With the huge overlap between the two codebases, this made sense, and would simplify our lives when bugs were found in both projects. @enigma0x3, @xorrior, @424f424f, @sixdub, @killswitch_gui, and I are happy to announce that this is now a reality. The 2.0_beta branch of the Empire project contains the new code base, and will eventually be merged into master after additional testing. The rest of this post will briefly cover some of the new 2.0 features.

Misc. Changes

First, a grab-bag of mods vs. 1.6:

  • For the PowerShell launcher/stager:
    • RC4 was implemented for first stage obfuscation instead of XOR
    • @mattifestation‘s AMSI bypass implemented in the stage0 launcher
    • staging now uses HMAC/nonces
  • For OS X/Python, lots of new stagers! @xorrior will have a more detailed post on these in the coming weeks:
  • Epoch-syncing was removed- we know this introduces a possibility of packet replay, but too many users had too many issues with the epoch-syncing approach.
  • Vastly increased debugging output. Use ––debug to output debug information to output to empire.debug, and ––debug 2 to display the same information to the screen.
  • If agents are ‘orphaned’ they will restage to the control server.
  • HTTP listener redone with Flask.
  • Improved Kerberoasting module with credentials/get_spn_tickets.
  • BloodHound module (situational_awareness/network/bloodhound) will execute collection and output to CSVs.
  • We implemented @enigma0x3’s fancy new eventvwr UAC bypass that doesn’t drop a DLL to disk. The module is privesc/bypassuac_eventvwr and has been set as the alias for “bypassuac <LISTENER>” in the agent menu.
  • Lots of code rot removed, several files got some fresh paint.

What’s still broken:

  • The CLI options and RESTful API need some love before release.
  • Misc. communication errors that we’re working though with the new core

New Packet Structure

In starting the redesign, we soon realized that the underlying packet structure needed to be redone. For example, agents implementing peer-to-peer approaches (like SMB) will need to be able to figure out how to route packets to other agents, despite each agent having a negotiated session key. Here’s how the Empire 1.X packet spec looks:

And here’s how the Empire 2.X packet spec looks:



Each client packet is wrapped in a metadata/routing ‘packet’ that’s encrypted using RC4 and the pre-determined server staging key. This means that every agent in the C2 mesh can decrypt the metadata packet, allowing it to route packets as appropriate. This also simplified handling multiple languages on the server side, allowing Python and PowerShell agents to communicate on the same port for HTTP[S]. These specs are also at the top of ./lib/common/

Python and PowerShell, Brothers in Arms

One of the big goals of the 2.0 release was to combine the PowerShell Empire and Python EmPyre code bases while maintaining as fluid of a transition for existing users as possible.

Listeners that handle multiple languages (more information in the following section) can easily generate language-specific launchers for the same listener:


Additionally, stager modules are now separated by operating system, and take a Language parameter:


Agent language types are now broken out on the main display menu. Interact <AGENT> will drop you into a language-appropriate menu (PowerShell or Python), keeping the UI seamless. Also, usemodule [tab] will tab-complete only modules appropriate for the language type:


Multiple language types can be supported for a single listener- the above screenshot shows a PowerShell agent running on a Windows target and Python agents running on both Linux and OS X, all communicating on the same listener and port. This is possible due to the revamped packet structure, which lets us extract the language type from the metadata packet and task data appropriately.

Listener Modularity

The other big goal with Empire 2.0 was the modularization of listeners. Previously, listener and staging logic was spread throughout a number of places, making modifications quite difficult. Listeners are now single self-contained modules that you can drag and drop into an Empire instance. This changes the UI just a bit:


It also makes listener modification much easier. A great example is the listeners/http_com module. This module uses hidden Internet Explorer COM objects to communicate instead of Net.WebClient. Since client launcher/stager/agent generation along with server logic are all handled in the same module, it’s now relatively easy to modify the communication pattern, e.g. to use a different cookie value or to embed commands in a webpage comment.

It’s also now possible to build listeners that communicate through third-party sites, as @enigma0x3 and I demonstrated in our DerbyCon presentation. While we are not planning on releasing third-party C2 modules (or accept them in pull requests), we will be releasing a third party ‘template’ module and associated post that walks through how you could build one yourself.

Wrap Up

We’ve had a blast devving Empire and EmPyre over the last year, and want to extend a serious thank you to everyone who contributed fixes, modules, testing cycles, and ideas for the project. The public response has been amazing, and we’re hoping to continue expanding features for the project going forward. After some additional mods and testing are completed, the 2.0_beta branch will be merged into master, with a final 1.6 release package remaining available.

Offensive Encrypted Data Storage

We generally try to keep off of disk as much as possible on engagements- there’s less to clean up and fewer chances of being caught. However, occasionally we have a need to store data on disk on a target system, and we want to try to do this in a secure way in case any incident responders start to catch on. Examples would be reboot-persistent keyloggers, something that monitors locations for specific files and clones/exfiltrates them, and the pilfering of KeePass files (see the Example: KeePass + EncryptedStore section below).

If we have to write a file to disk, we want to do it in a way that prevents the recovery of the data as best we can and uses only built-in tools to do so. This post will detail one of our solutions to this problem. The code detailed in this post is live on GitHub.

An Encrypted Store Design

We have a few specific design requirements for our encrypted datastore. We want something with:

  • reasonably strong crypto – we chose AES with cipher block chaining (CBC) and a randomized IV, as well as RSA + AES to use a public key to encrypt a random AES key per encrypted unit (more on this below)
  • doesn’t leave the password with the file (in order to prevent easy recovery)
  • accepts multiple files, and doesn’t require decrypting/re-encrypting the entire store on each addition to it
  • accepts arbitrary data like keystrokes as well as files
  • ‘platform independent’ decryption on a variety of platforms with a variety of languages

The storage format we came up with is ‘packetized’, with discrete units of a specific format appended to a single file. This way the store can be appended to easily without constant encryption/decryption. The store format is as follows:

To encrypt a file for ENCSTORE.bin:

  • Read raw file contents
  • Pad original full file PATH to 260 Bytes
  • Compress [PATH + file] using IO.Compression.DeflateStream
  • If using RSA+AES, generate a random AES key and encrypt using the RSA public key
  • Generate random 16 Byte IV
  • Encrypt compressed stream with AES-CBC using the predefined key and generated IV
  • Calculate length of encrypted block + IV
  • Append 4 Byte representation of length to ENCSTORE.bin
  • Append 0 byte if straight AES used, 1 if RSA+AES used
  • Optionally append 128 bytes of RSA encrypted random AES key if RSA+AES scheme used
  • Append IV to ENCSTORE.bin
  • Append encrypted file to ENCSTORE.bin

Decryption happens in reverse:

  • While there is more data to decrypt:
    • Read first 4 bytes of ENCSTORE.bin and calculate length value X
    • Read next X bytes of encrypted file
    • Read the first byte of the encrypted block to see if AES or RSA decryption is specified
    • If RSA-AES is specified (byte == 1):
      • Read the next 128 bytes of encrypted block and decrypt the random AES key using the RSA private key
    • Read the next 16 bytes of block and extract the IV
    • Read remaining block and decrypt AES-CBC compressed stream using specified key and extracted IV
    • Decompress [PATH + file]
    • Split path by \ and create nested folder structure to mirror original path
    • Write original file\data to mirrored path

To implement the integration of arbitrary data into the same container format, a ‘data tag’ string (like ‘keylog’) is used in lieu of the file path, and the arbitrarily passed data is used instead of extracting the file contents.

The AES/RSA “packets” are also stackable and any number of both types of packets can be appended to the same write location.


The PowerShell code to do this is currently on GitHub. The EncryptedStore.ps1 script is PowerShell version 2.0 compatible, and uses [System.Security.Cryptography.AesCryptoServiceProvider] for the AES implementation, [System.Security.Cryptography.RSACryptoServiceProvider] for the RSA implementation, and [System.IO.Compression.DeflateStream] for the compression implementation.

If you want to use RSA encryption for the store, you first need to generate an RSA public/private key pair with $Key = New-RSAKeyPair. Be sure to save the key object if you want to be able to decrypt any of your data!

Write-EncryptedStore will create an encrypted store and accepts data/file paths on the pipeline. There’s also a 1 gigabyte default storage limit which can be modified with -StoreSizeLimit 100MBIt requires a -StorePath and -Key, which is MD5 hashed for an AES password if not 32 characters. If the key string is of the format ‘^<RSAKeyValue><Modulus>.*</Modulus><Exponent>.*</Exponent></RSAKeyValue>$‘, the public key format generated by New-RSAKeyPair, then the RSA+AES scheme is used instead of straight AES. SecureStrings are also usable with the -SecureKey parameter.

Here’s how to store off a set of target files into C:\Temp\debug.bin:

If you have arbitrary data to store, the function also takes a -DataTag X argument to pretag the saved data with something like “keylog”. Here’s an example:

Since the atomic storage unit is indifferent to tagged data or files, you can store both in the same container. Write-EncryptedStore actually wraps the more generic Out-EncryptedStore function, which takes the types of input specified above and outputs the set of encrypted bytes containing the encrypted data. Out-EncryptedStore also has a -Base64Encode flag that will return everything as a base64-encoded string. This can be useful in some situations for transport (like in a RAT).

-StorePath defaults to $Env:Temp\debug.bin if a value is not specified. It also accepts \\UNC\file.bin paths, registry paths (“HKLM:\SOFTWARE\\something\key\valuename”), and WMI namespaces (“ROOT\Software\namespace:ClassName”) for additional storage options. A remote computer is specifiable for all three storage options with -ComputerName <Computer> with a separate -Credential <X> being specifiable as well. All of these options are present with Read-EncryptedStore as well (described below).

Here are all the local/remote storage options available:

Read-EncryptedStore will recover the data from a specified encrypted store. It also requires -StorePath and -Key/-SecureKey. If you want to just list the files in a store, use the -List parameter:

If you want to extract the data leave off the -List parameter, and Read-EncryptedStore will extract out all the data to the local folder, cloning the original paths. If there’s a filename conflict, additional files are appended with a counter:

As shown in the test examples, the Get-EncryptedStoreData and Remove-EncryptedStore functions can be used to retrieve/remove encrypted store data, again from all three storage options, local or remote.

There’s also a Python version of Read-EncryptedStore. It uses pycrypto for the crypto implementations and zlib for the decompression implementation. It currently only supports AES containers.

To list the files for a given store:

./ –store debug.bin –key ‘password’ –list

To extract the files:

./ –store debug.bin –key ‘password’


Example Use Case: KeePass + EncryptedStore

A while back I released a post detailing how to operationally “attack” KeePass databases. As a follow up I wrote a script that searches for any KeePass.ini (version 1.X) or KeePass.config.xml (version 2.X) configuration files in C:\Users\, C:\Program Files\, and C:\Program Files (x86)\. This script is also included with EncryptedStore. Any found configurations are parsed and a custom PSObject is output with relevant information detailing database/keyfile locations, as well as information like the 2.X SecureDesktop setting, and whether a Windows user account was used to create the composite master key. In the situation where a user account is used as a mixin, user name/SID/domain information is output along with user master key locations:


This made a great candidate to pair with the EncryptedStore approach. Write-EncryptedStore and Out-EncryptedStore can take the output from Find-KeePassconfig on the pipeline and encrypt all found KeePass files into a single datastore:


Hopefully others find this of use. The storage format is simple enough that other language implementations should be possible as well if anyone has any interest.

Command and Control Using Active Directory

‘Exotic’ command and control (C2) channels always interest me. As defenses start to get more sophisticated, standard channels that have been stealthy before (like DNS) may start to lose their efficacy. I’m always on the lookout for non-obvious, one-way (or ideally two-way) communication methods. This post will cover a proof of concept for an internal C2 approach that uses standard Active Directory object properties in a default domain setup.

Active Directory Property Sets

This dawned on me when reviewing access control list entry information during training prep. In a default domain setup, there is a set of ACLs for user objects that apply to the user itself, defined by the ‘NT AUTHORITY\SELF’ IdentityReference. If you want to check these out for a sample domain, you can run the following PowerView command:

Here’s an interesting entry:


So all users are able to write read and write to their own “Personal-Information” in Active Directory. This is what’s known as a property set in AD, which were created to group specific common properties in order to reduce storage requirements on the Active Directory database. Unfortunately the material on that link has been archived, but if you download the document, page 8213 has more information on property sets in general, and this MSDN page breaks out the members of the “Personal-Information” property set.

Now let’s see which properties can hold the most data by examining the schema for the ‘user’ object in this domain:


The above query will list ALL properties for a generic ‘user’ object given the current domain schema, but not all of these properties are self-writable for a user. We want to choose the property with the largest storage limit that is also in ‘Personal Information’ property set, which will give us the most flexibility with our communication channel. The mSMQSignCertificates field is interesting, as it has a 1MB upper size limit and meets all of our qualifications. Since every user can edit the mSMQSignCertificates property for their own user object, we have a nice 1MB two-way data channel (mSMQSignCertificatesMig is also interesting but not a member of ‘Personal Information’, so it’s not quite what we need at this point).

Now what’s the best way to take advantage of this?


The use of mSMQSignCertificates gives us a one-to-many broadcast approach. One user changes their property field while other users continually query for that world-readable information, and then report results back through their own mSMQSignCertificates field. This two-way 1MB channel is stored and propagated by Active Directory itself, which lends a few advantages. We never have to send packets directly to targets, and with some tweaking this should get around some network segmentation setups (see the Bending Traffic Around Network Boundaries section below for caveats and more details).

The proof of concept code below is hosted on this gist:

Use New-ADPayload to register a new broadcast trigger for the current (or specified) user and output a one-line launcher in a custom PSObject. This launcher is usable from any user logged on anywhere in the forest (more on this at the end of the post). All code taskings and results are compressed using .NET’s [IO.Compression.DeflateStream] in order to save on space, and then base64’ed before being stored in the mSMQSignCertificates property of the target user.


After the TriggerScript logic is launched on a target host, use Get-ADPayloadResult to query all users EXCEPT the -TriggerAccount used to broadcast the script logic (default of [Environment]::UserName), extract out the compressed data, and display the per-user results.


Get-ADPayload will retrieve any payload stored in mSMQSignCertificates for the given -TriggerAccount (defaulting to the current user) and Remove-ADPayload will remove the script payload:


Bending Traffic Around Network Boundaries

As I mentioned briefly, one of the coolest side effects of this approach is that you can get around some network segmentation setups, assuming that the broadcast user and victim user are in the same forest. While I’m not going to go deep into domain trusts, I’ll cover a few quick points. Check out Sean Metcalf‘s 2016 BlackHat/DEF CON “Beyond the MCSE*” presentations for more information.

An Active Directory global catalog is a, “a domain controller that stores a full copy of all objects in the directory for its host domain and a partial, read-only copy of all objects for all other domains in the forest“. Not all object properties are replicated, but rather only properties in the “partial attribute set” defined in the domain schema. We can enumerate all the schema objects by using the “(isMemberOfPartialAttributeSet=TRUE)” LDAP filter, for example using PowerView:

And luckily for us, the mSMQSignCertificates field is included in the partial attribute set for the default schema! This is also documented by Microsoft here.


Any time we modify the mSMQSignCertificates field for a user, that data should propagate to all copies of the global catalog in the forest. So even if our trigger or victim users can’t reach each others’ domains directly due to proper network segmentation, as long as the global catalog is allowed to replicate, we have a basic two-way channel between any two users in a forest (as long as each user can reach their normal domain controller/global catalog).

We can read our ‘broadcast’ traffic through the global catalog, but we can’t write to attributes using this method; overall we don’t care since the default behavior is for each user to modify their own mSMQSignCertificates in their current domain. We’re also at the mercy of the replication speed of the global catalog, so while this channel is reasonably sized (1MB), it’s not going to be practical for interactive communications.

For the proof of concept code in this post, the TriggerScript generated by New-ADPayload will automatically query the victim’s global catalog for the trigger account. Get-ADPayload and Get-ADPayloadResult by default will query only the current domain, unless a -TriggerAccount X argument is passed, in which case the global catalog is searched. The following screenshot shows results from users in two domains in the forest, where the machine each user is currently on is explicitly disallowed from direct communication with the foreign domain controller:


As far as defensive mitigations go, Carlos Perez pointed me to the “Audit Directory Service Changes” AD policy. With this auditing policy enabled, changes to an active directory object will produce an event with ID 5136, meaning “a directory service object was modified”. This should let you track the modifications of object fields like mSMQSignCertificates. There’s more information on this event ID in this article.

As a last note, the proof of concept code doesn’t implement any encryption (though this would be relatively simple), so I wouldn’t recommend using it in its unmodified state on engagements.

Have fun :D

PowerShell RC4

Every language needs an RC4 implementation. Despite its insecurities, RC4 is widely used due to its simple algorithm and the minimal amount of code it takes to implement it. Some people have even tried to fit implementations into single tweets. It’s commonly used by malware due to its low overhead, and I’m actually shocked that RosettaCode doesn’t have an entry for RC4.

The only PowerShell implementation I’m aware of is Remko Weijnen’s code here, and as far as I know .NET doesn’t include an RC4 implementation that we can take advantage of. This post will cover a ‘proper’-(esque) implementation of RC4, a practical ‘minimized’ version, and a version that I collaborated with some PowerShell madmen on in the quest to get it under 140 characters for a tweet.

RC4 Background

Read the Wikipedia page if you’re actually curious.

Proper Implementation

Without further ado:

Yes, there’s not a completely proper PRGA implementation, but this was partly to take advantage of PowerShell’s pipelining. This was the approach that made the most sense to me. It requires byte arrays for the -InputObject and -Key, so here’s how you use it:

Note that it can accept an -InputObject byte array on the pipeline, or by calling the parameter, your choice.

Minimization Take 1

The minimization idea came about from doing training prep, where we wanted a practical RC4 implementation for decrypting, or executing, malware communications. For malware staging, space and (some) obfuscation matters, so proper script ‘etiquette’ can mostly be thrown out the window. The optimization for this function is heavily due to @lee_holmes, @mattifestation, @secabstraction, and @tifkin_, which will come to properly crazy fruition in the last section.

Here’s the code:

For the optimization, we’re taking advantage of a few things- I’ll try to outline a few here as it was an interesting thought exercise:

  • Uninitialized variables are assumed to be $Null/0 – $J in the KSA, $I and $H (replacing $J) in the PRGA.
  • We obviously dropped pipeline support, and used nameless/lambda functions (@mattifestation‘s idea) to cut down a bit more.
  • Spaces? Who need spaces?

PowerShell has its own form of Lambda functions – anonymous script blocks that can serve as functions without a formal name. There’s more information on PowerShell and Lambda functions from @mattifestation. These functions can be invoked with the call operator (&) like the following:

Getting Weird With PowerShell

So how can we cut this down even more to fit into a tweet? Matt had the great idea of converting the ASCII representation of our logic to bytes and then repacking those bytes as UNICODE. Since an ASCII character is encoded as 8 bits/1 byte and UNICODE is encoded with 16 bits/2 bytes, we can pack two ASCII characters into a single UNICODE encoding. Here’s how we can accomplish this in PowerShell:

So we can cut our script down to len(script)/2 + len(decoding logic). Unfortunately, we were only able to get the logic down to 141 characters with piping to IEX, so I tweeted out the version that just echoed the V3+ algorithm (if anyone can shave another few characters off, let us know). Before running, $D needs to be initialized as the data array and $K initialized as the key array. The weird % *es bit is a PowerShell v3.0+ shortcut for ForEach-Object -MemberName *es, which returns the GetBytes() method for the [Text.Encoding]::Unicode instantiation. This is a quick way to call [System.Text.Encoding]::Unicode.GetBytes(‘UNICODE’).

The UNICODE packing technique seems like it might have additional use cases for offensive obfuscation, but this is an exercise left to the reader.

KeeThief – A Case Study in Attacking KeePass Part 2

Note: this post and code were co-written with my fellow ATD workmate Lee Christensen (@tifkin_) who developed several of the interesting components of the project.

The other week I published the “A Case Study in Attacking KeePass” post detailing a few notes on how to operationally “attack” KeePass installations. This generated an unexpected amount of responses, most good, but a few negative and dismissive. Some comments centered around the mentality of “if an attacker has code execution on your system you’re screwed already so who cares“. Our counterpoint to this is that protecting your computer from malicious compromise is a very different problem when it’s joined to a domain versus isolated for home use. As professional pentesters/red teamers we’re highly interested in post-exploitation techniques applicable to enterprise environments, which is why we started looking into ways to “attack” KeePass installations in the first place. Our targets are not isolated home users.

Other responses centered around the misconception that you need administrative access to perform most of these actions, that “all of this basically relies on getting the password from a keylogger“, or that the secure desktop setting negates everything mentioned. This post hopes to address all of those points.

Lee and I dove back into KeePass during the few days following the post’s release and came up with an additional approach that a) doesn’t need administrative rights, b) doesn’t require a keylogger, and c) negates the secure desktop protection (assuming the database is unlocked). If the database isn’t opened, see the Persistently Mining KeePass section of this post which details ways to execute this logic whenever KeePass launches.

The Exfiltration Without Malware – KeePass’ Trigger System section shows simple ways to dump all password entries on a database unlock without malware. This method also doesn’t need administrative rights nor a keylogger, and is also indifferent to the secure desktop protection.

Note: this write-up does not cover any ‘vulnerability’ in KeePass or a KeePass database/deployment and we are not claiming that we “broke” KeePass. There’s no CVE here and there’s no universal fix for this type of approach (though we do cover a few mitigation approaches in the Defenses section). We don’t really view this as an attack on KeePass specifically, as memory manipulation key recovery attacks are likely applicable to any other password managers by nature of an attacker operating in the same security context as the program. We’ll emphasize this point throughout the post, which will likely read as a ‘duh’ to many people: if a database is unlocked, the key material likely has to be somewhere in the process space, so we can probably extract it.

Here’s tl;dr :


1. Entering a KeePass master password, keyfile, and Windows User Account through “secure desktop”.


2. Extracting all three key material components from the memory space of the running KeePass.exe process with the unlocked database.


3. Entering the extracted key material with a patched KeePass installation on a separate computer with the exfiltrated database.


4. The exfiltrated database opened on another machine.

KeeThief is our open source project that is capable of extracting key material out of the memory of a running KeePass process with an unlocked database, including the plaintext of the master database password. It includes a C# executable/assembly and a .NET version 2.0 compatible, self-contained PowerShell script that works on stock Windows 7+. The project also includes a patched version of KeePass 2.34 that accepts the extracted key material to unlock an exfiltrated database (as seen in the above screenshots) instead of requiring the complete key file and/or Windows user account master keys. The KeeThief project code is live here.

KeeFarce’s Approach

Some of you probably heard of denandz’ awesome KeeFarce project, which made some waves at the end of last year. This approach was a bit of black magic to me when it came out, until Lee explained exactly how it worked and I dove into the source. Here’s how we currently understand the KeeFarce process:

  1. First KeeFarce loads a malicious DLL into the target KeePass process by using VirtualAllocEx()/CreateRemoteThread() to force a call LoadLibraryA() in order to load a bootstrap DLL off of disk.
  2. The bootstrap DLL loads the .NET common language runtime (CLR) and then loads a custom .NET assembly/DLL from disk once the CLR is started.
  3. The malicious assembly loads CLR MD and attaches to the current KeePass.exe process. It then walks the heap enumerating .NET objects, searching for a KeePass.UI.DocumentManagerEx object and saving information about this object. We’ll talk more about CLR MD in a bit.
  4. The malicious assembly then loads the KeePass assembly with reflection and instantiates a KeePass.DataExchange.PwExportInfo object. The malicious .NET assembly can do this because it’s operating in the same process space as the managed (.NET) KeePass.exe binary.
  5. A KeePass.DataExchange.Formats.KeePassCsv1x type is instantiated, additional export parameters are set, including the saved document manager data, and the export method is invoked. This exports all of the current database passwords to a .csv file in %AppData%.

One thing to note here is that in order to invoke methods of .NET objects on the heap of a CLR application, you must be in the same process space as the methods you’re targeting. So if you want to execute a specific KeePass .NET method (e.g. KeePass.DataExchange.Formats.KeePassCsv1x::Export), you must have code executing inside the KeePass.exe process space. Invoking .NET methods is a tall order (but not impossible) for straight shellcode, so the easiest method is to inject a .DLL à la the KeeFarce or Invoke-PSInject approach.

KeePass and Data Protection

I mentioned the Data Protection Application Programming Interface (DPAPI) briefly in the last post. DPAPI gives programmers a simple way to reasonably secure data on disk while a program is executing, where implicit per-user encryption keys are used to protect data “blobs” with minimal additional effort on the programmer’s part. The methods RtlEncryptMemory() and RtlDecryptMemory() can be used to protect data in memory, also with per-user (or per-process, depending on selection options) ephemeral keys being used to encrypt the data.

KeePass stores in-memory master key material as byte arrays using its internal ProtectedBinary class. This class encrypts these arrays by means of the the .NET class System.Security.Cryptography.ProtectedMemory class, which underneath calls the methods RtlEncryptMemory() and RtlDecryptMemory(). For in-memory/same process protection the OptionFlags parameter for these API calls is set to 0 (a.k.a. the “SameProcess” scope) which causes the call to, “Encrypt and decrypt memory in the same process. An application running in a different process will not be able to decrypt the data“. This means that the encrypted master keys can only be decrypted from within the KeePass.exe process*. We’ll come back to this in just a bit.

For the “Windows User Account” setting, KeePass stores a generated secret key as a DPAPI blob on disk at %APPDATA%\KeePass\ProtectedUserKey.bin. This data is encrypted using the user’s DPAPI master key and entropy specific to KeePass (see m_pbEntropy). This data is protected with a “CurrentUser” scope, which is why we were able to recover that key material from disk in the last post.

* That is, unless you write and load a driver which dumps the the per-process encryption keys from the kernel. See this Twitter thread with Benjamin Delpy, the author of Mimikatz.

KeeThief’s Approach

Both KeeThief and KeeFarce make use of “CLR MD”, aka the “Microsoft.Diagnostics.Runtime.dll” assembly released under the MIT license by Microsoft. This is a .NET/CLR process and crash dump introspection library which also allows for the attachment to live processes. It lets you do useful things like walk the heap of a live process for CLR objects and inspect the types/data for each, assuming you have access to the remote space (i.e. meaning same user/integrity level or administrative rights). Microsoft released some good getting started documentation in case anyone’s interested.

So let’s attach to the KeePass.exe process space using CLR MD and walk the heap objects until we find a KeePassLib.PwDatabase object (similar to KeeFarce’s initial approach). This is the currently opened KeePass database:

Now we can use the GetReferencedObjects() method to enumerate all the objects referenced by the database instances. We’ll first walk all objects looking for a KeePassLib.Serialization.IOConnectionInfo object (this is the open database file). This is so we can extract the opened database path:

Then we walk all referenced objects again, searching for any KeePassLib.Keys.KcpPassword, KeePassLib.Keys.KcpKeyFile, or KeePassLib.Keys.KcpUserAccount objects that are a part of a KeePassLib.Keys.CompositeKey. These objects are internal to KeePass and contain the protected data blobs for passwords, key files, and user account protections, respectively:

For each key object type, we enumerate the ProtectedBinary object associated with the key and ultimately pull out the protected “m_pbData” blobs which hold the in-memory protected byte arrays:

Here we hit a small roadblock. Since the binary blobs are protected with the “SameProcess” flag for RtlEncryptMemory(), we can’t just decrypt the data (since we’re not in the same process). The answer that Lee came up with is some simple shellcode that calls RtlDecryptMemory() to decrypt a specified encrypted blob. We can inject this into the running KeePass.exe process to ride on top of the per-process encryption keys, retrieving the result after decryption. This injection only requires permission to modify the KeePass process space (which the current user running KeePass.exe has); it doesn’t require administrative rights.

Since neither Lee nor I are shellcode experts, he used Matt Graeber‘s PIC_Bindshell project. This code (written in C) and Matt’s guidance on the subject greatly simplifies writing position-independent shellcode, and Lee was able to build x86/x64 shellcode that calls RtlDecryptMemory() on the encrypted data. The shellcode used by KeeThief is located in the ./DecryptionShellcode/ folder.

Since we can compile this project as a single self-contained C# binary we aren’t restricted to running a binary on disk, as .NET provides the [System.Reflection.Assembly]::Load(byte[] rawAssembly) static method which will load a .NET EXE/DLL into memory. Matt talked about this previously in his 2012 “In-Memory Managed Dll Loading With PowerShell” post. We used the the Out-CompressedDll PowerSploit function mentioned in the post to compress the resulting KeeThief assembly and load it in memory in a PowerShell script, invoking the GetKeePassMasterKeys() method.

Here’s the end result of the Get-KeePassDatabaseKey function with the decrypted plaintext key material for a running KeePass.exe process (on a stock Windows 7 machine):


Now we have the issue of how to reuse this plaintext data to open an exfiltrated database on another system. Luckily for us KeePass is open source and GPL’ed, so we can modify the source code to manually specify our extracted key material. If we modify the constructors of the KcpKeyFile.cs and KcpUserAccount.cs files to accept raw bytes of the unprotected key material, as well as some of the front-end UI forms (KeePromptForm.cs, KeePromptForm.Designer.cs) we can get the result that was seen in the initial screenshots. The “Base64 Key File” and “Base64 WUA” are the base64-encoded representations of the “plaintext” binary key material recovered by Get-KeePassDatabaseKey above.


This patched KeePass version is located in the ./KeePass-2.34-Source-Patched/ folder.

Also, since we’re not relying upon a keylogger to extract the master password, KeePass’ Secure Desktop feature (which prompts for the input of credentials in a high-integrity context similar to UAC) doesn’t come into play. If the database is unlocked, the key material likely has to be somewhere in the process space, so we can probably extract it.

KeeThief vs. KeeFarce

So why use KeeThief over KeeFarce?

KeeThief will decrypt the plaintext of the master database password, which could prove useful if reused. KeeThief is also built as a fully self-contained .NET assembly (instead of multiple files that are required to be on disk), so we can also load and execute it in a PowerShell script without touching disk. This is something KeeFarce is definitely capable of as well with a bit of refactoring, but the process will be more complex as it includes more unmanaged code, and a reflective DLL would likely need to be used. KeeFarce also uses the current public version of CLR MD, which by default is only compatible with .NET 4.0; this means that it won’t work with the stock PowerShell 2.0 installation on Windows 7, as powershell.exe is built against version 2.0 by default. Lee customized CLR MD to allow for compatibility with the 2.0 .NET CLR so KeeThief will work out of the box on stock Windows 7 installations.

The downside is that KeeThief will not (yet) pull out all passwords contained in the currently opened database as KeeFarce does. You will need to run the key extraction and also download the target KeePass database.

Persistently Mining KeePass

But wait, this requires the database to be unlocked right? I have autolock settings and only open my database for a few minutes at a time, so I’m safe.

Yes, agreed, the database must be unlocked in order to walk the proper objects on the KeePass heap. But admins who use KeePass tend to actually use KeePass at some point, so let’s think of a way to trigger out key extraction logic at the right moment.

The easiest method is to leave a hidden PowerShell script running that loops on an interval, enumerating any KeePass processes for key material and exiting once results are found. Note that this doesn’t require administrative rights, if we do happen to have admin rights on a target domain user’s machine (which isn’t unlikely in a real engagement unless this machine is the initial pivot) we can use WMI subscriptions similar to the first post to fire off the KeeThief logic. These are exercises left to the reader, but we can confirm that a proof-of-concept works.

Exfiltration Without Malware – KeePass’ Trigger System

If your only goal is to extract the password entries for any opened database, there’s an even easier way that doesn’t involve heap enumeration or code injection.

Lee noticed KeePass 2.X’s extensive  “trigger” framework, which lets you execute specific actions when certain KeePass events occur. The most interesting events for us are “Opened database file” which fires after a database file has been opened successfully, and “Copied entry data to clipboard” which will fire whenever usernames/passwords are copied to the clipboard. Two interesting actions are “Execute command line / URL”, which can execute shell commands, and “Export active database”, which can export the currently active database to a specified location. If we have write access to the KeePass.config.xml file linked to the currently running KeePass installation, we can trojanize the configuration XML to either launch KeeThief on database unlock (through the command line trigger) or export the database à la KeeFarce. Remember that KeePass.config.xml is located in the same directory as a portable KeePass.exe instance or at %APPDATA%\KeePass\KeePass.config.xml for an installed instance. You can use Find-KeePassconfig to enumerate all config locations.

For example, if you add the following to a KeePass.config.xml it will dump each opened database to C:\Temp\<database_name>.csv, regardless of additional key files/user account mixins (this is actually an example from KeePass):


The “Export active database” action also accepts \\UNC paths as well as URLs, so you could build a trigger that exfiltrates a .csv export of any database to a capture site as soon as it’s opened.

The “Copied entry data to clipboard” event is great as well when paired with the “Execute command line / URL” action. In order to prevent a window from showing to the user (as it would if we launched powershell.exe or cmd.exe) let’s call C:\Windows\System32\wscript.exe to trigger a .vbs file stored on disk that will handle the local storage (or remote exfiltration) of any credential entry that’s copied to the clipboard. Here’s the exfil.vbs file and the XML trigger configuration:



Both KeeThief and KeeFarce require injecting code into KeePass.exe. This is something that some defensive solutions can catch, as it mirrors other typical shellcode injection processes. The best thing you can do is use a host-based monitoring system and monitor for cross-process interactions with KeePass (opening process handles, allocating/reading/writing memory, and creating remote threads). For example, this could be accomplished (for free!) using Sysmon and Windows Event Forwarding to monitor for abnormal CreateRemoteThread events (Event ID 8) with KeePass.exe as the TargetImage, as well as monitoring the forthcoming ProcessOpen event. Several EDR systems (e.g. CarbonBlack) also have detection capabilities for cross-process interaction.

There’s not really a good protection against KeePass.config.xml modification. As KeePass states, “If you use the KeePass installer and install the program with administrator rights, the program directory will be write-protected when working as a normal/limited user. KeePass will use local configuration files, i.e. save and load the configuration from a file in your user directory“. This means that whether a user is using a portable or installed instance, an attacker within that user’s context will almost certainly have the ability to insert malicious triggers. You could try to modify the ACLs of the KeePass.config.xmls to remove all write access once you have the settings you want saved, but if the current user is a local administrator this ultimately wouldn’t be a complete fix. From a defensive standpoint, it would be a good idea to inventory all user KeePass.config.xmls and examine them for malicious triggers. The ./PowerShell/KeePassConfig.ps1 file has methods to do this: Find-KeePassConfig | Get-KeePassConfigTrigger.

In addition to host based monitoring, if you enroll KeePass.exe in Microsoft’s awesome Enhanced Mitigation Experience Toolkit (EMET) it will detect the shellcode injection through its EAF mitigation and create a log entry. The bad news is that we still get the key material, so if you see something like the following we’d recommend starting incident response procedures and rolling passwords for accounts in any opened databases:


We should note that while this is a great best practice, it’s also likely not a silver bullet. Josh Pitts (@midnite_runr) and Casey Smith (@subtee) did some awesome research this year on “The EMET Serendipity: EMET’s (In)Effectiveness Against Non-Exploitation Uses“. The tl;dr is that you can bypass EMET with custom shellcode if LoadLibraryA/GetProcAddress is in the IAT of your target process (or one of its libraries) …which is the case with emet.dll. We’re assuming that this approach for KeeThief’s shellcode likely wouldn’t be too hard for someone with the background and motivation, but using EMET increases the bar and creates another opportunity for the attacker to make a mistake and be detected.

In addition to host-based monitoring, organizations should take steps towards segregating IT workstations from normal day-to-day operations and reducing their reliance on passwords. Building Privileged Access Workstations and restricting KeePass usage to only those hosts will go a long ways in reducing credential theft in general. In addition, take steps towards using technologies such as Group Managed Service Accounts so administrators don’t have to manage passwords at all. Remember: it’s impossible to steal passwords from KeePass if they’re never stored there in the first place :)


To reiterate from the last KeePass post, KeePass is not “bad” or “vulnerable” – it’s a much better solution than what we see in many environments, and the developers did pretty much everything right when coding it (including strong in-memory protections and DPAPI). Still, some admins/companies sometimes tend to see solutions like this as a silver bullet, so one point of this post is to (again) show that practical attack vectors against KeePass and similar vaults are not unrealistic. Our intention is not to convince anyone NOT to use a password manager (we believe you definitely SHOULD use a password manager), but rather to combat the false sense of security it may give some users.

For those who feel that 2-factor is silver bullet as far as local password managers go, we would caution you yet again: the resulting key material is likely in memory somewhere if the database is unlocked, and the method of unlocking ultimately doesn’t matter if the KeePass.config.xml is modified. KeePass knows these issues the trigger system was intended functionality and KeePass doesn’t consider tools like KeeFarce a threat. We agree that protecting your program against a malicious attacker operating in the same security context is an extremely difficult problem.

As an aside, this project was developed off hours by two of our ATD team members purely out of research interest. You can imagine what an advanced adversary with much more talent, funding, time, and manpower could produce against other password manager solutions in a targeted operation.