序
介绍
欢迎回到威胁检测与搜寻建模博客系列文章,我的一位同事向我介绍了函数式编程中的组合概念。在此过程中,他向我介绍了一本名为《应用范畴理论的邀请:构成性的七幅草图》的书。除其他外,本书还探讨了在某些情况下使用范畴论来隐藏复杂性,以及函数如何通过共享输入和输出组合在一起的想法。例如,考虑下面的图片(图 1),它来自书中,代表了柠檬蛋白酥皮派的食谱作为接线图。
在这张图中,我们看到了如何从原材料转移到最终产品,假设所有必要的成分都在手边,并且厨师了解每个盒子所代表的功能。如果你有鸡蛋,那么你可以将蛋黄与蛋白分开。然后,这两个不同的组件充当不同子程序的输入,特别是“制作柠檬馅”和“制作蛋白酥皮”。
相关视频教程
恶意软件开发(更新到了155节)
这篇博文的目的是鼓励你开始在同一上下文中思考恶意函数链。实现此功能链所需的原材料是什么,为了生产中间成分(例如蛋白酥皮)而必须实现哪些子程序,以及生产最终产品所需的步骤及其顺序是什么?
在下一节中,我们将研究三种不同的功能链(即令牌模拟/盗窃、进程注入和文件映射),以了解组合如何表现,并讨论组合对我们的检测工程工作的一些影响。一旦我们调整了函数链的概念以适应这个模型,我们就可以开始就如何基于这些“依赖关系图”构建弹性检测规则做出明智的决策。
令牌冒充/盗窃
我们将深入研究的第一个示例是标准的令牌模拟/盗窃。这是你希望在许多所谓的工具中看到的实现,例如我为 PSReflect-Functions PowerShell 模块编写的 Get-System 函数(图 2)。getsystem
上面显示的源代码指示调用四个 Windows API 函数来实现此行为。
OpenProcess
— 打开远程/目标进程的句柄;此过程将在目标用户的上下文中运行OpenProcessToken
— 打开主进程的主令牌(即要“被盗”的令牌)的句柄DuplicateToken
— 创建令牌的副本,以便将其应用于调用线程SetThreadToken
— 将令牌的重复副本应用于调用线程,从而模拟目标用户
下面,我们看到一个函数链,表示系统令牌模拟/盗窃的实现(图 3)。
映射功能依赖关系
由于这是第一个例子,因此明确映射功能组合在实践中的工作原理似乎很有用。在本节中,我们将查看每个函数的 Microsoft 文档的语法部分,并跟踪每个函数的输出,因为它用作后续函数的输入。
OpenProcess
第一个函数是 。此函数采用三个参数并将 a 输出到指定的进程。第一个参数是 ,它指示调用程序请求对目标进程的访问类型(此值必须至少包含访问权限)。第二个参数 指定“由此进程创建的进程是否将继承生成的句柄”。在此用例中不需要句柄继承,因此可以将此值设置为 。最后一个参数是目标进程的进程标识符。程序员可以通过多种方式获取此值,但在本次讨论中,我们将假设攻击者已经掌握了此信息。OpenProcess
HANDLE
dwDesiredAccess
PROCESS_QUERY_LIMITED_INFORMATION
bInheritHandle
FALSE
dwProcessId
然后,我们可以首先使用 GraphViz 对函数进行建模,其中图 6 中的框表示,每行表示其输出(第一行)和参数。由于是链中的第一个函数,因此我们还没有任何有趣的关系可供我们绘制。OpenProcess
OpenProcess
OpenProcess
OpenProcessToken
链中的第二个函数是 。接下来,我们查看 的语法部分,并看到该函数采用三个参数,即 、 和一个指向 调用 的指针。我们还注意到,此函数的返回值类型为(图 7)。OpenProcessToken
OpenProcessToken
ProcessHandle
DesiredAccess
HANDLE
TokenHandle
BOOL
在咨询了每个参数的具体详细信息后,我们发现它应该是“将打开其访问令牌的进程的句柄”,这意味着这将是从调用返回的进程句柄。我们开始看到构图在行动。调用的输出被用作调用的输入。ProcessHandle
OpenProcess
OpenProcess
OpenProcessToken
第二个参数 需要“一个访问掩码,用于指定对访问令牌的请求访问类型”。这取决于程序员接下来要做什么(在这种情况下),但该值是可预测的,因此不依赖于某些先前的函数调用。DesiredAccess
TOKEN_DUP_HANDLE
最后,第三个参数 实际上是返回“新打开的访问令牌”的句柄的位置。此函数的作用与 略有不同。在这种情况下,句柄将作为输出参数返回,而不是返回值。对“返回值”部分的简短咨询告诉我们,返回值只是报告请求是成功还是不成功。TokenHandle
OpenProcess
BOOL
现在,我们可以更新 GraphViz 图,以包含一条记录和一个箭头,该箭头表示函数返回的值与函数参数之间的关系(图 8)。OpenProcessToken
HANDLE
OpenProcess
OpenProcessToken
ProcessHandle
DuplicateToken (复制令牌)
现在我们有了一个令牌句柄,我们可以开始考虑使用它了。只有一个问题:我们的令牌句柄已被现有进程使用。要模拟令牌,我们必须首先复制它。幸运的是,我们的第三个函数 就是这样做的。DuplicateToken
文档页面的语法部分显示该函数采用三个参数,即 、 和 。此外,与 非常相似,我们看到它返回一个值,根据文档,该值还报告了函数是否成功执行(图 9)。DuplicateToken
ExistingTokenHandle
ImpersonationLevel
DuplicateTokenHandle
OpenProcessToken
BOOL
更深入地研究 Parameter 部分,我们发现该参数是访问令牌的句柄。这是从我们对函数的调用返回的值将被传递的地方。再一次,我们看到并作曲。该参数指定新令牌的 。这是一个可预测的值,因此没有依赖关系。最后,该参数是另一个输出参数,其中返回访问令牌的新副本。ExistingTokenHandle
OpenProcessToken
OpenProcessToken
DuplicateToken
ImpersonationLevel
SECURITY_IMPERSONATION_LEVEL
DuplicateTokenHandle
我们现在可以更新我们的 GraphViz 图以包含函数调用,并显示返回给 参数的值如何成为调用 的输入;特别是参数。现在,我们已经完成了函数链的 3/4,到目前为止,我们看到所有函数都组成了一个“有效”的函数链(图 10)。DuplicateToken
OpenProcessToken
TokenHandle
DuplicateToken
ExistingTokenHandle
SetThreadToken
此时,我们有一个目标访问令牌的副本。现在我们所要做的就是将其应用于我们当前的线程。这将允许我们接管目标帐户的身份(在本例中)。为此,最后一个函数调用是对函数的调用。这个函数非常简单,因为它只有两个参数,和 ,并返回一个报告成功执行的值(图 11)。NT AUTHORITYSYSTEM
SetThreadToken
Thread
Token
BOOL
根据文档中的参数详细信息,该参数标识“函数向其分配模拟令牌的线程”;但是,它还提到,如果此参数设置为 ,则“函数将模拟令牌分配给调用线程”。这意味着攻击者只需将此参数设置为,令牌就会分配给他们的线程。Thread
NULL
NULL
该参数是要模拟的令牌的句柄。此参数将设置为从调用函数返回的令牌的副本。同样,这是我们的函数组成的地方!Token
DuplicateToken
现在,我们可以为这个函数链生成最终的 GraphViz 图,我们将看到链中的每个函数都与后续函数组成(图 12)。
这张图告诉我们一些事情。首先,链中的每个函数都是完成目标行为所必需的,即代币模拟。其次,链中函数的顺序是固定的。由于每个函数都依赖于后续函数的输出,因此我们不能按任何其他顺序调用这些函数。考虑到这些观察结果,我们必须记住,我们只分析了一个功能链。当我们研究更多的链时,我们应该睁大眼睛,以确定这些观察结果是适用于所有函数链的一般规则,还是因为它们仅适用于这个特定的或类似的函数链。
Process Injection
我们的第二个例子是传统的工艺注入实现。在这里,我们将研究包括 、 、 在内的标准功能链,因为这是实现 Process Injection 的最常见功能链(图 13)。OpenProcess
VirtualAllocEx
WriteProcessMemory
CreateRemoteThread
OpenProcess
— 打开远程/目标进程的句柄VirtualAllocEx
— 在目标进程中分配一个内存缓冲区,该缓冲区将用于保存有效负载WriteProcessMemory
— 将 shellcode 有效负载写入目标进程中先前分配的内存缓冲区CreateRemoteThread
— 在目标进程中创建一个执行有效负载的线程
映射功能依赖关系
现在让我们看一下这个函数链中的函数是如何组成的。
OpenProcess
如果我们要对每个函数执行与上一个示例相同的分析类型,我们会发现输出一个句柄到类型为 .然后我们会发现 、 和 都把该进程句柄作为输入。这意味着对每个后续调用的调用都是必需的。然后,生成的依赖关系图将如下所示(图 14):OpenProcess
HANDLE
VirtualAllocEx
WriteProcessMemory
CreateRemoteThread
OpenProcess
VirtualAllocEx
现在的问题是了解其余的依赖项。我们看到,我们的示例函数链接下来调用该函数。这只是偏好,还是由于函数的组成方式而需要此顺序?如果我们查看文档,我们会看到它的既定用途是“保留、提交或更改指定进程的虚拟地址空间内内存区域的状态”。这告诉我们,在我们最终将有效负载写入内存缓冲区之前,内存缓冲区正在分配。VirtualAllocEx
VirtualAllocEx
接下来,我们看到返回值是一个(指向内存缓冲区的指针),文档说“如果函数成功,则返回值是分配的页面区域的基址。事实证明,这个基址将用作 参数和 参数的输入。这表明 MUST 在函数链中排在第二位,因为它依赖于 ,但它依赖于 和 。有了这些知识,我们可以更新依赖关系图,如下所示(图 15):LPVOID
WriteProcessMemory
lpBaseAddress
CreateRemoteThread
lpStartAddress
VirtualAllocEx
OpenProcess
WriteProcessMemory
CreateRemoteThread
WriteProcessMemory
第三个函数是 。在此上下文中,此函数用于将代码写入以前分配的内存缓冲区。在分析了我们最后两个函数之间的关系时,似乎没有像我们之前看到的那样显式关系。没有输出用作 的输入。但是,我们确实知道 的目的是执行由 编写的有效负载。如果没有发生,那么调用 .因此,对这两个功能进行排序仍然有必要的因素。WriteProcessMemory
WriteProcessMemory
CreateRemoteThread
WriteProcessMemory
CreateRemoteThread
CreateRemoteThread
WriteProcessMemory
WriteProcessMemory
CreateRemoteThread
为了表示 和 之间的这种更隐式的关系,我使用了一个灰色箭头来指向 的参数(即有效负载传递给函数的参数)和 的参数,该参数是指向进程内存中有效负载所在的缓冲区的指针。现在,我们可以为这个函数链生成最终的依赖关系图,如下所示(图 16):WriteProcessMemory
CreateRemoteThread
WriteProcessMemory
lpBuffer
CreateRemoteThread
lpStartAddress
我们对第二条链的分析再次显示了链中函数顺序的固定性。这些关系允许我们说出诸如 NECESSARY for 之类的话,并且还表明,一旦我们看到后续函数,我们就可以开始推断哪些函数可能已经发生。例如,如果我们看到对 的调用,或者更一般地说是 Process Write 操作,我们可以推断它可能被调用并且请求包含访问权限。对于检测工程师来说,这是一项有用的功能,因为现代端点检测和响应 (EDR) 解决方案一次呈现一个事件。这意味着我们必须将检测规则基于单个操作。OpenProcess
VirtualAllocEx
WriteProcessMemory
OpenProcess
PROCESS_VM_WRITE
我打算在下一篇文章中更深入地研究这一点,但此推理是选择检测规则所依据的事件的重要组成部分。
文件映射
许多读者会熟悉我们在上一节中刚刚探讨的传统“工艺注射”功能机制。更抽象地表达此链的一种常见方法是,为了进行进程注入,必须分配内存,必须将代码写入该缓冲区,并且必须执行代码。前面提到的经典注入方法用作分配原语、写入原语和执行原语。然而,多年来,我们已经看到工艺注入的许多演变,它们改变了用于一个或多个这些基元的功能子链。VirtualAllocEx
WriteProcessMemory
CreateRemoteThread
在本节中,我们将研究文件映射注入,其中函数子链用于替换经典的分配原语,并且(即本地复制函数)用于替换写入原语。下面(图 17)是一个函数链,它表示我找到的一个示例是如何实现的。CreateFileMapping -> MapViewOfFile -> MapViewOfFile2
memcpy
OpenProcess
— 打开远程/目标进程的句柄CreateFileMapping
— 创建内存中文件映射对象MapViewOfFile
— 将文件映射对象映射到调用进程MapViewOfFile2
— 将文件映射对象映射到目标进程Memcpy
— 将有效负载复制到文件映射对象的本地映射地址CreateRemoteThread
— 在目标进程集中创建一个线程,以执行位于文件映射对象中的有效负载
映射功能依赖关系
由于这个例子有点复杂,我将采用稍微不同的方法,只有在我们讨论了源函数和目标函数之后才表示函数之间的关系。正如我们将看到的,虽然不常见,但某些函数链在函数顺序方面可能是动态的。我们将在这个分析中看到原因。
OpenProcess
我们在此函数链中遇到的第一个函数是(图 18)。同样,此调用的重点是打开我们注入的目标进程的句柄。OpenProcess
CreateFileMapping(创建文件映射)
接下来,我们看到对 的调用。请记住,这是进程注入的一个子技术,其目标是更改内存分配和将代码写入目标进程的方式。这允许恶意软件跳过对 和 的潜在“危险”调用。该函数创建一个文件映射对象,该对象允许多个进程“共享同一文件映射对象的视图”。通常,这将用于加载共享文件,但如果保留该参数,则会创建一个空文件映射对象,该对象可以用作恶意负载的缓冲区。请注意,所有参数都不采用进程句柄,因此 和 之间没有关系或依赖关系。这是我们第一次在函数链中看到两个连续的函数不组合(图 19)。CreateFileMapping
VirtualAllocEx
WriteProcessMemory
CreateFileMapping
hFile
NULL
CreateFileMapping’s
OpenProcess
CreateFileMapping
MapViewOfFile
现在文件映射对象已经建立,并且我们有了它的句柄,我们可以开始将其映射到我们的源进程和目标进程中。默认情况下,文件映射对象不与任何进程关联,因此必须调用该函数来“将文件映射的视图映射到调用进程的地址空间”。在这种情况下,调用进程是恶意软件,因此这是本地调用。需要许多参数,但第一个参数 是 ,是我们最感兴趣的参数,因为我们必须将句柄传递给从中返回的文件映射对象。在这里,我们看到 和 函数组成(图 20)。MapViewOfFile
MapViewOfFile
hFileMappingObject
CreateFileMapping
CreateFileMapping
MapViewOfFile
MapViewOfFile2
接下来,必须将文件映射对象的视图映射到目标进程中。这将造成源进程和目标进程可以访问相同的内存缓冲区的情况,一旦有效负载写入其中,这将非常有用。该函数无法将文件映射对象的视图映射到远程进程,但调用的单独函数具有该功能。在这种情况下,打开使用的进程的句柄和通过创建的文件映射对象的句柄将分别传递给 和 参数。现在,我们开始了解 和 如何间接相关(图 21)。MapViewOfFile
MapViewOfFile2
OpenProcess
CreateFileMapping
ProcessHandle
FileMappingHandle
OpenProcess
CreateFileMapping
在这一点上,您可能想知道这种“间接”关系在 和 之间有什么含义。正如我们在完成依赖关系图构建后将更详细地看到的那样,答案是,这个函数链中函数的顺序并不像我们在前面的示例中看到的那样是固定的。OpenProcess
CreateFileMapping
memcpy
现在,内存缓冲区已分配,无需调用 。接下来,必须在不调用 的情况下将有效负载写入缓冲区。为此,调用 C 函数。将源缓冲区复制到目标缓冲区;但是,它只能在调用进程的内存上下文中执行此操作。这就是为什么必须将文件映射对象的视图映射到本地进程的原因。有效负载将使用本地视图(映射的视图)写入,并使用远程视图(映射的视图)执行。此函数需要将本地视图的地址(由调用的返回值表示)传递给 的 dest 参数。完成后,代码将写入缓冲区,并可供调用进程和目标进程访问(图 22)。VirtualAllocEx
WriteProcessMemory
memcpy
memcpy
MapViewOfFile
MapViewOfFile2
LPVOID
MapViewOfFile
memcpy
CreateRemoteThread
最后,我们必须执行恶意负载。虽然有几种不同的方法可以实现这一点,但我们坚持使用我们在上一个示例中看到的方法。将需要通过 打开的目标进程的句柄。它还需要通过映射的视图的地址(由返回值表示)。此外,正如我们在前面的示例中所讨论的,只有在代码写入缓冲区后才有意义,因此我们将再次包含从函数的 src 参数到函数参数的隐式关系(用灰色箭头表示)(图 23)。CreateRemoteThread
CreateRemoteThread
OpenProcess
MapViewOfFile2
PVOID
CreateRemoteThread
memcpy
lpStartAddress
CreateRemoteThread
在这一点上,我们认识到这个依赖关系图与我们之前分析的示例不同;具体来说,我们看到没有一个基于依赖关系的固定函数链。相反,有多个可能的函数链。请考虑以下两个函数链。第一个(图 24)是我们开始的示例,第二个(图 25)是替代链。
这些链具有相同的确切功能,但它们的调用顺序不同。如果您遇到两个分别实现这些链的恶意软件样本,您会认为它们实现了相同的“行为”吗?这是我在分析不同技术实现时遇到的一个问题。构图,特别是依赖关系图,给了我这个问题的答案。有问题的答案是,如果两个函数链产生相同的依赖关系图,则可以认为它们“功能等效”;因此,从检测的角度来看,顺序无关紧要。
结论
正如引言中提到的,这篇文章并不是本系列中最实用的帖子。相反,目标是为我们在后续文章中使用的概念基础奠定基础。
对于检测工程师来说,了解可以组合的功能链以在其网络中产生某些效果非常重要。通过了解所需的原始成分以及产生对手所需结果所需的不同子程序,我们可以通过拒绝这些成分或子程序或监视其实例化来创建更强大的安全控制。同样重要的是,我们要明白,两个工具可以以不同的顺序实现它们的功能,同时仍然是“功能等效的”。与纯函数链相比,依赖图似乎是理解“相似性”的更好模型。
在我结束之前,我要给你们留下最后一个临别的想法。在上述书中,当他们介绍柠檬蛋白酥皮图(图 1)时,他们提供了以下提示:
此提示与攻击者交易的问题有关。攻击者自然拥有或通过先前的行动收集了哪些资源?他们想要实现什么?可以从A点到B点吗?如果是这样,有什么不同的方式(此时的思维功能链)可以到达那里?哪种方式可以以最低的成本实现目标?本系列旨在了解攻击者需要什么才能实现某些目标,以及他们从原始资源到所需最终状态的不同方式。
二进制漏洞课程(更新中)
windows网络安全防火墙与虚拟网卡(更新完成)
windows文件过滤(更新完成)
USB过滤(更新完成)
游戏安全(更新中)
ios逆向
windbg
还有很多免费教程(限学员)
更多详细内容添加作者微信
推荐站内搜索:最好用的开发软件、免费开源系统、渗透测试工具云盘下载、最新渗透测试资料、最新黑客工具下载……
还没有评论,来说两句吧...