告别ExploitShield:漏洞防护技术的深度剖析与挑战
ExploitShield被宣传为能够“防护所有已知和未知的0-day漏洞利用,在传统杀毒软件和安全产品失败时保护用户”。这一主张令人印象深刻且兴奋!软件应用中的漏洞确实是全球计算机用户面临的真实问题。迄今为止,我们在提供实际技术帮助个人用户防御软件漏洞方面做得并不好。
在我看来,微软通过其增强缓解体验工具包(EMET)取得了最佳进展。EMET通过改变操作系统行为,增加攻击者制作有效漏洞利用的难度。有博客文章详细记录了EMET的功能。
总体而言,我认为那些公开其方法的系统比依赖“秘密配方”的系统更值得信赖。EMET非常公开其方法,而ExploitShield则隐藏其方法,试图通过 obscurity(隐蔽性)获得额外的安全性。
我分析了ExploitShield系统和技术,分析结果如下。总结来说,该系统非常可预测,攻击者可以轻松研究并调整攻击以克服它,且实现本身创造了新的攻击面。经过此分析,我认为该系统无法帮助个人或组织防御有能力编写自己的漏洞利用(无论是0-day还是其他)的攻击者。
注意事项
我进行的分析是针对其“浏览器”版本。可能其“企业”版本中有更先进的东西,我无法确定,因为我没有见过。然而,基于我分析的实现“基调”以及其中的实现缺陷,我怀疑这种可能性,并认为“企业”版本只是“更多相同的东西”。我欢迎被证明是错误的。
初步分析
通常,我们可以使用一些优秀且免费的工具来了解软件的足迹。我喜欢使用GMER。GMER调查整个系统,并使用交叉视图技术识别对运行程序的补丁。
如果您回想一下ExploitShield的营销信息,我们会看到如下的弹出框:
此屏幕截图中有一些提示,例如,为什么指定了路径?如果这真的在阻止“漏洞利用”,难道不应该在指定文件系统路径之前就阻止吗?
在以下部分,我将详细介绍我分析的每个阶段,涉及ExploitShield的组件或概念。
ExploitShield使用设备驱动程序
ExploitShield系统的一个组件是设备驱动程序。该设备驱动程序使用操作系统支持的机制(PsSetCreateProcessNotifyRoutine)在操作系统启动进程时接收通知。
每次进程启动时,设备驱动程序检查该进程,并可选地将自己的用户模式代码模块加载到启动的进程中。加载用户模式代码模块的标准取决于启动的进程是否是ExploitShield保护的进程。
用户模式组件
用户模式组件似乎仅用于钩子/绕道特定函数。
函数钩子(也称为函数绕道)涉及修改函数的开头,使得当调用该函数时,改为调用另一个函数。MS Research关于Detours的论文详细解释了这一概念。
函数钩子通常用作实现应用程序检查器或参考监视器的方式。安全系统可以绕道一个函数,例如CreateProcessA,并对CreateProcessA的参数进行基于启发式的决策。如果启发式表明行为可疑,安全系统可以采取一些行动,例如使调用CreateProcessA失败或终止进程。
钩子函数
ExploitShield似乎主要通过绕道以下方法运作:
- WinExec
- CreateProcessW/A
- CreateFileW/A
- ShellExecute
- UrlDownloadToFileW/A
- UrlDownloadToCacheFileW/A
在这里,我们可以理解ExploitShield作者所说的“在研究数千个漏洞利用后,ZeroVulnerabilityLabs开发了一种创新的专利待批技术,能够检测受保护的应用程序是否被恶意利用”的含义。这些是shellcode常用于丢弃和执行其他程序的函数!
函数钩子行为
每个函数实现一个直接的启发式。在调用任何过程(在x86上)之前,过程完成后返回的地址被推送到堆栈上。每个钩子从堆栈中检索返回地址,并询问有关返回地址属性的问题。
地址的页面权限是否为RX(读-执行)? 地址是否位于加载模块的边界内?
如果这两个测试中的任何一个失败,ExploitShield报告它发现了漏洞利用!
术语混淆
漏洞:漏洞是软件的一种属性,允许某种信任违规。漏洞的定义非常广泛。内存损坏漏洞对计算机安全产生了如此大的影响,以至于很多时候,“漏洞”仅用作“内存损坏漏洞”的简写,然而其他类型的漏洞确实存在,例如信息泄露漏洞或身份验证绕过漏洞。信息泄露漏洞有时对个人隐私的损害可能比内存损坏漏洞更严重。
漏洞利用:漏洞利用是使用漏洞来执行某些操作的软件或过程,通常是执行有效负载。
有效负载:攻击者创建的软件,在漏洞被用于危害系统后执行。
我相信当ExploitShield使用术语“漏洞利用”时,他们实际上指的是“有效负载”。
ExploitShield的好日子
那么ExploitShield按预期运作的详细过程是什么?让我们看一下,抽象出具体使用哪个漏洞利用的细节:
用户被诱骗导航到攻击者控制的恶意网页。他们对此不能太受责备,他们只需要犯一次错误,访问可能是攻击者入侵合法网站并使用它提供恶意软件的结果。
该网页包含用户浏览器中漏洞的漏洞利用。Web浏览器加载包含漏洞利用的文档,并开始解析和处理漏洞利用文档。
漏洞利用文档中的数据已被修改,使得解析文档的程序做了一些坏事。假设漏洞利用说服Web浏览器做的是覆盖存储在内存中某处的函数指针,其值是也由漏洞利用提供的数据的地址。接下来,易受攻击的程序调用此函数指针。
现在,Web浏览器执行由漏洞利用提供的代码。此时,Web浏览器已被利用。用户正在运行攻击者/漏洞利用提供的代码。此时,任何事情都可能发生。注意我们如何已经完成了此过程的“利用”阶段,而ExploitShield尚未进入画面。
执行的代码调用其中一个钩子函数,例如WinExec。对于此示例,假设执行的代码是从堆上的页面调用的,因此其权限为RWX(读-写-执行)。
如果攻击者不知道ExploitShield存在,并且它没有全局代表性到对攻击者构成大问题,那么ExploitShield是很好的。如果攻击者知道它存在并关心,他们可以轻松绕过它。
ExploitShield的坏日子
如果攻击者知道ExploitShield,创建不触发ExploitShield监视警报的漏洞利用需要多少努力?我认为根本不需要太多努力。立即想到两种可能性:
使用(非常)原始的ROP(返回导向编程)形式。识别加载模块中的ret指令,并将其作为返回地址推送到堆栈上。在此地址之前将您的返回地址推送到堆栈上。ExploitShield进行的检查将通过。
使用等效于其中一个钩子函数但不是钩子函数的函数。如果CreateProcess被钩住,使用NtCreateProcess代替。
这两种方法都会击败我在ExploitShield中发现的保护。此外,这些技术将在没有ExploitShield的系统上运行,这意味着如果攻击者关心在存在ExploitShield时绕过它,他们只需要实现这些绕过一次。
隐蔽性并不总是坏的
“通过隐蔽性获得安全”的原则经常被安全极客引用为安全系统的负面属性。然而,只要防御系统保持隐蔽或不可预测,隐蔽性实际上确实使系统更安全。基于隐蔽性的防御技术的困难在于找到一个可以以低成本进行的隐蔽更改,并且攻击者在其被破坏之前无法适应,或者一个在其隐蔽性受损时可以以非常低的成本更改的更改。
例如,考虑微软的PatchGuard。PatchGuard通过在检测到修改时崩溃来“保护”系统。PatchGuard的操作被隐藏,微软未发布。只要PatchGuard的操作被隐藏和保密,它就可以通过在检测到rootkit所做的修改时使系统崩溃来保护系统。
然而,PatchGuard经常被安全研究人员逆向工程和研究。每次研究人员坐下来意图绕过PatchGuard时,他们都取得了成功。有趣的是接下来发生的事情:在未来的某个时间点,微软默默发布一个更新,改变PatchGuard的行为,使其仍然实现在检测到修改时使系统崩溃的目标,但不脆弱于安全研究人员创建的攻击。
在这种情况下,隐蔽性起作用。微软制作新的PatchGuard非常便宜,内核团队可能有十个“在板凳上”等待当前部署版本被解剖和绕过。这将内核从静态目标转变为移动目标。隐蔽性起作用,因为改变机制的主动权在微软手中,更改既便宜又有效,并且攻击者无法轻易准备避免这些更改。
ExploitShield引入的更改非常脆弱,不能轻易修改。也许如果ExploitShield是一个引擎,可以快速提供各种运行时更改并在每个应用程序中随机变化,这种动态会有所不同。
一些实现问题
正确实现HIPS(主机入侵防御系统)是一项大量工作!到处都有棘手的工程决策要做,作为作者,您将自己介入一个非常棘手的安全情况。ExploitShield做出了一些不必要的实现决策。
IOCTL接口
驱动程序暴露一个所有用户都可访问的接口。传统Windows驱动程序的最佳实践要求只有应该访问它的用户才能访问驱动程序的接口。然而,ExploitShield接口对整个系统可访问,包括非特权用户。
驱动程序处理发送给它的消息。我没有完全发现这些消息的类型或格式,但IOCTL处理代码充满了微妙错误的可能性。IOCTL处理代码中的任何错误都可能导致内核级漏洞,这将危害整个系统的安全性。
此接口创建了额外的攻击面。
钩子逻辑
每个钩子调用一个例程来检查返回地址是否位于加载的模块中。此例程使用一个全局模块列表,该列表仅通过调用EnumerateLoadedModules与程序员提供的回调填充一次。ExploitShield检索加载模块列表的方法中有两个错误。
第一个错误是显然在填充全局列表的关键部分周围没有互斥。多个线程可以同时调用CreateProcessA,因此理论上用户模式逻辑可能使自己处于不一致状态。
第二个错误是模块仅枚举一次。一旦调用了EnumerateLoadedModules,一个全局标志被设置为true,然后EnumerateLoadedModules再也不会被调用。如果系统观察到对CreateProcess的调用,然后随后加载了一个新模块,并且该模块有对CreateProcess的调用,安全系统将错误地将该模块标记为尝试的漏洞利用。
这些缺陷都不会使用户面临任何额外危险,它们只是表明编程实践不佳。
为什么使用钩子?
在ExploitShield的实现中一个特别令人困惑的决定是使用钩子!对于ExploitShield关心的每个事件(进程创建和文件写入),NT内核中存在强大的回调基础设施。确实,传统防病毒软件的作者经常因过度使用钩子而降低系统稳定性,以至于微软非常强烈地鼓励他们使用此内核内监视API。
ExploitShield使用不必要的危险编程实践来实现通过使用合法系统服务可能实现的效果,可能暴露出对他们旨在保护的平台缺乏理解。
ExploitShield成功的不可可能性
ExploitShield能做什么来改变这种动态?问题是,不多。像这样的防御系统完全依赖隐蔽性。一旦被攻击者研究,这些系统就失去了价值。对于像这样的软件,一个问题是反馈循环不会通知安全软件的作者或用户攻击者已经适应了安全系统。另一个问题是系统的隐蔽性难以维持。软件必须被客户使用,因此它必须在某种意义上可用,如果它对客户可用,它很可能也可供攻击者研究。
我们有什么希望?
重要的是要注意EMET在一个重要方面与ExploitShield不同:EMET旨在破坏利用程序的行为,而ExploitShield旨在破坏在系统上执行有效负载的行为。这些可能看起来是细微的差别,但可以在“攻击者有多少有效的选择”方面做出区分。当涉及到执行有效负载时,攻击者的选择几乎是无限的,因为他们已经在执行任意代码。
在这方面,EMET通常不基于隐蔽性。EMET的作者非常愿意详细讨论他们实施的不同缓解策略,而ExploitShield的作者尚未这样做。
通常,我认为如果防御技术对程序或运行时行为做出确定性更改,攻击将失败,直到它适应此技术。攻击的有效性依赖于技术的隐蔽性,以及更改是否影响漏洞、漏洞利用或有效负载。如果攻击无法适应修改后的环境,那么缓解的隐蔽性就无关紧要。
然而,如果技术不是隐蔽的,而是不可预测的,会怎样?如果有一种防御技术会随机调整系统实现行为,同时保留程序所体验的系统语义行为,会怎样?需要的是识别系统的属性,如果更改,会影响攻击的功能,但不会改变程序的功能。
当这些属性随机变化时,攻击者的选择更少。也许他们知道一个可以超越任何实现细节排列的漏洞。如果他们不知道,然而,他们的攻击是否会成功完全取决于机会。
结论
ExploitShield是一个时间胶囊,包含2004年提供的最佳基于主机的安全技术。在我看来,它并不代表计算机安全格局的有意义变化。使用的技术完全依赖于隐蔽性和保密性,需要很少的工作来克服,并且只影响计算机攻击的后期阶段,即有效负载,而不是漏洞利用。
与其他防御技术相比,ExploitShield不足。它使用实现不佳的技术,这些技术针对攻击的阶段,需要攻击者很少的适应来克服。一旦ExploitShield获得足够的市场吸引力,恶意软件作者和漏洞利用编写者将自动化绕过它的技术。
ExploitShield甚至通过安装一个内核模式驱动程序来增加您的攻击面,该驱动程序将处理系统上任何用户发送的消息。该内核模式驱动程序中的任何缺陷都可能导致在您的系统中引入权限升级错误。
它用于查找shellcode的检测逻辑并非完全有缺陷,它包含一个可能导致一些误报的实现错误,然而通常调用运行时库函数且返回地址不在加载模块边界内是可疑的。此检测签名的问题在于它可以轻松修改以实现相同的效果。此外,此检测签名并不新颖,HIPS产品已经实施此检查很长时间了。
这是一种耻辱,因为在我看来,这类软件仍有严重的创新空间…