Windows软件调试初探-架构和系统部件

系统概览

在内核空间中:

硬件抽象层HAL隔离CPU架构层面核心硬件的硬件差异性,使内核和顶层模块可使同一的方式访问硬件,不负责的外设设备硬件通过I/O管理器加载不同设备驱动程序解决。执行体如内存管理器、进程管理器、I/O管理器。Windows子系统驱动程序Win32K.sys包括USER和GDI两部分,USER负责窗口管理、用户输入等,GDI负责显示输出和各种图形操作,还有DxgKrnl.sys图形核心负责管理GPU 有关核心任务。内核支持模块有用于内核调试的KDCOM.DLL,用于启动截断显示驱动的BOOTVID.DLL,用于检查模块完好性的CI.DLL,用于支持日志功能的CLFS.SYS,支持WHEA的PSHED.DLL,管理流媒体的KS.sys,网络套接字WinSock的内核空间接口驱动AFD.sys,管理网卡驱动的NDIS.sys,管理网络过滤驱动的Windows过滤平台清凉过滤器驱动程序Wfplw.sys,支持ACPI标准的ACPI.sys,PCI总线的PCI.sys,NTFS文件系统的实现NTFS.SYS。

在用户空间中:

会话管理器进程SMSS.EXE是系统中第一个根据映像文件创建的进程,它加载初始化Win32K.SYS,创建CSRSS.EXE和WinLogon.EXE。Windows客户端/服务端运行时子系统服务器进程CSRSS.EXE为Windows子系统各个进程提供服务如登记进程和线程、管理控制台窗口、管理DOS程序虚拟机VDM等。登录进程WinLogon.EXE负责用户登录和安全相关,它创建LSASS进程和Services.EXE进程,实现sfc.dll和sfc_os.dll的文件保护功能。本地安全和认证进程LSASS.EXE负责用户身份验证。服务管理进程Services.EXE负责启动和管理系统服务程序。外壳程序Shell默认为Explorer.exe。

内核和HAL模块

所谓内核文件为NTOSKRNL.EXE,32位且支持物理地址扩展PAE的改为NTKRNLPA.EXE,对于x64架构的只有前者,后者用不上就没了。Windows系统启动时由系统加载程序NTLDR或WinLoad根据启动选项是否启用PAE加载其中一个。

HAL文件有多个版本,系统中不一定是哪个,如下表。注意原始文件名指的是安装文件复制出的文件名,后期都将改为hal.dll,文件右键属性详细信息中能够看到原始文件名。

原始文件名 适用平台
hal.dll 标准平台
halacpi.dll 符合ACPI的标准硬件平台
halapic.dll 支持高级可编程中断控制器APIC的硬件平台
halaacpi.dll 同时支持ACPI 和APIC的硬件平台
halmps.dll 系统中有一个多处理器
halmacpi.dll ACPI的多处理器系统

函数nt!KzLowerIrql直接操作APIC任务优先级寄存器TPR的别名CR8寄存器,用于降低中断请求级别IRQL。

!process不显示空闲进程,内核调试中先用!prcb显示处理器控制块。Threads行的Current字段为当前CPU 正执行线程的ETHREAD结构,Next为等待执行的线程,Idle为当前CPU的空闲线程ETHREAD结构地址,用!thread显示线程详细信息。Cid字段为0表示空闲进程,系统进程为4,Owning Process为空闲进程EPROCESS,Image映像名Idle是瞎扯的,UserTime为0表示只在内核模式执行,KernelTime为在内核模式执行时间。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
1: kd> !prcb
PRCB for Processor 1 at ffffd7014b867180:
Current IRQL -- 13
Threads-- Current ffffd7014b872140 Next ffffe78b104c7080 Idle ffffd7014b872140
Processor Index 1 Number (0, 1) GroupSetMember 2
Interrupt Count -- 00027b13
Times -- Dpc 00000025 Interrupt 00000006
Kernel 00007083 User 00000063
1: kd> !thread ffffd7014b872140
THREAD ffffd7014b872140 Cid 0000.0000 Teb: 0000000000000000 Win32Thread: 0000000000000000 STANDBY
Not impersonating
DeviceMap ffff800d6f247840
Owning Process fffff80753b24a00 Image: Idle
Attached Process ffffe78b0b076040 Image: System
Wait Start TickCount 0 Ticks: 28939 (0:00:07:32.171)
Context Switch Count 50535 IdealProcessor: 1
UserTime 00:00:00.000
KernelTime 00:07:17.718
Win32 Start Address nt!KiIdleLoop (0xfffff807531f8ce0)
Stack Init ffffa18350229c90 Current ffffa18350229c20
Base ffffa1835022a000 Limit ffffa18350224000 Call 0000000000000000
Priority 0 BasePriority 0 PriorityDecrement 0 IoPriority 0 PagePriority 0
Child-SP RetAddr : Args to Child : Call Site
ffffa183`50229c60 00000000`00000000 : ffffa183`5022a000 ffffa183`50224000 00000000`00000000 00000000`00000000 : nt!KiIdleLoop+0x54

例如查看该空闲进程回溯栈:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
1: kd> .process /r /p fffff80753b24a00
Implicit process is now fffff807`53b24a00
.cache forcedecodeuser done
Loading User Symbols
1: kd> knc
# Child-SP RetAddr Call Site
00 ffffd701`4b8bfde8 fffff807`53293972 nt!DbgBreakPointWithStatus
01 ffffd701`4b8bfdf0 fffff807`53287427 nt!KdCheckForDebugBreak+0x112216
02 ffffd701`4b8bfe20 fffff807`53126853 nt!KeAccumulateTicks+0x15e377
03 ffffd701`4b8bfe80 fffff807`5312633a nt!KeClockInterruptNotify+0x453
04 ffffd701`4b8bff30 fffff807`5302ecd5 nt!HalpTimerClockIpiRoutine+0x1a
05 ffffd701`4b8bff60 fffff807`531f6cba nt!KiCallInterruptServiceRoutine+0xa5
06 ffffd701`4b8bffb0 fffff807`531f7227 nt!KiInterruptSubDispatchNoLockNoEtw+0xfa
07 ffffa183`50229510 fffff807`531f100f nt!KiInterruptDispatchNoLockNoEtw+0x37
08 ffffa183`502296a8 fffff807`5318d02e nt!HalProcessorIdle+0xf
09 ffffa183`502296b0 fffff807`53128246 nt!PpmIdleGuestExecute+0xe
0a ffffa183`502296f0 fffff807`53127004 nt!PpmIdleExecuteTransition+0x10c6
0b ffffa183`50229af0 fffff807`531f8d34 nt!PoIdle+0x374

nt!PoIdle是电源执行体Power为空闲线程设计的工作函数,一般调用CPU处理器电源管理PPM模块进入省电模式,其下面的nt!KiIdleLoop为空闲线程入口和主函数。PPM模块导出一系列函数供nt!PpmIdleExecuteTransition调用。CPU在空闲线程中执行时可能用nt!KiRetireDpcList执行挂在延迟过程调用DPC队伍里的任务。

系统进程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
1: kd> !process 4 1
Searching for Process with Cid == 4
PROCESS ffffe78b0b076040
SessionId: none Cid: 0004 Peb: 00000000 ParentCid: 0000
DirBase: 001ad000 ObjectTable: ffff800d6f242e00 HandleCount: 1386.
Image: System
VadRoot ffffe78b0b067ce0 Vads 5 Clone 0 Private 22. Modified 4107. Locked 0.
DeviceMap ffff800d6f247840
Token ffff800d6f2557e0
ElapsedTime 00:12:28.514 //约等于系统总运行时间
UserTime 00:00:00.000
KernelTime 00:00:02.875
QuotaPoolUsage[PagedPool] 0
QuotaPoolUsage[NonPagedPool] 272
Working Set Sizes (now,min,max) (53, 50, 450) (212KB, 200KB, 1800KB)
PeakWorkingSetSize 219
VirtualSize 3 Mb
PeakVirtualSize 13 Mb
PageFaultCount 1734
MemoryPriority BACKGROUND
BasePriority 8
CommitCharge 40

一些常见的高CPU时间的系统线程有:管理GPU内存的VidMM线程,工作函数dxgmms2!VidMmWorkerThreadProc;调度GPU任务的VidSch线程,工作函数dxgmms2!VidSchiWorkerThread,其内调用dxgmms2!VidsSchiRun_PriorityTable;内存管理器工作集平衡线程,工作函数KeBalanceSetManager扫描每个进程页表,必要时把暂时不用的内存也交换到虚拟内存;内存管理器清零线程,工作函数ZeroPageThread,把需要的内存页清零。

NTDLL.DLL

映像加载器LDR是NTDLL.DLL的一部分,其负责在进程空间创建,即形成初始线程后,用异步过程调用APC机制让新线程在用户空间运行。Ldr前缀函数为接口函数,Ldrp前缀函数为内部函数。其中ntdll!LdrInitializeThunk是CPU从内核空间切换到用户空间的着陆点,ntdll!LdrpInitializeProcess是执行进程初始化的核心函数。

1
2
3
4
5
6
7
0:000> k
# Child-SP RetAddr Call Site
00 00000000`0063f370 00007ffd`9ec86351 ntdll!LdrpDoDebuggerBreak+0x30
01 00000000`0063f3b0 00007ffd`9ec79b9e ntdll!LdrpInitializeProcess+0x1cf9
02 00000000`0063f780 00007ffd`9ec23f33 ntdll!_LdrpInitialize+0x55c32
03 00000000`0063f800 00007ffd`9ec23e5e ntdll!LdrpInitializeInternal+0x6b
04 00000000`0063fa80 00000000`00000000 ntdll!LdrInitializeThunk+0xe

Rtl开头的是运行时库函数,这类函数NTDLL.DLL中数量最多,达2000多个。

环境子系统

POSIX和OS/2子系统已过时,Windows子系统在用户模式下主要结构有:

重要文件 描述
CSRSS.EXE Windows子系统服务进程主程序
ADVAPI32.DLL 包含API:数据加密Crpt、用户账号管理Lsa 、注册表操作Reg、WMI、终端服务Wts
GDI32.DLL 包含图形文字绘制API入口,TextOutBitBlt
KERNEL32.DLL 包含API:进程线程管理如CreateThread、调试Debug、文件操作、内存分配Local或Global
USER32.DLL 包含窗口管理、消息处理、用户输入API,EndDialogBeginPaintSetWindowPosMessageBox

原生进程

不依赖任何子系统,通过特殊私有结构直接与内核交互的进程叫原生进程,常见的有磁盘检查程序autochk.exe、SMSS、CSRSS等,他们是标准的PE格式,但头信息Sybsystem字段为0001表示IMAGE_SUBSYSTEM_NATIVE。下面是个最简单原生进程的例子:

1
2
3
4
5
VOID NTAPI NTProcessStartup(PSTARTUP_ARGUMENT pArgument){
LONG nResult=0;
NtDisplayString("Hello Native Process\n");
NtTerminateProcess(NtCurrentProcess(),nResult);
};

对于SMSS进程的创建过程中nt!Phase1Initialization代表内核启动过程中执行体阶段1初始化,之后nt!StartFirstUserProcess启动第一个用户进程,即SMSS是NT启动过程第一个EXE方式创建的进程,最后nt!MmCreateProcessAddressSpace创建进程地址空间。对于SMSS创建磁盘检查进程autochk.exe时,最开始smss!NtProcessStartupW为SMSS入口,然后smss!SmpLoadDataFromRegistry读取注册表,其次用smss!SmpExecuteCommand执行HKLM\System\CurrentControlSet\Control\Session Manager\BootExecute表键指定的程序。

系统启动后,SMSS内部运行两个线程,一个主线程用NtWaitForMultipleObjects等待CSRSS和WinLogon,这俩意外退出时触发蓝屏;另一个线程用NtWaitForWorkViaWorkerFactory等待登录会话有关任务。