“就像我们知道的那样,有一些事情我们是知道的。有一些事情我们知道我们知道,也有些我们不知道我们知道。也就是说我们知道有些事情我们不知道,但是也有些事情是我们不知道我们不知道。” ——唐纳德 · 拉姆斯菲尔德
简介
从防御的角度来看,我们应用于安全的最危险的事情之一就是假设。 假设是造成不确定性的盲点。 通过在检测过程中枚举和消除尽可能多的假设,我们限制了攻击面和敌人可以逃避我们检测努力的区域。 虽然总会有盲点,但是知道盲点总比不知道盲点好。 如果我们意识到我们的盲点,我们就可以在我们的检测努力中更有准备和更有效率。
问题: 我们如何限制盲点和假设的数量?
答: 发现攻击面并理解环境中的攻击向量。 有了这样的认识,我们就可以发现攻击是如何破坏和逃避防御或侦查的。
映射数据来源
只有当指定环境中的数据源(进程监视、文件监视等)映射到恶意活动时,鲁棒的检测才能开始。
这具体意味着什么?
场景: 作为防御者,你希望监视环境中的进程创建,以确定对手何时可能试图生成新进程。
解决方案: 你开始记录ID为 4688 的 Windows 事件——已经创建了一个新进程(如果你的环境中有 Sysmon 电话) Sysmon 事件 ID 为 1——Process Creation。
作为一个防御者,你已经建立了相关性,通过记录这些事件,你将能够监视进程创建事件。 通过创建这些关联和映射,防御程序将更好地理解它们的数据如何映射到检测过程中的恶意活动和行为。
事件数据与恶意活动关联
注意: 为了了解更多,我鼓励你查看由罗伯特·罗德里格斯创建的ATT&CK数据模型表。
到目前为止,我们已经成功地将数据源 / 事件数据映射到恶意行为; 然而,作为防御者,我们仍然存在关于日志工作的盲点和假设。 虽然我们正确地转发了正确的事件 ID,但是我们不知道这些事件是如何开始生成的。 当调用 Windows API 时,什么会导致 Sysmon 记录特定的数据源呢? 同样,重要的是要理解这些事件是如何产生的,因为它揭示了如何绕过我们的防御能力。
将 Windows API 映射到 Sysmon 事件 ID
为了给这个主题带来更多的亮点,我开始利用 WinDbg 和 IDA Pro 对 Sysmon 驱动程序和服务可执行程序进行“逆向工程师”。 我之所以在这里说“逆向工程师” ,是因为没有马特·格雷伯的帮助,我不可能做到这一点。 我无论如何都要感谢他的帮助和努力。即时完成这个项目也不会让我成为逆向工程的专业人士; 我还有很多东西要学,而且我还是这个技能的初学者。
这个项目的映射如下:
映射流
上面显示了松散的事件注册机制映射。 我决定将其添加到研究中,以便更好地了解对手可能如何篡改 Sysmon 日志工作。 我不会详细介绍这是如何实现的,但是可以通过篡改 ETW 提供程序、卸载 Sysmon 驱动程序、更改配置以及直接篡改内核回调来实现。 如果你对如何做到这些事情感兴趣,我建议你看看这些写得非常好的博客:
· 规避 Sysmon DNS 监控——作者亚当
· Shhmon——卸载驱动程序让 Sysmon 沉默——作者马特·汉德
这个项目的目标: 将 Windows API 映射到事件注册机制,然后是 Sysmon 事件,以帮助理解攻击面、攻击向量,以及敌方如何绕过这个日志工作。 这个项目可以在 GitHub 上找到: Windows-API-To-Sysmon-Events。
API 映射表
注意: 我不是创建这些相关性和映射的唯一研究人员。 罗伯特·罗德里格斯已经创建了一个名为 API-To-Event 的项目。 他也在做这项研究,并在 DerbyCon 做了一个演讲: 使用对手仿真提升检测工程能力。
通过这些项目,防御者可以更好地理解我们面对的攻击面、攻击向量和行为数据。 一旦我们理解了攻击技术的行为,我们就可以将 API 与我们期望在数据中看到的事件相关联。
下文概述了为适当建立这一映射而采取的步骤:
· 在 IDA Pro 中的 Imports 中定位注册机制(回调例程、注册回调、过滤器寄存器等)。通过研究与特定数据源相关的函数调用来定位注册机制。 例如,假设我们想要映射出与进程创建事件相关的 API 调用,我会通过 Sysmon 驱动程序逻辑来查找被调用的函数:
事件注册机制的导入视图ー PsSetCreateProcessNotifyRoutine
· 研究这些机制,以更好地理解它们,并得出一个逻辑结论,它们将与Sysmon内部的哪些事件相关。
微软官方文档中对于PsSetCreateProcessNotifyRoutine 的说明
· 通过将 WinDbg 内核调试器附加到所选的主机来测试逻辑。 在机制之前的回调函数上设置一个断点:
PsSetCreateProcessNotifyRoutine 前面的函数,我们需要检查这个函数
中断掉突出显示的函数。 这个函数将执行 PsSetCreateProcessNotifyRoutine
· 一旦调试器中断,就能获取当前进程的进程对象地址:
获取进程对象地址
注意: 这里要记录进程 CID (也就是 CLIENT_ID) ,因为在下面的几个步骤中它会变得很方便。
· 获取显示的地址并切换到进程上下文。 重新加载符号以便调试器显示用户模式符号以及内核模式符号:
切换到进程上下文并重新加载用户模式符号
· 查看本地 Win32 API 调用的堆栈回调:
检查堆栈回调
上面有很多信息。 如何筛选 Windows API 调用呢? 可以筛选任何来自以下 API 库之一的调用:
· nt
· ntdll
· KERNELBASE
· KERNEL32
我会研究并确定 API 调用与触发的事件的相关性。
· 使用 Sysmon 中的进程创建事件的 ParentProcessID 关联进程上下文中的CID:
将 CID 与 Sysmon 中的进程创建事件的 ParentProcessID 相关联
CID 是十六进制格式,将其转换为十进制为3536,这个ParentProcessID就是创建进程的进程ID也就是父进程ID。
注: 每个注册机制都有自己的挑战,这改变了上述逆向过程。 一些机制通过反汇编 Sysmon 服务可执行文件而不是驱动程序来解决,还有一些机制通过动态调试发现,例如: ObRegistersCallback。
不错的研究结果,我怎样才能有效地使用呢?
我已经在以前的博客中展示了这项工作的方法和研究,博文标题是: 检测进程重镜像行为。 但是,现在让我们通过对反射 DLL 注入的快速检测再次将这项研究付诸实践。
检测工程
进程注入是在后漏洞利用活动中使用的一种非常常见的攻击技术。 对于这篇博文,我将使用一个被称为反射 DLL 注入的进程注入迭代技术。 请记住,这并不能解释所有版本的“进程注入”攻击。 有许多不同的变体——例如,进程空洞化、 DLL 注入等等。 下面我将介绍研究的检测工程流程、数据行为相关性,以及使用 Apache Spark 和 Jupyter notebook 进行的数据分析。 需要注意的是,这是从 魔多 项目中的 Empire-Psinject 数据集中获取的。
让我们开始数据工程吧
反射 DLL 注入允许攻击者从内存和磁盘加载 DLL。 攻击者可以枚举系统上正在运行的进程,然后通过向目标进程的地址空间注入一个 DLL 来执行任意代码。 通过这样做,攻击者可以在他们选择的任何目标进程的上下文中运行他们的代码。 该攻击流程如下:
1. 攻击者选定一个进程作为注入的目标。
2. 攻击者调用OpenProcess获取目标进程的句柄。
3. 攻击者调用VirtualAllocEx在远程进程中有一个地址空间来写入反射DLL。
4. 攻击者调用WriteProcessMemory将反射DLL从上面的地址空间写到分配的内存中。
5. 攻击者调用CreateRemoteThreadEx,指向VirtualAllocEx指定的区域开始执行反射DLL。
基于这种行为,有两个与两个 Sysmon 事件相关的 API 可用于攻击检测:
· ID 为 8的Sysmon 事件 ——检测 CreateRemoteThread。 此事件将调用事件注册机制:PsSetCreateThreadNotifyRoutine,这是一个 Windows 内部的内核回调函数。 在 Sysmon 驱动程序内部,CreateRemoteThreadEx API 是通过这个事件注册机制创建了 ID 为 8的事件。
· ID 为 10 的 Sysmon 事件—— 进程访问。这个事件会调用事件注册机制:ObRegisterCallbacks,这也是一个 Windows 内部的内核回调函数。 在 Sysmon 驱动程序内部,nt!NtOpenProcess API是通过这个事件注册机制创建了 ID 为10的事件。
虽然有2个 API 与 Sysmon 事件 ID 相关联,但是在这个技术行为中实际使用了4个 Window API 调用。 为了更好地理解这种恶意活动的行为,最好在使用这些 API 时,制定出攻击者访问进程句柄所需的最小特权。
为了制定出对手访问进程句柄所需的最小特权,我翻阅了微软内部的每个 API 文档,并制定了访问进程句柄所需的特权。 需要的特权如下:
PROCESS_CREATE_THREAD (0x0002)
PROCESS_QUERY_INFORMATION (0x0400)
PROCESS_QUERY_LIMITED_INFORMATION (0x1000) ——如果某个句柄具有 PROCESS_QUERY_INFORMATION 则会自动分配该权限
PROCESS_VM_OPERATION (0x0008)
PROCESS_VM_WRITE (0x0020)
PROCESS_VM_READ (0x0010)
在添加了这些特权之后,访问进程句柄所需的最小权限是: (0x1440)。
数据分析
下面我将介绍两种通过Jupyter Notebook 查询这些数据的方法。 第一种方法是在 HELK 堆栈中转换数据之后。 这个查询来自 Elasticsearch:
ReflectiveDLL_ProcessInjection = spark.sql( ''' SELECT b.process_path, b.process_target_name, b.process_target_id, b.thread_new_id, a.process_id, a.process_granted_access FROM sysmon_events b INNER JOIN( SELECT event_id, process_granted_access, process_guid, process_id FROM sysmon_events WHERE event_id = 10 AND (process_granted_access & 5184) == 5184 -- 5184 is decimal for 0x1440. The minimal privileges you need to access process handle ) a ON a.process_guid = b.process_guid WHERE b.event_id = 8 ''' ).show(1,False)
上述查询的输出
注意: 上面你可以看到函数: (process_granted_access & 5184) == 5184。 5184是0x1440的十进制版本。 这样做是为了提取具有 process_granted_access数据属性的进程句柄所需的最小特权的任何事件。 process_granted_access数据属性表示 Sysmon 事件 ID 10中的 GrantedAccess,该 ID 为 10 的事件具有授予进程的权限值。
有两个不同的 notebook 可供使用: Reflective_DLL_Injection(原始数据)和Reflective_DLL_Injection(原始数据)、Reflective_DLL_Injection(转换后的数据)。 其中一个notebook 将从数据集本身的原始数据中提取数据。 另一个将从 Elasticsearch 中的转换数据中提取数据。 我想同时提供这两种选择,因为它可以帮助任何想要用到它的人,如果他们愿意的话,他们可以更好地掌握如何使用 Apache Spark 和 Jupyter notebook。
结论
从上面的例子可以看出,发现我们的盲点和制定我们的数据源是多么有用。 随着攻击技术的发展,我们必须与它一起进步,以便我们可以充分了解如何检测这些恶意行为。 对手仿真将继续学习更多,并使用更好的谍报技术。 为了做好准备,我们必须为数据建模并创建数据关系,以便更好地理解数据行为。 同样,这个项目可以在 GitHub 上找到,欢迎提供反馈!
致谢
非常感谢和赞扬以下个人对这个项目的帮助和洞察力:
· 马特 · 格雷伯(Matt Graeber ) ——引导我完成逆向工程调用,帮助我完成多个函数调用,并验证许多类似的回调函数。
· 布莱恩 · 赖茨(Brian Reitz ) ——帮助我理解函数调用和进程间通信。
· 贾里德 · 阿特金森(Jared Atkinson ) ——帮助我理解函数调用和进程间通信。 同时帮助实现上面查询中的十进制转换和最小可行访问权限。
参考资料
· 微软文档和各种函数调用以及 API
· 颠覆 Sysmon ——作者马特 · 格雷伯 及李 · 克里斯滕森
· 规避 Sysmon DNS 监控 ——作者亚当
· Shhmon——通过卸载驱动程序让 Sysmon 闭嘴 ——作者马特 · 汉德
· 德怀特 · 霍恩斯坦 及李 · 克里斯滕森 整理的进程注入知识