diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 2ad811c..672c19a 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -9,7 +9,8 @@ "Bash(python -c:*)", "WebSearch", "Bash(python:*)", - "Bash(du:*)" + "Bash(du:*)", + "Bash(git -C \"D:\\\\Users\\\\21405\\\\source\\\\repos\\\\Filerestore_CLI\" log --oneline -3 master)" ] } } diff --git a/.gitignore b/.gitignore index 707fb5e..9b92e92 100644 --- a/.gitignore +++ b/.gitignore @@ -33,10 +33,8 @@ x86/ *.VC.db-wal *.VC.opendb -# Build directories -# (已统一输出到解决方案根目录的 x64/) -x64/ - +#development notes +dev_notes/ # NuGet packages (can be restored via nuget restore) packages/ *.nupkg @@ -111,5 +109,6 @@ Filerestore_CLI/deps/ftxui/build/ Filerestore_CLI/deps/ftxui/.cache/ Filerestore_CLI/deps/ftxui/cmake-build-*/ -# Developer notes (private, not tracked) -dev_notes/ \ No newline at end of file +# Kernel driver build outputs +*.sys +*.cer \ No newline at end of file diff --git a/Filerestore_CLI/src/fileRestore/MonitorDaemon.cpp b/Filerestore_CLI/src/fileRestore/MonitorDaemon.cpp index 8b12a28..dcf1674 100644 --- a/Filerestore_CLI/src/fileRestore/MonitorDaemon.cpp +++ b/Filerestore_CLI/src/fileRestore/MonitorDaemon.cpp @@ -233,6 +233,7 @@ void MonitorDaemon::DetachSharedMemory() { bool MonitorDaemon::ReadState(MonitorSharedState& out) { if (!sharedPtr_) return false; if (sharedPtr_->magic != MONITOR_SHARED_MAGIC) return false; + if (sharedPtr_->version != MONITOR_SHARED_VERSION) return false; // seqlock 读端:重试直到拿到一致快照 for (int retry = 0; retry < 100; retry++) { @@ -258,6 +259,9 @@ bool MonitorDaemon::ReadState(MonitorSharedState& out) { } // 超过重试上限(不应发生),仍返回最后一次拷贝 memcpy(&out, (const void*)sharedPtr_, sizeof(MonitorSharedState)); + SpinLockRelease(&sharedPtr_->spinLock); + + // 拷贝完成后释放锁,out 是本地副本,后续操作无需锁 return true; } diff --git a/Filerestore_sys/Filerestore_sys/Filerestore_sys.inf b/Filerestore_sys/Filerestore_sys/Filerestore_sys.inf new file mode 100644 index 0000000..996f5d9 --- /dev/null +++ b/Filerestore_sys/Filerestore_sys/Filerestore_sys.inf @@ -0,0 +1,84 @@ +; +; Filerestore_sys.inf - Minifilter driver installation +; + +[Version] +Signature = "$WINDOWS NT$" +Class = "ActivityMonitor" +ClassGuid = {b86dff51-a31e-4bac-b3cf-e8cfe75c9fc2} +Provider = %ManufacturerName% +CatalogFile = Filerestore_sys.cat +DriverVer = 02/19/2026,1.0.0.0 +PnpLockdown = 1 + +[DestinationDirs] +DefaultDestDir = 12 ; %windir%\system32\drivers +FileRestoreMon.CopyFiles = 12 + +[SourceDisksNames] +1 = %DiskName%,,,"" + +[SourceDisksFiles] +Filerestore_sys.sys = 1,, + +;***************************************** +; DefaultInstall Section (non-PnP) +;***************************************** + +[DefaultInstall.NT$ARCH$] +OptionDesc = %ServiceDescription% +CopyFiles = FileRestoreMon.CopyFiles + +[DefaultInstall.NT$ARCH$.Services] +AddService = FileRestoreMon,,FileRestoreMon.Service + +[DefaultUninstall.NT$ARCH$] +LegacyUninstall = 1 +DelFiles = FileRestoreMon.CopyFiles + +[DefaultUninstall.NT$ARCH$.Services] +DelService = FileRestoreMon,0x200 ; SPSVCINST_STOPSERVICE + +;***************************************** +; File copy +;***************************************** + +[FileRestoreMon.CopyFiles] +Filerestore_sys.sys + +;***************************************** +; Service installation +;***************************************** + +[FileRestoreMon.Service] +DisplayName = %ServiceName% +Description = %ServiceDescription% +ServiceType = 2 ; SERVICE_FILE_SYSTEM_DRIVER +StartType = 3 ; SERVICE_DEMAND_START +ErrorControl = 1 ; SERVICE_ERROR_NORMAL +ServiceBinary = %12%\Filerestore_sys.sys +LoadOrderGroup = "FSFilter Activity Monitor" +AddReg = FileRestoreMon.AddRegistry + +;***************************************** +; Minifilter instance registration +;***************************************** + +[FileRestoreMon.AddRegistry] +HKR,"Instances","DefaultInstance",0x00000000,%DefaultInstance% +HKR,"Instances\"%Instance1.Name%,"Altitude",0x00000000,%Instance1.Altitude% +HKR,"Instances\"%Instance1.Name%,"Flags",0x00010001,%Instance1.Flags% + +;***************************************** +; Strings +;***************************************** + +[Strings] +ManufacturerName = "FileRestore" +ServiceName = "FileRestoreMon" +ServiceDescription = "FileRestore Monitor Minifilter Driver" +DiskName = "FileRestoreMon Installation Disk" +DefaultInstance = "FileRestoreMon Instance" +Instance1.Name = "FileRestoreMon Instance" +Instance1.Altitude = "370100" +Instance1.Flags = 0x0 ; automatic attachment diff --git a/Filerestore_sys/Filerestore_sys/Filerestore_sys.vcxproj b/Filerestore_sys/Filerestore_sys/Filerestore_sys.vcxproj new file mode 100644 index 0000000..f48cde3 --- /dev/null +++ b/Filerestore_sys/Filerestore_sys/Filerestore_sys.vcxproj @@ -0,0 +1,168 @@ + + + + + + + + Debug + x64 + + + Release + x64 + + + Debug + ARM64 + + + Release + ARM64 + + + + {4E97BA7A-9203-3699-D57F-5596CAD720A0} + {1bc93793-694f-48fe-9372-81e2b05556fd} + v4.5 + 12.0 + Debug + x64 + Filerestore_sys + + + + Windows10 + true + WindowsKernelModeDriver10.0 + Driver + WDM + Universal + + + Windows10 + false + WindowsKernelModeDriver10.0 + Driver + WDM + Universal + + + Windows10 + true + WindowsKernelModeDriver10.0 + Driver + WDM + Universal + + + Windows10 + false + WindowsKernelModeDriver10.0 + Driver + WDM + Universal + + + + + + + + + + false + false + + + DbgengKernelDebugger + + + DbgengKernelDebugger + + + DbgengKernelDebugger + + + DbgengKernelDebugger + + + + sha256 + + + %(PreprocessorDefinitions) + %(AdditionalIncludeDirectories) + + + %(AdditionalDependencies);fltMgr.lib + + + + + sha256 + + + %(PreprocessorDefinitions) + %(AdditionalIncludeDirectories) + + + %(AdditionalDependencies);fltMgr.lib + + + + + sha256 + + + %(PreprocessorDefinitions) + %(AdditionalIncludeDirectories) + + + %(AdditionalDependencies);fltMgr.lib + + + + + sha256 + + + %(PreprocessorDefinitions) + %(AdditionalIncludeDirectories) + + + %(AdditionalDependencies);fltMgr.lib + + + + + + + + + + + + + + + + + + + + + + + + + + + 这台计算机上缺少此项目引用的 NuGet 程序包。使用“NuGet 程序包还原”可下载这些程序包。有关更多信息,请参见 http://go.microsoft.com/fwlink/?LinkID=322105。缺少的文件是 {0}。 + + + + + + + \ No newline at end of file diff --git a/Filerestore_sys/Filerestore_sys/Filerestore_sys.vcxproj.filters b/Filerestore_sys/Filerestore_sys/Filerestore_sys.vcxproj.filters new file mode 100644 index 0000000..ee59303 --- /dev/null +++ b/Filerestore_sys/Filerestore_sys/Filerestore_sys.vcxproj.filters @@ -0,0 +1,48 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + {8E41214B-6785-4CFE-B992-037D68949A14} + inf;inv;inx;mof;mc; + + + + + Source Files + + + Source Files + + + Source Files + + + + + Header Files + + + Header Files + + + + + Driver Files + + + + + + diff --git a/Filerestore_sys/Filerestore_sys/common.h b/Filerestore_sys/Filerestore_sys/common.h new file mode 100644 index 0000000..fc0cbe9 --- /dev/null +++ b/Filerestore_sys/Filerestore_sys/common.h @@ -0,0 +1,99 @@ +/* + * common.h - Shared definitions between kernel driver and user-mode client + * + * This header is included by both the minifilter driver (kernel) and the + * user-mode application (Filerestore_CLI). Keep it pure C compatible. + */ + +#ifndef _FILERESTORE_COMMON_H_ +#define _FILERESTORE_COMMON_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +/* Communication port name */ +#define FILERESTORE_PORT_NAME L"\\FileRestoreMonPort" + +/* Command codes (user -> kernel) */ +#define CMD_START_MONITOR 1 +#define CMD_STOP_MONITOR 2 +#define CMD_GET_EVENTS 3 +#define CMD_GET_STATS 4 +#define CMD_GET_VERSION 5 + +/* Delete types */ +#define DELETE_TYPE_PERMANENT 0 +#define DELETE_TYPE_RECYCLE 1 + +/* Limits */ +#define MAX_LCN_ENTRIES 64 +#define MAX_FILE_PATH_CHARS 520 +#define DRIVER_VERSION_STRING "1.0.0" + +/* Connection authentication */ +#define FILERESTORE_CONNECTION_MAGIC 0x46524D43 /* 'FRMC' */ +#define FILERESTORE_PROTOCOL_VERSION 1 +#define FILERESTORE_CLIENT_IMAGE_NAME L"\\Filerestore_CLI.exe" + +/* Connection context (user -> kernel, passed via FilterConnectCommunicationPort) */ +typedef struct _CONNECTION_CONTEXT { + ULONG Magic; /* must be FILERESTORE_CONNECTION_MAGIC */ + ULONG Version; /* must be FILERESTORE_PROTOCOL_VERSION */ + ULONG ProcessId; /* caller PID (kernel can cross-validate) */ + ULONG Reserved; /* alignment padding */ +} CONNECTION_CONTEXT, *PCONNECTION_CONTEXT; + +/* ===== Structures ===== */ + +/* LCN (Logical Cluster Number) entry for file extent mapping */ +typedef struct _LCN_ENTRY { + ULONGLONG StartLCN; + ULONG ClusterCount; + ULONG Reserved; +} LCN_ENTRY, *PLCN_ENTRY; + +/* Single delete event notification */ +typedef struct _DELETE_NOTIFICATION { + LARGE_INTEGER Timestamp; + ULONG ProcessId; + ULONG DeleteType; + LARGE_INTEGER FileSize; + LARGE_INTEGER AllocationSize; + LARGE_INTEGER CreationTime; + LARGE_INTEGER LastWriteTime; + ULONG LCNCount; + LCN_ENTRY LCNEntries[MAX_LCN_ENTRIES]; + USHORT FileNameLength; /* in bytes */ + WCHAR FileName[MAX_FILE_PATH_CHARS]; +} DELETE_NOTIFICATION, *PDELETE_NOTIFICATION; + +/* Command message (user -> kernel) */ +typedef struct _COMMAND_MESSAGE { + ULONG Command; +} COMMAND_MESSAGE, *PCOMMAND_MESSAGE; + +/* Events reply header (kernel -> user, for CMD_GET_EVENTS) */ +typedef struct _EVENTS_REPLY { + ULONG EventCount; + DELETE_NOTIFICATION Events[1]; /* variable-length array */ +} EVENTS_REPLY, *PEVENTS_REPLY; + +/* Statistics reply (kernel -> user, for CMD_GET_STATS) */ +typedef struct _STATS_REPLY { + ULONG TotalEvents; + ULONG PendingEvents; + ULONG DroppedEvents; + ULONG MonitorActive; +} STATS_REPLY, *PSTATS_REPLY; + +/* Version reply (kernel -> user, for CMD_GET_VERSION) */ +typedef struct _VERSION_REPLY { + CHAR Version[64]; +} VERSION_REPLY, *PVERSION_REPLY; + +#ifdef __cplusplus +} +#endif + +#endif /* _FILERESTORE_COMMON_H_ */ diff --git a/Filerestore_sys/Filerestore_sys/communication.c b/Filerestore_sys/Filerestore_sys/communication.c new file mode 100644 index 0000000..24b97f4 --- /dev/null +++ b/Filerestore_sys/Filerestore_sys/communication.c @@ -0,0 +1,348 @@ +/* + * communication.c - FltMgr communication port management and event buffering + */ + +#include "driver.h" + +/* Forward declarations for port callbacks */ +static NTSTATUS +ConnectNotifyCallback( + _In_ PFLT_PORT ClientPort, + _In_opt_ PVOID ServerPortCookie, + _In_reads_bytes_opt_(SizeOfContext) PVOID ConnectionContext, + _In_ ULONG SizeOfContext, + _Outptr_result_maybenull_ PVOID *ConnectionCookie + ); + +static VOID +DisconnectNotifyCallback( + _In_opt_ PVOID ConnectionCookie + ); + +static NTSTATUS +MessageNotifyCallback( + _In_opt_ PVOID PortCookie, + _In_reads_bytes_opt_(InputBufferLength) PVOID InputBuffer, + _In_ ULONG InputBufferLength, + _Out_writes_bytes_to_opt_(OutputBufferLength, *ReturnOutputBufferLength) PVOID OutputBuffer, + _In_ ULONG OutputBufferLength, + _Out_ PULONG ReturnOutputBufferLength + ); + +/* ===== SetupCommunicationPort ===== */ + +NTSTATUS +SetupCommunicationPort(VOID) +{ + NTSTATUS status; + UNICODE_STRING portName; + PSECURITY_DESCRIPTOR sd = NULL; + OBJECT_ATTRIBUTES oa; + + RtlInitUnicodeString(&portName, FILERESTORE_PORT_NAME); + + /* Build a security descriptor that only allows admin access */ + status = FltBuildDefaultSecurityDescriptor(&sd, FLT_PORT_ALL_ACCESS); + if (!NT_SUCCESS(status)) { + return status; + } + + InitializeObjectAttributes( + &oa, + &portName, + OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, + NULL, + sd + ); + + status = FltCreateCommunicationPort( + g_Context.FilterHandle, + &g_Context.ServerPort, + &oa, + NULL, /* ServerPortCookie */ + ConnectNotifyCallback, + DisconnectNotifyCallback, + MessageNotifyCallback, + 1 /* MaxConnections: single client */ + ); + + FltFreeSecurityDescriptor(sd); + return status; +} + +/* ===== VerifyCallerImage ===== */ + +/* ZwQueryInformationProcess is not declared in standard WDK headers */ +NTSYSAPI NTSTATUS NTAPI ZwQueryInformationProcess( + _In_ HANDLE ProcessHandle, + _In_ PROCESSINFOCLASS ProcessInformationClass, + _Out_writes_bytes_(ProcessInformationLength) PVOID ProcessInformation, + _In_ ULONG ProcessInformationLength, + _Out_opt_ PULONG ReturnLength + ); + +/* + * VerifyCallerImage - Verify the calling process image name matches the + * expected client executable. Uses ZwQueryInformationProcess(ProcessImageFileName) + * to get the NT path, then checks if the path suffix matches + * FILERESTORE_CLIENT_IMAGE_NAME. + */ +static NTSTATUS VerifyCallerImage(VOID) +{ + NTSTATUS status; + UCHAR buffer[512]; + ULONG returnedLength; + PUNICODE_STRING imagePath; + UNICODE_STRING expectedSuffix; + UNICODE_STRING actualSuffix; + USHORT suffixBytes; + + status = ZwQueryInformationProcess( + NtCurrentProcess(), + ProcessImageFileName, /* = 27 */ + buffer, + sizeof(buffer), + &returnedLength + ); + if (!NT_SUCCESS(status)) { + return status; + } + + imagePath = (PUNICODE_STRING)buffer; + RtlInitUnicodeString(&expectedSuffix, FILERESTORE_CLIENT_IMAGE_NAME); + + suffixBytes = expectedSuffix.Length; + if (imagePath->Length < suffixBytes) { + return STATUS_ACCESS_DENIED; + } + + /* Extract the suffix of the image path matching the expected length */ + actualSuffix.Buffer = (PWCH)((PUCHAR)imagePath->Buffer + + imagePath->Length - suffixBytes); + actualSuffix.Length = suffixBytes; + actualSuffix.MaximumLength = suffixBytes; + + /* Case-insensitive comparison */ + if (RtlCompareUnicodeString(&actualSuffix, &expectedSuffix, TRUE) != 0) { + return STATUS_ACCESS_DENIED; + } + + return STATUS_SUCCESS; +} + +/* ===== ConnectNotifyCallback ===== */ + +static NTSTATUS +ConnectNotifyCallback( + _In_ PFLT_PORT ClientPort, + _In_opt_ PVOID ServerPortCookie, + _In_reads_bytes_opt_(SizeOfContext) PVOID ConnectionContext, + _In_ ULONG SizeOfContext, + _Outptr_result_maybenull_ PVOID *ConnectionCookie + ) +{ + PCONNECTION_CONTEXT ctx; + + UNREFERENCED_PARAMETER(ServerPortCookie); + + /* 1. Validate connection context (handshake) */ + if (ConnectionContext == NULL || + SizeOfContext < sizeof(CONNECTION_CONTEXT)) { + return STATUS_ACCESS_DENIED; + } + + ctx = (PCONNECTION_CONTEXT)ConnectionContext; + if (ctx->Magic != FILERESTORE_CONNECTION_MAGIC || + ctx->Version != FILERESTORE_PROTOCOL_VERSION) { + return STATUS_ACCESS_DENIED; + } + + /* 2. Verify caller process image name */ + if (!NT_SUCCESS(VerifyCallerImage())) { + return STATUS_ACCESS_DENIED; + } + + g_Context.ClientPort = ClientPort; + *ConnectionCookie = NULL; + return STATUS_SUCCESS; +} + +/* ===== DisconnectNotifyCallback ===== */ + +static VOID +DisconnectNotifyCallback( + _In_opt_ PVOID ConnectionCookie + ) +{ + UNREFERENCED_PARAMETER(ConnectionCookie); + + if (g_Context.ClientPort != NULL) { + FltCloseClientPort(g_Context.FilterHandle, &g_Context.ClientPort); + g_Context.ClientPort = NULL; + } + + /* Stop monitoring when client disconnects */ + InterlockedExchange(&g_Context.MonitorActive, 0); +} + +/* ===== MessageNotifyCallback ===== */ + +static NTSTATUS +MessageNotifyCallback( + _In_opt_ PVOID PortCookie, + _In_reads_bytes_opt_(InputBufferLength) PVOID InputBuffer, + _In_ ULONG InputBufferLength, + _Out_writes_bytes_to_opt_(OutputBufferLength, *ReturnOutputBufferLength) PVOID OutputBuffer, + _In_ ULONG OutputBufferLength, + _Out_ PULONG ReturnOutputBufferLength + ) +{ + PCOMMAND_MESSAGE cmdMsg; + KIRQL oldIrql; + LONG count; + LONG tail; + LONG i; + + UNREFERENCED_PARAMETER(PortCookie); + + *ReturnOutputBufferLength = 0; + + if (InputBuffer == NULL || InputBufferLength < sizeof(COMMAND_MESSAGE)) { + return STATUS_INVALID_PARAMETER; + } + + cmdMsg = (PCOMMAND_MESSAGE)InputBuffer; + + switch (cmdMsg->Command) { + + case CMD_START_MONITOR: + InterlockedExchange(&g_Context.MonitorActive, 1); + return STATUS_SUCCESS; + + case CMD_STOP_MONITOR: + InterlockedExchange(&g_Context.MonitorActive, 0); + return STATUS_SUCCESS; + + case CMD_GET_EVENTS: + { + PEVENTS_REPLY reply; + ULONG replySize; + + if (OutputBuffer == NULL || OutputBufferLength < sizeof(EVENTS_REPLY)) { + return STATUS_BUFFER_TOO_SMALL; + } + + reply = (PEVENTS_REPLY)OutputBuffer; + + KeAcquireSpinLock(&g_Context.BufferLock, &oldIrql); + + count = g_Context.EventCount; + if (count == 0) { + KeReleaseSpinLock(&g_Context.BufferLock, oldIrql); + reply->EventCount = 0; + *ReturnOutputBufferLength = FIELD_OFFSET(EVENTS_REPLY, Events); + return STATUS_SUCCESS; + } + + /* Calculate how many events fit in the output buffer */ + replySize = FIELD_OFFSET(EVENTS_REPLY, Events); + { + LONG maxEvents = (LONG)((OutputBufferLength - replySize) / sizeof(DELETE_NOTIFICATION)) + 1; + if (count > maxEvents) { + count = maxEvents; + } + } + + /* Copy events from ring buffer */ + tail = g_Context.EventTail; + for (i = 0; i < count; i++) { + RtlCopyMemory( + &reply->Events[i], + &g_Context.Events[tail], + sizeof(DELETE_NOTIFICATION) + ); + tail = (tail + 1) % MAX_PENDING_EVENTS; + } + + g_Context.EventTail = tail; + g_Context.EventCount -= count; + + KeReleaseSpinLock(&g_Context.BufferLock, oldIrql); + + reply->EventCount = (ULONG)count; + *ReturnOutputBufferLength = FIELD_OFFSET(EVENTS_REPLY, Events) + + (ULONG)count * sizeof(DELETE_NOTIFICATION); + return STATUS_SUCCESS; + } + + case CMD_GET_STATS: + { + PSTATS_REPLY statsReply; + + if (OutputBuffer == NULL || OutputBufferLength < sizeof(STATS_REPLY)) { + return STATUS_BUFFER_TOO_SMALL; + } + + statsReply = (PSTATS_REPLY)OutputBuffer; + statsReply->TotalEvents = (ULONG)g_Context.TotalEvents; + statsReply->DroppedEvents = (ULONG)g_Context.DroppedEvents; + statsReply->MonitorActive = (ULONG)g_Context.MonitorActive; + + KeAcquireSpinLock(&g_Context.BufferLock, &oldIrql); + statsReply->PendingEvents = (ULONG)g_Context.EventCount; + KeReleaseSpinLock(&g_Context.BufferLock, oldIrql); + + *ReturnOutputBufferLength = sizeof(STATS_REPLY); + return STATUS_SUCCESS; + } + + case CMD_GET_VERSION: + { + PVERSION_REPLY verReply; + + if (OutputBuffer == NULL || OutputBufferLength < sizeof(VERSION_REPLY)) { + return STATUS_BUFFER_TOO_SMALL; + } + + verReply = (PVERSION_REPLY)OutputBuffer; + RtlZeroMemory(verReply, sizeof(VERSION_REPLY)); + RtlStringCbCopyA(verReply->Version, sizeof(verReply->Version), DRIVER_VERSION_STRING); + + *ReturnOutputBufferLength = sizeof(VERSION_REPLY); + return STATUS_SUCCESS; + } + + default: + return STATUS_INVALID_PARAMETER; + } +} + +/* ===== BufferPushEvent ===== */ + +VOID +BufferPushEvent( + _In_ const DELETE_NOTIFICATION* Event + ) +{ + KIRQL oldIrql; + + KeAcquireSpinLock(&g_Context.BufferLock, &oldIrql); + + if (g_Context.EventCount >= MAX_PENDING_EVENTS) { + /* Buffer full - drop the oldest event by advancing tail */ + g_Context.EventTail = (g_Context.EventTail + 1) % MAX_PENDING_EVENTS; + g_Context.EventCount--; + InterlockedIncrement(&g_Context.DroppedEvents); + } + + RtlCopyMemory( + &g_Context.Events[g_Context.EventHead], + Event, + sizeof(DELETE_NOTIFICATION) + ); + + g_Context.EventHead = (g_Context.EventHead + 1) % MAX_PENDING_EVENTS; + g_Context.EventCount++; + + KeReleaseSpinLock(&g_Context.BufferLock, oldIrql); +} diff --git a/Filerestore_sys/Filerestore_sys/driver.c b/Filerestore_sys/Filerestore_sys/driver.c new file mode 100644 index 0000000..f91b65a --- /dev/null +++ b/Filerestore_sys/Filerestore_sys/driver.c @@ -0,0 +1,118 @@ +/* + * driver.c - DriverEntry, filter registration, unload + */ + +#include "driver.h" + +/* Global context - zero-initialized */ +GLOBAL_CONTEXT g_Context = { 0 }; + +/* Forward declarations */ +DRIVER_INITIALIZE DriverEntry; + +static NTSTATUS +FilterUnloadCallback( + _In_ FLT_FILTER_UNLOAD_FLAGS Flags + ); + +/* ===== Filter registration ===== */ + +static const FLT_OPERATION_REGISTRATION FilterCallbacks[] = { + { + IRP_MJ_SET_INFORMATION, + 0, + PreSetInformation, /* PreOperation */ + NULL /* PostOperation - not needed */ + }, + { IRP_MJ_OPERATION_END } +}; + +static const FLT_REGISTRATION FilterRegistration = { + sizeof(FLT_REGISTRATION), /* Size */ + FLT_REGISTRATION_VERSION, /* Version */ + 0, /* Flags */ + NULL, /* ContextRegistration */ + FilterCallbacks, /* OperationRegistration */ + FilterUnloadCallback, /* FilterUnloadCallback */ + NULL, /* InstanceSetupCallback */ + NULL, /* InstanceQueryTeardownCallback */ + NULL, /* InstanceTeardownStartCallback */ + NULL, /* InstanceTeardownCompleteCallback */ + NULL, /* GenerateFileNameCallback */ + NULL, /* NormalizeNameComponentCallback */ + NULL /* NormalizeContextCleanupCallback */ +}; + +/* ===== DriverEntry ===== */ + +NTSTATUS +DriverEntry( + _In_ PDRIVER_OBJECT DriverObject, + _In_ PUNICODE_STRING RegistryPath + ) +{ + NTSTATUS status; + + UNREFERENCED_PARAMETER(RegistryPath); + + /* 1. Initialize the event ring buffer lock */ + KeInitializeSpinLock(&g_Context.BufferLock); + g_Context.EventCount = 0; + g_Context.EventHead = 0; + g_Context.EventTail = 0; + g_Context.TotalEvents = 0; + g_Context.DroppedEvents = 0; + g_Context.MonitorActive = 0; + + /* 2. Register the minifilter */ + status = FltRegisterFilter( + DriverObject, + &FilterRegistration, + &g_Context.FilterHandle + ); + + if (!NT_SUCCESS(status)) { + return status; + } + + /* 3. Create the communication port */ + status = SetupCommunicationPort(); + if (!NT_SUCCESS(status)) { + FltUnregisterFilter(g_Context.FilterHandle); + return status; + } + + /* 4. Start filtering */ + status = FltStartFiltering(g_Context.FilterHandle); + if (!NT_SUCCESS(status)) { + FltCloseCommunicationPort(g_Context.ServerPort); + FltUnregisterFilter(g_Context.FilterHandle); + return status; + } + + return STATUS_SUCCESS; +} + +/* ===== FilterUnloadCallback ===== */ + +static NTSTATUS +FilterUnloadCallback( + _In_ FLT_FILTER_UNLOAD_FLAGS Flags + ) +{ + UNREFERENCED_PARAMETER(Flags); + + /* Close the server port first - prevents new connections */ + if (g_Context.ServerPort != NULL) { + FltCloseCommunicationPort(g_Context.ServerPort); + g_Context.ServerPort = NULL; + } + + /* Unregister the filter */ + if (g_Context.FilterHandle != NULL) { + FltUnregisterFilter(g_Context.FilterHandle); + g_Context.FilterHandle = NULL; + } + + return STATUS_SUCCESS; +} diff --git a/Filerestore_sys/Filerestore_sys/driver.h b/Filerestore_sys/Filerestore_sys/driver.h new file mode 100644 index 0000000..af4611e --- /dev/null +++ b/Filerestore_sys/Filerestore_sys/driver.h @@ -0,0 +1,59 @@ +/* + * driver.h - Internal driver declarations + */ + +#ifndef _FILERESTORE_DRIVER_H_ +#define _FILERESTORE_DRIVER_H_ + +#include +#include +#include "common.h" +#include +/* Pool tag: 'MsFR' (stored little-endian as 'RFsM') */ +#define POOL_TAG 'RFsM' + +/* Maximum pending events in the kernel ring buffer */ +#define MAX_PENDING_EVENTS 256 + +/* ===== Global context ===== */ + +typedef struct _GLOBAL_CONTEXT { + PFLT_FILTER FilterHandle; + PFLT_PORT ServerPort; /* server-side communication port */ + PFLT_PORT ClientPort; /* connected client port (single client) */ + + /* Kernel event ring buffer */ + KSPIN_LOCK BufferLock; + LONG EventCount; /* number of events currently buffered */ + LONG EventHead; /* next write position */ + LONG EventTail; /* next read position */ + DELETE_NOTIFICATION Events[MAX_PENDING_EVENTS]; + + /* Statistics */ + volatile LONG TotalEvents; + volatile LONG DroppedEvents; + volatile LONG MonitorActive; /* 1 = monitoring active */ +} GLOBAL_CONTEXT, *PGLOBAL_CONTEXT; + +extern GLOBAL_CONTEXT g_Context; + +/* ===== Function prototypes ===== */ + +/* communication.c */ +NTSTATUS +SetupCommunicationPort(VOID); + +VOID +BufferPushEvent( + _In_ const DELETE_NOTIFICATION* Event + ); + +/* filter.c */ +FLT_PREOP_CALLBACK_STATUS +PreSetInformation( + _Inout_ PFLT_CALLBACK_DATA Data, + _In_ PCFLT_RELATED_OBJECTS FltObjects, + _Flt_CompletionContext_Outptr_ PVOID *CompletionContext + ); + +#endif /* _FILERESTORE_DRIVER_H_ */ diff --git a/Filerestore_sys/Filerestore_sys/filter.c b/Filerestore_sys/Filerestore_sys/filter.c new file mode 100644 index 0000000..a4456e1 --- /dev/null +++ b/Filerestore_sys/Filerestore_sys/filter.c @@ -0,0 +1,275 @@ +/* + * filter.c - PreSetInformation callback and file metadata capture + */ + +#include "driver.h" + +/* Forward declarations */ +static VOID +CaptureDeleteEvent( + _Inout_ PFLT_CALLBACK_DATA Data, + _In_ PCFLT_RELATED_OBJECTS FltObjects, + _In_ FILE_INFORMATION_CLASS InfoClass + ); + +static NTSTATUS +QueryFileLCNs( + _In_ PCFLT_RELATED_OBJECTS FltObjects, + _Inout_ PDELETE_NOTIFICATION Notification + ); + +/* ===== PreSetInformation callback ===== */ + +FLT_PREOP_CALLBACK_STATUS +PreSetInformation( + _Inout_ PFLT_CALLBACK_DATA Data, + _In_ PCFLT_RELATED_OBJECTS FltObjects, + _Flt_CompletionContext_Outptr_ PVOID *CompletionContext + ) +{ + FILE_INFORMATION_CLASS infoClass; + + *CompletionContext = NULL; + + /* Check if monitoring is active */ + if (!g_Context.MonitorActive) { + return FLT_PREOP_SUCCESS_NO_CALLBACK; + } + + infoClass = Data->Iopb->Parameters.SetFileInformation.FileInformationClass; + + switch (infoClass) { + + case FileDispositionInformation: + { + PFILE_DISPOSITION_INFORMATION dispInfo; + dispInfo = (PFILE_DISPOSITION_INFORMATION) + Data->Iopb->Parameters.SetFileInformation.InfoBuffer; + if (dispInfo == NULL || !dispInfo->DeleteFile) { + return FLT_PREOP_SUCCESS_NO_CALLBACK; + } + break; + } + + case FileDispositionInformationEx: + { + PFILE_DISPOSITION_INFORMATION_EX dispInfoEx; + dispInfoEx = (PFILE_DISPOSITION_INFORMATION_EX) + Data->Iopb->Parameters.SetFileInformation.InfoBuffer; + if (dispInfoEx == NULL || + !(dispInfoEx->Flags & FILE_DISPOSITION_DELETE)) { + return FLT_PREOP_SUCCESS_NO_CALLBACK; + } + break; + } + + default: + /* Not a delete operation */ + return FLT_PREOP_SUCCESS_NO_CALLBACK; + } + + /* Capture metadata before the delete reaches NTFS */ + CaptureDeleteEvent(Data, FltObjects, infoClass); + + /* Never block the original operation */ + return FLT_PREOP_SUCCESS_NO_CALLBACK; +} + +/* ===== CaptureDeleteEvent ===== */ + +static VOID +CaptureDeleteEvent( + _Inout_ PFLT_CALLBACK_DATA Data, + _In_ PCFLT_RELATED_OBJECTS FltObjects, + _In_ FILE_INFORMATION_CLASS InfoClass + ) +{ + DELETE_NOTIFICATION notif; + PFLT_FILE_NAME_INFORMATION nameInfo = NULL; + FILE_STANDARD_INFORMATION stdInfo; + FILE_BASIC_INFORMATION basicInfo; + NTSTATUS status; + USHORT copyLen; + + UNREFERENCED_PARAMETER(InfoClass); + + RtlZeroMemory(¬if, sizeof(notif)); + + __try { + + /* 1. Get file name */ + status = FltGetFileNameInformation( + Data, + FLT_FILE_NAME_NORMALIZED | FLT_FILE_NAME_QUERY_DEFAULT, + &nameInfo + ); + + if (NT_SUCCESS(status)) { + status = FltParseFileNameInformation(nameInfo); + if (NT_SUCCESS(status)) { + copyLen = nameInfo->Name.Length; + if (copyLen > (MAX_FILE_PATH_CHARS - 1) * sizeof(WCHAR)) { + copyLen = (MAX_FILE_PATH_CHARS - 1) * sizeof(WCHAR); + } + RtlCopyMemory(notif.FileName, nameInfo->Name.Buffer, copyLen); + notif.FileNameLength = copyLen; + notif.FileName[copyLen / sizeof(WCHAR)] = L'\0'; + } + FltReleaseFileNameInformation(nameInfo); + nameInfo = NULL; + } + + /* 2. Get file size */ + status = FltQueryInformationFile( + FltObjects->Instance, + FltObjects->FileObject, + &stdInfo, + sizeof(stdInfo), + FileStandardInformation, + NULL + ); + + if (NT_SUCCESS(status)) { + notif.FileSize = stdInfo.EndOfFile; + notif.AllocationSize = stdInfo.AllocationSize; + } + + /* 3. Get timestamps */ + status = FltQueryInformationFile( + FltObjects->Instance, + FltObjects->FileObject, + &basicInfo, + sizeof(basicInfo), + FileBasicInformation, + NULL + ); + + if (NT_SUCCESS(status)) { + notif.CreationTime = basicInfo.CreationTime; + notif.LastWriteTime = basicInfo.LastWriteTime; + } + + /* 4. Get LCN mapping (may fail for resident files - that's OK) */ + QueryFileLCNs(FltObjects, ¬if); + + /* 5. Fill remaining fields */ + notif.ProcessId = FltGetRequestorProcessId(Data); + notif.DeleteType = DELETE_TYPE_PERMANENT; + KeQuerySystemTimePrecise(¬if.Timestamp); + + /* 6. Push to ring buffer */ + BufferPushEvent(¬if); + InterlockedIncrement(&g_Context.TotalEvents); + } + __except (EXCEPTION_EXECUTE_HANDLER) { + /* Silent failure - never interfere with the original delete */ + if (nameInfo != NULL) { + FltReleaseFileNameInformation(nameInfo); + } + } +} + +/* ===== QueryFileLCNs ===== */ + +static NTSTATUS +QueryFileLCNs( + _In_ PCFLT_RELATED_OBJECTS FltObjects, + _Inout_ PDELETE_NOTIFICATION Notification + ) +{ + NTSTATUS status; + STARTING_VCN_INPUT_BUFFER vcnInput; + PRETRIEVAL_POINTERS_BUFFER rpBuf = NULL; + ULONG rpBufSize = 4096; + ULONG returned; + ULONG i; + ULONG count; + LARGE_INTEGER prevVcn; + + vcnInput.StartingVcn.QuadPart = 0; + + /* Allocate initial buffer */ + rpBuf = (PRETRIEVAL_POINTERS_BUFFER)ExAllocatePool2( + POOL_FLAG_NON_PAGED, + rpBufSize, + POOL_TAG + ); + + if (rpBuf == NULL) { + return STATUS_INSUFFICIENT_RESOURCES; + } + + /* Query retrieval pointers */ + status = FltFsControlFile( + FltObjects->Instance, + FltObjects->FileObject, + FSCTL_GET_RETRIEVAL_POINTERS, + &vcnInput, + sizeof(vcnInput), + rpBuf, + rpBufSize, + &returned + ); + + if (status == STATUS_BUFFER_OVERFLOW) { + /* Retry with larger buffer */ + ExFreePoolWithTag(rpBuf, POOL_TAG); + rpBufSize = 16384; + rpBuf = (PRETRIEVAL_POINTERS_BUFFER)ExAllocatePool2( + POOL_FLAG_NON_PAGED, + rpBufSize, + POOL_TAG + ); + if (rpBuf == NULL) { + return STATUS_INSUFFICIENT_RESOURCES; + } + + status = FltFsControlFile( + FltObjects->Instance, + FltObjects->FileObject, + FSCTL_GET_RETRIEVAL_POINTERS, + &vcnInput, + sizeof(vcnInput), + rpBuf, + rpBufSize, + &returned + ); + } + + if (!NT_SUCCESS(status)) { + /* Resident files or other failures - LCNCount stays 0 */ + ExFreePoolWithTag(rpBuf, POOL_TAG); + return status; + } + + /* Parse extents */ + count = rpBuf->ExtentCount; + if (count > MAX_LCN_ENTRIES) { + count = MAX_LCN_ENTRIES; + } + + prevVcn = rpBuf->StartingVcn; + for (i = 0; i < count; i++) { + LARGE_INTEGER nextVcn = rpBuf->Extents[i].NextVcn; + LARGE_INTEGER lcn = rpBuf->Extents[i].Lcn; + + /* LCN == -1 means sparse/unallocated extent, skip */ + if (lcn.QuadPart != -1) { + Notification->LCNEntries[Notification->LCNCount].StartLCN = + (ULONGLONG)lcn.QuadPart; + Notification->LCNEntries[Notification->LCNCount].ClusterCount = + (ULONG)(nextVcn.QuadPart - prevVcn.QuadPart); + Notification->LCNEntries[Notification->LCNCount].Reserved = 0; + Notification->LCNCount++; + + if (Notification->LCNCount >= MAX_LCN_ENTRIES) { + break; + } + } + + prevVcn = nextVcn; + } + + ExFreePoolWithTag(rpBuf, POOL_TAG); + return STATUS_SUCCESS; +} diff --git a/Filerestore_sys/Filerestore_sys/packages.config b/Filerestore_sys/Filerestore_sys/packages.config new file mode 100644 index 0000000..bb6933b --- /dev/null +++ b/Filerestore_sys/Filerestore_sys/packages.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/README.md b/README.md index 45103ca..39d0a18 100644 --- a/README.md +++ b/README.md @@ -176,6 +176,13 @@ usnrecover C 3 D:\recovered\ # 按索引恢复 recover C important.docx D:\recovered\ # 智能恢复向导 ``` +### 2. USN 精准恢复 (v1.0.0+) +```bash +usnlist C # 列出最近删除文件 +usnrecover C 3 D:\recovered\ # 按索引恢复 +recover C important.docx D:\recovered\ # 智能恢复向导 +``` + ### 3. 实时删除监控 (v1.0.0+) - 后台守护进程监听 USN 删除事件 - 文件删除瞬间自动捕获 MFT 快照 @@ -818,6 +825,12 @@ msbuild Filerestore_CLI.vcxproj /p:Configuration=Release /p:Platform=x64 | `usnlist ` | List recently deleted files (with confidence scoring) | | `usnrecover ` | Recover by index/filename/record number | +### USN Recovery (v1.0.0+) +| Command | Description | +|---------|-------------| +| `usnlist ` | List recently deleted files (with confidence scoring) | +| `usnrecover ` | Recover by index/filename/record number | + ### Signature Carving | Command | Description | |---------|-------------| diff --git a/dev_notes/kernel_solution.docx b/dev_notes/kernel_solution.docx new file mode 100644 index 0000000..7956ea8 Binary files /dev/null and b/dev_notes/kernel_solution.docx differ diff --git a/dev_notes/kernel_solution.md b/dev_notes/kernel_solution.md new file mode 100644 index 0000000..f441245 --- /dev/null +++ b/dev_notes/kernel_solution.md @@ -0,0 +1,884 @@ +# 内核层删除监控方案 — 技术设计文档 + +> 背景:MFT 复用后丢失文件的精确大小和 LCN 信息,导致签名扫描退化为全盘盲扫。 +> 本文档讨论两种解决方案:用户态 MFT 快照、内核态微过滤驱动实时监控。 + +--- + +## 一、问题定义 + +``` +现有痛点: + 文件删除 → MFT 记录被复用 → 丢失大小和 LCN → 签名扫描盲目 + +解决思路: + 在 MFT 复用之前,保存文件的关键元数据(大小、LCN、文件名) + 恢复时利用这些元数据进行定向扫描 +``` + +--- + +## 二、方案一:MFT 元数据快照(用户态) + +### 2.1 核心优势 + +| 已知信息 | 收益 | +|---------|------| +| 精确大小 | 避免截断/过度读取 | +| LCN 范围 | 扫描范围缩小 99% | +| 文件名 | 无需人工识别 | + +### 2.2 快照数据结构 + +每条记录约 100-200 字节: + +```cpp +struct MFTSnapshotEntry { + uint64_t mftRecordNo; // 8 bytes + uint64_t parentRecordNo; // 8 bytes + uint32_t sequenceNo; // 4 bytes + uint64_t fileSize; // 8 bytes + uint64_t allocSize; // 8 bytes + uint16_t lcnCount; // 2 bytes + uint64_t timestamps[4]; // 32 bytes (创建/修改/访问/变更) + uint32_t attributes; // 4 bytes + uint16_t nameLen; // 2 bytes + wchar_t name[256]; // 512 bytes (可变长) + LCNEntry lcnEntries[]; // 变长 (offset + length) +}; +``` + +### 2.3 空间估算(D 盘 731,904 条记录) + +- 精简版(无文件名):~70 MB +- 完整版(含文件名):~150 MB +- 压缩后(ZSTD):~30-50 MB + +### 2.4 实现策略 + +``` +快照管理系统: + ├── 全量快照(每日定时 / 手动触发) + ├── 增量快照(USN 驱动,检测到删除时抢读 MFT) + └── 快照合并与自动清理 + │ + ▼ + 快照存储引擎(二进制 + 索引) + │ + ┌─────┼──────┐ + ▼ ▼ ▼ +签名扫描 大小验证 LCN 过滤 +精准定位 完整性校验 范围限制 +``` + +### 2.5 对签名扫描的改进 + +**当前流程(盲目扫描):** + +``` +全盘扫描 → 找到签名头 → 猜测大小 → 读取 → 验证尾部 +问题: 大小不确定,可能截断或过度读取 +``` + +**快照辅助流程:** + +``` +查快照获取 LCN 范围 → 只扫描这些簇 → 已知精确大小 → 精准读取 +优势: 扫描范围 1% + 精确截断 + 可验证完整性 +``` + +### 2.6 命令设计 + +```bash +# 创建快照 +listdeleted D --create-snapshot + +# 自动后台快照(可选) +listdeleted D --snapshot-schedule hourly + +# 使用快照恢复 +recover D document.docx D:\out --use-snapshot + +# 签名扫描 + 快照辅助 +carvepool D all D:\out --snapshot-guided + +# 快照管理 +snapshot list D +snapshot delete D --older-than 7d +``` + +### 2.7 效果预估 + +``` +无快照: 有快照: + 扫描范围: 550 GB 扫描范围: ~5.5 GB (1%) + 扫描时间: ~4 分钟 扫描时间: ~2 秒 + 大小精度: 估计值(误差大) 大小精度: 精确值 +``` + +--- + +## 三、方案二:内核层实时监控(微过滤驱动) + +### 3.1 技术路径 + +使用 Windows 文件系统微过滤驱动(Minifilter),通过 FltMgr 框架注册 Pre-operation 回调,在文件删除操作到达文件系统之前捕获元数据。 + +### 3.2 可获取的信息 + +``` +内核层能获取的精确信息: +├── 文件大小 (FILE_STANDARD_INFORMATION) +├── LCN 列表 (FSCTL_GET_RETRIEVAL_POINTERS) +├── 完整路径 +├── 删除类型 (回收站 vs Shift+Delete) +├── 删除进程 PID/名称 +├── 精确时间戳 (微秒级) +└── 文件属性 (压缩/加密/稀疏) +``` + +### 3.3 整体架构 + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ 用户态 (Filerestore_CLI.exe) │ +│ │ +│ MonitorClient │ +│ ├── Connect() → CreateFile(设备) │ +│ ├── StartMonitoring() → DeviceIoControl(START) │ +│ ├── StopMonitoring() → DeviceIoControl(STOP) │ +│ ├── GetEvents() → 读取共享内存队列 │ +│ └── WaitForData() → WaitForSingleObject(事件) │ +└─────────────────────────┬──────────────────▲─────────────────────┘ + │ DeviceIoControl │ 共享内存 + ▼ │ +┌─────────────────────────────────────────────────────────────────┐ +│ 内核态 (FileRestoreMonitor.sys) │ +│ │ +│ DriverEntry │ +│ ├── 创建设备对象 \Device\FileRestoreMon │ +│ ├── 创建符号链接 \DosDevices\FileRestoreMon │ +│ ├── 注册 IRP 派发函数 │ +│ └── 注册文件系统过滤器 │ +│ │ │ +│ ┌───────┼───────────────────┐ │ +│ ▼ ▼ ▼ │ +│ IRP 派发 过滤回调 共享内存管理 │ +│ IRP_CREATE PreSetInformation RingBuffer │ +│ IRP_CLOSE PreCleanup DataSection │ +│ IRP_IOCTL PreCreate Event │ +│ │ │ +│ ▼ │ +│ 写入环形缓冲区 → 触发事件通知 │ +└─────────────────────────────────────────────────────────────────┘ + │ + ▼ + NTFS / ReFS +``` + +### 3.4 挑战与应对 + +| 挑战 | 解决方案 | +|------|---------| +| 驱动签名 | 开发阶段:测试签名模式;发布:EV 证书 + 硬件开发人员计划 | +| 稳定性 (BSOD) | 严格错误处理 + 内存池管理 + 参数校验 | +| 性能开销 | 无锁环形缓冲区 + Per-CPU 队列 | +| 安装部署 | 可选组件,用户态降级方案(无驱动时退回快照模式) | + +--- + +## 四、用户态-内核态通信机制对比 + +### 4.1 DeviceIoControl(传统方式) + +``` +用户态 内核态 + │ │ + │ CreateFile("\\\\.\\FileRestoreMon") │ + │ ─────────────────────────────────────────►│ IRP_MJ_CREATE + │ │ + │ DeviceIoControl(IOCTL_GET_EVENTS) │ + │ ─────────────────────────────────────────►│ IRP_MJ_DEVICE_CONTROL + │ │ 查询缓冲区 + │ ◄─────────────────────────────────────────│ 返回数据 + │ │ + │ CloseHandle() │ + │ ─────────────────────────────────────────►│ IRP_MJ_CLOSE +``` + +### 4.2 共享内存 + 事件(高性能方式) + +``` +用户态 内核态 + │ │ + │ OpenFileMapping() │ + │ ─────────────────────────────────────────►│ + │ MapViewOfFile() ◄──────────────────────►│ ZwCreateSection + │ ▲ ▲ │ + │ └────────── 共享内存区域 ────────┘ │ + │ │ + │ WaitForSingleObject() ◄────────────────►│ KeSetEvent() + │ ▲ ▲ │ + │ └────────── 事件通知 ───────────┘ │ + │ │ + │ 等待事件 → 读取共享内存 → 处理数据 │ +``` + +### 4.3 对比 + +| 维度 | DeviceIoControl | 共享内存 + 事件 | +|------|----------------|----------------| +| 实现复杂度 | 低(标准模式) | 中(需同步机制) | +| 每次通信开销 | 系统调用 + IRP 构建 | 仅内存访问 | +| 适合场景 | 低频、小数据量 | 高频、大数据流 | +| 缓冲区管理 | 内核分配,用户拷贝 | 预分配,零拷贝 | +| 同步机制 | 隐式(调用阻塞) | 显式(事件/信号量) | +| 数据方向 | 双向灵活 | 适合单向流 | + +### 4.4 删除监控场景分析 + +``` +删除事件特征: +├── 频率: 高(每秒可能数十次) +├── 方向: 单向(内核 → 用户态) +├── 数据量: 每条 ~200-500 bytes +├── 实时性要求: 中(毫秒级可接受) +└── 缓冲需求: 需要队列(突发流量) +``` + +**结论:混合方案最优** + +``` +推荐架构: +├── 控制通道 (DeviceIoControl) +│ ├── 启动/停止监控 +│ ├── 配置过滤规则 +│ ├── 获取统计信息 +│ └── 获取事件句柄 +│ +└── 数据通道 (共享内存 + 事件) + ├── 高吞吐量删除事件流 + ├── 零拷贝读取 + └── 事件驱动通知 +``` + +--- + +## 五、核心代码参考 + +### 5.1 全局上下文与驱动入口 + +```cpp +// driver.cpp +#include +#include + +typedef struct _GLOBAL_CONTEXT { + PDEVICE_OBJECT DeviceObject; + PFLT_FILTER FilterHandle; + PFLT_PORT ServerPort; + HANDLE SectionHandle; + PVOID SharedMemoryBase; + SIZE_T SharedMemorySize; + KEVENT DataReadyEvent; + KSPIN_LOCK BufferLock; + ULONG WriteIndex; + ULONG ReadIndex; +} GLOBAL_CONTEXT, *PGLOBAL_CONTEXT; + +static GLOBAL_CONTEXT g_Context = {0}; + +NTSTATUS DriverEntry( + PDRIVER_OBJECT DriverObject, + PUNICODE_STRING RegistryPath +) { + NTSTATUS status; + UNICODE_STRING deviceName = RTL_CONSTANT_STRING(L"\\Device\\FileRestoreMon"); + UNICODE_STRING symLink = RTL_CONSTANT_STRING(L"\\DosDevices\\FileRestoreMon"); + + // 1. 创建控制设备 + status = IoCreateDevice( + DriverObject, 0, &deviceName, + FILE_DEVICE_UNKNOWN, FILE_DEVICE_SECURE_OPEN, + FALSE, &g_Context.DeviceObject + ); + if (!NT_SUCCESS(status)) return status; + + // 2. 创建符号链接(用户态通过 \\\\.\\FileRestoreMon 访问) + status = IoCreateSymbolicLink(&symLink, &deviceName); + if (!NT_SUCCESS(status)) { + IoDeleteDevice(g_Context.DeviceObject); + return status; + } + + // 3. 注册 IRP 派发函数 + DriverObject->MajorFunction[IRP_MJ_CREATE] = DispatchCreate; + DriverObject->MajorFunction[IRP_MJ_CLOSE] = DispatchClose; + DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = DispatchIoControl; + DriverObject->DriverUnload = DriverUnload; + + // 4. 初始化共享内存 + status = InitializeSharedMemory(); + if (!NT_SUCCESS(status)) { + IoDeleteSymbolicLink(&symLink); + IoDeleteDevice(g_Context.DeviceObject); + return status; + } + + // 5. 注册文件系统过滤器 + status = RegisterFilter(DriverObject); + if (!NT_SUCCESS(status)) { + CleanupSharedMemory(); + IoDeleteSymbolicLink(&symLink); + IoDeleteDevice(g_Context.DeviceObject); + return status; + } + + return STATUS_SUCCESS; +} +``` + +### 5.2 共享内存初始化 + +```cpp +// shared_memory.cpp +#define SHARED_MEMORY_SIZE (16 * 1024 * 1024) // 16 MB 环形缓冲区 + +NTSTATUS InitializeSharedMemory() { + NTSTATUS status; + OBJECT_ATTRIBUTES attr; + LARGE_INTEGER maxSize; + UNICODE_STRING sectionName = + RTL_CONSTANT_STRING(L"\\BaseNamedObjects\\FileRestoreMonBuffer"); + + // 1. 创建命名 Section(用户态可通过名称打开) + maxSize.QuadPart = SHARED_MEMORY_SIZE; + InitializeObjectAttributes( + &attr, §ionName, + OBJ_KERNEL_HANDLE | OBJ_OPENIF, + NULL, NULL + ); + + status = ZwCreateSection( + &g_Context.SectionHandle, + SECTION_ALL_ACCESS, &attr, &maxSize, + PAGE_READWRITE, SEC_COMMIT, NULL + ); + if (!NT_SUCCESS(status)) return status; + + // 2. 映射到内核地址空间 + SIZE_T viewSize = SHARED_MEMORY_SIZE; + status = ZwMapViewOfSection( + g_Context.SectionHandle, + ZwCurrentProcess(), + &g_Context.SharedMemoryBase, + 0, 0, NULL, &viewSize, + ViewUnmap, 0, PAGE_READWRITE + ); + if (!NT_SUCCESS(status)) { + ZwClose(g_Context.SectionHandle); + return status; + } + + // 3. 初始化环形缓冲区 + RtlZeroMemory(g_Context.SharedMemoryBase, SHARED_MEMORY_SIZE); + g_Context.WriteIndex = 0; + g_Context.ReadIndex = 0; + KeInitializeSpinLock(&g_Context.BufferLock); + + // 4. 创建通知事件 + KeInitializeEvent(&g_Context.DataReadyEvent, NotificationEvent, FALSE); + + return STATUS_SUCCESS; +} +``` + +### 5.3 文件系统过滤回调 + +```cpp +// filter_callback.cpp +#include + +// 过滤器操作注册表 +FLT_OPERATION_REGISTRATION filterOps[] = { + { IRP_MJ_SET_INFORMATION, 0, PreSetInformation, NULL }, + { IRP_MJ_CREATE, 0, PreCreate, NULL }, + { IRP_MJ_OPERATION_END } +}; + +FLT_REGISTRATION filterRegistration = { + sizeof(FLT_REGISTRATION), + FLT_REGISTRATION_VERSION, + 0, + NULL, // Context + filterOps, // Operation Registration + UnloadFilter, + NULL, NULL, NULL, NULL, NULL, NULL +}; + +NTSTATUS RegisterFilter(PDRIVER_OBJECT DriverObject) { + return FltRegisterFilter( + DriverObject, &filterRegistration, &g_Context.FilterHandle + ); +} + +// 核心回调:拦截删除操作 +FLT_PREOP_CALLBACK_STATUS PreSetInformation( + PFLT_CALLBACK_DATA Data, + PCFLT_RELATED_OBJECTS FltObjects, + PVOID *CompletionContext +) { + FILE_INFORMATION_CLASS infoClass = + Data->Iopb->Parameters.SetFileInformation.FileInformationClass; + + // 只处理删除和重命名(回收站是重命名操作) + switch (infoClass) { + case FileDispositionInformation: + case FileDispositionInformationEx: + break; + case FileRenameInformation: + case FileRenameInformationEx: + break; + default: + return FLT_PREOP_SUCCESS_NO_CALLBACK; + } + + // 检查是否真的要删除 + if (infoClass == FileDispositionInformation) { + PFILE_DISPOSITION_INFO dispInfo = + (PFILE_DISPOSITION_INFO)Data->Iopb->Parameters + .SetFileInformation.InfoBuffer; + if (!dispInfo->DeleteFile) { + return FLT_PREOP_SUCCESS_NO_CALLBACK; + } + } + + // 捕获删除事件 + CaptureDeleteEvent(FltObjects->FileObject, infoClass); + + return FLT_PREOP_SUCCESS_NO_CALLBACK; +} +``` + +### 5.4 删除事件捕获 + +```cpp +// capture.cpp + +VOID CaptureDeleteEvent( + PFILE_OBJECT FileObject, + FILE_INFORMATION_CLASS InfoClass +) { + NTSTATUS status; + DELETE_EVENT_RECORD record = {0}; + + // 1. 获取文件基本信息(时间戳、属性) + status = QueryFileBasicInfo(FileObject, &record); + if (!NT_SUCCESS(status)) return; // 静默失败,不影响原始操作 + + // 2. 获取文件大小 + status = QueryFileStandardInfo(FileObject, &record); + if (!NT_SUCCESS(status)) return; + + // 3. 获取完整路径 + status = QueryFileName(FileObject, record.FullPath, sizeof(record.FullPath)); + if (!NT_SUCCESS(status)) return; + + // 4. 获取 LCN 信息(关键) + status = QueryFileLCNs(FileObject, &record); + // 注意:这可能失败(常驻文件没有非常驻数据属性) + + // 5. 填充其他信息 + record.DeleteType = (InfoClass == FileRenameInformation) + ? DELETE_TYPE_RECYCLE + : DELETE_TYPE_PERMANENT; + record.Timestamp = KeQueryPerformanceCounter(NULL).QuadPart; + record.ProcessId = (ULONG)(ULONG_PTR)PsGetCurrentProcessId(); + + // 6. 写入环形缓冲区 + WriteToRingBuffer(&record); +} +``` + +### 5.5 获取 LCN 信息(关键技术点) + +```cpp +// lcn_query.cpp +// +// 使用 FSCTL_GET_RETRIEVAL_POINTERS 获取文件的 VCN → LCN 映射。 +// 这是 NTFS 特有的 FSCTL,返回文件数据在磁盘上的物理位置。 + +NTSTATUS QueryFileLCNs( + PFILE_OBJECT FileObject, + PDELETE_EVENT_RECORD Record +) { + NTSTATUS status; + IO_STATUS_BLOCK iosb; + STARTING_VCN_INPUT_BUFFER vcnInput = {0}; + RETRIEVAL_POINTERS_BUFFER *rpBuf = NULL; + ULONG bufSize = 4096; // 初始缓冲区 + + vcnInput.StartingVcn.QuadPart = 0; + + // 分配缓冲区并查询 + rpBuf = (RETRIEVAL_POINTERS_BUFFER*) + ExAllocatePoolWithTag(NonPagedPool, bufSize, 'CnLG'); + if (!rpBuf) return STATUS_INSUFFICIENT_RESOURCES; + + status = FltFsControlFile( + FltObjects->Instance, // 需要从回调参数传入 + FileObject, + FSCTL_GET_RETRIEVAL_POINTERS, + &vcnInput, sizeof(vcnInput), + rpBuf, bufSize, + &iosb + ); + + // 缓冲区不够时重新分配 + if (status == STATUS_BUFFER_OVERFLOW) { + ExFreePoolWithTag(rpBuf, 'CnLG'); + bufSize = (ULONG)iosb.Information; + rpBuf = (RETRIEVAL_POINTERS_BUFFER*) + ExAllocatePoolWithTag(NonPagedPool, bufSize, 'CnLG'); + if (!rpBuf) return STATUS_INSUFFICIENT_RESOURCES; + + status = FltFsControlFile( + FltObjects->Instance, + FileObject, + FSCTL_GET_RETRIEVAL_POINTERS, + &vcnInput, sizeof(vcnInput), + rpBuf, bufSize, + &iosb + ); + } + + if (NT_SUCCESS(status)) { + // 解析 VCN → LCN 映射 + LARGE_INTEGER prevVcn = rpBuf->StartingVcn; + Record->LCNCount = 0; + + for (ULONG i = 0; + i < rpBuf->ExtentCount && Record->LCNCount < MAX_LCN_ENTRIES; + i++) + { + Record->LCNEntries[Record->LCNCount].StartLCN = + rpBuf->Extents[i].Lcn.QuadPart; + Record->LCNEntries[Record->LCNCount].ClusterCount = + (ULONG)(rpBuf->Extents[i].NextVcn.QuadPart - prevVcn.QuadPart); + + prevVcn = rpBuf->Extents[i].NextVcn; + Record->LCNCount++; + } + } + + if (rpBuf) ExFreePoolWithTag(rpBuf, 'CnLG'); + return status; +} +``` + +### 5.6 环形缓冲区数据结构与写入 + +```cpp +// ring_buffer.cpp + +typedef struct _DELETE_EVENT_RECORD { + ULONG Magic; // 0xDE1E7E54 + ULONG RecordSize; // 本条记录总大小 + LARGE_INTEGER Timestamp; // 删除时间 + ULONG ProcessId; // 删除进程 PID + ULONG DeleteType; // 0=永久删除, 1=回收站 + ULONG FileNameOffset; // 文件名在记录中的偏移 + USHORT FileNameLength; // 文件名长度 (bytes) + + // 文件基本信息 + LARGE_INTEGER FileSize; + LARGE_INTEGER AllocationSize; + LARGE_INTEGER CreationTime; + LARGE_INTEGER LastWriteTime; + + // LCN 信息 + ULONG LCNCount; + struct { + ULONGLONG StartLCN; + ULONG ClusterCount; + } LCNEntries[64]; // 最多 64 个片段 + + // 文件名紧随结构体之后(变长) + WCHAR FullPath[1]; +} DELETE_EVENT_RECORD, *PDELETE_EVENT_RECORD; + + +VOID WriteToRingBuffer(PDELETE_EVENT_RECORD Record) { + KIRQL oldIrql; + ULONG recordSize = Record->RecordSize; + ULONG newWriteIndex; + + KeAcquireSpinLock(&g_Context.BufferLock, &oldIrql); + + newWriteIndex = g_Context.WriteIndex + recordSize; + + if (newWriteIndex >= SHARED_MEMORY_SIZE) { + // 环绕到缓冲区开头 + newWriteIndex = recordSize; + if (g_Context.ReadIndex < recordSize) { + // 缓冲区满,丢弃最旧记录 + g_Context.ReadIndex = recordSize; + } + } + + // 检查是否会覆盖未读数据 + if (newWriteIndex > g_Context.ReadIndex && + g_Context.WriteIndex < g_Context.ReadIndex) { + KeReleaseSpinLock(&g_Context.BufferLock, oldIrql); + return; // 缓冲区满,丢弃本条 + } + + // 写入记录 + RtlCopyMemory( + (PUCHAR)g_Context.SharedMemoryBase + g_Context.WriteIndex, + Record, recordSize + ); + g_Context.WriteIndex = newWriteIndex; + + KeReleaseSpinLock(&g_Context.BufferLock, oldIrql); + + // 通知用户态有新数据 + KeSetEvent(&g_Context.DataReadyEvent, IO_NO_INCREMENT, FALSE); +} +``` + +### 5.7 IRP 派发(DeviceIoControl 处理) + +```cpp +// dispatch.cpp + +#define IOCTL_FILE_RESTORE_START_MONITORING \ + CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800, METHOD_BUFFERED, FILE_ANY_ACCESS) +#define IOCTL_FILE_RESTORE_STOP_MONITORING \ + CTL_CODE(FILE_DEVICE_UNKNOWN, 0x801, METHOD_BUFFERED, FILE_ANY_ACCESS) +#define IOCTL_FILE_RESTORE_GET_EVENT_HANDLE \ + CTL_CODE(FILE_DEVICE_UNKNOWN, 0x802, METHOD_BUFFERED, FILE_ANY_ACCESS) +#define IOCTL_FILE_RESTORE_GET_STATS \ + CTL_CODE(FILE_DEVICE_UNKNOWN, 0x803, METHOD_BUFFERED, FILE_ANY_ACCESS) + +typedef struct _MONITOR_STATS { + ULONG TotalEvents; + ULONG DroppedEvents; + ULONG BufferSize; + ULONG UsedSize; +} MONITOR_STATS, *PMONITOR_STATS; + +NTSTATUS DispatchIoControl( + PDEVICE_OBJECT DeviceObject, + PIRP Irp +) { + NTSTATUS status = STATUS_SUCCESS; + PIO_STACK_LOCATION irpSp = IoGetCurrentIrpStackLocation(Irp); + ULONG ioControlCode = irpSp->Parameters.DeviceIoControl.IoControlCode; + ULONG outBufLen = irpSp->Parameters.DeviceIoControl.OutputBufferLength; + PVOID outBuf = Irp->AssociatedIrp.SystemBuffer; + + switch (ioControlCode) { + case IOCTL_FILE_RESTORE_START_MONITORING: + // 启动监控(可扩展:从输入缓冲区读取过滤条件) + status = STATUS_SUCCESS; + break; + + case IOCTL_FILE_RESTORE_STOP_MONITORING: + status = STATUS_SUCCESS; + break; + + case IOCTL_FILE_RESTORE_GET_STATS: + if (outBufLen >= sizeof(MONITOR_STATS)) { + PMONITOR_STATS stats = (PMONITOR_STATS)outBuf; + stats->TotalEvents = g_Context.TotalEvents; + stats->DroppedEvents = g_Context.DroppedEvents; + stats->BufferSize = SHARED_MEMORY_SIZE; + stats->UsedSize = g_Context.WriteIndex - g_Context.ReadIndex; + Irp->IoStatus.Information = sizeof(MONITOR_STATS); + } else { + status = STATUS_BUFFER_TOO_SMALL; + } + break; + + default: + status = STATUS_INVALID_DEVICE_REQUEST; + break; + } + + Irp->IoStatus.Status = status; + if (!NT_SUCCESS(status)) Irp->IoStatus.Information = 0; + IoCompleteRequest(Irp, IO_NO_INCREMENT); + return status; +} +``` + +### 5.8 用户态客户端 + +```cpp +// monitor_client.cpp (用户态,集成到 Filerestore_CLI) +#include +#include +#include + +struct DeleteEvent { + std::wstring FilePath; + uint64_t FileSize; + LARGE_INTEGER DeleteTime; + bool IsRecycled; + struct { uint64_t StartLCN; uint32_t ClusterCount; }; + std::vector LCNRanges; // 简化示意 +}; + +class FileRestoreMonitorClient { + HANDLE hDevice = INVALID_HANDLE_VALUE; + HANDLE hSharedMemory = NULL; + HANDLE hEvent = NULL; + PVOID pSharedBuffer = nullptr; + SIZE_T sharedMemorySize = 0; + ULONG readIndex = 0; + +public: + bool Connect() { + // 1. 打开驱动设备 + hDevice = CreateFileW( + L"\\\\.\\FileRestoreMon", + GENERIC_READ | GENERIC_WRITE, + 0, NULL, OPEN_EXISTING, 0, NULL + ); + if (hDevice == INVALID_HANDLE_VALUE) return false; + + // 2. 打开共享内存 + hSharedMemory = OpenFileMappingW( + FILE_MAP_READ, FALSE, + L"Local\\FileRestoreMonBuffer" + ); + if (!hSharedMemory) { + CloseHandle(hDevice); + return false; + } + + pSharedBuffer = MapViewOfFile( + hSharedMemory, FILE_MAP_READ, 0, 0, 0 + ); + + readIndex = 0; + return true; + } + + bool WaitForEvent(DWORD timeoutMs) { + return WaitForSingleObject(hEvent, timeoutMs) == WAIT_OBJECT_0; + } + + std::vector ReadEvents() { + std::vector events; + PUCHAR buffer = (PUCHAR)pSharedBuffer; + + while (readIndex < sharedMemorySize) { + auto* record = (PDELETE_EVENT_RECORD)(buffer + readIndex); + if (record->Magic != 0xDE1E7E54) break; + + DeleteEvent evt; + evt.FilePath = std::wstring( + (wchar_t*)((PUCHAR)record + record->FileNameOffset), + record->FileNameLength / sizeof(wchar_t) + ); + evt.FileSize = record->FileSize.QuadPart; + evt.DeleteTime = record->Timestamp; + evt.IsRecycled = (record->DeleteType == 1); + + // 提取 LCN 信息 + for (ULONG i = 0; i < record->LCNCount; i++) { + evt.LCNRanges.push_back({ + record->LCNEntries[i].StartLCN, + record->LCNEntries[i].ClusterCount + }); + } + + events.push_back(std::move(evt)); + readIndex += record->RecordSize; + } + + ResetEvent(hEvent); + return events; + } + + void PersistToFile(const std::wstring& path) { + // 将捕获的事件持久化到磁盘,供后续恢复使用 + // 格式与 MFT 快照兼容,便于统一查询 + } + + ~FileRestoreMonitorClient() { + if (pSharedBuffer) UnmapViewOfFile(pSharedBuffer); + if (hSharedMemory) CloseHandle(hSharedMemory); + if (hEvent) CloseHandle(hEvent); + if (hDevice != INVALID_HANDLE_VALUE) CloseHandle(hDevice); + } +}; +``` + +--- + +## 六、关键技术难点 + +| 难点 | 解决方案 | +|------|---------| +| 获取 LCN 需要 FltInstance | 从 `PCFLT_RELATED_OBJECTS FltObjects` 参数获取 `FltObjects->Instance` | +| 文件名编码 | NTFS 使用 UTF-16LE,内核中使用 `UNICODE_STRING`,用户态转 `std::wstring` | +| 回收站识别 | 检测 `FileRenameInformation` 且目标路径包含 `$Recycle.Bin` | +| 高并发写入 | Per-CPU 队列 + 自旋锁(或无锁环形缓冲区) | +| 内存泄漏防护 | 使用 `ExAllocatePoolWithTag` / `ExFreePoolWithTag` 严格配对,`DriverUnload` 中清理所有资源 | +| BSOD 防护 | `__try/__except` 包裹可能失败的操作,所有指针校验后再访问 | +| 常驻文件无 LCN | `FSCTL_GET_RETRIEVAL_POINTERS` 对常驻文件返回失败,需优雅处理 | + +--- + +## 七、两方案对比 + +| 维度 | MFT 快照 | 内核监控 | +|------|---------|---------| +| 开发成本 | 低(复用 MFTCache) | 高(全新开发) | +| 实时性 | 延迟(取决于快照/轮询间隔) | 实时(Pre-op 回调) | +| 捕获率 | 取决于快照频率 | ~100% | +| 大文件恢复提升 | 5% → 30-50% | 5% → 80%+ | +| 技术风险 | 低 | 中(BSOD、兼容性) | +| 部署复杂度 | 简单(纯用户态) | 需安装驱动 + 签名 | +| 现有基础 | MFTCache 可直接复用 | 全新开发 | +| 学习价值 | 低 | 很高(内核编程) | + +--- + +## 八、实施路径 + +### Phase 1:MFT 快照(优先,复用现有基础设施) + +``` +├── 复用 MFTCache/MFTReader 读取 MFT 元数据 +├── 添加快照持久化(二进制格式 + 索引)/ 加载 +├── USN 触发模式:轮询 USN Journal,检测到删除后抢读 MFT +├── 修改 carvepool 签名扫描接入快照信息 +└── 预计提升: 扫描速度 100x,大文件恢复率 5% → 30%+ +``` + +### Phase 2:内核监控(学习项目,独立开发) + +``` +├── 搭建 WDK 开发环境,配置测试签名 +├── 实现最小化微过滤驱动原型(仅捕获删除事件,打印日志) +├── 逐步添加 LCN 查询、共享内存、环形缓冲区 +├── 用户态客户端集成到 Filerestore_CLI +└── 预计提升: 实时删除捕获 ~100% +``` + +### Phase 3:混合方案(远期) + +``` +├── 检测驱动是否已安装/运行 +├── 有驱动 → 使用实时事件流 +├── 无驱动 → 降级到 MFT 快照模式 +└── 两种数据源统一存储格式,恢复逻辑无需区分来源 +``` + +--- + +*文档整理自项目讨论记录,2026-02-17* diff --git a/dev_notes/monitor_daemon_implementation.md b/dev_notes/monitor_daemon_implementation.md new file mode 100644 index 0000000..897ac5a --- /dev/null +++ b/dev_notes/monitor_daemon_implementation.md @@ -0,0 +1,165 @@ +# Monitor Daemon + TUI Dashboard 实现总结 + +## 概述 + +本次实现将 `monitor` 命令从进程内 `std::thread` 模式升级为独立守护进程架构。CLI 退出后守护进程继续运行,通过共享内存 IPC 实现状态查询,支持开机自启动和 TUI 实时面板。 + +## 架构 + +``` + 共享内存 (MonitorSharedState) + ┌─────────────────────────┐ + CLI/TUI 进程 │ magic, version, pid │ 守护进程 + ┌──────────┐ 读取 │ 统计计数器 │ ┌──────────────┐ + │ monitor │◄────────►│ 环形缓冲区(最近事件) │◄─────│ RunDaemonMain│ + │ status │ │ daemonRunning 标志 │ 写入 │ │ + └──────────┘ └─────────────────────────┘ │ UsnDelete │ + │ Monitor │ + Named Event └──────┬───────┘ + ┌──────────┐ SetEvent │ + │ StopEvent│─────────────────────────────────────────────────►│ WaitFor + └──────────┘ │ SingleObject + ▼ + Named Mutex 退出主循环 + ┌──────────┐ + │ Mutex │ 单例保证(同一驱动器只有一个守护进程) + └──────────┘ +``` + +## 新增/修改文件清单 + +### 新建文件 + +| 文件 | 说明 | +|------|------| +| `src/fileRestore/MonitorDaemon.h` | 共享内存 POD 结构体 + MonitorDaemon 类声明 | +| `src/fileRestore/MonitorDaemon.cpp` | 守护进程启停、共享内存 IPC、注册表自启动、RunDaemonMain 主循环 | + +### 修改文件 + +| 文件 | 变更内容 | +|------|----------| +| `src/fileRestore/UsnDeleteMonitor.h` | 新增 `EventCallback` 类型和 `SetEventCallback()` | +| `src/fileRestore/UsnDeleteMonitor.cpp` | `HandleDeleteEvent` 成功/失败路径末尾调用回调 | +| `src/core/Main.cpp` | 新增 `--monitor-daemon ` 参数解析,守护进程入口 | +| `src/commands/UsnRecoverCommands.cpp` | 重写 MonitorCommand,删除 `g_monitor`,改为通过 MonitorDaemon IPC | +| `src/tui/TuiApp.h` | ViewMode 枚举新增 `Monitor`,新增 `monitorDrive_` 成员 | +| `src/tui/TuiApp.cpp` | 菜单新增 "USN Delete Monitor",Monitor Dashboard 渲染和键盘处理 | +| `src/tui/CommandHelper.cpp` | 新增 `monitor`/`snapshot`/`snapshotquery` 命令元数据 | +| `Filerestore_CLI.vcxproj` | 添加 MonitorDaemon.cpp/.h | +| `Filerestore_CLI.vcxproj.filters` | 添加 MonitorDaemon.cpp/.h 到筛选器 | + +## 核心数据结构 + +### MonitorSharedState(共享内存,固定大小 POD) + +```cpp +#pragma pack(push, 8) +struct MonitorSharedState { + DWORD magic, version; // 0x46524D44, 1 + char driveLetter; // 监控的驱动器 + DWORD pid; // 守护进程 PID + FILETIME startTime, lastUpdate; + volatile LONGLONG totalEvents, capturedCount, missedCount, skippedCount, snapshotCount; + DWORD pollIntervalMs; + volatile LONG recentEventCount, recentEventHead; + MonitorRecentEvent recentEvents[16]; // 环形缓冲区 + volatile LONG daemonRunning, autoStartEnabled; +}; +#pragma pack(pop) +``` + +### 命名对象约定 + +| 对象 | 命名格式 | 用途 | +|------|----------|------| +| Mutex | `Global\FileRestoreMonitor_D` | 单例保证 | +| FileMapping | `Global\FileRestoreMonitor_D_Mem` | 共享内存 | +| Event | `Global\FileRestoreMonitor_D_Stop` | 停止信号(手动复位) | + +## 命令接口 + +``` +monitor start 启动守护进程(CREATE_NO_WINDOW + DETACHED_PROCESS) +monitor stop 发送停止事件,等待守护进程退出 +monitor status 读取共享内存,显示统计和最近事件 +monitor autostart 写入 HKCU\...\Run 注册表键 +monitor unautostart 删除注册表键 +``` + +## TUI Monitor Dashboard + +### 菜单入口 +主菜单第 5 项 "USN Delete Monitor",选中后进入 `ViewMode::Monitor`。 + +### 面板布局 +- 头部:驱动器、PID、运行状态、自启动状态、启动时间 +- 统计区:Events / Captured / Missed / Skipped / Snapshots +- 事件表:最近 8 条删除事件(MFT#、大小、状态、文件名) +- 底部:快捷键提示 + +### 键盘快捷键 +| 按键 | 功能 | +|------|------| +| S | 启动守护进程 | +| T | 停止守护进程 | +| A | 切换自启动开关 | +| Esc | 返回主菜单 | + +Dashboard 每 200ms 自动刷新(复用异步刷新线程)。 + +## 守护进程生命周期 + +### 启动流程 (`StartDaemon`) +1. `OpenMutexW` 检查是否已有实例 +2. `GetModuleFileNameW` 获取自身 exe 路径 +3. `CreateProcessW` 以 `CREATE_NO_WINDOW | DETACHED_PROCESS` 启动子进程 +4. 命令行:`"" --monitor-daemon D` +5. 轮询最多 3 秒等待 Mutex 出现 + +### 守护进程主循环 (`RunDaemonMain`) +1. `CreateMutexW` 获取单例锁 +2. `CreateEventW` 创建停止事件(手动复位) +3. `CreateFileMappingW` + `MapViewOfFile` 创建共享内存 +4. 初始化 `MonitorSharedState` +5. 创建 `UsnDeleteMonitor`,设置 `EventCallback`(写入环形缓冲区) +6. `CaptureExistingDeleted(24)` 捕获已有删除记录 +7. `Start()` 启动后台轮询 +8. 主循环:`WaitForSingleObject(stopEvent, 500)` + 同步统计到共享内存 +9. 收到停止信号后:`Stop()` + 清理所有 Handle + +### 停止流程 (`StopDaemon`) +1. `OpenEventW` 打开停止事件 +2. `SetEvent` 通知守护进程 +3. 轮询最多 5 秒等待 Mutex 消失 + +## 自启动注册表 + +- 键路径:`HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Run` +- 值名称:`FileRestoreMonitor_D`(D 为驱动器字母) +- 值数据:`"" --monitor-daemon D` +- API:`RegOpenKeyExW` / `RegSetValueExW` / `RegDeleteValueW` / `RegQueryValueExW` + +## 构建验证 + +Release x64 编译通过,0 error,0 warning。 + +``` +Filerestore_CLI.vcxproj -> D:\Users\21405\source\repos\Filerestore_CLI\x64\Release\Filerestore_CLI.exe +``` + +## 功能验证步骤 + +```bash +# CLI 验证 +Filerestore_CLI.exe --cmd "monitor D start" # 启动守护进程 +tasklist | findstr Filerestore # 查看进程 +Filerestore_CLI.exe --cmd "monitor D status" # 查看状态 +Filerestore_CLI.exe --cmd "monitor D autostart" # 启用自启动 +Filerestore_CLI.exe --cmd "monitor D stop" # 停止守护进程 + +# TUI 验证 +Filerestore_CLI.exe --tui # 进入 TUI +# 选择 "USN Delete Monitor" → Dashboard 面板 +# S=启动 T=停止 A=切换自启动 Esc=返回 +```