Мегаобучалка Главная | О нас | Обратная связь


Что такое «перехват API-функций»



2019-07-04 237 Обсуждений (0)
Что такое «перехват API-функций» 0.00 из 5.00 0 оценок




Перехват API-функций в Windows NT/2000/XP

Тихомиров В.А.

Системные программисты, работавшие под MS DOS, прекрасно помнят технологию перехвата системных прерываний, позволявшую брать под контроль практически все процессы, проходившие в любимой операционной системе.

С переходом на Windows использование системных ресурсов программистами в большом объеме стало осуществляться через функции API, и у многих «сиспрогов» стал возникать вопрос: «существуют ли в Windows технологии перехватов этих системных функций?» Особый интерес это вызывает применительно к высокозащищенным ОС, выполненным на ядре NT. Данная статья подробнейшим образом, с действующими примерами покажет практическую реализацию такой технологии (предполагается, что читатель знаком с принципами системного программирования в Windows и умеет применять в своей работе Visual C++).

Что такое «перехват API-функций»

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

Перехват функций чужого процесса удобнее всего осуществлять внедрением собственной DLL с функцией-двойником в адресное пространство того процесса, контроль над функциями API которого вы хотите установить. При написании двойников функций следует особое внимание обратить на соглашения о вызовах функций __cdecl и __stdcall. В __cdecl функциях подразумевается, что параметры кладутся в стек справа налево, и вызывающая функция очищает стек от аргументов. В __stdcall функциях подразумевается, что параметры кладутся в стек справа налево, но стек от аргументов очищает вызываемая функция. Кроме того, следует учитывать, что в Windows API многие функции встречается в 2-х экземплярах: ANSI и UNICODE. Первые обозначаются суффиксом A: например MessageBoxA, вторые – суффиксом W – например MessageBoxW.

Рассмотрим два метода перехвата API функций:

Непосредственная запись в код функции.

Подмена адреса функции в таблице импорта.

Метод 1. Перехват API непосредственной записью в код системной функции.

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

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

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

Разберем пример программы (в виде DLL-файла), перехватывающей функцию MessageBoxA методом 1.

Для работы нам потребуются следующие заголовочные файлы:

#include "stdafx.h" #include "intercpt.h"

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

push xxxxxxxx ret

где хххххххх – это адрес функции-двойника. В результате структура, которая будет хранить нужный код перехода, выглядит так:

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; //здесь будет машинный код инструкции перехода

Главная функция DLL будет выглядеть следующим образом:

BOOL APIENTRY DllMain( HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ) { // Если система подключает DLL к какому-либо процессу, // она сначала вызовет главную функцию DLL с параметром // DLL_PROCESS_ATTACH, на что мы сразу вызовем нашу функцию // InterceptFunctions, которая произведет подмену стандартной API функции // MessageBoxA нашей функцией Intercept_MessageBoxA (см. ниже)    if(ul_reason_for_call = = DLL_PROCESS_ATTACH )  {  InterceptFunctions();  }  return TRUE; }

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

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); }

Теперь посмотрим, как выглядит сама функция-двойник. Она должна заменить стандартную MessageBoxA, поэтому её тип и состав параметров должны точно соответствовать оригиналу:

//данное определение аналогично __srtdcall BOOL WINAPI Intercept_MessageBoxA(HWND hwnd, char *text, char *hdr, UINT utype) {  //Сначала восстанавливаем 6 первых байт функции. Это не обязательное  // действие, просто мы решили подшутить над пользователем, и все  // сообщения функции MessageBoxA переделать на свои, поэтому нам придется  // вызвать оригинальную функцию, а для этого следует восстановить ее адрес:  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; }

Если откомпилировать этот код как DLL, то получим файл, который в дальнейшем (см.ниже) следует внедрить в процесс, в котором мы хотим перехватить API MessageBoxA.

Метод 2. Перехват API через таблицу импорта.

Прием заключается в замене адреса функции в таблице импорта на адрес функции-двойника. Для понимания данного метода потребуется знание формата PE исполняемых файлов Windows. Как известно, большинство приложений вызывает функции из dll через таблицу импорта, представляющую собой после загрузки exe файла в память списки адресов функций, импортируемых из различных Dll. Откомпилированный вызов функции через таблицу импорта выглядит следующим образом:

Call dword ptr[address_of_function]

или что-то наподобие. Здесь address_of_function – адрес в таблице импорта, по которому находится адрес вызываемой функции. (Тем, кто не знаком со структурой PE заголовка EXE файла, рекомендуем заглянуть в Интернет за соответствующей информацией.)

При перехвате API через таблицу импорта надо:

найти в таблице импорта элемент IMAGE_IMPORT_DESCRIPTOR, соответствующий той DLL, из которой импортирована функция;

узнать адрес перехватываемой функции при помощи GetProcAddress;

перебирая элементы массива, на который указывает поле FirstThunk, найти адрес перехватываемой функции;

запомнить этот адрес где-нибудь и записать на его место адрес функции-двойника.

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

Достоинство данного метода в том, что он будет корректно работать в многопоточном приложении, когда несколько потоков одновременно вызывают подмененную функцию. Так же данный метод будет работать в ОС WINDOWS 9.x.

Недостаток – не все функции вызываются через таблицу импорта.

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

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)  {  MessageBox(NULL, "Unable to find .idata section", "Error!", 0);  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; }


2019-07-04 237 Обсуждений (0)
Что такое «перехват API-функций» 0.00 из 5.00 0 оценок









Обсуждение в статье: Что такое «перехват API-функций»

Обсуждений еще не было, будьте первым... ↓↓↓

Отправить сообщение

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



©2015-2024 megaobuchalka.ru Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав. (237)

Почему 1285321 студент выбрали МегаОбучалку...

Система поиска информации

Мобильная версия сайта

Удобная навигация

Нет шокирующей рекламы



(0.008 сек.)