在 Windows 操作系统中,要实现真正的文件保护,使其不被其他高权限程序(如管理员权限的程序或恶意软件)修改或删除,必须在内核态(Kernel Mode)进行。这是因为内核是操作系统的核心,拥有最高的权限,可以拦截和控制所有用户态(User Mode)的I/O操作。
Windows内核驱动文件保护的核心原理是利用**文件系统过滤驱动(File System Filter Driver)**技术。
Windows 内核文件保护原理
文件系统过滤驱动,顾名思义,就是位于文件系统和磁盘驱动器之间的一层软件。它的作用就像一个“守门员”,可以拦截所有对文件系统发起的I/O请求,并根据其自定义的规则来决定是放行、修改请求还是直接拒绝。
具体来说,一个文件保护驱动通常会执行以下步骤:
- 注册为过滤器:驱动程序通过 Windows 的 Filter Manager 框架,将自己注册为一个文件系统过滤驱动。
- 拦截 I/O 请求:一旦注册成功,操作系统会将所有经过文件系统的 I/O 请求包(IRP - I/O Request Packet)发送给该驱动。常见的请求类型包括:
IRP_MJ_CREATE
:创建或打开文件。
IRP_MJ_WRITE
:写入文件。
IRP_MJ_SET_INFORMATION
:设置文件信息,如修改文件名、删除文件等。
IRP_MJ_CLEANUP
:关闭文件句柄,如果文件被标记为删除,在此阶段会执行删除操作。
- 判断与决策:在拦截到请求后,驱动会根据其内部设定的保护策略进行判断。例如:
- 判断文件路径:检查请求操作的文件路径是否在保护列表中。
- 判断操作类型:检查请求是写入、删除还是修改。
- 判断发起进程:检查发起此操作的进程ID,以区分哪些是受信任的进程(如杀毒软件自身),哪些是非法的。
- 执行操作:根据判断结果,驱动会采取相应的行动:
- 放行:如果操作是合法的,驱动会将请求传递给下一个更底层的驱动。
- 拒绝:如果操作是不合法的,驱动会直接返回一个错误状态(如
STATUS_ACCESS_DENIED
),从而阻止该操作的发生。
- 修改请求:驱动甚至可以修改I/O请求的内容,例如在文件写入时进行加密,或者在读取时进行解密。
通过这种机制,一个内核驱动能够从根本上阻止任何用户态程序对受保护文件的非法操作,即使是管理员权限也无法绕过,因为驱动本身运行在更高的权限级别。
示例代码:一个简单的文件保护过滤器驱动
下面的代码是一个简化的文件系统**迷你过滤驱动(Minifilter Driver)**示例。该驱动旨在保护一个特定的文件不被删除。
注意:
- 这只是一个概念性代码,用于演示原理。一个完整的驱动程序需要处理更多的I/O请求、错误处理和内存管理。
- 编写和运行内核驱动程序是高风险行为,任何错误都可能导致系统崩溃(蓝屏)。
- 驱动需要进行数字签名才能在 Windows 系统上运行。
1. 驱动入口和卸载函数
DriverEntry
是驱动的入口点,负责注册过滤器和设置回调函数。DriverUnload
负责卸载和清理。
// 包含必要的头文件
#include <fltKernel.h>
// 全局变量
PFLT_FILTER g_FilterHandle = NULL;
UNICODE_STRING g_ProtectedFileName = RTL_CONSTANT_STRING(L"\\??\\C:\\protected_file.txt");
// 驱动卸载回调
VOID
FilterUnload(
_In_ FLT_FILTER_UNLOAD_FLAGS Flags
)
{
UNREFERENCED_PARAMETER(Flags);
FltUnregisterFilter(g_FilterHandle);
DbgPrint("File protector driver unloaded.\n");
}
// 驱动入口函数
NTSTATUS
DriverEntry(
_In_ PDRIVER_OBJECT DriverObject,
_In_ PUNICODE_STRING RegistryPath
)
{
NTSTATUS status;
// 注册过滤器
status = FltRegisterFilter(
DriverObject,
&g_Registration, // 这是下面定义的注册信息
&g_FilterHandle
);
if (NT_SUCCESS(status))
{
// 开始过滤
status = FltStartFiltering(g_FilterHandle);
if (!NT_SUCCESS(status))
{
FltUnregisterFilter(g_FilterHandle);
}
}
DbgPrint("File protector driver loaded.\n");
return status;
}
2. 回调函数
这是核心逻辑所在,用于在I/O操作发生前后进行处理。
// Pre-Operation Callback:在I/O操作开始前调用
FLT_PREOP_CALLBACK_STATUS
PreOperationCallback(
_Inout_ PFLT_CALLBACK_DATA Data,
_In_ PCFLT_RELATED_OBJECTS FltObjects,
_Flt_CompletionContext_Outptr_ PVOID *CompletionContext
)
{
UNREFERENCED_PARAMETER(FltObjects);
UNREFERENCED_PARAMETER(CompletionContext);
PFLT_FILE_NAME_INFORMATION nameInfo = NULL;
NTSTATUS status;
// 检查是否是删除操作
if (Data->Iopb->MajorFunction == IRP_MJ_CREATE)
{
// 检查请求是否包含删除意图
if (FlagOn(Data->Iopb->Parameters.Create.Options, FILE_DELETE_ON_CLOSE))
{
// 获取文件名信息
status = FltGetFileNameInformation(Data,
FLT_FILE_NAME_NORMALIZED |
FLT_FILE_NAME_QUERY_DEFAULT,
&nameInfo);
if (NT_SUCCESS(status) && nameInfo != NULL)
{
// 检查文件名是否匹配受保护文件
if (RtlCompareUnicodeString(&nameInfo->Name, &g_ProtectedFileName, TRUE) == 0)
{
DbgPrint("Blocking delete request for protected file.\n");
// 阻止删除操作,返回“拒绝访问”状态
Data->IoStatus.Status = STATUS_ACCESS_DENIED;
Data->IoStatus.Information = 0;
FltReleaseFileNameInformation(nameInfo);
return FLT_PREOP_COMPLETE; // 终止此I/O请求
}
FltReleaseFileNameInformation(nameInfo);
}
}
}
return FLT_PREOP_SUCCESS_WITH_CALLBACK;
}
3. 注册信息
在 DriverEntry
中,我们需要一个结构体来告诉 Filter Manager 如何调用我们的回调函数。
const FLT_OPERATION_REGISTRATION Callbacks[] =
{
// 在创建/打开文件操作前,调用我们的回调
{ IRP_MJ_CREATE, 0, PreOperationCallback, NULL },
// 这里可以添加更多回调,例如对写入或重命名进行拦截
{ IRP_MJ_OPERATION_END }
};
const FLT_REGISTRATION g_Registration =
{
sizeof(FLT_REGISTRATION),
FLT_REGISTRATION_VERSION,
0,
NULL,
Callbacks,
FilterUnload,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
};