Welcome back to another installment of the On Detection: Tactical to Functional series. In the previous article, I argued that we perceive actions within our environment at the Operational level (especially when it comes to endpoint events), which means that we should “conceive” of attacker tradecraft at the Operational level as well. In this article, I want to demonstrate WHY this convergence between conception and perception is so important. With that in mind, I want to explore a common issue I see in Detection Engineering (DE); that is, “process based detection.”
In his post describing proposed updates to the data sources represented in MITRE ATT&CK, Jose Luis Rodriguez shared the following graph:
This graph was created based on the relationships between techniques and their associated data sources as they were originally represented in ATT&CK. A quick view of the sheer numbers indicates that “process monitoring” and “process command-line parameters” are far and away the most commonly associated data sources. In fact, “process monitoring” provided coverage of 234 different techniques at the time this graph was produced (i.e., September 2020). So what does this mean for detection engineers? At first glance, one could not be blamed for assuming that if they have process monitoring they have sufficient data to detect the vast majority of techniques. This however is not the case.
According to the MITRE ATT&CK Design and Philosophy white paper a “Data Source” is defined as a, “Source of information collected by a sensor or logging system that may be used to collect information relevant to identifying the action being performed, sequence of actions, or the results of those actions by an adversary. The data source list can incorporate different variations of how the action could be performed for a particular (sub-)technique. This attribute is intended to be restricted to a defined list to allow analysis of technique coverage based on unique data sources. (For example, ‘what techniques can I detect if I have process monitoring in place?’)”
The phrase “to collect information relevant to identifying the action being performed” tells us that the mapping from a data source to a technique does not indicate that the data source is SUFFICIENT for detecting the behavior. Instead, these mappings were meant to indicate that the data source may provide context that may be valuable in relation to the technique.
Unfortunately, it appears that many of us interpret Jose’s graph as, “234 techniques can be detected if I have process monitoring in place,” versus, “Process monitoring provides potentially valuable context for up to 234 different techniques.” In this post, I hope to demonstrate WHY process monitoring often does not provide sufficient context for robust detections. The problem is something that I call the “Implicit Process Create operation.”
Does that mean that all process-based detections should be considered signatures? Well, as any good consultant would respond, it depends. In order to answer this question more thoroughly, we must understand how process creation occurs in the context of attacker tradecraft and techniques. In this section, I hope to demonstrate the difference between implicit process creation (i.e., when a process is created to execute the malicious code but outside of the related operation chain) and explicit process creation (i.e., when a process is created as part of the operation chain for the technique). The remainder of this post will explore four example techniques, two implicit and two explicit, in order to demonstrate why using the same data source can provide holistic coverage in some cases but not others. Following the process of sample analysis laid out in this series, you should easily be able to discern what type of sample/technique you are confronting.
Our first example is simple Service Creation or T1543.003 Create or Modify System Process: Windows Service. Windows ships with a built-in utility called sc.exe
which facilitates the creation, enumeration, management, etc. of Windows services. Below is an example of a common detection rule for detecting service creation. You see that it specifically looks for a process named sc.exe
and a command line argument including the string create
, which is a sub-command of sc.exe
.
If we analyze sc.exe
, we can begin to understand how that tool works under the hood. Here I opened the tool in IDA Pro and navigated to the wmain
function. Once there, I looked for the code path that would execute if the create
sub-command was issued. In the screenshot below, we see an if statement comparing a string (v10
) to the string create
. If they match, then the subsequent code is executed. We see three functions, which are listed below:
CreateUsage
, is a helper function meant to display the command line syntax for this sub-command if the proper arguments are not suppliedOpenSCManagerW
) that opens a handle to the service control manager (SCM) on a local or remote computer (this is specified by the first argument lpMachineName
)SERVICES_ACTIVE_DATABASE
opensHere we see the value is set to 2
, which corresponds to SC_MANAGER_CREATE_SERVICE
which makes sense based on the purpose of the create
sub-command. It is important to note that the output of this function, the handle to the SCM, is returned to the variable v5
which is subsequently passed to the internal DoCreateService
function which we will look at next.
The DoCreateService
function has a lot of argument parsing, but the core of the function can be seen in the screenshot below. Here, we see a call to the CreateServiceW
API function. We can also see that the a1
variable, which corresponds with the handle to the SCM from the prior OpenSCManagerW
call, is passed as the first parameter. We then see a bunch of details about the service that is being created being passed such as the service name (lpServiceName
), the service type (dwServiceType
), the service start type (dwStartType
), and the command line to be executed when the service starts (lpBinaryPathName
).
NOTE: An interesting point is that although the parameter is called
lpBinaryPathName
, it is not actually limited to a file path to a binary. It is actually a command line with arguments, so it is not uncommon to see an absolute or relative path to a binary AND its arguments specified in this parameter.
Next, we see the ChangeServiceConfig2W
function, but this function is not called in all code paths; for simplicity, we will ignore it. Similarly, the DeleteService
function is only called if the ChangeServiceConfig2W
function call fails. Instead, we will finish our analysis with the call to CloseServiceHandle
, which simply closes the handle to the newly created service that is returned from CreateServiceW
. There isn’t anything special about this call, but it is good practice to close handles once they are no longer necessary.
With our analysis completed, we can summarize the functionality of sc.exe
’s create
sub-command with the following function chain:
We can then use function call stacks to eventually derive the following operation chain:
Why is this important? Notice that the operation chain is composed of three operations: SCM Open
, Service Create
, and Handle Close
. An attacker with minimal programming capabilities could reimplement these operations in a completely novel tool. Also, notice that there is no “Process Create
” operation included in this operation chain. How then, can it be, that we’ve created a detection rule which is focused on process creation (Sysmon Event ID 1, for example) when the behavior of the tool does not include process creation?
The answer is that process creation is IMPLICIT to the execution of any application (sc.exe
, in this case). In order to run code, the code must be run in the context of a process which means that there will always be a process creation event associated with malicious code execution. However, due to the fact that this functionality can be hidden behind any arbitrary process, that doesn’t necessarily mean that we should rely on process creation as our primary data source. The arbitrary point is important because that means it is not predictable from a detection engineering perspective.
Let’s look at a second example. A common detection approach is to look for discovery commands. One discovery command that is particularly interesting is whoami.exe
. The reason whoami.exe
is notable is because it is relatively unlikely that a normal legitimate user would have a need to ask this question. Generally speaking, when a user logs on legitimately, they know who they are; asking the question can be seen, in some cases, as being suspicious all by itself. As a result, we often see a simple high fidelity rule for detecting the execution of whoami.exe
such as in the example shown below:
This example was recently salient to me because Florian Roth and Daniel Card were having a conversation about creative ways to execute whoami.exe
in a SYSTEM
context. Their examples included downloading psexec
and using that to run whoami
, as well as creating a scheduled task to do the same thing. This caused me to wonder what whoami.exe
is actually doing under the hood; that way, I could recreate the functionality without the need to literally execute this tool.
Again, I opened whoami.exe
in IDA Pro and began to dig in. When doing so, the first function we encounter is the WsUser::Init
function, which calls the GetCurrentProcess
and OpenProcessToken
functions. The purpose of these functions is to open a pseudo handle to the calling process (whoami.exe
), then that process handle is used as an input to open a handle to the access token associated with that process. Next, we see a set of calls to internal functions. The WsAccessToken::InitUserSid
function is of particular interest, so we’ll dig into that next.
Once we arrive at the WsAccessToken::InitUserSid
we see a call to the GetTokenInformation
function which specifically asks for the TokenUser
information class that returns the security identifier (SID) of the user context of the token. This SID represents the answer to the question, “Who am I?”
Now that we’ve completed our code analysis, we can produce a function chain composed of the following three functions: GetCurrentProcess -> OpenProcessToken -> GetTokenInformation
.
We can again generalize the behavior whoami.exe
implemented operationally where we see that a handle to the current process is obtained (Process Open
), a handle to the token associated to the current process is opened (Token Open
), and that token is queried to determine the user account associated with the token. Notice the absence of a Process Create
operation.
Again, why are we building a detection based on an operation that is not even included in the relevant operation chain?
We can then create a “custom tool” to implement this function/operation chain that would bypass detections focused on the non-existent (implicit) Process Create
operation. Below is an example implementation built on my PSReflect-Function PowerShell module. This implementation obtains the same output (the name of the calling user account) without creating a process called whoami.exe
.
In the previous section, we analyzed two example operation chains that did not include an explicit Process Create
operation. At this point, your conclusion might be that detections based on events related to process creation are inherently tool focused; however, in this section we will analyze two additional techniques where the Process Create
operation is an explicit component of the operation chain. This changes the value of process creation-related detection rules.
The first example of the use of an explicit Process Create
operation is found in so-called WMI lateral movement or, according to ATT&CK, T1047 Windows Management Instrumentation. WMI’s Win32_Process
class, in particular, implements a method called Create
which facilitates the local or remote creation of an arbitrary process. The remote component specifically provides an excellent primitive for lateral movement.
If you are interested in learning more about WMI internals, I highly recommend Jonathan Johnson’s WMI Internals blog series. In the series, he describes WMI classes, methods, providers, and many other features.
I want to take a moment to recognize how useful blog posts like Jonny’s that walk through the tactical process of looking up information are. I basically never remember the PowerShell syntax for deriving this information, so I find myself referencing Jonny’s post every single time I work with WMI Providers.
In Part 1, Jonny provides the PowerShell commands necessary to derive the file path of the WMI provider (typically a dynamic link library [DLL] file) that implements a specific WMI class. For ease of use, I’ve adapted his commands to a PowerShell function that accepts the name of a WMI class and outputs the provider’s file path. The function is shared in the gist snippet below.
Now we can execute the Get-WmiProviderPath
function on our Windows machine to identify the provider that implements the Win32_Process
class.
Now that we’ve found the provider that implements the Win32_Process
class, we can review the implementation. Once the provider has been opened in our disassembler, our first question is where should we start our analysis? In order to answer this question, it is important to understand how remote WMI method calls are implemented.
WMI relies on remote procedure calls (RPC) to execute methods on remote systems. Specifically, the IWbemServices::ExecMethod
procedure facilitates the execution of WMI methods on remote systems. This procedure requires seven parameters, with the first two parameters specifying the WMI class (strObjectPath
) and the WMI method (strMethodName
) respectively.
To begin, let’s search internal functions that have the string ExecMethod
in their name. When we do so, we find that there is an ExecMethod
function for each of the WMI classes that the provider implements. For example, we see Provider::ExecMethod
for the Win32_Provider
class, Process::ExecMethod
for the Win32_Process
class, and LogicalDisk::ExecMethod
for the Win32_LogicalDisk
class. Since we are interested in investigating the implementation of the Win32_Process
class’s Create
method, we should check out the Process::ExecMethod
function.
Within the Process::ExecMethod
function, we see a series of conditional statements which appear to compare the a3
parameter to strings that correspond to the methods the class (i.e. Create
, Terminate
, GetOwner
, etc.) implemented. In this case, the code shows that when the Create
method is specified, the Process::ExecCreate
is called. We can continue our analysis by following that function.
After following a few internal function calls, we see the actual Windows API functions. The crux of the capability is related to the CreateProcessAsUserW
function. This shows us how the method actually works. We see that the Win32_Process::Create
method functions as a wrapper to allow for the execution of the CreateProcessAsUserW
function.
I’ve included a simplified function chain that the Create
method implements. Here, we see GetCurrentThread
, which opens a pseudo handle to the calling thread; then OpenThreadToken
, which opens the token associated with the calling thread. I didn’t track it all the way back, but typically the RPC server will impersonate the RPC client so this token will be related to that impersonation. Next, we see the call to DuplicateTokenEx
which allows for the subsequent call to GetTokenInformation
, specifically to query the TokenUser
information class. Finally, we see that information supplied to the CreateProcessAsUserW
function to create the requested process which in a malicious example would be the payload.
We can then derive the operation chain, which is composed of Thread Open -> Token Open -> Token Duplicate -> Token Query -> PROCESS CREATE
. You should, at this point, notice that the chain includes a Process Create
operation. This is meaningfully different from the previous examples where the Process Create
operation was purely implicit.
This creates a new question, however. In this case, there are now two Process Create
operations: one implicit and the other explicit. We can review two additional Sigma rules to see how this dichotomy plays out.
Rule 1: wmic process call create
The first rule looks for process creation events where the image (i.e., the process being executed) is wmic.exe
and the CommandLine
(i.e., command line arguments) include the strings process
, call
, and create
. This is because the Windows Management Instrumentation Console (WMIC) is the built-in traditional application for interacting with WMI and if one were to use WMIC to execute the Win32_Process::Create
method, the command line would look something like wmic.exe process call create
. However, there is a problem; what if the attacker uses PowerShell’s Invoke-WmiMethod
or they created a custom tool? This detection rule is built specifically for the canonical variation of this technique, but it does not generalize to capture alternative variations. The reason for this is that this rule is focused on the implicit Process Create
operation.
Rule 2: Child of wmiprvse.exe
The second rule focuses on detecting process creation events where the ParentImage
(i.e., parent process) is wmiprvse.exe
. This parent/child relationship is created because the wmiprvse.exe
process loads the DLL that facilitates remote WMI requests (cimwin32.dll
); so when the Win32_Process::Create
method is called, the CreateProcessAsUserW
function is called in the context of the wmiprvse.exe
process.
This event is related to the explicit Process Create
operation that we are looking for. The added benefit of this explicit Process Create
is that the explicit nature of this operation often makes some aspect of the event static or predictable. While we do not know the particular details of what application will be executed via this method, we do know that said application will be executed as a child of wmiprvse.exe
. The same cannot be said about the implicit Process Create
operation. In that case, there are no features that we can reliably predict across all possible variations.
This example hopefully demonstrated that:
Process Create
operations to be explicit in SOME operation chainsProcess Create
operation is present, we must discern between the always present implicit and the uniquely explicit operationProcess Create
operations typically have some sort of static feature (the parent process, in this case) that allows for the detection engineer to zero inAnother example of a technique where we find the presence of an explicit Process Create
operation is process hollowing or, for those following along in ATT&CK, T1055.012 Process Injection: Process Hollowing. For this particular sample, I used the source code described in Recipe 15–8
of Malware Analyst’s Cookbook. If you’d like to see the source code, I’d encourage you to check out the book as there are tons of great nuggets in there.
Below is the function chain described in the book. Notice that the chain is a little longer, which indicates that this implementation is maybe a bit more complicated. We see that it begins with a call to the CreateProcessA
function, which is used to create a “sacrificial process” to hollow out. A key feature of this process is that it will be created in a “suspended state” because the CREATE_SUSPENDED
flag is set as a parameter to the function call. Next, we see NtUnmapViewOfSection -> VirtualAllocEx -> WriteProcessMemory -> WriteProcessMemory
, which represents a sequence where the legitimate code associated with the sacrificial process is hollowed out and replaced with malicious code. There’s a lot of complexity involved in making sure the new malicious code is written in a way that allows for execution, but we will skip those details here.
Next, we see GetThreadContext -> SetThreadContext -> ResumeThread
, which is responsible for adjusting the execution context of the hollowed process’s primary thread to point to the entry point of the malicious code that was “injected” in place of the original portable executable (PE). We end up with the following function chain:
We can then derive the operation chain which is composed of the PROCESS CREATE -> Section Unmap -> Memory Allocate -> Process Write -> Process Write -> Thread Read -> Thread Write -> Thread Resume
operations respectively. Again, the key discovery of this operation chain is that we find the presence of an explicit Process Create
operation; so the logical next question is, “What about this Process Create
operation makes it predictable and therefore observable?”
The key feature here is that the process is started using the CREATE_SUSPENDED
flag. While this feature may not be commonly included across all events that report on process creation, the feature itself is theoretically observable. Similar to our WMI Lateral Movement example, there will be both an implicit and an explicit Process Create
operation when this operation chain is executed. The implicit Process Create
will be related to the utility that is implementing Process Hollowing. In general, the details related to this utility are unpredictable. On the other hand, the explicit Process Create
is related to the “sacrificial process” that will be hollowed out to eventually execute the malicious code. The attacker again has complete freedom to select the application that they use as the sacrificial process; however, the suspended initial state will be consistent for at least this particular operation chain. Therefore, we can glean that a process might be subject to hollowing if we can observe that it was created in a suspended state. It is important to remember that all instances of process hollowing will be created in a suspended state, however not all processes created in a suspended state will be indicative of process hollowing.
The goal of this article is not to say that all detections based on process creation are brittle. It’s to point out that not all process creation events are created equal; that process creation is a key component of some techniques while it is incidental to many others; and that a single event can have different detective powers in different situations.
I hope that I’ve demonstrated the utility of understanding HOW a technique works, mapping that understanding in a comprehensible manner, and converting that understanding into a detection rule. It is also important to understand that even when an operation chain explicitly includes a Process Create
operation, we must be cautious to ensure that we select the correct process to focus on. We must also work to ascertain the feature(s) that remain static enough to facilitate predictive results (i.e., a predictable parent process or the suspended state).
Lastly, I will leave you with an idea. There’s long been a debate about the meaning of two relative abstract concepts in the world of detection engineering: namely “signature based detection” vs. “behavior based detection”. Generally speaking, signatures focus on the implementation (the what) while behaviors focus on the interaction between the tool and the operating system (the how). While this understanding provides a nice heuristic, I’ve found that there is a wide gray area where it becomes difficult to discern between the two. My proposal is that we can use the operation chain to determine whether a detection rule is focused on the behavior or not. What I mean is that if a detection is focused on an operation that is not an explicit component of the operation chain (e.g., the implicit Process Create
) then it fits squarely into the signature category. If, on the other hand, a detection is based on an operation that is explicitly included in the operation chain (i.e. explicit Process Create
, Token Query
, Service Create
, etc.) then it is behavioral.