AADInternals-Endpoints 😈
In September 2024, I tweeted about my intentions to split AADInternals module in two as AVs and MDE was blocking its installation.
In this blog, I’ll explain the what and the why of the new AADInternals-Endpoints 😈 module.
AADInternals-Endpoints: Why??
AADInternals have been out there since 2018. At the time writing this blog, it has over 230 000 dowloads from PowerShell Gallery ❤. As you can see from the documentation, it has a lot of functions created for various purposes during the years.
Since the early days, the purpose of the module has been to allow users to test their security posture using same TTPs threat actors are using. The functionality included in the module can be categorised under two categories. First, some functions are used to operate on endpoints, such as users’ laptops or servers related to hybrid identities. For instance, you can export certificates of Entra ID joined devices or export Entra ID Connect Sync service credentials. The second category of the functionality is “cloud” functions, that can be used to add, remove, or modify things in the cloud.
As the endpoint related functionality is actually accessing stuff in a computer it is running, anti-virus and products like Microsoft Defender for Endpoint (MDE) has been blocking the installation of AADInternals module. I’ve received a lot of request to do something about that (but no, I can’t nor won’t disable MDE detections 😜)
To allow people of using AADInternals functionality that doesn’t even in theory do any harm on computer where it is installed, I decided to split the module in two. Before going into details, here’s what have caused problems with AVs & MDE.
File | Description |
---|---|
PTASpy.ps1 and PTASpy.dll | This has been flagged to be a Trojan. PTASpy is used to harvest credentials from computers running Pass-through Authentication (PTA) agents (see blog). |
Win32Ntv.dll | This has been flagged as a Backdoor. The file itself doesn’t do any harm, but it has functionality that allows elavating local admins to local system etc. |
AADInternals.png | This has been flagged as a Trojan. The is not an image file, but a service executable used to get AD FS token signing certificate encryption key as AD FS service account (source code here) |
AADInternals-Endpoints: How?
So the big question was, how can I implement the same functionality without immediately being flagged by AV/MDE?
I took two actions:
- I moved all endpoint related functionality to a new AADInternals-Endpoints module, availabe at PowerShell Gallery and GitHub.
- I removed malicious files and implemented the functionality in a new way
Replacing Win32Ntv.dll
As I mentioned above, Win32Ntv.dll contains functionality such as elevation to local system. As that was flagged by AV/MDE, I decided to get rid of those functions.
Currently, the reminding functionality is included in Win32Ntv.ps1 and it’s compiled on the fly when the module is installed.
Elevating to local system or service accounts
Now that there is no more elevation functionality in Win32Ntv.ps1, I had to find another way to run stuff.
The idea started to form towards services. First, I wrote earlier about elevating from local admins to gMSA. Basically, if you are a local administrator, you can start service as a local system.
Second, I found an interesting article written by Jean-François Larvoire that explained how to run PowerShell script as a service. His PSService.ps1 inspired me to try to implement something similar.
My implementation does the following:
- Compile a service executable on-the-fly
- Start the service as the local system or as user we want to impersonate
- Use named pipe to receive parameters from PowerShell session
- Execute PowerShell.exe with provided parameters
- Return response to PowerShell session using named pipe
- Stop the service and remove service executable
The service executable source code is as follows. $ServiceName variable is evaluated during the compilation time, so each service has a unique name.
using System;
using System.IO.Pipes;
using System.IO;
using System.Reflection;
using System.ServiceProcess;
using System.Threading;
using System.Management;
using System.Diagnostics;
using System.Text;
namespace AADInternals
{
public class $ServiceName : ServiceBase
{
public static void Main()
{
ServiceBase[] ServicesToRun;
ServicesToRun = new ServiceBase[]
{
new $ServiceName()
};
ServiceBase.Run(ServicesToRun);
}
protected override void OnStart(string[] args)
{
new Thread(Service).Start();
}
private static void Service()
{
string command = "";
//
// Wait for the command
//
using (NamedPipeServerStream pipeServer = new NamedPipeServerStream("$ServiceName-out", PipeDirection.InOut))
{
// Wait for a client to connect
pipeServer.WaitForConnection();
try
{
// Read the command
using (StreamReader sr = new StreamReader(pipeServer))
{
while (!sr.EndOfStream)
command += sr.ReadLine();
}
}
catch (IOException e){}
}
//
// Run the command
//
string returnValue;
try
{
Process p = new Process();
p.StartInfo.UseShellExecute = false;
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.FileName = "PowerShell.exe";
p.StartInfo.Arguments = String.Format("-ExecutionPolicy Bypass -Command \"& {{{0}}}\"", command);
p.Start();
// Read the output
returnValue = p.StandardOutput.ReadToEnd();
p.WaitForExit();
}
catch (Exception e)
{
returnValue = e.InnerException.Message.Replace(System.Environment.NewLine, "");
}
//
// Send the response back to client
//
using (NamedPipeClientStream pipeClient = new NamedPipeClientStream(".", "$ServiceName-in", PipeDirection.InOut))
{
// Connect
pipeClient.Connect();
try
{
using (StreamWriter sw = new StreamWriter(pipeClient,Encoding.UTF8,UInt16.MaxValue))
{
sw.AutoFlush = true;
sw.WriteLine(returnValue);
}
}
catch (IOException e){};
}
}
}
}
The Invoke-AADIntScriptAs is using the new elevation procedure and allows running PowerShell scripts as a local system, (g)MSA account, or any user with given credentials.
For instance, you can run the script as Entra ID Connect service account:
# Invoke script as Group Managed Service Account
Invoke-AADIntScriptAs -Command "whoami" -GMSA 'CONTOSO\ADSyncMSA55a35$' -Verbose
VERBOSE: Creating service AADInternals3486
VERBOSE: Creating service to be run as Local System
VERBOSE: Changing user to CONTOSO\ADSyncMSA55a35$
VERBOSE: Setting ServiceAccountManaged property
VERBOSE: Starting service AADInternals3486
VERBOSE: Creating outbound named pipe AADInternals3486-out
VERBOSE: Sending command AADInternals3486-out
VERBOSE: Creating inbound named pipe AADInternals3486-in
VERBOSE: Waiting for connection
VERBOSE: Reading response from AADInternals3486-in
contoso\adsyncmsa55a35$
VERBOSE: Stopping service AADInternals3486
VERBOSE: Deleting service AADInternals3486
VERBOSE: Deleting service executable C:\Program Files\WindowsPowerShell\Modules\AADinternals-endpoints\0.9.6\AADInternals3486.exe
Or as a local system:
# Invoke script as local system
Invoke-AADIntScriptAs -Command "whoami" -Verbose
VERBOSE: Creating service AADInternals5749
VERBOSE: Creating service to be run as Local System
VERBOSE: Starting service AADInternals5749
VERBOSE: Creating outbound named pipe AADInternals5749-out
VERBOSE: Sending command AADInternals5749-out
VERBOSE: Creating inbound named pipe AADInternals5749-in
VERBOSE: Waiting for connection
VERBOSE: Reading response from AADInternals5749-in
nt authority\system
VERBOSE: Stopping service AADInternals5749
VERBOSE: Deleting service AADInternals5749
VERBOSE: Deleting service executable C:\Program Files\WindowsPowerShell\Modules\AADinternals-endpoints\0.9.6\AADInternals5749.exe
Wait, what about detections?
So, now that the malicious files are moved and elevation functionality is using a new procedure, are we still safe? For short, yes we are!
Even though missing malicious files are not preventing the installation of the AADInternals-Endpoints module, the used techniques have not changed (see MITRE definitions for technique and procedure here) so those are detected by AVs & MDE same way as before 😊
Summary
Now all endpoint related functionality is moved to the new AADInternals-Endpoint module. I had to do some modifications to many functions due to changed elevation procedure, but everything should work now. If not, raise an issue or pull request on GitHub!
References
- AADInternals-Endpoints at PowerShell Gallery
- AADInternals-Endpoints at GitHub
- Jean-François Larvoire: Writing Windows Services in PowerShell
- Jean-François Larvoire: PSService.ps1
- MITRE ATT&CK®: Frequently Asked Questions