Communication Architecture
This document describes the two inter-process communication channels that connect the kernel driver, user-mode monitor, and scanner: the Filter Communication Port and the Named Pipe.
Related: System Overview ยท Driver Architecture ยท Kernel โ User Interface ยท Monitor Module
1. Communication Topology
flowchart LR
subgraph Ring0["Kernel (Ring 0)"]
Driver["WindowsFileSystemMinifilter.sys"]
ServerPort["Server Port\n(\\FsMinifilterPort)"]
Driver --> ServerPort
end
subgraph Ring3["User (Ring 3)"]
Monitor["FsMinifilterMonitor.exe"]
ClientPort["Client Port Handle"]
Pipe["Named Pipe\n(\\\\.\\pipe\\ScannerPipe)"]
Scanner["Scanner.exe"]
Monitor --> ClientPort
Monitor --> Pipe
Pipe --> Scanner
end
ServerPort -.->|"FltSendMessage\n(MINIFILTER_MESSAGE)"| ClientPort
ClientPort -.->|"FilterGetMessage"| Monitor
style Ring0 fill:#7b2cbf,color:#fff
style Ring3 fill:#2d6a4f,color:#fff
2. Channel 1: Filter Communication Port
Overview
The kernel driver creates a filter communication port during DriverEntry. The monitor connects to this port using FilterConnectCommunicationPort. The kernel sends unidirectional messages (driver โ monitor); no replies are expected.
Lifecycle Sequence
sequenceDiagram
participant D as Driver (Kernel)
participant FM as Filter Manager
participant M as Monitor (User)
Note over D: DriverEntry()
D->>FM: FltBuildDefaultSecurityDescriptor()
D->>FM: FltCreateCommunicationPort("\\FsMinifilterPort")
FM-->>D: g_serverPort handle
Note over M: wmain()
M->>FM: FilterConnectCommunicationPort("\\FsMinifilterPort")
FM->>D: PortConnectCallback()
D->>D: g_clientPort = ClientPort
D->>D: g_clientProcessId = PsGetCurrentProcessId()
FM-->>M: port handle
loop File System Events
D->>FM: FltSendMessage(MINIFILTER_MESSAGE)
FM->>M: FilterGetMessage() returns
M->>M: Process message
end
Note over M: Process exit or Ctrl+C
M->>FM: CloseHandle(port)
FM->>D: PortDisconnectCallback()
D->>D: g_clientPort = NULL
D->>D: g_clientProcessId = 0
Port Configuration
| Parameter | Value | Rationale |
|---|---|---|
| Port Name | \FsMinifilterPort |
NT object namespace path |
| Security | FLT_PORT_ALL_ACCESS |
Default security descriptor |
| Max Connections | 1 |
Single monitor instance at a time |
| Message Direction | Driver โ Monitor | No MessageNotifyCallback registered |
| Timeout | 100ms (-1000000 in 100ns units) |
Non-blocking; timeout errors are silently ignored |
Message Format
classDiagram
class MINIFILTER_MESSAGE {
+ULONG MessageType
+ULONG ProcessId
+WCHAR FilePath[520]
}
class FILTER_MESSAGE {
+FILTER_MESSAGE_HEADER Header
+MINIFILTER_MESSAGE Message
}
FILTER_MESSAGE --> MINIFILTER_MESSAGE : contains
The kernel sends raw MINIFILTER_MESSAGE via FltSendMessage. The user-mode monitor receives it wrapped in FILTER_MESSAGE which includes the FILTER_MESSAGE_HEADER prepended by Filter Manager.
| MessageType Constant | Value | Meaning |
|---|---|---|
MSG_TYPE_FILE_CREATE |
1 | File/directory created or opened |
MSG_TYPE_FILE_READ |
2 | File data was read |
MSG_TYPE_FILE_MODIFY |
3 | File data was written/modified |
MSG_TYPE_FILE_DELETE |
4 | File was deleted |
See Data Types Reference for complete struct layouts.
3. Channel 2: Named Pipe (Monitor โ Scanner)
Overview
The monitor forwards scan-worthy events to the scanner via a Win32 named pipe. The scanner creates the pipe server; the monitor connects as a client.
Lifecycle Sequence
sequenceDiagram
participant S as Scanner.exe
participant M as Monitor.exe
Note over S: wmain() โ InitializeScanner()
S->>S: CreateNamedPipe("\\\\.\\pipe\\ScannerPipe")
S->>S: ConnectNamedPipe() โ blocks
Note over M: Receives first .exe/.dll event
M->>S: CreateFile(PIPE_NAME) โ connects
S-->>S: ConnectNamedPipe returns
loop Scan Requests
M->>S: WriteFile(SCAN_REQUEST)
S->>S: ReadFile() โ Enqueue โ ScanWorker
end
Note over M: Process exits
M->>S: Pipe disconnected
S-->>S: ReadFile returns FALSE
Pipe Configuration
| Parameter | Value |
|---|---|
| Pipe Name | \\.\pipe\ScannerPipe |
| Access | PIPE_ACCESS_DUPLEX |
| Mode | PIPE_TYPE_MESSAGE \| PIPE_READMODE_MESSAGE \| PIPE_WAIT |
| Max Instances | 1 |
| Buffer Sizes | 1024 bytes (in/out) |
Message Format
The pipe carries raw SCAN_REQUEST structs:
classDiagram
class SCAN_REQUEST {
+WCHAR filePath[260]
+DWORD pid
+FILETIME timestamp
}
Connection Resilience
The monitor implements a retry loop for pipe connection (EnsurePipeConnection):
- Up to 20 retries with 100ms intervals
- If a pipe write fails, the handle is closed and re-established on the next request
- This allows the scanner to be started before or after the monitor
4. Deduplication Layer
Between receiving kernel messages and forwarding to the scanner, the monitor applies deduplication to avoid flooding the scanner with repeated events for the same file.
flowchart TD
MSG["Kernel Message Received"]
MSG --> IsExe{"Is .exe or .dll?"}
IsExe -->|No| Drop1["Drop"]
IsExe -->|Yes| Dedup{"Seen within\nlast 5 seconds?"}
Dedup -->|Yes| Drop2["Drop (Deduplicated)"]
Dedup -->|No| Cache["Update Cache\n+ Forward to Scanner"]
subgraph CacheMgmt["Cache Management"]
direction TB
Cleanup["Periodic Cleanup\n(every 30s)"]
Evict["Evict entries\n> 5s old"]
Cleanup --> Evict
end
style Drop1 fill:#6c757d,color:#fff
style Drop2 fill:#6c757d,color:#fff
style Cache fill:#2d6a4f,color:#fff
| Parameter | Value |
|---|---|
| Cooldown Window | 5,000ms |
| Cleanup Interval | 30,000ms |
| Data Structure | std::unordered_map<wstring, ULONGLONG> |
5. End-to-End Message Flow
flowchart LR
A["App writes\nmalware.exe"]
--> B["NTFS I/O"]
--> C["Filter Manager"]
--> D["PreOperationWrite"]
--> E["SendMessageToUserMode\n(MSG_TYPE_FILE_MODIFY)"]
--> F["FilterGetMessage\n(Monitor)"]
--> G["Dedup Check"]
--> H["WriteFile\n(Named Pipe)"]
--> I["ReadFile\n(Scanner)"]
--> J["Enqueue"]
--> K["ScanWorker\nDequeue + Analyze"]
style A fill:#e63946,color:#fff
style K fill:#2d6a4f,color:#fff
Next Steps
- See the full message structure definitions: Data Types Reference
- See how the monitor processes messages: Monitor Module
- Understand the scan pipeline after queue: Scan Pipeline Flow