Read this in: English · Deutsch · 中文 · हिन्दी · Español · Русский
Edition: Community. Local detection and local response on a single server. What this build contains and what it does not: README-EDITION.md.
RDPAudit is a production-ready Windows platform for monitoring RDP activity and responding to RDP-based threats.
It consists of a Windows Service (background event collection, 21 alert rules, automatic blocking) and a WinForms Configurator (GUI for management, statistics, diagnostics, and policy configuration).
- 1. Product Overview
- 2. System Requirements
- 3. Solution Layout
- 4. Quick Start
- 5. RdpAudit.Service — Monitoring Service
- 6. RdpAudit.Configurator — GUI
- 6.14. Session Map Tab
- 6.15. Notifications Tab
- 6.16. Web Access Tab
- 7. RdpAudit.Core — Shared Library
- 8. External Integrations
- 9. Auto-Blocking Model
- 10. Data Maintenance
- 11. Security and Auditing
- 12. Configuration
- 13. Debugging and Diagnostics
- 14. Build and Test
- 15. Troubleshooting
- 16. Roadmap (v2.0 → v3.0)
- 17. License and Author
RDPAudit is designed to provide continuous monitoring of RDP activity on Windows servers and workstations. The product runs in the background as a system service, even when no interactive user is logged on, records connection attempts, authentication events, and high-value system changes, evaluates them through 21 security rules, and can automatically block attackers through Windows Firewall and/or MikroTik RouterOS.
Typical use cases:
- Protecting Internet-exposed RDP endpoints from brute-force attacks
- Supporting SOC2, ISO 27001, and internal audit requirements for privileged access
- Detecting account compromise patterns such as a successful logon after repeated failures
- Identifying accessibility binary backdoors such as Sticky Keys and Utilman abuse
- Detecting attacks against LSA/LSASS, Kerberos spraying, and RDP port changes
- Integrating with MikroTik so malicious IPs can be blocked at the network edge
┌──────────────────────────────────────────────────────────────────┐
│ Windows Event Log │
│ Security │ TerminalServices-RemoteConnectionManager │ LocalSession│
└───────────────────────────┬──────────────────────────────────────┘
│ EventLogWatcher
▼
┌──────────────────────────────────────────────────────────────────┐
│ RdpAudit.Service (Windows Service) │
│ │
│ EventCollectorHostedWorker ──► EventProcessorWorker │
│ │ │
│ ▼ │
│ AlertWorker ──► 21 Alert Rules │
│ │ │
│ ▼ │
│ FirewallAutoBlockWorker │
│ (Windows FW + MikroTik REST) │
│ │ │
│ ▼ │
│ SQLite (WAL, %ProgramData%\RdpAudit) │
│ │ │
│ Named Pipe IPC Server │
└───────────────────────────────────┬──────────────────────────────┘
│ MessagePack over NamedPipe
▼
┌──────────────────────────────────────────────────────────────────┐
│ RdpAudit.Configurator (WinForms GUI) │
│ │
│ Overview │ Prerequisites │ AuditPolicy │ Service │ Settings │
│ LiveEvents │ Firewall │ AttackStatistics │ RemoteRdpClients │
│ AbuseIPDB │ MikroTik │ Logs │ Diagnostics │
└──────────────────────────────────────────────────────────────────┘
- Real-time monitoring through
EventLogWatchersubscriptions instead of polling - 21 alert rules covering brute force, LSASS attacks, Kerberos spraying, suspicious policy changes, and more
- Automatic blocking using per-IP Windows Firewall rules with manual reversal support
- MikroTik integration through RouterOS v7 REST APIs with DPAPI-protected credentials
- AbuseIPDB reporting with local deduplication and rate-limit awareness
- Threat-intel reputation checks (Backlog #5) - background AbuseIPDB /api/v2/check lookups with a disk cache and a High-risk mark on Attack Statistics and Live Events
- Threat analytics aggregated by IP, country, time range, and calculated threat level
- Session-to-IP correlation for mapping RDP sessions back to source addresses
- Retention pruning for long-term stability and reduced database growth
- Backup and restore for configuration state, with no plaintext secrets included
- Secure Named Pipe IPC restricted to
BUILTIN\Administrators - Read-only HTTPS REST API (optional, off by default) with bearer-token auth, per-IP rate limiting and an OpenAPI descriptor
- SIEM export (CEF / LEEF 2.0 / NDJSON) of per-IP events and correlated alerts from the Live Events and Attack Statistics pages
| Component | Requirement |
|---|---|
| OS | Windows 10 or Windows Server 2016 and newer (x64) |
| .NET Runtime | .NET 8.0 for Windows (x64) |
| Privileges | Local Administrator for service installation and system configuration |
| Disk | 200 MB for binaries plus room for the event database |
| Memory | 64 MB working set for the service under normal load |
RDPAudit depends on Windows Security Auditing to receive the events it analyzes. For correct operation, the following audit categories must be enabled:
| Audit Category | Subcategory GUID | Purpose |
|---|---|---|
| Logon / Logoff | {0CCE9215-69AE-11D9-BED3-505054503030} |
Events 4624, 4625, 4634 |
| Account Logon | {0CCE9240-69AE-11D9-BED3-505054503030} |
Kerberos events |
| Process Creation | {0CCE922B-69AE-11D9-BED3-505054503030} |
Event 4688 and process visibility |
| Object Access | {0CCE9217-69AE-11D9-BED3-505054503030} |
SACL-backed events such as 4656 and 4663 |
| Policy Change | {0CCE922F-69AE-11D9-BED3-505054503030} |
Firewall and policy change visibility |
The Configurator applies these settings automatically by calling auditpol.exe with GUID-based identifiers, which makes the process locale-independent.
| Project | Type | Responsibility |
|---|---|---|
RdpAudit.Core |
Class Library (net8.0-windows) |
Entities, EF Core DbContext, migrations, MessagePack IPC contracts, shared helpers |
RdpAudit.Service |
Worker Service (net8.0-windows, win-x64) |
Event collection, normalization, alert evaluation, auto-blocking, IPC server |
RdpAudit.Configurator |
WinForms App (net8.0-windows) |
GUI, IPC client, prerequisite checks, service control, configuration workflows |
RdpAudit.Web |
ASP.NET Core App (net8.0-windows) |
IIS-hosted read-only web dashboard and loopback YARP proxy: bearer injection from the DPAPI vault, security headers, pinned backend TLS |
RdpAudit.Core.Tests |
xUnit | Core unit tests |
RdpAudit.Service.Tests |
xUnit | Alert rule tests, threshold validation, whitelist coverage, allocation checks |
RdpAudit.Web.Tests |
xUnit | Web proxy integration tests: host filter, bearer injection, static CSP/DOM contract |
RdpAudit.Benchmarks |
BenchmarkDotNet | Benchmark coverage for hot paths |
RdpAudit.sln
├── src/
│ ├── RdpAudit.Core/
│ │ ├── Models/ — Entities, enums, projections
│ │ ├── Data/ — AppDbContext, EF Core migrations
│ │ ├── IPC/ — MessagePack request/response contracts
│ │ └── Services/ — Shared scoring, formatting, and support services
│ ├── RdpAudit.Service/
│ │ ├── Workers/ — BackgroundService workers
│ │ ├── Alerts/ — 21 alert rules
│ │ ├── Firewall/ — Windows Firewall and MikroTik providers
│ │ ├── Collectors/ — Event source wrappers
│ │ └── Program.cs — DI, Serilog, host wiring
│ ├── RdpAudit.Web/
│ │ ├── wwwroot/ - Static dashboard (index.html + assets)
│ │ ├── Proxy/ - Loopback YARP proxy pipeline and security middleware
│ │ ├── Config/ - WebUiProxyOptions
│ │ └── Program.cs - Fail-fast WebUI proxy host
│ └── RdpAudit.Configurator/
│ ├── Forms/ — 22 GUI pages
│ ├── IPC/ — Named Pipe client
│ └── Program.cs — Elevation, startup, UI bootstrap
├── tests/
│ ├── RdpAudit.Core.Tests/
│ ├── RdpAudit.Service.Tests/
│ ├── RdpAudit.Web.Tests/
│ └── RdpAudit.Benchmarks/
├── docs/
│ ├── 90-windows-validation.md
│ └── 91-troubleshooting.md
└── publish.ps1
git clone https://github.com/paulmann/RDPAudit.git
cd RDPAudit
dotnet build RdpAudit.sln -c Release# Publish binaries
./publish.ps1
# Copy the service payload into Program Files
Copy-Item -Recurse publish/Service "$env:ProgramFiles\RdpAudit\Service"
# Install as a Windows Service manually, or use the Configurator UI
sc.exe create RdpAudit binPath= "$env:ProgramFiles\RdpAudit\Service\RdpAudit.Service.exe" start= auto
sc.exe description RdpAudit "RDP Security Monitoring & Auto-Block Service"
sc.exe start RdpAuditpublish/Configurator/RdpAudit.Configurator.exeOn first launch, the recommended sequence is:
- Open Prerequisites and verify required channels and platform settings.
- Open Audit Policy and apply the required audit subcategories through
auditpol.exe. - Open Service and install or start the Windows Service.
- Open Settings and tune alert thresholds, trusted IPs, retention, and integrations.
The service uses System.Diagnostics.Eventing.Reader.EventLogWatcher to subscribe to relevant events in real time. The design is already abstracted behind event source interfaces so the collection backend can later move to ETW-based ingestion without rewriting the rest of the pipeline.
Monitored channels:
SecurityMicrosoft-Windows-TerminalServices-RemoteConnectionManager/OperationalMicrosoft-Windows-TerminalServices-LocalSessionManager/Operational
| Event ID | Channel | Description |
|---|---|---|
| 4624 | Security | Successful logon, especially Logon Type 10 |
| 4625 | Security | Failed logon |
| 4634 | Security | Logoff |
| 4647 | Security | User-initiated logoff |
| 4648 | Security | Logon with explicit credentials |
| 4688 | Security | Process creation |
| 4720 | Security | User account created |
| 4723 / 4724 | Security | Password change / password reset |
| 4740 | Security | Account lockout |
| 4769 | Security | Kerberos service ticket request |
| 4771 | Security | Kerberos pre-authentication failure |
| 4776 | Security | NTLM authentication |
| 4954 | Security | Windows Firewall rule or policy change |
| 1149 | TerminalServices-RCM | RDP connection with source IP |
| 21, 23, 24, 25 | TerminalServices-LSM | Session start, reconnect, disconnect, end |
The database is stored at %ProgramData%\RdpAudit\rdpaudit.db.
SQLite settings:
PRAGMA journal_mode = WAL;
PRAGMA synchronous = NORMAL;Primary tables:
| Table | Purpose |
|---|---|
RawEvents |
Normalized security and RDP events |
RdpConnectionFacts |
Connection facts with IP, user, and outcome |
AuthAttemptFacts |
Authentication attempt facts |
Sessions |
RDP session records |
SessionIpCorrelations |
Session-to-IP mapping |
AttackStats |
Aggregated attack statistics by source |
ActiveBlocks |
Currently active automatic blocks |
Alerts |
Alert history |
WhitelistEntries |
Trusted IP and CIDR entries |
BlocklistEntries |
Persistent block rules and sources |
AbuseIpDbReportHistory |
AbuseIPDB report history |
OperationLogs |
Operational log entries surfaced in the GUI |
DbProps |
Key-value metadata store |
Bookmarks |
Crash-recovery bookmarks for event processing |
LoginRules |
Work-hours and logon policy rules |
Write strategy: the hot path uses raw SqliteCommand with explicit transactions and batched writes.
Bookmark durability: bookmarks are flushed every 100 events and every 30 seconds, which minimizes loss after a crash or power interruption.
Each rule is implemented as a dedicated alert evaluator and participates in a common rule pipeline.
| # | Rule ID | Severity | Description |
|---|---|---|---|
| 1 | BRUTE_FORCE |
High | Failed logons from the same source exceed threshold |
| 2 | BRUTE_FORCE_NTLM |
High | NTLM brute-force with cooldown logic |
| 3 | KERBEROS_SPRAY |
High | Kerberos password spraying behavior |
| 4 | SUCCESSFUL_AFTER_FAILS |
Medium | Successful logon after repeated failures |
| 5 | OFF_HOURS_LOGIN |
Medium | Logon outside approved working hours |
| 6 | ACCOUNT_LOCKOUT |
Medium | Account lockout event |
| 7 | NEW_ACCOUNT_CREATED |
Medium | New local or domain account created |
| 8 | PASSWORD_RESET |
Low | Password reset or password change |
| 9 | MULTIPLE_ACCOUNTS_SAME_IP |
High | Username spray from one source IP |
| 10 | STICKY_KEYS_BACKDOOR |
Critical | Accessibility-binary backdoor behavior |
| 11 | LSASS_ACCESS |
Critical | Suspicious handle access to LSASS |
| 12 | LSASS_PPL_TAMPER |
Critical | Attempt to weaken LSASS PPL protection |
| 13 | RDP_PORT_CHANGED |
High | Registry-based RDP port change detected |
| 14 | FIREWALL_RULE_CHANGED |
Medium | Firewall rule or policy changed |
| 15 | EXPLICIT_CREDENTIALS |
Medium | Explicit credential usage pattern |
| 16 | LOGON_TYPE_NETWORK |
Low | Network logon to a protected system |
| 17 | CONCURRENT_SESSIONS |
Low | Excess concurrent session count |
| 18 | GEO_ANOMALY |
Medium | Logon from a disallowed or unexpected geography |
| 19 | IP_REPUTATION |
High | Source IP appears in known blocklists |
| 20 | SESSION_HIJACK_SUSPECT |
High | Session correlation anomaly |
| 21 | RAPID_RECONNECT |
Medium | Repeated reconnect activity from the same IP |
The PRIVILEGED_LOGIN rule (Event 4672 - SeDebug / SeTcb assigned at logon) is hardened against the start-of-service well-known-SID storm (identical SYSTEM alerts with no source IP):
- Exact SID filter -
S-1-5-18(SYSTEM),S-1-5-19(LOCAL SERVICE) andS-1-5-20(NETWORK SERVICE) are rejected by exact SID read from the normalizedDetailsJSON (subjectUserSid), never by localized account names. - Network context required - the event must carry a non-empty
SourceIpand a network logon type (3 / 7 / 10). A missingLogonType(Windows 4672 has no field of its own) falls back to correlating the matching Security 4624 viaSubjectLogonId == TargetLogonId. - Suppression window - identical triggers per (user, source ip, logon type) are suppressed inside
PrivilegedLoginSuppressionWindowMinutes(default 5); when the window expires, a single summary alert carries the suppressed count. - Per-minute alert budget -
PrivilegedLoginRateLimitPerMinute(default 20) caps the output; throttling is logged once per minute. - Historical age gate - events older than
AlertEventMaxAgeMinutes(default 5) remain persisted asRawEventfacts but never alert (applied byAlertWorkerto all rules). - Zero-allocation rejection path -
EvaluateAsyncis a non-async method returning a cached null-task singleton, so rejected evaluations allocate nothing.
When rules such as BRUTE_FORCE, BRUTE_FORCE_NTLM, or IP_REPUTATION are triggered, the service can create a per-IP inbound block rule.
netsh advfirewall firewall add rule `
name="RdpAudit_Block_<IP>" `
dir=in action=block remoteip=<IP> `
protocol=any enable=yesImplementation characteristics:
- Idempotent rule creation
- Input sanitization before shell execution
- Manual removal through the GUI
- Full auditing through
ActiveBlocksandOperationLogs
Pipe name: \\.\pipe\RdpAuditService
Security model: only BUILTIN\Administrators are allowed to connect.
Protocol: MessagePack request and response contracts defined in RdpAudit.Core.
Operational notes:
- The GUI never writes service-owned configuration files directly.
- Settings are sent over IPC and committed atomically by the service.
- Connection deadlines prevent the Configurator from hanging indefinitely.
- The pipe is served by
IpcServerWorker, which requestsMaxConcurrent + 1OS instances so the always-listening standby pipe never competes with a connected handler. Win32ERROR_PIPE_BUSY(231) is classified as "instance cap reached", not as AV/EDR interception; saturation drains automatically as handlers complete. - Each worker instance writes observable lifecycle facts to
%ProgramData%\RdpAudit\logs\ipc-startup.log:instance constructed/ExecuteAsync entered/ExecuteAsync exiting (pid, instanceId, entryCount, exitCount, iteration, activeHandlers, livePipes, stoppingTokenCancelled, hostApplicationStoppingCancelled, exitReason, lifetimeMs). These fields prove whether one process ever ran more than one worker or host instance.
- Exponential backoff for transient database contention such as
SQLITE_BUSY - End-to-end
CancellationTokenpropagation across async flows - Every hosted worker is wrapped in
TimedHostedServiceand writesstartup-sequence.logbegin/end/failure marks IpcServerWorkerlogs per-instance lifecycle breadcrumbs so a repeatedExecuteAsync enteredin one PID can be attributed to one or multiple worker/host instances- Graceful shutdown on service stop
- EF Core migrations applied at startup
- Event Log source registration during installation
The service can expose an optional read-only HTTPS REST API (Kestrel, off by
default) for monitoring agents and scripts. It answers only GET/HEAD and reuses the
same audited read commands as the named pipe, so the REST surface has no write paths.
- Endpoints:
/healthz(anonymous),/api/status,/api/alerts,/api/sessions,/api/addresses,/api/attacks,/api/config(secrets masked),/api/events(bounded, filtered, ordered page) and/openapi/v1.json(committed OpenAPI 3.0 descriptor). - Authentication: static bearer token (
Api.Token, DPAPI-protected) withWWW-Authenticate: Bearer realm="rdpaudit"on 401. - Transport: HTTPS only. Listener defaults to
127.0.0.1:8443; a TLS certificate inLocalMachine\Myis selected byApi.CertificateThumbprint/Api.CertificateSubject. Non-loopback listeners log an explicit risk warning at startup. - Hardening: per-IP fixed-window rate limit (
Api.RateLimitPerMinute), connection cap (Api.MaxConnections), security headers, and RFC 9457problem+jsonerror bodies for every non-200 response. - Fail-closed startup:
Api.Enabled=false(default) or any missing prerequisite (token/certificate/address) means no listener at all.
Configuration is in the Api section of appsettings.json. Full reference:
docs/40-options.md (ApiOptions) and docs/51-rest-api.md (endpoints, errors,
authentication, operation).
The Configurator runs elevated and communicates with the service strictly through the Named Pipe IPC channel.
The Overview tab is the operator dashboard. It summarizes service state, current block counts, recent alerts, and recent event volume.
The Prerequisites tab checks the required Windows conditions:
- All seven monitored event-log channels present and enabled (Security, System, and the Terminal Services / RDP channels)
- Required audit-policy subcategories applied (object access + the five-subcategory baseline)
- Service installed and reachable
A disabled channel offers a wevtutil sl /enabled:true fix. A channel that does not exist
on this Windows build/SKU (for example TerminalServices-Gateway on a host without the RD
Gateway role) is reported as missing with no automatic fix, so the tab never offers an
enable button for a channel this Windows edition cannot create. Each fixable failure can be
applied directly from the UI.
The Audit Policy tab reads the current Windows audit configuration and applies the required subcategories through GUID-based auditpol.exe calls. It also handles SACL setup for high-value registry locations and IFEO paths tied to RDP abuse detection.
The Service tab supports:
- Install and uninstall
- Start, stop, restart
- Service status display
- Startup mode control
The Settings tab exposes operational configuration, including:
- Alert thresholds
- Brute-force windows
- Work-hour definitions
- Data retention windows
- AbuseIPDB settings
- MikroTik settings
- Geolocation and whitelist settings
Changes are submitted over IPC and applied atomically by the service.
The Live Events tab provides a tail-style stream of real-time events with color coding for failures, warnings, and successful activity. It supports filtering by Event ID and source IP. A High risk column shows the cached threat-intel mark (Stage 15) for each source IP.
The Firewall tab surfaces active blocks from ActiveBlocks and supports:
- Viewing blocked IPs, reasons, and providers
- Manually adding block entries
- Removing blocks with confirmation
- Importing or exporting blocklists
The Attack Statistics tab presents aggregated attack data:
- Top attacking IPs
- Threat level scoring
- Time-based charts
- Country-level grouping
- CSV export support
- High risk column backed by the cached threat-intel mark (Stage 15)
Threat level is calculated through the scoring logic in AttackThreatScoring.
The Remote RDP Clients tab correlates source IPs, connection counts, usernames, and historical RDP sessions. It is intended to separate legitimate remote administration from noisy attack traffic.
The AbuseIPDB tab manages:
- API key configuration
- Manual and automatic report workflows
- Report history with deduplication
- Rate-limit-aware status and validation
- Threat-intel reputation checks (/api/v2/check) with cache status, daily usage and the High-risk threshold (Backlog #5)
Secrets are never displayed back in plaintext.
The MikroTik tab configures RouterOS v7 REST integration:
- Router address and port
- DPAPI-protected credentials
- TLS validation behavior
- Address-list synchronization
- Connectivity testing
The Logs tab displays operational entries from OperationLogs, grouped by severity:
InfoWarningErrorCritical
The Diagnostics tab provides operator-facing health data:
- Worker state
- IPC health
- Database size and status
- Integration checks
- Diagnostic export support
The Session Map tab lets a SOC operator inspect one historical RDP session
as a chronological timeline (Backlog #6). Selecting a fact from the bounded
session list (served by the existing ListConnectionFacts command with IP /
User filters) loads the GetSessionTimeline response and shows it twice:
- Event table - time, kind (logon start / connected / reconnected /
disconnected / logoff / process / file / other), event id, process, object
and the explicit correlation method.
UserWindow-correlated rows are tinted amber so the lower forensic confidence is always visible; raw DEBUG detail text is available in a tooltip only when the service runs with DEBUG mode on. - Gantt panel - a built-in GDI+ timeline with a UTC time axis,
colour-coded lanes, legend, a dashed "now" beam for active sessions and a
TRUNCATEDbadge when the server-side activity cap (50..2000, default 500) fired.
The command is strictly read-only and adds no tables or migrations; see
docs/54-session-map.md for the contract, correlation rules, threat model and
rollback guidance.
The Notifications tab configures the elevated RdpAudit.Notifier tray app:
- Enable tray notifier - master switch; when off the notifier exits silently.
- Start with Windows - registers / removes the
RdpAudit.Notifierlogon task throughschtasks(ONLOGON,/RL HIGHEST). - Poll interval - service polling cadence in seconds (clamped 1..60).
- Minimum severity - alerts below this severity are never shown.
- Max toasts per minute - sliding-window rate cap (clamped 1..60); surplus alerts collapse into one aggregate toast.
- Use native toasts - prefer WinRT toasts over tray balloon tips; the notifier falls back to balloons when the native channel is unavailable.
- Alerts / Events sub-tabs - per-rule and per-event visibility maps.
Save / Refresh flow through the IPC commands GetNotificationSettings (78) and
SaveNotificationSettings (79); the service clamps values, falls back the
severity to High and persists only the RdpAudit.Notifications section of
appsettings.json. The notifier polls the service over the named pipe,
degrades to a safe interval while it is unreachable, and never edits the
database.
The Web Access tab (placed right after Service) is the entry point for the read-only IIS-hosted web dashboard. It is privacy-first: everything stays off until the operator explicitly opts in.
- Show Web Access - persists the operator's visibility choice; off by default and only stored when the operator toggles it.
- Allow management from the web - phase-gated: visible but locked until a later management phase unlocks it; the stored preference can never become true before that gate opens.
- Refresh status - re-runs the fail-closed IIS probe (registry
InetStp, read-onlydism /English /Online /Get-FeatureInfo, W3SVC service check) and shows a one-line verdict. - Install IIS - guided, idempotent installation driven by
IisInstallPlan, the single source of truth for both the pre-run checklist and the executed workflow. The progress window lists every item before anything runs and paints a tick or a cross per item: elevation, the static DISM feature set (IIS-WebServerRole,IIS-WebServer,IIS-StaticContent,IIS-DefaultDocument,IIS-HttpErrorsviadism ... /NoRestart), the IIS management tools (IIS-WebServerManagementTools,IIS-ManagementScriptingTools, which provideappcmd.exeused by Configure IIS), the ASP.NET Core Hosting Bundle installed throughwinget install --id Microsoft.DotNet.HostingBundle.8 --exactas the single source of the nativeAspNetCoreModuleV2, and a final verification of what is actually on the machine. The bundle step is skipped when the module is already present, so winget is never called twice for the same machine, and DISM exit code 3010 is reported as a warning asking for a restart - never as success. - Configure IIS - guided setup of the
RdpAuditWebUisite: requires elevation plus the ASP.NET Core Module v2 (Hosting Bundle), resolves or creates the loopback HTTPS certificate, stores the web token only as a DPAPI-protected envelope in the serviceappsettings.json, stages the RdpAudit.Web payload into%ProgramData%\RdpAudit\WebUI, binds the site tohttps://127.0.0.1:443and restarts the service.
Both workflow buttons stay disabled until the probe can actually declare the
IIS state. The browser never sees the bearer token: the in-process YARP proxy
injects it from the machine-scope DPAPI vault and strips any browser-supplied
Authorization header; a Host filter additionally rejects non-loopback host
names (DNS-rebinding defense).
Key entities include:
| Type | Description |
|---|---|
RdpConnectionFact |
RDP connection fact record |
AuthAttemptFact |
Authentication attempt fact |
Session |
RDP session metadata |
SessionIpCorrelation |
Session-to-IP mapping |
AttackStat |
Aggregated attack metrics |
AttackThreatLevel |
Threat level enum |
AttackThreatScoring |
Threat scoring logic |
AttackStatProjection |
UI-facing attack statistics projection |
ActiveBlock |
Active block record |
BlocklistEntry |
Persistent blocklist entry |
WhitelistEntry |
Trusted IP or CIDR entry |
Alert |
Alert record |
AbuseReport |
AbuseIPDB report payload |
AbuseIpDbReportHistory |
Report history and deduplication |
RawEvent |
Normalized raw event record |
OperationLog |
Service operation log record |
LoginRule |
Time-based access rule |
Bookmark |
Event processing bookmark |
DbProp |
Key-value metadata record |
ThreatIntelLookup |
Cached /api/v2/check reputation snapshot per IP (Stage 15) |
ThreatIntelUsageCounter |
Persisted daily /check usage budget counter (Stage 15) |
Address |
IP address and related metadata |
- EF Core 8 with SQLite
AppDbContextfor schema ownership and query support- Automatic migration application at service startup
- EF Core reserved for configuration, migrations, and UI reads
- Hot-path writes handled through raw SQLite commands and explicit transactions
IPC requests and responses are serialized with MessagePack. Representative contracts include:
GetStatusRequest/GetStatusResponseGetEventsRequest/GetEventsResponseGetFirewallBlocksRequest/GetFirewallBlocksResponseAddBlockRequest/AddBlockResponseRemoveBlockRequest/RemoveBlockResponseGetSettingsRequest/GetSettingsResponseSaveSettingsRequest/SaveSettingsResponseGetAttackStatsRequest/GetAttackStatsResponseGetOperationLogsRequest/GetOperationLogsResponse
RDPAudit can report attacking IPs to AbuseIPDB when brute-force or high-confidence malicious behavior is detected.
Design notes:
- API keys are stored as DPAPI-protected values
- Duplicate reports are suppressed through local history
- Rate limits are respected
- Report categories are configurable
RDPAudit can also look up IP reputation on read: the opt-in threat-intel feature
(AbuseIpDb.CheckEnabled) runs GET /api/v2/check through a background worker,
warms a disk cache for public attack-source IPs, and surfaces a High-risk mark
on the Attack Statistics and Live Events grids. The API key is sent only in the
Key header and never logged.
RDPAudit can push malicious IPs into a MikroTik address list through the RouterOS v7 REST API.
Design notes:
- Works over HTTPS REST endpoints
- Supports configurable TLS validation
- Stores credentials through DPAPI protection
- Avoids duplicate address-list entries
- Falls back gracefully when the router is unavailable
Default behavior:
- 10 failed logons from the same IP within 10 minutes triggers a block
- Thresholds are configurable through Settings
- Whitelisted sources are excluded before a block is applied
Provider: WindowsFirewallProvider
Action: Create inbound rule "RdpAudit_Block_<IP>" (block, any protocol)
Reverse: Remove rule by name
Verify: netsh advfirewall firewall show rule name="RdpAudit_Block_<IP>"
Provider: MikroTikFirewallProvider
Action: PUT /rest/ip/firewall/address-list { address: <IP>, list: "RdpAudit-Blocklist" }
Reverse: DELETE /rest/ip/firewall/address-list/<id>
Verify: GET /rest/ip/firewall/address-list?address=<IP>
- Supports single IPs and CIDR ranges
- Evaluated before any block is created
- Managed through the GUI
- Stored in
WhitelistEntrieswith audit-friendly metadata
A pruning worker periodically deletes stale records.
| Table | Default Retention | Configurable |
|---|---|---|
RawEvents |
90 days | Yes |
Alerts |
180 days | Yes |
AbuseIpDbReportHistory |
365 days | Yes |
ActiveBlocks (inactive) |
30 days | Yes |
AttackStats |
365 days | Yes |
Implementation notes:
- Batched deletes
- Exponential backoff on
SQLITE_BUSY - Cancellation support
- Operation logging
The backup workflow captures configuration state, not the event database.
Included in backup:
appsettings.jsonwith DPAPI envelopes only- Audit policy export
- Relevant registry keys
- Service configuration metadata
Not included:
rdpaudit.db
A pre-restore safety snapshot is created before any restore operation is executed.
| Component | Access Model |
|---|---|
RdpAudit.Service |
Runs as SYSTEM or a dedicated service identity |
| Named Pipe IPC | BUILTIN\Administrators only |
RdpAudit.Configurator |
Requires elevation |
| SQLite DB | Restricted to SYSTEM and Administrators |
appsettings.json |
Restricted to SYSTEM and Administrators |
The Configurator sets SACLs on high-value targets so sensitive changes become auditable:
- IFEO accessibility keys
RDP-Tcpregistry configurationLsaregistry configuration
This supports detection of Sticky Keys backdoors, RDP port changes, and LSASS/PPL tampering.
- AbuseIPDB keys and MikroTik credentials are protected with Windows DPAPI
- Only DPAPI envelopes are stored in configuration
- The GUI does not reveal plaintext secrets
- Backups include protected values, not decrypted ones
You can override configuration values through environment variables, which is useful for automated deployment and diagnostics:
RDPAUDIT_RdpAudit__LogLevel=Debug
RDPAUDIT_RdpAudit__AlertRules__BruteForce__FailThreshold=5
When a debugger is attached, the service can run as a console application instead of a formal Windows Service.
cd src\RdpAudit.Service
dotnet run --configuration DebugLogs are written to:
%ProgramData%\RdpAudit\logs\rdpaudit-<date>.log- Windows Event Log →
Application, sourceRdpAudit
To increase verbosity:
$env:RDPAUDIT_RdpAudit__LogLevel = "Debug"& "C:\Program Files\DB Browser for SQLite\DB Browser for SQLite.exe" `
"$env:ProgramData\RdpAudit\rdpaudit.db"Useful queries:
SELECT * FROM RawEvents ORDER BY TimeUtc DESC LIMIT 100;
SELECT * FROM ActiveBlocks WHERE Status = 'Active';
SELECT SourceIp, SUM(FailCount) AS Fails
FROM AttackStats
GROUP BY SourceIp
ORDER BY Fails DESC
LIMIT 20;The Diagnostics UI exposes:
- Events-per-second counters
- Worker status
- IPC state
- Database health
- External integration reachability
dotnet build RdpAudit.sln -c Release
dotnet build src/RdpAudit.Service/RdpAudit.Service.csproj -c Releasedotnet test RdpAudit.sln -c Release
dotnet test RdpAudit.sln -c Release --collect:"XPlat Code Coverage"
dotnet test tests/RdpAudit.Service.Tests/RdpAudit.Service.Tests.csprojEach alert-rule test should validate:
- Threshold boundary behavior
- Whitelist bypass behavior
- Zero managed allocations across repeated evaluations
./publish.ps1Expected output:
publish/
├── Service/
└── Configurator/
Detailed guidance: docs/91-troubleshooting.md
| Symptom | First Diagnostic Step |
|---|---|
| Service does not start | Check sc.exe query RdpAudit, then review the Application Event Log |
| No live events appear | Open Prerequisites and confirm audit configuration |
| GUI cannot connect | Verify the service is running and the pipe ACL is correct |
sc.exe returns 1639 |
Re-check quoting in the service binary path |
| AbuseIPDB returns HTTP 429 | The API rate limit has been reached |
| MikroTik TLS failure | Disable verification for self-signed certs or install the CA cert |
SQLITE_BUSY appears |
Short-term contention is expected and retried automatically |
Audit policy displays ? |
Re-apply through the Audit Policy tab |
| ProgramData ACL issues | Repair folder ACLs for SYSTEM and Administrators |
See also: docs/90-windows-validation.md
| Feature | Status |
|---|---|
ETW ingestion (OpenTrace / ProcessTrace) |
Planned |
Direct EVTX parsing with Span<byte> |
Planned |
| Lock-free MPMC ring buffer | Planned |
| SIMD CIDR and parsing acceleration | Planned |
| Zero-allocation event normalization | Planned |
| Network-layer MS-RDPBCGR / RDPEUDP visibility | Research |
| Blazor-based web UI | Planned |
| Elastic / OpenSearch integration | Roadmap |
| Central multi-server collector | Roadmap |
License: Business Source License 1.1
RDPAudit Community is source-available. You may copy, modify, redistribute and use it for
non-production purposes; the Additional Use Grant written into LICENSE additionally allows
production use, except offering the software to third parties as a commercial hosted or managed
service. On 2030-10-25 this version converts to the Apache License 2.0.
The Pro and Enterprise editions are proprietary and are not covered by this licence - each carries its own terms. Which capability belongs to which edition is documented in docs/editions.md.
Author: Mikhail Deynekin
- Website: deynekin.com
- Email: Mikhail@Deynekin.com
- GitHub: github.com/paulmann
Repository: github.com/paulmann/RDPAudit
Related resources:
© 2025–2026 Mikhail Deynekin. Released under the Business Source License 1.1.
{ "RdpAudit": { "DatabasePath": "%ProgramData%\\RdpAudit\\rdpaudit.db", "EnabledEventIds": , "AlertRules": { "BruteForce": { "IsEnabled": true, "FailThreshold": 10, "WindowSeconds": 600 }, "OffHoursLogin": { "IsEnabled": true, "WorkHoursStart": "08:00", "WorkHoursEnd": "20:00", "WorkDays": ["Monday","Tuesday","Wednesday","Thursday","Friday"], "TimeZoneId": "UTC" } }, "Alerts": { "EnablePrivilegedLoginDetection": true, "PrivilegedLoginSuppressionWindowMinutes": 5, "PrivilegedLoginRateLimitPerMinute": 20, "AlertEventMaxAgeMinutes": 5 }, "AutoBlock": { "IsEnabled": true, "Providers": ["WindowsFirewall"], "BlockTtlHours": 0 }, "Retention": { "RawEventsDays": 90, "AlertsDays": 180, "AttackStatsDays": 365 }, "AbuseIpDb": { "Enabled": false, "ReportAttacks": false, "ApiKey": "", "BaseUrl": "https://api.abuseipdb.com", "EndpointUrl": "https://api.abuseipdb.com/api/v2/report", "TimeoutSeconds": 15, "MaxReportsPerMinute": 60, "MaxReportsPerHour": 100, "MaxReportsPerDay": 500, "DeduplicationWindowMinutes": 15, "CacheLookups": true, "CacheTtlMinutes": 60, "ReportThreshold": 80, "MinThreatScore": 60.0, "MinFailedAttempts": 10, "ReportCategories": [18, 22], "ReportDedupeEnabled": true, "ReportCooldownHours": 24, "CheckEnabled": false, "CheckMaxLookupsPerMinute": 30, "CheckMaxLookupsPerDay": 500, "CheckMaxAgeInDays": 30, "CheckWorkerIntervalSeconds": 300 }, "MikroTik": { "IsEnabled": false, "Address": "", "Port": 443, "UsernameDpapi": "", "PasswordDpapi": "", "VerifyTls": true, "AddressListName": "RdpAudit-Blocklist" } }, "Serilog": { "MinimumLevel": { "Default": "Information" }, "WriteTo": [ { "Name": "File", "Args": { "path": "%ProgramData%\\RdpAudit\\logs\\rdpaudit-.log", "rollingInterval": "Day" } }, { "Name": "EventLog", "Args": { "source": "RdpAudit", "logName": "Application" } } ] } }