stringtranslate.com

Зацепка

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

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

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

Перехват также может быть использован вредоносным кодом. Например, руткиты , программы, которые пытаются стать невидимыми, имитируя выходные данные вызовов API , которые в противном случае раскрыли бы их существование, часто используют методы перехвата.

Методы

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

Модификация исходного кода

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

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

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

Модификация времени выполнения

Операционные системы и программное обеспечение могут предоставлять средства для простой вставки перехватчиков событий во время выполнения . Он доступен при условии, что процессу , вставляющему перехватчик, предоставлено достаточно разрешений для этого. Например, Microsoft Windows позволяет пользователям вставлять перехватчики, которые можно использовать для обработки или изменения системных событий и событий приложений для диалоговых окон , полос прокрутки и меню , а также других элементов. Он также позволяет перехватчику вставлять, удалять, обрабатывать или изменять события клавиатуры и мыши . Linux предоставляет еще один пример, где перехватчики могут использоваться аналогичным образом для обработки сетевых событий внутри ядра через NetFilter .

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

Образец кода

Перехват таблицы виртуальных методов

Всякий раз, когда класс определяет/наследует виртуальную функцию (или метод), компиляторы добавляют в класс скрытую переменную-член, которая указывает на таблицу виртуальных методов (VMT или Vtable). Большинство компиляторов помещают скрытый указатель VMT в первые 4 байта каждого экземпляра класса. VMT — это, по сути, массив указателей на все виртуальные функции, которые могут вызывать экземпляры класса. Во время выполнения эти указатели настроены так, чтобы указывать на правильные функции, поскольку во время компиляции еще не известно, следует ли вызывать базовую функцию или следует вызывать переопределенную версию функции из производного класса (тем самым позволяя для полиморфизма ). Таким образом, виртуальные функции можно подключить, заменив указатели на них в любом VMT, где они появляются. В приведенном ниже коде показан пример типичного перехватчика VMT в Microsoft Windows, написанного на C++. [1]

#include <iostream> #include "windows.h" с использованием пространства имен std ; класс VirtualClass { общественный : число int ; virtual void VirtualFn1 () //Это виртуальная функция, которая будет подключена. { cout << "Вызов VirtualFn1 " << число ++ << " \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 старая защита ; VirtualProtect ( vTablePtr , 4 , PAGE_EXECUTE_READWRITE и oldProtection ) ; //Удаляет защиту страницы в начале VMT, чтобы мы могли перезаписать ее первый указатель. orig_VirtualFn1 = reinterpret_cast <VirtualFn1_t> ( * vTablePtr ) ; _ //Сохраняет указатель на VirtualFn1 из VMT в глобальной переменной, чтобы к нему можно было снова получить доступ позже после того, как его запись в VMT будет //перезаписана нашей функцией-перехватчиком. * vTablePtr = & hkVirtualFn1 ; //Замените указатель на VirtualFn1 в виртуальной таблице на указатель на нашу функцию-перехватчик (hkVirtualFn1). VirtualProtect ( vTablePtr , 4 , oldProtection , 0 ); //Восстанавливаем старую защиту страницы. мойКласс -> VirtualFn1 (); //Вызов виртуальной функции из экземпляра нашего класса. Поскольку теперь он подключен, это фактически вызовет нашу функцию перехвата (hkVirtualFn1). мойКласс -> VirtualFn1 (); мойКласс -> VirtualFn1 (); удалить мойКласс ; вернуть 0 ; }                                                                   

Важно отметить, что все виртуальные функции должны быть функциями-членами класса, а все (нестатические) функции-члены класса вызываются с соглашением о вызовах __thiscall (если только функция-член не принимает переменное количество аргументов, в этом случае она вызывается с __cdecl). Соглашение о вызовах __thiscall передает указатель на экземпляр вызывающего класса (обычно называемый указателем «this») через регистр ECX (в архитектуре x86). Следовательно, чтобы функция-перехватчик правильно перехватила переданный указатель «this» и приняла его в качестве аргумента, она должна просмотреть регистр ECX. В приведенном выше примере это делается путем установки функции-перехватчика (hkVirtualFn1) на использование соглашения о вызовах __fastcall, что заставляет функцию-перехватчик искать один из своих аргументов в регистре ECX.

Также обратите внимание, что в приведенном выше примере функция-перехватчик (hkVirtualFn1) сама по себе не является функцией-членом, поэтому она не может использовать соглашение о вызовах __thiscall. Вместо этого следует использовать __fastcall, поскольку это единственное соглашение о вызовах, которое ищет аргумент в регистре ECX.

Перехватчик событий клавиатуры C#

В следующем примере будут подключены события клавиатуры в Microsoft Windows с использованием Microsoft .NET Framework .

используя System.Runtime.InteropServices ; Пространство имен Хуки ; public class KeyHook { /* Переменные-члены */ protected static int Hook ; защищенный статический LowLevelKeyboardDelegate Delegate ; защищенный статический объект только для чтения Lock = новый объект (); protected static bool IsRegistered = false ;                          /* импорт DLL */ [DllImport("user32")] Private static extern int SetWindowsHookEx ( int idHook , LowLevelKeyboardDelegate lpfn , int hmod , int dwThreadId );              [DllImport("user32")] Private static extern int CallNextHookEx ( int hHook , int nCode , int wParam , KBDLLHOOKSTRUCT lParam );             [DllImport("user32")] Private static extern int UnhookWindowsHookEx ( int hHook );       /* Типы и константы */ защищенный делегат int LowLevelKeyboardDelegate ( int nCode , int wParam , ref KBDLLHOOKSTRUCT lParam ); частная константа int HC_ACTION = 0 ; частная константа int WM_KEYDOWN = 0 x0100 ; частная константа int WM_KEYUP = 0 x0101 ; частная константа int WH_KEYBOARD_LL = 13 ;                                   [StructLayout (LayoutKind.Sequential)] общественная структура KBDLLHOOKSTRUCT { public int vkCode ; public 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 . Консоль . Вне . WriteLine ( "Клавиша вниз: " + lParam.vkCode ) ; иначе , если ( wParam == WM_KEYUP ) System . Консоль . Вне . WriteLine ( "Key Up: " + lParam.vkCode ) ; } Возвращаем CallNextHookEx ( Hook , nCode , wParam , lParam ); }                                       public static bool RegisterHook () { lock ( Lock ) { if ( IsRegistered ) return true ; Делегат = LowLevelKeyboardHandler ; Hook = SetWindowsHookEx ( WH_KEYBOARD_LL , Delegate , Marshal . GetHINSTANCE ( System . Reflection . Assembly . 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 <windows.h> #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 [ РАЗМЕР ] = { 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" ), // получаем адрес исходного "MessageBoxW" " ); если ( pOrigMBAddress != NULL ) BeginRedirect ( MyMessageBoxW ); // начинаем объезд Break ;                                   случай DLL_PROCESS_DETACH : VirtualProtect (( LPVOID ) pOrigMBAddress , SIZE , myProtect и oldProtect ) ; // назначаем защиту от чтения и записи memcpy ( pOrigMBAddress , oldBytes , SIZE ); // восстанавливаем резервную копию VirtualProtect (( LPVOID ) pOrigMBAddress , SIZE , oldProtect и myProtect ) ; // сброс защиты                 случай DLL_THREAD_ATTACH : случай DLL_THREAD_DETACH : перерыв ; } Вернуть ИСТИНА ; }              void BeginRedirect ( LPVOID newFunction ) { БАЙТ tempJMP [ РАЗМЕР ] = { 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(4bytes),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 , SIZE , 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 ; // возвращаем исходное возвращаемое значение }                                          

Крючок сетевого фильтра

В этом примере показано, как использовать перехват для изменения сетевого трафика в ядре Linux с помощью Netfilter .

#include <linux/module.h> #include <linux /kernel.h> #include <linux/skbuff.h>   #include <linux/ip.h> #include <linux/tcp.h> #include <linux/in.h> #include <linux/netfilter.h> #include <linux/netfilter_ipv4.h>     /* Порт, на который мы хотим отбрасывать пакеты */ static const uint16_t port = 25 ;     /* Это сама функция-перехватчик */ static unsigned intook_func ( unsigned intooknum , 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 ); структура tcphdr * tcph , tcpbuf ;                              if ( iph -> протокол != IPPROTO_TCP ) вернуть NF_ACCEPT ;      tcph = skb_header_pointer ( * pskb , ip_hdrlen ( * pskb ), sizeof ( * tcph ) и tcpbuf ); если ( tcph == NULL ) вернуть NF_ACCEPT ;            return ( tcph -> dest == порт ) ? NF_DROP : NF_ACCEPT ; }       /* Используется для регистрации нашей функции-перехватчика */ static struct nf_hook_ops nfho = { . крючок = функция_крючка , . номер подключения = NF_IP_PRE_ROUTING , . пф = NFPROTO_IPV4 , . приоритет = NF_IP_PRI_FIRST , };                 static __init int my_init ( void ) { return nf_register_hook ( & nfho ); }     статический __exit void my_exit ( void ) { nf_unregister_hook ( & nfho ); }    модуль_инит ( my_init ); модуль_выход ( мой_выход );

Внутреннее подключение IAT

Следующий код демонстрирует, как перехватить функции, импортированные из другого модуля. Это можно использовать для перехвата функций в процессе, отличном от вызывающего процесса. Для этого код необходимо скомпилировать в файл DLL , а затем загрузить в целевой процесс, используя любой метод внедрения DLL . Преимущество этого метода заключается в том, что его меньше обнаруживает антивирусное и/или античит-программное обеспечение . Его можно превратить во внешний перехват, который не использует никаких вредоносных вызовов. Заголовок Portable Executable содержит таблицу адресов импорта (IAT), которой можно манипулировать, как показано в исходном коде ниже. Источник ниже работает под управлением Microsoft Windows.


#include <windows.h> typedef int ( __stdcall * pMessageBoxA ) ( HWND hWnd , LPCSTR lpText , LPCSTR lpCaption , UINT uType ); //Это «тип» вызова MessageBoxA. pMessageBoxA RealMessageBoxA ; //Это сохранит указатель на исходную функцию.             void DetourIATptr ( const char * функция , void * новая функция , модуль HMODULE );       int __stdcall NewMessageBoxA ( HWND hWnd , LPCSTR lpText , LPCSTR lpCaption , UINT uType ) { //Наша поддельная функция printf ( "Строка, отправленная в MessageBoxA, была: %s \n " , lpText ); вернуть 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 mod ) { //Находим запись IAT (таблица адресов импорта), специфичную для данной функции. интервал 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 » );   for ( IMAGE_IMPORT_DESCRIPTOR * iid = pImgImportDesc ; iid -> Name != NULL ; iid ++ ) { for ( int funcIdx = 0 ; * ( funcIdx + ( LPVOID * )( iid -> FirstThunk + ( SIZE_T ) модуль )) != NULL ; funcIdx ++ ) { char * modFuncName = ( char * )( * ( funcIdx + ( SIZE_T * )( iid -> OriginalFirstThunk + ( SIZE_T ) модуль )) + ( SIZE_T ) модуль + 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 ) модуль ); } } } вернуть 0 ; }                                                          void DetourIATptr ( const char * функция , void * newfunction , модуль HMODULE ) { void ** funcptr = IATfind ( функция , модуль ); if ( * funcptr == новая функция ) return ;                DWORD старые права , новые права = PAGE_READWRITE ; //Обновляем защиту до READWRITE VirtualProtect ( funcptr , sizeof ( LPVOID ), newrights и oldrights ) ;       RealMessageBoxA = ( pMessageBoxA ) * funcptr ; //Некоторые компиляторы требуют приведения типов (например, "MinGW"), хотя насчет MSVC не уверен * funcptr = newfunction ;     //Восстанавливаем старые флаги защиты памяти. VirtualProtect ( funcptr , sizeof ( LPVOID ), oldrights и newrights ) ; }   

Смотрите также

Рекомендации

  1. ^ психфл, [1]
  2. ^ Для получения дополнительной информации см. http://ntvalk.blogspot.nl/2013/11/hooking-explained-detouring-library.html.

Внешние ссылки

Окна

Линукс

Эмакс

ОС Х и iOS

Подробно о перехвате API