C# Keyhook (WinAPI)

04/01/2015 17:32 Mostey#1
Code:
using WPARAM = System.IntPtr;
using LPARAM = System.IntPtr;


public struct KeyHookEventArgs
{
    public Keys PressedKey { get; set; }
    public string TranslatedCharacters { get; set; }
}

public class KeyHook : IDisposable
{
    public enum HookType
    {
        WH_KEYBOARD_LL = 13,
        WH_GETMESSAGE = 3
    }

    public enum WindowsMessage : uint
    {
        KEYDOWN = 0x100,
        SYSKEYDOWN = 0x0104,
        KEYUP = 0x0101,
        SYSKEYUP = 0x0105
    }

    public enum MapVirtualKeyMapType : uint
    {
        VK_TO_VSC = 0x00,
        VSC_TO_VK = 0x01,
        VK_TO_CHAR = 0x02,
        VSC_TO_VK_EX = 0x03,
        VK_TO_VSC_EX = 0x04
    }

    public enum VirtualKey
    {
        SHIFT = 0x10,
        LSHIFT = 0xA0,
        CONTROL = 0x11
    }

    private IntPtr _HookID = IntPtr.Zero;
    private bool _shiftPressed = false;

    public Func<KeyHookEventArgs, bool> OnKeyPressed = (KeyHookEventArgs eventArgs) => false;

    public KeyHook(Func<KeyHookEventArgs, bool> onKeyPressed)
    {
        OnKeyPressed = onKeyPressed;
    }

    public void Install()
    {
        _HookID = SetWindowsHookEx((int)HookType.WH_KEYBOARD_LL, HookCallback, IntPtr.Zero, 0);
    }

    public void Uninstall()
    {
        UnhookWindowsHookEx(_HookID);
    }

    private delegate IntPtr LowLevelKeyboardProc(
        int nCode, WPARAM wParam, LPARAM lParam);

    private IntPtr HookCallback(int nCode, WPARAM wParam, LPARAM lParam)
    {
        uint processID;
        var windowThreadID = GetWindowThreadProcessId(GetForegroundWindow(), out processID);
        var threadID = GetCurrentThreadId();

        /* Kann zum filtern benutzt werden
        using (var process = Process.GetCurrentProcess())
        {
            if (process.Id != processID)
                return CallNextHookEx(_HookID, nCode, wParam, lParam);
        }*/

        if (nCode >= 0)
        {
            var virtualKey = (uint)Marshal.ReadInt32(lParam);

            if ((int) wParam == (int) WindowsMessage.KEYDOWN || (int) wParam == (int) WindowsMessage.SYSKEYDOWN)
            {
                AttachThreadInput(threadID, windowThreadID, true);
                
                var keyboardState = new byte[256];
                GetKeyboardState(keyboardState);

                if (_shiftPressed)
                    keyboardState[(int) VirtualKey.SHIFT] |= 0x80;
                else
                    keyboardState[(int) VirtualKey.SHIFT] |= 0x00;

                if (virtualKey == (int) VirtualKey.LSHIFT)
                    _shiftPressed = true;

                var charBuffer = new char[16];
                var translatedCharacterCount = ToUnicode(virtualKey,
                    MapVirtualKey(virtualKey, (uint) MapVirtualKeyMapType.VK_TO_VSC),
                    keyboardState, charBuffer, charBuffer.Length, 0);

                AttachThreadInput(threadID, windowThreadID, false);

                var forwardEvent = OnKeyPressed(new KeyHookEventArgs()
                {
                    PressedKey = (Keys) virtualKey,
                    TranslatedCharacters = new string(charBuffer, 0, translatedCharacterCount)
                });

                if (forwardEvent)
                    return lParam;
            }
            else if ((int) wParam == (int) WindowsMessage.KEYUP)
            {
                if (virtualKey == (int)VirtualKey.LSHIFT)
                    _shiftPressed = false;
            }
        }

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


    public void Dispose()
    {
        Uninstall();
    }

    [DllImport("USER32.DLL", CharSet = CharSet.Unicode)]
    public static extern int ToUnicode(uint virtualKey, uint scanCode, byte[] keyStates, [MarshalAs(UnmanagedType.LPArray)] [Out] char[] chars, int charMaxCount, uint flags);

    [DllImport("user32.dll")]
    static extern uint MapVirtualKey(uint uCode, uint uMapType);

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

    [DllImport("User32.dll")]
    private static extern IntPtr GetForegroundWindow();

    [DllImport("User32.dll")]
    private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);

    [DllImport("user32.dll")]
    private static extern bool AttachThreadInput(uint idAttach, uint idAttachTo, bool fAttach);

    [DllImport("kernel32.dll")]
    private static extern uint GetCurrentThreadId();
    [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool UnhookWindowsHookEx(IntPtr hhk);

    [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    private static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode,
        WPARAM wParam, LPARAM lParam);

    [DllImport("user32.dll", SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    static extern bool GetKeyboardState(byte[] lpKeyState);

    [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    private static extern IntPtr GetModuleHandle(string lpModuleName);
}
Voraussetzung: Aktive Message loop. Bei WinForms Anwendungen läuft die ohnehin schon, ansonsten muss diese halt manuell implementiert werden.

Ihr braucht euch nicht im Zielprozess befinden, da der Hook global ist und jede Anwendung die auf dem selben Desktop läuft, gehooked wird.

Nutzung:

Code:
using (var keyHook = new KeyHook((KeyHookEventArgs eventArgs) =>
                {
                    // return false wenn der Key an die Applikation selbst weitergeleitet werden soll,
                    // return true wenn der Key nicht weitergeleitet werden soll
                }))
                {
                    keyHook.Install();

                    tagMSG msg;
                    bool res = true;
  
                    while (res)
                    {
                        res = Convert.ToBoolean(GetMessage(out msg, IntPtr.Zero, 0, 0));
                        TranslateMessage(ref msg);
                        DispatchMessage(ref msg);
                    }
                }
Der Rückgabewert im Callback (sofern ein Key gedrückt wird) ist sehr wichtig. Wenn ihr nämlich true (statt false) zurückgebt, bekommt die originale Anwendung die ihr gehooked habt die Information nicht, dass der Key gedrückt wurde. Ist relativ handlich um alternativen Input zu ermöglichen, von dem die Anwendung nichts mitbekommen soll.
07/28/2015 19:55 saixo#2
nicht dass ich es vor hätte aber könnte man mit der methode einen keylogger schreiben? ^^
07/29/2015 09:01 Xio.#3
Natürlich. Du jedoch nicht, da du das fragen musst.