Реферат по предмету "Программирование"


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

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


 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 для корректного завершения данного потока.

В WinNT стартовый адрес отображения системных DLL
(user32, kernel32 и т. д.) один и тот же для всех приложений. Это означает, что
адрес некоторой функции из системной DLL в одном приложении будет актуален и в
другом приложении. То есть точки входа функций системных DLL всегда одни и те
же.

Ниже приведен пример процедуры, внедряющей dll с
заданным именем в процесс с заданным PID (идентификатором процесса) (их можно
наблюдать в закладке «процессы» диспетчера задач или получить с помощью
стандартных API-функций).




//структура описывает поля,
в которых содержится код внедрения


struct INJECTORCODE


{


 BYTE
instr_push_loadlibrary_arg; //инструкция push


 DWORD loadlibrary_arg;  //аргумент push



 WORD instr_call_loadlibrary; //инструкция call []


 DWORD
adr_from_call_loadlibrary;



 BYTE instr_push_exitthread_arg;


 DWORD exitthread_arg;



 WORD instr_call_exitthread;


 DWORD adr_from_call_exitthread;



 DWORD addr_loadlibrary;


 DWORD addr_exitthread; //адрес функции ExitTHread


 BYTE libraryname[100]; //имя и путь к загружаемой библиотеке


};



BOOL InjectDll(DWORD pid, char *lpszDllName)


{


 HANDLE hProcess;


 BYTE *p_code;


 INJECTORCODE cmds;


 DWORD wr, id;



 //открыть процесс с нужным доступом


 hProess=OpenProcess(PROCESS_CREATE_THREAD|PROCESS_VM_WRITE|


 PROCESS_VM_OPERATION, FALSE,
pid);


 if(hProcess == NULL)


 {


 MessageBoxA(NULL, "You
have not enough rights to attach dlls",


  "Error!", 0);


 return FALSE;


 }


 


 //зарезервировать память в процессе


 p_code = (BYTE*)VirtualAllocEx(hProcess, 0,
sizeof(INJECTORCODE),


     MEM_COMMIT, PAGE_EXECUTE_READWRITE);


 if(p_code==NULL)


 {


 MessageBox(NULL, "Unable
to alloc memory in remote process",


   "Error!", 0);


 return FALSE;


 }



 //инициализировать машинный код


 cmds.instr_push_loadlibrary_arg = 0x68;
//машинный код инструкции push


 cmds.loadlibrary_arg = (DWORD)((BYTE*)p_code


  + offsetof(INJECTORCODE, libraryname));


 


 cmds.instr_call_loadlibrary =
0x15ff; //машинный код инструкции call


 cmds.adr_from_call_loadlibrary
=


 (DWORD)(p_code +
offsetof(INJECTORCODE, addr_loadlibrary));


 


 cmds.instr_push_exitthread_arg
= 0x68;


 cmds.exitthread_arg = 0;


 


 cmds.instr_call_exitthread =
0x15ff;


 cmds.adr_from_call_exitthread =



 (DWORD)(p_code +
offsetof(INJECTORCODE, addr_exitthread));


 


 cmds.addr_loadlibrary =


 (DWORD)GetProcAddress(GetModuleHandle("kernel32.dll"),
"LoadLibraryA");


 


 cmds.addr_exitthread =


 (DWORD)GetProcAddress(GetModuleHandle("kernel32.dll"),"ExitThread");


 


 if(strlen(lpszDllName)>99)


 {


 MessageBox(NULL, "Dll Name
too long", "Error!", 0);


 return FALSE;


 }


 strcpy((char*)cmds.libraryname,
lpszDllName );


 


 /*После инициализации cmds в мнемонике ассемблера выглядит следующим


 образом:


 push adr_library_name  ;аргумент ф-ции loadlibrary


 call dword ptr
[loadlibrary_adr] ; вызвать LoadLibrary


 push exit_thread_arg   ;аргумент для ExitThread


 call dword ptr
[exit_thread_adr] ;вызвать ExitThread


 */


 


 //записать машинный код по
зарезервированному адресу


 WriteProcessMemory(hProcess, p_code,
&cmds, sizeof(cmds), &wr);


 


 //выполнить машинный код


 HANDLE z =
CreateRemoteThread(hProcess, NULL, 0,


  (unsigned long (__stdcall
*)(void *))p_code, 0, 0, &id);



 //ожидать завершения удаленного потока


 WaitForSingleObject(z, INFINITE);


 //освободить память


 VirtualFreeEx(hProcess, (void*)p_code,
sizeof(cmds), MEM_RELEASE);



 return TRUE;


}






Внедрение 2


Второй способ внедрения исполняемого кода (через хуки)
наиболее прост в использовании. Он основан на технологии хуков, а именно: если
установить хук на поток чужого процесса, то, как только поток получит
сообщение, соответствующее заданному типу хука, система автоматически подключит
DLL c хуком к данному процессу. Недостатком данного способа в том, что нельзя
внедрить DLL в процесс, не имеющий очереди сообщений. Данная DLL будет
присоединена к чужому процессу лишь до тех пор, пока запущена программа,
установившая хук. Как только вы завершите эту программу, dll автоматически
будет отключена. Первый способ лишен таких недостатков.

С другой стороны, первый способ будет работать лишь в
WinNT, по причине использования функции VirtualAllocEx, которая резервирует
память в заданном (отличном от того, в котором происходит вызов этой функции)
процессе. Теоретически, данную проблему можно обойти, если писать код в
некоторую часть отображения exe-файла чужого процесса, например в заголовок
DOS, который после загрузки не используется. Но ОС не всегда позволяет писать в
эту область памяти, даже если попытаться изменить разрешения при помощи
VirtualProtextEx.

Есть еще и третий способ внедрения, но он наиболее
опасен, так как может привести к краху системы. При помощи данного метода ОС
сама внедряет указанную dll во все без исключения процессы операционной
системы, даже защищенные. Для реализации необходимо прописать в реестре по пути
Hkey_local_machinesoftwaremicrosoftwindowsntcurrentversionwindows в ключе
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 в
код внешнего процесса. Для работоспособности этой процедуры к ней нужно
добавить код главного модуля программы, нечто вроде:




int main(int argc, char* argv[])


{


 if(argc


 {


 printf("Parameters: PID ,
Dllname");


 getch();


 return 0;


 }


 InjectDll(atol(argv[1]),
argv[2]);


 return 0;


}






При запуске этой программы (назовем ее ATTACH .EXE) в
качестве параметров надо будет указать идентификатор процесса, в который мы
внедряем свой код, и имя DLL, которую следует прицепить к внешнему процессу.

Скопируйте все три полученных модуля METOD1.DLL,
METOD2.DLL, ATTACH .EXE в один каталог (например, C:TEST). Теперь можно
приступать к тестированию.

Запустите программу-жертву (в нашем случае это
MESS.EXE). Откройте Диспетчер задач, найдите в нем запущенный процесс
(mess.exe):

 

Рисунок 3

и определите его PID (в нашем случае PID mess.exe
равен 1076).

Теперь из командной строки запустите программу
внедрения кода первой DLL:




АТТАСН.EXE 1076 C:TEST
METOD1.DLL






В результате при попытке вызвать окно MessageBox в
программе MESS.EXE вы будете получать одно и то же изображение:

 

Рисунок 4

Перехват функции API произошел!

Заключение


“Не так страшен черт, как программы MicroSoft…” Тем не
менее, если читатель вдумчиво пропустил через себя изложенный материал, то
увидел, что, как обычно, все гениальное – просто. И даже такая вещь, как
перехват API в Windows NT, не требует сверхсложного программного кода и может
быть реализована по первому желанию.
Список литературы

Для подготовки данной работы были использованы
материалы с сайта http://www.rsdn.ru/


Не сдавайте скачаную работу преподавателю!
Данный реферат Вы можете использовать для подготовки курсовых проектов.

Поделись с друзьями, за репост + 100 мильонов к студенческой карме :

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

Читайте также:
Виды рефератов Какими бывают рефераты по своему назначению и структуре.