无招胜有招: 看我如何通过劫持COM服务器绕过AMSI

在Windows 10中,Microsoft的反恶意软件扫描接口(AMSI)被作为新功能被引入,作为标准接口,该功能可以让反病毒引擎将特征规则应用于机器的内存和磁盘上的缓冲区中去。这使的反病毒产品能够在恶意程序的脚本被解释执行之前执行劫持操作,这在一定程度上意味着任何的代码混淆或加密都有相对应的例程去还原和解密程序。如果需要更多详细的有关AMSI的信息,您可以在这里阅读有关AMSI的更多信息。
在这篇文章中,我们将阐述一种通过劫持COM服务器来绕过AMSI的方法, 并分析Microsoft如何在build#16232中修复该绕过,然后再讨论如何再次绕过微软对该漏洞的修复。
通过劫持COM服务器来绕过AMSI的这个问题在5月3日我们向微软递交了报告,并且微软官方已经修复了该漏洞,具体修复信息可见Build#16232中的“深度防御”补丁。
在本文中,我们的实验是一个通过PowerShell进行的AMSI测试示例,测试过程是当AMSI模块接受外部传进来的脚本块并将其传递给Defender进行分析的时候进行劫持操作,具体可见下图所示:

正如你所看到的,AMSI接受了我们构造的恶意代码并将该代码块传递给被调用的Invoke-Expression。由于该代码被认为是恶意的,因此 该代码块被阻止执行。这里需要我们去研究的是:这种阻止恶意代码执行操作是如何工作的呢 ?之后我们通过查看amsi.dll的导出,可以看到AMSI导出的各种函数调用:

通过查看AMSI导出的函数,我们可以发现一些很重要的函数信息,那就是amsi!DllGetClassObject和amsi!DllRegisterServer这两个函数 ,因为这些都是COM入口点,这些函数都是用于方便实例化一个COM对象的。幸运的是,COM服务器易于劫持,因为COM服务在处理 流程上默认在查找HKCR/HKLM之前会去先搜索当前用户的注册表配置单元(HKCU) ,以用于COM服务器来正常处理。这个过程我们在IDA中可以看出,从图中 我们可以看到COM服务接口ID(IID)和ClassID(CLSID)传递给CoCreateInstance():

甚至,我们可以通过查看ProcMon来验证这一点:

通过以上的分析最终我们可以发现,AMSI扫描恶意程序的功能似乎是通过自己的COM服务器来实现的,该功能在COM服务器被实例化时被导出。当AMSI加载时,它首先实例化其COM组件,它导出了诸如amsi!AmsiOpenSession,amsi!AmsiScanBuffer,amsi!AmsiScanString和amsi!AmsiCloseSession之类的函数。在这个过程中如果我们强制COM实例化失败,那么AMSI将无法调用用来扫描恶意程序内容所需的函数方法。由于COM服务器首先通过HKCU配置单元进行解析,因此普通用户可以劫持InProcServer32键值并注册不存在的DLL(或者是一段恶意执行的代码)。为了做到这一点,有两个注册表项需要修改:

劫持COM服务的整个过程是:当AMSI尝试实例化其COM组件时,它将查询其在注册表中注册的CLSID并返回 一个不存在的数值。这将导致其加载失败,并阻止任何扫描恶意软件的方法被访问,最终使得AMSI不可使用。您可以看到,导入上述更改的注册表将导致COM服务器返回”C:IDontExist”:

现在,当我们尝试运行我们的“恶意”的AMSI测试样本时,我们可以发现我们的恶意代码段被允许执行,因为AMSI无法通过其COM接口访问任何扫描恶意程序的方法 ,结果如下图所示:

您可以在这里找到更改注册表的方法:
https://gist.github.com/enigma0x3/00990303951942775ebb834d5502f1a6
现在我们可以看看微软如何在build#16232中修复该漏洞。由于amsi.dll也是AMSI的COM服务器,因此将这两个DLL分开似乎是一个很好的修复方法。我们来看一下漏洞被修复前后的不同,从图中可以看到AmsiInitialize函数,它可能包含了实际实例化AMSI的逻辑代码。

在左侧,我们有旧的AMSI DLL,在右边,我们有新更新的AMSI DLL。如您所见,Microsoft似乎删除了对CoCreateInstance()的调用,并将其替换为直接调用DllGetClassObject()。 CoCreateInstance()可以定义为高级函数,该函数用于实例化使用CoGetClassObject()生成的COM例程 。该函数解析完成后(部分通过注册表CLSID查找)以及定位到COM服务器后,服务器的导出函数“DllGetClassObject()”将被调用。通过直接调用amsi.dll的DllGetClassObject()函数替换CoCreateInstance,这一修复方法避免了注册表解析操作,由于AMSI不再在COM服务器的注册表中查询CLSID,因此我们无法再劫持它。

 

现在我们知道修复,那么我们如何去绕过它呢?在进行研究之前,我们需要明白的是:基本上,脚本解释器(如PowerShell)从工作目录加载amsi.dll,而不是从安全路径(如System32)加载它。由于这个原因,我们可以将PowerShell.exe复制到我们可以写入的目录,并 将易受攻击的amsi.dll版本放到这个目录中。通过这些操作后,我们获许就可以劫持DLL,或者我们可以创建相同的注册表项来劫持AMSI的COM组件。由于这个易受攻击的AMSI版本仍然调用CoCreateInstance()函数,因此我们仍然可以通过劫持注册表的搜索顺序来劫持AMSI,整个操作方法如下:
首先,我们可以通过为powershell.exe和AMSI的CLSID创建一个ProcMon过滤器来验证修补后的amsi.dll版本不再通过注册表查询COM服务器。 当PowerShell启动时,您将注意到没有任何条目出现:

接下来,我们删除易受攻击的AMSI DLL并将PowerShell移动到同一目录。 如您所见,现在正在查询注册表以查找AMSI的COM服务器:

使用易受攻击的AMSI DLL,从图中可以看出我们现在可以执行COM服务器劫持:

总结:
尽管微软在补丁#16232中对该漏洞进行了修复,但仍然可以通过使用旧的,易受攻击的AMSI DLL执行DLL劫持来执行劫持操作。 关于防御方法,我们觉得对那些在正常目录之外执行任何的二进制文件(wscript,cscript,PowerShell)操作进行监视操作将是一个好的想法。 由于绕过修复补丁需要将二进制文件移动到用户可写位置,所以在非标准位置执行这些命令可以被当成一种异常的操作行为。