Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop

Windows Service to block RDP attacks

4.97/5 (34 votes)
28 Jul 2015CPOL8 min read 42.4K   1.5K  
A Service listening to Windows EventLog, recognizing attacker IPs and block them in the Windows Firewall.

Introduction

I recently got a new Windows 2008 server online and like always you can see in the Windows EventLog how the Remote Desktop Protocol (RDP) is being brute-forced. Also it's just a small machine thus the endless authorization attempts take quite a big part of the server's processor power. So I started manually blocking the IPs extracted from the EventLog entries, but of course it didn't really help for long. So I decided to create a Windows service to do the task.

Image 1

Shown above is an example for the Windows EventLog Explorer showing Audit Failure entries - the ones indicating a brute-force attack.

How the Service Works

Image 2

The diagram above shows the basic structure of the RDPGuard service. The blue parts are mainly simple Windows APIs, the yellowish ones show the simplified program parts.

The EventLog Subscription

The Service subscribes to the Windows EventLog "Security" being notified with every new entry as long as the service is running. If the EventLog entry is an AudithFailure entry with a valid source IP address specified, this information is stored in the database as a RDPGuardHit entry (containing the timestamp, the IP address and the attempted logon username - the latter out of simple curiosity of mine). The subscription is made quite easy with C# - you simply open an EventLog by name and subscribe to the offered EntryWritten event:

C#
_log = new EventLog("Security");
_log.EnableRaisingEvents = true;
_log.EntryWritten += EventLog_EntryWritten;

Extracting the wanted data is a bit tricky because the Message property contains a localized string which is poorly formatted for extracting data. But there is another property, ReplacementStrings - it contains a string array of the data which is used to create the localized message string. For the AuditFailure entry interesting for this service, the fields are the following:

  1. SubjectSecurityID
  2. SubjectAccountName
  3. SubjectAccountDomain
  4. SubjectLogonID
  5. AccountSecurityID
  6. AccountAccountName
  7. AccountAccountDomain
  8. Status
  9. FailureReason
  10. SubStatus
  11. LogonType
  12. LogonProcess
  13. AuthenticationPackage
  14. SourceWorkstationName
  15. TransitedServices
  16. PackageName
  17. KeyLength
  18. CallerProcessID
  19. CallerProcessName
  20. SourceNetworkAddress
  21. SourcePort

So, the first check on the entry is for the field count to be 21, then the field of interest is at index 19 which is to be checked for a well formatted IP address.

The Background Thread and Data Provider

Meanwhile, a separate thread is used to periodically check on the saved RDPGuardHit entries. Some logic in this service has been moved to the data provider for the sake of performance because a lot of it can be done in SQL much easier. I chose to use SQLite simply because I'm comfortable with it. The following steps are taken every period of the thread:

Image 3

Every blocked IP is stored as a RDPGuardBlock containing only the timestamp of the block and the blocked IP address. As the first two actions in every loop show, all collected data older than the set ban time is deleted - the attacking IPs are in a manner of speaking rehabilitated. Although there is a log written in the database, too, using the RDPGuardLog entries.

The SQLite data provider in this service uses simple SQL commands with a few helpers methods. It is initialized when the service is started and the database file is placed in the same directory under the same name as the service executable with the ending .db3 and all necessary tables are created with the CREATE TABLE IF NOT EXISTS command.

The Firewall Rule

To block the recognized attacker's IPs, this service uses the Windows Firewall API. To access it, Visual Studio must be started with administrative rights or the COM reference NetFwTypeLib for the firewallapi.dll won't show up at all. To make it possible to work with the code without administrative rights, I extracted the Interop-DLLs and used them instead - although debugging without administrative rights cannot work of course because these rights are needed to alter the Windows Firewall. Initializing the Interop-classes requires a bit of Googling but then it's pretty straightforward.

When the service starts, the helper class FirewallBlockIpRule is initialized. It attempts to open the Firewall rule by name (using the one set in the settings) and if that fails, creates a new inbound block rule under that name. From then on, the usage is very simple: By setting the RemoteAddresses property IPs to block can be set as a comma separates string and the rule can be enabled or disabled by setting the Enabled property.

Using the Service

Source Code

If you'd like to change the source code, feel free to do so - I tried to add as much inline commenting as I could somehow still call sensible. The 64 bit project of the solution just links to all code files of the 32 bits version.

Here is an overview of the project's classes:

Class name Description
RDPGuardBlock Located in the file RDPGuardEntities.cs; this entity is used to model a blocked IP and can also be managed by the DataProvider.
RDPGuardHit Located in the file RDPGuardEntities.cs; this entity is used to model a recognized failed logon attempt and can also be managed by the DataProvider.
RDPGuardLog Located in the file RDPGuardEntities.cs; this entity is used to model a log entry and can also be managed by the DataProvider.
ABjSThread An abstract class, implements all tasks of a general thread which task is periodically called with a specified pause beween the executions. It also implements Start and Stop functions and a StatusMessage event.
RDPGuardThread This is the central part of the service. It's an implementation of ABjSThread and its function is explained above.
RDPGuardDao This is the DataProvider using SQLite as a backend.
FirewallBlockIpRule This is a helper class around the NetFwTypeLib classes giving access to a firewall rule.
GenericEventArgs<<font color="#000">T</font>> This is a helper class to pass any type of object with an EventHandler.
Program This is the main entry point of the program. It determines whether or not the service is run in command-line (or Debug) and switches the behaviour accordingly.
Service1 This is used to control RDPGuardThread when used as service.
ServiceInstaller1 This is used to control the installation of the service to set name, description and running account type.

Install

To install a .NET Windows service in general, I usually use the installutil.exe on the command-line. It most probably is located in these folders (depending on what versions of .NET you have installed):

  • C:\Windows\Microsoft.NET\Framework64\v4.0.30319\InstallUtil.exe
  • C:\Windows\Microsoft.NET\Framework64\v2.0.50727\InstallUtil.exe
  • C:\Windows\Microsoft.NET\Framework\v4.0.30319\InstallUtil.exe
  • C:\Windows\Microsoft.NET\Framework\v2.0.50727\InstallUtil.exe

Depending on which version of the service (32 or 64 bit) you intend to install, you should use either the Framework folder for 32 bit or the Framework64 folder for 64 bit and then the newest version.

Change to the folder of installutil.exe and run the following command replacing %path_to_service% with the actual path of the service:

BAT
installutil.exe /i %path_to_service%\BjSTools.RDPGuard.exe

To uninstall the service, simply use the same command, replacing the switch /i with /u.

Configuration

The service has a configuration file in standard XML format. It contains the following options:

Option name Type Description
WhiteList string A comma seperated list of white-listed IP addresses which are never blocked
BlockSpanHours int The number of hours an IP address is blocked before the block is removed
BlockHitCount int The minimum number of failed logon attempts before an IP addess is blocked
LogCategoryFilter int A binary filter setting what categories of log messages are logged (see below)
FirewallRuleName string The name of the blocking rule in the Windows firewall

The LogCategoryFilter option uses the bits of the integer - if a bit is set to 1, the according log category is logged. You can simply add the bit values of the log categories to be logged together to get the filter:

Bit Bit value Category Description
0 1 Debug Message is for debug purpose only
1 2 Information Status or otherwise non-disturbing message
2 4 Warning Disturbing information without effect on the ongoing program
3 8 Error A disturbing error which does not cause the program to quit
4 16 Critical A disturbing error which causes the program to quit

The settings can be changed even when the service is already installed but will only be read when the service is starting.

Command-line Usage

The service can also be used as a command-line program. Trying to start the service without any of the following command-line options will result in an error because the service is trying to start as a service and that is only possible if the Windows service controller does it. These options are available:

Option Description
/? Displays a help message describing the command-line options.
/L[=DateTime] Outputs all log entries. If DateTime is specified with a valid DateTime value, only log entries from that time on are loaded. The Convert.ToDateTime(string) method is used.
/C Start the service as a command-line tool. Log messages are also written to the command-line.

Here are some examples for the command-line usage:

  • Run service in command-line:
    BAT
    BjSTools.RDPGuard /C
  • Write complete log to the file log.txt:
    BAT
    BjSTools.RDPGuard /L > log.txt
  • Show log from 2015 and newer:
    BAT
    BjSTools.RDPGuard /L=2015-01-01
  • Show log from April 1st 2015 noon and newer:
    BAT
    BjSTools.RDPGuard /L=2015-04-01T12:00:00

When running the service in command-line mode, it can be stopped by pressing [CTRL]+[C] - it will stop the service in a managed fashion.

Points of Interest

When I started coding this service, I was prepared for some nasty invoking interop code and was then supprised how well it is implemented by the COM references. A rare: Well done Microsoft!

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)