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

Хуки — это технология перехвата вызовов функций в чужих процессах. Хуки, как и любая достаточно мощная технология, могут быть использованы как в благих целях (снифферы, аудио\видеограбберы, расширения функционала закрытого ПО, логирование, багфиксинг) так и со злым умыслом (трояны, кряки, кейлоггеры). О хуках уже не раз писали и на Хабре и не на Хабре. Но вот в чём беда — почему-то каждая статья о хуках буквально со второго абзаца начинает рассказывать о «таблице виртуальных функций», «архитектуре памяти» и предлагает к изучению огромные блоки ассемблерного кода. Известно, что каждая формула в тексте снижает количество читателей вдвое, а уж такие вещи — так и вовсе вчетверо. Поэтому нужна статья, которая расскажет о хуках просто. Под катом нет ассемблера, нет сложных терминов и буквально два десятка строк очень простого кода на С++. Если вы давно хотели изучить хуки, но не знали с чего начать — начните с этой статьи.
Реальная задача
Для лучшего понимания того, что мы делаем — поставим себе какую-нибудь реальную задачу. Давайте, например сделаем так, чтобы браузер 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 я писал вот тут.
Хитрый план
- Понять, на какую функцию ставить хук.
- Сделать свою библиотеку с функцией, которая будет заменять исходную и делать нужные нам вещи.
- Установить хук (загрузить библиотеку в память нужного процесса и переставить указатель на нужную нам функцию).
- PROFIT!
Куда ставить хук
- Вместо SendMessage может использоваться PostMessage или что-то еще
- SendMessage может быть вообще не функцией, а макросом, ссылающимся на другую функцию (в дальнейшем мы увидим, что так оно и есть)
- Firefox, как некоторые кроссплатформенные приложения, может вообще не использовать функции Windows для рисования стандартных элементов окна, используя вместо этого какие-то собственные кросплатформенные элементы GUI (к счастью, это не так — но вдруг!)
Переходим в Firefox, открываем Хабр, дожидаемся изменения заголовка на нужный и возвращаемся в Api Monitor чтобы остановить мониторинг. Скорее всего, вы будете удивлены количеством вызванных функций — их могут быть сотни тысяч буквально за несколько секунд мониторинга. А мы ведь еще и следим далеко не за всем. Да-да, это всё реально происходит внутри безобидного открытия всего одного сайта в браузере! А вы еще жалуетесь, что эта пара секунд — слишком долго. 🙂

Найти нужную нам функцию поможет поиск по вкладке с результатами мониторинга. Вбиваем в поиск «WM_SETTEXT» и убеждаемся, что действительно имеются вызовы функции SendMessageW с этим параметром — с высокой вероятностью это и есть установка заголовка окна. Обратите внимание на «W» в конце названия функции — оно означает, что используется её юникодная версия. Для установки хуков важно знать точное имя подменяемой функции и теперь мы его знаем.
Делаем свою библиотеку
1. Запускаем Visual Studio.
2. Создаём новый проект: File->New->Project. Тип Visual C++ -> Win32 -> Win32 Project. В диалоге создания проекта указываем тип «Dll».
3. Открываем файл dllmain.cpp и пишем туда вот такой код:
#include #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. Это нам понадобиться для двух целей:
- Для вызова настоящей функции SendMessageW из нашей «подделки».
- Для восстановления указателя на реальную функцию в момент, когда мы захотим снять хук.
Функция 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!

Успехов в изучении хуков.
Объясните что такое hook в программировании.
Хуки — это просто Хуки — это технология перехвата вызовов функций в чужих процессах. Хуки, как и любая достаточно мощная технология, могут быть использованы как в благих целях (снифферы, аудио\видеограбберы, расширения функционала закрытого ПО, логирование, багфиксинг) так и со злым умыслом (трояны, кряки, кейлоггеры)
ты про ад-хук полиморфизм?
это когда убивают принудительный полиморфизм через виртуальные перегрузки или чето такое, сам не понял
Как уже сказал человек: «Хуки. — это технология перехвата вызовов функций в чужих процессах.»
Перед тем, как передать управление вызванной функции, вы исполняете какой-то дополнительный код. Выполняете эдакую вставку.
Эта «мощная технология, могут быть использованы как в благих целях (снифферы, аудио\видеограбберы, расширения функционала закрытого ПО, логирование, багфиксинг) так и со злым умыслом (трояны, кряки, кейлоггеры)»
Зачем вы его ответ забанили — непонятно. Объяснил кратко, конечно, но.
Англ. hook — крючёк, ловушка. На это и похоже — вы в какой-то точке ловите нить выполнения программного кода, разрезаете её ножничками, заставляете выполнять свой кусочек нити, потом связываете куски снова и отпускаете выполняться дальше. Но что-то дополнительное, непредусмотренное исходной нитью кода, ваш код уже проделал.
P. S.: Зато лучшим ответом был выбран ответ какого-то малолетнего озабоченного глупого подростка про содержимое трусов.
Привлечение школьников (из-за халявных домашних заданий) на данный сервис его однозначно испортило и убило в своей сути.
Хуки. Управление функциональными компонентами
Хуки позволяют определять и использовать состояние и другие возможности React без создания классов. По сути хуки представляют функции, которые позволяют подключиться к состоянию и другим возможностям, которые есть в React.
Мы можем создавать свои хуки, однако React по умолчанию уже предоставляет ряд встроенных хуков:
- useState : предназначен для управления состоянием компонентов
- useEffect : предназначен для перехвата различного рода изменений в компонентах, которые нельзя обработать внутри компонентов
- useContext : позволяет подписываться на контекст React
- useReducer : позволяет управлять локальным состоянием сложных компонентов
- useCallback : позволяет управлять функциями обратного вызова
- useMemo : предназначен для управления мемоизированными (грубо говоря кэшированными) значениями
- useRef : возвращать некоторое изменяемое значение, например, ссылку на html-элементы DOM, которыми затем можно управлять в коде JavaScript
- useImperativeHandle : настраивает объект, который передается родительскому компоненту при использовании ref
- useLayoutEffect : аналогичен хуку useEffect() , но вызывается синхронно после всех изменений в структуре DOM
- useDebugValue : предназначен для отображения некоторого значения в целях отладки
- useId : предназначен для генерации уникального идентификатора
- useTransition : применяется для создания переходов при рендеринге
- useDeferredValue : позволяет отложить рендеринг некритичных частей структуры DOM
- useSyncExternalStore : предназначен для синхронизации данных с внешними хранилищами
- useInsertionEffect : предназначен для библиотек, которые используют CSS в JS, для оптимизации при внедрении стилей при рендеринге
Переход от классов к хукам
Рассмотрим простейший пример, как мы можем перейти от классов к хукам. Допустим, у нас есть следующий класс-компонент:
METANIT.COM
Здесь определен компонент ClickButton, который принимает через props некоторое значение increment . В конструкторе определяется состояние в виде переменной counter , которая равна 0. Кроме того, в классе определяется метод press() , в котором изменяется состояние компонента.
Для изменения состояния вызывается другой метод — incrementCounter , который берет из props значение increment и использует его для увеличения значения переменной counter .
В коде класса-компонента определяется кнопка, по нажатию на которую как раз и вызывается метод press() :
В итоге по нажатию на кнопку мы получим увеличение переменной counter :

Теперь определим аналогичный компонент с использованием хуков:
METANIT.COM
В данном случае определен функциональный компонент ClickButtonHook , так как мы не можем использовать хуки в классах-компонентах, а только в функциях.
Вначале определяем переменные состояния:
const [count, setCount] = React.useState(0);
В данном случае определяются две переменных: count и setCount . Переменная count хранит состояние компонента, а переменная setCount позволяет изменять значение переменной count.
В функцию useState() передается число 0 — это то значение, которое по умолчанию получает переменная count .
Для изменения состояния в компоненте определена функция press() , которая выполняет выражение setCount(count + props.increment); — к переменной count прибавляется значение increment из props . Это выражение ( count + props.increment ) и определяет новое значение переменной count . Таким образом, состояние компонента ClickButtonHook будет изменено.
В коде также определена кнопка, по нажатию на которую вызывается метод press() . В итоге мы получим ту же программу, но с использованием хуков. И как видно, этот код несколько короче, чем код класса-компонента.
Подключение хуков
В примере выше библиотека React подключалась непосредственно на веб-страницу, где и определен весь код приложения. Однако если компоненты расположены в отдельных файлах, то мы можем импортировать хук useState (впрочем как и другие встроенные хуки) следующим образом:
import React, < useState >from "react"; function ClickButtonHook(props) < const [count, setCount] = React.useState(0); const press= () =>setCount(count + props.increment); returnCounter:; >
Increment:Ограничения при использовании хуков
Хуки имеют ряд ограничений при определении и использовании:
- Хуки вызываются только на верхнем уровне (top-level) компонента. Они НЕ вызываются внутри циклов, условных конструкций, внутри стандартных функций javascript.
- Хуки можно вызывать только из функциональных компонентов React, либо из других хуков. Но их нельзя вызывать из классов-компонентов. Функциональные компоненты можно определять как обычные функции:
function ClickButtonHook(props) < const [count, setCount] = React.useState(0); const press= () =>setCount(count + props.increment); returnCounter:; >
Increment:Либо в виде стрелочных функций:
const ClickButtonHook = (props)=> < const [count, setCount] = React.useState(0); const press= () =>setCount(count + props.increment); returnCounter:; >
Increment:Windows hook: просто о сложном
Что такое хук?
Что такое хук функций и для чего он нужен? В переводе с английского «hook» — ловушка. Поэтому о назначении хуков функции в Windows можно догадаться — это ловушка для функции. Иными словами, мы ловим функцию и берем управление на себя. После этого определения нам открываются заманчивые перспективы: мы можем перехватить вызов любой функции, подменить код на свой, тем самым изменив поведение любой программы на то, которое нам нужно (конечно, в рамках определенных ограничений).Целью данной статьи является демонстрация установки хука и его непосредственная реализация.
— Нельзя поверить в невозможное!
— Просто у тебя мало опыта, – заметила Королева. – В твоем возрасте я уделяла этому полчаса каждый день! В иные дни я успевала поверить в десяток невозможностей до завтрака!Где мне реально пригодились эти знания
Эти знания являются очень узкоспециализированными, и в повседневной практике разработки маловероятно, что они пригодятся, но знать о них, на мой взгляд, крайне желательно, даже если эти знания чисто теоретические. На моей практики же мне пригодились эти знания для решения следующих задач:
• Контроль входящего http-траффика и подмена «взрослого» контента на более безобидный.
• Логирование информации в случае копирования каких-либо файлов с подконтрольной сетевой папки.
• Незначительная модификация кода в проекте, от которого были утеряны исходники (да, и такое тоже случается)Методы установки хуков
Давайте перейдем от общих фраз к более детальному рассмотрению хуков. Мне известно несколько разновидностей реализации хука:
● Использование функции SetWindowsHookEx. Это весьма простой, оттого и ограниченный, метод. Он позволяет перехватывать только определенные функции, в основном связанные с окном (например, перехват событий, которые получает окно, щелчков мышкой, клавиатурного ввода). Достоинством этого метода является возможность установки глобальных хуков (например, сразу на все приложениях перехватывать клавиатурный ввод).
● Использование подмены адресов в разделе импорта DLL. Суть метода заключается в том, что любой модуль имеет раздел импорта, в котором перечислены все используемые в нем другие модули, а также адреса в памяти для экспортируемых этим модулем функций. Нужно подменить адрес в этом модуле на свой и управление будет передано по указанному адресу.
● Использование ключа реестра HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Windows\AppInit_Dlls. В нем необходимо прописать путь к DLL, но сделать это могут только пользователи с правами администратора. Этот метод хорош, если приложение не использует kernel32.dll (нельзя вызвать функцию LoadLibrary).
● Использование инъектирования DLL в процесс. На мой взгляд, это самый гибкий и самый показательный способ. Его-то мы и рассмотрим более подробно.Метод инъектирования
Инъектирование возможно, потому что функция ThreadStart, которая передается функции CreateThread, имеет схожую сигнатуру с функцией LoadLibrary (да и вообще структура dll и исполняемого файла очень схожи). Это позволяет указать метод LoadLibrary в качестве аргумента при создании потока.
Алгоритм инъектирования DLL выглядит так:
1. Находим адрес функции LoadLibrary из Kernel32.dll для потока, куда мы хотим инжектировать DLL.
2. Выделяем память для записи аргументов этой функции.
3. Создаем поток и в качестве ThreadStart функции указываем LoadLibrary и ее аргумент.
4. Поток идет на исполнение, загружает библиотеку и завершается.
5. Наша библиотека инъектирована в адресное пространство постороннего потока. При этом при загрузке DLL будет вызван метод DllMain с флагом PROCESS_ATTACH. Это как раз то место, где можно установить хуки на нужные функции. Далее рассмотрим саму установку хука.Установка хука
Подход, используемый при установке хука, можно разбить на следующие составные части:
1. Находим адрес функции, вызов которой мы хотим перехватывать (например, MessageBox в user32.dll).
2. Сохраняем несколько первых байтов этой функции в другом участке памяти.
3. На их место вставим машинную команду JUMP для перехода по адресу подставной функции. Естественно, сигнатура функции должна быть такой же, как и исходной, т. е. все параметры, возвращаемое значение и правила вызова должны совпадать.
4. Теперь, когда поток вызовет перехватываемую функцию, команда JUMP перенаправит его к нашей функции. На этом этапе мы можем выполнить любой нужный код.Далее можно снять ловушку, вернув первые байты из п.2 на место.
Итак, теперь нам понятно, как внедрить нужную нам DLL в адресное пространство потока и каким образом установить хук на функцию. Теперь попробуем совместить эти подходы на практике.
Тестовое приложение
Наше тестовое приложение будет довольно простым и написано на С#. Оно будет содержать в себе кнопку для показа MessageBox. Для примера, установим хук именно на эту функцию. Код тестового приложения:
public partial class MainForm : Form < [DllImport("user32.dll", CharSet = CharSet.Unicode)] public static extern int MessageBox(IntPtr hWnd, String text, String caption, uint type); public MainForm() < InitializeComponent(); this.Text = "ProcessID: " + Process.GetCurrentProcess().Id; >private void btnShowMessage_Click(Object sender, EventArgs e) < MessageBox(new IntPtr(0), "Hello World!", "Hello Dialog", 0); >>В качестве инъектора рассмотрим два варианта. Инъекторы, написанные на С++ и С#. Почему на двух языках? Дело в том, что многие считают, что С# — это язык, в котором нельзя использовать системные вещи, — это миф, можно :). Итак, код инъектора на С++:
#include "stdafx.h" #include #include #include int Wait(); int main() < // Пусть до библиотеки, которую хотим инъектировать. DWORD processId = 55; char* dllName = "C:\\_projects\\CustomHook\\Hooking\\Debug\\HookDll.dll"; // Запрашиваем PID процесса куда хотим инъектировать. printf("Enter PID to inject dll: "); std::cin >> processId; // Получаем доступ к процессу. HANDLE openedProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, processId); if (openedProcess == NULL) < printf("OpenProcess error code: %d\r\n", GetLastError()); return Wait(); >// Ищем kernel32.dll HMODULE kernelModule = GetModuleHandleW(L"kernel32.dll"); if (kernelModule == NULL) < printf("GetModuleHandleW error code: %d\r\n", GetLastError()); return Wait(); >// Ищем LoadLibrary (Суффикс A означает что работаем в ANSI, один байт на символ) LPVOID loadLibraryAddr = GetProcAddress(kernelModule, "LoadLibraryA"); if (loadLibraryAddr == NULL) < printf("GetProcAddress error code: %d\r\n", GetLastError()); return Wait(); >// Выделяем память под аргумент LoadLibrary, а именно - строку с адресом инъектируемой DLL LPVOID argLoadLibrary = (LPVOID)VirtualAllocEx(openedProcess, NULL, strlen(dllName), MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); if (argLoadLibrary == NULL) < printf("VirtualAllocEx error code: %d\r\n", GetLastError()); return Wait(); >// Пишем байты по указанному адресу. int countWrited = WriteProcessMemory(openedProcess, argLoadLibrary, dllName, strlen(dllName), NULL); if (countWrited == NULL) < printf("WriteProcessMemory error code: %d\r\n", GetLastError()); return Wait(); >// Создаем поток, передаем адрес LoadLibrary и адрес ее аргумента HANDLE threadID = CreateRemoteThread(openedProcess, NULL, 0, (LPTHREAD_START_ROUTINE)loadLibraryAddr, argLoadLibrary, NULL, NULL); if (threadID == NULL) < printf("CreateRemoteThread error code: %d\r\n", GetLastError()); return Wait(); >else < printf("Dll injected!"); >// Закрываем поток. CloseHandle(openedProcess); return 0; > int Wait() < char a; printf("Press any key to exit"); std::cin >> a; return 0; >Теперь тоже самое, но только на С#. Оцените, насколько код более компактен, нет буйства типов (HANDLE, LPVOID, HMODULE, DWORD, которые, по сути, означают одно и тоже).
public class Exporter < [DllImport("kernel32.dll", SetLastError = true)] public static extern IntPtr OpenProcess(ProcessAccessFlags processAccess, bool bInheritHandle, int processId); [DllImport("kernel32.dll", SetLastError = true)] public static extern IntPtr GetModuleHandle(string lpModuleName); [DllImport("kernel32.dll", CharSet = CharSet.Ansi, SetLastError = true)] public static extern IntPtr GetProcAddress(IntPtr hModule, string procName); [DllImport("kernel32.dll", SetLastError = true)] public static extern IntPtr VirtualAllocEx(IntPtr hProcess, IntPtr lpAddress, IntPtr dwSize, AllocationType flAllocationType, MemoryProtection flProtect); [DllImport("kernel32.dll", SetLastError = true)] public static extern bool WriteProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, byte[] lpBuffer, UIntPtr nSize, out IntPtr lpNumberOfBytesWritten); [DllImport("kernel32.dll", SetLastError = true)] public static extern IntPtr CreateRemoteThread(IntPtr hProcess, IntPtr lpThreadAttributes, uint dwStackSize, IntPtr lpStartAddress, IntPtr lpParameter, uint dwCreationFlags, out IntPtr lpThreadId); [DllImport("kernel32.dll", SetLastError = true)] public static extern Int32 CloseHandle(IntPtr hObject); >public class Injector < public static void Inject(Int32 pid, String dllPath) < IntPtr openedProcess = Exporter.OpenProcess(ProcessAccessFlags.All, false, pid); IntPtr kernelModule = Exporter.GetModuleHandle("kernel32.dll"); IntPtr loadLibratyAddr = Exporter.GetProcAddress(kernelModule, "LoadLibraryA"); Int32 len = dllPath.Length; IntPtr lenPtr = new IntPtr(len); UIntPtr uLenPtr = new UIntPtr((uint)len); IntPtr argLoadLibrary = Exporter.VirtualAllocEx(openedProcess, IntPtr.Zero, lenPtr, AllocationType.Reserve | AllocationType.Commit, MemoryProtection.ReadWrite); IntPtr writedBytesCount; Boolean writed = Exporter.WriteProcessMemory(openedProcess, argLoadLibrary, System.Text.Encoding.ASCII.GetBytes(dllPath), uLenPtr, out writedBytesCount); IntPtr threadIdOut; IntPtr threadId = Exporter.CreateRemoteThread(openedProcess, IntPtr.Zero, 0, loadLibratyAddr, argLoadLibrary, 0, out threadIdOut); Exporter.CloseHandle(threadId); >>Инъектируемая библиотека
Теперь самое интересное — код библиотеки, которая устанавливает хуки. Эта библиотека написана на С++, пока без аналога на C#.
// dllmain.cpp : Defines the entry point for the DLL application. #include "stdafx.h" #include #define SIZE 6 // Объявления функций и кастомных типов typedef int (WINAPI *pMessageBoxW)(HWND, LPCWSTR, LPCWSTR, UINT); int WINAPI MyMessageBoxW(HWND, LPCWSTR, LPCWSTR, UINT); void BeginRedirect(LPVOID); pMessageBoxW pOrigMBAddress = NULL; BYTE oldBytes[SIZE] = < 0 >; BYTE JMP[SIZE] = < 0 >; DWORD oldProtect, myProtect = PAGE_EXECUTE_READWRITE; BOOL APIENTRY DllMain( HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ) < switch (ul_reason_for_call) < case DLL_PROCESS_ATTACH: // Уведомим пользователя что мы подключились к процессу. MessageBoxW(NULL, L"I hook MessageBox!", L"Hello", MB_OK); // Идем адрес MessageBox pOrigMBAddress = (pMessageBoxW)GetProcAddress(GetModuleHandleW(L"user32.dll"), "MessageBoxW"); if (pOrigMBAddress != NULL) < BeginRedirect(MyMessageBoxW); >break; case DLL_THREAD_ATTACH: break; case DLL_THREAD_DETACH: break; case DLL_PROCESS_DETACH: break; > return TRUE; > void BeginRedirect(LPVOID newFunction) < // Массив-маска для записи команды перехода BYTE tempJMP[SIZE] = < 0xE9, 0x90, 0x90, 0x90, 0x90, 0xC3 >; memcpy(JMP, tempJMP, SIZE); // Вычисляем смещение относительно оригинальной функции DWORD JMPSize = ((DWORD)newFunction - (DWORD)pOrigMBAddress - 5); // Получаем доступ к памяти VirtualProtect((LPVOID)pOrigMBAddress, SIZE, PAGE_EXECUTE_READWRITE, &oldProtect); // Запоминаем старые байты memcpy(oldBytes, pOrigMBAddress, SIZE); // Пишем 4байта смещения. Да, код рассчитан только на x86 memcpy(&JMP[1], &JMPSize, 4); // Записываем вместо оригинальных memcpy(pOrigMBAddress, JMP, SIZE); // Восстанавливаем старые права доступа VirtualProtect((LPVOID)pOrigMBAddress, SIZE, oldProtect, NULL); > int WINAPI MyMessageBoxW(HWND hWnd, LPCWSTR lpText, LPCWSTR lpCaption, UINT uiType) < // Получаем доступ к памяти VirtualProtect((LPVOID)pOrigMBAddress, SIZE, myProtect, NULL); // Возвращаем старые байты (иначе будет переполнение стека) memcpy(pOrigMBAddress, oldBytes, SIZE); // Зовем оригинальную функцию, но подменяем заголовок int retValue = MessageBoxW(hWnd, lpText, L"Hooked", uiType); // Снова ставим хук memcpy(pOrigMBAddress, JMP, SIZE); // Восстанавливаем старые права доступа VirtualProtect((LPVOID)pOrigMBAddress, SIZE, oldProtect, NULL); return retValue; >Ну и несколько картинок напоследок. До установки хука:
И после установки:
В следующем нашей материале мы постараемся написать код библиотеки, которая устанавливает хуки на C#, т. к. механизм инъектирования управляемого кода заслуживает отдельной статьи.


