Docsity
Docsity

Подготовься к экзаменам
Подготовься к экзаменам

Учись благодаря многочисленным ресурсам, которые есть на Docsity


Получи баллы для скачивания
Получи баллы для скачивания

Заработай баллы, помогая другим студентам, или приобретай их по тарифом Премиум


Руководства и советы
Руководства и советы

Перехват API-функций в Windows NT/2000/XP реферат по информатике , Сочинения из Информатика

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

Вид: Сочинения

2016/2017

Загружен 11.04.2017

refbank15181
refbank15181 🇷🇺

10 документы

1 / 14

Toggle sidebar

Сопутствующие документы


Частичный предварительный просмотр текста

Скачай Перехват API-функций в Windows NT/2000/XP реферат по информатике и еще Сочинения в формате PDF Информатика только на Docsity! Перехват 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 ) { 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; } Внедрение кода в чужой процесс в Windows NT Теперь осталось показать, как вышеописанные DLL можно внедрить в процесс, избранный в качестве жертвы эксперимента. (Нелишне напомнить, что для нашего примера процесс-жертва должен иметь окна со стандартными сообщениями MessageBox ). Внедрить код – значит, записать некоторую программу в чужой процесс и исполнить ее от имени этого процесса. Таким образом, внедренный код становится частью процесса и получает доступ ко всем ресурсам, которыми обладает процесс. В отличие от DOS, семейство ОС Windows (на ядре NT) – операционные системы с разделяемой памятью, т.е каждое приложение выполняется в своем адресном пространстве, не пересекающемся с другими, и не имеет непосредственного доступа к памяти чужого приложения. Таким образом, внедрение кода является нетривиальной задачей. Существует несколько способов внедрить свой код: 1. «Вручную». 2. При помощи хуков. Внедрение 1 Рассмотрим наиболее эффективный, на наш взгляд, способ внедрения – первый. Он заключается в записи короткого участка машинного кода в память процесса, который должен присоединить DLL к этому процессу, запустить ее код, после чего Dll сможет выполнять любые действия от имени данного процесса. В принципе можно и не присоединять DLL, а реализовать нужные действия во внедряемом машинном коде, но это будет слишком трудоемкой задачей, поскольку все смещения для данных потеряют смысл, и вы не сможете корректно обратиться к ним, не настроив соответствующим образом смещения (морока :( ). При присоединении DLL загрузчик автоматически устанавливает все смещения в соответствии с адресом, по которому загружена DLL. Следует также отметить, что для записи кода в процесс и его исполнения необходимо открыть процесс с доступом как минимум: PROCESS_CREATE_THREAD|PROCESS_VM_WRITE|PROCESS_VM_OPERATION. ПРЕДУПРЕЖДЕНИЕ При реализации данного метода необходимо указать компилятору выравнивать структуры ПОБАЙТОВО. Иначе структура с машинным кодом будет содержать совершенно не тот код, что был запланирован. Общая схема внедрения: Открыть процесс (OpenProcess). Выделить в нем память (VirtualAllocEx – доступно только для WinNT). Записать внедряемый код в эту память (WriteProcessMemory). Исполнить его(CreateRemoteThread). Внедряемый машинный код должен (по нашему сценарию) произвести такие действия: call LoadLibrary – вызвать функцию LoadLibrary из kernel32.dll для загрузки присоединяемой библиотеки (одной из разбиравшихся выше). Call ExitThread – вызвать функцию ExitThread из kernel32.dll для корректного завершения данного потока. Второй способ внедрения исполняемого кода (через хуки) наиболее прост в использовании. Он основан на технологии хуков, а именно: если установить хук на поток чужого процесса, то, как только поток получит сообщение, соответствующее заданному типу хука, система автоматически подключит DLL c хуком к данному процессу. Недостатком данного способа в том, что нельзя внедрить DLL в процесс, не имеющий очереди сообщений. Данная DLL будет присоединена к чужому процессу лишь до тех пор, пока запущена программа, установившая хук. Как только вы завершите эту программу, dll автоматически будет отключена. Первый способ лишен таких недостатков. С другой стороны, первый способ будет работать лишь в WinNT, по причине использования функции VirtualAllocEx, которая резервирует память в заданном (отличном от того, в котором происходит вызов этой функции) процессе. Теоретически, данную проблему можно обойти, если писать код в некоторую часть отображения exe-файла чужого процесса, например в заголовок DOS, который после загрузки не используется. Но ОС не всегда позволяет писать в эту область памяти, даже если попытаться изменить разрешения при помощи VirtualProtextEx. Есть еще и третий способ внедрения, но он наиболее опасен, так как может привести к краху системы. При помощи данного метода ОС сама внедряет указанную dll во все без исключения процессы операционной системы, даже защищенные. Для реализации необходимо прописать в реестре по пути Hkey_local_machine\software\microsoft\windowsnt \currentversion\windows в ключе AppInit_DLLs полный путь к своей dll. Как отлаживать такие выкрутасы Большинство программистов для отладки своих программ используют встроенные отладчики компиляторов. Они просты в использовании и удовлетворяют большинству требований, предъявляемых при отладке. Но если некоторый программный код будет внедрен и исполнен в рамках другого, постороннего процесса встроенный отладчик использовать очень тяжело. Для этих целей удобно применить системный отладчик SoftIce, который грузится раньше операционной системы, работает в нулевом кольце и поэтому имеет доступ к любым объектам ОС. Обсудим, как отлаживать внедренный код, выполняющий перехват API функций внутри постороннего процесса. Все виды отладки условно можно разделить на 3 группы: отладка кода загрузчика (на ассемблере), который, будучи внедренным в чужой процесс, исполняется как отдельный поток и присоединяет Dll от имени процесса. отладка функций, выполняющихся при старте данной Dll. Обычно это функции, которые выполняют подмену кода внутри тела API-функции для передачи управления функции-двойнику. отладка функций–двойников, которые получают управление при вызове перехваченной API-функции. Отладка кода загрузчика Итак, есть 2 процесса: Процесс, который внедряет код. Обозначим его П1. Процесс, в который внедряют код. Обозначим его П2. Задача заключается в том, чтобы поставить точку останова в П2 перед выполнением внедренного кода. Изначально неизвестно, по какому адресу будет внедрен код в П2. При этом предполагается, что П2 уже загружен и висит где-то в памяти. Для простоты запускаем П1 в каком-либо встроенном отладчике и трассируем, для того, чтобы узнать по какому адресу в П2 будет выделена память. Узнав этот адрес, включаем SoftIce (ctrl+d). Подключаемся к П2 (addr П2-name), при этом SoftIce установит контекст адресов, соответствующий процессу П2. Устанавливаем точку останова по узнанному адресу (bpx address). Закрываем SoftIce(ctrl+d). Выполняем П1. При этом он создает поток в П2. Когда этот поток начинает исполняться, на первой инструкции внедренного кода выскакивает окно SoftIce. Отладка функций, выполняющихся при старте DLL В примере это функция InterceptFunctions библиотеки intercpt.dll, которая вызывается из DllMain при присоединении библиотеки к процессу и выполняет перехват функций. Для начала необходимо откомпилировать эту библиотеку с отладочной информацией, которую в дальнейшем SoftIce будет использовать для вывода инструкций на языке С. В MS Visual C это делается так: Project->Settings->C/C++ список Debug Info – там необходимо выбрать тип символьной информации – Program database for Edit and Continue, а так же Project->Settings->Link список Category -> debug, установить галочку в поле Debug info и выбрать формат отладочной информации, например Microsoft Format. Альтернатива – можно просто установить тип конфигурации проекта, при этом все параметры для получения отладочной информации будут установлены автоматически. Это делается так : Build->Set Active Configuration -> Win32 Debug. Теперь можно приступать к использованию SoftIce. Для начала нужно загрузить в отладчик символьную информацию из данной Dll, при этом сама dll еще загружена не будет. Символьная информация понадобится впоследствии, для установки точек останова и представления кодов на языке высокого уровня. Это делается при помощи утилиты Symbol Loader из комплекта SoftIce. Вначале необходимо открыть модуль dll (пункт File->Open Module). Затем необходимо загрузить его в отладчик (Module -> Load). При успешном выполнении всех этих операций на экране Symbol Loader должно быть что-то вроде этого: Рисунок 1 Теперь приступим к главному. Необходимо поставить точку останова на функцию InterceptFunctions из dll, при этом сама Dll пока еще не присоединена к процессу! Запускаем SoftIce. Создаем точку останова по символьному имени: bpx InterceptFunctions, (InterceptFunctions – символьное имя функции из таблицы символов. Чтобы просмотреть всю таблицу, можно воспользоваться командой sym). Теперь при помощи написанной ранее программы внедряем эту dll в указанный процесс. Должно произойти следующее: Dll присоединяется к процессу, выполняется DllMain, которая вызывает IntercptFunctions и в этот момент должен произойти останов и вылезти окно отладчика. При этом весь код из dll будет представлен на языке высокого уровня. Отладка функций – двойников, получающих управление при вызове перехваченных API функций Для начала необходимо загрузить символьную информацию о Dll перехвата. В данном примере это intercpt.dll. В утилите Symbol Loader выбираем File->Open Module, затем, в меню Module->Load, загружаем символьную информацию в отладчик. Dll пока еще не присоединена ни к какому процессу. Далее, зная имена функций-двойников ставим точку останова на имя функции. Например, функция-двойник Intercept_MessageBoxA, которая будет вызываться всякий раз, когда произойдет вызов функции MessageBoxA из программы. Поставим точку останова на нее – в окне отладчика набираем: bpx Intercept_MessageBoxA. Теперь можно присоединить intercpt.dll к какому-либо процессу. Когда этот процесс вызовет перехваченную функцию MessageboxA, управление будет передано на функцию Intercpt_MessageBoxA и сработает точка останова. Тестирование Чтобы опробовать все вышесказанное в деле, сначала подыщите на своем компьютере какое-либо приложение, имеющее в своем составе окна сообщений типа MessageBox. Подопытное приложение написано нами самими, оно называется MESS.EXE и выводит друг за другом три окна сообщений, коллаж из которых показан на рисунке: Рисунок 2 Затем, откомпилируйте примеры внедряемых DLL, описанных выше. Результат компиляции мы назвали у себя METOD1.DLL и METOD2.DLL. Откомпилируйте пример процедуры внедрения этих DLL в код внешнего процесса. Для работоспособности этой процедуры к ней нужно добавить код главного модуля программы, нечто вроде:
Docsity logo