The answer is: no, it's not.
In the next few lines, I will show you how to easily write some code which will allow you to intercept text which is being typed into any other application! In particular, I will illustrate how to write a simple dll which will delete any lowercase vowel typed by the user...
And yes, this is one of the techniques behind keyloggers...
Since these are multimedial days, here there is a video showing how this dll can interact with a common application (Microsoft Word):
The first imoprtant thing to point out is the idea behind this project. In order to intercept keystrokes, our software must stay in the Address Space of the process we are writing in. This may sound very tricky, but it enough to write a simple DLL, conaining a global (system wide) hook. Windows Hooks are a mechanism provided by the Windows Kernel to let programmers intercept events and produce an adequate response. In case a global hook is installed, whenever the corresponding event is produced, the Kernel calls the associated function in the DLL. But, in order to call it, it firstly creates a new instance of the dll itsefl within the process' address space. Which is just what we needed.
So, let's get started. Fire up Visual Studio and create a new project, choosing to create an MFC Dll project and call it novowels. The editor will set up a bunch of code, which is a good starting point. First of all, we need to set up the global variables needed to correctly install the hook:
#pragma data_seg(".SHARDAT") static HHOOK hkb = NULL; #pragma data_seg() #pragma comment(linker, "/SECTION:.SHARDAT,RWS") HINSTANCE hins;
The "#pragma data_seg(".SHARDAT")" directive is used to tell the compiler that the global variables must be placed within the executable in a section called ".SHARDAT". This, together with the directive "#pragma comment(linker, "/SECTION:.SHARDAT,RWS")", allows multiple instances of the dll (that is, all the dll resident in the different processes) to share the data (the meaning of RWS is to tell the linker to make the section Readable, Writeable, and Shared).
By the way, "#pragma data_seg()" restores the normal section.
Then, in the initialization function (created by Visual Studio), we place one single line to make the dll aware of its own HINSTANCE. This will be needed when installing the hook.
BOOL CnovowelsApp::InitInstance() { AFX_MANAGE_STATE(AfxGetStaticModuleState()); CWinApp::InitInstance(); hins = AfxGetInstanceHandle(); return TRUE; }
void __declspec(dllexport)__stdcall Install() { hkb = SetWindowsHookEx(WH_KEYBOARD, (HOOKPROC)KeyboardProc, hins, 0); }Although simple, some interesting aspects appear in this function. The "__declspec(dllexport)" is a declaration the purpose of which is to export the function in the DLL, which means that other applications loading the library will be able to invoke it. The second directive, "__stdcall", is used to tell the compiler that the call to this function must be performed according to the "C-standard", which means that the stack is cleaned by the callee instead of the caller.
As for the most interesting part, this function simply calls the SetWindowsHookEx function. This system call tells the windows kernel that a new hook, pointing to the function "KeyboardProc" belonging to the dll "hins", must be attached to the hook chain. In particular, this call tell the windows kernel that we are interested in "WH_KEYBOARD" events, which are related to keyboard interaction.
So, now, let's define the KeyboardProc function, which is the core function of our "disturbing dll":
LRESULT __declspec(dllexport)__stdcall CALLBACK KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam) { char ch = '\0'; if (((DWORD)lParam & 0x40000000) && (HC_ACTION == nCode)) { // Look only for normal characters if (wParam >= 0x2f && wParam <= 0x100) { BYTE ks[256]; GetKeyboardState(ks); WORD w; UINT scan = 0; ToAscii(wParam,scan,ks,&w,0); ch = (char)w; } // Was the character a vowel? if (ch == 'a' || ch == 'e' || ch == 'i' || ch == 'o' || ch == 'u') { // Send the application a backspace GenerateKey('\b'); } } return ::CallNextHookEx(hkb, nCode, wParam, lParam); }
When this function is called, wParam contains the actual keycode, and nCode contains information concerning the action associated to the key itself. If a call to this function was made because of a normal keystroke, and if the keystroke was associated to a valid character (this is what the control wParam >= 0x2f && wParam <= 0x100 is for), then the virtual key code is converted to a normal ASCII character. Then, is the character is a vowel, a backspace ('\b') is sent to the application via a call to the GenerateKey function. This is just simple programming!
In the end, ::CallNextHookEx(hkb, nCode, wParam, lParam) is used to pass the control to the next hook in the chain. This call is strongly necessary, because if the chain is not completely traversed, this may result in unexpected behaviour by the whole system. And we don't want the system to crash, we just want to annoy the user!
Now, let's have a quick glance at the function used to generate keystrokes to the focused application (which is the one the user is typing into):
static void GenerateKey (int vk) { KEYBDINPUT kb = {0}; INPUT Input = {0}; // generate down kb.wVk = vk; Input.type = INPUT_KEYBOARD; Input.ki = kb; ::SendInput(1, &Input, sizeof(Input)); // generate up ::ZeroMemory(&kb, sizeof(KEYBDINPUT)); ::ZeroMemory(&Input, sizeof(INPUT)); kb.dwFlags = KEYEVENTF_KEYUP; kb.wVk = vk; Input.type = INPUT_KEYBOARD; Input.ki = kb; ::SendInput(1, &Input, sizeof(Input)); }It simply uses the SendInput system function to generate both the keydown and keyup event, which are the ones normally generated when a user presses a key, passing as a payload the specified keycode.
To complete the project, there is still a last pass. In the file, the following lines must be added:
; Explicit exports can go here
Install @2
This is needed because Visual Studio actually changes the names of the symbols when compiling. In this way, we tell the linker to leave untouched the function Install name. The @2 is the number of the exported symbol, which can be any number.
Finally, let's have a look at how to use such a dll. In a second MFC Dialog-Based project, let write this small code snippet into a button's action:
HMODULE hMod = LoadLibrary (L"novowels.dll"); typedef void (CALLBACK *installdll)(void); installdll install; install = (installdll)GetProcAddress(hMod, "Install"); if(install != NULL) install();This portion of code (stripped of almost all the error checks!) will load the first instance of our dll. Then, it will look for a function called Install in it (the signature of which is defined thanks to the line "typedef void (CALLBACK *installdll)(void);") and if found will perform a call. In this way, this first instance will set up the keyboard hook, so that it will be automatically mapped into any other application where the user presses a key. And we are done! :)
If you want to investigate further, here you can find links to:
The source is embedded into a Visual Studio project, so that you can compile it yourself and try it out!
Hey, I'm really interested in getting the source code for this, unfortunately the links you posted point to files that no longer exist.
ReplyDeleteI've uploaded the files on another host. The links should be fixed now!
ReplyDeleteThanks for the interest!