AADInternals-Endpoints 😈

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:

  1. I moved all endpoint related functionality to a new AADInternals-Endpoints module, availabe at PowerShell Gallery and GitHub.
  2. 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:

  1. Compile a service executable on-the-fly
  2. Start the service as the local system or as user we want to impersonate
  3. Use named pipe to receive parameters from PowerShell session
  4. Execute PowerShell.exe with provided parameters
  5. Return response to PowerShell session using named pipe
  6. 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

Dr Nestori Syynimaa (@DrAzureAD) avatar
About Dr Nestori Syynimaa (@DrAzureAD)
Dr Syynimaa works as Principal Identity Security Researcher at Microsoft Security Research.
Before his security researcher career, Dr Syynimaa worked as a CIO, consultant, trainer, and university lecturer for over 20 years. He is a regular speaker in scientific and professional conferences related to Microsoft 365 and Entra ID (Azure AD) security.

Before joining Microsoft, Dr Syynimaa was Microsoft MVP in security category and Microsoft Most Valuable Security Researcher (MVR).