函数调用引擎盖下的内容远不止于此
这篇文章基于 2021 年 9 月的 Twitter 帖子,我写了该帖子来描述有关函数调用及其隐藏层次结构的相同概念。该线程的灵感来自inversecos的一系列推文,他们分享了恶意软件作者如何经常使用本机API而不是Win32 API作为逃避朴素检测的机制,这些检测假设每个应用程序都将使用Win32 API函数。我想解释为什么这种方法有效,现在我认为在博客形式中巩固我的想法是合适的。
操作系统的一个共同特性是需要为开发人员提供一种与操作系统交互的机制。此功能通常通过称为应用程序编程接口 (API) 的东西表现出来,该接口由一系列“函数”组成,开发人员可以使用这些“函数”来执行常见任务。API 中有一些固有的价值主张。首先,API 函数充当简化复杂任务的抽象层。并非所有开发人员都必须了解文件系统的复杂组件(如主文件表)、如何确定硬盘驱动器上的可用空间位置以及如何将数据写入物理硬盘驱动器。这都是由相关职能部门代表开发人员管理的。其次,API 的作用是限制开发人员与操作系统交互方式的可变性。操作系统是一个相对敏感的系统,对某些系统设置的错误更改可能会导致严重的不稳定。例如,许多功能(如 Windows 服务创建)需要与 Windows 注册表进行交互并对其进行更改。直接更改注册表可能很危险,因为操作系统对数据的结构有非常具体的期望,某些更改可能会导致致命的系统错误,从而导致蓝屏死机 (BSOD)。为了避免所有开发人员都需要与 Windows 注册表进行交互并了解 Windows 注册表的结构,API 函数提供了一个抽象层来为我们管理这个复杂的问题。
相关视频教程
恶意软件开发(更新到了125节)
在 Microsoft 的上下文中,实际上存在多层函数,它们倾向于相互嵌套或调用。最肤浅的层或供第三方开发人员与之交互的层称为 Win32 API。这个 API 很酷的地方在于,它被 Microsoft 很好地记录下来,这意味着很容易查找有关函数做什么以及如何调用它的指令。Microsoft 给出的另一个保证是,记录在案的功能将继续按预期永久工作(它可能不是字面上的永久工作,但您可以期望记录的功能在很长一段时间内以类似的方式工作)。因此,一般来说,Microsoft 的建议和愿望是第三方开发人员使用这些通常称为 Win32 API 的公共或记录的函数。但是,这些记录的函数并不是唯一存在的函数。事实上,许多记录的函数实际上在后台调用了其他记录的函数,甚至是未记录的函数。
为了更好地理解函数调用或函数调用嵌套之间的关系,我们应该研究一个常见的 Win32 API 函数的案例研究,特别是 CreateFileW。
深入研究 CreateFileW 的第一步是确定实现此函数的动态链接库 (DLL) 。DLL 是创建共享功能的二进制文件(实现代码的文件)。这个想法是,当每个程序员正在执行的任务是常见且可预测的时,重新发明轮子是没有意义的。相反,该功能可以而且应该集成到 DLL 中,然后应用程序可以引用这些 DLL。Win32 API 在一组默认或内置 DLL 中实现,用于应用程序使用的函数标记为“导出的函数”。若要确定哪个 DLL 实现或导出 CreateFileW,我们可以参考该函数的公共文档。在要求部分,我们看到 CreateFileW 是由 kernel32.dll 实现的。
一旦我们确定了哪个DLL实现了该函数,我们就可以研究该函数在后台的工作方式。一种方法是将 DLL 加载到反汇编器(如 IDA 或 Ghidra)中。一旦kernel32.dll加载到IDA中并应用了公共符号,我们就可以单击导出选项卡,其中包含DLL导出的所有函数的列表。此列表包括函数的友好名称、函数实现的相对地址以及函数的序号。我们可以搜索 CreateFileW,找到它后,我们可以在列表中双击它,这将带我们进入函数的实现。
当我们到达 CreateFileW 的实现时,它立即显得相当平淡。这是我们前面提到的函数嵌套的开始。奇怪的是,CreateFileW 似乎在调用自己,但事实并非如此简单。请注意表示法,该部分表示当前 DLL (kernel32.dll) 中不存在此版本的 CreateFileW。相反,此版本的函数实际上是从外部 DLL“导入”的。正如我们讨论的 DLL 导出供其他应用程序使用的函数一样,二进制文件使用导出函数的方式是通过“导入”它,正如我们在这里看到的。__imp_CreateFileW
__imp_
我们可以查看kernel32.dll的导入表来查找正在导入的CreateFileW版本。请注意,在本例中,该库不是 kernel32,而是 api-ms-win-core-file-l1–1–0,这是一个不同的库。有一种方法可以引用具有相同名称的函数,但这些函数由不同的 DLL 实现,这些 DLL 遵循此约定 DLLName!函数名称。例如,由 kernel32.dll 实现的 CreateFileW 版本称为 kernel32!CreateFileW,而 api-ms-win-core-file-l1–1–0 实现的版本是 api-ms-win-core-file-l1–1–0!CreateFileW。好的,因此,为了继续了解函数调用层次结构,我们必须了解 api-ms-win-core-file-l1–1–0 表示什么。
在上一节中,我们发现 kernel32!CreateFileW 正在导入 api-ms-win-core-file-l1–1–0!CreateFileW。api-ms-win-core-file-l1–1–0 组件是指较新版本的 Windows 中存在的一种称为 API 集的技术。API 集对用户和开发人员是透明的,但对我们来说,了解如何导航它们以继续深入研究 CreateFileW 实现非常重要。杰夫·查佩尔(Geoff Chappell)在他的博客上出色地记录了这项技术。请注意,有许多与文件相关的 API 都重定向到相同的库名称。此库用作 CreateFileW 的第三个实现的重定向器,但此版本的实现驻留在何处并不明显。为了弄清楚 CreateFileW 的实现位于何处,我们必须“解析”API 集以确定它指向执行的位置。这可以使用 James Forshaw 出色的 PowerShell 模块 NtObjectManager 来完成,该模块具有一个名为 Get-NtApiSet 的超级有用的 cmdlet。此 cmdlet 将 API 集解析为其重定向 DLL。
通过使用 Get-NtApiSet,我们发现 api-ms-win-core-file-l1–1–0 解析为 kernelbase.dll。这意味着链中的下一个函数是 kernelbase!CreateFileW 是一个未记录的函数。未记录的函数是指虽然外部可供第三方应用程序使用,但未在任何地方公开记录的功能。这意味着它有时可能难以使用,因为它采用的参数可能与记录的版本略有不同,并且 Microsoft 通常保留更改未记录功能实际工作方式的权利。这意味着合法的开发人员通常应避免使用未记录的功能,但恶意软件开发人员可能会使用这些功能向防御者呈现意想不到的外观。现在,我们可以将 kernelbase.dll 加载到反汇编器中,加载符号,并导航导出表以查找 CreateFileW 实现。
我们可以双击 CreateFileInternal,kernelbase 调用的内部函数!CreateFileW,以查看其内容,其中显示对名为 NtCreateFile 的导入函数的调用。
让我们检查一下导入表,看看什么 DLL 实现了这个函数。在下面的屏幕截图中,我们看到 NtCreateFile 驻留在 ntdll.dll 中,这意味着我们可以将此特定函数称为 ntdll!NtCreateFile。Ntdll 实现的函数属于称为本机函数的特殊函数类。一般来说,Microsoft 不鼓励直接使用本机函数,原因与它不鼓励使用未记录的函数类似。Win32 API 通常可以帮助开发人员完成在本机 API 层可能不那么容易实现的复杂任务。
我们可以将 ntdll.dll 加载到反汇编程序中,加载符号,然后导航到 NtCreateFile 的实现。原生 API 通常在执行层次结构中扮演着简单但非常重要的角色。他们的职责是进行适当的系统调用(系统调用)。系统调用是一种特殊类型的函数调用,负责通过 64 位系统上的指令将执行从用户模式转移到内核模式。请注意,右下角的基本块执行“系统调用”操作。此外,突出显示的值 , 表示内核中 NtCreateFile 对应项的值,我们可以将其称为 ZwCreateFile。一个复杂的因素是,与特定系统调用关联的数字可能会因操作系统版本而异,因此为了在此级别运行,开发人员必须非常清楚他们正在操作的操作系统版本。最近,直接进行系统调用已成为一种相对常见的可见性绕过,特别是因为它是绕过任何用户模式信号生成的好方法。要了解它是如何被利用的,请查看 j00ru 的 windows-syscalls 项目。SYSCALL
55h
虽然我们可以继续对内核进行分析,但我认为我们已经探索了足够多的方法来理解函数调用的分层性质。重要的一点是要认识到,任何“导出”的函数都是应用程序的入口点。虽然绝大多数应用程序将遵循调用相关 Win32 API 函数 (kernel32!CreateFileW),但是,攻击者有时以非正统的方式执行操作是有利的。如果防御者假设所有文件创建都将通过批准或规定的路径执行,因此只注意 Win32 API 的使用,那么攻击者可以利用这种方法的幼稚来避免可见性。
我生成了我们在这篇博文中观察到的函数调用的图形表示,以便我们可以看到它们之间的关系。我还以红色突出显示了导出的函数,以指示这些函数是图形的有效入口点(即使大多数开发人员只选择使用记录的 Win32 API 函数)。
主要的结论是,在网络安全和检测的背景下,我们不应该假设攻击者会选择最常走的路径或规定的路径来执行特定行为。因此,我们有必要了解这些选项并评估观察每个功能级别行为的机会。此外,值得一提的是,此图仅表示可用于创建文件的函数的一个路径。根据我的经验,通常有许多可能的路径可以使用。未来的博客文章将演示我们如何评估某些行为的众多路径以生成复杂的图形,以及如何使用该复杂图形来评估检测规则覆盖率并预测对手程序在未来的发展方式。同样的原则也适用。如果攻击者可以通过意想不到的路径完成行为,那么他们将在隐身方面占据上风。同时,通过映射所有可能的路径或至少所有已知的路径,防御者可以预测假设的实现、工具或路径,即使他们没有明确观察到利用该方法的工具。
其它课程
二进制漏洞课程(更新中)
windows网络安全防火墙与虚拟网卡(更新完成)
windows文件过滤(更新完成)
USB过滤(更新完成)
游戏安全(更新中)
ios逆向
windbg
还有很多免费教程(限学员)
更多详细内容添加作者微信