Главная » Статьи » Программирование » WinApi

Механизм установки глобального хука клавиатуры (C#)

Для установки глобального хука на C# потребуются системные функции Windows, а также отдельная dll. В отдельной библиотеке классов dll разместятся системные функции установки, удаления хука и функция-фильтр для обработки перехватываемой информации. Однако, думаю, не стоит обрабатывать в фильтре массивы данных, а лучше посылать их тому приложению, в которое встраиваем dll, так как перехваченные данные нужны именно приложению, а не самой dll. Поэтому для пересылки воспользуемся системной функцией SendMessage, чтобы отослать пользовательское сообщение в процедуру обработки системных сообщений WndProc. Можно воспользоваться также функцией PostMessage, чтобы поставить наши сообщения в очередь на обработку системой.

Приведем неполный список необходимых системных функций:

    1. [DllImport("user32.dll", CharSet = CharSet.Auto)]
    2. static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, IntPtr lParam);
    1. [DllImport("user32.dll", CharSet=CharSet.Auto, SetLastError=true)]
    2. static extern IntPtr SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hMod, uint dwThreadId);
    1. [DllImport("user32.dll", CharSet=CharSet.Auto, SetLastError=true)]
    2. static extern bool UnhookWindowsHookEx(IntPtr hhk);
    1. [DllImport("user32.dll", CharSet=CharSet.Auto, SetLastError=true)]
    2. static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam);

С помощью функции SendMessage будем отсылать сообщение оконной процедуре того приложения, в котором хотим обработать полученную информацию. Функция SetWindowsHookEx установит наш хук, а функция UnhookWindowsHookEx его отменит. Функция CallNextHookEx необходима для организации цепочки windows хуков, если таковая есть.

Если нам понадобится пересылать в главное окно приложения строку (например, название приложения, связанного с клавиатурным вводом), то мы используем тогда такую функцию:

 

  1. [DllImport("user32.dll", CharSet = CharSet.Auto)]
  2. static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, [MarshalAs(UnmanagedType.LPStr)] string lParam);

 

Еще для "правильного" хука нам понадобится ряд системных функций для получения описателя окна того приложения, куда мы отсылаем сообщение; и для получения описателя окна того приложения, с которым связан ввод, чтобы затем получить его название. Эти функции в C#-стиле получат следующий вид:

    1. [DllImport("user32.dll", SetLastError=true)]
    2. static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
    1. [DllImport("user32.dll")]
    2. static extern IntPtr GetForegroundWindow();
    1. [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    2. static extern int GetWindowText(IntPtr hWnd, StringBuilder text, int nMaxcount);
    1. [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    2. static extern int GetWindowTextLength(IntPtr hWnd);

Функция FindWindow найдет окно приложения, оконная процедура которого должна обработать полученное сообщение. А GetForegroundWindow даст нам возможность получить описатель окна,в который производится ввод — оно активно в это время. Полученный описатель нам потребуется в GetWindowText, третьим параметром которой передадим полученную длину заголовка этого окна из функции GetWindowTextLength. 

Глобальный хук клавиатуры в стиле C# также нуждается в некоторых постоянных. Так как в нашем случае мы хотим установить глобальный хук именно на клавиатурный ввод, то воспользуемся фильтром WH_KEYBOARD_LL, для чего установим постоянное целое:

const int WH_KEYBOARD_LL = 13;
const int HC_ACTION = 0;
const int WM_KEYDOWN = 0x0100;
const int WM_USER = 0x0400;

Первое постоянное описывает фильтр хука, который отлавливает низкоуровневые клавиатурные события, но им мы будем отлавливать сообщения нажатия клавиши, для чего используем постоянное целое WM_KEYDOWN, описывающее сообщение нажатия клавиши. HC_ACTION проверяет надо ли обрабатывать полученное сообщение или передавать его дальше по цепочке. А WM_USER — пользовательское сообщение.

Кроме всего выше сказанного, нам понадобится еще функция-обработчик, где будет происходить процесс обработки данных. Ее мы прикрепим к делегату, который должно будет передать в функцию SetWindowsHookEx, вторым параметром. В С++ передавали бы в данном случае указатель на функцию-обработчик. Делегат имеет следующий вид:

delegate IntPtr HookProc(int nCode, IntPtr wParam, IntPtr lParam);

Теперь, можно представить примерный код устанавливаемого хука. Ниже, приведем код, размещаемый в отдельной dll:

  1. [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
  2. private static extern int GetWindowText(IntPtr hWnd, StringBuilder text, int nMaxcount);
  3. [DllImport("user32.dll")]
  4. private static extern IntPtr GetForegroundWindow();
  5. [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
  6. private static extern int GetWindowTextLength(IntPtr hWnd);
  7. [DllImport("user32.dll", SetLastError = true)]
  8. private static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
  9. [DllImport("user32.dll", CharSet = CharSet.Ansi)]
  10. private static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, [MarshalAs(UnmanagedType.LPStr)] string lParam);
  11. private const int WH_KEYBOARD_LL = 13;
  12. private const int HC_ACTION = 0;
  13. private const int WM_KEYDOWN = 0x0100;
  14. private const int WM_USER = 0x0400;
  15. private static HookProc proc = HookCallback;
  16. public static IntPtr hook = IntPtr.Zero;
  17.  
  18. private static IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam)
  19. {
  20.          if(nCode >= HC_ACTION && wParam == (IntPtr)WM_KEYDOWN)
  21.          {
  22.             try
  23.             {
  24.               int vkCode = Marshal.ReadInt32(lParam);
  25.               IntPtr handle = FindWindow(null, "Wind");
  26.               IntPtr ahandle = GetForegroundWindow();
  27.               int len = GetWindowTextLength(ahandle);
  28.               StringBuilder str = new StringBuilder(len + 1);
  29.               GetWindowText(ahandle, str, str.Capacity);
  30.               SendMessage(handle, WM_USER + 755, wParam, str.ToString() + "|" +       vkCode.ToString());
  31.              }
  32.              catch(Exception e)
  33.              {
  34.                   MessageBox.Show(e.Message);
  35.              }
  36.           }
  37.       return CallNextHookEx(hook, nCode, wParam, lParam);
  38. }
  39.    public override IntPtr SetHook()
  40.   {
  41.      return SetWindowsHookEx(WH_KEYBOARD_LL, proc, IntPtr.Zero, 0);
  42.   }
  43.    public override void UnsetHook(IntPtr hhk)
  44.   {
  45.      UnhookWindowsHookEx(hhk);
  46.   }

 

Функция HookCallBack — как раз и есть функцией-обработчиком, которая цепляется к делегату. В ней мы проверяем параметр nCode — если он ниже нуля значит хук передается по цепочке обработчиков далее, а если он выше или равен 0 и одновременно в wParam передается значение нажатой клавиши, то мы обрабатываем поступившее событие.Строчка 24 описывает получение кода нажатой клавиши; на следующей строчке мы получаем описатель окна, оконной процедуре которого мы хотим отправить сообщение. В строчке 26 мы получаем описатель активного окна, в который осуществляется ввод, а на следующей строке получаем длину заголовка этого окна. Далее, строим пустую строку на основе длины заголовка, которую передаем как второй параметр в GetWindowText, чтобы получить значение заголовка окна ahandle. На следующем шаге отсылаем пользовательское сообщение окну handle. В нем в виде строкового параметра передаем название окна и значение кода нажатой клавиши.

Функцией SetHook мы устанавливаем собственно сам глобальный хук, передавая в функцию SetWindowsHookEx наш клавиатурный фильтр и экземпляр делегата. SetHook возвращает описатель хука. Функцией UnsetHook мы отменяем установленный хук. 

Далее, приведем пример перегруженной оконной процедуры окна, получающего сообщение от функции-обработчика глобального хука. В стиле C# она будет примерно такой:

 

  1. protected override void WndProc(ref Message m)
  2. {
  3.        String keyapp = String.Empty;
  4.        switch (m.Msg)
  5.        {
  6.        case WM_KEYSTROK:
  7.        keyapp = Marshal.PtrToStringAnsi(m.LParam);
  8.        MessageBox.Show(keyapp);
  9.        break;
  10.        }
  11.    base.WndProc(ref m);
  12. }

 

Где WM_KEYSTROK — ничто иное как наш WM_USER + 775. Если это событие приходит в оконную процедуру, то в сообщении m нас интересует параметр LParam, из которого получаем нашу строку, а далее выводим ее на экран.

Короткий итог

В этой статье описан простой механизм установки C# глобального хука клавиатуры. Приведены примеры возможного кода, который позволит осуществить перехват нажатой клавиши и отослать информацию о ней оконному приложению. Данный вариант кода можно использовать для приложений типа "Родительский контроль", в которых, например, может применяться функция кейлоггера. 



Ключевые слова: как установить хук, как отменить хук, хуки C#, глобальный хук C#, С# хук клавиатуры, С# глобальный хук клавиатуры
Категория: WinApi | Добавил: lesha (18.03.2015) | Автор: Иевенко Алексей W
Просмотров: 6182 | Комментарии: 89 | Теги: глобальный хук клавиатуры, установка глобального хука клавиату, хуки, C#, hooks | Рейтинг: 0.0/0
Всего комментариев: 0
Имя *:
Email *:
Код *: