Monitor Module (FsMinifilterMonitor)
The user-mode monitor acts as a bridge between the kernel driver and the scanner. It receives file system event messages from the minifilter via a filter communication port and forwards scan-worthy events to the scanner via a named pipe.
Related: Communication Architecture ยท Kernel Driver Module ยท Scanner Module ยท Kernel โ User Interface
1. Source Files
| File | Purpose |
|---|---|
FsMinifilterMonitor/main.cpp |
Single-file application: port connection, message loop, deduplication, pipe forwarding |
2. Module Architecture
flowchart TB
subgraph Monitor["FsMinifilterMonitor.exe"]
direction TB
subgraph Init["Initialization"]
Connect["FilterConnectCommunicationPort()\nโ port handle"]
end
subgraph Loop["Main Message Loop"]
GetMsg["FilterGetMessage()\n(blocking)"]
ExtFilter["IsExecutableOrDll()"]
Dedup["ShouldSendScanRequest()\n(dedup cache)"]
BuildReq["Build SCAN_REQUEST"]
SendPipe["SubmitScanRequestToScanner()\n(named pipe)"]
end
subgraph PipeMgmt["Pipe Management"]
Ensure["EnsurePipeConnection()\n(lazy + retry)"]
Write["WriteFile(pipe, SCAN_REQUEST)"]
Recover["Reset handle on failure"]
end
Connect --> GetMsg
GetMsg --> ExtFilter --> Dedup --> BuildReq --> SendPipe
SendPipe --> Ensure --> Write
Write -.-> Recover
end
style Init fill:#3a0ca3,color:#fff
style Loop fill:#4361ee,color:#fff
style PipeMgmt fill:#7209b7,color:#fff
3. Main Loop
flowchart TD
Start["wmain()"]
Start --> ConnPort["FilterConnectCommunicationPort(\n '\\FsMinifilterPort')"]
ConnPort --> Failed{"FAILED(hr)?"}
Failed -->|Yes| Exit1["Print error, exit(1)"]
Failed -->|No| MsgLoop["Enter message loop"]
MsgLoop --> GetMsg["FilterGetMessage(\n port, &message)"]
GetMsg --> Succeeded{"SUCCEEDED(hr)?"}
Succeeded -->|No| LostConn{"ERROR_INVALID_HANDLE?"}
LostConn -->|Yes| Exit2["Connection lost, exit"]
LostConn -->|No| MsgLoop
Succeeded -->|Yes| IsExe{"IsExecutableOrDll(\n message.FilePath)?"}
IsExe -->|No| MsgLoop
IsExe -->|Yes| ShouldSend{"ShouldSendScanRequest(\n message.FilePath)?"}
ShouldSend -->|No| MsgLoop
ShouldSend -->|Yes| Build["Build SCAN_REQUEST:\n filePath = message.FilePath\n pid = message.ProcessId\n timestamp = now"]
Build --> Submit["SubmitScanRequestToScanner(&req)"]
Submit --> MsgLoop
style Exit1 fill:#e63946,color:#fff
style Exit2 fill:#e63946,color:#fff
style Build fill:#2d6a4f,color:#fff
4. Key Functions
4.1 IsExecutableOrDll
BOOL IsExecutableOrDll(const WCHAR* filePath)
Quick case-insensitive extension check using _wcsicmp on the last 4 characters. Returns TRUE for .exe and .dll.
Note: This duplicates the kernel-side IsTargetExtension() check. Both are needed because the kernel sends all message types (including DELETE which applies to all files), and the monitor adds a second layer of filtering.
4.2 ShouldSendScanRequest (Deduplication)
BOOL ShouldSendScanRequest(const WCHAR* filePath)
flowchart TD
Input["filePath"]
Input --> Now["now = GetTickCount64()"]
Now --> Cleanup{"now - lastCleanup\n> 30,000ms?"}
Cleanup -->|Yes| Evict["Evict entries > 5s old\nfrom cache"]
Cleanup -->|No| Lookup
Evict --> Lookup
Lookup --> Found{"filePath in cache\nAND age < 5,000ms?"}
Found -->|Yes| RetFalse["Return FALSE\n(duplicate)"]
Found -->|No| Update["cache[filePath] = now"]
Update --> RetTrue["Return TRUE\n(send it)"]
style RetFalse fill:#6c757d,color:#fff
style RetTrue fill:#2d6a4f,color:#fff
Data structure: std::unordered_map<std::wstring, ULONGLONG> mapping file paths to the last-seen timestamp.
| Parameter | Value |
|---|---|
| Cooldown window | 5,000ms |
| Cache cleanup interval | 30,000ms |
| Cleanup strategy | Iterate all entries, erase those older than cooldown |
4.3 EnsurePipeConnection
BOOL EnsurePipeConnection()
Lazy connection with retry logic:
- If
g_hPipe != INVALID_HANDLE_VALUE: returnTRUE(already connected) - Otherwise: retry
CreateFile(PIPE_NAME)up to 20 times with 100ms sleep between attempts - Returns
FALSEif all retries fail
4.4 SubmitScanRequestToScanner
BOOL SubmitScanRequestToScanner(const SCAN_REQUEST* req)
- Call
EnsurePipeConnection()to ensure pipe is ready WriteFile(g_hPipe, req, sizeof(*req))- On write failure: close handle, set
g_hPipe = INVALID_HANDLE_VALUEfor reconnection on next call
5. Message Transformation
The monitor transforms kernel messages into scan requests:
flowchart LR
subgraph KernelMsg["FILTER_MESSAGE (from kernel)"]
FMH["FILTER_MESSAGE_HEADER"]
MM["MINIFILTER_MESSAGE {\n MessageType: ULONG\n ProcessId: ULONG\n FilePath: WCHAR[520]\n}"]
end
subgraph ScanReq["SCAN_REQUEST (to scanner)"]
SR["SCAN_REQUEST {\n filePath: WCHAR[260]\n pid: DWORD\n timestamp: FILETIME\n}"]
end
KernelMsg -->|"Extract + transform"| ScanReq
style KernelMsg fill:#4361ee,color:#fff
style ScanReq fill:#2d6a4f,color:#fff
Transformation steps:
filePathโmessage.Message.FilePath(truncated from 520 to 260 chars)pidโmessage.Message.ProcessIdtimestampโGetSystemTimeAsFileTime()(added by monitor, not kernel)
6. Error Handling
| Scenario | Behavior |
|---|---|
| Cannot connect to filter port | Print error, exit with code 1 |
FilterGetMessage returns ERROR_INVALID_HANDLE |
Driver disconnected, exit message loop |
FilterGetMessage returns other error |
Retry (continue loop) |
| Cannot connect to scanner pipe | Print warning, skip scan request |
| Pipe write fails | Close handle, reconnect on next request |
Next Steps
- See what the scanner does with these requests: Scanner Module
- Understand the kernel messages: Kernel โ User Interface
- See the message format details: Data Types Reference