Defenders Think in Graphs Too! Part 1

Jared Atkinson
Posts By SpecterOps Team Members
6 min readMar 12, 2018

--

Introduction

Welcome to the first part of our blog series titled “Defenders Think in Graphs Too!” In this series, Roberto Rodriguez (@cyb3rward0g) and I (@jaredcatkinson) will discuss our views on data acquisition, data quality, and data analysis through a case study focused on detecting Process Injection. This post (Part One) will discuss an existing detection technology, a PowerShell script called Get-InjectedThread, to make sure all readers have the same baseline knowledge. In subsequent posts, we will dissect the issues with Get-InjectedThread’s data quality, identify how we can improve on the current script, cover integration with the HELK project, and investigate using graph technology to improve our overall product.

Detecting Injected Threads

Last year at the SANS Threat Hunting Summit in New Orleans, Endgame’s Joe Desimone and I released Get-InjectedThread, a PowerShell script for detecting code injection by inspecting threads on a machine. If you are interested in hearing more about how this detection works, check out the video here or check out my explanation a bit further down. For those that just want to see the script in action you can check out the demo video of Get-InjectedThread detecting a PowerShell Empire agent after it migrated into the LSASS process.

In the meantime, let’s take a look at Get-InjectedThread in action. For this post, we will focus on the functionality rather than the utility of Get-InjectedThread, so we are using a benign testing function as an example. Joe wrote a quick test binary, called ThreadStart.exe, that mimics code injection behavior to test our script’s functionality. Being the PowerShell junkie that I am, I decided to rewrite ThreadStart in PowerShell to make it a bit easier for our use case. So our first step is to execute the New-InjectedThread function from our PowerShell process. The code for New-InjectedThread can be found in the PSReflect-Functions repository on GitHub.

Let’s quickly review how New-InjectedThread functions:

  • Execute the Get-Process cmdlet with the Id parameter set to the current PowerShell process. The resulting System.Diagnostics.Process instance will include a Process handle that can be used later.
  • Allocate a memory region with VirtualAllocEx to write the “malicious” code to.
  • Use WriteProcessMemory to write our “shellcode” to the newly allocated memory region. This step is unnecessary for testing, but we are currently writing MZ as the first two bytes to mimic a PE header.
  • Call CreateThread with the CREATE_SUSPENDED flag set and point at the memory region allocated with VirtualAllocEx.
  • Close the Thread Handle with CloseHandle since we are no longer using it.
  • Output information necessary to validate detection of the “malicious” thread.

When you execute New-InjectedThread you should see a result similar to the image below:

Creating a test injected thread with New-InjectedThread

After running New-InjectedThread, it looks like we have a sample thread to detect. Notice that the function reports the ProcessId (1008), ThreadId (9220), and Memory Base Address (2144746340352) for the “malicious” Thread.

Next, we can run Get-InjectedThread on the endpoint and expect to detect at least one instance of code injection. This is as simple as loading Get-InjectedThread.ps1 into your current PowerShell runspace and calling the Get-InjectedThread function with no parameters, as shown below:

Detecting the injection with Get-InjectedThread

Great! Get-InjectedThread detected injection in the powershell.exe process. Upon further investigation, we notice that the ThreadId and BaseAddress field match our expectation based on what New-InjectedThread reported to us.

Let’s take some time to get an understanding of how Get-InjectedThread works under the hood. Get-InjectedThread is a PowerShell script built on Matt Graeber’s PSReflect module. PSReflect abstracts the complexities of using Reflection to build functions, enums, and structures around the Win32 API, for access by PowerShell, in memory. Get-InjectedThread uses PSReflect specifically to collect information about Processes, Threads, Access Tokens, and Logon Sessions. This information is then formed into a custom psobject instance and output to the PowerShell pipeline. Below you can see a visualization of an InjectedThread object with the information collected via PSReflect.

The Current Get-InjectedThread Data Structure

For those that are interested in the technical nuance, here is a much more technical explanation of what is happening within Get-InjectedThread:

  • Call CreateToolhelpSnapshot with a ProcessId of 0 (all processes) and the Flag parameter set to 4 (TH32CS_SNAPTHREAD). This returns a snapshot of all currently running threads.
  • Iterate through all threads in the snapshot with Thread32First and Thread32Next.

Each of the following steps will be performed on every thread:

  • Call OpenThread to receive a handle to the Thread object in the kernel.
  • With the Thread handle, call NtQueryInformationThread specifying a value of 9 (ThreadQuerySetWin32StartAddress) for the ThreadInformationClass parameter. This returns the Thread’s memory start address.
  • Call OpenProcess to receive a handle to the current Thread’s owning Process.
  • Pass the Process Handle and Thread Start Address to VirtualQueryEx to query the target memory page. This will result in a MEMORY_BASIC_INFORMATION structure.
  • Checks the State and Type fields in the returned structure.
  • State should be MEM_COMMIT for all threads
  • Type should be MEM_IMAGE for all threads
  • If Type is not equal to MEM_IMAGE, then you have a thread that is running code that is not backed by a file on disk (aka injection).

Perform the following steps for all “Injected Threads”:

  • Read the content at the BaseAddress with ReadProcessMemory. The first 100 bytes at the Base Address will be returned from the function.
  • Use OpenThreadToken to get a handle to the Access Token applied to the Thread. If this function fails, use OpenProcessToken to get a handle to the Processes “Primary” Access Token. By default, in case where Impersonation is not being used, a Thread will use the Processes Access Token.
  • Call GetTokenInformation to query information about the Access Token, such as User, Privileges, Impersonation Level, Integrity Level, and many other properties.

Now, we all understand how Get-InjectedThread works and have a simple test use case that was used to validate our logic.

Improving Injected Thread Detection

The reception of this PowerShell script was greater than I could have expected. It felt awesome to give the community a capability that was previously reserved for commercial EDR solutions or heavy duty analysis techniques like Memory Forensics. With Get-InjectedThread, any security practitioner could begin hunting for memory resident attacks across their enterprise, but why stop there!?

Recently, I began to wonder about how Get-InjectedThread could be improved. I still think the underlying functionality is great, but I feel we can do much more with the data. As it stands, Get-InjectedThread detects code injection at the micro level, but misses the greater context on the endpoint. For instance, Get-InjectedThread only returns output for threads for which injection has been detected. This means that computing resources are being used, but our analysts are not receiving that data. What if that omitted data could be used for other detections? In the case of Get-InjectedThread, we would have to run a completely separate collection for any other detection efforts. The main takeaway is that analysis efforts should typically not be performed on the endpoint. Instead, collect all relevant data and forward to a centralized log source such as HELK to perform the analysis.

Conclusion

Over the years I have focused on data collection and Roberto has focused heavily on data analysis. We believe we have pieced together a pretty good methodology for handling data from cradle to grave. This blog series is our attempt at sharing our findings with the community through a practical use case. In Part Two, we will dig deeper into issues with Get-InjectedThread and discuss how to make improvements on the collection script. Future posts will examine this data from the perspective of data quality, correlation, and analysis. This post is meant to serve as an introduction to the series, so be on the lookout for Part Two!

Other Posts in the “Defenders Think in Graphs Too!” series:

--

--