Saturday, July 7, 2012

Keylogging with Win32 Hooks

Keylogger (from Wikipedia):
Keystroke logging is the action of tracking (or logging) the keys struck on a keyboard, typically in a covert manner so that the person using the keyboard is unaware that their actions are being monitored.

It may seem strange, but there're many keylogging methods. People are getting more inventive as it comes to obtaining other people's secrets.
Protecting your secrets becomes challenging task. Even more than you can think of. Depending on your level of paranoia, perhaps you have to make sure that:
  • you're not using wireless keyboards
  • your keyboard installed directly into computer, and that there's no external device attached in between
  • you're using silent keyboard for preventing acoustic cryptanalysis attacks, and if you don't have one - you have to install random keypress sound generating machine
And I didn't even mention software-based threats!

Typical keylogger paranoid
Read the following excellent article, written by Nikolay Grebennikov, current CTO of Kaspersky Lab, where he outlines keylogger history including several publicized keylogging incidents.

Software Keylogger

Software keylogger is a program that monitors and logs user's keystrokes. Writing software keylogger generally means that you have to deal with low-level OS components. As I wrote previously, there're many methods for doing so, but I will focus on Windows Hooks mechanism.

Windows provides a mechanism where any application can intercept system events by installing a subroutine (aka filter function aka hook procedure) for trapping certain event type (such as message, mouse action or keystroke). This subroutine will be called each time this event occurs.

Regular software programs can install hook procedures for debugging purposes, mouse and keyboard input simulation, computer-based training (CBT) implementation and more. On the other hand, malicious software can misuse this mechanism mainly for logging keystrucks, mouse clicks and well.. everything you can think of given this set of exposed hook types.

There're two scope types of hooks: local and remote. Local hooks used to trap events that occur in your own process. Remote hooks trap events that occur in other processes (the Win32 API allows you to monitor a specific thread in a specific process or a whole system).

Of course, installing global hooks affects overall system performance, and should be used with care. Having a bug in hook procedure can crash the remote process. However, performance is not an issue of this post, so I will not go into details.

Win32 exposes three hook-related functions:
  • SetWindowsHookEx - for installing new hook procedure.
  • CallNextHookEx - for passing hook information to the next hook procedure. The hooks are chained in a linked list. Therefore, once your hook procedure finished to process the message, you should pass it to the next linked list node.
  • UnhookWindowsHookEx - for uninstalling hook procedure.

Installing a hook procedure

Let's look on SetWindowsHookEx function declaration in greater detail.

HHOOK WINAPI SetWindowsHookEx(
  __in  int idHook,
  __in  HOOKPROC lpfn,
  __in  HINSTANCE hMod,
  __in  DWORD dwThreadId
);

Function's return value is a handle to the hook procedure if succeeds, or NULL if fails. We have to save the handle, as we will have to use it in CallNextHookEx and UnhookWindowsHookEx functions.

The idHook parameter specifies the type of hook procedure (i.e. mouse events, keyboard events and other messages). I'm interested in logging keystrokes, so I have two options: WH_KEYBOARD or WH_KEYBOARD_LL. Both hook types intercept keyboard input messages. WH_KEYBOARD_LL is a more powerful hook, because it intercepts the message sooner in the messaging system than WH_KEYBOARD hook. Another difference is that WH_KEYBOARD_LL can be installed only as a system-wide hook, whereas WH_KEYBOARD hook can be installed as both local and remote hook. For a more detailed comparison, see Chapter 12 in Stephen Teilhet's Subclassing and Hooking with Visual Basic book.

The lpfn parameter is a pointer to a hook procedure, which depends on a type of hook procedure. If you're using WH_KEYBOARD hook, then you should refer to KeyboardProc callback function. In the same way, if you're using WH_KEYBOARD hook you should refer to LowLevelKeyboardProc callback function.

The hMod and dwThreadId are handle to DLL and thread identifier, respectively. These both parameters specify the scope of the hook procedure:
  • For installing local hook, hMod's value should be NULL; dwThreadId should receive one of your own process's thread IDs. 
  • For installing remote thread-specific hook, hMod should receive a handle to the DLL that contains the hook procedure; dwThreadId should receive the remote process's thread ID.
  • For installing remote global hook, hMod should receive a handle to the DLL that contains the hook procedure; dwThreadId should receive a 0.

In this example I will install a global hook, therefore the function that installing the hook will look like that:

int AddHook()
{
    HHook hookHandle = SetWindowsHookEx(WH_KEYBOARD_LL, (HOOKPROC)HookProcedure, dllHandle, 0);
    if(hookHandle == NULL)
        return 0;
    return 1;
}

A brief description on what happens when user presses on a key:
First, the keyboard sends a scancode to the keyboard driver. The driver converts the scancode to a virtual key. The system creates a keyboard message structure, which includes message type and the virtual key, and sends it to the raw input thread (RIT). RIT will determine which application is supposed to receive the input, and then will add the message to the application's internal message queue. Then, the target application can use the Win32's GetMessage/PeekMessage functions to read the message.

HookProcedure function is my LowLevelKeyboardProc filter function for handling and processing the keystrokes. Its parameters specify, among other things, the message type (wParam) and a pointer to a KBDLLHOOKSTRUCT structure (lParam). The KBDLLHOOKSTRUCT structure includes aforementioned scancode and virtual key.
The general pattern for handling the keystrokes should be as following:
  1. Verify that the code parameter equals to HC_ACTION. If this is not true, you shouldn't process the message.
  2. Monitor the WM_SYSKEYDOWN and WM_KEYDOWN messages.
  3. Once you received a message that meets the 2nd section's condition, get the actual key from the  KBDLLHOOKSTRUCT structure. This post does a great job explaining how to do that right, according to the current keyboard layout.
  4. Write the key to a file / send it to a remote computer.
  5. Call the CallNextHookEx function.  The arguments of this function are the hook handle you get when you registered your hook procedure and the three parameters of the hook procedure.

LRESULT CALLBACK HookProcedure (
  int code,       // A code the hook procedure uses to determine how to process the message.
  WPARAM wParam,  // The identifier of the keyboard message.
  LPARAM lParam   // A pointer to a KBDLLHOOKSTRUCT structure.
)
{
    if(code == HC_ACTION)
    {
        switch(wParam)
        {
            case WM_SYSKEYDOWN:
            case WM_KEYDOWN:
                // TODO: get the key value and do whatever you want with it
                break;
        }
     }
 
    return CallNextHookEx(hookHandle, code, wParam, lParam);
}

Additional thing you have to bear in mind is the hook procedure location. Local hooks can be located in a DLL or in executable, but remote hooks have to reside in a DLL! This is because in remote hooking, your hook procedure should be injected into the address space of other processes. The only way this can be done is when the procedure resides in a DLL.

Uninstalling a hook procedure

Uninstalling the hook procedure is simple. All you have to do is to call the UnhookWindowsHookEx function and pass your hook handle to it.

int UnloadHook()
{ 
    BOOL result = UnhookWindowsHookEx(hookHandle);
    return result;
}


To summarize

We will call the AddHook() function usually from a separate executable, which will act as a loader. The hook procedure will start to monitor and log the keystrokes. It is recommended to keep this program alive, so it will unhook your procedure at the end of the usage.


Image with quiet cover for keyboard taken from here.

1 comment: