Перехват API. Это просто!

Перехват API. Это просто!

В статье рассказывает о перехвате Windows API функций. Кроме самого перехвата, затронут вопрос запуска оригинала перехваченной функции.

Под перехватом понимается такое изменение тела API функции или вызывающего её кода, что-бы при вызове функции, управление передавалось не ей, а нашей функции.

Многие считают что перехват API это что-то такое сложное и "тёмное", а реализовать на Visual Basic'е вообще невозможно...

На самом деле всё очень просто!

В данной статье мы рассмотрим перехват методом модификации тела функции (сплайсинга) на примере "Beep" из библиотеки "Kernel32.dll".

Причём мы не только перехватим функцию, но и ещё вызовем оригинал с изменёнными параметрами и вернём в вызывающую программу своё значение :)

Всё это мы сделаем на "чистом" VB!

Для упрощения статьи, будем перехватывать вызов API в своём процессе.

Отличие перехвата функций в "чужих" процессах заключается лишь в способе модификации функции.

Давайте подумаем, что нам нужно для перехвата API функции?

Во первых, сама перехватываемая функция :)

Во вторых, Функция - перехватчик, принимающая точно такие-же параметры как и перехватываемая функция.

В третьих, код, который будет устанавливать перехватчик. И наконец сам перехватчик =)

Рассмотрим способ перехвата.

Как было написано выше, он заключается в модификации тела перехватываемой API функции.

Для осуществления перехвата нам нужно получить адрес перехватываемой функции, после чего записать по этому адресу 6 байт кода, который передаст управление на нашу функцию. Это всё =)

Что же за такие 6 байт кода? Это 2 инструкции в машинном коде, по байту на каждую, и число типа Long, являющееся адресом нашей функции.

Вот этот код на языке ассемблера (для объяснения происходящего): push dword Адрес_Нашей_Функции ret

Первая инструкция кладёт в стёк адрес нашей функции, а вторая осуществяет переход по данному адресу, одновременно удаляя его из стёка. Таким образом состояние стёка остаётся точно таким-же как и до выполнения кода-перехватчика.

В машинном коде, инструкция push это число число 68 в шестнадцатеричной системе счисления, а ret - С3 соответственно.

Если нам понадобится снять перехват, то нужно просто восстанавивить эти 6 байт оригинальными.

Чтож, с методом перехвата разобрались. Переходим к практической реализации.

Сначала напишем функцию-двойник перехватываемой API.

Поскольку перехватываемая функция находится в библиотеке, то и наша функция также будет находиться в библиотеке.

Стоп! А как мы создадим API DLL в Visual Basiс??? Он же поддерживает только создание ActivX DLL!

По секрету, VB может намного больше чем заявлено. Просто некоторые возможности не документированы.

Для компиляции API DLL мы воспользуемся Add-in'ом от SCINER'a.

Скачать данный Add-in можно здесь

А теперь наша функция:

Как помните, мы хотим не просто перехватить функцию, но ещё и вызвать оригинал с изменёнными параметрами, а в вызвашую программу вернуть произвольный результат, который может не иметь никакого отношения к тому, что вернёт оригинал :)

Тут возникает вопрос - "Как вызвать оригинал, если функция перехвачена?".

Всё верно, никак... Для этого нужно сначала восстановить функцию и только потом вызывать.

После завершения работы оригинала мы снова установим перехватчик.

Объявим необходимые функции и переменные.

Листинг : Объявление функций и переменных

Private Declare Function GetProcAddress Lib "kernel32" (ByVal hModule As Long, ByVal lpProcName As String) As Long

Private Declare Function GetModuleHandle Lib "kernel32" Alias "GetModuleHandleA" (ByVal lpModuleName As String) As Long

Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As Long)

Private SourceAddr As Long ' - Адрес буффера с перехватчиком

Private BackupAddr As Long ' - Адрес буффера с оригинальными байтами функции

' - Декларация перехватываемой функции для вызова оригинала

Public Declare Function Beep Lib "kernel32" (ByVal dwFreq As Long, ByVal dwDuration As Long) As Long

Теперь напишем функцию для настройки "буфферов".

Листинг : Функция настройки

'

' - Установка адресов буфферов

' - необходимо для временного снятия перехвата

'

Public Sub setBuffers(ByVal SourceAddress As Long, ByVal BackupAddress As Long)

SourceAddr = SourceAddress

BackupAddr = BackupAddress

End Sub

И наконец, функция-двойник перехватываемой API.

К слову, имя функции может быть любым, т. к. вызов функций всегда осуществляется по адресу.

Главное что-бы функция принимала такое же количество и тип параметров как и оригинал.

Листинг : Функция перехватчик

'

' - Функция-перехватчик для API Beep

' - Вызывает оригинальную функцию

'

Public Function myBeep(ByVal Freq As Long, ByVal Duration As Long) As Long

Dim hModule As Long, PAddr As Long

' - Снимаем перехват hModule = GetModuleHandle("kernel32")

PAddr = GetProcAddress(hModule, "Beep")

CopyMemory ByVal PAddr, ByVal BackupAddr, 6

' - Вызываем оригинальную функцию с изменёнными параметрами

Beep Freq + 5000, 500

' - И снова устанавливаем перехват

CopyMemory ByVal PAddr, ByVal SourceAddr, 6

' - Возвращаем произвольный результат myBeep = Freq + Duration

End Function

На этом с библиотекой закончено. Компилируем с именем "vbApiHook. dll" и переходим к написанию тестовой программки.

Создаём проект обычного EXE, удаляем форму, добавляем модуль (в примере весь код находитс в модуле, хотя от этого ничего не зависит).

Как обычно, сначала объявим необходимые функции и переменные.

Листинг : Объявление функций и переменных

' - Функции для работы с памятью

Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As Long)

Private Declare Function VirtualProtect Lib "kernel32" (ByVal lpAddress As Long, ByVal dwSize As Long, ByVal flNewProtect As Long, lpflOldProtect As Long) As Long

Private Const PAGE_UTE_READWRITE = &H40&

' - Функции для работы с модулями

Private Declare Function GetProcAddress Lib "kernel32" (ByVal hModule As Long, ByVal lpProcName As String) As Long

Private Declare Function GetModuleHandle Lib "kernel32" Alias "GetModuleHandleA" (ByVal lpModuleName As String) As Long

' - Декларация перехватываемой функции

Private Declare Function Beep Lib "kernel32" (ByVal dwFreq As Long, ByVal dwDuration As Long) As Long

' - Настройка библиотки с нашей функцией

Private Declare Sub setBuffers Lib "vbapihook. dll" (ByVal SourceAddress As Long, ByVal BackupAddr As Long)

Dim HookBackup(5) As Byte ' - Массив для сохранения оригинального начала функции

Dim HookSource(5) As Byte ' - Массив для кода перехватчика

И теперь, самая главная функция, выполняющая перехват функции по указаному адресу.

Она принимает 2 параметра: адрес перехватываемой функции и адрес нашей функции.

Обычно, область памяти с библиотекой защищена от записи, поэтому перед записью перехватчика необходимо разрешить запись в данном участке памяти.

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

После этого собираем перехватчик в отдельном буфере (напомню, что он используется из нашего двойника API для восстановления загрузчика)

Когда перехватчик готов, а запись разрешена, то мы просто копируем перехватчик в начало перехватываемой функции.

Листинг : Перехват функции

'

' - Перехват функции

'

Private Sub HookFunction(Addr As Long, NewAddr As Long)

Dim OldProtect as Long

' - Меняем права доступа.

VirtualProtect ByVal Addr, 5, PAGE_UTE_READWRITE, OldProtect

' - Сохраняем оригинальное начало функции

CopyMemory HookBackup(0), ByVal Addr, 6

' - Собираем перехватчик

HookSource(0) = &H68 ' - Push

CopyMemory HookSource(1), NewAddr, 4 ' - Адрес нашей функции

HookSource(5) = &HC3 ' - Ret

' - Записываем перехватчик в функцию

CopyMemory ByVal Addr, ByVal VarPtr(HookSource(0)), 6

End Sub

Восстанавливается перехваченая функция предельно просто.

Для этого нужно просто вернуть её начало в первозданный вид.

Листинг : Снятие перехвата

'

' - Снятие перехвата

'

Private Sub RestoreFunction(ByVal Addr As Long)

CopyMemory ByVal Addr, HookBackup(0), 6

End Sub

Осталось только протестировать всё это в дейтсвии.

Поскольку мы задекларировали функцию setBuffers из библиотеки с нашим двойником API функции Beep, то данная DLL будет подгружена автоматически при запуске приложения.

Листинг : Тестируем перехват

Sub Main()

Dim hModule As Long

Dim TargetAddr As Long, HookAddr As Long ' - Адреса цели и нашей функции

' - Настраиваем библиотеку с нашей функцией setBuffers ByVal VarPtr(HookSource(0)), ByVal VarPtr(HookBackup(0))

' - Получаем адрес перехватываемой функции hModule = GetModuleHandle("kernel32")

TargetAddr = GetProcAddress(hModule, "Beep")

' - Получаем адрес функции-двойника hModule = GetModuleHandle("vbapihook")

HookAddr = GetProcAddress(hModule, "myBeep")

' - Перехватываем функцию

HookFunction TargetAddr, HookAddr

' - Тестируем перехват...

MsgBox Beep(1000, 1000)

' - Восстанавливаем функцию

RestoreFunction ByVal TargetAddr

' - Тестируем оригинал Beep...

Beep 1000, 1000

End Sub

Стоит заметить, что перед вызовом оригинальной функции желательно "замораживать" все потоки приложения (кроме текущего).

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

Существует более стабильный способ вызова перехваченной функции. Он заключается в том, что нужно скопировать из оригинальной функции в отдельное место не произвольное число байт, а целое число инструкций. Затем добавить к ним инструкцию перехода на "продолжение" оригинальной функции.

Вызов оригинала происходит через этот "переходник". Таким образом необходимость в снятии / установке перехвата пропадает.


Карта сайта


Информационный сайт Webavtocat.ru