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




还没有评论,来说两句吧...