Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Рихтер Дж., Назар К. - Windows via C C++. Программирование на языке Visual C++ - 2009

.pdf
Скачиваний:
6268
Добавлен:
13.08.2013
Размер:
31.38 Mб
Скачать

726 Часть IV. Динамически подключаемые библиотеки

действительным описателем модуля, что приведет к возникновению исключения с кодом ОхС0000005. В частности, такое возможно в контексте Explorer, который очень быстро динамически загружает и выгружает DLL в потоках, отличных от потоков, вызывающих ReplacelATEntrylnOneMod.

Если же в DataBase.exe раздел импорта присутствует, то ImageDirectoryEntryToData возвращает его адрес как указатель типа PIMAGE_IMPORT_DESCRIPTOR. Тогда мы должны искать в разделе импорта DLL, содержащую требуемую импортируемую функцию. В данном примере мы ищем идентификаторы, импортируемые из Kernel32.dll (имя которой указывается

впервом параметре ReplacelATEntrylnOneMod). В цикле for сканируются имена DLL. Заметьте, что в разделах импорта все строки имеют формат ANSI (Unicode не применяется). Вот почему я вызываю функцию lstrcmpiA, а не макрос lstrcmpi.

Если программа не найдет никаких ссылок на идентификаторы в Кегпе132.dll, то и в этом случае функция просто вернет управление и ничего делать не станет. А если такие ссылки есть, мы получим адрес массива структур IMAGE_THUNK_DATA, в котором содержится информация об импортируемых идентификаторах. Далее в списке из Kernel32.dll ведется поиск идентификатора с адресом, совпадающим с искомым. В данном Случае мы ищем адрес, соответствующий адресу функции ExitProcess. Если поиск ссылок на идентификаторы в Kernel32.dll в цикле завершится безрезультатно, функция возвращает управление. Если в секции импорта модуля все же обнаружены ссылки на Kernel32.dll, мы получаем массив структур IMAGE_THUNK_DATA, содержащих информацию об импортированных функциях. Заметьте, что некоторые компиляторы, такие как компилятор Borland Delphi, генерируют несколько разделов импорта для одного модуля, поэтому мы не останавливаем поиск на первом найденном разделе. Далее

вкаждом из подходящих разделов импорта мы перебираем идентификаторы, импортированные из Kernel32.dll в поисках адреса, соответствующего текущему адресу функции. В нашем примере ведется поиск адреса, соответствующего адресу функции ExitProcess.

Если такого адреса нет, значит, данный модуль не импортирует нужный идентификатор, и ReplacelATEntryInOneMod просто возвращает управление. Но если адрес обнаруживается, мы вызываем WriteProcessMemory, чтобы заменить его на адрес подставной функции. Я применяю WriteProcessMemory, а не InterlockedExchangePointer, потому что она изменяет байты, не обращая внимания на тип защиты страницы памяти, в которой эти байты находятся. Так, если страница имеет атрибут защиты PAGE_READONLY, вызов InterlockedExchangePointer приведет к нарушению доступа, а WriteProcessMemory сама модифицирует атрибуты защиты и без проблем выполнит свою задачу.

Сэтого момента любой поток, выполняющий код в модуле DataBase.exe, при обращении к ExitProcess будет вызывать нашу функцию. А из нее мы сможем легко получить адрес исходной функции ExitProcess в Kernel32.dll и при необходимости вызвать ее.

Глава 22. Внедрение DLL и перехват API-вызовов.docx 727

Обратите внимание, что ReplaceIATEntryInOneMod подменяет вызовы функций только в одном модуле. Если в его адресном пространстве присутствует другая DLL, использующая ExitProcess, она будет вызывать именно ExitProcess из

Kernel32.dll.

Если вы хотите перехватывать обращения к ExitProcess из всех модулей, вам придется вызывать ReplacelATEntrylnOneMod для каждого модуля в адресном пространстве процесса. Я, кстати, написал еще одну функцию, ReplaceIATEntryInAllMods. С помощью Toolhelp-функций она перечисляет все модули, загруженные в адресное пространство процесса, и для каждого из них вызывает ReplaceIATEntryInOneMod, передавая в качестве последнего параметра описатель соответствующего модуля.

Но и в этом случае могут быть проблемы. Например, что получится, если по-

сле вызова ReplaceIATEntryInAllMods какой-нибудь поток вызовет LoadLibrary

для загрузки новой DLL? Если в только что загруженной DLL имеются вызовы ExitProcess, она будет обращаться не к вашей функции, а к исходной. Для решения этой проблемы вы должны перехватывать функции LoadLibraryA, LoadLibraryW, LoadLibraryExA и LoadLibraryExW и вызывать ReplaceIATEntryInOneMod

для каждого загружаемого модуля. Но и этого мало. Представьте себе, что у только что загруженного модуля есть зависимости периода компоновки с другими DLL, которые также могут вызвать ExitProcess. При вызове LoadLibrary*- функции Windows сначала загружает такие статически связанные DLL, не позволяя обновить адрес ExitProcess в таблице импортируемых адресов (Import Address Table, IAT). Решение здесь простое: вместо вызова ReplaceIATEntryInOneMod на каждой явно загружаемой DLL мы вызываем функцию ReplaceIATEntryInAllMods, которая учитывает неявно загружаемые модули.

И, наконец, есть еще одна проблема, связанная с GetProcAddress. Допустим, поток выполняет такой код:

typedef int (WINAPI *PFNEXITPROCESS)(UINT uExitCode); PENEXITPROCESS pfnExitProcess = (PFNEXITPROCESS) GetProcAddress(

GetModuleHandle("Kernel32"), "ExitProcess"); pfnExitProcess(0);

Этот код сообщает системе, что надо получить истинный адрес ExitProcess в Kernel32.dll, а затем сделать вызов по этому адресу. Данный код будет выполнен в обход вашей подставной функции. Проблема решается перехватом обращений к GetProcAddress. При ее вызове Вы должны возвращать адрес своей функции.

В следующем разделе я покажу, как на практике реализовать перехват APIвызовов и решить все проблемы, связанные с использованием LoadLibrary и GetProcAddress.

728 Часть IV. Динамически подключаемые библиотеки

Примечание. О создании более полного протокола для двухсторонних коммуникаций между программой и процессом, вызовы которого она перехватывает, с использованием специальных потоков и спроецированных в память файлов см. в статье «Detect and Plug GDI Leaks in Your Code with Two Powerful Tools for Windows XP» MSDN Magazine (http://msdn.microsoft.com/msdnmag/issues/03/01/GDILeaks).

Программа-пример LastMsgBoxInfo

Эта программа, (22-LastMsgBoxInfo.exe), демонстрирует перехват API-вызовов. Она перехватывает все обращения к функции MessageBox из User32.dll. Для этого программа внедряет DLL с использованием ловушек. Файлы исходного кода и ресурсов этой программы и DLL находятся в каталогах 22-LastMsgBoxInfo и 22LastMsgBoxInfoLib внутри архива, доступного на сайте поддержки этой книги.

После запуска LastMsgBoxInfo открывает диалоговое окно, показанное ниже.

В этот момент программа находится в состоянии ожидания. Запустите какоенибудь приложение и заставьте его открыть окно с тем или иным сообщением. Тестируя свою программу, я использовал программу-пример 20DelayLoadApp.exe (см. главу 20). Это простая С++-программа выводит различные сообщения при отложенной загрузке DLL.

Как видите, LastMsgBoxInfo позволяет наблюдать за вызовами функции MessageBox из других процессов. Однако несложно заметить, что программа не «видит» первого вызова MessageBox. Причина этого проста: ловушка Windows, используемая для внедрения седящего кода, срабатывает на первый вызов MessageBox, то есть слишком поздно.

Код, отвечающий за вывод диалогового окна LastMsgBoxInfo и управление им весьма прост. Трудности начинаются при настройке перехвата APIвызовов. Чтобы упростить эту задачу, я создал С++-класс CAPIHook, определенный в заголовочном файле APIHook.h и реализованный в файле АРIHоок.срр. Пользоваться им очень легко, так как в нем лишь несколько

Глава 22. Внедрение DLL и перехват API-вызовов.docx 729

открытых функций-членов: конструктор, деструктор и метод, возвращающий адрес исходной функции, на которую вы ставите ловушку.

Для перехвата вызова какой-либо функции вы просто создаете экземпляр этого класса:

CAPIHook g_MessageBoxA("User32.dll", "MessageBoxA",

(PROC) Hook_HessageBoxA, TRUE);

CAPIHook g_MessageBoxW("User32.dll", "MessageBoxW",

(PROC) Hook_MessageBoxW, TRUE);

Мне приходится ставить ловушки на две функции: MessageBoxA и MessageBoxW. Обе эти функции находятся в User32.dll. Я хочу, чтобы при обращении к

MessageBoxA вызывалась Hook_MessageBoxA, а при вызове MessageBoxW Hook_MessageBoxW.

Конструктор класса CAPIHook просто запоминает, какую API-функцию нужно перехватывать, и вызывает ReplacelATEntrylnAllMods, которая, собственно, и выполняет эту задачу.

Следующая открытая функция-член — деструктор. Когда объект CAPIHook выходит за пределы области видимости, деструктор вызывает ReplaceIATEntryInAllMods для восстановления исходного адреса идентификатора во всех модулях, т. е. для снятия ловушки.

Третий открытый член класса возвращает адрес исходной функции. Эта функция обычно вызывается из подставной функции для обращения к перехватываемой функции. Вот как выглядит код функции Hook_MessageBoxA:

int WINAPI Hook_HessageBoxA(HWND hWnd, PCSTR pszText, PCSTR pszCaption, UINT uType) {

int nResult = ((PFNMESSAGEBOXA)(PROC) g_MessageBoxA) (hWnd, pszText, pszCaption, uType);

SendLastHsgBoxInfo(FALSE, (PVOID) pszCaption, (PVOID) pszText, nResult); return(nResult);

}

Этот код ссылается на глобальный объект g_MessageBoxA класса CAPIHook. Приведение его к типу PROC заставляет функцию-член вернуть адрес исходной функции MessageBoxA в User32.dll.

Всю работу по установке и снятию ловушек этот С++-класс берет на себя. До конца просмотрев файл CAPIHook.cpp, вы заметите, что мой С++-класс автоматически создает экземпляры объектов CAPIHook для перехвата вызовов LoadLibraryA, LoadLibraryW, LoadLibraryExA, LoadLibraryExW и GetProcAddress. Так что он сам справляется с проблемами, о которых я рассказывал в предыдущем разделе.

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

730 Часть IV. Динамически подключаемые библиотеки

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

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

1.Снова получить таблицу импорта уже загруженного модуля, поскольку теперь вызовом GetProcAddress можно получить указатель на исходную функцию, которую нужно перехватить. Заметьте, что имя этой функции должно храниться в поле класса и устанавливаться во время исполнения конструктора.

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

Но что, если модуль, экспортирующий перехватываемую функцию, будет вы-

гружен вызовом FreeLibrary, а затем снова загружен? Эта задача не является предметом этой главы, но в этом примере содержится все необходимое для ее решения.

Примечание. На сайте Microsoft Research опубликовано описание API пе-

рехвата под названием Detours (см. http://research.microsoft.com/sn/detours/).

LastMsgBoxInfo.cpp

/****************************************************************************** Module: LastMsgBoxInfo.cpp

Notices: Copyright (c) 2008 Jeffrey Richter & Christophe Nasarre

******************************************************************************/

#include "..\CommonFiles\CmnHdr.h" /* See Appendix A. */ #include <windowsx.h>

#include <tchar.h> #include "Resource.h"

#include "..\22-LastMsgBoxInfoLib\LastMsgBoxInfoLib.h"

///////////////////////////////////////////////////////////////////////////////

BOOL Dlg_OnInitDialog(HWND hWnd, HWND hWndFocus, LPARAM lParam) {

chSETDLGICONS(hWnd, IDI_LASTMSGBOXINFO); SetDlgItemText(hWnd, IDC_INFO,

TEXT("Waiting for a Message Box to be dismissed"));

Глава 22. Внедрение DLL и перехват API-вызовов.docx 731

return(TRUE);

}

///////////////////////////////////////////////////////////////////////////////

void Dlg_OnSize(HWND hWnd, UINT state, int cx, int cy) {

SetWindowPos(GetDlgItem(hWnd, IDC_INFO), NULL, 0, 0, cx, cy, SWP_NOZORDER);

}

///////////////////////////////////////////////////////////////////////////////

void Dlg_OnCommand(HWND hWnd, int id, HWND hWndCtl, UINT codeNotify) {

switch (id) { case IDCANCEL:

EndDialog(hWnd, id); break;

}

}

///////////////////////////////////////////////////////////////////////////////

BOOL Dlg_OnCopyData(HWND hWnd, HWND hWndFrom, PCOPYDATASTRUCT pcds) {

// Some hooked process sent us some message box info, display it SetDlgItemTextW(hWnd, IDC_INFO, (PCWSTR) pcds->lpData); return(TRUE);

}

///////////////////////////////////////////////////////////////////////////////

INT_PTR WINAPI Dlg_Proc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {

switch (uMsg) {

chHANDLE_DLGMSG(hWnd, WM_INITDIALOG,

Dlg_OnInitDialog);

chHANDLE_DLGMSG(hWnd, WM_SIZE,

Dlg_OnSize);

chHANDLE_DLGMSG(hWnd, WM_COMMAND,

Dlg_OnCommand);

chHANDLE_DLGMSG(hWnd, WM_COPYDATA,

Dlg_OnCopyData);

}

 

return(FALSE);

 

}

///////////////////////////////////////////////////////////////////////////////

732 Часть IV. Динамически подключаемые библиотеки

int WINAPI _tWinMain(HINSTANCE hInstExe, HINSTANCE, PTSTR pszCmdLine, int) {

DWORD dwThreadId = 0; LastMsgBoxInfo_HookAllApps(TRUE, dwThreadId);

DialogBox(hInstExe, MAKEINTRESOURCE(IDD_LASTMSGBOXINFO), NULL, Dlg_Proc); LastMsgBoxInfo_HookAllApps(FALSE, 0);

return(0);

}

//////////////////////////////// End of File //////////////////////////////////

LastMsgBoxInfoLib.cpp

/****************************************************************************** Module: LastMsgBoxInfoLib.cpp

Notices: Copyright (c) 2008 Jeffrey Richter & Christophe Nasarre

******************************************************************************/

#include "..\CommonFiles\CmnHdr.h" #include <WindowsX.h>

#include <tchar.h> #include <stdio.h> #include "APIHook.h"

#define LASTMSGBOXINFOLIBAPI extern "C" __declspec(dllexport) #include "LastMsgBoxInfoLib.h"

#include <StrSafe.h>

///////////////////////////////////////////////////////////////////////////////

// Prototypes for the hooked functions

typedef int (WINAPI *PFNMESSAGEBOXA)(HWND hWnd, PCSTR pszText, PCSTR pszCaption, UINT uType);

typedef int (WINAPI *PFNMESSAGEBOXW)(HWND hWnd, PCWSTR pszText, PCWSTR pszCaption, UINT uType);

// We need to reference these variables before we create them. extern CAPIHook g_MessageBoxA;

extern CAPIHook g_MessageBoxW;

///////////////////////////////////////////////////////////////////////////////

// This function sends the MessageBox info to our main dialog box void SendLastMsgBoxInfo(BOOL bUnicode,

PVOID pvCaption, PVOID pvText, int nResult) {

Глава 22. Внедрение DLL и перехват API-вызовов.docx 733

//Get the pathname of the process displaying the message box wchar_t szProcessPathname[MAX_PATH]; GetModuleFileNameW(NULL, szProcessPathname, MAX_PATH);

//Convert the return value into a human-readable string PCWSTR pszResult = L"(Unknown)";

switch (nResult) {

case IDOK:

pszResult = L"Ok";

break;

case IDCANCEL:

pszResult = L"Cancel";

break;

case IDABORT:

pszResult = L"Abort";

break;

case IDRETRY:

pszResult = L"Retry";

break;

case IDIGNORE:

pszResult = L"Ignore";

break;

case IDYES:

pszResult = L"Yes";

break;

case IDNO:

pszResult = L"No";

break;

case IDCLOSE:

pszResult = L"Close";

break;

case IDHELP:

pszResult = L"Help";

break;

case IDTRYAGAIN:

pszResult = L"Try Again"; break;

case IDCONTINUE:

pszResult = L"Continue";

break;

}

//Construct the string to send to the main dialog box wchar_t sz[2048];

StringCchPrintfW(sz, _countof(sz), bUnicode

? L"Process: (%d) %s\r\nCaption: %s\r\nMessage: %s\r\nResult: %s" : L"Process: (%d) %s\r\nCaption: %S\r\nMessage: %S\r\nResult: %s", GetCurrentProcessId(), szProcessPathname,

pvCaption, pvText, pszResult);

//Send the string to the main dialog box

COPYDATASTRUCT cds = { 0, ((DWORD)wcslen(sz) + 1) * sizeof(wchar_t), sz }; FORWARD_WM_COPYDATA(FindWindow(NULL, TEXT("Last MessageBox Info")),

NULL, &cds, SendMessage);

}

///////////////////////////////////////////////////////////////////////////////

// This is the MessageBoxW replacement function

int WINAPI Hook_MessageBoxW(HWND hWnd, PCWSTR pszText, LPCWSTR pszCaption, UINT uType) {

// Call the original MessageBoxW function

int nResult = ((PFNMESSAGEBOXW)(PROC) g_MessageBoxW) (hWnd, pszText, pszCaption, uType);

// Send the information to the main dialog box SendLastMsgBoxInfo(TRUE, (PVOID) pszCaption, (PVOID) pszText, nResult);

734 Часть IV. Динамически подключаемые библиотеки

// Return the result back to the caller return(nResult);

}

///////////////////////////////////////////////////////////////////////////////

// This is the MessageBoxA replacement function

int WINAPI Hook_MessageBoxA(HWND hWnd, PCSTR pszText, PCSTR pszCaption, UINT uType) {

// Call the original MessageBoxA function

int nResult = ((PFNMESSAGEBOXA)(PROC) g_MessageBoxA) (hWnd, pszText, pszCaption, uType);

//Send the information to the main dialog box SendLastMsgBoxInfo(FALSE, (PVOID) pszCaption, (PVOID) pszText, nResult);

//Return the result back to the caller

return(nResult);

}

///////////////////////////////////////////////////////////////////////////////

// Hook the MessageBoxA and MessageBoxW functions CAPIHook g_MessageBoxA("User32.dll", "MessageBoxA",

(PROC) Hook_MessageBoxA);

CAPIHook g_MessageBoxW("User32.dll", "MessageBoxW",

(PROC) Hook_MessageBoxW);

HHOOK g_hhook = NULL;

///////////////////////////////////////////////////////////////////////////////

static LRESULT WINAPI GetMsgProc(int code, WPARAM wParam, LPARAM lParam) { return(CallNextHookEx(g_hhook, code, wParam, lParam));

}

///////////////////////////////////////////////////////////////////////////////

// Returns the HMODULE that contains the specified memory address static HMODULE ModuleFromAddress(PVOID pv) {

MEMORY_BASIC_INFORMATION mbi; return((VirtualQuery(pv, &mbi, sizeof(mbi)) != 0)

? (HMODULE) mbi.AllocationBase : NULL);

Глава 22. Внедрение DLL и перехват API-вызовов.docx 735

}

///////////////////////////////////////////////////////////////////////////////

BOOL WINAPI LastMsgBoxInfo_HookAllApps(BOOL bInstall, DWORD dwThreadId) {

BOOL bOk;

if (bInstall) {

chASSERT(g_hhook == NULL); // Illegal to install twice in a row

// Install the Windows' hook

g_hhook = SetWindowsHookEx(WH_GETMESSAGE, GetMsgProc, ModuleFromAddress(LastMsgBoxInfo_HookAllApps), dwThreadId);

bOk = (g_hhook != NULL); } else {

chASSERT(g_hhook != NULL); // Can't uninstall if not installed bOk = UnhookWindowsHookEx(g_hhook);

g_hhook = NULL;

}

return(bOk);

}

//////////////////////////////// End of File //////////////////////////////////

LastMsgBoxInfoLib.h

/****************************************************************************** Module: LastMsgBoxInfoLib.h

Notices: Copyright (c) 2008 Jeffrey Richter & Christophe Nasarre

******************************************************************************/

#ifndef LASTMSGBOXINFOLIBAPI

#define LASTMSGBOXINFOLIBAPI extern "C" __declspec(dllimport) #endif

///////////////////////////////////////////////////////////////////////////////

LASTMSGBOXINFOLIBAPI BOOL WINAPI LastMsgBoxInfo_HookAllApps(BOOL bInstall,

DWORD dwThreadId);

//////////////////////////////// End of File //////////////////////////////////

Соседние файлы в предмете Программирование на C++