Зацепка
В компьютерном программировании термин «перехват» охватывает ряд методов, используемых для изменения или улучшения поведения операционной системы , приложений или других компонентов программного обеспечения путем перехвата вызовов функций , сообщений или событий, передаваемых между компонентами программного обеспечения . Код, который обрабатывает такие перехваченные вызовы функций, события или сообщения, называется ловушкой .
Методы-перехватчики имеют особое значение в шаблоне шаблонного метода , где общий код в абстрактном классе может быть дополнен пользовательским кодом в подклассе. В этом случае каждый метод-перехватчик определяется в абстрактном классе с пустой реализацией, что затем позволяет предоставлять другую реализацию в каждом конкретном подклассе.
Перехват используется для многих целей, включая отладку и расширение функциональности. Примеры могут включать перехват сообщений о событиях клавиатуры или мыши до того, как они достигнут приложения, или перехват вызовов операционной системы для отслеживания поведения или изменения функции приложения или другого компонента. Он также широко используется в программах сравнительного анализа, например, для измерения частоты кадров в 3D-играх, где вывод и ввод выполняются посредством перехвата.
Перехват также может быть использован вредоносным кодом. Например, руткиты , программы, которые пытаются стать невидимыми, имитируя выходные данные вызовов API , которые в противном случае раскрыли бы их существование, часто используют методы перехвата.
Методы
[ редактировать ]Обычно перехваты вставляются, когда программное обеспечение уже запущено, но перехват — это тактика, которую также можно использовать до запуска приложения. Оба эти метода описаны более подробно ниже.
Модификация исходного кода
[ редактировать ]Перехват может быть достигнут путем изменения исходного кода исполняемого файла или библиотеки перед запуском приложения с помощью методов обратного проектирования . Обычно это используется для перехвата вызовов функций для их мониторинга или полной замены.
Например, с помощью можно точку входа функции модуля внутри дизассемблера найти . Затем его можно изменить, чтобы вместо этого динамически загружать какой-либо другой библиотечный модуль, а затем выполнять нужные методы в этой загруженной библиотеке. Если применимо, другой похожий подход, с помощью которого можно добиться перехвата, — это изменение таблицы импорта исполняемого файла. Эту таблицу можно изменить для загрузки любых дополнительных библиотечных модулей, а также для изменения того, какой внешний код вызывается при вызове функции приложением.
Альтернативный метод перехвата функций — перехват вызовов функций через библиотеку-оболочку . Оболочка — это версия библиотеки, загружаемая приложением, со всеми теми же функциями исходной библиотеки, которую она заменит. То есть все доступные функции у оригинала и замены по сути одинаковы. Эта библиотека-оболочка может быть спроектирована так, чтобы вызывать любую функциональность исходной библиотеки или заменять ее совершенно новым набором логики.
Модификация времени выполнения
[ редактировать ]Операционные системы и программное обеспечение могут предоставлять средства для простой вставки перехватчиков событий во время выполнения . Он доступен при условии, что процессу, вставляющему перехватчик, предоставлено достаточно разрешений для этого. Например, Microsoft Windows позволяет пользователям вставлять перехватчики, которые можно использовать для обработки или изменения системных событий и событий приложений для диалоговых окон , полос прокрутки и меню , а также других элементов. Он также позволяет перехватчику вставлять, удалять, обрабатывать или изменять клавиатуры и мыши события . Linux предоставляет еще один пример, где перехватчики могут использоваться аналогичным образом для обработки сетевых событий внутри ядра через NetFilter .
Когда такая функциональность не предусмотрена, специальная форма перехвата использует перехват вызовов библиотечных функций, выполняемых процессом. Перехват функции реализуется путем изменения первых нескольких инструкций кода целевой функции для перехода к внедренному коду. В качестве альтернативы в системах, использующих концепцию общей библиотеки , таблица векторов прерываний или таблица дескрипторов импорта могут быть изменены в памяти. По сути, эта тактика использует те же идеи, что и модификация исходного кода, но вместо этого изменяет инструкции и структуры, расположенные в памяти процесса, когда он уже запущен.
Пример кода
[ редактировать ]Перехват таблицы виртуальных методов
[ редактировать ]Всякий раз, когда класс определяет/наследует виртуальную функцию (или метод), компиляторы добавляют в класс скрытую переменную-член, которая указывает на таблицу виртуальных методов (VMT или Vtable). Большинство компиляторов помещают скрытый указатель VMT в первые 4 байта каждого экземпляра класса. VMT — это, по сути, массив указателей на все виртуальные функции, которые могут вызывать экземпляры класса. Во время выполнения эти указатели настроены так, чтобы указывать на правильные функции, поскольку во время компиляции еще не известно, следует ли вызывать базовую функцию или следует вызывать переопределенную версию функции из производного класса (тем самым позволяя для полиморфизма ). Таким образом, виртуальные функции можно подключить, заменив указатели на них в любом VMT, где они появляются. В приведенном ниже коде показан пример типичного перехватчика VMT в Microsoft Windows, написанного на C++. [ 1 ]
#include <iostream>
#include "windows.h"
using namespace std;
class VirtualClass
{
public:
int number;
virtual void VirtualFn1() //This is the virtual function that will be hooked.
{
cout << "VirtualFn1 called " << number++ << "\n\n";
}
};
using VirtualFn1_t = void(__thiscall*)(void* thisptr);
VirtualFn1_t orig_VirtualFn1;
void __fastcall hkVirtualFn1(void* thisptr, int edx) //This is our hook function which we will cause the program to call instead of the original VirtualFn1 function after hooking is done.
{
cout << "Hook function called" << "\n";
orig_VirtualFn1(thisptr); //Call the original function.
}
int main()
{
VirtualClass* myClass = new VirtualClass(); //Create a pointer to a dynamically allocated instance of VirtualClass.
void** vTablePtr = *reinterpret_cast<void***>(myClass); //Find the address that points to the base of VirtualClass' VMT (which then points to VirtualFn1) and store it in vTablePtr.
DWORD oldProtection;
VirtualProtect(vTablePtr, 4, PAGE_EXECUTE_READWRITE, &oldProtection); //Removes page protection at the start of the VMT so we can overwrite its first pointer.
orig_VirtualFn1 = reinterpret_cast<VirtualFn1_t>(*vTablePtr); //Stores the pointer to VirtualFn1 from the VMT in a global variable so that it can be accessed again later after its entry in the VMT has been
//overwritten with our hook function.
*vTablePtr = &hkVirtualFn1; //Overwrite the pointer to VirtualFn1 within the virtual table to a pointer to our hook function (hkVirtualFn1).
VirtualProtect(vTablePtr, 4, oldProtection, 0); //Restore old page protection.
myClass->VirtualFn1(); //Call the virtual function from our class instance. Because it is now hooked, this will actually call our hook function (hkVirtualFn1).
myClass->VirtualFn1();
myClass->VirtualFn1();
delete myClass;
return 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;
namespace Hooks;
public class KeyHook
{
/* Member variables */
protected static int Hook;
protected static LowLevelKeyboardDelegate Delegate;
protected static readonly object Lock = new object();
protected static bool IsRegistered = false;
/* DLL imports */
[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);
/* Types & constants */
protected delegate int LowLevelKeyboardDelegate(int nCode, int wParam, ref KBDLLHOOKSTRUCT lParam);
private const int HC_ACTION = 0;
private const int WM_KEYDOWN = 0x0100;
private const int WM_KEYUP = 0x0101;
private const int WH_KEYBOARD_LL = 13;
[StructLayout(LayoutKind.Sequential)]
public struct KBDLLHOOKSTRUCT
{
public int vkCode;
public int scanCode;
public int flags;
public int time;
public int dwExtraInfo;
}
/* Methods */
static private int LowLevelKeyboardHandler(int nCode, int wParam, ref KBDLLHOOKSTRUCT lParam)
{
if (nCode == HC_ACTION)
{
if (wParam == WM_KEYDOWN)
System.Console.Out.WriteLine("Key Down: " + lParam.vkCode);
else if (wParam == WM_KEYUP)
System.Console.Out.WriteLine("Key Up: " + lParam.vkCode);
}
return CallNextHookEx(Hook, nCode, wParam, lParam);
}
public static bool RegisterHook()
{
lock (Lock)
{
if (IsRegistered)
return true;
Delegate = LowLevelKeyboardHandler;
Hook = SetWindowsHookEx(
WH_KEYBOARD_LL, Delegate,
Marshal.GetHINSTANCE(
System.Reflection.Assembly.GetExecutingAssembly().GetModules()[0]
).ToInt32(), 0
);
if (Hook != 0)
return IsRegistered = true;
Delegate = null;
return false;
}
}
public static bool UnregisterHook()
{
lock (Lock)
{
return IsRegistered = (UnhookWindowsHookEx(Hook) != 0);
}
}
}
Перехват/перехват API/функций с использованием инструкции JMP, также известной как сращивание
[ редактировать ]Следующий исходный код является примером метода перехвата API/функции, который перезаписывает первые шесть байтов целевой функции с помощью инструкции JMP в новую функцию. Код компилируется в файл DLL , а затем загружается в целевой процесс с помощью любого метода внедрения DLL . Используя резервную копию исходной функции, можно затем снова восстановить первые шесть байтов, чтобы вызов не прерывался. В этом примере Win32 API MessageBoxW. подключена функция [ 2 ]
/*
This idea is based on chrom-lib approach, Distributed under GNU LGPL License.
Source 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); // Messagebox prototype
int WINAPI MyMessageBoxW(HWND, LPCWSTR, LPCWSTR, UINT); // Our detour
void BeginRedirect(LPVOID);
pMessageBoxW pOrigMBAddress = NULL; // address of original
BYTE oldBytes[SIZE] = {0}; // backup
BYTE JMP[SIZE] = {0}; // 6 byte JMP instruction
DWORD oldProtect, myProtect = PAGE_EXECUTE_READWRITE;
INT APIENTRY DllMain(HMODULE hDLL, DWORD Reason, LPVOID Reserved)
{
switch (Reason)
{
case DLL_PROCESS_ATTACH: // if attached
pOrigMBAddress = (pMessageBoxW)
GetProcAddress(GetModuleHandleA("user32.dll"), // get address of original
"MessageBoxW");
if (pOrigMBAddress != NULL)
BeginRedirect(MyMessageBoxW); // start detouring
break;
case DLL_PROCESS_DETACH:
VirtualProtect((LPVOID)pOrigMBAddress, SIZE, myProtect, &oldProtect); // assign read write protection
memcpy(pOrigMBAddress, oldBytes, SIZE); // restore backup
VirtualProtect((LPVOID)pOrigMBAddress, SIZE, oldProtect, &myProtect); // reset protection
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); // store jmp instruction to JMP
DWORD JMPSize = ((DWORD)newFunction - (DWORD)pOrigMBAddress - 5); // calculate jump distance
VirtualProtect((LPVOID)pOrigMBAddress, SIZE, // assign read write protection
PAGE_EXECUTE_READWRITE, &oldProtect);
memcpy(oldBytes, pOrigMBAddress, SIZE); // make backup
memcpy(&JMP[1], &JMPSize, 4); // fill the nop's with the jump distance (JMP,distance(4bytes),RET)
memcpy(pOrigMBAddress, JMP, SIZE); // set jump instruction at the beginning of the original function
VirtualProtect((LPVOID)pOrigMBAddress, SIZE, oldProtect, &myProtect); // reset protection
}
int WINAPI MyMessageBoxW(HWND hWnd, LPCWSTR lpText, LPCWSTR lpCaption, UINT uiType)
{
VirtualProtect((LPVOID)pOrigMBAddress, SIZE, myProtect, &oldProtect); // assign read write protection
memcpy(pOrigMBAddress, oldBytes, SIZE); // restore backup
int retValue = MessageBoxW(hWnd, lpText, lpCaption, uiType); // get return value of original function
memcpy(pOrigMBAddress, JMP, SIZE); // set the jump instruction again
VirtualProtect((LPVOID)pOrigMBAddress, SIZE, oldProtect, &myProtect); // reset protection
return retValue; // return original return value
}
Крючок сетевого фильтра
[ редактировать ]В этом примере показано, как использовать перехват для изменения сетевого трафика в ядре 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>
/* Port we want to drop packets on */
static const uint16_t port = 25;
/* This is the hook function itself */
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;
if (iph->protocol != IPPROTO_TCP)
return NF_ACCEPT;
tcph = skb_header_pointer(*pskb, ip_hdrlen(*pskb), sizeof(*tcph), &tcpbuf);
if (tcph == NULL)
return NF_ACCEPT;
return (tcph->dest == port) ? NF_DROP : NF_ACCEPT;
}
/* Used to register our hook function */
static struct nf_hook_ops nfho = {
.hook = hook_func,
.hooknum = NF_IP_PRE_ROUTING,
.pf = NFPROTO_IPV4,
.priority = NF_IP_PRI_FIRST,
};
static __init int my_init(void)
{
return nf_register_hook(&nfho);
}
static __exit void my_exit(void)
{
nf_unregister_hook(&nfho);
}
module_init(my_init);
module_exit(my_exit);
Внутреннее подключение IAT
[ редактировать ]Следующий код демонстрирует, как перехватить функции, импортированные из другого модуля. Это можно использовать для перехвата функций в процессе, отличном от вызывающего процесса. Для этого код необходимо скомпилировать в файл DLL , а затем загрузить в целевой процесс, используя любой метод внедрения DLL . Преимущество этого метода в том, что его меньше обнаруживает антивирусное и/или античит-программное обеспечение . Его можно превратить во внешний перехват, не использующий никаких вредоносных вызовов. Заголовок Portable Executable содержит таблицу адресов импорта (IAT), которой можно манипулировать, как показано в исходном коде ниже. Источник ниже работает под управлением Microsoft Windows.
#include <windows.h>
typedef int(__stdcall *pMessageBoxA) (HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType); //This is the 'type' of the MessageBoxA call.
pMessageBoxA RealMessageBoxA; //This will store a pointer to the original function.
void DetourIATptr(const char* function, void* newfunction, HMODULE module);
int __stdcall NewMessageBoxA(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType) { //Our fake function
printf("The String Sent to MessageBoxA Was : %s\n", lpText);
return RealMessageBoxA(hWnd, lpText, lpCaption, uType); //Call the real function
}
int main(int argc, CHAR *argv[]) {
DetourIATptr("MessageBoxA",(void*)NewMessageBoxA,0); //Hook the function
MessageBoxA(NULL, "Just A MessageBox", "Just A MessageBox", 0); //Call the function -- this will invoke our fake hook.
return 0;
}
void **IATfind(const char *function, HMODULE module) { //Find the IAT (Import Address Table) entry specific to the given function.
int ip = 0;
if (module == 0)
module = GetModuleHandle(0);
PIMAGE_DOS_HEADER pImgDosHeaders = (PIMAGE_DOS_HEADER)module;
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 Error : e_magic is no valid DOS signature\n");
for (IMAGE_IMPORT_DESCRIPTOR *iid = pImgImportDesc; iid->Name != NULL; iid++) {
for (int funcIdx = 0; *(funcIdx + (LPVOID*)(iid->FirstThunk + (SIZE_T)module)) != 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);
if (*funcptr == newfunction)
return;
DWORD oldrights, newrights = PAGE_READWRITE;
//Update the protection to READWRITE
VirtualProtect(funcptr, sizeof(LPVOID), newrights, &oldrights);
RealMessageBoxA = (pMessageBoxA)*funcptr; //Some compilers require the cast (like "MinGW"), not sure about MSVC though
*funcptr = newfunction;
//Restore the old memory protection flags.
VirtualProtect(funcptr, sizeof(LPVOID), oldrights, &newrights);
}
См. также
[ редактировать ]- Обратный звонок (информатика)
- Делегирование (программирование)
- Программа прекращения пребывания и проживания
- Пользовательский выход
- WinAPIOverride32
Ссылки
[ редактировать ]- ^ псих
- ^ Для получения дополнительной информации см. 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 г.
{{cite web}}
:|author=
имеет общее имя ( справка )
Внешние ссылки
[ редактировать ]Окна
[ редактировать ]- Информация о перехвате функции импорта таблицы адресов.
- Информация от 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 Application Trace SpyStudio — это трассировщик приложений, который перехватывает вызовы и отображает результаты в структурированном виде.
- rohitab.com API Monitor — это бесплатное приложение, которое может подключать и отображать более 10 000 API-интерфейсов Windows и COM-интерфейсов в 32-битных и 64-битных приложениях и службах.
- Deviare API Hook Deviare — это бесплатная платформа межпроцессных перехватчиков, которую можно использовать для перехвата вызовов API других процессов и отображения полной информации о параметрах или создания мониторов API.
- WinAPIOverride WinAPIOverride — бесплатная программа для некоммерческого использования. Он может подключать Win32 API, COM, OLE, ActiveX, .NET в 32-битных и 64-битных процессах. Он включает в себя инструменты пост-анализа мониторинга.
- urmem C++11 (x86) для работы с памятью (хуки, патчи, обертка указателя, сканер подписей и т.д.) Кроссплатформенная библиотека
Линукс
[ редактировать ]- [1] Студенческий исследовательский проект, в котором используется зацепка.
- [2] Функциональность, позволяющая программному обеспечению наблюдать и контролировать выполнение другого процесса.
- [3] Использование LD_PRELOAD для перехвата вызовов общей библиотеки.
Эмакс
[ редактировать ]- Хуки Emacs Хуки — это важный механизм настройки Emacs. Хук — это переменная Лиспа, содержащая список функций, которые должны быть вызваны в каком-то четко определенном случае. (Это называется запуском крючка.)
ОС Х и iOS
[ редактировать ]- Cydia Substrate — это платформа для взломанных iOS-устройств, позволяющая разработчикам подключаться к любой другой платформе или приложению.
- harpoon — это библиотека OS X для перехвата функций во время выполнения.
Подробно о перехвате API
[ редактировать ]- Раскрытие секретов перехвата API x86. Статья о различных методах перехвата API для архитектуры x86.