Рейтинговые книги
Читем онлайн Программирование на Visual C++. Архив рассылки - Алекс Jenter

Шрифт:

-
+

Интервал:

-
+

Закладка:

Сделать
1 ... 135 136 137 138 139 140 141 142 143 ... 156

 if (::IsBadReadPtr(pDosHeader, sizeof(IMAGE_DOS_HEADER)) || IMAGE_DOS_SIGNATURE != pDosHeader->e_magic) {

  return E_INVALIDARG;

 }

 PIMAGE_NT_HEADERS pNTHeaders =

  MakePtr(PIMAGE_NT_HEADERS, hModule, pDosHeader->e_lfanew);

 if (::IsBadReadPtr(pNTHeaders, sizeof(IMAGE_NT_HEADERS)) || IMAGE_NT_SIGNATURE != pNTHeaders->Signature) {

  return E_INVALIDARG;

 }

 HRESULT hr = E_UNEXPECTED;

 IMAGE_DATA_DIRECTORY& expDir =

  pNTHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT];

 PIMAGE_EXPORT_DIRECTORY pExpDir =

  MakePtr(PIMAGE_EXPORT_DIRECTORY, hModule, expDir.VirtualAddress);

 LPDWORD pdwAddrs = MakePtr(LPDWORD, hModule, pExpDir->AddressOfFunctions);

 LPWORD pdwOrd = MakePtr(LPWORD, hModule, pExpDir->AddressOfNameOrdinals);

 DWORD dwAddrIndex = -1;

 if (IS_INTRESOURCE(szEntry)) {

  // By ordinal

  dwAddrIndex = WORD(szEntry) - pExpDir->Base;

  hr = S_OK;

 } else {

  // By name

  LPDWORD pdwNames = MakePtr(LPDWORD, hModule, pExpDir->AddressOfNames);

  for (DWORD iName = 0; iName < pExpDir->NumberOfNames; iName++) {

   if (0 == ::lstrcmpiA(MakePtr(LPSTR, hModule, pdwNames[iName]), szEntry)) {

    dwAddrIndex = pdwOrd[iName];

    hr = S_OK;

    break;

   }

  }

 }

 if (SUCCEEDED(hr)) {

  if (pdwAddrs[dwAddrIndex] >= expDir.VirtualAddress && pdwAddrs[dwAddrIndex] < expDir.VirtualAddress + expDir.Size) {

   // We have a redirection

   LPSTR azRedir = MakePtr(LPSTR, hModule, pdwAddrs[dwAddrIndex]);

   ATLASSERT(!IsBadStringPtrA(azRedir, -1));

   LPSTR azDot = strchr(azRedir, '.');

   int nLen = azDot - azRedir;

   LPSTR azModule = (LPSTR)alloca(nLen);

   memcpy(azModule, azRedir, nLen);

   azModule[nLen] = 'x0';

   // Try to patch redirected function

   return ApiHijackExports(

    ::GetModuleHandle(azModule), azDot + 1, pHijacker, ppOrig);

  }

  if (ppOrig)

   *ppOrig = MakePtr(LPVOID, hModule, pdwAddrs[dwAddrIndex]);

  DWORD dwOffset = DWORD_PTR(pHijacker) - DWORD_PTR(hModule);

  // write to write-protected memory

  hr = WriteProtectedMemory(pdwAddrs + dwAddrIndex, &dwOffset, sizeof(LPVOID));

 }

 return hr;

}

Имейте в виду, под Windows9x нельзя честно подменить экспорты для разделяемых библиотек, таких как user32.dll, kernel32.dll и gdi32.dll. Это связано с тем, что область памяти начиная с адреса 7FC00000h и выше совместно используестя всеми процессами в системе, и модификация сказалась бы на каждом из них. А это нежелательно, поскольку память, занимаемая нашей функцией-перехватчиком, наоборот, принадлежит только нашему процессу. Во всех остальных процессах в системе ::GetProcAddress(), после подмены таблицы экспорта, вернула бы неправильный указатель. Тем не менее, если нельзя, но очень хочется, то можно. Для этого нам придется вручную создать новый дескриптор в GDT (вот тут-то у Windows9x проблем не возникает) и используя этот дескриптор произвести необходимые изменения. Но будьте готовы к тому, что понадобится написать свою разделяемую библиотеку, установить ее в системе и проверять ID процесса при каждом обращении. Рабочий пример есть на internals.com.

Модификация самого обработчика

Эти два способа работают в 99% случаев. Последний процент – это подмена функции, вызываемой внутри чужого модуля, т.е. когда и вызаваемая и вызывающая процедура находятся в одном и том же, да к тому же чужом, модуле. В этом случае, вызов будет сделан напрямик, а не через таблицы импорта/экспорта. Тут уже ничего сделать нельзя. Почти. Можно изменить саму функцию-обработчик, с тем чтобы перенаправить вызовы в нашу собственную. Делается это довольно просто: в начало исходного обработчика прописывается команда безусловного перехода на нашу процедуру, а если нужно вызвать оригинал, то нужно просто сохранить первые 5 байт затертых командой перехода, добавить после них опять-таки команду безусловного перехода на изначальный код +5 байт. Разумется, эти пять байт кода не дожны содержать команд перехода или вызова. Кроме того, может понадобиться больше чем 5 байт, ведь команда перехода посреди длинной инструкции работать не будет. Это случается крайне редко. Обычно код функции, как его генерит компилятор для I86 выглядит примерно так: инициализация стека, загрузка в регистры параметров функции, их проверка и переход в случае неудовлетворительных результатов. Этого вполне хватает чтобы вставить наш маленький перехватчик. Но бывает и так:

CSomeClass::Release:

FF152410E475 call dword ptr [InterlockedDecrement]

85C0         test eax,eax

Или даже

CSomeClass::NonImplemented:

C20400       ret 4

Что, впрочем, можно распознать и вернуть код ошибки если инструкции ret, jmp или call встретится слишком рано. Но вот такой случай распознать не получится:

SomeFunction:

33C0         xor eax,eax

SomeFunction2:

55           push ebp

8BEC         mov ebp,esp

Иными словами, модификация SomeFunction приведет к неизвестным изменениям в SomeFunction2, и, возможно, краху всей системы.

Все это сильно усложняет нам задачу. Нужно дизассемблировать эти байты и проверить каждую инструкцию. Чтобы немного облегчить нам жизнь, фирма Майкрософт разработала специальный SDK для такого рода трюков: Microsoft Detours. С этим SDK задача подмены чужой функции реализуется удивительно просто:

#include <detours.h>

DetourFunction(PBYTE(::MessageBeep), PBYTE(MyMessageBeep));

После чего все вызовы ::MessageBeep(), откуда бы они не были произведены, окажутся вызовами нашей MyMessageBeep(). Что и требовалось.

Модификация самого обработчика 2

Довольно оригинальный вариант предыдущего способа был предложен Дмитрием Крупорницким: первая инструкция перехватываемой функции заменяется инструкцией прерывания INT 3. Далее процедура обработки необработанных исключений (unhandled exception handler) подменяет регистр EIP на адрес нашей функции-перехватчика.

static DWORD_PTR m_dwFunction;

static LONG WINAPI MyUnhandledExceptionFilter(PEXCEPTION_POINTERS pExceptionInfo) {

 if (pExceptionInfo->ContextRecord->Eip != m_dwFunction)

  return EXCEPTION_CONTINUE_SEARCH;

 // Continue execution from MyMessageBeep

 pExceptionInfo->ContextRecord->Eip = (DWORD_PTR)MyMessageBeep;

 return EXCEPTION_CONTINUE_EXECUTION;

}

LRESULT CMainDlg::OnMethod5(WORD /*wNotifyCode*/, WORD wID, HWND /*hWndCtl*/, BOOL& /*bHandled*/) {

 m_dwFunction =

  (DWORD_PTR)::GetProcAddress(::GetModuleHandle("USER32.dll"), "MessageBeep");

 BYTE nSavedByte = *(LPBYTE)m_dwFunction;

 LPTOP_LEVEL_EXCEPTION_FILTER pOldFilter =

  ::SetUnhandledExceptionFilter(MyUnhandledExceptionFilter);

 const BYTE nInt3 = 0xCC;

 // Inject int 3

 HRESULT hr = WriteProtectedMemory(LPVOID(m_dwFunction), &nInt3, sizeof(const BYTE));

 if (SUCCEEDED(hr)) {

  ::MessageBeep(m_uType);

  // Restore function

  hr = WriteProtectedMemory(LPVOID(m_dwFunction), &nSavedByte, sizeof(BYTE));

 }

 ::SetUnhandledExceptionFilter(pOldFilter);

 return 0;

}

Недостатком такого способа является его непредсказуемость. Кто угодно может зарегистрировать свой обработчик исключений и поломать нам логику. Более того, инструкции (…)/(), часто встречающиеся в программах, могут перехватить управление и не дать нашему обработчику шанса.

Подмена с использованием оберток(wrappers)

Еще один способ, о котором я хотел бы упомянуть. Он заключается в написании собственной динамической библиотеки с тем же именем и набором экспортимых функций. Такая библиотека кладется на место ориганальной, запускается использующий эту библиотеку процесс, тот находит нашу DLL и ничего не подозревая ее использует. А если переименовать оригинальную DLL и положить рядом, то можно даже переадресовывать часть вызовов в оригинальную библиотеку, а часть оставлять себе. К сожалению, это будет работать с экспортируемыми функциями, но не с экспортируемыми переменными. Пример такого рода обертки можно найти у Алексея Остапенко.

А вот с COM-объектами и интерфейсами обертки работают как нельзя лучше. Для этого создается другой COM-объект, реализующий нужный нам интерфейс, к нему создатся аггрегированный оригинальный com-объект, а перехватчик отдается тем кто с ним будет в дальнейшем работать. Если оригинальный COM-объект не поддерживает аггрегацию, то придется реализовать все его интерфейсы, и если у него нету никаких внутренних (недокументированных) интерфейсов, то, возможно, все и заработает.

Все это касается подмены API текущего процесса. Если понадобится перехватить вызов в чужом процессе, то следует выбрать любой из приведенных здесь методов, поместить их в DLL и закинуть ее в чужой процесс. Это тема для отдельного разговора и здесь рассматриваться не будет.

Использованные статьи и литература

Форматы PE и COFF файлов

Расширение MSGINA – это просто

API Spying Techniques for Windows 9x, NT and 2000

APIHijack

EliCZ ApiHooks

Контроль вызова API функций в среде систем Windows '95, Windows '98 и Windows NT

Это все на сегодня. Пока! 

Алекс Jenter [email protected] Duisburg, 2001. Публикуемые в рассылке материалы принадлежат сайту RSDN. 

Программирование на Visual C++

1 ... 135 136 137 138 139 140 141 142 143 ... 156
На этой странице вы можете бесплатно читать книгу Программирование на Visual C++. Архив рассылки - Алекс Jenter бесплатно.
Похожие на Программирование на Visual C++. Архив рассылки - Алекс Jenter книги

Оставить комментарий