bucket-sort logo bucket-sort

プログラミングとインフラエンジニアリングの覚え書き

  • Posts
  • About
  • Contact
  1. Home
  2. All Posts
  3. [C#] グローバルフックとキーボードフックを理解する - SetWindowsHookExの使い方

[C#] グローバルフックとキーボードフックを理解する - SetWindowsHookExの使い方

Jan 25, 2026 C# , .NET bucket-sort

キーロガーのようなものを作る必要が出てきたので、グローバルフック / キーボードフックについて調べました。以下にざっとその内容をまとめます。

ここでいう「キーロガー」は、打たれたキーをロギングする仕組みという純粋に技術的な意味です。本記事では、その基盤となる Windows のフック機構について整理します。

グローバルフックとは?

フック(Hook)の基本概念

フックとは、

Windows がイベントを処理する途中に、自分の処理を割り込ませる仕組み

です。

通常、キー入力は次のように処理されます。

キーボード
   ↓
Windows メッセージキュー
   ↓
対象アプリ

フックを使うと、この途中段階で処理を受け取ることができます。

グローバルフックとは?

フックには大きく2種類あります。

種類 対象
スレッドフック 特定スレッドのみ
グローバルフック システム全体

グローバルフックは、

すべてのプロセスに届くイベントを監視できる仕組み

です。

グローバルフックでできること

  • システム全体のキー入力監視
  • マウス操作監視
  • 独自ショートカット実装
  • 入力分析やログ収集

「どのアプリがアクティブか」に関係なく、入力を取得できるのが特徴です。

グローバルフックのAPI:SetWindowsHookEx

Windowsでは以下のAPIを使います。

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

引数の意味

引数 内容
idHook フックの種類
lpfn コールバック関数
hMod モジュールハンドル
dwThreadId 対象スレッドID(0でグローバル)

キーボードフックで使う定数

WH_KEYBOARD_LL = 13

これは Low-Level Keyboard Hook を意味します。

特徴:

  • システム全体を対象
  • DLLインジェクション不要
  • 入力メッセージ生成前に取得可能

キーボードフックとは?

キーボードフックとは、

キーが押された瞬間 / 離された瞬間を取得する仕組み

です。

今回のサンプルでは、

WH_KEYBOARD_LL

を使用しています。

SetWindowsHookEx の呼び出し

SetWindowsHookEx(WH_KEYBOARD_LL, proc, hModule, 0);

ポイント:

  • WH_KEYBOARD_LL
  • dwThreadId = 0 → グローバル
  • hModule → GetModuleHandle で取得

サンプルクラス

低レベルキーボードフックを .NET で扱えるようにラップしたクラス。

using System;
using System.Diagnostics;
using System.Runtime.InteropServices;

namespace Sample;

public sealed class KeyboardHook : IDisposable
{
    // Low-level keyboard hook
    private const int WH_KEYBOARD_LL = 13;

    private const int WM_KEYDOWN = 0x0100;
    private const int WM_SYSKEYDOWN = 0x0104;

    private readonly HookProc _proc;
    private nint _hookId;

    public event EventHandler<KeyPressedEventArgs>? KeyPressed;

    public KeyboardHook()
    {
        _proc = HookCallback;
    }

    public void Start()
    {
        if (_hookId != nint.Zero) return;
        _hookId = SetHook(_proc);
        if (_hookId == nint.Zero)
        {
            throw new System.ComponentModel.Win32Exception(
                Marshal.GetLastWin32Error(), "Failed to set keyboard hook.");
        }
    }

    public void Stop()
    {
        if (_hookId == nint.Zero) return;
        UnhookWindowsHookEx(_hookId);
        _hookId = nint.Zero;
    }

    public void Dispose()
    {
        Stop();
        GC.SuppressFinalize(this);
    }

    private static nint SetHook(HookProc proc)
    {
        using var curProcess = Process.GetCurrentProcess();
        using var curModule = curProcess.MainModule!;
        var hModule = GetModuleHandle(curModule.ModuleName);

        // For WH_KEYBOARD_LL, hMod is ok; threadId must be 0 for global
        return SetWindowsHookEx(WH_KEYBOARD_LL, proc, hModule, 0);
    }

    private nint HookCallback(int nCode, nint wParam, nint lParam)
    {
        if (nCode >= 0)
        {
            int msg = unchecked((int)wParam);
            if (msg == WM_KEYDOWN || msg == WM_SYSKEYDOWN)
            {
                var data = Marshal.PtrToStructure<KBDLLHOOKSTRUCT>(lParam);

                // vkCode is what we want (virtual-key code)
                KeyPressed?.Invoke(this, new KeyPressedEventArgs((int)data.vkCode, data.flags));
            }
        }

        return CallNextHookEx(_hookId, nCode, wParam, lParam);
    }

    public sealed class KeyPressedEventArgs : EventArgs
    {
        public KeyPressedEventArgs(int vkCode, uint flags)
        {
            VkCode = vkCode;
            Flags = flags;
        }

        public int VkCode { get; }
        public uint Flags { get; }
    }

    private delegate nint HookProc(int nCode, nint wParam, nint lParam);

    [StructLayout(LayoutKind.Sequential)]
    private struct KBDLLHOOKSTRUCT
    {
        public uint vkCode;
        public uint scanCode;
        public uint flags;
        public uint time;
        public nint dwExtraInfo;
    }

    [DllImport("user32.dll", SetLastError = true)]
    private static extern nint SetWindowsHookEx(int idHook, HookProc lpfn, nint hMod, uint dwThreadId);

    [DllImport("user32.dll", SetLastError = true)]
    private static extern bool UnhookWindowsHookEx(nint hhk);

    [DllImport("user32.dll")]
    private static extern nint CallNextHookEx(nint hhk, int nCode, nint wParam, nint lParam);

    [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    private static extern nint GetModuleHandle(string lpModuleName);
}

まとめ

グローバルフック

  • システム全体のイベントを取得
  • SetWindowsHookEx
  • dwThreadId = 0

キーボードフック

  • WH_KEYBOARD_LL
  • 低レベル取得
  • DLL不要
  • 仮想キーコード取得可能
C# .NET Windows Code Snippet
← git submoduleの仕組みと使いどころ [C#] LiveChartsCoreで折れ線グラフを表示する(基本からレンジ切替まで) →

Related Posts

  • [C#] .NETが用意しているアプリケーションの設定クラスを試す - ConfigurationBuilder Jan 20, 2026
  • [C#] アプリケーションの設定をJSON形式で保存する - JsonSerializer Jan 19, 2026
  • [C#] アプリケーションのデータをJSONにシリアライズする - JsonSerializer Jan 18, 2026
  • [C#] LiveChartsCoreで折れ線グラフを表示する(基本からレンジ切替まで) Jan 26, 2026

Table of Contents

  • グローバルフックとは?
    • フック(Hook)の基本概念
    • グローバルフックとは?
    • グローバルフックでできること
  • グローバルフックのAPI:SetWindowsHookEx
    • 引数の意味
    • キーボードフックで使う定数
  • キーボードフックとは?
    • SetWindowsHookEx の呼び出し
  • サンプルクラス
  • まとめ
    • グローバルフック
    • キーボードフック

Recent Posts

  • Laravel の Event / Listener で Pub/Sub を実装する Apr 2, 2026
  • [C#] delegate と event の仕組みを整理する Apr 1, 2026
  • Pub/Sub パターンとは何か Mar 31, 2026
  • PHP/Laravel で値の状態を判定するヘルパ関数まとめ Mar 30, 2026
  • Filament の dehydrated メソッドとは何か Mar 29, 2026

Categories

  • AWS27
  • C#22
  • .NET20
  • Laravel16
  • Linux12
  • Apache8
  • MySQL8
  • PHP8
  • DynamoDB6
  • Nginx5
  • WordPress4
  • インフラ4
  • Hugo3
  • セキュリティ3
  • .NET Framework1
  • Aurora1
  • Filament1
  • Git1
  • SQS1

Tags

  • AWS
  • C#
  • .NET
  • Laravel
  • PHP
  • MySQL
  • セキュリティ
  • Linux
  • Apache
  • Code Snippet
  • DynamoDB
  • NoSQL
  • PHP-FPM
  • RDS
  • DoS
  • Nginx
  • Windows
  • WordPress
  • パフォーマンス
  • 監視
  • Amazon Linux 2023
  • CMS
  • Docker
  • Ipset
  • Iptables
  • OPCache
  • Webサーバー
  • 認可
  • Aurora
  • Blade
Powered by Hugo & Explore Theme.