Powershell调用AMSI分析 - lampvipstudy

admin 2021年12月31日15:12:13评论46 views字数 4897阅读16分19秒阅读模式

1、Antimalware Scan Interface

  微软官方是这样解释的:Windows 反恶意软件扫描接口 (AMSI) 是一种通用的接口标准,可让应用程序和服务与计算机上存在的任何反恶意软件产品集成。 AMSI 为最终用户及其数据、应用程序和工作负荷提供增强的恶意软件防护。
  默认情况下,Windows Defender是与 AMSI API进行交互的。在运行所有PowerShell命令或脚本时,为了防止用户免受恶意的PowerShell脚本的攻击,都要调用这个接口进行检查。在整个操作系统中,AMSI会捕获每个PowerShell、Jscrip、VBScript、VBA或运行.NET命令或.NET脚本,都会将其传递给本地防病毒软件进行检查。
  AMSI是在2015年发布的Windows10的版本中首次引入的,在之前的版本中是没有的。

2、AMSI 的工作原理

  当用户执行脚本或启动 PowerShell 时,AMSI.dll 被动态加载进入内存空间。在执行之前,防病毒软件使用以下两个 API 来扫描缓冲区和字符串以查找恶意软件的迹象。
  AmsiScanBuffer()
  AmsiScanString()
  如果是恶意软件则不会启动执行,并且会显示一条消息,表明脚本已被防病毒软件阻止。
  如下图所示,我大概绘制了执行过程方便大家理解

  我们查阅微软官方文档可以找到AMSI的api,其中包括AmsiInitialize, AmsiOpenSession, AmsiScanString, AmsiScanBuffer, AmsiCloseSession。由于这些功能已经被微软记录下来,我们能够很容易的理解捕获复杂的过程。
https://docs.microsoft.com/en-us/windows/win32/amsi/antimalware-scan-interface-functions

AmsiInitialize

  当PowerShell启动时,它会加载AMSI.DLL和AmsiInitialize函数,它有两个参数,如下的函数原型所示

HRESULT AmsiInitialize( 
LPCWSTR appName, 
HAMSICONTEXT *amsiContext 
);

appName
调用 AMSI API 的应用程序的名称、版本或 GUID 字符串。
amsiContext
必须传递给 AMSI API 的所有后续调用的 HAMSICONTEXT 类型的句柄。
AmsiInitialize发生在我们能够调用任何PowerShell命令之前,这就意味着我们不能以任何方式修改它。
https://docs.microsoft.com/zh-cn/windows/win32/api/amsi/nf-amsi-amsiinitialize

AmsiOpenSession

  一旦AmsiInitialize执行完成并且创建HAMSICONTEXT类型的句柄,AMSI就可以接受我们发出的命令。在我们执行powershell命令时,它会调用AmsiOpenSession,AmsiOpenSession函数结构如下:

HRESULT AmsiOpenSession(
  HAMSICONTEXT amsiContext,
  HAMSISESSION *amsiSession
);

amsiContext
最初从AmsiInitialize收到的HAMSICONTEXT类型的句柄。
amsiSession
HAMSISESSION 类型的句柄,必须传递给会话中对 AMSI API 的所有后续调用。
https://docs.microsoft.com/en-us/windows/win32/api/amsi/nf-amsi-amsiopensession

AmsiScanBuffer

  函数的官方说明:

HRESULT AmsiScanBuffer( 
HAMSICONTEXT amsiContext, 
PVOID buffer, 
ULONG length, 
LPCWSTR contentName, 
HAMSISESSION amsiSession, 
AMSI_RESULT *result 
);

amsiContext
最初从AmsiInitialize收到的HAMSICONTEXT类型的句柄。
buffer
从中读取要扫描的数据的缓冲区。
length
要从buffer读取的数据的长度(以字节为单位)。
contentName
正在扫描的内容的文件名、URL、唯一脚本 ID 或类似内容。
amsiSession
如果要在一个会话中关联多个扫描请求,请将session设置为最初从AmsiOpenSession接收的HAMSISESSION类型的句柄。否则,将session设置为nullptr。
result
扫描结果。请参阅AMSI_RESULT
应用程序应使用AmsiResultIsMalware来确定是否应阻止内容。
https://docs.microsoft.com/en-us/windows/win32/api/amsi/nf-amsi-amsiscanbuffer

AmsiCloseSession

  AmsiCloseSession函数发生在扫描完成后,一旦扫描完成,AMSI将调用AmsiCloseSession函数来关闭当前的AMSI扫描会话,这个函数对我们来说,并不是很重要。因为它发生在扫描结果之后,并且如果我们要绕过AMSI扫描的话,都必须在调用它之前进行。

3、windbg、HOOKING----powershell进程

  我们首先使用frida-trace工具,attach到正在运行的PowerShell进程。并且HOOK 所有Amsi开头的所有函数
  我们在powershell中执行'AmsiUtils'命令

Frida-trace监控到的amsi.dll函数调用,可以看到AMSI中函数调用顺序

  我们来混淆一下这个’AmsiUtils’,改变为’Ams’+’iUtils’

  当我们使用编码,或者拼接的方法来绕过powershell时,这种方法也是有效的。但是这种方法非常的有限。随着defender的升级,或者策略改变,这种方法也将失效。我们今天要讨论是有没有方法让AMSI失效。从而逃避defender的检查。
  我们使用windbg来查看一下,AmsiOpenSession函数

  反汇编 AmsiOpenSession函数看到如下代码,

  在上图中,我们反汇编了AmsiOpenSession会话的所有汇编指令。可以到到,如果需要跳过函数执行,我们可以将前三个字节修改,从而达到控制je的跳转条件,让JE跳转到最后,让函数直接执行结束。

00007ff9`de71250b b857000780      mov     eax,80070057h
00007ff9`de712510 c3               ret

  JE跳转的条件是(ZF)标志位为1。如果我们将TEST指令修改为XOR指令,我们就可以强制修改ZF零标志设置为1,并且欺骗CPU,从而控制执行流程,函数将返回无效的返回值。

  XOR将两个寄存器作为参数,但如果我们提供与第一个和第二个参数相同的寄存器,XOR的运算结果为0。因为如果运算结果最终为零,则设置零标志位位1。

  我们将TEST RDX、RDX指令覆盖,强制执行流程到错误分支结束函数执行,尝试禁用AMSI。
  另外我们需要注意的是目标TEST RDX,RDX 的十六进制是3个字节0x4885d2,我们的指令也需要三个字节,这个很重要。XOR EAX,EAX指令它的十六进制为0x31C0,为了保持长度一致,我们在后边增加0x90

nasm > xor eax,eax
00000000  31C0              xor eax,eax

  最后指令为0x31c090

  好了现在让我们开始使用windbg进行修改吧,技术test rdx,rdx的地址为00007ff9`de7124c0
直接将指令修改为

xor eax,eax 
Nop

  并且在跳转到的00007ff9`de71250b地址打断点在执行的时候,我看到,cpu执行到了我们的预想的流程处

  我们再次执行被标记为恶意程序的’AmsiUtils’字符串

已经成功执行。

4、拓展

  换一种思路,既然我们可以替换TEST RDX,RDX。那么我们也可以直接将函数的开头直接替换为最后的返回指令

00007ff9`de71250b b857000780      mov     eax,80070057h
00007ff9`de712510 c3               ret

 修改以后

  经过测试这种修改也是可以的。

5、在powershell中直接进行修改

  我们首先获取AmsiOpenSession的地址,这一点使用.net非常方便。废话不多说,直接上图

  获得地址AmsiOpenSession地址:140711155541184
  获取到地址以后,就剩下修改了。在Windows中我们必须考虑Windows的内存保护。通常情况下,代码页被设置为PAGE_EXECUTE_READ(0x20),只读权限,这意味着我们只可以读取和执行此代码,但不能写入它。这是一个无法绕过的问题。现在就想办法解决这个问题
  微软内存保护相关文章
https://docs.microsoft.com/en-us/windows/win32/memory/memory-protection-constants

  修改内存权限,可以通过win32api函数VirtualProtect进行修改。VirtualProtect函数参数

BOOL VirtualProtect( 
LPVOID lpAddress, 
SIZE_T dwSize, 
DWORD flNewProtect, 
PDWORD lpflOldProtect 
);

  微软官网文档
https://docs.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualprotect
  直接上图修改查看

  将内存修改为可写可执行以后,接下来就可以直接使用Marshal.Copy方法进行复制覆盖了。
Marshal.Copy

BOOL public static void Copy (
IntPtr source, 
IntPtr[] destination, 
int startIndex, 
int length
);

 微软官网文档https://docs.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.marshal.copy?view=net-5.0

[System.Runtime.InteropServices.Marshal]::Copy($buf, 0, $Address, 3)

 最后查看修改成功

总结

  这里呢我还是跟以往一样,也只提供一种分析的思路和方法。有能力的同学或者熟悉C# .net的,写出自动化的修改代码,自然也不在话下。当然呢也不仅仅限于AmsiOpenSession函数,也可以分析分析AmsiScanBuffer、AmsiScanString函数。

  在写这个笔记分享之前,在网上看了很多关于绕过AMSI的技术文章,这些技术也略有不同。但主要还是两大方向进行。一种是通过一些自动化的混淆工具,将自己的代码进行编码。这种方法呢,节省时间,速度快。而且呢也不会影响功能。但缺点就是被同一工具混效过的代码特征明显,时间久了很容易被查杀。文件大小也变的非常大,例如Invoke-Mimikatz,嵌入了base64 编码的 Mimikatz 二进制文件,大小约为3MB。使用ISE-Steroids 进行混淆后其大小约 9MB 大。而且自动化混淆工具,也并不能保证一定会绕过 AMSI。另外一种就是手动操作,优点当然是可靠了。但是相比自动化工具,就过程就显得比较复杂一些了。这种方式需要了解代码以及AMSI的实际工作方式。
  一篇学习笔记拿过来分享,欢迎指正。。。。

BY:先知论坛

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2021年12月31日15:12:13
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   Powershell调用AMSI分析 - lampvipstudyhttps://cn-sec.com/archives/710708.html

发表评论

匿名网友 填写信息