My Learning Journal
← Back to home

Windows AMSI Analysis for Bypass

AMSIBypass

Recently I started looking into how Windows AMSI works. Found some materials like this PDF file. It contains a summary of the current AMSI bypass techniques and also 2 new ideas. With this reference, I looked into Ghidra disassmebly code on amsi.dll and recovered psudo C++ type code with chatgpt’s help.

Exported functions

AmsiInitialize AmsiOpenSession AmsiScanBuffer AmsiScanString AmsiUacInitialize AmsiUacScan AmsiCloseSession AmsiUacUninitialize AmsiUninitialize

Psudo Code

AmsiInitialize

HRESULT AmsiInitialize(LPCWSTR appName, HAMSICONTEXT *out)
{
   if (!appName || !out) return E_INVALIDARG;
   if (wcslen(appName) > 0x7ffd) return E_INVALIDARG;   
   auto ctx = (_HAMSICONTEXT*)CoTaskMemAlloc(32);
   if (!ctx)                      
      return E_OUTOFMEMORY;

   ctx->Signature = 'AMSI';       // Win10 only
   ctx->AppName   = CopyString(appName);
   if (!ctx->AppName)             { Free(ctx); return E_OUTOFMEMORY; }

   IClassFactory *cf;
   RETURN_IF_FAILED(DllGetClassObject(CLSID_Antimalware, IID_IClassFactory, &cf));
   RETURN_IF_FAILED(cf->CreateInstance(nullptr, IID_IAntimalware, &ctx->Antimalware));

   ctx->SessionCount = rand();
   *out = (HAMSICONTEXT)ctx;
   cf->Release();
   return S_OK;
}

AmsiOpenSession

HRESULT AmsiOpenSession(HAMSICONTEXT   amsiContext, HAMSISESSION*  amsiSession)
{
    // 1.  Validate arguments
    if (amsiSession == nullptr ||
        amsiContext == nullptr ||
        amsiContext->Signature   != 'AMSI' ||          // ASCII for “AMSI”
        amsiContext->AppName     == nullptr ||        // caller must have provided an app name
        amsiContext->Antimalware == nullptr)          // must have a CAmsiAntimalware instance
    {
        return E_INVALIDARG;  // 0x80070057
    }

    // 2.  Atomically bump the session counter
    // We use InterlockedIncrement so no explicit lock/unlock is needed.
    //
    // This ensures that each call gets a unique, nonzero session ID.
    ULONG newId = InterlockedIncrement(&amsiContext->SessionCount);

    // 3.  Handle the unlikely wrap‑around case (ID==0)
    if (newId == 0)
    {
        // If SessionCount wrapped back to zero, bump again
        newId = InterlockedIncrement(&amsiContext->SessionCount);
    }

    // 4.  Return the session handle (just the ID)
    *amsiSession = newId;

    return S_OK;  // 0
}

AmsiScanBuffer

HRESULT AmsiScanBuffer(HAMSICONTEXT*  amsiContext,
                       void*          buffer,
                       ULONG          length,
                       LPCWSTR        contentName,
                       HAMSISESSION   session,
                       AMSI_RESULT*   result)
{
    // 1.  Optional WPP trace of the call (if logging level >= 4)
    // if (WPP_GLOBAL_Control->Flags & 0x4)
    //     WPP_SF_qqDqq(…, buffer, length, amsiContext, …);

    // 2.  Validate arguments
    if (buffer        == nullptr ||
        length        == 0       ||
        result        == nullptr ||
        amsiContext   == nullptr ||
        amsiContext->Signature != 'AMSI' ||            // literal ASCII “AMSI”
        amsiContext->AppName   == nullptr ||
        amsiContext->Antimalware == nullptr)
    {
        return E_INVALIDARG;   // 0x80070057
    }

    // 3.  Build the CAmsiBufferStream wrapper
    CAmsiBufferStream stream;
    stream.vtable       = &CAmsiBufferStream::`vftable`;
    stream.amsiSession  = session;
    stream.buffer       = buffer;
    stream.length       = length;
    stream.name         = amsiContext->AppName;
    stream.contentName  = contentName;

    // 4.  Delegate to the provider chain
    //    HRESULT Scan(
    //        IAmsiStream*          stream,
    //        AMSI_RESULT*          result,
    //        IAntimalwareProvider* *provider  // optional out
    //    );
    HRESULT hr = amsiContext->Antimalware->Scan(
                     &stream,
                     result,
                     /*provider=*/ nullptr);

    // 5.  Return the provider’s verdict HRESULT
    return hr;
}

AmsiScanString

HRESULT AmsiScanString(HAMSICONTEXT    amsiContext,
                       LPCWSTR         string,
                       LPCWSTR         contentName,
                       HAMSISESSION    session,
                       AMSI_RESULT*    result)
{
    // 1.  Validate basic pointers
    if (string == nullptr || result == nullptr)
    {
        return E_INVALIDARG;    // 0x80070057
    }

    // 2.  Compute the length of the string in characters
    size_t charCount = 0;
    while (string[charCount] != L'\0')
    {
        ++charCount;
    }

    // 3.  Convert to byte length and check for overflow
    //    (sizeof(wchar_t) == 2 on Windows)
    uint64_t byteCount = charCount * sizeof(wchar_t);
    if (byteCount > 0xFFFFFFFFu)
    {
        return E_INVALIDARG;    // length too large to pass as a ULONG
    }

    // 4.  Delegate to AmsiScanBuffer for the actual scan
    return AmsiScanBuffer(
        amsiContext,
        const_cast<LPWSTR>(string),      // buffer pointer
        static_cast<ULONG>(byteCount),   // buffer size in bytes
        contentName,
        session,
        result);
}

AmsiUacInitialize

HRESULT AmsiUacInitialize(HAMSICONTEXT *outCtx)
{
       if (WPP_INFO_ON) TraceEnter(outCtx);
   
       if (!outCtx)
           return E_INVALIDARG;
   
       auto ctx = (_HAMSICONTEXT*)CoTaskMemAlloc(32);
       if (!ctx)
           return E_OUTOFMEMORY;
   
       ZeroMemory(ctx, 32);
       ctx->Signature = 'OMSI';                   // UAC tag
   
       auto engine = new (std::nothrow) CAmsiUacAntimalware;
       if (!engine) {
           AmsiUninitializeImpl(ctx);
           return E_OUTOFMEMORY;
       }
   
       HRESULT hr = engine->Initialize();        // load UAC providers
       if (FAILED(hr)) {
           AmsiUninitializeImpl(ctx);
           return hr;
       }
   
       ctx->AppName      = engine;              // note the field reuse!
       ctx->Antimalware  = nullptr;             // not used in UAC flavour
       ctx->SessionCount = 0;                   // unused for UAC
   
       *outCtx = ctx;
   
       if (WPP_INFO_ON) TraceSuccess(ctx);
       return S_OK;
 }

AmsiUacScan

HRESULT AmsiUacScan(HAMSICONTEXT                  hCtx,
                    const AMSI_UAC_REQUEST_CONTEXT *req,
                    AMSI_RESULT                   *result,
                    ULONGLONG                     *providerData)
{
    TRACE_ENTRY(hCtx, req);

    if (!hCtx || hCtx->Signature != 'OMSI' ||
        !hCtx->AppName || !req || !result || !providerData)
        return E_INVALIDARG;

    IAntimalwareUacProvider *prov = nullptr;
    HRESULT hr = static_cast<CAmsiUacAntimalware*>(hCtx->uacAntiMalware)
                       ->UacScan(req, result, &prov);

    if (FAILED(hr))
    {
        TRACE_FAILURE(hr);
        if (prov) prov->Release();
        return hr;
    }

    *providerData = 0;
    if (prov)
    {
        prov->GetProviderData(providerData);   // provider‑specific
        prov->Release();
    }

    return S_OK;
}

AmsiCloseSession

void AmsiCloseSession(HAMSICONTEXT    amsiContext,
                      HAMSISESSION    session)
{
    // Directly invoke the COM provider’s CloseSession implementation.
    // This informs the anti‑malware provider that the given session is ending.
    amsiContext->Antimalware->CloseSession(session);
}

AmsiUacUninitialize

void AmsiUacUninitialize(HAMSICONTEXT* amsiContext)
{
    // 1.  Optional WPP trace if the verbose‑trace flag is set
    if (WPP_GLOBAL_Control != nullptr &&
        (WPP_GLOBAL_Control->Flags & 0x4) != 0)
    {
        // opcode 0x1F ⇒ “UAC uninitialize” event
        WPP_SF_q(
            WPP_GLOBAL_Control->u,
            0x1F,
            &WPP_89359e7f70ff34f84acef37beb0b5af8_Traceguids,
            amsiContext
        );
    }

    // 2.  Only uninitialize if we were ever initialized
    if (amsiContext != nullptr)
    {
        AmsiUninitializeImpl(amsiContext);
    }
}

AmsiUninitialize

void AmsiUninitialize(HAMSICONTEXT amsiContext)
{
    // 1.  Optional WPP trace if the verbose‑trace flag is set
    if (WPP_GLOBAL_Control != nullptr &&
        (WPP_GLOBAL_Control->Flags & 0x4) != 0)
    {
        // opcode 0x17 ⇒ “AMSI uninitialize” event
        WPP_SF_q(
            WPP_GLOBAL_Control->u,
            0x17,
            &WPP_89359e7f70ff34f84acef37beb0b5af8_Traceguids,
            amsiContext
        );
    }

    // 2.  Only uninitialize if we were ever initialized
    if (amsiContext != nullptr)
    {
        AmsiUninitializeImpl(amsiContext);
    }
}

Data Structures and Classes

HAMSICONTEXT

typedef struct _AMSICONTEXT {
    DWORD           Signature;      // 'AMSI' – absent on Win11+
#if defined(_WIN64)
    DWORD           _Padding0;
#endif
    Union {
        PCWSTR     AppName;        // points to heap‑copied string
        CAmsiUacAntimalware *    puacAntimalware;
    };
    IAntimalware *    Antimalware;
    ULONGLONG     SessionCount;   // incremented by AmsiOpenSession
} AMSICONTEXT, *PAMSICONTEXT;

typedef HAMSICONTEXT AMSICONTEXT *;

IAntimalware

[
    object,
    pointer_default(unique),
    uuid(82d29c2e‑f062‑44e6‑b5c9‑3d9a2f24a2df)   // IID_IAntimalware
]
interface IAntimalware : IUnknown
{
    HRESULT Scan(
        [in]  IAmsiStream            *stream,
        [out] AMSI_RESULT            *result,
        [out] IAntimalwareProvider  **provider   // optional – who detected it?
    );

    void    CloseSession(
        [in]  ULONGLONG               session     // HAMSISESSION from AmsiOpenSession
    );
};

CAmsiAntimalware

//----------------------------------------------------------------------
// Pseudo‑class definition (fields only shown as comments)
//----------------------------------------------------------------------
class CAmsiAntimalware 
  : public CComObjectRootEx<CComMultiThreadModel>,
    public IAntimalware
{
public:
    // COM map
    BEGIN_COM_MAP(CAmsiAntimalware)
        COM_INTERFACE_ENTRY(IAntimalware)
    END_COM_MAP()

    // Constructor / Destructor
    CAmsiAntimalware();
    ~CAmsiAntimalware();

    // ATL lifecycle hooks
    HRESULT FinalConstruct();
    void    FinalRelease();

    // IUnknown
    STDMETHOD(QueryInterface)(REFIID riid, void** ppv) override;
    STDMETHOD_(ULONG, AddRef)() override;
    STDMETHOD_(ULONG, Release)() override;

    // IAntimalware
    STDMETHOD(Scan)(
        IAmsiStream*             stream,
        AMSI_RESULT*             result,
        IAntimalwareProvider**   provider
    ) override;

    STDMETHOD(CloseSession)(
        ULONGLONG session
    ) override;

private:
    // ETW configuration callback
    static void WINAPI EtwConfigurationCallback(
        const GUID*                   sourceId,
        ULONG                         isEnabled,
        UCHAR                         level,
        ULONGLONG                     matchAnyKeyword,
        ULONGLONG                     matchAllKeyword,
        const EVENT_FILTER_DESCRIPTOR* filterData,
        PVOID                         context
    );

    // Helper to emit the AMSI_SCANBUFFER event
    HRESULT GenerateEtwEvent(
        IAmsiStream* stream,
        ScanStatus   scanStatus,
        AMSI_RESULT  result
    );

private:
    // COM reference count
    LONG     m_refCount;

    // Critical section + init flag
    CRITICAL_SECTION m_cs;
    bool             m_csInitialized;

    // Dynamically loaded antimalware providers
    CComPtr<IAntimalwareProvider> m_providers[16];
    ULONG                         m_providerCount;

    // ETW registration
    REGHANDLE m_regHandle;
    bool      m_isEventProvEnabled;

    // ETW content‐size thresholds (min & max bytes to include in events)
    ULONG     m_minEtwContentSize;
    ULONG     m_maxEtwContentSize;

    // CLSIDs read from the registry for provider creation
    GUID      m_clsids[16];
};

//----------------------------------------------------------------------
// Pseudo‑implementation of the constructor
//----------------------------------------------------------------------
CAmsiAntimalware::CAmsiAntimalware()
  : m_refCount(0)                      // *&this->super_CComObjectRootEx = 0
  , m_providerCount(0)                   // this->providerCount = 0
  , m_regHandle(nullptr)                 // this->regHandle = 0
  , m_isEventProvEnabled(0)                         // this->m_isEventProvEnabled = 0
  , m_minEtwContentSize(6)                         // this->m_minEtwContentSize = 6
  , m_maxEtwContentSize(0x8000)                    // this->m_maxEtwContentSize = 0x8000
{
    // 1) Initialize the critical section
    InitializeCriticalSection(&m_cs);
    //    (equivalent of zeroing OwningThread and SpinCount)

    // 2) Construct the provider array slots
    //    (calls default CComPtr ctor 16 times)
    for (int i = 0; i < 16; ++i) {
        new (&m_providers[i]) CComPtr<IAntimalwareUacProvider>();
    }

    // 3) Zero out the CLSID array
    for (int i = 0; i < 16; ++i) {
        m_clsids[i] = GUID{ 0, 0, 0, {0,0,0,0,0,0,0,0} };
    }

    // note: other internal fields (e.g. session lists) would be initialized here
}

CAmsiAntimalware::~CAmsiAntimalware()
{
    // 1) If we registered an ETW provider (or similar), unregister it
    if (m_regHandle != 0) {
        EventUnregister(m_regHandle);
        m_regHandle = 0;
    }

    // 2) Destroy each CComPtr<IAntimalwareUacProvider> in the array
    //    (matches the eh_vector_destructor_iterator)
    for (int i = 0; i < 16; ++i) {
        m_providers[i].~CComPtr<IAntimalwareUacProvider>();
    }

    // 3) If we ever initialized the critical section, delete it
    if (m_csInitialized != 0) {
        m_csInitialized = 0;
        DeleteCriticalSection(&m_cs);
    }

    // Note: base‑class (CComObjectRootEx) cleanup is automatic
}

HRESULT CAmsiAntimalware::Scan(IAmsiStream* stream,
                               AMSI_RESULT* result,
                               IAntimalwareProvider** winner)
{
    if (!result) return E_POINTER;
    *result  = AMSI_RESULT_CLEAN;
    if (winner) *winner = nullptr;

    trace_enter(stream);                    // WPP

    AMSI_RESULT best = AMSI_RESULT_CLEAN;
    int         bestIdx = -1;
    HRESULT     hrLast  = S_OK;

    for (unsigned i = 0; i < m_providerCount; ++i)
    {
        AMSI_RESULT rProv = AMSI_RESULT_CLEAN;
        ULONGLONG   t0    = now();
        hrLast = m_providers[i]->Scan(stream, &rProv);
        ULONGLONG dt = now() - t0;

        sample_latency_telemetry(i, dt, hrLast, rProv);

        if (SUCCEEDED(hrLast))
        {
            if (rProv < best) { best = rProv; bestIdx = i; }
            trace_provider_ok(i, rProv == best);
        }
        else
            trace_provider_failed(i, hrLast);
    }

    if (bestIdx >= 0)                       // at least one success
    {
        *result = best;
        if (winner) { *winner = m_providers[bestIdx]; (*winner)->AddRef(); }
    }
    else                                    // nobody succeeded
        *result = AMSI_RESULT_NOT_DETECTED;

    emit_final_etw(stream, hrLast, *result);
    trace_exit(stream, hrLast, *result);

    return hrLast;
}

// Pseudo‑implementation of CAmsiAntimalware::CloseSession
void CAmsiAntimalware::CloseSession(ULONGLONG session)
{
    // If we have any registered providers, notify each one to close the session
    for (size_t i = 0; i < m_providerCount; ++i) {
        // Grab the raw interface pointer from our CComPtr array
        IAntimalwareProvider* pProvider = m_providers[i];
        if (pProvider) {
            // Call the provider’s CloseSession (vtable slot at offset 0x20)
            pProvider->CloseSession(session);
        }
    }
}

HRESULT CAmsiAntimalware::FinalConstruct()
{
    // 1) If we already have an ETW registration, unregister it (preserving last‑error)
    if (m_regHandle != 0) {
        DWORD dwErr = GetLastError();
        EventUnregister(m_regHandle);
        SetLastError(dwErr);
    }
    m_regHandle = 0;

    // 2) Register our AMSI ETW provider
    //    - &AMSI is the GUID for our ETW provider
    //    - EtwConfigurationCallback will be invoked on config changes
    //    - 'this' is context passed to the callback
    //    - regHandle receives the registration handle
    ULONG status = EventRegister(&AMSI,
                                 EtwConfigurationCallback,
                                 this,
                                 &m_regHandle);

    if (status != ERROR_SUCCESS) {
        // Convert the Win32 error into an NTSTATUS via WIL helper
        return wil::details::in1diag3::Return_NtStatus(/*return‑addr*/,
                                                        0x270,
                                                        this,
                                                        status);
    }

    // 3) Check whether our ETW provider is currently enabled
    //    TRACE_LEVEL_INFORMATION (4) and keyword 1 are what AMSI uses
    isEventProvEnabled = (EventProviderEnabled(m_regHandle, /*level*/4, /*keyword*/1) != FALSE);

    // 4) Load configured COM‑based AmsiProviders from the registry
    //    - regHandle: ETW registration handle
    //    - hKeyRoot: root hive where AMSI settings live (e.g. HKEY_LOCAL_MACHINE)
    //    - amsiProviders: output array of CComPtr<IAntimalwareProvider>
    //    - clsids: array of GUID slots we zeroed in ctor
    //    - in_stack…: reserved/context info (pass through)
    //    - &providerCount: number of providers actually created
    HRESULT hr = AmsiComCreateProviders<IAntimalwareProvider>(
                    m_regHandle,
                    HKEY_LOCAL_MACHINE,      // or whichever root hive you use
                    m_providers,
                    m_clsids,
                    /*reserved*/ nullptr,
                    &m_providerCount);

    // 5) Special case: if no registry entries were found but ETW is still enabled,
    //    treat that as success (AMSI can still work via ETW)
    if (hr == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND) && m_isEventProvEnabled) {
        hr = S_OK;
    }

    return hr;
}

void WINAPI CAmsiAntimalware::EtwConfigurationCallback(
    const GUID*                   /*sourceId*/,
    ULONG                         /*isEnabled*/,
    UCHAR                         /*level*/,
    ULONGLONG                     /*matchAnyKeyword*/,
    ULONGLONG                     /*matchAllKeyword*/,
    const EVENT_FILTER_DESCRIPTOR* filterData,
    PVOID                         context
)
{
    // Recover our 'this' pointer from the callback context
    CAmsiAntimalware* self = static_cast<CAmsiAntimalware*>(context);

    // If no filter descriptor was provided, optionally emit a WPP trace
    if (filterData == nullptr) {
        // If WPP tracing is initialized and the Information level is enabled...
        if (WPP_GLOBAL_Control != nullptr && (WPP_GLOBAL_Control->Flags & 0x4) != 0) {
            // Log a “configuration-cleared” event (opcode 0x23)
            WPP_SF_(WPP_GLOBAL_Control->u,
                    0x23,
                    &WPP_775dec0a01b93551f8626bd70cc9859c_Traceguids);
        }
    }
    // Otherwise, if the filter descriptor carries our special config type...
    else if (filterData->Type == static_cast<ULONG>(-0x80000000)) {
        // The FilterData->Ptr points to a block whose offsets 0x18 and 0x1C
        // hold the new ETW level and keyword mask, respectively.
        auto dataBlock = reinterpret_cast<uint8_t*>(filterData->Ptr);

        // Update our instance fields at offsets 0x1D4 and 0x1D8
        // (these correspond to the same fields set in FinalConstruct)
        self->m_minEtwContentSize = *reinterpret_cast<ULONG*>(dataBlock + 0x18);
        self->m_maxEtwContentSize = *reinterpret_cast<ULONG*>(dataBlock + 0x1C);
    }
    // If we got a descriptor of any other type, optionally log a warning
    else {
        if (WPP_GLOBAL_Control != nullptr && (WPP_GLOBAL_Control->Flags & 0x4) != 0) {
            // Log a “configuration-unknown-type” event (opcode 0x24)
            WPP_SF_(WPP_GLOBAL_Control->u,
                    0x24,
                    &WPP_775dec0a01b93551f8626bd70cc9859c_Traceguids);
        }
    }
}

HRESULT CAmsiAntimalware::GenerateEtwEvent(
    IAmsiStream* stream,
    ScanStatus   scanStatus,
    AMSI_RESULT  result
)
{
    if (!isEventProvEnabled) {
        // ETW tracing is disabled—nothing to do.
        return S_OK;
    }

    HRESULT hr;
    ULONG   fetched = 0;

    // 1) Read the content size (ATTRIBUTE_CONTENT_SIZE == 2)
    ULONGLONG contentSize = 0;
    hr = stream->GetAttribute(
             AMSI_ATTRIBUTE_CONTENT_SIZE,
             sizeof(contentSize),
             &contentSize,
             &fetched);
    if (FAILED(hr)) {
        return wil::Return_Hr(HRESULT_FROM_WIN32(ERROR_AMSI_ATTRIBUTE),
                               L"Failed to read content size",
                               hr);
    }

    // 2) Read the content address (ATTRIBUTE_CONTENT_ADDRESS == 3)
    BYTE* contentAddr = nullptr;
    hr = stream->GetAttribute(
             AMSI_ATTRIBUTE_CONTENT_ADDRESS,
             sizeof(contentAddr),
             &contentAddr,
             &fetched);
    if (FAILED(hr)) {
        return wil::Return_Hr(HRESULT_FROM_WIN32(ERROR_AMSI_ATTRIBUTE),
                               L"Failed to read content address",
                               hr);
    }

    // 3) Read the AMSI session ID (ATTRIBUTE_SESSION == 4)
    ULONGLONG sessionId = 0;
    hr = stream->GetAttribute(
             AMSI_ATTRIBUTE_SESSION,
             sizeof(sessionId),
             &sessionId,
             &fetched);
    if (FAILED(hr)) {
        return wil::Return_Hr(HRESULT_FROM_WIN32(ERROR_AMSI_ATTRIBUTE),
                               L"Failed to read session ID",
                               hr);
    }

    // 4) Read the application name (ATTRIBUTE_APP_NAME == 0)
    //    First query length, then allocate CoTaskMem and fetch the string.
    ULONG appNameLen = 0;
    hr = stream->GetAttribute(
             AMSI_ATTRIBUTE_APP_NAME,
             /*cbSize*/ 1,
             /*pBuffer*/ nullptr,
             &appNameLen);
    if (hr == HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER)) {
        // Allocate space for the wide‑string (appNameLen bytes)
        PWSTR appName = wil::make_cotaskmem_string(nullptr, appNameLen);
        if (!appName) {
            return E_OUTOFMEMORY;
        }
        hr = stream->GetAttribute(
                 AMSI_ATTRIBUTE_APP_NAME,
                 appNameLen,
                 appName,
                 &appNameLen);
        if (FAILED(hr)) {
            CoTaskMemFree(appName);
            return wil::Return_Hr(HRESULT_FROM_WIN32(ERROR_AMSI_ATTRIBUTE),
                                   L"Failed to read application name",
                                   hr);
        }

        // 5) Similarly, read the content name (ATTRIBUTE_CONTENT_NAME == 1)
        ULONG contentNameLen = 0;
        hr = stream->GetAttribute(
                 AMSI_ATTRIBUTE_CONTENT_NAME,
                 /*cbSize*/ 1,
                 /*pBuffer*/ nullptr,
                 &contentNameLen);
        if (hr == HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER)) {
            PWSTR contentName = wil::make_cotaskmem_string(nullptr, contentNameLen);
            if (!contentName) {
                CoTaskMemFree(appName);
                return E_OUTOFMEMORY;
            }
            hr = stream->GetAttribute(
                     AMSI_ATTRIBUTE_CONTENT_NAME,
                     contentNameLen,
                     contentName,
                     &contentNameLen);
            if (FAILED(hr)) {
                CoTaskMemFree(appName);
                CoTaskMemFree(contentName);
                return wil::Return_Hr(HRESULT_FROM_WIN32(ERROR_AMSI_ATTRIBUTE),
                                       L"Failed to read content name",
                                       hr);
            }

            // 6) Only emit the event if contentSize meets our thresholds
            if (contentSize >= minEtwContentSize) {
                // Truncate to maxEtwContentSize if necessary
                SIZE_T truncatedSize = static_cast<SIZE_T>(std::min<ULONGLONG>(
                    contentSize, maxEtwContentSize));

                // Allocate a buffer and copy the first truncatedSize bytes
                BYTE* buffer = static_cast<BYTE*>(CoTaskMemAlloc(truncatedSize));
                if (!buffer) {
                    CoTaskMemFree(appName);
                    CoTaskMemFree(contentName);
                    return E_OUTOFMEMORY;
                }
                memcpy_s(buffer, truncatedSize, contentAddr, truncatedSize);

                // 7) Compute a SHA‑256 hash of the buffer (optional)
                BYTE hashValue[32] = {};
                ComputeSha256Hash(buffer, truncatedSize, hashValue);

                // 8) Prepare ETW data descriptors
                EVENT_DATA_DESCRIPTOR desc[10] = {};
                EventDataDescCreate(&desc[0], &sessionId,      sizeof(sessionId));
                EventDataDescCreate(&desc[1], &scanStatus,     sizeof(scanStatus));
                EventDataDescCreate(&desc[2], &result,         sizeof(result));
                EventDataDescCreate(&desc[3], appName,         appNameLen);
                EventDataDescCreate(&desc[4], contentName,     contentNameLen);
                EventDataDescCreate(&desc[5], &minEtwContentSize, sizeof(minEtwContentSize));
                EventDataDescCreate(&desc[6], &maxEtwContentSize, sizeof(maxEtwContentSize));
                EventDataDescCreate(&desc[7], &contentSize,    sizeof(contentSize));
                EventDataDescCreate(&desc[8], &truncatedSize,  sizeof(truncatedSize));
                EventDataDescCreate(&desc[9], buffer,          truncatedSize);
                // (If you want to include the hash, add more descriptors here)

                // 9) Fire the ETW event
                ULONG etwHr = EventWrite(m_regHandle, &AMSI_SCANBUFFER, _countof(desc), desc);
                // ignore etwHr failures for this pseudocode

                // 10) Cleanup the buffer
                CoTaskMemFree(buffer);
            }

            // 11) Cleanup string allocations
            CoTaskMemFree(contentName);
        }

        CoTaskMemFree(appName);
    }

    return S_OK;
}

IAmsiStream

[
    object,
    uuid(3e47f2e5‑81d4‑4d3b‑897f‑545096770373),
    pointer_default(unique)
]
interface IAmsiStream : IUnknown
{
    HRESULT GetAttribute(               // ask for metadata
        [in]  AMSI_ATTRIBUTE attribute,
        [in]  ULONG          dataSize,  // size of caller’s buffer
        [out] unsigned char  *data,     // receives the value
        [out] ULONG          *retData); // bytes returned

    HRESULT Read(                       // pull payload bytes
        [in]  ULONGLONG      position,  // offset from start
        [in]  ULONG          size,      // bytes requested
        [out] unsigned char  *buffer,   // caller‑allocated
        [out] ULONG          *readSize);// bytes actually copied
};

static const GUID IID_IAmsiStream = 
{ 0x3e47f2e5, 0x81d4, 0x4d3b, { 0x89, 0x7f, 0x54, 0x50, 0x96, 0x77, 0x03, 0x73 } };

class AmsiStream : public IAmsiStream {
private:
    long                refCount_;
    PVOID               buffer_;           // pointer to payload data
    ULONGLONG           length_;           // length of buffer
    PVOID               name_;             // arbitrary name identifier
    LPCWSTR             contentName_;      // content name (e.g., file path)
    HAMSISESSION        amsiSession_;      // AMSI session handle

public:
    // Constructor
    AmsiStream(PVOID buffer,
               ULONGLONG length,
               PVOID name,
               LPCWSTR contentName,
               HAMSISESSION amsiSession)
        : refCount_(1),
          buffer_(buffer),
          length_(length),
          name_(name),
          contentName_(contentName),
          amsiSession_(amsiSession)
    {}

    // IUnknown methods
    HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppvObject) override {
        if (riid == IID_IUnknown || riid == IID_IAmsiStream) {
            *ppvObject = static_cast<IAmsiStream*>(this);
            AddRef();
            return S_OK;
        }
        *ppvObject = nullptr;
        return E_NOINTERFACE;
    }

    ULONG STDMETHODCALLTYPE AddRef() override {
        return InterlockedIncrement(&refCount_);
    }

    ULONG STDMETHODCALLTYPE Release() override {
        ULONG count = InterlockedDecrement(&refCount_);
        if (count == 0) {
            delete this;
        }
        return count;
    }

    // IAmsiStream methods
    HRESULT STDMETHODCALLTYPE GetAttribute(
        AMSI_ATTRIBUTE attribute,
        ULONG dataSize,
        unsigned char *data,
        ULONG *retData) override
    {
        if (!data || !retData) {
            return E_POINTER;
        }
        switch (attribute) {
            case AMSI_ATTRIBUTE_APP_NAME:
                {
                    size_t nameLen = (wcslen(contentName_) + 1) * sizeof(WCHAR);
                    *retData = (ULONG)min((size_t)dataSize, nameLen);
                    memcpy(data, contentName_, *retData);
                }
                break;
            case AMSI_ATTRIBUTE_CONTENT_NAME:
                {
                    // Example: return contentName_
                    size_t nameLen = (wcslen(contentName_) + 1) * sizeof(WCHAR);
                    *retData = (ULONG)min((size_t)dataSize, nameLen);
                    memcpy(data, contentName_, *retData);
                }
                break;
            case AMSI_ATTRIBUTE_CONTENT_SIZE:
                {
                    // Return length as ULONGLONG
                    if (dataSize < sizeof(ULONGLONG)) {
                        return E_INVALIDARG;
                    }
                    *(ULONGLONG*)data = length_;
                    *retData = sizeof(ULONGLONG);
                }
                break;
            default:
                return E_NOTIMPL;
        }
        return S_OK;
    }

    HRESULT STDMETHODCALLTYPE Read(
        ULONGLONG position,
        ULONG size,
        unsigned char *buffer,
        ULONG *readSize) override
    {
        if (!buffer || !readSize) {
            return E_POINTER;
        }
        if (position >= length_) {
            *readSize = 0;
            return S_OK;
        }
        // Calculate available bytes
        ULONGLONG available = length_ - position;
        ULONG toRead = (ULONG)min<ULONGLONG>(size, available);
        memcpy(buffer, (BYTE*)buffer_ + position, toRead);
        *readSize = toRead;
        return S_OK;
    }
};

IAntimalwareProvider

static const IID IID_IAntimalwareProvider =
{ 0xb2cabfe3, 0xfe04, 0x42b1,
  { 0xa5, 0xdf, 0x08, 0xd4, 0x83, 0xd4, 0xd1, 0x25 } };

[
    object,
    uuid(B2CABFE3‑FE04‑42B1‑A5DF‑08D483D4D125),
    pointer_default(unique)
]
interface IAntimalwareProvider : IUnknown
{
    // v‑table slot 3  (offset +0x18)
    HRESULT Scan(
        [in]  IAmsiStream *stream,
        [out] AMSI_RESULT *verdict);

    // v‑table slot 4  (offset +0x20)
    void    CloseSession(
        [in]  ULONGLONG    session);      // same handle AmsiOpenSession returns

    // Vendors are free to add more methods after slot 4,
    // but amsi.dll never calls them.
};

Keep functions related to bypass

The call stack for AMSI provider initialization

The call stack for initialization:
AmsiInitialize
└─► DllGetClassObject
     └─► CComCreator&lt;class_ATL::CComAggObject_&gt;::CreateInstance
          └─► CAmsiAntimalware::FinalConstruct
               └─► AmsiComCreateProviders
                    └─► AmsiComSecureLoadInProcServer
                         └─► DllGetClassObject -- 3rd party provider com dll function
                             CheckTrustLevel
		              └─► CheckTrustLevelImpl
	                           └─► NtGetCachedSigningLevel
		                       NtSetCachedSigningLevel
		                       NtCompareSigningLevels

From the pdf file mentioned in the beginning of this article, besides other methods to bypass, the new ones focus on the point when providers are being loaded. The function “DllGetClassObject” of each 3rd party provider com dlls create the requested provider classes. “DllGetClassObject” fails can lead to the result of create providers failure. No providers then no detection.

There’s another point can do the same, which is “CheckTrustLevel” function under the same function “AmsiComSecureLoadInProcServer”. This function loads NT functions from ntdll.dll for trust level checking. However, if we patch functions from ntdll.dll to fail the provider creation, it might not succeed because EDRs may monitor ntdll.dll memory to detect memory patch.

AmsiComCreateProviders<IAntimalwareProvider>

void AmsiComCreateProviders<IAntimalwareProvider>(HKEY           hRoot,
                            IAntimalwareProvider*  providers[16],
                            GUID           clsids[16],
                            UINT          *pCount)
{
    *pCount = 0;

    CGuidEnum e;
    HRESULT hr = e.StartEnum(hRoot, L"Software\\Microsoft\\AMSI\\Providers");
    if (FAILED(hr)) { TRACE_ERROR(hr); return; }

    const bool sample = (rand() % 100) == 0;

    while (*pCount < 16)
    {
        GUID clsid;
        hr = e.NextGuid(&clsid);
        if (hr == ERROR_NO_MORE_ITEMS) break;
        if (FAILED(hr)) { TRACE_WARN(hr); continue; }

        IAntimalwareProvider* prov = nullptr;
        ULONGLONG t0 = UtilGetCurrentTime();
        hr = AmsiComSecureLoadInProcServer(&clsid,
                                           &IID_IAntimalwareProvider,
                                           &prov);
        ULONGLONG dt = UtilGetCurrentTime() - t0;

        if (SUCCEEDED(hr))
        {
            providers[*pCount] = prov;   // refcount kept
            clsids[*pCount]    = clsid;
            ++*pCount;

            TRACE_INFO_LOAD_OK(clsid, dt, sample);
        }
        else
        {
            TRACE_WARN_LOAD_FAIL(clsid, hr);
            if (prov) prov->Release();
        }
    }

    if (*pCount == 0)
        TRACE_ERROR_NO_PROVIDER();
}

AmsiComSecureLoadInProcServer

void AmsiComSecureLoadInProcServer(
    const GUID& clsid,
    const IID& iid,
    IAntimalwareProvider** provider)
{
    // 1. Initialize output
    *provider = nullptr;

    // 2. Attempt to find a cached module under shared lock
    AcquireSRWLockShared(&g_ModCacheLock);
    HMODULE hModule = FindModuleInCache(clsid);
    ReleaseSRWLockShared(&g_ModCacheLock);

    if (hModule) {
        // Cached: directly instantiate via DllGetClassObject
        auto dllGetClassObject =
            (PFN_DllGetClassObject)GetProcAddress(hModule, "DllGetClassObject");
        if (!dllGetClassObject) return;  // handle error

        IClassFactory* factory = nullptr;
        HRESULT hr = dllGetClassObject(clsid, IID_IClassFactory,
                                       reinterpret_cast<void**>(&factory));
        if (FAILED(hr)) return;  // handle error

        hr = factory->CreateInstance(nullptr, iid,
                                     reinterpret_cast<void**>(provider));
        factory->Release();
        return;
    }

    // 3. Convert CLSID to string for registry lookup
    LPOLESTR clsidStr = nullptr;
    if (FAILED(StringFromCLSID(clsid, &clsidStr))) {
        return;  // handle error
    }

    // 4. Build registry key path: HKLM\Software\Classes\CLSID\<clsidStr>\InprocServer32
    std::wstring regPath = L"Software\\Classes\\CLSID\\" +
                           std::wstring(clsidStr) + L"\\InprocServer32";
    CoTaskMemFree(clsidStr);

    // 5. Read DLL path from registry
    WCHAR rawPath[MAX_PATH] = {0};
    DWORD size = sizeof(rawPath);
    if (ERROR_SUCCESS != RegGetValueW(
            HKEY_LOCAL_MACHINE,
            regPath.c_str(),
            nullptr,
            RRF_RT_REG_SZ,
            nullptr,
            rawPath,
            &size))
    {
        return;  // handle error
    }

    // 6. Expand environment variables and trim quotes/spaces
    std::wstring dllPath = ExpandEnvironmentStringsW(rawPath);
    TrimQuotesAndSpaces(dllPath);

    // 7. Verify trust of the DLL
    if (FAILED(CheckTrustLevel(dllPath.c_str(), clsid))) {
        return;  // handle error
    }

    // 8. Load library and cache under exclusive lock
    hModule = LoadLibraryExW(dllPath.c_str(), nullptr, 0);
    if (!hModule) {
        return;  // handle error
    }

    AcquireSRWLockExclusive(&g_ModCacheLock);
    InsertModuleInCache(clsid, hModule);
    ReleaseSRWLockExclusive(&g_ModCacheLock);

    // 9. Instantiate provider via class factory
    auto dllGetClassObject2 =
        (PFN_DllGetClassObject)GetProcAddress(hModule, "DllGetClassObject");
    if (!dllGetClassObject2) return;  // handle error

    IClassFactory* factory2 = nullptr;
    HRESULT hr2 = dllGetClassObject2(clsid, IID_IClassFactory,
                                     reinterpret_cast<void**>(&factory2));
    if (FAILED(hr2)) return;  // handle error

    factory2->CreateInstance(nullptr, iid,
                              reinterpret_cast<void**>(provider));
    factory2->Release();
}

CheckTrustLevel

HRESULT CheckTrustLevel(LPWSTR dllPath, const GUID *clsid)
{
    // -----------------------------------------------------------------
    // 1. Read AMSI\FeatureBits from HKLM
    // -----------------------------------------------------------------
    DWORD featureBits = 0;
    DWORD cbData      = sizeof(featureBits);
    DWORD type        = 0;
    int   policy      = 1;                    // default if anything goes wrong

    LSTATUS sts = RegGetValueW(
        HKEY_LOCAL_MACHINE,
        L"Software\\Microsoft\\AMSI",
        L"FeatureBits",
        RRF_RT_REG_DWORD | RRF_SUBKEY_WOW6432KEY,   // 0x10000010
        &type,
        &featureBits,
        &cbData);

    if (sts == ERROR_SUCCESS)
    {
        // Accept only 1, 2 or 4.  Value 3 or anything outside that range ⇒ revert to 1
        if ((featureBits < 1 || featureBits > 4) || featureBits == 3)
        {
            // WPP_TRACE: out‑of‑range registry value
            policy = 1;
        }
        else
        {
            policy = static_cast<int>(featureBits);
        }
    }
    else
    {
        // WPP_TRACE: registry read failed (sts holds the Win32 error)
        policy = 1;
    }

    // WPP_TRACE: selected policy (1,2,4)

    // -----------------------------------------------------------------
    // 2. Derive the flag for the implementation call
    // -----------------------------------------------------------------
    BYTE flag = 4;                // normal
    if (policy == 4) flag = 7;    // “highest” trust level becomes 7

    BYTE implResult = 0;          // will be written by the callee

    // -----------------------------------------------------------------
    // 3. Invoke the real checker
    // -----------------------------------------------------------------
    HRESULT hr = CheckTrustLevelImpl(dllPath, flag, &implResult);

    // WPP_TRACE: dllPath, flag, implResult

    // -----------------------------------------------------------------
    // 4. Occasionally emit telemetry
    // -----------------------------------------------------------------
    if ((rand() % 1000) == 0 &&
        UtilGetCurrentTime() >  DAT_180018050 &&
        (_DAT_180018060 & 0x400000000000ull) != 0 &&
        (DAT_180018068 & 0x400000000000ull) == DAT_180018068)
    {
        // Package up a telemetry payload (sizes, pointers etc. omitted here)
        // _tlgWriteTransfer_EventWriteTransfer(...);
    }

    // -----------------------------------------------------------------
    // 5. Done
    // -----------------------------------------------------------------
    return hr;
}

CheckTrustLevelImpl

HRESULT CheckTrustLevelImpl(LPCWSTR dllPath, DWORD flag, DWORD *trustLevelOut)
{
    // 1.  Lazy‑initialise the function‑pointer table
    if (!g_CodeIntegrityAPIs.initialized)
    {
        AcquireSRWLockExclusive(&g_CILock);
        if (!g_CodeIntegrityAPIs.initialized)
        {
            if (!InitCodeIntegrity(&g_CodeIntegrityAPIs))      // loads ntdll & resolves Nt* APIs
            {
                // WPP trace: InitCodeIntegrity failed
                ReleaseSRWLockExclusive(&g_CILock);
                return 0x8008136F;                             // ‑0x7FF8EC61
            }
        }
        ReleaseSRWLockExclusive(&g_CILock);
    }

    // 2.  Open the target file read‑only
    HANDLE hFile = CreateFileW(dllPath,
                               GENERIC_READ,
                               FILE_SHARE_READ,
                               nullptr,
                               OPEN_EXISTING,
                               FILE_ATTRIBUTE_NORMAL,
                               nullptr);
    if (hFile == INVALID_HANDLE_VALUE)
        return HRESULT_FROM_WIN32(GetLastError());             // plus WPP logging

    // 3.  Try to read its cached signing level
    SIGNING_LEVEL  level = 0;
    NTSTATUS status = g_CodeIntegrityAPIs.NtGetCachedSigningLevel(
                          hFile, nullptr, &level, nullptr, nullptr, nullptr);

    // 4.  If there is no cache entry, ask the kernel to compute one
    if (!NT_SUCCESS(status))
    {
        status = g_CodeIntegrityAPIs.NtSetCachedSigningLevel(
                     SE_IMAGE_TYPE,           // 4
                     flag,                    // 4 or 7 from caller
                     &hFile,                  // array of 1 handle
                     1,
                     hFile);

        if (!NT_SUCCESS(status))
        {
            CloseHandle(hFile);
            return HRESULT_FROM_NT(status) | 0x10000000;       // mark as CI error
        }

        // Retry the “Get”
        status = g_CodeIntegrityAPIs.NtGetCachedSigningLevel(
                     hFile, nullptr, &level, nullptr, nullptr, nullptr);

        if (!NT_SUCCESS(status))
        {
            CloseHandle(hFile);
            return HRESULT_FROM_NT(status) | 0x10000000;
        }
    }

    // 5.  Success: give the caller the level byte
    *trustLevelOut = level;

    // 6.  Compare the obtained level against the policy flag
    status = g_CodeIntegrityAPIs.NtCompareSigningLevels(level, (SIGNING_LEVEL)flag);
    HRESULT hr = NT_SUCCESS(status) ? S_OK
                                    : (HRESULT_FROM_NT(status) | 0x10000000);

    CloseHandle(hFile);
    return hr;
}