Skip to main content

进程与线程

在恶意软件开发领域,诸多代码注入防御规避技术是围绕着进程与线程展开的,因此我们首先需要理解进程与线程的相关概念。

进程与线程

Windows 进程是指当前运行在 Windows 主机上的程序或者应用的实例,每个进程都与其他进程隔离,并拥有由操作系统分配的私有资源。进程可以是用户或者操作系统开启的,消耗着如内存、磁盘空间等资源。

线程是进程内的最小执行单元,每个 Windows 进程由 1 个或多个线程并发运行。进程中运行的每个线程共享进程的内存和资源。 与进程不同,线程之间不是相互隔离的,可以直接与进程中的其他线程交互。

总之,进程是一个正在运行的程序,拥有操作系统分配的独自的资源,而线程是进程内的执行路径。进程中的多个线程共享进程的资源,但独立地并发地执行。因此,虽然一个进程可以在其中运行多个线程,但每个线程独立操作,执行自己的指令,这个概念是并发编程的关键,其中同时执行多个任务以提高程序的效率和性能。

image.png

 

进程内存

Windows 进程也使用内存来存储数据和指令。当进程创建时,会被分配内存,分配的内存量可以由进程本身设置。操作系统使用虚拟内存和物理内存来管理内存。通过创建可由应用程序访问的虚拟地址空间,虚拟内存允许操作系统使用比物理可用内存更多的内存。这些虚拟地址空间被划分为页,然后分配给进程。

进程可以有不同类型的内存:

私有内存:专用于单个进程,不能被其他进程所共享,这种类型的内存用于存储特定于进程的数据。
映射内存:可以在 2 个或多个进程之间共享,它用于在进程之间共享数据,例如共享库、共享内存段和共享文件。映射内存对其他进程可见,但不会被其他进程修改。
映像内存:包含可执行文件的代码和数据,它用于存储进程使用的代码和数据,例如程序的代码、数据和资源。 映像内存通常与加载到进程地址空间中的 DLL 文件相关。


PEB

进程环境块 (PEB) 是 Windows 中的一种数据结构,其中包含有关进程的信息,例如进程的参数启动信息分配的堆信息加载的 DLL 等。操作系统使用 PEB 来存储正在运行的进程的信息,Windows 加载程序使用它来启动应用程序。它还存储有关进程的信息,例如进程 ID (PID) 和可执行文件的路径

创建的每个进程都有自己的 PEB 数据结构,C 语言下的 PEB 结构体如下所示:

typedef struct _PEB {
  BYTE                          Reserved1[2];
  BYTE                          BeingDebugged;
  BYTE                          Reserved2[1];
  PVOID                         Reserved3[2];
  PPEB_LDR_DATA                 Ldr;
  PRTL_USER_PROCESS_PARAMETERS  ProcessParameters;
  PVOID                         Reserved4[3];
  PVOID                         AtlThunkSListPtr;
  PVOID                         Reserved5;
  ULONG                         Reserved6;
  PVOID                         Reserved7;
  ULONG                         Reserved8;
  ULONG                         AtlThunkSListPtr32;
  PVOID                         Reserved9[45];
  BYTE                          Reserved10[96];
  PPS_POST_PROCESS_INIT_ROUTINE PostProcessInitRoutine;
  BYTE                          Reserved11[128];
  PVOID                         Reserved12[1];
  ULONG                         SessionId;
} PEB, *PPEB;

微软并没有在文档中记录所有的元素,并且 PEB 可能在以后进行修改。该结构体中的一些元素对于进程的操作较为重要


BegingDebugged

该元素表示当前进程是否正在被 Debug,当进程正在被调试时,该元素被设置为 1 (TRUE),反之则为 0 (FALSE)。


LDR

Ldr 是指向 PEB 中的 PEB_LDR_DATA 结构体的指针,该结构包含有关进程加载的 DLL 模块的信息。它包含了进程中加载的 DLL 的列表、每个 DLL 的基址以及大小,Windows 加载程序使用它来跟踪进程中加载的 DLL。我们则可以通过 LDR 枚举进程载入的 DLL 以及查找进程内存中的特定 DLL。

PEB_LDR_DATA 结构体如下所示:

typedef struct _PEB_LDR_DATA {
  BYTE       Reserved1[8];
  PVOID      Reserved2[3];
  LIST_ENTRY InMemoryOrderModuleList;
} PEB_LDR_DATA, *PPEB_LDR_DATA;

我们可以通过 LDR 枚举进程载入的 DLL 以及查找进程内存中的特定 DLL,以及寻找特定 DLL (例如 kernel32.dll) 的基址。并且根据这些信息来实现自定义的 GetModuleHandle


ProcessParameters

ProcessParameters 是 PEB 中的结构体,包含了映像路径、传递给进程的命令行参数等信息。Windows 加载程序将这些参数添加到进程的 PEB 结构中。ProcessParameters 是指向 RTL_USER_PROCESS_PARAMETERS 结构体的指针,代码如下所示:

typedef struct _RTL_USER_PROCESS_PARAMETERS {
  BYTE           Reserved1[16];
  PVOID          Reserved2[10];
  UNICODE_STRING ImagePathName;
  UNICODE_STRING CommandLine;
} RTL_USER_PROCESS_PARAMETERS, *PRTL_USER_PROCESS_PARAMETERS;

ProcessParameter 可被利用于实现命令行伪造攻击,我们在下一章节会介绍到。



PostProcessInitRoutine

PEB 结构中的 PostProcessInitRoutine 元素用于存储指向一个函数的指针,该函数在进程中的所有线程完成 TLS (线程本地存储) 初始化后由操作系统调用。该函数可用于执行该进程所需的任何其他初始化任务。


SessionId

PEB 中的 SessionID 元素是分配给单个会话的唯一标识符,用于追踪会话期间的用户活动。



TEB

TEB (线程环境块) 是 Windows 中存储有关线程信息的结构体,是为进程中的每个线程分配的数据块,操作系统使用它来管理线程。它包含线程的环境、安全上下文和其他相关信息。它存储在线程的中,供 Windows 内核用来管理线程。

在 C 语言中,TEB 的结构体如下所示:

typedef struct _TEB {
  PVOID Reserved1[12];
  PPEB  ProcessEnvironmentBlock;
  PVOID Reserved2[399];
  BYTE  Reserved3[1952];
  PVOID TlsSlots[64];
  BYTE  Reserved4[8];
  PVOID Reserved5[26];
  PVOID ReservedForOle;
  PVOID Reserved6[4];
  PVOID TlsExpansionSlots;
} TEB, *PTEB;

TEB 中的一些元素较为重要,我们分别来查看:

 

ProcessEnvironmentBlock

该元素是 PEB 结构体的指针,PEB 在上文刚讨论过。

 

TlsSlots

线程本地存储 (TLS) 是一种机制,使得给定多线程进程中的每个线程都可以分配位置来存储线程特定的数据。TlsSlots 是一个包含 64 个指针的数组,可用于存储特定于线程的数据。如果应用需要存储线程特定的数据,则它可以使用这些槽位之一来存储指向该数据的指针。

 

TlsExpansionSlots

如果需要超过 64 个 TLS 槽位,系统会分配一组额外槽位,并且指向该数组的指针存储在 TlsExpansionSlots 中。

 

在 Windows 操作系统上,每个进程都有一个唯一的进程标识符 PID,这是操作系统在创建进程时分配的。同样的概念也适用于正在运行的线程,正在运行的线程也有着唯一的 ID 与其他的线程进行区分。

有了唯一的标识符后,分别可以用 OpenProcess 与 OpenThread 来获得进程或线程的句柄:

HANDLE OpenProcess(
  [in] DWORD dwDesiredAccess,
  [in] BOOL  bInheritHandle,
  [in] DWORD dwProcessId
);
HANDLE OpenThread(
  [in] DWORD dwDesiredAccess,
  [in] BOOL  bInheritHandle,
  [in] DWORD dwThreadId
);