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


Перехват методов интерфейса Iunknown

Перехват методов интерфейса IUnknown

Алексей Остапенко
Введение


В этой статье рассматривается технология, позволяющая
перехватывать вызовы методов интерфейса IUnknown COM-объекта. Кроме
исследовательских целей, эта технология может иметь и практическое применение.
Она позволяет осуществлять такие полезные действия, как почти прозрачная
подмена контекста пользователя, “под которым” производятся вызовы методов
удаленного объекта, “агрегирование” удаленных объектов, агрегирование объектов,
не поддерживающих агрегацию и т.п. С исследовательской точки зрения перехват
вызовов IUnknown позволяет заглянуть во внутренности взаимодействия приложения
и используемых им COM-объектов. Например, отлаживая приведенный в статье
пример, я обнаружил, что вызов функции CreateObject скриптового рантайма
приводит к запросу четырех (!) интерфейсов вместо одного у создаваемого
объекта. :)
Немного теории


Интерфейс IUnknown является основополагающим элементом
COM. Он имеет 3 метода, управляющих доступом к другим интерфейсам объекта:

QueryInterface.

AddRef.

Release.

Перехватив вызовы методов IUnknown, можно управлять
набором интерфейсов, предоставляемых объектом “наружу” (например, можно
спрятать некоторые из них или добавить свои интерфейсы, сделав вид, что они
тоже предоставляются объектом), а также управлять некоторыми параметрами вызова
методов интерфейсов (например, proxy blanket’ом). Любой другой интерфейс,
наследуемый от IUnknown, соответственно, наследует и эти три метода.

Работа с любым интерфейсом осуществляется через
указатель на этот интерфейс. Физически указатель на интерфейс – это указатель
на переменную, которая, в свою очередь, указывает на таблицу указателей на
методы этого интерфейса (VTBL, см. рисунок 1).

 

Рисунок 1.

Несколько интерфейсов могут ссылаться как на одну и ту
же VTBL, так и на разные – для клиента это не имеет значения. Кроме того,
физически разные экземпляры COM-класса имеют разные указатели на интерфейсы
(так как из них при вызове выводится this, см. ниже), но могут иметь (а
объекты, реализованные с помощью ATL - имеют) одни и те же VTBL.

Интерфейс может использоваться клиентом напрямую, если
COM-объект создавался внутри процесса (in-proc) клиента и в том же апартаменте
(apartment), из которого происходит вызов. В противном случае (внепроцессный
(out-of-proc) объект или другой апартамент) клиент вместо реального интерфейса
будет использовать его прокси. Однако в обоих случаях указатель на интерфейс
ссылается на указатель на VTBL, содержащую указатели на методы. Таким образом,
в обоих случаях VTBL интерфейса (или его прокси) напрямую доступна в процессе
клиента для чтения, и может быть сделана доступной для записи.




ПРИМЕЧАНИЕ


Компилятор C++ может разместить VTBL в
константном сегменте данных, который может быть загружен в страницу памяти с
атрибутами защиты “только для чтения” (PAGE_READONLY) (за это замечание
отдельное спасибо Николаю Меркину и Алексею Ширшову). В этом случае просто
так писать в VTBL не получится. Но, поскольку VTBL находится в адресном
пространстве процесса, можно поменять атрибуты защиты страницы на “для чтения
и записи” (PAGE_READWRITE) с помощью функции VirtualProtect.






Кроме этих кратких сведений об устройстве указателей
на интерфейс нам понадобится понимание того, как именно происходит вызов и
передача параметров в метод интерфейса, реализованного как метод C++ класса,
унаследованного от интерфейса:

Клиент вызывает метод QueryInterface:




pUnknown->QueryInterface(IID_IDispatch,
reinterpret_cast(&pDispatch));






Этот вызов транслируется примерно в такой:




((VTBL*)pUnknown)->vtbl->QueryInterface(pUnknown,
IID_IDispatch,


 reinterpret_cast(&pDispatch));






т.е. указатель на интерфейс передается первым
параметром для метода. Формат вызова фактически соответствует формату вызова
нестатического метода класса в конвенции _stdcall.

В начале метода указатель на интерфейс заменяется
указателем на this (на самом деле через VTBL вызывается не сам метод, а
сгенерированный компилятором переходник, который корректирует указатель на
интерфейс и передает управление собственно методу).

Принцип перехвата


Для перехвата IUnknown потребуется подменить указатели
на методы QueryInterface, AddRef и Release в VTBL всех интерфейсов COM-объекта
указателями на нашу реализацию этих методов. Другими словами, необходимо
установить хук на вызовы этих методов. Методы-перехватчики не могут быть
нестатическими методами класса, так как передаваемый в метод указатель на
интерфейс никак не связан с this объекта-перехватчика. Кроме того, по
полученному указателю на интерфейс нужно уметь определять указатели на
оригинальные методы QueryInterface, AddRef и Release перехваченного интерфейса.
Эту задачу можно легко решить с помощью статического экземпляра контейнера
std::map (или hash_map), позволяющего по указателю на VTBL получить структуру
(или класс), содержащую указатели на оригинальные реализации QueryInterface и т.п.

Наша реализация QueryInterface, помимо делегирования
вызовов оригинальному QueryInterface, должна также осуществлять перехват
запрашиваемых интерфейсов (если они не были перехвачены ранее) и добавлять
соответствующие пары в map. AddRef и Release должны заниматься дополнительным
подсчетом ссылок, чтобы отследить момент, когда необходимо снять с интерфейса
ранее установленный хук.

Реализация перехвата


Большая часть действий по перехвату возложена на
вспомогательный класс HookEntry. Изначальный перехват интерфейса осуществляется
в статическом методе HookInteface:

Метод
HookInterface




RPC_AUTH_IDENTITY_HANDLE HookEntry::HookInterface(IUnknown* pItf,
const CredentialsHolder::CredentialsPtr& pCredentials)


{


 {


 void* tmp =
*reinterpret_cast(pItf);


 CComCritSecLock
lock(m_csHooks);


 HookMap::iterator it = m_hooks.lower_bound(tmp);


 if (it == m_hooks.end() || (*it).first != tmp) //а нет ли уже такого
хука?


 {


 // ставим новый хук


 // по хорошему, тут бы стоило еще проверять,
не вернул ли new 0


 HookPtr pNewHook(new HookEntry()); //


 if (pNewHook->Hook(pItf))


 {


 m_hooks.insert(it,
HookMap::value_type(tmp, pNewHook));


 //предотвращаем преждевременную выгрузку dll


 if (++m_totalRefCount == 1)


 _Module.Lock();


 }


 else


 return NULL;


 }


 else


 (*it).second->AddRef(); //хук уже есть.
просто добавляем ссылку


 } //lock.Unlock();



 //добавляем или достаем credentials


 CComCritSecLock
lock2(m_csCredentials);


 CredentialsMap::iterator itc =
m_credentials.lower_bound(pItf);


 //такого элемента еще нет – придется добавить


 if (itc == m_credentials.end() ||
(*itc).first != pItf)


 {


 m_credentials.insert(itc,
CredentialsMap::value_type(pItf, pCredentials));


 return
pCredentials->GetCredentials();


 }


 else


 return
(*itc).second->GetCredentials();


}






В дальнейшем этот же метод вызывается из перехватчика
QueryInterface:

Метод
QueryInterfaceHook




//хук QueryInterface


STDMETHODIMP HookEntry::QueryInterfaceHook(void* pItf, REFIID iid,
void** ppvObject)


{


 //добываем хелпер-объект


 HookEntry* pHook =
HookFromItf(reinterpret_cast(pItf));


 if (pHook == NULL)


 //хук уже кто-то снял :(


 return ((IUnknown*)pItf)->QueryInterface(iid,
ppvObject);



 //IClientSecurity должен проходить мимо


 if (::InlineIsEqualGUID(iid,
IID_IClientSecurity))


 return pHook->m_oldQI(pItf,
iid, ppvObject);



 //собственно QueryInterface


 CComPtr
spUnknown;


 HRESULT hr =
pHook->m_oldQI(pItf, iid, (void**)&spUnknown.p);


 if (FAILED(hr))


 return hr;



 //добываем пары «логин-пароль»


 CredentialsHolder::CredentialsPtr*
pCredentials = CredentialsFromItf(reinterpret_cast(pItf));


 if (pCredentials != NULL)


 {


 // устанавливаем хук на интерфейс


 RPC_AUTH_IDENTITY_HANDLE ident =
HookInterface(spUnknown.p, *pCredentials);


 if (ident)


 {


 //и устанавливаем на интерфейс proxy blanket


 hr = ::CoSetProxyBlanket(spUnknown.p,
RPC_C_AUTHN_WINNT,


 RPC_C_AUTHZ_NONE, NULL,
RPC_C_AUTHN_LEVEL_DEFAULT,


 RPC_C_IMP_LEVEL_IMPERSONATE,
ident, EOAC_NONE);


 if (FAILED(hr) && hr !=
E_NOINTERFACE)


 return hr;


 }


 }


 *ppvObject =
reinterpret_cast(spUnknown.Detach());


 return S_OK;


}






Подсчет ссылок для подправленных VTBL реализован
непосредственно в классе HookEntry.




ПРЕДУПРЕЖДЕНИЕ


Первый вызов HookInterface должен быть
произведен сразу после получения первого указателя на любой интерфейс
COM-объекта. В противном случае в дальнейшем может возникнуть ситуация, когда
хук будет снят, но объект все еще будет жить.






Полезная нагрузка


В качестве полезной нагрузки рассматриваемый пример
осуществляет подмену контекста пользователя. Таким образом, пример раcсчитан на
работу с удаленными COM-объектами (CLSCTX_REMOTE_SERVER). Контекст пользователя
подменяется при помощи вызова CoSetProxyBlanket с указанием нового
RPC_AUTH_IDENTITY_HANDLE.




ПРИМЕЧАНИЕ


Довольно интересный момент - в MSDN
практически отсутствует информация о том, как правильно создавать RPC_AUTH_IDENTITY_HANDLE.
Написано, что при определенных условиях он является просто указателем на
структуру SEC_WINNT_AUTH_IDENTITY(_EX). Однако эксперименты показали, что
простое создание такой структуры и подсовывание указателя на нее в
CoSetProxyBlanket не приводит ни к чему, кроме ошибки (в то же время с
CoCreateInstanseEx такой фокус проходит). Опытным путем было установлено, что
правильную структуру можно создать вызовом DsMakePasswordCredentials. Увы,
этот API специфичен для ОС Win2k и далее, и пример не будет работать под NT
4.






В итоге, для “борьбы” с RPC_AUTH_IDENTITY_HANDLE был
создан отдельный хелпер-класс CredentialsHolder. Для хранения соответствия
credentials конкретным интерфейсам используется еще один контейнер std::map.
Стоит отдельно заметить, что в контейнере хранятся не экземпляры классов
CredentialsHolder, а “умные” (smart) указатели shared_ptr, реализующие подсчет
ссылок. Это сделано для предотвращения необходимости копирования экземпляров
CredentialsHolder, для которых операция копирования не только накладна, но и
попросту не очевидна.

Использование


В файле
с проектом содержится демонстрационный скрипт test.js, показывающий, как можно
подменять пользовательский контекст при создании и использовании удаленного
объекта. Метод CreateObject позволяет инстанциировать удаленный объект и
вернуть указатель на его главный интерфейс IDispatch. Метод HookObject
позволяет перехватить ранее полученный интерфейс IDispatch.




ПРИМЕЧАНИЕ


В теории HookObject может давать не
совсем те результаты, на которые хочется расчитывать. Это связано с тем, что
в данном случае невозможно точно отследить момент освобождения интерфейса,
т.к. на него (а возможно, что и на другие интерфейсы объекта) уже имеются
ссылки, не учтенные до перехвата. Однако, при практическом использовании
данного механизма внутри ASP-скриптов, преждевременного снятия хуков не
наблюдалось.






С помощью описанного механизма перехвата можно делать
и другие забавные трюки. Например, агрегировать объекты, не поддерживающие
агрегацию. В самом деле, раз мы имеем полный контроль над вызовами IUnknown,
ничто не мешает полностью заменить его реализацию так, что она будет в точности
эмулировать поведение IUnknown агрегированного объекта.
Список литературы

MSDN

Введение в COM

Защита в DCOM/COM+

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


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

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

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

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