Хуки — это просто

  • Tutorial


Хуки — это технология перехвата вызовов функций в чужих процессах. Хуки, как и любая достаточно мощная технология, могут быть использованы как в благих целях (снифферы, аудио\видеограбберы, расширения функционала закрытого ПО, логирование, багфиксинг) так и со злым умыслом (трояны, кряки, кейлоггеры). О хуках уже не раз писали и на Хабре и не на Хабре. Но вот в чём беда — почему-то каждая статья о хуках буквально со второго абзаца начинает рассказывать о «таблице виртуальных функций», «архитектуре памяти» и предлагает к изучению огромные блоки ассемблерного кода. Известно, что каждая формула в тексте снижает количество читателей вдвое, а уж такие вещи — так и вовсе вчетверо. Поэтому нужна статья, которая расскажет о хуках просто. Под катом нет ассемблера, нет сложных терминов и буквально два десятка строк очень простого кода на С++. Если вы давно хотели изучить хуки, но не знали с чего начать — начните с этой статьи.

Реальная задача


Для лучшего понимания того, что мы делаем — поставим себе какую-нибудь реальную задачу. Давайте, например сделаем так, чтобы браузер Firefox при заходе на Хабр писал в своём заголовке «Привет, Хабр!» вместо того, что там пишется сейчас (а сейчас там пришется "*** / Хабрахабр — Mozilla Firefox", где *** — меняется в зависимости от раздела). Да, я знаю, что это можно сделать правкой исходников Firefox, браузерными плагинами, юзерскриптами и еще десятком способов. Но мы в учебных целях сделаем это хуками.

Совсем чуть-чуть теории


Когда Вы запускаете любое приложение — операционная система создаёт его процесс. Грубо говоря, exe-файл копируется в память, далее определяется какие именно библиотеки (dll-файлы) ему нужны для работы (эта информация записана в начале каждого exe-файла), эти библиотеки ищутся (в папке с программой и в системных папках) и загружаются в память процесса. Потом определяется, какие именно функции библиотек использует программа и где они находятся (в какой библиотеке и где именно в этой библиотеке). Строится табличка вида «функция SomeFunction1() — библиотека SomeLibrary1.dll — %адрес_функции_SomeFunction1()%». Когда программе понадобиться вызвать эту функцию — она найдет в своей памяти нужную библиотеку, отсчитает нужный адрес и передаст туда управление.



Суть хукинга — заставить программу поверить, что нужная ей функция находится в другом месте.



Делается это таким образом — мы пишем свою библиотеку SomeLibrary2.dll, в которой будет находится наша функция SomeFunction2(). Далее мы загружаем эту библиотеку в память чужого процесса (в ОС Windows есть специальная функция для этого) и изменяем ту самую табличку, о которой я писал чуть выше, так, чтобы теперь она содержала запись «функция SomeFunction1() — библиотека SomeLibrary2.dll — %адрес_нашей_функции_SomeFunction2()%». Для того, чтобы понять, как вручную сделать всё описанное в этом абзаце, нужно знать весьма прилично всего — как устроена память в Windows, как вызываются функции, как им передаются аргументы и т.д. Это сложно. Ну на самом деле не очень, просто можно обойтись и без этого. Если вам это нужно — почитайте какую-нибудь продвинутую статью (а хоть бы из тех, что указаны в начале). Мы пойдем другим путем — используем готовую библиотеку Microsoft Detours, которая сделает всю грязную работу за нас.

Пару слов о Microsoft Detours


Проста в изучении и использовании
Весьма эффективна
Хорошая документация
Содержит много примеров в исходниках
Разработана Microsoft — неплохо «дружит» с ОС
Бесплатна для исследовательских целей и некоммерческих проектов
Не требует знания ассемблера

Закрыта
Стоит приличных денег для коммерческого использования или х64-архитектуры

В целом, я бы посоветовал начинать изучение хуков именно с Detours — если это будет всего лишь вашим разовым развлечением, то этого вполне хватит, у вас быстро всё получится и вам понравится. Если же хуки понадобятся в серьёзном проекте — вы легко переключитесь на бесплатные и открытые (но чуть более сложные) библиотеки типа mhook, купите Detours или напишете свой велосипед (для последних двух решений нужны весьма веские причины).
О том где взять и как собрать Detours я писал вот тут.

Хитрый план


  1. Понять, на какую функцию ставить хук.
  2. Сделать свою библиотеку с функцией, которая будет заменять исходную и делать нужные нам вещи.
  3. Установить хук (загрузить библиотеку в память нужного процесса и переставить указатель на нужную нам функцию).
  4. PROFIT!

Куда ставить хук


MSDN весьма ясно намекает нам, что заголовок окна можно установить функцией SendMessage — при этом вторым параметром должно быть передано WM_SETTEXT, а последним — сам текст. Но тут могут быть нюансы:
  1. Вместо SendMessage может использоваться PostMessage или что-то еще
  2. SendMessage может быть вообще не функцией, а макросом, ссылающимся на другую функцию (в дальнейшем мы увидим, что так оно и есть)
  3. Firefox, как некоторые кроссплатформенные приложения, может вообще не использовать функции Windows для рисования стандартных элементов окна, используя вместо этого какие-то собственные кросплатформенные элементы GUI (к счастью, это не так — но вдруг!)

Так что нужно всё хорошенько проверить. Нам поможет прекрасная бесплатная программа API Monitor. Она позволяет присоединиться к определенному процессу и подсмотреть, какие именно функции он вызывает и с какими параметрами. Вы, может быть, уже догадались — делает она это тоже с помощью хуков. Итак запускаем Firefox и API Monitor. Первым делом в API Monitor нужно указать фильтр — какую именно группу функций мы хотим мониторить. Если выберем вообще всё — исследуемая программа будет работать очень медленно (а может даже зависнет), выберем слишком мало — упустим нужное. Поэтому тут придётся думать и выбрать лишь ту группу, где потенциально могут находится функции работы с элементами GUI Windows. Давайте выберем группы Graphics и Windows Application UI Development а в панели Running Processes дважды кликнем по нашему Firefox. Начиная с этого момента API Monitor в панели справа будет показывать вызовы всех API-функций и их параметры.

Переходим в Firefox, открываем Хабр, дожидаемся изменения заголовка на нужный и возвращаемся в Api Monitor чтобы остановить мониторинг. Скорее всего, вы будете удивлены количеством вызванных функций — их могут быть сотни тысяч буквально за несколько секунд мониторинга. А мы ведь еще и следим далеко не за всем. Да-да, это всё реально происходит внутри безобидного открытия всего одного сайта в браузере! А вы еще жалуетесь, что эта пара секунд — слишком долго. :)



Найти нужную нам функцию поможет поиск по вкладке с результатами мониторинга. Вбиваем в поиск «WM_SETTEXT» и убеждаемся, что действительно имеются вызовы функции SendMessageW с этим параметром — с высокой вероятностью это и есть установка заголовка окна. Обратите внимание на «W» в конце названия функции — оно означает, что используется её юникодная версия. Для установки хуков важно знать точное имя подменяемой функции и теперь мы его знаем.

Делаем свою библиотеку


1. Запускаем Visual Studio.
2. Создаём новый проект: File->New->Project. Тип Visual C++ -> Win32 -> Win32 Project. В диалоге создания проекта указываем тип «Dll».
3. Открываем файл dllmain.cpp и пишем туда вот такой код:

#include <windows.h>
#include "C:\Program Files\Microsoft Research\Detours Express 3.0\src\detours.h"

LRESULT (WINAPI * TrueSendMessageW)(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam) = SendMessageW;

__declspec(dllexport) LRESULT WINAPI MySendMessageW(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam)
{
	if (Msg == WM_SETTEXT && wcsstr((LPCTSTR)lParam, L"/ Хабрахабр - Mozilla Firefox") != NULL)
		return TrueSendMessageW(hWnd, Msg, wParam, (LPARAM)L"Привет, Хабр!");

	return TrueSendMessageW(hWnd, Msg, wParam, lParam);
}

BOOL WINAPI DllMain(HINSTANCE hinst, DWORD dwReason, LPVOID reserved)
{
	if (dwReason == DLL_PROCESS_ATTACH) 
	{
		DetourRestoreAfterWith();
		DetourTransactionBegin();
		DetourUpdateThread(GetCurrentThread());
		DetourAttach(&(PVOID&)TrueSendMessageW, MySendMessageW);
		DetourTransactionCommit();
	}
	else if (dwReason == DLL_PROCESS_DETACH)
	{
		DetourTransactionBegin();
		DetourUpdateThread(GetCurrentThread());
		DetourDetach(&(PVOID&)TrueSendMessageW, MySendMessageW);
		DetourTransactionCommit();
	}
	return TRUE;
}


4. Открываем свойства проекта и на вкладке настроек линкера добавляем в поле Additional Dependencies значение «C:\Program Files\Microsoft Research\Detours Express 3.0\lib.X86\detours.lib». Внимание, у вас путь может быть другой — смотря куда установили библиотеку Detours.



5. Компилируем проект: Build -> Build Solution. На выходе получаем длл-ку (пусть будет называться hooktest.dll)

Давайте разберем исходник. В начале мы подключаем заголовочные файлы Windows (чтобы пользоваться функцией SendMessageW) и Detours (чтобы иметь возможность ставить\снимать хуки).
В сложной на первый взгляд строке №3 мы всего лишь сохраняем реальный указатель на функцию SendMessageW в переменную TrueSendMessageW. Это нам понадобиться для двух целей:

  1. Для вызова настоящей функции SendMessageW из нашей «подделки».
  2. Для восстановления указателя на реальную функцию в момент, когда мы захотим снять хук.

Далее идет наша поддельная функция MySendMessageW. Она предельно проста. Если попалось сообщение WM_SETTEXT и в его тексте есть упоминание Хабра — заменяем его на своё. Иначе — работаем как прозрачный прокси. Обратите внимание на префикс __declspec(dllexport) — он нужен чтобы этой функцией смогли воспользоваться другие процессы.

Функция DllMain вызывается операционной системой в определенных случаях — например, в моменты аттача\детача библиотеки к процессу. Тут тоже всё просто. В момент аттача нам нужно установить хуки, в момент детача — снять. Библиотека Detour требует делать это транзакциями, и в этом есть смысл — представьте себе что будет, если сразу несколько желающих захотят поставить хуки в один процесс. Самое важное в этом коде это строка
DetourAttach(&(PVOID&)TrueSendMessageW, MySendMessageW);

Именно она заставляет процесс «поверить» что теперь вместо настоящей функции SendMessageW нужно вызывать нашу MySendMessageW. Ради этой строки всё и затевалось. Если кому интересно, однажды я писал аналог этой функции вручную. С учетом всех возможных комбинаций типов функций и архитектур это заняло у меня несколько недель. Вы вот только что их сэкономили — поздравляю.

Устанавливаем хук


Microsoft Detours предлагает разные варианты установки хуков — мы воспользуемся самым простым. В комплекте примеров, которые идут с библиотекой, есть программа withdll.exe — она принимает в качестве параметров путь к приложению и библиотеку, которую нужно подгрузить в память этого приложения после его запуска. Запускаем всё это как-то вот так:
withdll.exe -d:hooktest.dll "C:\Program Files\Mozilla Firefox\firefox.exe"

PROFIT!


Открываем Хабр:



Ура, работает!

Успехов в изучении хуков.
Инфопульс Украина
96.90
Creating Value, Delivering Excellence
Share post
AdBlock has stolen the banner, but banners are not teeth — they will be back

More
Ads

Comments 61

  • UFO just landed and posted this here
      0
      Вот тоже неплохой вариант с примером инжектора вдобавок, правда на ассемблере http://goo.gl/EY9sg
      +10
      Когда я переходил на *nix-системы, я испытал небольшой шок от того, насколько проще там осуществлять перехват библиотечных функций. Всё встроено в системные загрузчики исполняемых файлов и контролируется установкой переменных окружения (LD_PRELOAD, DYLD_INSERT_LIBRARIES, etc).
        +3
        Более того, там это и не нужно чаще всего — взяли исходники, почитали, поправили, собрали. Удобно, не поспоришь. Но ведь и в Windows надо уметь что-то делать.
          +2
          Далеко не всё можно просто взять и пересобрать. Геморрой ещё тот порою. И не всё открыто.
            +3
            Более того, там это и не нужно чаще всего
            Ну для штук вроде fakeroot незаменимо. Помимо этого есть всякие padsp, socksify, v4l2compat.so и прочие, эмулирующие для софта несколько иное програмное окружение, чем то, в котором он реально находится.
            0
            Даже догадываюсь почему. Всякие хакеры давно облюбовали винду для испытания всяких гадостей. Вполне естественно, что мелкософт с этим посильно борется, повышая планку сложности реализации хука. Т.е. реализовать хук либо сложно, либо дорого :-).
              +4
              Да нет. Просто в форточках (которые до Win2K были «набором 32-битных расширений и графической оболочкой для 16-битного патча для 8-битной ОС, изначально созднанной для 4-битного микропроцессора») изначально никакой функциональности для перехвата чего либо (ну кроме SetWindowsHookEx) предусмотрено не было. В итоге народ начал изобретать велосипеды, т. к. проблему надо было решать, а нормально она решается только вставкой джампа на свой код в начале функции с предварительным дизассемблированием её кода на предмет получения длины замещаемых команд, дабы их потом перед переходом обратно выполнить. Удаление гланд через прямую кишку, в общем, как это обычно с форточками и бывает.
              0
              Подменить либу там действительно легко, но как же перехватить вызов метода и заменить его на свой? Развлекаться с SystemTap'ом или DTrace?
                0
                Вызов чего? Вы про перехват методов библиотечных плюсовых классов? Если да, то смотрите сырцы SkypeTab, я там, вроде как, адекватно это сделал и без плясок с бубном.
                  0
                  Вот про него родимого)))
                    0
                    Перехват там делается банальным объявлением своей реализации и загрузчик её подтянет. Единственное, надо выяснить имя символа для получения адреса оригинала, но его можно посмотреть либо в самом бинарнике, либо пнуть dladdr.
              –7
              Лучше бы рассказали как это сделать бесплатно.
                +6
                Извините, вы коммерческий софт пишете, или что? Вам в любом случае придётся раскошелиться на MS Authenticode, т. к. антивирусы с большим подозрением относятся к софту, выполняющему перехваты функций и при этом не имеющему цифровой подписи. Если же софт некоммерческий, то для такого использования есть бесплатная версия.
                  0
                  Мне интересно для развития.
                    +8
                    Интересно для развития — используйте версию для некоммерческого использования. В чём проблема?
                      +2
                      Читаете с нужного адреса пару десятков байт (максимальная длина команды в x86 в районе 15, если не ошибаюсь со всеми префиксами. Берете абсолютно любой дизассемблер длин, или полноценный и смотрите сколько команд затрет ваш jmp ptr длинной в 5 байт. Копируете эти команды в другое место, за ними пишете еще один jmp обратно (с учетом затертых команд). После этого пишете в нужном месте jmp на себя.
                      Вместо jmp addr(0xEB) можно использовать связку push const/ret. Выходит тоже самое.
                      еще как вариант — установить собственный обработчик векторного или структурного прерывания, и в нужных местах вписать int3/int1/ud2 или прочие команды, вызываюшие exception. Обрабатываете его, и прыгаете куда вам надо.
                      Таким же способом можно поставить аппаратный breakpoint через регистры dr0-dr3,dr7, тогда по факту вообще никакого патча не будет.
                        0
                        спасибо, что объяснили!
                        0
                        И я уж молчу про то, что в нормальных приложениях адреса API берутся из IAT. Таким образом будет достаточно подменить там адрес функции на адрес своего обработчика.
                        IAT можно найти распарсив MZ-PE заголовок
                          0
                          Эм. А дотнетовские приложения использующие P/Invoke с каких пор перестали быть нормальными? Не говоря уже о всяких древностях на VB6.
                      +2
                      В тексте есть 3 ссылки на другие статьи и библиотеки. Статьи хорошие, библиотеки бесплатные. Но на мой взгляд, и статьи и библиотеки существенно сложнее изложенного материала.
                        +3
                        1. Не «лучше», а " расскажите, пожалуйста".
                        2. Вроде как рассказали достаточно
                          +1
                          Вам достаточно. Про подмену средствами WinAPI не говорится.
                          «В этой статье мы рассмотрим замену колеса у машины. Покупаем колесо, едем на шиномонтаж, нам меняют колесо». Супер.
                            +2
                            Да, не говорится. В начале статьи сразу и говорят, что эта статья будет выгодно отличаться от остальных минимальностью захламления сложностями. При этом даются ссылки на более подробные статьи, посмотрите их…
                              –1
                              Спасибо кеп. Но для того, чтобы понять, что тут таки не WinAPI а сторонняя библиотека, приходится пол статьи читать. Это бесит.
                              +1
                              Продублирую для Вас ссылки из начала статьи: раз, два. Там Вам расскажут и про домкраты, и про секретные гайки и о том, как это делать ночью в темноте для чужой машины.
                              Моя статья предназначена для того, кому сразу читать не статьи сложно.
                                +1
                                *те статьи сложно
                                0
                                Вместо перехвата SendMessage можно попробовать сделать GetWindowsLong\SetWindowsLong с параметром WNDPROC. И точно так же обрабатывать SETTEXT
                            +1
                            Хуки — это хаки. Пускай заранее предусмотренные архитектурой, но хаки. Вызывающий код о них ничего не знает. Посылка сообщений «честнее».
                              +4
                              Данный конкретный пример можно сделать кучей способов (в том числе и посылкой сообщения). Есть многое, что посылкой сообщения сделать нельзя, а хуками можно.
                                0
                                Если хуки предусмотрены архитектурой. Или архитектура не может внешним хукам противодействовать.
                                  +2
                                  Ага, вот так что-нибудь сделают хуками, а пользователи потом третируют разработчика исходного софта — откуда, дескать, у меня там глюки. У нас был пример несколько лет назад. В программе использовался встроенный движок скрипта для автоматизации некоторых действий. Касперский со своими хуками влезал, чтобы проверить этот скрипт и вылетал с громким треском (он-то думал, что это Internet Explorer...). А выглядело так, что на ровном месте падает наша программа.
                                  • UFO just landed and posted this here
                                      +1
                                      Да, хуки очень легко использовать не верно. Вот даже Касперский с Интелом иногда ошибаются. Тем не менее, их можно использовать и правильно. Хорошие библиотеки могут иногда в этом помочь.
                                      • UFO just landed and posted this here
                                          +1
                                          Отличный ответ! программировать можно правильно… а можно неправильно :-)
                                            +2
                                            И я даже больше скажу! Программировать правильно — это более правильно, чем делать это неправильно!
                                            :)
                                  +2
                                  Извините, но #include «C:\Program Files\Microsoft Research\Detours Express 3.0\src\detours.h» — это аццкий ад, хоть и приложение тестовое. Для таких вещей в студии есть Additional Include Derictories.
                                    +4
                                    Я в курсе. Это был бы еще один скриншот и 3 строки текста. В статье вообще многим пожертвовано в ущерб простоте и краткости. И всё равно мне кажется излишне сложно и длинно вышло…
                                    +1
                                    Рекомендую madCodeHook(http://www.madshi.net/madCodeHookDescription.htm), помимо того что эта библиотека хучит она еще умеет инжектить во все вновь запущенные процессы. Очень удобная вещь
                                      +1
                                      А существует ли что-нибудь подобное для C#?
                                        +1
                                        Делаете в шарпе либу, экспортирующую функции через секцию PE-файла, используете описанный в статье метод.
                                          0
                                          Хотя, тут в DllMain всё это, придётся писать небольшой враппер.
                                          +1
                                          С некоторой натяжкой.
                                          +2
                                          Да в майкрософте совсем обалдели: за про версию (с поддержкой х64 и разрешением на коммерческое использование) просят, тарам-пам-пам, 10К долларов. И это при том, что сама библиотека занимает всего 250Кб кода (пусть, 500 с учетом отсутствия х64 в express edition). При чем написана она в достаточно примитивном стиле. Беда, в общем :(
                                            0
                                            Сколько просят — это их дело… мне вот интересно, её покупают?
                                              0
                                              Мы покупали.
                                              Писать дизассемблер для x64 было затратнее по деньгам и времени.
                                              Хотя добавлю, что дизассемблер в detours довольно паршивенький — увидит jmp в первых 5 байтах функции — и ква.
                                              Попадалась недавно с виду неплохая бесплатная библиотечка от одного из крупных вендоров — найти не могу.
                                            +1
                                            А как это Вы ценность продукта в строках кода меряете? По Вашему, любая строка кода — хоть в студенческой лабе, а хоть в автопилоте шаттла — одинаково стоит? Плюсы и минусы Detours честно описаны в статье, равно как и ссылки на альтернативные решения. ИМХО Detours прекрасен в качестве учебной библиотеки, он не вываливает на неподготовленного юзера горы системной информации. А вот в реальных проектах я бы использовал что-то другое (цена всё-таки аргумент).
                                              0
                                              Я же читал исходники :) единственная более-менее стоящая вещь там — дизассемблер, и то весьма средненького качества (выше уже писали), кроме того есть бесплатные аналоги. Например, я для тех же целей использовал «медиану», один день допиливания дал возможность перехватывать все что угодно. Так что не стоит оно таких денег.
                                            0
                                            Я некоторое время назад начал писать свой небольшой фреймворк для установки хуков на API, чтобы можно было сделать что-то типа:

                                            auto& hook = SD_HP_CREATE_HOOK( MessageBoxA, "user32.dll" );
                                            hook.setActive(true);
                                            hook.setCallback( boost::bind( consoleMsg, _3 ) );

                                            Конечно, она не дотягивает по могуществу до великого творения от микрософт, зато маленькая, открытая и моя )).

                                            Сейчас он работает на x32, хотя и не очень стабильно, да и код ещё бяка, но если всё пойдёт нормально и ко мне не наведается птица обломинго то через пару месяцев появится и x64 версия.
                                            Кому интересно — может посмотреть здесь
                                              0
                                              Описание:
                                              >> sd poroject free libs
                                              щто? sd? poroject?

                                              Ну прям 1-2 фразы на русском, а еще лучше с примерами использования (пусть даже еще не существующими, но то, как ожидается) — было бы заметно приятнее.
                                                0
                                                Здесь — test\hookproc\HookProcTest.cpp все примеры, работающие на текущий момент, описание действительно стоит поправить, просто не планировал это публиковать нигде, пока эту статью не увидел.
                                              –4
                                              Официальная библиотека для написания костылей? Внедрять чужой код в адресное пространство другого кода? Да хотя это еще ничего, раньше было принято вообще свои конфиги в c:\windows сваливать и системные библиотеки dll заменять. Уиндоуз-разработчики, они такие… особенные.
                                                +1
                                                Вы что-то совсем всё в одну кучу попутали.
                                                  +1
                                                  Ах, простите, посмотрел Ваш профиль — Вам можно.
                                                    +2
                                                    После ваших слов тоже полюбопытствовал. Ни фига себе, стрелку осциллографа рейтингометра зашкалило :-)
                                                  +2
                                                  Как ньюфаг, очень рад таким постам. Вдруг проявился интерес к ассемблеру и работе windows «за ширмой». И такие вещи как понимание вызовов функций и их перехват очень в этом помогает. Спасибо!
                                                    +1
                                                    я один неправильно прочитал заголовок с первого раза?
                                                      0
                                                      Вот сейчас сижу мучаюсь, никак не могу понять почему функцию в вашем примере хукает, а ReleaseDC никак не хочет…
                                                        0
                                                        Трейнеры для взрослых :-)

                                                        Only users with full accounts can post comments. Log in, please.