欢迎回到“威胁检测与搜寻建模”系列。到目前为止,我们已经探索了 OS 凭据转储:LSASS 内存子技术,特别是 mimikatz,作为了解此子技术如何工作的示例。第一篇文章的重点是确定 mimikatz 命令用于实现其预期结果的 API 函数。函数是必不可少的,因为它们是操作系统中功能的构建块。第二篇文章介绍了操作的概念,它充当抽象类别,用于根据其目的论结果对相似的功能进行分组。假设我可以用一个函数替换另一个函数,就像开发人员可以用 .在这种情况下,这些函数执行相同的操作(在本例中为进程访问)。第三篇文章建立在我开始本系列之前写过的一个概念之上,这篇博文名为“理解函数调用堆栈”。这个想法是,记录在案的 API 函数实际上是实际功能的包装器。当我们调用像这样的函数时,许多函数在操作完成之前被调用。此函数序列构建“函数调用路径”。在第三篇文章中,我们探讨了如何生成新的函数调用路径,并将其与其他现有的函数调用路径集成,以实现相同的操作,从而生成“函数调用图”。该图允许我们评估开发人员可用于实现操作的所有不同功能选项,至少是我们已知的那些选项。
sekurlsa::logonPasswords
OpenProcess
NtOpenProcess
ReadProcessMemory
这篇文章有点弯路,但仍然是必不可少的。本博客系列致力于通过连贯的分类法将战术与功能联系起来。尽管如此,有时我们会进行观察并基于它们建立假设,却发现我们的观点分辨率太低,无法连贯地应用于我们应该考虑的实现范围。这篇文章讨论了一个这样的例子。
相关视频教程
恶意软件开发(更新到了142节)
正如我之前提到的,第二篇文章介绍了函数的概念。我提出的一个基本公理假设是,每个函数都代表一个且只有一个操作。当我继续研究 OS Credential Dumping: LSASS Memory 和构建相关函数调用图时,我遇到了一些与此公理相矛盾的函数。我想用这篇文章来展示我遇到的例子,解释它是如何工作的,提供我们可以用来讨论它的语言,并描述我如何调整分类法来解释这种现象。
本系列的第一篇文章分析了 Mimikatz 源代码,发现它依赖于对 的调用。然后,可以使用“了解函数调用堆栈”一文中讨论的方法生成 的函数调用路径。如果应用程序调用 ,它随后将调用 ,然后 ,然后 ,最后通过关联的系统调用将执行转换为内核。我认为这个函数调用路径代表了正常的功能行为。它非常简单,执行从路径中的每个函数传递,没有任何弯路,如下图所示。kernel32!ReadProcessMemory
kernel32!ReadProcessMemory
kernel32!ReadProcessMemory
api-ms-win-core-memory-l1–1–0!ReadProcessMemory
kernelbase!ReadProcessMemory
ntdll!NtReadVirtualMemory
NtReadVirtualMemory
正如了解函数调用堆栈一文中提到的,分析过程中的第一步是打开实现 DLL 并在导出表中搜索对相关函数的引用。这种搜索最终导致了函数的代码实现,这有助于我们理解函数的工作原理。在搜索 时,我偶然发现了第二个名称相似的函数,称为 ,这激起了我的兴趣。ReadProcessMemory
kernel32.dll
Toolhelp32ReadProcessMemory
根据该函数的文档,它的行为类似于 ,但有一个例外。 需要从中读取的进程的句柄,而只需要进程标识符 ()。它似乎在功能上等同于 ReadProcessMemory,但可能更易于使用,或者至少可能允许绕过 所需的讨厌的进程访问操作。跳过进程访问操作对攻击者很有用,因为此技术的绝大多数检测规则都专门针对此操作。Toolhelp32ReadProcessMemory
ReadProcessMemory
ReadProcessMemory
Toolhelp32ReadProcessMemory
th32ProcessID
Toolhelp32ReadProcessMemory
ReadProcessMemory
如果我们在 IDA 中打开该函数,我们可以看到它实际上需要我们。似乎它可能只是添加到我们的函数调用图中的另一层包装器代码。Toolhelp32ReadProcessMemory
ReadProcessMemory
可以通过查阅 的导入表找到使用的确切版本。似乎调用 .ReadProcessMemory
kernel32.dll
Toolhelp32ReadProcessMemory
api-ms-win-core-memory-l1–1–2!ReadProcessMemory
回想一下,本系列的第 2 部分引入了一个新的操作抽象层,它允许我们按目的论(基于函数的目的、目标、目的或目标)对函数进行分组。例如,属于进程读取操作,因为它负责允许应用程序读取进程的易失性内存。同时,在第 3 部分中,我们演示了如何为给定操作组合多个单独的函数调用路径,以形成与操作一致的函数调用图,并描述执行特定操作的所有已知功能选项。ReadProcessMemory
我的第一个想法是,我们可以更早地添加到函数调用路径中,以便为 Process Read 操作生成函数调用图。Toolhelp32ReadProcessMemory
ReadProcessMemory
虽然这似乎是一个足够简单的解决方案,但它困扰着我,因为似乎有些不对劲。 不是那么简单的功能.虽然它确实调用了 的 API 集版本,但这并不是它所做的一切。还记得当我们观察到只需要进程标识符而不是进程句柄时,我们认为也许我们可以完全跳过进程访问操作吗?如果我们再仔细观察一下,这次是 IDA 的反编译器生成的代码,我们会发现它不仅调用 .它调用 、 和 。Toolhelp32ReadProcessMemory
ReadProcessMemory
ReadProcessMemory
Toolhelp32ReadProcessMemory
Toolhelp32ReadProcessMemory
ReadProcessMemory
OpenProcess
ReadProcessMemory
CloseHandle
Toolhelp32ReadProcessMemory
是执行多 (3) 个操作的单个函数。 用于“进程访问”操作、“进程读取”操作和“处理关闭”操作。While 遵循简单的函数调用路径,但情况并非总是如此。某些函数(如 )实际上充当微型独立应用程序。我已经开始将这些多操作函数(如 )称为“复合函数”,同时将单操作函数(如 )称为“简单函数”。OpenProcess
ReadProcessMemory
CloseHandle
kernel32!ReadProcessMemory
Toolhelp32ReadProcessMemory
Toolhelp32ReadProcessMemory
ReadProcessMemory
Toolhelp32ReadProcessMemory
这为我们的绘图工作带来了一个难题。虽然它确实执行 Process Read 操作,因此应包含在 Process Read 函数调用图中,但它也属于 Process Read 和 Handle Close 函数调用图。
问题在于,这个函数不再是原子的,这意味着它不能与给定操作的其他函数实现混合和匹配。假设应用程序选择用于“进程读取”操作。在这种情况下,应用程序通常可以选择与进程访问图中的任何简单函数配对。这种配对能力并非如此。实质上,使用此复合函数的应用程序被锁定为 using 和 。NtReadVirtualMemory
NtReadVirtualMemory
Toolhelp32ReadProcessMemory
OpenProcess
ReadProcessMemory
为了了解复合函数在函数调用图和操作中的工作原理,我创建了两种方法来可视化这些函数。第一种是在我称之为“复合函数图”的原子中查看函数,第二种是在相关操作的函数调用图的上下文中查看它。
复合函数图是理解复合函数如何工作的一种有趣方法。复合函数位于图形的左侧,其节点为紫色。然后,我们看到源自复合函数的箭头,并指向黄色节点,表示复合函数的操作。在本例中,我们看到 Process Access、Process Read 和 Handle Close。然后我们看到每个操作节点都指向相关操作的函数调用图的入口点,并显示后续进行的函数调用。例如,用于实现进程访问操作的调用。复合函数图可用于全面了解单个复合函数的工作原理。Toolhelp32ReadProcessMemory
api-ms-win-core-processesthreads-l1–1–2!OpenProcess
可视化复合函数的第二种方法是将它们集成到相关操作的函数调用图中。例如,下面的 Process Read 操作的函数调用图包括复合函数。然而,这一次,复合函数的节点是紫色的,表示它是一个复合函数,因此不能像其他带有红色节点的函数那样以原子方式使用。Toolhelp32ReadProcessMemory
我还包含了进程访问操作的函数调用图,以演示我们应该将复合函数添加到所有相关操作的函数调用图中。
请记住,我们为 mimikatz 创建的 Operational Graph 是 ,但允许将其折叠成如下所示:sekurlsa::logonPasswords
Process Enumerate -> Process Access -> Process Read
Toolhelp32ReadProcessMemory
Process Enumerate -> Toolhelp32ReadProcessMemory
我认为包含复合函数的第二个示例会很有帮助。我感兴趣的一种技术是访问令牌操作。
罗比·温彻斯特
我最初在 2017 年的 Black Hat Europe 上介绍了访问令牌操作,随后发布了有关该主题的白皮书。该论文确定了三类令牌盗窃,这些类别现在被归类为 MITRE ATT&CK 中访问令牌操作技术的子技术(令牌模拟/盗窃、使用令牌创建进程以及制作和冒充令牌)。访问令牌操作是业界似乎对这种技术有很好的理解,但随着时间的推移,我们不断完善这种理解。一些很好的例子是贾斯汀·布伊(Justin Bui)和乔纳森·约翰逊(Jonathan Johnson)的作品(这里和这里)。
我最近回顾了这种构建函数调用图的技术,我重新发现了一个有趣的发散用例,它似乎与本文密切相关。应用程序可以在两个函数之间进行选择,以将模拟令牌应用于当前线程。第一个是 ,第二个是 。Justin Bui 之前花了一些时间研究相关的 API 函数来执行 SYSTEM 令牌盗窃(想想 meterpreter 的命令),所以我问他有什么区别。在我们的对话中,这两个函数之间的显着区别之一是应用程序必须首先创建目标令牌的副本,然后才能调用 。同时,不需要这一步。这种差异似乎是有利的,但是当我们研究他们的代码实现时,这种情况会改变吗?SetThreadToken
ImpersonateLoggedOnUser
getsystem
SetThreadToken
ImpersonateLoggedOnUser
ImpersonateLoggedOnUser
下面是函数调用路径,后跟 。Like or 是一个简单的函数,它只执行一个操作,即 Thread Write(它使用 将所需的令牌写入线程)。SetThreadToken
ReadProcessMemory
OpenProcess
SetThreadToken
NtSetInformationThread
经过调查,我们看到了一个略有不同的情况,而且情况更复杂。事实证明,这是一个复合函数,它再次执行三个操作,令牌读取(获取有关令牌本身的信息)、令牌复制(创建目标令牌的副本)和线程写入(将令牌应用于目标线程)。我们看到,不需要重复令牌并不完全正确。相反,它通过 隐式执行复制。ImpersonateLoggedOnUser
ImpersonateLoggedOnUser
ImpersonateLoggedOnUser
NtDuplicateToken
上面我们看到了 的复合函数图。尽管如此,我们还是可以看到这个复合函数如何集成到令牌复制和线程写入操作的函数调用图中。我希望你注意的一个关键细节是,这次有三个紫色节点,而不是我们看到的那个。 具有与我们见过的许多简单函数相似的分层结构。它有一个记录的函数、一个 API 集和一个未记录的函数组件。关键是应用程序可以调用这三个函数中的任何一个,但这三个函数都会导致复合结果。因此,我已将所有三个节点都包含在函数调用图中。然而,所有三个节点都呈紫色,以表明它们的复合性质。ImpersonateLoggedOnUser
Toolhelp32ReadProcessMemory
ImpersonateLoggedOnUser
我通过这项工作和这个博客系列的目标是探索似乎存在的从战术到函数的新兴分类法。在我探索和构建层和类别时,我偶尔会偶然发现一些不太适合我创建的架构的示例。这种不一致是一个奇妙的问题,因为它允许我扩展或完善模式以更好地表示现实,或者证明我的模式存在根本错误。在这种情况下,复合函数挑战了我的模式的一个公理化假设:所有函数都执行单个操作。这个公理显然是错误的,我不得不更新我对世界(网络世界)的看法来应对这一事实。似乎将函数分类为简单函数,执行一个且仅执行一个操作的函数,以及充当微型独立应用程序并执行多个操作的复合函数,工作得很好,并且与架构的其余部分一致(目前)。希望这也能帮助你理解,无论你是站在红色还是蓝色的一边,事情比我们看到的要多,仅仅因为你没有显式调用一个函数并不意味着你没有隐式调用它。
二进制漏洞课程(更新中)
windows网络安全防火墙与虚拟网卡(更新完成)
windows文件过滤(更新完成)
USB过滤(更新完成)
游戏安全(更新中)
ios逆向
windbg
还有很多免费教程(限学员)
更多详细内容添加作者微信