password
status
date
icon
category
tags
slug
summary

0x00 什么是AMSI

AMSI 全称 Antimalware Scan Interface,反恶意软件扫描接口,是微软为了对抗无文件攻击而开发的安全组件。历史上,杀毒引擎和 EDR 产品在对抗基于文件的恶意软件方面较为有效,但内存中的恶意代码一直是一个具有挑战性的盲点。在没有 AMSI 之前,安全 厂商要想检测在内存中执行的代码,需要将检测代码注入到待检测的程序中,这显然并不容易实现,并且随着系统更新需要不断对实现方式进行维护。
微软认识到需要改进内存中的检测能力,同时提供一个稳定的接口供自己和第三方供应商使用。由此产生的就是反恶意软件扫描接口(AMSI)。AMSI 实质上是一个管道,把 powershell 或者其他程序执行的代码传送给防病毒程序进行检测,微软设计的初衷是希望各种程序都可以接入 AMSI ,现在一般情况下只有微软开发的程序接入了 AMSI。
本体是 c:\\windows\\system32\\amsi.dll 它提供了通用的标准接口(COM接口、Win32 API),API是为接入 AMSI 的程序提供的,COM 接口是为防病毒厂商提供的。
可以使用以下命令获取接入了 AMSI 的 exe 及 dll 文件。
接入了 AMSI 接口的程序如下:
  • PowerShell(>2.0):由 System.Management.Automation.dll 实现
  • VBScript:由 vbscript.dll 实现
  • JScript:由 jscript.dlljscript9.dlljscriptlegacy.dll 实现
  • Office 文档中的 VBA 宏:由 VBE7.dll 实现
  • Excel 4.0 宏:由 excel.exeexcelcnv.exe 实现
  • Exchange Server 2016:由 Microsoft.Exchange.HttpRequestFiltering.dll 实现
  • WMI:由 fastprox.dll 实现
  • .NET 内存中的程序集加载:在 .NET 4.8+ 中由 clr.dllcoreclr.dll 实现
  • 卷影复制操作:由 VSSVC.exeswprv.dll 实现
  • 用户账户控制(UAC)提升:由 consent.exe 实现

0x01 AMSI 的实现

当应用程序尝试提交要由 AMSI 提供程序扫描的内容时,应用程序将 amsi.dll 加载并调用其 AmsiInitialize 和 AmsiOpenSession 函数以建立 AMSI 会话。然后通过 AmsiScanString 或  AmsiScanBuffer 函数提交要扫描的内容。
AmsiScanString 绕过方法公开后, AmsiScanBuffer取代了 AmsiScanString。(顺带一提,AmsiScanBuffer也能被绕过,但微软不再认为 AMSI 绕过是一种漏洞,修复的并不积极。)
  • 第一个参数是应用程序的名称,第二个参数是指向名为 amsiContext的上下文句柄的指针。这个名为 amsiContext的上下文句柄在每个后续 AMSI 相关函数中都会使用。
  • AmsiInitialize的调用发生在我们能够调用任何 PowerShell 命令之前,这意味着我们无法以任何方式影响这个过程。
    • AmsiInitialize的第一次调用无法被干扰,但可以反射调用 AmsiUninitialize ,这会导致powershell 重新进行 AmsiInitialize ,这次调用是可以提前干扰的。
一旦 AmsiInitialize 完成并创建上下文结构,AMSI 就可以解析发出的命令。当我们执行 PowerShell 命令时,会调用 AmsiOpenSession API。
  • 其作用是使用已经初始化的amsiContext上下文句柄来创建一个新的会话句柄。amsiContext保存了 AMSI 的全局状态,而每个会话句柄则代表了一个独立的扫描会话。
随后调用 AmsiScanBuffer 对内容进行扫描。
  • 第一个参数是 AMSI 上下文句柄,后面是指向包含要扫描的内容的缓冲区的指针,以及缓冲区的长度。以下参数是输入标识符 (contentName)、会话句柄 (amsiSession),最后是指向扫描结果存储缓冲区的指针。
  • 根据微软文档的说法,返回结果越大威胁越大大于等于32768被视为恶意软件。软件对于不同的返回结果可以自行实现如何处理,对于 powershell,判断为恶意时会对命令进行阻断。
供应商的 AMSI 提供程序 DLL 接收AmsiScanBuffer提交的内容并进行分析。供应商可以使用其关联的 COM GUID 值在 HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\AMSI\Providers 注册表项中注册多个 AMSI 提供程序。
  • 对于多个 AMSI 提供程序,有一个认为恶意,那 AMSI 就会认为恶意,并且不再送给接下来的提供程序扫描。
在 Windows 10 上注册AMSI 提供程序的方法 (具体实现见第二章 ):
  • 在键中 HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\AMSI\Providers ,有一个子键,其名称为 {2781761E-28E0-4109-99FE-B9D127C57AFE}
  • 若要确定与该 GUID 值关联的相应 AMSI 提供程序 DLL,可以查看如下键  HKEY_CLASSES_ROOT\CLSID\{2781761E-28E0-4109-99FE-B9D127C57AFE}\InprocServer32 - (default) .该注册表值的内容为 %ProgramData%\Microsoft\Windows Defender\Platform\4.18.2110.6-0\MpOav.dll 。因此,MpOav.dll 是特定供应商(在本例中为 Microsoft)的 AMSI 提供程序 DLL,负责接收和处理应用程序传递给它的内容。

0x02 分析 Powershell AMSI 扫描的实现

2.1 源码分析

powershell是开源的,可以对源码直接进行分析,要分析的代码主要在 PowerShell/src/System.Management.Automation/security/SecuritySupport.csPowerShell/src/System.Management.Automation/engine/runtime/CompiledScriptBlock.cs
前者实现了 AmsiUtils 类,后者调用 AmsiUtils 类的方法进行扫描。
CompiledScriptBlock.csPerformSecurityChecks 函数调用 ScanContent 进行扫描。
ScanContent 只是调用了WinScanContent ,实际的扫描逻辑都是在WinScanContent 里。
不少文章 都是这么写的,但这只是开源的跨平台 powershell 7.x 才有的设计,在 windows 自带的 powershell 5.x 中,扫描逻辑位于 ScanContent
PSEtwLog.LogAmsiUtilStateEvent 这个也是7.x新增的,windows自带的 powershell 没有,有了这个,安全产品检测 AMSI 绕过就容易多了(这个也能绕过)。

2.2 汇编分析

notion image
使用 API Monitor 看下api 调用。
AmsiInitialize 调用发生在powershell初始化时,执行语句时,先 AmsiOpenSession 打开会话 ,然后用 AmsiScanBuffer 扫描,再用AmsiCloseSession 关闭会话。
这里只分析 AmsiOpenSession 的调用,其他函数的调用与之类似 。

AmsiOpenSession

使用 windbg 设置断点。
bp amsi!AmsiOpenSession
查看调用栈。
最近的3处调用如下,内存的权限均为 PAGE_EXECUTE_READ ,不修改内存权限的情况下只有 call rax 这个有可能用于绕过,别的 都是写死的。

0x03 分析  [System.Reflection.Assembly]::Load() AMSI 扫描的实现

notion image
[System.Reflection.Assembly]::Load() 可以加载C#程序集,在powershell中也可以调用,其也会调用 AMSI ,扫描不通过时会产生上述报错。
使用 dnSpy 分析 Load方法的实现。
跟进 nLoadImage
标记为 [MethodImpl(MethodImplOptions.InternalCall)],这意味着它是通过内部调用实现的,而不是用托管代码(C#)实现。
使用 windbg 进行调试
反汇编结果如下
使用 mdToken 检索 nLoadImage 方法的 MethodDesc
列出了 MethodDesc 的信息。这将提供实现 nLoadImage 的对象的方法表的地址。
方法表 (Method Table) 是一种数据结构,用于描述类型的所有方法,并提供访问这些方法的机制。
列出方法表并找到 nLoadImage
因此,nLoadImage 的本机地址是 0x00007fffffc24c70
clr!AssemblyNative::LoadImage 调用 clr!AmsiScan 的调用栈为
可以用 IDA 分析 clr.dll 研究 AmsiScan 具体实现。
CLRLoadLibraryEx 是对 LoadLibraryExW 的封装,因为没有启动新进程,当前进程还是 powershell进程,LoadLibraryExW 会返回已经加载的 amsi.dll 模块的句柄,并增加其引用计数。这意味着在powershell 中对 amsi.dll 进行的修改,在 [System.Reflection.Assembly]::Load() 也会生效。
在 AmsiScan 中,调用 AmsiInitialize 建立了新的 amsiContext ,着意味着对 powershell amsiContext 的修改不会影响到Load() 的 AMSI 检测。
随后调用 AmsiScanBuffer 进行扫描(这里并没有使用 AmsiOpenSession 建立会话),根据扫描结果决定是否进行阻断。

0x04 分析 fastprox.dll AMSI 扫描的实现

逆向 fastprox.dll ,对象初始化函数如下
检查注册表,如果 AmsiEnable 值被设置为0后续就不进行 AMSI 扫描,这块和 JScript.dll 处理差不多,早期 win10 版本可以修改 HKCU 注册表绕过,现在的版本都是检查 HKLM 了。
JAmsiInitialize 函数中,加载 amsi 前进行了一些判断。g_Amsi 是前文检查的注册表项,JAmsiIsCurrentProcessWmiprvse 则是比对当前文件的路径是否为系统目录下的\wbem\wmiprvse.exe ,如果是的话则不进行检测。
💡
经常干蓝队的肯定知道,wmi 操作时会先由 svchost.exe 调用 WmiPrvSE.exe 然后由 WmiPrvSE 调用指定的程序。他这不检测 WmiPrvSE.exe ,那这个基本对 wmi 远程攻击没啥检测能力了。 目前只有本机用 wmic、powershell 等程序操作 wmi 的时候才能触发检测,并且本地操作也不是全触发检测,还要判断hash,搞不懂微软这么设计图啥,搞得这块的检测非常容易绕过。
JAmsi::JAmsiProcessor 中进行扫描前,还进行了一个判断,判断需不需要进行扫描。
JAmsiIsScannerNeeded 会对对象及方法进行判断,比如执行
wmic Process Call Create "cmd /c whoami"
会依次对以下对象及方法进行hash(hash算法为CRC32,此处不展开说明)。
对象
方法
MSFT_CliAlias.FriendlyName='Process'
GetObject
MSFT_LocalizablePropertyValue.ObjectLocator="",PropertyName="Description",RelPath="MSFT_CliAlias.FriendlyName=\"Process\""
GetObject
MSFT_CliAlias.FriendlyName='Process'
GetObject
Win32_Process
GetObject
SetPropValue
CommandLine
当有 hash 等于以下的内置hash时,启动 AMSI 检测。
SetPropValueCommandLine hash得到的结果为 c0b29b3d,故进行AMSI检测。
WMI 检测的 hash 没太大爆破的价值,jscript 和 vba 的检测 hash 如下文所示。 https://github.com/synacktiv/AMSI-Bypass/tree/master

0x05 分析AMSI.dll 的实现

AMSI.dll 不开源,只能通过逆向的方法分析,并且不同版本之间存在一定的差异,以下分析以windows 11 为准,与旧版本的差异会指出一部分,

5.1 AmsiInitialize

首先会创建 COM 对象,在早期版本调用 CoCreateInstance 实现,其会通过注册表CLSID查找和定位COM服务器,由于其会先在注册表 HKCU 项查询,故普通用户也可以对其进行劫持。微软为了修复这个问题将代码改为直接调用更底层的 DllGetClassObject 创建 COM 类工厂。
在 win11 上,此处被替换为调用 AtlComModuleGetClassObject ,网上并未找到分析为什么替换的文章。
调试 AtlComModuleGetClassObject 查看参数。
随后使用 类工厂对象创建 COM对象,ATL::CComClassFactory::CreateInstance 是 ATL 类工厂的一部分,用于创建 COM 对象的实例。创建了 CAmsiAntimalware 对象。 amsi!ATL::CComCreator<ATL::CComObject<CAmsiAntimalware> >::CreateInstance函数负责实际的对象创建过程。CAmsiAntimalware 是 AMSI 的一个具体实现类。
AmsiComCreateProviders 负责创建和初始化 IAntimalwareProvider 接口的实际实例。
AmsiComCreateProviders 调用栈如下。
AmsiComCreateProviders 函数中调用 RegOpenKeyExWRegQueryInfoKeyW 获取
"Software\\Microsoft\\AMSI\\Providers" 的信息,并使用 RegEnumKeyExW 枚举其子项,获取已注册的 AMSI 提供程序的类标识符。随后将提供程序的类标识符传递给 AmsiComSecureLoadInProcServer ,用 RegGetValueW() 查询与 AMSI 提供程序对应的 InProcServer32 值。
然后,AmsiComSecureLoadInProcServer 会调用 amsi!CheckTrustLevel() 读取SOFTWARE\Microsoft\AMSI\FeatureBits 的值,这个键包含一个 DWORD 值,可以是 1(默认值)或 2,用于禁用或启用对提供程序的 Authenticode 签名检查。
随后会调用 LoadLibraryExW 以加载 AMSI 提供程序的 DLL。通过以下方法调试可以看到加载了 MpOav.dll
加载 dll 成功后会调用 dll 的DllGetClassObject函数创建一个类对象,并调用调用 IClassFactory::CreateInstance 获取请求的接口指针。

5.2 AmsiOpenSession

需要重点关注的内容如下
检查输入参数是否有效。如果 amsiSession 或 amsiContext 为 NULL,或者 amsiContext 的第二和第三个 _QWORD 为0,则返回错误代码 -2147024809(通常表示无效参数)。
在 win10 上,这个判断逻辑为
在 win10 中还会额外判断 amsiContext 的第一个 DWORD 值是否为 1230196033 (对应16进制数据0x49534D41,对应字符串 ISMA ,因为小端序的原因,实际匹配的字符串是 AMSI),可能是因为有相当的多的文章修改这个字符串绕过 AMSI,微软在 win11 移除了这个判断逻辑。不过这样治标不治本,攻击者还可以修改另外几处判断。

5.3 AmsiScanBuffer

首先会检测各个参数。
随后通过指针调用了一个函数,用 windbg 看下调用的函数是 amsi!CAmsiAntimalware::Scan
CAmsiAntimalware::Scan 会遍历每个已注册的 AMSI 提供程序,调用供应商实现的IAntimalwareProvider::Scan() 函数,如果各个 AMSI 提供程序都没有发现恶意软件,则将结果设置为AMSI_RESULT_NOT_DETECTE

参考文章

AMSI研究(2) - 检测windows 部署开发环境记录 (wsl2 + vscode)
公告
password
status
date
icon
category
tags
slug
summary
年轻时,你的潜力是无限的。说实在的,任何事都有可能做成。你可以成为爱因斯坦,也可以成为迪马吉奥。直到某一天,你身上的可能性消失殆尽,你没能成为爱因斯坦,你只是一个无名之辈。那真是糟糕的时刻。
🔮
所谓魔法不过是我们尚未了解的科学。
📚
潜心学习,低调发展。