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

Посмотреть архив целиком

Перехват 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]


Случайные файлы

Файл
14335.rtf
42921.rtf
106919.rtf
169639.rtf
12085.rtf




Чтобы не видеть здесь видео-рекламу достаточно стать зарегистрированным пользователем.
Чтобы не видеть никакую рекламу на сайте, нужно стать VIP-пользователем.
Это можно сделать совершенно бесплатно. Читайте подробности тут.