AMSI Primer
AMSI - Antimalware Scan Interface
Carlos Diaz
@dfirence
Overview
From a defensive standpoint, let's discuss and learn AMSI!
But first, let's align with the standard definition by the creators of AMSI at Microsoft Corporation.
Definition - By Microsoft Corporation
The Windows Antimalware Scan Interface (AMSI) is a versatile interface standard that allows your applications and services to integrate with any antimalware product that's present on a machine.
AMSI provides enhanced malware protection for your end-users and their data, applications, and workloads.
Ref - Learn More, MSDN AMSI
What is AMSI?
AMSI is Visibility
AMSI is basically another security visibility event source. Specifically, it is one of several resources that can allow defenders to gain detection capabilities of script based code. For example, vbScript or jScript code that would be executed within office applications or scriptlets can be inspected and matched with basic string pattern matching operators.
AMSI Visibility Source
Because AMSI is an interface offered by the Windows Operating System, it is accessed through code.
The general idea is that script based strings are extracted by the operating system and given to the AMSI component, then a subscriber can read the contents from the AMSI component, and consequently apply pattern matches against the contents.
What does AMSI do?
Basically it is a mechanism that can inform a detection/prevention component that some data (strings) matched an interesting pattern, and by consequence the detection/prevention component can take an action. By action we mean the way common security products apply a restriction (preventing), or notifying (reporting).
AMSI is not the only mechanism used on a device to determine or identify the presence of malware. It is one of many layers in the overall design of such solutions.
AMSI Notifies - Mainly
AMSI does not block, prevent, disrupt, or by any means restrict anything in the operating system. It scans and reports a match from declared patterns.
Let's explore how this all works conceptually, starting with a standard FILE_CREATION transaction from a fake application FOO.EXE.
Kernel Drivers - Disrupt
Since most of the needs to restrict something in Windows relates to special things called - Executive Objects, then it is a kernel-driver that can intercept those objects and apply a restriction before these objects are created.
Now, let's illustrate the way AMSI and KERNEL Drivers perform security restrictions based on a matching condition for the string "HelloWorld" being written into a fake file called FILE_TXT.
How is AMSI implemented?
Microsoft offers a programmatic API where software engineers can create AMSI Providers. Conceptually, an AMSI Provider is a program typically created by vendors of security solutions offering business value to disrupt the presence of malicious threats.
We will look at the conceptual usage of the Microsoft AMSI API functions and create some experiments to learn about AMSI.
Ref - Learn More MSDN AMSI API
AMSI API
The AMSI API is fairly simple and the table below briefly describes what each AMSI function does. Pay special attention to the Function Signature, we can derive an intuitive understanding that there are 2 crucial structures when using the AMSI API, those being the HAMSICONTEXT and HAMSISESSION.
AMSI API Function | Function Purpose | Function Signature |
---|---|---|
AmsiCloseSession | Close a session that was opened by AmsiOpenSession. |
|
AmsiInitialize | Initialize the AMSI API. |
|
AmsiNotifyOperation | Sends to the antimalware provider a notification of an arbitrary operation. |
|
AmsiOpenSession | Opens a session within which multiple scan requests can be correlated. |
|
AmsiResultIsMalware | Determines if the result of a scan indicates that the content should be blocked. |
|
AmsiScanBuffer | Scans a buffer-full of content for malware. |
|
AmsiScanString | Scans a string for malware. |
|
AmsiUninitialize | Remove the instance of the AMSI API that was originally opened by AmsiInitialize. |
|
AMSI API via AMSI.DLL
The Microsoft Corporation provides the AMSI API through a library (DLL). This DLL has exports, and the functions of the API are prefixed with Amsi*.
Using my parser pe-compass, we can see the exports below from the 64Bit version of amsi.dll.
{
"name": "amsi.dll",
"path": "\\\\?\\C:\\Windows\\System32\\amsi.dll",
"size": 111104,
"is_64": true,
"is_lib": true,
"is_dotnet": false,
"has_imports": true,
"has_exports": true,
"subsystem": 2,
"subsystem_caption": "The Windows Graphical User Interface (GUI) Subsystem",
"exports": {
"count": 13,
"functions": [
"AmsiCloseSession", // Close a session opened by AmsiOpenSession
"AmsiInitialize", // Initialize the AMSI API
"AmsiOpenSession", // Opens a session for multiple scan requests
"AmsiScanBuffer", // Scans a buffer-full of content for malware
"AmsiScanString", // Scans a string for malware.
"AmsiUacInitialize",
"AmsiUacScan",
"AmsiUacUninitialize",
"AmsiUninitialize", // Clears initialized AMSI Session
"DllCanUnloadNow",
"DllGetClassObject",
"DllRegisterServer",
"DllUnregisterServer"
]
},
"hashes": {
"sha2": "9267a786db2f180128f7a0e6d668f03e6348f85f5fe91465bc2f5a341c13d81c",
"ssdeep": "1536:M5Z6rNbz+pwTP2bzlmxX4suz0qL+FM6iuS4av25Vt+F+AQOSNy+Q/9ipFj2Nc8jk:M6MqbscczXuHiwE+fNylcRscgDRST"
}
}
AMSI API Mental Model
When designing a research tool or product prototype, we can start outlining 5 steps to use the AMSI API.
Ensure SAFE resource handling - you must use: AmsiCloseSession, AmsiUninitialize
AMSI API in C++
Using CPP or C++ language, a developer would use the AMSI API from the header file <amsi.h>.
Click To Expand: C++ AMSI Conceptual Implementation
#include <windows.h>
#include <amsi.h>
#include <iostream>
#include <string>
/**
* ShowBanner - Displays the program's banner.
*
* This function prints a banner to the console that includes
* the name of the program and some decorative dashes.
*/
void ShowBanner()
{
std::string dashes(64, '-'); // Create a string with 64 dashes
std::string header = "\n" + dashes + "\n";
std::string pname = header + "Cyballistics - amsi_concept.exe" + "\n" + dashes;
std::cout << pname << std::endl;
}
/**
* IsMalware - Checks if the scan result indicates malware.
*
* @param scan_result The result of the AMSI scan.
* @return True if the result indicates malware, otherwise false.
*/
bool IsMalware(AMSI_RESULT scan_result)
{
return (scan_result == AMSI_RESULT_DETECTED);
}
/**
* HResultToString - Converts an HRESULT value to a readable string.
*
* @param hr The HRESULT value to convert.
* @return A string representing the error message associated with the HRESULT.
*/
std::string HResultToString(HRESULT hr)
{
char* errorMsg = nullptr;
DWORD size = FormatMessageA(
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
nullptr, hr, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&errorMsg, 0, nullptr);
std::string errorString;
if (size)
{
errorString = errorMsg;
LocalFree(errorMsg);
}
else
{
errorString = "Unknown error";
}
return errorString;
}
int main()
{
HAMSICONTEXT amsiContext = nullptr;
HAMSISESSION amsiSession = nullptr;
AMSI_RESULT amsiResult;
HRESULT hrStatus;
LPCWSTR amsiAppName = L"DefenderSquad-AMSI-Provider";
LPCWSTR amsiContentName = L"TestContent";
std::string msg = "";
ShowBanner(); // Display the banner
// Our Test Buffer For AMSI Engine
const char* testBuffer = "Hello From Evil World";
ULONG bufferSize = (ULONG)strlen(testBuffer);
// Step 1: Initialize AMSI
hrStatus = AmsiInitialize(amsiAppName, &amsiContext);
if (FAILED(hrStatus))
{
msg = "Unable to initialize AMSI: " + HResultToString(hrStatus);
goto exitEarly;
}
// Step 2: Open New AMSI Session
hrStatus = AmsiOpenSession(amsiContext, &amsiSession);
if (FAILED(hrStatus))
{
msg = "Unable to open new AMSI session: " + HResultToString(hrStatus);
goto exitProgram;
}
// Step 3: Scan the buffer
hrStatus = AmsiScanBuffer(amsiContext,
(PVOID)testBuffer,
bufferSize,
amsiContentName,
amsiSession,
&amsiResult);
if (FAILED(hrStatus))
{
msg = "[error] - AmsiScanBuffer: " + HResultToString(hrStatus);
goto exitProgram;
}
// Step 4: Check Scan Result Status
if (IsMalware(amsiResult))
{
msg = "Scan Result: MALWARE DETECTION\n";
}
else
{
msg = "Scan Result: CLEAN VERDICT\n";
}
msg += "\nBuffer Size: " + std::to_string(bufferSize) + "\nBuffer Content: " + testBuffer;
// Leaves Early, No Cleanup Needed
exitEarly:
std::cout << msg << "Early Exit" << std::endl;
return 1;
// Proper AMSI Cleanup
exitProgram:
if (amsiSession)
{
AmsiCloseSession(amsiContext, amsiSession);
}
if (amsiContext)
{
AmsiUninitialize(amsiContext);
}
std::cout << msg << std::endl;
return 0;
}
AMSI API in C#
Using C#, a developer will use InteropServices for the creation of a foreign function interface (FFI). The FFI between C#
and C++
is commonly known as a bridge where Managed Code (C#) is safely bridged by the operating system to route calls to Unmanaged Code (C++).
What is Managed Code?
Managed Code is a term reserved for C#
development, it basically means that when you write C# code, the execution of such code is constrained to the runtime virtual machine where all code execution and management of resources is handled by the runtime. Things like garbage collection are explict examples of a management action handled by the runtime.
Click To Expand: C# AMSI Conceptual Implementation
using System;
using System.Runtime.InteropServices;
/// <summary>
/// Provides native methods for interacting with AMSI
/// (Anti-Malware Scan Interface).
/// </summary>
public static class AmsiNativeMethods
{
private const string AmsiDll = "amsi.dll";
/// <summary>
/// Initializes the AMSI interface.
/// </summary>
/// <param name="appName">The name of the application.</param>
/// <param name="amsiContext">The AMSI context handle.</param>
/// <returns>Returns 0 on success, or an error code on failure.</returns>
[DllImport(
AmsiDll,
EntryPoint = "AmsiInitialize",
CallingConvention = CallingConvention.StdCall)]
public static extern int AmsiInitialize(
[MarshalAs(UnmanagedType.LPWStr)] string appName,
out IntPtr amsiContext);
/// <summary>
/// Uninitializes the AMSI interface.
/// </summary>
/// <param name="amsiContext">The AMSI context handle.</param>
[DllImport(
AmsiDll,
EntryPoint = "AmsiUninitialize",
CallingConvention = CallingConvention.StdCall)]
public static extern void AmsiUninitialize(
IntPtr amsiContext);
/// <summary>
/// Opens an AMSI session.
/// </summary>
/// <param name="amsiContext">The AMSI context handle.</param>
/// <param name="amsiSession">The AMSI session handle.</param>
/// <returns>Returns 0 on success, or an error code on failure.</returns>
[DllImport(
AmsiDll,
EntryPoint = "AmsiOpenSession",
CallingConvention = CallingConvention.StdCall)]
public static extern int AmsiOpenSession(
IntPtr amsiContext,
out IntPtr amsiSession);
/// <summary>
/// Closes an AMSI session.
/// </summary>
/// <param name="amsiContext">The AMSI context handle.</param>
/// <param name="amsiSession">The AMSI session handle.</param>
[DllImport(
AmsiDll,
EntryPoint = "AmsiCloseSession",
CallingConvention = CallingConvention.StdCall)]
public static extern void AmsiCloseSession(
IntPtr amsiContext,
IntPtr amsiSession);
/// <summary>
/// Scans a buffer for malware.
/// </summary>
/// <param name="amsiContext">The AMSI context handle.</param>
/// <param name="buffer">Pointer to the buffer to be scanned.</param>
/// <param name="length">Length of the buffer.</param>
/// <param name="contentName">Name of the content.</param>
/// <param name="amsiSession">The AMSI session handle.</param>
/// <param name="result">Scan result.</param>
/// <returns>Returns 0 on success, or an error code on failure.</returns>
[DllImport(
AmsiDll,
EntryPoint = "AmsiScanBuffer",
CallingConvention = CallingConvention.StdCall)]
public static extern int AmsiScanBuffer(
IntPtr amsiContext,
IntPtr buffer,
uint length,
[MarshalAs(UnmanagedType.LPWStr)] string contentName,
IntPtr amsiSession,
out AMSI_RESULT result);
public enum AMSI_RESULT
{
AMSI_RESULT_CLEAN = 0,
AMSI_RESULT_NOT_DETECTED = 1,
AMSI_RESULT_DETECTED = 32768
}
}
class Program
{
static void Main(string[] args)
{
IntPtr amsiContext;
IntPtr amsiSession;
AmsiNativeMethods.AMSI_RESULT result;
// Initialize AMSI
int hr = AmsiNativeMethods.AmsiInitialize(
"AMSI-DefenderSquad-Scanner",
out amsiContext);
if (hr != 0)
{
Console.WriteLine("AmsiInitialize failed with error: " + hr);
return;
}
// Open a session
hr = AmsiNativeMethods.AmsiOpenSession(
amsiContext,
out amsiSession);
if (hr != 0)
{
Console.WriteLine("AmsiOpenSession failed with error: " + hr);
AmsiNativeMethods.AmsiUninitialize(amsiContext);
return;
}
// The buffer to be scanned
byte[] buffer = System.Text.Encoding.UTF8.GetBytes(
"This is a test buffer to be scanned for malware.");
uint bufferSize = (uint)buffer.Length;
// Allocate unmanaged memory for the buffer
IntPtr bufferPtr = Marshal.AllocHGlobal(buffer.Length);
Marshal.Copy(buffer, 0, bufferPtr, buffer.Length);
// Scan the buffer
hr = AmsiNativeMethods.AmsiScanBuffer(
amsiContext,
bufferPtr,
bufferSize,
"BufferScan",
amsiSession,
out result);
if (hr != 0)
{
Console.WriteLine("AmsiScanBuffer failed with error: " + hr);
}
else
{
if (result == AmsiNativeMethods.AMSI_RESULT.AMSI_RESULT_DETECTED)
{
Console.WriteLine("Malware detected!");
}
else
{
Console.WriteLine("No malware detected.");
}
}
// Free the allocated memory
Marshal.FreeHGlobal(bufferPtr);
// Close the session
AmsiNativeMethods.AmsiCloseSession(
amsiContext,
amsiSession);
// Uninitialize AMSI
AmsiNativeMethods.AmsiUninitialize(amsiContext);
Console.ReadKey();
}
}
AMSI and ETW Events
AMSI and its notifications can be consumed in several ways, remember we talked about AMSI being another visibility event source. Keeping this in mind, AMSI has several ETW providers accessible for experimentation, they are recognizable by the Antimalware keyword as seen in the image below.
ETW Providers
Provider Name | Event Types |
---|---|
Microsoft-Antimalware-AMFilter | Various filtering events related to antimalware activities |
Microsoft-Antimalware-Engine | Engine-related events including initialization, updates, and operations |
Microsoft-Antimalware-Protection | Protection events related to antimalware mechanisms |
Microsoft-Antimalware-RTP | Real-time protection events |
Microsoft-Antimalware-Service | Service-related events such as on-demand scans, engine updates, cache operations, and Spynet activities |
Microsoft-Antimalware-UI | User interface-related events for antimalware software |
ETW Event ID 1101
Event ID 1101 is generated by the AMSI ETW Provider - Microsoft-Antimalware-Scan-Interface.
This ETW provider is subscribed to any software program that emits AMSI scans, and specifically, this ETW provider captures the activity of the AmsiScanBuffer function provided by the AMSI Api.
Event ID 1101 Context
Field | Contextual Value |
---|---|
AppName | The name of the application where strings of interest were processed |
Content | The strings (data) scanned |
ContentSize | Size in bytes of content field |
OriginalSize | Size in bytes of content originally processed |
ScanStatus | Status of the scan at the time AmsiScanBuffer is executing |
ScanResult | Status (conclusive) verdict based on AMSI Enum Scan Value e.g., AMSI_RESULT_DETECTED |
Hash | A private hashing implementation for the sequence of bytes seen in the Content field |
The image below is a live trace session using the tool Microsoft ETW Message Analyzer. I configure the ETW AMSI Scan Interface provider and start testing with a command-prompt by running powershell commands interactively.
ETW Test For Event ID 1101
To test for this event, you can use your own or preferred ETW trace monitoring tool like Message Analyzer, and run these commands from a command-prompt.
Open a command-prompt, and run these commands while observing the result of the commands being captured by the ETW provider.
powershell.exe
From Powershell, run this command
gwmi -class win32_computersystem
References
These are excellent resources to learn more about AMSI.
Community Research
- S3CUR3TH1SSH1T - Bypass AMSI
- F-SECURE - Hunting AMSI Bypass
- GITHUB TOPIC - AMSI
- MDSEC - Powershell Bypass AMSI
- PAYATU - Bypass AMSI
- REDCANARY - Better Know A Data Source
- TRENDMICRO - Hunting AMSI Bypass
Practical References