windows_LowLevelKeyboardProcでキーボード入力をフックしてコードを変更してみる

Windows環境 LowLevelKeyboardProcで入力されたキーコードを変更する

LowLevelKeyboardProcwindows環境でsetWindowsHookEx関数と併用してキーボード入力があったときに呼び出されるコールバック関数になります。アクティブなウィンドウがなにかにかかわらずコールバックで呼び出されるので、これを利用して入力されたキーコードを変更するいたずらを実行してみたいと思います。

visual c++のフォームプロジェクトを作成して、以下の手順で修正します。

まず、setWindowsHookExでLowLevelKeyboardProcにコールバックさせる処理は以下のようになります。

Hook.cpp

#include "stdafx.h"
#include "Hook.h"

HHOOK hMyHook;

LRESULT CALLBACK LowLevelKeyboardProc(int nCode, WPARAM wp, LPARAM lp)
{
    KBDLLHOOKSTRUCT* kbs =(KBDLLHOOKSTRUCT*)lp;
    if (!(kbs->flags & LLKHF_INJECTED)) {
        INPUT input;
        input.type = INPUT_KEYBOARD;
        input.ki.dwFlags = (wp == WM_KEYDOWN || wp == WM_SYSKEYDOWN) ? 0 : KEYEVENTF_KEYUP;

        if (kbs->vkCode >= 'A' && kbs->vkCode <= 'Z') {
            if (kbs->vkCode == 'Z') {
                input.ki.wVk = 'A';
            }
            else {
                input.ki.wVk = kbs->vkCode + 1;
            }
            SendInput(1, &input, sizeof(input));
            return -1;
        }
        else {
            return CallNextHookEx(hMyHook, nCode, wp, lp);
        }
    }
    else {
        return CallNextHookEx(hMyHook, nCode, wp, lp);
    }
}


bool MyHookStart(HWND hWnd)
{
    HINSTANCE hInst;

    hInst = (HINSTANCE)GetWindowLong(hWnd, GWL_HINSTANCE);
    hMyHook = SetWindowsHookEx(WH_KEYBOARD_LL, //フック関数のタイプ
        (HOOKPROC)LowLevelKeyboardProc, //フックプロシージャのアドレス
        hInst, //フックプロシージャが入っているインスタンスハンドル
        0); //フックされるスレッド 0ならすべてのスレッド
    if (hMyHook == NULL) {
        MessageBox(hWnd, "フック開始に失敗", "Error", MB_OK);
        return false;
    }
    else {
        return true;
    }
}

bool MyHookEnd(HWND hWnd)
{
    if (UnhookWindowsHookEx(hMyHook) != 0) {
        return true;
    }
    else {
        MessageBox(hWnd, "フック解除に失敗", "Error", MB_OK);
        return false;
    }
}

setWindowsHookExを呼び出してhookを開始しているのはMyHookStart関数内の以下の箇所になります。

hMyHook = SetWindowsHookEx(WH_KEYBOARD_LL, //フック関数のタイプ
  (HOOKPROC)LowLevelKeyboardProc, //フックプロシージャのアドレス
  hInst, //フックプロシージャが入っているインスタンスハンドル
  0); //フックされるスレッド 0ならすべてのスレッド

SetWindowsHookExに渡している第一引数はフック関数のタイプとなり、今回はLowLevelkeyboardProcなのでWH_KEYBOARD_LLを渡しています。 フック関数のタイプは公式のドキュメントに一覧があります。

フックプロシージャが入っているインスタンスハンドルは自分自身なので、以下の処理でハンドルを取得しています。

hInst = (HINSTANCE)GetWindowLong(hWnd, GWL_HINSTANCE);

それからコールバックで呼び出す関数としてLowLevelKeyboardProcを定義しているので、これをSetWindowsHookExに渡しています、が公式のドキュメントではフックプロシージャが現在のプロセスに関連付けられているコード内に存在する場合、hMod パラメータでNULLを指定しなければなりませんとあるのでNULLを渡すのが正しそうです。

フック対象は全てのスレッドなので0を渡しています。

hookの終了は UnhookWindowsHookEx(hMyHook) の箇所になり、MyHookEnd(HWND hWnd)関数内で呼び出しています。

それから、LowLevelKeyboardProc 関数内でキーコードの入力変換を行っておりa ~ zのキーの入力を一つづらすようにしています(a->b, b->c,,,,z->a)。

LRESULT CALLBACK LowLevelKeyboardProc(int nCode, WPARAM wp, LPARAM lp)
{
    KBDLLHOOKSTRUCT* kbs =(KBDLLHOOKSTRUCT*)lp;
    if (!(kbs->flags & LLKHF_INJECTED)) {
        INPUT input;
        input.type = INPUT_KEYBOARD;
        input.ki.dwFlags = (wp == WM_KEYDOWN || wp == WM_SYSKEYDOWN) ? 0 : KEYEVENTF_KEYUP;

        if (kbs->vkCode >= 'A' && kbs->vkCode <= 'Z') {
            if (kbs->vkCode == 'Z') {
                input.ki.wVk = 'A';
            }
            else {
                input.ki.wVk = kbs->vkCode + 1;
            }
            SendInput(1, &input, sizeof(input));
            return -1;
        }
        else {
            return CallNextHookEx(hMyHook, nCode, wp, lp);
        }
    }
    else {
        return CallNextHookEx(hMyHook, nCode, wp, lp);
    }
}

このヘッダーファイルは以下のようになります。

Hook.h

#pragma once
#include<Windows.h>

bool MyHookStart(HWND hWnd);
bool MyHookEnd(HWND hWnd);

あとはフォーム内でボタンをクリックしてhookの開始、終了を呼び出せるようにしたら完成です。自分は以下のようにフォームを作成しました。

Form1.h

#pragma once
#include "Hook.h"
#pragma comment(lib, "User32.lib")

namespace CppCLR_WinformsProjekt {

    using namespace System;
    using namespace System::ComponentModel;
    using namespace System::Collections;
    using namespace System::Windows::Forms;
    using namespace System::Data;
    using namespace System::Drawing;

    /// <summary>
    /// Zusammenfassung f・ Form1
    /// </summary>
    public ref class Form1 : public System::Windows::Forms::Form
    {
    public:
        Form1(void)
        {
            InitializeComponent();
            //
            //TODO: Konstruktorcode hier hinzuf・en.
            //
        }

    protected:
        /// <summary>
        /// Verwendete Ressourcen bereinigen.
        /// </summary>
        ~Form1()
        {
            if (components)
            {
                delete components;
            }
        }
    private: System::Windows::Forms::Button^  button1;
    private: System::Windows::Forms::Button^  button2;
    private: System::Windows::Forms::Label^  label1;


    protected:

    private:
        /// <summary>
        /// Erforderliche Designervariable.
        /// </summary>
        System::ComponentModel::Container ^components;

#pragma region Windows Form Designer generated code
        /// <summary>
        /// Erforderliche Methode f・ die Designerunterst・zung.
        /// Der Inhalt der Methode darf nicht mit dem Code-Editor ge舅dert werden.
        /// </summary>
        void InitializeComponent(void)
        {
            this->button1 = (gcnew System::Windows::Forms::Button());
            this->button2 = (gcnew System::Windows::Forms::Button());
            this->label1 = (gcnew System::Windows::Forms::Label());
            this->SuspendLayout();
            //
            // button1
            //
            this->button1->Location = System::Drawing::Point(33, 53);
            this->button1->Name = L"button1";
            this->button1->Size = System::Drawing::Size(101, 40);
            this->button1->TabIndex = 0;
            this->button1->Text = L"start";
            this->button1->UseVisualStyleBackColor = true;
            this->button1->Click += gcnew System::EventHandler(this, &Form1::button1_Click);
            //
            // button2
            //
            this->button2->Location = System::Drawing::Point(152, 53);
            this->button2->Name = L"button2";
            this->button2->Size = System::Drawing::Size(101, 40);
            this->button2->TabIndex = 1;
            this->button2->Text = L"stop";
            this->button2->UseVisualStyleBackColor = true;
            this->button2->Click += gcnew System::EventHandler(this, &Form1::button2_Click);
            //
            // label1
            //
            this->label1->AutoSize = true;
            this->label1->Font = (gcnew System::Drawing::Font(L"MS UI Gothic", 22));
            this->label1->Location = System::Drawing::Point(37, 9);
            this->label1->Name = L"label1";
            this->label1->Size = System::Drawing::Size(156, 30);
            this->label1->TabIndex = 2;
            this->label1->Text = L"HOOK OFF";
            this->label1->Click += gcnew System::EventHandler(this, &Form1::label1_Click);
            //
            // Form1
            //
            this->AutoScaleDimensions = System::Drawing::SizeF(6, 12);
            this->AutoScaleMode = System::Windows::Forms::AutoScaleMode::Font;
            this->ClientSize = System::Drawing::Size(283, 105);
            this->Controls->Add(this->label1);
            this->Controls->Add(this->button2);
            this->Controls->Add(this->button1);
            this->Name = L"Form1";
            this->Text = L"Form1";
            this->ResumeLayout(false);
            this->PerformLayout();

        }
#pragma endregion
    private: System::Void button1_Click(System::Object^  sender, System::EventArgs^  e) {
        HWND hWnd = static_cast<HWND>(this->Handle.ToPointer());
        if( MyHookStart(hWnd)) {
            this->label1->Text = L"HOOK ON";
        }
    }
    private: System::Void button2_Click(System::Object^  sender, System::EventArgs^  e) {
        HWND hWnd = static_cast<HWND>(this->Handle.ToPointer());
        if (MyHookEnd(hWnd)) {
            this->label1->Text = L"HOOK OFF";
        }
    }
    private: System::Void richTextBox1_TextChanged(System::Object^  sender, System::EventArgs^  e) {
    }
private: System::Void label1_Click(System::Object^  sender, System::EventArgs^  e) {
}
};
}

あとはvisual studioでデバック実行したあとにhookを開始して、メモ帳などで入力を行うとキーコードが変更されて入力されることが確認できると思います。