Реферат

Реферат Перехват API-функций в Windows NT2000XP

Работа добавлена на сайт bukvasha.net: 2015-10-28

Поможем написать учебную работу

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

Предоплата всего

от 25%

Подписываем

договор

Выберите тип работы:

Скидка 25% при заказе до 8.11.2024



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

}

Внедрение кода в чужой процесс в 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_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 в код внешнего процесса. Для работоспособности этой процедуры к ней нужно добавить код главного модуля программы, нечто вроде:

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

{

 if(argc<3)

 {

 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/



1. Реферат Место России в современной системе международного разделения труда МРТ
2. Реферат на тему Cromwell Essay Research Paper Greetings dearest cousin
3. Реферат на тему Egyptian Civilization Essay Research Paper Egyptian CivilizationAround
4. Курсовая Почвы землепользования Освобождение Задонского района Липецкой области их генезис состав
5. Лекция на тему Особенности строения нервной системы
6. Курсовая на тему Правила погрузочно-разгрузочных работ по выгрузке угля
7. Реферат на тему Декоративно прикладное искусство в детском саду
8. Сочинение Гуманизм в творчестве Андрея Платонова на примере рассказа Потомки солнца
9. Реферат Круизный туризм
10. Курсовая на тему Разработка технологического процесса изготовления детали Основа излучателя