В компьютерном программировании , термин зацепляющие охватывает целый ряд методов , используемых для изменения или усиливают поведение операционной системы , из приложений , или других программных компонентов путем перехвата вызовов функций или сообщения или события , передаваемые между программными компонентами . Код, обрабатывающий такие перехваченные вызовы функций, события или сообщения, называется ловушкой .
Перехват используется для многих целей, включая отладку и расширение функциональности. Примеры могут включать перехват сообщений о событиях клавиатуры или мыши до того, как они достигнут приложения, или перехват вызовов операционной системы для отслеживания поведения или изменения функции приложения или другого компонента. Он также широко используется в программах для тестирования производительности, например, для измерения частоты кадров в 3D-играх, где вывод и ввод данных осуществляются посредством перехвата.
Перехват также может быть использован вредоносным кодом. Например, руткиты , части программного обеспечения, которые пытаются сделать себя невидимыми, подделывая выходные данные вызовов API , которые в противном случае раскрыли бы их существование, часто используют методы перехвата.
Методы
Обычно перехватчики вставляются, когда программное обеспечение уже запущено, но перехват - это тактика, которую также можно использовать до запуска приложения. Оба эти метода более подробно описаны ниже.
Модификация исходного кода
Изменяя исходный код исполняемого файла или библиотеки перед запуском приложения с помощью методов обратного проектирования , вы также можете добиться перехвата. Обычно это используется для перехвата вызовов функций, чтобы контролировать или полностью заменять их.
Например, с помощью дизассемблера , то точка входа из функции внутри модуля может быть найдена. Затем его можно изменить, чтобы вместо этого динамически загружать какой-либо другой библиотечный модуль, а затем заставить его выполнять желаемые методы в этой загруженной библиотеке. Если применимо, другой связанный подход, с помощью которого может быть достигнуто подключение, - это изменение таблицы импорта исполняемого файла. Эта таблица может быть изменена для загрузки любых дополнительных библиотечных модулей, а также для изменения внешнего кода, вызываемого при вызове функции приложением.
Альтернативный метод достижения перехвата функций - перехват вызовов функций через библиотеку-оболочку . При создании оболочки вы создаете свою собственную версию библиотеки, загружаемой приложением, со всеми функциями исходной библиотеки, которую оно заменит. То есть все доступные функции в оригинале и замене практически одинаковы. Эта библиотека-оболочка может быть спроектирована так, чтобы вызывать любую функциональность из исходной библиотеки или заменять ее совершенно новым набором логики.
Модификация среды выполнения
Операционные системы и программное обеспечение могут предоставлять средства для простой вставки обработчиков событий во время выполнения . Он доступен при условии, что процессу, вставляющему ловушку, предоставлено достаточно разрешений для этого. Microsoft Windows, например, позволяет вставлять хуки, которые можно использовать для обработки или изменения системных событий и событий приложений для диалогов , полос прокрутки и меню, а также других элементов. Он также позволяет ловушке вставлять, удалять, обрабатывать или изменять события клавиатуры и мыши . Linux предоставляет еще один пример, где перехватчики могут использоваться аналогичным образом для обработки сетевых событий в ядре через NetFilter .
Когда такая функциональность не предоставляется, особая форма перехвата использует перехват вызовов библиотечных функций, выполняемых процессом. Перехват функций реализуется путем изменения самых первых нескольких инструкций кода целевой функции для перехода к внедренному коду. В качестве альтернативы в системах, использующих концепцию разделяемых библиотек , таблица векторов прерываний или таблица дескрипторов импорта могут быть изменены в памяти. По сути, в этой тактике используются те же идеи, что и при модификации исходного кода, но вместо этого изменяются инструкции и структуры, расположенные в памяти процесса, когда он уже запущен.
Образец кода
Перехват таблицы виртуальных методов
Каждый раз, когда класс определяет / наследует виртуальную функцию (или метод), компиляторы добавляют в класс скрытую переменную-член, которая указывает на таблицу виртуальных методов (VMT или Vtable). Большинство компиляторов помещают скрытый указатель VMT в первые 4 байта каждого экземпляра класса. VMT - это в основном массив указателей на все виртуальные функции, которые могут вызывать экземпляры класса. Во время выполнения эти указатели устанавливаются так, чтобы указывать на нужные функции, потому что во время компиляции еще не известно, должна ли быть вызвана базовая функция или должна быть вызвана переопределенная версия функции из производного класса (тем самым позволяя для полиморфизма ). Следовательно, виртуальные функции могут быть подключены путем замены указателей на них в любом VMT, который они появляются. В приведенном ниже коде показан пример типичного перехвата VMT в Microsoft Windows, написанного на C ++. [1]
#include #include "windows.h" используя пространство имен std ; класс VirtualClass { общедоступный : int число ; virtual void VirtualFn1 () // Это виртуальная функция, которая будет подключена. { cout << "VirtualFn1 вызывается" << number ++ << " \ n \ n " ; } }; используя VirtualFn1_t = void ( __thiscall * ) ( void * thisptr ); VirtualFn1_t orig_VirtualFn1 ; void __fastcall hkVirtualFn1 ( void * thisptr , int edx ) // Это наша функция перехвата, которую мы заставим программу вызывать вместо исходной функции VirtualFn1 после завершения перехвата. { cout << "Функция перехвата называется" << " \ n " ; orig_VirtualFn1 ( thisptr ); // Вызов исходной функции. } int main () { VirtualClass * myClass = новый VirtualClass (); // Создаем указатель на динамически выделяемый экземпляр VirtualClass. void ** vTablePtr = * reinterpret_cast < void ***> ( myClass ); // Находим адрес, который указывает на базу VMT VirtualClass (которая затем указывает на VirtualFn1), и сохраняем его в vTablePtr. DWORD oldProtection ; VirtualProtect ( vTablePtr , 4 , PAGE_EXECUTE_READWRITE , & oldProtection ); // Снимает защиту страницы в начале VMT, чтобы мы могли перезаписать ее первый указатель. orig_VirtualFn1 = переинтерпретировать_каст < VirtualFn1_t > ( * vTablePtr ); // Сохраняет указатель на VirtualFn1 из VMT в глобальной переменной, чтобы к нему можно было получить доступ позже после того, как его запись в VMT была // перезаписана нашей функцией перехвата. * vTablePtr = & hkVirtualFn1 ; // Перезаписываем указатель на VirtualFn1 в виртуальной таблице указателем на нашу функцию-перехватчик (hkVirtualFn1). VirtualProtect ( vTablePtr , 4 , oldProtection , 0 ); // Восстановить старую защиту страницы. myClass -> VirtualFn1 (); // Вызов виртуальной функции из нашего экземпляра класса. Поскольку теперь он подключен, это вызовет нашу функцию-перехватчик (hkVirtualFn1). myClass -> VirtualFn1 (); myClass -> VirtualFn1 (); удалить myClass ; возврат 0 ; }
Важно отметить, что все виртуальные функции должны быть функциями-членами класса, и все (нестатические) функции-члены класса вызываются с соглашением о вызовах __thiscall (если функция-член не принимает переменное количество аргументов, и в этом случае она вызывается с __cdecl). Соглашение о вызове __thiscall передает указатель на экземпляр вызывающего класса (обычно называемый указателем «this») через регистр ECX (в архитектуре x86). Следовательно, чтобы функция-перехватчик могла правильно перехватить переданный указатель this и принять его в качестве аргумента, она должна заглянуть в регистр ECX. В приведенном выше примере это делается путем установки функции перехвата (hkVirtualFn1) на использование соглашения о вызовах __fastcall, которое заставляет функцию перехвата искать один из своих аргументов в регистре ECX.
Также обратите внимание, что в приведенном выше примере функция-перехватчик (hkVirtualFn1) сама не является функцией-членом, поэтому она не может использовать соглашение о вызовах __thiscall. Вместо этого следует использовать __fastcall, потому что это единственное другое соглашение о вызовах, которое ищет аргумент в регистре ECX.
C # обработчик событий клавиатуры
В следующем примере выполняется подключение к событиям клавиатуры в Microsoft Windows с помощью Microsoft .NET Framework .
using System.Runtime.InteropServices ;Пространство имен Крючки { общественный класс KeyHook { / * Переменные - члены * / защищенный статический INT Hook ; защищенный статический делегат LowLevelKeyboardDelegate ; защищенный статический объект только для чтения Lock = новый объект (); защищенный статический логический объект IsRegistered = false ; / * Импорт DLL * / [DllImport ("user32")] private static extern int SetWindowsHookEx ( int idHook , LowLevelKeyboardDelegate lpfn , int hmod , int dwThreadId ); [DllImport ("user32")] частный статический extern int CallNextHookEx ( int hHook , int nCode , int wParam , KBDLLHOOKSTRUCT lParam ); [DllImport ("user32")] частный статический extern int UnhookWindowsHookEx ( int hHook ); / * Типы и константы * / защищенный делегат int LowLevelKeyboardDelegate ( int nCode , int wParam , ref KBDLLHOOKSTRUCT lParam ); частный Const ИНТ HC_ACTION = 0 ; частный Const ИНТ WM_KEYDOWN = 0 x0100 ; частный Const ИНТ WM_KEYUP = 0 x0101 ; частный Const ИНТ WH_KEYBOARD_LL = 13 ; [StructLayout (LayoutKind.Sequential)] общедоступная структура KBDLLHOOKSTRUCT { общедоступная int vkCode ; общедоступный int scanCode ; общедоступные флаги int ; общественное ИНТ время ; public int dwExtraInfo ; } / * Методы * / static private int LowLevelKeyboardHandler ( int nCode , int wParam , ref KBDLLHOOKSTRUCT lParam ) { if ( nCode == HC_ACTION ) { if ( wParam == WM_KEYDOWN ) System . Консоль . Out . WriteLine ( "Ключ вниз:" + lParam . VkCode ); иначе, если ( wParam == WM_KEYUP ) System . Консоль . Out . WriteLine ( "Key Up:" + lParam . VkCode ); } return CallNextHookEx ( Hook , nCode , wParam , lParam ); } общедоступный статический логический регистр RegisterHook () { блокировка ( Блокировка ) { если ( IsRegistered ) вернет истину ; Делегат = LowLevelKeyboardHandler ; Крючок = SetWindowsHookEx ( WH_KEYBOARD_LL , Делегат , Маршал . GetHINSTANCE ( Система . Отражение . Сборка . GetExecutingAssembly (). GetModules () [ 0 ] ). ToInt32 (), 0 ); if ( Hook ! = 0 ) return IsRegistered = true ; Делегат = ноль ; вернуть ложь ; } } public static bool UnregisterHook () { lock ( Lock ) { return IsRegistered = ( UnhookWindowsHookEx ( Hook ) ! = 0 ); } } } }
Перехват API / функций / перехват с использованием инструкции JMP, также известной как сращивание
Следующий исходный код является примером способа зацепления API / функции , которые крючки путем перезаписи первых шесть байт в целевой функции с JMP инструкцией к новой функции. Код компилируется в файл DLL, а затем загружается в целевой процесс с использованием любого метода внедрения DLL . Используя резервную копию исходной функции, можно затем снова восстановить первые шесть байтов, чтобы вызов не прерывался. В этом примере подключена функция Win32 API MessageBoxW. [2]
/ * Эта идея основана на подходе chrom-lib, распространяемом по лицензии GNU LGPL. Источник chrom-lib: https://github.com/linuxexp/chrom-lib Copyright (C) 2011 Raja Jamwal * / #include #define SIZE 6 ЬурейеЕ междунар ( WINAPI * pMessageBoxW ) ( HWND , LPCWSTR , LPCWSTR , UINT ); // Messagebox прототип INT WINAPI MyMessageBoxW ( HWND , LPCWSTR , LPCWSTR , UINT ); // Наш объезд void BeginRedirect ( LPVOID ); pMessageBoxW pOrigMBAddress = NULL ; // адрес исходного BYTE oldBytes [ РАЗМЕР ] = { 0 }; // резервное копирование БАЙТА JMP [ РАЗМЕР ] = { 0 }; // 6-байтовая инструкция JMP DWORD oldProtect , myProtect = PAGE_EXECUTE_READWRITE ; INT APIENTRY DllMain ( HMODULE hDLL , DWORD Reason , LPVOID Reserved ) { switch ( Reason ) { case DLL_PROCESS_ATTACH : // если прикреплен pOrigMBAddress = ( pMessageBoxW ) GetProcAddress ( GetModuleHandleA ( "user32.dll" ), // получить адрес исходного "сообщения" " ); если ( pOrigMBAddress ! = NULL ) BeginRedirect ( MyMessageBoxW ); // начать объезд break ; Случай DLL_PROCESS_DETACH : VirtualProtect (( LPVOID ) pOrigMBAddress , РАЗМЕР , myProtect , & oldProtect ); // назначаем защиту от чтения и записи memcpy ( pOrigMBAddress , oldBytes , SIZE ); // восстановление резервной копии VirtualProtect (( LPVOID ) pOrigMBAddress , SIZE , oldProtect , & myProtect ); // сброс защиты case DLL_THREAD_ATTACH : case DLL_THREAD_DETACH : break ; } return TRUE ; } void BeginRedirect ( LPVOID newFunction ) { BYTE tempJMP [ SIZE ] = { 0xE9 , 0x90 , 0x90 , 0x90 , 0x90 , 0xC3 }; // 0xE9 = JMP 0x90 = NOP 0xC3 = RET memcpy ( JMP , tempJMP , SIZE ); // сохранить инструкцию jmp в JMP DWORD JMPSize = (( DWORD ) newFunction - ( DWORD ) pOrigMBAddress - 5 ); // вычисляем расстояние перехода VirtualProtect (( LPVOID ) pOrigMBAddress , SIZE , // назначаем защиту от чтения и записи PAGE_EXECUTE_READWRITE , & oldProtect ); memcpy ( oldBytes , pOrigMBAddress , SIZE ); // делаем резервную копию memcpy ( & JMP [ 1 ], & JMPSize , 4 ); // заполняем nop расстоянием перехода (JMP, distance (4 байта), RET) memcpy ( pOrigMBAddress , JMP , SIZE ); // установить инструкцию перехода в начале исходной функции VirtualProtect (( LPVOID ) pOrigMBAddress , SIZE , oldProtect , & myProtect ); // сброс защиты } INT WINAPI MyMessageBoxW ( HWND HWND , LPCWSTR lpText , LPCWSTR lpCaption , UINT uiType ) { VirtualProtect (( LPVOID ) pOrigMBAddress , РАЗМЕР , myProtect , & oldProtect ); // назначаем защиту от чтения и записи memcpy ( pOrigMBAddress , oldBytes , SIZE ); // восстанавливаем резервную копию int retValue = MessageBoxW ( hWnd , lpText , lpCaption , uiType ); // получить возвращаемое значение исходной функции memcpy ( pOrigMBAddress , JMP , SIZE ); // снова устанавливаем инструкцию перехода VirtualProtect (( LPVOID ) pOrigMBAddress , SIZE , oldProtect , & myProtect ); // сброс защиты return retValue ; // возвращаем исходное возвращаемое значение }
Крючок Netfilter
В этом примере показано, как использовать подключение для изменения сетевого трафика в ядре Linux с помощью Netfilter .
#include #include #include #include #include #include #include #include / * Порт, на который мы хотим отбрасывать пакеты * / static const uint16_t port = 25 ;/ * Это сама функция перехвата * / static unsigned int hook_func ( unsigned int hooknum , struct sk_buff ** pskb , const struct net_device * in , const struct net_device * out , int ( * okfn ) ( struct sk_buff * )) { struct iphdr * iph = ip_hdr ( * pskb ); struct tcphdr * tcph , tcpbuf ; если ( iph -> протокол ! = IPPROTO_TCP ) вернуть NF_ACCEPT ; tcph = skb_header_pointer ( * pskb , ip_hdrlen ( * pskb ), sizeof ( * tcph ), & tcpbuf ); если ( tcph == NULL ) вернуть NF_ACCEPT ; возврат ( tcph -> dest == порт ) ? NF_DROP : NF_ACCEPT ; }/ * Используется для регистрации нашей функции-перехватчика * / static struct nf_hook_ops nfho = { . крюк = hook_func , . hooknum = NF_IP_PRE_ROUTING , . пф = NFPROTO_IPV4 , . приоритет = NF_IP_PRI_FIRST , };статический __init int my_init ( void ) { return nf_register_hook ( & nfho ); }статический __exit void my_exit ( void ) { nf_unregister_hook ( & nfho ); }module_init ( my_init ); module_exit ( my_exit );
Внутренняя привязка IAT
Следующий код демонстрирует, как перехватить функции, импортированные из другого модуля. Это можно использовать для перехвата функций в процессе, отличном от вызывающего. Для этого код должен быть скомпилирован в файл DLL, а затем загружен в целевой процесс с использованием любого метода внедрения DLL . Преимущество этого метода заключается в том, что он менее обнаруживается антивирусным программным обеспечением и / или античитерским программным обеспечением , его можно превратить во внешний хук, который не использует никаких злонамеренных вызовов. Портативный Исполняемый заголовок содержит Import Address Table (IAT), который можно манипулировать , как показано в источнике ниже. Приведенный ниже источник работает под Microsoft Windows.
#include ЬурейеЕ INT ( __stdcall * pMessageBoxA ) ( HWND HWND , LPCSTR lpText , LPCSTR lpCaption , UINT uType ); // Это «тип» вызова MessageBoxA. pMessageBoxA RealMessageBoxA ; // Это сохранит указатель на исходную функцию.void DetourIATptr ( const char * function , void * newfunction , HMODULE module );ИНТ __stdcall NewMessageBoxA ( HWND HWND , LPCSTR lpText , LPCSTR lpCaption , UINT uType ) { // Наша функция поддельной Printf ( "Строка Направлено MessageBoxA Был:% s \ п " , lpText ); return RealMessageBoxA ( hWnd , lpText , lpCaption , uType ); // Вызов реальной функции }int main ( int argc , CHAR * argv []) { DetourIATptr ( "MessageBoxA" , ( void * ) NewMessageBoxA , 0 ); // Перехватываем функцию MessageBoxA ( NULL , «Just A MessageBox» , «Just A MessageBox» , 0 ); // Вызов функции - это вызовет наш фальшивый хук. возврат 0 ; }void ** IATfind ( const char * function , HMODULE module ) { // Найти запись IAT (импорт таблицы адресов), специфичную для данной функции. int ip = 0 ; если ( модуль == 0 ) модуль = GetModuleHandle ( 0 ); PIMAGE_DOS_HEADER pImgDosHeaders = ( PIMAGE_DOS_HEADER ) модуль ; PIMAGE_NT_HEADERS pImgNTHeaders = ( PIMAGE_NT_HEADERS ) (( LPBYTE ) pImgDosHeaders + pImgDosHeaders -> e_lfanew ); PIMAGE_IMPORT_DESCRIPTOR pImgImportDesc = ( PIMAGE_IMPORT_DESCRIPTOR ) (( LPBYTE ) pImgDosHeaders + pImgNTHeaders -> OptionalHeader . DataDirectory [ IMAGE_DIRECTORY_ENTRY_IMPORT ]. VirtualAddress );if ( pImgDosHeaders -> e_magic ! = IMAGE_DOS_SIGNATURE ) printf ( "Ошибка libPE: e_magic не является действительной подписью DOS \ n " );для ( IMAGE_IMPORT_DESCRIPTOR * н.о.р. = pImgImportDesc ; IID -> Name =! NULL ; н.о.р. ++ ) { для ( INT funcIdx = 0 ; * ( funcIdx + ( LPVOID * ) ( н.о.р. -> FirstThunk + ( SIZE_T ) модуль )) =! NULL ; funcIdx ++ ) { char * modFuncName = ( char * ) ( * ( funcIdx + ( SIZE_T * ) ( iid -> OriginalFirstThunk + ( SIZE_T ) module )) + ( SIZE_T ) module + 2 ); const uintptr_t nModFuncName = ( uintptr_t ) modFuncName ; bool isString = ! ( nModFuncName & ( sizeof ( nModFuncName ) == 4 ? 0x80000000 : 0x8000000000000000 )); if ( isString ) { if ( ! _stricmp ( function , modFuncName )) return funcIdx + ( LPVOID * ) ( iid -> FirstThunk + ( SIZE_T ) module ); } } } return 0 ; }void DetourIATptr ( const char * function , void * newfunction , HMODULE module ) { void ** funcptr = IATfind ( function , module ); если ( * funcptr == newfunction ) return ;DWORD oldrights , newrights = PAGE_READWRITE ; // Обновляем защиту до READWRITE VirtualProtect ( funcptr , sizeof ( LPVOID ), newrights , & oldrights );RealMessageBoxA = ( pMessageBoxA ) * funcptr ; // Некоторым компиляторам требуется приведение типа "MinGW", но они не уверены в MSVC * funcptr = newfunction ;// Восстанавливаем старые флаги защиты памяти. VirtualProtect ( funcptr , SizeOf ( LPVOID ), oldrights , и newrights ); }
Смотрите также
- Обратный звонок (информатика)
- Делегирование (программирование)
- Прекратить и остаться резидентом
- Выход пользователя
- WinAPIOverride32
Рекомендации
- ^ psyfl, [1]
- ^ Для получения дополнительной информации см. Http://ntvalk.blogspot.nl/2013/11/hooking-explained-detouring-library.html
- Джонатан Дэниел (27 ноября 2013 г.). «Объяснение перехвата: обход вызовов библиотек и исправление vtable в Windows / Linux / MAC-OSX» . Проверено 1 января 2014 .
- Бинь Нгуен (16 августа 2004 г.). "Hacking-Lexicon / Linux Dictionary V 0.16" . Проверено 23 февраля 2008 .
Крюк
- [2012-06-29: ссылка кажется мертвой]Автор: Святой Отец (10.06.2002). «Перехват Windows API - Техника перехвата функций API в Windows 1.1 на английском языке» (PDF) . Архивировано из оригинального (PDF) 29 декабря 2009 года . Проверено 21 февраля 2008 .
Внешние ссылки
Окна
- Информация о перехвате функции Import Address Table.
- Информация от Microsoft о подключении
- Информация и различные методы, касающиеся перехвата x86.
- APISpy32 - это приложение, используемое для подключения Win32 API.
- Detours - это библиотека перехвата функций общего назначения, созданная Microsoft Research и работающая на C / C ++.
- winspy Три способа внедрения кода в другой процесс.
- HookTool SDK (ACF SDK) Предоставляет исчерпывающий обзор подключения API и внедрения кода. Также доступен коммерческий продукт.
- madCodeHook - это коммерческая библиотека перехвата API x86 и x64 и DLL-инъекций для C ++ и Delphi.
- EasyHook - это механизм подключения с открытым исходным кодом, поддерживающий x86 и x64 в Windows как на уровне пользователя, так и на уровне ядра.
- Трассировка приложений SpyStudio SpyStudio - трассировщик приложений, который вызывает перехват, отображая результаты в структурированном виде.
- rohitab.com API Monitor - это бесплатное приложение, которое может подключать и отображать более 10 000 Windows API и COM-интерфейсов в 32-битных и 64-битных приложениях и службах.
- Deviare API Hook Deviare - это бесплатная среда взаимодействия между процессами, которая может использоваться для перехвата вызовов API других процессов и отображения информации с полным параметром или создания мониторов API.
- WinAPIOverride WinAPIOverride - бесплатная программа для некоммерческого использования. Он может подключать Win32 API, COM, OLE, ActiveX, .NET в 32-битных и 64-битных процессах. Он включает инструменты мониторинга и пост-анализа.
- urmem C ++ 11 кроссплатформенная библиотека (x86) для работы с памятью (хуки, патчи, оболочка указателя, сканер сигнатур и т. д.)
Linux
- [2] Студенческий исследовательский проект, в котором используется привязка.
- [3] Функциональность, которая позволяет программе наблюдать и контролировать выполнение другого процесса.
- [4] Использование LD_PRELOAD для перехвата вызовов разделяемой библиотеки.
Emacs
- Emacs Hooks Хуки - важный механизм настройки Emacs. Ловушка - это переменная Лиспа, которая содержит список функций, которые должны быть вызваны в каком-то четко определенном случае. (Это называется запуском ловушки.)
OS X и iOS
- Cydia Substrate - это платформа для взломанных устройств iOS, позволяющая разработчикам подключаться к любой другой платформе или приложению.
- harpoon - это библиотека OS X для перехвата функций времени выполнения.
Углубленный перехват API
- Демистификация перехвата API x86 Статья о различных методах перехвата API для архитектуры x86.