Scanner Module
The scanner is the core analysis engine. It receives scan requests (via named pipe or manual input), queues them, and processes them through a multi-stage pipeline: PE parsing โ feature extraction โ classification โ policy enforcement.
Related: Monitor Module ยท PE Parser Module ยท ML Classifier ยท Policy Engine ยท Scan Pipeline Flow
1. Source Files
| File | Purpose |
|---|---|
scanner/scanner.cpp |
Entry point (wmain), worker thread, pipe server, single-scan mode |
scanner/scanner_api.h |
Public API: InitializeScanner, SubmitScanRequest, ShutdownScanner |
scanner/queue.cpp/h |
Thread-safe bounded scan queue |
2. Module Architecture
flowchart TB
subgraph Scanner["Scanner.exe"]
direction TB
subgraph Entry["Entry Point (wmain)"]
Init["InitializeScanner()"]
Mode{"Select Mode?"}
Full["Mode 1: RunPipeServer()\n(blocking)"]
Single["Mode 2: RunSingleFileScan()\n(interactive)"]
Shut["ShutdownScanner()"]
end
subgraph QueueSys["Queue System"]
Q["std::queue<SCAN_REQUEST>\n(max 10,000)"]
Mtx["std::mutex"]
Evt["Win32 Event\n(auto-reset)"]
Enq["Enqueue()"]
Deq["Dequeue()"]
end
subgraph Worker["ScanWorker Thread"]
WLoop["Dequeue loop"]
Norm["NormalizePath()"]
Valid["Validate file"]
Parse["SafeParsePE_SEH()"]
Feat["ExtractFeatures()"]
Class["Classify()"]
Policy["ApplyPolicy()"]
end
Init --> Mode
Mode -->|1| Full
Mode -->|2| Single
Full --> Enq
Single --> Enq
Enq --> Q
Q --> Deq --> WLoop
WLoop --> Norm --> Valid --> Parse --> Feat --> Class --> Policy
end
style Entry fill:#3a0ca3,color:#fff
style QueueSys fill:#e07a5f,color:#fff
style Worker fill:#2d6a4f,color:#fff
3. Scan Modes
Mode 1: Full System Scan (Pipe Server)
sequenceDiagram
participant M as Monitor
participant P as Named Pipe
participant S as Scanner.wmain
S->>P: CreateNamedPipe("\\.\pipe\ScannerPipe")
S->>P: ConnectNamedPipe() โ blocks
M->>P: CreateFile() โ connects
P-->>S: Client connected
loop Until disconnect
M->>P: WriteFile(SCAN_REQUEST)
P->>S: ReadFile(&req)
S->>S: SubmitScanRequest(&req)
end
Note over S: ReadFile returns FALSE
S->>S: CloseHandle(hPipe)
Pipe configuration:
- Name:
\\.\pipe\ScannerPipe - Access:
PIPE_ACCESS_DUPLEX - Mode:
PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT - Instances: 1
- Buffer: 1024 bytes each direction
Mode 2: Single File Scan (Interactive)
flowchart TD
Start["RunSingleFileScan()"]
Start --> Prompt["Print prompt '> '"]
Prompt --> Read["fgetws(path)"]
Read --> Empty{"Empty input?"}
Empty -->|Yes| Exit["Exit scan mode"]
Empty -->|No| Build["Build SCAN_REQUEST\n(filePath = input)"]
Build --> Submit["SubmitScanRequest(&req)"]
Submit --> Sleep["Sleep(100ms)\n(let worker process)"]
Sleep --> Prompt
style Exit fill:#6c757d,color:#fff
4. Worker Thread (ScanWorker)
The ScanWorker is a dedicated thread created by InitializeScanner() that runs for the lifetime of the process.
flowchart TD
Start["ScanWorker started"]
Start --> Loop["while (g_Running)"]
Loop --> TryDeq["Dequeue(&req)"]
TryDeq --> Got{"Success?"}
Got -->|No| Wait["WaitForQueueSignal()\n(500ms timeout)"]
Wait --> Loop
Got -->|Yes| Norm["NormalizePath(req.filePath)"]
Norm --> NormOK{"Normalized?"}
NormOK -->|No| Loop
NormOK -->|Yes| Attrs["GetFileAttributesW()"]
Attrs --> AttrCheck{"Valid file?\nNot directory?"}
AttrCheck -->|No| Loop
AttrCheck -->|Yes| SelfCheck{"Is Scanner.exe or\nFsMinifilterMonitor.exe?"}
SelfCheck -->|Yes| Loop
SelfCheck -->|No| Parse["SafeParsePE_SEH()"]
Parse --> ParseOK{"Parsed?"}
ParseOK -->|No| Loop
ParseOK -->|Yes| Extract["ExtractFeatures()"]
Extract --> Classify["Classify()"]
Classify --> Apply["ApplyPolicy()"]
Apply --> Loop
style Parse fill:#4361ee,color:#fff
style Classify fill:#e07a5f,color:#fff
style Apply fill:#2d6a4f,color:#fff
5. Queue System
Thread-Safe Queue Implementation
| Component | Type | Purpose |
|---|---|---|
g_Queue |
std::queue<SCAN_REQUEST> |
FIFO request buffer |
g_Mutex |
std::mutex |
Mutual exclusion for queue access |
g_QueueEvent |
HANDLE (Win32 auto-reset event) |
Signal worker thread when items are enqueued |
MAX_QUEUE_SIZE |
10,000 |
Backpressure limit |
API
| Function | Description |
|---|---|
InitializeQueue() |
Creates the auto-reset event |
Enqueue(const SCAN_REQUEST*) |
Adds request; drops oldest if at capacity |
Dequeue(SCAN_REQUEST*) |
Removes front item; returns FALSE if empty |
WaitForQueueSignal() |
Blocks up to 500ms on g_QueueEvent |
DestroyQueue() |
Drains queue, closes event handle |
Backpressure Strategy
flowchart LR
New["New Request"] --> Lock["Lock mutex"]
Lock --> Check{"size >= 10,000?"}
Check -->|Yes| Drop["Pop oldest\n(tail-drop)"]
Check -->|No| Push["Push back"]
Drop --> Push
Push --> Signal["SetEvent"]
Signal --> Unlock["Unlock mutex"]
6. Lifecycle Management
sequenceDiagram
participant Main as wmain()
participant Init as InitializeScanner()
participant Q as Queue
participant W as ScanWorker
participant Shut as ShutdownScanner()
Main->>Init: InitializeScanner()
Init->>Q: InitializeQueue()
Note over Q: Creates auto-reset event
Init->>W: CreateThread(ScanWorker)
Note over W: Worker starts dequeue loop
Main->>Main: RunPipeServer() or RunSingleFileScan()
Note over Main: Blocks until done
Main->>Shut: ShutdownScanner()
Shut->>Shut: g_Running = FALSE
Shut->>W: WaitForSingleObject(INFINITE)
Note over W: Worker exits loop
Shut->>W: CloseHandle(g_WorkerThread)
Shut->>Q: DestroyQueue()
7. Global State
| Variable | Type | Scope | Lifetime |
|---|---|---|---|
g_WorkerThread |
HANDLE |
scanner.cpp (static) |
InitializeScanner โ ShutdownScanner |
g_Running |
volatile BOOL |
scanner.cpp (static) |
Set FALSE during shutdown |
g_Queue |
std::queue |
queue.cpp (static) |
InitializeQueue โ DestroyQueue |
g_Mutex |
std::mutex |
queue.cpp (static) |
Process lifetime |
g_QueueEvent |
HANDLE |
queue.cpp (static) |
InitializeQueue โ DestroyQueue |
Next Steps
- Deep-dive into PE parsing: PE Parser Module
- See the classification logic: ML Classifier Module
- Full pipeline walkthrough: Scan Pipeline Flow