Slivering Through The Cracks

  • Jordan McGrath Photo

Jordan McGrath

As defenders, defense-in-depth was a principle drilled into us since we started our learning journeys. We must always operate under the principle that "one is none." Our detection libraries must follow this same line of thought. Imagine a smoke detector that didn't have a low battery warning - our detections need detections!

With modern protections deployed on a host, no self-valuing threat actor is going to compromise a machine and proceed to spray-and-pray:

cmd.exe /c pOwErShEll.exe -w Hidden -nop -EnCo "VwByAGkAdABlAC0ASABvAHMAdAAgACIAVABoAGEAbgBrAHMAIABmAG8AcgAgAHIAZQBhAGQAaQBuAGcAIQAiAA

Times have changed!

Often, threat actors will attempt to evade our detections first. Whether this is done by disabling Event Tracing for Windows (ETW), excluding their tools from Windows Defender, or simply uninstalling the security agent, the result is largely the same. It's our job to catch these evasion techniques before we're blinded. In this blog post, we'll dive into a common evasion technique and how it is implemented into the Sliver C2 framework.

Sliver is an open-source, cross-platform Command and Control (C2) framework. Sliver has made a name for itself within the red team community by providing reliable out of the box communications and a feature-rich platform for offensive operations – all at the low cost of free. Threat actors alike utilize Sliver as an extremely capable baseline for their operations, with a wonderfully documented Golang codebase for customizing to their needs. 

For those unfamiliar, NTDLL.dll (referred to as just NTDLL) is a Windows-native dynamic link library (DLL). Semi-uniquely, this DLL holds the instructions for user-to-kernel-mode function calls. Due to the flow of execution and its inherent importance, this DLL has historically been heavily monitored by security agents. This monitoring is done via small modifications to the executable code called "hooks." Put simply, these hooks rewire the flow of execution to enable security agents’ visibility into function calls. Patching, or more specifically, NTDLL patching, is the process of re-re-writing NTDLL in memory and removing those hooks to blind the security agents.

Figure 1 - NTDLL Patch Execution Flow

Let's review Sliver's code to gain a further understanding of NTDLL patching. The following code can be found within the official Sliver GitHub repository.

Figure 2 - RefreshPE() Function Breakdown

First, `RefreshPE()` takes in the `name` parameter loaded with the path of an on-disk, clean (read: unhooked) NTDLL. `f` is a `File` structure from Golang's `debug/pe` package, which receives the clean DLL. `x` holds a pointer to the address of NTDLL's `.text` (executable) section directed by `f.section(".text")`. `ddf` is our final product, the clean byte-array of NTDLL from `x.Data()`. Finally, `writeGoodBytes()` is called to handle the dirty work.

Figure 3 - writeGoodBytes() Function Breakdown

`writeGoodBytes()` takes several parameters: `b` is an unmodified copy of NTDLL, `pn` is the name of the DLL we wish to overwrite, `virtualoffset` is the virtual address of NTDLL's `.text` section, `secname` is unused, and `vsize` is the size of NTDLL's `.text` section.

`t` uses `LoadDLL` from the `x/sys` package to initialize a `DLL` structure from the current memory-resident NTDLL. `h` represents the `t` structure's `Handle` property. What follows can be confusing pointer math. `dllOffset` equals the base address (the beginning) of in memory NTDLL summed with the virtual address of the `.text` section gathered earlier. This equation gives the exact byte at which to begin rewriting NTDLL. If this is confusing, keep in mind that virtual memory is a contiguous block. Two copies of the same executable look nearly identical in virtual memory, pointer math between them should line up.

New Diagram - Windows Defender Bypass

This, in many cases, is the beginning of something much worse. With reduced oversight, threat actors have much more room to breathe and can take further actions on objectives. Unfortunately for them, utilizing the telemetry generated by security agents, Binary Defense can detect and stop this behavior in its tracks. Using pseudo code, let's walk through how a sample detection works for this tactic.

Figure 5 - Detection Breakdown

Note: This same detection, with even more precision due to a hardcoded `RegionSize` variable, can be applied to Sliver's `patchAmsi()` function!

Like that, we've turned what should be blinding us into a meaningful tipoff. By giving defense evasion techniques the same level of care as our normal execution techniques, we ensure our detections are layered. This methodology generates a spider web of tripwires that threat actors must now navigate- only we control when and where these tripwires are placed.