Windows 系统架构包括以下组成部分:
内核:Windows 内核是操作系统的核心部分,它负责管理系统资源、处理用户程序和驱动程序的请求、协调各种系统组件之间的通信等任务。Windows 内核分为用户模式和内核模式,其中内核模式是更高级别、更安全的模式,用户程序无法直接访问内核模式。
驱动程序:Windows 系统需要很多不同类型的驱动程序来管理硬件设备和提供系统功能,如网络驱动程序、声卡驱动程序、显卡驱动程序等。这些驱动程序运行在内核模式下,可以访问系统的底层资源和硬件设备。
用户模式:用户模式是 Windows 操作系统中运行应用程序的模式。在用户模式下,应用程序可以访问一些系统资源,如文件系统、网络、进程和线程等,但不能直接访问内核模式或底层硬件设备。
Win32 子系统:Win32 子系统是 Windows 操作系统中的一种应用程序接口,它允许 32 位应用程序在 Windows 操作系统上运行。Win32 子系统提供了访问操作系统资源和功能的标准接口,使得应用程序可以在各种 Windows 系统上运行。
Windows API:Windows API 是一组应用程序接口,用于访问 Windows 操作系统的各种资源和功能。Windows API 可以在任何编程语言中使用,包括 C/C++、Java、Python 和 C# 等。
用户界面:Windows 操作系统的用户界面包括桌面、任务栏、窗口、菜单和对话框等组件。这些组件通过 Windows API 和用户模式实现,提供了用户与操作系统进行交互和操作的方式。
总体来说,Windows 系统架构是一个包括多个组件和模式的复杂系统,通过这些组件和模式协同工作,提供了一个稳定、安全、易用的操作系统环境。
这里将简单介绍 Windows 的基本系统架构。通过前面章节的内容,我们已经知道了 Windows 分为用户模式和内核模式两部分。简单示意图如下:
这里最底层可以看到一个“虚拟机监控程序”,它也是运行在内核模式下(RING-0),但是因为i使用了特殊的 CPU 指令(VT-x、SVM),所以可以将自己与内核隔离的同时继续监视内核(或应用程序)。因此我们经常会听到 Ring-1 这个称呼。
这里需要先简单介绍一下关于 Windows 操作系统下的四种基本的用户模式进程:
用户进程可能是下面某种类型之一:
(注意:16 位进程只能运行在 32 位的 Windows 上,Windows 8 开始不再支持 POSIX 应用程序)
服务进程承载了 Windows 服务,如 Task Scheduler 和 Print Spooler 服务。
通常来说,服务需要能在用户不登录的情况下运行,很多 Windows 服务器应用。如 Microsoft SQL Server 和 Microsoft Exchange Server 也包含了以服务方式运行的组件。
系统进程是指静态或硬编码的进程,例如非 Windows 服务的登录进程和会话管理器。也就是说,这些进程并非由服务控制管理器启动。
环境子系统服务进程是指那些实现了操作系统环境的支持部分的进程。
所谓“环境”是指呈现给用户和程序员的、操作系统中可进行个性化的部分。Windows NT 首发时提供了三个环境子系统:Windows、POSIX、OS/2。但是对 OS/2 子系统的支持到 Windows 2000 之后便已经停止,对 POSIX 的支持在 Windows XP 之后停止。Windows 7 旗舰版和企业版客户端以及服务器版本的 Windows 2008 R2 提供了一种名为 Subsystem for UNIX-based Applications(SUA)的增强型 POSIX 子系统。SUA 现在已经停止支持,不再作为可选功能包含在(客户端或服务器版本)Windows 中。
在 Windows 中,用户应用程序无法直接调用原生的 Windows 操作系统服务,而是需要通过一个或多个子系统动态链接库(DLL)调用。子系统 DLL 的作用在于将文档化的函数转换为相应的内部(通常未文档化)原生系统服务调用,这些调用通常是在 Ntdll.dll 中实现的。这种转换可能涉及,也可能不涉及将消息发送给用户进程提供服务的环境子系统进程。
Windows 内核模式组件如下:
前面已经提到了 Windows 的基础架构以及常见的进程类型,也捎带提到了关于 Windows 系统组件的内容。下图将这部分的系统组件结合起来可以直观看到 Windows 核心系统架构和组件。
从上图中可以看 Windows 操作系统环境中重要的几个系统组件分别是:
下面对上述内容分析进行详细的说明和介绍。
环境子系统的角色在于将基本 Windows 执行体系统服务的某些子集暴漏给应用程序。此类子系统可以访问 Windows 原生服务的不同子集。这意味着在一个子系统的基础上建立的应用程序所能执行的某个操作,可能无法被建立在另一个子系统的基础上的应用程序做到。例如,Windows 应用程序无法使用 SUA 的 fork 函数。
每个可执行映像(.exe)都会绑定到唯一的子系统。映像运行时,负责创建进程的代码会在映像头部检查子系统的类型代码,进而将新建进程告知给正确的子系统。该类型代码可以使用 Microsoft Visual Studio 的 linker 命令中的 /SUBSYSTEM linker 选项来制定(或使用工程属性中 Linker/System 属性页的 SubSystem 选项来指定)。
如前所述,用户应用程序不会直接调用 Windows 系统服务,而是要通过一个或多个子系统 DLL 来进行。这些库导出的接口都有相应的文档说明,链接到对应子系统的程序均可调用。例如,Windows 子系统 DLL (如 Kernel32.dll、Advapi32.dll、User32.dll 和 Gdi32.dll)实现了 Windows API 函数,SUA 子系统 DLL(Psxdll.dll)可实现 SUA API 函数。
这里,可以使用 Dependency Walker 工具(Dependency.exe)来查看映像文件的子系统类型。(关于这个工具可以查看这篇文章中有提到如何下载和使用,https://blog.csdn.net/qq_37596943/article/details/131515390?spm=1001.2014.3001.5501)
分别查看 Windows 操作系统自带的 Notepad.exe 和 CMD.exe。
从上面两张图可以看出,Notepad.exe 的依赖 DLL 和 CMD.exe 的依赖 DLL 完全不一样。并且 Notepad.exe 由于是 GUI 应用程序会依赖 GDI32.DLL,而 CMD.exe 就很简单只依赖最基础的 NTDLL.DLL 库。
这里说明一下当应用程序调用子系统 DLL 中的函数时会发生的情况:
某些函数会结合上述第二种和第三种结合使用,例如 Windows 的 CreateProcess 以及 ExitWindowsEx 函数。
子系统是由会话管理器(Smss.exe)进程启动的。子系统的启动信息存储在注册表 HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\SubSystems 键下。
图中的 Required 键值列出了系统引导时加载的子系统。该值包含两个字符串:Windows 和 Debug。
Windows 值包含了 Windows 子系统的文件规范,其中 Csrss.exe 代表了客户局端/服务器运行时子系统。
Debug 值为空(该值从 Windows XP 开始就不需要了,单位了维持兼容性依然保留),因此不会生效。
optional 值代表可选的子系统,在本例子中同样为空,因为 Windows 10 已经无法再使用 SUA。原本可用的使用,将通过 POSIX 的数值指向另一个值,进而指向 Psxss.exe(POSIX 子系统进程)。Optional 的值均为“按需加载”的,这意味着会在首次遇到 POSIX 映像的时候再开始加载。Kmode 注册表值包含 Windows 子系统内核模式部分的文件名(Win32K.sys)。
虽然 Windows 在设计的时候即可支持多种独立的环境子系统,但实际上让每个子系统实现所有的代码以处理窗口和显示 I/O 会产生大量重复的系统函数,最终对系统的体积和性能产生不利影响。由于 Windows 是最主要的子系统,Windows 的设计者决定将基本函数放在 Windows 子系统中,并让其他子系统调用 Windows 子系统以实现显示 I/O,因此 SUA 子系统会调用 Windows 子系统中的服务来实现显示 I/O。
在这种设计决策的影响下,Windows 子系统成了任何 Windows 系统的必须组件,甚至在不提供交互式用户登录的服务器系统上也是如此。因此这个进程也成为关键进程(如果该进程因为任何情况退出,那么系统将崩溃)。
Windows 子系统包含下列重要组件:
Windows 10 设备最基本的窗口管理要求高度依赖于具体的设备类型,运行 Windows 的完整桌面计算机需要具备所有的窗口管理能力,例如调整窗口大小、窗口所有者、子窗口等。而手机或小型平板电脑上运行的 Windows mobile 10 并不需要如此多的功能,因为只有一个前台窗口,无法最小化或调整大小。物联网设备也是如此,这类设备甚至可能根本无须显示画面。
因此 Win32K.sys 的功能被拆分到多个内核模块中,而某些系统可能并不需要所有这些模块。由于代码的复杂度降低,这样的做法大幅减少了窗口管理器的攻击面,并移除了很多遗留代码。
应用程序需要调用标准的 USER 函数来创建用户界面上的控件,如窗口和按钮,并显示在屏幕上。窗口管理器会将这些请求传递给 GDI,GDI 会将其传递给图形设备驱动程序,并由图形设备驱动程序结合显示设备进行必要的调整。显示器驱动程序会与视图微端口驱动程序成对使用,借此为完整的视频显示提供支持。
GDI 提供了一组标准的二维函数,可供应用程序在不了解与图形设备有关的任何信息的前提下与其通信。GDI 函数位于应用程序和诸如显示器驱动程序与打印机驱动程序等图形设备之间,负责解释应用程序的图像输出请求,并将请求发送给图形显示器驱动程序。GDI 还为使用不同图形输出设备的应用程序提供了标准化的接口。借助这些接口,应用程序代码可以与硬件设备及其驱动程序保持独立。GDI 还会针对设备的能力对消息进行裁剪,通常会将请求拆分为多个可管理的部件。例如,一些设备可以理解绘制椭圆的命令,一些设备则可能需要 GDI 将命令解释为“放置在某一个坐标位置的一系列像素点”。
由于子系统的大部分内容运行在内核模式下,因此只有少数 Windows 函数需要向 Windows 子系统进程发送进程和线程创建与终止、DOS 设备盘符映射等消息。
如前所述,Windows 最初可支持 POSIX 和 OS/2 子系统。由于新的 Windows 已不包含这些子系统,这里就不详细说明了。
Pico 提供程序和适用于 Linux 的 Windows 子系统
过去十多年来,传统子系统模型以足够的扩展性和能力为 POSIX 和 OS/2 提供了支持,然而该模型的两个技术局限使其难以适用于更广泛的非 Windows 二进制文件,只能用于一些非常具体的场景中。
为了解决 Windows 下针对 POSIX 的兼容性留下的一系列问题。现在 Windows 采用了新的方式来构建子系统,可以无须对其他环境的系统调用以及传统 PE 映像的执行进行传统的用户模式包装。
该模型定义了 Pico 提供程序这一概念。这是一种自定义的内核模式驱动程序,可通过 PsRegisterPicoProvider API 接收对专用内核接口的访问。这种专用接口会带来很多好处。
原生映像
使用 Dependency Walker 打开 Smss.exe (会话管理器进程),可以看到它仅依赖 Ntdll.dll ,并且这里显示它的子系统是 Native。
Windows 执行体是 Ntoskrnl.exe 的上层。(下层为内核)执行体包含下列类型的函数:
对于执行体的主要组件,这里简单列举一下:
此外,还包含多种其他基础架构例程:
内核由 Ntoskrnl.exe 中一系列用于提供基础机制的函数组成,例如被执行体使用的线程调度和同步服务,以及与底层硬件架构独立的支持。内核代码主要用 C 语言编写,不过对于需要使用特殊处理器指令和寄存器的任务,由于难以用 C 语言代码访问,因此继续使用了汇编代码。
很多内核函数均在 WDK 中提供了文档(搜索以 Ke 开头的函数即可),这是因为设备驱动程序的实现也需要它们。
内核提供了一种明确定义,可预知的操作系统底层原语和机制,可供执行体中的高层组件执行自己所需的操作。
在内核之外,执行体会将线程和其他共享的资源看做对象。
有一组名为控制对象的内核对象建立了控制各种操作系统函数所需的语义。这其中包括异步过程调用(APC)对象,延迟过程调用(Deferred Procedure Call,DPC)对象,以及 I/O 管理器使用的多种对象,如中断对象。
另一组名为分发器对象的内核对象所包含的同步能力可以改变或影响线程的调度。分发器对象包括内核线程、互斥体、时间、内核事件对、信号量、定时器以及可等待的定时器。执行体会使用内核函数创建并维护内核对象实例,构建更复杂的对象并提供给用户模式。
内核使用一种名为内核处理控制区(Kernel Processor Control Region,KPCR)的数据结构存储与处理器有关的数据。KPCR 包含一些基本信息,如处理器的中断分发表(interrupt Dispatch Table,IDT)、任务状态段(Task State Segment,TSS)以及全局描述符表(Global Descriptor Table,GDT)。
KPCR 还包含中断控制器状态,这是与其他模块共享的(ACPI、HAL)。
KPCR 还包含一种名为内核处理器控制块(Kernel Processor Control Block,KPRCB)的嵌入式数据结构。为了供第三方驱动程序和其他内部 Windows 内核组件使用,KPCR 是文档化的数据结构;而 KPCB 是一种专供 Ntoskrnl.exe 中内核代码使用的私有结构,它包含下列内容:
可以通过 windbg 利用 !prcb 查看 PRCB 的地址和数据信息
内核的另一个重要工作是将执行体和设备驱动程序从 Windows 支持的不同硬件体系架构的差异中抽象或抽离出来,这项工作也包括处理各种功能(例如中断处理、异常分发和处理器同步)之间的差异。
硬件抽象层(HAL)是实现可移植性的关键。HAL 是一种可加载的内核模式模块(Hal.dll),为运行 Windows 的硬件平台提供了底层接口。它可以将诸如 I/O 接口、中断控制器、多处理器通信机制等与特定硬件有关的细节以及与特定体系架构和计算机有关的功能隐藏起来。
WDK 中介绍了很多 HAL 例程,具体大家可以参考 WDK 文档。关于具体的模块有以下分别的描述:
HAL 文件名 | 支持的系统 |
---|---|
Halacpi.dll | 高级配置和电源接口(ACPI)计算机,暗指只有一颗处理器且不支持 APIC (中断控制器),两个条件有任意不满足的情况则使用下面的 HAL |
Halmacpi.dll | 支持 ACPI 的高级可编程中断控制器(APIC)计算机,使用 APIC 也意味着可支持 SMP |
这里,可以使用 Dependency Walker 工具来查看 Ntoskrnl.exe 的依赖。
设备驱动程序是一种可加载的内核模式模块(其文件通常使用 .sys 扩展),它在 I/O 管理器和相关硬件之间建立了接口。设备驱动程序在内核模式下,使用下列三种上下文之一来运行。
Windows 的设备驱动程序并不会直接操作硬件,而是会调用 HAL 中的函数与硬件进行交互。驱动程序通常使用 C 或者 C++ 编写,因此正确使用 HAL 例程可以在 Windows 支持的不同 CPU 体系架构之间进行源代码级别的移植,在同一体系架构族内则可以实现二进制移植。
设备驱动程序主要包含以下几种类型:
最初的 Windows 驱动程序是在第一版 NT 系统(3.1)创建的,但是由于即插即用(PnP)的原因(NT 驱动不支持 PnP),一直持续到 Windows 2000 发布支持了 PnP 驱动,通过 WDM 的驱动模型(Windows driver model,WDM)支持了 PnP 和电源选项的支持。一直到现在,WDM 经过多次更新依然被保留下来,并成为了 Windows 2000 和后续版本系统编写硬件驱动程序的基本模型。
WDM 驱动模型可以分为三种类型:
在 WDM 驱动程序环境中,无法由单一驱动程序控制某个设备的方方面面。总线驱动程序负责向 PnP 管理器报告总线上连接的设备,功能驱动程序则负责操作设备。
Windows 驱动程序基础(Windows driver foundation,WDF)提供了内核模式驱动程序框架(kernel-mode driver framework,KMDF)和用户模式驱动程序框架(user-mode driver framework,UMDF)。这两种框架简化了 Windows 驱动程序的开发工作。
KMDF 提供了一种更简化的 WDM 接口,并在不改动底层总线、总能、筛选器模型的前提下隐藏了驱动开发过程中的复杂性。KMDF 驱动程序能够响应自己可以注册的事情,并调用 KMDF 库来执行非自己所管理设备特定的各种工作,例如常规的电源管理和同步。某些情况下,单一的 KMDF 函数调用即可取代原本大量的 WDM 代码实现的操作。
UMDF 使得某些类型的设备(大部分为 USB 设备或其他高延迟协议总线,例如视频摄像头、MP3 播放器、手机、打印机等)能够实现用户模式的驱动。从本质上看,UMDF 会将每个用户模式的驱动程序作为用户模式的服务来运行,并使用 ALPC 与内核模式下运行的包装(wrapper)驱动程序通信,借此实现对硬件的实际访问。如果 UMDF 驱动程序崩溃,进程会“死掉”并且通常会重新启动。这样就不会导致系统变得不翁当,唯一的代价仅仅是在服务宿主重新启动驱动程序的过程中,这个设备暂时不可用。
从 Windows 10 开始,可以借助通用 Windows 驱动程序(universal Windows driver,UWD),使用 Windows 10 通用内核提供的共享 API 和设备驱动程序接口(device driver interface,DDI)实现驱动程序的“一次编写,处处执行”。这类驱动程序可以面向特定 CPU 架构实现二进制兼容,可以用于包括物联网设备、手机、HoloLens、Xbox one、笔记本电脑登不同形态的设备。通用 Windows 驱动程序可以使用 KMDF、UMDF 2.x 或者 WDM 作为自己的驱动程序模型。
可以通过 Msinfo32.exe 查看本机已安装的设备驱动程序。
从图中可以看出,我们通过这里能够找到当前已经安装的设备驱动程序,并且还能显示出驱动程序对应的驱动文件的位置及当前服务的状态。
当我们手动启动该服务后,也能看到它现在处于运行状态了。
使用 process explorer 工具也可以通过 system 进程查看当前加载的设备驱动程序。
每一个 Windows 10 系统都会包含一些特殊的系统进程。它们并没有运行用户模式的可执行文件,这类进程称为最小进程。(例如:Idle、system、secure system、memory compression)
可以通过打开 Process explorer 工具查看进程树来了解上述这些进程都是谁创建的,有助于了解每个进程的来源。
Windows 执行体组件常用的大部分函数的名称前缀都是有一定的含义。例如前缀的第一个字母后跟 i(代表内部,internal),完整前缀后跟字母 p (代表私有,private)。常见如下:
前缀 | 组件 |
---|---|
Alpc | 高级本地过程调用,advanced local procedure call |
Cc | 公用缓存,common cache |
Cm | 配置管理器,configuration manager |
Dbg | 内核调试支持,kernel debug support |
Dbgk | 用户模式调试框架,debugging framework for user mode |
Em | 勘误管理器,errata manager |
Etw | Windows 事件跟踪,event tracing for Windows |
Ex | 执行体支持例程,executive support routine |
FsRtl | 文件系统运行库,file system runtime library |
Hv | 配置单元库,hive library |
Hvl | 虚拟机监控程序库,hypervisor library |
Io | I/O 管理器,I/O manager |
Kd | 内核模式调试器,kernel debugger |
Ke | 内核,kernel |
Kse | 内核填充码引擎,kernel shim engine |
Lsa | 本地安全机构,local security authority |
Mm | 内存管理器,memory manager |
Nt | NT 系统服务,NT system service,可在用户模式下通过系统调用访问 |
Ob | 对象管理器,object manager |
Pf | 预读取器,Prefetchr |
Po | 电源管理器,Power manager |
PoFx | 电源框架,power framework |
Pp | PnP 管理器,PnP manager |
Ppm | 处理器电源管理器,processor power manager |
Ps | 进程支持,process support |
Rtl | 运行时库,Run-time library |
Se | 安全引用监视器,security reference monitor |
Sm | 存储管理器,store manager |
Tm | 事务管理器,transaction manager |
Ttm | 终端超时管理器,terminal timeout manager |
Vf | 驱动程序验证器,driver verifier |
Vsl | 虚拟安全模式库,virtual secure mode library |
Wdi | Windows 诊断基础架构,Windows diagnostic infrastructure |
Wfp | Windows 指纹,Windows finger print |
Whea | Windows 硬件错误架构,Windows hardware error architecture |
Wmi | Windows management instrumentation |
Zw | 系统服务入口点镜像,可将之前访问模式设置为内核模式。 |
通常,Windows 例程的命名规范如下格式:
<Prefix><Operation><Object>