Створення пасток і фільтрів для перехоплення подій на delphi, блог it-фахівця

Коли то давно виникла у мене необхідність написати програму для створення скріншотів. А саме користувач виділяє мишею необхідну йому область на екрані, потім відпускає кнопку і отримує скріншот. У той час я про пастки ще не знав. Кілька днів я "бився" над поставленим завданням, але мої експерименти так і ні до чого, ні привели. Почитавши різну літературу і статті, і дізнавшись, що таке пастки, і з чим їх "їдять", я почав експериментувати далі. А почав я з книги Михайла Фленово «Програмування в Delphi очами хакера». На той час, все те, що я черпав з його книги, мені здалося досить легко, але тільки потім я зрозумів (коли в цій справі піднабрався досвіду), що сильно помилився.

Але сталося диво. Потрапила до мене в руки книга Юрія Ревича "Нестандартні прийоми програмування на Delphi". Погортавши яку, я був трохи шокований, ось воно те, що мені потрібно. А не виходило раніше, тому що потрібно передавати значення з пастки через MAP файли (Memory Mapped Files) - відображення файлу на пам'ять.

Ну що починаємо писати шпигуна?

І так все поступово ...
В операційній системі Microsoft Windows пасткою називається механізм перехоплення особливою функцією подій (таких як повідомлення, введення з миші або клавіатури) до того, як вони дійдуть до додатка. Ця функція може потім реагувати на події і, в деяких випадках, змінювати або скасовувати їх. Функції, які отримують повідомлення про події, називаються фільтруючими функціями і розрізняються за типами перехоплюваних ними подій. Приклад - фільтруюча функція для перехоплення всіх подій миші або клавіатури. Щоб Windows змогла викликати функцію фільтр. ця функція повинна бути встановлена, тобто, прикріплена до пастки (наприклад, до клавіатурній пастці).

Все, вистачить теорії, починаємо писати. Ми напишемо простий клавіатурний шпигун. Чому простий? Та тому що шпигун повинен не тільки перехоплювати натиснуті клавіші, а також стежити за додатками, в яких ці клавіші натискати. Ще можна записувати і час запуску пастки, додатки яке мало на момент натискання клавіш фокус введення і т.д. Але це ви вже зможете реалізувати самі.
Створюємо новий проект. Кидаємо TMemo і дві кнопки:

Створення пасток і фільтрів для перехоплення подій на delphi, блог it-фахівця

Потім оголошуємо константу з призначеним для користувача повідомленням:

Тепер нам треба імпортувати процедури запуску і видалення пастки. Хоча бібліотека ще не написана, але це так, щоб потім не повертатися. Додайте у себе такі ось рядки:

На наступному кроці створимо, обробники для кнопок і в них напишемо наступний код:

У обробнику кнопки «Start» є змінні, що не були оголошені, ці змінні знаходяться в іншому модулі, який ми розглянемо пізніше.

Тепер нам необхідно написати обробник для нашого призначеного для користувача повідомлення. Для цього помістіть наступний прототип процедури в область private:

Ну а сам обробник настільки простий, що простіше нікуди.

В змінної Message. в параметрі Wparam. знаходиться код з віртуальним ідентифікатором клавіші. Вся проблема в тому, що за цим кодом ми не можемо визначити, ні в якому регістрі знаходиться символ, ні вже тим більше розкладку клавіатури, при якій він був введений. Саме тому я наводжу функцію GetCharFromVKey. яка повертає саме той символ, який ми ввели. У ній вона й регістр і розкладка.

Сама функція призначена тільки для конвертації клавіш по їх коду. Тому на ній ми зупинятися не будемо. Скажу тільки що, вона розпізнає всі символи від A до Z, від a до z, від А до Я і від а до я і т.д. Тобто по регістру і по розкладці.

Тепер нам належить написати модуль для створення загальної розділяється області. Модуль у мене називається IniHook. з додатковими секціями initialization і finalization. Створюємо новий модуль і називаємо його IniHook. Підключаємо два модуля: Windows і Messages.

Оголосимо покажчик на змінну THookInfo
Потім напишемо запис THookInfo.

Далі оголошуємо дві змінні:

Через змінну DataArea ми будемо звертатися до полів запису THookInfo. hMapArea буде містити дескриптор об'єкта «проектованого» файлу.

Далі в розділі initialization викличемо функцію CreateFileMapping і дамо її повернене значення змінної hMapArea. Потім викличемо функцію MapViewOfFile і дамо її повернене значення змінної DataArea. Вихідний код дивіться нижче:

Коротко розглянемо використані тут функції.
Створення і використання об'єктів файлового відображення здійснюється за допомогою функцій Windows API. Цих функцій три:

  • CreateFileMapping;
  • MapViewOfFile;
  • UnMapViewOfFile.

Трохи відволіклися? Ну що продовжуємо. Далі нам необхідно в розділі finalization написати код, який прибирає файл з пам'яті.

Тут тільки слід зазначити, що функція UnMapViewOfFile повинна викликатися перед функцією CloseHandle. Тобто ні в якому разі цей порядок порушувати не можна.

Ну що модуль ми написали, тепер його необхідно підключити до основної програми. Залишилося нам написати саму пастку, яка буде розміщуватися в бібліотеці. А поки йдемо пити каву. Попили? Продовжуємо. Почніть новий проект, тільки не для написання програми, а для бібліотеки. Для цього потрібно вибрати команду File / New / Other, потім перед вами відкриється наступне вікно:

Створення пасток і фільтрів для перехоплення подій на delphi, блог it-фахівця

Знайдіть елемент DLL Wizard і двічі клацніть на ньому. І Delphi створить порожній проект динамічної бібліотеки. Не забудьте відразу зберегтися. Нижче представлений вихідний код моєї бібліотеки:

Тепер про все по порядку. Для початку необхідно підключити три модуля: Windows, Messages, SysUtils і один наш модуль, а саме IniHook Щоб мені не тримати копії модуля в каталозі самої програми і в каталозі бібліотеки я його виніс в загальний каталог, в якому знаходяться каталоги основної програми і бібліотеки. Але ви можете його підключити стандартним способом, тобто, оголосивши його з усіма модулями, тільки тоді вам доведеться покласти цей модуль в каталог з бібліотекою. Це вже справа смаку.

Тепер, як і в основній програмі, ми оголосили константу WM_ReadWithHook = WM_USER + 120. для нашого призначеного для користувача повідомлення. Функція KeyboardProc - це оброблювач нашої пастки. Ця функція має три параметри. Зараз ми і їх розглянемо більш докладно. При встановленому типі пастки WH_KEYBOARD. ці параметри можуть мати наступні значення:

  • nCode. Визначає код використання процедури пастки, щоб визначити як обробити повідомлення. Цей параметр може мати кілька значень.
  • AC_ACTION - WParam і LParam параметри містять інформацію щодо натиснутоюклавіші. Повідомлення іншого типу у нас немає необхідності обробляти.
    • WParam. Визначає код з віртуальним ідентифікатором клавіші, яка генерувала повідомлення натискання клавіші.
    • LParam. визначає повторний рахунок, скан-код, прапорець розширеної клавіші, контекстний код, попередній прапорець стану клавіші, і прапорець перехідного стану. Цей параметр може мати комбінацію певних значень. Але ми їх розглядати не будемо, оскільки вони в нашому випадку не пропонують інтересу.

Функція CallNextHookEx має чотири параметри:

  1. (Hhk) - дескриптор пастки, повернутий функцією SetWindowsHookEx;
  2. (Code) - визначає код перехоплення;
  3. (WParam) - визначає приходить довжину в процедурі по обробці пастки. Його значення залежить від типу встановленої пастки;
  4. (LParam) - визначає приходить довжину в процедурі по обробці пастки. Його значення залежить від типу встановленої пастки.

В даний момент нас цікавить ось цей рядок коду:

У цьому рядку ми передаємо команду нашому додатку, в якому викликається обробник нашого призначеного для користувача повідомлення WM_ReadWithHook = WM_USER +120 і параметр WPARAM. який містить код клавіші.

І, на кінець я викликаю знову функцію CallNextHookEx. яке значення якої я передаю в змінну. Я помітив, що так практично ніхто не робить. Воно то все працює. Але в грі, наприклад «Counter-Strike» при включеній пастці, були зависання клавіш. А після додавання в кінець нашого обробника функції CallNextHookEx. прийшло все в норму.
Процедура SetHook містить всього лише один рядок коду:

Тут викликається функція установки пастки SetWindowsHookEx. У цій функції має бути чотири параметри:

  1. Тип пастки. Вказано WH_KEYBOARD, ця пастка контролює повідомлення натискання клавіш;
  2. Ідентифікатор, що містить процедуру пастки;
  3. Покажчик на додаток;
  4. Ідентифікатор потоку. Якщо параметр дорівнює нулю, то використовується поточний.

Процедура DelHook також має всього один рядок коду:

Функція UnhookWindowsHookEx має всього один параметр і це дескриптор пастки.
Процедури SetHook і DelHook оголошені як експортні.
Ну от і все. Ви можете завантажити demo версію або повний исходник проекту.