Blue teams helping red teams: A tale of a process crash, PowerShell, and the MITRE ATT&CK evaluation

In September 2019, MITRE evaluated Microsoft Threat Protection (MTP) and other endpoint security solutions. The ATT&CK evaluation lasted for three days, with a professional red team from MITRE emulating many advanced attack behaviors used by the nation-state threat group known as YTTRIUM (APT29). After releasing the results of the evaluation, MITRE published the emulation methodology, including all of the attack scripts, tools, and code.

During the evaluation, the Microsoft Threat Protection team noted an interesting behavior related to one of the steps in the APT29 attack chain: Step 19 was supposed to perform stealthy deletion of files using the SDELETE tool reflectively loaded in memory. However, we observed that the step repeatedly caused process crashes during the execution of red team operations.

Crashes are unexpected surprises that could be a true gem for defenders for being a major indicator of an imminent attack, ruining the party for red teams and real attackers alike. Inspired by the transparency of MITRE publishing all the payloads and tools used in the attack simulation, in this blog post, we’ll describe the mystery that is Step 19, share our root cause analysis of the Step 19 attack script, and tell a story about how blue teams, once in a while, share important learnings for red teams and their tools.

Step 19 of the APT29 evaluation

The APT29 emulation involved 20 steps consisting of attacker techniques from the MITRE ATT&CK matrix related to the APT29 group. These steps were executed in the course of two days (plus an extra day reserved as a buffer), 10 steps per day. Since these steps spanned the entire attack chain, each step logically flowed from the previous one.

Step 19 was part of the attack chain executed on the second day. It emulated the attacker’s goal of deleting artifacts from the machine at the end of the breach using the SDELETE tool, which was loaded via PowerShell through a reflective loader mechanism, without ever touching disk.

Figure 1. Step 19 of the MITRE evaluation

This was done by dropping and running a script file called wipe.ps1, in a process that included:

  1. Loading a PowerShell reflective loader
  2. Reflectively loading sdelete.exe, a Sysinternals tool for secure file deletions
  3. Running the reflected exe with the desired files to be deleted

It’s important to note that the wipe.ps1 payload was based on and inspired by the famous “Invoke-ReflectivePEInjection” script from Joseph Bialek (@JosephBialek) and Matt Graeber (@mattifestation), which is also affected by the same issue that we discovered in our investigation and root cause analysis.

Figure 2. Microsoft Threat Protection detection of the reflective loader with relevant cmdlets

Figure 3. Entire script fetched using advanced hunting (truncated for brevity)

Microsoft Threat Protection automatically detected the execution of the reflective loader via PowerShell; however, during the execution of this attack, the telemetry provided by the product also captured the launch of WerFault.exe process (the Windows Error Reporting process) forked from PowerShell.exe, which was a sign of a crashing process.

Having noticed the repeated process crashing behavior, we decided to investigate further to understand what was happening in Step 19, and we observed the following:

Test Result
Execution in MITRE test environment #1 (primary) with MTP wipe.ps1 generated crash
Execution in MITRE test environment #2 (backup) with MTP wipe.ps1 generated crash
Execution in MITRE private environment without MTP wipe.ps1 executed with no crashes
Onboarding MTP to MITRE private environment wipe.ps1 generated crash

Indeed, it looked as if MTP was the cause of the wipe.ps1 script crashing. However, we validated that this shouldn’t be the case. Therefore, we performed an extensive analysis independent of the MITRE test, with the hope of finding the root cause of this behavior and sharing with MITRE, red teams, and other researchers.

Deep dive into the crash

Debugging the script wipe.ps1, we noticed an unexpected crash in the GetCommandLineW API, which was quite odd.

Figure 4. Call stack analysis for crash

Since the crash happens at kernelbase!GetCommandLineW, we examined its code before reflective loading:

Figure 5. GetCommandLineW code before patching

Note that the code consists of:

  1. An assignment to the RAX register (the return value register); the returned Unicode string is pointed by address 00007ffd200f9e68, as shown in the debugging session
  2. The RET instruction, which causes the function to return from the function
  3. Padding with the byte CC, which is encoded as INT 3; this is a debug-breakpoint and should never be executed due to the RET instruction

We then examined the code at the moment of the crash:

Figure 6. GetCommandLineW code at the crash

Note that there’s no RET instruction, so INT 3 (debug-breakpoint) was executed, causing the crash during the test (since no debugger is attached). Noting the byte encoding of the instructions and comparing them at a normal state and in the moment of the crash, we noticed a one-byte difference: the second byte changed from 8B to B8, causing a complete modification of the interpreted instruction! 8B is the opcode for a relative addressing move, while B8 is an immediate value move. The first byte 48 is a REX.W prefix, making the instruction refer to 64-bit operands.

Clearly, something strange was happening in the attack script wipe.ps1, so we decided to perform an extensive, line-by-line analysis of the attack script internals.

Anatomy of the reflective loader

As mentioned, the reflective loader used in the MITRE evaluation was inspired by Invoke-ReflectivePEInjection from PowerSploit, so analysis was relatively easier, vis-à-vis reverse engineering a new reflective loader.

A reflective loader is a tool for loading executable code into a process address space without invoking the operating system API, allowing attackers to avoid security products’ instrumentation of APIs such as LoadLibrary WinAPI that loads a DLL. Since .exe files are compiled with relocation tables (due to address space layout randomization (ASLR) support), many reflective loaders support loading of .exe files as well as DLLs.

When reflectively loading an .exe file, special care must be taken, as processes tend to rely on certain memory structures to be uniquely reserved to them. This is especially true for structures like the Process Environment Block (PEB), which contains important information about the current running process without transitioning into kernel mode. The reflective loader used by MITRE indeed takes special care of certain APIs that obtain information from the PEB; it does so by inline hooking.

Specifically, the reflective loader hooks the function GetCommandLineW that we saw earlier. Unless it does so, the reflected .exe code (sdelete.exe in this case) would fetch the original command line (the one for PowerShell.exe in this case) instead of the intended command line. Here’s a step-by-step analysis of the hooking:

  1. In the Update-ExeFunctions PowerShell function, the code fetches GetCommandLineW (and GetCommandLineA) by calling GetProcAddress on kernelbase.dll.

  1. The reflective loader then prepares a shellcode composed of the following parts:
    1. Possible REX.W prefix (byte 48) in case of a 64-bit process
    2. The MOV immediate instruction opcode (byte B8)
    3. An immediate value, which is an allocated address for the new command line buffer
    4. The RET instruction (byte C3)

  1. The reflective loader hooks the GetCommandLineW function by doing the following:
    1. Change the page permissions to RWX with the VirtualProtect API
    2. Call Write-BytesToMemory to copy the REX.W prefix and the MOV opcode to their place
    3. Call StructureToPtr to encode the new address after the MOV instructionl; this also takes care of endianness
    4. Call Write-BytesToMemory again, this time to copy the RET instruction

When performed correctly and fully, this should work well. However, our debugging showed only one-byte change (from 8B to B8) and no RET instruction. This meant that either StructureToPtr had some bug, or that patching was done partially. Assuming the latter, we concluded that the crash happens during the patching itself, after placing the MOV instruction but before encoding the new address, i.e. right after invoking Write-BytesToMemory.

Partial patching and unexpected callbacks

Debugging further, we discovered that the crash indeed happens after the first Write-BytesToMemory cmdlet. The call stack analysis showed that the call originates from PowerShell itself (or more precisely, from the CLR which is invoked by PowerShell), which is odd. This means that some code in PowerShell somehow tries to fetch the current process command line immediately after the cmdlet is executed.

We discovered that the code responsible for fetching the command line is the code that generates Event Tracing for Windows (ETW) for cmdlets. The Microsoft-Windows-PowerShell event provider exposes event IDs that log cmdlets, such as event 7937. Here’s an example of such an event:

Figure 7. Cmdlet tracing with ETW

Note the captured information, such as the cmdlet name, cmdlet type, and the process command line. The ETW writer for cmdlets is invoked after the cmdlet has finished running and has logged all the information. The command line itself is fetched by the ETW writer by invoking GetCommandLineW.

This means that an the ETW writer invoked for the first Write-BytesToMemory would invoke GetCommandLineW, but since only the first two bytes were patched, then GetCommandLineW is “half-patched”, eventually executing INT 3 and causing a crash.

While this explains the crash, it doesn’t explain why there was no crash when Microsoft Threat Protection was not present. The solution for this is simple: if there are no ETW listeners to the event, the ETW writer is never invoked, and therefore never tries to fetch the command line. Indeed, Microsoft Threat Protection listens to many ETW providers, including the Microsoft-Windows-PowerShell ETW.

To summarize, here is a flow diagram showing how this scenario runs:

Figure 8. Flow chart for the first Write-BytesToMemory cmdlet run

This conclusively proves that if any ETW listener registers to this ETW event (and not just Microsoft Threat Protection), the PowerSploit reflective loader implementation will crash. We reproduced this behavior without Microsoft Threat Protection and reported it to the MITRE red team to decide the course of action with Step 19.

What red teams can learn from this incident

PowerSploit is a known and widely used infrastructure for red teams. It’s used extensively and its codebase is regularly checked and updated. Even such a well-established project may contain unexpected bugs, some of which could only occur under special conditions such as specific environment changes like the one we described here.

Data we gathered using the advanced hunting capability in MTP further establishes this strong correlation: in real-world environments, 66% of the Invoke-ReflectivePEInjection invocations end up crashing their hosting PowerShell instance. This is a statistically significant proof of this bug in PowerSploit.

Figure 9. Advanced hunting query for correlating PowerShell crashes and Cmdlet invocation

The TL;DR advice for red teams is this: if you use “Invoke-ReflectivePEInjection” script during your regular penetration testing, be aware of an unexpected surprise in certain circumstances that may lead to immediate detection.

We thank MITRE for leading a transparent and collaborative evaluation process that encourages partnership and threat intelligence sharing. To learn how Microsoft Threat Protection did in the evaluation, read: Microsoft Threat Protection leads in real-world detection in MITRE ATT&CK evaluation.

Jonathan Bar Or

Microsoft Threat Protection Research Team

Talk to us

Questions, concerns, or insights on this story? Join discussions at the Microsoft Threat Protection and Microsoft Defender ATP tech communities.

Read all Microsoft security intelligence blog posts.

Follow us on Twitter @MsftSecIntel.