tryhackme——Windows Internals
文章目录
- 一、进程
- 二、线程
- 三、虚拟内存
- 四、动态链接库
- 五、PE格式
- 六、与windows内核交互
一、进程
进程(Process)是操作系统中资源分配的基本单元,Windows的大多数功能都可以包含在一个应用程序中,并具有相应的进程。
攻击者可以攻击进程来逃避检测,并将恶意软件隐藏为合法进程。以下是攻击者可能针对进程使用的一些潜在攻击向量:
- 进程注入
- 进程挖空
- 进程伪装
从高层抽象视角,进程包括:
Process Component | Purpose |
---|---|
私有虚拟地址空间 | 每个进程拥有独立的虚拟内存空间,与其他进程隔离,由操作系统映射到物理内存或磁盘 |
可执行程序 | 包含进程的代码(指令)和初始数据(如全局变量),存储在虚拟地址空间中 |
打开句柄(Open Handles) | 进程已打开的系统资源(如文件、网络连接、设备)的引用标识符。管理进程对共享资源的访问(如读写文件)。 |
安全上下文 | 包含访问令牌(Access Token),定义进程所属用户、安全组、权限(如管理员权限)。 |
进程ID(Process ID, PID) | 操作系统分配的唯一数字标识符,用于区分不同进程。 |
线程 | 进程内的执行单元,共享进程资源(如内存),但拥有独立的栈和寄存器。 |
从底层内存视角,进程包括:
Process Component | Purpose |
---|---|
代码段 | 存储可执行的机器指令 |
全局变量 | 存储程序中定义的全局/静态变量,生命周期与进程相同 |
进程堆 | 动态分配的内存区域(如通过 malloc() 或 new 申请的内存) |
进程资源 | 包括打开的文件、加载的动态库(DLL)、图形句柄等,支持进程运行时的外部依赖。 |
环境块 | 存储进程的环境变量(如 PATH)、启动参数等 |
高层组件是操作系统对进程的管理抽象(如PID、安全令牌),而底层组件是进程在内存中的实际存储结构(如代码段、堆)。
观察进程的相关工具:Process Hacker 2、 Process Explorer、Procmon。
在Windows操作系统中,进程的完整性级别(Integrity Level) 是安全上下文(Security Context) 的一部分,用于定义进程的信任级别和权限限制。它是 Windows 强制完整性控制(Mandatory Integrity Control, MIC) 机制的核心概念,用于防止低权限进程篡改高权限进程或资源。
如,Integrity:High
表示管理员权限进程的级别。
二、线程
线程是执行的基本单元,线程与其父进程共享相同的详细信息和资源,例如代码、全局变量等。线程还具有其独特的值和数据,如下表所示。
Process Component | Purpose |
---|---|
堆栈 | 所有与线程相关的特定数据(异常、过程调用等) |
线程本地存储 | 允许每个线程拥有独立的全局或静态变量副本,避免多线程竞争 |
堆栈参数 | 存储线程启动时传递的参数,通常通过栈或寄存器传递给线程入口函数 |
上下文结构 | 保存线程被挂起(如切换或中断)时的CPU状态 |
三、虚拟内存
虚拟内存(Virtual Memory)是Windows内核的核心组件之一,它负责管理进程的内存访问,确保不同应用程序和系统组件之间不会因直接操作物理内存而产生冲突。
虚拟内存的核心作用
-
提供进程隔离的虚拟地址空间
- 每个进程都认为自己独占 4GB(32位)或 256TB(64位) 的连续内存空间(称为 虚拟地址空间,VAS)。
- 实际物理内存由操作系统动态映射,进程无法直接访问,避免恶意程序篡改其他进程或内核数据。
-
内存映射与分页:虚拟内存通过分页机制管理物理内存。
- 内存按页(Page,通常4KB) 划分。
- 进程访问的虚拟地址由 内存管理单元(MMU) 转换为物理地址。
- 若物理内存不足,部分页面会被换出到 磁盘(Pagefile.sys),称为 分页(Paging) 或 交换(Swapping)。
-
权限控制
每块虚拟内存区域都有访问权限(如可读、可写、可执行),防止代码注入或越界访问。
notepad.exe
的基地址:
四、动态链接库
动态链接库(Dynamic Link Library),允许多个程序共享同一份代码和数据,其有助于代码复用、模块化开发、节省内存/磁盘空间。针对DLL的攻击有:
- DLL劫持(Hijacking):恶意 DLL 被优先加载(利用搜索路径缺陷)。
- DLL侧载(Side-Loading):替换合法程序的签名 DLL。
- DLL注入(Injection):强制目标进程加载恶意 DLL(如窃取数据)。
以下是Visual C++ Win32
动态链接库项目中一个DLL的示例:
// stdafx.h Visual Studio项目中自动生成的预编译头文件,用于加速编译过程
#include "stdafx.h"
// 标识当前正在编译DLL,EXPORTING_DLL只是一个编译开关,而不是dll的名字
// 区分 DLL 的编译和使用阶段,控制函数导出/导入
#define EXPORTING_DLL
// 确保函数声明一致,无论是编译DLL还是使用 DLL,HelloWorld()的声明必须相同,避免链接错误
#include "sampleDLL.h"// DllMain相当于main函数
BOOL APIENTRY DllMain( HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved
)
{return TRUE;
}void HelloWorld()
{MessageBox( NULL, TEXT("Hello World"), TEXT("In a DLL"), MB_OK);
}
EXPORTING_DLL
用于识别编译dll还是使用dll,一般编译的时候,需要导出dll;而使用的时候,需要导入dll。相关代码如下:
#ifndef INDLL_H// 头文件保护宏,防止重复包含#define INDLL_H#ifdef EXPORTING_DLL// extern:这是一个声明,避免重复定义错误;// __declspec(dllexport) :告诉编译器HelloWorld() 函数需要导出extern __declspec(dllexport) void HelloWorld();#elseextern __declspec(dllimport) void HelloWorld();#endif#endif
DLL使用:通过加载时动态链接或运行时动态链接加载到程序中。
加载时动态链接
使用加载时动态链接加载时,应用程序会显式调用 DLL 函数,只能通过提供头文件 (.h) 和导入库 (.lib) 文件来实现这种链接。以下是从应用程序调用导出的 DLL 函数的示例:
// 预编译头文件(用于 MSVC 优化编译)
#include "stdafx.h"
// 包含DLL的头文件,声明了HelloWorld()
#include "sampleDLL.h"
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{HelloWorld();return 0;
}
运行时动态链接
使用运行时动态链接加载时,会使用单独的函数(LoadLibrary
或 LoadLibraryEx
)在运行时加载 DLL,加载后,需要使用GetProcAddress
来识别要调用的导出 DLL 函数。以下是在应用程序中加载和导入 DLL 函数的示例:
...
typedef VOID (*DLLPROC) (LPTSTR);
...
HINSTANCE hinstDLL;
DLLPROC HelloWorld;
BOOL fFreeDLL;hinstDLL = LoadLibrary("sampleDLL.dll"); // 包含HelloWorld()函数的dll文件
if (hinstDLL != NULL)
{HelloWorld = (DLLPROC) GetProcAddress(hinstDLL, "HelloWorld");if (HelloWorld != NULL)(HelloWorld);fFreeDLL = FreeLibrary(hinstDLL);
}
...
在恶意代码中,威胁行为者通常更多地使用运行时动态链接,而不是加载时动态链接。因为恶意程序可能需要在内存区域之间传输文件,而传输单个 DLL 比使用其他文件导入方式更易于管理。
五、PE格式
Windows 的可执行文件(.exe
)、动态链接库(.dll
)、驱动程序(.sys
)等均采用 PE (Portable Executable) 格式。PE 定义了可执行文件的结构,使其能被 Windows 加载并运行。下面从结构组成和实际分析(以file.exe
为例) 进行详细解析。
DOS Header
表示文件类型,其中MZ
代表.exe
文件。
DOS Stub
是一个默认在文件开头运行的程序,用于打印兼容性消息,一般DOS Stub
会打印以下消息:This program cannot be run in DOS mode
。
PE文件头
是PE文件的核心元数据区域,定义了文件的全局属性和布局,包括PE签名、机器类型等。
Image Optional Header
尽管名为“可选”,但所有PE文件都必须包含此头,包含程序入口点、映像基址、数据目录等。
节表紧接在PE header
之后,定义了每个节的属性和位置。节存储实际内容,而节表描述如何解析它们。常见的节如下:
Section | Purpose |
---|---|
.text | 包含可执行代码和入口点 |
.data | 包含已初始化数据(字符串、变量等) |
.rdata 或 .idata | 包含导入(Windows API)和 DLL |
.reloc | 包含重定位信息 |
.rsrc | 包含应用程序资源(图像等) |
.debug | 包含调试信息 |
六、与windows内核交互
关于进程注入技术:
1、获取目标进程句柄 (OpenProcess)
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, // 请求所有权限(可能被安全软件拦截)FALSE, // 句柄不可继承DWORD(atoi(argv[1])) // 目标进程 PID(通过命令行参数传入)
);
2、在目标进程中分配内存 (VirtualAllocEx)
LPVOID remoteBuffer = VirtualAllocEx(hProcess, // 目标进程句柄NULL, // 由系统自动选择分配地址sizeof(payload), // 分配的内存大小(Payload 长度)(MEM_RESERVE | MEM_COMMIT), // 保留并提交物理内存PAGE_EXECUTE_READWRITE // 内存权限:可执行、可读写
);
3、将Payload写入目标内存 (WriteProcessMemory)
WriteProcessMemory(hProcess, // 目标进程句柄remoteBuffer, // 目标内存地址(由 VirtualAllocEx 返回)payload, // 本地 Payload 数据指针sizeof(payload), // Payload 大小NULL // 可选参数(返回实际写入字节数)
);
4、创建远程线程执行 Payload (CreateRemoteThread)
HANDLE remoteThread = CreateRemoteThread(hProcess, // 目标进程句柄NULL, // 默认安全属性0, // 默认栈大小(LPTHREAD_START_ROUTINE)remoteBuffer, // 线程起始地址(指向 Payload)NULL, // 无参数传递给线程0, // 线程立即运行NULL // 不返回线程 ID
);