Pass-the-Hash Is Dead: Long Live LocalAccountTokenFilterPolicy

Nearly three years ago, I wrote a post named “Pass-the-Hash is Dead: Long Live Pass-the-Hash” that detailed some operational implications of Microsoft’s KB2871997 patch. A specific sentence in the security advisory, “Changes to this feature include: prevent network logon and remote interactive logon to domain-joined machine using local accounts…” led me to believe (for the last 3 years) that the patch modified Windows 7 and Server 2008 behavior to prevent the ability to pass-the-hash with non-RID 500 local administrator accounts. My colleague Lee Christensen recently pointed out that this was actually incorrect, despite Microsoft’s wording, and that the situation is more nuanced than we initially believed. It’s worth noting that pwnag3’s seminal “What Did Microsoft Just Break with KB2871997 and KB2928120” article also suffers from the same misunderstandings that my initial post did.

We now have a better understanding of these topics and wanted to set the record straight as best we could. This is my mea culpa for finally realizing that KB2871997, in the majority of situations, had absolutely nothing to do with stopping “complicating” the use of pass-the-hash in Windows enterprises. Apologies for preaching the incorrect message for nearly 3 years- I hope to atone for my sins :) And as always, if there are errors in this post, please let me know and I will update!

Clarifying KB2871997

So what did this patch actually do if it didn’t automatically “prevent network logon and remote interactive logon to domain-joined machines using local accounts”? As Aaron Margosis describes, the patch introduced, among many other changes, two new security identifiers (SIDs): S-1-5-113 (NT AUTHORITY\Local account) and S-1-5-114 (NT AUTHORITY\Local account and member of Administrators group). As detailed in the Microsoft article, these SIDs can be used through group policy to effectively block the use of all local administrative accounts for remote logon. Note that while KB2871997 backported these SIDs to Windows 7 and Server 2008/2012, they were incorporated by default in the Windows operating system from Windows 8.1 and Server 2012 R2+. This is something that Sean Metcalf has previously mentioned and Aaron specifically clarified in the comments of that Microsoft post.

Sidenote: Luckily for us, this also means that any user authenticated on the domain can enumerate these policies and see what machines have these restrictions set. I’ll cover how to perform this type of enumeration and correlation in a future post.

I assumed, incorrectly, that this patch modified existing behavior on Windows 7 machines. Since Windows Vista, attackers have been unable to pass-the-hash to local admin accounts that weren’t the built-in RID 500 Administrator (in most situations, see more below). Here we can see that KB2871997 is not installed on a basic Windows 7 install:

Yet executing pass-the-hash with the ‘admin’ non-RID 500 account that’s a member of local Administrators fails:

So this behavior existed even before the KB2871997 release. Part of this confusion was due to the language used in the security advisory, but I take responsibility for not testing the situation fully and relaying the correct information. While we do highly recommend Aaron’s recommendations of deploying GPOs with these new SIDs to help mitigate lateral spread, we also reserve the right to still smirk at the KB’s original title ;)

Remote Access and User Account Control

So if the patch isn’t affecting this behavior, what is preventing us from using pass-the-hash with local admin accounts? And why does the RID 500 account operate as a special case? Adding to that, why are domain accounts that are members of local administrators exempt from this blocking behavior as well? Also, over the past several years we’ve also noticed on some engagements that pass-the-hash will still work with non-RID 500 local admin accounts, despite the patch being applied. This behavior always bugged us but we think we can finally explain all these inconsistencies.

The actual culprit for all these questions is user account control (UAC) token filtering in the context of remote access. I always thought of UAC solely in the context of local host actions but there are various implications for remote situations as well. The ”User Account Control and Remote Scenarios” section of the Microsoft “Windows Vista Application Development Requirements for User Account Control Compatibility” document and “Description of User Account Control and remote restrictions in Windows Vista” post both explain a lot of this behavior, and clarified several points for me personally.

Tl;dr for any non-RID 500 local admin account remotely connecting to a Windows Vista+ machine, whether through WMI, PSEXEC, or other methods, the token returned is “filtered” (i.e. medium integrity) even though the user is a local administrator. Since there isn’t a method to remotely escalate to a high-integrity context, except through RDP (which needs a plaintext password unless ‘Restricted Admin’ mode is enabled) the token remains medium integrity. So when the user attempts to access a privileged resource remotely, e.g. ADMIN$, they receive an “Access is Denied” message despite technically having administrative access. I’ll get to the RID 500 exception in a bit ;)

For local user accounts in a local “Administrators” group, the “Windows Vista Application Development Requirements for User Account Control Compatibility” document describes the following behavior:

When a user with an administrator account in a Windows Vista computer’s local Security Accounts Manager (SAM) database remotely connects to a Windows Vista computer, the user has no elevation potential on the remote computer and cannot perform administrative tasks.

Microsoft’s “Description of User Account Control and remote restrictions in Windows Vista” post describes this in another way:

When a user who is a member of the local administrators group on the target remote computer establishes a remote administrative connection…they will not connect as a full administrator. The user has no elevation potential on the remote computer, and the user cannot perform administrative tasks. If the user wants to administer the workstation with a Security Account Manager (SAM) account, the user must interactively log on to the computer that is to be administered with Remote Assistance or Remote Desktop.

And for domain user accounts in a local “Administrators” group, the document states:

When a user with a domain user account logs on to a Windows Vista computer remotely, and the user is a member of the Administrators group, the domain user will run with a full administrator access token on the remote computer and UAC is disabled for the user on the remote computer for that session.

So that explains why local admin accounts fail with remote access (except through RDP) as well as why domain accounts are successful. But why does the built-in RID 500 Administrator account act as a special case? Because by default the built-in administrator account (even if renamed) runs all applications with full administrative privileges (“full token mode”), meaning that user account control is effectively not applied. So when remote actions are initiated using this account, a full high-integrity (i.e. non-filtered) token is granted, allowing for proper administrative access!

There is one exception- “Admin Approval Mode”. The key that specifies this is at HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System\FilterAdministratorToken and is disabled by default. However, if this key is enabled, the RID 500 account (even if it’s renamed) is enrolled in UAC protection. This means that remote PTH to the machine using that account will then fail. But there’s a silver lining for attackers- this key is often set through Group Policy, meaning that any domain authenticated user can enumerate what machines do and do not have FilterAdministratorToken set through the application of GPOs. While this will miss cases where the key is set on a standard “gold” image, performing this key enumeration from the initial machine an attacker lands on, combined with GPO enumeration, should cover most situations.

And remember that while Windows disables the built-in -500 Administrator account by default, it’s still fairly common to see it enabled across enterprises. My original pass-the-hash post covered basic remote enumeration of this information, and this post goes into even more detail.


There’s another silver lining for us attackers, something that has much more defensive implications than we initially realized. Jonathan Renard touched on some of this (as well as Admin Approval Mode) in his “*Puff* *Puff* PSExec” post, but I wanted to expand just a bit in relation to the overall pass-the-hash discussion.

If the HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System\LocalAccountTokenFilterPolicy key exists (which doesn’t by default) and is set to 1, then remote connections from all local members of Administrators are granted full high-integrity tokens during negotiation. This means that a non-RID 500 account connections aren’t filtered and can successfully pass-the-hash!

So why would you possibly set this registry entry? Googling for the key name will turn up different scenarios where this functions as a workaround, but there’s one frequent violator: Windows Remoting. There is a non-trivial amount of Microsoft documentation that recommends setting LocalAccountTokenFilterPolicy to 1 as a workaround or solution to various issues:

In addition, I believe there are some situations where the WinRM quickconfig may even set this key automatically, but I was not able to reliably recreate this scenario. Microsoft’s “Obtaining Data from a Remote Computer” document further details:

Because of User Account Control (UAC), the remote account must be a domain account and a member of the remote computer Administrators group. If the account is a local computer member of the Administrators group, then UAC does not allow access to the WinRM service. To access a remote WinRM service in a workgroup, UAC filtering for local accounts must be disabled by creating the following DWORD registry entry and setting its value to 1: [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System] LocalAccountTokenFilterPolicy.

This is bad advice, BAD BAD BAD BAD BAD! I realize that this setting may be needed to facilitate some specific WinRM deployment scenarios, but once LocalAccountTokenFilterPolicy is set to 1 then ANY local administrator account on a machine can be used to pass-the-hash to the target. I feel that most people, myself included, have not realized the actual security implications of this modification. The only real warning I saw through all of the Microsoft documentation was “Caution: The LocalAccountTokenFilterPolicy entry disables user account control (UAC) remote restrictions for all users of all affected computers. Consider the implications of this setting carefully before changing the policy”. As this setting enables a large amount of risk for an enterprise environment, I hoped for a better set of definitive guidance and warnings from Microsoft beyond “consider the implications”, but ¯\_(ツ)_/¯

Operationally (from an offensive perspective) it’s good to check if your pivot machine has the LocalAccountTokenFilterPolicy key set to 1, as other machines in the same subnet/OU may have the same setting. You can also enumerate Group Policy settings to see if this key is set through GPO, something again that I will cover in a future post. Finally, you can use PowerView to enumerate any Windows 7 and Service 2008 machines with Windows Remoting enabled, hoping that they have run some kind of Windows Remoting setup incorrectly:

It’s also worth noting that Microsoft’s LAPS effectively renders everything here moot. As LAPS randomizes the local administrator password for machines on a periodic basis, pass-the-hash will effectively still work, but it greatly limits the ability to recover and reuse local key material. This renders traditional PTH attacks (with local accounts at least) largely ineffective.

Have fun!

Targeted Kerberoasting

This is a short followup demonstrating a technique that dawned on me after posting about decrypting AS-REPs earlier this week. As mentioned previously, @_wald0, @cptjesus, and I are currently working Active Directory ACL integration for BloodHound. One of the control relationships we’re interested in is GenericAll/GenericWrite over a target user object, say victimuser in this instance. If we want to utilize the user’s access, we could force a password reset, but this is fairly ‘destructive’ in that the target user would notice. We’ve been brainstorming another method to abuse these types of relationships with the target remaining unaware, and we believe we now have another option.

Given GenericWrite/GenericAll DACL rights over a target, we can modify most of the user’s attributes, save for attributes related to delegation and other protected components like sidHistory. However, we can change a victim’s userAccountControl to not require Kerberos preauthentication, grab the user’s crackable AS-REP, and then change the setting back:

Then it dawned on me: why not execute this with ‘normal’ Kerberoasting instead, taking advantage of existing John the Ripper and Hashcat cracking modules. Given modification rights on a target, we can change the user’s serviceprincipalname to any SPN we want (even something fake), Kerberoast the service ticket, and then repair the serviceprincipalname value. And the best part is that everything needed is already implemented in PowerView with Set-DomainObject and Get-DomainSPNTicket!

This approach is still dependent on the target user having a weak/crackable password, but it’s a nice alternative to force-resetting the user’s password. And while the modified SPN doesn’t remain in the domain to be detected by defensive sweeping, there are event logs that can be enabled to detect these types of specific malicious modification. If you have elevated (i.e. Domain Admin) rights, you can always ‘downgrade’ a user to reversible encryption and then DCSync their plaintext password, so this approach is only really useful in cases where you encounter these type of rights before you’re able to elevate on the domain itself.

Roasting AS-REPs

Last November, I published a post titled “Kerberoasting Without Mimikatz” that detailed new developments with PowerView and Tim Medin‘s Kerberoasting attack. This started me down the path of looking at Kerberos just a bit more closely. Then a few weeks ago, my coworker Lee Christensen found an interesting presentation from Geoff Janjua of Exumbra Operations titled “Kerberos Party Tricks: Weaponizing Kerberos Protocol Flaws“, slides and toolkit located here. One of the interesting points that Geoff mentioned, and that his Python-based “Party Trick” toolkit executes, was abusing user accounts that don’t require Kerberos preauthentication.

I recently dove much deeper into this topic and wanted to share what I was able to learn and develop. This post will give some detailed background on the aspect of Kerberos we’re abusing, what the precise issue is, how to easily enumerate accounts that don’t need preauth, how to extract crackable hashes in these situations, and finally how to crack these retrieved hashes efficiently. There is also an associated PowerShell toolkit, ASREPRoast, that is now live on GitHub.

tl;dr – if you can enumerate any accounts in a Windows domain that don’t require Kerberos preauthentication, you can now easily request a piece of encrypted information for said accounts and efficiently crack the material offline, revealing the user’s password.

Note: this isn’t anything revolutionary, and obviously isn’t as useful as Kerberoasting, as accounts have to have DONT_REQ_PREAUTH explicitly set for them to be vulnerable – you’re still reliant upon weak password complexity for the attack to work. However, this setting still exists on some accounts in some environments, we’re just not sure as to the frequency as it’s not something we normally looked for before. Our guess is that it’s likely enabled for older accounts, specifically Unix-related ones. If you happen to find it “in the wild”, we’d love to hear from you ;) (@harmj0y or will [at]

[Edit] if you have GenericWrite/GenericAll rights over a target user, you can maliciously modify their userAccountControl to not require preauth, use ASREPRoast, and then reset the value ;)


I’m not going to go through all aspects of Kerberos, as people like Sean Metcalf have already done a great job of this. If terms like AS-REQ and AS-REP are completely foreign to you, I would recommend reading Sean’s post for some basic background first. The aspect we care for the purposes of this post is something called Kerberos preauthentication.

Under normal operations in a Windows Kerberos environment, when you initiate a TGT request for a given user (Kerberos AS-REQ, message type 10) you have to supply a timestamp encrypted with that user’s key/password. This structure is PA-ENC-TIMESTAMP and is embedded in PA-DATA (preauthorization data) of the AS-REQ – both of these structure are described in detail on page 60 of RFC4120 and were introduced in Kerberos Version 5. The KDC then decrypts the timestamp to verify if the subject making the AS-REQ really is that user, and then returns the AS-REQ and continues with normal authentication procedures.

Note: the KDC does increase the badpwdcount attribute for any incorrect PA-ENC-TIMESTAMP attempts, so we can’t use this as a method to online brute-force account passwords :(

The reason for Kerberos preauthentication is to prevent offline password guessing. While the AS-REP ticket itself is encrypted with the service key (in this case the krbtgt hash) the AS-REP “encrypted part” is signed with the client key, i.e. the key of the user we send an AS-REQ for. If preauthentication isn’t enabled, an attacker can send an AS-REQ for any user that doesn’t have preauth required and receive a bit of encrypted material back that can be cracked offline to reveal the target user’s password.

This is something that has been known for a long time, after all, it’s the reason preauth was implemented in Kerberos! In modern Windows environments, all user accounts require Kerberos preauthentication, but interestingly enough, by default Windows attempts the AS-REQ/AS-REP exchange without preauthentication first, falling back to supplying the encrypted timestamp on the second submission:

I have no idea why this behavior happens ¯\_(ツ)_/¯

[Edit] @munmap pointed out on Twitter that this behavior is due to the client not knowing the supported ETYPES ahead of time, something explicitly detailed in section 2.2 of RFC6113.

However, Windows offers a way to manually disable this protection for specific accounts through a useraccountcontrol modification:

If you’re already an authenticated (but otherwise unprivileged) user, you can easily enumerate what users in the domain have this setting with the LDAP filter (userAccountControl:1.2.840.113556.1.4.803:=4194304). PowerView‘s Get-DomainUser already has this implemented with the -PreauthNotRequired parameter:

So now we know what the issue is and how to identify vulnerable users. While people have executed brute-forcing of the AS-REQ’s PA-ENC-TIMESTAMP component of Kerberos exchanges for well over a decade (the hash format is even in Hashcat, -m 7500/ ‘Kerberos 5 AS-REQ Pre-Auth’) the only toolset I’ve seen that attacks RC4 AS-REPs is Geoff’s Python toolkit. We wanted something that was Windows based that also didn’t need administrative privileges on a machine to allow us flexibility in our attack workflow. We also wanted a faster way to crack these hashes.


My first hope was to find something in .NET that exposed the raw bytes of the AS-REP similar to the Kerberoasting approach. I spent a while searching for any .NET method that would allow access to the raw byte response of the AS-REP and unfortunately came up short. Though I can’t say definitively if this is impossible, my gut feeling is that it’s likely an abstraction level too deep for us to access easily through .NET. Even if there was, we would still have one complication, as modern Windows Kerberos environments default to the the AES256-CTS-HMAC-SHA1-96 encryption in the AS-REP instead of the much quicker ARCFOUR-HMAC-MD5/RC4 approach. RC4-HMAC is significantly quicker to crack, so we prefer it if possible.

The approach I ended up taking was to construct the AS-REQ by hand in order to control the necessary parameters, and parsing the KDC’s AS-REP response in order to determine success/failure and extract the encrypted material. Here was another roadblock- Kerberos uses ASN.1 encoding for its structures, something that .NET does not have built in encoders or decoders for. Luckily, there is an open source C# version of the Bouncy Castle crypto library that features, among many, many other things, robust capability for ASN.1 encoding and decoding.

Unfortunately, I don’t have time to give a full ASN.1 tutorial, but I will share a few pointers that helped me while developing this tool. The specifications we care about for the AS-REQ are laid out on page 55 of RFC1510 and page 74 of RFC4120. Benjamin Delpy also documents all these ASN.1 structures amazingly in his Kekeo project. Here’s the structure description:

Another thing that helped me a lot was to Wireshark legitimate Kerberos exchanges, export the Kerberos packet bytes, and visualize the data using this JavaScript ASN.1 decoder:

This particularly helped during the next part, which was learning how to use Bouncy Castle through PowerShell to construct a proper ASN.1 encoded AS-REQ. But after a few struggles with tagging and finding the correct data structures, I came up with New-ASReq, which takes a user/domain name, builds the properly nested components, and returns the raw bytes for the request.

And because we’re building this by hand, we can include or omit anything we want. So we can include just the ARCFOUR-HMAC-MD5 etype instead of all supported encryption etypes. This type and its use in Windows Kerberos auth is explained in detail in RFC4757. What’s especially nice is that section 3 includes the message types for different uses of the algorithm. While the AS-REP ticket uses type 2 like a TGS-REP ticket (i.e. kerberoasting) this component of the response is encrypted with the service key, which in this case is the krbtgt hash and therefore not crackable. However, the AS-REP encrypted part, which is the section we can essentially ‘downgrade’ to RC4-HMAC, is the same algorithm but of message type 8. This will come into play later during the cracking section.

A second function in ASREPRoastGet-ASREPHash, wraps New-ASReq to generate the appropriate AS-REQ for a specific user/domain, enumerates a domain controller for the passed domain, sends the crafted AS-REQ, and receives the response bytes. Bouncy Castle is used to decode the response, checking whether it is a KRB-ERROR response or a proper AS-REP. If the request succeeded, we can extract out the enc-part section that’s RC4-HMAC encrypted using the specified user’s hash and return it in a nice format:

The final useful function in ASREPRoast is Invoke-ASREPRoast. If run from a domain authenticated, but otherwise unprivileged, user context in a Windows Kerberos environment, this function will first enumerate all users who have “Do not require Kerberos preauthentication” set in their user account control settings by using the LDAP filter (userAccountControl:1.2.840.113556.1.4.803:=4194304). For each user returned Get-ASREPHash is used to return a crackable hash:

Cracking The Hashes

We now have a nice set hash representations of RC4-HMAC AS-REPs, each of which are encrypted with a user’s password. We should now be able to crack these offline à la Kerberosting (krb5tgs format in John the Ripper), but remember that despite using the same algorithm and approach as the existing TGS-REP format, the message type here is 8 instead of 2.

This unfortunately means that existing plugins won’t work, but luckily for us, all we have to do is change this line to an 8 instead of a 2, remove some of the specific TGS ASN.1 speedups, and change the format naming. I have a included a tweaked version of this krb5_asrep_fmt_plug.c plugin with the ASREPRoast project. Simply drop it into the source folder for Magnumripper, run the normal build instructions, and you’d good to go for cracking the output of ASREPRoast.ps1:

I believe that it should be simple to modify Hashcat’s existing TGS-REP format as well in a similar way, but I haven’t attempted it yet. Also, because this is the same algorithm as the krb5tgs/Kerberoasting format, just with a tweak in key material, performance should be similar to the existing modules.

Closing Thoughts

As I mentioned at the beginning, this obviously isn’t as useful as the Kerberoasting attack, as accounts have to have DONT_REQ_PREAUTH explicitly set for them to be vulnerable, and you’re still reliant upon weak password complexity for the attack to work. However, this setting is sometimes present in some environments, often on aging accounts for backwards compatibility reasons, and we feel that the toolset will be operationally useful in some situations at least.

Defensively, the same protections outlined for Kerberoasting apply here, specifically have really long passwords for these types of accounts and alert when abnormal hosts are sent an AS-REP for the account. Also, audit what accounts have this setting, which is easy with PowerView (Get-DomainUser -PreauthNotRequired) or other LDAP toolsets with the (userAccountControl:1.2.840.113556.1.4.803:=4194304) filter. Carefully consider whether accounts with this setting truly are needed.

[Edit] also for the defensive side, @munmap suggested investigating Kerberos FAST pre-authentication and/or Public Key Cryptography for Initial Authentication in Kerberos (PKINIT).

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.