Windows内核学习笔记(六)
常见的分发器对象
事件(event):用于线程之间的通信和同步,它有俩种状态:已触发和未触发。事件可以用于线程等待某个事件发生,或者通知其他线程事件的发生。
互斥量(Mutex)(或互斥锁):互斥量用于确保在同一时刻只有一个线程能够访问共享资源。它允许线程请求锁定,当一个线程获得锁定时,其他线程必须等待,直到锁被释放。
信号量(Semaphore):用于控制对共享资源的并发访问。它维护一个计数器,允许指定数量的线程同时访问资源。
条件变量(Condition Variable):条件变量用于线程之间传递信号和等待特定条件的发生。通常与互斥量一起使用,用于等待某个条件满足后执行操作。
读写锁(Read-Write Locks):读写锁用于控制对共享资源的读和写,它允许多个线程同时读取资源,但只允许一个线程写入资源。
计数器对象(Counting Semaphore):一种特殊的信号量,它可以增加和减少计数器的值,通常用于跟踪资源的可用性或完成的任务数量。
关于互斥锁和自旋锁:互斥锁基于线程调度,而自旋锁不依赖线程调度;还有一个区别是线程尝试获取锁时,如果该锁被占用,线程是被挂起还是一直自旋;互斥锁适用于长时间的临界区和资源竞争激烈的情况,而自旋锁适用于非常短暂的临界区。
Windows在上述基础上,实现了同步语义更为丰富的一些同步机制,包括:快速互斥体(fast mutex)、守护互斥体(guarded mutex)、执行体资源(executive resource)和推锁(push lock)。
一听又是历史,见过fastcall的都知道。
SSDT
系统服务描述表(System Services Descriptor Table,SSDT)其在内核中的实际名称是 “KeServiceDescriptorTable”。
它用于处理应用层通过kernel32.dll下发的各个API操作请求。ntdll.dll中的API是一个简单的包装函数,当kernel32.dll中的API通过ntdll.dll时,会先完成对参数的检查,再调用一个中断(int 2Eh或者SysEnter指令),从而实现从R3层进入R0层,并将要调用的服务号(也就是SSDT数组中的索引号index值)存放到寄存器EAX中,最后根据存在EAX中的索引值在SSDT数组中调用指定的服务(Nt*系列函数)。
这个好玩。
我在用户态的程序需要调用我自己的驱动程序服务,是用这种方式吗
如果你在用户态的程序中需要调用自己编写的驱动程序服务,通常不会直接通过SSDT来实现。SSDT主要用于调用内核提供的标准服务(Nt*系列函数),而不是直接调用自定义驱动程序的服务。相反,你需要通过一种机制来与你的驱动程序进行通信,常见的方法是使用设备驱动接口(DDI)和设备控制操作(IOCTL)。以下是详细的步骤和原理:
- 设备驱动接口(DDI)
设备驱动接口(DDI)是Windows内核提供的一组函数和接口,用于用户态程序与驱动程序之间的通信。用户态程序可以通过这些接口与驱动程序进行交互。 - 设备控制操作(IOCTL)
IOCTL(Input/Output Control)是一种设备控制操作,允许用户态程序向驱动程序发送自定义的命令和数据。这是用户态程序与驱动程序通信的常用方式。
HANDLE hDevice = CreateFile(
L"\\\\.\\MyDriver", // 驱动程序的设备名
GENERIC_READ | GENERIC_WRITE, // 访问权限
0, // 不共享
NULL, // 安全属性
OPEN_EXISTING, // 打开已存在的设备
0, // 不使用文件属性
NULL // 不使用模板
);
if (hDevice == INVALID_HANDLE_VALUE)
{
// 错误处理
}
DWORD bytesReturned;
BOOL success = DeviceIoControl(
hDevice, // 设备句柄
IOCTL_MY_DRIVER_CUSTOM_COMMAND, // 自定义的IOCTL代码
&inputBuffer, // 输入缓冲区(可选)
sizeof(inputBuffer), // 输入缓冲区大小
&outputBuffer, // 输出缓冲区(可选)
sizeof(outputBuffer), // 输出缓冲区大小
&bytesReturned, // 返回的数据大小
NULL // 不使用重叠I/O
);
if (!success)
{
// 错误处理
}
定义设备对象:
在驱动程序中,定义一个设备对象,并在驱动程序加载时创建该设备对象。设备对象的名称需要与用户态程序中使用的名称一致。
示例代码(驱动程序):
NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
{
UNICODE_STRING deviceName = RTL_CONSTANT_STRING(L"\\Device\\MyDriver");
UNICODE_STRING symbolicLinkName = RTL_CONSTANT_STRING(L"\\DosDevices\\MyDriver");
// 创建设备对象
IoCreateDevice(DriverObject, 0, &deviceName, FILE_DEVICE_UNKNOWN, 0, FALSE, &deviceObject);
// 创建符号链接
IoCreateSymbolicLink(&symbolicLinkName, &deviceName);
// 设置设备对象的派遣函数
DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = MyDeviceControl;
return STATUS_SUCCESS;
}
处理IOCTL命令:
在驱动程序中,实现一个派遣函数(例如MyDeviceControl),用于处理用户态程序发送的IOCTL命令。
示例代码(驱动程序):
NTSTATUS MyDeviceControl(PDEVICE_OBJECT DeviceObject, PIRP Irp)
{
PIO_STACK_LOCATION irpStack = IoGetCurrentIrpStackLocation(Irp);
ULONG ioctlCode = irpStack->Parameters.DeviceIoControl.IoControlCode;
switch (ioctlCode)
{
case IOCTL_MY_DRIVER_CUSTOM_COMMAND:
// 处理自定义命令
break;
default:
return STATUS_INVALID_DEVICE_REQUEST;
}
// 完成IRP请求
Irp->IoStatus.Status = STATUS_SUCCESS;
Irp->IoStatus.Information = 0;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
居然不一样,看来没办法融会贯通。
也是,驱动和系统,驱动依附于系统。
SSDT表的基地址和SSDT函数的索引号(index),从而求出对应的服务函数地址。在x86平台上,它们之间满足如下规则:
FuncAddr = KeServiceDescriptorTable + 4 * index
与x86平台上直接在SSDT中存放SSDT函数地址不同,在x64平台上,SSDT中存放的是索引号锁对象的SSDT函数地址和SSDT表基地址的偏移量左移4位的值,因而计算公式变为:
FuncAddr = ([KeServiceDescriptorTable + index * 4] >> 4 + KeServiceDescriptorTable)
SSDT表存储在哪里,修改是对全部进程生效吗
SSDT 表存储在哪里
SSDT(系统服务描述符表)存储在内核空间中,具体位置可以通过以下方式获取:
通过内核导出符号访问:
SSDT 表的基地址可以通过内核导出符号 KeServiceDescriptorTable 获取。在 Windows 系统中,KeServiceDescriptorTable 是一个导出的全局变量,存储了 SSDT 的相关信息。
使用 Windbg 工具
可以通过命令 dd nt!KeServiceDescriptorTable 查看 SSDT 表的内容。
通过内存扫描:
在某些情况下,也可以通过扫描内存中的特定特征码来找到 SSDT 表的基地址。
SSDT Hook 修改是否对全部进程生效
SSDT Hook 是在内核层面进行的修改,因此对系统中的所有进程都生效。当 SSDT 表中的某个系统服务被 Hook 后,无论哪个进程调用该系统服务,都会被重定向到自定义的 Hook 函数。
例如,通过 Hook NtTerminateProcess 函数,可以实现对所有进程的终止操作进行拦截,从而保护特定进程不被结束。
PEB
操作系统会为每个进程设置一个数据结构,用来记录进程的相关信息。该结构就是PEB(Process Environment Block,进程环境块),PEB存在于用户地址空间中,记录了进程的相关信息。
其中,BeingDebugged成员用于指定该进程是否处于被调试状态,该值为0时进程未处于调试状态,若该值为非零值,则进程处于调试状态。(可以使用Windows API,如IsDebuggerPresent、CheckRemoteDebuggerPresent函数来访问该成员)
Ldr字段也是一个很重要的成员,该字段指向的结构记录了进程加载进内存的所有模块的基地址,通过Ldr指向的三个链表就可以找到kernel32.dll的基地址。
TEB
TEB(Thread Environment Block,线程环境块) 同样位于应用层之中。它包含了系统频繁使用的一些与线程相关的数据,进程中的每个线程都有一个自己的TEB。一个进程的所有TEB都存放在从0x7FFDE000开始的线性内存中,每4KB为一个完整的TEB。
在NT中,FS:[0]的地址指向了TEB结构,这个结构的开头是一个NT_TIB结构
可以通过NtCurrentTeb函数调用和FS段寄存器俩种方法来访问TEB结构
注册表
这个是windows在文件太过零散时创造的一个统一数据库。
没什么特别的,经常和它打交道。
只不过需要注意的是它和文件最大的区别还在于它里面存储的那些windows相关信息。
当我知道任务计划程序的配置也是存储在注册表里面的时候,我非常震惊。
但是想到这是windows的数据库的时候,也是释怀了。
因此,如果说要开机自启什么东西,注册表基本是绕不过去的一环。
驱动、服务、计划任务、任务表自带的开机自启、资源管理器带的开机自启
基本都绕不开注册表。
Windows服务的注册表项目主要存储在以下位置:
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services:这是系统服务安装信息的主要存储位置。每个服务都会在这个路径下以服务的名称(ServiceName)创建一个子项,其中包含了服务的相关信息,如“DisplayName”(服务名称)、“Description”(服务描述)、“ImagePath”(服务程序所在的路径)、“ObjectName”(服务运行时使用的用户账户)、“ErrorControl”(错误控制选项)、“Start”(服务启动类型,如自动、手动或禁止)和“Type”(服务类型)等。
HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Services:此注册表项下也存储了系统服务的安装信息,可以用来获取服务的详细信息。
需要注意的是,修改注册表时要格外小心,因为不当的修改可能会导致系统不稳定或无法启动。在进行修改之前,建议先备份注册表。
Windows驱动程序的相关信息主要存储在注册表的以下位置:
服务键(Service Key)
服务键用于指定驱动程序(.sys 文件)的位置以及控制驱动程序加载的一些参数。这些键存储在注册表的以下路径中:
路径:HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services
内容:每个驱动程序都会在该路径下以驱动程序的服务名称(通常是.sys文件的名称)创建一个子项。子项中包含以下重要信息:
ImagePath:驱动程序文件的路径。
Start:驱动程序的启动类型(如自动、手动或禁用)。
Type:驱动程序的类型(如内核模式驱动程序)。
ErrorControl:错误控制选项,指定如果驱动程序加载失败时系统的响应方式。
硬件键(Hardware Key)
硬件键包含单个设备的信息,通常用于描述硬件设备及其驱动程序的实例。
路径:HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Enum\总线\硬件ID
内容:每个硬件设备都会在该路径下创建一个子项,子项中包含设备的硬件ID、实例ID等信息。例如,一个USB设备的路径可能是:
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Enum\USB\VID_1234&PID_5678\1234567890
其中,USB是总线类型,VID_1234&PID_5678是硬件ID,1234567890是设备的实例ID。
类键(Class Key)
类键包含所有相同类型设备的共同信息。
路径:HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Class\GUID
内容:每个设备类(如所有USB设备或所有显卡设备)都会在该路径下创建一个子项,子项中包含该类设备的共同信息,如设备类的GUID(全局唯一标识符)。
参数键(Parameters Key)
参数键用于存储驱动程序的配置信息。
路径:对于内核模式驱动程序,参数键位于HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\服务名\Parameters。
内容:驱动程序的配置信息,如设备的特定设置或驱动程序运行时的参数。
注意事项
修改注册表的风险:修改注册表时需要格外小心,因为不当的修改可能会导致系统不稳定或无法启动。在修改之前,建议备份注册表。
访问权限:某些注册表项(如硬件键)的访问权限非常严格,通常只有系统账号和内核模式程序可以访问。
最后
终于告一段落了。
在阅读这篇文章的时候,我就逐渐意识到这是在走马观花。
但,走马观花也算是有一些收获,或许收获不多。
也应该足够我应付入门了,至于入门以后,再等待就好。
只有真正的实操,才能领悟。不过可见的是,第一次实操内核相关,肯定会无比困难。

浙公网安备 33010602011771号