Методы организации перехвата АРI функций в ОС Windows Перехват системной функции API заключается в изменении некоторого адреса в памяти процесса или некоторого кода в теле функции таким образом, чтобы при вызове это самой API функции управление передавалось не ей, а вашей функции – подменяющей системную. Эта функция, взамен системной, выполняет какие-то, запланированные вами, действия и затем, в зависимости от вашего желания, либо вызывает оригинальную функцию либо не вызывает ее вообще. Рассмотрим два метода перехвата API функций: 1) Непосредственная запись в код функции. 2) Подмена адреса функции в таблице импорта.
Метод 1. Перехват API непосредственной записью в код системной функции Прием заключается в том, чтобы в начало перехватываемой функции записать команду jmp ваша_функция_двойник или эквивалентную ей. Затираемые байты желательно где-нибудь сохранить. После вызова исправленной функции приложением, управление будет передано вашей функции. Она должна корректно обработать стек, то есть извлечь переданные ей параметры. Сделать необходимые вам действия. Затем, если вы собираетесь вызывать оригинальную функцию необходимо восстановить затертые байты в начале оригинальной функции. Вызвать ее, передав ей все необходимые параметры. После возврата из оригинальной функции, необходимо снова в начало записать команду перехода на вашу функцию. Вернуть управление вызвавшей программе. Достоинство данного метода состоит в том, что он позволяет перехватывать любые функции, а не только те, которые указаны в таблице импорта. Недостаток: в многопоточных приложениях может случиться такая ситуация, когда один поток вызвал перехваченную вами функцию, управление было передано вашей функции-двойнику, она восстановила оригинальное начало функции и в этот момент, параллельно выполняющийся поток произвел вызов той же функции. В результате управление будет передано сразу оригинальной функции, минуя вашу. Пример программы (в виде DLL файла), перехватывающей функцию MessageBoxA методом 1.
Код на C++ #include "stdafx.h" //Структура, содержащая код дальнего перехода на фукцию-двойник struct jmp_far { BYTE instr_push; //здесь будет код инструкции push DWORD arg; //аргумент push BYTE instr_ret; //здесь будет код инструкции ret }; BYTE old[6]; //область для хранения 6-ти затираемых байт начала функции DWORD adr_MessageBoxA //будущий адрес оригинальной функции DWORD written; //вспомогательная переменная jmp_far jump; //здесь будет машинный код инструкции перехода BOOL APIENTRY DllMain( HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ) { if(ul_reason_for_call = = DLL_PROCESS_ATTACH ) { InterceptFunctions(); } return TRUE; } void InterceptFunctions(void) { DWORD op; //сначала получим абсолютный адрес функции для перехвата adr_MessageBoxA = (DWORD)GetProcAddress( GetModuleHandle("user32.dll"), "MessageBoxA"); if(adr_MessageBoxA == 0) { MessageBox(NULL, "Can`t get adr_MessageBoxA", "Error!", 0); return; } //Зададим машинный код инструкции перехода, который мы затем впишем в начало //полученного адреса: jump.instr_push = 0x68; jump.arg = (DWORD)&Intercept_MessageBoxA; jump.instr_ret = 0xC3; //Прочитаем и сохраним первые оригинальные 6 байт стандартной API функции ReadProcessMemory(GetCurrentProcess(),(void*) adr_MessageBoxA, (void*)&old, 6, &written); //Запишем команду перехода на нашу функцию поверх этих 6-ти байт WriteProcessMemory(GetCurrentProcess(), (void*)adr_MessageBoxA, (void*)&jump, sizeof(jmp_far), &written); } //данное определение аналогично __srtdcall BOOL WINAPI Intercept_MessageBoxA(HWND hwnd, char *text, char *hdr, UINT utype) { //Сначала восстанавливаем 6 первых байт функции WriteProcessMemory(GetCurrentProcess(), (void*)adr_MessageBoxA, (void*)&old, 6, &written); //Здесь вы можете выполнить любые действия char *str = "Hi From MessageBOX!!!!"; //Вызываем оригинальную функцию через указатель ((BOOL (__stdcall*)(HWND, char*, char*, UINT))adr_MessageBoxA) (hwnd, str, hdr, utype); //Снова заменяем 6 байт функции на команду перехода на нашу функцию WriteProcessMemory(GetCurrentProcess(), (void*)adr_MessageBoxA, (void*)&jump, 6,&written); return TRUE; }
Метод 2. Перехват API через таблицу импорта. Прием заключается в замене адреса функции в таблице импорта на адрес функции-двойника. Для понимания данного метода потребуется знание формата PE исполняемых файлов Windows. Как известно, большинство приложений вызывает функции из dll через таблицу импорта, представляющую собой после загрузки exe файла в память списки адресов функций, импортируемых из различных Dll. Откомпилированный вызов функции через таблицу импорта выглядит следующим образом: Call dword ptr[address_of_function] Здесь address_of_function – адрес в таблице импорта, по которому находится адрес вызываемой функции. При перехвате API через таблицу импорта надо: - найти в таблице импорта элемент IMAGE_IMPORT_DESCRIPTOR, соответствующий той DLL, из которой импортирована функция; - узнать адрес перехватываемой функции при помощи GetProcAddress; - перебирая элементы массива, на который указывает поле FirstThunk найти адрес перехватываемой функции; - запомнить этот адрес где-нибудь и записать на его место адрес функции-двойника. Теперь, при вызове подмененной функции вначале будет вызываться функция-двойник, а за тем она может вызвать оригинальную функцию, или не вызывать ее вообще. Достоинство данного метода в том, что он корректно будет работать в многопоточном приложении, когда несколько потоков одновременно вызывают подмененную функцию. Так же данный метод будет работать в ОС WINDOWS 9.x. Недостатки – не все функции вызываются через таблицу импорта. Пример программы (в виде DLL файла), перехватывающей функцию MessageBoxA методом 2.
Код на C++ #include "stdafx.h" DWORD adr_MessageBoxA; BOOL APIENTRY DllMain( HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ) { if(ul_reason_for_call == DLL_PROCESS_ATTACH ) { InterceptFunctions(); } return TRUE; } //Эта функция ищет в таблице импорта - //.idata нужный адрес и заменяет на адрес процедуры-двойника void InterceptFunctions(void) { //начало отображения в памяти процесса BYTE *pimage = (BYTE*)GetModuleHandle(NULL); BYTE *pidata; //стандартные структуры описания PE заголовка IMAGE_DOS_HEADER *idh; IMAGE_OPTIONAL_HEADER *ioh; IMAGE_SECTION_HEADER *ish; IMAGE_IMPORT_DESCRIPTOR *iid; DWORD *isd; //image_thunk_data dword //получаем указатели на стандартные структуры данных //PE заголовка idh = (IMAGE_DOS_HEADER*)pimage; ioh = (IMAGE_OPTIONAL_HEADER*)(pimage + idh->e_lfanew + 4 + sizeof(IMAGE_FILE_HEADER)); ish = (IMAGE_SECTION_HEADER*)((BYTE*)ioh + sizeof(IMAGE_OPTIONAL_HEADER)); //если не обнаружен магический код, то у этой программы нет PE //заголовка if (idh->e_magic != 0x5A4D) { MessageBox(NULL, "Not exe hdr", "Error!", 0); return; } //ищем секцию .idata for(int i=0; i<16; i++) if(strcmp((char*)((ish+ i)->Name) , ".idata") == 0) break; if(i==16) { Merror("Unable to find .idata section"); return; } //получаем адрес секции .idata(первого элемента IMAGE_IMPORT_DESCRIPTOR) iid = (IMAGE_IMPORT_DESCRIPTOR*)(pimage + (ish +i)->VirtualAddress ); //получаем абсолютный адрес функции для перехвата adr_MessageBoxA = (DWORD)GetProcAddress(GetModuleHandle( "user32.dll"), "MessageBoxA"); if(adr_MessageBoxA == 0) { MessageBox(NULL, "Can`t get addr_MessageBoxA", "Error!", 0); return; } //в таблице импорта ищем соответствующий элемент для библиотеки user32.dll while(iid->Name) //до тех пор пока поле сруктуры не содержит 0 { if(strcmp((char*)(pimage + iid->Name), "USER32.dll") ==0 ) break; iid++; } //ищем в IMAGE_THUNK_DATA нужный адрес isd = (DWORD*)(pimage + iid->FirstThunk); while(*isd!=adr_MessageBoxA && *isd!=0) isd++; if(*isd == 0) { MessageBox(NULL, "adr_MessageBoxA not found in .idata", "Error!", 0); return; } //заменяем адрес на свою функцию DWORD buf = (DWORD)&Intercept_MessageBoxA; DWORD op; //обычно страницы в этой области недоступны для записи поэтому принудительно разрешаем запись VirtualProtect((void*)(isd),4,PAGE_READWRITE, &op); //пишем новый адрес WriteProcessMemory(GetCurrentProcess(), (void*)(isd), (void*) &buf,4,&written); //восстанавливаем первоначальную защиту области по записи VirtualProtect((void*)(isd),4,op, &op); //если записать не удалось if(written!=4) { MessageBox(NULL, "Unable rewrite address", "Error!", 0); return; } } BOOL WINAPI Intercept_MessageBoxA(HWND hwnd, char *text, char *hdr, UINT utype) { //здесь можно выполнить любые действия char *str = "Hi From MessageBOX!!!!"; // вызываем оригинальную функцию через указатель ((BOOL (__stdcall*)(HWND, char*, char*, UINT))adr_MessageBoxA) (hwnd, str, hdr, utype); return TRUE; }
|