In this blog post we will explain how you can enumerate Active Directory from Cobalt Strike using the Active Directory Service Interfaces (ADSI) in combination with C/C++. This may help staying under the radar in environments where PowerShell and .NET are heavily monitored.
Imagine you are in a TIBER, CBEST or other long-term red team assessment and finally managed to get a foothold into the target’s network. Many hours of preparation finally pay off and your payload plus C2 channel bypass the target’s security measures. How can you run your tools for the next steps in your attack (such as Active Directory reconnaissance), without triggering modern defenses on the compromised host such as AMSI or script block logging?
The following topics are covered in the remainder of this blog post:
- Why you might want to avoid tools which are based on PowerShell and C# in some situations.
- How you can implement the functionality of Active Directory reconnaissance tools such as PowerView and SharpView in C/C++ using ADSI.
- How to embed this functionality in your favorite C2 framework / post-exploitation toolkit using reflective DLLs.
Bye bye PowerShell for AD recon
A lot of good tools have been developed to perform reconnaissance in an Active Directory environment. Tools written in PowerShell have long been the standard (most notably PowerView), but with the introduction of PowerShell 5 new detection optics have been introduced. The Antimalware Scan interface (AMSI) and script block logging gave eyes to the blue team and, when properly implemented, defenders are now finally empowered to detect malicious PowerShell usage. So, we don’t want to risk hours of work spent on establishing initial foothold by simply firing up a PowerShell script – with high chances of being detected or at least having our actions logged.
Even if we bypass AMSI, we still have to defeat script block logging, which often requires a registry change that can be monitored with tools like Sysmon for example. Now in many mature environments, using PowerShell for offensive actions is not an OPSEC-safe choice.
Who wins the battle on .NET?
With the introduction of optics for defenders in PowerShell 5, many threat actors, pentesters and red teamers realized that they needed to move away from PowerShell. Many PowerShell scripts already used a lot of .Net/C# code to do the heavy lifting, so why not use C# directly?
Another very interesting characteristic of .NET for attackers exists within the .NET Reflection library and this is the Assembly.Load method. This method allows one to load a .NET assembly from memory without touching disk. Now we can pack our new shiny offensive C# tools into a Byte array and use the Load(Byte) method to invoke the tool from memory. Tools like Cobalt Strike and Metasploit use the execute-assembly technique by injecting the .NET Common Language Runtime (CLR) into a spawned process and use this method to reflectively load and execute a C# program within this process.
These features of .NET are some of the main reasons why during the last few years we have seen many tools being ported from PowerShell to C#. For this blog post’s use case of Active Directory reconnaissance, SharpView is an excellent example of such a port from PowerShell to C#.
However, Microsoft also noticed this shift and recently responded by integrating the Antimalware Scan Interface into the latest version of .NET (v4.8). Now defenders also have methods to detect malicious usage of C# using AMSI by scanning the Byte array before getting invoked by the CLR. This is great for defenders but not so great for attackers.
Bypasses already exist and while this only works with the latest 4.8 version of .NET, the battle for C#/.NET has definitely begun. Defenders are already experimenting with techniques such as CLR hooking and using Event Tracing for Windows (ETW) to catch .NET abuse. It is not very difficult to predict that offensive .NET tradecraft will get more and more complicated in the coming years.
Back to the future: C/C++
So how about using good old C or C++ for developing an offensive tool? You might be worried about having to drop your compiled tools on disk. With PowerShell and C# you were able to inject a program into memory, without touching disk (e.g. using the assembly.load method). Can this also be achieved with tools developed in C/C++? The answer is yes: to inject a compiled C/C++ program reflectively in memory you have quite some options too. Some of our current favorites are:
- Donut‘s PE loader (good write-up here)
- Reflective DLL injection, original and improved
- Shellcode reflective DLL injection
I assume that most readers of this blogpost are familiar with the Reflective DLL injection technique. If not I recommend reading the overview on Stephen Fewer’s Github page.
Active Directory Service Interfaces (ADSI)
Let’s get back to our use case: Active Directory reconnaissance. We want to start digging in AD without triggering AMSI or leaving artefacts in the event log of the compromised host. Let’s see if we can do this with C/C++ and stay a bit more under the radar than with tools such as PowerView and SharpView. To interface with the Active Directory and enumerate its objects and attributes we can use the Active Directory Service Interfaces (ADSI). But what is ADSI exactly?
“Active Directory Service Interfaces (ADSI) is a set of COM interfaces used to access the features of directory services from different network providers.”
“Independent Software Vendors and end-user developers can use ADSI to directory enable their products and applications.”
Okay that sound good… Let’s see if we can build our own Active Directory enumeration client in C/C++. To do this, we could read all the documentation and write a new client application. But we could also take a look at the great examples Microsoft already provided to us within this Git Repository and use these examples to develop our own client (no need to reinvent the wheel). Let’s look at the following example for instance: QueryUsers
“The QueryUsers sample queries an Active Directory domain partition for user objects that match a specified filter.”
“The sample uses the IDirectorySearch interface to perform the search.”
With this code we can search for specific users (or all users) and return all properties for the identified user(s). So how does this code work in a nutshell:
- ADSI is built upon COM, so we need to initialize COM within our client using the CoInitialize() function.
- Next we use ADsOpenObject() to bind to the LDAP rootDSE so we can collect information about the directory and use the returned IADs COM object to get the defaultNamingContext.
- When we have the AD defaultNamingContext, we can use ADsOpenObject again to bind to the Domain container and use it to return an IDirectorySearch COM interface, which can be used to query/search the Active Directory.
- When the FindUsers function is called it will construct an LDAP syntax filter based on the function parameter and the following string: “(&(objectClass=user)(objectCategory=person)%s)”. If we supplied the following search filter as a parameter for the program: “(sAMAccountName=Administrator)”, our search filter would become: “(&(objectClass=user)(objectCategory=person) (sAMAccountName=Administrator))”
- Search preference are set using an ADS_SEARCHPREF_INFO structure.
- Now the ExecuteSearch method from the IDirectorySearch object is executed, which should return all results based on our LDAP filter.
- Finally, the specific user attributes are printed while looping through the results using the GetFirstRow, GetNextColumnName, GetColumn and GetNextRow methods.
With a relatively good understanding of this code and some knowledge about the ADSI programming interfaces, we could now continue and build our own custom enumeration client(s) in C/C++.
Integration with your favorite C2 framework
But how can you integrate such a tool written in C/C++ in your favorite C2 / post-exploitation framework? For example, Cobalt Strike is a very popular Adversary Simulations toolkit for Red Teams, which we also happen to use a lot during our assessments. Cobalt Strike has several options for injecting code and DLLs and has a powerful scripting language which can be used to extend and modify the Cobalt Strike client. For example, the bdllspawn is a very useful function if you want to execute a C/C++ (reflective) DLL in memory and still be able to provide arguments to the program.
Building your own C2 framework? Use your favorite shellcode injection technique in combination with Donut or sRDI and you’re ready to go. You might want to have a look at pinjectra for some inspiration on injection techniques. If you’re worried about your injection calls being hooked, read the previous blog post in this series on unhooking using direct syscalls.
As a proof of concept, we developed an Active Directory reconnaissance tool based on ADSI and reflective DLLs which can be used within Cobalt Strike. The tool is called “Recon-AD” and at this moment consist of seven Reflective DLLs and a corresponding aggressor script.
The tool is available on our Github Page.
The following functionality is included:
- Recon-AD-Domain: to enumerate Domain information (Domain name, GUID, site name, password policy, DC list e.g.).
- Recon-AD-Users: to query for user objects and corresponding attributes.
- Recon-AD-Groups: to query for group objects and corresponding attributes.
- Recon-AD-Computers: to query for computer objects and corresponding attributes.
- Recon-AD-SPNs: to query for user objects with Service Principal Names (SPN) configured and display useful attributes.
- Recon-AD-AllLocalGroups: to query a computer for all local groups and group-members.
- Recon-AD-LocalGroups: to query a computer for specific local groups and group-members (default Administrators group).
The following screenshots show some examples on how to use the tool:
- Download the Recon-AD tool here and load the Recon-AD script within the Cobalt Strike Script Manager.
- To show information about the local Domain use the Recon-AD-Domain command:
- To list the attributes of the Domain Admins group we can use the “Recon-AD-Groups Domain Admins” command.
- To list the attributes of a specific user we can use the “Recon-AD-User username” command.
Furthermore, there is a command which enables you to enumerate computer objects, a command to list all SPN enabled user accounts and commands to enumerate local groups on remote machines using the WinNT ADSI provider. We’re planning to publish more useful enumeration options soon.
Options exist to detect reflectively injected DLLs within memory by using advanced memory scanning techniques and API hooking. This is something a good EDR solution should be capable of to a certain extent. If you stumble upon a client using advanced detection software like an EDR, there are some configuration options within Cobalt Strike’s malleable profile which should assist in Memory Detection evasion. Also make sure to check out our previous blog post in this series on unhooking.
Optics for defenders into ADSI are limited. Event Tracing for ADSI does exist, but we believe this might be difficult to gather and monitor at scale.
Of course, Active Directory reconnaissance can also be detected on a domain level, but that might be the topic for a future blog post. In this blog post we focussed on evading detection at the originating host.
New monitoring and defense optics are being applied within Microsoft operating systems and security products. This should help defenders in detecting malicious behavior within their environments. While PowerShell has long been very popular for post exploitation, now it’s something attackers try to avoid. .NET is the current hype for offensive tradecraft, but Microsoft is rapidly developing new measures by adding optics to catch malicious behavior on this platform. Time will tell how long attackers are able to use the .NET platform for offensive operations.
In this blog we touched upon the Active Directory Service Interfaces (ADSI) programming interface and how it can be used with C/C++ to enumerate the Active Directory within the Cobalt Strike client console.
This example should provide some insight into modern tradecraft on moving away from PowerShell and .NET and help you stay under the radar from the latest monitoring and defense technologies being applied within modern environments.
Any feedback or additional ideas? Let me know!