逆向工程恶意软件,第 4 部分:Windows 内部

欢迎回到我的逆向工程恶意软件系列。

原文链接:https://www.hackers-arise.com/post/2017/07/04/Reverse-Engineering-Malware-Part-4-Windows-Internals

通常,恶意软件的逆向工程是在Windows系统上完成的。这是因为尽管Linux和Mac OS最近取得了进展,但Windows系统仍然占世界上所有计算系统的90%以上。因此,超过90%的恶意软件旨在破坏Windows系统。因此,将我们的注意力集中在Windows操作系统上是有意义的。

在逆转恶意软件时,操作系统起着关键作用。所有应用程序都与操作系统交互,并与操作系统紧密集成。我们可以通过探测操作系统和应用程序(恶意软件)之间的接口来收集有关恶意软件的大量信息。

为了了解恶意软件如何使用和操纵Windows,我们需要更好地了解Windows操作系统的内部工作原理。在本文中,我们将检查内部工作原理或Windows 32位系统,以便我们可以更好地了解恶意软件如何使用操作系统来实现其恶意目的。

Windows内部可以填充几本教科书(并且已经),因此我将尝试仅以粗略的方式涵盖最重要的主题。我希望为您提供足够的信息,以便您可以在以下文章中有效地逆转恶意软件。

虚拟内存

虚拟内存的想法是,CPU和操作系统不是直接访问物理内存,而是在软件和物理内存之间创建一个不可见的层。

操作系统创建一个 CPU 查阅的表,称为页表,该页表将进程定向到它应使用的物理内存的位置。

处理器将内存划分为多个页面

页面是固定大小的内存块。页表中的每个条目都引用一页内存。通常,32 位处理器使用 4k 大小的页面,但有一些例外。

内核 v 用户模式

具有页表使处理器能够强制执行有关如何访问内存的规则。例如,页表条目通常具有确定是否可以从非特权模式(用户模式)访问页的标志。

通过这种方式,操作系统的代码可以驻留在进程的地址空间内,而不必担心非特权进程将访问它。这可以保护操作系统的敏感数据。

特权模式与非特权模式之间的区别变成了内核(特权)和非特权(用户)模式。

内核内存空间

内核为自己保留 2GB 的地址空间。此地址空间包含所有内核代码,包括内核本身和任何其他内核组件,如设备驱动程序。

Paging

分页是内存区域在最近未使用时临时刷新到硬盘驱动器的过程。处理器跟踪自上次使用内存页以来的时间,并刷新最旧的内存页。显然,物理内存比硬盘驱动器上的空间更快,更昂贵。

Windows 操作系统跟踪上次访问页面的时间,然后使用该信息查找一段时间以来未访问的页面。然后,Windows 会将其内容刷新到文件中。然后可以丢弃刷新页面的内容以及其他信息使用的空间。当操作系统需要访问这些刷新的页面时,将生成页面错误,然后系统将信息“分页”到文件中。然后,操作系统将访问页面文件并将信息拉回内存以供使用。

对象和句柄

Windows 内核使用集中式对象管理器组件管理对象。此对象管理器负责所有内核对象,如节、文件和设备对象、同步对象、进程和线程。它只管理内核对象。

与 GUI 相关的对象由单独的对象管理器管理,这些对象管理器在 WIN32K.SYS 中实现。

内核代码通常使用指向对象数据结构的直接指针来访问对象。应用程序使用句柄来访问各个对象。

句柄

句柄是特定于进程的数字标识符,它是进程专用句柄表的索引。句柄表中的每个条目都包含一个指向基础对象的指针,这是系统将句柄与对象相关联的方式。每个句柄条目还包含一个访问掩码,该掩码确定可以使用此特定句柄对对象执行哪些类型的操作。

进程

进程实际上只是用于运行程序的隔离内存地址空间。为每个程序创建地址空间,以确保每个程序在其自己的地址空间中运行,而不会与其他进程发生冲突。在进程的地址空间内,系统可以加载代码模块,但必须至少有一个线程正在运行才能执行此操作。

进程初始化

创建流程对象和新地址空间是第一步。当新进程调用 Win32 API CreateProcess 时,API 会创建一个进程对象,并为该进程分配新的内存地址空间。

CreateProcess 将 NTDLL.DLL和程序可执行文件(.exe文件)映射到新创建的地址空间。CreateProcess 创建进程的第一个线程并为其分配堆栈空间。进程的第一个线程将恢复并开始在 NTDLL 内的 LdrpInitialization 函数中运行.DLL

LdrpInitialization以递归方式遍历主可执行文件的导入表,并将它们映射到内存中所需的每个可执行文件。

此时,控制权传递到 LdrpRunInitializeRoutines,这是一个内部 NTDLL 例程,负责初始化当前加载到地址空间中的所有静态链接 DLL。初始化过程由每个 DLL 的入口点与DLL_PROCESS_ATTACH常量的链接组成。初始化所有 DLL 后,LdrpInitialize 将调用线程的实际初始化例程,即 KERNELL32.DLL 中的 BaseProcessStart 函数。此函数依次调用可执行文件的 WinMain 入口点,此时进程已完成其初始化序列。

线程

在给定的时刻,系统中的每个处理器都在运行一个线程。Windows 可以决定在给定的给定时间中断正在运行的线程并切换到另一个线程的执行,而不是继续运行一段代码直到它完成。

线程是具有 CONTEXT 数据结构的数据结构。本背景包括:

(1) 线程上次运行时处理器的状态

(2) 一个或两个用于堆栈空间的内存块

(3)堆栈空间用于在上下文切换时保存线程的当前状态

(4)在Windows中管理线程的组件是调度程序和调度程序

(5) 决定哪个线程得到 s 运行多长时间并执行上下文切换

上下文切换

上下文切换是线程中断。在某些情况下,线程只是自己放弃CPU,内核不必中断。每个线程都被分配了一个量程,它量化了线程可以不间断地运行的时间。量程过期后,线程将中断,并允许其他线程运行。整个过程对线程是透明的。然后,内核在挂起之前存储 CPU 寄存器的状态,然后在线程恢复时恢复该寄存器状态。

Win32 API

API 是操作系统提供给应用程序以与操作系统通信的一组函数。Win32 API 是构成 Windows 应用程序的官方低级编程接口的大量函数。MFC 是 Win32 API 的通用接口。

Win 32 API的三个主要组件是;

(1) 内核或基础 API:这些是与 GUI 无关的服务,如 I/O、内存、对象和进程 d 线程管理

(2)GDI API:这些包括低级图形服务,例如用于绘制线条,显示位图等的服务。

(3)USER API:这些是更高级别的GUI相关服务,例如窗口管理,菜单,对话框,用户界面控件。

系统调用

系统调用是指用户模式代码需要计算内核模式函数。当应用程序调用操作系统 API 时,通常会发生这种情况。用户模式代码调用一条特殊的 CPU 指令,该指令告诉处理器切换到其特权模式并调用调度例程。然后,此调度例程调用从用户模式请求的特定系统函数。

PE 格式

Windows 可执行文件格式是 PE(可移植可执行文件)。术语“可移植”是指格式在众多环境和体系结构中的多功能性。

可执行文件是可重定位的。这意味着每次加载它们时都可以在不同的虚拟地址加载它们。可执行文件必须与加载在同一内存地址中的其他可执行文件共存。除了主可执行文件之外,每个程序都有一定数量的附加可执行文件加载到其地址空间中,无论它是否有自己的 DLL。

Relocation Issues

如果尝试将两个可执行对象加载到同一个虚拟空间中,则必须将一个可执行对象重新定位到另一个虚拟空间中。每个可执行文件模块都被分配了一个基址,如果已经存在某些东西,则必须重新定位它。

可执行标头中永远不会有绝对的内存地址,这些地址只存在于代码中。为了实现这一点,每当可执行标头内有指针时,它始终是相对虚拟地址 (RVA)。可以将其视为简单的偏移量。加载文件时,会为其分配一个虚拟地址,加载的通过将模块基址添加到 RVA 来计算 RVA 中的实际虚拟地址。

Image 部分

可执行部分分为单独的部分,其中存储了文件的内容。节是必需的,因为在加载模块时,内存管理器会以不同的方式处理文件中的不同区域。这种划分发生在包含可执行文件代码的代码部分(也称为文本)和包含可执行文件数据的数据部分。

加载时,内存管理器会根据不同部分标题中的设置来设置不同部分的内存页的访问权限。

Section Alignment

各个部分通常具有在可执行标头中定义的不同访问设置。加载可执行映像时,内存管理器必须应用这些访问设置。将可执行文件加载到内存中时,各节通常必须页面对齐。这将占用磁盘上的额外空间来页面对齐磁盘上的部分。因此,PE 标头具有两种不同类型的对齐字段:截面对齐和文件对齐。

DLL 的

DLL允许将程序分解为多个可执行文件。通过这种方式,可以减少总体内存消耗,在需要实现可执行文件的功能之前不会加载可执行文件。可以更换或升级单个组件以修改或改进程序的某个方面。

DLL 可以显著降低整体系统内存消耗,因为系统可以检测到某个可执行文件已加载到多个地址空间中,然后将其映射到每个地址空间,而不是将其重新加载到新的内存位置。DLL 不同于链接到可执行文件的静态库 (.lib)。

加载 DLL 的

静态链接是通过让每个模块列出它使用的模块以及它在每个模块中调用的函数来实现的。这称为导入表(请参阅 IDA 专业教程)。运行时链接是指一个不同的进程,可执行文件可以决定在运行时加载另一个可执行文件,并从该可执行文件调用函数。

PE Headers

可移植可执行 (PE) 文件以 DOS 标头开头。

“此程序无法在 DOS 模式下运行”

typedef struct _IMAGE_NT_HEADERS {

    DWORD Signature;

    IMAFE_FILE_HEADER Fileheader;

    IMAGE_OPTIONAL_HEADER32 OptionHeader;

}  Image_NT_HEADERS32, *PIMAGE_NT_HEADERS32

此数据结构引用包含实际 PE 标头的两个数据结构。

输入和输出

导入和导出是启用可执行文件的动态链接过程的机制。编译器不知道导入函数的实际地址,只有在运行时才能知道这些地址。为了解决此问题,链接器将创建一个导入表,该表按名称列出当前模块导入的所有函数。

发表评论

您的电子邮箱地址不会被公开。